Who can replace the code running on Solana

An audit describes the code that existed on the day it was written. On Solana, the median busy program is replaced about every two weeks — and half the time the replacement needs a single signature.

What we found

On 29 July 2026 we asked Solana mainnet two questions about every upgradeable program it carries: who is allowed to replace this code, and how recently did they. The answers come from 65,119 program accounts and from the 348 programs we observed actually being invoked.

49%
of the 100 busiest programs are upgradeable by a single keypair rather than an on-chain multisig
13 days
median time since the last deploy, among those same 100
3%
have given up the ability to change at all — the authority is revoked

Immutability is not the norm on Solana; it is a rounding error. Whatever you are interacting with, someone almost certainly retains the power to replace it, and on current rates they exercise that power often.

Why anyone should care

Solana's upgradeable loader is a feature, not a flaw. Being able to ship a fix without migrating state is why protocols can respond to an incident in hours instead of weeks, and nobody sensible argues that every program should be frozen on day one.

But it has a consequence that is easy to state and rarely quantified: a security review describes a binary, and that binary has an expiry date nobody writes down. If you read a protocol's audit, integrated it, and moved on, the question of what is running at that address today is a separate question — and this report is an attempt to say how separate.

The second question, who holds the key, is the one that decides how quickly a mistake becomes irreversible. A program behind an on-chain multisig can only be replaced by a quorum whose members and threshold anyone can read from the chain. A program behind a single keypair can be replaced by whoever holds that key.

Two very different networks

The first thing the data says is that "programs on Solana" is not one population. Of a random 2,000 drawn from the 65,119 upgradeable program accounts, only 610 still have a ProgramData account at all — the other 70% have been closed or abandoned. Their median program was last deployed 353 days ago. That is a graveyard, and any statistic computed over it describes the dead.

So we measured a second population: every program that appeared in a real instruction, top-level or inner, across 25 sampled blocks. That gives 348 distinct programs across 132,563 invocations — the network as it is actually used, weighted by use. Eight of the 348 are native programs with no ProgramData (ComputeBudget, Vote, System, Associated Token, Memo, Ed25519, Secp256k1), which is a check on the method rather than a gap in it.

Both populations appear in every table below, because the gap between them is one of the findings.

Who can replace the code

An upgrade authority is either a plain public key, or a program-derived address (a PDA), or absent. The three are cleanly distinguishable without trusting any label: a PDA is by construction not a valid ed25519 curve point — that is exactly what makes it unsignable by a private key. So an on-curve authority is one a keypair can sign for, an off-curve authority is one only a program can authorise, and an absent authority means the program is immutable.

PopulationProgramsSingle keypairProgram-controlledImmutable
Top 25 by invocations2513 (52%)11 (44%)1
Top 50 by invocations5028 (56%)21 (42%)1
Top 100 by invocations10049 (49%)48 (48%)3
All invoked340215 (63%)114 (33%)11
Random sample of all 65,119610538 (88%)56 (9%)16
Solana mainnet, 29 July 2026, slot 435,941,295. Percentages are of the resolved programs in each row.

The gradient is the story. Abandoned programs are overwhelmingly single-key, which surprises nobody — they are hobby deploys and dead experiments. Custody discipline improves steeply with how much a program is actually used. And it improves to roughly a coin flip: among the hundred programs carrying the most traffic on Solana, 49 are not behind an on-chain multisig.

One number is flat across every population, and it is the one we did not expect: immutability sits at 3% everywhere. Almost nothing on Solana has ever given up its upgrade authority. Among the busiest hundred, the notable exception is SPL Token itself, whose authority is revoked.

How often the code changes

Recency first. Ages are computed against a measured slot time of 0.3976 seconds, calibrated over 50 million slots rather than assumed from the nominal 400 ms.

PopulationMedian age≤ 30 days≤ 90 days
Top 25 by invocations13 d19 (76%)24 (96%)
Top 50 by invocations12 d35 (70%)46 (92%)
Top 100 by invocations13 d60 (60%)81 (81%)
All invoked25 d178 (52%)254 (74%)
Random sample of all 65,119353 d62 (10%)144 (23%)
Days since the last deploy, and the share of each population redeployed within 30 and 90 days.

A snapshot of ages can be misread, so we checked it a second way: for the 60 busiest programs we pulled a full year of transactions touching their ProgramData accounts.

Writes in a yearPrograms
0 — untouched3 (5%)
1–32 (3%)
4–1115 (25%)
12 or more40 (66%)
Successful writes to ProgramData in the trailing 365 days, for the 60 most-invoked programs. Median 20, mean 56.6, maximum 468.

