İlk Solana Programınızı Oluşturun

Bu hızlı başlangıç kılavuzu, anchor init tarafından oluşturulan Anchor framework başlangıç projesini kullanır. Projeyi yerel olarak oluşturacak, testlerini çalıştıracak, programı derleyecek ve Anchor'ın oluşturduğu program kodunu inceleyeceksiniz.

Ön Koşullar

Başlamadan önce, Solana geliştirme araçlarını yükleyin. Kurulum; Rust, Solana CLI ve Anchor CLI'yi içerir.

Bu şablon için Anchor CLI sürüm 1.1.2 veya daha yüksek bir sürüm kullanın. Yüklü sürümünüzü kontrol edin:

Terminal
$
anchor --version

Projeyi Oluşturun

Terminalinizde aşağıdaki komutları çalıştırın:

Terminal
$
anchor init my-program
$
cd my-program

Başlangıç projesi, programs/my-program altında bir Solana programı içerir. Program, bir sayaç hesabını başlatmak ve sayacı artırmak için iki talimat içerir.

Şablonun bazı bölümleri yaygın Solana program kalıplarını gösterir: PDA hesap adreslerini türetme, SOL aktarmak için Cross Program Invocation (CPI) yapma ve bir koşul başarısız olduğunda talimatı durdurmak için özel hata denetimleri kullanma.

Anchor.toml
Cargo.toml
Cargo.toml
lib.rs
constants.rs
error.rs
instructions.rs
initialize.rs
increment.rs
state.rs
test_initialize.rs

Programı Derleyin

Başlangıç programını derlemek için anchor build komutunu çalıştırın:

Terminal
$
anchor build

Derlenen program, target/deploy/my_program.so konumuna yazılır. Program dağıtıldığında, bu .so dosyasının içeriği zincir üzerinde bir hesapta depolanır.

Testi Çalıştır

Varsayılan testi çalıştırın:

Terminal
$
anchor test

Bu şablonun Anchor.toml'i Rust test komutunu kullanır:

Anchor.toml
skip_local_validator = true
[scripts]
test = "cargo test"

Test, derlenmiş programı LiteSVM içine yükler, bir ödeyici oluşturur, initialize ve increment talimatlarını gönderir, ardından sayaç hesabının durumunu kontrol eder.

anchor test komutunun çalıştırılması programı da derler; bu nedenle yerel testlerde anchor build komutunu önceden çalıştırmanız gerekmez.

Programı Dağıt

Yerel testler en hızlı geri bildirim döngüsüdür. Bir ağa, örneğin devnet'e dağıtmaya hazır olduğunuzda önce derleyin, ardından bir kümeye dağıtın.

Bir Solana programının dağıtılması SOL gerektirir; çünkü program bir hesapta depolanır ve hesap kullandığı alan için ücret ödemek zorundadır. Devnet'te, Solana Faucet adresinden veya Solana CLI ile ücretsiz devnet SOL talep edebilirsiniz:

Terminal
$
solana airdrop 2 --url devnet
Terminal
$
anchor build
$
anchor deploy --provider.cluster devnet

Kaynak dosyalar

src dizini Solana programını içerir. Anchor'ın Program Yapısı dokümantasyonu, declare_id!, #[program], #[derive(Accounts)] ve #[account] dahil olmak üzere burada kullanılan temel makroları açıklar. Bu bölüm şablon dosyalarını adım adım ele alır.

lib.rs

lib.rs, programın giriş noktasıdır. Kaynak dosyaları birbirine bağlar, program adresini tanımlar ve kullanıcıların çağırabileceği program talimatlarını tanımlar.

programs/my-program/src/lib.rs
pub mod constants;
pub mod error;
pub mod instructions;
pub mod state;
use anchor_lang::prelude::*;
pub use constants::*;
pub use instructions::*;
pub use state::*;
declare_id!("82sFkffP9wxwpyfZyeaKHH2chQoJPUGsJZSPi9mrUuXd");
#[program]
pub mod my_program {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
crate::instructions::initialize::handle_initialize(ctx)
}
pub fn increment(ctx: Context<Increment>) -> Result<()> {
crate::instructions::increment::handle_increment(ctx)
}
}
Anchor.toml
[programs.localnet]
my_program = "82sFkffP9wxwpyfZyeaKHH2chQoJPUGsJZSPi9mrUuXd"
lib.rs
declare_id!("82sFkffP9wxwpyfZyeaKHH2chQoJPUGsJZSPi9mrUuXd");

