Skip to main content
TLDR:
  • A program can hold another program’s upgrade authority through a PDA and upgrade it with a cross-program invocation (CPI) to the upgradeable loader. Multisig and governance programs use this to put upgrades behind their own approval rules.
  • Through CPI, a program can call only the loader’s Upgrade, SetAuthority, SetAuthorityChecked, and Close instructions. Writing a buffer, deploying a new program, and extending a program stay top-level instructions that your wallet sends.
  • ExtendProgram needs no authority signature, so your wallet can grow a PDA-controlled program before a larger build goes in. Each extension adds at least 10,240 bytes.
  • A buffer handed to the PDA can only be closed by the PDA. Give the upgrading program a close instruction, or the buffer’s rent stays locked.
  • Once SIMD-0500 activates, the buffer has to hold an sBPFv3 build, whoever signs the upgrade. See Solana: Rebuild your programs for sBPFv3.

Main article

Every program deployed with the upgradeable loader, BPFLoaderUpgradeab1e11111111111111111111111, has an upgrade authority: the one address that can replace its code. The authority does not have to be a wallet. Set it to a program derived address (PDA), and only the program that derives the PDA can sign for it, which it does with invoke_signed. That program decides when an upgrade goes through. This guide builds an upgrader program that holds a PDA authority, hands a second program to it, and upgrades that program to a larger build through CPI. The examples use Devnet through a Chainstack endpoint.
To follow along, you need a Solana node endpoint and the Agave 4.3.0 toolchain. See Solana tooling and Install the toolchain.

What a program can call on the loader

The runtime filters CPIs to the upgradeable loader by instruction: A rejected CPI fails the calling program before the loader runs:
So the work splits in two. Your wallet uploads the new build to a buffer and extends the program when the build grows. The upgrader program signs the upgrade itself.

Write the upgrader program

The upgrader derives its authority PDA from the seed upgrade_authority and the admin’s address. Each admin gets a separate PDA, and the upgrader signs with it only when that admin signs the transaction. It has three instructions:
  • 0 — upgrade the target program from a buffer that the PDA owns.
  • 1 — hand the upgrade authority back to the admin wallet.
  • 2 — close a buffer that the PDA owns and return its lamports to the admin.
The program writes each loader instruction as its 4-byte tag, a little-endian u32. The instruction builders in solana-loader-v3-interface need that crate’s wincode feature, which takes this program from 17 KB to about 50 KB and raises the rent to deploy it about threefold. Build it for sBPFv3:

Write the program to upgrade

The target is a minimal program in a crate named greeter. Version 1 logs a fixed message:
Version 2 formats its output, which pulls in more code and makes the build larger:
src/lib.rs
Build version 1 with cargo build-sbf --arch v3 and keep the version 2 source for later.

Deploy both programs

Deploy the upgrader and version 1 of the target:

Hand the upgrade authority to the PDA

Derive the PDA for your wallet:
Make it the target’s upgrade authority. By default, the CLI requires the new authority to sign, which a PDA cannot do, so skip that check:
From here on, your wallet can no longer upgrade the target directly. Only the upgrader can.

Prepare the new build

Build version 2 and upload it to a buffer, then give the buffer to the same PDA. The loader requires the buffer and the program to have the same authority:

Extend the program when the build grows

A program’s ProgramData account holds its current build and nothing more: after the version 1 deployment, solana program show reports a Data Length of 9,152 bytes, the size of that .so file. Version 2 is 14,592 bytes. An upgrade to a larger build fails with ProgramData account not large enough until you extend the program. ExtendProgram cannot go through CPI, and it does not need the upgrade authority to sign, so your wallet sends it. Every extension adds at least 10,240 bytes, the minimum set by SIMD-0431:
The loader treats an extension like a deployment in that slot. An upgrade in the same slot fails with Program was deployed in this block already, so wait for the next slot before you upgrade.

Upgrade through the upgrader

This client sends the upgrader’s instructions with Solana Kit. It derives the authority PDA and the target’s ProgramData address, then passes the accounts each instruction reads:
upgrade.mjs
Run the upgrade with the buffer address:
The upgrade keeps the program ID, closes the buffer, and returns the buffer’s lamports to the admin. Calling the target with the call.mjs script from Call the program now runs version 2:

Close buffers that the PDA owns

A buffer that you give to the PDA and never use for an upgrade still holds rent. Your wallet cannot close it, and solana program close fails with Buffer account authority Some(<PDA>) does not match Some(<wallet>). Close it through the upgrader instead:

Hand the authority back

To return the target to direct wallet control, call the hand-back instruction. It uses SetAuthorityChecked, which requires the new authority to sign. The admin signs the transaction, so the authority cannot go to an address nobody controls:

Errors

Before you use this in production

  • Gate the upgrade instruction. The example accepts any upgrade the admin signs, while a production upgrader checks multisig approvals, a timelock, or a governance vote before it calls Upgrade.
  • Check what the buffer holds. The upgrader installs whatever bytes the buffer contains, so compare the buffer’s hash with your verified build, for example with solana-verify get-buffer-hash.
  • Protect the upgrader itself. Whoever controls the upgrader’s own upgrade authority can replace its rules, so put that authority behind the same controls or make the upgrader immutable.

Additional resources

Last modified on October 7, 2026