How to use this page
Work top to bottom. Every checkpoint ends with a command that prints something, and the page tells you what to expect.
If your output does not match, stop there. A broken tool carried forward throws its error three checkpoints later, pointing at the wrong thing.
Tick each checkpoint as you go. Progress is saved in this browser, so you can close the tab and come back.
What you are actually installing
Four separate tools that people treat as one thing called "Solana". Knowing which is which is most of your debugging skill for the term.
When something breaks, name the tool first. anchor build
failing is usually rustc or a missing system library, not Anchor.
Where your code runs
Three networks, all running the same software. What changes is who can see them and what the SOL is worth. A cluster is just one of these networks — the word comes up constantly.
| localnet | devnet | mainnet | |
|---|---|---|---|
| SOL | Free, unlimited | Faucet, rate limited | Real money |
| Speed | Instant | Real network conditions | Real network conditions |
| Persistence | Wiped on every restart | Persistent | Permanent |
| Visible to others | No | Yes, shareable explorer link | Yes |
| Use it for | ~95% of development | Integration testing, demos, sharing | Production |
Almost all of your development time this term happens on localnet — a Solana network running on your own laptop.
Localnet SOL is free and unlimited. No faucet, no airdrop request, no waiting.
It is also worthless, and it cannot move to devnet or mainnet. These are separate chains that share nothing.
Nobody else can see your localnet. That is the only reason this guide touches devnet at all — devnet gives you a link other people can open.
anchor test starts and stops its own local validator
automatically. You never manage one.
Prepare your OS
When you finish this: your machine has the system libraries every later step depends on.
Everything after this checkpoint is identical on all three platforms. Do only the block that matches your machine, then rejoin at checkpoint 1.
Windows
Solana tooling does not run on Windows natively. WSL gives you a real Ubuntu inside Windows. Run this in PowerShell as administrator:
wsl --install
Reboot, then open the Ubuntu app from the Start menu and set a username and password.
From here on, "your terminal" means that Ubuntu window, never PowerShell. Run the Linux block below inside it.
Keep your code in the Linux home directory (~/solana), not
in /mnt/c/Users/....
Reading Windows files from WSL crosses a filesystem boundary. A
cargo build that takes 90 seconds in ~ can take
ten minutes on /mnt/c.
To edit in VS Code, install the WSL extension and open the folder
from inside Ubuntu with code .
Linux and WSL
Anchor compiles from source and needs a C toolchain plus a few libraries. Missing these is the most common install failure.
sudo apt-get update
sudo apt-get install -y \
build-essential \
pkg-config \
libudev-dev llvm libclang-dev \
protobuf-compiler libssl-dev
apt-get update. Run it and try again. On Fedora or
RHEL the equivalents are gcc gcc-c++ pkgconf-pkg-config systemd-devel
llvm clang-devel protobuf-compiler openssl-devel.
macOS
Install Apple's command line tools, which include the linker Rust calls.
xcode-select --install
xcode-select -p # should print a path, not an error
If they were already installed, the first line says so and the second prints a path.
Use rustup instead — the next checkpoint does it for you. A Homebrew Rust lands in a directory Anchor does not search.
The failure shows up much later as
No such file or directory (os error 2). If you already have
one: brew uninstall rust.
Install everything
When you finish this: all five tools installed and printing versions.
One script installs the whole toolchain. Try it first. If it completes, skip checkpoint 2 entirely.
curl --proto '=https' --tlsv1.2 -sSfL https://solana-install.solana.workers.dev | bash
It finishes by printing what it installed:
Installed Versions:
Rust: rustc 1.91.1 (ed61e7d7e 2025-11-07)
Solana CLI: solana-cli 3.0.10 (src:96c3a851; feat:3604001754, client:Agave)
Anchor CLI: anchor-cli 0.32.1
Surfpool CLI: surfpool 0.12.0
Node.js: v24.10.0
Yarn: 1.22.1
Surfpool is a local validator. The installer bundles it, this guide does not use it, and it comes up in a later class. Ignore it for now.
Most of that is compiling the Anchor CLI from source. The terminal looks frozen and your fan gets loud. That is normal.
Do not interrupt it. If you kill it halfway, you will need checkpoint 2 to finish the job by hand.
Close your terminal and open a new one. The script edited your shell profile and the current session has not read it.
Now check all five tools:
rustc --version && solana --version && anchor --version && \
node --version && yarn --version
If all five print a version, you are done. Skip checkpoint 2 and go to checkpoint 3.
If a line says command not found, that one tool did not
land. Install just that one from checkpoint 2.
The manual path
When you finish this: the tools checkpoint 1 missed, installed by hand.
Install one at a time, in this order, verifying each before moving on. Anchor depends on Rust and on the checkpoint 0 libraries, so order matters.
If only one tool is missing, jump straight to that block.
Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
. "$HOME/.cargo/env"
rustc --version
Solana CLI
sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"
solana --version
Aside Why it says client:Agave
The CLI is built by Anza and reports itself as
client:Agave. Same tool — the validator client was
renamed.
To update it later, run agave-install update, which pulls
the newest stable release.
Hint solana: command not found after installing
PATH, essentially always. The installer printed a line and you did not run it. Find your shell first, because the file differs:
echo $SHELL # /bash → .bashrc, /zsh → .zshrc
Then append the line permanently and reload:
echo 'export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
Swap .bashrc for .zshrc if that is your shell. Typing
export PATH=... directly into the terminal also works, but only
until you close that window — which is why people think the install
broke overnight.
Node and Yarn
Anchor's test template is TypeScript, so anchor test needs
both.
Install Node through nvm rather than your system package manager, so you can switch versions per project.
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
# restart your terminal, then:
nvm install --lts
npm install -g yarn
node --version && yarn --version
Anchor, through AVM
Install the version manager, not the framework directly. Anchor makes breaking changes between minor versions, and you will eventually need two versions for two projects.
curl -sSfL https://raw.githubusercontent.com/otter-sec/anchor/master/avm/install | sh
avm --version
avm install latest
avm use latest
anchor --version
avm install downloads a version. avm use is what
points the anchor command at it. Skip the second and
anchor --version keeps reporting the old one.
This build takes several minutes and needs the checkpoint 0
libraries. could not exec the linker cc means you skipped
them.
The AVM installer URL has moved between repositories more than once. Check the line above against solana.com/docs/intro/installation/dependencies and update it before this guide is published.
Students: opening that page to confirm a URL before piping any script into your shell is a habit worth more than this guide.
Pin your versions
When you finish this: your versions match the rest of the class.
Solana tooling moves fast enough that a three-month-old blog post is often wrong. When forty people debug together, mismatched versions waste more time than any real bug.
Get onto these versions. If you cannot, say so in the channel before you ask for help.
| Tool | Check | We are on | Fix a mismatch |
|---|---|---|---|
| Rust | rustc --version | 1.91.x | rustup update stable |
| Solana | solana --version | 3.0.x | agave-install update |
| Anchor | anchor --version | 0.32.1 | avm install 0.32.1 && avm use 0.32.1 |
| Node | node --version | 22 or 24 | nvm install --lts |
A patch-level difference is fine. A minor-version difference in Anchor is not.
0.31 and 0.32 generate different client code, and code
from one will not compile against the other.
Wallet and config
Network devnet
When you finish this: a wallet with about 5 devnet SOL in it, and a CLI pointed at devnet.
The CLI keeps one small config file. It decides which network your commands talk to and which keypair signs them.
Almost every "it worked yesterday" story is one of those two being different from what you assumed. Look at it first:
solana config get
You should see something like this:
Config File: /Users/you/.config/solana/cli/config.yml
RPC URL: https://api.mainnet.solana.com
WebSocket URL: wss://api.mainnet.solana.com/ (computed)
Keypair Path: /Users/you/.config/solana/id.json
Commitment: confirmed
Point at devnet
Switch the network your commands talk to:
solana config set --url devnet
solana config get # confirm the RPC URL now says devnet
The RPC URL should now say devnet. Check it before you blame your code — this one line explains a lot of confusing errors.
Aside Short forms for switching cluster
solana config set -ud does the same thing as
--url devnet. -um switches to mainnet and
-ul switches to your local machine.
Create a keypair
Write a new keypair to the default path:
solana-keygen new
solana address
It prints a public key and a twelve-word seed phrase. That public key is your address on every Solana network at once.
id.json is an unencrypted private key sitting on disk. Fine
for a course where it only holds test SOL. Not fine for anything
with value.
It must never reach GitHub. A bot will find it within minutes and drain it.
Anchor's .gitignore covers target/deploy/*.json. If
you copy a keypair anywhere else in the repo, add that path
yourself. Never reuse this key on mainnet.
Get devnet SOL from the faucet
Do this now, at the start. Four steps:
- Go to faucet.solana.com.
- Sign in with GitHub. This unlocks the higher limit of 5 SOL. The faucet works signed out, but gives you less than the checkpoint 8 deploy needs.
- Paste in the address that
solana addressprinted. - Confirm it landed.
solana balance
You want to see roughly 5 SOL.
Faucet requests are rate limited with a cooldown between them.
If you reach checkpoint 8 short of SOL, you may be waiting hours to top up. Get your 5 SOL at the start and the rest of the guide never blocks.
Send a transaction
Network devnet
When you finish this: a confirmed transaction you can open in the explorer.
Send a transaction yourself, then read back what the network recorded.
This runs on devnet because you need a link other people can open. The whole thing costs 5000 lamports — essentially nothing.
Make somewhere to send it
Create a second keypair to receive the transfer:
solana-keygen new --no-bip39-passphrase --silent -o ~/throwaway.json
solana address -k ~/throwaway.json
It prints the throwaway address.
Send
Transfer 0.1 SOL to it:
solana transfer $(solana address -k ~/throwaway.json) 0.1 --allow-unfunded-recipient
You get back a signature — a long base58 string. Copy it.
That signature is the transaction's identity. It exists because your keypair signed the message; the network did not assign it.
Now ask the network what happened:
solana confirm -v <YOUR_SIGNATURE>
Read what you just did
Open the signature in the explorer. Keep the cluster parameter on the end.
https://explorer.solana.com/tx/<YOUR_SIGNATURE>?cluster=devnet
Match what you see on the page against the terms below:
| What you see | What it is |
|---|---|
| Signature | The transaction ID, produced by signing rather than assigned |
| Fee payer | The first signer. Pays whether or not the transaction succeeds |
| Fee | 5,000 lamports per signature, taken up front |
| Instructions | One call to the System Program. Programs do the work; a transaction just carries instructions |
| Account inputs | Every account the instruction touches, listed in advance and marked writable or read-only |
| Slot | Which block a validator packed it into |
| Status | Processed, then confirmed, then finalized — three levels of "it happened" |
A Solana transaction declares every account it will read or write before it runs.
That one constraint is why the runtime can execute unrelated transactions in parallel. It also explains most of what looks like boilerplate in an Anchor program.
You just sent the simplest possible example of it.
Hint Transaction simulation failed: insufficient funds
Check solana balance and solana config get.
Either the faucet SOL never landed, or you are pointed at a cluster where your wallet is empty.
Same fix for both: confirm the RPC URL, then fund the address that
solana address prints.
Optional If devnet's public RPC is throwing errors
The default public devnet RPC is shared by everyone and rate limited. Deploys hit it hardest, sending many transactions in quick succession.
If you see 429 errors or timeouts, get a free devnet endpoint from Helius and point your CLI at it:
solana config set --url <your-helius-devnet-url>
This is a fallback, not a required step. Skip it unless the public RPC is actually failing on you.
First project
Network localnet — no SOL needed
When you finish this: a generated Anchor project that builds and passes its test.
Anchor generates a working program, a test, and the config that ties them together.
Everything here runs on your own machine. anchor test handles
the network itself, so there is no faucet and no SOL involved.
Do this somewhere sensible — on WSL, inside your Linux home directory:
mkdir -p ~/solana && cd ~/solana
anchor init hello_solana
cd hello_solana
What it made
| Path | What it holds | You edit it? |
|---|---|---|
| programs/hello_solana/src/lib.rs | The on-chain program | Constantly |
| tests/hello_solana.ts | TypeScript tests that call it | Constantly |
| Anchor.toml | Cluster, wallet, program IDs | Sometimes |
| target/deploy/ | Compiled .so and the program keypair | Never by hand |
| target/idl/ | Generated interface the client reads | Never by hand |
Open lib.rs and read it before building. It is about fifteen
lines: a program ID, one instruction, one log.
The instruction takes a Context listing its accounts — the same
up-front account declaration you saw in the explorer at checkpoint 5.
Everything you write this term is a variation on that shape.
Build it
Compile the program to SBF bytecode:
anchor build
Expect no output for a long time. That is normal.
Building also generates a keypair for your program. Its public key is the program ID.
That ID appears in two text files as well, and they start out disagreeing with the keypair. Sync them:
anchor keys sync
anchor keys list
Both should now print the same program ID.
Hint Why a program has its own keypair
A program on Solana lives in an account like everything else, and
that account needs an address. anchor build generates one at
target/deploy/hello_solana-keypair.json. The address is baked
into your compiled binary through the declare_id! macro, so if
lib.rs claims one ID and the deployed account sits at another,
every instruction fails with DeclaredProgramIdMismatch.
anchor keys sync rewrites declare_id! and
Anchor.toml to match the keypair. Run it after your first build
and any time you clone someone else's repo.
Test it
Build, deploy locally, and run the TypeScript tests:
anchor test
You should see one passing test and a transaction signature.
That single command started a disposable network on your machine, deployed to it, ran the tests, then tore the network down. You never start or stop anything yourself.
Optional · 5 min See the validator that anchor test hides
anchor test starts and stops a local validator for you, so
you never see one. Run it yourself once to see what is actually
happening, and to meet solana logs — the real debugging tool
for Solana work.
Start a validator in a second terminal and leave it running:
solana-test-validator # leave this running
solana config set --url localhost
solana airdrop 100
solana balance
100 SOL, instantly. No GitHub sign-in, no cooldown, no faucet page. Compare that to what you just went through on devnet.
Now open a third terminal and watch the network:
solana logs
Every msg! your program prints shows up here live. This is
how you debug a program that is misbehaving.
Clean up — not optional
Point your CLI back at devnet and stop the validator:
solana config set --url devnet
pkill -f solana-test-validator
A validator left running holds port 8899.
The next anchor test then fails with a port-in-use error
that looks nothing like its actual cause, and you will lose time
hunting the wrong problem.
Derive a PDA
Network localnet — nothing to fund
When you finish this: a PDA you derived yourself, and proof that deriving is not creating.
A PDA is an address computed from a program ID plus some seeds. It has no private key, which is exactly why programs use it to own state.
Deriving one is pure local maths. No network, no SOL, nothing to fund.
In your hello_solana directory, create pda.js:
const { PublicKey } = require("@coral-xyz/anchor").web3;
const programId = new PublicKey(process.argv[2]);
const seed = process.argv[3] ?? "counter";
const [pda, bump] = PublicKey.findProgramAddressSync(
[Buffer.from(seed)],
programId
);
console.log("program", programId.toBase58());
console.log("seed ", seed);
console.log("pda ", pda.toBase58());
console.log("bump ", bump);
Run it with your own program ID from anchor keys list:
node pda.js <YOUR_PROGRAM_ID> counter
Three things to try
- Run it twice with the same seed. Same address, every time, on every machine. Nothing was stored anywhere — the address is computed.
-
Change the seed to
vault. Completely different address, from the same program. This is how one program keeps thousands of separate accounts without tracking a single keypair. - Look up the address you derived. It does not exist:
solana account <THE_PDA> # Error: AccountNotFound
You just computed an address that nobody owns and nothing occupies.
Making it a real account takes a transaction where the program allocates space and pays rent — a one-off deposit for storing data on chain, refunded when the account closes.
In Anchor that becomes the seeds and bump constraints
on an init account. The address existed before the account
did.
Hint What the bump is for
Roughly half of all seed-and-program-ID combinations land on the Ed25519 curve, meaning a private key could exist for them — useless for a program-owned address. So the derivation appends one extra byte, the bump, and counts down from 255 until the result falls off the curve.
findProgramAddressSync returns the first value that works.
That one is called the canonical bump — the bump
everyone agrees to use for a given set of seeds.
Programs should always use it. A program that accepts any bump has effectively created several valid addresses for the same seeds, which is a real class of exploit.
Hint Cannot find module '@coral-xyz/anchor'
You are outside the project directory, or dependencies never
installed. From ~/solana/hello_solana, run yarn install
and try again.
Deploy to devnet
Network devnet — this is the expensive step
When you finish this: your program live on devnet, visible in the explorer.
Same program as checkpoint 6, but a network you do not control. It costs real devnet SOL and takes a minute rather than a second.
Follow these six steps in order. Do not skip step 4.
1 · Check where you are and what you have
solana config get # RPC URL must say devnet
solana balance # need at least 3 SOL
Short on SOL? Go back to checkpoint 4 and use the faucet before you continue.
2 · Point Anchor at devnet
Edit Anchor.toml:
[provider]
cluster = "devnet"
wallet = "~/.config/solana/id.json"
3 · Build
anchor build
This should take seconds — you already built once.
4 · Preview the cost before you spend it
Ask what storing the compiled program will cost:
solana rent $(wc -c < target/deploy/hello_solana.so)
Expect a figure in the region of 2 to 3 SOL for a small Anchor program.
Two things make it that high. Solana allocates roughly double your binary size, leaving room to upgrade the program later.
The deploy also creates buffer accounts — temporary accounts that hold your program's bytes while they upload. They cost SOL too.
5 · Deploy
anchor deploy
It prints your program ID when it succeeds.
6 · If the deploy failed, run this before retrying
solana program close --buffers
A failed deploy leaves buffer accounts holding your SOL.
Retry without closing them and you will run out of SOL with nothing deployed — and then you are waiting on the faucet cooldown.
Close the buffers, check solana balance, then retry.
See it on the network
Open your program ID in the explorer, keeping the cluster on the URL:
https://explorer.solana.com/address/<PROGRAM_ID>?cluster=devnet
solana program show <PROGRAM_ID>
Your program is now an account on a public network, owned by the BPF loader, holding your compiled bytecode. Same account model as everything else you have touched today.
This is the single most common source of confusion here. Without it, the explorer looks on mainnet, finds nothing, and says the account does not exist.
Your deploy was fine. This costs people about twenty minutes each, once.
Clean up when you are done
Close the program to reclaim most of the SOL:
solana program close <PROGRAM_ID> --bypass-warning
solana program close --buffers # reclaims leftovers from failed deploys
Have a look at it in the explorer first — closing is irreversible.
Optional If devnet's public RPC is throwing errors
Deploys hit the shared public RPC hard, sending many transactions in quick succession. 429 errors and timeouts here are common.
If that happens, get a free devnet endpoint from Helius and point your CLI at it, then retry from step 1:
solana config set --url <your-helius-devnet-url>
Optional fallback. Only bother if the public RPC is failing on you.
Check your work
When you finish this: confidence that every piece actually works.
Run through these five yourself before you close the tab:
- The verify script below runs clean.
- Your
anchor testrun shows the test passing. - Your transfer signature from checkpoint 5 opens in the explorer.
- Your devnet program ID opens in the explorer, with
?cluster=devnet. - You can say in a couple of sentences what broke and how you fixed it. If nothing broke, say why the PDA in checkpoint 7 had no account behind it.
Verify script
Paste this into your terminal. It prints one line per tool, marking each present or missing, then shows your current cluster and wallet. Run it before asking for help, and paste the output with your question.
#!/usr/bin/env bash
# solana-doctor — checks the toolchain for Solana Fall School
for t in rustc cargo solana anchor avm node yarn; do
if command -v "$t" >/dev/null 2>&1; then
printf 'ok %-7s %s\n' "$t" "$("$t" --version 2>/dev/null | head -1)"
else
printf 'MISS %-7s not on PATH\n' "$t"
fi
done
echo
solana config get 2>/dev/null | grep -E 'RPC URL|Keypair Path'
echo "address: $(solana address 2>/dev/null || echo 'no keypair yet')"
echo "balance: $(solana balance 2>/dev/null || echo 'unreachable')"
Healthy output has seven ok lines, an RPC URL pointing at devnet,
and a balance. Any MISS line names the tool to reinstall from
checkpoint 2 — and only that tool.
Optional Keep it as a command
Save it once and call it by name whenever something feels off:
mkdir -p ~/bin
# paste the script into ~/bin/solana-doctor, then:
chmod +x ~/bin/solana-doctor
echo 'export PATH="$HOME/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
solana-doctor
Troubleshooting
Ranked by how often it actually happens in a room of forty people.
Error Deploy failed and now I have no SOL
Buffer accounts from the failed deploy are holding it. Close them and check what came back:
solana program close --buffers
solana balance
Do this before you retry the deploy, every time. Retrying without it is how people end up at zero with nothing deployed.
Error Faucet says rate limited
There is a cooldown between requests. Waiting it out is the only real fix.
This is why checkpoint 4 tells you to fund your wallet at the start. If you are blocked and up against the deadline, post your address in the course Discord.
Error GitHub sign-in refused at the faucet
The faucet verifies GitHub accounts, and very new accounts sometimes fail that check.
Ask a TA in the course Discord to send devnet SOL to your address
directly. Post the output of solana address.
Error 429 or timeout errors during deploy
The public devnet RPC is shared and overloaded. A deploy sends many transactions quickly, so it hits the limit before anything else does.
Switch to a free Helius devnet endpoint and retry:
solana config set --url <your-helius-devnet-url>
Check solana program close --buffers first if the deploy
already failed once.
Error command not found: solana / anchor / avm
PATH, essentially always. Either you have not opened a new terminal
since installing, or the export line went into the wrong shell
profile. Check with echo $SHELL, then append the export to
.bashrc or .zshrc — see the hint under checkpoint 2.
On macOS, also check you do not have a Homebrew Rust shadowing the
rustup one: which -a rustc should show only the
~/.cargo/bin path.
Error could not exec the linker cc — Permission denied (os error 13)
Missing build dependencies on Linux or WSL, despite what the message
implies about permissions. Run the apt-get install block from
checkpoint 0, then retry the Anchor install. Do not prefix
cargo or avm with sudo — that creates
root-owned files in your cargo directory and a second, worse problem.
Error No such file or directory (os error 2)
Anchor tried to spawn a binary and will not say which. Ask it directly — whichever comes back empty is your answer:
which rustc cargo solana anchor node yarn cargo-build-sbf
During anchor test specifically, this usually means Node or
Yarn is missing, since the test template is TypeScript.
Error anchor test fails with a port already in use
Something is already holding port 8899, so the new validator cannot bind.
Two causes. Either a previous run crashed and left its validator
behind, or you started solana-test-validator yourself — for
example in the optional detour under checkpoint 6 — and never
stopped it.
Same fix for both:
pkill -f solana-test-validator
anchor test
Error DeclaredProgramIdMismatch
The ID in declare_id! does not match the keypair the program
was deployed under. Standard after a fresh clone or a
target/ wipe:
anchor keys sync
anchor build
anchor deploy
Error Account not found in the explorer
You are looking at mainnet. Append ?cluster=devnet to the URL,
or pick the cluster in the explorer's top-right dropdown. Anything
deployed by anchor test will not appear there at all — that
network only existed for the duration of the command.
Error solana airdrop fails / 429 Too Many Requests
solana airdrop goes through the public devnet RPC, which
rate limits by IP. A lecture hall on one wifi network hits that
limit constantly.
Use faucet.solana.com signed in with GitHub instead, as checkpoint 4 describes. Ask once rather than repeatedly.
On localnet none of this applies — solana airdrop 100 works
instantly and as often as you like.
Error Builds are unbearably slow on Windows
Your project is on /mnt/c. Every file read crosses from WSL to
the Windows filesystem, which is roughly an order of magnitude
slower. Move it:
cp -r /mnt/c/Users/you/hello_solana ~/
cd ~/hello_solana && anchor build
Error lock file version 4 requires -Znext-lockfile-bump
Your Rust is older than the lockfile the project's dependencies
expect. rustup update stable fixes it. The general shape of
this class of error is a dependency requiring a newer toolchain than
you have — read the version it names before editing anything, and
use cargo tree -i <crate> to find which dependency pulled it in.
Guide 01 · never commit a keypair · commands verified against solana.com/docs, August 2026