In Solana's ecosystem, Anchor stands out as a powerful framework for building Solana programs, offering a range of utilities to streamline the development process. However, navigating the intricacies of Rust and interacting with Anchor programs can be challenging, especially when it comes to calling instructions and handling serialized data.
Having faced these challenges firsthand, I created this tutorial to provide clear guidance on calling an instruction in an Anchor program using Solana's Rust SDK and Borsh serialization.
You can find the entire source code for the referenced example here.
Define discriminants
Anchor programs often use discriminants to identify different instructions. Initialize your discriminant as a constant array:
const INITIALIZE_DISCRIMINANT: [u8; 8] = [175, 175, 109, 31, 13, 152, 155, 237];
Define Structs
In your Rust project, define the structs representing the data passed to the instruction. You can either copy and paste them from the Anchor Program or define them yourself. These structs should derive BorshSerialize and BorshDeserialize. For example:
#[derive(BorshSerialize, BorshDeserialize)]
pub struct UserInfoParams {
pub name: String,
pub age: u8,
}
Create Instruction
In Anchor's serialized instruction, the first 8 bytes will be used for the instruction discriminant, followed by the actual instruction data. So, we need to follow the same structure.
We create the Solana instruction using Instruction::new_with_borsh
This function takes three arguments:
program_id: The public key of the Solana program associated with the instruction.
data: This is the instruction data we want to pass to it.
accounts: A vector of account public keys required by the instruction. These accounts must be included in the transaction and may be accessed by the program during execution.
Define instruction data
let params = UserInfoParams {
name: "Alice".to_string(),
age: 25,
};
Now create an instruction with the instruction discriminant and instruction inputs. We can directly send the discriminant and Borsh-serializable data structures to this.
let ix = Instruction::new_with_borsh(
program_id,
&(INITIALIZE_DISCRIMINANT, user),
vec![
AccountMeta::new(pda_account, false),
AccountMeta::new_readonly(signer_pubkey, true),
AccountMeta::new_readonly(system_program::ID, false),
],
);
Create and Sign Transaction
Now we need to create the Transaction and sign it
let message = Message::new(&[ix], Some(&signer_pubkey));
let mut tx = Transaction::new_unsigned(message);
tx.sign(&[&signer], connection.get_latest_blockhash().unwrap());
let tx_id = connection
.send_and_confirm_transaction_with_spinner(&tx)
.map_err(|err| {
println!("{:?}", err);
}).unwrap();
println!("Program uploaded successfully. Transaction ID: {}", tx_id);
Conclusion:
This tutorial explored calling instruction in an Anchor program using Solana's Rust SDK with Borsh serialization. By following these steps, you can interact with Anchor programs efficiently, leveraging the power of Borsh serialization for efficient data handling.
Additionally, I plan to develop a CLI tool for interacting with Anchor programs using IDL. I'll provide updates on this project here.
Top comments (2)
Thanks for the guide. How does one get the discriminates of other functions?
I subsequently found how to get discriminants and published a lightweight program client crate that allows you to pass the function name directly.
You can check out the crate here: crates.io/crates/solana-program-cl...