Rust 프로그램 구조
Rust로 작성된 Solana 프로그램은 최소한의 구조적 요구사항만 있어 코드 구성 방식에
유연성을 제공합니다. 유일한 요구사항은 프로그램이 entrypoint
를 가져야 한다는
것인데, 이는 프로그램 실행이 시작되는 지점을 정의합니다.
프로그램 구조
파일 구조에 대한 엄격한 규칙은 없지만, Solana 프로그램은 일반적으로 다음과 같은 공통 패턴을 따릅니다:
entrypoint.rs
: 들어오는 명령어를 라우팅하는 엔트리포인트를 정의합니다.state.rs
: 프로그램 상태(계정 데이터)를 정의합니다.instructions.rs
: 프로그램이 실행할 수 있는 명령어를 정의합니다.processor.rs
: 각 명령어에 대한 비즈니스 로직을 구현하는 명령어 핸들러(함수)를 정의합니다.error.rs
: 프로그램이 반환할 수 있는 사용자 정의 오류를 정의합니다.
예를 들어, 토큰 프로그램을 참조하세요.
예제 프로그램
여러 명령어가 있는 네이티브 Rust 프로그램을 구축하는 방법을 보여주기 위해, 두 가지 명령어를 구현하는 간단한 카운터 프로그램을 살펴보겠습니다:
InitializeCounter
: 초기값으로 새 계정을 생성하고 초기화합니다.IncrementCounter
: 기존 계정에 저장된 값을 증가시킵니다.
간단하게 하기 위해, 프로그램은 단일 lib.rs
파일에 구현될 것입니다. 실제로는 더
큰 프로그램을 여러 파일로 분할하는 것이 좋을 수 있습니다.
파트 1: 프로그램 작성하기
카운터 프로그램을 만들어 봅시다. 시작 값으로 카운터를 초기화하고 증가시킬 수 있는 프로그램을 만들 것입니다.
새 프로그램 만들기
먼저, Solana 프로그램을 위한 새로운 Rust 프로젝트를 만들어 보겠습니다.
$cargo new counter_program --lib$cd counter_program
기본 src/lib.rs
및 Cargo.toml
파일이 보일 것입니다.
Cargo.toml
의 edition
필드를 2021로 업데이트하세요. 그렇지 않으면
프로그램을 빌드할 때 오류가 발생할 수 있습니다.
의존성 추가하기
이제 Solana 프로그램을 구축하는 데 필요한 의존성을 추가해 보겠습니다. 핵심 SDK를
위한 solana-program
와 직렬화를 위한 borsh
가 필요합니다.
$cargo add solana-program@2.2.0$cargo add borsh
Borsh를 사용해야 하는 필수 요건은 없습니다. 그러나 Solana 프로그램에서 일반적으로 사용되는 직렬화 라이브러리입니다.
crate-type 구성하기
Solana 프로그램은 동적 라이브러리로 컴파일되어야 합니다. Cargo가 프로그램을
빌드하는 방법을 구성하기 위해 [lib]
섹션을 추가하세요.
[lib]crate-type = ["cdylib", "lib"]
이 구성을 포함하지 않으면 프로그램을 빌드할 때 target/deploy 디렉토리가 생성되지 않습니다.
프로그램 진입점 설정하기
모든 Solana 프로그램에는 프로그램이 호출될 때 실행되는 함수인 진입점이 있습니다. 프로그램에 필요한 임포트를 추가하고 진입점을 설정하는 것부터 시작해 보겠습니다.
lib.rs
에 다음 코드를 추가하세요:
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
매크로는 input
데이터를 process_instruction
함수의 매개변수로 역직렬화하는
작업을 처리합니다.
Solana 프로그램 entrypoint
는 다음과 같은 함수 시그니처를 가집니다. 개발자는
자유롭게 entrypoint
함수의 구현을 만들 수 있습니다.
#[no_mangle]pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64;
프로그램 상태 정의하기
이제 카운터 계정에 저장될 데이터 구조를 정의해 보겠습니다. 이 데이터는 계정의
data
필드에 저장됩니다.
다음 코드를 lib.rs
에 추가하세요:
#[derive(BorshSerialize, BorshDeserialize, Debug)]pub struct CounterAccount {pub count: u64,}
명령어 열거형 정의하기
프로그램이 실행할 수 있는 명령어를 정의해 보겠습니다. 각 변형이 서로 다른 명령어를 나타내는 열거형을 사용할 것입니다.
다음 코드를 lib.rs
에 추가하세요:
#[derive(BorshSerialize, BorshDeserialize, Debug)]pub enum CounterInstruction {InitializeCounter { initial_value: u64 },IncrementCounter,}
명령어 역직렬화 구현하기
이제 instruction_data
(원시 바이트)를 우리의 CounterInstruction
열거형 변형
중 하나로 역직렬화해야 합니다. Borsh의 try_from_slice
메서드가 이 변환을
자동으로 처리합니다.
Borsh 역직렬화를 사용하도록 process_instruction
함수를 업데이트하세요:
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(())}
명령어를 핸들러로 라우팅하기
이제 명령어를 적절한 핸들러 함수로 라우팅하도록 메인 process_instruction
함수를 업데이트해 보겠습니다.
이러한 라우팅 패턴은 Solana 프로그램에서 일반적입니다. instruction_data
는
명령어를 나타내는 열거형의 변형으로 역직렬화된 다음, 적절한 핸들러 함수가
호출됩니다. 각 핸들러 함수는 해당 명령어에 대한 구현을 포함합니다.
process_instruction
함수를 업데이트하고 InitializeCounter
와
IncrementCounter
명령어에 대한 핸들러를 추가하는 다음 코드를 lib.rs
에
추가하세요:
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(())}
초기화 핸들러 구현하기
새 카운터 계정을 생성하고 초기화하는 핸들러를 구현해 보겠습니다. Solana에서는 System Program만이 계정을 생성할 수 있으므로, 교차 프로그램 호출(Cross Program Invocation, CPI)을 사용하여 우리 프로그램에서 다른 프로그램을 호출할 것입니다.
우리 프로그램은 CPI를 통해 System Program의 create_account
명령어를
호출합니다. 새 계정은 우리 프로그램을 소유자로 생성되어, 우리 프로그램이 계정에
쓰기 작업을 하고 데이터를 초기화할 수 있는 권한을 갖게 됩니다.
다음 코드를 lib.rs
에 추가하여 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(())}
이 명령어는 데모 목적으로만 제공됩니다. 프로덕션 프로그램에 필요한 보안 및 유효성 검사가 포함되어 있지 않습니다.
증가 핸들러 구현하기
이제 기존 카운터를 증가시키는 핸들러를 구현해 보겠습니다. 이 명령어는 다음과 같은 작업을 수행합니다:
counter_account
에 대한 계정data
필드를 읽습니다- 이를
CounterAccount
구조체로 역직렬화합니다 count
필드를 1 증가시킵니다CounterAccount
구조체를 다시 계정의data
필드로 직렬화합니다
다음 코드를 lib.rs
에 추가하여 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(())}
이 명령어는 데모 목적으로만 제공됩니다. 프로덕션 프로그램에 필요한 보안 및 유효성 검사가 포함되어 있지 않습니다.
완성된 프로그램
축하합니다! 모든 Solana 프로그램이 공유하는 기본 구조를 보여주는 완전한 Solana 프로그램을 구축했습니다:
- 진입점(Entrypoint): 프로그램 실행이 시작되는 위치를 정의하고 모든 수신 요청을 적절한 명령어 핸들러로 라우팅합니다
- 명령어 처리(Instruction Handling): 명령어와 관련 핸들러 함수를 정의합니다
- 상태 관리(State Management): 계정 데이터 구조를 정의하고 프로그램 소유 계정에서 상태를 관리합니다
- Cross Program Invocation (CPI): 새로운 프로그램 소유 계정을 생성하기 위해 System Program을 호출합니다
다음 단계는 프로그램을 테스트하여 모든 것이 올바르게 작동하는지 확인하는 것입니다.
파트 2: 프로그램 테스트하기
이제 카운터 프로그램을 테스트해 보겠습니다. 클러스터에 배포하지 않고도 프로그램을 테스트할 수 있는 LiteSVM 테스트 프레임워크를 사용할 것입니다.
테스트 의존성 추가하기
먼저 테스트에 필요한 의존성을 추가해 보겠습니다. 테스트를 위해 litesvm
와
solana-sdk
를 사용할 것입니다.
$cargo add litesvm@0.6.1 --dev$cargo add solana-sdk@2.2.0 --dev
테스트 모듈 생성
이제 프로그램에 테스트 모듈을 추가해 보겠습니다. 기본 구조와 임포트부터 시작하겠습니다.
다음 코드를 lib.rs
에 프로그램 코드 바로 아래에 추가하세요:
#[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)]
속성은 이 코드가 테스트를 실행할 때만 컴파일되도록 보장합니다.
테스트 환경 초기화
LiteSVM으로 테스트 환경을 설정하고 지불자 계정에 자금을 지원해 보겠습니다.
LiteSVM은 Solana 런타임 환경을 시뮬레이션하여 실제 클러스터에 배포하지 않고도 프로그램을 테스트할 수 있게 해줍니다.
다음 코드를 lib.rs
에 추가하여 test_counter_program
함수를 업데이트하세요:
let mut svm = LiteSVM::new();let payer = Keypair::new();svm.airdrop(&payer.pubkey(), 1_000_000_000).expect("Failed to airdrop");
프로그램 로드
이제 프로그램을 빌드하고 테스트 환경에 로드해야 합니다. cargo build-sbf
명령을
실행하여 프로그램을 빌드하세요. 이렇게 하면 target/deploy
디렉토리에
counter_program.so
파일이 생성됩니다.
$cargo build-sbf
Cargo.toml
에서 edition
가 2021
로 설정되어 있는지 확인하세요.
빌드 후에는 프로그램을 로드할 수 있습니다.
test_counter_program
함수를 업데이트하여 프로그램을 테스트 환경에 로드하세요.
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
를 실행하여 .so
파일을 생성해야
합니다. 테스트는 컴파일된 프로그램을 로드합니다.
초기화 명령어 테스트
시작 값으로 새 카운터 계정을 생성하여 초기화 명령어를 테스트해 보겠습니다.
다음 코드를 lib.rs
에 추가하여 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);
초기화 확인
초기화 후, 카운터 계정이 예상된 값으로 올바르게 생성되었는지 확인해 봅시다.
다음 코드를 lib.rs
에 추가하여 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);
증가 명령어 테스트
이제 증가 명령어를 테스트하여 카운터 값을 올바르게 업데이트하는지 확인해 봅시다.
다음 코드를 lib.rs
에 추가하여 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);
최종 결과 확인
마지막으로, 업데이트된 카운터 값을 확인하여 증가가 올바르게 작동했는지 확인해 봅시다.
다음 코드를 lib.rs
에 추가하여 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);
다음 명령어로 테스트를 실행하세요. --nocapture
플래그는 테스트 출력을
표시합니다.
$cargo test -- --nocapture
예상 출력:
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
파트 3: 프로그램 호출하기
이제 프로그램을 호출하기 위한 클라이언트 스크립트를 추가해 봅시다.
클라이언트 예제 생성하기
배포된 프로그램과 상호작용할 Rust 클라이언트를 만들어 보겠습니다.
$mkdir examples$touch examples/client.rs
다음 구성을 Cargo.toml
에 추가하세요:
[[example]]name = "client"path = "examples/client.rs"
클라이언트 의존성을 설치하세요:
$cargo add solana-client@2.2.0 --dev$cargo add tokio --dev
클라이언트 코드 구현하기
이제 배포된 프로그램을 호출할 클라이언트를 구현해 보겠습니다.
keypair 파일에서 프로그램 ID를 가져오기 위해 다음 명령을 실행하세요:
$solana address -k ./target/deploy/counter_program-keypair.json
클라이언트 코드를 examples/client.rs
에 추가하고 program_id
를 이전 명령의
출력으로 대체하세요:
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);}}}
파트 4: 프로그램 배포하기
이제 프로그램과 클라이언트가 준비되었으니 프로그램을 빌드하고, 배포하고, 호출해 보겠습니다.
프로그램 빌드하기
먼저 프로그램을 빌드해 보겠습니다.
$cargo build-sbf
이 명령어는 프로그램을 컴파일하고 target/deploy/
에 두 개의 중요한 파일을
생성합니다:
counter_program.so # The compiled programcounter_program-keypair.json # Keypair for the program ID
다음 명령어를 실행하여 프로그램의 ID를 확인할 수 있습니다:
$solana address -k ./target/deploy/counter_program-keypair.json
출력 예시:
HQ5Q2XXqbTKKQsWPtLzMn7rDhM8v9UPYPe7DfSoFQqJF
로컬 validator 시작하기
개발을 위해 로컬 테스트 validator를 사용할 것입니다.
먼저, Solana CLI를 localhost로 구성합니다:
$solana config set -ul
출력 예시:
Config File: ~/.config/solana/cli/config.ymlRPC URL: http://localhost:8899WebSocket URL: ws://localhost:8900/ (computed)Keypair Path: ~/.config/solana/id.jsonCommitment: confirmed
이제 별도의 터미널에서 테스트 validator를 시작합니다:
$solana-test-validator
프로그램 배포하기
validator가 실행 중인 상태에서 로컬 클러스터에 프로그램을 배포합니다:
$solana program deploy ./target/deploy/counter_program.so
출력 예시:
Program Id: HQ5Q2XXqbTKKQsWPtLzMn7rDhM8v9UPYPe7DfSoFQqJFSignature: 5xKdnh3dDFnZXB5UevYYkFBpCVcuqo5SaUPLnryFWY7eQD2CJxaeVDKjQ4ezQVJfkGNqZGYqMZBNqymPKwCQQx5h
프로그램 ID와 함께 solana program show
명령어를 사용하여 배포를 확인할 수
있습니다:
$solana program show HQ5Q2XXqbTKKQsWPtLzMn7rDhM8v9UPYPe7DfSoFQqJF
출력 예시:
Program Id: HQ5Q2XXqbTKKQsWPtLzMn7rDhM8v9UPYPe7DfSoFQqJFOwner: BPFLoaderUpgradeab1e11111111111111111111111ProgramData Address: 47MVf5tRZ4zWXQMX7ydrkgcFQr8XTk1QBjohwsUzaiuMAuthority: 4kh6HxYZiAebF8HWLsUWod2EaQQ6iWHpHYCz8UcmFbM1Last Deployed In Slot: 16Data Length: 82696 (0x14308) bytesBalance: 0.57676824 SOL
클라이언트 실행하기
로컬 validator가 계속 실행 중인 상태에서 클라이언트를 실행하세요:
$cargo run --example client
예상 출력:
Requesting airdrop...Airdrop confirmedInitializing counter...Counter initialized!Transaction: 2uenChtqNeLC1fitqoVE2LBeygSBTDchMZ4gGqs7AiDvZZVJguLDE5PfxsfkgY7xs6zFWnYsbEtb82dWv9tDT14kCounter address: EppPAmwqD42u4SCPWpPT7wmWKdFad5VnM9J4R9ZfofcyIncrementing counter...Counter incremented!Transaction: 4qv1Rx6FHu1M3woVgDQ6KtYUaJgBzGcHnhej76ZpaKGCgsTorbcHnPKxoH916UENw7X5ppnQ8PkPnhXxEwrYuUxS
로컬 validator가 실행 중일 때, 출력된 트랜잭션 서명을 사용하여
Solana Explorer에서 트랜잭션을
확인할 수 있습니다. Solana Explorer에서 클러스터를 "Custom RPC URL"로 설정해야
하며, 기본값은 http://localhost:8899
로 solana-test-validator
가 실행 중인
주소입니다.
Is this page helpful?