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