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