Sol.College

Full lesson

Anvil

Anvil is a compiler that converts Anchor smart contracts into optimized Solana runtimes such as Pinocchio, Quasar, and native programs.

What it does

From the project README:

Transpile an Anchor program to a cargo-buildable Pinocchio project — then prove the port is deploy-safe with a byte-equal gate that runs both inside a real VM.
Original
Pratikkale26/Anvil
SuperStack fork
Blockchains/Anvil @ c792c62
Stars
5
Last push (original)
2026-10-02
Languages
TypeScript, Rust, Dockerfile, Shell, CSS, JavaScript
Detected stack
MetaplexNext.jsweb3.js
Categories
Infrastructure & Tooling
Licence
Apache-2.0
Homepage
https://www.anvilsol.xyz/

Architecture

764 source files. By extension: .ts ×614, .rs ×115, .tsx ×35.

Top-level directorySource files
api676
web46
cli18
scripts14
demo10

Dapp: devnet deploy pending

⏳ Devnet deploy pending — goes live when the devnet wallet is topped up. This fork's own dapp is not deployed yet, so there is no program address or transaction to show. It is queued in dapp-queue.txt; once devnet SOL is available it is deployed, exercised with real devnet transactions, and this section switches to the live dapp at /dapps/anvil/. Until then, the lesson and step-by-step guide below are complete, and you can run it locally with the commands below.

Key code, explained

Excerpts chosen automatically where the code uses the most Solana concepts, pinned to the fork's commit.

Pratikkale26/Anvil · api/src/demo-programs/close-account.rs L37–60 @ c792c62 · Apache-2.0 · original
    #[account(        init,        payer = owner,        space = 8 + 32 + 8,        seeds = [b"note", owner.key().as_ref()],        bump    )]    pub note: Account<'info, Note>,    #[account(mut)]    pub owner: Signer<'info>,    pub system_program: Program<'info, System>,} #[derive(Accounts)]pub struct Shut<'info> {    #[account(        mut,        close = receiver,        has_one = owner,        seeds = [b"note", owner.key().as_ref()],        bump    )]    pub note: Account<'info, Note>,    /// CHECK: receiver of the rent refund — any pubkey is fine.
  1. Line 38 init,: Creates and pays for a new account. Space and payer must be specified.
  2. Line 41 seeds = [b"note", owner.key().as_ref()],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  3. Line 42 bump: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
  4. Line 46 pub owner: Signer<'info>,: Requires this account to have signed the transaction.
  5. Line 54 close = receiver,: Closes the account at the end of the instruction and refunds its lamports.
  6. Line 55 has_one = owner,: Checks that a stored pubkey field matches the passed account.
Pratikkale26/Anvil · api/src/demo-programs/event-emit.rs L25–48 @ c792c62 · Apache-2.0 · original
        let counter = &mut ctx.accounts.counter;        counter.value = counter.value.checked_add(amount).ok_or(EventError::Overflow)?;        emit!(Incremented {            new_value: counter.value,            delta: amount,        });        Ok(())    }} #[derive(Accounts)]pub struct Initialize<'info> {    #[account(        init,        payer = authority,        space = 8 + 8 + 1,        seeds = [b"evt-counter", authority.key().as_ref()],        bump    )]    pub counter: Account<'info, EventCounter>,    #[account(mut)]    pub authority: Signer<'info>,    pub system_program: Program<'info, System>,}
  1. Line 26 counter.value = counter.value.checked_add(amount).ok_or(EventError::Overflow)?;: Uses checked arithmetic so overflow fails instead of wrapping.
  2. Line 27 emit!(Incremented {: Emits an Anchor event that clients can parse from the logs.
  3. Line 38 init,: Creates and pays for a new account. Space and payer must be specified.
  4. Line 41 seeds = [b"evt-counter", authority.key().as_ref()],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  5. Line 42 bump: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
  6. Line 46 pub authority: Signer<'info>,: Requires this account to have signed the transaction.
Pratikkale26/Anvil · api/src/demo-programs/staking.rs L109–132 @ c792c62 · Apache-2.0 · original
            .ok_or(StakingError::Underflow)? as u64;         let rewards: u64 = (user_stake.amount as u128)            .checked_mul(elapsed as u128)            .ok_or(StakingError::Overflow)?            .checked_mul(user_stake.reward_rate_snapshot as u128)            .ok_or(StakingError::Overflow)?            .checked_div(1_000_000)            .ok_or(StakingError::Overflow)?            .try_into()            .map_err(|_| error!(StakingError::Overflow))?;         require!(rewards > 0, StakingError::NoRewards);         let pool_seeds = &[            b"pool",            pool.stake_mint.as_ref(),            &[pool.bump],        ];        let signer_seeds = &[&pool_seeds[..]];         token::mint_to(            CpiContext::new_with_signer(                ctx.accounts.token_program.to_account_info(),
  1. Line 112 .checked_mul(elapsed as u128): Uses checked arithmetic so overflow fails instead of wrapping.
  2. Line 121 require!(rewards > 0, StakingError::NoRewards);: An explicit validation that aborts the instruction with an error.
  3. Line 123 let pool_seeds = &[: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  4. Line 126 &[pool.bump],: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
  5. Line 130 token::mint_to(: Mints new tokens. The mint authority must sign.
  6. Line 131 CpiContext::new_with_signer(: Makes a CPI signed by a PDA. The signer seeds prove the program controls that address.
Pratikkale26/Anvil · api/src/demo-programs/simple-staking.rs L49–72 @ c792c62 · Apache-2.0 · original
        let earned = stake.amount            .saturating_mul(elapsed)            .saturating_mul(stake.reward_rate) / 1_000_000;        let total = stake.accumulated_rewards.saturating_add(earned);        stake.accumulated_rewards = 0;        stake.last_claim = now;        emit!(RewardsClaimed {            owner: stake.owner,            amount: total,            timestamp: now,        });        msg!("rewards claimed");        Ok(())    }} #[derive(Accounts)]pub struct InitializeStake<'info> {    #[account(        init,        payer = user,        space = 8 + UserStake::LEN,        seeds = [b"stake", user.key().as_ref()],        bump
  1. Line 49 let earned = stake.amount: Touches staking state: the amount locked and when it was locked.
  2. Line 55 emit!(RewardsClaimed {: Emits an Anchor event that clients can parse from the logs.
  3. Line 60 msg!("rewards claimed");: Logs to the program log, which is visible in explorers and the test output.
  4. Line 68 init,: Creates and pays for a new account. Space and payer must be specified.
  5. Line 71 seeds = [b"stake", user.key().as_ref()],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  6. Line 72 bump: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.

How to run, build and test

These commands come from the repo's own manifests (package.json scripts, Anchor.toml, Cargo, go.mod, Makefile). Check the README for prerequisites and environment variables.

DirectoryCommandRuns
webnpm run devnext dev
webnpm run startnext start
webnpm run buildnext build --webpack
webnpm run linteslint
apinpm run devbun --watch src/index.ts
apinpm run startbun src/index.ts
apinpm run buildbun build src/index.ts --outdir dist --target bun
apinpm run testbun test tests/
clinpm run testbun anvil.ts --help
demo/on-chainnpm run buildbun build.ts
demo/on-chainnpm run testbun test.ts

What you could build from it

Used in lessons