Crie Seu Primeiro Programa Solana

Este guia de início rápido usa o projeto inicial do framework Anchor gerado por anchor init. Você criará o projeto localmente, executará seus testes, compilará o programa e percorrerá o código que o Anchor gera.

Pré-requisitos

Antes de começar, instale as ferramentas de desenvolvimento Solana. A instalação inclui Rust, a CLI do Solana e a CLI do Anchor.

Use a versão 1.1.2 ou superior da CLI do Anchor para este template. Verifique sua versão instalada:

Terminal
$
anchor --version

Criar o projeto

Execute os seguintes comandos no seu terminal:

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

O projeto inicial inclui um programa Solana em programs/my-program. O programa inclui duas instruções: uma para inicializar uma conta contadora e outra para incrementar o contador.

Algumas partes do template demonstram padrões comuns de programas Solana: derivação de endereços de conta PDA, realização de um Cross Program Invocation (CPI) para transferir SOL, e uso de verificações de erro personalizadas para interromper uma instrução quando uma condição falha.

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

Compilar o Programa

Execute anchor build para compilar o programa inicial:

Terminal
$
anchor build

O programa compilado é gravado em target/deploy/my_program.so. Quando o programa é implantado, o conteúdo deste arquivo .so é armazenado em uma conta na blockchain.

Executar Teste

Execute o teste padrão:

Terminal
$
anchor test

O Anchor.toml deste template usa o comando de teste do Rust:

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

O teste carrega o programa compilado no LiteSVM, cria um pagador, envia as instruções initialize e increment, em seguida verifica o estado da conta do contador.

Executar anchor test também compila o programa, portanto não é necessário executar anchor build primeiro ao testar localmente.

Implantar Programa

Os testes locais são o ciclo de feedback mais rápido. Quando estiver pronto para implantar em uma rede, por exemplo devnet, primeiro faça o build e depois implante em um cluster.

Implantar um programa Solana requer SOL porque o programa é armazenado em um program account, e o program account deve pagar pelo espaço que utiliza. Na devnet, solicite SOL gratuito da devnet no Solana Faucet ou com a Solana CLI:

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

Arquivos de origem

O diretório src contém o programa Solana. A documentação de Estrutura de Programa do Anchor explica as macros principais usadas aqui, incluindo declare_id!, #[program], #[derive(Accounts)] e #[account]. Esta seção percorre os arquivos do template.

lib.rs

lib.rs é o ponto de entrada do programa. Ele conecta os arquivos de origem, define o endereço do programa e define as instruções do programa que os usuários podem chamar.

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");

O mesmo endereço de programa aparece na configuração e no código. Anchor.toml informa ao Anchor qual endereço implantar ou chamar para um cluster. declare_id! define o endereço do programa para verificações de segurança.

constants.rs

constants.rs mantém valores compartilhados em um único lugar. Neste template, COUNTER_SEED deriva o PDA do contador, HELLO_WORLD_LAMPORTS é transferido durante a inicialização, e MAX_COUNT é verificado antes de incrementar.

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 usa duas constantes:

  • COUNTER_SEED deriva o endereço PDA do contador.
  • HELLO_WORLD_LAMPORTS define o valor transferido do pagador para a conta do contador.
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 usa MAX_COUNT como limite superior do contador. Se a contagem atual já estiver no máximo, require! retorna CounterOverflow e os dados da conta não são alterados.

state.rs

state.rs define tipos de dados personalizados para as contas que o programa cria e gerencia. O programa define instruções para criar, inicializar e atualizar esses dados, mas os dados do contador não são armazenados dentro do próprio programa. Eles são armazenados em uma conta separada com seu próprio endereço.

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 define os dados da conta Counter. Os arquivos de instrução usam esse tipo quando criam e atualizam a conta:

  • Counter::INIT_SPACE dimensiona a conta para os campos definidos em state.rs.
  • count e authority são os valores dos campos gravados quando a conta é inicializada.
  • count += 1 atualiza o valor do contador armazenado após a validação ser concluída.

