本地开发
开启本地验证器
在本地测试验证器比在开发网络(devnet)上进行测试更可靠,并且可以帮助你在开发网络上运行之前进行测试。
你可以通过安装 solana工具套件 并运行以下命令来设置本地测试验证器:
solana-test-validator
使用本地测试验证器的好处包括:
- 无RPC速率限制
- 无空投限制
- 直接在链上部署程序(
--bpf-program ...
) - 从公共集群克隆账户,包括程序(
--clone ...
) - 可配置的事务历史保留(
--limit-ledger-size ...
) - 可配置的纪元长度(
--slots-per-epoch ...
) - 跳转到任意槽位(
--warp-slot ...
)
连接到不同环境
当你进行Solana开发时,你需要连接到特定的RPC API端点。Solana有三个公共的开发环境:
- 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
最后,你还可以连接到私有集群,无论是本地的还是远程运行的,使用以下方式:
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
订阅事件
Websockets提供了一种发布/订阅接口,你可以在其中监听特定的事件。与在固定时间间隔内对典型的HTTP端点进行轮询以获取频繁的更新不同,你可以仅在事件发生时才接收这些更新。
Solana的web3连接
在底层生成一个websocket端点,并在创建新的Connection
实例时注册一个websocket客户端(请参阅 此处) 的源代码)。
Connection
类提供了发布/订阅方法,它们都以on
开头,类似于事件发射器。当您调用这些监听器方法时,它会在该Connection
实例的websocket客户端中注册一个新的订阅。下面我们使用的示例发布/订阅方法是onAccountChange
。 回调函数将通过参数提供更新的状态数据(例如,查看AAccountChangeCallback
作为示例)。
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)
获取测试用的SOL
你在本地工作时,为了发送交易,你需要一些 SOL。在非主网环境中,你可以向你的地址空投 SOL,获取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
使用主网 (Mainnet) 账户和程序
本地测试通常依赖于仅在主网上可用的程序和账户。Solana CLI 提供了以下两个功能:
*下载程序和账户 *将程序和账户加载到本地验证器中
如何从主网加载账户
可以将SRM代币的铸造(mint)账户下载到文件中:
# 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
然后,通过在启动验证器时传递该账户文件和目标地址(在本地集群上)你可以将其加载到本地网络:
# 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
如何从主网加载程序
同样地,我们可以下载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
然后,在启动验证器时,通过传递程序的文件和目标地址(在本地集群上)来将其加载到本地网络:
# 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