Skip to main content

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