Solana Cookbook | Local Development (2024)

# Starting a Local Validator

Testing your program code locally can be a lot more reliable than testing on devnet, and can help you test before trying it out on devnet.

You can setup your local-test-validator by installing the solana tool suite and running

solana-test-validator

1

Benefits of using local-test-validator include:

  • No RPC rate-limits
  • No airdrop limits
  • Direct on-chain program deployment (--bpf-program ...)
  • Clone accounts from a public cluster, including programs (--clone ...)
  • Configurable transaction history retention (--limit-ledger-size ...)
  • Configurable epoch length (--slots-per-epoch ...)
  • Jump to an arbitrary slot (--warp-slot ...)

# Connecting to Environments

When you are working on Solana development, you will need to connect to a specific RPC API endpoint. Solana has 3 public development environments:

  • mainnet-beta https://api.mainnet-beta.solana.com
  • devnet https://api.devnet.solana.com
  • testnet https://api.testnet.solana.com

Press </> button to view full source

import { clusterApiUrl, Connection } from "@solana/web3.js";(async () => { const connection = new Connection(clusterApiUrl("mainnet-beta"), "confirmed");})();

1
2
3
4
5

const connection = new Connection(clusterApiUrl("mainnet-beta"), "confirmed");

1

from solana.rpc.api import Clientclient = Client("https://api.mainnet-beta.solana.com")

1
2
3

client = Client("https://api.mainnet-beta.solana.com")

1

#include "solana.hpp"using namespace many::solana;int main() { Connection connection("https://api.mainnet-beta.solana.com"); return 0;}

1
2
3
4
5
6
7
8

Connection connection("https://api.mainnet-beta.solana.com");

1

use solana_client::rpc_client::RpcClient;use solana_sdk::commitment_config::CommitmentConfig;fn main() { let rpc_url = String::from("https://api.mainnet-beta.solana.com"); let client = RpcClient::new_with_commitment(rpc_url, CommitmentConfig::confirmed());}

1
2
3
4
5
6
7

let rpc_url = String::from("https://api.mainnet-beta.solana.com");let client = RpcClient::new_with_commitment(rpc_url, CommitmentConfig::confirmed());

1
2

solana config set --url https://api.mainnet-beta.solana.com

1

solana config set --url https://api.mainnet-beta.solana.com

1

Finally, you can also connect to a private cluster, either one local or running remotely with the following:

Press </> button to view full source