error.rs

error.rs define os erros personalizados do programa. Neste template, os erros demonstram como os handlers de instrução são interrompidos quando um chamador não tem permissão para atualizar o contador ou quando o contador já atingiu MAX_COUNT.

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 nomeia os erros que a instrução pode retornar:

  • ErrorCode::Unauthorized é retornado quando o assinante não é a autoridade armazenada na conta do contador.
  • ErrorCode::CounterOverflow é retornado quando o contador já atingiu MAX_COUNT.

instructions.rs

instructions.rs conecta os arquivos de instrução ao crate do programa para que lib.rs possa acessar o código de instrução initialize e increment. Cada arquivo de instrução define as contas exigidas por essa instrução e a lógica do handler que é executada após o Anchor validar essas contas.

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

initialize.rs

initialize.rs define as contas necessárias para criar a conta do contador e, em seguida, grava os primeiros valores da conta. A struct #[derive(Accounts)] utiliza as restrições de conta do Anchor para especificar quais contas são necessárias e como a nova conta do contador é criada.

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(())
}

Account Context

A struct Initialize define as contas que devem ser incluídas quando um usuário chama a instrução initialize. O Anchor verifica essas contas antes de o handler ser executado.

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>,
}

Payer Account

A conta payer paga pela criação da conta do contador. O tipo Signer<'info> significa que o pagador deve assinar a transação, e #[account(mut)] indica que a conta do pagador pode ser alterada, pois lamports serão deduzidos.

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>,
}

Counter Account

A conta counter armazena os dados Counter de state.rs. init instrui o Anchor a criar essa conta antes de o handler ser executado, e payer = payer informa ao Anchor qual conta paga pela criação.

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>,
}

Tamanho da Conta

A restrição space informa ao Anchor quanto espaço de dados da conta deve ser alocado. O Anchor armazena primeiro um discriminador de 8 bytes, seguido dos bytes necessários para os campos Counter. O discriminador permite que o Anchor reconheça esta conta como uma conta Counter antes de desserializar os dados da conta.

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>,
}

Endereço do Contador

As restrições seeds e bump definem o endereço PDA esperado para a conta do contador. O Anchor verifica se a conta counter fornecida corresponde a esse endereço. O template usa um PDA para que os utilizadores possam derivar o endereço do contador a partir do ID do programa e do seed, tornando o endereço do contador determinístico.

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

A conta system_program é necessária porque a criação de uma nova conta utiliza o System Program.

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>,
}

Função Handler

A função handle_initialize é executada depois que o Anchor valida as contas em Initialize. O valor ctx fornece ao handler acesso a essas contas verificadas.

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(())
}

Dados Iniciais

O handler grava os primeiros valores na nova conta do contador. A contagem começa em 0, e o pagador torna-se a autoridade autorizada a incrementar o contador posteriormente.

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(())
}

Contas de Transferência

Esta CPI de transferência está incluída apenas para demonstrar como uma CPI passa contas para outro programa. A estrutura Transfer lista as contas utilizadas pela transferência do System Program.

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(())
}

Contexto CPI

CpiContext::new combina o programa sendo chamado com as contas passadas para esse programa. Esta é a forma básica de um CPI: escolher o programa a invocar, coletar as contas que esse programa espera, depois passar ambos para a invocação. Aqui, o programa sendo chamado é o System Program.

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(())
}

Invocar Transferência

anchor_lang::system_program::transfer invoca a instrução de transferência do System Program. Neste modelo, a transferência é um pequeno exemplo de como chamar outro programa a partir do seu programa. Se o CPI de transferência falhar, a instrução initialize também falhará.

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(())
}

Registrar Mensagem

msg! escreve uma mensagem nos logs do programa. Ok(()) indica que a instrução foi concluída com sucesso.

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(())
}

Account Context

