alloy_tx_macros/
lib.rs

1#![doc = include_str!("../README.md")]
2#![doc(
3    html_logo_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/alloy.jpg",
4    html_favicon_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/favicon.ico"
5)]
6#![cfg_attr(not(test), warn(unused_crate_dependencies))]
7#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
8#![allow(clippy::option_if_let_else)]
9
10mod expand;
11mod parse;
12mod serde;
13
14use expand::Expander;
15use parse::{EnvelopeArgs, GroupedVariants};
16use proc_macro::TokenStream;
17use quote::quote;
18use syn::{parse_macro_input, parse_quote, DeriveInput, Error, Ident};
19
20/// Derive macro for creating transaction envelope types.
21///
22/// This macro generates a transaction envelope implementation that supports
23/// multiple transaction types following the EIP-2718 standard.
24///
25/// # Container Attributes
26///
27/// - `#[envelope(tx_type_name = MyTxType)]` - Custom name for the generated transaction type enum
28/// - `#[envelope(alloy_consensus = path::to::alloy)]` - Custom path to alloy_consensus crate
29///
30/// # Variant Attributes
31/// - Each variant must be annotated with `envelope` attribute with one of the following options:
32///   - `#[envelope(ty = N)]` - Specify the transaction type ID (0-255)
33///   - `#[envelope(flatten)]` - Flatten this variant to delegate to inner envelope type
34///
35/// # Generated Code
36///
37/// The macro generates:
38/// - A `MyTxType` enum with transaction type variants
39/// - Implementations of `Transaction`, `Typed2718`, `Encodable2718`, `Decodable2718`
40/// - Serde serialization/deserialization support (if `serde` feature is enabled)
41/// - Arbitrary implementations (if `arbitrary` feature is enabled)
42#[proc_macro_derive(TransactionEnvelope, attributes(envelope, serde))]
43pub fn derive_transaction_envelope(input: TokenStream) -> TokenStream {
44    let input = parse_macro_input!(input as DeriveInput);
45
46    match expand_transaction_envelope(input) {
47        Ok(tokens) => tokens.into(),
48        Err(err) => err.to_compile_error().into(),
49    }
50}
51
52/// Expand the transaction envelope derive macro.
53fn expand_transaction_envelope(input: syn::DeriveInput) -> Result<proc_macro2::TokenStream, Error> {
54    use darling::FromDeriveInput;
55
56    // Parse the input with darling
57    let args = EnvelopeArgs::from_derive_input(&input)
58        .map_err(|e| Error::new_spanned(&input.ident, e.to_string()))?;
59
60    // Extract config values before consuming args
61    let input_type_name = args.ident.clone();
62    let tx_type_enum_name = args
63        .tx_type_name
64        .clone()
65        .unwrap_or_else(|| Ident::new(&format!("{input_type_name}Type"), input_type_name.span()));
66    let alloy_consensus =
67        args.alloy_consensus.clone().unwrap_or_else(|| parse_quote!(::alloy_consensus));
68    let generics = args.generics.clone();
69    let serde_cfg = match args.serde_cfg.as_ref() {
70        Some(syn::Meta::List(list)) => list.tokens.clone(),
71        Some(_) => {
72            return Err(Error::new_spanned(
73                &input.ident,
74                "serde_cfg must be a list like `serde_cfg(feature = \"serde\")`",
75            ))
76        }
77        // this is always true
78        None => quote! { all() },
79    };
80
81    let arbitrary_cfg = match args.arbitrary_cfg.as_ref() {
82        Some(syn::Meta::List(list)) => list.tokens.clone(),
83        Some(_) => {
84            return Err(Error::new_spanned(
85                &input.ident,
86                "arbitrary_cfg must be a list like `arbitrary_cfg(feature = \"arbitrary\")`",
87            ))
88        }
89        None => quote! { all() },
90    };
91
92    let variants = GroupedVariants::from_args(args)?;
93
94    let alloy_primitives = quote! { #alloy_consensus::private::alloy_primitives };
95    let alloy_eips = quote! { #alloy_consensus::private::alloy_eips };
96    let alloy_rlp = quote! { #alloy_consensus::private::alloy_rlp };
97
98    // Expand the macro
99    let expander = Expander {
100        input_type_name,
101        tx_type_enum_name,
102        alloy_consensus,
103        generics,
104        serde_enabled: cfg!(feature = "serde"),
105        serde_cfg,
106        arbitrary_cfg,
107        arbitrary_enabled: cfg!(feature = "arbitrary"),
108        alloy_primitives,
109        alloy_eips,
110        alloy_rlp,
111        variants,
112    };
113    Ok(expander.expand())
114}