Two independent methods — a one-shot age snapshot and a year of history — agree in order of magnitude. A busy Solana program is replaced roughly every two to three weeks, and two thirds of the busiest are replaced at least monthly. The code running the network is not a fixed artifact; it is a moving one.

What follows if you integrate

Take a protocol that composes with eight external programs — an unremarkable number for anything doing routing, lending or liquid staking. At the rates above, roughly five of those eight will be replaced within a month of the day you reviewed them.

That does not mean five hostile changes. Almost all of it is ordinary engineering: fee tweaks, compute optimisation, new instructions. But it does mean the sentence "we reviewed our dependencies" decays fast enough to need a date attached, and that the review you commissioned in April describes April.

The practical asks are unglamorous and cheap:

  • Record the deploy slot, not just the address. "We integrated program X" is not a reproducible statement; "we integrated program X as deployed at slot N" is.
  • Know which of your dependencies are single-key. It is one RPC call and one curve check per address, and it changes how much of your risk sits outside your own repository.
  • Ask what the upgrade path is before integrating, the same way you would ask about admin keys on any other system.

And for anyone shipping a program rather than consuming one: an on-chain multisig is a weekend of work and it moves you from the 49% to the 48%. Revoking the authority entirely is a much larger decision and this report is not an argument for it — 97% of the network has declined, mostly for good reasons.

What this does not show

Every number above has a boundary, and quoting one without its boundary would make this report worse than useless.

On-curve does not mean one person. It means "not behind an on-chain multisig". A key held in MPC or threshold custody — Fireblocks, Turnkey and similar — requires several parties to sign and still presents on chain as an ordinary keypair. We cannot see that from outside, and some of the 49 certainly use it. Read the figure as an upper bound on single-signature control, never as a claim that one person can act alone.

Off-curve does not mean multisig. It means program-controlled. Squads is the common case, but the check does not identify which program, and a PDA belonging to a badly governed program is not safer than a well-kept key.

25 blocks is about ten seconds of mainnet. The invoked population is therefore weighted toward high-frequency traffic — DEXs, oracles, market makers — and will miss a program used a few times a day. The top-100 cut is robust to this; the 63% figure in the "all invoked" row is not, and should not be quoted as a network-wide rate.

A ProgramData write is an upper bound on redeploys. Authority transfers and program extensions write there too, and the maximum of 468 is implausible as 468 releases. The median of 20 is corroborated by the independent age snapshot; the mean is not trustworthy and we report it only for completeness.

Finally, this is one point in time, taken from one RPC endpoint, with no cross-check against a second provider. It is a snapshot and it will drift — which is the reason to repeat it rather than to discount it.

We are deliberately not publishing the list of single-key programs. The aggregate is research; the roster is a target list. The method below is complete enough that anyone can check their own dependencies, which is the use we want it put to.

Method

The population is every account owned by BPFLoaderUpgradeab1e11111111111111111111111 with a data length of exactly 36 bytes — a 4-byte enum plus a ProgramData address, which makes the size an exact predicate for "is a program". That returns 65,119 accounts.

Each program's ProgramData address is a PDA of the program id under the loader, so it is derived locally rather than looked up. Its first 45 bytes are a discriminant, a u64 slot of the last deploy, and an Option<Pubkey> authority; those are read 100 at a time with a data slice, so the whole census costs a few hundred requests rather than tens of thousands.

Authorities are classified by ed25519 point decompression, as described above. Slot-to-time is measured, not assumed. Redeploy history comes from paging getSignaturesForAddress on the ProgramData account and counting successful transactions in the trailing year.

Aggregate results, machine-readable
upgradeability-2026-q3.csv

Download the CSV — one row per population, counts only. Published under CC BY 4.0; please link back to this page if you use it.

Run it yourself

The census is a single dependency-free Python script in the VaultLint repository, at docs/measurements/scripts/mainnet-census.py. Every parameter that could bias a result — the random seed, the block count and stride, the calibration span — is a flag whose default is the value used here, so a later run is comparable to this one.

The whole measurement
./mainnet-census.py population
./mainnet-census.py active
./mainnet-census.py headers active
./mainnet-census.py headers all 2000
./mainnet-census.py report
./mainnet-census.py churn 60

The full working notes, including the checks that were run against the results, are in the measurement record.

We intend to repeat this quarterly. The next edition is Q4 2026, and the interesting number will not be any single figure above — it will be whether the 49% moves.

Check your own program before it ships.

VaultLint is a security linter for Solana and Anchor programs. Five narrow rules, hand-written, no telemetry, no network calls unless you ask for one — and --mainnet tells you which findings are in code that is live right now.

See the rules