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
150impl<T> EthereumTypedTransaction<TxEip4844Variant<T>> {
151    /// Strips the sidecar from EIP-4844 transactions and returns both the transaction and the
152    /// sidecar separately, keeping the same sidecar type parameter.
153    ///
154    /// This method consumes the typed transaction and returns:
155    /// - An [`EthereumTypedTransaction<TxEip4844Variant<T>>`] with the sidecar stripped from
156    ///   EIP-4844 transactions
157    /// - An [`Option<T>`] containing the sidecar if this was an EIP-4844 transaction with a sidecar
158    ///
159    /// For non-EIP-4844 transactions, this returns the transaction unchanged with `None` for the
160    /// sidecar.
161    ///
162    /// This is a convenience wrapper around
163    /// [`strip_eip4844_sidecar_into`](Self::strip_eip4844_sidecar_into) that keeps the same type
164    /// parameter.
165    ///
166    /// # Examples
167    ///
168    /// ```
169    /// # use alloy_consensus::{EthereumTypedTransaction, TxEip4844Variant};
170    /// # use alloy_eips::eip4844::BlobTransactionSidecar;
171    /// # fn example(tx: EthereumTypedTransaction<TxEip4844Variant<BlobTransactionSidecar>>) {
172    /// // Strip the sidecar from the transaction (type parameter stays the same)
173    /// let (tx_without_sidecar, maybe_sidecar) = tx.strip_eip4844_sidecar();
174    ///
175    /// if let Some(sidecar) = maybe_sidecar {
176    ///     // Process the blob sidecar separately
177    ///     println!("Transaction had {} blobs", sidecar.blobs.len());
178    /// }
179    /// # }
180    /// ```
181    pub fn strip_eip4844_sidecar(self) -> (Self, Option<T>) {
182        self.strip_eip4844_sidecar_into()
183    }
184
185    /// Strips the sidecar from EIP-4844 transactions and returns both the transaction and the
186    /// sidecar separately, converting to a different sidecar type parameter.
187    ///
188    /// This method consumes the typed transaction and returns:
189    /// - An [`EthereumTypedTransaction<TxEip4844Variant<U>>`] with the sidecar stripped from
190    ///   EIP-4844 transactions
191    /// - An [`Option<T>`] containing the sidecar if this was an EIP-4844 transaction with a sidecar
192    ///
193    /// For non-EIP-4844 transactions, this simply converts the type parameter and returns `None`
194    /// for the sidecar.
195    ///
196    /// This is useful when you need to:
197    /// - Extract blob data from pooled transactions for separate processing
198    /// - Convert between different sidecar type parameters
199    /// - Prepare transactions for storage (without sidecars)
200    ///
201    /// # Examples
202    ///
203    /// ```
204    /// # use alloy_consensus::{EthereumTypedTransaction, TxEip4844Variant};
205    /// # use alloy_eips::eip4844::BlobTransactionSidecar;
206    /// # use alloy_eips::eip7594::BlobTransactionSidecarVariant;
207    /// # fn example(tx: EthereumTypedTransaction<TxEip4844Variant<BlobTransactionSidecar>>) {
208    /// // Strip the sidecar and convert to a different type parameter
209    /// let (tx_without_sidecar, maybe_sidecar): (
210    ///     EthereumTypedTransaction<TxEip4844Variant<BlobTransactionSidecarVariant>>,
211    ///     _,
212    /// ) = tx.strip_eip4844_sidecar_into();
213    ///
214    /// if let Some(sidecar) = maybe_sidecar {
215    ///     // Process the blob sidecar separately
216    ///     println!("Transaction had {} blobs", sidecar.blobs.len());
217    /// }
218    /// # }
219    /// ```
220    pub fn strip_eip4844_sidecar_into<U>(
221        self,
222    ) -> (EthereumTypedTransaction<TxEip4844Variant<U>>, Option<T>) {
223        match self {
224            Self::Legacy(tx) => (EthereumTypedTransaction::Legacy(tx), None),
225            Self::Eip2930(tx) => (EthereumTypedTransaction::Eip2930(tx), None),
226            Self::Eip1559(tx) => (EthereumTypedTransaction::Eip1559(tx), None),
227            Self::Eip4844(tx) => {
228                let (tx_variant, sidecar) = tx.strip_sidecar_into();
229                (EthereumTypedTransaction::Eip4844(tx_variant), sidecar)
230            }
231            Self::Eip7702(tx) => (EthereumTypedTransaction::Eip7702(tx), None),
232        }
233    }
234
235    /// Drops the sidecar from EIP-4844 transactions and returns only the transaction, keeping the
236    /// same sidecar type parameter.
237    ///
238    /// This is a convenience method that discards the sidecar from EIP-4844 transactions,
239    /// returning only the transaction without a sidecar.
240    ///
241    /// This is equivalent to calling [`strip_eip4844_sidecar`](Self::strip_eip4844_sidecar) and
242    /// taking only the first element of the tuple.
243    ///
244    /// # Examples
245    ///
246    /// ```
247    /// # use alloy_consensus::{EthereumTypedTransaction, TxEip4844Variant};
248    /// # use alloy_eips::eip4844::BlobTransactionSidecar;
249    /// # fn example(tx: EthereumTypedTransaction<TxEip4844Variant<BlobTransactionSidecar>>) {
250    /// // Drop the sidecar, keeping only the transaction
251    /// let tx_without_sidecar = tx.drop_eip4844_sidecar();
252    /// # }
253    /// ```
254    pub fn drop_eip4844_sidecar(self) -> Self {
255        self.strip_eip4844_sidecar().0
256    }
257
258    /// Drops the sidecar from EIP-4844 transactions and returns only the transaction, converting
259    /// to a different sidecar type parameter.
260    ///
261    /// This is a convenience method that discards the sidecar from EIP-4844 transactions,
262    /// returning only the transaction without a sidecar.
263    ///
264    /// This is equivalent to calling
265    /// [`strip_eip4844_sidecar_into`](Self::strip_eip4844_sidecar_into) and taking only the first
266    /// element of the tuple.
267    ///
268    /// # Examples
269    ///
270    /// ```
271    /// # use alloy_consensus::{EthereumTypedTransaction, TxEip4844Variant};
272    /// # use alloy_eips::eip4844::BlobTransactionSidecar;
273    /// # use alloy_eips::eip7594::BlobTransactionSidecarVariant;
274    /// # fn example(tx: EthereumTypedTransaction<TxEip4844Variant<BlobTransactionSidecar>>) {
275    /// // Drop the sidecar and convert to a different type parameter
276    /// let tx_without_sidecar: EthereumTypedTransaction<
277    ///     TxEip4844Variant<BlobTransactionSidecarVariant>,
278    /// > = tx.drop_eip4844_sidecar_into();
279    /// # }
280    /// ```
281    pub fn drop_eip4844_sidecar_into<U>(self) -> EthereumTypedTransaction<TxEip4844Variant<U>> {
282        self.strip_eip4844_sidecar_into().0
283    }
284}
285
286#[cfg(feature = "kzg")]
287impl EthereumTxEnvelope<TxEip4844WithSidecar<alloy_eips::eip4844::BlobTransactionSidecar>> {
288    /// Converts the envelope to EIP-7594 format using default KZG settings.
289    ///
290    /// For EIP-4844 transactions, this computes cell KZG proofs and converts the sidecar to
291    /// EIP-7594 format. Non-EIP-4844 transactions are converted to the appropriate envelope type
292    /// without modification.
293    ///
294    /// # Returns
295    ///
296    /// - `Ok(EthereumTxEnvelope<TxEip4844WithSidecar<alloy_eips::eip7594::BlobTransactionSidecarEip7594>>)` - The
297    ///   envelope with EIP-7594 sidecars
298    /// - `Err(c_kzg::Error)` - If KZG proof computation fails
299    ///
300    /// # Examples
301    ///
302    /// ```no_run
303    /// # use alloy_consensus::EthereumTxEnvelope;
304    /// # use alloy_consensus::TxEip4844WithSidecar;
305    /// # use alloy_eips::eip4844::BlobTransactionSidecar;
306    /// # fn example(envelope: EthereumTxEnvelope<TxEip4844WithSidecar<BlobTransactionSidecar>>) -> Result<(), c_kzg::Error> {
307    /// // Convert to EIP-7594 format
308    /// let eip7594_envelope = envelope.try_into_7594()?;
309    /// # Ok(())
310    /// # }
311    /// ```
312    pub fn try_into_7594(
313        self,
314    ) -> Result<
315        EthereumTxEnvelope<
316            TxEip4844WithSidecar<alloy_eips::eip7594::BlobTransactionSidecarEip7594>,
317        >,
318        c_kzg::Error,
319    > {
320        self.try_into_7594_with_settings(
321            alloy_eips::eip4844::env_settings::EnvKzgSettings::Default.get(),
322        )
323    }
324
325    /// Converts the envelope to EIP-7594 format using custom KZG settings.
326    ///
327    /// For EIP-4844 transactions, this computes cell KZG proofs and converts the sidecar to
328    /// EIP-7594 format using the provided KZG settings. Non-EIP-4844 transactions are converted
329    /// to the appropriate envelope type without modification.
330    ///
331    /// # Arguments
332    ///
333    /// * `settings` - The KZG settings to use for computing cell proofs
334    ///
335    /// # Returns
336    ///
337    /// - `Ok(EthereumTxEnvelope<TxEip4844WithSidecar<alloy_eips::eip7594::BlobTransactionSidecarEip7594>>)` - The
338    ///   envelope with EIP-7594 sidecars
339    /// - `Err(c_kzg::Error)` - If KZG proof computation fails
340    ///
341    /// # Examples
342    ///
343    /// ```no_run
344    /// # use alloy_consensus::EthereumTxEnvelope;
345    /// # use alloy_consensus::TxEip4844WithSidecar;
346    /// # use alloy_eips::eip4844::BlobTransactionSidecar;
347    /// # use alloy_eips::eip4844::env_settings::EnvKzgSettings;
348    /// # fn example(envelope: EthereumTxEnvelope<TxEip4844WithSidecar<BlobTransactionSidecar>>) -> Result<(), c_kzg::Error> {
349    /// // Load custom KZG settings
350    /// let kzg_settings = EnvKzgSettings::Default.get();
351    ///
352    /// // Convert using custom settings
353    /// let eip7594_envelope = envelope.try_into_7594_with_settings(kzg_settings)?;
354    /// # Ok(())
355    /// # }
356    /// ```
357    pub fn try_into_7594_with_settings(
358        self,
359        settings: &c_kzg::KzgSettings,
360    ) -> Result<
361        EthereumTxEnvelope<
362            TxEip4844WithSidecar<alloy_eips::eip7594::BlobTransactionSidecarEip7594>,
363        >,
364        c_kzg::Error,
365    > {
366        self.try_map_eip4844(|tx| tx.try_into_7594_with_settings(settings))
367    }
368}
369
370#[cfg(feature = "kzg")]
371impl EthereumTxEnvelope<TxEip4844Variant<alloy_eips::eip4844::BlobTransactionSidecar>> {
372    /// Converts the envelope to EIP-7594 format using default KZG settings.
373    ///
374    /// For EIP-4844 transactions with sidecars, this computes cell KZG proofs and converts the
375    /// sidecar to EIP-7594 format. Transactions without sidecars and non-EIP-4844 transactions
376    /// are converted to the appropriate envelope type without modification.
377    ///
378    /// # Returns
379    ///
380    /// - `Ok(EthereumTxEnvelope<TxEip4844Variant<alloy_eips::eip7594::BlobTransactionSidecarEip7594>>)` - The envelope
381    ///   with EIP-7594 sidecars
382    /// - `Err(c_kzg::Error)` - If KZG proof computation fails
383    ///
384    /// # Examples
385    ///
386    /// ```no_run
387    /// # use alloy_consensus::EthereumTxEnvelope;
388    /// # use alloy_consensus::TxEip4844Variant;
389    /// # use alloy_eips::eip4844::BlobTransactionSidecar;
390    /// # fn example(envelope: EthereumTxEnvelope<TxEip4844Variant<BlobTransactionSidecar>>) -> Result<(), c_kzg::Error> {
391    /// // Convert to EIP-7594 format
392    /// let eip7594_envelope = envelope.try_into_7594()?;
393    /// # Ok(())
394    /// # }
395    /// ```
396    pub fn try_into_7594(
397        self,
398    ) -> Result<
399        EthereumTxEnvelope<TxEip4844Variant<alloy_eips::eip7594::BlobTransactionSidecarEip7594>>,
400        c_kzg::Error,
401    > {
402        self.try_into_7594_with_settings(
403            alloy_eips::eip4844::env_settings::EnvKzgSettings::Default.get(),
404        )
405    }
406
407    /// Converts the envelope to EIP-7594 format using custom KZG settings.
408    ///
409    /// For EIP-4844 transactions with sidecars, this computes cell KZG proofs and converts the
410    /// sidecar to EIP-7594 format using the provided KZG settings. Transactions without sidecars
411    /// and non-EIP-4844 transactions are converted to the appropriate envelope type without
412    /// modification.
413    ///
414    /// # Arguments
415    ///
416    /// * `settings` - The KZG settings to use for computing cell proofs
417    ///
418    /// # Returns
419    ///
420    /// - `Ok(EthereumTxEnvelope<TxEip4844Variant<alloy_eips::eip7594::BlobTransactionSidecarEip7594>>)` - The envelope
421    ///   with EIP-7594 sidecars
422    /// - `Err(c_kzg::Error)` - If KZG proof computation fails
423    ///
424    /// # Examples
425    ///
426    /// ```no_run
427    /// # use alloy_consensus::EthereumTxEnvelope;
428    /// # use alloy_consensus::TxEip4844Variant;
429    /// # use alloy_eips::eip4844::BlobTransactionSidecar;
430    /// # use alloy_eips::eip4844::env_settings::EnvKzgSettings;
431    /// # fn example(envelope: EthereumTxEnvelope<TxEip4844Variant<BlobTransactionSidecar>>) -> Result<(), c_kzg::Error> {
432    /// // Load custom KZG settings
433    /// let kzg_settings = EnvKzgSettings::Default.get();
434    ///
435    /// // Convert using custom settings
436    /// let eip7594_envelope = envelope.try_into_7594_with_settings(kzg_settings)?;
437    /// # Ok(())
438    /// # }
439    /// ```
440    pub fn try_into_7594_with_settings(
441        self,
442        settings: &c_kzg::KzgSettings,
443    ) -> Result<
444        EthereumTxEnvelope<TxEip4844Variant<alloy_eips::eip7594::BlobTransactionSidecarEip7594>>,
445        c_kzg::Error,
446    > {
447        self.try_map_eip4844(|tx| tx.try_into_7594_with_settings(settings))
448    }
449}
450
451#[cfg(feature = "kzg")]
452impl TryFrom<EthereumTxEnvelope<TxEip4844WithSidecar<alloy_eips::eip4844::BlobTransactionSidecar>>>
453    for EthereumTxEnvelope<TxEip4844WithSidecar<alloy_eips::eip7594::BlobTransactionSidecarEip7594>>
454{
455    type Error = c_kzg::Error;
456
457    fn try_from(
458        value: EthereumTxEnvelope<
459            TxEip4844WithSidecar<alloy_eips::eip4844::BlobTransactionSidecar>,
460        >,
461    ) -> Result<Self, Self::Error> {
462        value.try_into_7594()
463    }
464}
465
466#[cfg(feature = "kzg")]
467impl TryFrom<EthereumTxEnvelope<TxEip4844Variant<alloy_eips::eip4844::BlobTransactionSidecar>>>
468    for EthereumTxEnvelope<TxEip4844Variant<alloy_eips::eip7594::BlobTransactionSidecarEip7594>>
469{
470    type Error = c_kzg::Error;
471
472    fn try_from(
473        value: EthereumTxEnvelope<TxEip4844Variant<alloy_eips::eip4844::BlobTransactionSidecar>>,
474    ) -> Result<Self, Self::Error> {
475        value.try_into_7594()
476    }
477}
478
479/// The Ethereum [EIP-2718] Transaction Envelope.
480///
481/// # Note:
482///
483/// This enum distinguishes between tagged and untagged legacy transactions, as
484/// the in-protocol merkle tree may commit to EITHER 0-prefixed or raw.
485/// Therefore we must ensure that encoding returns the precise byte-array that
486/// was decoded, preserving the presence or absence of the `TransactionType`
487/// flag.
488///
489/// [EIP-2718]: https://eips.ethereum.org/EIPS/eip-2718
490#[derive(Clone, Debug, TransactionEnvelope)]
491#[envelope(
492    alloy_consensus = crate,
493    tx_type_name = TxType,
494    typed = EthereumTypedTransaction,
495    arbitrary_cfg(feature = "arbitrary")
496)]
497#[doc(alias = "TransactionEnvelope")]
498pub enum EthereumTxEnvelope<Eip4844> {
499    /// An untagged [`TxLegacy`].
500    #[envelope(ty = 0)]
501    Legacy(Signed<TxLegacy>),
502    /// A [`TxEip2930`] tagged with type 1.
503    #[envelope(ty = 1)]
504    Eip2930(Signed<TxEip2930>),
505    /// A [`TxEip1559`] tagged with type 2.
506    #[envelope(ty = 2)]
507    Eip1559(Signed<TxEip1559>),
508    /// A TxEip4844 tagged with type 3.
509    /// An EIP-4844 transaction has two network representations:
510    /// 1 - The transaction itself, which is a regular RLP-encoded transaction and used to retrieve
511    /// historical transactions..
512    ///
513    /// 2 - The transaction with a sidecar, which is the form used to
514    /// send transactions to the network.
515    #[envelope(ty = 3)]
516    Eip4844(Signed<Eip4844>),
517    /// A [`TxEip7702`] tagged with type 4.
518    #[envelope(ty = 4)]
519    Eip7702(Signed<TxEip7702>),
520}
521
522impl<T, Eip4844> From<Signed<T>> for EthereumTxEnvelope<Eip4844>
523where
524    EthereumTypedTransaction<Eip4844>: From<T>,
525    T: RlpEcdsaEncodableTx,
526{
527    fn from(v: Signed<T>) -> Self {
528        let (tx, sig, hash) = v.into_parts();
529        let typed = EthereumTypedTransaction::from(tx);
530        match typed {
531            EthereumTypedTransaction::Legacy(tx_legacy) => {
532                let tx = Signed::new_unchecked(tx_legacy, sig, hash);
533                Self::Legacy(tx)
534            }
535            EthereumTypedTransaction::Eip2930(tx_eip2930) => {
536                let tx = Signed::new_unchecked(tx_eip2930, sig, hash);
537                Self::Eip2930(tx)
538            }
539            EthereumTypedTransaction::Eip1559(tx_eip1559) => {
540                let tx = Signed::new_unchecked(tx_eip1559, sig, hash);
541                Self::Eip1559(tx)
542            }
543            EthereumTypedTransaction::Eip4844(tx_eip4844_variant) => {
544                let tx = Signed::new_unchecked(tx_eip4844_variant, sig, hash);
545                Self::Eip4844(tx)
546            }
547            EthereumTypedTransaction::Eip7702(tx_eip7702) => {
548                let tx = Signed::new_unchecked(tx_eip7702, sig, hash);
549                Self::Eip7702(tx)
550            }
551        }
552    }
553}
554
555impl<Eip4844: RlpEcdsaEncodableTx> From<EthereumTxEnvelope<Eip4844>>
556    for Signed<EthereumTypedTransaction<Eip4844>>
557where
558    EthereumTypedTransaction<Eip4844>: From<Eip4844>,
559{
560    fn from(value: EthereumTxEnvelope<Eip4844>) -> Self {
561        value.into_signed()
562    }
563}
564
565impl<Eip4844> From<(EthereumTypedTransaction<Eip4844>, Signature)> for EthereumTxEnvelope<Eip4844>
566where
567    Eip4844: RlpEcdsaEncodableTx + SignableTransaction<Signature>,
568{
569    fn from(value: (EthereumTypedTransaction<Eip4844>, Signature)) -> Self {
570        value.0.into_signed(value.1).into()
571    }
572}
573
574impl<T> From<EthereumTxEnvelope<TxEip4844WithSidecar<T>>> for EthereumTxEnvelope<TxEip4844> {
575    fn from(value: EthereumTxEnvelope<TxEip4844WithSidecar<T>>) -> Self {
576        value.map_eip4844(|eip4844| eip4844.into())
577    }
578}
579
580impl<T> From<EthereumTxEnvelope<TxEip4844Variant<T>>> for EthereumTxEnvelope<TxEip4844> {
581    fn from(value: EthereumTxEnvelope<TxEip4844Variant<T>>) -> Self {
582        value.map_eip4844(|eip4844| eip4844.into())
583    }
584}
585
586impl<T> From<EthereumTxEnvelope<TxEip4844>> for EthereumTxEnvelope<TxEip4844Variant<T>> {
587    fn from(value: EthereumTxEnvelope<TxEip4844>) -> Self {
588        value.map_eip4844(|eip4844| eip4844.into())
589    }
590}
591
592impl<Eip4844> EthereumTxEnvelope<Eip4844> {
593    /// Converts the EIP-4844 variant of this transaction with the given closure.
594    ///
595    /// This is intended to convert between the EIP-4844 variants, specifically for stripping away
596    /// non consensus data (blob sidecar data).
597    pub fn map_eip4844<U>(self, f: impl FnMut(Eip4844) -> U) -> EthereumTxEnvelope<U> {
598        match self {
599            Self::Legacy(tx) => EthereumTxEnvelope::Legacy(tx),
600            Self::Eip2930(tx) => EthereumTxEnvelope::Eip2930(tx),
601            Self::Eip1559(tx) => EthereumTxEnvelope::Eip1559(tx),
602            Self::Eip4844(tx) => EthereumTxEnvelope::Eip4844(tx.map(f)),
603            Self::Eip7702(tx) => EthereumTxEnvelope::Eip7702(tx),
604        }
605    }
606
607    /// Converts the EIP-4844 variant of this transaction with the given closure, returning an error
608    /// if the mapping fails.
609    pub fn try_map_eip4844<U, E>(
610        self,
611        f: impl FnOnce(Eip4844) -> Result<U, E>,
612    ) -> Result<EthereumTxEnvelope<U>, E> {
613        match self {
614            Self::Legacy(tx) => Ok(EthereumTxEnvelope::Legacy(tx)),
615            Self::Eip2930(tx) => Ok(EthereumTxEnvelope::Eip2930(tx)),
616            Self::Eip1559(tx) => Ok(EthereumTxEnvelope::Eip1559(tx)),
617            Self::Eip4844(tx) => tx.try_map(f).map(EthereumTxEnvelope::Eip4844),
618            Self::Eip7702(tx) => Ok(EthereumTxEnvelope::Eip7702(tx)),
619        }
620    }
621
622    /// Return the [`TxType`] of the inner txn.
623    #[doc(alias = "transaction_type")]
624    pub const fn tx_type(&self) -> TxType {
625        match self {
626            Self::Legacy(_) => TxType::Legacy,
627            Self::Eip2930(_) => TxType::Eip2930,
628            Self::Eip1559(_) => TxType::Eip1559,
629            Self::Eip4844(_) => TxType::Eip4844,
630            Self::Eip7702(_) => TxType::Eip7702,
631        }
632    }
633
634    /// Consumes the type into a [`Signed`]
635    pub fn into_signed(self) -> Signed<EthereumTypedTransaction<Eip4844>>
636    where
637        EthereumTypedTransaction<Eip4844>: From<Eip4844>,
638    {
639        match self {
640            Self::Legacy(tx) => tx.convert(),
641            Self::Eip2930(tx) => tx.convert(),
642            Self::Eip1559(tx) => tx.convert(),
643            Self::Eip4844(tx) => tx.convert(),
644            Self::Eip7702(tx) => tx.convert(),
645        }
646    }
647}
648
649impl<Eip4844: RlpEcdsaEncodableTx> EthereumTxEnvelope<Eip4844> {
650    /// Returns true if the transaction is a legacy transaction.
651    #[inline]
652    pub const fn is_legacy(&self) -> bool {
653        matches!(self, Self::Legacy(_))
654    }
655
656    /// Returns true if the transaction is an EIP-2930 transaction.
657    #[inline]
658    pub const fn is_eip2930(&self) -> bool {
659        matches!(self, Self::Eip2930(_))
660    }
661
662    /// Returns true if the transaction is an EIP-1559 transaction.
663    #[inline]
664    pub const fn is_eip1559(&self) -> bool {
665        matches!(self, Self::Eip1559(_))
666    }
667
668    /// Returns true if the transaction is an EIP-4844 transaction.
669    #[inline]
670    pub const fn is_eip4844(&self) -> bool {
671        matches!(self, Self::Eip4844(_))
672    }
673
674    /// Returns true if the transaction is an EIP-7702 transaction.
675    #[inline]
676    pub const fn is_eip7702(&self) -> bool {
677        matches!(self, Self::Eip7702(_))
678    }
679
680    /// Returns true if the transaction is replay protected.
681    ///
682    /// All non-legacy transactions are replay protected, as the chain id is
683    /// included in the transaction body. Legacy transactions are considered
684    /// replay protected if the `v` value is not 27 or 28, according to the
685    /// rules of [EIP-155].
686    ///
687    /// [EIP-155]: https://eips.ethereum.org/EIPS/eip-155
688    #[inline]
689    pub const fn is_replay_protected(&self) -> bool {
690        match self {
691            Self::Legacy(tx) => tx.tx().chain_id.is_some(),
692            _ => true,
693        }
694    }
695
696    /// Returns the [`TxLegacy`] variant if the transaction is a legacy transaction.
697    pub const fn as_legacy(&self) -> Option<&Signed<TxLegacy>> {
698        match self {
699            Self::Legacy(tx) => Some(tx),
700            _ => None,
701        }
702    }
703
704    /// Returns the [`TxEip2930`] variant if the transaction is an EIP-2930 transaction.
705    pub const fn as_eip2930(&self) -> Option<&Signed<TxEip2930>> {
706        match self {
707            Self::Eip2930(tx) => Some(tx),
708            _ => None,
709        }
710    }
711
712    /// Returns the [`TxEip1559`] variant if the transaction is an EIP-1559 transaction.
713    pub const fn as_eip1559(&self) -> Option<&Signed<TxEip1559>> {
714        match self {
715            Self::Eip1559(tx) => Some(tx),
716            _ => None,
717        }
718    }
719
720    /// Returns the [`TxEip4844Variant`] variant if the transaction is an EIP-4844 transaction.
721    pub const fn as_eip4844(&self) -> Option<&Signed<Eip4844>> {
722        match self {
723            Self::Eip4844(tx) => Some(tx),
724            _ => None,
725        }
726    }
727
728    /// Returns the [`TxEip7702`] variant if the transaction is an EIP-7702 transaction.
729    pub const fn as_eip7702(&self) -> Option<&Signed<TxEip7702>> {
730        match self {
731            Self::Eip7702(tx) => Some(tx),
732            _ => None,
733        }
734    }
735
736    /// Consumes the type and returns the [`TxLegacy`] variant if the transaction is a legacy
737    /// transaction. Returns an error otherwise.
738    pub fn try_into_legacy(self) -> Result<Signed<TxLegacy>, ValueError<Self>> {
739        match self {
740            Self::Legacy(tx) => Ok(tx),
741            _ => Err(ValueError::new_static(self, "Expected legacy transaction")),
742        }
743    }
744
745    /// Consumes the type and returns the [`TxEip2930`] variant if the transaction is an EIP-2930
746    /// transaction. Returns an error otherwise.
747    pub fn try_into_eip2930(self) -> Result<Signed<TxEip2930>, ValueError<Self>> {
748        match self {
749            Self::Eip2930(tx) => Ok(tx),
750            _ => Err(ValueError::new_static(self, "Expected EIP-2930 transaction")),
751        }
752    }
753
754    /// Consumes the type and returns the [`TxEip1559`] variant if the transaction is an EIP-1559
755    /// transaction. Returns an error otherwise.    
756    pub fn try_into_eip1559(self) -> Result<Signed<TxEip1559>, ValueError<Self>> {
757        match self {
758            Self::Eip1559(tx) => Ok(tx),
759            _ => Err(ValueError::new_static(self, "Expected EIP-1559 transaction")),
760        }
761    }
762
763    /// Consumes the type and returns the [`TxEip4844`] variant if the transaction is an EIP-4844
764    /// transaction. Returns an error otherwise.
765    pub fn try_into_eip4844(self) -> Result<Signed<Eip4844>, ValueError<Self>> {
766        match self {
767            Self::Eip4844(tx) => Ok(tx),
768            _ => Err(ValueError::new_static(self, "Expected EIP-4844 transaction")),
769        }
770    }
771
772    /// Consumes the type and returns the [`TxEip7702`] variant if the transaction is an EIP-7702
773    /// transaction. Returns an error otherwise.
774    pub fn try_into_eip7702(self) -> Result<Signed<TxEip7702>, ValueError<Self>> {
775        match self {
776            Self::Eip7702(tx) => Ok(tx),
777            _ => Err(ValueError::new_static(self, "Expected EIP-7702 transaction")),
778        }
779    }
780
781    /// Calculate the signing hash for the transaction.
782    pub fn signature_hash(&self) -> B256
783    where
784        Eip4844: SignableTransaction<Signature>,
785    {
786        match self {
787            Self::Legacy(tx) => tx.signature_hash(),
788            Self::Eip2930(tx) => tx.signature_hash(),
789            Self::Eip1559(tx) => tx.signature_hash(),
790            Self::Eip4844(tx) => tx.signature_hash(),
791            Self::Eip7702(tx) => tx.signature_hash(),
792        }
793    }
794
795    /// Return the reference to signature.
796    pub const fn signature(&self) -> &Signature {
797        match self {
798            Self::Legacy(tx) => tx.signature(),
799            Self::Eip2930(tx) => tx.signature(),
800            Self::Eip1559(tx) => tx.signature(),
801            Self::Eip4844(tx) => tx.signature(),
802            Self::Eip7702(tx) => tx.signature(),
803        }
804    }
805
806    /// Return the hash of the inner Signed.
807    #[doc(alias = "transaction_hash")]
808    pub fn tx_hash(&self) -> &B256 {
809        match self {
810            Self::Legacy(tx) => tx.hash(),
811            Self::Eip2930(tx) => tx.hash(),
812            Self::Eip1559(tx) => tx.hash(),
813            Self::Eip4844(tx) => tx.hash(),
814            Self::Eip7702(tx) => tx.hash(),
815        }
816    }
817
818    /// Reference to transaction hash. Used to identify transaction.
819    pub fn hash(&self) -> &B256 {
820        match self {
821            Self::Legacy(tx) => tx.hash(),
822            Self::Eip2930(tx) => tx.hash(),
823            Self::Eip1559(tx) => tx.hash(),
824            Self::Eip7702(tx) => tx.hash(),
825            Self::Eip4844(tx) => tx.hash(),
826        }
827    }
828
829    /// Return the length of the inner txn, including type byte length
830    pub fn eip2718_encoded_length(&self) -> usize {
831        match self {
832            Self::Legacy(t) => t.eip2718_encoded_length(),
833            Self::Eip2930(t) => t.eip2718_encoded_length(),
834            Self::Eip1559(t) => t.eip2718_encoded_length(),
835            Self::Eip4844(t) => t.eip2718_encoded_length(),
836            Self::Eip7702(t) => t.eip2718_encoded_length(),
837        }
838    }
839}
840
841impl<Eip4844: RlpEcdsaEncodableTx> TxHashRef for EthereumTxEnvelope<Eip4844> {
842    fn tx_hash(&self) -> &B256 {
843        Self::tx_hash(self)
844    }
845}
846
847#[cfg(any(feature = "secp256k1", feature = "k256"))]
848impl<Eip4844> crate::transaction::SignerRecoverable for EthereumTxEnvelope<Eip4844>
849where
850    Eip4844: RlpEcdsaEncodableTx + SignableTransaction<Signature>,
851{
852    fn recover_signer(&self) -> Result<alloy_primitives::Address, crate::crypto::RecoveryError> {
853        match self {
854            Self::Legacy(tx) => crate::transaction::SignerRecoverable::recover_signer(tx),
855            Self::Eip2930(tx) => crate::transaction::SignerRecoverable::recover_signer(tx),
856            Self::Eip1559(tx) => crate::transaction::SignerRecoverable::recover_signer(tx),
857            Self::Eip4844(tx) => crate::transaction::SignerRecoverable::recover_signer(tx),
858            Self::Eip7702(tx) => crate::transaction::SignerRecoverable::recover_signer(tx),
859        }
860    }
861
862    fn recover_signer_unchecked(
863        &self,
864    ) -> Result<alloy_primitives::Address, crate::crypto::RecoveryError> {
865        match self {
866            Self::Legacy(tx) => crate::transaction::SignerRecoverable::recover_signer_unchecked(tx),
867            Self::Eip2930(tx) => {
868                crate::transaction::SignerRecoverable::recover_signer_unchecked(tx)
869            }
870            Self::Eip1559(tx) => {
871                crate::transaction::SignerRecoverable::recover_signer_unchecked(tx)
872            }
873            Self::Eip4844(tx) => {
874                crate::transaction::SignerRecoverable::recover_signer_unchecked(tx)
875            }
876            Self::Eip7702(tx) => {
877                crate::transaction::SignerRecoverable::recover_signer_unchecked(tx)
878            }
879        }
880    }
881
882    fn recover_unchecked_with_buf(
883        &self,
884        buf: &mut alloc::vec::Vec<u8>,
885    ) -> Result<alloy_primitives::Address, crate::crypto::RecoveryError> {
886        match self {
887            Self::Legacy(tx) => {
888                crate::transaction::SignerRecoverable::recover_unchecked_with_buf(tx, buf)
889            }
890            Self::Eip2930(tx) => {
891                crate::transaction::SignerRecoverable::recover_unchecked_with_buf(tx, buf)
892            }
893            Self::Eip1559(tx) => {
894                crate::transaction::SignerRecoverable::recover_unchecked_with_buf(tx, buf)
895            }
896            Self::Eip4844(tx) => {
897                crate::transaction::SignerRecoverable::recover_unchecked_with_buf(tx, buf)
898            }
899            Self::Eip7702(tx) => {
900                crate::transaction::SignerRecoverable::recover_unchecked_with_buf(tx, buf)
901            }
902        }
903    }
904}
905
906/// Bincode-compatible [`EthereumTxEnvelope`] serde implementation.
907#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
908pub mod serde_bincode_compat {
909    use crate::{EthereumTypedTransaction, Signed};
910    use alloc::borrow::Cow;
911    use alloy_primitives::Signature;
912    use serde::{Deserialize, Deserializer, Serialize, Serializer};
913    use serde_with::{DeserializeAs, SerializeAs};
914
915    /// Bincode-compatible [`super::EthereumTxEnvelope`] serde implementation.
916    ///
917    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
918    /// ```rust
919    /// use alloy_consensus::{serde_bincode_compat, EthereumTxEnvelope};
920    /// use serde::{de::DeserializeOwned, Deserialize, Serialize};
921    /// use serde_with::serde_as;
922    ///
923    /// #[serde_as]
924    /// #[derive(Serialize, Deserialize)]
925    /// struct Data<T: Serialize + DeserializeOwned + Clone + 'static> {
926    ///     #[serde_as(as = "serde_bincode_compat::EthereumTxEnvelope<'_, T>")]
927    ///     receipt: EthereumTxEnvelope<T>,
928    /// }
929    /// ```
930    #[derive(Debug, Serialize, Deserialize)]
931    pub struct EthereumTxEnvelope<'a, Eip4844: Clone = crate::transaction::TxEip4844> {
932        /// Transaction signature
933        signature: Signature,
934        /// bincode compatible transaction
935        transaction:
936            crate::serde_bincode_compat::transaction::EthereumTypedTransaction<'a, Eip4844>,
937    }
938
939    impl<'a, T: Clone> From<&'a super::EthereumTxEnvelope<T>> for EthereumTxEnvelope<'a, T> {
940        fn from(value: &'a super::EthereumTxEnvelope<T>) -> Self {
941            match value {
942                super::EthereumTxEnvelope::Legacy(tx) => Self {
943                    signature: *tx.signature(),
944                    transaction:
945                        crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Legacy(
946                            tx.tx().into(),
947                        ),
948                },
949                super::EthereumTxEnvelope::Eip2930(tx) => Self {
950                    signature: *tx.signature(),
951                    transaction:
952                        crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Eip2930(
953                            tx.tx().into(),
954                        ),
955                },
956                super::EthereumTxEnvelope::Eip1559(tx) => Self {
957                    signature: *tx.signature(),
958                    transaction:
959                        crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Eip1559(
960                            tx.tx().into(),
961                        ),
962                },
963                super::EthereumTxEnvelope::Eip4844(tx) => Self {
964                    signature: *tx.signature(),
965                    transaction:
966                        crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Eip4844(
967                            Cow::Borrowed(tx.tx()),
968                        ),
969                },
970                super::EthereumTxEnvelope::Eip7702(tx) => Self {
971                    signature: *tx.signature(),
972                    transaction:
973                        crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Eip7702(
974                            tx.tx().into(),
975                        ),
976                },
977            }
978        }
979    }
980
981    impl<'a, T: Clone> From<EthereumTxEnvelope<'a, T>> for super::EthereumTxEnvelope<T> {
982        fn from(value: EthereumTxEnvelope<'a, T>) -> Self {
983            let EthereumTxEnvelope { signature, transaction } = value;
984            let transaction: crate::transaction::typed::EthereumTypedTransaction<T> =
985                transaction.into();
986            match transaction {
987                EthereumTypedTransaction::Legacy(tx) => Signed::new_unhashed(tx, signature).into(),
988                EthereumTypedTransaction::Eip2930(tx) => Signed::new_unhashed(tx, signature).into(),
989                EthereumTypedTransaction::Eip1559(tx) => Signed::new_unhashed(tx, signature).into(),
990                EthereumTypedTransaction::Eip4844(tx) => {
991                    Self::Eip4844(Signed::new_unhashed(tx, signature))
992                }
993                EthereumTypedTransaction::Eip7702(tx) => Signed::new_unhashed(tx, signature).into(),
994            }
995        }
996    }
997
998    impl<T: Serialize + Clone> SerializeAs<super::EthereumTxEnvelope<T>> for EthereumTxEnvelope<'_, T> {
999        fn serialize_as<S>(
1000            source: &super::EthereumTxEnvelope<T>,
1001            serializer: S,
1002        ) -> Result<S::Ok, S::Error>
1003        where
1004            S: Serializer,
1005        {
1006            EthereumTxEnvelope::<'_, T>::from(source).serialize(serializer)
1007        }
1008    }
1009
1010    impl<'de, T: Deserialize<'de> + Clone> DeserializeAs<'de, super::EthereumTxEnvelope<T>>
1011        for EthereumTxEnvelope<'de, T>
1012    {
1013        fn deserialize_as<D>(deserializer: D) -> Result<super::EthereumTxEnvelope<T>, D::Error>
1014        where
1015            D: Deserializer<'de>,
1016        {
1017            EthereumTxEnvelope::<'_, T>::deserialize(deserializer).map(Into::into)
1018        }
1019    }
1020
1021    #[cfg(test)]
1022    mod tests {
1023        use super::super::{serde_bincode_compat, EthereumTxEnvelope};
1024        use crate::TxEip4844;
1025        use arbitrary::Arbitrary;
1026        use bincode::config;
1027        use rand::Rng;
1028        use serde::{Deserialize, Serialize};
1029        use serde_with::serde_as;
1030
1031        #[test]
1032        fn test_typed_tx_envelope_bincode_roundtrip() {
1033            #[serde_as]
1034            #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
1035            struct Data {
1036                #[serde_as(as = "serde_bincode_compat::EthereumTxEnvelope<'_>")]
1037                transaction: EthereumTxEnvelope<TxEip4844>,
1038            }
1039
1040            let mut bytes = [0u8; 1024];
1041            rand::thread_rng().fill(bytes.as_mut_slice());
1042            let data = Data {
1043                transaction: EthereumTxEnvelope::arbitrary(&mut arbitrary::Unstructured::new(
1044                    &bytes,
1045                ))
1046                .unwrap(),
1047            };
1048
1049            let encoded = bincode::serde::encode_to_vec(&data, config::legacy()).unwrap();
1050            let (decoded, _) =
1051                bincode::serde::decode_from_slice::<Data, _>(&encoded, config::legacy()).unwrap();
1052            assert_eq!(decoded, data);
1053        }
1054    }
1055}
1056
1057#[cfg(test)]
1058mod tests {
1059    use super::*;
1060    use crate::{
1061        transaction::{Recovered, SignableTransaction},
1062        Transaction, TxEip4844, TxEip4844WithSidecar,
1063    };
1064    use alloc::vec::Vec;
1065    use alloy_eips::{
1066        eip2930::{AccessList, AccessListItem},
1067        eip4844::BlobTransactionSidecar,
1068        eip7702::Authorization,
1069    };
1070    #[allow(unused_imports)]
1071    use alloy_primitives::{b256, Bytes, TxKind};
1072    use alloy_primitives::{hex, Address, Signature, U256};
1073    use alloy_rlp::Decodable;
1074    use std::{fs, path::PathBuf, str::FromStr, vec};
1075
1076    #[test]
1077    fn assert_encodable() {
1078        fn assert_encodable<T: Encodable2718>() {}
1079
1080        assert_encodable::<EthereumTxEnvelope<TxEip4844>>();
1081        assert_encodable::<Recovered<EthereumTxEnvelope<TxEip4844>>>();
1082        assert_encodable::<Recovered<EthereumTxEnvelope<TxEip4844Variant>>>();
1083    }
1084
1085    #[test]
1086    #[cfg(feature = "k256")]
1087    // Test vector from https://etherscan.io/tx/0xce4dc6d7a7549a98ee3b071b67e970879ff51b5b95d1c340bacd80fa1e1aab31
1088    fn test_decode_live_1559_tx() {
1089        use alloy_primitives::address;
1090
1091        let raw_tx = alloy_primitives::hex::decode("02f86f0102843b9aca0085029e7822d68298f094d9e1459a7a482635700cbc20bbaf52d495ab9c9680841b55ba3ac080a0c199674fcb29f353693dd779c017823b954b3c69dffa3cd6b2a6ff7888798039a028ca912de909e7e6cdef9cdcaf24c54dd8c1032946dfa1d85c206b32a9064fe8").unwrap();
1092        let res = TxEnvelope::decode(&mut raw_tx.as_slice()).unwrap();
1093
1094        assert_eq!(res.tx_type(), TxType::Eip1559);
1095
1096        let tx = match res {
1097            TxEnvelope::Eip1559(tx) => tx,
1098            _ => unreachable!(),
1099        };
1100
1101        assert_eq!(tx.tx().to, TxKind::Call(address!("D9e1459A7A482635700cBc20BBAF52D495Ab9C96")));
1102        let from = tx.recover_signer().unwrap();
1103        assert_eq!(from, address!("001e2b7dE757bA469a57bF6b23d982458a07eFcE"));
1104    }
1105
1106    #[test]
1107    fn test_is_replay_protected_v() {
1108        let sig = Signature::test_signature();
1109        assert!(!&TxEnvelope::Legacy(Signed::new_unchecked(
1110            TxLegacy::default(),
1111            sig,
1112            Default::default(),
1113        ))
1114        .is_replay_protected());
1115        let r = b256!("840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565");
1116        let s = b256!("25e7109ceb98168d95b09b18bbf6b685130e0562f233877d492b94eee0c5b6d1");
1117        let v = false;
1118        let valid_sig = Signature::from_scalars_and_parity(r, s, v);
1119        assert!(!&TxEnvelope::Legacy(Signed::new_unchecked(
1120            TxLegacy::default(),
1121            valid_sig,
1122            Default::default(),
1123        ))
1124        .is_replay_protected());
1125        assert!(&TxEnvelope::Eip2930(Signed::new_unchecked(
1126            TxEip2930::default(),
1127            sig,
1128            Default::default(),
1129        ))
1130        .is_replay_protected());
1131        assert!(&TxEnvelope::Eip1559(Signed::new_unchecked(
1132            TxEip1559::default(),
1133            sig,
1134            Default::default(),
1135        ))
1136        .is_replay_protected());
1137        assert!(&TxEnvelope::Eip4844(Signed::new_unchecked(
1138            TxEip4844Variant::TxEip4844(TxEip4844::default()),
1139            sig,
1140            Default::default(),
1141        ))
1142        .is_replay_protected());
1143        assert!(&TxEnvelope::Eip7702(Signed::new_unchecked(
1144            TxEip7702::default(),
1145            sig,
1146            Default::default(),
1147        ))
1148        .is_replay_protected());
1149    }
1150
1151    #[test]
1152    #[cfg(feature = "k256")]
1153    // Test vector from https://etherscan.io/tx/0x280cde7cdefe4b188750e76c888f13bd05ce9a4d7767730feefe8a0e50ca6fc4
1154    fn test_decode_live_legacy_tx() {
1155        use alloy_primitives::address;
1156
1157        let raw_tx = alloy_primitives::bytes!("f9015482078b8505d21dba0083022ef1947a250d5630b4cf539739df2c5dacb4c659f2488d880c46549a521b13d8b8e47ff36ab50000000000000000000000000000000000000000000066ab5a608bd00a23f2fe000000000000000000000000000000000000000000000000000000000000008000000000000000000000000048c04ed5691981c42154c6167398f95e8f38a7ff00000000000000000000000000000000000000000000000000000000632ceac70000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006c6ee5e31d828de241282b9606c8e98ea48526e225a0c9077369501641a92ef7399ff81c21639ed4fd8fc69cb793cfa1dbfab342e10aa0615facb2f1bcf3274a354cfe384a38d0cc008a11c2dd23a69111bc6930ba27a8");
1158        let res = TxEnvelope::decode_2718(&mut raw_tx.as_ref()).unwrap();
1159        assert_eq!(res.tx_type(), TxType::Legacy);
1160
1161        let tx = match res {
1162            TxEnvelope::Legacy(tx) => tx,
1163            _ => unreachable!(),
1164        };
1165
1166        assert_eq!(tx.tx().chain_id(), Some(1));
1167
1168        assert_eq!(tx.tx().to, TxKind::Call(address!("7a250d5630B4cF539739dF2C5dAcb4c659F2488D")));
1169        assert_eq!(
1170            tx.hash().to_string(),
1171            "0x280cde7cdefe4b188750e76c888f13bd05ce9a4d7767730feefe8a0e50ca6fc4"
1172        );
1173        let from = tx.recover_signer().unwrap();
1174        assert_eq!(from, address!("a12e1462d0ceD572f396F58B6E2D03894cD7C8a4"));
1175    }
1176
1177    #[test]
1178    #[cfg(feature = "k256")]
1179    // Test vector from https://sepolia.etherscan.io/tx/0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0
1180    // Blobscan: https://sepolia.blobscan.com/tx/0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0
1181    fn test_decode_live_4844_tx() {
1182        use crate::Transaction;
1183        use alloy_primitives::{address, b256};
1184
1185        // https://sepolia.etherscan.io/getRawTx?tx=0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0
1186        let raw_tx = alloy_primitives::hex::decode("0x03f9011d83aa36a7820fa28477359400852e90edd0008252089411e9ca82a3a762b4b5bd264d4173a242e7a770648080c08504a817c800f8a5a0012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921aa00152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4a0013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7a001148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1a0011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e654901a0c8de4cced43169f9aa3d36506363b2d2c44f6c49fc1fd91ea114c86f3757077ea01e11fdd0d1934eda0492606ee0bb80a7bf8f35cc5f86ec60fe5031ba48bfd544").unwrap();
1187
1188        let res = TxEnvelope::decode_2718(&mut raw_tx.as_slice()).unwrap();
1189        assert_eq!(res.tx_type(), TxType::Eip4844);
1190
1191        let tx = match res {
1192            TxEnvelope::Eip4844(tx) => tx,
1193            _ => unreachable!(),
1194        };
1195
1196        assert_eq!(
1197            tx.tx().kind(),
1198            TxKind::Call(address!("11E9CA82A3a762b4B5bd264d4173a242e7a77064"))
1199        );
1200
1201        // Assert this is the correct variant of the EIP-4844 enum, which only contains the tx.
1202        assert!(matches!(tx.tx(), TxEip4844Variant::TxEip4844(_)));
1203
1204        assert_eq!(
1205            tx.tx().tx().blob_versioned_hashes,
1206            vec![
1207                b256!("012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921a"),
1208                b256!("0152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4"),
1209                b256!("013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7"),
1210                b256!("01148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1"),
1211                b256!("011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e6549")
1212            ]
1213        );
1214
1215        let from = tx.recover_signer().unwrap();
1216        assert_eq!(from, address!("0xA83C816D4f9b2783761a22BA6FADB0eB0606D7B2"));
1217    }
1218
1219    fn test_encode_decode_roundtrip<T: SignableTransaction<Signature>>(
1220        tx: T,
1221        signature: Option<Signature>,
1222    ) where
1223        Signed<T>: Into<TxEnvelope>,
1224    {
1225        let signature = signature.unwrap_or_else(Signature::test_signature);
1226        let tx_signed = tx.into_signed(signature);
1227        let tx_envelope: TxEnvelope = tx_signed.into();
1228        let encoded = tx_envelope.encoded_2718();
1229        let mut slice = encoded.as_slice();
1230        let decoded = TxEnvelope::decode_2718(&mut slice).unwrap();
1231        assert_eq!(encoded.len(), tx_envelope.encode_2718_len());
1232        assert_eq!(decoded, tx_envelope);
1233        assert_eq!(slice.len(), 0);
1234    }
1235
1236    #[test]
1237    fn test_encode_decode_legacy() {
1238        let tx = TxLegacy {
1239            chain_id: None,
1240            nonce: 2,
1241            gas_limit: 1000000,
1242            gas_price: 10000000000,
1243            to: Address::left_padding_from(&[6]).into(),
1244            value: U256::from(7_u64),
1245            ..Default::default()
1246        };
1247        test_encode_decode_roundtrip(tx, Some(Signature::test_signature().with_parity(true)));
1248    }
1249
1250    #[test]
1251    fn test_encode_decode_eip1559() {
1252        let tx = TxEip1559 {
1253            chain_id: 1u64,
1254            nonce: 2,
1255            max_fee_per_gas: 3,
1256            max_priority_fee_per_gas: 4,
1257            gas_limit: 5,
1258            to: Address::left_padding_from(&[6]).into(),
1259            value: U256::from(7_u64),
1260            input: vec![8].into(),
1261            access_list: Default::default(),
1262        };
1263        test_encode_decode_roundtrip(tx, None);
1264    }
1265
1266    #[test]
1267    fn test_encode_decode_eip1559_parity_eip155() {
1268        let tx = TxEip1559 {
1269            chain_id: 1u64,
1270            nonce: 2,
1271            max_fee_per_gas: 3,
1272            max_priority_fee_per_gas: 4,
1273            gas_limit: 5,
1274            to: Address::left_padding_from(&[6]).into(),
1275            value: U256::from(7_u64),
1276            input: vec![8].into(),
1277            access_list: Default::default(),
1278        };
1279        let signature = Signature::test_signature().with_parity(true);
1280
1281        test_encode_decode_roundtrip(tx, Some(signature));
1282    }
1283
1284    #[test]
1285    fn test_encode_decode_eip2930_parity_eip155() {
1286        let tx = TxEip2930 {
1287            chain_id: 1u64,
1288            nonce: 2,
1289            gas_price: 3,
1290            gas_limit: 4,
1291            to: Address::left_padding_from(&[5]).into(),
1292            value: U256::from(6_u64),
1293            input: vec![7].into(),
1294            access_list: Default::default(),
1295        };
1296        let signature = Signature::test_signature().with_parity(true);
1297        test_encode_decode_roundtrip(tx, Some(signature));
1298    }
1299
1300    #[test]
1301    fn test_encode_decode_eip4844_parity_eip155() {
1302        let tx = TxEip4844 {
1303            chain_id: 1,
1304            nonce: 100,
1305            max_fee_per_gas: 50_000_000_000,
1306            max_priority_fee_per_gas: 1_000_000_000_000,
1307            gas_limit: 1_000_000,
1308            to: Address::random(),
1309            value: U256::from(10e18),
1310            input: Bytes::new(),
1311            access_list: AccessList(vec![AccessListItem {
1312                address: Address::random(),
1313                storage_keys: vec![B256::random()],
1314            }]),
1315            blob_versioned_hashes: vec![B256::random()],
1316            max_fee_per_blob_gas: 0,
1317        };
1318        let signature = Signature::test_signature().with_parity(true);
1319        test_encode_decode_roundtrip(tx, Some(signature));
1320    }
1321
1322    #[test]
1323    fn test_encode_decode_eip4844_sidecar_parity_eip155() {
1324        let tx = TxEip4844 {
1325            chain_id: 1,
1326            nonce: 100,
1327            max_fee_per_gas: 50_000_000_000,
1328            max_priority_fee_per_gas: 1_000_000_000_000,
1329            gas_limit: 1_000_000,
1330            to: Address::random(),
1331            value: U256::from(10e18),
1332            input: Bytes::new(),
1333            access_list: AccessList(vec![AccessListItem {
1334                address: Address::random(),
1335                storage_keys: vec![B256::random()],
1336            }]),
1337            blob_versioned_hashes: vec![B256::random()],
1338            max_fee_per_blob_gas: 0,
1339        };
1340        let sidecar = BlobTransactionSidecar {
1341            blobs: vec![[2; 131072].into()],
1342            commitments: vec![[3; 48].into()],
1343            proofs: vec![[4; 48].into()],
1344        };
1345        let tx = TxEip4844WithSidecar { tx, sidecar };
1346        let signature = Signature::test_signature().with_parity(true);
1347
1348        let tx_signed = tx.into_signed(signature);
1349        let tx_envelope: TxEnvelope = tx_signed.into();
1350
1351        let mut out = Vec::new();
1352        tx_envelope.network_encode(&mut out);
1353        let mut slice = out.as_slice();
1354        let decoded = TxEnvelope::network_decode(&mut slice).unwrap();
1355        assert_eq!(slice.len(), 0);
1356        assert_eq!(out.len(), tx_envelope.network_len());
1357        assert_eq!(decoded, tx_envelope);
1358    }
1359
1360    #[test]
1361    fn test_encode_decode_eip4844_variant_parity_eip155() {
1362        let tx = TxEip4844 {
1363            chain_id: 1,
1364            nonce: 100,
1365            max_fee_per_gas: 50_000_000_000,
1366            max_priority_fee_per_gas: 1_000_000_000_000,
1367            gas_limit: 1_000_000,
1368            to: Address::random(),
1369            value: U256::from(10e18),
1370            input: Bytes::new(),
1371            access_list: AccessList(vec![AccessListItem {
1372                address: Address::random(),
1373                storage_keys: vec![B256::random()],
1374            }]),
1375            blob_versioned_hashes: vec![B256::random()],
1376            max_fee_per_blob_gas: 0,
1377        };
1378        let tx = TxEip4844Variant::TxEip4844(tx);
1379        let signature = Signature::test_signature().with_parity(true);
1380        test_encode_decode_roundtrip(tx, Some(signature));
1381    }
1382
1383    #[test]
1384    fn test_encode_decode_eip2930() {
1385        let tx = TxEip2930 {
1386            chain_id: 1u64,
1387            nonce: 2,
1388            gas_price: 3,
1389            gas_limit: 4,
1390            to: Address::left_padding_from(&[5]).into(),
1391            value: U256::from(6_u64),
1392            input: vec![7].into(),
1393            access_list: AccessList(vec![AccessListItem {
1394                address: Address::left_padding_from(&[8]),
1395                storage_keys: vec![B256::left_padding_from(&[9])],
1396            }]),
1397        };
1398        test_encode_decode_roundtrip(tx, None);
1399    }
1400
1401    #[test]
1402    fn test_encode_decode_eip7702() {
1403        let tx = TxEip7702 {
1404            chain_id: 1u64,
1405            nonce: 2,
1406            gas_limit: 3,
1407            max_fee_per_gas: 4,
1408            max_priority_fee_per_gas: 5,
1409            to: Address::left_padding_from(&[5]),
1410            value: U256::from(6_u64),
1411            input: vec![7].into(),
1412            access_list: AccessList(vec![AccessListItem {
1413                address: Address::left_padding_from(&[8]),
1414                storage_keys: vec![B256::left_padding_from(&[9])],
1415            }]),
1416            authorization_list: vec![(Authorization {
1417                chain_id: U256::from(1),
1418                address: Address::left_padding_from(&[10]),
1419                nonce: 1u64,
1420            })
1421            .into_signed(Signature::from_str("48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c8041b").unwrap())],
1422        };
1423        test_encode_decode_roundtrip(tx, None);
1424    }
1425
1426    #[test]
1427    fn test_encode_decode_transaction_list() {
1428        let signature = Signature::test_signature();
1429        let tx = TxEnvelope::Eip1559(
1430            TxEip1559 {
1431                chain_id: 1u64,
1432                nonce: 2,
1433                max_fee_per_gas: 3,
1434                max_priority_fee_per_gas: 4,
1435                gas_limit: 5,
1436                to: Address::left_padding_from(&[6]).into(),
1437                value: U256::from(7_u64),
1438                input: vec![8].into(),
1439                access_list: Default::default(),
1440            }
1441            .into_signed(signature),
1442        );
1443        let transactions = vec![tx.clone(), tx];
1444        let encoded = alloy_rlp::encode(&transactions);
1445        let decoded = Vec::<TxEnvelope>::decode(&mut &encoded[..]).unwrap();
1446        assert_eq!(transactions, decoded);
1447    }
1448
1449    #[test]
1450    fn decode_encode_known_rpc_transaction() {
1451        // test data pulled from hive test that sends blob transactions
1452        let network_data_path =
1453            PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/rpc_blob_transaction.rlp");
1454        let data = fs::read_to_string(network_data_path).expect("Unable to read file");
1455        let hex_data = hex::decode(data.trim()).unwrap();
1456
1457        let tx: TxEnvelope = TxEnvelope::decode_2718(&mut hex_data.as_slice()).unwrap();
1458        let encoded = tx.encoded_2718();
1459        assert_eq!(encoded, hex_data);
1460        assert_eq!(tx.encode_2718_len(), hex_data.len());
1461    }
1462
1463    #[cfg(feature = "serde")]
1464    fn test_serde_roundtrip<T: SignableTransaction<Signature>>(tx: T)
1465    where
1466        Signed<T>: Into<TxEnvelope>,
1467    {
1468        let signature = Signature::test_signature();
1469        let tx_envelope: TxEnvelope = tx.into_signed(signature).into();
1470
1471        let serialized = serde_json::to_string(&tx_envelope).unwrap();
1472
1473        let deserialized: TxEnvelope = serde_json::from_str(&serialized).unwrap();
1474
1475        assert_eq!(tx_envelope, deserialized);
1476    }
1477
1478    #[test]
1479    #[cfg(feature = "serde")]
1480    fn test_serde_roundtrip_legacy() {
1481        let tx = TxLegacy {
1482            chain_id: Some(1),
1483            nonce: 100,
1484            gas_price: 3_000_000_000,
1485            gas_limit: 50_000,
1486            to: Address::default().into(),
1487            value: U256::from(10e18),
1488            input: Bytes::new(),
1489        };
1490        test_serde_roundtrip(tx);
1491    }
1492
1493    #[test]
1494    #[cfg(feature = "serde")]
1495    fn test_serde_roundtrip_eip1559() {
1496        let tx = TxEip1559 {
1497            chain_id: 1,
1498            nonce: 100,
1499            max_fee_per_gas: 50_000_000_000,
1500            max_priority_fee_per_gas: 1_000_000_000_000,
1501            gas_limit: 1_000_000,
1502            to: TxKind::Create,
1503            value: U256::from(10e18),
1504            input: Bytes::new(),
1505            access_list: AccessList(vec![AccessListItem {
1506                address: Address::random(),
1507                storage_keys: vec![B256::random()],
1508            }]),
1509        };
1510        test_serde_roundtrip(tx);
1511    }
1512
1513    #[test]
1514    #[cfg(feature = "serde")]
1515    fn test_serde_roundtrip_eip2930() {
1516        let tx = TxEip2930 {
1517            chain_id: u64::MAX,
1518            nonce: u64::MAX,
1519            gas_price: u128::MAX,
1520            gas_limit: u64::MAX,
1521            to: Address::random().into(),
1522            value: U256::MAX,
1523            input: Bytes::new(),
1524            access_list: Default::default(),
1525        };
1526        test_serde_roundtrip(tx);
1527    }
1528
1529    #[test]
1530    #[cfg(feature = "serde")]
1531    fn test_serde_roundtrip_eip4844() {
1532        let tx = TxEip4844Variant::TxEip4844(TxEip4844 {
1533            chain_id: 1,
1534            nonce: 100,
1535            max_fee_per_gas: 50_000_000_000,
1536            max_priority_fee_per_gas: 1_000_000_000_000,
1537            gas_limit: 1_000_000,
1538            to: Address::random(),
1539            value: U256::from(10e18),
1540            input: Bytes::new(),
1541            access_list: AccessList(vec![AccessListItem {
1542                address: Address::random(),
1543                storage_keys: vec![B256::random()],
1544            }]),
1545            blob_versioned_hashes: vec![B256::random()],
1546            max_fee_per_blob_gas: 0,
1547        });
1548        test_serde_roundtrip(tx);
1549
1550        let tx = TxEip4844Variant::TxEip4844WithSidecar(TxEip4844WithSidecar {
1551            tx: TxEip4844 {
1552                chain_id: 1,
1553                nonce: 100,
1554                max_fee_per_gas: 50_000_000_000,
1555                max_priority_fee_per_gas: 1_000_000_000_000,
1556                gas_limit: 1_000_000,
1557                to: Address::random(),
1558                value: U256::from(10e18),
1559                input: Bytes::new(),
1560                access_list: AccessList(vec![AccessListItem {
1561                    address: Address::random(),
1562                    storage_keys: vec![B256::random()],
1563                }]),
1564                blob_versioned_hashes: vec![B256::random()],
1565                max_fee_per_blob_gas: 0,
1566            },
1567            sidecar: Default::default(),
1568        });
1569        test_serde_roundtrip(tx);
1570    }
1571
1572    #[test]
1573    #[cfg(feature = "serde")]
1574    fn test_serde_roundtrip_eip7702() {
1575        let tx = TxEip7702 {
1576            chain_id: u64::MAX,
1577            nonce: u64::MAX,
1578            gas_limit: u64::MAX,
1579            max_fee_per_gas: u128::MAX,
1580            max_priority_fee_per_gas: u128::MAX,
1581            to: Address::random(),
1582            value: U256::MAX,
1583            input: Bytes::new(),
1584            access_list: AccessList(vec![AccessListItem {
1585                address: Address::random(),
1586                storage_keys: vec![B256::random()],
1587            }]),
1588            authorization_list: vec![(Authorization {
1589                chain_id: U256::from(1),
1590                address: Address::left_padding_from(&[1]),
1591                nonce: 1u64,
1592            })
1593            .into_signed(Signature::from_str("48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c8041b").unwrap())],
1594        };
1595        test_serde_roundtrip(tx);
1596    }
1597
1598    #[test]
1599    #[cfg(feature = "serde")]
1600    fn serde_tx_from_contract_call() {
1601        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"}"#;
1602
1603        let te = serde_json::from_str::<TxEnvelope>(rpc_tx).unwrap();
1604
1605        assert_eq!(
1606            *te.tx_hash(),
1607            alloy_primitives::b256!(
1608                "018b2331d461a4aeedf6a1f9cc37463377578244e6a35216057a8370714e798f"
1609            )
1610        );
1611    }
1612
1613    #[test]
1614    #[cfg(feature = "k256")]
1615    fn test_arbitrary_envelope() {
1616        use crate::transaction::SignerRecoverable;
1617        use arbitrary::Arbitrary;
1618        let mut unstructured = arbitrary::Unstructured::new(b"arbitrary tx envelope");
1619        let tx = TxEnvelope::arbitrary(&mut unstructured).unwrap();
1620
1621        assert!(tx.recover_signer().is_ok());
1622    }
1623
1624    #[test]
1625    #[cfg(feature = "serde")]
1626    fn test_serde_untagged_legacy() {
1627        let data = r#"{
1628            "hash": "0x97efb58d2b42df8d68ab5899ff42b16c7e0af35ed86ae4adb8acaad7e444220c",
1629            "input": "0x",
1630            "r": "0x5d71a4a548503f2916d10c6b1a1557a0e7352eb041acb2bac99d1ad6bb49fd45",
1631            "s": "0x2627bf6d35be48b0e56c61733f63944c0ebcaa85cb4ed6bc7cba3161ba85e0e8",
1632            "v": "0x1c",
1633            "gas": "0x15f90",
1634            "from": "0x2a65aca4d5fc5b5c859090a6c34d164135398226",
1635            "to": "0x8fbeb4488a08d60979b5aa9e13dd00b2726320b2",
1636            "value": "0xf606682badd7800",
1637            "nonce": "0x11f398",
1638            "gasPrice": "0x4a817c800"
1639        }"#;
1640
1641        let tx: TxEnvelope = serde_json::from_str(data).unwrap();
1642
1643        assert!(matches!(tx, TxEnvelope::Legacy(_)));
1644
1645        let data_with_wrong_type = r#"{
1646            "hash": "0x97efb58d2b42df8d68ab5899ff42b16c7e0af35ed86ae4adb8acaad7e444220c",
1647            "input": "0x",
1648            "r": "0x5d71a4a548503f2916d10c6b1a1557a0e7352eb041acb2bac99d1ad6bb49fd45",
1649            "s": "0x2627bf6d35be48b0e56c61733f63944c0ebcaa85cb4ed6bc7cba3161ba85e0e8",
1650            "v": "0x1c",
1651            "gas": "0x15f90",
1652            "from": "0x2a65aca4d5fc5b5c859090a6c34d164135398226",
1653            "to": "0x8fbeb4488a08d60979b5aa9e13dd00b2726320b2",
1654            "value": "0xf606682badd7800",
1655            "nonce": "0x11f398",
1656            "gasPrice": "0x4a817c800",
1657            "type": "0x12"
1658        }"#;
1659
1660        assert!(serde_json::from_str::<TxEnvelope>(data_with_wrong_type).is_err());
1661    }
1662
1663    #[test]
1664    fn test_tx_type_try_from_u8() {
1665        assert_eq!(TxType::try_from(0u8).unwrap(), TxType::Legacy);
1666        assert_eq!(TxType::try_from(1u8).unwrap(), TxType::Eip2930);
1667        assert_eq!(TxType::try_from(2u8).unwrap(), TxType::Eip1559);
1668        assert_eq!(TxType::try_from(3u8).unwrap(), TxType::Eip4844);
1669        assert_eq!(TxType::try_from(4u8).unwrap(), TxType::Eip7702);
1670        assert!(TxType::try_from(5u8).is_err()); // Invalid case
1671    }
1672
1673    #[test]
1674    fn test_tx_type_try_from_u64() {
1675        assert_eq!(TxType::try_from(0u64).unwrap(), TxType::Legacy);
1676        assert_eq!(TxType::try_from(1u64).unwrap(), TxType::Eip2930);
1677        assert_eq!(TxType::try_from(2u64).unwrap(), TxType::Eip1559);
1678        assert_eq!(TxType::try_from(3u64).unwrap(), TxType::Eip4844);
1679        assert_eq!(TxType::try_from(4u64).unwrap(), TxType::Eip7702);
1680        assert!(TxType::try_from(10u64).is_err()); // Invalid case
1681    }
1682
1683    #[test]
1684    fn test_tx_type_from_conversions() {
1685        let legacy_tx = Signed::new_unchecked(
1686            TxLegacy::default(),
1687            Signature::test_signature(),
1688            Default::default(),
1689        );
1690        let eip2930_tx = Signed::new_unchecked(
1691            TxEip2930::default(),
1692            Signature::test_signature(),
1693            Default::default(),
1694        );
1695        let eip1559_tx = Signed::new_unchecked(
1696            TxEip1559::default(),
1697            Signature::test_signature(),
1698            Default::default(),
1699        );
1700        let eip4844_variant = Signed::new_unchecked(
1701            TxEip4844Variant::TxEip4844(TxEip4844::default()),
1702            Signature::test_signature(),
1703            Default::default(),
1704        );
1705        let eip7702_tx = Signed::new_unchecked(
1706            TxEip7702::default(),
1707            Signature::test_signature(),
1708            Default::default(),
1709        );
1710
1711        assert!(matches!(TxEnvelope::from(legacy_tx), TxEnvelope::Legacy(_)));
1712        assert!(matches!(TxEnvelope::from(eip2930_tx), TxEnvelope::Eip2930(_)));
1713        assert!(matches!(TxEnvelope::from(eip1559_tx), TxEnvelope::Eip1559(_)));
1714        assert!(matches!(TxEnvelope::from(eip4844_variant), TxEnvelope::Eip4844(_)));
1715        assert!(matches!(TxEnvelope::from(eip7702_tx), TxEnvelope::Eip7702(_)));
1716    }
1717
1718    #[test]
1719    fn test_tx_type_is_methods() {
1720        let legacy_tx = TxEnvelope::Legacy(Signed::new_unchecked(
1721            TxLegacy::default(),
1722            Signature::test_signature(),
1723            Default::default(),
1724        ));
1725        let eip2930_tx = TxEnvelope::Eip2930(Signed::new_unchecked(
1726            TxEip2930::default(),
1727            Signature::test_signature(),
1728            Default::default(),
1729        ));
1730        let eip1559_tx = TxEnvelope::Eip1559(Signed::new_unchecked(
1731            TxEip1559::default(),
1732            Signature::test_signature(),
1733            Default::default(),
1734        ));
1735        let eip4844_tx = TxEnvelope::Eip4844(Signed::new_unchecked(
1736            TxEip4844Variant::TxEip4844(TxEip4844::default()),
1737            Signature::test_signature(),
1738            Default::default(),
1739        ));
1740        let eip7702_tx = TxEnvelope::Eip7702(Signed::new_unchecked(
1741            TxEip7702::default(),
1742            Signature::test_signature(),
1743            Default::default(),
1744        ));
1745
1746        assert!(legacy_tx.is_legacy());
1747        assert!(!legacy_tx.is_eip2930());
1748        assert!(!legacy_tx.is_eip1559());
1749        assert!(!legacy_tx.is_eip4844());
1750        assert!(!legacy_tx.is_eip7702());
1751
1752        assert!(eip2930_tx.is_eip2930());
1753        assert!(!eip2930_tx.is_legacy());
1754        assert!(!eip2930_tx.is_eip1559());
1755        assert!(!eip2930_tx.is_eip4844());
1756        assert!(!eip2930_tx.is_eip7702());
1757
1758        assert!(eip1559_tx.is_eip1559());
1759        assert!(!eip1559_tx.is_legacy());
1760        assert!(!eip1559_tx.is_eip2930());
1761        assert!(!eip1559_tx.is_eip4844());
1762        assert!(!eip1559_tx.is_eip7702());
1763
1764        assert!(eip4844_tx.is_eip4844());
1765        assert!(!eip4844_tx.is_legacy());
1766        assert!(!eip4844_tx.is_eip2930());
1767        assert!(!eip4844_tx.is_eip1559());
1768        assert!(!eip4844_tx.is_eip7702());
1769
1770        assert!(eip7702_tx.is_eip7702());
1771        assert!(!eip7702_tx.is_legacy());
1772        assert!(!eip7702_tx.is_eip2930());
1773        assert!(!eip7702_tx.is_eip1559());
1774        assert!(!eip7702_tx.is_eip4844());
1775    }
1776
1777    #[test]
1778    fn test_tx_type() {
1779        let legacy_tx = TxEnvelope::Legacy(Signed::new_unchecked(
1780            TxLegacy::default(),
1781            Signature::test_signature(),
1782            Default::default(),
1783        ));
1784        let eip2930_tx = TxEnvelope::Eip2930(Signed::new_unchecked(
1785            TxEip2930::default(),
1786            Signature::test_signature(),
1787            Default::default(),
1788        ));
1789        let eip1559_tx = TxEnvelope::Eip1559(Signed::new_unchecked(
1790            TxEip1559::default(),
1791            Signature::test_signature(),
1792            Default::default(),
1793        ));
1794        let eip4844_tx = TxEnvelope::Eip4844(Signed::new_unchecked(
1795            TxEip4844Variant::TxEip4844(TxEip4844::default()),
1796            Signature::test_signature(),
1797            Default::default(),
1798        ));
1799        let eip7702_tx = TxEnvelope::Eip7702(Signed::new_unchecked(
1800            TxEip7702::default(),
1801            Signature::test_signature(),
1802            Default::default(),
1803        ));
1804
1805        assert_eq!(legacy_tx.tx_type(), TxType::Legacy);
1806        assert_eq!(eip2930_tx.tx_type(), TxType::Eip2930);
1807        assert_eq!(eip1559_tx.tx_type(), TxType::Eip1559);
1808        assert_eq!(eip4844_tx.tx_type(), TxType::Eip4844);
1809        assert_eq!(eip7702_tx.tx_type(), TxType::Eip7702);
1810    }
1811
1812    #[test]
1813    fn test_try_into_legacy_success() {
1814        let legacy_tx = TxEnvelope::Legacy(Signed::new_unchecked(
1815            TxLegacy::default(),
1816            Signature::test_signature(),
1817            Default::default(),
1818        ));
1819
1820        let result = legacy_tx.try_into_legacy();
1821        assert!(result.is_ok());
1822    }
1823
1824    #[test]
1825    fn test_try_into_legacy_failure() {
1826        let eip1559_tx = TxEnvelope::Eip1559(Signed::new_unchecked(
1827            TxEip1559::default(),
1828            Signature::test_signature(),
1829            Default::default(),
1830        ));
1831
1832        let result = eip1559_tx.try_into_legacy();
1833        assert!(result.is_err());
1834        let error = result.unwrap_err();
1835        assert!(error.to_string().contains("Expected legacy transaction"));
1836        // Test that we can recover the original envelope
1837        let recovered_envelope = error.into_value();
1838        assert!(recovered_envelope.is_eip1559());
1839    }
1840
1841    #[test]
1842    fn test_try_into_eip2930_success() {
1843        let eip2930_tx = TxEnvelope::Eip2930(Signed::new_unchecked(
1844            TxEip2930::default(),
1845            Signature::test_signature(),
1846            Default::default(),
1847        ));
1848
1849        let result = eip2930_tx.try_into_eip2930();
1850        assert!(result.is_ok());
1851    }
1852
1853    #[test]
1854    fn test_try_into_eip2930_failure() {
1855        let legacy_tx = TxEnvelope::Legacy(Signed::new_unchecked(
1856            TxLegacy::default(),
1857            Signature::test_signature(),
1858            Default::default(),
1859        ));
1860
1861        let result = legacy_tx.try_into_eip2930();
1862        assert!(result.is_err());
1863        let error = result.unwrap_err();
1864        assert!(error.to_string().contains("Expected EIP-2930 transaction"));
1865        let recovered_envelope = error.into_value();
1866        assert!(recovered_envelope.is_legacy());
1867    }
1868
1869    #[test]
1870    fn test_try_into_eip1559_success() {
1871        let eip1559_tx = TxEnvelope::Eip1559(Signed::new_unchecked(
1872            TxEip1559::default(),
1873            Signature::test_signature(),
1874            Default::default(),
1875        ));
1876
1877        let result = eip1559_tx.try_into_eip1559();
1878        assert!(result.is_ok());
1879    }
1880
1881    #[test]
1882    fn test_try_into_eip1559_failure() {
1883        let eip2930_tx = TxEnvelope::Eip2930(Signed::new_unchecked(
1884            TxEip2930::default(),
1885            Signature::test_signature(),
1886            Default::default(),
1887        ));
1888
1889        let result = eip2930_tx.try_into_eip1559();
1890        assert!(result.is_err());
1891        let error = result.unwrap_err();
1892        assert!(error.to_string().contains("Expected EIP-1559 transaction"));
1893        let recovered_envelope = error.into_value();
1894        assert!(recovered_envelope.is_eip2930());
1895    }
1896
1897    #[test]
1898    fn test_try_into_eip4844_success() {
1899        let eip4844_tx = TxEnvelope::Eip4844(Signed::new_unchecked(
1900            TxEip4844Variant::TxEip4844(TxEip4844::default()),
1901            Signature::test_signature(),
1902            Default::default(),
1903        ));
1904
1905        let result = eip4844_tx.try_into_eip4844();
1906        assert!(result.is_ok());
1907    }
1908
1909    #[test]
1910    fn test_try_into_eip4844_failure() {
1911        let eip1559_tx = TxEnvelope::Eip1559(Signed::new_unchecked(
1912            TxEip1559::default(),
1913            Signature::test_signature(),
1914            Default::default(),
1915        ));
1916
1917        let result = eip1559_tx.try_into_eip4844();
1918        assert!(result.is_err());
1919        let error = result.unwrap_err();
1920        assert!(error.to_string().contains("Expected EIP-4844 transaction"));
1921        let recovered_envelope = error.into_value();
1922        assert!(recovered_envelope.is_eip1559());
1923    }
1924
1925    #[test]
1926    fn test_try_into_eip7702_success() {
1927        let eip7702_tx = TxEnvelope::Eip7702(Signed::new_unchecked(
1928            TxEip7702::default(),
1929            Signature::test_signature(),
1930            Default::default(),
1931        ));
1932
1933        let result = eip7702_tx.try_into_eip7702();
1934        assert!(result.is_ok());
1935    }
1936
1937    #[test]
1938    fn test_try_into_eip7702_failure() {
1939        let eip4844_tx = TxEnvelope::Eip4844(Signed::new_unchecked(
1940            TxEip4844Variant::TxEip4844(TxEip4844::default()),
1941            Signature::test_signature(),
1942            Default::default(),
1943        ));
1944
1945        let result = eip4844_tx.try_into_eip7702();
1946        assert!(result.is_err());
1947        let error = result.unwrap_err();
1948        assert!(error.to_string().contains("Expected EIP-7702 transaction"));
1949        let recovered_envelope = error.into_value();
1950        assert!(recovered_envelope.is_eip4844());
1951    }
1952
1953    // <https://sepolia.etherscan.io/getRawTx?tx=0xe5b458ba9de30b47cb7c0ea836bec7b072053123a7416c5082c97f959a4eebd6>
1954    #[test]
1955    fn decode_raw_legacy() {
1956        let raw = hex!("f8aa0285018ef61d0a832dc6c094cb33aa5b38d79e3d9fa8b10aff38aa201399a7e380b844af7b421018842e4628f3d9ee0e2c7679e29ed5dbaa75be75efecd392943503c9c68adce800000000000000000000000000000000000000000000000000000000000000641ca05e28679806caa50d25e9cb16aef8c0c08b235241b8f6e9d86faadf70421ba664a02353bba82ef2c7ce4dd6695942399163160000272b14f9aa6cbadf011b76efa4");
1957        let tx = TxEnvelope::decode_2718(&mut raw.as_ref()).unwrap();
1958        assert!(tx.chain_id().is_none());
1959    }
1960
1961    #[test]
1962    #[cfg(feature = "serde")]
1963    fn can_deserialize_system_transaction_with_zero_signature_envelope() {
1964        let raw_tx = r#"{
1965            "blockHash": "0x5307b5c812a067f8bc1ed1cc89d319ae6f9a0c9693848bd25c36b5191de60b85",
1966            "blockNumber": "0x45a59bb",
1967            "from": "0x0000000000000000000000000000000000000000",
1968            "gas": "0x1e8480",
1969            "gasPrice": "0x0",
1970            "hash": "0x16ef68aa8f35add3a03167a12b5d1268e344f6605a64ecc3f1c3aa68e98e4e06",
1971            "input": "0xcbd4ece900000000000000000000000032155c9d39084f040ba17890fe8134dbe2a0453f0000000000000000000000004a0126ee88018393b1ad2455060bc350ead9908a000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000469f700000000000000000000000000000000000000000000000000000000000000644ff746f60000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002043e908a4e862aebb10e7e27db0b892b58a7e32af11d64387a414dabc327b00e200000000000000000000000000000000000000000000000000000000",
1972            "nonce": "0x469f7",
1973            "to": "0x4200000000000000000000000000000000000007",
1974            "transactionIndex": "0x0",
1975            "value": "0x0",
1976            "v": "0x0",
1977            "r": "0x0",
1978            "s": "0x0",
1979            "queueOrigin": "l1",
1980            "l1TxOrigin": "0x36bde71c97b33cc4729cf772ae268934f7ab70b2",
1981            "l1BlockNumber": "0xfd1a6c",
1982            "l1Timestamp": "0x63e434ff",
1983            "index": "0x45a59ba",
1984            "queueIndex": "0x469f7",
1985            "rawTransaction": "0xcbd4ece900000000000000000000000032155c9d39084f040ba17890fe8134dbe2a0453f0000000000000000000000004a0126ee88018393b1ad2455060bc350ead9908a000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000469f700000000000000000000000000000000000000000000000000000000000000644ff746f60000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002043e908a4e862aebb10e7e27db0b892b58a7e32af11d64387a414dabc327b00e200000000000000000000000000000000000000000000000000000000"
1986        }"#;
1987
1988        let tx = serde_json::from_str::<TxEnvelope>(raw_tx).unwrap();
1989
1990        assert_eq!(tx.signature().r(), U256::ZERO);
1991        assert_eq!(tx.signature().s(), U256::ZERO);
1992        assert!(!tx.signature().v());
1993
1994        assert_eq!(
1995            tx.hash(),
1996            &b256!("0x16ef68aa8f35add3a03167a12b5d1268e344f6605a64ecc3f1c3aa68e98e4e06"),
1997            "hash should match the transaction hash"
1998        );
1999    }
2000
2001    // <https://github.com/succinctlabs/kona/issues/31>
2002    #[test]
2003    #[cfg(feature = "serde")]
2004    fn serde_block_tx() {
2005        let rpc_tx = r#"{
2006      "blockHash": "0xc0c3190292a82c2ee148774e37e5665f6a205f5ef0cd0885e84701d90ebd442e",
2007      "blockNumber": "0x6edcde",
2008      "transactionIndex": "0x7",
2009      "hash": "0x2cb125e083d6d2631e3752bd2b3d757bf31bf02bfe21de0ffa46fbb118d28b19",
2010      "from": "0x03e5badf3bb1ade1a8f33f94536c827b6531948d",
2011      "to": "0x3267e72dc8780a1512fa69da7759ec66f30350e3",
2012      "input": "0x62e4c545000000000000000000000000464c8ec100f2f42fb4e42e07e203da2324f9fc6700000000000000000000000003e5badf3bb1ade1a8f33f94536c827b6531948d000000000000000000000000a064bfb5c7e81426647dc20a0d854da1538559dc00000000000000000000000000000000000000000000000000c6f3b40b6c0000",
2013      "nonce": "0x2a8",
2014      "value": "0x0",
2015      "gas": "0x28afd",
2016      "gasPrice": "0x23ec5dbc2",
2017      "accessList": [],
2018      "chainId": "0xaa36a7",
2019      "type": "0x0",
2020      "v": "0x1546d71",
2021      "r": "0x809b9f0a1777e376cd1ee5d2f551035643755edf26ea65b7a00c822a24504962",
2022      "s": "0x6a57bb8e21fe85c7e092868ee976fef71edca974d8c452fcf303f9180c764f64"
2023    }"#;
2024
2025        let _ = serde_json::from_str::<TxEnvelope>(rpc_tx).unwrap();
2026    }
2027
2028    // <https://github.com/succinctlabs/kona/issues/31>
2029    #[test]
2030    #[cfg(feature = "serde")]
2031    fn serde_block_tx_legacy_chain_id() {
2032        let rpc_tx = r#"{
2033      "blockHash": "0xc0c3190292a82c2ee148774e37e5665f6a205f5ef0cd0885e84701d90ebd442e",
2034      "blockNumber": "0x6edcde",
2035      "transactionIndex": "0x8",
2036      "hash": "0xe5b458ba9de30b47cb7c0ea836bec7b072053123a7416c5082c97f959a4eebd6",
2037      "from": "0x8b87f0a788cc14b4f0f374da59920f5017ff05de",
2038      "to": "0xcb33aa5b38d79e3d9fa8b10aff38aa201399a7e3",
2039      "input": "0xaf7b421018842e4628f3d9ee0e2c7679e29ed5dbaa75be75efecd392943503c9c68adce80000000000000000000000000000000000000000000000000000000000000064",
2040      "nonce": "0x2",
2041      "value": "0x0",
2042      "gas": "0x2dc6c0",
2043      "gasPrice": "0x18ef61d0a",
2044      "accessList": [],
2045      "chainId": "0xaa36a7",
2046      "type": "0x0",
2047      "v": "0x1c",
2048      "r": "0x5e28679806caa50d25e9cb16aef8c0c08b235241b8f6e9d86faadf70421ba664",
2049      "s": "0x2353bba82ef2c7ce4dd6695942399163160000272b14f9aa6cbadf011b76efa4"
2050    }"#;
2051
2052        let _ = serde_json::from_str::<TxEnvelope>(rpc_tx).unwrap();
2053    }
2054}