Aynı program adresi hem yapılandırmada hem de kodda yer alır. Anchor.toml, Anchor'a bir küme için hangi adresi dağıtacağını veya çağıracağını bildirir. declare_id! ise güvenlik denetimleri için program adresini program içinde tanımlar.

constants.rs

constants.rs, paylaşılan değerleri tek bir yerde tutar. Bu şablonda COUNTER_SEED sayaç PDA'sını türetir, HELLO_WORLD_LAMPORTS başlatma sırasında aktarılır ve MAX_COUNT artırmadan önce kontrol edilir.

programs/my-program/src/constants.rs
use anchor_lang::prelude::*;
#[constant]
pub const COUNTER_SEED: &[u8] = b"counter";
#[constant]
pub const HELLO_WORLD_LAMPORTS: u64 = 1;
#[constant]
pub const MAX_COUNT: u64 = 10;
initialize.rs
#[derive(Accounts)]
pub struct Initialize<'info> {
// ...
#[account(
init,
payer = payer,
space = 8 + Counter::INIT_SPACE,
seeds = [COUNTER_SEED],
bump
)]
pub counter: Account<'info, Counter>,
// ...
}
pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {
// ...
anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;
Ok(())
}

initialize.rs iki sabit kullanır:

  • COUNTER_SEED sayaç PDA adresini türetir.
  • HELLO_WORLD_LAMPORTS ödeyiciden sayaç hesabına aktarılacak miktarı belirler.
increment.rs
pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {
// ...
require!(
ctx.accounts.counter.count < MAX_COUNT,
ErrorCode::CounterOverflow,
);
ctx.accounts.counter.count += 1;
Ok(())
}

increment.rs, sayacın üst sınırı olarak MAX_COUNT değerini kullanır. Mevcut sayım zaten maksimum değerdeyse, require! değeri CounterOverflow döndürür ve hesap verisi değiştirilmez.

state.rs

state.rs, programın oluşturduğu ve sahip olduğu hesaplar için özel veri türlerini tanımlar. Program bu veriyi oluşturmak, başlatmak ve güncellemek için talimatlar tanımlar; ancak sayaç verisi programın kendisinde saklanmaz. Kendi adresiyle ayrı bir hesapta saklanır.

programs/my-program/src/state.rs
use anchor_lang::prelude::*;
#[account]
#[derive(InitSpace)]
pub struct Counter {
pub count: u64,
pub authority: Pubkey,
}
initialize.rs
#[account(
init,
payer = payer,
space = 8 + Counter::INIT_SPACE,
seeds = [COUNTER_SEED],
bump
)]
pub counter: Account<'info, Counter>,
// ...
pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {
ctx.accounts.counter.count = 0;
ctx.accounts.counter.authority = ctx.accounts.payer.key();
Ok(())
}
increment.rs
pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {
// ...
ctx.accounts.counter.count += 1;
Ok(())
}

state.rs, Counter hesap verilerini tanımlar. Talimat dosyaları, hesabı oluştururken ve güncellerken bu türü kullanır:

  • Counter::INIT_SPACE, state.rs içinde tanımlanan alanlar için hesabı boyutlandırır.
  • count ve authority, hesap başlatıldığında yazılan alan değerleridir.
  • count += 1, doğrulama geçtikten sonra depolanan sayaç değerini günceller.

error.rs

error.rs, programın özel hatalarını tanımlar. Bu şablonda hatalar, bir çağıran sayacı güncelleme yetkisine sahip olmadığında veya sayaç zaten MAX_COUNT değerine ulaştığında talimat işleyicilerinin nasıl durduğunu göstermektedir.

programs/my-program/src/error.rs
use anchor_lang::prelude::*;
#[error_code]
pub enum ErrorCode {
#[msg("Only the counter authority can update this counter")]
Unauthorized,
#[msg("Counter has reached the maximum value")]
CounterOverflow,
}
increment.rs
pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {
require_keys_eq!(
ctx.accounts.counter.authority,
ctx.accounts.authority.key(),
ErrorCode::Unauthorized,
);
require!(
ctx.accounts.counter.count < MAX_COUNT,
ErrorCode::CounterOverflow,
);
ctx.accounts.counter.count += 1;
msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);
Ok(())
}

error.rs, talimatın döndürebileceği hataları listeler:

  • ErrorCode::Unauthorized, imzalayıcı sayaç hesabında depolanan yetkili olmadığında döndürülür.
  • ErrorCode::CounterOverflow, sayaç zaten MAX_COUNT değerine ulaştığında döndürülür.

