alloy_consensus/transaction/
typed.rs

1use crate::{
2    transaction::{
3        eip4844::{TxEip4844, TxEip4844Variant, TxEip4844WithSidecar},
4        RlpEcdsaEncodableTx,
5    },
6    EthereumTxEnvelope, SignableTransaction, Transaction, TxEip1559, TxEip2930, TxEip7702,
7    TxLegacy, TxType,
8};
9use alloy_eips::{eip2930::AccessList, eip7702::SignedAuthorization, Typed2718};
10use alloy_primitives::{bytes::BufMut, Bytes, ChainId, Signature, TxHash, TxKind, B256, U256};
11
12/// Basic typed transaction which can contain both [`TxEip4844`] and [`TxEip4844WithSidecar`].
13pub type TypedTransaction = EthereumTypedTransaction<TxEip4844Variant>;
14
15/// The TypedTransaction enum represents all Ethereum transaction request types.
16///
17/// Its variants correspond to specific allowed transactions:
18/// 1. Legacy (pre-EIP2718) [`TxLegacy`]
19/// 2. EIP2930 (state access lists) [`TxEip2930`]
20/// 3. EIP1559 [`TxEip1559`]
21/// 4. EIP4844 [`TxEip4844Variant`]
22///
23/// This type is generic over Eip4844 variant to support the following cases:
24/// 1. Only-[`TxEip4844`] transaction type, such transaction representation is returned by RPC and
25///    stored by nodes internally.
26/// 2. Only-[`TxEip4844WithSidecar`] transactions which are broadcasted over the network, submitted
27///    to RPC and stored in transaction pool.
28/// 3. Dynamic [`TxEip4844Variant`] transactions to support both of the above cases via a single
29///    type.
30#[derive(Clone, Debug, PartialEq, Eq, Hash)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32#[cfg_attr(
33    feature = "serde",
34    serde(
35        from = "serde_from::MaybeTaggedTypedTransaction<Eip4844>",
36        into = "serde_from::TaggedTypedTransaction<Eip4844>",
37        bound = "Eip4844: Clone + serde::Serialize + serde::de::DeserializeOwned"
38    )
39)]
40#[cfg_attr(all(any(test, feature = "arbitrary"), feature = "k256"), derive(arbitrary::Arbitrary))]
41#[doc(alias = "TypedTx", alias = "TxTyped", alias = "TransactionTyped")]
42pub enum EthereumTypedTransaction<Eip4844> {
43    /// Legacy transaction
44    #[cfg_attr(feature = "serde", serde(rename = "0x00", alias = "0x0"))]
45    Legacy(TxLegacy),
46    /// EIP-2930 transaction
47    #[cfg_attr(feature = "serde", serde(rename = "0x01", alias = "0x1"))]
48    Eip2930(TxEip2930),
49    /// EIP-1559 transaction
50    #[cfg_attr(feature = "serde", serde(rename = "0x02", alias = "0x2"))]
51    Eip1559(TxEip1559),
52    /// EIP-4844 transaction
53    #[cfg_attr(feature = "serde", serde(rename = "0x03", alias = "0x3"))]
54    Eip4844(Eip4844),
55    /// EIP-7702 transaction
56    #[cfg_attr(feature = "serde", serde(rename = "0x04", alias = "0x4"))]
57    Eip7702(TxEip7702),
58}
59
60impl<Eip4844> From<TxLegacy> for EthereumTypedTransaction<Eip4844> {
61    fn from(tx: TxLegacy) -> Self {
62        Self::Legacy(tx)
63    }
64}
65
66impl<Eip4844> From<TxEip2930> for EthereumTypedTransaction<Eip4844> {
67    fn from(tx: TxEip2930) -> Self {
68        Self::Eip2930(tx)
69    }
70}
71
72impl<Eip4844> From<TxEip1559> for EthereumTypedTransaction<Eip4844> {
73    fn from(tx: TxEip1559) -> Self {
74        Self::Eip1559(tx)
75    }
76}
77
78impl<Eip4844: From<TxEip4844>> From<TxEip4844> for EthereumTypedTransaction<Eip4844> {
79    fn from(tx: TxEip4844) -> Self {
80        Self::Eip4844(tx.into())
81    }
82}
83
84impl<Eip4844: From<TxEip4844WithSidecar>> From<TxEip4844WithSidecar>
85    for EthereumTypedTransaction<Eip4844>
86{
87    fn from(tx: TxEip4844WithSidecar) -> Self {
88        Self::Eip4844(tx.into())
89    }
90}
91
92impl<Eip4844: From<TxEip4844Variant>> From<TxEip4844Variant> for EthereumTypedTransaction<Eip4844> {
93    fn from(tx: TxEip4844Variant) -> Self {
94        Self::Eip4844(tx.into())
95    }
96}
97
98impl<Eip4844> From<TxEip7702> for EthereumTypedTransaction<Eip4844> {
99    fn from(tx: TxEip7702) -> Self {
100        Self::Eip7702(tx)
101    }
102}
103
104impl<Eip4844> From<EthereumTxEnvelope<Eip4844>> for EthereumTypedTransaction<Eip4844> {
105    fn from(envelope: EthereumTxEnvelope<Eip4844>) -> Self {
106        match envelope {
107            EthereumTxEnvelope::Legacy(tx) => Self::Legacy(tx.strip_signature()),
108            EthereumTxEnvelope::Eip2930(tx) => Self::Eip2930(tx.strip_signature()),
109            EthereumTxEnvelope::Eip1559(tx) => Self::Eip1559(tx.strip_signature()),
110            EthereumTxEnvelope::Eip4844(tx) => Self::Eip4844(tx.strip_signature()),
111            EthereumTxEnvelope::Eip7702(tx) => Self::Eip7702(tx.strip_signature()),
112        }
113    }
114}
115
116impl<Eip4844: RlpEcdsaEncodableTx> EthereumTypedTransaction<Eip4844> {
117    /// Return the [`TxType`] of the inner txn.
118    #[doc(alias = "transaction_type")]
119    pub const fn tx_type(&self) -> TxType {
120        match self {
121            Self::Legacy(_) => TxType::Legacy,
122            Self::Eip2930(_) => TxType::Eip2930,
123            Self::Eip1559(_) => TxType::Eip1559,
124            Self::Eip4844(_) => TxType::Eip4844,
125            Self::Eip7702(_) => TxType::Eip7702,
126        }
127    }
128
129    /// Return the inner legacy transaction if it exists.
130    pub const fn legacy(&self) -> Option<&TxLegacy> {
131        match self {
132            Self::Legacy(tx) => Some(tx),
133            _ => None,
134        }
135    }
136
137    /// Return the inner EIP-2930 transaction if it exists.
138    pub const fn eip2930(&self) -> Option<&TxEip2930> {
139        match self {
140            Self::Eip2930(tx) => Some(tx),
141            _ => None,
142        }
143    }
144
145    /// Return the inner EIP-1559 transaction if it exists.
146    pub const fn eip1559(&self) -> Option<&TxEip1559> {
147        match self {
148            Self::Eip1559(tx) => Some(tx),
149            _ => None,
150        }
151    }
152
153    /// Return the inner EIP-7702 transaction if it exists.
154    pub const fn eip7702(&self) -> Option<&TxEip7702> {
155        match self {
156            Self::Eip7702(tx) => Some(tx),
157            _ => None,
158        }
159    }
160
161    /// Calculate the transaction hash for the given signature.
162    pub fn tx_hash(&self, signature: &Signature) -> TxHash {
163        match self {
164            Self::Legacy(tx) => tx.tx_hash(signature),
165            Self::Eip2930(tx) => tx.tx_hash(signature),
166            Self::Eip1559(tx) => tx.tx_hash(signature),
167            Self::Eip4844(tx) => tx.tx_hash(signature),
168            Self::Eip7702(tx) => tx.tx_hash(signature),
169        }
170    }
171}
172
173impl<Eip4844: Transaction> Transaction for EthereumTypedTransaction<Eip4844> {
174    #[inline]
175    fn chain_id(&self) -> Option<ChainId> {
176        match self {
177            Self::Legacy(tx) => tx.chain_id(),
178            Self::Eip2930(tx) => tx.chain_id(),
179            Self::Eip1559(tx) => tx.chain_id(),
180            Self::Eip4844(tx) => tx.chain_id(),
181            Self::Eip7702(tx) => tx.chain_id(),
182        }
183    }
184
185    #[inline]
186    fn nonce(&self) -> u64 {
187        match self {
188            Self::Legacy(tx) => tx.nonce(),
189            Self::Eip2930(tx) => tx.nonce(),
190            Self::Eip1559(tx) => tx.nonce(),
191            Self::Eip4844(tx) => tx.nonce(),
192            Self::Eip7702(tx) => tx.nonce(),
193        }
194    }
195
196    #[inline]
197    fn gas_limit(&self) -> u64 {
198        match self {
199            Self::Legacy(tx) => tx.gas_limit(),
200            Self::Eip2930(tx) => tx.gas_limit(),
201            Self::Eip1559(tx) => tx.gas_limit(),
202            Self::Eip4844(tx) => tx.gas_limit(),
203            Self::Eip7702(tx) => tx.gas_limit(),
204        }
205    }
206
207    #[inline]
208    fn gas_price(&self) -> Option<u128> {
209        match self {
210            Self::Legacy(tx) => tx.gas_price(),
211            Self::Eip2930(tx) => tx.gas_price(),
212            Self::Eip1559(tx) => tx.gas_price(),
213            Self::Eip4844(tx) => tx.gas_price(),
214            Self::Eip7702(tx) => tx.gas_price(),
215        }
216    }
217
218    #[inline]
219    fn max_fee_per_gas(&self) -> u128 {
220        match self {
221            Self::Legacy(tx) => tx.max_fee_per_gas(),
222            Self::Eip2930(tx) => tx.max_fee_per_gas(),
223            Self::Eip1559(tx) => tx.max_fee_per_gas(),
224            Self::Eip4844(tx) => tx.max_fee_per_gas(),
225            Self::Eip7702(tx) => tx.max_fee_per_gas(),
226        }
227    }
228
229    #[inline]
230    fn max_priority_fee_per_gas(&self) -> Option<u128> {
231        match self {
232            Self::Legacy(tx) => tx.max_priority_fee_per_gas(),
233            Self::Eip2930(tx) => tx.max_priority_fee_per_gas(),
234            Self::Eip1559(tx) => tx.max_priority_fee_per_gas(),
235            Self::Eip4844(tx) => tx.max_priority_fee_per_gas(),
236            Self::Eip7702(tx) => tx.max_priority_fee_per_gas(),
237        }
238    }
239
240    #[inline]
241    fn max_fee_per_blob_gas(&self) -> Option<u128> {
242        match self {
243            Self::Legacy(tx) => tx.max_fee_per_blob_gas(),
244            Self::Eip2930(tx) => tx.max_fee_per_blob_gas(),
245            Self::Eip1559(tx) => tx.max_fee_per_blob_gas(),
246            Self::Eip4844(tx) => tx.max_fee_per_blob_gas(),
247            Self::Eip7702(tx) => tx.max_fee_per_blob_gas(),
248        }
249    }
250
251    #[inline]
252    fn priority_fee_or_price(&self) -> u128 {
253        match self {
254            Self::Legacy(tx) => tx.priority_fee_or_price(),
255            Self::Eip2930(tx) => tx.priority_fee_or_price(),
256            Self::Eip1559(tx) => tx.priority_fee_or_price(),
257            Self::Eip4844(tx) => tx.priority_fee_or_price(),
258            Self::Eip7702(tx) => tx.priority_fee_or_price(),
259        }
260    }
261
262    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
263        match self {
264            Self::Legacy(tx) => tx.effective_gas_price(base_fee),
265            Self::Eip2930(tx) => tx.effective_gas_price(base_fee),
266            Self::Eip1559(tx) => tx.effective_gas_price(base_fee),
267            Self::Eip4844(tx) => tx.effective_gas_price(base_fee),
268            Self::Eip7702(tx) => tx.effective_gas_price(base_fee),
269        }
270    }
271
272    #[inline]
273    fn is_dynamic_fee(&self) -> bool {
274        match self {
275            Self::Legacy(tx) => tx.is_dynamic_fee(),
276            Self::Eip2930(tx) => tx.is_dynamic_fee(),
277            Self::Eip1559(tx) => tx.is_dynamic_fee(),
278            Self::Eip4844(tx) => tx.is_dynamic_fee(),
279            Self::Eip7702(tx) => tx.is_dynamic_fee(),
280        }
281    }
282
283    #[inline]
284    fn kind(&self) -> TxKind {
285        match self {
286            Self::Legacy(tx) => tx.kind(),
287            Self::Eip2930(tx) => tx.kind(),
288            Self::Eip1559(tx) => tx.kind(),
289            Self::Eip4844(tx) => tx.kind(),
290            Self::Eip7702(tx) => tx.kind(),
291        }
292    }
293
294    #[inline]
295    fn is_create(&self) -> bool {
296        match self {
297            Self::Legacy(tx) => tx.is_create(),
298            Self::Eip2930(tx) => tx.is_create(),
299            Self::Eip1559(tx) => tx.is_create(),
300            Self::Eip4844(tx) => tx.is_create(),
301            Self::Eip7702(tx) => tx.is_create(),
302        }
303    }
304
305    #[inline]
306    fn value(&self) -> U256 {
307        match self {
308            Self::Legacy(tx) => tx.value(),
309            Self::Eip2930(tx) => tx.value(),
310            Self::Eip1559(tx) => tx.value(),
311            Self::Eip4844(tx) => tx.value(),
312            Self::Eip7702(tx) => tx.value(),
313        }
314    }
315
316    #[inline]
317    fn input(&self) -> &Bytes {
318        match self {
319            Self::Legacy(tx) => tx.input(),
320            Self::Eip2930(tx) => tx.input(),
321            Self::Eip1559(tx) => tx.input(),
322            Self::Eip4844(tx) => tx.input(),
323            Self::Eip7702(tx) => tx.input(),
324        }
325    }
326
327    #[inline]
328    fn access_list(&self) -> Option<&AccessList> {
329        match self {
330            Self::Legacy(tx) => tx.access_list(),
331            Self::Eip2930(tx) => tx.access_list(),
332            Self::Eip1559(tx) => tx.access_list(),
333            Self::Eip4844(tx) => tx.access_list(),
334            Self::Eip7702(tx) => tx.access_list(),
335        }
336    }
337
338    #[inline]
339    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
340        match self {
341            Self::Legacy(tx) => tx.blob_versioned_hashes(),
342            Self::Eip2930(tx) => tx.blob_versioned_hashes(),
343            Self::Eip1559(tx) => tx.blob_versioned_hashes(),
344            Self::Eip4844(tx) => tx.blob_versioned_hashes(),
345            Self::Eip7702(tx) => tx.blob_versioned_hashes(),
346        }
347    }
348
349    #[inline]
350    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
351        match self {
352            Self::Legacy(tx) => tx.authorization_list(),
353            Self::Eip2930(tx) => tx.authorization_list(),
354            Self::Eip1559(tx) => tx.authorization_list(),
355            Self::Eip4844(tx) => tx.authorization_list(),
356            Self::Eip7702(tx) => tx.authorization_list(),
357        }
358    }
359}
360
361impl<Eip4844: Typed2718> Typed2718 for EthereumTypedTransaction<Eip4844> {
362    fn ty(&self) -> u8 {
363        match self {
364            Self::Legacy(tx) => tx.ty(),
365            Self::Eip2930(tx) => tx.ty(),
366            Self::Eip1559(tx) => tx.ty(),
367            Self::Eip4844(tx) => tx.ty(),
368            Self::Eip7702(tx) => tx.ty(),
369        }
370    }
371}
372
373impl<Eip4844: RlpEcdsaEncodableTx + Typed2718> RlpEcdsaEncodableTx
374    for EthereumTypedTransaction<Eip4844>
375{
376    fn rlp_encoded_fields_length(&self) -> usize {
377        match self {
378            Self::Legacy(tx) => tx.rlp_encoded_fields_length(),
379            Self::Eip2930(tx) => tx.rlp_encoded_fields_length(),
380            Self::Eip1559(tx) => tx.rlp_encoded_fields_length(),
381            Self::Eip4844(tx) => tx.rlp_encoded_fields_length(),
382            Self::Eip7702(tx) => tx.rlp_encoded_fields_length(),
383        }
384    }
385
386    fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
387        match self {
388            Self::Legacy(tx) => tx.rlp_encode_fields(out),
389            Self::Eip2930(tx) => tx.rlp_encode_fields(out),
390            Self::Eip1559(tx) => tx.rlp_encode_fields(out),
391            Self::Eip4844(tx) => tx.rlp_encode_fields(out),
392            Self::Eip7702(tx) => tx.rlp_encode_fields(out),
393        }
394    }
395
396    fn eip2718_encode_with_type(&self, signature: &Signature, _ty: u8, out: &mut dyn BufMut) {
397        match self {
398            Self::Legacy(tx) => tx.eip2718_encode_with_type(signature, tx.ty(), out),
399            Self::Eip2930(tx) => tx.eip2718_encode_with_type(signature, tx.ty(), out),
400            Self::Eip1559(tx) => tx.eip2718_encode_with_type(signature, tx.ty(), out),
401            Self::Eip4844(tx) => tx.eip2718_encode_with_type(signature, tx.ty(), out),
402            Self::Eip7702(tx) => tx.eip2718_encode_with_type(signature, tx.ty(), out),
403        }
404    }
405
406    fn eip2718_encode(&self, signature: &Signature, out: &mut dyn BufMut) {
407        match self {
408            Self::Legacy(tx) => tx.eip2718_encode(signature, out),
409            Self::Eip2930(tx) => tx.eip2718_encode(signature, out),
410            Self::Eip1559(tx) => tx.eip2718_encode(signature, out),
411            Self::Eip4844(tx) => tx.eip2718_encode(signature, out),
412            Self::Eip7702(tx) => tx.eip2718_encode(signature, out),
413        }
414    }
415
416    fn network_encode_with_type(&self, signature: &Signature, _ty: u8, out: &mut dyn BufMut) {
417        match self {
418            Self::Legacy(tx) => tx.network_encode_with_type(signature, tx.ty(), out),
419            Self::Eip2930(tx) => tx.network_encode_with_type(signature, tx.ty(), out),
420            Self::Eip1559(tx) => tx.network_encode_with_type(signature, tx.ty(), out),
421            Self::Eip4844(tx) => tx.network_encode_with_type(signature, tx.ty(), out),
422            Self::Eip7702(tx) => tx.network_encode_with_type(signature, tx.ty(), out),
423        }
424    }
425
426    fn network_encode(&self, signature: &Signature, out: &mut dyn BufMut) {
427        match self {
428            Self::Legacy(tx) => tx.network_encode(signature, out),
429            Self::Eip2930(tx) => tx.network_encode(signature, out),
430            Self::Eip1559(tx) => tx.network_encode(signature, out),
431            Self::Eip4844(tx) => tx.network_encode(signature, out),
432            Self::Eip7702(tx) => tx.network_encode(signature, out),
433        }
434    }
435
436    fn tx_hash_with_type(&self, signature: &Signature, _ty: u8) -> TxHash {
437        match self {
438            Self::Legacy(tx) => tx.tx_hash_with_type(signature, tx.ty()),
439            Self::Eip2930(tx) => tx.tx_hash_with_type(signature, tx.ty()),
440            Self::Eip1559(tx) => tx.tx_hash_with_type(signature, tx.ty()),
441            Self::Eip4844(tx) => tx.tx_hash_with_type(signature, tx.ty()),
442            Self::Eip7702(tx) => tx.tx_hash_with_type(signature, tx.ty()),
443        }
444    }
445
446    fn tx_hash(&self, signature: &Signature) -> TxHash {
447        match self {
448            Self::Legacy(tx) => tx.tx_hash(signature),
449            Self::Eip2930(tx) => tx.tx_hash(signature),
450            Self::Eip1559(tx) => tx.tx_hash(signature),
451            Self::Eip4844(tx) => tx.tx_hash(signature),
452            Self::Eip7702(tx) => tx.tx_hash(signature),
453        }
454    }
455}
456
457impl<Eip4844: SignableTransaction<Signature>> SignableTransaction<Signature>
458    for EthereumTypedTransaction<Eip4844>
459{
460    fn set_chain_id(&mut self, chain_id: ChainId) {
461        match self {
462            Self::Legacy(tx) => tx.set_chain_id(chain_id),
463            Self::Eip2930(tx) => tx.set_chain_id(chain_id),
464            Self::Eip1559(tx) => tx.set_chain_id(chain_id),
465            Self::Eip4844(tx) => tx.set_chain_id(chain_id),
466            Self::Eip7702(tx) => tx.set_chain_id(chain_id),
467        }
468    }
469
470    fn encode_for_signing(&self, out: &mut dyn BufMut) {
471        match self {
472            Self::Legacy(tx) => tx.encode_for_signing(out),
473            Self::Eip2930(tx) => tx.encode_for_signing(out),
474            Self::Eip1559(tx) => tx.encode_for_signing(out),
475            Self::Eip4844(tx) => tx.encode_for_signing(out),
476            Self::Eip7702(tx) => tx.encode_for_signing(out),
477        }
478    }
479
480    fn payload_len_for_signature(&self) -> usize {
481        match self {
482            Self::Legacy(tx) => tx.payload_len_for_signature(),
483            Self::Eip2930(tx) => tx.payload_len_for_signature(),
484            Self::Eip1559(tx) => tx.payload_len_for_signature(),
485            Self::Eip4844(tx) => tx.payload_len_for_signature(),
486            Self::Eip7702(tx) => tx.payload_len_for_signature(),
487        }
488    }
489}
490
491#[cfg(feature = "serde")]
492impl<Eip4844, T: From<EthereumTypedTransaction<Eip4844>>> From<EthereumTypedTransaction<Eip4844>>
493    for alloy_serde::WithOtherFields<T>
494{
495    fn from(value: EthereumTypedTransaction<Eip4844>) -> Self {
496        Self::new(value.into())
497    }
498}
499
500#[cfg(feature = "serde")]
501impl<Eip4844, T> From<EthereumTxEnvelope<Eip4844>> for alloy_serde::WithOtherFields<T>
502where
503    T: From<EthereumTxEnvelope<Eip4844>>,
504{
505    fn from(value: EthereumTxEnvelope<Eip4844>) -> Self {
506        Self::new(value.into())
507    }
508}
509
510#[cfg(feature = "serde")]
511mod serde_from {
512    //! NB: Why do we need this?
513    //!
514    //! Because the tag may be missing, we need an abstraction over tagged (with
515    //! type) and untagged (always legacy). This is
516    //! [`MaybeTaggedTypedTransaction`].
517    //!
518    //! The tagged variant is [`TaggedTypedTransaction`], which always has a
519    //! type tag.
520    //!
521    //! We serialize via [`TaggedTypedTransaction`] and deserialize via
522    //! [`MaybeTaggedTypedTransaction`].
523    use crate::{EthereumTypedTransaction, TxEip1559, TxEip2930, TxEip7702, TxLegacy};
524
525    #[derive(Debug, serde::Deserialize)]
526    #[serde(untagged)]
527    pub(crate) enum MaybeTaggedTypedTransaction<Eip4844> {
528        Tagged(TaggedTypedTransaction<Eip4844>),
529        Untagged {
530            #[serde(default, rename = "type", deserialize_with = "alloy_serde::reject_if_some")]
531            _ty: Option<()>,
532            #[serde(flatten)]
533            tx: TxLegacy,
534        },
535    }
536
537    #[derive(Debug, serde::Serialize, serde::Deserialize)]
538    #[serde(tag = "type")]
539    pub(crate) enum TaggedTypedTransaction<Eip4844> {
540        /// Legacy transaction
541        #[serde(rename = "0x00", alias = "0x0")]
542        Legacy(TxLegacy),
543        /// EIP-2930 transaction
544        #[serde(rename = "0x01", alias = "0x1")]
545        Eip2930(TxEip2930),
546        /// EIP-1559 transaction
547        #[serde(rename = "0x02", alias = "0x2")]
548        Eip1559(TxEip1559),
549        /// EIP-4844 transaction
550        #[serde(rename = "0x03", alias = "0x3")]
551        Eip4844(Eip4844),
552        /// EIP-7702 transaction
553        #[serde(rename = "0x04", alias = "0x4")]
554        Eip7702(TxEip7702),
555    }
556
557    impl<Eip4844> From<MaybeTaggedTypedTransaction<Eip4844>> for EthereumTypedTransaction<Eip4844> {
558        fn from(value: MaybeTaggedTypedTransaction<Eip4844>) -> Self {
559            match value {
560                MaybeTaggedTypedTransaction::Tagged(tagged) => tagged.into(),
561                MaybeTaggedTypedTransaction::Untagged { tx, .. } => Self::Legacy(tx),
562            }
563        }
564    }
565
566    impl<Eip4844> From<TaggedTypedTransaction<Eip4844>> for EthereumTypedTransaction<Eip4844> {
567        fn from(value: TaggedTypedTransaction<Eip4844>) -> Self {
568            match value {
569                TaggedTypedTransaction::Legacy(signed) => Self::Legacy(signed),
570                TaggedTypedTransaction::Eip2930(signed) => Self::Eip2930(signed),
571                TaggedTypedTransaction::Eip1559(signed) => Self::Eip1559(signed),
572                TaggedTypedTransaction::Eip4844(signed) => Self::Eip4844(signed),
573                TaggedTypedTransaction::Eip7702(signed) => Self::Eip7702(signed),
574            }
575        }
576    }
577
578    impl<Eip4844> From<EthereumTypedTransaction<Eip4844>> for TaggedTypedTransaction<Eip4844> {
579        fn from(value: EthereumTypedTransaction<Eip4844>) -> Self {
580            match value {
581                EthereumTypedTransaction::Legacy(signed) => Self::Legacy(signed),
582                EthereumTypedTransaction::Eip2930(signed) => Self::Eip2930(signed),
583                EthereumTypedTransaction::Eip1559(signed) => Self::Eip1559(signed),
584                EthereumTypedTransaction::Eip4844(signed) => Self::Eip4844(signed),
585                EthereumTypedTransaction::Eip7702(signed) => Self::Eip7702(signed),
586            }
587        }
588    }
589}
590
591/// Bincode-compatible [`EthereumTypedTransaction`] serde implementation.
592#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
593pub(crate) mod serde_bincode_compat {
594    use alloc::borrow::Cow;
595    use serde::{Deserialize, Deserializer, Serialize, Serializer};
596    use serde_with::{DeserializeAs, SerializeAs};
597
598    /// Bincode-compatible [`super::EthereumTypedTransaction`] serde implementation.
599    ///
600    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
601    /// ```rust
602    /// use alloy_consensus::{serde_bincode_compat, EthereumTypedTransaction};
603    /// use serde::{de::DeserializeOwned, Deserialize, Serialize};
604    /// use serde_with::serde_as;
605    ///
606    /// #[serde_as]
607    /// #[derive(Serialize, Deserialize)]
608    /// struct Data<T: Serialize + DeserializeOwned + Clone + 'static> {
609    ///     #[serde_as(as = "serde_bincode_compat::EthereumTypedTransaction<'_, T>")]
610    ///     receipt: EthereumTypedTransaction<T>,
611    /// }
612    /// ```
613    #[derive(Debug, Serialize, Deserialize)]
614    pub enum EthereumTypedTransaction<'a, Eip4844: Clone = crate::transaction::TxEip4844> {
615        /// Legacy transaction
616        Legacy(crate::serde_bincode_compat::transaction::TxLegacy<'a>),
617        /// EIP-2930 transaction
618        Eip2930(crate::serde_bincode_compat::transaction::TxEip2930<'a>),
619        /// EIP-1559 transaction
620        Eip1559(crate::serde_bincode_compat::transaction::TxEip1559<'a>),
621        /// EIP-4844 transaction
622        /// Note: assumes EIP4844 is bincode compatible, which it is because no flatten or skipped
623        /// fields.
624        Eip4844(Cow<'a, Eip4844>),
625        /// EIP-7702 transaction
626        Eip7702(crate::serde_bincode_compat::transaction::TxEip7702<'a>),
627    }
628
629    impl<'a, T: Clone> From<&'a super::EthereumTypedTransaction<T>>
630        for EthereumTypedTransaction<'a, T>
631    {
632        fn from(value: &'a super::EthereumTypedTransaction<T>) -> Self {
633            match value {
634                super::EthereumTypedTransaction::Legacy(tx) => Self::Legacy(tx.into()),
635                super::EthereumTypedTransaction::Eip2930(tx) => Self::Eip2930(tx.into()),
636                super::EthereumTypedTransaction::Eip1559(tx) => Self::Eip1559(tx.into()),
637                super::EthereumTypedTransaction::Eip4844(tx) => Self::Eip4844(Cow::Borrowed(tx)),
638                super::EthereumTypedTransaction::Eip7702(tx) => Self::Eip7702(tx.into()),
639            }
640        }
641    }
642
643    impl<'a, T: Clone> From<EthereumTypedTransaction<'a, T>> for super::EthereumTypedTransaction<T> {
644        fn from(value: EthereumTypedTransaction<'a, T>) -> Self {
645            match value {
646                EthereumTypedTransaction::Legacy(tx) => Self::Legacy(tx.into()),
647                EthereumTypedTransaction::Eip2930(tx) => Self::Eip2930(tx.into()),
648                EthereumTypedTransaction::Eip1559(tx) => Self::Eip1559(tx.into()),
649                EthereumTypedTransaction::Eip4844(tx) => Self::Eip4844(tx.into_owned()),
650                EthereumTypedTransaction::Eip7702(tx) => Self::Eip7702(tx.into()),
651            }
652        }
653    }
654
655    impl<T: Serialize + Clone> SerializeAs<super::EthereumTypedTransaction<T>>
656        for EthereumTypedTransaction<'_, T>
657    {
658        fn serialize_as<S>(
659            source: &super::EthereumTypedTransaction<T>,
660            serializer: S,
661        ) -> Result<S::Ok, S::Error>
662        where
663            S: Serializer,
664        {
665            EthereumTypedTransaction::<'_, T>::from(source).serialize(serializer)
666        }
667    }
668
669    impl<'de, T: Deserialize<'de> + Clone> DeserializeAs<'de, super::EthereumTypedTransaction<T>>
670        for EthereumTypedTransaction<'de, T>
671    {
672        fn deserialize_as<D>(
673            deserializer: D,
674        ) -> Result<super::EthereumTypedTransaction<T>, D::Error>
675        where
676            D: Deserializer<'de>,
677        {
678            EthereumTypedTransaction::<'_, T>::deserialize(deserializer).map(Into::into)
679        }
680    }
681
682    #[cfg(test)]
683    mod tests {
684        use super::super::{serde_bincode_compat, EthereumTypedTransaction};
685        use crate::TxEip4844;
686        use arbitrary::Arbitrary;
687        use bincode::config;
688        use rand::Rng;
689        use serde::{Deserialize, Serialize};
690        use serde_with::serde_as;
691
692        #[test]
693        fn test_typed_tx_bincode_roundtrip() {
694            #[serde_as]
695            #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
696            struct Data {
697                #[serde_as(as = "serde_bincode_compat::EthereumTypedTransaction<'_>")]
698                transaction: EthereumTypedTransaction<TxEip4844>,
699            }
700
701            let mut bytes = [0u8; 1024];
702            rand::thread_rng().fill(bytes.as_mut_slice());
703            let data = Data {
704                transaction: EthereumTypedTransaction::arbitrary(
705                    &mut arbitrary::Unstructured::new(&bytes),
706                )
707                .unwrap(),
708            };
709
710            let encoded = bincode::serde::encode_to_vec(&data, config::legacy()).unwrap();
711            let (decoded, _) =
712                bincode::serde::decode_from_slice::<Data, _>(&encoded, config::legacy()).unwrap();
713            assert_eq!(decoded, data);
714        }
715    }
716}