Local Development

เริ่มใช้ Local Validator

การทดสอบ program ที่ local จะเสถียรกว่าการทดสอบบน devnet และมันยังช่วยเราทดสอบก่อนที่จะไปลองบน devnet อีกด้วย

เราสามารถติดตั้ง local-test-validator โดยการลง solana tool suite และ run คำสั่งนี้

solana-test-validator

ประโยชน์ของการใช้ local-test-validator คือ:

  • ไม่มีข้อจำกัด (rate-limits) ​ในการเรียก RPC
  • ไม่มีข้อจำกัดในการขอ airdrop
  • deploy program on-chain ได้โดยตรง (--bpf-program ...)
  • สามารถ clone accounts และ programs จาก public cluster ได้ (--clone ...)
  • ตั้งค่า transaction history retention ได้ (--limit-ledger-size ...)
  • ตั้งค่าความยาว epoch ได้ (--slots-per-epoch ...)
  • ข้ามไป slot ไหนก็ได้ตามใจ (--warp-slot ...)

Connecting to Environments

เวลา dev บน Solana development เราต้อง connect ไปที่ RPC API endpoint ซึ่ง Solana จะมีอยู่ 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");
})();
from solana.rpc.api import Client

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

using namespace many::solana;

int main() {
    Connection connection("https://api.mainnet-beta.solana.com");
    return 0;
}
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());
}
solana config set --url https://api.mainnet-beta.solana.com

Finally, you สามารถ 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");
})();
from solana.rpc.api import Client

client = Client("http://127.0.0.1:8899")
#include "solana.hpp"

using namespace many::solana;

int main() {
    Connection connection("http://127.0.0.1:8899");
    return 0;
}
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());
}
solana config set --url http://privaterpc.com

Subscribing to Events

Websockets จะมี pub/sub interface ที่เราสามารถฟัง events ที่เราสนใจแทนที่จะคอยวนๆ เรียก HTTP endpoint บ่อยๆ เราสามารถรับข้อมูลเฉพาะตอนมันเกิดขึ้นได้

Solana's web3 Connectionopen in new window จะมี websocket endpoint และจะ registers ตัว websocket client เมื่อเรา new Connection ขึ้นมาใหม่ (ดู source code ที่นี่open in new window).

Connection class จะมี pub/sub methods - ที่จะมีชื่อเริ่มด้วย on เหมือน event emitters ทั่วไป เวลาเราเรียก listener methods พวกนี้ มันจะ registers subscription ใหม่ไปที่ websocket client ของ Connection นั้นๆ ตัวอย่างของ pub/sub method ที่เราใช้ด้านล่างคือ onAccountChangeopen in new window. ส่วน callback จะให้ updated state data ผ่าน arguments (ดูตัวอย่างได้ที่ AccountChangeCallbackopen in new window).

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"
  );
})();
import asyncio
from solders.keypair import Keypair
from solana.rpc.websocket_api import connect

async 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())
// 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;
}
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");
    }
}

Getting Test SOL

เมื่อเราทำงานที่ local เราจะต้องการ SOL ในการส่ง transactions บน non-mainnet environments เราสามารถขอ SOL ได้ด้วยการ airdrop ไปที่ 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
    });
})();
from solders.keypair import Keypair
from solana.rpc.api import Client

wallet = Keypair()

client = Client("https://api.devnet.solana.com")

#Input Airdrop amount in LAMPORTS
client.request_airdrop(wallet.pubkey(), 1000000000)

#Airdrops 1 SOL
// 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;
}
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"),
    };
}
solana airdrop 1

# Return
# "1 SOL"

วิธีใช้ Mainnet Accounts และ Programs

local tests มักจะต้องใช้ programs และ accounts ที่มีอยู่แล้วบน mainnet Solana CLI สามารถที่จะ:

  • Download Programs และ Accounts
  • Load Programs และ Accounts มาที่ local validator

วิธี load accounts จาก mainnet

เราสามารถ download SRM token mint account มาเป็น 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

การ load มาที่ localnet ของเราสามารถทำได้โดยการส่ง account's file และ address เป้าหมาย(ไปยัง local cluster) ตอนเริ่มเปิดใช้ validator:

Press </> button to view full source
# solana-test-validator --account <address to load the account to> <path to account file> --reset
solana-test-validator --account SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt SRM_token.json --reset

วิธี load programs from mainnet

และเช่นกันเราสามารถ download 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

การ load มาที่ localnet ของเราสามารถทำได้โดยการส่ง account's file และ address เป้าหมาย(ไปยัง local cluster) ตอนเริ่มเปิดใช้ validator:

Press </> button to view full source
# solana-test-validator --bpf-program <address to load the program to> <path to program file> --reset
solana-test-validator --bpf-program 9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin serum_dex_v3.so --reset
Last Updated:
Contributors: Partially Sorted, Todsaporn Banjerdkit