instructions.rs

instructions.rs, talimat dosyalarını program crate'ine bağlar; böylece lib.rs, initialize ve increment talimat kodlarına erişebilir. Her talimat dosyası, o talimat için gereken hesapları ve Anchor bu hesapları doğruladıktan sonra çalışan işleyici mantığını tanımlar.

programs/my-program/src/instructions.rs
pub mod initialize;
pub mod increment;
pub use initialize::*;
pub use increment::*;

initialize.rs

initialize.rs, sayaç hesabını oluşturmak için gereken hesapları tanımlar ve hesabın ilk değerlerini yazar. #[derive(Accounts)] yapısı, hangi hesapların gerekli olduğunu ve yeni sayaç hesabının nasıl oluşturulduğunu belirtmek için Anchor hesap kısıtlamalarını kullanır.

programs/my-program/src/instructions/initialize.rs
use anchor_lang::prelude::*;
use crate::{constants::*, state::Counter};
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
init,
payer = payer,
space = 8 + Counter::INIT_SPACE,
seeds = [COUNTER_SEED],
bump
)]
pub counter: Account<'info, Counter>,
pub system_program: Program<'info, System>,
}
pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {
ctx.accounts.counter.count = 0;
ctx.accounts.counter.authority = ctx.accounts.payer.key();
let cpi_accounts = anchor_lang::system_program::Transfer {
from: ctx.accounts.payer.to_account_info(),
to: ctx.accounts.counter.to_account_info(),
};
let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);
anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;
msg!("Hello, world! Counter initialized");
Ok(())
}

Hesap Bağlamı

Initialize yapısı, bir kullanıcı initialize talimatını çağırdığında dahil edilmesi gereken hesapları tanımlar. Anchor, işleyici çalışmadan önce bu hesapları doğrular.

initialize.rs
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
init,
payer = payer,
space = 8 + Counter::INIT_SPACE,
seeds = [COUNTER_SEED],
bump
)]
pub counter: Account<'info, Counter>,
pub system_program: Program<'info, System>,
}

Ödeyici Hesabı

payer hesabı, sayaç hesabını oluşturma maliyetini karşılar. Signer<'info> türü, ödeyicinin işlemi imzalaması gerektiği anlamına gelir; #[account(mut)] ise lamport düşüleceğinden ödeyici hesabının değiştirilebileceği anlamına gelir.

initialize.rs
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
init,
payer = payer,
space = 8 + Counter::INIT_SPACE,
seeds = [COUNTER_SEED],
bump
)]
pub counter: Account<'info, Counter>,
pub system_program: Program<'info, System>,
}

Sayaç Hesabı

counter hesabı, state.rs kaynağından gelen Counter verilerini depolar. init, Anchor'a işleyici çalışmadan önce bu hesabı oluşturmasını söyler; payer = payer ise Anchor'a oluşturma ücretini hangi hesabın ödeyeceğini bildirir.

initialize.rs
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
init,
payer = payer,
space = 8 + Counter::INIT_SPACE,
seeds = [COUNTER_SEED],
bump
)]
pub counter: Account<'info, Counter>,
pub system_program: Program<'info, System>,
}

Hesap Boyutu

space kısıtlaması, Anchor'a ne kadar hesap verisi tahsis edeceğini söyler. Anchor önce 8 baytlık bir ayrımcı depolar, ardından Counter alanları için gereken baytları ekler. Ayrımcı, Anchor'ın hesap verilerini seri dışı bırakmadan önce bu hesabı bir Counter hesabı olarak tanımasını sağlar.

initialize.rs
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
init,
payer = payer,
space = 8 + Counter::INIT_SPACE,
seeds = [COUNTER_SEED],
bump
)]
pub counter: Account<'info, Counter>,
pub system_program: Program<'info, System>,
}

Sayaç Adresi

seeds ve bump kısıtlamaları, sayaç hesabı için beklenen PDA adresini tanımlar. Anchor, sağlanan counter hesabının bu adresle eşleştiğini doğrular. Şablon, kullanıcıların program kimliği ve seed'den sayaç adresini türetebilmesi için bir PDA kullanır; bu da sayaç adresini deterministik hale getirir.

initialize.rs
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
init,
payer = payer,
space = 8 + Counter::INIT_SPACE,
seeds = [COUNTER_SEED],
bump
)]
pub counter: Account<'info, Counter>,
pub system_program: Program<'info, System>,
}

System Program

