alloy_consensus/transaction/
envelope.rs

1use super::SignableTransaction;
2use crate::{
3    error::ValueError,
4    transaction::{
5        eip4844::{TxEip4844, TxEip4844Variant},
6        RlpEcdsaEncodableTx, TxHashRef,
7    },
8    Signed, TransactionEnvelope, TxEip1559, TxEip2930, TxEip4844WithSidecar, TxEip7702, TxLegacy,
9};
10use alloy_eips::{eip2718::Encodable2718, eip7594::Encodable7594};
11use alloy_primitives::{Bytes, Signature, B256};
12use core::fmt::Debug;
13
14/// The Ethereum [EIP-2718] Transaction Envelope.
15///
16/// # Note:
17///
18/// This enum distinguishes between tagged and untagged legacy transactions, as
19/// the in-protocol merkle tree may commit to EITHER 0-prefixed or raw.
20/// Therefore we must ensure that encoding returns the precise byte-array that
21/// was decoded, preserving the presence or absence of the `TransactionType`
22/// flag.
23///
24/// [EIP-2718]: https://eips.ethereum.org/EIPS/eip-2718
25pub type TxEnvelope = EthereumTxEnvelope<TxEip4844Variant>;
26
27impl<T: Encodable7594> EthereumTxEnvelope<TxEip4844Variant<T>> {
28    /// Attempts to convert the envelope into the pooled variant.
29    ///
30    /// Returns an error if the envelope's variant is incompatible with the pooled format:
31    /// [`crate::TxEip4844`] without the sidecar.
32    pub fn try_into_pooled(
33        self,
34    ) -> Result<EthereumTxEnvelope<TxEip4844WithSidecar<T>>, ValueError<Self>> {
35        match self {
36            Self::Legacy(tx) => Ok(tx.into()),
37            Self::Eip2930(tx) => Ok(tx.into()),
38            Self::Eip1559(tx) => Ok(tx.into()),
39            Self::Eip4844(tx) => EthereumTxEnvelope::try_from(tx).map_err(ValueError::convert),
40            Self::Eip7702(tx) => Ok(tx.into()),
41        }
42    }
43}
44
45impl EthereumTxEnvelope<TxEip4844> {
46    /// Attempts to convert the envelope into the pooled variant.
47    ///
48    /// Returns an error if the envelope's variant is incompatible with the pooled format:
49    /// [`crate::TxEip4844`] without the sidecar.
50    pub fn try_into_pooled<T>(
51        self,
52    ) -> Result<EthereumTxEnvelope<TxEip4844WithSidecar<T>>, ValueError<Self>> {
53        match self {
54            Self::Legacy(tx) => Ok(tx.into()),
55            Self::Eip2930(tx) => Ok(tx.into()),
56            Self::Eip1559(tx) => Ok(tx.into()),
57            Self::Eip4844(tx) => {
58                Err(ValueError::new(tx.into(), "pooled transaction requires 4844 sidecar"))
59            }
60            Self::Eip7702(tx) => Ok(tx.into()),
61        }
62    }
63
64    /// Converts from an EIP-4844 transaction to a [`EthereumTxEnvelope<TxEip4844WithSidecar<T>>`]
65    /// with the given sidecar.
66    ///
67    /// Returns an `Err` containing the original [`EthereumTxEnvelope`] if the transaction is not an
68    /// EIP-4844 variant.
69    pub fn try_into_pooled_eip4844<T>(
70        self,
71        sidecar: T,
72    ) -> Result<EthereumTxEnvelope<TxEip4844WithSidecar<T>>, ValueError<Self>> {
73        match self {
74            Self::Eip4844(tx) => {
75                Ok(EthereumTxEnvelope::Eip4844(tx.map(|tx| tx.with_sidecar(sidecar))))
76            }
77            this => Err(ValueError::new_static(this, "Expected 4844 transaction")),
78        }
79    }
80}
81
82impl<T> EthereumTxEnvelope<T> {
83    /// Creates a new signed transaction from the given transaction, signature and hash.
84    ///
85    /// Caution: This assumes the given hash is the correct transaction hash.
86    pub fn new_unchecked(
87        transaction: EthereumTypedTransaction<T>,
88        signature: Signature,
89        hash: B256,
90    ) -> Self
91    where
92        T: RlpEcdsaEncodableTx,
93    {
94        Signed::new_unchecked(transaction, signature, hash).into()
95    }
96
97    /// Creates a new signed transaction from the given transaction, signature and hash.
98    ///
99    /// Caution: This assumes the given hash is the correct transaction hash.
100    #[deprecated(note = "Use new_unchecked() instead")]
101    pub fn new(transaction: EthereumTypedTransaction<T>, signature: Signature, hash: B256) -> Self
102    where
103        T: RlpEcdsaEncodableTx,
104    {
105        Self::new_unchecked(transaction, signature, hash)
106    }
107
108    /// Creates a new signed transaction from the given typed transaction and signature without the
109    /// hash.
110    ///
111    /// Note: this only calculates the hash on the first [`EthereumTxEnvelope::hash`] call.
112    pub fn new_unhashed(transaction: EthereumTypedTransaction<T>, signature: Signature) -> Self
113    where
114        T: RlpEcdsaEncodableTx + SignableTransaction<Signature>,
115    {
116        transaction.into_signed(signature).into()
117    }
118
119    /// Consumes the type, removes the signature and returns the transaction.
120    #[inline]
121    pub fn into_typed_transaction(self) -> EthereumTypedTransaction<T>
122    where
123        T: RlpEcdsaEncodableTx,
124    {
125        match self {
126            Self::Legacy(tx) => EthereumTypedTransaction::Legacy(tx.into_parts().0),
127            Self::Eip2930(tx) => EthereumTypedTransaction::Eip2930(tx.into_parts().0),
128            Self::Eip1559(tx) => EthereumTypedTransaction::Eip1559(tx.into_parts().0),
129            Self::Eip4844(tx) => EthereumTypedTransaction::Eip4844(tx.into_parts().0),
130            Self::Eip7702(tx) => EthereumTypedTransaction::Eip7702(tx.into_parts().0),
131        }
132    }
133
134    /// Returns a mutable reference to the transaction's input.
135    #[doc(hidden)]
136    pub fn input_mut(&mut self) -> &mut Bytes
137    where
138        T: AsMut<TxEip4844>,
139    {
140        match self {
141            Self::Eip1559(tx) => &mut tx.tx_mut().input,
142            Self::Eip2930(tx) => &mut tx.tx_mut().input,
143            Self::Legacy(tx) => &mut tx.tx_mut().input,
144            Self::Eip7702(tx) => &mut tx.tx_mut().input,
145            Self::Eip4844(tx) => &mut tx.tx_mut().as_mut().input,
146        }
147    }
148}
149
150/// The Ethereum [EIP-2718] Transaction Envelope.
151///
152/// # Note:
153///
154/// This enum distinguishes between tagged and untagged legacy transactions, as
155/// the in-protocol merkle tree may commit to EITHER 0-prefixed or raw.
156/// Therefore we must ensure that encoding returns the precise byte-array that
157/// was decoded, preserving the presence or absence of the `TransactionType`
158/// flag.
159///
160/// [EIP-2718]: https://eips.ethereum.org/EIPS/eip-2718
161#[derive(Clone, Debug, TransactionEnvelope)]
162#[envelope(
163    alloy_consensus = crate,
164    tx_type_name = TxType,
165    typed = EthereumTypedTransaction,
166    arbitrary_cfg(feature = "arbitrary")
167)]
168#[doc(alias = "TransactionEnvelope")]
169pub enum EthereumTxEnvelope<Eip4844> {
170    /// An untagged [`TxLegacy`].
171    #[envelope(ty = 0)]
172    Legacy(Signed<TxLegacy>),
173    /// A [`TxEip2930`] tagged with type 1.
174    #[envelope(ty = 1)]
175    Eip2930(Signed<TxEip2930>),
176    /// A [`TxEip1559`] tagged with type 2.
177    #[envelope(ty = 2)]
178    Eip1559(Signed<TxEip1559>),
179    /// A TxEip4844 tagged with type 3.
180    /// An EIP-4844 transaction has two network representations:
181    /// 1 - The transaction itself, which is a regular RLP-encoded transaction and used to retrieve
182    /// historical transactions..
183    ///
184    /// 2 - The transaction with a sidecar, which is the form used to
185    /// send transactions to the network.
186    #[envelope(ty = 3)]
187    Eip4844(Signed<Eip4844>),
188    /// A [`TxEip7702`] tagged with type 4.
189    #[envelope(ty = 4)]
190    Eip7702(Signed<TxEip7702>),
191}
192
193impl<T, Eip4844> From<Signed<T>> for EthereumTxEnvelope<Eip4844>
194where
195    EthereumTypedTransaction<Eip4844>: From<T>,
196    T: RlpEcdsaEncodableTx,
197{
198    fn from(v: Signed<T>) -> Self {
199        let (tx, sig, hash) = v.into_parts();
200        let typed = EthereumTypedTransaction::from(tx);
201        match typed {
202            EthereumTypedTransaction::Legacy(tx_legacy) => {
203                let tx = Signed::new_unchecked(tx_legacy, sig, hash);
204                Self::Legacy(tx)
205            }
206            EthereumTypedTransaction::Eip2930(tx_eip2930) => {
207                let tx = Signed::new_unchecked(tx_eip2930, sig, hash);
208                Self::Eip2930(tx)
209            }
210            EthereumTypedTransaction::Eip1559(tx_eip1559) => {
211                let tx = Signed::new_unchecked(tx_eip1559, sig, hash);
212                Self::Eip1559(tx)
213            }
214            EthereumTypedTransaction::Eip4844(tx_eip4844_variant) => {
215                let tx = Signed::new_unchecked(tx_eip4844_variant, sig, hash);
216                Self::Eip4844(tx)
217            }
218            EthereumTypedTransaction::Eip7702(tx_eip7702) => {
219                let tx = Signed::new_unchecked(tx_eip7702, sig, hash);
220                Self::Eip7702(tx)
221            }
222        }
223    }
224}
225
226impl<Eip4844: RlpEcdsaEncodableTx> From<EthereumTxEnvelope<Eip4844>>
227    for Signed<EthereumTypedTransaction<Eip4844>>
228where
229    EthereumTypedTransaction<Eip4844>: From<Eip4844>,
230{
231    fn from(value: EthereumTxEnvelope<Eip4844>) -> Self {
232        value.into_signed()
233    }
234}
235
236impl<Eip4844> From<(EthereumTypedTransaction<Eip4844>, Signature)> for EthereumTxEnvelope<Eip4844>
237where
238    Eip4844: RlpEcdsaEncodableTx + SignableTransaction<Signature>,
239{
240    fn from(value: (EthereumTypedTransaction<Eip4844>, Signature)) -> Self {
241        value.0.into_signed(value.1).into()
242    }
243}
244
245impl<T> From<EthereumTxEnvelope<TxEip4844WithSidecar<T>>> for EthereumTxEnvelope<TxEip4844> {
246    fn from(value: EthereumTxEnvelope<TxEip4844WithSidecar<T>>) -> Self {
247        value.map_eip4844(|eip4844| eip4844.into())
248    }
249}
250
251impl<T> From<EthereumTxEnvelope<TxEip4844Variant<T>>> for EthereumTxEnvelope<TxEip4844> {
252    fn from(value: EthereumTxEnvelope<TxEip4844Variant<T>>) -> Self {
253        value.map_eip4844(|eip4844| eip4844.into())
254    }
255}
256
257impl<T> From<EthereumTxEnvelope<TxEip4844>> for EthereumTxEnvelope<TxEip4844Variant<T>> {
258    fn from(value: EthereumTxEnvelope<TxEip4844>) -> Self {
259        value.map_eip4844(|eip4844| eip4844.into())
260    }
261}
262
263impl<Eip4844> EthereumTxEnvelope<Eip4844> {
264    /// Converts the EIP-4844 variant of this transaction with the given closure.
265    ///
266    /// This is intended to convert between the EIP-4844 variants, specifically for stripping away
267    /// non consensus data (blob sidecar data).
268    pub fn map_eip4844<U>(self, f: impl FnMut(Eip4844) -> U) -> EthereumTxEnvelope<U> {
269        match self {
270            Self::Legacy(tx) => EthereumTxEnvelope::Legacy(tx),
271            Self::Eip2930(tx) => EthereumTxEnvelope::Eip2930(tx),
272            Self::Eip1559(tx) => EthereumTxEnvelope::Eip1559(tx),
273            Self::Eip4844(tx) => EthereumTxEnvelope::Eip4844(tx.map(f)),
274            Self::Eip7702(tx) => EthereumTxEnvelope::Eip7702(tx),
275        }
276    }
277
278    /// Converts the EIP-4844 variant of this transaction with the given closure, returning an error
279    /// if the mapping fails.
280    pub fn try_map_eip4844<U, E>(
281        self,
282        f: impl FnOnce(Eip4844) -> Result<U, E>,
283    ) -> Result<EthereumTxEnvelope<U>, E> {
284        match self {
285            Self::Legacy(tx) => Ok(EthereumTxEnvelope::Legacy(tx)),
286            Self::Eip2930(tx) => Ok(EthereumTxEnvelope::Eip2930(tx)),
287            Self::Eip1559(tx) => Ok(EthereumTxEnvelope::Eip1559(tx)),
288            Self::Eip4844(tx) => tx.try_map(f).map(EthereumTxEnvelope::Eip4844),
289            Self::Eip7702(tx) => Ok(EthereumTxEnvelope::Eip7702(tx)),
290        }
291    }
292
293    /// Return the [`TxType`] of the inner txn.
294    #[doc(alias = "transaction_type")]
295    pub const fn tx_type(&self) -> TxType {
296        match self {
297            Self::Legacy(_) => TxType::Legacy,
298            Self::Eip2930(_) => TxType::Eip2930,
299            Self::Eip1559(_) => TxType::Eip1559,
300            Self::Eip4844(_) => TxType::Eip4844,
301            Self::Eip7702(_) => TxType::Eip7702,
302        }
303    }
304
305    /// Consumes the type into a [`Signed`]
306    pub fn into_signed(self) -> Signed<EthereumTypedTransaction<Eip4844>>
307    where
308        EthereumTypedTransaction<Eip4844>: From<Eip4844>,
309    {
310        match self {
311            Self::Legacy(tx) => tx.convert(),
312            Self::Eip2930(tx) => tx.convert(),
313            Self::Eip1559(tx) => tx.convert(),
314            Self::Eip4844(tx) => tx.convert(),
315            Self::Eip7702(tx) => tx.convert(),
316        }
317    }
318}
319
320impl<Eip4844: RlpEcdsaEncodableTx> EthereumTxEnvelope<Eip4844> {
321    /// Returns true if the transaction is a legacy transaction.
322    #[inline]
323    pub const fn is_legacy(&self) -> bool {
324        matches!(self, Self::Legacy(_))
325    }
326
327    /// Returns true if the transaction is an EIP-2930 transaction.
328    #[inline]
329    pub const fn is_eip2930(&self) -> bool {
330        matches!(self, Self::Eip2930(_))
331    }
332
333    /// Returns true if the transaction is an EIP-1559 transaction.
334    #[inline]
335    pub const fn is_eip1559(&self) -> bool {
336        matches!(self, Self::Eip1559(_))
337    }
338
339    /// Returns true if the transaction is an EIP-4844 transaction.
340    #[inline]
341    pub const fn is_eip4844(&self) -> bool {
342        matches!(self, Self::Eip4844(_))
343    }
344
345    /// Returns true if the transaction is an EIP-7702 transaction.
346    #[inline]
347    pub const fn is_eip7702(&self) -> bool {
348        matches!(self, Self::Eip7702(_))
349    }
350
351    /// Returns true if the transaction is replay protected.
352    ///
353    /// All non-legacy transactions are replay protected, as the chain id is
354    /// included in the transaction body. Legacy transactions are considered
355    /// replay protected if the `v` value is not 27 or 28, according to the
356    /// rules of [EIP-155].
357    ///
358    /// [EIP-155]: https://eips.ethereum.org/EIPS/eip-155
359    #[inline]
360    pub const fn is_replay_protected(&self) -> bool {
361        match self {
362            Self::Legacy(tx) => tx.tx().chain_id.is_some(),
363            _ => true,
364        }
365    }
366
367    /// Returns the [`TxLegacy`] variant if the transaction is a legacy transaction.
368    pub const fn as_legacy(&self) -> Option<&Signed<TxLegacy>> {
369        match self {
370            Self::Legacy(tx) => Some(tx),
371            _ => None,
372        }
373    }
374
375    /// Returns the [`TxEip2930`] variant if the transaction is an EIP-2930 transaction.
376    pub const fn as_eip2930(&self) -> Option<&Signed<TxEip2930>> {
377        match self {
378            Self::Eip2930(tx) => Some(tx),
379            _ => None,
380        }
381    }
382
383    /// Returns the [`TxEip1559`] variant if the transaction is an EIP-1559 transaction.
384    pub const fn as_eip1559(&self) -> Option<&Signed<TxEip1559>> {
385        match self {
386            Self::Eip1559(tx) => Some(tx),
387            _ => None,
388        }
389    }
390
391    /// Returns the [`TxEip4844Variant`] variant if the transaction is an EIP-4844 transaction.
392    pub const fn as_eip4844(&self) -> Option<&Signed<Eip4844>> {
393        match self {
394            Self::Eip4844(tx) => Some(tx),
395            _ => None,
396        }
397    }
398
399    /// Returns the [`TxEip7702`] variant if the transaction is an EIP-7702 transaction.
400    pub const fn as_eip7702(&self) -> Option<&Signed<TxEip7702>> {
401        match self {
402            Self::Eip7702(tx) => Some(tx),
403            _ => None,
404        }
405    }
406
407    /// Consumes the type and returns the [`TxLegacy`] variant if the transaction is a legacy
408    /// transaction. Returns an error otherwise.
409    pub fn try_into_legacy(self) -> Result<Signed<TxLegacy>, ValueError<Self>> {
410        match self {
411            Self::Legacy(tx) => Ok(tx),
412            _ => Err(ValueError::new_static(self, "Expected legacy transaction")),
413        }
414    }
415
416    /// Consumes the type and returns the [`TxEip2930`] variant if the transaction is an EIP-2930
417    /// transaction. Returns an error otherwise.
418    pub fn try_into_eip2930(self) -> Result<Signed<TxEip2930>, ValueError<Self>> {
419        match self {
420            Self::Eip2930(tx) => Ok(tx),
421            _ => Err(ValueError::new_static(self, "Expected EIP-2930 transaction")),
422        }
423    }
424
425    /// Consumes the type and returns the [`TxEip1559`] variant if the transaction is an EIP-1559
426    /// transaction. Returns an error otherwise.    
427    pub fn try_into_eip1559(self) -> Result<Signed<TxEip1559>, ValueError<Self>> {
428        match self {
429            Self::Eip1559(tx) => Ok(tx),
430            _ => Err(ValueError::new_static(self, "Expected EIP-1559 transaction")),
431        }
432    }
433
434    /// Consumes the type and returns the [`TxEip4844`] variant if the transaction is an EIP-4844
435    /// transaction. Returns an error otherwise.
436    pub fn try_into_eip4844(self) -> Result<Signed<Eip4844>, ValueError<Self>> {
437        match self {
438            Self::Eip4844(tx) => Ok(tx),
439            _ => Err(ValueError::new_static(self, "Expected EIP-4844 transaction")),
440        }
441    }
442
443    /// Consumes the type and returns the [`TxEip7702`] variant if the transaction is an EIP-7702
444    /// transaction. Returns an error otherwise.
445    pub fn try_into_eip7702(self) -> Result<Signed<TxEip7702>, ValueError<Self>> {
446        match self {
447            Self::Eip7702(tx) => Ok(tx),
448            _ => Err(ValueError::new_static(self, "Expected EIP-7702 transaction")),
449        }
450    }
451
452    /// Calculate the signing hash for the transaction.
453    pub fn signature_hash(&self) -> B256
454    where
455        Eip4844: SignableTransaction<Signature>,
456    {
457        match self {
458            Self::Legacy(tx) => tx.signature_hash(),
459            Self::Eip2930(tx) => tx.signature_hash(),
460            Self::Eip1559(tx) => tx.signature_hash(),
461            Self::Eip4844(tx) => tx.signature_hash(),
462            Self::Eip7702(tx) => tx.signature_hash(),
463        }
464    }
465
466    /// Return the reference to signature.
467    pub const fn signature(&self) -> &Signature {
468        match self {
469            Self::Legacy(tx) => tx.signature(),
470            Self::Eip2930(tx) => tx.signature(),
471            Self::Eip1559(tx) => tx.signature(),
472            Self::Eip4844(tx) => tx.signature(),
473            Self::Eip7702(tx) => tx.signature(),
474        }
475    }
476
477    /// Return the hash of the inner Signed.
478    #[doc(alias = "transaction_hash")]
479    pub fn tx_hash(&self) -> &B256 {
480        match self {
481            Self::Legacy(tx) => tx.hash(),
482            Self::Eip2930(tx) => tx.hash(),
483            Self::Eip1559(tx) => tx.hash(),
484            Self::Eip4844(tx) => tx.hash(),
485            Self::Eip7702(tx) => tx.hash(),
486        }
487    }
488
489    /// Reference to transaction hash. Used to identify transaction.
490    pub fn hash(&self) -> &B256 {
491        match self {
492            Self::Legacy(tx) => tx.hash(),
493            Self::Eip2930(tx) => tx.hash(),
494            Self::Eip1559(tx) => tx.hash(),
495            Self::Eip7702(tx) => tx.hash(),
496            Self::Eip4844(tx) => tx.hash(),
497        }
498    }
499
500    /// Return the length of the inner txn, including type byte length
501    pub fn eip2718_encoded_length(&self) -> usize {
502        match self {
503            Self::Legacy(t) => t.eip2718_encoded_length(),
504            Self::Eip2930(t) => t.eip2718_encoded_length(),
505            Self::Eip1559(t) => t.eip2718_encoded_length(),
506            Self::Eip4844(t) => t.eip2718_encoded_length(),
507            Self::Eip7702(t) => t.eip2718_encoded_length(),
508        }
509    }
510}
511
512impl<Eip4844: RlpEcdsaEncodableTx> TxHashRef for EthereumTxEnvelope<Eip4844> {
513    fn tx_hash(&self) -> &B256 {
514        Self::tx_hash(self)
515    }
516}
517
518#[cfg(any(feature = "secp256k1", feature = "k256"))]
519impl<Eip4844> crate::transaction::SignerRecoverable for EthereumTxEnvelope<Eip4844>
520where
521    Eip4844: RlpEcdsaEncodableTx + SignableTransaction<Signature>,
522{
523    fn recover_signer(&self) -> Result<alloy_primitives::Address, crate::crypto::RecoveryError> {
524        match self {
525            Self::Legacy(tx) => crate::transaction::SignerRecoverable::recover_signer(tx),
526            Self::Eip2930(tx) => crate::transaction::SignerRecoverable::recover_signer(tx),
527            Self::Eip1559(tx) => crate::transaction::SignerRecoverable::recover_signer(tx),
528            Self::Eip4844(tx) => crate::transaction::SignerRecoverable::recover_signer(tx),
529            Self::Eip7702(tx) => crate::transaction::SignerRecoverable::recover_signer(tx),
530        }
531    }
532
533    fn recover_signer_unchecked(
534        &self,
535    ) -> Result<alloy_primitives::Address, crate::crypto::RecoveryError> {
536        match self {
537            Self::Legacy(tx) => crate::transaction::SignerRecoverable::recover_signer_unchecked(tx),
538            Self::Eip2930(tx) => {
539                crate::transaction::SignerRecoverable::recover_signer_unchecked(tx)
540            }
541            Self::Eip1559(tx) => {
542                crate::transaction::SignerRecoverable::recover_signer_unchecked(tx)
543            }
544            Self::Eip4844(tx) => {
545                crate::transaction::SignerRecoverable::recover_signer_unchecked(tx)
546            }
547            Self::Eip7702(tx) => {
548                crate::transaction::SignerRecoverable::recover_signer_unchecked(tx)
549            }
550        }
551    }
552
553    fn recover_unchecked_with_buf(
554        &self,
555        buf: &mut alloc::vec::Vec<u8>,
556    ) -> Result<alloy_primitives::Address, crate::crypto::RecoveryError> {
557        match self {
558            Self::Legacy(tx) => {
559                crate::transaction::SignerRecoverable::recover_unchecked_with_buf(tx, buf)
560            }
561            Self::Eip2930(tx) => {
562                crate::transaction::SignerRecoverable::recover_unchecked_with_buf(tx, buf)
563            }
564            Self::Eip1559(tx) => {
565                crate::transaction::SignerRecoverable::recover_unchecked_with_buf(tx, buf)
566            }
567            Self::Eip4844(tx) => {
568                crate::transaction::SignerRecoverable::recover_unchecked_with_buf(tx, buf)
569            }
570            Self::Eip7702(tx) => {
571                crate::transaction::SignerRecoverable::recover_unchecked_with_buf(tx, buf)
572            }
573        }
574    }
575}
576
577/// Bincode-compatible [`EthereumTxEnvelope`] serde implementation.
578#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
579pub mod serde_bincode_compat {
580    use crate::{EthereumTypedTransaction, Signed};
581    use alloc::borrow::Cow;
582    use alloy_primitives::Signature;
583    use serde::{Deserialize, Deserializer, Serialize, Serializer};
584    use serde_with::{DeserializeAs, SerializeAs};
585
586    /// Bincode-compatible [`super::EthereumTxEnvelope`] serde implementation.
587    ///
588    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
589    /// ```rust
590    /// use alloy_consensus::{serde_bincode_compat, EthereumTxEnvelope};
591    /// use serde::{de::DeserializeOwned, Deserialize, Serialize};
592    /// use serde_with::serde_as;
593    ///
594    /// #[serde_as]
595    /// #[derive(Serialize, Deserialize)]
596    /// struct Data<T: Serialize + DeserializeOwned + Clone + 'static> {
597    ///     #[serde_as(as = "serde_bincode_compat::EthereumTxEnvelope<'_, T>")]
598    ///     receipt: EthereumTxEnvelope<T>,
599    /// }
600    /// ```
601    #[derive(Debug, Serialize, Deserialize)]
602    pub struct EthereumTxEnvelope<'a, Eip4844: Clone = crate::transaction::TxEip4844> {
603        /// Transaction signature
604        signature: Signature,
605        /// bincode compatible transaction
606        transaction:
607            crate::serde_bincode_compat::transaction::EthereumTypedTransaction<'a, Eip4844>,
608    }
609
610    impl<'a, T: Clone> From<&'a super::EthereumTxEnvelope<T>> for EthereumTxEnvelope<'a, T> {
611        fn from(value: &'a super::EthereumTxEnvelope<T>) -> Self {
612            match value {
613                super::EthereumTxEnvelope::Legacy(tx) => Self {
614                    signature: *tx.signature(),
615                    transaction:
616                        crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Legacy(
617                            tx.tx().into(),
618                        ),
619                },
620                super::EthereumTxEnvelope::Eip2930(tx) => Self {
621                    signature: *tx.signature(),
622                    transaction:
623                        crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Eip2930(
624                            tx.tx().into(),
625                        ),
626                },
627                super::EthereumTxEnvelope::Eip1559(tx) => Self {
628                    signature: *tx.signature(),
629                    transaction:
630                        crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Eip1559(
631                            tx.tx().into(),
632                        ),
633                },
634                super::EthereumTxEnvelope::Eip4844(tx) => Self {
635                    signature: *tx.signature(),
636                    transaction:
637                        crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Eip4844(
638                            Cow::Borrowed(tx.tx()),
639                        ),
640                },
641                super::EthereumTxEnvelope::Eip7702(tx) => Self {
642                    signature: *tx.signature(),
643                    transaction:
644                        crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Eip7702(
645                            tx.tx().into(),
646                        ),
647                },
648            }
649        }
650    }
651
652    impl<'a, T: Clone> From<EthereumTxEnvelope<'a, T>> for super::EthereumTxEnvelope<T> {
653        fn from(value: EthereumTxEnvelope<'a, T>) -> Self {
654            let EthereumTxEnvelope { signature, transaction } = value;
655            let transaction: crate::transaction::typed::EthereumTypedTransaction<T> =
656                transaction.into();
657            match transaction {
658                EthereumTypedTransaction::Legacy(tx) => Signed::new_unhashed(tx, signature).into(),
659                EthereumTypedTransaction::Eip2930(tx) => Signed::new_unhashed(tx, signature).into(),
660                EthereumTypedTransaction::Eip1559(tx) => Signed::new_unhashed(tx, signature).into(),
661                EthereumTypedTransaction::Eip4844(tx) => {
662                    Self::Eip4844(Signed::new_unhashed(tx, signature))
663                }
664                EthereumTypedTransaction::Eip7702(tx) => Signed::new_unhashed(tx, signature).into(),
665            }
666        }
667    }
668
669    impl<T: Serialize + Clone> SerializeAs<super::EthereumTxEnvelope<T>> for EthereumTxEnvelope<'_, T> {
670        fn serialize_as<S>(
671            source: &super::EthereumTxEnvelope<T>,
672            serializer: S,
673        ) -> Result<S::Ok, S::Error>
674        where
675            S: Serializer,
676        {
677            EthereumTxEnvelope::<'_, T>::from(source).serialize(serializer)
678        }
679    }
680
681    impl<'de, T: Deserialize<'de> + Clone> DeserializeAs<'de, super::EthereumTxEnvelope<T>>
682        for EthereumTxEnvelope<'de, T>
683    {
684        fn deserialize_as<D>(deserializer: D) -> Result<super::EthereumTxEnvelope<T>, D::Error>
685        where
686            D: Deserializer<'de>,
687        {
688            EthereumTxEnvelope::<'_, T>::deserialize(deserializer).map(Into::into)
689        }
690    }
691
692    #[cfg(test)]
693    mod tests {
694        use super::super::{serde_bincode_compat, EthereumTxEnvelope};
695        use crate::TxEip4844;
696        use arbitrary::Arbitrary;
697        use bincode::config;
698        use rand::Rng;
699        use serde::{Deserialize, Serialize};
700        use serde_with::serde_as;
701
702        #[test]
703        fn test_typed_tx_envelope_bincode_roundtrip() {
704            #[serde_as]
705            #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
706            struct Data {
707                #[serde_as(as = "serde_bincode_compat::EthereumTxEnvelope<'_>")]
708                transaction: EthereumTxEnvelope<TxEip4844>,
709            }
710
711            let mut bytes = [0u8; 1024];
712            rand::thread_rng().fill(bytes.as_mut_slice());
713            let data = Data {
714                transaction: EthereumTxEnvelope::arbitrary(&mut arbitrary::Unstructured::new(
715                    &bytes,
716                ))
717                .unwrap(),
718            };
719
720            let encoded = bincode::serde::encode_to_vec(&data, config::legacy()).unwrap();
721            let (decoded, _) =
722                bincode::serde::decode_from_slice::<Data, _>(&encoded, config::legacy()).unwrap();
723            assert_eq!(decoded, data);
724        }
725    }
726}
727
728#[cfg(test)]
729mod tests {
730    use super::*;
731    use crate::{
732        transaction::{Recovered, SignableTransaction},
733        Transaction, TxEip4844, TxEip4844WithSidecar,
734    };
735    use alloc::vec::Vec;
736    use alloy_eips::{
737        eip2930::{AccessList, AccessListItem},
738        eip4844::BlobTransactionSidecar,
739        eip7702::Authorization,
740    };
741    #[allow(unused_imports)]
742    use alloy_primitives::{b256, Bytes, TxKind};
743    use alloy_primitives::{hex, Address, Signature, U256};
744    use alloy_rlp::Decodable;
745    use std::{fs, path::PathBuf, str::FromStr, vec};
746
747    #[test]
748    fn assert_encodable() {
749        fn assert_encodable<T: Encodable2718>() {}
750
751        assert_encodable::<EthereumTxEnvelope<TxEip4844>>();
752        assert_encodable::<Recovered<EthereumTxEnvelope<TxEip4844>>>();
753        assert_encodable::<Recovered<EthereumTxEnvelope<TxEip4844Variant>>>();
754    }
755
756    #[test]
757    #[cfg(feature = "k256")]
758    // Test vector from https://etherscan.io/tx/0xce4dc6d7a7549a98ee3b071b67e970879ff51b5b95d1c340bacd80fa1e1aab31
759    fn test_decode_live_1559_tx() {
760        use alloy_primitives::address;
761
762        let raw_tx = alloy_primitives::hex::decode("02f86f0102843b9aca0085029e7822d68298f094d9e1459a7a482635700cbc20bbaf52d495ab9c9680841b55ba3ac080a0c199674fcb29f353693dd779c017823b954b3c69dffa3cd6b2a6ff7888798039a028ca912de909e7e6cdef9cdcaf24c54dd8c1032946dfa1d85c206b32a9064fe8").unwrap();
763        let res = TxEnvelope::decode(&mut raw_tx.as_slice()).unwrap();
764
765        assert_eq!(res.tx_type(), TxType::Eip1559);
766
767        let tx = match res {
768            TxEnvelope::Eip1559(tx) => tx,
769            _ => unreachable!(),
770        };
771
772        assert_eq!(tx.tx().to, TxKind::Call(address!("D9e1459A7A482635700cBc20BBAF52D495Ab9C96")));
773        let from = tx.recover_signer().unwrap();
774        assert_eq!(from, address!("001e2b7dE757bA469a57bF6b23d982458a07eFcE"));
775    }
776
777    #[test]
778    fn test_is_replay_protected_v() {
779        let sig = Signature::test_signature();
780        assert!(!&TxEnvelope::Legacy(Signed::new_unchecked(
781            TxLegacy::default(),
782            sig,
783            Default::default(),
784        ))
785        .is_replay_protected());
786        let r = b256!("840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565");
787        let s = b256!("25e7109ceb98168d95b09b18bbf6b685130e0562f233877d492b94eee0c5b6d1");
788        let v = false;
789        let valid_sig = Signature::from_scalars_and_parity(r, s, v);
790        assert!(!&TxEnvelope::Legacy(Signed::new_unchecked(
791            TxLegacy::default(),
792            valid_sig,
793            Default::default(),
794        ))
795        .is_replay_protected());
796        assert!(&TxEnvelope::Eip2930(Signed::new_unchecked(
797            TxEip2930::default(),
798            sig,
799            Default::default(),
800        ))
801        .is_replay_protected());
802        assert!(&TxEnvelope::Eip1559(Signed::new_unchecked(
803            TxEip1559::default(),
804            sig,
805            Default::default(),
806        ))
807        .is_replay_protected());
808        assert!(&TxEnvelope::Eip4844(Signed::new_unchecked(
809            TxEip4844Variant::TxEip4844(TxEip4844::default()),
810            sig,
811            Default::default(),
812        ))
813        .is_replay_protected());
814        assert!(&TxEnvelope::Eip7702(Signed::new_unchecked(
815            TxEip7702::default(),
816            sig,
817            Default::default(),
818        ))
819        .is_replay_protected());
820    }
821
822    #[test]
823    #[cfg(feature = "k256")]
824    // Test vector from https://etherscan.io/tx/0x280cde7cdefe4b188750e76c888f13bd05ce9a4d7767730feefe8a0e50ca6fc4
825    fn test_decode_live_legacy_tx() {
826        use alloy_primitives::address;
827
828        let raw_tx = alloy_primitives::bytes!("f9015482078b8505d21dba0083022ef1947a250d5630b4cf539739df2c5dacb4c659f2488d880c46549a521b13d8b8e47ff36ab50000000000000000000000000000000000000000000066ab5a608bd00a23f2fe000000000000000000000000000000000000000000000000000000000000008000000000000000000000000048c04ed5691981c42154c6167398f95e8f38a7ff00000000000000000000000000000000000000000000000000000000632ceac70000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006c6ee5e31d828de241282b9606c8e98ea48526e225a0c9077369501641a92ef7399ff81c21639ed4fd8fc69cb793cfa1dbfab342e10aa0615facb2f1bcf3274a354cfe384a38d0cc008a11c2dd23a69111bc6930ba27a8");
829        let res = TxEnvelope::decode_2718(&mut raw_tx.as_ref()).unwrap();
830        assert_eq!(res.tx_type(), TxType::Legacy);
831
832        let tx = match res {
833            TxEnvelope::Legacy(tx) => tx,
834            _ => unreachable!(),
835        };
836
837        assert_eq!(tx.tx().chain_id(), Some(1));
838
839        assert_eq!(tx.tx().to, TxKind::Call(address!("7a250d5630B4cF539739dF2C5dAcb4c659F2488D")));
840        assert_eq!(
841            tx.hash().to_string(),
842            "0x280cde7cdefe4b188750e76c888f13bd05ce9a4d7767730feefe8a0e50ca6fc4"
843        );
844        let from = tx.recover_signer().unwrap();
845        assert_eq!(from, address!("a12e1462d0ceD572f396F58B6E2D03894cD7C8a4"));
846    }
847
848    #[test]
849    #[cfg(feature = "k256")]
850    // Test vector from https://sepolia.etherscan.io/tx/0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0
851    // Blobscan: https://sepolia.blobscan.com/tx/0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0
852    fn test_decode_live_4844_tx() {
853        use crate::Transaction;
854        use alloy_primitives::{address, b256};
855
856        // https://sepolia.etherscan.io/getRawTx?tx=0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0
857        let raw_tx = alloy_primitives::hex::decode("0x03f9011d83aa36a7820fa28477359400852e90edd0008252089411e9ca82a3a762b4b5bd264d4173a242e7a770648080c08504a817c800f8a5a0012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921aa00152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4a0013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7a001148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1a0011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e654901a0c8de4cced43169f9aa3d36506363b2d2c44f6c49fc1fd91ea114c86f3757077ea01e11fdd0d1934eda0492606ee0bb80a7bf8f35cc5f86ec60fe5031ba48bfd544").unwrap();
858
859        let res = TxEnvelope::decode_2718(&mut raw_tx.as_slice()).unwrap();
860        assert_eq!(res.tx_type(), TxType::Eip4844);
861
862        let tx = match res {
863            TxEnvelope::Eip4844(tx) => tx,
864            _ => unreachable!(),
865        };
866
867        assert_eq!(
868            tx.tx().kind(),
869            TxKind::Call(address!("11E9CA82A3a762b4B5bd264d4173a242e7a77064"))
870        );
871
872        // Assert this is the correct variant of the EIP-4844 enum, which only contains the tx.
873        assert!(matches!(tx.tx(), TxEip4844Variant::TxEip4844(_)));
874
875        assert_eq!(
876            tx.tx().tx().blob_versioned_hashes,
877            vec![
878                b256!("012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921a"),
879                b256!("0152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4"),
880                b256!("013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7"),
881                b256!("01148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1"),
882                b256!("011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e6549")
883            ]
884        );
885
886        let from = tx.recover_signer().unwrap();
887        assert_eq!(from, address!("0xA83C816D4f9b2783761a22BA6FADB0eB0606D7B2"));
888    }
889
890    fn test_encode_decode_roundtrip<T: SignableTransaction<Signature>>(
891        tx: T,
892        signature: Option<Signature>,
893    ) where
894        Signed<T>: Into<TxEnvelope>,
895    {
896        let signature = signature.unwrap_or_else(Signature::test_signature);
897        let tx_signed = tx.into_signed(signature);
898        let tx_envelope: TxEnvelope = tx_signed.into();
899        let encoded = tx_envelope.encoded_2718();
900        let mut slice = encoded.as_slice();
901        let decoded = TxEnvelope::decode_2718(&mut slice).unwrap();
902        assert_eq!(encoded.len(), tx_envelope.encode_2718_len());
903        assert_eq!(decoded, tx_envelope);
904        assert_eq!(slice.len(), 0);
905    }
906
907    #[test]
908    fn test_encode_decode_legacy() {
909        let tx = TxLegacy {
910            chain_id: None,
911            nonce: 2,
912            gas_limit: 1000000,
913            gas_price: 10000000000,
914            to: Address::left_padding_from(&[6]).into(),
915            value: U256::from(7_u64),
916            ..Default::default()
917        };
918        test_encode_decode_roundtrip(tx, Some(Signature::test_signature().with_parity(true)));
919    }
920
921    #[test]
922    fn test_encode_decode_eip1559() {
923        let tx = TxEip1559 {
924            chain_id: 1u64,
925            nonce: 2,
926            max_fee_per_gas: 3,
927            max_priority_fee_per_gas: 4,
928            gas_limit: 5,
929            to: Address::left_padding_from(&[6]).into(),
930            value: U256::from(7_u64),
931            input: vec![8].into(),
932            access_list: Default::default(),
933        };
934        test_encode_decode_roundtrip(tx, None);
935    }
936
937    #[test]
938    fn test_encode_decode_eip1559_parity_eip155() {
939        let tx = TxEip1559 {
940            chain_id: 1u64,
941            nonce: 2,
942            max_fee_per_gas: 3,
943            max_priority_fee_per_gas: 4,
944            gas_limit: 5,
945            to: Address::left_padding_from(&[6]).into(),
946            value: U256::from(7_u64),
947            input: vec![8].into(),
948            access_list: Default::default(),
949        };
950        let signature = Signature::test_signature().with_parity(true);
951
952        test_encode_decode_roundtrip(tx, Some(signature));
953    }
954
955    #[test]
956    fn test_encode_decode_eip2930_parity_eip155() {
957        let tx = TxEip2930 {
958            chain_id: 1u64,
959            nonce: 2,
960            gas_price: 3,
961            gas_limit: 4,
962            to: Address::left_padding_from(&[5]).into(),
963            value: U256::from(6_u64),
964            input: vec![7].into(),
965            access_list: Default::default(),
966        };
967        let signature = Signature::test_signature().with_parity(true);
968        test_encode_decode_roundtrip(tx, Some(signature));
969    }
970
971    #[test]
972    fn test_encode_decode_eip4844_parity_eip155() {
973        let tx = TxEip4844 {
974            chain_id: 1,
975            nonce: 100,
976            max_fee_per_gas: 50_000_000_000,
977            max_priority_fee_per_gas: 1_000_000_000_000,
978            gas_limit: 1_000_000,
979            to: Address::random(),
980            value: U256::from(10e18),
981            input: Bytes::new(),
982            access_list: AccessList(vec![AccessListItem {
983                address: Address::random(),
984                storage_keys: vec![B256::random()],
985            }]),
986            blob_versioned_hashes: vec![B256::random()],
987            max_fee_per_blob_gas: 0,
988        };
989        let signature = Signature::test_signature().with_parity(true);
990        test_encode_decode_roundtrip(tx, Some(signature));
991    }
992
993    #[test]
994    fn test_encode_decode_eip4844_sidecar_parity_eip155() {
995        let tx = TxEip4844 {
996            chain_id: 1,
997            nonce: 100,
998            max_fee_per_gas: 50_000_000_000,
999            max_priority_fee_per_gas: 1_000_000_000_000,
1000            gas_limit: 1_000_000,
1001            to: Address::random(),
1002            value: U256::from(10e18),
1003            input: Bytes::new(),
1004            access_list: AccessList(vec![AccessListItem {
1005                address: Address::random(),
1006                storage_keys: vec![B256::random()],
1007            }]),
1008            blob_versioned_hashes: vec![B256::random()],
1009            max_fee_per_blob_gas: 0,
1010        };
1011        let sidecar = BlobTransactionSidecar {
1012            blobs: vec![[2; 131072].into()],
1013            commitments: vec![[3; 48].into()],
1014            proofs: vec![[4; 48].into()],
1015        };
1016        let tx = TxEip4844WithSidecar { tx, sidecar };
1017        let signature = Signature::test_signature().with_parity(true);
1018
1019        let tx_signed = tx.into_signed(signature);
1020        let tx_envelope: TxEnvelope = tx_signed.into();
1021
1022        let mut out = Vec::new();
1023        tx_envelope.network_encode(&mut out);
1024        let mut slice = out.as_slice();
1025        let decoded = TxEnvelope::network_decode(&mut slice).unwrap();
1026        assert_eq!(slice.len(), 0);
1027        assert_eq!(out.len(), tx_envelope.network_len());
1028        assert_eq!(decoded, tx_envelope);
1029    }
1030
1031    #[test]
1032    fn test_encode_decode_eip4844_variant_parity_eip155() {
1033        let tx = TxEip4844 {
1034            chain_id: 1,
1035            nonce: 100,
1036            max_fee_per_gas: 50_000_000_000,
1037            max_priority_fee_per_gas: 1_000_000_000_000,
1038            gas_limit: 1_000_000,
1039            to: Address::random(),
1040            value: U256::from(10e18),
1041            input: Bytes::new(),
1042            access_list: AccessList(vec![AccessListItem {
1043                address: Address::random(),
1044                storage_keys: vec![B256::random()],
1045            }]),
1046            blob_versioned_hashes: vec![B256::random()],
1047            max_fee_per_blob_gas: 0,
1048        };
1049        let tx = TxEip4844Variant::TxEip4844(tx);
1050        let signature = Signature::test_signature().with_parity(true);
1051        test_encode_decode_roundtrip(tx, Some(signature));
1052    }
1053
1054    #[test]
1055    fn test_encode_decode_eip2930() {
1056        let tx = TxEip2930 {
1057            chain_id: 1u64,
1058            nonce: 2,
1059            gas_price: 3,
1060            gas_limit: 4,
1061            to: Address::left_padding_from(&[5]).into(),
1062            value: U256::from(6_u64),
1063            input: vec![7].into(),
1064            access_list: AccessList(vec![AccessListItem {
1065                address: Address::left_padding_from(&[8]),
1066                storage_keys: vec![B256::left_padding_from(&[9])],
1067            }]),
1068        };
1069        test_encode_decode_roundtrip(tx, None);
1070    }
1071
1072    #[test]
1073    fn test_encode_decode_eip7702() {
1074        let tx = TxEip7702 {
1075            chain_id: 1u64,
1076            nonce: 2,
1077            gas_limit: 3,
1078            max_fee_per_gas: 4,
1079            max_priority_fee_per_gas: 5,
1080            to: Address::left_padding_from(&[5]),
1081            value: U256::from(6_u64),
1082            input: vec![7].into(),
1083            access_list: AccessList(vec![AccessListItem {
1084                address: Address::left_padding_from(&[8]),
1085                storage_keys: vec![B256::left_padding_from(&[9])],
1086            }]),
1087            authorization_list: vec![(Authorization {
1088                chain_id: U256::from(1),
1089                address: Address::left_padding_from(&[10]),
1090                nonce: 1u64,
1091            })
1092            .into_signed(Signature::from_str("48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c8041b").unwrap())],
1093        };
1094        test_encode_decode_roundtrip(tx, None);
1095    }
1096
1097    #[test]
1098    fn test_encode_decode_transaction_list() {
1099        let signature = Signature::test_signature();
1100        let tx = TxEnvelope::Eip1559(
1101            TxEip1559 {
1102                chain_id: 1u64,
1103                nonce: 2,
1104                max_fee_per_gas: 3,
1105                max_priority_fee_per_gas: 4,
1106                gas_limit: 5,
1107                to: Address::left_padding_from(&[6]).into(),
1108                value: U256::from(7_u64),
1109                input: vec![8].into(),
1110                access_list: Default::default(),
1111            }
1112            .into_signed(signature),
1113        );
1114        let transactions = vec![tx.clone(), tx];
1115        let encoded = alloy_rlp::encode(&transactions);
1116        let decoded = Vec::<TxEnvelope>::decode(&mut &encoded[..]).unwrap();
1117        assert_eq!(transactions, decoded);
1118    }
1119
1120    #[test]
1121    fn decode_encode_known_rpc_transaction() {
1122        // test data pulled from hive test that sends blob transactions
1123        let network_data_path =
1124            PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/rpc_blob_transaction.rlp");
1125        let data = fs::read_to_string(network_data_path).expect("Unable to read file");
1126        let hex_data = hex::decode(data.trim()).unwrap();
1127
1128        let tx: TxEnvelope = TxEnvelope::decode_2718(&mut hex_data.as_slice()).unwrap();
1129        let encoded = tx.encoded_2718();
1130        assert_eq!(encoded, hex_data);
1131        assert_eq!(tx.encode_2718_len(), hex_data.len());
1132    }
1133
1134    #[cfg(feature = "serde")]
1135    fn test_serde_roundtrip<T: SignableTransaction<Signature>>(tx: T)
1136    where
1137        Signed<T>: Into<TxEnvelope>,
1138    {
1139        let signature = Signature::test_signature();
1140        let tx_envelope: TxEnvelope = tx.into_signed(signature).into();
1141
1142        let serialized = serde_json::to_string(&tx_envelope).unwrap();
1143
1144        let deserialized: TxEnvelope = serde_json::from_str(&serialized).unwrap();
1145
1146        assert_eq!(tx_envelope, deserialized);
1147    }
1148
1149    #[test]
1150    #[cfg(feature = "serde")]
1151    fn test_serde_roundtrip_legacy() {
1152        let tx = TxLegacy {
1153            chain_id: Some(1),
1154            nonce: 100,
1155            gas_price: 3_000_000_000,
1156            gas_limit: 50_000,
1157            to: Address::default().into(),
1158            value: U256::from(10e18),
1159            input: Bytes::new(),
1160        };
1161        test_serde_roundtrip(tx);
1162    }
1163
1164    #[test]
1165    #[cfg(feature = "serde")]
1166    fn test_serde_roundtrip_eip1559() {
1167        let tx = TxEip1559 {
1168            chain_id: 1,
1169            nonce: 100,
1170            max_fee_per_gas: 50_000_000_000,
1171            max_priority_fee_per_gas: 1_000_000_000_000,
1172            gas_limit: 1_000_000,
1173            to: TxKind::Create,
1174            value: U256::from(10e18),
1175            input: Bytes::new(),
1176            access_list: AccessList(vec![AccessListItem {
1177                address: Address::random(),
1178                storage_keys: vec![B256::random()],
1179            }]),
1180        };
1181        test_serde_roundtrip(tx);
1182    }
1183
1184    #[test]
1185    #[cfg(feature = "serde")]
1186    fn test_serde_roundtrip_eip2930() {
1187        let tx = TxEip2930 {
1188            chain_id: u64::MAX,
1189            nonce: u64::MAX,
1190            gas_price: u128::MAX,
1191            gas_limit: u64::MAX,
1192            to: Address::random().into(),
1193            value: U256::MAX,
1194            input: Bytes::new(),
1195            access_list: Default::default(),
1196        };
1197        test_serde_roundtrip(tx);
1198    }
1199
1200    #[test]
1201    #[cfg(feature = "serde")]
1202    fn test_serde_roundtrip_eip4844() {
1203        let tx = TxEip4844Variant::TxEip4844(TxEip4844 {
1204            chain_id: 1,
1205            nonce: 100,
1206            max_fee_per_gas: 50_000_000_000,
1207            max_priority_fee_per_gas: 1_000_000_000_000,
1208            gas_limit: 1_000_000,
1209            to: Address::random(),
1210            value: U256::from(10e18),
1211            input: Bytes::new(),
1212            access_list: AccessList(vec![AccessListItem {
1213                address: Address::random(),
1214                storage_keys: vec![B256::random()],
1215            }]),
1216            blob_versioned_hashes: vec![B256::random()],
1217            max_fee_per_blob_gas: 0,
1218        });
1219        test_serde_roundtrip(tx);
1220
1221        let tx = TxEip4844Variant::TxEip4844WithSidecar(TxEip4844WithSidecar {
1222            tx: TxEip4844 {
1223                chain_id: 1,
1224                nonce: 100,
1225                max_fee_per_gas: 50_000_000_000,
1226                max_priority_fee_per_gas: 1_000_000_000_000,
1227                gas_limit: 1_000_000,
1228                to: Address::random(),
1229                value: U256::from(10e18),
1230                input: Bytes::new(),
1231                access_list: AccessList(vec![AccessListItem {
1232                    address: Address::random(),
1233                    storage_keys: vec![B256::random()],
1234                }]),
1235                blob_versioned_hashes: vec![B256::random()],
1236                max_fee_per_blob_gas: 0,
1237            },
1238            sidecar: Default::default(),
1239        });
1240        test_serde_roundtrip(tx);
1241    }
1242
1243    #[test]
1244    #[cfg(feature = "serde")]
1245    fn test_serde_roundtrip_eip7702() {
1246        let tx = TxEip7702 {
1247            chain_id: u64::MAX,
1248            nonce: u64::MAX,
1249            gas_limit: u64::MAX,
1250            max_fee_per_gas: u128::MAX,
1251            max_priority_fee_per_gas: u128::MAX,
1252            to: Address::random(),
1253            value: U256::MAX,
1254            input: Bytes::new(),
1255            access_list: AccessList(vec![AccessListItem {
1256                address: Address::random(),
1257                storage_keys: vec![B256::random()],
1258            }]),
1259            authorization_list: vec![(Authorization {
1260                chain_id: U256::from(1),
1261                address: Address::left_padding_from(&[1]),
1262                nonce: 1u64,
1263            })
1264            .into_signed(Signature::from_str("48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c8041b").unwrap())],
1265        };
1266        test_serde_roundtrip(tx);
1267    }
1268
1269    #[test]
1270    #[cfg(feature = "serde")]
1271    fn serde_tx_from_contract_call() {
1272        let rpc_tx = r#"{"hash":"0x018b2331d461a4aeedf6a1f9cc37463377578244e6a35216057a8370714e798f","nonce":"0x1","blockHash":"0x3ca295f1dcaf8ac073c543dc0eccf18859f411206df181731e374e9917252931","blockNumber":"0x2","transactionIndex":"0x0","from":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","to":"0x5fbdb2315678afecb367f032d93f642f64180aa3","value":"0x0","gasPrice":"0x3a29f0f8","gas":"0x1c9c380","maxFeePerGas":"0xba43b7400","maxPriorityFeePerGas":"0x5f5e100","input":"0xd09de08a","r":"0xd309309a59a49021281cb6bb41d164c96eab4e50f0c1bd24c03ca336e7bc2bb7","s":"0x28a7f089143d0a1355ebeb2a1b9f0e5ad9eca4303021c1400d61bc23c9ac5319","v":"0x0","yParity":"0x0","chainId":"0x7a69","accessList":[],"type":"0x2"}"#;
1273
1274        let te = serde_json::from_str::<TxEnvelope>(rpc_tx).unwrap();
1275
1276        assert_eq!(
1277            *te.tx_hash(),
1278            alloy_primitives::b256!(
1279                "018b2331d461a4aeedf6a1f9cc37463377578244e6a35216057a8370714e798f"
1280            )
1281        );
1282    }
1283
1284    #[test]
1285    #[cfg(feature = "k256")]
1286    fn test_arbitrary_envelope() {
1287        use crate::transaction::SignerRecoverable;
1288        use arbitrary::Arbitrary;
1289        let mut unstructured = arbitrary::Unstructured::new(b"arbitrary tx envelope");
1290        let tx = TxEnvelope::arbitrary(&mut unstructured).unwrap();
1291
1292        assert!(tx.recover_signer().is_ok());
1293    }
1294
1295    #[test]
1296    #[cfg(feature = "serde")]
1297    fn test_serde_untagged_legacy() {
1298        let data = r#"{
1299            "hash": "0x97efb58d2b42df8d68ab5899ff42b16c7e0af35ed86ae4adb8acaad7e444220c",
1300            "input": "0x",
1301            "r": "0x5d71a4a548503f2916d10c6b1a1557a0e7352eb041acb2bac99d1ad6bb49fd45",
1302            "s": "0x2627bf6d35be48b0e56c61733f63944c0ebcaa85cb4ed6bc7cba3161ba85e0e8",
1303            "v": "0x1c",
1304            "gas": "0x15f90",
1305            "from": "0x2a65aca4d5fc5b5c859090a6c34d164135398226",
1306            "to": "0x8fbeb4488a08d60979b5aa9e13dd00b2726320b2",
1307            "value": "0xf606682badd7800",
1308            "nonce": "0x11f398",
1309            "gasPrice": "0x4a817c800"
1310        }"#;
1311
1312        let tx: TxEnvelope = serde_json::from_str(data).unwrap();
1313
1314        assert!(matches!(tx, TxEnvelope::Legacy(_)));
1315
1316        let data_with_wrong_type = r#"{
1317            "hash": "0x97efb58d2b42df8d68ab5899ff42b16c7e0af35ed86ae4adb8acaad7e444220c",
1318            "input": "0x",
1319            "r": "0x5d71a4a548503f2916d10c6b1a1557a0e7352eb041acb2bac99d1ad6bb49fd45",
1320            "s": "0x2627bf6d35be48b0e56c61733f63944c0ebcaa85cb4ed6bc7cba3161ba85e0e8",
1321            "v": "0x1c",
1322            "gas": "0x15f90",
1323            "from": "0x2a65aca4d5fc5b5c859090a6c34d164135398226",
1324            "to": "0x8fbeb4488a08d60979b5aa9e13dd00b2726320b2",
1325            "value": "0xf606682badd7800",
1326            "nonce": "0x11f398",
1327            "gasPrice": "0x4a817c800",
1328            "type": "0x12"
1329        }"#;
1330
1331        assert!(serde_json::from_str::<TxEnvelope>(data_with_wrong_type).is_err());
1332    }
1333
1334    #[test]
1335    fn test_tx_type_try_from_u8() {
1336        assert_eq!(TxType::try_from(0u8).unwrap(), TxType::Legacy);
1337        assert_eq!(TxType::try_from(1u8).unwrap(), TxType::Eip2930);
1338        assert_eq!(TxType::try_from(2u8).unwrap(), TxType::Eip1559);
1339        assert_eq!(TxType::try_from(3u8).unwrap(), TxType::Eip4844);
1340        assert_eq!(TxType::try_from(4u8).unwrap(), TxType::Eip7702);
1341        assert!(TxType::try_from(5u8).is_err()); // Invalid case
1342    }
1343
1344    #[test]
1345    fn test_tx_type_try_from_u64() {
1346        assert_eq!(TxType::try_from(0u64).unwrap(), TxType::Legacy);
1347        assert_eq!(TxType::try_from(1u64).unwrap(), TxType::Eip2930);
1348        assert_eq!(TxType::try_from(2u64).unwrap(), TxType::Eip1559);
1349        assert_eq!(TxType::try_from(3u64).unwrap(), TxType::Eip4844);
1350        assert_eq!(TxType::try_from(4u64).unwrap(), TxType::Eip7702);
1351        assert!(TxType::try_from(10u64).is_err()); // Invalid case
1352    }
1353
1354    #[test]
1355    fn test_tx_type_from_conversions() {
1356        let legacy_tx = Signed::new_unchecked(
1357            TxLegacy::default(),
1358            Signature::test_signature(),
1359            Default::default(),
1360        );
1361        let eip2930_tx = Signed::new_unchecked(
1362            TxEip2930::default(),
1363            Signature::test_signature(),
1364            Default::default(),
1365        );
1366        let eip1559_tx = Signed::new_unchecked(
1367            TxEip1559::default(),
1368            Signature::test_signature(),
1369            Default::default(),
1370        );
1371        let eip4844_variant = Signed::new_unchecked(
1372            TxEip4844Variant::TxEip4844(TxEip4844::default()),
1373            Signature::test_signature(),
1374            Default::default(),
1375        );
1376        let eip7702_tx = Signed::new_unchecked(
1377            TxEip7702::default(),
1378            Signature::test_signature(),
1379            Default::default(),
1380        );
1381
1382        assert!(matches!(TxEnvelope::from(legacy_tx), TxEnvelope::Legacy(_)));
1383        assert!(matches!(TxEnvelope::from(eip2930_tx), TxEnvelope::Eip2930(_)));
1384        assert!(matches!(TxEnvelope::from(eip1559_tx), TxEnvelope::Eip1559(_)));
1385        assert!(matches!(TxEnvelope::from(eip4844_variant), TxEnvelope::Eip4844(_)));
1386        assert!(matches!(TxEnvelope::from(eip7702_tx), TxEnvelope::Eip7702(_)));
1387    }
1388
1389    #[test]
1390    fn test_tx_type_is_methods() {
1391        let legacy_tx = TxEnvelope::Legacy(Signed::new_unchecked(
1392            TxLegacy::default(),
1393            Signature::test_signature(),
1394            Default::default(),
1395        ));
1396        let eip2930_tx = TxEnvelope::Eip2930(Signed::new_unchecked(
1397            TxEip2930::default(),
1398            Signature::test_signature(),
1399            Default::default(),
1400        ));
1401        let eip1559_tx = TxEnvelope::Eip1559(Signed::new_unchecked(
1402            TxEip1559::default(),
1403            Signature::test_signature(),
1404            Default::default(),
1405        ));
1406        let eip4844_tx = TxEnvelope::Eip4844(Signed::new_unchecked(
1407            TxEip4844Variant::TxEip4844(TxEip4844::default()),
1408            Signature::test_signature(),
1409            Default::default(),
1410        ));
1411        let eip7702_tx = TxEnvelope::Eip7702(Signed::new_unchecked(
1412            TxEip7702::default(),
1413            Signature::test_signature(),
1414            Default::default(),
1415        ));
1416
1417        assert!(legacy_tx.is_legacy());
1418        assert!(!legacy_tx.is_eip2930());
1419        assert!(!legacy_tx.is_eip1559());
1420        assert!(!legacy_tx.is_eip4844());
1421        assert!(!legacy_tx.is_eip7702());
1422
1423        assert!(eip2930_tx.is_eip2930());
1424        assert!(!eip2930_tx.is_legacy());
1425        assert!(!eip2930_tx.is_eip1559());
1426        assert!(!eip2930_tx.is_eip4844());
1427        assert!(!eip2930_tx.is_eip7702());
1428
1429        assert!(eip1559_tx.is_eip1559());
1430        assert!(!eip1559_tx.is_legacy());
1431        assert!(!eip1559_tx.is_eip2930());
1432        assert!(!eip1559_tx.is_eip4844());
1433        assert!(!eip1559_tx.is_eip7702());
1434
1435        assert!(eip4844_tx.is_eip4844());
1436        assert!(!eip4844_tx.is_legacy());
1437        assert!(!eip4844_tx.is_eip2930());
1438        assert!(!eip4844_tx.is_eip1559());
1439        assert!(!eip4844_tx.is_eip7702());
1440
1441        assert!(eip7702_tx.is_eip7702());
1442        assert!(!eip7702_tx.is_legacy());
1443        assert!(!eip7702_tx.is_eip2930());
1444        assert!(!eip7702_tx.is_eip1559());
1445        assert!(!eip7702_tx.is_eip4844());
1446    }
1447
1448    #[test]
1449    fn test_tx_type() {
1450        let legacy_tx = TxEnvelope::Legacy(Signed::new_unchecked(
1451            TxLegacy::default(),
1452            Signature::test_signature(),
1453            Default::default(),
1454        ));
1455        let eip2930_tx = TxEnvelope::Eip2930(Signed::new_unchecked(
1456            TxEip2930::default(),
1457            Signature::test_signature(),
1458            Default::default(),
1459        ));
1460        let eip1559_tx = TxEnvelope::Eip1559(Signed::new_unchecked(
1461            TxEip1559::default(),
1462            Signature::test_signature(),
1463            Default::default(),
1464        ));
1465        let eip4844_tx = TxEnvelope::Eip4844(Signed::new_unchecked(
1466            TxEip4844Variant::TxEip4844(TxEip4844::default()),
1467            Signature::test_signature(),
1468            Default::default(),
1469        ));
1470        let eip7702_tx = TxEnvelope::Eip7702(Signed::new_unchecked(
1471            TxEip7702::default(),
1472            Signature::test_signature(),
1473            Default::default(),
1474        ));
1475
1476        assert_eq!(legacy_tx.tx_type(), TxType::Legacy);
1477        assert_eq!(eip2930_tx.tx_type(), TxType::Eip2930);
1478        assert_eq!(eip1559_tx.tx_type(), TxType::Eip1559);
1479        assert_eq!(eip4844_tx.tx_type(), TxType::Eip4844);
1480        assert_eq!(eip7702_tx.tx_type(), TxType::Eip7702);
1481    }
1482
1483    #[test]
1484    fn test_try_into_legacy_success() {
1485        let legacy_tx = TxEnvelope::Legacy(Signed::new_unchecked(
1486            TxLegacy::default(),
1487            Signature::test_signature(),
1488            Default::default(),
1489        ));
1490
1491        let result = legacy_tx.try_into_legacy();
1492        assert!(result.is_ok());
1493    }
1494
1495    #[test]
1496    fn test_try_into_legacy_failure() {
1497        let eip1559_tx = TxEnvelope::Eip1559(Signed::new_unchecked(
1498            TxEip1559::default(),
1499            Signature::test_signature(),
1500            Default::default(),
1501        ));
1502
1503        let result = eip1559_tx.try_into_legacy();
1504        assert!(result.is_err());
1505        let error = result.unwrap_err();
1506        assert!(error.to_string().contains("Expected legacy transaction"));
1507        // Test that we can recover the original envelope
1508        let recovered_envelope = error.into_value();
1509        assert!(recovered_envelope.is_eip1559());
1510    }
1511
1512    #[test]
1513    fn test_try_into_eip2930_success() {
1514        let eip2930_tx = TxEnvelope::Eip2930(Signed::new_unchecked(
1515            TxEip2930::default(),
1516            Signature::test_signature(),
1517            Default::default(),
1518        ));
1519
1520        let result = eip2930_tx.try_into_eip2930();
1521        assert!(result.is_ok());
1522    }
1523
1524    #[test]
1525    fn test_try_into_eip2930_failure() {
1526        let legacy_tx = TxEnvelope::Legacy(Signed::new_unchecked(
1527            TxLegacy::default(),
1528            Signature::test_signature(),
1529            Default::default(),
1530        ));
1531
1532        let result = legacy_tx.try_into_eip2930();
1533        assert!(result.is_err());
1534        let error = result.unwrap_err();
1535        assert!(error.to_string().contains("Expected EIP-2930 transaction"));
1536        let recovered_envelope = error.into_value();
1537        assert!(recovered_envelope.is_legacy());
1538    }
1539
1540    #[test]
1541    fn test_try_into_eip1559_success() {
1542        let eip1559_tx = TxEnvelope::Eip1559(Signed::new_unchecked(
1543            TxEip1559::default(),
1544            Signature::test_signature(),
1545            Default::default(),
1546        ));
1547
1548        let result = eip1559_tx.try_into_eip1559();
1549        assert!(result.is_ok());
1550    }
1551
1552    #[test]
1553    fn test_try_into_eip1559_failure() {
1554        let eip2930_tx = TxEnvelope::Eip2930(Signed::new_unchecked(
1555            TxEip2930::default(),
1556            Signature::test_signature(),
1557            Default::default(),
1558        ));
1559
1560        let result = eip2930_tx.try_into_eip1559();
1561        assert!(result.is_err());
1562        let error = result.unwrap_err();
1563        assert!(error.to_string().contains("Expected EIP-1559 transaction"));
1564        let recovered_envelope = error.into_value();
1565        assert!(recovered_envelope.is_eip2930());
1566    }
1567
1568    #[test]
1569    fn test_try_into_eip4844_success() {
1570        let eip4844_tx = TxEnvelope::Eip4844(Signed::new_unchecked(
1571            TxEip4844Variant::TxEip4844(TxEip4844::default()),
1572            Signature::test_signature(),
1573            Default::default(),
1574        ));
1575
1576        let result = eip4844_tx.try_into_eip4844();
1577        assert!(result.is_ok());
1578    }
1579
1580    #[test]
1581    fn test_try_into_eip4844_failure() {
1582        let eip1559_tx = TxEnvelope::Eip1559(Signed::new_unchecked(
1583            TxEip1559::default(),
1584            Signature::test_signature(),
1585            Default::default(),
1586        ));
1587
1588        let result = eip1559_tx.try_into_eip4844();
1589        assert!(result.is_err());
1590        let error = result.unwrap_err();
1591        assert!(error.to_string().contains("Expected EIP-4844 transaction"));
1592        let recovered_envelope = error.into_value();
1593        assert!(recovered_envelope.is_eip1559());
1594    }
1595
1596    #[test]
1597    fn test_try_into_eip7702_success() {
1598        let eip7702_tx = TxEnvelope::Eip7702(Signed::new_unchecked(
1599            TxEip7702::default(),
1600            Signature::test_signature(),
1601            Default::default(),
1602        ));
1603
1604        let result = eip7702_tx.try_into_eip7702();
1605        assert!(result.is_ok());
1606    }
1607
1608    #[test]
1609    fn test_try_into_eip7702_failure() {
1610        let eip4844_tx = TxEnvelope::Eip4844(Signed::new_unchecked(
1611            TxEip4844Variant::TxEip4844(TxEip4844::default()),
1612            Signature::test_signature(),
1613            Default::default(),
1614        ));
1615
1616        let result = eip4844_tx.try_into_eip7702();
1617        assert!(result.is_err());
1618        let error = result.unwrap_err();
1619        assert!(error.to_string().contains("Expected EIP-7702 transaction"));
1620        let recovered_envelope = error.into_value();
1621        assert!(recovered_envelope.is_eip4844());
1622    }
1623
1624    // <https://sepolia.etherscan.io/getRawTx?tx=0xe5b458ba9de30b47cb7c0ea836bec7b072053123a7416c5082c97f959a4eebd6>
1625    #[test]
1626    fn decode_raw_legacy() {
1627        let raw = hex!("f8aa0285018ef61d0a832dc6c094cb33aa5b38d79e3d9fa8b10aff38aa201399a7e380b844af7b421018842e4628f3d9ee0e2c7679e29ed5dbaa75be75efecd392943503c9c68adce800000000000000000000000000000000000000000000000000000000000000641ca05e28679806caa50d25e9cb16aef8c0c08b235241b8f6e9d86faadf70421ba664a02353bba82ef2c7ce4dd6695942399163160000272b14f9aa6cbadf011b76efa4");
1628        let tx = TxEnvelope::decode_2718(&mut raw.as_ref()).unwrap();
1629        assert!(tx.chain_id().is_none());
1630    }
1631
1632    #[test]
1633    #[cfg(feature = "serde")]
1634    fn can_deserialize_system_transaction_with_zero_signature_envelope() {
1635        let raw_tx = r#"{
1636            "blockHash": "0x5307b5c812a067f8bc1ed1cc89d319ae6f9a0c9693848bd25c36b5191de60b85",
1637            "blockNumber": "0x45a59bb",
1638            "from": "0x0000000000000000000000000000000000000000",
1639            "gas": "0x1e8480",
1640            "gasPrice": "0x0",
1641            "hash": "0x16ef68aa8f35add3a03167a12b5d1268e344f6605a64ecc3f1c3aa68e98e4e06",
1642            "input": "0xcbd4ece900000000000000000000000032155c9d39084f040ba17890fe8134dbe2a0453f0000000000000000000000004a0126ee88018393b1ad2455060bc350ead9908a000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000469f700000000000000000000000000000000000000000000000000000000000000644ff746f60000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002043e908a4e862aebb10e7e27db0b892b58a7e32af11d64387a414dabc327b00e200000000000000000000000000000000000000000000000000000000",
1643            "nonce": "0x469f7",
1644            "to": "0x4200000000000000000000000000000000000007",
1645            "transactionIndex": "0x0",
1646            "value": "0x0",
1647            "v": "0x0",
1648            "r": "0x0",
1649            "s": "0x0",
1650            "queueOrigin": "l1",
1651            "l1TxOrigin": "0x36bde71c97b33cc4729cf772ae268934f7ab70b2",
1652            "l1BlockNumber": "0xfd1a6c",
1653            "l1Timestamp": "0x63e434ff",
1654            "index": "0x45a59ba",
1655            "queueIndex": "0x469f7",
1656            "rawTransaction": "0xcbd4ece900000000000000000000000032155c9d39084f040ba17890fe8134dbe2a0453f0000000000000000000000004a0126ee88018393b1ad2455060bc350ead9908a000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000469f700000000000000000000000000000000000000000000000000000000000000644ff746f60000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002043e908a4e862aebb10e7e27db0b892b58a7e32af11d64387a414dabc327b00e200000000000000000000000000000000000000000000000000000000"
1657        }"#;
1658
1659        let tx = serde_json::from_str::<TxEnvelope>(raw_tx).unwrap();
1660
1661        assert_eq!(tx.signature().r(), U256::ZERO);
1662        assert_eq!(tx.signature().s(), U256::ZERO);
1663        assert!(!tx.signature().v());
1664
1665        assert_eq!(
1666            tx.hash(),
1667            &b256!("0x16ef68aa8f35add3a03167a12b5d1268e344f6605a64ecc3f1c3aa68e98e4e06"),
1668            "hash should match the transaction hash"
1669        );
1670    }
1671
1672    // <https://github.com/succinctlabs/kona/issues/31>
1673    #[test]
1674    #[cfg(feature = "serde")]
1675    fn serde_block_tx() {
1676        let rpc_tx = r#"{
1677      "blockHash": "0xc0c3190292a82c2ee148774e37e5665f6a205f5ef0cd0885e84701d90ebd442e",
1678      "blockNumber": "0x6edcde",
1679      "transactionIndex": "0x7",
1680      "hash": "0x2cb125e083d6d2631e3752bd2b3d757bf31bf02bfe21de0ffa46fbb118d28b19",
1681      "from": "0x03e5badf3bb1ade1a8f33f94536c827b6531948d",
1682      "to": "0x3267e72dc8780a1512fa69da7759ec66f30350e3",
1683      "input": "0x62e4c545000000000000000000000000464c8ec100f2f42fb4e42e07e203da2324f9fc6700000000000000000000000003e5badf3bb1ade1a8f33f94536c827b6531948d000000000000000000000000a064bfb5c7e81426647dc20a0d854da1538559dc00000000000000000000000000000000000000000000000000c6f3b40b6c0000",
1684      "nonce": "0x2a8",
1685      "value": "0x0",
1686      "gas": "0x28afd",
1687      "gasPrice": "0x23ec5dbc2",
1688      "accessList": [],
1689      "chainId": "0xaa36a7",
1690      "type": "0x0",
1691      "v": "0x1546d71",
1692      "r": "0x809b9f0a1777e376cd1ee5d2f551035643755edf26ea65b7a00c822a24504962",
1693      "s": "0x6a57bb8e21fe85c7e092868ee976fef71edca974d8c452fcf303f9180c764f64"
1694    }"#;
1695
1696        let _ = serde_json::from_str::<TxEnvelope>(rpc_tx).unwrap();
1697    }
1698
1699    // <https://github.com/succinctlabs/kona/issues/31>
1700    #[test]
1701    #[cfg(feature = "serde")]
1702    fn serde_block_tx_legacy_chain_id() {
1703        let rpc_tx = r#"{
1704      "blockHash": "0xc0c3190292a82c2ee148774e37e5665f6a205f5ef0cd0885e84701d90ebd442e",
1705      "blockNumber": "0x6edcde",
1706      "transactionIndex": "0x8",
1707      "hash": "0xe5b458ba9de30b47cb7c0ea836bec7b072053123a7416c5082c97f959a4eebd6",
1708      "from": "0x8b87f0a788cc14b4f0f374da59920f5017ff05de",
1709      "to": "0xcb33aa5b38d79e3d9fa8b10aff38aa201399a7e3",
1710      "input": "0xaf7b421018842e4628f3d9ee0e2c7679e29ed5dbaa75be75efecd392943503c9c68adce80000000000000000000000000000000000000000000000000000000000000064",
1711      "nonce": "0x2",
1712      "value": "0x0",
1713      "gas": "0x2dc6c0",
1714      "gasPrice": "0x18ef61d0a",
1715      "accessList": [],
1716      "chainId": "0xaa36a7",
1717      "type": "0x0",
1718      "v": "0x1c",
1719      "r": "0x5e28679806caa50d25e9cb16aef8c0c08b235241b8f6e9d86faadf70421ba664",
1720      "s": "0x2353bba82ef2c7ce4dd6695942399163160000272b14f9aa6cbadf011b76efa4"
1721    }"#;
1722
1723        let _ = serde_json::from_str::<TxEnvelope>(rpc_tx).unwrap();
1724    }
1725}