Skip to main content

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