Local Development
Starting a Local Validator
당신의 Program 코드를 로컬에서 테스트하는 것은 devnet에서 테스트하는 것보다 더 신뢰할 수 있습니다. 그리고 devnet에 보내기 전에 테스트할 수 있습니다.
solana tool suite를 설치하고 실행함으로써 local-test-validator를 구축할 수 있습니다.
solana-test-validator
local-test-validator를 사용하는 것은 아래의 이점들을 가지고 있습니다:
- RPC 제한이 없음
 - airdrop 제한이 없음
 - 직접적인 on-chain 프로그램 배포 (
--bpf-program ...) - public cluster로부터 Program들을 포함한 Account들에 대한 복사 (
--clone ...) - Transaction 히스토리 유지에 대한 설정 가능 (
--limit-ledger-size ...) - epoch 길이에 대한 설정 가능 (
--slots-per-epoch ...) - 임의의 slot으로 건너뛰기 (
--warp-slot ...) 
Connecting to Environments
Solana에서 개발할 때 우리는 특저 RPC API endpoint에 연결할 필요가 있을 것입니다. Solana는 3 개의 public 개발 환경을 가지고 있습니다.
- mainnet-beta https://api.mainnet-beta.solana.com
 - devnet https://api.devnet.solana.com
 - testnet https://api.testnet.solana.com
 
import { clusterApiUrl, Connection } from "@solana/web3.js";
(async () => {
  const connection = new Connection(clusterApiUrl("mainnet-beta"), "confirmed");
})();
const connection = new Connection(clusterApiUrl("mainnet-beta"), "confirmed");
from solana.rpc.api import Client
client = Client("https://api.mainnet-beta.solana.com")
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;
}
Connection connection("https://api.mainnet-beta.solana.com");
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());
}
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
solana config set --url https://api.mainnet-beta.solana.com
아래 내용에 따라, 당신은 로컬이거나 원격에서 실행하는 사설 cluster에도 연결할 수 있습니다.
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");
})();
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")
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;
}
Connection connection("http://127.0.0.1:8899");
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());
}
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
solana config set --url http://privaterpc.com
Subscribing to Events
Websocket들은 당신이 특정 이벤트들을 구독할 수 있는 pub/sub 인터페이스를 제공합니다. 잦은 업데이트를 위해 주기적으로 HTTP endpoint에 핑을 보내는 것 대신에, 당신은 업데이트가 발생했을 때 바로 수신할 수 있습니다.
Solana의 web3 Connection은 websocket endpoint를 만들어낼 수 있고 당신이 Connection 인스턴스를 생성할 때 websocket client를 등록할 수 있습니다. (see source code here)
Connection 클래스는 이벤트 emmitter와 같이 on으로 시작하는 pub/sub 메소드를 노출한다. 당신이 이 listener 메소드들을 호출할 때, 이것은 Connection 인스턴스의 websocket client에 새로운 구독을 등록합니다. 아래에서 우리가 사용하는 pub/sub 예제는 onAccountChange입니다. 이 callbacck은 인자들을 통해 업데이트된 상태 데이터를 제공할 것입니다. (see AccountChangeCallback as an example).
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"
  );
})();
// 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())
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)    
// 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;
}
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);
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");
    }
}
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)
Getting Test SOL
로컬에서 작업할 때 당신은 Transaction을 보내기 위해 SOL이 필요합니다. non-mainnet 환경에서 당신은 airdrop 해서 당신의 Address에 SOL을 받을 수 있습니다.
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
    });
})();
const airdropSignature = await connection.requestAirdrop(
  keypair.publicKey,
  LAMPORTS_PER_SOL
);
await connection.confirmTransaction(airdropSignature);
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
#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;
}
connection.request_airdrop(key_pair.public_key).unwrap();
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"),
    };
}
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"
solana airdrop 1
Using Mainnet Accounts and Programs
로컬 테스트는 종종 mainnet에 있는 Program들과 Account들에 의존한다. Solana CLI는 아래 두 가지를 허락합니다:
- Program들과 Account들을 다운로드하는 것
 - Program들과 Account들을 local validator에 올리는 것
 
How to load accounts from mainnet
SRN token mint account를 파일로 다운로드하는 것이 가능합니다:
# 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
solana account -u m --output json-compact --output-file SRM_token.json SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt
validator를 시작할 때 이 Account 파일과 목적지 address (local cluster에 있는)를 넘김으로써 로컬 넷에 올리는 것이 가능합니다:
# solana-test-validator --account <address to load the account to> <path to account file> --reset
solana-test-validator --account SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt SRM_token.json --reset
solana-test-validator --account SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt SRM_token.json --reset
How to load programs from mainnet
비슷하게 Serum Dex v3 프로그램을 다운로드할 수 있습니다:
# 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
solana program dump -u m 9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin serum_dex_v3.so
validator를 시작할 때 Program 파일과 목적지 Address (local cluster에 있는)를 넘김으로써 로컬 넷에 올리는 것이 가능합니다:
# 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
solana-test-validator --bpf-program 9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin serum_dex_v3.so --reset