1use crate::{SignableTransaction, Signed, Transaction, TxType};
2
3use alloc::vec::Vec;
4use alloy_eips::{
5 eip2930::AccessList, eip4844::DATA_GAS_PER_BLOB, eip7702::SignedAuthorization, Typed2718,
6};
7use alloy_primitives::{
8 Address, Bytes, ChainId, PrimitiveSignature as Signature, TxKind, B256, U256,
9};
10use alloy_rlp::{BufMut, Decodable, Encodable, Header};
11use core::mem;
12
13#[doc(inline)]
14pub use alloy_eips::eip4844::BlobTransactionSidecar;
15
16#[cfg(feature = "kzg")]
17#[doc(inline)]
18pub use alloy_eips::eip4844::BlobTransactionValidationError;
19
20use super::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx};
21
22#[derive(Clone, Debug, PartialEq, Eq, Hash)]
29#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
30#[cfg_attr(feature = "serde", derive(serde::Serialize))]
31#[cfg_attr(feature = "serde", serde(untagged))]
32#[doc(alias = "Eip4844TransactionVariant")]
33pub enum TxEip4844Variant {
34 TxEip4844(TxEip4844),
36 TxEip4844WithSidecar(TxEip4844WithSidecar),
38}
39
40#[cfg(feature = "serde")]
41impl<'de> serde::Deserialize<'de> for TxEip4844Variant {
42 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
43 where
44 D: serde::Deserializer<'de>,
45 {
46 #[derive(serde::Deserialize)]
47 struct TxEip4844SerdeHelper {
48 #[serde(flatten)]
49 #[doc(alias = "transaction")]
50 tx: TxEip4844,
51 #[serde(flatten)]
52 sidecar: Option<BlobTransactionSidecar>,
53 }
54
55 let tx = TxEip4844SerdeHelper::deserialize(deserializer)?;
56
57 if let Some(sidecar) = tx.sidecar {
58 Ok(TxEip4844WithSidecar::from_tx_and_sidecar(tx.tx, sidecar).into())
59 } else {
60 Ok(tx.tx.into())
61 }
62 }
63}
64
65impl From<Signed<TxEip4844>> for Signed<TxEip4844Variant> {
66 fn from(value: Signed<TxEip4844>) -> Self {
67 let (tx, signature, hash) = value.into_parts();
68 Self::new_unchecked(TxEip4844Variant::TxEip4844(tx), signature, hash)
69 }
70}
71
72impl From<Signed<TxEip4844WithSidecar>> for Signed<TxEip4844Variant> {
73 fn from(value: Signed<TxEip4844WithSidecar>) -> Self {
74 let (tx, signature, hash) = value.into_parts();
75 Self::new_unchecked(TxEip4844Variant::TxEip4844WithSidecar(tx), signature, hash)
76 }
77}
78
79impl From<TxEip4844WithSidecar> for TxEip4844Variant {
80 fn from(tx: TxEip4844WithSidecar) -> Self {
81 Self::TxEip4844WithSidecar(tx)
82 }
83}
84
85impl From<TxEip4844> for TxEip4844Variant {
86 fn from(tx: TxEip4844) -> Self {
87 Self::TxEip4844(tx)
88 }
89}
90
91impl From<(TxEip4844, BlobTransactionSidecar)> for TxEip4844Variant {
92 fn from((tx, sidecar): (TxEip4844, BlobTransactionSidecar)) -> Self {
93 TxEip4844WithSidecar::from_tx_and_sidecar(tx, sidecar).into()
94 }
95}
96
97impl From<TxEip4844Variant> for TxEip4844 {
98 fn from(tx: TxEip4844Variant) -> Self {
99 match tx {
100 TxEip4844Variant::TxEip4844(tx) => tx,
101 TxEip4844Variant::TxEip4844WithSidecar(tx) => tx.tx,
102 }
103 }
104}
105impl AsRef<TxEip4844> for TxEip4844Variant {
106 fn as_ref(&self) -> &TxEip4844 {
107 match self {
108 Self::TxEip4844(tx) => tx,
109 Self::TxEip4844WithSidecar(tx) => &tx.tx,
110 }
111 }
112}
113
114impl AsMut<TxEip4844> for TxEip4844Variant {
115 fn as_mut(&mut self) -> &mut TxEip4844 {
116 match self {
117 Self::TxEip4844(tx) => tx,
118 Self::TxEip4844WithSidecar(tx) => &mut tx.tx,
119 }
120 }
121}
122impl TxEip4844Variant {
123 #[cfg(feature = "kzg")]
127 pub fn validate(
128 &self,
129 proof_settings: &c_kzg::KzgSettings,
130 ) -> Result<(), BlobTransactionValidationError> {
131 match self {
132 Self::TxEip4844(_) => Err(BlobTransactionValidationError::MissingSidecar),
133 Self::TxEip4844WithSidecar(tx) => tx.validate_blob(proof_settings),
134 }
135 }
136
137 #[doc(alias = "transaction_type")]
139 pub const fn tx_type() -> TxType {
140 TxType::Eip4844
141 }
142
143 #[doc(alias = "transaction")]
145 pub const fn tx(&self) -> &TxEip4844 {
146 match self {
147 Self::TxEip4844(tx) => tx,
148 Self::TxEip4844WithSidecar(tx) => tx.tx(),
149 }
150 }
151
152 #[inline]
154 pub fn size(&self) -> usize {
155 match self {
156 Self::TxEip4844(tx) => tx.size(),
157 Self::TxEip4844WithSidecar(tx) => tx.size(),
158 }
159 }
160
161 pub fn try_into_4844_with_sidecar(self) -> Result<TxEip4844WithSidecar, Self> {
164 match self {
165 Self::TxEip4844WithSidecar(tx) => Ok(tx),
166 _ => Err(self),
167 }
168 }
169}
170
171impl Transaction for TxEip4844Variant {
172 #[inline]
173 fn chain_id(&self) -> Option<ChainId> {
174 match self {
175 Self::TxEip4844(tx) => Some(tx.chain_id),
176 Self::TxEip4844WithSidecar(tx) => Some(tx.tx().chain_id),
177 }
178 }
179
180 #[inline]
181 fn nonce(&self) -> u64 {
182 match self {
183 Self::TxEip4844(tx) => tx.nonce,
184 Self::TxEip4844WithSidecar(tx) => tx.tx().nonce,
185 }
186 }
187
188 #[inline]
189 fn gas_limit(&self) -> u64 {
190 match self {
191 Self::TxEip4844(tx) => tx.gas_limit,
192 Self::TxEip4844WithSidecar(tx) => tx.tx().gas_limit,
193 }
194 }
195
196 #[inline]
197 fn gas_price(&self) -> Option<u128> {
198 None
199 }
200
201 #[inline]
202 fn max_fee_per_gas(&self) -> u128 {
203 match self {
204 Self::TxEip4844(tx) => tx.max_fee_per_gas(),
205 Self::TxEip4844WithSidecar(tx) => tx.max_fee_per_gas(),
206 }
207 }
208
209 #[inline]
210 fn max_priority_fee_per_gas(&self) -> Option<u128> {
211 match self {
212 Self::TxEip4844(tx) => tx.max_priority_fee_per_gas(),
213 Self::TxEip4844WithSidecar(tx) => tx.max_priority_fee_per_gas(),
214 }
215 }
216
217 #[inline]
218 fn max_fee_per_blob_gas(&self) -> Option<u128> {
219 match self {
220 Self::TxEip4844(tx) => tx.max_fee_per_blob_gas(),
221 Self::TxEip4844WithSidecar(tx) => tx.max_fee_per_blob_gas(),
222 }
223 }
224
225 #[inline]
226 fn priority_fee_or_price(&self) -> u128 {
227 match self {
228 Self::TxEip4844(tx) => tx.priority_fee_or_price(),
229 Self::TxEip4844WithSidecar(tx) => tx.priority_fee_or_price(),
230 }
231 }
232
233 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
234 match self {
235 Self::TxEip4844(tx) => tx.effective_gas_price(base_fee),
236 Self::TxEip4844WithSidecar(tx) => tx.effective_gas_price(base_fee),
237 }
238 }
239
240 #[inline]
241 fn is_dynamic_fee(&self) -> bool {
242 match self {
243 Self::TxEip4844(tx) => tx.is_dynamic_fee(),
244 Self::TxEip4844WithSidecar(tx) => tx.is_dynamic_fee(),
245 }
246 }
247
248 #[inline]
249 fn kind(&self) -> TxKind {
250 match self {
251 Self::TxEip4844(tx) => tx.to,
252 Self::TxEip4844WithSidecar(tx) => tx.tx.to,
253 }
254 .into()
255 }
256
257 #[inline]
258 fn is_create(&self) -> bool {
259 false
260 }
261
262 #[inline]
263 fn value(&self) -> U256 {
264 match self {
265 Self::TxEip4844(tx) => tx.value,
266 Self::TxEip4844WithSidecar(tx) => tx.tx.value,
267 }
268 }
269
270 #[inline]
271 fn input(&self) -> &Bytes {
272 match self {
273 Self::TxEip4844(tx) => tx.input(),
274 Self::TxEip4844WithSidecar(tx) => tx.tx().input(),
275 }
276 }
277
278 #[inline]
279 fn access_list(&self) -> Option<&AccessList> {
280 match self {
281 Self::TxEip4844(tx) => tx.access_list(),
282 Self::TxEip4844WithSidecar(tx) => tx.access_list(),
283 }
284 }
285
286 #[inline]
287 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
288 match self {
289 Self::TxEip4844(tx) => tx.blob_versioned_hashes(),
290 Self::TxEip4844WithSidecar(tx) => tx.blob_versioned_hashes(),
291 }
292 }
293
294 #[inline]
295 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
296 None
297 }
298}
299impl Typed2718 for TxEip4844 {
300 fn ty(&self) -> u8 {
301 TxType::Eip4844 as u8
302 }
303}
304
305impl RlpEcdsaEncodableTx for TxEip4844Variant {
306 fn rlp_encoded_fields_length(&self) -> usize {
307 match self {
308 Self::TxEip4844(inner) => inner.rlp_encoded_fields_length(),
309 Self::TxEip4844WithSidecar(inner) => inner.rlp_encoded_fields_length(),
310 }
311 }
312
313 fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
314 match self {
315 Self::TxEip4844(inner) => inner.rlp_encode_fields(out),
316 Self::TxEip4844WithSidecar(inner) => inner.rlp_encode_fields(out),
317 }
318 }
319
320 fn rlp_header_signed(&self, signature: &Signature) -> Header {
321 match self {
322 Self::TxEip4844(inner) => inner.rlp_header_signed(signature),
323 Self::TxEip4844WithSidecar(inner) => inner.rlp_header_signed(signature),
324 }
325 }
326
327 fn rlp_encode_signed(&self, signature: &Signature, out: &mut dyn BufMut) {
328 match self {
329 Self::TxEip4844(inner) => inner.rlp_encode_signed(signature, out),
330 Self::TxEip4844WithSidecar(inner) => inner.rlp_encode_signed(signature, out),
331 }
332 }
333
334 fn tx_hash_with_type(&self, signature: &Signature, ty: u8) -> alloy_primitives::TxHash {
335 match self {
336 Self::TxEip4844(inner) => inner.tx_hash_with_type(signature, ty),
337 Self::TxEip4844WithSidecar(inner) => inner.tx_hash_with_type(signature, ty),
338 }
339 }
340}
341
342impl RlpEcdsaDecodableTx for TxEip4844Variant {
343 const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 };
344
345 fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
346 let needle = &mut &**buf;
347
348 let trial = &mut &**buf;
351
352 if Header::decode(needle).is_ok_and(|h| h.list) {
359 if let Ok(tx) = TxEip4844WithSidecar::rlp_decode_fields(trial) {
360 *buf = *trial;
361 return Ok(tx.into());
362 }
363 }
364 TxEip4844::rlp_decode_fields(buf).map(Into::into)
365 }
366
367 fn rlp_decode_with_signature(buf: &mut &[u8]) -> alloy_rlp::Result<(Self, Signature)> {
368 let needle = &mut &**buf;
371
372 let trial = &mut &**buf;
375
376 Header::decode(needle)?;
378
379 if Header::decode(needle).is_ok_and(|h| h.list) {
386 if let Ok((tx, signature)) = TxEip4844WithSidecar::rlp_decode_with_signature(trial) {
387 *buf = *trial;
390 return Ok((tx.into(), signature));
391 }
392 }
393 TxEip4844::rlp_decode_with_signature(buf).map(|(tx, signature)| (tx.into(), signature))
394 }
395}
396
397impl Typed2718 for TxEip4844Variant {
398 fn ty(&self) -> u8 {
399 TxType::Eip4844 as u8
400 }
401}
402
403impl SignableTransaction<Signature> for TxEip4844Variant {
404 fn set_chain_id(&mut self, chain_id: ChainId) {
405 match self {
406 Self::TxEip4844(inner) => {
407 inner.set_chain_id(chain_id);
408 }
409 Self::TxEip4844WithSidecar(inner) => {
410 inner.set_chain_id(chain_id);
411 }
412 }
413 }
414
415 fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
416 self.tx().encode_for_signing(out);
422 }
423
424 fn payload_len_for_signature(&self) -> usize {
425 self.tx().payload_len_for_signature()
426 }
427}
428
429#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
433#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
434#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
435#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
436#[doc(alias = "Eip4844Transaction", alias = "TransactionEip4844", alias = "Eip4844Tx")]
437pub struct TxEip4844 {
438 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
440 pub chain_id: ChainId,
441 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
443 pub nonce: u64,
444 #[cfg_attr(
450 feature = "serde",
451 serde(with = "alloy_serde::quantity", rename = "gas", alias = "gasLimit")
452 )]
453 pub gas_limit: u64,
454 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
466 pub max_fee_per_gas: u128,
467 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
475 pub max_priority_fee_per_gas: u128,
476 pub to: Address,
478 pub value: U256,
483 pub access_list: AccessList,
489
490 pub blob_versioned_hashes: Vec<B256>,
492
493 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
497 pub max_fee_per_blob_gas: u128,
498
499 pub input: Bytes,
505}
506
507impl TxEip4844 {
508 #[inline]
510 pub fn blob_gas(&self) -> u64 {
511 self.blob_versioned_hashes.len() as u64 * DATA_GAS_PER_BLOB
513 }
514
515 #[cfg(feature = "kzg")]
529 pub fn validate_blob(
530 &self,
531 sidecar: &BlobTransactionSidecar,
532 proof_settings: &c_kzg::KzgSettings,
533 ) -> Result<(), BlobTransactionValidationError> {
534 sidecar.validate(&self.blob_versioned_hashes, proof_settings)
535 }
536
537 #[doc(alias = "transaction_type")]
539 pub const fn tx_type() -> TxType {
540 TxType::Eip4844
541 }
542
543 #[inline]
545 pub fn size(&self) -> usize {
546 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>() }
558}
559
560impl RlpEcdsaEncodableTx for TxEip4844 {
561 fn rlp_encoded_fields_length(&self) -> usize {
562 self.chain_id.length()
563 + self.nonce.length()
564 + self.gas_limit.length()
565 + self.max_fee_per_gas.length()
566 + self.max_priority_fee_per_gas.length()
567 + self.to.length()
568 + self.value.length()
569 + self.access_list.length()
570 + self.blob_versioned_hashes.length()
571 + self.max_fee_per_blob_gas.length()
572 + self.input.0.length()
573 }
574
575 fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
576 self.chain_id.encode(out);
577 self.nonce.encode(out);
578 self.max_priority_fee_per_gas.encode(out);
579 self.max_fee_per_gas.encode(out);
580 self.gas_limit.encode(out);
581 self.to.encode(out);
582 self.value.encode(out);
583 self.input.0.encode(out);
584 self.access_list.encode(out);
585 self.max_fee_per_blob_gas.encode(out);
586 self.blob_versioned_hashes.encode(out);
587 }
588}
589
590impl RlpEcdsaDecodableTx for TxEip4844 {
591 const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 };
592
593 fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
594 Ok(Self {
595 chain_id: Decodable::decode(buf)?,
596 nonce: Decodable::decode(buf)?,
597 max_priority_fee_per_gas: Decodable::decode(buf)?,
598 max_fee_per_gas: Decodable::decode(buf)?,
599 gas_limit: Decodable::decode(buf)?,
600 to: Decodable::decode(buf)?,
601 value: Decodable::decode(buf)?,
602 input: Decodable::decode(buf)?,
603 access_list: Decodable::decode(buf)?,
604 max_fee_per_blob_gas: Decodable::decode(buf)?,
605 blob_versioned_hashes: Decodable::decode(buf)?,
606 })
607 }
608}
609
610impl SignableTransaction<Signature> for TxEip4844 {
611 fn set_chain_id(&mut self, chain_id: ChainId) {
612 self.chain_id = chain_id;
613 }
614
615 fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
616 out.put_u8(Self::tx_type() as u8);
617 self.encode(out);
618 }
619
620 fn payload_len_for_signature(&self) -> usize {
621 self.length() + 1
622 }
623}
624
625impl Transaction for TxEip4844 {
626 #[inline]
627 fn chain_id(&self) -> Option<ChainId> {
628 Some(self.chain_id)
629 }
630
631 #[inline]
632 fn nonce(&self) -> u64 {
633 self.nonce
634 }
635
636 #[inline]
637 fn gas_limit(&self) -> u64 {
638 self.gas_limit
639 }
640
641 #[inline]
642 fn gas_price(&self) -> Option<u128> {
643 None
644 }
645
646 #[inline]
647 fn max_fee_per_gas(&self) -> u128 {
648 self.max_fee_per_gas
649 }
650
651 #[inline]
652 fn max_priority_fee_per_gas(&self) -> Option<u128> {
653 Some(self.max_priority_fee_per_gas)
654 }
655
656 #[inline]
657 fn max_fee_per_blob_gas(&self) -> Option<u128> {
658 Some(self.max_fee_per_blob_gas)
659 }
660
661 #[inline]
662 fn priority_fee_or_price(&self) -> u128 {
663 self.max_priority_fee_per_gas
664 }
665
666 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
667 base_fee.map_or(self.max_fee_per_gas, |base_fee| {
668 let tip = self.max_fee_per_gas.saturating_sub(base_fee as u128);
671 if tip > self.max_priority_fee_per_gas {
672 self.max_priority_fee_per_gas + base_fee as u128
673 } else {
674 self.max_fee_per_gas
676 }
677 })
678 }
679
680 #[inline]
681 fn is_dynamic_fee(&self) -> bool {
682 true
683 }
684
685 #[inline]
686 fn kind(&self) -> TxKind {
687 self.to.into()
688 }
689
690 #[inline]
691 fn is_create(&self) -> bool {
692 false
693 }
694
695 #[inline]
696 fn value(&self) -> U256 {
697 self.value
698 }
699
700 #[inline]
701 fn input(&self) -> &Bytes {
702 &self.input
703 }
704
705 #[inline]
706 fn access_list(&self) -> Option<&AccessList> {
707 Some(&self.access_list)
708 }
709
710 #[inline]
711 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
712 Some(&self.blob_versioned_hashes)
713 }
714
715 #[inline]
716 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
717 None
718 }
719}
720
721impl Encodable for TxEip4844 {
722 fn encode(&self, out: &mut dyn BufMut) {
723 self.rlp_encode(out);
724 }
725
726 fn length(&self) -> usize {
727 self.rlp_encoded_length()
728 }
729}
730
731impl Decodable for TxEip4844 {
732 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
733 Self::rlp_decode(buf)
734 }
735}
736
737impl From<TxEip4844WithSidecar> for TxEip4844 {
738 fn from(tx_with_sidecar: TxEip4844WithSidecar) -> Self {
740 tx_with_sidecar.tx
741 }
742}
743
744#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
754#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
755#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
756#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
757#[doc(alias = "Eip4844TransactionWithSidecar", alias = "Eip4844TxWithSidecar")]
758pub struct TxEip4844WithSidecar {
759 #[cfg_attr(feature = "serde", serde(flatten))]
761 #[doc(alias = "transaction")]
762 pub tx: TxEip4844,
763 #[cfg_attr(feature = "serde", serde(flatten))]
765 pub sidecar: BlobTransactionSidecar,
766}
767
768impl TxEip4844WithSidecar {
769 #[doc(alias = "from_transaction_and_sidecar")]
771 pub const fn from_tx_and_sidecar(tx: TxEip4844, sidecar: BlobTransactionSidecar) -> Self {
772 Self { tx, sidecar }
773 }
774
775 #[cfg(feature = "kzg")]
779 pub fn validate_blob(
780 &self,
781 proof_settings: &c_kzg::KzgSettings,
782 ) -> Result<(), BlobTransactionValidationError> {
783 self.tx.validate_blob(&self.sidecar, proof_settings)
784 }
785
786 #[doc(alias = "transaction_type")]
788 pub const fn tx_type() -> TxType {
789 TxEip4844::tx_type()
790 }
791
792 #[doc(alias = "transaction")]
794 pub const fn tx(&self) -> &TxEip4844 {
795 &self.tx
796 }
797
798 pub const fn sidecar(&self) -> &BlobTransactionSidecar {
800 &self.sidecar
801 }
802
803 pub fn into_sidecar(self) -> BlobTransactionSidecar {
805 self.sidecar
806 }
807
808 pub fn into_parts(self) -> (TxEip4844, BlobTransactionSidecar) {
811 (self.tx, self.sidecar)
812 }
813
814 #[inline]
816 pub fn size(&self) -> usize {
817 self.tx.size() + self.sidecar.size()
818 }
819}
820
821impl SignableTransaction<Signature> for TxEip4844WithSidecar {
822 fn set_chain_id(&mut self, chain_id: ChainId) {
823 self.tx.chain_id = chain_id;
824 }
825
826 fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
827 self.tx.encode_for_signing(out);
833 }
834
835 fn payload_len_for_signature(&self) -> usize {
836 self.tx.payload_len_for_signature()
839 }
840}
841
842impl Transaction for TxEip4844WithSidecar {
843 #[inline]
844 fn chain_id(&self) -> Option<ChainId> {
845 self.tx.chain_id()
846 }
847
848 #[inline]
849 fn nonce(&self) -> u64 {
850 self.tx.nonce()
851 }
852
853 #[inline]
854 fn gas_limit(&self) -> u64 {
855 self.tx.gas_limit()
856 }
857
858 #[inline]
859 fn gas_price(&self) -> Option<u128> {
860 self.tx.gas_price()
861 }
862
863 #[inline]
864 fn max_fee_per_gas(&self) -> u128 {
865 self.tx.max_fee_per_gas()
866 }
867
868 #[inline]
869 fn max_priority_fee_per_gas(&self) -> Option<u128> {
870 self.tx.max_priority_fee_per_gas()
871 }
872
873 #[inline]
874 fn max_fee_per_blob_gas(&self) -> Option<u128> {
875 self.tx.max_fee_per_blob_gas()
876 }
877
878 #[inline]
879 fn priority_fee_or_price(&self) -> u128 {
880 self.tx.priority_fee_or_price()
881 }
882
883 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
884 self.tx.effective_gas_price(base_fee)
885 }
886
887 #[inline]
888 fn is_dynamic_fee(&self) -> bool {
889 self.tx.is_dynamic_fee()
890 }
891
892 #[inline]
893 fn kind(&self) -> TxKind {
894 self.tx.kind()
895 }
896
897 #[inline]
898 fn is_create(&self) -> bool {
899 false
900 }
901
902 #[inline]
903 fn value(&self) -> U256 {
904 self.tx.value()
905 }
906
907 #[inline]
908 fn input(&self) -> &Bytes {
909 self.tx.input()
910 }
911
912 #[inline]
913 fn access_list(&self) -> Option<&AccessList> {
914 Some(&self.tx.access_list)
915 }
916
917 #[inline]
918 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
919 self.tx.blob_versioned_hashes()
920 }
921
922 #[inline]
923 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
924 None
925 }
926}
927
928impl Typed2718 for TxEip4844WithSidecar {
929 fn ty(&self) -> u8 {
930 TxType::Eip4844 as u8
931 }
932}
933
934impl RlpEcdsaEncodableTx for TxEip4844WithSidecar {
935 fn rlp_encoded_fields_length(&self) -> usize {
936 self.sidecar.rlp_encoded_fields_length() + self.tx.rlp_encoded_length()
937 }
938
939 fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
940 self.tx.rlp_encode(out);
941 self.sidecar.rlp_encode_fields(out);
942 }
943
944 fn rlp_header_signed(&self, signature: &Signature) -> Header {
945 let payload_length = self.tx.rlp_encoded_length_with_signature(signature)
946 + self.sidecar.rlp_encoded_fields_length();
947 Header { list: true, payload_length }
948 }
949
950 fn rlp_encode_signed(&self, signature: &Signature, out: &mut dyn BufMut) {
951 self.rlp_header_signed(signature).encode(out);
952 self.tx.rlp_encode_signed(signature, out);
953 self.sidecar.rlp_encode_fields(out);
954 }
955
956 fn tx_hash_with_type(&self, signature: &Signature, ty: u8) -> alloy_primitives::TxHash {
957 self.tx.tx_hash_with_type(signature, ty)
959 }
960}
961
962impl RlpEcdsaDecodableTx for TxEip4844WithSidecar {
963 const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 };
964
965 fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
966 let tx = TxEip4844::rlp_decode(buf)?;
967 let sidecar = BlobTransactionSidecar::rlp_decode_fields(buf)?;
968 Ok(Self { tx, sidecar })
969 }
970
971 fn rlp_decode_with_signature(buf: &mut &[u8]) -> alloy_rlp::Result<(Self, Signature)> {
972 let header = Header::decode(buf)?;
973 if !header.list {
974 return Err(alloy_rlp::Error::UnexpectedString);
975 }
976 let remaining = buf.len();
977
978 let (tx, signature) = TxEip4844::rlp_decode_with_signature(buf)?;
979 let sidecar = BlobTransactionSidecar::rlp_decode_fields(buf)?;
980
981 if buf.len() + header.payload_length != remaining {
982 return Err(alloy_rlp::Error::UnexpectedLength);
983 }
984
985 Ok((Self { tx, sidecar }, signature))
986 }
987}
988
989#[cfg(test)]
990mod tests {
991 use super::{BlobTransactionSidecar, TxEip4844, TxEip4844WithSidecar};
992 use crate::{transaction::eip4844::TxEip4844Variant, SignableTransaction, TxEnvelope};
993 use alloy_eips::eip2930::AccessList;
994 use alloy_primitives::{address, b256, bytes, PrimitiveSignature as Signature, U256};
995 use alloy_rlp::{Decodable, Encodable};
996
997 #[test]
998 fn different_sidecar_same_hash() {
999 let tx = TxEip4844 {
1002 chain_id: 1,
1003 nonce: 1,
1004 max_priority_fee_per_gas: 1,
1005 max_fee_per_gas: 1,
1006 gas_limit: 1,
1007 to: Default::default(),
1008 value: U256::from(1),
1009 access_list: Default::default(),
1010 blob_versioned_hashes: vec![Default::default()],
1011 max_fee_per_blob_gas: 1,
1012 input: Default::default(),
1013 };
1014 let sidecar = BlobTransactionSidecar {
1015 blobs: vec![[2; 131072].into()],
1016 commitments: vec![[3; 48].into()],
1017 proofs: vec![[4; 48].into()],
1018 };
1019 let mut tx = TxEip4844WithSidecar { tx, sidecar };
1020 let signature = Signature::test_signature();
1021
1022 let expected_signed = tx.clone().into_signed(signature);
1024
1025 tx.sidecar = BlobTransactionSidecar {
1027 blobs: vec![[1; 131072].into()],
1028 commitments: vec![[1; 48].into()],
1029 proofs: vec![[1; 48].into()],
1030 };
1031
1032 let actual_signed = tx.into_signed(signature);
1034
1035 assert_eq!(expected_signed.hash(), actual_signed.hash());
1037
1038 let expected_envelope: TxEnvelope = expected_signed.into();
1040 let actual_envelope: TxEnvelope = actual_signed.into();
1041
1042 let len = expected_envelope.length();
1044 let mut buf = Vec::with_capacity(len);
1045 expected_envelope.encode(&mut buf);
1046 assert_eq!(buf.len(), len);
1047
1048 assert_eq!(buf.len(), actual_envelope.length());
1051
1052 let decoded = TxEnvelope::decode(&mut &buf[..]).unwrap();
1054 assert_eq!(decoded, expected_envelope);
1055 }
1056
1057 #[test]
1058 fn test_4844_variant_into_signed_correct_hash() {
1059 let tx =
1061 TxEip4844 {
1062 chain_id: 1,
1063 nonce: 15435,
1064 gas_limit: 8000000,
1065 max_fee_per_gas: 10571233596,
1066 max_priority_fee_per_gas: 1000000000,
1067 to: address!("a8cb082a5a689e0d594d7da1e2d72a3d63adc1bd"),
1068 value: U256::ZERO,
1069 access_list: AccessList::default(),
1070 blob_versioned_hashes: vec![
1071 b256!("01e5276d91ac1ddb3b1c2d61295211220036e9a04be24c00f76916cc2659d004"),
1072 b256!("0128eb58aff09fd3a7957cd80aa86186d5849569997cdfcfa23772811b706cc2"),
1073 ],
1074 max_fee_per_blob_gas: 1,
1075 input: bytes!("701f58c50000000000000000000000000000000000000000000000000000000000073fb1ed12e288def5b439ea074b398dbb4c967f2852baac3238c5fe4b62b871a59a6d00000000000000000000000000000000000000000000000000000000123971da000000000000000000000000000000000000000000000000000000000000000ac39b2a24e1dbdd11a1e7bd7c0f4dfd7d9b9cfa0997d033ad05f961ba3b82c6c83312c967f10daf5ed2bffe309249416e03ee0b101f2b84d2102b9e38b0e4dfdf0000000000000000000000000000000000000000000000000000000066254c8b538dcc33ecf5334bbd294469f9d4fd084a3090693599a46d6c62567747cbc8660000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000073fb20000000000000000000000000000000000000000000000000000000066254da10000000000000000000000000000000000000000000000000000000012397d5e20b09b263779fda4171c341e720af8fa469621ff548651f8dbbc06c2d320400c000000000000000000000000000000000000000000000000000000000000000b50a833bb11af92814e99c6ff7cf7ba7042827549d6f306a04270753702d897d8fc3c411b99159939ac1c16d21d3057ddc8b2333d1331ab34c938cff0eb29ce2e43241c170344db6819f76b1f1e0ab8206f3ec34120312d275c4f5bbea7f5c55700000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000480000000000000000000000000000000000000000000000000000000000000031800000000000000000000000000000000000000000000800b0000000000000000000000000000000000000000000000000000000000000004ed12e288def5b439ea074b398dbb4c967f2852baac3238c5fe4b62b871a59a6d00000ca8000000000000000000000000000000000000800b000000000000000000000000000000000000000000000000000000000000000300000000000000000000000066254da100000000000000000000000066254e9d00010ca80000000000000000000000000000000000008001000000000000000000000000000000000000000000000000000000000000000550a833bb11af92814e99c6ff7cf7ba7042827549d6f306a04270753702d897d800010ca800000000000000000000000000000000000080010000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000b00010ca8000000000000000000000000000000000000801100000000000000000000000000000000000000000000000000000000000000075c1cd5bd0fd333ce9d7c8edfc79f43b8f345b4a394f6aba12a2cc78ce4012ed700010ca80000000000000000000000000000000000008011000000000000000000000000000000000000000000000000000000000000000845392775318aa47beaafbdc827da38c9f1e88c3bdcabba2cb493062e17cbf21e00010ca800000000000000000000000000000000000080080000000000000000000000000000000000000000000000000000000000000000c094e20e7ac9b433f44a5885e3bdc07e51b309aeb993caa24ba84a661ac010c100010ca800000000000000000000000000000000000080080000000000000000000000000000000000000000000000000000000000000001ab42db8f4ed810bdb143368a2b641edf242af6e3d0de8b1486e2b0e7880d431100010ca8000000000000000000000000000000000000800800000000000000000000000000000000000000000000000000000000000000022d94e4cc4525e4e2d81e8227b6172e97076431a2cf98792d978035edd6e6f3100000000000000000000000000000000000000000000000000000000000000000000000000000012101c74dfb80a80fccb9a4022b2406f79f56305e6a7c931d30140f5d372fe793837e93f9ec6b8d89a9d0ab222eeb27547f66b90ec40fbbdd2a4936b0b0c19ca684ff78888fbf5840d7c8dc3c493b139471750938d7d2c443e2d283e6c5ee9fde3765a756542c42f002af45c362b4b5b1687a8fc24cbf16532b903f7bb289728170dcf597f5255508c623ba247735538376f494cdcdd5bd0c4cb067526eeda0f4745a28d8baf8893ecc1b8cee80690538d66455294a028da03ff2add9d8a88e6ee03ba9ffe3ad7d91d6ac9c69a1f28c468f00fe55eba5651a2b32dc2458e0d14b4dd6d0173df255cd56aa01e8e38edec17ea8933f68543cbdc713279d195551d4211bed5c91f77259a695e6768f6c4b110b2158fcc42423a96dcc4e7f6fddb3e2369d00000000000000000000000000000000000000000000000000000000000000") };
1076 let variant = TxEip4844Variant::TxEip4844(tx);
1077
1078 let signature = Signature::new(
1079 b256!("6c173c3c8db3e3299f2f728d293b912c12e75243e3aa66911c2329b58434e2a4").into(),
1080 b256!("7dd4d1c228cedc5a414a668ab165d9e888e61e4c3b44cd7daf9cdcc4cec5d6b2").into(),
1081 false,
1082 );
1083
1084 let signed = variant.into_signed(signature);
1085 assert_eq!(
1086 *signed.hash(),
1087 b256!("93fc9daaa0726c3292a2e939df60f7e773c6a6a726a61ce43f4a217c64d85e87")
1088 );
1089 }
1090}