Native Program Basics
10 examples to get you started with Native Programs - 7 basic and 3 intermediate.
Prerequisites
- Solana CLI 3.0.10 with
cargo build-sbf. - Rust 1.91.1 and a program crate with
solana-programdependency. - Familiarity with Rust for Solana Basics.
Basic Examples
1. Minimal Cargo.toml
Native programs target SBF with cdylib crate type.
[lib]
crate-type = ["cdylib"]
[dependencies]
solana-program = "2.2"
borsh = "1.5"cdylibproduces the deployable.so.- Pin
solana-programto match Agave 4.1.1 SDK. - Keep dependency count low.
2. lib.rs skeleton
Crate root wires modules and entrypoint.
#![no_std]
extern crate alloc;
mod error;
mod instruction;
mod processor;
use solana_program::entrypoint;
entrypoint!(processor::process_instruction);- Split dispatch, state, and errors into modules.
- Only one
entrypoint!per crate. - Export instruction enum in
instruction.rs.
3. Instruction enum
Tag instructions with a Borsh enum.
use borsh::{BorshDeserialize, BorshSerialize};
#[derive(BorshSerialize, BorshDeserialize)]
pub enum HelloInstruction {
Greet { name: String },
}- First byte is variant index by default.
- Keep variants stable across upgrades.
- Match client serialization exactly.
Related: Instruction Data (de)Serialization - parsing
4. Dispatch match
Route parsed instructions to handlers.
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
data: &[u8],
) -> ProgramResult {
let ix = HelloInstruction::deserialize(&mut &data[..])?;
match ix {
HelloInstruction::Greet { name } => greet(accounts, name),
}
}- Deserialize once at the top level.
- Pass
program_idinto handlers that derive PDAs. - Use
?for error propagation.
Related: The Entrypoint & Dispatch - patterns
5. Account iterator
Consume accounts in a fixed order.
let iter = &mut accounts.iter();
let payer = next_account_info(iter)?;
let state = next_account_info(iter)?;- Order must match client
AccountMetalist. - Return
NotEnoughAccountKeysif iterator empty. - Document order in program README/IDL.
6. Custom error module
Centralize domain errors.
#[repr(u32)]
pub enum MyError { BadName = 0 }
impl From<MyError> for ProgramError {
fn from(e: MyError) -> Self { ProgramError::Custom(e as u32) }
}- Map to
ProgramError::Customfor clients. - Avoid overlapping Anchor ranges if mixed tooling.
- Export codes for @solana/kit 7.0.0 clients.
7. State struct with LEN
Define account size constant.
#[derive(BorshSerialize, BorshDeserialize)]
pub struct State { pub counter: u64 }
impl State {
pub const LEN: usize = 8;
}- Use constant when calling
create_account. - Assert length on every access.
- Anchor
InitSpaceis the macro equivalent.
Intermediate Examples
8. Create account via CPI
System Program allocates space and assigns owner.
invoke(
&system_instruction::create_account(
payer.key, state.key, rent, State::LEN as u64, program_id,
),
&[payer.clone(), state.clone(), system_program.clone()],
)?;- Payer must sign and be writable.
- Rent exemption calculated with
Rentsysvar. - After CPI, serialize initial state.
Related: Creating Accounts via CPI - full flow
9. Read and update state
Borrow data, mutate, serialize back.
let mut state = State::deserialize(&mut &account.data.borrow()[..])?;
state.counter = state.counter.saturating_add(1);
state.serialize(&mut &mut account.data.borrow_mut()[..])?;- Drop borrows before CPI touching same account.
- Use checked/saturating math.
- Owner must be your program.
Related: Reading & Writing Account Data - patterns
10. Emit a log line
Debug with msg! during development.
solana_program::msg!("greet: {}", name);- Visible in explorers and simulation.
- Costs CUs - strip in hot paths if needed.
- Prefer errors for user-facing failures.
Related: Emitting Logs - logging guide
Stack versions: This page was written for Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, anchor-lang 0.32.1, Rust 1.91.1, @solana/kit 7.0.0, Surfpool 0.12.0, and LiteSVM 0.6.x.