# 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 Connection
open 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 onAccountChange
open in new window. The callback will provide the updated state data through arguments (see AccountChangeCallback
open 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