alloy_tx_macros/
expand.rs

1use crate::parse::{GroupedVariants, VariantKind};
2use proc_macro2::TokenStream;
3use quote::{quote, ToTokens};
4use syn::{Ident, Path};
5
6/// Expander for the TransactionEnvelope derive macro.
7pub(crate) struct Expander {
8    /// The name of the input enum.
9    pub(crate) input_type_name: Ident,
10    /// The name of the generated transaction type enum.
11    pub(crate) tx_type_enum_name: Ident,
12    /// The path to alloy_consensus.
13    pub(crate) alloy_consensus: Path,
14    /// The generics of the input enum.
15    pub(crate) generics: syn::Generics,
16    /// Whether serde feature is enabled.
17    pub(crate) serde_enabled: bool,
18    /// Custom serde cfg_attr.
19    pub(crate) serde_cfg: TokenStream,
20    /// Custom arbitrary cfg_attr.
21    pub(crate) arbitrary_cfg: TokenStream,
22    /// Whether arbitrary feature is enabled.
23    pub(crate) arbitrary_enabled: bool,
24    /// Cached path for alloy_primitives.
25    pub(crate) alloy_primitives: TokenStream,
26    /// Cached path for alloy_eips.
27    pub(crate) alloy_eips: TokenStream,
28    /// Cached path for alloy_rlp.
29    pub(crate) alloy_rlp: TokenStream,
30    /// Grouped variants for code generation.
31    pub(crate) variants: GroupedVariants,
32    /// Optional typed transaction enum name.
33    pub(crate) typed: Option<Ident>,
34}
35
36impl Expander {
37    /// Expand the derive macro into implementations.
38    pub(crate) fn expand(&self) -> TokenStream {
39        let imports = self.generate_imports();
40        let tx_type_enum = self.generate_tx_type_enum();
41        let trait_impls = self.generate_trait_impls();
42        let serde_impls = self.generate_serde_impls();
43        let arbitrary_impls = self.generate_arbitrary_impls();
44        let typed_transaction = self.generate_typed_transaction();
45
46        quote! {
47            #imports
48            #tx_type_enum
49            #trait_impls
50            #serde_impls
51            #arbitrary_impls
52            #typed_transaction
53        }
54    }
55
56    /// Generate necessary imports.
57    fn generate_imports(&self) -> TokenStream {
58        let alloy_eips = &self.alloy_eips;
59        quote! {
60            use #alloy_eips::Encodable2718 as _;
61            use #alloy_eips::Decodable2718 as _;
62        }
63    }
64
65    /// Generate the transaction type enum.
66    fn generate_tx_type_enum(&self) -> TokenStream {
67        let tx_type_enum_name = &self.tx_type_enum_name;
68        let alloy_eips = &self.alloy_eips;
69        let alloy_consensus = &self.alloy_consensus;
70
71        let variants = self.generate_tx_type_variants();
72        let conversions = self.generate_tx_type_conversions();
73        let typed_impls = self.generate_tx_type_typed_impls();
74        let serde_derive = self.generate_tx_type_serde_derive();
75
76        let doc_comment = format!("Transaction types supported by [`{}`].", self.input_type_name);
77
78        quote! {
79            #[doc = #doc_comment]
80            #[repr(u8)]
81            #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
82            #serde_derive
83            pub enum #tx_type_enum_name {
84                #variants
85            }
86
87            #conversions
88            #typed_impls
89
90            impl #alloy_eips::eip2718::IsTyped2718 for #tx_type_enum_name {
91                fn is_type(type_id: u8) -> bool {
92                    Self::try_from(type_id).is_ok()
93                }
94            }
95
96            impl PartialEq<u8> for #tx_type_enum_name {
97                fn eq(&self, other: &u8) -> bool {
98                    u8::from(*self) == *other
99                }
100            }
101
102            impl PartialEq<#tx_type_enum_name> for u8 {
103                fn eq(&self, other: &#tx_type_enum_name) -> bool {
104                    *self == u8::from(*other)
105                }
106            }
107
108            impl #alloy_consensus::private::alloy_rlp::Encodable for #tx_type_enum_name {
109                fn encode(&self, out: &mut dyn #alloy_consensus::private::alloy_rlp::BufMut) {
110                    u8::from(*self).encode(out);
111                }
112
113                fn length(&self) -> usize {
114                    u8::from(*self).length()
115                }
116            }
117
118            impl #alloy_consensus::private::alloy_rlp::Decodable for #tx_type_enum_name {
119                fn decode(buf: &mut &[u8]) -> #alloy_consensus::private::alloy_rlp::Result<Self> {
120                    let ty = u8::decode(buf)?;
121                    Self::try_from(ty).map_err(|_| #alloy_consensus::private::alloy_rlp::Error::Custom("invalid transaction type"))
122                }
123            }
124        }
125    }
126
127    /// Generate variants for the transaction type enum.
128    fn generate_tx_type_variants(&self) -> TokenStream {
129        let alloy_consensus = &self.alloy_consensus;
130        let variants = self.variants.all.iter().map(|v| {
131            let name = &v.name;
132            let ty = &v.ty;
133
134            match &v.kind {
135                VariantKind::Flattened => {
136                    let doc_comment =
137                        format!("Transaction type of an inner `{}`.", ty.to_token_stream());
138                    quote! {
139                        #[doc = #doc_comment]
140                        #name(<#ty as #alloy_consensus::TransactionEnvelope>::TxType)
141                    }
142                }
143                VariantKind::Typed(ty_id) => {
144                    let doc_comment = format!("Transaction type of `{}`.", ty.to_token_stream());
145                    quote! {
146                        #[doc = #doc_comment]
147                        #name = #ty_id
148                    }
149                }
150            }
151        });
152
153        quote! { #(#variants),* }
154    }
155
156    /// Generate conversion implementations for the transaction type enum.
157    fn generate_tx_type_conversions(&self) -> TokenStream {
158        let tx_type_enum_name = &self.tx_type_enum_name;
159        let alloy_primitives = &self.alloy_primitives;
160        let alloy_eips = &self.alloy_eips;
161
162        let from_arms = self.variants.all.iter().map(|v| {
163            let name = &v.name;
164            match &v.kind {
165                VariantKind::Typed(ty_id) => quote! { #tx_type_enum_name::#name => #ty_id },
166                VariantKind::Flattened => {
167                    quote! { #tx_type_enum_name::#name(inner) => inner.into() }
168                }
169            }
170        });
171
172        let try_from_arms = self.variants.all.iter().map(|v| {
173            let name = &v.name;
174            match &v.kind {
175                VariantKind::Flattened => quote! {
176                    if let Ok(inner) = TryFrom::try_from(value) {
177                        return Ok(Self::#name(inner))
178                    }
179                },
180                VariantKind::Typed(ty_id) => quote! {
181                    if value == #ty_id {
182                        return Ok(Self::#name)
183                    }
184                },
185            }
186        });
187
188        quote! {
189            impl From<#tx_type_enum_name> for u8 {
190                fn from(value: #tx_type_enum_name) -> Self {
191                    match value {
192                        #(#from_arms),*
193                    }
194                }
195            }
196
197            impl From<#tx_type_enum_name> for #alloy_primitives::U8 {
198                fn from(value: #tx_type_enum_name) -> Self {
199                    Self::from(u8::from(value))
200                }
201            }
202
203            impl TryFrom<u8> for #tx_type_enum_name {
204                type Error = #alloy_eips::eip2718::Eip2718Error;
205
206                fn try_from(value: u8) -> Result<Self, Self::Error> {
207                    #(#try_from_arms);*
208                    return Err(#alloy_eips::eip2718::Eip2718Error::UnexpectedType(value))
209                }
210            }
211
212            impl TryFrom<u64> for #tx_type_enum_name {
213                type Error = &'static str;
214
215                fn try_from(value: u64) -> Result<Self, Self::Error> {
216                    let err = || "invalid tx type";
217                    let value: u8 = value.try_into().map_err(|_| err())?;
218                    Self::try_from(value).map_err(|_| err())
219                }
220            }
221
222            impl TryFrom<#alloy_primitives::U8> for #tx_type_enum_name {
223                type Error = #alloy_eips::eip2718::Eip2718Error;
224
225                fn try_from(value: #alloy_primitives::U8) -> Result<Self, Self::Error> {
226                    value.to::<u8>().try_into()
227                }
228            }
229
230            impl TryFrom<#alloy_primitives::U64> for #tx_type_enum_name {
231                type Error = &'static str;
232
233                fn try_from(value: #alloy_primitives::U64) -> Result<Self, Self::Error> {
234                    value.to::<u64>().try_into()
235                }
236            }
237        }
238    }
239
240    /// Generate typed implementation for transaction type enum.
241    fn generate_tx_type_typed_impls(&self) -> TokenStream {
242        let tx_type_enum_name = &self.tx_type_enum_name;
243        let alloy_consensus = &self.alloy_consensus;
244
245        let arms = self.variants.all.iter().map(|v| {
246            let name = &v.name;
247            match &v.kind {
248                VariantKind::Flattened => quote! {
249                    Self::#name(inner) => #alloy_consensus::Typed2718::ty(inner)
250                },
251                VariantKind::Typed(ty_id) => quote! {
252                    Self::#name => #ty_id
253                },
254            }
255        });
256
257        quote! {
258            impl #alloy_consensus::Typed2718 for #tx_type_enum_name {
259                fn ty(&self) -> u8 {
260                    match self {
261                        #(#arms),*
262                    }
263                }
264            }
265        }
266    }
267
268    /// Generate serde derive for transaction type enum if enabled.
269    fn generate_tx_type_serde_derive(&self) -> TokenStream {
270        if self.serde_enabled {
271            let alloy_primitives = &self.alloy_primitives;
272            let alloy_consensus = &self.alloy_consensus;
273            let u8_path = quote! { #alloy_primitives::U8 }.to_string();
274            let u64_path = quote! { #alloy_primitives::U64 }.to_string();
275            let serde = quote! { #alloy_consensus::private::serde };
276            let serde_str = serde.to_string();
277            let serde_cfg = &self.serde_cfg;
278
279            quote! {
280                #[cfg_attr(#serde_cfg, derive(#serde::Serialize, #serde::Deserialize))]
281                #[cfg_attr(#serde_cfg, serde(into = #u8_path, try_from = #u64_path, crate = #serde_str))]
282            }
283        } else {
284            quote! {}
285        }
286    }
287
288    /// Generate trait implementations for the main enum.
289    fn generate_trait_impls(&self) -> TokenStream {
290        let eq_impl = self.generate_eq_impl();
291        let hash_impl = self.generate_hash_impl();
292        let transaction_impl = self.generate_transaction_impl(false);
293        let typed_impl = self.generate_typed_impl();
294        let encodable_impl = self.generate_encodable_impl();
295        let decodable_impl = self.generate_decodable_impl();
296        let envelope_impl = self.generate_envelope_impl();
297
298        quote! {
299            #eq_impl
300            #hash_impl
301            #transaction_impl
302            #typed_impl
303            #encodable_impl
304            #decodable_impl
305            #envelope_impl
306        }
307    }
308
309    /// Generate PartialEq and Eq implementations.
310    fn generate_eq_impl(&self) -> TokenStream {
311        let input_type_name = &self.input_type_name;
312        let (impl_generics, ty_generics, _) = self.generics.split_for_impl();
313
314        let variant_names = self.variants.variant_names();
315        let variant_types = self.variants.variant_types();
316
317        quote! {
318            impl #impl_generics PartialEq for #input_type_name #ty_generics
319            where
320                #(#variant_types: PartialEq),*
321            {
322                fn eq(&self, other: &Self) -> bool {
323                    match (self, other) {
324                        #((Self::#variant_names(tx), Self::#variant_names(other)) => tx.eq(other),)*
325                        _ => false,
326                    }
327                }
328            }
329
330            impl #impl_generics Eq for #input_type_name #ty_generics where #(#variant_types: PartialEq),* {}
331        }
332    }
333
334    /// Generate Hash implementation.
335    fn generate_hash_impl(&self) -> TokenStream {
336        let input_type_name = &self.input_type_name;
337        let (impl_generics, ty_generics, _) = self.generics.split_for_impl();
338        let alloy_eips = &self.alloy_eips;
339
340        quote! {
341            impl #impl_generics core::hash::Hash for #input_type_name #ty_generics
342            where
343                Self: #alloy_eips::Encodable2718,
344            {
345                fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
346                    self.trie_hash().hash(state);
347                }
348            }
349        }
350    }
351
352    /// Generate Transaction trait implementation.
353    fn generate_transaction_impl(&self, for_typed: bool) -> TokenStream {
354        let input_type_name =
355            if for_typed { self.typed.as_ref().unwrap() } else { &self.input_type_name };
356        let (impl_generics, ty_generics, _) = self.generics.split_for_impl();
357        let alloy_consensus = &self.alloy_consensus;
358        let alloy_primitives = &self.alloy_primitives;
359        let alloy_eips = &self.alloy_eips;
360
361        let variant_names = self.variants.variant_names();
362        let variant_types = if for_typed {
363            self.variants.typed.iter().map(|v| v.inner_type()).collect::<Vec<_>>()
364        } else {
365            self.variants.variant_types().iter().map(|v| v.to_token_stream()).collect()
366        };
367
368        quote! {
369            impl #impl_generics #alloy_consensus::Transaction for #input_type_name #ty_generics
370            where
371                Self: core::fmt::Debug,
372                #(#variant_types: #alloy_consensus::Transaction),*
373            {
374                #[inline]
375                fn chain_id(&self) -> Option<u64> {
376                    match self { #(Self::#variant_names(tx) => tx.chain_id(),)* }
377                }
378
379                #[inline]
380                fn nonce(&self) -> u64 {
381                    match self { #(Self::#variant_names(tx) => tx.nonce(),)* }
382                }
383
384                #[inline]
385                fn gas_limit(&self) -> u64 {
386                    match self { #(Self::#variant_names(tx) => tx.gas_limit(),)* }
387                }
388
389                #[inline]
390                fn gas_price(&self) -> Option<u128> {
391                    match self { #(Self::#variant_names(tx) => tx.gas_price(),)* }
392                }
393
394                #[inline]
395                fn max_fee_per_gas(&self) -> u128 {
396                    match self { #(Self::#variant_names(tx) => tx.max_fee_per_gas(),)* }
397                }
398
399                #[inline]
400                fn max_priority_fee_per_gas(&self) -> Option<u128> {
401                    match self { #(Self::#variant_names(tx) => tx.max_priority_fee_per_gas(),)* }
402                }
403
404                #[inline]
405                fn max_fee_per_blob_gas(&self) -> Option<u128> {
406                    match self { #(Self::#variant_names(tx) => tx.max_fee_per_blob_gas(),)* }
407                }
408
409                #[inline]
410                fn priority_fee_or_price(&self) -> u128 {
411                    match self { #(Self::#variant_names(tx) => tx.priority_fee_or_price(),)* }
412                }
413
414                #[inline]
415                fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
416                    match self { #(Self::#variant_names(tx) => tx.effective_gas_price(base_fee),)* }
417                }
418
419                #[inline]
420                fn is_dynamic_fee(&self) -> bool {
421                    match self { #(Self::#variant_names(tx) => tx.is_dynamic_fee(),)* }
422                }
423
424                #[inline]
425                fn kind(&self) -> #alloy_primitives::TxKind {
426                    match self { #(Self::#variant_names(tx) => tx.kind(),)* }
427                }
428
429                #[inline]
430                fn is_create(&self) -> bool {
431                    match self { #(Self::#variant_names(tx) => tx.is_create(),)* }
432                }
433
434                #[inline]
435                fn value(&self) -> #alloy_primitives::U256 {
436                    match self { #(Self::#variant_names(tx) => tx.value(),)* }
437                }
438
439                #[inline]
440                fn input(&self) -> &#alloy_primitives::Bytes {
441                    match self { #(Self::#variant_names(tx) => tx.input(),)* }
442                }
443
444                #[inline]
445                fn access_list(&self) -> Option<&#alloy_eips::eip2930::AccessList> {
446                    match self { #(Self::#variant_names(tx) => tx.access_list(),)* }
447                }
448
449                #[inline]
450                fn blob_versioned_hashes(&self) -> Option<&[#alloy_primitives::B256]> {
451                    match self { #(Self::#variant_names(tx) => tx.blob_versioned_hashes(),)* }
452                }
453
454                #[inline]
455                fn authorization_list(&self) -> Option<&[#alloy_eips::eip7702::SignedAuthorization]> {
456                    match self { #(Self::#variant_names(tx) => tx.authorization_list(),)* }
457                }
458            }
459        }
460    }
461
462    /// Generate Typed2718 implementations.
463    fn generate_typed_impl(&self) -> TokenStream {
464        let input_type_name = &self.input_type_name;
465        let tx_type_enum_name = &self.tx_type_enum_name;
466        let (impl_generics, ty_generics, _) = self.generics.split_for_impl();
467        let alloy_consensus = &self.alloy_consensus;
468        let alloy_eips = &self.alloy_eips;
469
470        let variant_names = self.variants.variant_names();
471        let variant_types = self.variants.variant_types();
472
473        quote! {
474            impl #impl_generics #alloy_eips::eip2718::IsTyped2718 for #input_type_name #ty_generics {
475                fn is_type(type_id: u8) -> bool {
476                    <#tx_type_enum_name as #alloy_eips::eip2718::IsTyped2718>::is_type(type_id)
477                }
478            }
479
480            impl #impl_generics #alloy_consensus::Typed2718 for #input_type_name #ty_generics
481            where
482                #(#variant_types: #alloy_eips::eip2718::Typed2718),*
483            {
484                fn ty(&self) -> u8 {
485                    match self {
486                        #(Self::#variant_names(tx) => tx.ty(),)*
487                    }
488                }
489            }
490        }
491    }
492
493    /// Generate Encodable2718 implementation.
494    fn generate_encodable_impl(&self) -> TokenStream {
495        let input_type_name = &self.input_type_name;
496        let (impl_generics, ty_generics, _) = self.generics.split_for_impl();
497        let alloy_primitives = &self.alloy_primitives;
498        let alloy_eips = &self.alloy_eips;
499        let alloy_rlp = &self.alloy_rlp;
500
501        let variant_names = self.variants.variant_names();
502        let variant_types = self.variants.variant_types();
503
504        quote! {
505            impl #impl_generics #alloy_eips::Encodable2718 for #input_type_name #ty_generics
506            where
507                #(#variant_types: #alloy_eips::Encodable2718),*
508            {
509                fn encode_2718_len(&self) -> usize {
510                    match self {
511                        #(Self::#variant_names(tx) => tx.encode_2718_len(),)*
512                    }
513                }
514
515                fn encode_2718(&self, out: &mut dyn #alloy_rlp::BufMut) {
516                    match self {
517                        #(Self::#variant_names(tx) => tx.encode_2718(out),)*
518                    }
519                }
520
521                fn trie_hash(&self) -> #alloy_primitives::B256 {
522                    match self {
523                        #(Self::#variant_names(tx) => tx.trie_hash(),)*
524                    }
525                }
526            }
527
528            impl #impl_generics #alloy_rlp::Encodable for #input_type_name #ty_generics
529            where
530                #(#variant_types: #alloy_eips::Encodable2718),*
531            {
532                fn encode(&self, out: &mut dyn #alloy_rlp::BufMut) {
533                    <Self as #alloy_eips::Encodable2718>::network_encode(self, out)
534                }
535
536                fn length(&self) -> usize {
537                    <Self as #alloy_eips::Encodable2718>::network_len(self)
538                }
539            }
540
541        }
542    }
543
544    /// Generate Decodable2718 implementation.
545    fn generate_decodable_impl(&self) -> TokenStream {
546        let input_type_name = &self.input_type_name;
547        let tx_type_enum_name = &self.tx_type_enum_name;
548        let (impl_generics, ty_generics, _) = self.generics.split_for_impl();
549        let alloy_eips = &self.alloy_eips;
550        let alloy_rlp = &self.alloy_rlp;
551
552        let typed_decode_arms = self.variants.all.iter().map(|v| {
553            let name = &v.name;
554            match &v.kind {
555                VariantKind::Flattened => quote! {
556                    #tx_type_enum_name::#name(_) => Ok(Self::#name(#alloy_eips::Decodable2718::typed_decode(ty, buf)?))
557                },
558                VariantKind::Typed(_) => quote! {
559                    #tx_type_enum_name::#name => Ok(Self::#name(#alloy_eips::Decodable2718::typed_decode(ty, buf)?))
560                },
561            }
562        });
563
564        let fallback_decode_arms = self.variants.all.iter().map(|v| {
565            let name = &v.name;
566            quote! {
567                if let Ok(tx) = #alloy_eips::Decodable2718::fallback_decode(buf) {
568                    return Ok(Self::#name(tx))
569                }
570            }
571        });
572
573        let variant_types = self.variants.variant_types();
574
575        quote! {
576            impl #impl_generics #alloy_eips::Decodable2718 for #input_type_name #ty_generics
577            where
578                #(#variant_types: #alloy_eips::Decodable2718),*
579            {
580                fn typed_decode(ty: u8, buf: &mut &[u8]) -> #alloy_eips::eip2718::Eip2718Result<Self> {
581                    match ty.try_into().map_err(|_| #alloy_rlp::Error::Custom("unexpected tx type"))? {
582                        #(#typed_decode_arms,)*
583                    }
584                }
585
586                fn fallback_decode(buf: &mut &[u8]) -> #alloy_eips::eip2718::Eip2718Result<Self> {
587                    #(#fallback_decode_arms)*
588
589                    return Err(#alloy_eips::eip2718::Eip2718Error::UnexpectedType(0))
590                }
591            }
592
593            impl #impl_generics #alloy_rlp::Decodable for #input_type_name #ty_generics
594            where
595                #(#variant_types: #alloy_eips::Decodable2718),*
596            {
597                fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
598                    Ok(<Self as #alloy_eips::Decodable2718>::network_decode(buf)?)
599                }
600            }
601
602        }
603    }
604
605    /// Generate TransactionEnvelope trait implementation.
606    fn generate_envelope_impl(&self) -> TokenStream {
607        let input_type_name = &self.input_type_name;
608        let tx_type_enum_name = &self.tx_type_enum_name;
609        let (impl_generics, ty_generics, _) = self.generics.split_for_impl();
610        let alloy_consensus = &self.alloy_consensus;
611
612        quote! {
613            impl #impl_generics #alloy_consensus::TransactionEnvelope for #input_type_name #ty_generics
614            where
615                Self: #alloy_consensus::Transaction
616            {
617                type TxType = #tx_type_enum_name;
618            }
619        }
620    }
621
622    /// Generate serde implementations if enabled.
623    fn generate_serde_impls(&self) -> TokenStream {
624        if !self.serde_enabled {
625            return quote! {};
626        }
627
628        crate::serde::SerdeGenerator::new(
629            &self.input_type_name,
630            &self.generics,
631            &self.variants,
632            &self.alloy_consensus,
633            &self.serde_cfg,
634        )
635        .generate()
636    }
637
638    /// Generate arbitrary implementations if enabled.
639    fn generate_arbitrary_impls(&self) -> TokenStream {
640        if !self.arbitrary_enabled {
641            return quote! {};
642        }
643
644        let input_type_name = &self.input_type_name;
645        let tx_type_enum_name = &self.tx_type_enum_name;
646        let (impl_generics, ty_generics, _) = self.generics.split_for_impl();
647        let alloy_consensus = &self.alloy_consensus;
648        let arbitrary = quote! { #alloy_consensus::private::arbitrary };
649        let arbitrary_cfg = &self.arbitrary_cfg;
650
651        let num_variants = self.variants.all.len();
652
653        let tx_type_arms = self.variants.all.iter().enumerate().map(|(i, v)| {
654            let name = &v.name;
655            match &v.kind {
656                VariantKind::Typed(_) => quote! { #i => Ok(Self::#name) },
657                VariantKind::Flattened => quote! { #i => Ok(Self::#name(u.arbitrary()?)) },
658            }
659        });
660
661        let enum_variant_arms = self.variants.all.iter().enumerate().map(|(i, v)| {
662            let name = &v.name;
663            quote! { #i => Ok(Self::#name(u.arbitrary()?)) }
664        });
665
666        let variant_types = self.variants.variant_types();
667
668        quote! {
669            #[cfg(#arbitrary_cfg)]
670            const _: () = {
671                impl #arbitrary::Arbitrary<'_> for #tx_type_enum_name {
672                    fn arbitrary(u: &mut #arbitrary::Unstructured<'_>) -> #arbitrary::Result<Self> {
673                        match u.int_in_range(0..=#num_variants-1)? {
674                            #(#tx_type_arms,)*
675                            _ => unreachable!(),
676                        }
677                    }
678                }
679
680                impl #impl_generics #arbitrary::Arbitrary<'_> for #input_type_name #ty_generics
681                where
682                    #(#variant_types: for<'a> #arbitrary::Arbitrary<'a>),*
683                {
684                    fn arbitrary(u: &mut #arbitrary::Unstructured<'_>) -> #arbitrary::Result<Self> {
685                        match u.int_in_range(0..=#num_variants-1)? {
686                            #(#enum_variant_arms,)*
687                            _ => unreachable!(),
688                        }
689                    }
690                }
691            };
692        }
693    }
694
695    /// Generate typed transaction enum if requested.
696    fn generate_typed_transaction(&self) -> TokenStream {
697        let Some(typed_name) = &self.typed else {
698            return quote! {};
699        };
700
701        let alloy_consensus = &self.alloy_consensus;
702        let arbitrary = quote! { #alloy_consensus::private::arbitrary };
703        let alloy_eips = &self.alloy_eips;
704        let arbitrary_cfg = &self.arbitrary_cfg;
705        let tx_type_enum_name = &self.tx_type_enum_name;
706        let (impl_generics, ty_generics, _) = self.generics.split_for_impl();
707
708        let variant_names = self.variants.variant_names();
709        let variant_types: Vec<_> = self.variants.all.iter().map(|v| v.inner_type()).collect();
710
711        // Generate variants for typed transaction - extract inner types from Signed wrappers
712        let variants =
713            variant_names.iter().zip(variant_types.iter()).zip(self.variants.all.iter()).map(
714                |((name, inner_type), v)| {
715                    let doc_attrs = &v.doc_attrs;
716                    quote! {
717                        #(#doc_attrs)*
718                        #name(#inner_type),
719                    }
720                },
721            );
722
723        let doc_comment = format!(
724            "Typed transaction enum corresponding to the [`{}`] envelope.",
725            self.input_type_name
726        );
727
728        let serde_impl = if self.serde_enabled {
729            self.generate_typed_transaction_serde(typed_name)
730        } else {
731            quote! {}
732        };
733
734        // Generate arbitrary derives only if arbitrary is enabled
735        let arbitrary_impl = if self.arbitrary_enabled {
736            let num_variants = variant_names.len();
737            let arms = variant_names.iter().enumerate().map(|(i, name)| {
738                quote! { #i => Ok(Self::#name(u.arbitrary()?)) }
739            });
740
741            quote! {
742                #[cfg(#arbitrary_cfg)]
743                const _: () = {
744                    impl #impl_generics #arbitrary::Arbitrary<'_> for #typed_name #ty_generics
745                    where
746                        #(#variant_types: for<'a> #arbitrary::Arbitrary<'a>),*
747                    {
748                        fn arbitrary(u: &mut #arbitrary::Unstructured<'_>) -> #arbitrary::Result<Self> {
749                            match u.int_in_range(0..=#num_variants-1)? {
750                                #(#arms,)*
751                                _ => unreachable!(),
752                            }
753                        }
754                    }
755                };
756            }
757        } else {
758            quote! {}
759        };
760
761        let transaction_impl = self.generate_transaction_impl(true);
762
763        quote! {
764            #[doc = #doc_comment]
765            #[derive(Clone, Debug, PartialEq, Eq, Hash)]
766            pub enum #typed_name #impl_generics {
767                #(#variants)*
768            }
769
770            #transaction_impl
771            #serde_impl
772            #arbitrary_impl
773
774            impl #impl_generics #alloy_eips::eip2718::Typed2718 for #typed_name #ty_generics
775            where
776                #(#variant_types: #alloy_eips::eip2718::Typed2718,)*
777            {
778                fn ty(&self) -> u8 {
779                    match self {
780                        #(Self::#variant_names(tx) => tx.ty(),)*
781                    }
782                }
783            }
784
785            impl #impl_generics #alloy_eips::eip2718::IsTyped2718 for #typed_name #ty_generics {
786                fn is_type(type_id: u8) -> bool {
787                    <#tx_type_enum_name as #alloy_eips::eip2718::IsTyped2718>::is_type(type_id)
788                }
789            }
790        }
791    }
792
793    /// Serde impl
794    fn generate_typed_transaction_serde(&self, typed_name: &Ident) -> TokenStream {
795        let (impl_generics, ty_generics, _) = self.generics.split_for_impl();
796        let unwrapped_generics = &self.generics.params;
797        let alloy_consensus = &self.alloy_consensus;
798        let serde_cfg = &self.serde_cfg;
799        let serde = quote! { #alloy_consensus::private::serde };
800        let serde_str = serde.to_string();
801        let reject_if_some =
802            quote! { #alloy_consensus::private::alloy_serde::reject_if_some }.to_string();
803
804        let typed_names = self.variants.typed.iter().map(|v| &v.name).collect::<Vec<_>>();
805
806        // Serde attributes and inner types for typed variants
807        let typed_variants: Vec<_> = self
808            .variants
809            .typed
810            .iter()
811            .map(|v| {
812                let name = &v.name;
813
814                // Custom type or extract from wrapper
815                let inner_type = v.inner_type();
816
817                let (rename, aliases) = v.kind.serde_tag_and_aliases();
818
819                quote! {
820                    #[serde(rename = #rename, #(alias = #aliases,)*)]
821                    #name(#inner_type)
822                }
823            })
824            .collect();
825
826        // Legacy variant for untagged handling
827        let legacy_variant = self.variants.all.iter().find(|v| v.is_legacy());
828
829        let tagged_enum_name = syn::Ident::new(&format!("Tagged{}", typed_name), typed_name.span());
830        let maybe_tagged_enum_name =
831            syn::Ident::new(&format!("MaybeTagged{}", typed_name), typed_name.span());
832
833        let (legacy_untagged, legacy_conversion) = if let Some(legacy) = legacy_variant {
834            let legacy_name = &legacy.name;
835            let inner_type = legacy.inner_type();
836            (
837                quote! {
838                    Untagged {
839                        #[serde(default, rename = "type", deserialize_with = #reject_if_some)]
840                        _ty: Option<()>,
841                        #[serde(flatten)]
842                        tx: #inner_type,
843                    }
844                },
845                quote! {
846                    #maybe_tagged_enum_name::Untagged { tx, .. } => Self::#legacy_name(tx),
847                },
848            )
849        } else {
850            (quote! {}, quote! {})
851        };
852
853        quote! {
854            #[cfg(#serde_cfg)]
855            const _: () = {
856                use super::*;
857
858                /// Tagged variant with type field
859                #[derive(Debug, #serde::Serialize, #serde::Deserialize)]
860                #[serde(tag = "type", crate = #serde_str)]
861                enum #tagged_enum_name #impl_generics {
862                    #(
863                        #typed_variants
864                    ),*
865                }
866
867                /// Maybe tagged variant to handle untagged legacy transactions
868                #[derive(Debug, #serde::Deserialize)]
869                #[serde(untagged, crate = #serde_str)]
870                enum #maybe_tagged_enum_name #impl_generics {
871                    Tagged(#tagged_enum_name #ty_generics),
872                    #legacy_untagged
873                }
874
875                impl #impl_generics From<#maybe_tagged_enum_name #ty_generics> for #typed_name #ty_generics {
876                    fn from(value: #maybe_tagged_enum_name #ty_generics) -> Self {
877                        match value {
878                            #maybe_tagged_enum_name::Tagged(tagged) => tagged.into(),
879                            #legacy_conversion
880                        }
881                    }
882                }
883
884                impl #impl_generics From<#tagged_enum_name #ty_generics> for #typed_name #ty_generics {
885                    fn from(value: #tagged_enum_name #ty_generics) -> Self {
886                        match value {
887                            #(
888                                #tagged_enum_name::#typed_names(tx) => Self::#typed_names(tx),
889                            )*
890                        }
891                    }
892                }
893
894                impl #impl_generics From<#typed_name #ty_generics> for #tagged_enum_name #ty_generics {
895                    fn from(value: #typed_name #ty_generics) -> Self {
896                        match value {
897                            #(
898                                #typed_name::#typed_names(tx) => Self::#typed_names(tx),
899                            )*
900                        }
901                    }
902                }
903
904                impl #impl_generics #serde::Serialize for #typed_name #ty_generics
905                where
906                    #tagged_enum_name #ty_generics: #serde::Serialize,
907                    Self: Clone,
908                {
909                    fn serialize<S: #serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
910                        #tagged_enum_name::from(self.clone()).serialize(serializer)
911                    }
912                }
913
914                impl<'de, #unwrapped_generics> #serde::Deserialize<'de> for #typed_name #ty_generics
915                where
916                    #maybe_tagged_enum_name #ty_generics: #serde::Deserialize<'de>
917                {
918                    fn deserialize<D: #serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
919                        #maybe_tagged_enum_name::deserialize(deserializer).map(Into::into)
920                    }
921                }
922            };
923        }
924    }
925}