Create a Solana Client with Rust
Solana is a decentralized blockchain that allows you to run decentralized apps.
I have been exploring this world a little in the last few months and would like to share some nice stuff about the Rust API for Solana. It has been being a good starting point for me.
This description is really brief but you can understand better about Solana on this link. (I’ll do the same)
Solana makes available some different client APIs, such as Rust and Node.js.
The full source code is on paoloposso/solana_rust_client (github.com). Check it out to get the code snippets and / or clone at if you prefer.
Getting Started
I assume you already have Rust installed. If this is not the case, use this link.Create a rust project withcargo new --bin rust_client
Navigate to the folder containing the project and open your favorite IDE / Editor. I’m using Visual Studio Code.
Create a lib.rs file.
Add the following dependencies to the .toml file.
[dependencies]
solana-sdk = "1.10.0"
solana-client = "1.10.0"
solana-program = "1.7.14"
Create an RPC client on main.rs, that will be injected in the functions on lib.rs file. This way we centralize the configuration.
Notice that I’m using the Solana devnet URL.
let rpc_client = RpcClient::new("https://api.devnet.solana.com");
Also for simplicity, I’m injecting the RPC client instead of creating a struct with the client, for example.
Imports to be included on lib.rs file:
use std::error::Error;
use solana_client::rpc_client::RpcClient;
use solana_program::pubkey::Pubkey;
use solana_sdk::{system_transaction, signature::{Keypair, Signature}};
I’ll create a method to return a new keypair, only to reduce the dependencies of the rust SDK inside of the main file and better centralize the calls to the Solana network.
pub fn create_keypair() -> Keypair {
Keypair::new()
}
Function that returns newly created keypair
In this example, for every execution, we will be creating new keypairs for both sender and receiver.
Airdrop: Adding Funds to an Account
The following snippet shows the code to request an airdrop.
The methods receive the amount in lamports, which represents 0.000000001 SOL. Therefore, it’s necessary to calculate the value in lamports prior to sending it to the rpc client methods.
We do it by creating a const LAMPORTS_PER_SOL, with the value 1000000000.
pub fn request_air_drop(rpc_client: &RpcClient, pub_key: &Pubkey, amount_sol: f64) -> Result<Signature, Box<dyn Error>> {
let sig = rpc_client.request_airdrop(&pub_key, (amount_sol * LAMPORTS_PER_SOL) as u64)?;
loop {
let confirmed = rpc_client.confirm_transaction(&sig)?;
if confirmed {
break;
}
}
Ok(sig)
}
Notice that, after requesting the airdrop, we call the confirm_transaction method and only return the function after it’s confirmed.
Check Account Balance
We can also check the balance using an account’s public key. The balance is returned in lamports, so we can format it by dividing by our created constant.
Recommended by LinkedIn
pub fn check_balance(rpc_client: &RpcClient, public_key: &Pubkey) -> Result<f64, Box<dyn Error>> {
Ok(rpc_client.get_balance(&public_key)? as f64 / LAMPORTS_PER_SOL)
}
Transferring Funds
And now the transfer between our two newly created accounts.
pub fn transfer_funds(rpc_client: &RpcClient, sender_keypair: &Keypair, receiver_pub_key: &Pubkey, amount_sol: f64)
-> core::result::Result<Signature, Box<dyn Error>> {
let amount_lamports = (amount_sol * LAMPORTS_PER_SOL) as u64;
Ok(rpc_client.send_and_confirm_transaction(
&system_transaction::transfer(
&sender_keypair, &receiver_pub_key,
amount_lamports,
rpc_client.get_latest_blockhash()?))?)
}
This send_and_confirm_transaction method contains this version that only returns after the confirmation. This way we can output the account balances in a consistent form.
Calling our functions
In the main method, main.rs file, we will call the create_keypair function to create a sender and a receiver.
let sender = create_keypair()
let receiver = create_keypair();
println!("Sender: {:?}", sender.pubkey());
println!("Receiver: {:?}", receiver.pubkey());
After that, we can print the keys and use the Solana Explorer to check their balances on devnet. The bellow example is the receiver after the transfer, therefore the 0.5 SOL balance.
Then we request an airdrop for the sender with request_air_drop of 1 SOL, so the sender will have a balance to be transferred to the receiver.
request_air_drop(&rpc_client, &sender.pubkey(), 1.0);
We can also check balance using check_balance for both keypairs, and print the balance of both. The transaction signature is also returned by the method.
check_balance(&rpc_client, &sender.pubkey());
check_balance(&rpc_client, &receiver.pubkey());
println!("Sender balance: {:?}", balance);
After the airdrop, we can call the transfer using transfer_funds.
transfer_funds(&rpc_client, &sender, &receiver.pubkey(), transfer_amount);
println!("Transfer of {:?} finished. Signature: {:?}", transfer_amount, sig);
Wrapping Everything Up!
We are almost finishing it. Here goes the snippet of the full execution in the main.rs file:
use solana_client::rpc_client::RpcClient
use solana_rust_client::{check_balance, request_air_drop, transfer_funds, create_keypair};
use solana_sdk::signer::Signer;
const URL: &str = "https://api.devnet.solana.com";
fn main() {
let rpc_client = RpcClient::new(URL);
let sender = create_keypair();
let receiver = create_keypair();
println!("Sender: {:?}", sender.pubkey());
println!("Receiver: {:?}", receiver.pubkey());
if let Ok(airdrop_signature) = request_air_drop(&rpc_client, &sender.pubkey(), 1.0) {
println!("Airdrop finished! Signature: {:?}", airdrop_signature);
if let Ok(balance) = check_balance(&rpc_client, &sender.pubkey()) {
println!("Sender balance: {:?}", balance);
}
let transfer_amount = 0.5;
match transfer_funds(&rpc_client, &sender, &receiver.pubkey(), transfer_amount) {
Ok(sig) => {
println!("Transfer of {:?} finished. Signature: {:?}", transfer_amount, sig);
if let Ok(balance) = check_balance(&rpc_client, &sender.pubkey()) {
println!("Sender balance after transfer: {:?}", balance);
}
if let Ok(balance) = check_balance(&rpc_client, &receiver.pubkey()) {
println!("Receiver balance after transfer: {:?}", balance);
}
},
Err(err) => println!("Error: {:?}", err),
}
} else {
println!("Airdrop failed");
}
}
Run cargo run to execute it. The output should be as follows:
You can check the transfers and also the account balances using their respective keys on Solana Explorer like shown before.
Here you can see the transfer transaction:
And that is all, folks. Hope it may have been useful for other that, like myself, are learning Rust and Solana.
See you in the next one!