system_program hesabı, yeni bir hesap oluşturmak System Program kullandığından zorunludur.

initialize.rs
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
init,
payer = payer,
space = 8 + Counter::INIT_SPACE,
seeds = [COUNTER_SEED],
bump
)]
pub counter: Account<'info, Counter>,
pub system_program: Program<'info, System>,
}

İşleyici Fonksiyon

handle_initialize fonksiyonu, Anchor Initialize içindeki hesapları doğruladıktan sonra çalışır. ctx değeri, işleyiciye bu doğrulanmış hesaplara erişim sağlar.

initialize.rs
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
init,
payer = payer,
space = 8 + Counter::INIT_SPACE,
seeds = [COUNTER_SEED],
bump
)]
pub counter: Account<'info, Counter>,
pub system_program: Program<'info, System>,
}
pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {
ctx.accounts.counter.count = 0;
ctx.accounts.counter.authority = ctx.accounts.payer.key();
let cpi_accounts = anchor_lang::system_program::Transfer {
from: ctx.accounts.payer.to_account_info(),
to: ctx.accounts.counter.to_account_info(),
};
let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);
anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;
msg!("Hello, world! Counter initialized");
Ok(())
}

Başlangıç Verileri

İşleyici, yeni sayaç hesabına ilk değerleri yazar. Sayım 0 ile başlar ve ödeyici, daha sonra sayacı artırma iznine sahip yetkili olur.

initialize.rs
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
init,
payer = payer,
space = 8 + Counter::INIT_SPACE,
seeds = [COUNTER_SEED],
bump
)]
pub counter: Account<'info, Counter>,
pub system_program: Program<'info, System>,
}
pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {
ctx.accounts.counter.count = 0;
ctx.accounts.counter.authority = ctx.accounts.payer.key();
let cpi_accounts = anchor_lang::system_program::Transfer {
from: ctx.accounts.payer.to_account_info(),
to: ctx.accounts.counter.to_account_info(),
};
let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);
anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;
msg!("Hello, world! Counter initialized");
Ok(())
}

Transfer Hesapları

Bu transfer CPI, yalnızca bir CPI'nin hesapları başka bir programa nasıl ilettiğini göstermek amacıyla eklenmiştir. Transfer yapısı, System Program transferi tarafından kullanılan hesapları listeler.

initialize.rs
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
init,
payer = payer,
space = 8 + Counter::INIT_SPACE,
seeds = [COUNTER_SEED],
bump
)]
pub counter: Account<'info, Counter>,
pub system_program: Program<'info, System>,
}
pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {
ctx.accounts.counter.count = 0;
ctx.accounts.counter.authority = ctx.accounts.payer.key();
let cpi_accounts = anchor_lang::system_program::Transfer {
from: ctx.accounts.payer.to_account_info(),
to: ctx.accounts.counter.to_account_info(),
};
let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);
anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;
msg!("Hello, world! Counter initialized");
Ok(())
}

CPI Bağlamı

CpiContext::new, çağrılan programı o programa iletilen hesaplarla birleştirir. Bu, bir CPI'nin temel yapısıdır: çağrılacak programı seçin, o programın beklediği hesapları toplayın, ardından ikisini de çağrıya iletin. Burada çağrılan program System Program'dır.

initialize.rs
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
init,
payer = payer,
space = 8 + Counter::INIT_SPACE,
seeds = [COUNTER_SEED],
bump
)]
pub counter: Account<'info, Counter>,
pub system_program: Program<'info, System>,
}
pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {
ctx.accounts.counter.count = 0;
ctx.accounts.counter.authority = ctx.accounts.payer.key();
let cpi_accounts = anchor_lang::system_program::Transfer {
from: ctx.accounts.payer.to_account_info(),
to: ctx.accounts.counter.to_account_info(),
};
let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);
anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;
msg!("Hello, world! Counter initialized");
Ok(())
}

Transfer'i Çağır

anchor_lang::system_program::transfer, System Program transfer talimatını çağırır. Bu şablonda transfer, programınızdan başka bir programı çağırmanın küçük bir örneğidir. Transfer CPI başarısız olursa, initialize talimatı da başarısız olur.

initialize.rs
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
init,
payer = payer,
space = 8 + Counter::INIT_SPACE,
seeds = [COUNTER_SEED],
bump
)]
pub counter: Account<'info, Counter>,
pub system_program: Program<'info, System>,
}
pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {
ctx.accounts.counter.count = 0;
ctx.accounts.counter.authority = ctx.accounts.payer.key();
let cpi_accounts = anchor_lang::system_program::Transfer {
from: ctx.accounts.payer.to_account_info(),
to: ctx.accounts.counter.to_account_info(),
};
let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);
anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;
msg!("Hello, world! Counter initialized");
Ok(())
}

