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;
18
19#[derive(Clone, Debug, PartialEq, Eq, Hash)]
26#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
27#[cfg_attr(feature = "serde", derive(serde::Serialize))]
28#[cfg_attr(feature = "serde", serde(untagged))]
29#[doc(alias = "Eip4844TransactionVariant")]
30pub enum TxEip4844Variant<T = BlobTransactionSidecar> {
31 TxEip4844(TxEip4844),
33 TxEip4844WithSidecar(TxEip4844WithSidecar<T>),
35}
36
37#[cfg(feature = "serde")]
38impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for TxEip4844Variant<T> {
39 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
40 where
41 D: serde::Deserializer<'de>,
42 {
43 #[derive(serde::Deserialize)]
44 struct TxEip4844SerdeHelper<Sidecar> {
45 #[serde(flatten)]
46 #[doc(alias = "transaction")]
47 tx: TxEip4844,
48 #[serde(flatten)]
49 sidecar: Option<Sidecar>,
50 }
51
52 let tx = TxEip4844SerdeHelper::<T>::deserialize(deserializer)?;
53
54 if let Some(sidecar) = tx.sidecar {
55 Ok(TxEip4844WithSidecar::from_tx_and_sidecar(tx.tx, sidecar).into())
56 } else {
57 Ok(tx.tx.into())
58 }
59 }
60}
61
62impl<T> From<Signed<TxEip4844>> for Signed<TxEip4844Variant<T>> {
63 fn from(value: Signed<TxEip4844>) -> Self {
64 let (tx, signature, hash) = value.into_parts();
65 Self::new_unchecked(TxEip4844Variant::TxEip4844(tx), signature, hash)
66 }
67}
68
69impl<T: Encodable7594> From<Signed<TxEip4844WithSidecar<T>>> for Signed<TxEip4844Variant<T>> {
70 fn from(value: Signed<TxEip4844WithSidecar<T>>) -> Self {
71 let (tx, signature, hash) = value.into_parts();
72 Self::new_unchecked(TxEip4844Variant::TxEip4844WithSidecar(tx), signature, hash)
73 }
74}
75
76impl<T> From<TxEip4844WithSidecar<T>> for TxEip4844Variant<T> {
77 fn from(tx: TxEip4844WithSidecar<T>) -> Self {
78 Self::TxEip4844WithSidecar(tx)
79 }
80}
81
82impl<T> From<TxEip4844> for TxEip4844Variant<T> {
83 fn from(tx: TxEip4844) -> Self {
84 Self::TxEip4844(tx)
85 }
86}
87
88impl From<(TxEip4844, BlobTransactionSidecar)> for TxEip4844Variant<BlobTransactionSidecar> {
89 fn from((tx, sidecar): (TxEip4844, BlobTransactionSidecar)) -> Self {
90 TxEip4844WithSidecar::from_tx_and_sidecar(tx, sidecar).into()
91 }
92}
93
94impl<T> From<TxEip4844Variant<T>> for TxEip4844 {
95 fn from(tx: TxEip4844Variant<T>) -> Self {
96 match tx {
97 TxEip4844Variant::TxEip4844(tx) => tx,
98 TxEip4844Variant::TxEip4844WithSidecar(tx) => tx.tx,
99 }
100 }
101}
102
103impl<T> AsRef<TxEip4844> for TxEip4844Variant<T> {
104 fn as_ref(&self) -> &TxEip4844 {
105 match self {
106 Self::TxEip4844(tx) => tx,
107 Self::TxEip4844WithSidecar(tx) => &tx.tx,
108 }
109 }
110}
111
112impl<T> AsMut<TxEip4844> for TxEip4844Variant<T> {
113 fn as_mut(&mut self) -> &mut TxEip4844 {
114 match self {
115 Self::TxEip4844(tx) => tx,
116 Self::TxEip4844WithSidecar(tx) => &mut tx.tx,
117 }
118 }
119}
120
121impl AsRef<Self> for TxEip4844 {
122 fn as_ref(&self) -> &Self {
123 self
124 }
125}
126
127impl AsMut<Self> for TxEip4844 {
128 fn as_mut(&mut self) -> &mut Self {
129 self
130 }
131}
132
133impl<T> TxEip4844Variant<T> {
134 #[doc(alias = "transaction_type")]
136 pub const fn tx_type() -> TxType {
137 TxType::Eip4844
138 }
139
140 #[doc(alias = "transaction")]
142 pub const fn tx(&self) -> &TxEip4844 {
143 match self {
144 Self::TxEip4844(tx) => tx,
145 Self::TxEip4844WithSidecar(tx) => tx.tx(),
146 }
147 }
148
149 pub const fn as_with_sidecar(&self) -> Option<&TxEip4844WithSidecar<T>> {
151 match self {
152 Self::TxEip4844WithSidecar(tx) => Some(tx),
153 _ => None,
154 }
155 }
156
157 pub fn try_into_4844_with_sidecar(self) -> Result<TxEip4844WithSidecar<T>, Self> {
160 match self {
161 Self::TxEip4844WithSidecar(tx) => Ok(tx),
162 _ => Err(self),
163 }
164 }
165
166 pub const fn sidecar(&self) -> Option<&T> {
168 match self {
169 Self::TxEip4844WithSidecar(tx) => Some(tx.sidecar()),
170 _ => None,
171 }
172 }
173}
174
175impl<T: TxEip4844Sidecar> TxEip4844Variant<T> {
176 #[cfg(feature = "kzg")]
180 pub fn validate(
181 &self,
182 proof_settings: &c_kzg::KzgSettings,
183 ) -> Result<(), BlobTransactionValidationError> {
184 match self {
185 Self::TxEip4844(_) => Err(BlobTransactionValidationError::MissingSidecar),
186 Self::TxEip4844WithSidecar(tx) => tx.validate_blob(proof_settings),
187 }
188 }
189
190 #[inline]
192 pub fn size(&self) -> usize {
193 match self {
194 Self::TxEip4844(tx) => tx.size(),
195 Self::TxEip4844WithSidecar(tx) => tx.size(),
196 }
197 }
198}
199
200impl<T> Transaction for TxEip4844Variant<T>
201where
202 T: fmt::Debug + Send + Sync + 'static,
203{
204 #[inline]
205 fn chain_id(&self) -> Option<ChainId> {
206 match self {
207 Self::TxEip4844(tx) => Some(tx.chain_id),
208 Self::TxEip4844WithSidecar(tx) => Some(tx.tx().chain_id),
209 }
210 }
211
212 #[inline]
213 fn nonce(&self) -> u64 {
214 match self {
215 Self::TxEip4844(tx) => tx.nonce,
216 Self::TxEip4844WithSidecar(tx) => tx.tx().nonce,
217 }
218 }
219
220 #[inline]
221 fn gas_limit(&self) -> u64 {
222 match self {
223 Self::TxEip4844(tx) => tx.gas_limit,
224 Self::TxEip4844WithSidecar(tx) => tx.tx().gas_limit,
225 }
226 }
227
228 #[inline]
229 fn gas_price(&self) -> Option<u128> {
230 None
231 }
232
233 #[inline]
234 fn max_fee_per_gas(&self) -> u128 {
235 match self {
236 Self::TxEip4844(tx) => tx.max_fee_per_gas(),
237 Self::TxEip4844WithSidecar(tx) => tx.max_fee_per_gas(),
238 }
239 }
240
241 #[inline]
242 fn max_priority_fee_per_gas(&self) -> Option<u128> {
243 match self {
244 Self::TxEip4844(tx) => tx.max_priority_fee_per_gas(),
245 Self::TxEip4844WithSidecar(tx) => tx.max_priority_fee_per_gas(),
246 }
247 }
248
249 #[inline]
250 fn max_fee_per_blob_gas(&self) -> Option<u128> {
251 match self {
252 Self::TxEip4844(tx) => tx.max_fee_per_blob_gas(),
253 Self::TxEip4844WithSidecar(tx) => tx.max_fee_per_blob_gas(),
254 }
255 }
256
257 #[inline]
258 fn priority_fee_or_price(&self) -> u128 {
259 match self {
260 Self::TxEip4844(tx) => tx.priority_fee_or_price(),
261 Self::TxEip4844WithSidecar(tx) => tx.priority_fee_or_price(),
262 }
263 }
264
265 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
266 match self {
267 Self::TxEip4844(tx) => tx.effective_gas_price(base_fee),
268 Self::TxEip4844WithSidecar(tx) => tx.effective_gas_price(base_fee),
269 }
270 }
271
272 #[inline]
273 fn is_dynamic_fee(&self) -> bool {
274 match self {
275 Self::TxEip4844(tx) => tx.is_dynamic_fee(),
276 Self::TxEip4844WithSidecar(tx) => tx.is_dynamic_fee(),
277 }
278 }
279
280 #[inline]
281 fn kind(&self) -> TxKind {
282 match self {
283 Self::TxEip4844(tx) => tx.to,
284 Self::TxEip4844WithSidecar(tx) => tx.tx.to,
285 }
286 .into()
287 }
288
289 #[inline]
290 fn is_create(&self) -> bool {
291 false
292 }
293
294 #[inline]
295 fn value(&self) -> U256 {
296 match self {
297 Self::TxEip4844(tx) => tx.value,
298 Self::TxEip4844WithSidecar(tx) => tx.tx.value,
299 }
300 }
301
302 #[inline]
303 fn input(&self) -> &Bytes {
304 match self {
305 Self::TxEip4844(tx) => tx.input(),
306 Self::TxEip4844WithSidecar(tx) => tx.tx().input(),
307 }
308 }
309
310 #[inline]
311 fn access_list(&self) -> Option<&AccessList> {
312 match self {
313 Self::TxEip4844(tx) => tx.access_list(),
314 Self::TxEip4844WithSidecar(tx) => tx.access_list(),
315 }
316 }
317
318 #[inline]
319 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
320 match self {
321 Self::TxEip4844(tx) => tx.blob_versioned_hashes(),
322 Self::TxEip4844WithSidecar(tx) => tx.blob_versioned_hashes(),
323 }
324 }
325
326 #[inline]
327 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
328 None
329 }
330}
331impl Typed2718 for TxEip4844 {
332 fn ty(&self) -> u8 {
333 TxType::Eip4844 as u8
334 }
335}
336
337impl<T: Encodable7594> RlpEcdsaEncodableTx for TxEip4844Variant<T> {
338 fn rlp_encoded_fields_length(&self) -> usize {
339 match self {
340 Self::TxEip4844(inner) => inner.rlp_encoded_fields_length(),
341 Self::TxEip4844WithSidecar(inner) => inner.rlp_encoded_fields_length(),
342 }
343 }
344
345 fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
346 match self {
347 Self::TxEip4844(inner) => inner.rlp_encode_fields(out),
348 Self::TxEip4844WithSidecar(inner) => inner.rlp_encode_fields(out),
349 }
350 }
351
352 fn rlp_header_signed(&self, signature: &Signature) -> Header {
353 match self {
354 Self::TxEip4844(inner) => inner.rlp_header_signed(signature),
355 Self::TxEip4844WithSidecar(inner) => inner.rlp_header_signed(signature),
356 }
357 }
358
359 fn rlp_encode_signed(&self, signature: &Signature, out: &mut dyn BufMut) {
360 match self {
361 Self::TxEip4844(inner) => inner.rlp_encode_signed(signature, out),
362 Self::TxEip4844WithSidecar(inner) => inner.rlp_encode_signed(signature, out),
363 }
364 }
365
366 fn tx_hash_with_type(&self, signature: &Signature, ty: u8) -> alloy_primitives::TxHash {
367 match self {
368 Self::TxEip4844(inner) => inner.tx_hash_with_type(signature, ty),
369 Self::TxEip4844WithSidecar(inner) => inner.tx_hash_with_type(signature, ty),
370 }
371 }
372}
373
374impl<T: Encodable7594 + Decodable7594> RlpEcdsaDecodableTx for TxEip4844Variant<T> {
375 const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 };
376
377 fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
378 let needle = &mut &**buf;
379
380 let trial = &mut &**buf;
383
384 if Header::decode(needle).is_ok_and(|h| h.list) {
391 if let Ok(tx) = TxEip4844WithSidecar::rlp_decode_fields(trial) {
392 *buf = *trial;
393 return Ok(tx.into());
394 }
395 }
396 TxEip4844::rlp_decode_fields(buf).map(Into::into)
397 }
398
399 fn rlp_decode_with_signature(buf: &mut &[u8]) -> alloy_rlp::Result<(Self, Signature)> {
400 let needle = &mut &**buf;
403
404 let trial = &mut &**buf;
407
408 Header::decode(needle)?;
410
411 if Header::decode(needle).is_ok_and(|h| h.list) {
418 if let Ok((tx, signature)) = TxEip4844WithSidecar::rlp_decode_with_signature(trial) {
419 *buf = *trial;
422 return Ok((tx.into(), signature));
423 }
424 }
425 TxEip4844::rlp_decode_with_signature(buf).map(|(tx, signature)| (tx.into(), signature))
426 }
427}
428
429impl<T> Typed2718 for TxEip4844Variant<T> {
430 fn ty(&self) -> u8 {
431 TxType::Eip4844 as u8
432 }
433}
434
435impl IsTyped2718 for TxEip4844 {
436 fn is_type(type_id: u8) -> bool {
437 matches!(type_id, 0x03)
438 }
439}
440
441impl<T> SignableTransaction<Signature> for TxEip4844Variant<T>
442where
443 T: fmt::Debug + Send + Sync + 'static,
444{
445 fn set_chain_id(&mut self, chain_id: ChainId) {
446 match self {
447 Self::TxEip4844(inner) => {
448 inner.set_chain_id(chain_id);
449 }
450 Self::TxEip4844WithSidecar(inner) => {
451 inner.set_chain_id(chain_id);
452 }
453 }
454 }
455
456 fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
457 self.tx().encode_for_signing(out);
463 }
464
465 fn payload_len_for_signature(&self) -> usize {
466 self.tx().payload_len_for_signature()
467 }
468}
469
470#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
474#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
475#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
476#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
477#[doc(alias = "Eip4844Transaction", alias = "TransactionEip4844", alias = "Eip4844Tx")]
478pub struct TxEip4844 {
479 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
481 pub chain_id: ChainId,
482 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
484 pub nonce: u64,
485 #[cfg_attr(
491 feature = "serde",
492 serde(with = "alloy_serde::quantity", rename = "gas", alias = "gasLimit")
493 )]
494 pub gas_limit: u64,
495 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
507 pub max_fee_per_gas: u128,
508 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
516 pub max_priority_fee_per_gas: u128,
517 pub to: Address,
519 pub value: U256,
524 pub access_list: AccessList,
530
531 pub blob_versioned_hashes: Vec<B256>,
533
534 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
538 pub max_fee_per_blob_gas: u128,
539
540 pub input: Bytes,
546}
547
548impl TxEip4844 {
549 #[inline]
551 pub fn blob_gas(&self) -> u64 {
552 self.blob_versioned_hashes.len() as u64 * DATA_GAS_PER_BLOB
554 }
555
556 #[cfg(feature = "kzg")]
570 pub fn validate_blob<T: TxEip4844Sidecar>(
571 &self,
572 sidecar: &T,
573 proof_settings: &c_kzg::KzgSettings,
574 ) -> Result<(), BlobTransactionValidationError> {
575 sidecar.validate(&self.blob_versioned_hashes, proof_settings)
576 }
577
578 #[doc(alias = "transaction_type")]
580 pub const fn tx_type() -> TxType {
581 TxType::Eip4844
582 }
583
584 pub const fn with_sidecar<T>(self, sidecar: T) -> TxEip4844WithSidecar<T> {
586 TxEip4844WithSidecar::from_tx_and_sidecar(self, sidecar)
587 }
588
589 #[inline]
591 pub fn size(&self) -> usize {
592 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>() }
604}
605
606impl RlpEcdsaEncodableTx for TxEip4844 {
607 fn rlp_encoded_fields_length(&self) -> usize {
608 self.chain_id.length()
609 + self.nonce.length()
610 + self.gas_limit.length()
611 + self.max_fee_per_gas.length()
612 + self.max_priority_fee_per_gas.length()
613 + self.to.length()
614 + self.value.length()
615 + self.access_list.length()
616 + self.blob_versioned_hashes.length()
617 + self.max_fee_per_blob_gas.length()
618 + self.input.0.length()
619 }
620
621 fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
622 self.chain_id.encode(out);
623 self.nonce.encode(out);
624 self.max_priority_fee_per_gas.encode(out);
625 self.max_fee_per_gas.encode(out);
626 self.gas_limit.encode(out);
627 self.to.encode(out);
628 self.value.encode(out);
629 self.input.0.encode(out);
630 self.access_list.encode(out);
631 self.max_fee_per_blob_gas.encode(out);
632 self.blob_versioned_hashes.encode(out);
633 }
634}
635
636impl RlpEcdsaDecodableTx for TxEip4844 {
637 const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 };
638
639 fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
640 Ok(Self {
641 chain_id: Decodable::decode(buf)?,
642 nonce: Decodable::decode(buf)?,
643 max_priority_fee_per_gas: Decodable::decode(buf)?,
644 max_fee_per_gas: Decodable::decode(buf)?,
645 gas_limit: Decodable::decode(buf)?,
646 to: Decodable::decode(buf)?,
647 value: Decodable::decode(buf)?,
648 input: Decodable::decode(buf)?,
649 access_list: Decodable::decode(buf)?,
650 max_fee_per_blob_gas: Decodable::decode(buf)?,
651 blob_versioned_hashes: Decodable::decode(buf)?,
652 })
653 }
654}
655
656impl SignableTransaction<Signature> for TxEip4844 {
657 fn set_chain_id(&mut self, chain_id: ChainId) {
658 self.chain_id = chain_id;
659 }
660
661 fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
662 out.put_u8(Self::tx_type() as u8);
663 self.encode(out);
664 }
665
666 fn payload_len_for_signature(&self) -> usize {
667 self.length() + 1
668 }
669}
670
671impl Transaction for TxEip4844 {
672 #[inline]
673 fn chain_id(&self) -> Option<ChainId> {
674 Some(self.chain_id)
675 }
676
677 #[inline]
678 fn nonce(&self) -> u64 {
679 self.nonce
680 }
681
682 #[inline]
683 fn gas_limit(&self) -> u64 {
684 self.gas_limit
685 }
686
687 #[inline]
688 fn gas_price(&self) -> Option<u128> {
689 None
690 }
691
692 #[inline]
693 fn max_fee_per_gas(&self) -> u128 {
694 self.max_fee_per_gas
695 }
696
697 #[inline]
698 fn max_priority_fee_per_gas(&self) -> Option<u128> {
699 Some(self.max_priority_fee_per_gas)
700 }
701
702 #[inline]
703 fn max_fee_per_blob_gas(&self) -> Option<u128> {
704 Some(self.max_fee_per_blob_gas)
705 }
706
707 #[inline]
708 fn priority_fee_or_price(&self) -> u128 {
709 self.max_priority_fee_per_gas
710 }
711
712 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
713 alloy_eips::eip1559::calc_effective_gas_price(
714 self.max_fee_per_gas,
715 self.max_priority_fee_per_gas,
716 base_fee,
717 )
718 }
719
720 #[inline]
721 fn is_dynamic_fee(&self) -> bool {
722 true
723 }
724
725 #[inline]
726 fn kind(&self) -> TxKind {
727 self.to.into()
728 }
729
730 #[inline]
731 fn is_create(&self) -> bool {
732 false
733 }
734
735 #[inline]
736 fn value(&self) -> U256 {
737 self.value
738 }
739
740 #[inline]
741 fn input(&self) -> &Bytes {
742 &self.input
743 }
744
745 #[inline]
746 fn access_list(&self) -> Option<&AccessList> {
747 Some(&self.access_list)
748 }
749
750 #[inline]
751 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
752 Some(&self.blob_versioned_hashes)
753 }
754
755 #[inline]
756 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
757 None
758 }
759}
760
761impl Encodable for TxEip4844 {
762 fn encode(&self, out: &mut dyn BufMut) {
763 self.rlp_encode(out);
764 }
765
766 fn length(&self) -> usize {
767 self.rlp_encoded_length()
768 }
769}
770
771impl Decodable for TxEip4844 {
772 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
773 Self::rlp_decode(buf)
774 }
775}
776
777impl<T> From<TxEip4844WithSidecar<T>> for TxEip4844 {
778 fn from(tx_with_sidecar: TxEip4844WithSidecar<T>) -> Self {
780 tx_with_sidecar.tx
781 }
782}
783
784#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
794#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
795#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
796#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
797#[doc(alias = "Eip4844TransactionWithSidecar", alias = "Eip4844TxWithSidecar")]
798pub struct TxEip4844WithSidecar<T = BlobTransactionSidecar> {
799 #[cfg_attr(feature = "serde", serde(flatten))]
801 #[doc(alias = "transaction")]
802 pub tx: TxEip4844,
803 #[cfg_attr(feature = "serde", serde(flatten))]
805 pub sidecar: T,
806}
807
808impl<T> TxEip4844WithSidecar<T> {
809 #[doc(alias = "from_transaction_and_sidecar")]
811 pub const fn from_tx_and_sidecar(tx: TxEip4844, sidecar: T) -> Self {
812 Self { tx, sidecar }
813 }
814
815 #[doc(alias = "transaction_type")]
817 pub const fn tx_type() -> TxType {
818 TxEip4844::tx_type()
819 }
820
821 #[doc(alias = "transaction")]
823 pub const fn tx(&self) -> &TxEip4844 {
824 &self.tx
825 }
826
827 pub const fn sidecar(&self) -> &T {
829 &self.sidecar
830 }
831
832 pub fn into_sidecar(self) -> T {
834 self.sidecar
835 }
836
837 pub fn into_parts(self) -> (TxEip4844, T) {
839 (self.tx, self.sidecar)
840 }
841
842 pub fn map_sidecar<U>(self, f: impl FnOnce(T) -> U) -> TxEip4844WithSidecar<U> {
844 TxEip4844WithSidecar { tx: self.tx, sidecar: f(self.sidecar) }
845 }
846
847 pub fn try_map_sidecar<U, E>(
849 self,
850 f: impl FnOnce(T) -> Result<U, E>,
851 ) -> Result<TxEip4844WithSidecar<U>, E> {
852 Ok(TxEip4844WithSidecar { tx: self.tx, sidecar: f(self.sidecar)? })
853 }
854}
855
856impl TxEip4844WithSidecar<BlobTransactionSidecar> {
857 #[cfg(feature = "kzg")]
862 pub fn try_into_7594(
863 self,
864 ) -> Result<
865 TxEip4844WithSidecar<alloy_eips::eip7594::BlobTransactionSidecarEip7594>,
866 c_kzg::Error,
867 > {
868 self.try_into_7594_with_settings(
869 alloy_eips::eip4844::env_settings::EnvKzgSettings::Default.get(),
870 )
871 }
872
873 #[cfg(feature = "kzg")]
878 pub fn try_into_7594_with_settings(
879 self,
880 settings: &c_kzg::KzgSettings,
881 ) -> Result<
882 TxEip4844WithSidecar<alloy_eips::eip7594::BlobTransactionSidecarEip7594>,
883 c_kzg::Error,
884 > {
885 self.try_map_sidecar(|sidecar| sidecar.try_into_7594(settings))
886 }
887}
888
889impl<T: TxEip4844Sidecar> TxEip4844WithSidecar<T> {
890 #[cfg(feature = "kzg")]
894 pub fn validate_blob(
895 &self,
896 proof_settings: &c_kzg::KzgSettings,
897 ) -> Result<(), BlobTransactionValidationError> {
898 self.tx.validate_blob(&self.sidecar, proof_settings)
899 }
900
901 #[inline]
903 pub fn size(&self) -> usize {
904 self.tx.size() + self.sidecar.size()
905 }
906}
907
908impl<T> SignableTransaction<Signature> for TxEip4844WithSidecar<T>
909where
910 T: fmt::Debug + Send + Sync + 'static,
911{
912 fn set_chain_id(&mut self, chain_id: ChainId) {
913 self.tx.chain_id = chain_id;
914 }
915
916 fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
917 self.tx.encode_for_signing(out);
923 }
924
925 fn payload_len_for_signature(&self) -> usize {
926 self.tx.payload_len_for_signature()
929 }
930}
931
932impl<T> Transaction for TxEip4844WithSidecar<T>
933where
934 T: fmt::Debug + Send + Sync + 'static,
935{
936 #[inline]
937 fn chain_id(&self) -> Option<ChainId> {
938 self.tx.chain_id()
939 }
940
941 #[inline]
942 fn nonce(&self) -> u64 {
943 self.tx.nonce()
944 }
945
946 #[inline]
947 fn gas_limit(&self) -> u64 {
948 self.tx.gas_limit()
949 }
950
951 #[inline]
952 fn gas_price(&self) -> Option<u128> {
953 self.tx.gas_price()
954 }
955
956 #[inline]
957 fn max_fee_per_gas(&self) -> u128 {
958 self.tx.max_fee_per_gas()
959 }
960
961 #[inline]
962 fn max_priority_fee_per_gas(&self) -> Option<u128> {
963 self.tx.max_priority_fee_per_gas()
964 }
965
966 #[inline]
967 fn max_fee_per_blob_gas(&self) -> Option<u128> {
968 self.tx.max_fee_per_blob_gas()
969 }
970
971 #[inline]
972 fn priority_fee_or_price(&self) -> u128 {
973 self.tx.priority_fee_or_price()
974 }
975
976 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
977 self.tx.effective_gas_price(base_fee)
978 }
979
980 #[inline]
981 fn is_dynamic_fee(&self) -> bool {
982 self.tx.is_dynamic_fee()
983 }
984
985 #[inline]
986 fn kind(&self) -> TxKind {
987 self.tx.kind()
988 }
989
990 #[inline]
991 fn is_create(&self) -> bool {
992 false
993 }
994
995 #[inline]
996 fn value(&self) -> U256 {
997 self.tx.value()
998 }
999
1000 #[inline]
1001 fn input(&self) -> &Bytes {
1002 self.tx.input()
1003 }
1004
1005 #[inline]
1006 fn access_list(&self) -> Option<&AccessList> {
1007 Some(&self.tx.access_list)
1008 }
1009
1010 #[inline]
1011 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
1012 self.tx.blob_versioned_hashes()
1013 }
1014
1015 #[inline]
1016 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
1017 None
1018 }
1019}
1020
1021impl<T> Typed2718 for TxEip4844WithSidecar<T> {
1022 fn ty(&self) -> u8 {
1023 TxType::Eip4844 as u8
1024 }
1025}
1026
1027impl<T: Encodable7594> RlpEcdsaEncodableTx for TxEip4844WithSidecar<T> {
1028 fn rlp_encoded_fields_length(&self) -> usize {
1029 self.sidecar.encode_7594_len() + self.tx.rlp_encoded_length()
1030 }
1031
1032 fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
1033 self.tx.rlp_encode(out);
1034 self.sidecar.encode_7594(out);
1035 }
1036
1037 fn rlp_header_signed(&self, signature: &Signature) -> Header {
1038 let payload_length =
1039 self.tx.rlp_encoded_length_with_signature(signature) + self.sidecar.encode_7594_len();
1040 Header { list: true, payload_length }
1041 }
1042
1043 fn rlp_encode_signed(&self, signature: &Signature, out: &mut dyn BufMut) {
1044 self.rlp_header_signed(signature).encode(out);
1045 self.tx.rlp_encode_signed(signature, out);
1046 self.sidecar.encode_7594(out);
1047 }
1048
1049 fn tx_hash_with_type(&self, signature: &Signature, ty: u8) -> alloy_primitives::TxHash {
1050 self.tx.tx_hash_with_type(signature, ty)
1052 }
1053}
1054
1055impl<T: Encodable7594 + Decodable7594> RlpEcdsaDecodableTx for TxEip4844WithSidecar<T> {
1056 const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 };
1057
1058 fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1059 let tx = TxEip4844::rlp_decode(buf)?;
1060 let sidecar = T::decode_7594(buf)?;
1061 Ok(Self { tx, sidecar })
1062 }
1063
1064 fn rlp_decode_with_signature(buf: &mut &[u8]) -> alloy_rlp::Result<(Self, Signature)> {
1065 let header = Header::decode(buf)?;
1066 if !header.list {
1067 return Err(alloy_rlp::Error::UnexpectedString);
1068 }
1069 let remaining = buf.len();
1070
1071 let (tx, signature) = TxEip4844::rlp_decode_with_signature(buf)?;
1072 let sidecar = T::decode_7594(buf)?;
1073
1074 if buf.len() + header.payload_length != remaining {
1075 return Err(alloy_rlp::Error::UnexpectedLength);
1076 }
1077
1078 Ok((Self { tx, sidecar }, signature))
1079 }
1080}
1081
1082#[cfg(test)]
1083mod tests {
1084 use super::{BlobTransactionSidecar, TxEip4844, TxEip4844WithSidecar};
1085 use crate::{
1086 transaction::{eip4844::TxEip4844Variant, RlpEcdsaDecodableTx},
1087 SignableTransaction, TxEnvelope,
1088 };
1089 use alloy_eips::{
1090 eip2930::AccessList, eip4844::env_settings::EnvKzgSettings,
1091 eip7594::BlobTransactionSidecarVariant, Encodable2718 as _,
1092 };
1093 use alloy_primitives::{address, b256, bytes, hex, Signature, U256};
1094 use alloy_rlp::{Decodable, Encodable};
1095 use assert_matches::assert_matches;
1096 use std::path::PathBuf;
1097
1098 #[test]
1099 fn different_sidecar_same_hash() {
1100 let tx = TxEip4844 {
1103 chain_id: 1,
1104 nonce: 1,
1105 max_priority_fee_per_gas: 1,
1106 max_fee_per_gas: 1,
1107 gas_limit: 1,
1108 to: Default::default(),
1109 value: U256::from(1),
1110 access_list: Default::default(),
1111 blob_versioned_hashes: vec![Default::default()],
1112 max_fee_per_blob_gas: 1,
1113 input: Default::default(),
1114 };
1115 let sidecar = BlobTransactionSidecar {
1116 blobs: vec![[2; 131072].into()],
1117 commitments: vec![[3; 48].into()],
1118 proofs: vec![[4; 48].into()],
1119 };
1120 let mut tx = TxEip4844WithSidecar { tx, sidecar };
1121 let signature = Signature::test_signature();
1122
1123 let expected_signed = tx.clone().into_signed(signature);
1125
1126 tx.sidecar = BlobTransactionSidecar {
1128 blobs: vec![[1; 131072].into()],
1129 commitments: vec![[1; 48].into()],
1130 proofs: vec![[1; 48].into()],
1131 };
1132
1133 let actual_signed = tx.into_signed(signature);
1135
1136 assert_eq!(expected_signed.hash(), actual_signed.hash());
1138
1139 let expected_envelope: TxEnvelope = expected_signed.into();
1141 let actual_envelope: TxEnvelope = actual_signed.into();
1142
1143 let len = expected_envelope.length();
1145 let mut buf = Vec::with_capacity(len);
1146 expected_envelope.encode(&mut buf);
1147 assert_eq!(buf.len(), len);
1148
1149 assert_eq!(buf.len(), actual_envelope.length());
1152
1153 let decoded = TxEnvelope::decode(&mut &buf[..]).unwrap();
1155 assert_eq!(decoded, expected_envelope);
1156 }
1157
1158 #[test]
1159 fn test_4844_variant_into_signed_correct_hash() {
1160 let tx =
1162 TxEip4844 {
1163 chain_id: 1,
1164 nonce: 15435,
1165 gas_limit: 8000000,
1166 max_fee_per_gas: 10571233596,
1167 max_priority_fee_per_gas: 1000000000,
1168 to: address!("a8cb082a5a689e0d594d7da1e2d72a3d63adc1bd"),
1169 value: U256::ZERO,
1170 access_list: AccessList::default(),
1171 blob_versioned_hashes: vec![
1172 b256!("01e5276d91ac1ddb3b1c2d61295211220036e9a04be24c00f76916cc2659d004"),
1173 b256!("0128eb58aff09fd3a7957cd80aa86186d5849569997cdfcfa23772811b706cc2"),
1174 ],
1175 max_fee_per_blob_gas: 1,
1176 input: bytes!("701f58c50000000000000000000000000000000000000000000000000000000000073fb1ed12e288def5b439ea074b398dbb4c967f2852baac3238c5fe4b62b871a59a6d00000000000000000000000000000000000000000000000000000000123971da000000000000000000000000000000000000000000000000000000000000000ac39b2a24e1dbdd11a1e7bd7c0f4dfd7d9b9cfa0997d033ad05f961ba3b82c6c83312c967f10daf5ed2bffe309249416e03ee0b101f2b84d2102b9e38b0e4dfdf0000000000000000000000000000000000000000000000000000000066254c8b538dcc33ecf5334bbd294469f9d4fd084a3090693599a46d6c62567747cbc8660000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000073fb20000000000000000000000000000000000000000000000000000000066254da10000000000000000000000000000000000000000000000000000000012397d5e20b09b263779fda4171c341e720af8fa469621ff548651f8dbbc06c2d320400c000000000000000000000000000000000000000000000000000000000000000b50a833bb11af92814e99c6ff7cf7ba7042827549d6f306a04270753702d897d8fc3c411b99159939ac1c16d21d3057ddc8b2333d1331ab34c938cff0eb29ce2e43241c170344db6819f76b1f1e0ab8206f3ec34120312d275c4f5bbea7f5c55700000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000480000000000000000000000000000000000000000000000000000000000000031800000000000000000000000000000000000000000000800b0000000000000000000000000000000000000000000000000000000000000004ed12e288def5b439ea074b398dbb4c967f2852baac3238c5fe4b62b871a59a6d00000ca8000000000000000000000000000000000000800b000000000000000000000000000000000000000000000000000000000000000300000000000000000000000066254da100000000000000000000000066254e9d00010ca80000000000000000000000000000000000008001000000000000000000000000000000000000000000000000000000000000000550a833bb11af92814e99c6ff7cf7ba7042827549d6f306a04270753702d897d800010ca800000000000000000000000000000000000080010000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000b00010ca8000000000000000000000000000000000000801100000000000000000000000000000000000000000000000000000000000000075c1cd5bd0fd333ce9d7c8edfc79f43b8f345b4a394f6aba12a2cc78ce4012ed700010ca80000000000000000000000000000000000008011000000000000000000000000000000000000000000000000000000000000000845392775318aa47beaafbdc827da38c9f1e88c3bdcabba2cb493062e17cbf21e00010ca800000000000000000000000000000000000080080000000000000000000000000000000000000000000000000000000000000000c094e20e7ac9b433f44a5885e3bdc07e51b309aeb993caa24ba84a661ac010c100010ca800000000000000000000000000000000000080080000000000000000000000000000000000000000000000000000000000000001ab42db8f4ed810bdb143368a2b641edf242af6e3d0de8b1486e2b0e7880d431100010ca8000000000000000000000000000000000000800800000000000000000000000000000000000000000000000000000000000000022d94e4cc4525e4e2d81e8227b6172e97076431a2cf98792d978035edd6e6f3100000000000000000000000000000000000000000000000000000000000000000000000000000012101c74dfb80a80fccb9a4022b2406f79f56305e6a7c931d30140f5d372fe793837e93f9ec6b8d89a9d0ab222eeb27547f66b90ec40fbbdd2a4936b0b0c19ca684ff78888fbf5840d7c8dc3c493b139471750938d7d2c443e2d283e6c5ee9fde3765a756542c42f002af45c362b4b5b1687a8fc24cbf16532b903f7bb289728170dcf597f5255508c623ba247735538376f494cdcdd5bd0c4cb067526eeda0f4745a28d8baf8893ecc1b8cee80690538d66455294a028da03ff2add9d8a88e6ee03ba9ffe3ad7d91d6ac9c69a1f28c468f00fe55eba5651a2b32dc2458e0d14b4dd6d0173df255cd56aa01e8e38edec17ea8933f68543cbdc713279d195551d4211bed5c91f77259a695e6768f6c4b110b2158fcc42423a96dcc4e7f6fddb3e2369d00000000000000000000000000000000000000000000000000000000000000") };
1177 let variant = TxEip4844Variant::<BlobTransactionSidecar>::TxEip4844(tx);
1178
1179 let signature = Signature::new(
1180 b256!("6c173c3c8db3e3299f2f728d293b912c12e75243e3aa66911c2329b58434e2a4").into(),
1181 b256!("7dd4d1c228cedc5a414a668ab165d9e888e61e4c3b44cd7daf9cdcc4cec5d6b2").into(),
1182 false,
1183 );
1184
1185 let signed = variant.into_signed(signature);
1186 assert_eq!(
1187 *signed.hash(),
1188 b256!("93fc9daaa0726c3292a2e939df60f7e773c6a6a726a61ce43f4a217c64d85e87")
1189 );
1190 }
1191
1192 #[test]
1193 fn decode_raw_7594_rlp() {
1194 let kzg_settings = EnvKzgSettings::default();
1195 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/7594rlp");
1196 let dir = std::fs::read_dir(path).expect("Unable to read folder");
1197 for entry in dir {
1198 let entry = entry.unwrap();
1199 let content = std::fs::read_to_string(entry.path()).unwrap();
1200 let raw = hex::decode(content.trim()).unwrap();
1201 let tx = TxEip4844WithSidecar::<BlobTransactionSidecarVariant>::eip2718_decode(
1202 &mut raw.as_ref(),
1203 )
1204 .map_err(|err| {
1205 panic!("Failed to decode transaction: {:?} {:?}", err, entry.path());
1206 })
1207 .unwrap();
1208
1209 let encoded = tx.encoded_2718();
1211 assert_eq!(encoded.as_slice(), &raw[..], "{:?}", entry.path());
1212
1213 let TxEip4844WithSidecar { tx, sidecar } = tx.tx();
1214 assert_matches!(sidecar, BlobTransactionSidecarVariant::Eip7594(_));
1215
1216 let result = sidecar.validate(&tx.blob_versioned_hashes, kzg_settings.get());
1217 assert_matches!(result, Ok(()));
1218 }
1219 }
1220
1221 #[test]
1222 fn decode_raw_7594_rlp_invalid() {
1223 let kzg_settings = EnvKzgSettings::default();
1224 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/7594rlp-invalid");
1225 let dir = std::fs::read_dir(path).expect("Unable to read folder");
1226 for entry in dir {
1227 let entry = entry.unwrap();
1228
1229 if entry.path().file_name().and_then(|f| f.to_str()) == Some("0.rlp") {
1230 continue;
1231 }
1232
1233 let content = std::fs::read_to_string(entry.path()).unwrap();
1234 let raw = hex::decode(content.trim()).unwrap();
1235 let tx = TxEip4844WithSidecar::<BlobTransactionSidecarVariant>::eip2718_decode(
1236 &mut raw.as_ref(),
1237 )
1238 .map_err(|err| {
1239 panic!("Failed to decode transaction: {:?} {:?}", err, entry.path());
1240 })
1241 .unwrap();
1242
1243 let encoded = tx.encoded_2718();
1245 assert_eq!(encoded.as_slice(), &raw[..], "{:?}", entry.path());
1246
1247 let TxEip4844WithSidecar { tx, sidecar } = tx.tx();
1248 assert_matches!(sidecar, BlobTransactionSidecarVariant::Eip7594(_));
1249
1250 let result = sidecar.validate(&tx.blob_versioned_hashes, kzg_settings.get());
1251 assert_matches!(result, Err(_));
1252 }
1253 }
1254}