A struct Initialize define as contas que devem ser incluídas quando um usuário chama a instrução initialize. O Anchor verifica essas contas antes de o handler ser executado.

Payer Account

A conta payer paga pela criação da conta do contador. O tipo Signer<'info> significa que o pagador deve assinar a transação, e #[account(mut)] indica que a conta do pagador pode ser alterada, pois lamports serão deduzidos.

Counter Account

A conta counter armazena os dados Counter de state.rs. init instrui o Anchor a criar essa conta antes de o handler ser executado, e payer = payer informa ao Anchor qual conta paga pela criação.

Tamanho da Conta

A restrição space informa ao Anchor quanto espaço de dados da conta deve ser alocado. O Anchor armazena primeiro um discriminador de 8 bytes, seguido dos bytes necessários para os campos Counter. O discriminador permite que o Anchor reconheça esta conta como uma conta Counter antes de desserializar os dados da conta.

Endereço do Contador

As restrições seeds e bump definem o endereço PDA esperado para a conta do contador. O Anchor verifica se a conta counter fornecida corresponde a esse endereço. O template usa um PDA para que os utilizadores possam derivar o endereço do contador a partir do ID do programa e do seed, tornando o endereço do contador determinístico.

System Program

A conta system_program é necessária porque a criação de uma nova conta utiliza o System Program.

Função Handler

A função handle_initialize é executada depois que o Anchor valida as contas em Initialize. O valor ctx fornece ao handler acesso a essas contas verificadas.

Dados Iniciais

O handler grava os primeiros valores na nova conta do contador. A contagem começa em 0, e o pagador torna-se a autoridade autorizada a incrementar o contador posteriormente.

Contas de Transferência

Esta CPI de transferência está incluída apenas para demonstrar como uma CPI passa contas para outro programa. A estrutura Transfer lista as contas utilizadas pela transferência do System Program.

Contexto CPI

CpiContext::new combina o programa sendo chamado com as contas passadas para esse programa. Esta é a forma básica de um CPI: escolher o programa a invocar, coletar as contas que esse programa espera, depois passar ambos para a invocação. Aqui, o programa sendo chamado é o System Program.

Invocar Transferência

anchor_lang::system_program::transfer invoca a instrução de transferência do System Program. Neste modelo, a transferência é um pequeno exemplo de como chamar outro programa a partir do seu programa. Se o CPI de transferência falhar, a instrução initialize também falhará.

Registrar Mensagem

msg! escreve uma mensagem nos logs do programa. Ok(()) indica que a instrução foi concluída com sucesso.

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 define as contas necessárias para atualizar uma conta de contador existente. O handler verifica se o signatário é a autoridade armazenada, verifica se a contagem não atingiu o MAX_COUNT especificado e, em seguida, incrementa a contagem.

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(())
}

Contexto da Conta

A struct Increment define as contas que devem ser incluídas quando um usuário chama a instrução increment. O Anchor verifica essas contas antes de o handler ser executado.

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

Conta do Contador

A conta counter armazena os dados de Counter. A restrição mut permite que o handler atualize a contagem armazenada, e as restrições seeds e bump verificam o endereço PDA do contador.

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

Signatário com Autoridade

A conta authority deve assinar a transação. O handler verifica posteriormente se este signatário corresponde à autoridade armazenada na conta do contador.

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

Função Handler

A função handle_increment é executada após o Anchor validar as contas em Increment. O valor ctx dá ao handler acesso a essas contas verificadas.

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(())
}

Verificação de Autoridade

A primeira verificação garante que o signatário tem permissão para atualizar este contador. Se o endereço do signatário não corresponder a counter.authority, a instrução é interrompida com ErrorCode::Unauthorized. Isso demonstra autorização em nível de aplicação: o programa é proprietário dos dados do contador, mas implementa uma regra que define qual signatário pode alterar esses dados.

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(())
}

Verificação de Contagem Máxima