Mesaj Kaydet

msg!, program günlüklerine bir mesaj yazar. Ok(()), talimatın başarıyla döndüğünü gösterir.

initialize.rs
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
init,
payer = payer,
space = 8 + Counter::INIT_SPACE,
seeds = [COUNTER_SEED],
bump
)]
pub counter: Account<'info, Counter>,
pub system_program: Program<'info, System>,
}
pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {
ctx.accounts.counter.count = 0;
ctx.accounts.counter.authority = ctx.accounts.payer.key();
let cpi_accounts = anchor_lang::system_program::Transfer {
from: ctx.accounts.payer.to_account_info(),
to: ctx.accounts.counter.to_account_info(),
};
let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);
anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;
msg!("Hello, world! Counter initialized");
Ok(())
}

Hesap Bağlamı

Initialize yapısı, bir kullanıcı initialize talimatını çağırdığında dahil edilmesi gereken hesapları tanımlar. Anchor, işleyici çalışmadan önce bu hesapları doğrular.

Ödeyici Hesabı

payer hesabı, sayaç hesabını oluşturma maliyetini karşılar. Signer<'info> türü, ödeyicinin işlemi imzalaması gerektiği anlamına gelir; #[account(mut)] ise lamport düşüleceğinden ödeyici hesabının değiştirilebileceği anlamına gelir.

Sayaç Hesabı

counter hesabı, state.rs kaynağından gelen Counter verilerini depolar. init, Anchor'a işleyici çalışmadan önce bu hesabı oluşturmasını söyler; payer = payer ise Anchor'a oluşturma ücretini hangi hesabın ödeyeceğini bildirir.

Hesap Boyutu

space kısıtlaması, Anchor'a ne kadar hesap verisi tahsis edeceğini söyler. Anchor önce 8 baytlık bir ayrımcı depolar, ardından Counter alanları için gereken baytları ekler. Ayrımcı, Anchor'ın hesap verilerini seri dışı bırakmadan önce bu hesabı bir Counter hesabı olarak tanımasını sağlar.

Sayaç Adresi

seeds ve bump kısıtlamaları, sayaç hesabı için beklenen PDA adresini tanımlar. Anchor, sağlanan counter hesabının bu adresle eşleştiğini doğrular. Şablon, kullanıcıların program kimliği ve seed'den sayaç adresini türetebilmesi için bir PDA kullanır; bu da sayaç adresini deterministik hale getirir.

System Program

system_program hesabı, yeni bir hesap oluşturmak System Program kullandığından zorunludur.

İşleyici Fonksiyon

handle_initialize fonksiyonu, Anchor Initialize içindeki hesapları doğruladıktan sonra çalışır. ctx değeri, işleyiciye bu doğrulanmış hesaplara erişim sağlar.

Başlangıç Verileri

İşleyici, yeni sayaç hesabına ilk değerleri yazar. Sayım 0 ile başlar ve ödeyici, daha sonra sayacı artırma iznine sahip yetkili olur.

Transfer Hesapları

Bu transfer CPI, yalnızca bir CPI'nin hesapları başka bir programa nasıl ilettiğini göstermek amacıyla eklenmiştir. Transfer yapısı, System Program transferi tarafından kullanılan hesapları listeler.

CPI Bağlamı

CpiContext::new, çağrılan programı o programa iletilen hesaplarla birleştirir. Bu, bir CPI'nin temel yapısıdır: çağrılacak programı seçin, o programın beklediği hesapları toplayın, ardından ikisini de çağrıya iletin. Burada çağrılan program System Program'dır.

Transfer'i Çağır

anchor_lang::system_program::transfer, System Program transfer talimatını çağırır. Bu şablonda transfer, programınızdan başka bir programı çağırmanın küçük bir örneğidir. Transfer CPI başarısız olursa, initialize talimatı da başarısız olur.

Mesaj Kaydet

msg!, program günlüklerine bir mesaj yazar. Ok(()), talimatın başarıyla döndüğünü gösterir.

initialize.rs
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
init,
payer = payer,
space = 8 + Counter::INIT_SPACE,
seeds = [COUNTER_SEED],
bump
)]
pub counter: Account<'info, Counter>,
pub system_program: Program<'info, System>,
}

