turdle_client/
program_client_generator.rs

1use crate::idl::Idl;
2use quote::{format_ident, ToTokens};
3use syn::{parse_quote, parse_str};
4
5/// Generates `program_client`'s `lib.rs` from [Idl] created from Anchor programs.
6/// Disable regenerating the `use` statements with a used imports `use_modules`
7///
8/// _Note_: See the crate's tests for output example.
9pub fn generate_source_code(idl: Idl, use_modules: &[syn::ItemUse]) -> String {
10    let mut output = "// DO NOT EDIT - automatically generated file (except `use` statements inside the `*_instruction` module\n".to_owned();
11    let code = idl
12        .programs
13        .into_iter()
14        .map(|idl_program| {
15            let program_name = idl_program.name.snake_case.replace('-', "_");
16            let instruction_module_name = format_ident!("{}_instruction", program_name);
17            let module_name: syn::Ident = parse_str(&program_name).unwrap();
18            let pubkey_bytes: syn::ExprArray = parse_str(&idl_program.id).unwrap();
19
20            let instructions = idl_program
21                .instruction_account_pairs
22                .into_iter()
23                .fold(
24                    Vec::new(),
25                    |mut instructions, (idl_instruction, idl_account_group)| {
26                        let instruction_fn_name: syn::Ident =
27                            parse_str(&idl_instruction.name.snake_case).unwrap();
28                        let instruction_struct_name: syn::Ident =
29                            parse_str(&idl_instruction.name.upper_camel_case).unwrap();
30                        let account_struct_name: syn::Ident =
31                            parse_str(&idl_account_group.name.upper_camel_case).unwrap();
32                        let instruction_name: syn::Ident =
33                            parse_str(&(idl_instruction.name.snake_case + "_ix")).unwrap();
34
35                        let parameters = idl_instruction
36                            .parameters
37                            .iter()
38                            .map(|(name, ty)| {
39                                let name = format_ident!("i_{name}");
40                                let ty: syn::Type = parse_str(ty).unwrap();
41                                let parameter: syn::FnArg = parse_quote!(#name: #ty);
42                                parameter
43                            })
44                            .collect::<Vec<_>>();
45
46                        let accounts = idl_account_group
47                            .accounts
48                            .iter()
49                            .map(|(name, ty)| {
50                                let name = format_ident!("a_{name}");
51                                let ty: syn::Type = parse_str(ty).unwrap();
52                                let account: syn::FnArg = parse_quote!(#name: #ty);
53                                account
54                            })
55                            .collect::<Vec<_>>();
56
57                        let field_parameters = idl_instruction
58                            .parameters
59                            .iter()
60                            .map(|(name, _)| {
61                                let name: syn::Ident = parse_str(name).unwrap();
62                                let value = format_ident!("i_{name}");
63                                let parameter: syn::FieldValue = parse_quote!(#name: #value);
64                                parameter
65                            })
66                            .collect::<Vec<_>>();
67
68                        let field_accounts = idl_account_group
69                            .accounts
70                            .iter()
71                            .map(|(name, _)| {
72                                let name: syn::Ident = parse_str(name).unwrap();
73                                let value = format_ident!("a_{name}");
74                                let account: syn::FieldValue = parse_quote!(#name: #value);
75                                account
76                            })
77                            .collect::<Vec<_>>();
78
79                        let instruction: syn::ItemFn = parse_quote! {
80                            pub async fn #instruction_fn_name(
81                                client: &Client,
82                                #(#parameters,)*
83                                #(#accounts,)*
84                                signers: impl IntoIterator<Item = Keypair> + Send + 'static,
85                            ) -> Result<EncodedConfirmedTransactionWithStatusMeta, ClientError> {
86                                Ok(client.send_instruction(
87                                    PROGRAM_ID,
88                                    #module_name::instruction::#instruction_struct_name {
89                                        #(#field_parameters,)*
90                                    },
91                                    #module_name::accounts::#account_struct_name {
92                                        #(#field_accounts,)*
93                                    },
94                                    signers,
95                                ).await?)
96                            }
97                        };
98
99                        let instruction_raw: syn::ItemFn = parse_quote! {
100                            pub  fn #instruction_name(
101                                #(#parameters,)*
102                                #(#accounts,)*
103                            ) -> Instruction {
104                                Instruction{
105                                    program_id: PROGRAM_ID,
106                                    data: #module_name::instruction::#instruction_struct_name {
107                                        #(#field_parameters,)*
108                                    }.data(),
109                                    accounts: #module_name::accounts::#account_struct_name {
110                                        #(#field_accounts,)*
111                                    }.to_account_metas(None),
112                                }
113                            }
114                        };
115
116                        instructions.push(instruction);
117                        instructions.push(instruction_raw);
118                        instructions
119                    },
120                )
121                .into_iter();
122
123            let program_module: syn::ItemMod = parse_quote! {
124                pub mod #instruction_module_name {
125                    #(#use_modules)*
126                    pub static PROGRAM_ID: Pubkey = Pubkey::new_from_array(#pubkey_bytes);
127                    #(#instructions)*
128                }
129            };
130            program_module.into_token_stream().to_string()
131        })
132        .collect::<String>();
133    output.push_str(&code);
134    output
135}