alloy_consensus/transaction/
eip4844.rs

1use super::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx, TxEip4844Sidecar};
2use crate::{SignableTransaction, Signed, Transaction, TxType};
3use alloc::vec::Vec;
4use alloy_eips::{
5    eip2718::IsTyped2718,
6    eip2930::AccessList,
7    eip4844::{BlobTransactionSidecar, DATA_GAS_PER_BLOB},
8    eip7594::{Decodable7594, Encodable7594},
9    eip7702::SignedAuthorization,
10    Typed2718,
11};
12use alloy_primitives::{Address, Bytes, ChainId, Signature, TxKind, B256, U256};
13use alloy_rlp::{BufMut, Decodable, Encodable, Header};
14use core::{fmt, mem};
15
16#[cfg(feature = "kzg")]
17use alloy_eips::eip4844::BlobTransactionValidationError;
18use alloy_eips::eip7594::{BlobTransactionSidecarEip7594, BlobTransactionSidecarVariant};
19
20/// [EIP-4844 Blob Transaction](https://eips.ethereum.org/EIPS/eip-4844#blob-transaction)
21///
22/// A transaction with blob hashes and max blob fee.
23/// It can either be a standalone transaction, mainly seen when retrieving historical transactions,
24/// or a transaction with a sidecar, which is used when submitting a transaction to the network and
25/// when receiving and sending transactions during the gossip stage.
26#[derive(Clone, Debug, PartialEq, Eq, Hash)]
27#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
28#[cfg_attr(feature = "serde", derive(serde::Serialize))]
29#[cfg_attr(feature = "serde", serde(untagged))]
30#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
31#[doc(alias = "Eip4844TransactionVariant")]
32pub enum TxEip4844Variant<T = BlobTransactionSidecar> {
33    /// A standalone transaction with blob hashes and max blob fee.
34    TxEip4844(TxEip4844),
35    /// A transaction with a sidecar, which contains the blob data, commitments, and proofs.
36    TxEip4844WithSidecar(TxEip4844WithSidecar<T>),
37}
38
39#[cfg(feature = "serde")]
40impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for TxEip4844Variant<T> {
41    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
42    where
43        D: serde::Deserializer<'de>,
44    {
45        #[derive(serde::Deserialize)]
46        struct TxEip4844SerdeHelper<Sidecar> {
47            #[serde(flatten)]
48            #[doc(alias = "transaction")]
49            tx: TxEip4844,
50            #[serde(flatten)]
51            sidecar: Option<Sidecar>,
52        }
53
54        let tx = TxEip4844SerdeHelper::<T>::deserialize(deserializer)?;
55
56        if let Some(sidecar) = tx.sidecar {
57            Ok(TxEip4844WithSidecar::from_tx_and_sidecar(tx.tx, sidecar).into())
58        } else {
59            Ok(tx.tx.into())
60        }
61    }
62}
63
64impl<T> From<Signed<TxEip4844>> for Signed<TxEip4844Variant<T>> {
65    fn from(value: Signed<TxEip4844>) -> Self {
66        let (tx, signature, hash) = value.into_parts();
67        Self::new_unchecked(TxEip4844Variant::TxEip4844(tx), signature, hash)
68    }
69}
70
71impl<T: Encodable7594> From<Signed<TxEip4844WithSidecar<T>>> for Signed<TxEip4844Variant<T>> {
72    fn from(value: Signed<TxEip4844WithSidecar<T>>) -> Self {
73        let (tx, signature, hash) = value.into_parts();
74        Self::new_unchecked(TxEip4844Variant::TxEip4844WithSidecar(tx), signature, hash)
75    }
76}
77
78impl From<TxEip4844Variant<BlobTransactionSidecar>>
79    for TxEip4844Variant<BlobTransactionSidecarVariant>
80{
81    fn from(value: TxEip4844Variant<BlobTransactionSidecar>) -> Self {
82        value.map_sidecar(Into::into)
83    }
84}
85
86impl From<TxEip4844Variant<BlobTransactionSidecarEip7594>>
87    for TxEip4844Variant<BlobTransactionSidecarVariant>
88{
89    fn from(value: TxEip4844Variant<BlobTransactionSidecarEip7594>) -> Self {
90        value.map_sidecar(Into::into)
91    }
92}
93
94impl<T> From<TxEip4844WithSidecar<T>> for TxEip4844Variant<T> {
95    fn from(tx: TxEip4844WithSidecar<T>) -> Self {
96        Self::TxEip4844WithSidecar(tx)
97    }
98}
99
100impl<T> From<TxEip4844> for TxEip4844Variant<T> {
101    fn from(tx: TxEip4844) -> Self {
102        Self::TxEip4844(tx)
103    }
104}
105
106impl From<(TxEip4844, BlobTransactionSidecar)> for TxEip4844Variant<BlobTransactionSidecar> {
107    fn from((tx, sidecar): (TxEip4844, BlobTransactionSidecar)) -> Self {
108        TxEip4844WithSidecar::from_tx_and_sidecar(tx, sidecar).into()
109    }
110}
111
112impl<T> From<TxEip4844Variant<T>> for TxEip4844 {
113    fn from(tx: TxEip4844Variant<T>) -> Self {
114        match tx {
115            TxEip4844Variant::TxEip4844(tx) => tx,
116            TxEip4844Variant::TxEip4844WithSidecar(tx) => tx.tx,
117        }
118    }
119}
120
121impl<T> AsRef<TxEip4844> for TxEip4844Variant<T> {
122    fn as_ref(&self) -> &TxEip4844 {
123        match self {
124            Self::TxEip4844(tx) => tx,
125            Self::TxEip4844WithSidecar(tx) => &tx.tx,
126        }
127    }
128}
129
130impl<T> AsMut<TxEip4844> for TxEip4844Variant<T> {
131    fn as_mut(&mut self) -> &mut TxEip4844 {
132        match self {
133            Self::TxEip4844(tx) => tx,
134            Self::TxEip4844WithSidecar(tx) => &mut tx.tx,
135        }
136    }
137}
138
139impl AsRef<Self> for TxEip4844 {
140    fn as_ref(&self) -> &Self {
141        self
142    }
143}
144
145impl AsMut<Self> for TxEip4844 {
146    fn as_mut(&mut self) -> &mut Self {
147        self
148    }
149}
150
151impl<T> TxEip4844Variant<T> {
152    /// Get the transaction type.
153    #[doc(alias = "transaction_type")]
154    pub const fn tx_type() -> TxType {
155        TxType::Eip4844
156    }
157
158    /// Get access to the inner tx [TxEip4844].
159    #[doc(alias = "transaction")]
160    pub const fn tx(&self) -> &TxEip4844 {
161        match self {
162            Self::TxEip4844(tx) => tx,
163            Self::TxEip4844WithSidecar(tx) => tx.tx(),
164        }
165    }
166
167    /// Strips the sidecar from this variant type leaving [`Self::TxEip4844`].
168    ///
169    /// Returns the sidecar if it was [`Self::TxEip4844WithSidecar`].
170    pub fn take_sidecar(&mut self) -> Option<T> {
171        // Use a placeholder to temporarily replace self
172        let placeholder = Self::TxEip4844(TxEip4844::default());
173        match mem::replace(self, placeholder) {
174            tx @ Self::TxEip4844(_) => {
175                // Put the original transaction back
176                *self = tx;
177                None
178            }
179            Self::TxEip4844WithSidecar(tx) => {
180                let (tx, sidecar) = tx.into_parts();
181                *self = Self::TxEip4844(tx);
182                Some(sidecar)
183            }
184        }
185    }
186
187    /// Strips the sidecar from the variant and returns both the transaction and the sidecar
188    /// separately, keeping the same sidecar type parameter.
189    ///
190    /// This method consumes the variant and returns:
191    /// - A [`TxEip4844Variant<T>`] containing only the transaction (always
192    ///   [`TxEip4844Variant::TxEip4844`])
193    /// - An [`Option<T>`] containing the sidecar if it existed
194    ///
195    /// This is a convenience wrapper around [`strip_sidecar_into`](Self::strip_sidecar_into)
196    /// that keeps the same type parameter.
197    ///
198    /// # Examples
199    ///
200    /// ```
201    /// # use alloy_consensus::TxEip4844Variant;
202    /// # use alloy_eips::eip4844::BlobTransactionSidecar;
203    /// # fn example(variant: TxEip4844Variant<BlobTransactionSidecar>) {
204    /// // Strip and extract the sidecar (type parameter stays the same)
205    /// let (tx_variant, maybe_sidecar) = variant.strip_sidecar();
206    ///
207    /// if let Some(sidecar) = maybe_sidecar {
208    ///     // Process the sidecar separately
209    ///     println!("Sidecar has {} blobs", sidecar.blobs.len());
210    /// }
211    /// # }
212    /// ```
213    pub fn strip_sidecar(self) -> (Self, Option<T>) {
214        self.strip_sidecar_into()
215    }
216
217    /// Strips the sidecar from the variant and returns both the transaction and the sidecar
218    /// separately, converting to a different sidecar type parameter.
219    ///
220    /// This method consumes the variant and returns:
221    /// - A [`TxEip4844Variant<U>`] containing only the transaction (always
222    ///   [`TxEip4844Variant::TxEip4844`])
223    /// - An [`Option<T>`] containing the sidecar if it existed
224    ///
225    /// This is useful when you need to:
226    /// - Extract the sidecar for separate processing
227    /// - Convert to a variant with a different sidecar type parameter
228    /// - Separate the transaction data from blob data
229    ///
230    /// # Examples
231    ///
232    /// ```
233    /// # use alloy_consensus::TxEip4844Variant;
234    /// # use alloy_eips::eip4844::BlobTransactionSidecar;
235    /// # use alloy_eips::eip7594::BlobTransactionSidecarVariant;
236    /// # fn example(variant: TxEip4844Variant<BlobTransactionSidecar>) {
237    /// // Strip and convert to a different type parameter
238    /// let (tx_variant, maybe_sidecar): (TxEip4844Variant<BlobTransactionSidecarVariant>, _) =
239    ///     variant.strip_sidecar_into();
240    ///
241    /// if let Some(sidecar) = maybe_sidecar {
242    ///     // Process the sidecar separately
243    ///     println!("Sidecar has {} blobs", sidecar.blobs.len());
244    /// }
245    /// # }
246    /// ```
247    pub fn strip_sidecar_into<U>(self) -> (TxEip4844Variant<U>, Option<T>) {
248        match self {
249            Self::TxEip4844(tx) => (TxEip4844Variant::TxEip4844(tx), None),
250            Self::TxEip4844WithSidecar(tx) => {
251                let (tx, sidecar) = tx.into_parts();
252                (TxEip4844Variant::TxEip4844(tx), Some(sidecar))
253            }
254        }
255    }
256
257    /// Drops the sidecar from the variant and returns only the transaction, keeping the same
258    /// sidecar type parameter.
259    ///
260    /// This is a convenience method that discards the sidecar, returning only the transaction
261    /// without a sidecar (always [`TxEip4844Variant::TxEip4844`]).
262    ///
263    /// This is equivalent to calling [`strip_sidecar`](Self::strip_sidecar) and taking only the
264    /// first element of the tuple.
265    ///
266    /// # Examples
267    ///
268    /// ```
269    /// # use alloy_consensus::TxEip4844Variant;
270    /// # use alloy_eips::eip4844::BlobTransactionSidecar;
271    /// # fn example(variant: TxEip4844Variant<BlobTransactionSidecar>) {
272    /// // Drop the sidecar, keeping only the transaction
273    /// let tx_without_sidecar = variant.drop_sidecar();
274    /// # }
275    /// ```
276    pub fn drop_sidecar(self) -> Self {
277        self.strip_sidecar().0
278    }
279
280    /// Drops the sidecar from the variant and returns only the transaction, converting to a
281    /// different sidecar type parameter.
282    ///
283    /// This is a convenience method that discards the sidecar, returning only the transaction
284    /// without a sidecar (always [`TxEip4844Variant::TxEip4844`]).
285    ///
286    /// This is equivalent to calling [`strip_sidecar_into`](Self::strip_sidecar_into) and taking
287    /// only the first element of the tuple.
288    ///
289    /// # Examples
290    ///
291    /// ```
292    /// # use alloy_consensus::TxEip4844Variant;
293    /// # use alloy_eips::eip4844::BlobTransactionSidecar;
294    /// # use alloy_eips::eip7594::BlobTransactionSidecarVariant;
295    /// # fn example(variant: TxEip4844Variant<BlobTransactionSidecar>) {
296    /// // Drop the sidecar and convert to a different type parameter
297    /// let tx_without_sidecar: TxEip4844Variant<BlobTransactionSidecarVariant> =
298    ///     variant.drop_sidecar_into();
299    /// # }
300    /// ```
301    pub fn drop_sidecar_into<U>(self) -> TxEip4844Variant<U> {
302        self.strip_sidecar_into().0
303    }
304
305    /// Returns the [`TxEip4844WithSidecar`] if it has a sidecar
306    pub const fn as_with_sidecar(&self) -> Option<&TxEip4844WithSidecar<T>> {
307        match self {
308            Self::TxEip4844WithSidecar(tx) => Some(tx),
309            _ => None,
310        }
311    }
312
313    /// Tries to unwrap the [`TxEip4844WithSidecar`] returns the transaction as error if it is not a
314    /// [`TxEip4844WithSidecar`]
315    pub fn try_into_4844_with_sidecar(self) -> Result<TxEip4844WithSidecar<T>, Self> {
316        match self {
317            Self::TxEip4844WithSidecar(tx) => Ok(tx),
318            _ => Err(self),
319        }
320    }
321
322    /// Returns the sidecar if this is [`TxEip4844Variant::TxEip4844WithSidecar`].
323    pub const fn sidecar(&self) -> Option<&T> {
324        match self {
325            Self::TxEip4844WithSidecar(tx) => Some(tx.sidecar()),
326            _ => None,
327        }
328    }
329
330    /// Maps the sidecar to a new type.
331    pub fn map_sidecar<U>(self, f: impl FnOnce(T) -> U) -> TxEip4844Variant<U> {
332        match self {
333            Self::TxEip4844(tx) => TxEip4844Variant::TxEip4844(tx),
334            Self::TxEip4844WithSidecar(tx) => {
335                TxEip4844Variant::TxEip4844WithSidecar(tx.map_sidecar(f))
336            }
337        }
338    }
339
340    /// Maps the sidecar to a new type, returning an error if the mapping fails.
341    pub fn try_map_sidecar<U, E>(
342        self,
343        f: impl FnOnce(T) -> Result<U, E>,
344    ) -> Result<TxEip4844Variant<U>, E> {
345        match self {
346            Self::TxEip4844(tx) => Ok(TxEip4844Variant::TxEip4844(tx)),
347            Self::TxEip4844WithSidecar(tx) => {
348                tx.try_map_sidecar(f).map(TxEip4844Variant::TxEip4844WithSidecar)
349            }
350        }
351    }
352}
353
354impl<T: TxEip4844Sidecar> TxEip4844Variant<T> {
355    /// Verifies that the transaction's blob data, commitments, and proofs are all valid.
356    ///
357    /// See also [TxEip4844::validate_blob]
358    #[cfg(feature = "kzg")]
359    pub fn validate(
360        &self,
361        proof_settings: &c_kzg::KzgSettings,
362    ) -> Result<(), BlobTransactionValidationError> {
363        match self {
364            Self::TxEip4844(_) => Err(BlobTransactionValidationError::MissingSidecar),
365            Self::TxEip4844WithSidecar(tx) => tx.validate_blob(proof_settings),
366        }
367    }
368
369    /// Calculates a heuristic for the in-memory size of the [TxEip4844Variant] transaction.
370    #[inline]
371    pub fn size(&self) -> usize {
372        match self {
373            Self::TxEip4844(tx) => tx.size(),
374            Self::TxEip4844WithSidecar(tx) => tx.size(),
375        }
376    }
377}
378
379impl TxEip4844Variant<BlobTransactionSidecar> {
380    /// Converts this legacy EIP-4844 sidecar into an EIP-7594 sidecar with the default settings.
381    ///
382    /// This requires computing cell KZG proofs from the blob data using the KZG trusted setup.
383    /// Each blob produces `CELLS_PER_EXT_BLOB` cell proofs.
384    #[cfg(feature = "kzg")]
385    pub fn try_into_7594(
386        self,
387    ) -> Result<TxEip4844Variant<alloy_eips::eip7594::BlobTransactionSidecarEip7594>, c_kzg::Error>
388    {
389        self.try_into_7594_with_settings(
390            alloy_eips::eip4844::env_settings::EnvKzgSettings::Default.get(),
391        )
392    }
393
394    /// Converts this legacy EIP-4844 sidecar into an EIP-7594 sidecar with the given settings.
395    ///
396    /// This requires computing cell KZG proofs from the blob data using the KZG trusted setup.
397    /// Each blob produces `CELLS_PER_EXT_BLOB` cell proofs.
398    #[cfg(feature = "kzg")]
399    pub fn try_into_7594_with_settings(
400        self,
401        settings: &c_kzg::KzgSettings,
402    ) -> Result<TxEip4844Variant<alloy_eips::eip7594::BlobTransactionSidecarEip7594>, c_kzg::Error>
403    {
404        self.try_map_sidecar(|sidecar| sidecar.try_into_7594(settings))
405    }
406}
407
408#[cfg(feature = "kzg")]
409impl TxEip4844Variant<alloy_eips::eip7594::BlobTransactionSidecarVariant> {
410    /// Attempts to convert this transaction's sidecar into the EIP-7594 format using default KZG
411    /// settings.
412    ///
413    /// For EIP-4844 sidecars, this computes cell KZG proofs from the blob data. If the sidecar is
414    /// already in EIP-7594 format, it returns itself unchanged.
415    ///
416    /// # Returns
417    ///
418    /// - `Ok(TxEip4844Variant<alloy_eips::eip7594::BlobTransactionSidecarVariant>)` - The
419    ///   transaction with converted sidecar
420    /// - `Err(c_kzg::Error)` - If KZG proof computation fails
421    pub fn try_convert_into_eip7594(self) -> Result<Self, c_kzg::Error> {
422        self.try_convert_into_eip7594_with_settings(
423            alloy_eips::eip4844::env_settings::EnvKzgSettings::Default.get(),
424        )
425    }
426
427    /// Attempts to convert this transaction's sidecar into the EIP-7594 format using custom KZG
428    /// settings.
429    ///
430    /// For EIP-4844 sidecars, this computes cell KZG proofs from the blob data using the
431    /// provided KZG settings. If the sidecar is already in EIP-7594 format, it returns itself
432    /// unchanged.
433    ///
434    /// # Arguments
435    ///
436    /// * `settings` - The KZG settings to use for computing cell proofs
437    ///
438    /// # Returns
439    ///
440    /// - `Ok(TxEip4844Variant<alloy_eips::eip7594::BlobTransactionSidecarVariant>)` - The
441    ///   transaction with converted sidecar
442    /// - `Err(c_kzg::Error)` - If KZG proof computation fails
443    pub fn try_convert_into_eip7594_with_settings(
444        self,
445        settings: &c_kzg::KzgSettings,
446    ) -> Result<Self, c_kzg::Error> {
447        self.try_map_sidecar(|sidecar| sidecar.try_convert_into_eip7594_with_settings(settings))
448    }
449}
450
451impl<T> Transaction for TxEip4844Variant<T>
452where
453    T: fmt::Debug + Send + Sync + 'static,
454{
455    #[inline]
456    fn chain_id(&self) -> Option<ChainId> {
457        match self {
458            Self::TxEip4844(tx) => Some(tx.chain_id),
459            Self::TxEip4844WithSidecar(tx) => Some(tx.tx().chain_id),
460        }
461    }
462
463    #[inline]
464    fn nonce(&self) -> u64 {
465        match self {
466            Self::TxEip4844(tx) => tx.nonce,
467            Self::TxEip4844WithSidecar(tx) => tx.tx().nonce,
468        }
469    }
470
471    #[inline]
472    fn gas_limit(&self) -> u64 {
473        match self {
474            Self::TxEip4844(tx) => tx.gas_limit,
475            Self::TxEip4844WithSidecar(tx) => tx.tx().gas_limit,
476        }
477    }
478
479    #[inline]
480    fn gas_price(&self) -> Option<u128> {
481        None
482    }
483
484    #[inline]
485    fn max_fee_per_gas(&self) -> u128 {
486        match self {
487            Self::TxEip4844(tx) => tx.max_fee_per_gas(),
488            Self::TxEip4844WithSidecar(tx) => tx.max_fee_per_gas(),
489        }
490    }
491
492    #[inline]
493    fn max_priority_fee_per_gas(&self) -> Option<u128> {
494        match self {
495            Self::TxEip4844(tx) => tx.max_priority_fee_per_gas(),
496            Self::TxEip4844WithSidecar(tx) => tx.max_priority_fee_per_gas(),
497        }
498    }
499
500    #[inline]
501    fn max_fee_per_blob_gas(&self) -> Option<u128> {
502        match self {
503            Self::TxEip4844(tx) => tx.max_fee_per_blob_gas(),
504            Self::TxEip4844WithSidecar(tx) => tx.max_fee_per_blob_gas(),
505        }
506    }
507
508    #[inline]
509    fn priority_fee_or_price(&self) -> u128 {
510        match self {
511            Self::TxEip4844(tx) => tx.priority_fee_or_price(),
512            Self::TxEip4844WithSidecar(tx) => tx.priority_fee_or_price(),
513        }
514    }
515
516    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
517        match self {
518            Self::TxEip4844(tx) => tx.effective_gas_price(base_fee),
519            Self::TxEip4844WithSidecar(tx) => tx.effective_gas_price(base_fee),
520        }
521    }
522
523    #[inline]
524    fn is_dynamic_fee(&self) -> bool {
525        match self {
526            Self::TxEip4844(tx) => tx.is_dynamic_fee(),
527            Self::TxEip4844WithSidecar(tx) => tx.is_dynamic_fee(),
528        }
529    }
530
531    #[inline]
532    fn kind(&self) -> TxKind {
533        match self {
534            Self::TxEip4844(tx) => tx.to,
535            Self::TxEip4844WithSidecar(tx) => tx.tx.to,
536        }
537        .into()
538    }
539
540    #[inline]
541    fn is_create(&self) -> bool {
542        false
543    }
544
545    #[inline]
546    fn value(&self) -> U256 {
547        match self {
548            Self::TxEip4844(tx) => tx.value,
549            Self::TxEip4844WithSidecar(tx) => tx.tx.value,
550        }
551    }
552
553    #[inline]
554    fn input(&self) -> &Bytes {
555        match self {
556            Self::TxEip4844(tx) => tx.input(),
557            Self::TxEip4844WithSidecar(tx) => tx.tx().input(),
558        }
559    }
560
561    #[inline]
562    fn access_list(&self) -> Option<&AccessList> {
563        match self {
564            Self::TxEip4844(tx) => tx.access_list(),
565            Self::TxEip4844WithSidecar(tx) => tx.access_list(),
566        }
567    }
568
569    #[inline]
570    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
571        match self {
572            Self::TxEip4844(tx) => tx.blob_versioned_hashes(),
573            Self::TxEip4844WithSidecar(tx) => tx.blob_versioned_hashes(),
574        }
575    }
576
577    #[inline]
578    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
579        None
580    }
581}
582impl Typed2718 for TxEip4844 {
583    fn ty(&self) -> u8 {
584        TxType::Eip4844 as u8
585    }
586}
587
588impl<T: Encodable7594> RlpEcdsaEncodableTx for TxEip4844Variant<T> {
589    fn rlp_encoded_fields_length(&self) -> usize {
590        match self {
591            Self::TxEip4844(inner) => inner.rlp_encoded_fields_length(),
592            Self::TxEip4844WithSidecar(inner) => inner.rlp_encoded_fields_length(),
593        }
594    }
595
596    fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
597        match self {
598            Self::TxEip4844(inner) => inner.rlp_encode_fields(out),
599            Self::TxEip4844WithSidecar(inner) => inner.rlp_encode_fields(out),
600        }
601    }
602
603    fn rlp_header_signed(&self, signature: &Signature) -> Header {
604        match self {
605            Self::TxEip4844(inner) => inner.rlp_header_signed(signature),
606            Self::TxEip4844WithSidecar(inner) => inner.rlp_header_signed(signature),
607        }
608    }
609
610    fn rlp_encode_signed(&self, signature: &Signature, out: &mut dyn BufMut) {
611        match self {
612            Self::TxEip4844(inner) => inner.rlp_encode_signed(signature, out),
613            Self::TxEip4844WithSidecar(inner) => inner.rlp_encode_signed(signature, out),
614        }
615    }
616
617    fn tx_hash_with_type(&self, signature: &Signature, ty: u8) -> alloy_primitives::TxHash {
618        match self {
619            Self::TxEip4844(inner) => inner.tx_hash_with_type(signature, ty),
620            Self::TxEip4844WithSidecar(inner) => inner.tx_hash_with_type(signature, ty),
621        }
622    }
623}
624
625impl<T: Encodable7594 + Decodable7594> RlpEcdsaDecodableTx for TxEip4844Variant<T> {
626    const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 };
627
628    fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
629        let needle = &mut &**buf;
630
631        // We also need to do a trial decoding of WithSidecar to see if it
632        // works. The trial ref is consumed to look for a WithSidecar.
633        let trial = &mut &**buf;
634
635        // If the next bytes are a header, one of 3 things is true:
636        // - If the header is a list, this is a WithSidecar tx
637        // - If there is no header, this is a non-sidecar tx with a single-byte chain ID.
638        // - If there is a string header, this is a non-sidecar tx with a multi-byte chain ID.
639        // To check these, we first try to decode the header. If it fails or is
640        // not a list, we lmow that it is a non-sidecar transaction.
641        if Header::decode(needle).is_ok_and(|h| h.list) {
642            if let Ok(tx) = TxEip4844WithSidecar::rlp_decode_fields(trial) {
643                *buf = *trial;
644                return Ok(tx.into());
645            }
646        }
647        TxEip4844::rlp_decode_fields(buf).map(Into::into)
648    }
649
650    fn rlp_decode_with_signature(buf: &mut &[u8]) -> alloy_rlp::Result<(Self, Signature)> {
651        // We need to determine if this has a sidecar tx or not. The needle ref
652        // is consumed to look for headers.
653        let needle = &mut &**buf;
654
655        // We also need to do a trial decoding of WithSidecar to see if it
656        // works. The original ref is consumed to look for a WithSidecar.
657        let trial = &mut &**buf;
658
659        // First we decode the outer header
660        Header::decode(needle)?;
661
662        // If the next bytes are a header, one of 3 things is true:
663        // - If the header is a list, this is a WithSidecar tx
664        // - If there is no header, this is a non-sidecar tx with a single-byte chain ID.
665        // - If there is a string header, this is a non-sidecar tx with a multi-byte chain ID.
666        // To check these, we first try to decode the header. If it fails or is
667        // not a list, we lmow that it is a non-sidecar transaction.
668        if Header::decode(needle).is_ok_and(|h| h.list) {
669            if let Ok((tx, signature)) = TxEip4844WithSidecar::rlp_decode_with_signature(trial) {
670                // If successful, we need to consume the trial buffer up to
671                // the same point.
672                *buf = *trial;
673                return Ok((tx.into(), signature));
674            }
675        }
676        TxEip4844::rlp_decode_with_signature(buf).map(|(tx, signature)| (tx.into(), signature))
677    }
678}
679
680impl<T> Typed2718 for TxEip4844Variant<T> {
681    fn ty(&self) -> u8 {
682        TxType::Eip4844 as u8
683    }
684}
685
686impl IsTyped2718 for TxEip4844 {
687    fn is_type(type_id: u8) -> bool {
688        matches!(type_id, 0x03)
689    }
690}
691
692impl<T> SignableTransaction<Signature> for TxEip4844Variant<T>
693where
694    T: fmt::Debug + Send + Sync + 'static,
695{
696    fn set_chain_id(&mut self, chain_id: ChainId) {
697        match self {
698            Self::TxEip4844(inner) => {
699                inner.set_chain_id(chain_id);
700            }
701            Self::TxEip4844WithSidecar(inner) => {
702                inner.set_chain_id(chain_id);
703            }
704        }
705    }
706
707    fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
708        // A signature for a [TxEip4844WithSidecar] is a signature over the [TxEip4844Variant]
709        // EIP-2718 payload fields:
710        // (BLOB_TX_TYPE ||
711        //   rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value,
712        //     data, access_list, max_fee_per_blob_gas, blob_versioned_hashes]))
713        self.tx().encode_for_signing(out);
714    }
715
716    fn payload_len_for_signature(&self) -> usize {
717        self.tx().payload_len_for_signature()
718    }
719}
720
721/// [EIP-4844 Blob Transaction](https://eips.ethereum.org/EIPS/eip-4844#blob-transaction)
722///
723/// A transaction with blob hashes and max blob fee. It does not have the Blob sidecar.
724#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
725#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
726#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
727#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
728#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
729#[doc(alias = "Eip4844Transaction", alias = "TransactionEip4844", alias = "Eip4844Tx")]
730pub struct TxEip4844 {
731    /// Added as EIP-pub 155: Simple replay attack protection
732    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
733    pub chain_id: ChainId,
734    /// A scalar value equal to the number of transactions sent by the sender; formally Tn.
735    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
736    pub nonce: u64,
737    /// A scalar value equal to the maximum
738    /// amount of gas that should be used in executing
739    /// this transaction. This is paid up-front, before any
740    /// computation is done and may not be increased
741    /// later; formally Tg.
742    #[cfg_attr(
743        feature = "serde",
744        serde(with = "alloy_serde::quantity", rename = "gas", alias = "gasLimit")
745    )]
746    pub gas_limit: u64,
747    /// A scalar value equal to the maximum
748    /// amount of gas that should be used in executing
749    /// this transaction. This is paid up-front, before any
750    /// computation is done and may not be increased
751    /// later; formally Tg.
752    ///
753    /// As ethereum circulation is around 120mil eth as of 2022 that is around
754    /// 120000000000000000000000000 wei we are safe to use u128 as its max number is:
755    /// 340282366920938463463374607431768211455
756    ///
757    /// This is also known as `GasFeeCap`
758    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
759    pub max_fee_per_gas: u128,
760    /// Max Priority fee that transaction is paying
761    ///
762    /// As ethereum circulation is around 120mil eth as of 2022 that is around
763    /// 120000000000000000000000000 wei we are safe to use u128 as its max number is:
764    /// 340282366920938463463374607431768211455
765    ///
766    /// This is also known as `GasTipCap`
767    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
768    pub max_priority_fee_per_gas: u128,
769    /// The 160-bit address of the message call’s recipient.
770    pub to: Address,
771    /// A scalar value equal to the number of Wei to
772    /// be transferred to the message call’s recipient or,
773    /// in the case of contract creation, as an endowment
774    /// to the newly created account; formally Tv.
775    pub value: U256,
776    /// The accessList specifies a list of addresses and storage keys;
777    /// these addresses and storage keys are added into the `accessed_addresses`
778    /// and `accessed_storage_keys` global sets (introduced in EIP-2929).
779    /// A gas cost is charged, though at a discount relative to the cost of
780    /// accessing outside the list.
781    pub access_list: AccessList,
782
783    /// It contains a vector of fixed size hash(32 bytes)
784    pub blob_versioned_hashes: Vec<B256>,
785
786    /// Max fee per data gas
787    ///
788    /// aka BlobFeeCap or blobGasFeeCap
789    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
790    pub max_fee_per_blob_gas: u128,
791
792    /// Input has two uses depending if transaction is Create or Call (if `to` field is None or
793    /// Some). pub init: An unlimited size byte array specifying the
794    /// EVM-code for the account initialisation procedure CREATE,
795    /// data: An unlimited size byte array specifying the
796    /// input data of the message call, formally Td.
797    pub input: Bytes,
798}
799
800impl TxEip4844 {
801    /// Returns the total gas for all blobs in this transaction.
802    #[inline]
803    pub const fn blob_gas(&self) -> u64 {
804        // SAFETY: we don't expect u64::MAX / DATA_GAS_PER_BLOB hashes in a single transaction
805        self.blob_versioned_hashes.len() as u64 * DATA_GAS_PER_BLOB
806    }
807
808    /// Verifies that the given blob data, commitments, and proofs are all valid for this
809    /// transaction.
810    ///
811    /// Takes as input the [KzgSettings](c_kzg::KzgSettings), which should contain the parameters
812    /// derived from the KZG trusted setup.
813    ///
814    /// This ensures that the blob transaction payload has the same number of blob data elements,
815    /// commitments, and proofs. Each blob data element is verified against its commitment and
816    /// proof.
817    ///
818    /// Returns [BlobTransactionValidationError::InvalidProof] if any blob KZG proof in the response
819    /// fails to verify, or if the versioned hashes in the transaction do not match the actual
820    /// commitment versioned hashes.
821    #[cfg(feature = "kzg")]
822    pub fn validate_blob<T: TxEip4844Sidecar>(
823        &self,
824        sidecar: &T,
825        proof_settings: &c_kzg::KzgSettings,
826    ) -> Result<(), BlobTransactionValidationError> {
827        sidecar.validate(&self.blob_versioned_hashes, proof_settings)
828    }
829
830    /// Get transaction type.
831    #[doc(alias = "transaction_type")]
832    pub const fn tx_type() -> TxType {
833        TxType::Eip4844
834    }
835
836    /// Attaches the blob sidecar to the transaction
837    pub const fn with_sidecar<T>(self, sidecar: T) -> TxEip4844WithSidecar<T> {
838        TxEip4844WithSidecar::from_tx_and_sidecar(self, sidecar)
839    }
840
841    /// Calculates a heuristic for the in-memory size of the [TxEip4844Variant] transaction.
842    #[inline]
843    pub fn size(&self) -> usize {
844        mem::size_of::<ChainId>() + // chain_id
845        mem::size_of::<u64>() + // nonce
846        mem::size_of::<u64>() + // gas_limit
847        mem::size_of::<u128>() + // max_fee_per_gas
848        mem::size_of::<u128>() + // max_priority_fee_per_gas
849        mem::size_of::<Address>() + // to
850        mem::size_of::<U256>() + // value
851        self.access_list.size() + // access_list
852        self.input.len() +  // input
853        self.blob_versioned_hashes.capacity() * mem::size_of::<B256>() + // blob hashes size
854        mem::size_of::<u128>() // max_fee_per_data_gas
855    }
856}
857
858impl RlpEcdsaEncodableTx for TxEip4844 {
859    fn rlp_encoded_fields_length(&self) -> usize {
860        self.chain_id.length()
861            + self.nonce.length()
862            + self.gas_limit.length()
863            + self.max_fee_per_gas.length()
864            + self.max_priority_fee_per_gas.length()
865            + self.to.length()
866            + self.value.length()
867            + self.access_list.length()
868            + self.blob_versioned_hashes.length()
869            + self.max_fee_per_blob_gas.length()
870            + self.input.0.length()
871    }
872
873    fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
874        self.chain_id.encode(out);
875        self.nonce.encode(out);
876        self.max_priority_fee_per_gas.encode(out);
877        self.max_fee_per_gas.encode(out);
878        self.gas_limit.encode(out);
879        self.to.encode(out);
880        self.value.encode(out);
881        self.input.0.encode(out);
882        self.access_list.encode(out);
883        self.max_fee_per_blob_gas.encode(out);
884        self.blob_versioned_hashes.encode(out);
885    }
886}
887
888impl RlpEcdsaDecodableTx for TxEip4844 {
889    const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 };
890
891    fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
892        Ok(Self {
893            chain_id: Decodable::decode(buf)?,
894            nonce: Decodable::decode(buf)?,
895            max_priority_fee_per_gas: Decodable::decode(buf)?,
896            max_fee_per_gas: Decodable::decode(buf)?,
897            gas_limit: Decodable::decode(buf)?,
898            to: Decodable::decode(buf)?,
899            value: Decodable::decode(buf)?,
900            input: Decodable::decode(buf)?,
901            access_list: Decodable::decode(buf)?,
902            max_fee_per_blob_gas: Decodable::decode(buf)?,
903            blob_versioned_hashes: Decodable::decode(buf)?,
904        })
905    }
906}
907
908impl SignableTransaction<Signature> for TxEip4844 {
909    fn set_chain_id(&mut self, chain_id: ChainId) {
910        self.chain_id = chain_id;
911    }
912
913    fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
914        out.put_u8(Self::tx_type() as u8);
915        self.encode(out);
916    }
917
918    fn payload_len_for_signature(&self) -> usize {
919        self.length() + 1
920    }
921}
922
923impl Transaction for TxEip4844 {
924    #[inline]
925    fn chain_id(&self) -> Option<ChainId> {
926        Some(self.chain_id)
927    }
928
929    #[inline]
930    fn nonce(&self) -> u64 {
931        self.nonce
932    }
933
934    #[inline]
935    fn gas_limit(&self) -> u64 {
936        self.gas_limit
937    }
938
939    #[inline]
940    fn gas_price(&self) -> Option<u128> {
941        None
942    }
943
944    #[inline]
945    fn max_fee_per_gas(&self) -> u128 {
946        self.max_fee_per_gas
947    }
948
949    #[inline]
950    fn max_priority_fee_per_gas(&self) -> Option<u128> {
951        Some(self.max_priority_fee_per_gas)
952    }
953
954    #[inline]
955    fn max_fee_per_blob_gas(&self) -> Option<u128> {
956        Some(self.max_fee_per_blob_gas)
957    }
958
959    #[inline]
960    fn priority_fee_or_price(&self) -> u128 {
961        self.max_priority_fee_per_gas
962    }
963
964    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
965        alloy_eips::eip1559::calc_effective_gas_price(
966            self.max_fee_per_gas,
967            self.max_priority_fee_per_gas,
968            base_fee,
969        )
970    }
971
972    #[inline]
973    fn is_dynamic_fee(&self) -> bool {
974        true
975    }
976
977    #[inline]
978    fn kind(&self) -> TxKind {
979        self.to.into()
980    }
981
982    #[inline]
983    fn is_create(&self) -> bool {
984        false
985    }
986
987    #[inline]
988    fn value(&self) -> U256 {
989        self.value
990    }
991
992    #[inline]
993    fn input(&self) -> &Bytes {
994        &self.input
995    }
996
997    #[inline]
998    fn access_list(&self) -> Option<&AccessList> {
999        Some(&self.access_list)
1000    }
1001
1002    #[inline]
1003    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
1004        Some(&self.blob_versioned_hashes)
1005    }
1006
1007    #[inline]
1008    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
1009        None
1010    }
1011}
1012
1013impl Encodable for TxEip4844 {
1014    fn encode(&self, out: &mut dyn BufMut) {
1015        self.rlp_encode(out);
1016    }
1017
1018    fn length(&self) -> usize {
1019        self.rlp_encoded_length()
1020    }
1021}
1022
1023impl Decodable for TxEip4844 {
1024    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1025        Self::rlp_decode(buf)
1026    }
1027}
1028
1029impl<T> From<TxEip4844WithSidecar<T>> for TxEip4844 {
1030    /// Consumes the [TxEip4844WithSidecar] and returns the inner [TxEip4844].
1031    fn from(tx_with_sidecar: TxEip4844WithSidecar<T>) -> Self {
1032        tx_with_sidecar.tx
1033    }
1034}
1035
1036/// [EIP-4844 Blob Transaction](https://eips.ethereum.org/EIPS/eip-4844#blob-transaction)
1037///
1038/// A transaction with blob hashes and max blob fee, which also includes the
1039/// [BlobTransactionSidecar]. This is the full type sent over the network as a raw transaction. It
1040/// wraps a [TxEip4844] to include the sidecar and the ability to decode it properly.
1041///
1042/// This is defined in [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844#networking) as an element
1043/// of a `PooledTransactions` response, and is also used as the format for sending raw transactions
1044/// through the network (eth_sendRawTransaction/eth_sendTransaction).
1045#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
1046#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
1047#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1048#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1049#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
1050#[doc(alias = "Eip4844TransactionWithSidecar", alias = "Eip4844TxWithSidecar")]
1051pub struct TxEip4844WithSidecar<T = BlobTransactionSidecar> {
1052    /// The actual transaction.
1053    #[cfg_attr(feature = "serde", serde(flatten))]
1054    #[doc(alias = "transaction")]
1055    pub tx: TxEip4844,
1056    /// The sidecar.
1057    #[cfg_attr(feature = "serde", serde(flatten))]
1058    pub sidecar: T,
1059}
1060
1061impl<T> TxEip4844WithSidecar<T> {
1062    /// Constructs a new [TxEip4844WithSidecar] from a [TxEip4844] and a sidecar.
1063    #[doc(alias = "from_transaction_and_sidecar")]
1064    pub const fn from_tx_and_sidecar(tx: TxEip4844, sidecar: T) -> Self {
1065        Self { tx, sidecar }
1066    }
1067
1068    /// Get the transaction type.
1069    #[doc(alias = "transaction_type")]
1070    pub const fn tx_type() -> TxType {
1071        TxEip4844::tx_type()
1072    }
1073
1074    /// Get access to the inner tx [TxEip4844].
1075    #[doc(alias = "transaction")]
1076    pub const fn tx(&self) -> &TxEip4844 {
1077        &self.tx
1078    }
1079
1080    /// Get access to the inner sidecar.
1081    pub const fn sidecar(&self) -> &T {
1082        &self.sidecar
1083    }
1084
1085    /// Consumes the [TxEip4844WithSidecar] and returns the inner sidecar.
1086    pub fn into_sidecar(self) -> T {
1087        self.sidecar
1088    }
1089
1090    /// Consumes the [TxEip4844WithSidecar] and returns the inner [TxEip4844] and a sidecar.
1091    pub fn into_parts(self) -> (TxEip4844, T) {
1092        (self.tx, self.sidecar)
1093    }
1094
1095    /// Maps the sidecar to a new type.
1096    pub fn map_sidecar<U>(self, f: impl FnOnce(T) -> U) -> TxEip4844WithSidecar<U> {
1097        TxEip4844WithSidecar { tx: self.tx, sidecar: f(self.sidecar) }
1098    }
1099
1100    /// Maps the sidecar to a new type, returning an error if the mapping fails.
1101    pub fn try_map_sidecar<U, E>(
1102        self,
1103        f: impl FnOnce(T) -> Result<U, E>,
1104    ) -> Result<TxEip4844WithSidecar<U>, E> {
1105        Ok(TxEip4844WithSidecar { tx: self.tx, sidecar: f(self.sidecar)? })
1106    }
1107}
1108
1109impl TxEip4844WithSidecar<BlobTransactionSidecar> {
1110    /// Converts this legacy EIP-4844 sidecar into an EIP-7594 sidecar with the default settings.
1111    ///
1112    /// This requires computing cell KZG proofs from the blob data using the KZG trusted setup.
1113    /// Each blob produces `CELLS_PER_EXT_BLOB` cell proofs.
1114    #[cfg(feature = "kzg")]
1115    pub fn try_into_7594(
1116        self,
1117    ) -> Result<
1118        TxEip4844WithSidecar<alloy_eips::eip7594::BlobTransactionSidecarEip7594>,
1119        c_kzg::Error,
1120    > {
1121        self.try_into_7594_with_settings(
1122            alloy_eips::eip4844::env_settings::EnvKzgSettings::Default.get(),
1123        )
1124    }
1125
1126    /// Converts this legacy EIP-4844 sidecar into an EIP-7594 sidecar with the given settings.
1127    ///
1128    /// This requires computing cell KZG proofs from the blob data using the KZG trusted setup.
1129    /// Each blob produces `CELLS_PER_EXT_BLOB` cell proofs.
1130    #[cfg(feature = "kzg")]
1131    pub fn try_into_7594_with_settings(
1132        self,
1133        settings: &c_kzg::KzgSettings,
1134    ) -> Result<
1135        TxEip4844WithSidecar<alloy_eips::eip7594::BlobTransactionSidecarEip7594>,
1136        c_kzg::Error,
1137    > {
1138        self.try_map_sidecar(|sidecar| sidecar.try_into_7594(settings))
1139    }
1140}
1141
1142impl<T: TxEip4844Sidecar> TxEip4844WithSidecar<T> {
1143    /// Verifies that the transaction's blob data, commitments, and proofs are all valid.
1144    ///
1145    /// See also [TxEip4844::validate_blob]
1146    #[cfg(feature = "kzg")]
1147    pub fn validate_blob(
1148        &self,
1149        proof_settings: &c_kzg::KzgSettings,
1150    ) -> Result<(), BlobTransactionValidationError> {
1151        self.tx.validate_blob(&self.sidecar, proof_settings)
1152    }
1153
1154    /// Calculates a heuristic for the in-memory size of the [TxEip4844WithSidecar] transaction.
1155    #[inline]
1156    pub fn size(&self) -> usize {
1157        self.tx.size() + self.sidecar.size()
1158    }
1159}
1160
1161impl<T> SignableTransaction<Signature> for TxEip4844WithSidecar<T>
1162where
1163    T: fmt::Debug + Send + Sync + 'static,
1164{
1165    fn set_chain_id(&mut self, chain_id: ChainId) {
1166        self.tx.chain_id = chain_id;
1167    }
1168
1169    fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
1170        // A signature for a [TxEip4844WithSidecar] is a signature over the [TxEip4844] EIP-2718
1171        // payload fields:
1172        // (BLOB_TX_TYPE ||
1173        //   rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value,
1174        //     data, access_list, max_fee_per_blob_gas, blob_versioned_hashes]))
1175        self.tx.encode_for_signing(out);
1176    }
1177
1178    fn payload_len_for_signature(&self) -> usize {
1179        // The payload length is the length of the `transaction_payload_body` list.
1180        // The sidecar is NOT included.
1181        self.tx.payload_len_for_signature()
1182    }
1183}
1184
1185impl<T> Transaction for TxEip4844WithSidecar<T>
1186where
1187    T: fmt::Debug + Send + Sync + 'static,
1188{
1189    #[inline]
1190    fn chain_id(&self) -> Option<ChainId> {
1191        self.tx.chain_id()
1192    }
1193
1194    #[inline]
1195    fn nonce(&self) -> u64 {
1196        self.tx.nonce()
1197    }
1198
1199    #[inline]
1200    fn gas_limit(&self) -> u64 {
1201        self.tx.gas_limit()
1202    }
1203
1204    #[inline]
1205    fn gas_price(&self) -> Option<u128> {
1206        self.tx.gas_price()
1207    }
1208
1209    #[inline]
1210    fn max_fee_per_gas(&self) -> u128 {
1211        self.tx.max_fee_per_gas()
1212    }
1213
1214    #[inline]
1215    fn max_priority_fee_per_gas(&self) -> Option<u128> {
1216        self.tx.max_priority_fee_per_gas()
1217    }
1218
1219    #[inline]
1220    fn max_fee_per_blob_gas(&self) -> Option<u128> {
1221        self.tx.max_fee_per_blob_gas()
1222    }
1223
1224    #[inline]
1225    fn priority_fee_or_price(&self) -> u128 {
1226        self.tx.priority_fee_or_price()
1227    }
1228
1229    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
1230        self.tx.effective_gas_price(base_fee)
1231    }
1232
1233    #[inline]
1234    fn is_dynamic_fee(&self) -> bool {
1235        self.tx.is_dynamic_fee()
1236    }
1237
1238    #[inline]
1239    fn kind(&self) -> TxKind {
1240        self.tx.kind()
1241    }
1242
1243    #[inline]
1244    fn is_create(&self) -> bool {
1245        false
1246    }
1247
1248    #[inline]
1249    fn value(&self) -> U256 {
1250        self.tx.value()
1251    }
1252
1253    #[inline]
1254    fn input(&self) -> &Bytes {
1255        self.tx.input()
1256    }
1257
1258    #[inline]
1259    fn access_list(&self) -> Option<&AccessList> {
1260        Some(&self.tx.access_list)
1261    }
1262
1263    #[inline]
1264    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
1265        self.tx.blob_versioned_hashes()
1266    }
1267
1268    #[inline]
1269    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
1270        None
1271    }
1272}
1273
1274impl<T> Typed2718 for TxEip4844WithSidecar<T> {
1275    fn ty(&self) -> u8 {
1276        TxType::Eip4844 as u8
1277    }
1278}
1279
1280impl<T: Encodable7594> RlpEcdsaEncodableTx for TxEip4844WithSidecar<T> {
1281    fn rlp_encoded_fields_length(&self) -> usize {
1282        self.sidecar.encode_7594_len() + self.tx.rlp_encoded_length()
1283    }
1284
1285    fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
1286        self.tx.rlp_encode(out);
1287        self.sidecar.encode_7594(out);
1288    }
1289
1290    fn rlp_header_signed(&self, signature: &Signature) -> Header {
1291        let payload_length =
1292            self.tx.rlp_encoded_length_with_signature(signature) + self.sidecar.encode_7594_len();
1293        Header { list: true, payload_length }
1294    }
1295
1296    fn rlp_encode_signed(&self, signature: &Signature, out: &mut dyn BufMut) {
1297        self.rlp_header_signed(signature).encode(out);
1298        self.tx.rlp_encode_signed(signature, out);
1299        self.sidecar.encode_7594(out);
1300    }
1301
1302    fn tx_hash_with_type(&self, signature: &Signature, ty: u8) -> alloy_primitives::TxHash {
1303        // eip4844 tx_hash is always based on the non-sidecar encoding
1304        self.tx.tx_hash_with_type(signature, ty)
1305    }
1306}
1307
1308impl<T: Encodable7594 + Decodable7594> RlpEcdsaDecodableTx for TxEip4844WithSidecar<T> {
1309    const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 };
1310
1311    fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1312        let tx = TxEip4844::rlp_decode(buf)?;
1313        let sidecar = T::decode_7594(buf)?;
1314        Ok(Self { tx, sidecar })
1315    }
1316
1317    fn rlp_decode_with_signature(buf: &mut &[u8]) -> alloy_rlp::Result<(Self, Signature)> {
1318        let header = Header::decode(buf)?;
1319        if !header.list {
1320            return Err(alloy_rlp::Error::UnexpectedString);
1321        }
1322        let remaining = buf.len();
1323
1324        let (tx, signature) = TxEip4844::rlp_decode_with_signature(buf)?;
1325        let sidecar = T::decode_7594(buf)?;
1326
1327        if buf.len() + header.payload_length != remaining {
1328            return Err(alloy_rlp::Error::UnexpectedLength);
1329        }
1330
1331        Ok((Self { tx, sidecar }, signature))
1332    }
1333}
1334
1335#[cfg(test)]
1336mod tests {
1337    use super::{BlobTransactionSidecar, TxEip4844, TxEip4844WithSidecar};
1338    use crate::{
1339        transaction::{eip4844::TxEip4844Variant, RlpEcdsaDecodableTx},
1340        SignableTransaction, TxEnvelope,
1341    };
1342    use alloy_eips::{
1343        eip2930::AccessList, eip4844::env_settings::EnvKzgSettings,
1344        eip7594::BlobTransactionSidecarVariant, Encodable2718 as _,
1345    };
1346    use alloy_primitives::{address, b256, bytes, hex, Signature, U256};
1347    use alloy_rlp::{Decodable, Encodable};
1348    use assert_matches::assert_matches;
1349    use std::path::PathBuf;
1350
1351    #[test]
1352    fn different_sidecar_same_hash() {
1353        // this should make sure that the hash calculated for the `into_signed` conversion does not
1354        // change if the sidecar is different
1355        let tx = TxEip4844 {
1356            chain_id: 1,
1357            nonce: 1,
1358            max_priority_fee_per_gas: 1,
1359            max_fee_per_gas: 1,
1360            gas_limit: 1,
1361            to: Default::default(),
1362            value: U256::from(1),
1363            access_list: Default::default(),
1364            blob_versioned_hashes: vec![Default::default()],
1365            max_fee_per_blob_gas: 1,
1366            input: Default::default(),
1367        };
1368        let sidecar = BlobTransactionSidecar {
1369            blobs: vec![[2; 131072].into()],
1370            commitments: vec![[3; 48].into()],
1371            proofs: vec![[4; 48].into()],
1372        };
1373        let mut tx = TxEip4844WithSidecar { tx, sidecar };
1374        let signature = Signature::test_signature();
1375
1376        // turn this transaction into_signed
1377        let expected_signed = tx.clone().into_signed(signature);
1378
1379        // change the sidecar, adding a single (blob, commitment, proof) pair
1380        tx.sidecar = BlobTransactionSidecar {
1381            blobs: vec![[1; 131072].into()],
1382            commitments: vec![[1; 48].into()],
1383            proofs: vec![[1; 48].into()],
1384        };
1385
1386        // turn this transaction into_signed
1387        let actual_signed = tx.into_signed(signature);
1388
1389        // the hashes should be the same
1390        assert_eq!(expected_signed.hash(), actual_signed.hash());
1391
1392        // convert to envelopes
1393        let expected_envelope: TxEnvelope = expected_signed.into();
1394        let actual_envelope: TxEnvelope = actual_signed.into();
1395
1396        // now encode the transaction and check the length
1397        let len = expected_envelope.length();
1398        let mut buf = Vec::with_capacity(len);
1399        expected_envelope.encode(&mut buf);
1400        assert_eq!(buf.len(), len);
1401
1402        // ensure it's also the same size that `actual` claims to be, since we just changed the
1403        // sidecar values.
1404        assert_eq!(buf.len(), actual_envelope.length());
1405
1406        // now decode the transaction and check the values
1407        let decoded = TxEnvelope::decode(&mut &buf[..]).unwrap();
1408        assert_eq!(decoded, expected_envelope);
1409    }
1410
1411    #[test]
1412    fn test_4844_variant_into_signed_correct_hash() {
1413        // Taken from <https://etherscan.io/tx/0x93fc9daaa0726c3292a2e939df60f7e773c6a6a726a61ce43f4a217c64d85e87>
1414        let tx =
1415            TxEip4844 {
1416                chain_id: 1,
1417                nonce: 15435,
1418                gas_limit: 8000000,
1419                max_fee_per_gas: 10571233596,
1420                max_priority_fee_per_gas: 1000000000,
1421                to: address!("a8cb082a5a689e0d594d7da1e2d72a3d63adc1bd"),
1422                value: U256::ZERO,
1423                access_list: AccessList::default(),
1424                blob_versioned_hashes: vec![
1425                    b256!("01e5276d91ac1ddb3b1c2d61295211220036e9a04be24c00f76916cc2659d004"),
1426                    b256!("0128eb58aff09fd3a7957cd80aa86186d5849569997cdfcfa23772811b706cc2"),
1427                ],
1428                max_fee_per_blob_gas: 1,
1429                input: bytes!("701f58c50000000000000000000000000000000000000000000000000000000000073fb1ed12e288def5b439ea074b398dbb4c967f2852baac3238c5fe4b62b871a59a6d00000000000000000000000000000000000000000000000000000000123971da000000000000000000000000000000000000000000000000000000000000000ac39b2a24e1dbdd11a1e7bd7c0f4dfd7d9b9cfa0997d033ad05f961ba3b82c6c83312c967f10daf5ed2bffe309249416e03ee0b101f2b84d2102b9e38b0e4dfdf0000000000000000000000000000000000000000000000000000000066254c8b538dcc33ecf5334bbd294469f9d4fd084a3090693599a46d6c62567747cbc8660000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000073fb20000000000000000000000000000000000000000000000000000000066254da10000000000000000000000000000000000000000000000000000000012397d5e20b09b263779fda4171c341e720af8fa469621ff548651f8dbbc06c2d320400c000000000000000000000000000000000000000000000000000000000000000b50a833bb11af92814e99c6ff7cf7ba7042827549d6f306a04270753702d897d8fc3c411b99159939ac1c16d21d3057ddc8b2333d1331ab34c938cff0eb29ce2e43241c170344db6819f76b1f1e0ab8206f3ec34120312d275c4f5bbea7f5c55700000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000480000000000000000000000000000000000000000000000000000000000000031800000000000000000000000000000000000000000000800b0000000000000000000000000000000000000000000000000000000000000004ed12e288def5b439ea074b398dbb4c967f2852baac3238c5fe4b62b871a59a6d00000ca8000000000000000000000000000000000000800b000000000000000000000000000000000000000000000000000000000000000300000000000000000000000066254da100000000000000000000000066254e9d00010ca80000000000000000000000000000000000008001000000000000000000000000000000000000000000000000000000000000000550a833bb11af92814e99c6ff7cf7ba7042827549d6f306a04270753702d897d800010ca800000000000000000000000000000000000080010000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000b00010ca8000000000000000000000000000000000000801100000000000000000000000000000000000000000000000000000000000000075c1cd5bd0fd333ce9d7c8edfc79f43b8f345b4a394f6aba12a2cc78ce4012ed700010ca80000000000000000000000000000000000008011000000000000000000000000000000000000000000000000000000000000000845392775318aa47beaafbdc827da38c9f1e88c3bdcabba2cb493062e17cbf21e00010ca800000000000000000000000000000000000080080000000000000000000000000000000000000000000000000000000000000000c094e20e7ac9b433f44a5885e3bdc07e51b309aeb993caa24ba84a661ac010c100010ca800000000000000000000000000000000000080080000000000000000000000000000000000000000000000000000000000000001ab42db8f4ed810bdb143368a2b641edf242af6e3d0de8b1486e2b0e7880d431100010ca8000000000000000000000000000000000000800800000000000000000000000000000000000000000000000000000000000000022d94e4cc4525e4e2d81e8227b6172e97076431a2cf98792d978035edd6e6f3100000000000000000000000000000000000000000000000000000000000000000000000000000012101c74dfb80a80fccb9a4022b2406f79f56305e6a7c931d30140f5d372fe793837e93f9ec6b8d89a9d0ab222eeb27547f66b90ec40fbbdd2a4936b0b0c19ca684ff78888fbf5840d7c8dc3c493b139471750938d7d2c443e2d283e6c5ee9fde3765a756542c42f002af45c362b4b5b1687a8fc24cbf16532b903f7bb289728170dcf597f5255508c623ba247735538376f494cdcdd5bd0c4cb067526eeda0f4745a28d8baf8893ecc1b8cee80690538d66455294a028da03ff2add9d8a88e6ee03ba9ffe3ad7d91d6ac9c69a1f28c468f00fe55eba5651a2b32dc2458e0d14b4dd6d0173df255cd56aa01e8e38edec17ea8933f68543cbdc713279d195551d4211bed5c91f77259a695e6768f6c4b110b2158fcc42423a96dcc4e7f6fddb3e2369d00000000000000000000000000000000000000000000000000000000000000") };
1430        let variant = TxEip4844Variant::<BlobTransactionSidecar>::TxEip4844(tx);
1431
1432        let signature = Signature::new(
1433            b256!("6c173c3c8db3e3299f2f728d293b912c12e75243e3aa66911c2329b58434e2a4").into(),
1434            b256!("7dd4d1c228cedc5a414a668ab165d9e888e61e4c3b44cd7daf9cdcc4cec5d6b2").into(),
1435            false,
1436        );
1437
1438        let signed = variant.into_signed(signature);
1439        assert_eq!(
1440            *signed.hash(),
1441            b256!("93fc9daaa0726c3292a2e939df60f7e773c6a6a726a61ce43f4a217c64d85e87")
1442        );
1443    }
1444
1445    #[test]
1446    fn decode_raw_7594_rlp() {
1447        let kzg_settings = EnvKzgSettings::default();
1448        let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/7594rlp");
1449        let dir = std::fs::read_dir(path).expect("Unable to read folder");
1450        for entry in dir {
1451            let entry = entry.unwrap();
1452            let content = std::fs::read_to_string(entry.path()).unwrap();
1453            let raw = hex::decode(content.trim()).unwrap();
1454            let tx = TxEip4844WithSidecar::<BlobTransactionSidecarVariant>::eip2718_decode(
1455                &mut raw.as_ref(),
1456            )
1457            .map_err(|err| {
1458                panic!("Failed to decode transaction: {:?} {:?}", err, entry.path());
1459            })
1460            .unwrap();
1461
1462            // Test roundtrip
1463            let encoded = tx.encoded_2718();
1464            assert_eq!(encoded.as_slice(), &raw[..], "{:?}", entry.path());
1465
1466            let TxEip4844WithSidecar { tx, sidecar } = tx.tx();
1467            assert_matches!(sidecar, BlobTransactionSidecarVariant::Eip7594(_));
1468
1469            let result = sidecar.validate(&tx.blob_versioned_hashes, kzg_settings.get());
1470            assert_matches!(result, Ok(()));
1471        }
1472    }
1473
1474    #[test]
1475    fn decode_raw_7594_rlp_invalid() {
1476        let kzg_settings = EnvKzgSettings::default();
1477        let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/7594rlp-invalid");
1478        let dir = std::fs::read_dir(path).expect("Unable to read folder");
1479        for entry in dir {
1480            let entry = entry.unwrap();
1481
1482            if entry.path().file_name().and_then(|f| f.to_str()) == Some("0.rlp") {
1483                continue;
1484            }
1485
1486            let content = std::fs::read_to_string(entry.path()).unwrap();
1487            let raw = hex::decode(content.trim()).unwrap();
1488            let tx = TxEip4844WithSidecar::<BlobTransactionSidecarVariant>::eip2718_decode(
1489                &mut raw.as_ref(),
1490            )
1491            .map_err(|err| {
1492                panic!("Failed to decode transaction: {:?} {:?}", err, entry.path());
1493            })
1494            .unwrap();
1495
1496            // Test roundtrip
1497            let encoded = tx.encoded_2718();
1498            assert_eq!(encoded.as_slice(), &raw[..], "{:?}", entry.path());
1499
1500            let TxEip4844WithSidecar { tx, sidecar } = tx.tx();
1501            assert_matches!(sidecar, BlobTransactionSidecarVariant::Eip7594(_));
1502
1503            let result = sidecar.validate(&tx.blob_versioned_hashes, kzg_settings.get());
1504            assert_matches!(result, Err(_));
1505        }
1506    }
1507}