Open this lesson in your favourite AI. It'll walk you through the why, explain the demo, and quiz you on the try-it list.
Solana programs hold ONLY code — they have no storage of their own. All data lives in accounts that the caller passes in. This forces an explicit, declarative dataflow: you must enumerate every account a transaction touches up front. That declaration is what enables Solana's parallel execution model (you'll see this in M2).
A minimal Anchor program that holds a counter.
Use these three in order. Each builds on the one before.
In one paragraph, explain why Solana programs are stateless and where state actually lives.
Walk me through what happens when a transaction calls a program — which accounts are mutable, which are read-only.
Given a multi-user game where each player has state, explain why each player gets their own account on Solana vs the EVM's `mapping(address => Player)` pattern — and the parallelism implication.
use anchor_lang::prelude::*;
declare_id!("Counter11111111111111111111111111111111111");
#[program]
pub mod counter {
use super::*;
pub fn increment(ctx: Context<Increment>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.value = counter.value.checked_add(1).unwrap();
Ok(())
}
}
// The program's code lives in the program account.
// The COUNTER VALUE lives in a separate account (PDA or user-owned).
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut)]
pub counter: Account<'info, Counter>,
}
#[account]
pub struct Counter {
pub value: u64,
}
// To call increment, the user submits a tx with:
// - program_id: <Counter program pubkey>
// - accounts: [counter_account_pubkey]
// - data: <serialized "increment" instruction>
// The program code reads/writes counter_account's data field.
// The program itself stores nothing across invocations.