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