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