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