increment.rs

increment.rs, mevcut bir sayaç hesabını güncellemek için gereken hesapları tanımlar. İşleyici, imzalayanın kayıtlı yetkili olup olmadığını kontrol eder, sayının belirtilen MAX_COUNT değerine ulaşıp ulaşmadığını kontrol eder ve ardından sayımı artırır.

programs/my-program/src/instructions/increment.rs
use anchor_lang::prelude::*;
use crate::{constants::*, error::ErrorCode, state::Counter};
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(
mut,
seeds = [COUNTER_SEED],
bump
)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}
pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {
require_keys_eq!(
ctx.accounts.counter.authority,
ctx.accounts.authority.key(),
ErrorCode::Unauthorized,
);
require!(
ctx.accounts.counter.count < MAX_COUNT,
ErrorCode::CounterOverflow,
);
ctx.accounts.counter.count += 1;
msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);
Ok(())
}

Hesap Bağlamı

Increment yapısı, bir kullanıcının increment talimatını çağırırken dahil edilmesi gereken hesapları tanımlar. Anchor, işleyici çalışmadan önce bu hesapları doğrular.

increment.rs
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(
mut,
seeds = [COUNTER_SEED],
bump
)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}

Sayaç Hesabı

counter hesabı, Counter verisini depolar. mut kısıtlaması, işleyicinin depolanan sayımı güncellemesine olanak tanır; seeds ve bump kısıtlamaları ise sayaç PDA adresini doğrular.

increment.rs
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(
mut,
seeds = [COUNTER_SEED],
bump
)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}

Yetkili İmzalayan

authority hesabının işlemi imzalaması gerekir. İşleyici daha sonra bu imzalayanın sayaç hesabında kayıtlı yetkiliyle eşleştiğini doğrular.

increment.rs
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(
mut,
seeds = [COUNTER_SEED],
bump
)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}

İşleyici Fonksiyon

handle_increment fonksiyonu, Anchor Increment içindeki hesapları doğruladıktan sonra çalışır. ctx değeri, işleyiciye doğrulanmış hesaplara erişim sağlar.

increment.rs
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(
mut,
seeds = [COUNTER_SEED],
bump
)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}
pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {
require_keys_eq!(
ctx.accounts.counter.authority,
ctx.accounts.authority.key(),
ErrorCode::Unauthorized,
);
require!(
ctx.accounts.counter.count < MAX_COUNT,
ErrorCode::CounterOverflow,
);
ctx.accounts.counter.count += 1;
msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);
Ok(())
}

Yetki Kontrolü

İlk kontrol, imzalayanın bu sayacı güncellemesine izin verilip verilmediğini doğrular. İmzalayanın adresi counter.authority ile eşleşmiyorsa, talimat ErrorCode::Unauthorized ile durur. Bu, uygulama düzeyinde yetkilendirmeyi gösterir: program sayaç verilerine sahipken, hangi imzalayanın bu verileri değiştirebileceğine dair bir kural uygular.

increment.rs
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(
mut,
seeds = [COUNTER_SEED],
bump
)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}
pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {
require_keys_eq!(
ctx.accounts.counter.authority,
ctx.accounts.authority.key(),
ErrorCode::Unauthorized,
);
require!(
ctx.accounts.counter.count < MAX_COUNT,
ErrorCode::CounterOverflow,
);
ctx.accounts.counter.count += 1;
msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);
Ok(())
}

Maksimum Sayı Kontrolü

İkinci kontrol, sayacın MAX_COUNT değerini aşmasını engeller. Sayaç zaten limite ulaşmışsa, talimat ErrorCode::CounterOverflow ile durur. Bu limit, şablondaki yapay bir kuraldır; böylece özel hataların hesap verisi değiştirilmeden önce bir talimatı nasıl durdurabileceğini görebilirsiniz.

increment.rs
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(
mut,
seeds = [COUNTER_SEED],
bump
)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}
pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {
require_keys_eq!(
ctx.accounts.counter.authority,
ctx.accounts.authority.key(),
ErrorCode::Unauthorized,
);
require!(
ctx.accounts.counter.count < MAX_COUNT,
ErrorCode::CounterOverflow,
);
ctx.accounts.counter.count += 1;
msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);
Ok(())
}

Sayacı Güncelle

İşleyici, yalnızca her iki kontrol de geçildikten sonra hesap verilerini günceller. Bu satır, depolanan sayaç değerine bir ekler.

