Rust ile yazılan Solana programları minimum yapısal gereksinimlere sahiptir ve
kodun nasıl organize edileceği konusunda esneklik sağlar. Tek gereksinim,
programın bir entrypoint içermesi ve bu noktanın programın yürütülmesinin
nerede başladığını tanımlamasıdır.
Program yapısı
Dosya yapısı için katı kurallar olmasa da, Solana programları genellikle ortak bir deseni takip eder:
entrypoint.rs: Gelen talimatları yönlendiren giriş noktasını tanımlar.state.rs: Program durumunu (hesap verisi) tanımlar.instructions.rs: Programın yürütebileceği talimatları tanımlar.processor.rs: Her talimat için iş mantığını uygulayan talimat işleyicilerini (fonksiyonlar) tanımlar.error.rs: Programın döndürebileceği özel hataları tanımlar.
Örneğin, Token Program sayfasına bakın.
Örnek program
Birden fazla talimata sahip yerel bir Rust programının nasıl oluşturulacağını göstermek için, iki talimat uygulayan basit bir sayaç programını adım adım inceleyeceğiz:
InitializeCounter: Başlangıç değeriyle yeni bir hesap oluşturur ve başlatır.IncrementCounter: Mevcut bir hesapta saklanan değeri artırır.
Basitlik açısından, program tek bir lib.rs dosyasında uygulanacaktır, ancak
pratikte daha büyük programları birden fazla dosyaya bölmek isteyebilirsiniz.
Bölüm 1: Programı yazmak
Sayaç programını oluşturmaya başlayalım. Bir sayacı başlangıç değeriyle başlatabilen ve artırabilen bir program oluşturacağız.
Yeni bir program oluşturma
İlk olarak, Solana programımız için yeni bir Rust projesi oluşturalım.
$cargo new counter_program --lib$cd counter_program
Varsayılan src/lib.rs ve Cargo.toml dosyalarını görmelisiniz.
Cargo.toml dosyasındaki edition alanını 2021 olarak güncelleyin. Aksi
takdirde, programı derlerken bir hatayla karşılaşabilirsiniz.
Bağımlılıkları ekleme
Şimdi bir Solana programı oluşturmak için gerekli bağımlılıkları ekleyelim.
Temel SDK için solana-program ve serileştirme için borsh kütüphanelerine
ihtiyacımız var.
$cargo add solana-program@2.2.0$cargo add borsh
Borsh kullanma zorunluluğu yoktur. Ancak, Solana programları için yaygın olarak kullanılan bir serileştirme kütüphanesidir.
Crate-type yapılandırması
Solana programları dinamik kütüphaneler olarak derlenmeli. Cargo'nun programı
nasıl derleyeceğini yapılandırmak için [lib] bölümünü ekleyin.
[lib]crate-type = ["cdylib", "lib"]
Bu yapılandırmayı eklemezseniz, programı derlediğinizde target/deploy dizini oluşturulmayacaktır.
Program giriş noktasını ayarlama
Her Solana programının bir giriş noktası vardır; bu, program çağrıldığında çalıştırılan fonksiyondur. Program için ihtiyaç duyacağımız içe aktarmaları ekleyerek ve giriş noktasını ayarlayarak başlayalım.
Aşağıdaki kodu lib.rs dosyasına ekleyin:
use borsh::{BorshDeserialize, BorshSerialize};use solana_program::{account_info::{next_account_info, AccountInfo},entrypoint,entrypoint::ProgramResult,msg,program::invoke,program_error::ProgramError,pubkey::Pubkey,system_instruction,sysvar::{rent::Rent, Sysvar},};entrypoint!(process_instruction);pub fn process_instruction(program_id: &Pubkey,accounts: &[AccountInfo],instruction_data: &[u8],) -> ProgramResult {Ok(())}
entrypoint
makrosu, input verisinin process_instruction fonksiyonunun parametrelerine
deserileştirilmesini gerçekleştirir.
Bir Solana programı entrypoint fonksiyonu aşağıdaki imzaya sahiptir.
Geliştiriciler entrypoint fonksiyonunun kendi uygulamalarını oluşturmakta
özgürdür.
#[no_mangle]pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64;
Program durumunu tanımlama
Şimdi sayaç hesaplarımızda saklanacak veri yapısını tanımlayalım. Bu, hesabın
data alanında saklanacak veridir.
Aşağıdaki kodu lib.rs dosyasına ekleyin:
#[derive(BorshSerialize, BorshDeserialize, Debug)]pub struct CounterAccount {pub count: u64,}
Talimat enum'ını tanımlama
Programımızın yürütebileceği talimatları tanımlayalım. Her varyantın farklı bir talimatı temsil ettiği bir enum kullanacağız.
Aşağıdaki kodu lib.rs dosyasına ekleyin:
#[derive(BorshSerialize, BorshDeserialize, Debug)]pub enum CounterInstruction {InitializeCounter { initial_value: u64 },IncrementCounter,}
Talimat deserializasyonunu uygulama
Şimdi instruction_data verisini (ham baytlar) CounterInstruction enum
varyantlarımızdan birine deserialize etmemiz gerekiyor. Borsh try_from_slice
metodu bu dönüşümü otomatik olarak gerçekleştirir.
process_instruction fonksiyonunu Borsh deserializasyonunu kullanacak şekilde
güncelleyin:
pub fn process_instruction(program_id: &Pubkey,accounts: &[AccountInfo],instruction_data: &[u8],) -> ProgramResult {let instruction = CounterInstruction::try_from_slice(instruction_data).map_err(|_| ProgramError::InvalidInstructionData)?;Ok(())}
Talimatları işleyicilere yönlendirme
Şimdi ana process_instruction fonksiyonunu, talimatları uygun işleyici
fonksiyonlarına yönlendirecek şekilde güncelleyelim.
Bu yönlendirme deseni Solana programlarında yaygındır. instruction_data,
talimatı temsil eden bir enum varyantına deserialize edilir, ardından uygun
işleyici fonksiyonu çağrılır. Her işleyici fonksiyonu, o talimat için uygulamayı
içerir.
Aşağıdaki kodu lib.rs dosyasına ekleyerek process_instruction fonksiyonunu
güncelleyin ve InitializeCounter ile IncrementCounter talimatları için
işleyicileri ekleyin:
pub fn process_instruction(program_id: &Pubkey,accounts: &[AccountInfo],instruction_data: &[u8],) -> ProgramResult {let instruction = CounterInstruction::try_from_slice(instruction_data).map_err(|_| ProgramError::InvalidInstructionData)?;match instruction {CounterInstruction::InitializeCounter { initial_value } => {process_initialize_counter(program_id, accounts, initial_value)?}CounterInstruction::IncrementCounter => {process_increment_counter(program_id, accounts)?}};Ok(())}fn process_initialize_counter(program_id: &Pubkey,accounts: &[AccountInfo],initial_value: u64,) -> ProgramResult {Ok(())}fn process_increment_counter(program_id: &Pubkey,accounts: &[AccountInfo],) -> ProgramResult {Ok(())}
Başlatma işleyicisini uygulama
Yeni bir sayaç hesabı oluşturmak ve başlatmak için işleyiciyi uygulayalım. Solana'da yalnızca System Program hesap oluşturabildiğinden, bir Cross Program Invocation (CPI) kullanacağız, yani programımızdan başka bir programı çağıracağız.
Programımız, System Program'ın create_account instructionını çağırmak için bir
CPI yapar. Yeni hesap, programımızın sahibi olarak oluşturulur ve programımıza
hesaba yazma ve verileri başlatma yeteneği verir.
Aşağıdaki kodu lib.rs dosyasına ekleyerek process_initialize_counter
fonksiyonunu güncelleyin:
fn process_initialize_counter(program_id: &Pubkey,accounts: &[AccountInfo],initial_value: u64,) -> ProgramResult {let accounts_iter = &mut accounts.iter();let counter_account = next_account_info(accounts_iter)?;let payer_account = next_account_info(accounts_iter)?;let system_program = next_account_info(accounts_iter)?;let account_space = 8;let rent = Rent::get()?;let required_lamports = rent.minimum_balance(account_space);invoke(&system_instruction::create_account(payer_account.key,counter_account.key,required_lamports,account_space as u64,program_id,),&[payer_account.clone(),counter_account.clone(),system_program.clone(),],)?;let counter_data = CounterAccount {count: initial_value,};let mut account_data = &mut counter_account.data.borrow_mut()[..];counter_data.serialize(&mut account_data)?;msg!("Counter initialized with value: {}", initial_value);Ok(())}
Bu instruction yalnızca gösterim amaçlıdır. Üretim programları için gerekli olan güvenlik ve doğrulama kontrollerini içermez.
Artırma işleyicisini uygulama
Şimdi mevcut bir sayacı artıran işleyiciyi uygulayalım. Bu instruction:
counter_accountiçin hesapdataalanını okur- Bunu bir
CounterAccountstruct'ına deserialize eder countalanını 1 artırırCounterAccountstruct'ını hesabındataalanına geri serialize eder
Aşağıdaki kodu lib.rs dosyasına ekleyerek process_increment_counter
fonksiyonunu güncelleyin:
fn process_increment_counter(program_id: &Pubkey,accounts: &[AccountInfo],) -> ProgramResult {let accounts_iter = &mut accounts.iter();let counter_account = next_account_info(accounts_iter)?;if counter_account.owner != program_id {return Err(ProgramError::IncorrectProgramId);}let mut data = counter_account.data.borrow_mut();let mut counter_data: CounterAccount = CounterAccount::try_from_slice(&data)?;counter_data.count = counter_data.count.checked_add(1).ok_or(ProgramError::InvalidAccountData)?;counter_data.serialize(&mut &mut data[..])?;msg!("Counter incremented to: {}", counter_data.count);Ok(())}
Bu instruction yalnızca gösterim amaçlıdır. Üretim programları için gerekli olan güvenlik ve doğrulama kontrollerini içermez.
Tamamlanmış program
Tebrikler! Tüm Solana programlarının paylaştığı temel yapıyı gösteren eksiksiz bir Solana programı oluşturdunuz:
- Entrypoint: Program yürütmesinin nerede başladığını tanımlar ve gelen tüm istekleri uygun instruction işleyicilerine yönlendirir
- Instruction işleme: Instructionları ve bunlarla ilişkili işleyici fonksiyonlarını tanımlar
- Durum yönetimi: Hesap veri yapılarını tanımlar ve bunların durumunu program sahipli hesaplarda yönetir
- Cross program invocation (CPI): Yeni program sahipli hesaplar oluşturmak için System Program'ı çağırır
Bir sonraki adım, her şeyin doğru çalıştığından emin olmak için programı test etmektir.
Bölüm 2: Programı test etme
Şimdi counter programımızı test edelim. Programları bir kümeye dağıtmadan test etmemizi sağlayan bir test framework'ü olan LiteSVM kullanacağız.
Test bağımlılıklarını ekleme
Öncelikle, test için gereken bağımlılıkları ekleyelim. Test için litesvm ve
solana-sdk kullanacağız.
$cargo add litesvm@0.6.1 --dev$cargo add solana-sdk@2.2.0 --dev
Test modülü oluşturma
Şimdi programımıza bir test modülü ekleyelim. Temel yapı ve import'larla başlayacağız.
Aşağıdaki kodu lib.rs dosyasına, doğrudan program kodunun altına ekleyin:
#[cfg(test)]mod test {use super::*;use litesvm::LiteSVM;use solana_sdk::{account::ReadableAccount,instruction::{AccountMeta, Instruction},message::Message,signature::{Keypair, Signer},system_program,transaction::Transaction,};#[test]fn test_counter_program() {// Test implementation will go here}}
#[cfg(test)] özniteliği, bu kodun yalnızca testler çalıştırılırken
derlenmesini sağlar.
Test ortamını başlatma
LiteSVM ile test ortamını kuralım ve bir payer hesabını fonlayalım.
LiteSVM, Solana runtime ortamını simüle ederek programımızı gerçek bir kümeye dağıtmadan test etmemizi sağlar.
Aşağıdaki kodu lib.rs dosyasına ekleyerek test_counter_program fonksiyonunu
güncelleyin:
let mut svm = LiteSVM::new();let payer = Keypair::new();svm.airdrop(&payer.pubkey(), 1_000_000_000).expect("Failed to airdrop");
Programı yükleme
Şimdi programımızı oluşturup test ortamına yüklememiz gerekiyor. Programı
oluşturmak için cargo build-sbf komutunu çalıştırın. Bu, target/deploy
dizininde counter_program.so dosyasını oluşturacaktır.
$cargo build-sbf
Cargo.toml dosyasındaki edition değerinin 2021 olarak ayarlandığından
emin olun.
Derleme işleminden sonra, programı yükleyebiliriz.
Test ortamına programı yüklemek için test_counter_program fonksiyonunu
güncelleyin.
let program_keypair = Keypair::new();let program_id = program_keypair.pubkey();svm.add_program_from_file(program_id,"target/deploy/counter_program.so").expect("Failed to load program");
cargo build-sbf komutunu çalıştırarak .so dosyasını oluşturmanız gerekir.
Test, derlenmiş programı yükler.
Test başlatma talimatı
Başlangıç değeriyle yeni bir sayaç hesabı oluşturarak başlatma talimatını test edelim.
Aşağıdaki kodu lib.rs dosyasına ekleyerek test_counter_program fonksiyonunu
güncelleyin:
let counter_keypair = Keypair::new();let initial_value: u64 = 42;println!("Testing counter initialization...");let init_instruction_data =borsh::to_vec(&CounterInstruction::InitializeCounter { initial_value }).expect("Failed to serialize instruction");let initialize_instruction = Instruction::new_with_bytes(program_id,&init_instruction_data,vec![AccountMeta::new(counter_keypair.pubkey(), true),AccountMeta::new(payer.pubkey(), true),AccountMeta::new_readonly(system_program::id(), false),],);let message = Message::new(&[initialize_instruction], Some(&payer.pubkey()));let transaction = Transaction::new(&[&payer, &counter_keypair],message,svm.latest_blockhash());let result = svm.send_transaction(transaction);assert!(result.is_ok(), "Initialize transaction should succeed");let logs = result.unwrap().logs;println!("Transaction logs:\n{:#?}", logs);
Başlatmayı doğrula
Başlatmadan sonra, sayaç hesabının beklenen değerle doğru şekilde oluşturulup oluşturulmadığını doğrulayalım.
Aşağıdaki kodu lib.rs dosyasına ekleyerek test_counter_program fonksiyonunu
güncelleyin:
let account = svm.get_account(&counter_keypair.pubkey()).expect("Failed to get counter account");let counter: CounterAccount = CounterAccount::try_from_slice(account.data()).expect("Failed to deserialize counter data");assert_eq!(counter.count, 42);println!("Counter initialized successfully with value: {}", counter.count);
Artırma talimatını test et
Şimdi artırma talimatını test ederek sayaç değerinin düzgün şekilde güncellendiğinden emin olalım.
Aşağıdaki kodu lib.rs dosyasına ekleyerek test_counter_program fonksiyonunu
güncelleyin:
println!("Testing counter increment...");let increment_instruction_data =borsh::to_vec(&CounterInstruction::IncrementCounter).expect("Failed to serialize instruction");let increment_instruction = Instruction::new_with_bytes(program_id,&increment_instruction_data,vec![AccountMeta::new(counter_keypair.pubkey(), true)],);let message = Message::new(&[increment_instruction], Some(&payer.pubkey()));let transaction = Transaction::new(&[&payer, &counter_keypair],message,svm.latest_blockhash());let result = svm.send_transaction(transaction);assert!(result.is_ok(), "Increment transaction should succeed");let logs = result.unwrap().logs;println!("Transaction logs:\n{:#?}", logs);
Sonuçları doğrula
Son olarak, güncellenen sayaç değerini kontrol ederek artırmanın doğru çalıştığını doğrulayalım.
Aşağıdaki kodu lib.rs dosyasına ekleyerek test_counter_program fonksiyonunu
güncelleyin:
let account = svm.get_account(&counter_keypair.pubkey()).expect("Failed to get counter account");let counter: CounterAccount = CounterAccount::try_from_slice(account.data()).expect("Failed to deserialize counter data");assert_eq!(counter.count, 43);println!("Counter incremented successfully to: {}", counter.count);
Testleri aşağıdaki komutla çalıştırın. --nocapture bayrağı testin çıktısını
yazdırır.
$cargo test -- --nocapture
Beklenen çıktı:
Testing counter initialization...Transaction logs:["Program 3QpyHXhFtYY32iY7foF3EjkVdCDrUppADk9aDwSWn6Sq invoke [1]","Program 11111111111111111111111111111111 invoke [2]","Program 11111111111111111111111111111111 success","Program log: Counter initialized with value: 42","Program 3QpyHXhFtYY32iY7foF3EjkVdCDrUppADk9aDwSWn6Sq consumed 3803 of 200000 compute units","Program 3QpyHXhFtYY32iY7foF3EjkVdCDrUppADk9aDwSWn6Sq success",]Counter initialized successfully with value: 42Testing counter increment...Transaction logs:["Program 3QpyHXhFtYY32iY7foF3EjkVdCDrUppADk9aDwSWn6Sq invoke [1]","Program log: Counter incremented to: 43","Program 3QpyHXhFtYY32iY7foF3EjkVdCDrUppADk9aDwSWn6Sq consumed 762 of 200000 compute units","Program 3QpyHXhFtYY32iY7foF3EjkVdCDrUppADk9aDwSWn6Sq success",]Counter incremented successfully to: 43
Bölüm 3: Programı Çağırma
Şimdi programı çağırmak için bir istemci betiği ekleyelim.
İstemci örneği oluşturma
Dağıtılmış programımızla etkileşime geçmek için bir Rust istemcisi oluşturalım.
$mkdir examples$touch examples/client.rs
Aşağıdaki yapılandırmayı Cargo.toml dosyasına ekleyin:
[[example]]name = "client"path = "examples/client.rs"
İstemci bağımlılıklarını yükleyin:
$cargo add solana-client@2.2.0 --dev$cargo add tokio --dev
İstemci kodunu uygulama
Şimdi dağıtılmış programımızı çağıracak istemciyi uygulayalım.
Program kimliğinizi keypair dosyasından almak için aşağıdaki komutu çalıştırın:
$solana address -k ./target/deploy/counter_program-keypair.json
İstemci kodunu examples/client.rs dosyasına ekleyin ve program_id değerini
önceki komutun çıktısıyla değiştirin:
let program_id = Pubkey::from_str("BDLLezrtFEXVGYqG3aS7eAC7GVeojJ4JHhKJM6pAFCDH").expect("Invalid program ID");
use solana_client::rpc_client::RpcClient;use solana_sdk::{commitment_config::CommitmentConfig,instruction::{AccountMeta, Instruction},pubkey::Pubkey,signature::{Keypair, Signer},system_program,transaction::Transaction,};use std::str::FromStr;use counter_program::CounterInstruction;#[tokio::main]async fn main() {// Replace with your actual program ID from deploymentlet program_id = Pubkey::from_str("BDLLezrtFEXVGYqG3aS7eAC7GVeojJ4JHhKJM6pAFCDH").expect("Invalid program ID");// Connect to local clusterlet rpc_url = String::from("http://localhost:8899");let client = RpcClient::new_with_commitment(rpc_url, CommitmentConfig::confirmed());// Generate a new keypair for paying feeslet payer = Keypair::new();// Request airdrop of 1 SOL for transaction feesprintln!("Requesting airdrop...");let airdrop_signature = client.request_airdrop(&payer.pubkey(), 1_000_000_000).expect("Failed to request airdrop");// Wait for airdrop confirmationloop {if client.confirm_transaction(&airdrop_signature).unwrap_or(false){break;}std::thread::sleep(std::time::Duration::from_millis(500));}println!("Airdrop confirmed");println!("\nInitializing counter...");let counter_keypair = Keypair::new();let initial_value = 100u64;// Serialize the initialize instruction datalet instruction_data = borsh::to_vec(&CounterInstruction::InitializeCounter { initial_value }).expect("Failed to serialize instruction");let initialize_instruction = Instruction::new_with_bytes(program_id,&instruction_data,vec![AccountMeta::new(counter_keypair.pubkey(), true),AccountMeta::new(payer.pubkey(), true),AccountMeta::new_readonly(system_program::id(), false),],);let mut transaction =Transaction::new_with_payer(&[initialize_instruction], Some(&payer.pubkey()));let blockhash = client.get_latest_blockhash().expect("Failed to get blockhash");transaction.sign(&[&payer, &counter_keypair], blockhash);match client.send_and_confirm_transaction(&transaction) {Ok(signature) => {println!("Counter initialized!");println!("Transaction: {}", signature);println!("Counter address: {}", counter_keypair.pubkey());}Err(err) => {eprintln!("Failed to initialize counter: {}", err);return;}}println!("\nIncrementing counter...");// Serialize the increment instruction datalet increment_data = borsh::to_vec(&CounterInstruction::IncrementCounter).expect("Failed to serialize instruction");let increment_instruction = Instruction::new_with_bytes(program_id,&increment_data,vec![AccountMeta::new(counter_keypair.pubkey(), true)],);let mut transaction =Transaction::new_with_payer(&[increment_instruction], Some(&payer.pubkey()));transaction.sign(&[&payer, &counter_keypair], blockhash);match client.send_and_confirm_transaction(&transaction) {Ok(signature) => {println!("Counter incremented!");println!("Transaction: {}", signature);}Err(err) => {eprintln!("Failed to increment counter: {}", err);}}}
Bölüm 4: Programı Dağıtma
Artık programımız ve istemcimiz hazır olduğuna göre, programı derleyelim, dağıtalım ve çağıralım.
Programı derleyin
Öncelikle programımızı derleyelim.
$cargo build-sbf
Bu komut programınızı derler ve target/deploy/ içinde iki önemli dosya
oluşturur:
counter_program.so # The compiled programcounter_program-keypair.json # Keypair for the program ID
Aşağıdaki komutu çalıştırarak programınızın ID'sini görüntüleyebilirsiniz:
$solana address -k ./target/deploy/counter_program-keypair.json
Örnek çıktı:
HQ5Q2XXqbTKKQsWPtLzMn7rDhM8v9UPYPe7DfSoFQqJF
Yerel validator'ı başlatın
Geliştirme için yerel bir test validator'ı kullanacağız.
Öncelikle Solana CLI'yi localhost kullanacak şekilde yapılandırın:
$solana config set -ul
Örnek çıktı:
Config File: ~/.config/solana/cli/config.ymlRPC URL: http://localhost:8899WebSocket URL: ws://localhost:8900/ (computed)Keypair Path: ~/.config/solana/id.jsonCommitment: confirmed
Şimdi ayrı bir terminalde test validator'ı başlatın:
$solana-test-validator
Programı dağıtın
Validator çalışırken, programınızı yerel kümeye dağıtın:
$solana program deploy ./target/deploy/counter_program.so
Örnek çıktı:
Program Id: HQ5Q2XXqbTKKQsWPtLzMn7rDhM8v9UPYPe7DfSoFQqJFSignature: 5xKdnh3dDFnZXB5UevYYkFBpCVcuqo5SaUPLnryFWY7eQD2CJxaeVDKjQ4ezQVJfkGNqZGYqMZBNqymPKwCQQx5h
Program kimliğinizle solana program show komutunu kullanarak dağıtımı
doğrulayabilirsiniz:
$solana program show HQ5Q2XXqbTKKQsWPtLzMn7rDhM8v9UPYPe7DfSoFQqJF
Örnek çıktı:
Program Id: HQ5Q2XXqbTKKQsWPtLzMn7rDhM8v9UPYPe7DfSoFQqJFOwner: BPFLoaderUpgradeab1e11111111111111111111111ProgramData Address: 47MVf5tRZ4zWXQMX7ydrkgcFQr8XTk1QBjohwsUzaiuMAuthority: 4kh6HxYZiAebF8HWLsUWod2EaQQ6iWHpHYCz8UcmFbM1Last Deployed In Slot: 16Data Length: 82696 (0x14308) bytesBalance: 0.57676824 SOL
İstemciyi çalıştırın
Yerel validator hala çalışırken, istemciyi çalıştırın:
$cargo run --example client
Beklenen çıktı:
Requesting airdrop...Airdrop confirmedInitializing counter...Counter initialized!Transaction: 2uenChtqNeLC1fitqoVE2LBeygSBTDchMZ4gGqs7AiDvZZVJguLDE5PfxsfkgY7xs6zFWnYsbEtb82dWv9tDT14kCounter address: EppPAmwqD42u4SCPWpPT7wmWKdFad5VnM9J4R9ZfofcyIncrementing counter...Counter incremented!Transaction: 4qv1Rx6FHu1M3woVgDQ6KtYUaJgBzGcHnhej76ZpaKGCgsTorbcHnPKxoH916UENw7X5ppnQ8PkPnhXxEwrYuUxS
Yerel validator çalışırken, çıktı işlem imzalarını kullanarak
Solana Explorer üzerinde
işlemleri görüntüleyebilirsiniz. Solana Explorer'daki kümenin "Custom RPC URL"
olarak ayarlanması gerektiğini unutmayın; bu, solana-test-validator'nin
çalıştığı http://localhost:8899 varsayılan değerine ayarlanır.
Is this page helpful?