alloy_consensus/transaction/
typed.rs

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