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 directory | Source files |
|---|---|
api | 676 |
web | 46 |
cli | 18 |
scripts | 14 |
demo | 10 |
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.
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.- Line 38
init,: Creates and pays for a new account. Space and payer must be specified. - 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. - Line 42
bump: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 46
pub owner: Signer<'info>,: Requires this account to have signed the transaction. - Line 54
close = receiver,: Closes the account at the end of the instruction and refunds its lamports. - Line 55
has_one = owner,: Checks that a stored pubkey field matches the passed account.
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>,}- Line 26
counter.value = counter.value.checked_add(amount).ok_or(EventError::Overflow)?;: Uses checked arithmetic so overflow fails instead of wrapping. - Line 27
emit!(Incremented {: Emits an Anchor event that clients can parse from the logs. - Line 38
init,: Creates and pays for a new account. Space and payer must be specified. - 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. - Line 42
bump: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 46
pub authority: Signer<'info>,: Requires this account to have signed the transaction.
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(),- Line 112
.checked_mul(elapsed as u128): Uses checked arithmetic so overflow fails instead of wrapping. - Line 121
require!(rewards > 0, StakingError::NoRewards);: An explicit validation that aborts the instruction with an error. - Line 123
let pool_seeds = &[: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 126
&[pool.bump],: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 130
token::mint_to(: Mints new tokens. The mint authority must sign. - Line 131
CpiContext::new_with_signer(: Makes a CPI signed by a PDA. The signer seeds prove the program controls that address.
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- Line 49
let earned = stake.amount: Touches staking state: the amount locked and when it was locked. - Line 55
emit!(RewardsClaimed {: Emits an Anchor event that clients can parse from the logs. - Line 60
msg!("rewards claimed");: Logs to the program log, which is visible in explorers and the test output. - Line 68
init,: Creates and pays for a new account. Space and payer must be specified. - 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. - 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.
| Directory | Command | Runs |
|---|---|---|
web | npm run dev | next dev |
web | npm run start | next start |
web | npm run build | next build --webpack |
web | npm run lint | eslint |
api | npm run dev | bun --watch src/index.ts |
api | npm run start | bun src/index.ts |
api | npm run build | bun build src/index.ts --outdir dist --target bun |
api | npm run test | bun test tests/ |
cli | npm run test | bun anvil.ts --help |
demo/on-chain | npm run build | bun build.ts |
demo/on-chain | npm run test | bun test.ts |
What you could build from it
- Fork the SuperStack copy and extend it within the Infrastructure & Tooling gaps listed on the idea page.
- Learn the pattern first: Building and sending transactions from a client
- Learn the pattern first: Anchor account constraints
Used in lessons
- Program Derived Addresses (PDAs)
- Cross-Program Invocations (CPIs)
- SPL Tokens: mints, accounts and transfers
- Token-2022 (Token Extensions)
- Escrow patterns
- Staking and reward accrual
- NFTs with Metaplex
- Compressed NFTs and state compression
- Oracles: Pyth and Switchboard
- Building and sending transactions from a client
- Anchor account constraints