import { Connection } from "@solana/web3.js";(async () => { // This will connect you to your local validator const connection = new Connection("http://127.0.0.1:8899", "confirmed");})();

1
2
3
4
5
6

const connection = new Connection("http://127.0.0.1:8899", "confirmed");

1

from solana.rpc.api import Clientclient = Client("http://127.0.0.1:8899")

1
2
3

client = Client("http://127.0.0.1:8899")

1

#include "solana.hpp"using namespace many::solana;int main() { Connection connection("http://127.0.0.1:8899"); return 0;}

1
2
3
4
5
6
7
8

Connection connection("http://127.0.0.1:8899");

1

use solana_client::rpc_client::RpcClient;use solana_sdk::commitment_config::CommitmentConfig;fn main() { let rpc_url = String::from("http://127.0.0.1:8899"); let client = RpcClient::new_with_commitment(rpc_url, CommitmentConfig::confirmed());}

1
2
3
4
5
6
7

let rpc_url = String::from("http://127.0.0.1:8899");let client = RpcClient::new_with_commitment(rpc_url, CommitmentConfig::confirmed());

1
2

solana config set --url http://privaterpc.com

1

solana config set --url http://privaterpc.com

1

# Subscribing to Events

Websockets provide a pub/sub interface where you can listen for certain events. Instead of pinging a typical HTTP endpoint at an interval to get frequent updates, you can instead receive those updates only when they happen.

Solana's web3 Connectionopen in new window under the hood generates a websocket endpoint and registers a websocket client when you create a new Connection instance (see source code hereopen in new window).

The Connection class exposes pub/sub methods - they all start with on, like event emitters. When you call these listener methods, it registers a new subscription to the websocket client of that Connection instance. The example pub/sub method we use below is onAccountChangeopen in new window. The callback will provide the updated state data through arguments (see AccountChangeCallbackopen in new window as an example).

Press </> button to view full source

import { clusterApiUrl, Connection, Keypair } from "@solana/web3.js";(async () => { // Establish new connect to devnet - websocket client connected to devnet will also be registered here const connection = new Connection(clusterApiUrl("devnet"), "confirmed"); // Create a test wallet to listen to const wallet = Keypair.generate(); // Register a callback to listen to the wallet (ws subscription) connection.onAccountChange( wallet.publicKey(), (updatedAccountInfo, context) => console.log("Updated account info: ", updatedAccountInfo), "confirmed" );})();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

// Establish new connect to devnet - websocket client connected to devnet will also be registered hereconst connection = new Connection(clusterApiUrl("devnet"), "confirmed");// Create a test wallet to listen toconst wallet = Keypair.generate();// Register a callback to listen to the wallet (ws subscription)connection.onAccountChange( wallet.publicKey(), (updatedAccountInfo, context) => console.log("Updated account info: ", updatedAccountInfo), "confirmed");

1
2
3
4
5
6
7
8
9
10
11
12
13

import asynciofrom solders.keypair import Keypairfrom solana.rpc.websocket_api import connectasync def main(): async with connect("wss://api.devnet.solana.com") as websocket: # Create a Test Wallet wallet = Keypair() # Subscribe to the Test wallet to listen for events await websocket.account_subscribe(wallet.pubkey()) # Capture response from account subscription  first_resp = await websocket.recv() print("Subscription successful with id {}, listening for events \n".format(first_resp.result)) updated_account_info = await websocket.recv() print(updated_account_info) asyncio.run(main())

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

async with connect("wss://api.devnet.solana.com") as websocket: # Create a Test Wallet wallet = Keypair() # Subscribe to the Test wallet to listen for events await websocket.account_subscribe(wallet.pubkey()) # Capture response from account subscription  first_resp = await websocket.recv() print("Subscription successful with id {}, listening for events \n".format(first_resp.result)) updated_account_info = await websocket.recv() print(updated_account_info) 

1
2
3
4
5
6
7
8
9
10

// clang++ on_account_change.cpp -o on_account_change -std=c++17 -lssl -lcrypto -lsodium#include "solana.hpp"using namespace many::solana;int main() { Connection connection("https://api.devnet.solana.com"); auto key_pair = Keypair::generate(); int subscriptionId = connection.on_account_change(key_pair.public_key, [&](Result<Account> result) { Account account = result.unwrap(); std::cout << "owner = " << account.owner.to_base58() << std::endl; std::cout << "lamports = " << account.lamports << std::endl; std::cout << "data = " << account.data << std::endl; std::cout << "executable = " << (account.executable ? "true" : "false") << std::endl; }); sleep(1); std::string tx_hash = connection.request_airdrop(key_pair.public_key).unwrap(); std::cout << "tx hash = " << tx_hash << std::endl; for (int i = 0; i < 10; i++) { connection.poll(); sleep(1); } connection.remove_account_listener(subscriptionId); return 0;}

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

auto key_pair = Keypair::generate();int subscriptionId = connection.on_account_change(key_pair.public_key, [&](Result<Account> result) { Account account = result.unwrap(); std::cout << "owner = " << account.owner.to_base58() << std::endl; std::cout << "lamports = " << account.lamports << std::endl; std::cout << "data = " << account.data << std::endl; std::cout << "executable = " << (account.executable ? "true" : "false") << std::endl;});for (int i = 0; i < 10; i++) { connection.poll(); sleep(1);}connection.remove_account_listener(subscriptionId);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

use solana_client::pubsub_client::PubsubClient;use solana_client::rpc_config::RpcAccountInfoConfig;use solana_sdk::commitment_config::CommitmentConfig;use solana_sdk::signature::{Keypair, Signer};fn main() { let wallet = Keypair::new(); let pubkey = Signer::pubkey(&wallet); let ws_url = String::from("wss://api.devnet.solana.com/"); println!("{}", ws_url); if let Ok(subscription) = PubsubClient::account_subscribe( &ws_url, &pubkey, Some(RpcAccountInfoConfig { encoding: None, data_slice: None, commitment: Some(CommitmentConfig::confirmed()), }), ) { let (mut ws_client, receiver) = subscription; println!("Subscription successful, listening for events"); let handle = std::thread::spawn(move || loop { println!("Waiting for a message"); match receiver.recv() { Ok(message) => println!("{:?}", message), Err(err) => { println!("Connection broke with {:}", err); break; } } }); handle.join().unwrap(); ws_client.shutdown().unwrap() } else { println!("Errooooor"); }}

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

let ws_url = String::from("wss://api.devnet.solana.com/");let (mut client, receiver) = PubsubClient::account_subscribe( &ws_url, &pubkey, Some(RpcAccountInfoConfig { encoding: None, data_slice: None, commitment: Some(CommitmentConfig::confirmed()), }),).unwrap();let message = match receiver.recv().unwrap();println!("{:?}", message)

1
2
3
4
5
6
7
8
9
10
11
12

# Getting Test SOL

When you're working locally, you need some SOL in order to send transactions. In non-mainnet environments you can receive SOL by airdropping it to your address

Press </> button to view full source

import { Connection, Keypair, LAMPORTS_PER_SOL } from "@solana/web3.js";(async () => { const keypair = Keypair.generate(); const connection = new Connection("http://127.0.0.1:8899", "confirmed"); const signature = await connection.requestAirdrop( keypair.publicKey, LAMPORTS_PER_SOL ); const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash(); await connection.confirmTransaction({ blockhash, lastValidBlockHeight, signature });})();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

const airdropSignature = await connection.requestAirdrop( keypair.publicKey, LAMPORTS_PER_SOL);await connection.confirmTransaction(airdropSignature);

1
2
3
4
5
6

from solders.keypair import Keypairfrom solana.rpc.api import Clientwallet = Keypair()client = Client("https://api.devnet.solana.com")#Input Airdrop amount in LAMPORTSclient.request_airdrop(wallet.pubkey(), 1000000000)#Airdrops 1 SOL

1
2
3
4
5
6
7
8
9
10
11

#Input Airdrop amount in LAMPORTSclient.request_airdrop(wallet.pubkey(), 1000000000)#Airdrops 1 SOL

1
2
3
4

// clang++ request_airdrop.cpp -o request_airdrop -std=c++17 -lssl -lcrypto -lsodium#include "solana.hpp"using namespace many::solana;int main() { Connection connection("https://api.devnet.solana.com"); auto key_pair = Keypair::generate(); std::string tx_hash = connection.request_airdrop(key_pair.public_key).unwrap(); std::cout << "tx hash = " << tx_hash << std::endl; return 0;}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

connection.request_airdrop(key_pair.public_key).unwrap();

1

use solana_client::rpc_client::RpcClient;use solana_sdk::commitment_config::CommitmentConfig;use solana_sdk::native_token::LAMPORTS_PER_SOL;use solana_sdk::signature::{Keypair, Signer};fn main() { let wallet = Keypair::new(); let pubkey = Signer::pubkey(&wallet); let rpc_url = String::from("https://api.devnet.solana.com"); let client = RpcClient::new_with_commitment(rpc_url, CommitmentConfig::confirmed()); match client.request_airdrop(&pubkey, LAMPORTS_PER_SOL) { Ok(sig) => loop { if let Ok(confirmed) = client.confirm_transaction(&sig) { if confirmed { println!("Transaction: {} Status: {}", sig, confirmed); break; } } }, Err(_) => println!("Error requesting airdrop"), };}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

match client.request_airdrop(&pubkey, LAMPORTS_PER_SOL) { Ok(sig) => loop { if let Ok(confirmed) = client.confirm_transaction(&sig) { if confirmed { println!("Transaction: {} Status: {}", sig, confirmed); break; } } }, Err(_) => println!("Error requesting airdrop"),};

1
2
3
4
5
6
7
8
9
10
11

solana airdrop 1# Return# "1 SOL"

1
2
3
4

solana airdrop 1

1

# Using Mainnet Accounts and Programs

Oftentimes, local tests rely on programs and accounts available only on mainnet. The Solana CLI allows to both:

  • Download Programs and Accounts
  • Load Programs and Accounts to a local validator

# How to load accounts from mainnet

It is possible to download the SRM token mint account to file:

Press </> button to view full source

# solana account -u <source cluster> --output <output format> --output-file <destination file name/path> <address of account to fetch>solana account -u m --output json-compact --output-file SRM_token.json SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt

1
2

solana account -u m --output json-compact --output-file SRM_token.json SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt

1

Loading it to your localnet is then done by passing the account's file and destination address (on the local cluster) when starting the validator:

Press </> button to view full source

# solana-test-validator --account <address to load the account to> <path to account file> --resetsolana-test-validator --account SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt SRM_token.json --reset

1
2

solana-test-validator --account SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt SRM_token.json --reset

1

# How to load programs from mainnet

Similarly, it is possible to download the Serum Dex v3 program:

Press </> button to view full source

# solana program dump -u <source cluster> <address of account to fetch> <destination file name/path>solana program dump -u m 9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin serum_dex_v3.so

1
2

solana program dump -u m 9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin serum_dex_v3.so

1

Loading it to your localnet is then done by passing the program's file and destination address (on the local cluster) when starting the validator:

Press </> button to view full source

# solana-test-validator --bpf-program <address to load the program to> <path to program file> --resetsolana-test-validator --bpf-program 9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin serum_dex_v3.so --reset

1
2

solana-test-validator --bpf-program 9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin serum_dex_v3.so --reset

1

Solana Cookbook | Local Development (2024)
Top Articles
Latest Posts
Article information

Author: Aracelis Kilback

Last Updated:

Views: 5903

Rating: 4.3 / 5 (44 voted)

Reviews: 91% of readers found this page helpful

Author information

Name: Aracelis Kilback

Birthday: 1994-11-22

Address: Apt. 895 30151 Green Plain, Lake Mariela, RI 98141

Phone: +5992291857476

Job: Legal Officer

Hobby: LARPing, role-playing games, Slacklining, Reading, Inline skating, Brazilian jiu-jitsu, Dance

Introduction: My name is Aracelis Kilback, I am a nice, gentle, agreeable, joyous, attractive, combative, gifted person who loves writing and wants to share my knowledge and understanding with you.