Pick a depth. Each prompt opens in your AI pre-loaded with the lesson. Click a row to preview the prompt.
Every Ethereum developer walks into Solana and trips over the same thing: Solana programs do not have their own storage. A contract on Ethereum owns its state — a mapping, a struct, a uint — and that state lives inside the contract's address. On Solana, programs are stateless code; all state is held in separate accounts that the program reads and writes during a transaction. You, the caller, must tell the program which accounts it will touch, up front. This is why Solana is fast (the runtime parallelises non-overlapping transactions) and why it feels alien for the first week. Getting this mental model right is the difference between fighting the runtime and flowing with it.
Compare two equivalent counters — one in Solidity, one in Anchor. Solidity keeps count inside contract storage at a fixed slot. Anchor puts count inside a Counter struct held in a separate account owned by the program. The Anchor transaction must name the account; the Solidity transaction does not. That naming is the entire difference.
count physically lives. It is NOT inside the program; it is inside the account at counter.publicKey. Confirm by fetching the account bytes directly with connection.getAccountInfo(counter.publicKey) and inspecting the 8-byte discriminator + 8-byte u64.increment without passing the counter account. The transaction will fail with a missing-account error before any Rust code runs. Note: Solana returns errors at the runtime layer, not from your program's body.program.account.counter.fetch(...)). On Ethereum this is eth_call; on Solana it is simply an RPC read. No execution happens — state is the bytes of the account.// Anchor program — programs/counter/src/lib.rs
use anchor_lang::prelude::*;
declare_id!("Ctr111111111111111111111111111111111111111");
#[program]
pub mod counter {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
ctx.accounts.counter.count = 0;
Ok(())
}
pub fn increment(ctx: Context<Increment>) -> Result<()> {
ctx.accounts.counter.count += 1;
Ok(())
}
}
#[account]
pub struct Counter { pub count: u64 }
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = user, space = 8 + 8)]
pub counter: Account<'info, Counter>,
#[account(mut)] pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut)] pub counter: Account<'info, Counter>,
}
// Note: the program has no state. All state lives in the 'counter' account,
// which the caller passes in explicitly.
cargo run