Développement Local
Démarrer un Validateur Local
Tester le code de votre programme localement peut être beaucoup plus fiable que de le tester sur le devnet, et peut vous aider à faire des tests avant de l'essayer sur le devnet.
Vous pouvez configurer votre validateur de test local (local-test-validator) en installant la suite d'outils de solana (solana tool suite) et en exécutant
solana-test-validator
Les avantages de l'utilisation de local-test-validator sont les suivants :
- Pas de limites de débit RPC
- Pas de limites d'airdrop
- Déploiement direct de programme sur la blockchain (
--bpf-program ...
) - Clonage de comptes à partir d'un cluster public, y compris les programmes (
--clone ...
) - Possibilité de configurer l'historique de conservation des transactions (
--limit-ledger-size ...
) - Possibilité de configurer la durée des époques (
--slots-per-epoch ...
) - Sauter à un slot précis (
--warp-slot ...
)
Connexion aux Environnements
Lorsque vous développez sur Solana, vous devez vous connecter à un point de terminaison API de RPC en particulier. Solana dispose de 3 environnements de développement publics :
- 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
Enfin, vous pouvez également vous connecter à un cluster privé, qu'il soit local ou fonctionnant à distance avec l'élément suivant :
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
Inscription à des Evénements
Les Websockets fournissent une interface pub/sub où vous pouvez écouter certains événements. Au lieu d'envoyer une requête à un point de terminaison HTTP classique à intervalles réguliers pour obtenir des mises à jour fréquentes, vous pouvez recevoir ces mises à jour uniquement lorsqu'elles se produisent.
La classe web3 Connection
de Solana génère un point de terminaison websocket et enregistre un client websocket lorsque vous créez une nouvelle instance Connection
(voir le code source ici).
La classe Connection
présente des méthodes pub/sub - elles commencent toutes par on
, comme les émetteurs d'événements. Lorsque vous appelez ces méthodes d'écoute, un nouvel enregistrement est effectué auprès du client websocket de l'instance Connection
. L'exemple de méthode pub/sub que nous utilisons ci-dessous est onAccountChange
. Le callback fournira les données d'état mises à jour par le biais d'arguments (voir AccountChangeCallback
en guise d'exemple).
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)
Obtention de SOL pour les Tests
Lorsque vous travaillez localement, vous avez besoin d'un certain nombre de SOL pour envoyer des transactions. Dans les environnements non-mainnet, vous pouvez recevoir des SOL en les airdroppant à votre adresse.
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
Utilisation des Comptes et des Programmes du Mainnet
Parfois, les tests locaux reposent sur des programmes et des comptes disponibles uniquement sur le mainnet. Le CLI de Solana permet à la fois de :
- Télécharger des Programmes et des Comptes
- Charger des Programmes et des Comptes dans un validateur local
Comment charger des comptes depuis le mainnet
Il est possible de télécharger les comptes de mint du jeton SRM dans un fichier comme suit :
# 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
Le chargement sur votre réseau local se fait ensuite en passant le fichier contenant les comptes et l'adresse de destination (sur le cluster local) lors du démarrage du validateur :
# 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
Comment charger des programmes depuis le mainnet
De même, il est possible de télécharger le programme 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
Le chargement sur votre réseau local se fait alors en passant le fichier du programme et l'adresse de destination (sur le cluster local) lors du démarrage du validateur :
# 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