Advanced· Lesson 6 of 8· 4 min
Reading an Anchor program from the outside
Most Solana programs are written with Anchor, and Anchor leaves a trail: an IDL, predictable discriminators and typed constraints. With those you can read what a program does without its source.
Anchor is the framework behind most programs on the chain. It generates the parsing, the account checks and an interface definition — the IDL — from annotated Rust. Explorers use the IDL to turn raw instruction bytes into names and arguments, which is why a Jupiter swap shows as “Route V2” with typed fields rather than hex.
The program account

- Explorer labels well-known programs. The address below is the only identity.
- Executable: this account's data is code the runtime may run.
- The bytecode lives in a second account, the program-data account, owned by the upgradeable loader.
- Upgradeable: the code can be replaced. A program with the upgrade authority set to none is frozen.
- No verified build: nobody has proven the on-chain bytes match published source.
- Who can upgrade it: here a multisig, so several parties must agree to change the code.
Three things on this page decide how much to trust a program: whether it is upgradeable and by whom, whether the build is verified against public source, and whether its upgrade authority is a single wallet, a multisig or nothing. “Immutable” is the strongest claim; “multisig with a public roster” is the common one for live protocols.
The IDL
The IDL is a JSON document listing every instruction with its arguments and the accounts it needs, every account type the program stores with its fields, and the program's error codes and events. Anchor can publish it on-chain in an account derived from the program id; explorers fetch it from there or from a registry. Jupiter's is on-chain, which is why the page below exists.

- Instructions: what the program can be asked to do.
- PDAs: the seeds the program derives its own accounts from.
- An instruction's name. Hash it and you get the discriminator that selects it.
- Its arguments, typed. This is the Borsh layout that follows the discriminator.
- Account flags: mutable means the handler writes it; signer means it must have signed.
- PDA: the program derives and checks this address itself. You cannot substitute another.
Discriminators
Instruction data on an Anchor program starts with eight bytes that identify the handler: the first eight bytes of sha256("global:<instruction_name>"). Stored accounts start with eight bytes from sha256("account:<TypeName>"), which is how a program refuses an account of the wrong type even when the size matches. Given a program's IDL you can compute every discriminator and identify any instruction in raw data.
import { createHash } from "crypto";
const disc = (ns, name) => createHash("sha256").update(`${ns}:${name}`).digest().subarray(0, 8);
disc("global", "release") // → the 8 bytes that start every `release` instruction
disc("account", "Lock") // → the 8 bytes that start every Lock accountConstraints: what is checked before the handler runs
mut | The account will be written; it must be marked writable in the transaction. |
|---|---|
signer | The account must have signed. This is how “only the owner can…” is enforced. |
seeds = [...], bump | The account must be the PDA of those seeds under this program. Prevents substitution. |
has_one = owner | A field in this account must equal another account passed in. Ties a vault to its lock. |
init, payer, space | Creates the account in this instruction, funded by the payer, with a fixed size. |
close = destination | Deletes the account afterwards and sends its rent to the destination. |
token::mint, token::authority | The account must be a token account for that mint, with that authority. |
constraint = expr | An arbitrary check, such as a clock comparison. The error names it. |
All of these run before the handler's own code. When a transaction fails with a constraint error, no logic ran at all; an account was not what the program required.
Reading stored state
Account types in the IDL are Borsh-serialised structs. With the IDL, an explorer's “Anchor Account” view or Account Inspector decodes them field by field. Solstack's Token Locker stores one Lock account per lock:
Lock {
creator, recipient, mint, vault, // who, for whom, what, where it sits
lock_id, name,
total_amount, released_amount, // progress
cliff_ts, cliff_amount, end_ts, period, // the schedule
created_ts, cancelable, transferable, bump
}Errors
Anchor's own errors have fixed codes: constraint failures in the 2000s (2000 ConstraintMut, 2001 ConstraintHasOne, 2002 ConstraintSigner, 2006 ConstraintSeeds), account problems in the 3000s (3002 AccountDiscriminatorMismatch, 3007 AccountOwnedByWrongProgram, 3012 AccountNotInitialized). A program's own errors start at 6000 (0x1770) in IDL order — the locker's first is ZeroAmount, its sixth NothingToRelease. The log line custom program error: 0x1775 therefore means error 6005 of whichever program was running.
Programs without an IDL
Native programs (System, Token, Stake) are not Anchor; explorers hard-code their layouts. A third-party program with no IDL shows as “Unknown Instruction” and you are left with the accounts, the data bytes and the logs. That is a signal in itself: a serious program publishes its IDL and, ideally, a verified build.
What to remember
- The program account tells you if code can change, by whom, and whether the build is verified. Read it before trusting anything else.
- The IDL lists instructions, arguments, account flags, stored types and errors. Explorers decode with it.
- Discriminators are the first 8 bytes of sha256 over global:name or account:Name.
- Constraints run before any handler code. A constraint error means an account was not what was required.
- Custom errors start at 6000 in IDL order; Anchor's own are in the 2000s and 3000s.