Rust Program Yapısı
Rust ile yazılan Solana programları, kodun nasıl düzenleneceği konusunda
esneklik sağlayan minimum yapısal gereksinimlere sahiptir. Tek gereksinim, bir
programın çalışmasının nereden başlayacağını tanımlayan bir entrypoint
içermesidir.
Program Yapısı
Dosya yapısı için katı kurallar olmasa da, Solana programları genellikle yaygın bir modeli 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.
Örnek olarak Token Programı'na bakabilirsiniz.
Örnek Program
Birden fazla talimat içeren yerel bir Rust programının nasıl oluşturulacağını göstermek için, iki talimat uygulayan basit bir sayaç programını inceleyeceğiz:
InitializeCounter
: Başlangıç değeri ile yeni bir hesap oluşturur ve başlatır.IncrementCounter
: Mevcut bir hesapta depolanan değeri artırır.
Basitlik açısından, program tek bir lib.rs
dosyasında uygulanacaktır, ancak
uygulamada daha büyük programları birden fazla dosyaya bölmek isteyebilirsiniz.
Bölüm 1: Programı Yazma
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ştur
Öncelikle, 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ı oluştururken bir hatayla karşılaşabilirsiniz.
Bağımlılıkları ekle
Ş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
ihtiyacımız var.
$cargo add solana-program@2.2.0$cargo add borsh
Borsh kullanmak zorunlu değildir. 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 derlenmelidir. Cargo'nun programı
nasıl oluşturacağını yapılandırmak için [lib]
bölümünü ekleyin.
[lib]crate-type = ["cdylib", "lib"]
Bu yapılandırmayı eklemezseniz, programı oluşturduğunuzda target/deploy dizini oluşturulmayacaktır.
Program giriş noktasını ayarla
Her Solana programının, program çağrıldığında çalıştırılan bir giriş noktası vardır. Program için ihtiyacımız olan 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
dönüştürülmesini sağlar.
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ımla
Ş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'unu 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
(ham baytlar) verisini CounterInstruction
enum
varyantlarımızdan birine dönüştürmemiz gerekiyor. Borsh'un try_from_slice
metodu bu dönüşümü otomatik olarak gerçekleştirir.
Borsh deserializasyonunu kullanmak için process_instruction
fonksiyonunu
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 talimatları uygun işleyici fonksiyonlara yönlendirmek için ana
process_instruction
fonksiyonunu güncelleyelim.
Bu yönlendirme modeli Solana programlarında yaygındır. instruction_data
verisi, talimatı temsil eden bir enum varyantına dönüştürülür, ardından uygun
işleyici fonksiyon çağrılır. Her işleyici fonksiyon, o talimat için uygulamayı
içerir.
process_instruction
fonksiyonunu güncelleyen ve InitializeCounter
ve
IncrementCounter
talimatları için işleyiciler ekleyen aşağıdaki kodu lib.rs
dosyasına 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şturup başlatmak için işleyiciyi uygulayalım. Solana'da yalnızca System Program hesap oluşturabildiğinden, programımızdan başka bir programı çağıran Cross Program Invocation (CPI) kullanacağız.
Programımız, System Program'ın create_account
talimatını çağırmak için bir CPI
yapar. Yeni hesap, programımız sahibi olarak oluşturulur ve bu da programımıza
hesaba yazma ve veriyi 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 talimat 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 talimat:
- Hesabın
data
alanındancounter_account
değerini okur - Bunu bir
CounterAccount
yapısına dönüştürür count
alanını 1 artırırCounterAccount
yapısını hesabındata
alanına geri yazar
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 talimat 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:
- Giriş noktası: Program yürütmesinin nerede başladığını tanımlar ve gelen tüm istekleri uygun talimat işleyicilerine yönlendirir
- Talimat İşleme: Talimatları ve bunlarla ilişkili işleyici fonksiyonlarını tanımlar
- Durum Yönetimi: Hesap veri yapılarını tanımlar ve program tarafından sahip olunan hesaplardaki durumlarını 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 sayaç programımızı test edelim. Programları bir kümeye dağıtmadan test etmemizi sağlayan bir test çerçevesi 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ştur
Şimdi programımıza bir test modülü ekleyelim. Temel yapı ve importlar ile 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şlat
LiteSVM ile test ortamını kuralım ve bir ödeyici hesabı fonlayalım.
LiteSVM, Solana çalışma zamanı 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ükle
Şimdi programımızı derleyip test ortamına yüklememiz gerekiyor. Programı
derlemek 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
içindeki edition
değerinin 2021
olarak ayarlandığından emin
olun.
Derlemeden sonra, programı yükleyebiliriz.
Programı test ortamına 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");
Testleri çalıştırmadan önce .so
dosyasını oluşturmak için cargo build-sbf
komutunu çalıştırmalısınız. Test, derlenmiş programı yükler.
Başlatma talimatını test et
Başlangıç değeri olan 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ğrulama
Başlatmadan sonra, sayaç hesabının beklenen değerle doğru şekilde oluşturulduğunu 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 etme
Şimdi artırma talimatını test edelim ve sayaç değerini düzgün şekilde güncellediğ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);
Son sonuçları doğrulama
Son olarak, güncellenmiş sayaç değerini kontrol ederek artırma işleminin 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şim kurmak 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.
Keypair dosyasından program kimliğinizi 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
kısmını
ö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ı derleme
Ö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.
Önce, 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 ID'niz ile 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ıdaki işlem imzalarını kullanarak işlemleri
Solana Explorer üzerinden
görüntüleyebilirsiniz. Solana Explorer'daki kümenin "Custom RPC URL" olarak
ayarlanması gerektiğini unutmayın, bu varsayılan olarak validator'ın çalıştığı
http://localhost:8899
adresine ayarlanır.
Is this page helpful?