increment.rs
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(
mut,
seeds = [COUNTER_SEED],
bump
)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}
pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {
require_keys_eq!(
ctx.accounts.counter.authority,
ctx.accounts.authority.key(),
ErrorCode::Unauthorized,
);
require!(
ctx.accounts.counter.count < MAX_COUNT,
ErrorCode::CounterOverflow,
);
ctx.accounts.counter.count += 1;
msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);
Ok(())
}

Günlük Mesajı

msg! güncellenmiş sayıyı program günlüklerine yazar. Ok(()), talimatın başarıyla döndüğünü belirtir.

increment.rs
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(
mut,
seeds = [COUNTER_SEED],
bump
)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}
pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {
require_keys_eq!(
ctx.accounts.counter.authority,
ctx.accounts.authority.key(),
ErrorCode::Unauthorized,
);
require!(
ctx.accounts.counter.count < MAX_COUNT,
ErrorCode::CounterOverflow,
);
ctx.accounts.counter.count += 1;
msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);
Ok(())
}

Hesap Bağlamı

Increment yapısı, bir kullanıcının increment talimatını çağırırken dahil edilmesi gereken hesapları tanımlar. Anchor, işleyici çalışmadan önce bu hesapları doğrular.

Sayaç Hesabı

counter hesabı, Counter verisini depolar. mut kısıtlaması, işleyicinin depolanan sayımı güncellemesine olanak tanır; seeds ve bump kısıtlamaları ise sayaç PDA adresini doğrular.

Yetkili İmzalayan

authority hesabının işlemi imzalaması gerekir. İşleyici daha sonra bu imzalayanın sayaç hesabında kayıtlı yetkiliyle eşleştiğini doğrular.

İşleyici Fonksiyon

handle_increment fonksiyonu, Anchor Increment içindeki hesapları doğruladıktan sonra çalışır. ctx değeri, işleyiciye doğrulanmış hesaplara erişim sağlar.

Yetki Kontrolü

İlk kontrol, imzalayanın bu sayacı güncellemesine izin verilip verilmediğini doğrular. İmzalayanın adresi counter.authority ile eşleşmiyorsa, talimat ErrorCode::Unauthorized ile durur. Bu, uygulama düzeyinde yetkilendirmeyi gösterir: program sayaç verilerine sahipken, hangi imzalayanın bu verileri değiştirebileceğine dair bir kural uygular.

Maksimum Sayı Kontrolü

İkinci kontrol, sayacın MAX_COUNT değerini aşmasını engeller. Sayaç zaten limite ulaşmışsa, talimat ErrorCode::CounterOverflow ile durur. Bu limit, şablondaki yapay bir kuraldır; böylece özel hataların hesap verisi değiştirilmeden önce bir talimatı nasıl durdurabileceğini görebilirsiniz.

Sayacı Güncelle

İşleyici, yalnızca her iki kontrol de geçildikten sonra hesap verilerini günceller. Bu satır, depolanan sayaç değerine bir ekler.

Günlük Mesajı

msg! güncellenmiş sayıyı program günlüklerine yazar. Ok(()), talimatın başarıyla döndüğünü belirtir.

increment.rs
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(
mut,
seeds = [COUNTER_SEED],
bump
)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}

Test dosyası

programs/my-program/tests/test_initialize.rs, bir Rust entegrasyon testidir. Yerel bir validator başlatmaz. Bunun yerine, derlenmiş .so dosyasını LiteSVM'e yükler, programı çağıran işlemler oluşturur ve her işlemden sonra sayaç hesabını okur. Test, çağrılacak programın program kimliğini belirterek, instruction data sağlayarak ve gerekli hesapları ileterek bir Solana işlemi için talimatlar oluşturur.

initialize.rs
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
init,
payer = payer,
space = 8 + Counter::INIT_SPACE,
seeds = [COUNTER_SEED],
bump
)]
pub counter: Account<'info, Counter>,
pub system_program: Program<'info, System>,
}
test_initialize.rs
let instruction = Instruction::new_with_bytes(
program_id,
&my_program::instruction::Initialize {}.data(),
my_program::accounts::Initialize {
payer: payer.pubkey(),
counter,
system_program: system_program::ID,
}
.to_account_metas(None),
);

Initialize hesap bağlamı, initialize talimatı için gereken hesapları tanımlar. Test, bu hesapları oluşturulan my_program::accounts::Initialize yardımcısına iletir:

  • payer, payer: payer.pubkey() olarak iletilir.
  • counter, counter olarak iletilir.
  • system_program, system_program::ID olarak iletilir.

