1use super::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx, TxEip4844Sidecar};
2use crate::{SignableTransaction, Signed, Transaction, TxType};
3use alloc::vec::Vec;
4use alloy_eips::{
5 eip2718::IsTyped2718,
6 eip2930::AccessList,
7 eip4844::{BlobTransactionSidecar, DATA_GAS_PER_BLOB},
8 eip7594::{Decodable7594, Encodable7594},
9 eip7702::SignedAuthorization,
10 Typed2718,
11};
12use alloy_primitives::{Address, Bytes, ChainId, Signature, TxKind, B256, U256};
13use alloy_rlp::{BufMut, Decodable, Encodable, Header};
14use core::{fmt, mem};
15
16#[cfg(feature = "kzg")]
17use alloy_eips::eip4844::BlobTransactionValidationError;
18use alloy_eips::eip7594::{BlobTransactionSidecarEip7594, BlobTransactionSidecarVariant};
19
20#[derive(Clone, Debug, PartialEq, Eq, Hash)]
27#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
28#[cfg_attr(feature = "serde", derive(serde::Serialize))]
29#[cfg_attr(feature = "serde", serde(untagged))]
30#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
31#[doc(alias = "Eip4844TransactionVariant")]
32pub enum TxEip4844Variant<T = BlobTransactionSidecar> {
33 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<T> From<TxEip4844Variant<T>> for TxEip4844 {
113 fn from(tx: TxEip4844Variant<T>) -> Self {
114 match tx {
115 TxEip4844Variant::TxEip4844(tx) => tx,
116 TxEip4844Variant::TxEip4844WithSidecar(tx) => tx.tx,
117 }
118 }
119}
120
121impl<T> AsRef<TxEip4844> for TxEip4844Variant<T> {
122 fn as_ref(&self) -> &TxEip4844 {
123 match self {
124 Self::TxEip4844(tx) => tx,
125 Self::TxEip4844WithSidecar(tx) => &tx.tx,
126 }
127 }
128}
129
130impl<T> AsMut<TxEip4844> for TxEip4844Variant<T> {
131 fn as_mut(&mut self) -> &mut TxEip4844 {
132 match self {
133 Self::TxEip4844(tx) => tx,
134 Self::TxEip4844WithSidecar(tx) => &mut tx.tx,
135 }
136 }
137}
138
139impl AsRef<Self> for TxEip4844 {
140 fn as_ref(&self) -> &Self {
141 self
142 }
143}
144
145impl AsMut<Self> for TxEip4844 {
146 fn as_mut(&mut self) -> &mut Self {
147 self
148 }
149}
150
151impl<T> TxEip4844Variant<T> {
152 #[doc(alias = "transaction_type")]
154 pub const fn tx_type() -> TxType {
155 TxType::Eip4844
156 }
157
158 #[doc(alias = "transaction")]
160 pub const fn tx(&self) -> &TxEip4844 {
161 match self {
162 Self::TxEip4844(tx) => tx,
163 Self::TxEip4844WithSidecar(tx) => tx.tx(),
164 }
165 }
166
167 pub fn take_sidecar(&mut self) -> Option<T> {
171 let placeholder = Self::TxEip4844(TxEip4844::default());
173 match mem::replace(self, placeholder) {
174 tx @ Self::TxEip4844(_) => {
175 *self = tx;
177 None
178 }
179 Self::TxEip4844WithSidecar(tx) => {
180 let (tx, sidecar) = tx.into_parts();
181 *self = Self::TxEip4844(tx);
182 Some(sidecar)
183 }
184 }
185 }
186
187 pub fn strip_sidecar(self) -> (Self, Option<T>) {
214 self.strip_sidecar_into()
215 }
216
217 pub fn strip_sidecar_into<U>(self) -> (TxEip4844Variant<U>, Option<T>) {
248 match self {
249 Self::TxEip4844(tx) => (TxEip4844Variant::TxEip4844(tx), None),
250 Self::TxEip4844WithSidecar(tx) => {
251 let (tx, sidecar) = tx.into_parts();
252 (TxEip4844Variant::TxEip4844(tx), Some(sidecar))
253 }
254 }
255 }
256
257 pub fn drop_sidecar(self) -> Self {
277 self.strip_sidecar().0
278 }
279
280 pub fn drop_sidecar_into<U>(self) -> TxEip4844Variant<U> {
302 self.strip_sidecar_into().0
303 }
304
305 pub const fn as_with_sidecar(&self) -> Option<&TxEip4844WithSidecar<T>> {
307 match self {
308 Self::TxEip4844WithSidecar(tx) => Some(tx),
309 _ => None,
310 }
311 }
312
313 pub fn try_into_4844_with_sidecar(self) -> Result<TxEip4844WithSidecar<T>, Self> {
316 match self {
317 Self::TxEip4844WithSidecar(tx) => Ok(tx),
318 _ => Err(self),
319 }
320 }
321
322 pub const fn sidecar(&self) -> Option<&T> {
324 match self {
325 Self::TxEip4844WithSidecar(tx) => Some(tx.sidecar()),
326 _ => None,
327 }
328 }
329
330 pub fn map_sidecar<U>(self, f: impl FnOnce(T) -> U) -> TxEip4844Variant<U> {
332 match self {
333 Self::TxEip4844(tx) => TxEip4844Variant::TxEip4844(tx),
334 Self::TxEip4844WithSidecar(tx) => {
335 TxEip4844Variant::TxEip4844WithSidecar(tx.map_sidecar(f))
336 }
337 }
338 }
339
340 pub fn try_map_sidecar<U, E>(
342 self,
343 f: impl FnOnce(T) -> Result<U, E>,
344 ) -> Result<TxEip4844Variant<U>, E> {
345 match self {
346 Self::TxEip4844(tx) => Ok(TxEip4844Variant::TxEip4844(tx)),
347 Self::TxEip4844WithSidecar(tx) => {
348 tx.try_map_sidecar(f).map(TxEip4844Variant::TxEip4844WithSidecar)
349 }
350 }
351 }
352}
353
354impl<T: TxEip4844Sidecar> TxEip4844Variant<T> {
355 #[cfg(feature = "kzg")]
359 pub fn validate(
360 &self,
361 proof_settings: &c_kzg::KzgSettings,
362 ) -> Result<(), BlobTransactionValidationError> {
363 match self {
364 Self::TxEip4844(_) => Err(BlobTransactionValidationError::MissingSidecar),
365 Self::TxEip4844WithSidecar(tx) => tx.validate_blob(proof_settings),
366 }
367 }
368
369 #[inline]
371 pub fn size(&self) -> usize {
372 match self {
373 Self::TxEip4844(tx) => tx.size(),
374 Self::TxEip4844WithSidecar(tx) => tx.size(),
375 }
376 }
377}
378
379impl TxEip4844Variant<BlobTransactionSidecar> {
380 #[cfg(feature = "kzg")]
385 pub fn try_into_7594(
386 self,
387 ) -> Result<TxEip4844Variant<alloy_eips::eip7594::BlobTransactionSidecarEip7594>, c_kzg::Error>
388 {
389 self.try_into_7594_with_settings(
390 alloy_eips::eip4844::env_settings::EnvKzgSettings::Default.get(),
391 )
392 }
393
394 #[cfg(feature = "kzg")]
399 pub fn try_into_7594_with_settings(
400 self,
401 settings: &c_kzg::KzgSettings,
402 ) -> Result<TxEip4844Variant<alloy_eips::eip7594::BlobTransactionSidecarEip7594>, c_kzg::Error>
403 {
404 self.try_map_sidecar(|sidecar| sidecar.try_into_7594(settings))
405 }
406}
407
408#[cfg(feature = "kzg")]
409impl TxEip4844Variant<alloy_eips::eip7594::BlobTransactionSidecarVariant> {
410 pub fn try_convert_into_eip7594(self) -> Result<Self, c_kzg::Error> {
422 self.try_convert_into_eip7594_with_settings(
423 alloy_eips::eip4844::env_settings::EnvKzgSettings::Default.get(),
424 )
425 }
426
427 pub fn try_convert_into_eip7594_with_settings(
444 self,
445 settings: &c_kzg::KzgSettings,
446 ) -> Result<Self, c_kzg::Error> {
447 self.try_map_sidecar(|sidecar| sidecar.try_convert_into_eip7594_with_settings(settings))
448 }
449}
450
451impl<T> Transaction for TxEip4844Variant<T>
452where
453 T: fmt::Debug + Send + Sync + 'static,
454{
455 #[inline]
456 fn chain_id(&self) -> Option<ChainId> {
457 match self {
458 Self::TxEip4844(tx) => Some(tx.chain_id),
459 Self::TxEip4844WithSidecar(tx) => Some(tx.tx().chain_id),
460 }
461 }
462
463 #[inline]
464 fn nonce(&self) -> u64 {
465 match self {
466 Self::TxEip4844(tx) => tx.nonce,
467 Self::TxEip4844WithSidecar(tx) => tx.tx().nonce,
468 }
469 }
470
471 #[inline]
472 fn gas_limit(&self) -> u64 {
473 match self {
474 Self::TxEip4844(tx) => tx.gas_limit,
475 Self::TxEip4844WithSidecar(tx) => tx.tx().gas_limit,
476 }
477 }
478
479 #[inline]
480 fn gas_price(&self) -> Option<u128> {
481 None
482 }
483
484 #[inline]
485 fn max_fee_per_gas(&self) -> u128 {
486 match self {
487 Self::TxEip4844(tx) => tx.max_fee_per_gas(),
488 Self::TxEip4844WithSidecar(tx) => tx.max_fee_per_gas(),
489 }
490 }
491
492 #[inline]
493 fn max_priority_fee_per_gas(&self) -> Option<u128> {
494 match self {
495 Self::TxEip4844(tx) => tx.max_priority_fee_per_gas(),
496 Self::TxEip4844WithSidecar(tx) => tx.max_priority_fee_per_gas(),
497 }
498 }
499
500 #[inline]
501 fn max_fee_per_blob_gas(&self) -> Option<u128> {
502 match self {
503 Self::TxEip4844(tx) => tx.max_fee_per_blob_gas(),
504 Self::TxEip4844WithSidecar(tx) => tx.max_fee_per_blob_gas(),
505 }
506 }
507
508 #[inline]
509 fn priority_fee_or_price(&self) -> u128 {
510 match self {
511 Self::TxEip4844(tx) => tx.priority_fee_or_price(),
512 Self::TxEip4844WithSidecar(tx) => tx.priority_fee_or_price(),
513 }
514 }
515
516 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
517 match self {
518 Self::TxEip4844(tx) => tx.effective_gas_price(base_fee),
519 Self::TxEip4844WithSidecar(tx) => tx.effective_gas_price(base_fee),
520 }
521 }
522
523 #[inline]
524 fn is_dynamic_fee(&self) -> bool {
525 match self {
526 Self::TxEip4844(tx) => tx.is_dynamic_fee(),
527 Self::TxEip4844WithSidecar(tx) => tx.is_dynamic_fee(),
528 }
529 }
530
531 #[inline]
532 fn kind(&self) -> TxKind {
533 match self {
534 Self::TxEip4844(tx) => tx.to,
535 Self::TxEip4844WithSidecar(tx) => tx.tx.to,
536 }
537 .into()
538 }
539
540 #[inline]
541 fn is_create(&self) -> bool {
542 false
543 }
544
545 #[inline]
546 fn value(&self) -> U256 {
547 match self {
548 Self::TxEip4844(tx) => tx.value,
549 Self::TxEip4844WithSidecar(tx) => tx.tx.value,
550 }
551 }
552
553 #[inline]
554 fn input(&self) -> &Bytes {
555 match self {
556 Self::TxEip4844(tx) => tx.input(),
557 Self::TxEip4844WithSidecar(tx) => tx.tx().input(),
558 }
559 }
560
561 #[inline]
562 fn access_list(&self) -> Option<&AccessList> {
563 match self {
564 Self::TxEip4844(tx) => tx.access_list(),
565 Self::TxEip4844WithSidecar(tx) => tx.access_list(),
566 }
567 }
568
569 #[inline]
570 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
571 match self {
572 Self::TxEip4844(tx) => tx.blob_versioned_hashes(),
573 Self::TxEip4844WithSidecar(tx) => tx.blob_versioned_hashes(),
574 }
575 }
576
577 #[inline]
578 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
579 None
580 }
581}
582impl Typed2718 for TxEip4844 {
583 fn ty(&self) -> u8 {
584 TxType::Eip4844 as u8
585 }
586}
587
588impl<T: Encodable7594> RlpEcdsaEncodableTx for TxEip4844Variant<T> {
589 fn rlp_encoded_fields_length(&self) -> usize {
590 match self {
591 Self::TxEip4844(inner) => inner.rlp_encoded_fields_length(),
592 Self::TxEip4844WithSidecar(inner) => inner.rlp_encoded_fields_length(),
593 }
594 }
595
596 fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
597 match self {
598 Self::TxEip4844(inner) => inner.rlp_encode_fields(out),
599 Self::TxEip4844WithSidecar(inner) => inner.rlp_encode_fields(out),
600 }
601 }
602
603 fn rlp_header_signed(&self, signature: &Signature) -> Header {
604 match self {
605 Self::TxEip4844(inner) => inner.rlp_header_signed(signature),
606 Self::TxEip4844WithSidecar(inner) => inner.rlp_header_signed(signature),
607 }
608 }
609
610 fn rlp_encode_signed(&self, signature: &Signature, out: &mut dyn BufMut) {
611 match self {
612 Self::TxEip4844(inner) => inner.rlp_encode_signed(signature, out),
613 Self::TxEip4844WithSidecar(inner) => inner.rlp_encode_signed(signature, out),
614 }
615 }
616
617 fn tx_hash_with_type(&self, signature: &Signature, ty: u8) -> alloy_primitives::TxHash {
618 match self {
619 Self::TxEip4844(inner) => inner.tx_hash_with_type(signature, ty),
620 Self::TxEip4844WithSidecar(inner) => inner.tx_hash_with_type(signature, ty),
621 }
622 }
623}
624
625impl<T: Encodable7594 + Decodable7594> RlpEcdsaDecodableTx for TxEip4844Variant<T> {
626 const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 };
627
628 fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
629 let needle = &mut &**buf;
630
631 let trial = &mut &**buf;
634
635 if Header::decode(needle).is_ok_and(|h| h.list) {
642 if let Ok(tx) = TxEip4844WithSidecar::rlp_decode_fields(trial) {
643 *buf = *trial;
644 return Ok(tx.into());
645 }
646 }
647 TxEip4844::rlp_decode_fields(buf).map(Into::into)
648 }
649
650 fn rlp_decode_with_signature(buf: &mut &[u8]) -> alloy_rlp::Result<(Self, Signature)> {
651 let needle = &mut &**buf;
654
655 let trial = &mut &**buf;
658
659 Header::decode(needle)?;
661
662 if Header::decode(needle).is_ok_and(|h| h.list) {
669 if let Ok((tx, signature)) = TxEip4844WithSidecar::rlp_decode_with_signature(trial) {
670 *buf = *trial;
673 return Ok((tx.into(), signature));
674 }
675 }
676 TxEip4844::rlp_decode_with_signature(buf).map(|(tx, signature)| (tx.into(), signature))
677 }
678}
679
680impl<T> Typed2718 for TxEip4844Variant<T> {
681 fn ty(&self) -> u8 {
682 TxType::Eip4844 as u8
683 }
684}
685
686impl IsTyped2718 for TxEip4844 {
687 fn is_type(type_id: u8) -> bool {
688 matches!(type_id, 0x03)
689 }
690}
691
692impl<T> SignableTransaction<Signature> for TxEip4844Variant<T>
693where
694 T: fmt::Debug + Send + Sync + 'static,
695{
696 fn set_chain_id(&mut self, chain_id: ChainId) {
697 match self {
698 Self::TxEip4844(inner) => {
699 inner.set_chain_id(chain_id);
700 }
701 Self::TxEip4844WithSidecar(inner) => {
702 inner.set_chain_id(chain_id);
703 }
704 }
705 }
706
707 fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
708 self.tx().encode_for_signing(out);
714 }
715
716 fn payload_len_for_signature(&self) -> usize {
717 self.tx().payload_len_for_signature()
718 }
719}
720
721#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
725#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
726#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
727#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
728#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
729#[doc(alias = "Eip4844Transaction", alias = "TransactionEip4844", alias = "Eip4844Tx")]
730pub struct TxEip4844 {
731 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
733 pub chain_id: ChainId,
734 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
736 pub nonce: u64,
737 #[cfg_attr(
743 feature = "serde",
744 serde(with = "alloy_serde::quantity", rename = "gas", alias = "gasLimit")
745 )]
746 pub gas_limit: u64,
747 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
759 pub max_fee_per_gas: u128,
760 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
768 pub max_priority_fee_per_gas: u128,
769 pub to: Address,
771 pub value: U256,
776 pub access_list: AccessList,
782
783 pub blob_versioned_hashes: Vec<B256>,
785
786 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
790 pub max_fee_per_blob_gas: u128,
791
792 pub input: Bytes,
798}
799
800impl TxEip4844 {
801 #[inline]
803 pub const fn blob_gas(&self) -> u64 {
804 self.blob_versioned_hashes.len() as u64 * DATA_GAS_PER_BLOB
806 }
807
808 #[cfg(feature = "kzg")]
822 pub fn validate_blob<T: TxEip4844Sidecar>(
823 &self,
824 sidecar: &T,
825 proof_settings: &c_kzg::KzgSettings,
826 ) -> Result<(), BlobTransactionValidationError> {
827 sidecar.validate(&self.blob_versioned_hashes, proof_settings)
828 }
829
830 #[doc(alias = "transaction_type")]
832 pub const fn tx_type() -> TxType {
833 TxType::Eip4844
834 }
835
836 pub const fn with_sidecar<T>(self, sidecar: T) -> TxEip4844WithSidecar<T> {
838 TxEip4844WithSidecar::from_tx_and_sidecar(self, sidecar)
839 }
840
841 #[inline]
843 pub fn size(&self) -> usize {
844 mem::size_of::<ChainId>() + mem::size_of::<u64>() + mem::size_of::<u64>() + mem::size_of::<u128>() + mem::size_of::<u128>() + mem::size_of::<Address>() + mem::size_of::<U256>() + self.access_list.size() + self.input.len() + self.blob_versioned_hashes.capacity() * mem::size_of::<B256>() + mem::size_of::<u128>() }
856}
857
858impl RlpEcdsaEncodableTx for TxEip4844 {
859 fn rlp_encoded_fields_length(&self) -> usize {
860 self.chain_id.length()
861 + self.nonce.length()
862 + self.gas_limit.length()
863 + self.max_fee_per_gas.length()
864 + self.max_priority_fee_per_gas.length()
865 + self.to.length()
866 + self.value.length()
867 + self.access_list.length()
868 + self.blob_versioned_hashes.length()
869 + self.max_fee_per_blob_gas.length()
870 + self.input.0.length()
871 }
872
873 fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
874 self.chain_id.encode(out);
875 self.nonce.encode(out);
876 self.max_priority_fee_per_gas.encode(out);
877 self.max_fee_per_gas.encode(out);
878 self.gas_limit.encode(out);
879 self.to.encode(out);
880 self.value.encode(out);
881 self.input.0.encode(out);
882 self.access_list.encode(out);
883 self.max_fee_per_blob_gas.encode(out);
884 self.blob_versioned_hashes.encode(out);
885 }
886}
887
888impl RlpEcdsaDecodableTx for TxEip4844 {
889 const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 };
890
891 fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
892 Ok(Self {
893 chain_id: Decodable::decode(buf)?,
894 nonce: Decodable::decode(buf)?,
895 max_priority_fee_per_gas: Decodable::decode(buf)?,
896 max_fee_per_gas: Decodable::decode(buf)?,
897 gas_limit: Decodable::decode(buf)?,
898 to: Decodable::decode(buf)?,
899 value: Decodable::decode(buf)?,
900 input: Decodable::decode(buf)?,
901 access_list: Decodable::decode(buf)?,
902 max_fee_per_blob_gas: Decodable::decode(buf)?,
903 blob_versioned_hashes: Decodable::decode(buf)?,
904 })
905 }
906}
907
908impl SignableTransaction<Signature> for TxEip4844 {
909 fn set_chain_id(&mut self, chain_id: ChainId) {
910 self.chain_id = chain_id;
911 }
912
913 fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
914 out.put_u8(Self::tx_type() as u8);
915 self.encode(out);
916 }
917
918 fn payload_len_for_signature(&self) -> usize {
919 self.length() + 1
920 }
921}
922
923impl Transaction for TxEip4844 {
924 #[inline]
925 fn chain_id(&self) -> Option<ChainId> {
926 Some(self.chain_id)
927 }
928
929 #[inline]
930 fn nonce(&self) -> u64 {
931 self.nonce
932 }
933
934 #[inline]
935 fn gas_limit(&self) -> u64 {
936 self.gas_limit
937 }
938
939 #[inline]
940 fn gas_price(&self) -> Option<u128> {
941 None
942 }
943
944 #[inline]
945 fn max_fee_per_gas(&self) -> u128 {
946 self.max_fee_per_gas
947 }
948
949 #[inline]
950 fn max_priority_fee_per_gas(&self) -> Option<u128> {
951 Some(self.max_priority_fee_per_gas)
952 }
953
954 #[inline]
955 fn max_fee_per_blob_gas(&self) -> Option<u128> {
956 Some(self.max_fee_per_blob_gas)
957 }
958
959 #[inline]
960 fn priority_fee_or_price(&self) -> u128 {
961 self.max_priority_fee_per_gas
962 }
963
964 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
965 alloy_eips::eip1559::calc_effective_gas_price(
966 self.max_fee_per_gas,
967 self.max_priority_fee_per_gas,
968 base_fee,
969 )
970 }
971
972 #[inline]
973 fn is_dynamic_fee(&self) -> bool {
974 true
975 }
976
977 #[inline]
978 fn kind(&self) -> TxKind {
979 self.to.into()
980 }
981
982 #[inline]
983 fn is_create(&self) -> bool {
984 false
985 }
986
987 #[inline]
988 fn value(&self) -> U256 {
989 self.value
990 }
991
992 #[inline]
993 fn input(&self) -> &Bytes {
994 &self.input
995 }
996
997 #[inline]
998 fn access_list(&self) -> Option<&AccessList> {
999 Some(&self.access_list)
1000 }
1001
1002 #[inline]
1003 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
1004 Some(&self.blob_versioned_hashes)
1005 }
1006
1007 #[inline]
1008 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
1009 None
1010 }
1011}
1012
1013impl Encodable for TxEip4844 {
1014 fn encode(&self, out: &mut dyn BufMut) {
1015 self.rlp_encode(out);
1016 }
1017
1018 fn length(&self) -> usize {
1019 self.rlp_encoded_length()
1020 }
1021}
1022
1023impl Decodable for TxEip4844 {
1024 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1025 Self::rlp_decode(buf)
1026 }
1027}
1028
1029impl<T> From<TxEip4844WithSidecar<T>> for TxEip4844 {
1030 fn from(tx_with_sidecar: TxEip4844WithSidecar<T>) -> Self {
1032 tx_with_sidecar.tx
1033 }
1034}
1035
1036#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
1046#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
1047#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1048#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1049#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
1050#[doc(alias = "Eip4844TransactionWithSidecar", alias = "Eip4844TxWithSidecar")]
1051pub struct TxEip4844WithSidecar<T = BlobTransactionSidecar> {
1052 #[cfg_attr(feature = "serde", serde(flatten))]
1054 #[doc(alias = "transaction")]
1055 pub tx: TxEip4844,
1056 #[cfg_attr(feature = "serde", serde(flatten))]
1058 pub sidecar: T,
1059}
1060
1061impl<T> TxEip4844WithSidecar<T> {
1062 #[doc(alias = "from_transaction_and_sidecar")]
1064 pub const fn from_tx_and_sidecar(tx: TxEip4844, sidecar: T) -> Self {
1065 Self { tx, sidecar }
1066 }
1067
1068 #[doc(alias = "transaction_type")]
1070 pub const fn tx_type() -> TxType {
1071 TxEip4844::tx_type()
1072 }
1073
1074 #[doc(alias = "transaction")]
1076 pub const fn tx(&self) -> &TxEip4844 {
1077 &self.tx
1078 }
1079
1080 pub const fn sidecar(&self) -> &T {
1082 &self.sidecar
1083 }
1084
1085 pub fn into_sidecar(self) -> T {
1087 self.sidecar
1088 }
1089
1090 pub fn into_parts(self) -> (TxEip4844, T) {
1092 (self.tx, self.sidecar)
1093 }
1094
1095 pub fn map_sidecar<U>(self, f: impl FnOnce(T) -> U) -> TxEip4844WithSidecar<U> {
1097 TxEip4844WithSidecar { tx: self.tx, sidecar: f(self.sidecar) }
1098 }
1099
1100 pub fn try_map_sidecar<U, E>(
1102 self,
1103 f: impl FnOnce(T) -> Result<U, E>,
1104 ) -> Result<TxEip4844WithSidecar<U>, E> {
1105 Ok(TxEip4844WithSidecar { tx: self.tx, sidecar: f(self.sidecar)? })
1106 }
1107}
1108
1109impl TxEip4844WithSidecar<BlobTransactionSidecar> {
1110 #[cfg(feature = "kzg")]
1115 pub fn try_into_7594(
1116 self,
1117 ) -> Result<
1118 TxEip4844WithSidecar<alloy_eips::eip7594::BlobTransactionSidecarEip7594>,
1119 c_kzg::Error,
1120 > {
1121 self.try_into_7594_with_settings(
1122 alloy_eips::eip4844::env_settings::EnvKzgSettings::Default.get(),
1123 )
1124 }
1125
1126 #[cfg(feature = "kzg")]
1131 pub fn try_into_7594_with_settings(
1132 self,
1133 settings: &c_kzg::KzgSettings,
1134 ) -> Result<
1135 TxEip4844WithSidecar<alloy_eips::eip7594::BlobTransactionSidecarEip7594>,
1136 c_kzg::Error,
1137 > {
1138 self.try_map_sidecar(|sidecar| sidecar.try_into_7594(settings))
1139 }
1140}
1141
1142impl<T: TxEip4844Sidecar> TxEip4844WithSidecar<T> {
1143 #[cfg(feature = "kzg")]
1147 pub fn validate_blob(
1148 &self,
1149 proof_settings: &c_kzg::KzgSettings,
1150 ) -> Result<(), BlobTransactionValidationError> {
1151 self.tx.validate_blob(&self.sidecar, proof_settings)
1152 }
1153
1154 #[inline]
1156 pub fn size(&self) -> usize {
1157 self.tx.size() + self.sidecar.size()
1158 }
1159}
1160
1161impl<T> SignableTransaction<Signature> for TxEip4844WithSidecar<T>
1162where
1163 T: fmt::Debug + Send + Sync + 'static,
1164{
1165 fn set_chain_id(&mut self, chain_id: ChainId) {
1166 self.tx.chain_id = chain_id;
1167 }
1168
1169 fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
1170 self.tx.encode_for_signing(out);
1176 }
1177
1178 fn payload_len_for_signature(&self) -> usize {
1179 self.tx.payload_len_for_signature()
1182 }
1183}
1184
1185impl<T> Transaction for TxEip4844WithSidecar<T>
1186where
1187 T: fmt::Debug + Send + Sync + 'static,
1188{
1189 #[inline]
1190 fn chain_id(&self) -> Option<ChainId> {
1191 self.tx.chain_id()
1192 }
1193
1194 #[inline]
1195 fn nonce(&self) -> u64 {
1196 self.tx.nonce()
1197 }
1198
1199 #[inline]
1200 fn gas_limit(&self) -> u64 {
1201 self.tx.gas_limit()
1202 }
1203
1204 #[inline]
1205 fn gas_price(&self) -> Option<u128> {
1206 self.tx.gas_price()
1207 }
1208
1209 #[inline]
1210 fn max_fee_per_gas(&self) -> u128 {
1211 self.tx.max_fee_per_gas()
1212 }
1213
1214 #[inline]
1215 fn max_priority_fee_per_gas(&self) -> Option<u128> {
1216 self.tx.max_priority_fee_per_gas()
1217 }
1218
1219 #[inline]
1220 fn max_fee_per_blob_gas(&self) -> Option<u128> {
1221 self.tx.max_fee_per_blob_gas()
1222 }
1223
1224 #[inline]
1225 fn priority_fee_or_price(&self) -> u128 {
1226 self.tx.priority_fee_or_price()
1227 }
1228
1229 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
1230 self.tx.effective_gas_price(base_fee)
1231 }
1232
1233 #[inline]
1234 fn is_dynamic_fee(&self) -> bool {
1235 self.tx.is_dynamic_fee()
1236 }
1237
1238 #[inline]
1239 fn kind(&self) -> TxKind {
1240 self.tx.kind()
1241 }
1242
1243 #[inline]
1244 fn is_create(&self) -> bool {
1245 false
1246 }
1247
1248 #[inline]
1249 fn value(&self) -> U256 {
1250 self.tx.value()
1251 }
1252
1253 #[inline]
1254 fn input(&self) -> &Bytes {
1255 self.tx.input()
1256 }
1257
1258 #[inline]
1259 fn access_list(&self) -> Option<&AccessList> {
1260 Some(&self.tx.access_list)
1261 }
1262
1263 #[inline]
1264 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
1265 self.tx.blob_versioned_hashes()
1266 }
1267
1268 #[inline]
1269 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
1270 None
1271 }
1272}
1273
1274impl<T> Typed2718 for TxEip4844WithSidecar<T> {
1275 fn ty(&self) -> u8 {
1276 TxType::Eip4844 as u8
1277 }
1278}
1279
1280impl<T: Encodable7594> RlpEcdsaEncodableTx for TxEip4844WithSidecar<T> {
1281 fn rlp_encoded_fields_length(&self) -> usize {
1282 self.sidecar.encode_7594_len() + self.tx.rlp_encoded_length()
1283 }
1284
1285 fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
1286 self.tx.rlp_encode(out);
1287 self.sidecar.encode_7594(out);
1288 }
1289
1290 fn rlp_header_signed(&self, signature: &Signature) -> Header {
1291 let payload_length =
1292 self.tx.rlp_encoded_length_with_signature(signature) + self.sidecar.encode_7594_len();
1293 Header { list: true, payload_length }
1294 }
1295
1296 fn rlp_encode_signed(&self, signature: &Signature, out: &mut dyn BufMut) {
1297 self.rlp_header_signed(signature).encode(out);
1298 self.tx.rlp_encode_signed(signature, out);
1299 self.sidecar.encode_7594(out);
1300 }
1301
1302 fn tx_hash_with_type(&self, signature: &Signature, ty: u8) -> alloy_primitives::TxHash {
1303 self.tx.tx_hash_with_type(signature, ty)
1305 }
1306}
1307
1308impl<T: Encodable7594 + Decodable7594> RlpEcdsaDecodableTx for TxEip4844WithSidecar<T> {
1309 const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 };
1310
1311 fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1312 let tx = TxEip4844::rlp_decode(buf)?;
1313 let sidecar = T::decode_7594(buf)?;
1314 Ok(Self { tx, sidecar })
1315 }
1316
1317 fn rlp_decode_with_signature(buf: &mut &[u8]) -> alloy_rlp::Result<(Self, Signature)> {
1318 let header = Header::decode(buf)?;
1319 if !header.list {
1320 return Err(alloy_rlp::Error::UnexpectedString);
1321 }
1322 let remaining = buf.len();
1323
1324 let (tx, signature) = TxEip4844::rlp_decode_with_signature(buf)?;
1325 let sidecar = T::decode_7594(buf)?;
1326
1327 if buf.len() + header.payload_length != remaining {
1328 return Err(alloy_rlp::Error::UnexpectedLength);
1329 }
1330
1331 Ok((Self { tx, sidecar }, signature))
1332 }
1333}
1334
1335#[cfg(test)]
1336mod tests {
1337 use super::{BlobTransactionSidecar, TxEip4844, TxEip4844WithSidecar};
1338 use crate::{
1339 transaction::{eip4844::TxEip4844Variant, RlpEcdsaDecodableTx},
1340 SignableTransaction, TxEnvelope,
1341 };
1342 use alloy_eips::{
1343 eip2930::AccessList, eip4844::env_settings::EnvKzgSettings,
1344 eip7594::BlobTransactionSidecarVariant, Encodable2718 as _,
1345 };
1346 use alloy_primitives::{address, b256, bytes, hex, Signature, U256};
1347 use alloy_rlp::{Decodable, Encodable};
1348 use assert_matches::assert_matches;
1349 use std::path::PathBuf;
1350
1351 #[test]
1352 fn different_sidecar_same_hash() {
1353 let tx = TxEip4844 {
1356 chain_id: 1,
1357 nonce: 1,
1358 max_priority_fee_per_gas: 1,
1359 max_fee_per_gas: 1,
1360 gas_limit: 1,
1361 to: Default::default(),
1362 value: U256::from(1),
1363 access_list: Default::default(),
1364 blob_versioned_hashes: vec![Default::default()],
1365 max_fee_per_blob_gas: 1,
1366 input: Default::default(),
1367 };
1368 let sidecar = BlobTransactionSidecar {
1369 blobs: vec![[2; 131072].into()],
1370 commitments: vec![[3; 48].into()],
1371 proofs: vec![[4; 48].into()],
1372 };
1373 let mut tx = TxEip4844WithSidecar { tx, sidecar };
1374 let signature = Signature::test_signature();
1375
1376 let expected_signed = tx.clone().into_signed(signature);
1378
1379 tx.sidecar = BlobTransactionSidecar {
1381 blobs: vec![[1; 131072].into()],
1382 commitments: vec![[1; 48].into()],
1383 proofs: vec![[1; 48].into()],
1384 };
1385
1386 let actual_signed = tx.into_signed(signature);
1388
1389 assert_eq!(expected_signed.hash(), actual_signed.hash());
1391
1392 let expected_envelope: TxEnvelope = expected_signed.into();
1394 let actual_envelope: TxEnvelope = actual_signed.into();
1395
1396 let len = expected_envelope.length();
1398 let mut buf = Vec::with_capacity(len);
1399 expected_envelope.encode(&mut buf);
1400 assert_eq!(buf.len(), len);
1401
1402 assert_eq!(buf.len(), actual_envelope.length());
1405
1406 let decoded = TxEnvelope::decode(&mut &buf[..]).unwrap();
1408 assert_eq!(decoded, expected_envelope);
1409 }
1410
1411 #[test]
1412 fn test_4844_variant_into_signed_correct_hash() {
1413 let tx =
1415 TxEip4844 {
1416 chain_id: 1,
1417 nonce: 15435,
1418 gas_limit: 8000000,
1419 max_fee_per_gas: 10571233596,
1420 max_priority_fee_per_gas: 1000000000,
1421 to: address!("a8cb082a5a689e0d594d7da1e2d72a3d63adc1bd"),
1422 value: U256::ZERO,
1423 access_list: AccessList::default(),
1424 blob_versioned_hashes: vec![
1425 b256!("01e5276d91ac1ddb3b1c2d61295211220036e9a04be24c00f76916cc2659d004"),
1426 b256!("0128eb58aff09fd3a7957cd80aa86186d5849569997cdfcfa23772811b706cc2"),
1427 ],
1428 max_fee_per_blob_gas: 1,
1429 input: bytes!("701f58c50000000000000000000000000000000000000000000000000000000000073fb1ed12e288def5b439ea074b398dbb4c967f2852baac3238c5fe4b62b871a59a6d00000000000000000000000000000000000000000000000000000000123971da000000000000000000000000000000000000000000000000000000000000000ac39b2a24e1dbdd11a1e7bd7c0f4dfd7d9b9cfa0997d033ad05f961ba3b82c6c83312c967f10daf5ed2bffe309249416e03ee0b101f2b84d2102b9e38b0e4dfdf0000000000000000000000000000000000000000000000000000000066254c8b538dcc33ecf5334bbd294469f9d4fd084a3090693599a46d6c62567747cbc8660000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000073fb20000000000000000000000000000000000000000000000000000000066254da10000000000000000000000000000000000000000000000000000000012397d5e20b09b263779fda4171c341e720af8fa469621ff548651f8dbbc06c2d320400c000000000000000000000000000000000000000000000000000000000000000b50a833bb11af92814e99c6ff7cf7ba7042827549d6f306a04270753702d897d8fc3c411b99159939ac1c16d21d3057ddc8b2333d1331ab34c938cff0eb29ce2e43241c170344db6819f76b1f1e0ab8206f3ec34120312d275c4f5bbea7f5c55700000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000480000000000000000000000000000000000000000000000000000000000000031800000000000000000000000000000000000000000000800b0000000000000000000000000000000000000000000000000000000000000004ed12e288def5b439ea074b398dbb4c967f2852baac3238c5fe4b62b871a59a6d00000ca8000000000000000000000000000000000000800b000000000000000000000000000000000000000000000000000000000000000300000000000000000000000066254da100000000000000000000000066254e9d00010ca80000000000000000000000000000000000008001000000000000000000000000000000000000000000000000000000000000000550a833bb11af92814e99c6ff7cf7ba7042827549d6f306a04270753702d897d800010ca800000000000000000000000000000000000080010000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000b00010ca8000000000000000000000000000000000000801100000000000000000000000000000000000000000000000000000000000000075c1cd5bd0fd333ce9d7c8edfc79f43b8f345b4a394f6aba12a2cc78ce4012ed700010ca80000000000000000000000000000000000008011000000000000000000000000000000000000000000000000000000000000000845392775318aa47beaafbdc827da38c9f1e88c3bdcabba2cb493062e17cbf21e00010ca800000000000000000000000000000000000080080000000000000000000000000000000000000000000000000000000000000000c094e20e7ac9b433f44a5885e3bdc07e51b309aeb993caa24ba84a661ac010c100010ca800000000000000000000000000000000000080080000000000000000000000000000000000000000000000000000000000000001ab42db8f4ed810bdb143368a2b641edf242af6e3d0de8b1486e2b0e7880d431100010ca8000000000000000000000000000000000000800800000000000000000000000000000000000000000000000000000000000000022d94e4cc4525e4e2d81e8227b6172e97076431a2cf98792d978035edd6e6f3100000000000000000000000000000000000000000000000000000000000000000000000000000012101c74dfb80a80fccb9a4022b2406f79f56305e6a7c931d30140f5d372fe793837e93f9ec6b8d89a9d0ab222eeb27547f66b90ec40fbbdd2a4936b0b0c19ca684ff78888fbf5840d7c8dc3c493b139471750938d7d2c443e2d283e6c5ee9fde3765a756542c42f002af45c362b4b5b1687a8fc24cbf16532b903f7bb289728170dcf597f5255508c623ba247735538376f494cdcdd5bd0c4cb067526eeda0f4745a28d8baf8893ecc1b8cee80690538d66455294a028da03ff2add9d8a88e6ee03ba9ffe3ad7d91d6ac9c69a1f28c468f00fe55eba5651a2b32dc2458e0d14b4dd6d0173df255cd56aa01e8e38edec17ea8933f68543cbdc713279d195551d4211bed5c91f77259a695e6768f6c4b110b2158fcc42423a96dcc4e7f6fddb3e2369d00000000000000000000000000000000000000000000000000000000000000") };
1430 let variant = TxEip4844Variant::<BlobTransactionSidecar>::TxEip4844(tx);
1431
1432 let signature = Signature::new(
1433 b256!("6c173c3c8db3e3299f2f728d293b912c12e75243e3aa66911c2329b58434e2a4").into(),
1434 b256!("7dd4d1c228cedc5a414a668ab165d9e888e61e4c3b44cd7daf9cdcc4cec5d6b2").into(),
1435 false,
1436 );
1437
1438 let signed = variant.into_signed(signature);
1439 assert_eq!(
1440 *signed.hash(),
1441 b256!("93fc9daaa0726c3292a2e939df60f7e773c6a6a726a61ce43f4a217c64d85e87")
1442 );
1443 }
1444
1445 #[test]
1446 fn decode_raw_7594_rlp() {
1447 let kzg_settings = EnvKzgSettings::default();
1448 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/7594rlp");
1449 let dir = std::fs::read_dir(path).expect("Unable to read folder");
1450 for entry in dir {
1451 let entry = entry.unwrap();
1452 let content = std::fs::read_to_string(entry.path()).unwrap();
1453 let raw = hex::decode(content.trim()).unwrap();
1454 let tx = TxEip4844WithSidecar::<BlobTransactionSidecarVariant>::eip2718_decode(
1455 &mut raw.as_ref(),
1456 )
1457 .map_err(|err| {
1458 panic!("Failed to decode transaction: {:?} {:?}", err, entry.path());
1459 })
1460 .unwrap();
1461
1462 let encoded = tx.encoded_2718();
1464 assert_eq!(encoded.as_slice(), &raw[..], "{:?}", entry.path());
1465
1466 let TxEip4844WithSidecar { tx, sidecar } = tx.tx();
1467 assert_matches!(sidecar, BlobTransactionSidecarVariant::Eip7594(_));
1468
1469 let result = sidecar.validate(&tx.blob_versioned_hashes, kzg_settings.get());
1470 assert_matches!(result, Ok(()));
1471 }
1472 }
1473
1474 #[test]
1475 fn decode_raw_7594_rlp_invalid() {
1476 let kzg_settings = EnvKzgSettings::default();
1477 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/7594rlp-invalid");
1478 let dir = std::fs::read_dir(path).expect("Unable to read folder");
1479 for entry in dir {
1480 let entry = entry.unwrap();
1481
1482 if entry.path().file_name().and_then(|f| f.to_str()) == Some("0.rlp") {
1483 continue;
1484 }
1485
1486 let content = std::fs::read_to_string(entry.path()).unwrap();
1487 let raw = hex::decode(content.trim()).unwrap();
1488 let tx = TxEip4844WithSidecar::<BlobTransactionSidecarVariant>::eip2718_decode(
1489 &mut raw.as_ref(),
1490 )
1491 .map_err(|err| {
1492 panic!("Failed to decode transaction: {:?} {:?}", err, entry.path());
1493 })
1494 .unwrap();
1495
1496 let encoded = tx.encoded_2718();
1498 assert_eq!(encoded.as_slice(), &raw[..], "{:?}", entry.path());
1499
1500 let TxEip4844WithSidecar { tx, sidecar } = tx.tx();
1501 assert_matches!(sidecar, BlobTransactionSidecarVariant::Eip7594(_));
1502
1503 let result = sidecar.validate(&tx.blob_versioned_hashes, kzg_settings.get());
1504 assert_matches!(result, Err(_));
1505 }
1506 }
1507}