// Copyright 2019-2025 Parity Technologies (UK) Ltd. // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. use super::CodegenError; use heck::{ToSnakeCase as _, ToUpperCamelCase as _}; use proc_macro2::TokenStream as TokenStream2; use quote::{format_ident, quote}; use scale_typegen::typegen::ir::ToTokensWithSettings; use scale_typegen::{TypeGenerator, typegen::ir::type_ir::CompositeIRKind}; use subxt_metadata::PalletMetadata; /// Generate calls from the provided pallet's metadata. Each call returns a `StaticPayload` /// that can be passed to the subxt client to submit/sign/encode. /// /// # Arguments /// /// - `type_gen` - [`scale_typegen::TypeGenerator`] that contains settings and all types from the runtime metadata. /// - `pallet` - Pallet metadata from which the calls are generated. /// - `crate_path` - The crate path under which the `subxt` crate is located, e.g. `::subxt` when using subxt as a dependency. pub fn generate_calls( type_gen: &TypeGenerator, pallet: &PalletMetadata, crate_path: &syn::Path, ) -> Result { // Early return if the pallet has no calls. let Some(call_ty) = pallet.call_ty_id() else { return Ok(quote!()); }; let variant_names_and_struct_defs = super::generate_structs_from_variants( type_gen, call_ty, |name| name.to_upper_camel_case().into(), "Call", )?; let (call_structs, call_fns): (Vec<_>, Vec<_>) = variant_names_and_struct_defs .into_iter() .map(|var| { let (call_fn_args, call_args): (Vec<_>, Vec<_>) = match &var.composite.kind { CompositeIRKind::Named(named_fields) => named_fields .iter() .map(|(name, field)| { // Note: fn_arg_type this is relative the type path of the type alias when prefixed with `super::`, e.g. `set_max_code_size::New` let fn_arg_type = field.type_path.to_token_stream(type_gen.settings()); let call_arg = if field.is_boxed { quote! { #name: #crate_path::alloc::boxed::Box::new(#name) } } else { quote! { #name } }; (quote!( #name: super::#fn_arg_type ), call_arg) }) .unzip(), CompositeIRKind::NoFields => Default::default(), CompositeIRKind::Unnamed(_) => { return Err(CodegenError::InvalidCallVariant(call_ty)); } }; let pallet_name = pallet.name(); let call_name = &var.variant_name; let struct_name = &var.composite.name; let Some(call_hash) = pallet.call_hash(call_name) else { return Err(CodegenError::MissingCallMetadata( pallet_name.into(), call_name.to_string(), )); }; let fn_name = format_ident!("{}", var.variant_name.to_snake_case()); // Propagate the documentation just to `TransactionApi` methods, while // draining the documentation of inner call structures. let docs = &var.composite.docs; // this converts the composite into a full struct type. No Type Parameters needed here. let struct_def = type_gen .upcast_composite(&var.composite) .to_token_stream(type_gen.settings()); let alias_mod = var.type_alias_mod; // The call structure's documentation was stripped above. let call_struct = quote! { #struct_def #alias_mod impl #struct_name { const PALLET_NAME: &'static str = #pallet_name; const CALL_NAME: &'static str = #call_name; } impl #crate_path::extrinsics::DecodeAsExtrinsic for #struct_name { fn is_extrinsic(pallet_name: &str, call_name: &str) -> bool { pallet_name == Self::PALLET_NAME && call_name == Self::CALL_NAME } } }; let client_fn = quote! { #docs pub fn #fn_name( &self, #( #call_fn_args, )* ) -> #crate_path::transactions::StaticPayload { #crate_path::transactions::StaticPayload::new_static( #pallet_name, #call_name, super::#struct_name { #( #call_args, )* }, [#(#call_hash,)*] ) } }; Ok((call_struct, client_fn)) }) .collect::, _>>()? .into_iter() .unzip(); let call_type = type_gen .resolve_type_path(call_ty)? .to_token_stream(type_gen.settings()); let call_ty = type_gen.resolve_type(call_ty)?; let docs = type_gen.docs_from_scale_info(&call_ty.docs); let types_mod_ident = type_gen.types_mod_ident(); Ok(quote! { #docs pub type Call = #call_type; pub mod calls { use super::root_mod; use super::#types_mod_ident; #( #call_structs )* pub mod api { pub struct TransactionApi; impl TransactionApi { #( #call_fns )* } } } }) }