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#[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 TxEip4844(TxEip4844),
35 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 #[doc(alias = "transaction_type")]
164 pub const fn tx_type() -> TxType {
165 TxType::Eip4844
166 }
167
168 #[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 pub fn take_sidecar(&mut self) -> Option<T> {
181 let placeholder = Self::TxEip4844(TxEip4844::default());
183 match core::mem::replace(self, placeholder) {
184 tx @ Self::TxEip4844(_) => {
185 *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 pub fn strip_sidecar(self) -> (Self, Option<T>) {
224 self.strip_sidecar_into()
225 }
226
227 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 pub fn drop_sidecar(self) -> Self {
287 self.strip_sidecar().0
288 }
289
290 pub fn drop_sidecar_into<U>(self) -> TxEip4844Variant<U> {
312 self.strip_sidecar_into().0
313 }
314
315 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 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 pub const fn sidecar(&self) -> Option<&T> {
334 match self {
335 Self::TxEip4844WithSidecar(tx) => Some(tx.sidecar()),
336 _ => None,
337 }
338 }
339
340 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 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 #[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 #[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 #[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 #[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 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 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 let trial = &mut &**buf;
644
645 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 let needle = &mut &**buf;
664
665 let trial = &mut &**buf;
668
669 Header::decode(needle)?;
671
672 if Header::decode(needle).is_ok_and(|h| h.list) {
679 if let Ok((tx, signature)) = TxEip4844WithSidecar::rlp_decode_with_signature(trial) {
680 *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 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#[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 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
743 pub chain_id: ChainId,
744 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
746 pub nonce: u64,
747 #[cfg_attr(
753 feature = "serde",
754 serde(with = "alloy_serde::quantity", rename = "gas", alias = "gasLimit")
755 )]
756 pub gas_limit: u64,
757 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
767 pub max_fee_per_gas: u128,
768 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
776 pub max_priority_fee_per_gas: u128,
777 pub to: Address,
779 pub value: U256,
784 pub access_list: AccessList,
790
791 pub blob_versioned_hashes: Vec<B256>,
793
794 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
798 pub max_fee_per_blob_gas: u128,
799
800 pub input: Bytes,
806}
807
808impl TxEip4844 {
809 #[inline]
811 pub const fn blob_gas(&self) -> u64 {
812 self.blob_versioned_hashes.len() as u64 * DATA_GAS_PER_BLOB
814 }
815
816 #[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 #[doc(alias = "transaction_type")]
840 pub const fn tx_type() -> TxType {
841 TxType::Eip4844
842 }
843
844 pub const fn with_sidecar<T>(self, sidecar: T) -> TxEip4844WithSidecar<T> {
846 TxEip4844WithSidecar::from_tx_and_sidecar(self, sidecar)
847 }
848
849 #[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 fn from(tx_with_sidecar: TxEip4844WithSidecar<T>) -> Self {
1033 tx_with_sidecar.tx
1034 }
1035}
1036
1037#[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 #[cfg_attr(feature = "serde", serde(flatten))]
1055 #[doc(alias = "transaction")]
1056 pub tx: TxEip4844,
1057 #[cfg_attr(feature = "serde", serde(flatten))]
1059 pub sidecar: T,
1060}
1061
1062impl<T> TxEip4844WithSidecar<T> {
1063 #[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 #[doc(alias = "transaction_type")]
1071 pub const fn tx_type() -> TxType {
1072 TxEip4844::tx_type()
1073 }
1074
1075 #[doc(alias = "transaction")]
1077 pub const fn tx(&self) -> &TxEip4844 {
1078 &self.tx
1079 }
1080
1081 pub const fn sidecar(&self) -> &T {
1083 &self.sidecar
1084 }
1085
1086 pub fn into_sidecar(self) -> T {
1088 self.sidecar
1089 }
1090
1091 pub fn into_parts(self) -> (TxEip4844, T) {
1093 (self.tx, self.sidecar)
1094 }
1095
1096 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 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 #[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 #[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 #[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 #[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 self.tx.encode_for_signing(out);
1177 }
1178
1179 fn payload_len_for_signature(&self) -> usize {
1180 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 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 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 let expected_signed = tx.clone().into_signed(signature);
1379
1380 tx.sidecar = BlobTransactionSidecar {
1382 blobs: vec![[1; 131072].into()],
1383 commitments: vec![[1; 48].into()],
1384 proofs: vec![[1; 48].into()],
1385 };
1386
1387 let actual_signed = tx.into_signed(signature);
1389
1390 assert_eq!(expected_signed.hash(), actual_signed.hash());
1392
1393 let expected_envelope: TxEnvelope = expected_signed.into();
1395 let actual_envelope: TxEnvelope = actual_signed.into();
1396
1397 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 assert_eq!(buf.len(), actual_envelope.length());
1406
1407 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 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 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 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}