my_program::instruction::Initialize {}.data(), instruction data oluşturur. Talimat argümanlarının kodlanacağı yer burasıdır; ancak bu initialize talimatı herhangi bir argüman gerektirmez.

increment.rs
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(
mut,
seeds = [COUNTER_SEED],
bump
)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}
test_initialize.rs
let instruction = Instruction::new_with_bytes(
program_id,
&my_program::instruction::Increment {}.data(),
my_program::accounts::Increment {
counter,
authority: payer.pubkey(),
}
.to_account_metas(None),
);

Increment hesap bağlamı, increment talimatı için gereken hesapları tanımlar. Test, bu hesapları oluşturulan my_program::accounts::Increment yardımcısına iletir:

  • counter, counter olarak iletilir.
  • authority, authority: payer.pubkey() olarak iletilir.

my_program::instruction::Increment {}.data(), instruction data oluşturur. Talimat argümanlarının kodlanacağı yer burasıdır; ancak bu increment talimatı herhangi bir argüman gerektirmez.

programs/my-program/tests/test_initialize.rs
use {
anchor_lang::{
prelude::Pubkey,
solana_program::{instruction::Instruction, system_program},
AccountDeserialize, InstructionData, ToAccountMetas,
},
litesvm::LiteSVM,
solana_keypair::Keypair,
solana_message::{Message, VersionedMessage},
solana_signer::Signer,
solana_transaction::versioned::VersionedTransaction,
};
#[test]
fn test_initialize() {
let program_id = my_program::id();
let payer = Keypair::new();
let counter = Pubkey::find_program_address(
&[my_program::constants::COUNTER_SEED],
&program_id,
)
.0;
let mut svm = LiteSVM::new();
let bytes = include_bytes!(concat!(
env!("CARGO_TARGET_TMPDIR"),
"/../deploy/my_program.so"
));
svm.add_program(program_id, bytes).unwrap();
svm.airdrop(&payer.pubkey(), 1_000_000_000).unwrap();
let instruction = Instruction::new_with_bytes(
program_id,
&my_program::instruction::Initialize {}.data(),
my_program::accounts::Initialize {
payer: payer.pubkey(),
counter,
system_program: system_program::ID,
}
.to_account_metas(None),
);
let blockhash = svm.latest_blockhash();
let msg = Message::new_with_blockhash(&[instruction], Some(&payer.pubkey()), &blockhash);
let tx = VersionedTransaction::try_new(VersionedMessage::Legacy(msg), &[&payer]).unwrap();
let res = svm.send_transaction(tx);
assert!(res.is_ok());
let counter_account = svm.get_account(&counter).unwrap();
let mut data: &[u8] = &counter_account.data;
let counter_state = my_program::state::Counter::try_deserialize(&mut data).unwrap();
assert_eq!(counter_state.count, 0);
assert_eq!(counter_state.authority, payer.pubkey());
let instruction = Instruction::new_with_bytes(
program_id,
&my_program::instruction::Increment {}.data(),
my_program::accounts::Increment {
counter,
authority: payer.pubkey(),
}
.to_account_metas(None),
);
let blockhash = svm.latest_blockhash();
let msg = Message::new_with_blockhash(&[instruction], Some(&payer.pubkey()), &blockhash);
let tx = VersionedTransaction::try_new(VersionedMessage::Legacy(msg), &[&payer]).unwrap();
let res = svm.send_transaction(tx);
assert!(res.is_ok());
let counter_account = svm.get_account(&counter).unwrap();
let mut data: &[u8] = &counter_account.data;
let counter_state = my_program::state::Counter::try_deserialize(&mut data).unwrap();
assert_eq!(counter_state.count, 1);
assert_eq!(counter_state.authority, payer.pubkey());
}

Proje yapılandırması

Kök proje dosyaları, Anchor ve Cargo'ya programı nasıl derleyeceğini, test edeceğini ve dağıtacağını söyler. Tam referans için Anchor.toml yapılandırması ve Anchor CLI için Anchor belgelerine bakın.

Anchor.toml
skip_local_validator = true
[toolchain]
[features]
resolution = true
skip-lint = false
[programs.localnet]
my_program = "82sFkffP9wxwpyfZyeaKHH2chQoJPUGsJZSPi9mrUuXd"
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"
[scripts]
test = "cargo test"
[hooks]

Is this page helpful?

İçindekiler

Sayfayı Düzenle
© 2026 Solana Vakfı. Tüm hakları saklıdır.