Solana deploy
Use this skill when the user wants to deploy or upgrade a Solana program from devnet to mainnet — verifiable builds, buffer accounts, priority fees, upgrade authority management, IDL upload, program close.From its SKILL.md
npx -y skills add locallaunchsc-cloud/solana-dev-skills --skill solana-deployAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
16.4 KB, ~4.2k tokens by cl100k_base, as published. Nobody here has run it
solana-deploy
Overview
Mainnet deploys are the part of Solana development where things actually break. Program binaries are tens to hundreds of KB, the BPF loader chunks them into ~1 KB transactions, each chunk needs its own signature and a non-expired blockhash, and during congestion validators silently drop unprioritized traffic. A naive solana program deploy program.so against mainnet typically dies somewhere in the middle and leaves SOL stuck in a half-written buffer account that the user doesn't know about.
This skill is the safe pipeline: build deterministically with solana-verify, write to a named buffer with bounded retries, deploy from the buffer (a single small transaction), pay competitive priority fees, transfer authority to a multisig, and submit the verifiable build for public attestation. Every step is recoverable; every failure mode has a known fix.
When to use this skill
Trigger this skill when the user says any of:
- "Deploy my Solana program to mainnet" / "ship to mainnet"
- "Upgrade the program at
<pubkey>" - "My
solana program deploykeeps failing / hangs / runs out of SOL" - "I'm seeing
Error: Data writes to account failed/Blockhash not found/transaction expired" - "Make my program verifiable" / "get the verified-build checkmark"
- "Move upgrade authority to a Squads multisig" / "make it immutable"
- "Close my program / buffer and recover SOL"
- "I have an orphaned buffer at
<pubkey>— how do I recover it?"
Do not use this skill for: writing program code (separate Anchor/program-design concern), localnet/devnet smoke tests where failure costs nothing, or SPL token deploys (those aren't BPF programs).
Prerequisites
Verified versions used throughout this skill. Pin these; do not float to latest.
# 1. Agave CLI (the maintained Solana validator + CLI fork from Anza).
# Pin to a known-good 2.x or 3.x release. As of this skill: agave 3.0.10.
# Confirm the current stable tag at https://github.com/anza-xyz/agave/releases
sh -c "$(curl -sSfL https://release.anza.xyz/v3.0.10/install)"
agave-install update
solana --version # → solana-cli 3.0.10 (2.1.x is also fine if you've pinned older)
# 2. Anchor (only if the program uses Anchor; 0.30+ or 1.0+ required for IDL v2 + correct buffer handling)
avm install 0.31.0 && avm use 0.31.0
anchor --version
# 3. solana-verify (deterministic builds + OtterSec remote submission)
cargo install solana-verify --locked --version 0.4.15
solana-verify --version
# 4. Docker — REQUIRED by `solana-verify build`. Must be running.
docker --version && docker info >/dev/null
# 5. Mainnet RPC that does not rate-limit deploys. Public api.mainnet-beta.solana.com
# will fail under any real load. Use Helius, Triton, QuickNode, or your own node.
solana config set --url https://mainnet.helius-rpc.com/?api-key=<KEY>
# 6. SOL in the deploy keypair (see Step 12 for sizing). Rule of thumb:
# 6-8 SOL liquid for a typical 250 KB program. Buffer rent is refunded on success.
solana balance
Before any destructive command, confirm cluster + signer:
solana config get # check RPC URL and keypair path
solana address # confirm signer
If solana config get shows localhost or devnet while the user said "mainnet," stop and ask.
Workflow
Step 1 — Verifiable build with solana-verify
Build inside Docker so the resulting .so is bit-for-bit reproducible. Never run anchor build or cargo build-sbf after this — the host toolchain will produce a different hash and break verification.
# Single-program workspace:
solana-verify build
# Multi-program workspace — name the lib explicitly:
solana-verify build --library-name my_program
# Output lands in target/deploy/<lib>.so. Print the hash:
solana-verify get-executable-hash target/deploy/my_program.so
Save that hash — you'll compare it after deploy. The helper scripts/verify-build.sh wraps this with Docker + Cargo.lock pre-flight checks.
Step 2 — Pre-flight: size, rent, balance, cluster
SO=target/deploy/my_program.so
SIZE=$(wc -c < "$SO")
# Programs are stored at 2x size to leave room for upgrades.
echo "Program size: $SIZE bytes"
solana rent $((SIZE * 2)) # prints rent-exempt minimum in lamports + SOL
solana balance
solana config get | grep 'RPC URL'
If balance is below rent + 0.5 SOL, top up. SOL spent on a failed deploy is recoverable, but only if you can identify the buffer pubkey — which is why Step 3 matters.
Step 3 — Generate a named buffer keypair (deterministic, recoverable)
A named buffer keypair is the single most important habit in this skill. If write-buffer dies halfway, you can resume; if you abandon it, you can close it to recover SOL. With an ephemeral buffer, both are impossible.
solana-keygen new --no-bip39-passphrase --silent -o buffer.json --force
BUFFER=$(solana address -k buffer.json)
echo "Buffer pubkey: $BUFFER" # write this down NOW, before anything else
Step 4 — write-buffer with retries (the long step)
This is where ~95% of deploy failures happen. The helper scripts/deploy-with-retries.sh runs this loop; the bare command:
solana program write-buffer "$SO" \
--buffer buffer.json \
--buffer-authority ~/.config/solana/deploy.json \
--with-compute-unit-price 100000 \
--max-sign-attempts 1000 \
--use-rpc
Flag notes:
--with-compute-unit-price 100000— 100k micro-lamports per CU. Adjust for live congestion (Step 7).--max-sign-attempts 1000— how many times the CLI re-signs with a fresh blockhash when chunks don't land. The default (5) is far too low for mainnet.--use-rpc— send through your configured RPC instead of validator TPU. Required on most paid RPCs.- Re-running the same command with the same
buffer.jsonresumes from the last successfully written chunk. Do not generate a new buffer just because the first try failed.
Step 5 — Set buffer authority to the deploy signer
Default behavior usually sets buffer authority to the current keypair, but be explicit when a hardware wallet or multisig will sign the final deploy. The buffer authority must equal the signer of deploy --buffer in Step 6.
solana program set-buffer-authority "$BUFFER" \
--new-buffer-authority $(solana address)
Step 6 — Deploy from buffer
This is a single, small transaction. It almost always lands first try.
Initial deploy (new program ID — --program-id is a keypair file):
solana program deploy \
--buffer "$BUFFER" \
--program-id ./target/deploy/my_program-keypair.json \
--upgrade-authority ~/.config/solana/deploy.json \
--with-compute-unit-price 100000 \
--max-sign-attempts 50 \
--use-rpc
Upgrade (existing program — --program-id is the pubkey, no keypair):
solana program deploy \
--buffer "$BUFFER" \
--program-id <EXISTING_PROGRAM_PUBKEY> \
--upgrade-authority ~/.config/solana/deploy.json \
--with-compute-unit-price 100000 \
--use-rpc
On success, the buffer is consumed and its rent rolls into the program data account — no buffer cleanup needed.
Step 7 — Priority fees, calibrated
50k–500k micro-lamports/CU is the practical range. Default --with-compute-unit-price 0 gets you silently dropped during any mainnet stress event. Always query a live estimate before sending:
curl https://mainnet.helius-rpc.com/?api-key=$HELIUS_KEY \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"getPriorityFeeEstimate",
"params":[{"accountKeys":["BPFLoaderUpgradeab1e11111111111111111111111"],
"options":{"priorityLevel":"High"}}]}' \
| jq -r '.result.priorityFeeEstimate'
Use 1.5x the "High" recommendation for the deploy. If congestion is extreme (>1M micro-lamports/CU), wait an hour rather than pay — deploys are not time-sensitive.
Step 8 — Retry patterns on failure
| Symptom | Cause | Fix |
|---|---|---|
Error: Data writes to account failed after N chunks | Blockhash expired mid-batch | Re-run the same write-buffer command — it resumes from the last written offset because buffer.json is reused. |
| Hangs at "Sending transactions..." with no progress | RPC dropping txs / congestion | Bump --with-compute-unit-price 2–5x, keep --use-rpc, switch to a paid RPC if you're on the public endpoint. |
Account already in use on deploy | Program ID keypair was used previously | Pick a fresh program-id keypair, or use the existing program ID via the upgrade flow. |
insufficient funds for rent | Underestimated rent | Top up; solana program show <id> reveals the gap. |
signer authority does not match on deploy --buffer | Buffer authority ≠ deploy signer | Re-run Step 5 with the correct authority. |
Stale blockhash from --use-rpc | RPC node behind tip | Switch RPC endpoints (Helius/Triton/QuickNode rotate well). |
Step 9 — Upload IDL (Anchor only)
The IDL is published to a PDA so frontends and explorers can decode instructions. With Anchor 0.30+:
# Initial publish:
anchor idl init <PROGRAM_ID> \
--filepath target/idl/my_program.json \
--provider.cluster mainnet
# Subsequent updates:
anchor idl upgrade <PROGRAM_ID> \
--filepath target/idl/my_program.json \
--provider.cluster mainnet
If the IDL is >10 KB (common for Anchor 0.30+ which inlines doc comments), use the buffer flow:
anchor idl write-buffer <PROGRAM_ID> --filepath target/idl/my_program.json
anchor idl set-buffer <PROGRAM_ID> --buffer <IDL_BUFFER_PUBKEY>
Anchor 0.30+ auto-closes the IDL buffer after a successful set-buffer and refunds its rent.
Step 10 — Transfer upgrade authority to Squads (or multisig of choice)
Single-key upgrade authority on a mainnet program is the most common high-severity audit finding. Move it as soon as the deploy is verified working.
# SQUADS_VAULT_PDA = the vault PDA of your Squads multisig
solana program set-upgrade-authority <PROGRAM_ID> \
--new-upgrade-authority <SQUADS_VAULT_PDA> \
--skip-new-upgrade-authority-signer-check
--skip-new-upgrade-authority-signer-check is required because the multisig PDA has no private key and cannot sign acceptance. Squads accepts ownership on-chain via the loader. Confirm with solana program show <PROGRAM_ID> before celebrating. See references/authority-management.md for the multisig vs. immutable decision tree.
Step 11 — Submit verifiable build to OtterSec
This is what gives users the verified-build checkmark in Solscan / SolanaFM / Solana Explorer.
# Local verification — confirms on-chain bytes match your repo at the given commit.
solana-verify verify-from-repo \
-u mainnet-beta \
--program-id <PROGRAM_ID> \
https://github.com/<owner>/<repo> \
--commit-hash $(git rev-parse HEAD) \
--library-name my_program \
--mount-path .
# Remote submission — queues a fresh build by OtterSec workers and posts the result on-chain.
solana-verify remote submit-job \
--program-id <PROGRAM_ID> \
--uploader $(solana address)
The remote worker rebuilds in the same Docker image; status shows up in explorers within ~10 minutes. Also submit a PR to the Solana Verified Builds registry for higher-visibility listing.
Step 12 — Close program / buffer to recover SOL
# Recover SOL from a stuck/abandoned buffer:
solana program close <BUFFER_PUBKEY> --bypass-warning
# Close every buffer owned by the current keypair (be careful — also matches old ones):
solana program show --buffers --buffer-authority $(solana address)
solana program close --buffers --bypass-warning
# Close a deployed program permanently (cannot be undone, program ID burned forever):
solana program close <PROGRAM_ID> --bypass-warning
SOL estimation rule of thumb: for any program of size S bytes,
deploy_cost_SOL ≈ (S * 2 * 0.00000696) + 0.5 # initial deploy
upgrade_cost_SOL ≈ 0.5 # buffer rent only, refunded on success
A 200 KB program: ~3 SOL initial, ~0.5 SOL per upgrade. A 300 KB program: ~4.5 SOL initial. Buffer rent is refunded when the deploy consumes the buffer, so steady-state cost across many upgrades is small. Closing a program returns the rent portion to the close authority — useful for deprecation cleanup.
Common pitfalls
-
Orphaned buffer account. The most expensive bug. You ran
solana program write-bufferwithout--buffer buffer.json, the deploy died, and now there's a multi-SOL account no one tracks. Always use a named buffer keypair (Step 3). Find existing ones:solana program show --buffers --buffer-authority $(solana address). Recover:solana program close <PUBKEY> --bypass-warning. -
Priority fee too low during congestion. Default
--with-compute-unit-price 0gets you silently dropped during any stress event (memecoin launches, NFT mints, IPO days). Always set at least 10,000 micro-lamports; query HeliusgetPriorityFeeEstimatefor live numbers and use 1.5x the "High" recommendation. -
Lost upgrade authority. Three flavors, all unrecoverable: (a) keypair file deleted with no backup, (b)
--finalset on mainnet "to test the command," (c) authority transferred to a typo'd pubkey. The BPF Upgradeable Loader has no recovery oracle — the program is frozen at its current bytecode forever. Seereferences/authority-management.md. -
IDL larger than transaction limit. Anchor 0.30+ inlines doc comments, easily pushing IDLs past the 1232-byte tx limit.
anchor idl initwill silently fail or partially write. Useanchor idl write-buffer+anchor idl set-buffer, mirroring the program buffer flow. -
Non-reproducible build. Running
anchor buildorcargo build-sbfbetweensolana-verify buildand verification submission. The host toolchain's rustc / solana-sdk versions almost certainly don't match the Docker image, so OtterSec verification fails with a hash mismatch. Only ever build throughsolana-verify build. -
Cargo.lockdrift.solana-verifyrequiresCargo.lockin the repo root and uses it to pin every dependency. IfCargo.lockis gitignored (Rust library default), the remote build resolves different deps and hashes differ. CommitCargo.lockfor any program crate. -
Wrong cluster.
solana config set --url devnetis sticky across shells. Confirm withsolana config getimmediately beforesolana program deploy. Deploying mainnet code to devnet wastes a keypair slot; deploying devnet code to mainnet wastes real money. A shell prompt that shows the current cluster is worth setting up. -
Buffer authority ≠ deploy signer. When the upgrade authority is a hardware wallet or multisig, the buffer must also be owned by that key — otherwise
deploy --bufferfails withsigner authority does not match. Set this explicitly withsolana program set-buffer-authority(Step 5) before the final deploy. -
Free public RPC for the actual deploy.
api.mainnet-beta.solana.comrate-limits aggressively; you'll see far more blockhash failures than necessary. Any free tier from Helius / QuickNode / Triton handles deploys reliably.
References
scripts/verify-build.sh— wrapssolana-verify buildwith Docker + Cargo.lock pre-flight checks, prints hash for later comparison.scripts/deploy-with-retries.sh—write-bufferwith bounded retries + auto-resume on partial writes, thendeploy --buffer, with orphan recovery instructions on failure.references/authority-management.md— single-key vs. multisig vs. immutable decision guide; lost-authority recovery checklist.- Solana docs — Deploying Programs
- Solana docs — Verified Builds
- Agave CLI — Deploy a Program
- Ellipsis-Labs/solana-verifiable-build / solana-foundation fork
- Helius — How to Land Transactions on Solana
- Helius — Priority Fee API
- Squads — Managing Program Upgrades with a Multisig
What ships with it: 3 files
20.9 KB alongside SKILL.md, 2 of them executable
references/
- authority-management.md6.9 KB
scripts/
- deploy-with-retries.shruns9.7 KB
- verify-build.shruns4.3 KB