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}
105
106impl TxEip4844Variant {
107 #[cfg(feature = "kzg")]
111 pub fn validate(
112 &self,
113 proof_settings: &c_kzg::KzgSettings,
114 ) -> Result<(), BlobTransactionValidationError> {
115 match self {
116 Self::TxEip4844(_) => Err(BlobTransactionValidationError::MissingSidecar),
117 Self::TxEip4844WithSidecar(tx) => tx.validate_blob(proof_settings),
118 }
119 }
120
121 #[doc(alias = "transaction_type")]
123 pub const fn tx_type() -> TxType {
124 TxType::Eip4844
125 }
126
127 #[doc(alias = "transaction")]
129 pub const fn tx(&self) -> &TxEip4844 {
130 match self {
131 Self::TxEip4844(tx) => tx,
132 Self::TxEip4844WithSidecar(tx) => tx.tx(),
133 }
134 }
135
136 #[inline]
138 pub fn size(&self) -> usize {
139 match self {
140 Self::TxEip4844(tx) => tx.size(),
141 Self::TxEip4844WithSidecar(tx) => tx.size(),
142 }
143 }
144
145 pub fn try_into_4844_with_sidecar(self) -> Result<TxEip4844WithSidecar, Self> {
148 match self {
149 Self::TxEip4844WithSidecar(tx) => Ok(tx),
150 _ => Err(self),
151 }
152 }
153}
154
155impl Transaction for TxEip4844Variant {
156 #[inline]
157 fn chain_id(&self) -> Option<ChainId> {
158 match self {
159 Self::TxEip4844(tx) => Some(tx.chain_id),
160 Self::TxEip4844WithSidecar(tx) => Some(tx.tx().chain_id),
161 }
162 }
163
164 #[inline]
165 fn nonce(&self) -> u64 {
166 match self {
167 Self::TxEip4844(tx) => tx.nonce,
168 Self::TxEip4844WithSidecar(tx) => tx.tx().nonce,
169 }
170 }
171
172 #[inline]
173 fn gas_limit(&self) -> u64 {
174 match self {
175 Self::TxEip4844(tx) => tx.gas_limit,
176 Self::TxEip4844WithSidecar(tx) => tx.tx().gas_limit,
177 }
178 }
179
180 #[inline]
181 fn gas_price(&self) -> Option<u128> {
182 None
183 }
184
185 #[inline]
186 fn max_fee_per_gas(&self) -> u128 {
187 match self {
188 Self::TxEip4844(tx) => tx.max_fee_per_gas(),
189 Self::TxEip4844WithSidecar(tx) => tx.max_fee_per_gas(),
190 }
191 }
192
193 #[inline]
194 fn max_priority_fee_per_gas(&self) -> Option<u128> {
195 match self {
196 Self::TxEip4844(tx) => tx.max_priority_fee_per_gas(),
197 Self::TxEip4844WithSidecar(tx) => tx.max_priority_fee_per_gas(),
198 }
199 }
200
201 #[inline]
202 fn max_fee_per_blob_gas(&self) -> Option<u128> {
203 match self {
204 Self::TxEip4844(tx) => tx.max_fee_per_blob_gas(),
205 Self::TxEip4844WithSidecar(tx) => tx.max_fee_per_blob_gas(),
206 }
207 }
208
209 #[inline]
210 fn priority_fee_or_price(&self) -> u128 {
211 match self {
212 Self::TxEip4844(tx) => tx.priority_fee_or_price(),
213 Self::TxEip4844WithSidecar(tx) => tx.priority_fee_or_price(),
214 }
215 }
216
217 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
218 match self {
219 Self::TxEip4844(tx) => tx.effective_gas_price(base_fee),
220 Self::TxEip4844WithSidecar(tx) => tx.effective_gas_price(base_fee),
221 }
222 }
223
224 #[inline]
225 fn is_dynamic_fee(&self) -> bool {
226 match self {
227 Self::TxEip4844(tx) => tx.is_dynamic_fee(),
228 Self::TxEip4844WithSidecar(tx) => tx.is_dynamic_fee(),
229 }
230 }
231
232 #[inline]
233 fn kind(&self) -> TxKind {
234 match self {
235 Self::TxEip4844(tx) => tx.to,
236 Self::TxEip4844WithSidecar(tx) => tx.tx.to,
237 }
238 .into()
239 }
240
241 #[inline]
242 fn is_create(&self) -> bool {
243 false
244 }
245
246 #[inline]
247 fn value(&self) -> U256 {
248 match self {
249 Self::TxEip4844(tx) => tx.value,
250 Self::TxEip4844WithSidecar(tx) => tx.tx.value,
251 }
252 }
253
254 #[inline]
255 fn input(&self) -> &Bytes {
256 match self {
257 Self::TxEip4844(tx) => tx.input(),
258 Self::TxEip4844WithSidecar(tx) => tx.tx().input(),
259 }
260 }
261
262 #[inline]
263 fn access_list(&self) -> Option<&AccessList> {
264 match self {
265 Self::TxEip4844(tx) => tx.access_list(),
266 Self::TxEip4844WithSidecar(tx) => tx.access_list(),
267 }
268 }
269
270 #[inline]
271 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
272 match self {
273 Self::TxEip4844(tx) => tx.blob_versioned_hashes(),
274 Self::TxEip4844WithSidecar(tx) => tx.blob_versioned_hashes(),
275 }
276 }
277
278 #[inline]
279 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
280 None
281 }
282}
283impl Typed2718 for TxEip4844 {
284 fn ty(&self) -> u8 {
285 TxType::Eip4844 as u8
286 }
287}
288
289impl RlpEcdsaEncodableTx for TxEip4844Variant {
290 const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 };
291
292 fn rlp_encoded_fields_length(&self) -> usize {
293 match self {
294 Self::TxEip4844(inner) => inner.rlp_encoded_fields_length(),
295 Self::TxEip4844WithSidecar(inner) => inner.rlp_encoded_fields_length(),
296 }
297 }
298
299 fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
300 match self {
301 Self::TxEip4844(inner) => inner.rlp_encode_fields(out),
302 Self::TxEip4844WithSidecar(inner) => inner.rlp_encode_fields(out),
303 }
304 }
305
306 fn rlp_header_signed(&self, signature: &Signature) -> Header {
307 match self {
308 Self::TxEip4844(inner) => inner.rlp_header_signed(signature),
309 Self::TxEip4844WithSidecar(inner) => inner.rlp_header_signed(signature),
310 }
311 }
312
313 fn rlp_encode_signed(&self, signature: &Signature, out: &mut dyn BufMut) {
314 match self {
315 Self::TxEip4844(inner) => inner.rlp_encode_signed(signature, out),
316 Self::TxEip4844WithSidecar(inner) => inner.rlp_encode_signed(signature, out),
317 }
318 }
319
320 fn tx_hash_with_type(&self, signature: &Signature, ty: u8) -> alloy_primitives::TxHash {
321 match self {
322 Self::TxEip4844(inner) => inner.tx_hash_with_type(signature, ty),
323 Self::TxEip4844WithSidecar(inner) => inner.tx_hash_with_type(signature, ty),
324 }
325 }
326}
327
328impl RlpEcdsaDecodableTx for TxEip4844Variant {
329 fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
330 let needle = &mut &**buf;
331
332 let trial = &mut &**buf;
335
336 if Header::decode(needle).is_ok_and(|h| h.list) {
343 if let Ok(tx) = TxEip4844WithSidecar::rlp_decode_fields(trial) {
344 *buf = *trial;
345 return Ok(tx.into());
346 }
347 }
348 TxEip4844::rlp_decode_fields(buf).map(Into::into)
349 }
350
351 fn rlp_decode_with_signature(buf: &mut &[u8]) -> alloy_rlp::Result<(Self, Signature)> {
352 let needle = &mut &**buf;
355
356 let trial = &mut &**buf;
359
360 Header::decode(needle)?;
362
363 if Header::decode(needle).is_ok_and(|h| h.list) {
370 if let Ok((tx, signature)) = TxEip4844WithSidecar::rlp_decode_with_signature(trial) {
371 *buf = *trial;
374 return Ok((tx.into(), signature));
375 }
376 }
377 TxEip4844::rlp_decode_with_signature(buf).map(|(tx, signature)| (tx.into(), signature))
378 }
379}
380
381impl Typed2718 for TxEip4844Variant {
382 fn ty(&self) -> u8 {
383 TxType::Eip4844 as u8
384 }
385}
386
387impl SignableTransaction<Signature> for TxEip4844Variant {
388 fn set_chain_id(&mut self, chain_id: ChainId) {
389 match self {
390 Self::TxEip4844(inner) => {
391 inner.set_chain_id(chain_id);
392 }
393 Self::TxEip4844WithSidecar(inner) => {
394 inner.set_chain_id(chain_id);
395 }
396 }
397 }
398
399 fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
400 self.tx().encode_for_signing(out);
406 }
407
408 fn payload_len_for_signature(&self) -> usize {
409 self.tx().payload_len_for_signature()
410 }
411}
412
413#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
417#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
418#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
419#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
420#[doc(alias = "Eip4844Transaction", alias = "TransactionEip4844", alias = "Eip4844Tx")]
421pub struct TxEip4844 {
422 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
424 pub chain_id: ChainId,
425 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
427 pub nonce: u64,
428 #[cfg_attr(
434 feature = "serde",
435 serde(with = "alloy_serde::quantity", rename = "gas", alias = "gasLimit")
436 )]
437 pub gas_limit: u64,
438 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
450 pub max_fee_per_gas: u128,
451 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
459 pub max_priority_fee_per_gas: u128,
460 pub to: Address,
462 pub value: U256,
467 pub access_list: AccessList,
473
474 pub blob_versioned_hashes: Vec<B256>,
476
477 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
481 pub max_fee_per_blob_gas: u128,
482
483 pub input: Bytes,
489}
490
491impl TxEip4844 {
492 #[inline]
494 pub fn blob_gas(&self) -> u64 {
495 self.blob_versioned_hashes.len() as u64 * DATA_GAS_PER_BLOB
497 }
498
499 #[cfg(feature = "kzg")]
513 pub fn validate_blob(
514 &self,
515 sidecar: &BlobTransactionSidecar,
516 proof_settings: &c_kzg::KzgSettings,
517 ) -> Result<(), BlobTransactionValidationError> {
518 sidecar.validate(&self.blob_versioned_hashes, proof_settings)
519 }
520
521 #[doc(alias = "transaction_type")]
523 pub const fn tx_type() -> TxType {
524 TxType::Eip4844
525 }
526
527 #[inline]
529 pub fn size(&self) -> usize {
530 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>() }
542}
543
544impl RlpEcdsaEncodableTx for TxEip4844 {
545 const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 };
546
547 fn rlp_encoded_fields_length(&self) -> usize {
548 self.chain_id.length()
549 + self.nonce.length()
550 + self.gas_limit.length()
551 + self.max_fee_per_gas.length()
552 + self.max_priority_fee_per_gas.length()
553 + self.to.length()
554 + self.value.length()
555 + self.access_list.length()
556 + self.blob_versioned_hashes.length()
557 + self.max_fee_per_blob_gas.length()
558 + self.input.0.length()
559 }
560
561 fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
562 self.chain_id.encode(out);
563 self.nonce.encode(out);
564 self.max_priority_fee_per_gas.encode(out);
565 self.max_fee_per_gas.encode(out);
566 self.gas_limit.encode(out);
567 self.to.encode(out);
568 self.value.encode(out);
569 self.input.0.encode(out);
570 self.access_list.encode(out);
571 self.max_fee_per_blob_gas.encode(out);
572 self.blob_versioned_hashes.encode(out);
573 }
574}
575
576impl RlpEcdsaDecodableTx for TxEip4844 {
577 fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
578 Ok(Self {
579 chain_id: Decodable::decode(buf)?,
580 nonce: Decodable::decode(buf)?,
581 max_priority_fee_per_gas: Decodable::decode(buf)?,
582 max_fee_per_gas: Decodable::decode(buf)?,
583 gas_limit: Decodable::decode(buf)?,
584 to: Decodable::decode(buf)?,
585 value: Decodable::decode(buf)?,
586 input: Decodable::decode(buf)?,
587 access_list: Decodable::decode(buf)?,
588 max_fee_per_blob_gas: Decodable::decode(buf)?,
589 blob_versioned_hashes: Decodable::decode(buf)?,
590 })
591 }
592}
593
594impl SignableTransaction<Signature> for TxEip4844 {
595 fn set_chain_id(&mut self, chain_id: ChainId) {
596 self.chain_id = chain_id;
597 }
598
599 fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
600 out.put_u8(Self::tx_type() as u8);
601 self.encode(out);
602 }
603
604 fn payload_len_for_signature(&self) -> usize {
605 self.length() + 1
606 }
607}
608
609impl Transaction for TxEip4844 {
610 #[inline]
611 fn chain_id(&self) -> Option<ChainId> {
612 Some(self.chain_id)
613 }
614
615 #[inline]
616 fn nonce(&self) -> u64 {
617 self.nonce
618 }
619
620 #[inline]
621 fn gas_limit(&self) -> u64 {
622 self.gas_limit
623 }
624
625 #[inline]
626 fn gas_price(&self) -> Option<u128> {
627 None
628 }
629
630 #[inline]
631 fn max_fee_per_gas(&self) -> u128 {
632 self.max_fee_per_gas
633 }
634
635 #[inline]
636 fn max_priority_fee_per_gas(&self) -> Option<u128> {
637 Some(self.max_priority_fee_per_gas)
638 }
639
640 #[inline]
641 fn max_fee_per_blob_gas(&self) -> Option<u128> {
642 Some(self.max_fee_per_blob_gas)
643 }
644
645 #[inline]
646 fn priority_fee_or_price(&self) -> u128 {
647 self.max_priority_fee_per_gas
648 }
649
650 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
651 base_fee.map_or(self.max_fee_per_gas, |base_fee| {
652 let tip = self.max_fee_per_gas.saturating_sub(base_fee as u128);
655 if tip > self.max_priority_fee_per_gas {
656 self.max_priority_fee_per_gas + base_fee as u128
657 } else {
658 self.max_fee_per_gas
660 }
661 })
662 }
663
664 #[inline]
665 fn is_dynamic_fee(&self) -> bool {
666 true
667 }
668
669 #[inline]
670 fn kind(&self) -> TxKind {
671 self.to.into()
672 }
673
674 #[inline]
675 fn is_create(&self) -> bool {
676 false
677 }
678
679 #[inline]
680 fn value(&self) -> U256 {
681 self.value
682 }
683
684 #[inline]
685 fn input(&self) -> &Bytes {
686 &self.input
687 }
688
689 #[inline]
690 fn access_list(&self) -> Option<&AccessList> {
691 Some(&self.access_list)
692 }
693
694 #[inline]
695 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
696 Some(&self.blob_versioned_hashes)
697 }
698
699 #[inline]
700 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
701 None
702 }
703}
704
705impl Encodable for TxEip4844 {
706 fn encode(&self, out: &mut dyn BufMut) {
707 self.rlp_encode(out);
708 }
709
710 fn length(&self) -> usize {
711 self.rlp_encoded_length()
712 }
713}
714
715impl Decodable for TxEip4844 {
716 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
717 Self::rlp_decode(buf)
718 }
719}
720
721impl From<TxEip4844WithSidecar> for TxEip4844 {
722 fn from(tx_with_sidecar: TxEip4844WithSidecar) -> Self {
724 tx_with_sidecar.tx
725 }
726}
727
728#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
738#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
739#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
740#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
741#[doc(alias = "Eip4844TransactionWithSidecar", alias = "Eip4844TxWithSidecar")]
742pub struct TxEip4844WithSidecar {
743 #[cfg_attr(feature = "serde", serde(flatten))]
745 #[doc(alias = "transaction")]
746 pub tx: TxEip4844,
747 #[cfg_attr(feature = "serde", serde(flatten))]
749 pub sidecar: BlobTransactionSidecar,
750}
751
752impl TxEip4844WithSidecar {
753 #[doc(alias = "from_transaction_and_sidecar")]
755 pub const fn from_tx_and_sidecar(tx: TxEip4844, sidecar: BlobTransactionSidecar) -> Self {
756 Self { tx, sidecar }
757 }
758
759 #[cfg(feature = "kzg")]
763 pub fn validate_blob(
764 &self,
765 proof_settings: &c_kzg::KzgSettings,
766 ) -> Result<(), BlobTransactionValidationError> {
767 self.tx.validate_blob(&self.sidecar, proof_settings)
768 }
769
770 #[doc(alias = "transaction_type")]
772 pub const fn tx_type() -> TxType {
773 TxEip4844::tx_type()
774 }
775
776 #[doc(alias = "transaction")]
778 pub const fn tx(&self) -> &TxEip4844 {
779 &self.tx
780 }
781
782 pub const fn sidecar(&self) -> &BlobTransactionSidecar {
784 &self.sidecar
785 }
786
787 pub fn into_sidecar(self) -> BlobTransactionSidecar {
789 self.sidecar
790 }
791
792 pub fn into_parts(self) -> (TxEip4844, BlobTransactionSidecar) {
795 (self.tx, self.sidecar)
796 }
797
798 #[inline]
800 pub fn size(&self) -> usize {
801 self.tx.size() + self.sidecar.size()
802 }
803}
804
805impl SignableTransaction<Signature> for TxEip4844WithSidecar {
806 fn set_chain_id(&mut self, chain_id: ChainId) {
807 self.tx.chain_id = chain_id;
808 }
809
810 fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
811 self.tx.encode_for_signing(out);
817 }
818
819 fn payload_len_for_signature(&self) -> usize {
820 self.tx.payload_len_for_signature()
823 }
824}
825
826impl Transaction for TxEip4844WithSidecar {
827 #[inline]
828 fn chain_id(&self) -> Option<ChainId> {
829 self.tx.chain_id()
830 }
831
832 #[inline]
833 fn nonce(&self) -> u64 {
834 self.tx.nonce()
835 }
836
837 #[inline]
838 fn gas_limit(&self) -> u64 {
839 self.tx.gas_limit()
840 }
841
842 #[inline]
843 fn gas_price(&self) -> Option<u128> {
844 self.tx.gas_price()
845 }
846
847 #[inline]
848 fn max_fee_per_gas(&self) -> u128 {
849 self.tx.max_fee_per_gas()
850 }
851
852 #[inline]
853 fn max_priority_fee_per_gas(&self) -> Option<u128> {
854 self.tx.max_priority_fee_per_gas()
855 }
856
857 #[inline]
858 fn max_fee_per_blob_gas(&self) -> Option<u128> {
859 self.tx.max_fee_per_blob_gas()
860 }
861
862 #[inline]
863 fn priority_fee_or_price(&self) -> u128 {
864 self.tx.priority_fee_or_price()
865 }
866
867 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
868 self.tx.effective_gas_price(base_fee)
869 }
870
871 #[inline]
872 fn is_dynamic_fee(&self) -> bool {
873 self.tx.is_dynamic_fee()
874 }
875
876 #[inline]
877 fn kind(&self) -> TxKind {
878 self.tx.kind()
879 }
880
881 #[inline]
882 fn is_create(&self) -> bool {
883 false
884 }
885
886 #[inline]
887 fn value(&self) -> U256 {
888 self.tx.value()
889 }
890
891 #[inline]
892 fn input(&self) -> &Bytes {
893 self.tx.input()
894 }
895
896 #[inline]
897 fn access_list(&self) -> Option<&AccessList> {
898 Some(&self.tx.access_list)
899 }
900
901 #[inline]
902 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
903 self.tx.blob_versioned_hashes()
904 }
905
906 #[inline]
907 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
908 None
909 }
910}
911
912impl Typed2718 for TxEip4844WithSidecar {
913 fn ty(&self) -> u8 {
914 TxType::Eip4844 as u8
915 }
916}
917
918impl RlpEcdsaEncodableTx for TxEip4844WithSidecar {
919 const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 };
920
921 fn rlp_encoded_fields_length(&self) -> usize {
922 self.sidecar.rlp_encoded_fields_length() + self.tx.rlp_encoded_length()
923 }
924
925 fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
926 self.tx.rlp_encode(out);
927 self.sidecar.rlp_encode_fields(out);
928 }
929
930 fn rlp_header_signed(&self, signature: &Signature) -> Header {
931 let payload_length = self.tx.rlp_encoded_length_with_signature(signature)
932 + self.sidecar.rlp_encoded_fields_length();
933 Header { list: true, payload_length }
934 }
935
936 fn rlp_encode_signed(&self, signature: &Signature, out: &mut dyn BufMut) {
937 self.rlp_header_signed(signature).encode(out);
938 self.tx.rlp_encode_signed(signature, out);
939 self.sidecar.rlp_encode_fields(out);
940 }
941
942 fn tx_hash_with_type(&self, signature: &Signature, ty: u8) -> alloy_primitives::TxHash {
943 self.tx.tx_hash_with_type(signature, ty)
945 }
946}
947
948impl RlpEcdsaDecodableTx for TxEip4844WithSidecar {
949 fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
950 let tx = TxEip4844::rlp_decode(buf)?;
951 let sidecar = BlobTransactionSidecar::rlp_decode_fields(buf)?;
952 Ok(Self { tx, sidecar })
953 }
954
955 fn rlp_decode_with_signature(buf: &mut &[u8]) -> alloy_rlp::Result<(Self, Signature)> {
956 let header = Header::decode(buf)?;
957 if !header.list {
958 return Err(alloy_rlp::Error::UnexpectedString);
959 }
960 let remaining = buf.len();
961
962 let (tx, signature) = TxEip4844::rlp_decode_with_signature(buf)?;
963 let sidecar = BlobTransactionSidecar::rlp_decode_fields(buf)?;
964
965 if buf.len() + header.payload_length != remaining {
966 return Err(alloy_rlp::Error::UnexpectedLength);
967 }
968
969 Ok((Self { tx, sidecar }, signature))
970 }
971}
972
973#[cfg(test)]
974mod tests {
975 use super::{BlobTransactionSidecar, TxEip4844, TxEip4844WithSidecar};
976 use crate::{transaction::eip4844::TxEip4844Variant, SignableTransaction, TxEnvelope};
977 use alloy_eips::eip2930::AccessList;
978 use alloy_primitives::{address, b256, bytes, PrimitiveSignature as Signature, U256};
979 use alloy_rlp::{Decodable, Encodable};
980
981 #[test]
982 fn different_sidecar_same_hash() {
983 let tx = TxEip4844 {
986 chain_id: 1,
987 nonce: 1,
988 max_priority_fee_per_gas: 1,
989 max_fee_per_gas: 1,
990 gas_limit: 1,
991 to: Default::default(),
992 value: U256::from(1),
993 access_list: Default::default(),
994 blob_versioned_hashes: vec![Default::default()],
995 max_fee_per_blob_gas: 1,
996 input: Default::default(),
997 };
998 let sidecar = BlobTransactionSidecar {
999 blobs: vec![[2; 131072].into()],
1000 commitments: vec![[3; 48].into()],
1001 proofs: vec![[4; 48].into()],
1002 };
1003 let mut tx = TxEip4844WithSidecar { tx, sidecar };
1004 let signature = Signature::test_signature();
1005
1006 let expected_signed = tx.clone().into_signed(signature);
1008
1009 tx.sidecar = BlobTransactionSidecar {
1011 blobs: vec![[1; 131072].into()],
1012 commitments: vec![[1; 48].into()],
1013 proofs: vec![[1; 48].into()],
1014 };
1015
1016 let actual_signed = tx.into_signed(signature);
1018
1019 assert_eq!(expected_signed.hash(), actual_signed.hash());
1021
1022 let expected_envelope: TxEnvelope = expected_signed.into();
1024 let actual_envelope: TxEnvelope = actual_signed.into();
1025
1026 let len = expected_envelope.length();
1028 let mut buf = Vec::with_capacity(len);
1029 expected_envelope.encode(&mut buf);
1030 assert_eq!(buf.len(), len);
1031
1032 assert_eq!(buf.len(), actual_envelope.length());
1035
1036 let decoded = TxEnvelope::decode(&mut &buf[..]).unwrap();
1038 assert_eq!(decoded, expected_envelope);
1039 }
1040
1041 #[test]
1042 fn test_4844_variant_into_signed_correct_hash() {
1043 let tx =
1045 TxEip4844 {
1046 chain_id: 1,
1047 nonce: 15435,
1048 gas_limit: 8000000,
1049 max_fee_per_gas: 10571233596,
1050 max_priority_fee_per_gas: 1000000000,
1051 to: address!("a8cb082a5a689e0d594d7da1e2d72a3d63adc1bd"),
1052 value: U256::ZERO,
1053 access_list: AccessList::default(),
1054 blob_versioned_hashes: vec![
1055 b256!("01e5276d91ac1ddb3b1c2d61295211220036e9a04be24c00f76916cc2659d004"),
1056 b256!("0128eb58aff09fd3a7957cd80aa86186d5849569997cdfcfa23772811b706cc2"),
1057 ],
1058 max_fee_per_blob_gas: 1,
1059 input: bytes!("701f58c50000000000000000000000000000000000000000000000000000000000073fb1ed12e288def5b439ea074b398dbb4c967f2852baac3238c5fe4b62b871a59a6d00000000000000000000000000000000000000000000000000000000123971da000000000000000000000000000000000000000000000000000000000000000ac39b2a24e1dbdd11a1e7bd7c0f4dfd7d9b9cfa0997d033ad05f961ba3b82c6c83312c967f10daf5ed2bffe309249416e03ee0b101f2b84d2102b9e38b0e4dfdf0000000000000000000000000000000000000000000000000000000066254c8b538dcc33ecf5334bbd294469f9d4fd084a3090693599a46d6c62567747cbc8660000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000073fb20000000000000000000000000000000000000000000000000000000066254da10000000000000000000000000000000000000000000000000000000012397d5e20b09b263779fda4171c341e720af8fa469621ff548651f8dbbc06c2d320400c000000000000000000000000000000000000000000000000000000000000000b50a833bb11af92814e99c6ff7cf7ba7042827549d6f306a04270753702d897d8fc3c411b99159939ac1c16d21d3057ddc8b2333d1331ab34c938cff0eb29ce2e43241c170344db6819f76b1f1e0ab8206f3ec34120312d275c4f5bbea7f5c55700000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000480000000000000000000000000000000000000000000000000000000000000031800000000000000000000000000000000000000000000800b0000000000000000000000000000000000000000000000000000000000000004ed12e288def5b439ea074b398dbb4c967f2852baac3238c5fe4b62b871a59a6d00000ca8000000000000000000000000000000000000800b000000000000000000000000000000000000000000000000000000000000000300000000000000000000000066254da100000000000000000000000066254e9d00010ca80000000000000000000000000000000000008001000000000000000000000000000000000000000000000000000000000000000550a833bb11af92814e99c6ff7cf7ba7042827549d6f306a04270753702d897d800010ca800000000000000000000000000000000000080010000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000b00010ca8000000000000000000000000000000000000801100000000000000000000000000000000000000000000000000000000000000075c1cd5bd0fd333ce9d7c8edfc79f43b8f345b4a394f6aba12a2cc78ce4012ed700010ca80000000000000000000000000000000000008011000000000000000000000000000000000000000000000000000000000000000845392775318aa47beaafbdc827da38c9f1e88c3bdcabba2cb493062e17cbf21e00010ca800000000000000000000000000000000000080080000000000000000000000000000000000000000000000000000000000000000c094e20e7ac9b433f44a5885e3bdc07e51b309aeb993caa24ba84a661ac010c100010ca800000000000000000000000000000000000080080000000000000000000000000000000000000000000000000000000000000001ab42db8f4ed810bdb143368a2b641edf242af6e3d0de8b1486e2b0e7880d431100010ca8000000000000000000000000000000000000800800000000000000000000000000000000000000000000000000000000000000022d94e4cc4525e4e2d81e8227b6172e97076431a2cf98792d978035edd6e6f3100000000000000000000000000000000000000000000000000000000000000000000000000000012101c74dfb80a80fccb9a4022b2406f79f56305e6a7c931d30140f5d372fe793837e93f9ec6b8d89a9d0ab222eeb27547f66b90ec40fbbdd2a4936b0b0c19ca684ff78888fbf5840d7c8dc3c493b139471750938d7d2c443e2d283e6c5ee9fde3765a756542c42f002af45c362b4b5b1687a8fc24cbf16532b903f7bb289728170dcf597f5255508c623ba247735538376f494cdcdd5bd0c4cb067526eeda0f4745a28d8baf8893ecc1b8cee80690538d66455294a028da03ff2add9d8a88e6ee03ba9ffe3ad7d91d6ac9c69a1f28c468f00fe55eba5651a2b32dc2458e0d14b4dd6d0173df255cd56aa01e8e38edec17ea8933f68543cbdc713279d195551d4211bed5c91f77259a695e6768f6c4b110b2158fcc42423a96dcc4e7f6fddb3e2369d00000000000000000000000000000000000000000000000000000000000000") };
1060 let variant = TxEip4844Variant::TxEip4844(tx);
1061
1062 let signature = Signature::new(
1063 b256!("6c173c3c8db3e3299f2f728d293b912c12e75243e3aa66911c2329b58434e2a4").into(),
1064 b256!("7dd4d1c228cedc5a414a668ab165d9e888e61e4c3b44cd7daf9cdcc4cec5d6b2").into(),
1065 false,
1066 );
1067
1068 let signed = variant.into_signed(signature);
1069 assert_eq!(
1070 *signed.hash(),
1071 b256!("93fc9daaa0726c3292a2e939df60f7e773c6a6a726a61ce43f4a217c64d85e87")
1072 );
1073 }
1074}