solana-deploy — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited solana-deploy (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
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.
Trigger this skill when the user says any of:
<pubkey>"solana program deploy keeps failing / hangs / runs out of SOL"Error: Data writes to account failed / Blockhash not found / transaction expired"<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).
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 balanceBefore any destructive command, confirm cluster + signer:
solana config get # check RPC URL and keypair path
solana address # confirm signerIf solana config get shows localhost or devnet while the user said "mainnet," stop and ask.
solana-verifyBuild 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.soSave that hash — you'll compare it after deploy. The helper scripts/verify-build.sh wraps this with Docker + Cargo.lock pre-flight checks.
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.
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 elsewrite-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-rpcFlag 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.buffer.json resumes from the last successfully written chunk. Do not generate a new buffer just because the first try failed.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)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-rpcUpgrade (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-rpcOn success, the buffer is consumed and its rent rolls into the program data account — no buffer cleanup needed.
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.
| 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). |
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 mainnetIf 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.
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.
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.
# 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-warningSOL 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 successA 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.
solana program write-buffer without --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.--with-compute-unit-price 0 gets you silently dropped during any stress event (memecoin launches, NFT mints, IPO days). Always set at least 10,000 micro-lamports; query Helius getPriorityFeeEstimate for live numbers and use 1.5x the "High" recommendation.--final set 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. See references/authority-management.md.anchor idl init will silently fail or partially write. Use anchor idl write-buffer + anchor idl set-buffer, mirroring the program buffer flow.anchor build or cargo build-sbf between solana-verify build and 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 through solana-verify build.solana-verify requires Cargo.lock in the repo root and uses it to pin every dependency. If Cargo.lock is gitignored (Rust library default), the remote build resolves different deps and hashes differ. Commit `Cargo.lock` for any program crate.solana config set --url devnet is sticky across shells. Confirm with solana config get immediately before solana 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.deploy --buffer fails with signer authority does not match. Set this explicitly with solana program set-buffer-authority (Step 5) before the final deploy.api.mainnet-beta.solana.com rate-limits aggressively; you'll see far more blockhash failures than necessary. Any free tier from Helius / QuickNode / Triton handles deploys reliably.scripts/verify-build.sh — wraps solana-verify build with Docker + Cargo.lock pre-flight checks, prints hash for later comparison.scripts/deploy-with-retries.sh — write-buffer with bounded retries + auto-resume on partial writes, then deploy --buffer, with orphan recovery instructions on failure.references/authority-management.md — single-key vs. multisig vs. immutable decision guide; lost-authority recovery checklist.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.