A segunda verificação impede que o contador ultrapasse MAX_COUNT. Se o contador já atingiu o limite, a instrução é interrompida com ErrorCode::CounterOverflow. Este limite é uma regra artificial no template para que você possa ver como erros personalizados interrompem uma instrução antes que os dados da conta sejam alterados.

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(())
}

Atualizar Contagem

Somente após ambas as verificações passarem é que o handler atualiza os dados da conta. Esta linha adiciona um ao valor do contador armazenado.

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(())
}

Mensagem de Log

msg! registra a contagem atualizada nos logs do programa. Ok(()) indica que a instrução foi executada com sucesso.

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(())
}

Contexto da Conta

A struct Increment define as contas que devem ser incluídas quando um usuário chama a instrução increment. O Anchor verifica essas contas antes de o handler ser executado.

Conta do Contador

A conta counter armazena os dados de Counter. A restrição mut permite que o handler atualize a contagem armazenada, e as restrições seeds e bump verificam o endereço PDA do contador.

Signatário com Autoridade

A conta authority deve assinar a transação. O handler verifica posteriormente se este signatário corresponde à autoridade armazenada na conta do contador.

Função Handler

A função handle_increment é executada após o Anchor validar as contas em Increment. O valor ctx dá ao handler acesso a essas contas verificadas.

Verificação de Autoridade

A primeira verificação garante que o signatário tem permissão para atualizar este contador. Se o endereço do signatário não corresponder a counter.authority, a instrução é interrompida com ErrorCode::Unauthorized. Isso demonstra autorização em nível de aplicação: o programa é proprietário dos dados do contador, mas implementa uma regra que define qual signatário pode alterar esses dados.

Verificação de Contagem Máxima

A segunda verificação impede que o contador ultrapasse MAX_COUNT. Se o contador já atingiu o limite, a instrução é interrompida com ErrorCode::CounterOverflow. Este limite é uma regra artificial no template para que você possa ver como erros personalizados interrompem uma instrução antes que os dados da conta sejam alterados.

Atualizar Contagem

Somente após ambas as verificações passarem é que o handler atualiza os dados da conta. Esta linha adiciona um ao valor do contador armazenado.

Mensagem de Log

msg! registra a contagem atualizada nos logs do programa. Ok(()) indica que a instrução foi executada com sucesso.

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

Arquivo de teste

programs/my-program/tests/test_initialize.rs é um teste de integração em Rust. Ele não inicia um validator local. Em vez disso, carrega o arquivo .so compilado no LiteSVM, constrói transações que chamam o programa e lê a conta do contador após cada transação. O teste constrói instruções para uma transação Solana especificando o ID do programa a ser invocado, fornecendo instruction data e passando as contas necessárias.

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),
);

O contexto de conta Initialize define as contas necessárias pela instrução initialize. O teste passa essas mesmas contas para o helper gerado my_program::accounts::Initialize:

  • payer é passado como payer: payer.pubkey().
  • counter é passado como counter.
  • system_program é passado como system_program::ID.

my_program::instruction::Initialize {}.data() cria o instruction data. É aqui que os argumentos da instrução seriam codificados, mas esta instrução initialize não requer nenhum argumento.

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),
);

O contexto de conta Increment define as contas necessárias pela instrução increment. O teste passa essas mesmas contas para o helper gerado my_program::accounts::Increment:

  • counter é passado como counter.
  • authority é passado como authority: payer.pubkey().

my_program::instruction::Increment {}.data() cria o instruction data. É aqui que os argumentos da instrução seriam codificados, mas esta instrução increment não requer nenhum argumento.

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());
}

Configuração do projeto

Os arquivos raiz do projeto informam ao Anchor e ao Cargo como compilar, testar e implantar o programa. Para uma referência completa, consulte a documentação do Anchor para configuração do Anchor.toml e o Anchor CLI.

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?

Índice

Editar Página
© 2026 Fundação Solana. Todos os direitos reservados.