In Rust geschriebene Solana-Programme haben minimale strukturelle Anforderungen,
was Flexibilität bei der Organisation des Codes ermöglicht. Die einzige
Anforderung ist, dass ein Programm einen entrypoint haben muss, der definiert,
wo die Ausführung eines Programms beginnt.
Programmstruktur
Obwohl es keine strengen Regeln für die Dateistruktur gibt, folgen Solana-Programme typischerweise einem gemeinsamen Muster:
entrypoint.rs: Definiert den Einstiegspunkt, der eingehende Anweisungen weiterleitet.state.rs: Definiert den Programmzustand (Kontodaten).instructions.rs: Definiert die Anweisungen, die das Programm ausführen kann.processor.rs: Definiert die Anweisungshandler (Funktionen), die die Geschäftslogik für jede Anweisung implementieren.error.rs: Definiert benutzerdefinierte Fehler, die das Programm zurückgeben kann.
Siehe zum Beispiel das Token-Programm.
Beispielprogramm
Um zu demonstrieren, wie man ein natives Rust-Programm mit mehreren Anweisungen erstellt, gehen wir ein einfaches Zählerprogramm durch, das zwei Anweisungen implementiert:
InitializeCounter: Erstellt und initialisiert ein neues Konto mit einem Anfangswert.IncrementCounter: Erhöht den in einem bestehenden Konto gespeicherten Wert.
Der Einfachheit halber wird das Programm in einer einzigen lib.rs-Datei
implementiert, obwohl Sie in der Praxis größere Programme möglicherweise in
mehrere Dateien aufteilen möchten.
Teil 1: Schreiben des Programms
Beginnen wir mit dem Erstellen des Zählerprogramms. Wir erstellen ein Programm, das einen Zähler mit einem Startwert initialisieren und ihn erhöhen kann.
Erstelle ein neues Programm
Erstellen wir zunächst ein neues Rust-Projekt für unser Solana-Programm.
$cargo new counter_program --lib$cd counter_program
Sie sollten die Standard-Dateien src/lib.rs und Cargo.toml sehen.
Aktualisieren Sie das Feld edition in Cargo.toml auf 2021. Andernfalls
kann beim Erstellen des Programms ein Fehler auftreten.
Füge Abhängigkeiten hinzu
Fügen wir nun die notwendigen Abhängigkeiten für die Erstellung eines
Solana-Programms hinzu. Wir benötigen solana-program für das Core-SDK und
borsh für die Serialisierung.
$cargo add solana-program@2.2.0$cargo add borsh
Es besteht keine Verpflichtung, Borsh zu verwenden. Es ist jedoch eine häufig verwendete Serialisierungsbibliothek für Solana-Programme.
Konfiguriere crate-type
Solana-Programme müssen als dynamische Bibliotheken kompiliert werden. Fügen Sie
den Abschnitt [lib] hinzu, um zu konfigurieren, wie Cargo das Programm
erstellt.
[lib]crate-type = ["cdylib", "lib"]
Wenn Sie diese Konfiguration nicht einschließen, wird das Verzeichnis target/deploy beim Erstellen des Programms nicht generiert.
Richte den Programm-Entrypoint ein
Jedes Solana-Programm hat einen Entrypoint, die Funktion, die aufgerufen wird, wenn das Programm ausgeführt wird. Beginnen wir damit, die benötigten Imports für das Programm hinzuzufügen und den Entrypoint einzurichten.
Fügen Sie den folgenden Code zu lib.rs hinzu:
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(())}
Das
entrypoint
Makro übernimmt die Deserialisierung der input Daten in die Parameter der
process_instruction Funktion.
Ein Solana-Programm entrypoint hat die folgende Funktionssignatur. Entwickler
können ihre eigene Implementierung der entrypoint Funktion erstellen.
#[no_mangle]pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64;
Definiere den Programmzustand
Jetzt definieren wir die Datenstruktur, die in unseren Counter-Konten
gespeichert wird. Dies sind die Daten, die im Feld data des Kontos gespeichert
werden.
Füge den folgenden Code zu lib.rs hinzu:
#[derive(BorshSerialize, BorshDeserialize, Debug)]pub struct CounterAccount {pub count: u64,}
Definiere das Anweisungs-Enum
Definieren wir die Anweisungen, die unser Programm ausführen kann. Wir verwenden ein Enum, bei dem jede Variante eine andere Anweisung darstellt.
Füge den folgenden Code zu lib.rs hinzu:
#[derive(BorshSerialize, BorshDeserialize, Debug)]pub enum CounterInstruction {InitializeCounter { initial_value: u64 },IncrementCounter,}
Implementiere die Deserialisierung von Anweisungen
Jetzt müssen wir die instruction_data (rohe Bytes) in eine unserer
CounterInstruction-Enum-Varianten deserialisieren. Die Borsh-Methode
try_from_slice übernimmt diese Konvertierung automatisch.
Aktualisiere die Funktion process_instruction, um die Borsh-Deserialisierung
zu verwenden:
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(())}
Leite Anweisungen an Handler weiter
Aktualisieren wir nun die Hauptfunktion process_instruction, um Anweisungen an
die entsprechenden Handler-Funktionen weiterzuleiten.
Dieses Routing-Muster ist in Solana-Programmen üblich. Die instruction_data
wird in eine Variante eines Enums deserialisiert, das die Anweisung
repräsentiert, dann wird die entsprechende Handler-Funktion aufgerufen. Jede
Handler-Funktion enthält die Implementierung für diese Anweisung.
Füge den folgenden Code zu lib.rs hinzu, aktualisiere die Funktion
process_instruction und füge die Handler für die Anweisungen
InitializeCounter und IncrementCounter hinzu:
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(())}
Implementiere den Initialize-Handler
Implementieren wir den Handler zum Erstellen und Initialisieren eines neuen Counter-Kontos. Da nur das System Program Konten auf Solana erstellen kann, verwenden wir einen Cross Program Invocation (CPI), also den Aufruf eines anderen Programms aus unserem Programm heraus.
Unser Programm führt einen CPI aus, um die Anweisung create_account des System
Program aufzurufen. Das neue Konto wird mit unserem Programm als Eigentümer
erstellt, wodurch unser Programm die Möglichkeit erhält, in das Konto zu
schreiben und die Daten zu initialisieren.
Fügen Sie den folgenden Code zu lib.rs hinzu und aktualisieren Sie die
Funktion process_initialize_counter:
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(())}
Diese Anweisung dient nur zu Demonstrationszwecken. Sie enthält keine Sicherheits- und Validierungsprüfungen, die für Produktionsprogramme erforderlich sind.
Inkrement-Handler implementieren
Implementieren wir nun den Handler, der einen bestehenden Zähler erhöht. Diese Anweisung:
- Liest das Feld
datades Kontos fürcounter_account - Deserialisiert es in eine
CounterAccount-Struktur - Erhöht das Feld
countum 1 - Serialisiert die
CounterAccount-Struktur zurück in das Felddatades Kontos
Fügen Sie den folgenden Code zu lib.rs hinzu und aktualisieren Sie die
Funktion process_increment_counter:
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(())}
Diese Anweisung dient nur zu Demonstrationszwecken. Sie enthält keine Sicherheits- und Validierungsprüfungen, die für Produktionsprogramme erforderlich sind.
Vollständiges Programm
Herzlichen Glückwunsch! Sie haben ein vollständiges Solana-Programm erstellt, das die grundlegende Struktur demonstriert, die alle Solana-Programme gemeinsam haben:
- Entrypoint: Definiert, wo die Programmausführung beginnt, und leitet alle eingehenden Anfragen an die entsprechenden Anweisungs-Handler weiter
- Anweisungsverarbeitung: Definiert Anweisungen und ihre zugehörigen Handler-Funktionen
- Zustandsverwaltung: Definiert Kontodatenstrukturen und verwaltet deren Zustand in programmeigenen Konten
- Cross Program Invocation (CPI): Ruft das System Program auf, um neue programmeigene Konten zu erstellen
Der nächste Schritt besteht darin, das Programm zu testen, um sicherzustellen, dass alles korrekt funktioniert.
Teil 2: Testen des Programms
Jetzt testen wir unser Counter-Programm. Wir verwenden LiteSVM, ein Test-Framework, mit dem wir Programme testen können, ohne sie auf einem Cluster bereitzustellen.
Test-Abhängigkeiten hinzufügen
Zunächst fügen wir die für das Testen benötigten Abhängigkeiten hinzu. Wir
verwenden litesvm zum Testen und solana-sdk.
$cargo add litesvm@0.6.1 --dev$cargo add solana-sdk@2.2.0 --dev
Test-Modul erstellen
Jetzt fügen wir unserem Programm ein Test-Modul hinzu. Wir beginnen mit dem grundlegenden Gerüst und den Imports.
Fügen Sie den folgenden Code zu lib.rs direkt unterhalb des Programmcodes
hinzu:
#[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}}
Das Attribut #[cfg(test)] stellt sicher, dass dieser Code nur beim Ausführen
von Tests kompiliert wird.
Test-Umgebung initialisieren
Richten wir die Test-Umgebung mit LiteSVM ein und finanzieren ein Payer-Konto.
LiteSVM simuliert die Solana-Laufzeitumgebung und ermöglicht es uns, unser Programm zu testen, ohne es auf einem echten Cluster bereitzustellen.
Fügen Sie den folgenden Code zu lib.rs hinzu, um die Funktion
test_counter_program zu aktualisieren:
let mut svm = LiteSVM::new();let payer = Keypair::new();svm.airdrop(&payer.pubkey(), 1_000_000_000).expect("Failed to airdrop");
Programm laden
Jetzt müssen wir unser Programm erstellen und in die Test-Umgebung laden. Führen
Sie den Befehl cargo build-sbf aus, um das Programm zu erstellen. Dadurch wird
die Datei counter_program.so im Verzeichnis target/deploy generiert.
$cargo build-sbf
Stellen Sie sicher, dass edition in Cargo.toml auf 2021 gesetzt ist.
Nach dem Build können wir das Programm laden.
Aktualisieren Sie die Funktion test_counter_program, um das Programm in die
Testumgebung zu laden.
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");
Sie müssen cargo build-sbf ausführen, bevor Sie Tests durchführen, um die
Datei .so zu generieren. Der Test lädt das kompilierte Programm.
Initialisierungsanweisung testen
Testen wir die Initialisierungsanweisung, indem wir ein neues Counter-Konto mit einem Startwert erstellen.
Fügen Sie den folgenden Code zu lib.rs hinzu und aktualisieren Sie die
Funktion test_counter_program:
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);
Initialisierung überprüfen
Nach der Initialisierung überprüfen wir, ob das Counter-Konto korrekt mit dem erwarteten Wert erstellt wurde.
Fügen Sie den folgenden Code zu lib.rs hinzu und aktualisieren Sie die
Funktion test_counter_program:
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);
Increment-Anweisung testen
Testen wir nun die Increment-Anweisung, um sicherzustellen, dass sie den Counter-Wert korrekt aktualisiert.
Fügen Sie den folgenden Code zu lib.rs hinzu und aktualisieren Sie die
Funktion test_counter_program:
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);
Endergebnisse überprüfen
Abschließend überprüfen wir, ob das Increment korrekt funktioniert hat, indem wir den aktualisierten Counter-Wert prüfen.
Fügen Sie den folgenden Code zu lib.rs hinzu und aktualisieren Sie die
Funktion test_counter_program:
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);
Führe die Tests mit dem folgenden Befehl aus. Das Flag --nocapture gibt die
Ausgabe des Tests aus.
$cargo test -- --nocapture
Erwartete Ausgabe:
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
Teil 3: Aufruf des Programms
Fügen wir nun ein Client-Skript hinzu, um das Programm aufzurufen.
Client-Beispiel erstellen
Erstellen wir einen Rust-Client, um mit unserem bereitgestellten Programm zu interagieren.
$mkdir examples$touch examples/client.rs
Füge die folgende Konfiguration zu Cargo.toml hinzu:
[[example]]name = "client"path = "examples/client.rs"
Installiere die Client-Abhängigkeiten:
$cargo add solana-client@2.2.0 --dev$cargo add tokio --dev
Client-Code implementieren
Implementieren wir nun den Client, der unser bereitgestelltes Programm aufruft.
Führe den folgenden Befehl aus, um deine Programm-ID aus der Keypair-Datei zu erhalten:
$solana address -k ./target/deploy/counter_program-keypair.json
Füge den Client-Code zu examples/client.rs hinzu und ersetze die program_id
durch die Ausgabe des vorherigen Befehls:
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);}}}
Teil 4: Das Programm bereitstellen
Jetzt, da wir unser Programm und unseren Client fertig haben, bauen, deployen und rufen wir das Programm auf.
Das Programm bauen
Zuerst bauen wir unser Programm.
$cargo build-sbf
Dieser Befehl kompiliert Ihr Programm und generiert zwei wichtige Dateien in
target/deploy/:
counter_program.so # The compiled programcounter_program-keypair.json # Keypair for the program ID
Sie können die ID Ihres Programms anzeigen, indem Sie den folgenden Befehl ausführen:
$solana address -k ./target/deploy/counter_program-keypair.json
Beispielausgabe:
HQ5Q2XXqbTKKQsWPtLzMn7rDhM8v9UPYPe7DfSoFQqJF
Lokalen Validator starten
Für die Entwicklung verwenden wir einen lokalen Test-Validator.
Konfigurieren Sie zunächst die Solana CLI für die Verwendung von localhost:
$solana config set -ul
Beispielausgabe:
Config File: ~/.config/solana/cli/config.ymlRPC URL: http://localhost:8899WebSocket URL: ws://localhost:8900/ (computed)Keypair Path: ~/.config/solana/id.jsonCommitment: confirmed
Starten Sie nun den Test-Validator in einem separaten Terminal:
$solana-test-validator
Deployment des Programms
Während der Validator läuft, deployen Sie Ihr Programm auf den lokalen Cluster:
$solana program deploy ./target/deploy/counter_program.so
Beispielausgabe:
Program Id: HQ5Q2XXqbTKKQsWPtLzMn7rDhM8v9UPYPe7DfSoFQqJFSignature: 5xKdnh3dDFnZXB5UevYYkFBpCVcuqo5SaUPLnryFWY7eQD2CJxaeVDKjQ4ezQVJfkGNqZGYqMZBNqymPKwCQQx5h
Sie können das Deployment mit dem Befehl solana program show und Ihrer
Programm-ID überprüfen:
$solana program show HQ5Q2XXqbTKKQsWPtLzMn7rDhM8v9UPYPe7DfSoFQqJF
Beispielausgabe:
Program Id: HQ5Q2XXqbTKKQsWPtLzMn7rDhM8v9UPYPe7DfSoFQqJFOwner: BPFLoaderUpgradeab1e11111111111111111111111ProgramData Address: 47MVf5tRZ4zWXQMX7ydrkgcFQr8XTk1QBjohwsUzaiuMAuthority: 4kh6HxYZiAebF8HWLsUWod2EaQQ6iWHpHYCz8UcmFbM1Last Deployed In Slot: 16Data Length: 82696 (0x14308) bytesBalance: 0.57676824 SOL
Ausführen des Clients
Während der lokale Validator noch läuft, führen Sie den Client aus:
$cargo run --example client
Erwartete Ausgabe:
Requesting airdrop...Airdrop confirmedInitializing counter...Counter initialized!Transaction: 2uenChtqNeLC1fitqoVE2LBeygSBTDchMZ4gGqs7AiDvZZVJguLDE5PfxsfkgY7xs6zFWnYsbEtb82dWv9tDT14kCounter address: EppPAmwqD42u4SCPWpPT7wmWKdFad5VnM9J4R9ZfofcyIncrementing counter...Counter incremented!Transaction: 4qv1Rx6FHu1M3woVgDQ6KtYUaJgBzGcHnhej76ZpaKGCgsTorbcHnPKxoH916UENw7X5ppnQ8PkPnhXxEwrYuUxS
Während der lokale Validator läuft, können Sie die Transaktionen im
Solana Explorer anhand der
ausgegebenen Transaktionssignaturen einsehen. Beachten Sie, dass der Cluster im
Solana Explorer auf "Custom RPC URL" eingestellt sein muss, was standardmäßig
auf http://localhost:8899 verweist, auf dem der solana-test-validator läuft.
Is this page helpful?