Full lesson
valhalla
Solana LP/Token Locker
What it does
From the project README:
You can use any normal anchor commands. Either move to the `anchor` directory and run the `anchor` command or prefix the command with `npm run`, eg: `npm run anchor`.
- Original
- restacked-dev/valhalla
- SuperStack fork
- Blockchains/valhalla @
7aa9ddf - Stars
- 8
- Last push (original)
- 2024-04-15
- Languages
- TypeScript, Rust, JavaScript, CSS
- Detected stack
- AnchorMetaplexNext.jsweb3.js
- Categories
- Other
- Licence
- MIT
Architecture
146 source files. By extension: .tsx ×62, .ts ×58, .rs ×21, .js ×5.
| Top-level directory | Source files |
|---|---|
web | 74 |
anchor | 32 |
lib | 31 |
scheduler | 6 |
(root) | 3 |
On-chain programs: anchor/programs/valhalla
🟡 Builds, verified 2026-11-02 Tested on 2026-11-02: full log results.json
Build it yourself: step-by-step guide
Every command below was run on 2026-11-02 by tests/run_fork.py against the pinned commit, and the results come from that run. The full output is in the committed log.
1. Prerequisites (versions we tested with)
- Rust:
rustc 1.99.1 (58cf8f9c3 2026-10-13) - Solana CLI (Agave):
solana-cli 1.18.17 (src:c027cfc3; feat:4215500110, client:Agave) - Anchor (via avm):
0.29.0 - Node.js:
v24.22.1 - npm:
11.21.1 - pnpm:
10.34.6
cargo install --git https://github.com/coral-xyz/anchor avm --locked
avm install 0.29.0 && avm use 0.29.0
sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"2. Clone at the tested commit
git clone https://github.com/Blockchains/valhalla.git && cd valhalla
git checkout 7aa9ddfb149f6f8b97efc5d523ca03a8b76aebf63. Configure
No environment file is needed.
4. Install dependencies
| Directory | Command | Result | Time |
|---|---|---|---|
. | npx -y yarn@1 install --ignore-engines --network-timeout 600000 | ✅ pass | 22s |
anchor | npx -y yarn@1 install --ignore-engines --network-timeout 600000 | ✅ pass | 2s |
anchor | npx -y yarn@1 install --ignore-engines --network-timeout 600000 | ✅ pass | 1s |
5. Build
| Directory | Command | Result | Time |
|---|---|---|---|
anchor | anchor@0.29.0 build | ✅ pass | 92s |
. | npx -y yarn@1 run web:build | ✅ pass | 156s |
6. Test
| Directory | Command | Result | Time |
|---|---|---|---|
anchor | anchor@0.29.0 test --skip-build --provider.cluster localnet --provider.wallet ~/.config/solana/id.json | ❌ exit 1 | 23s |
Output tail: anchor@0.29.0 test --skip-build --provider.cluster localnet --provider.wallet ~/
at fulfilled (../node_modules/@solana/spl-token/lib/cjs/state/account.js:5:58)
Test Suites: 1 failed, 1 total
Tests: 26 failed, 26 total
Snapshots: 0 total
Time: 15.251 s
Ran all test suites.7. Deploy to a local validator
solana-test-validator --reset # separate terminal
solana config set -ul && solana airdrop 100
anchor deploy --provider.cluster localnet| Directory | Command | Result | Time |
|---|---|---|---|
anchor | solana airdrop 100 -u localhost >/dev/null; anchor@0.29.0 deploy --provider.cluster localnet --provider.wallet ~/.config/solana/id.json | ✅ pass | 8s |
Proof: program IDs and transactions
valhalla:8GWqUTMexNSAGRk455M5qBtDabP8BxxsGWkxeAjSWbCJ(local validator)
Transaction signatures recorded on the local validator (1 shown). Local ledgers are thrown away after the run, so these work as receipts in the log and can't be looked up on an explorer:
valhalla4ZRuDhn64XyqQKQQsS5h…Vs2zmMou
Deploy to devnet (free)
solana config set --url devnet
solana airdrop 2 # or https://faucet.solana.com
anchor deploy --provider.cluster devnet # native: solana program deploy target/deploy/<name>.so
# point the frontend RPC env var at https://api.devnet.solana.comHow it works
Every instruction handler in the program, with the accounts struct it validates. Each link points to the line at the pinned commit.
| Instruction | Accounts | Where |
|---|---|---|
update_admin | UpdateAdmin | anchor/programs/valhalla/src/lib.rs:76 |
update_dao_treasury | UpdateDaoTreasury | anchor/programs/valhalla/src/lib.rs:89 |
update_dev_fee | UpdateDevFee | anchor/programs/valhalla/src/lib.rs:103 |
mint_governance_tokens | MintGovernanceTokens | anchor/programs/valhalla/src/lib.rs:151 |
disburse | DisburseVault | anchor/programs/valhalla/src/lib.rs:205 |
close | CloseVault | anchor/programs/valhalla/src/lib.rs:218 |
cancel | CancelVault | anchor/programs/valhalla/src/lib.rs:231 |
Extend it
- Add an integration test that runs the main flow end to end and asserts every account state change.
- Add a negative test: call each instruction with the wrong signer or account and check that it fails.
- Add an event (
emit!) to each state-changing instruction and subscribe to it from the client. - Replace any unchecked
AccountInfowith typed accounts plushas_one/seedsconstraints. - Wire the build into CI by copying this site's verify-forks workflow.
Key code, explained
Excerpts chosen automatically where the code uses the most Solana concepts, pinned to the fork's commit.
anchor/programs/valhalla/src/instructions/disburse.rs L1–24 @ 7aa9ddf · MIT · originaluse anchor_lang::prelude::*;use anchor_spl::{ associated_token::AssociatedToken, token_interface::{ mint_to, transfer_checked, Mint, MintTo, TokenAccount, TokenInterface, TransferChecked, },}; use crate::{constants, errors::ValhallaError, state::Vault, Config}; #[derive(Accounts)]pub struct DisburseVault<'info> { #[account(mut)] pub signer: Signer<'info>, pub creator: SystemAccount<'info>, pub recipient: SystemAccount<'info>, #[account(mut)] pub dev_treasury: SystemAccount<'info>, #[account(seeds = [constants::CONFIG_SEED], bump, has_one = dev_treasury)] pub config: Box<Account<'info, Config>>,- Line 5
mint_to, transfer_checked, Mint, MintTo, TokenAccount, TokenInterface, TransferChecked,: A checked token transfer, which also verifies the mint and decimals. - Line 5
mint_to, transfer_checked, Mint, MintTo, TokenAccount, TokenInterface, TransferChecked,: Mints new tokens. The mint authority must sign. - Line 14
pub signer: Signer<'info>,: Requires this account to have signed the transaction. - Line 23
#[account(seeds = [constants::CONFIG_SEED], bump, has_one = dev_treasury)]: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 23
#[account(seeds = [constants::CONFIG_SEED], bump, has_one = dev_treasury)]: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 23
#[account(seeds = [constants::CONFIG_SEED], bump, has_one = dev_treasury)]: Checks that a stored pubkey field matches the passed account.
anchor/programs/valhalla/src/instructions/create.rs L13–36 @ 7aa9ddf · MIT · original Authority,}; #[derive(Accounts)]#[instruction(identifier: u64)]pub struct CreateVault<'info> { #[account(mut)] pub creator: Signer<'info>, #[account(mut)] pub recipient: SystemAccount<'info>, #[account(mut)] pub dev_treasury: SystemAccount<'info>, #[account(mut)] pub dao_treasury: SystemAccount<'info>, #[account(seeds = [constants::CONFIG_SEED], bump, has_one = dev_treasury)] pub config: Box<Account<'info, Config>>, #[account( init, payer = creator,- Line 20
pub creator: Signer<'info>,: Requires this account to have signed the transaction. - Line 31
#[account(seeds = [constants::CONFIG_SEED], bump, has_one = dev_treasury)]: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 31
#[account(seeds = [constants::CONFIG_SEED], bump, has_one = dev_treasury)]: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 31
#[account(seeds = [constants::CONFIG_SEED], bump, has_one = dev_treasury)]: Checks that a stored pubkey field matches the passed account. - Line 35
init,: Creates and pays for a new account. Space and payer must be specified.
anchor/programs/valhalla/src/instructions/admin/update_governance_token_amount.rs L1–24 @ 7aa9ddf · MIT · originaluse anchor_lang::prelude::*; use crate::{constants, errors::ValhallaError, state::Config}; #[derive(Accounts)]pub struct UpdateGovernanceTokenAmount<'info> { #[account(mut)] pub admin: Signer<'info>, #[account( mut, seeds = [constants::CONFIG_SEED], bump, has_one = admin, )] pub config: Box<Account<'info, Config>>,} impl<'info> UpdateGovernanceTokenAmount<'info> { pub fn update(&mut self, governance_token_amount: u64) -> Result<()> { // Ensure that the caller is authorized to update the configuration. require!( self.config.admin == self.admin.key(), ValhallaError::Unauthorized- Line 8
pub admin: Signer<'info>,: Requires this account to have signed the transaction. - Line 12
seeds = [constants::CONFIG_SEED],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 13
bump,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 14
has_one = admin,: Checks that a stored pubkey field matches the passed account. - Line 22
require!(: An explicit validation that aborts the instruction with an error.
anchor/programs/valhalla/src/instructions/admin/update_token_fee_basis_points.rs L1–24 @ 7aa9ddf · MIT · originaluse anchor_lang::prelude::*; use crate::{constants, errors::ValhallaError, state::Config}; #[derive(Accounts)]pub struct UpdateTokenFeeBasisPoints<'info> { #[account(mut)] pub admin: Signer<'info>, #[account( mut, seeds = [constants::CONFIG_SEED], bump, has_one = admin, )] pub config: Box<Account<'info, Config>>,} impl<'info> UpdateTokenFeeBasisPoints<'info> { pub fn update(&mut self, token_fee_basis_points: u64) -> Result<()> { // Ensure that the caller is authorized to update the configuration. require!( self.config.admin == self.admin.key(), ValhallaError::Unauthorized- Line 8
pub admin: Signer<'info>,: Requires this account to have signed the transaction. - Line 12
seeds = [constants::CONFIG_SEED],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 13
bump,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 14
has_one = admin,: Checks that a stored pubkey field matches the passed account. - Line 22
require!(: An explicit validation that aborts the instruction with an error.
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 |
|---|---|---|
anchor | anchor build | compile programs and generate IDLs |
anchor | anchor test | local validator + test suite |
anchor | anchor run create-data | yarn run ts-node ./scripts/create-data.ts |
anchor | anchor run init | yarn ts-node ./scripts/init.ts |
anchor | anchor run test | ../node_modules/.bin/nx run anchor:jest |
What you could build from it
- Fork the SuperStack copy and extend it within the Other gaps listed on the idea page.
- Learn the pattern first: Program Derived Addresses (PDAs)
- Learn the pattern first: Cross-Program Invocations (CPIs)