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