Two secrets with different jobs

Seed phrase storage and signing key storage are separate problems with separate answers. A phrase is a human backup for a wallet a person opens by hand, so it belongs offline, on paper or metal, and never on a machine that runs automation. A keypair file is what an unattended signer loads at start, so a process must be able to read it, which caps how well it can ever be protected.

The distinction matters because the two artefacts have very different blast radii. A seed phrase controls every account derived from it, on every derivation path, for as long as those words exist anywhere. A keypair file controls exactly one address. Operators who treat them as interchangeable end up deriving a bot key from the same phrase as a personal wallet, and that single decision converts a routine host compromise into the loss of everything the phrase ever touched.

On Solana the signing key is an ed25519 keypair, and the command line tool writes it as a JSON array of sixty-four bytes. That file is not a pointer to a secret held somewhere safer. It is the secret. Anyone who can read those bytes can sign for the address with no password, no second factor and no trace in your own logs. Judge every storage decision by one question: who can read that file, and under what conditions.

A seed phrase has exactly one safe place, and a keyboard is not it

Never type a seed phrase into a website, a bot configuration file, a support chat, a screenshot or a note that syncs to a cloud account. No legitimate service needs those words, and no genuine recovery process begins with them being typed somewhere new. Whoever reads them controls every account derived from them, permanently, and nothing in the protocol can revoke that control once it is taken. The opposite failure is equally final: destroy the only copy of a phrase or a keypair file and the accounts behind it are unreachable forever, with no desk anywhere able to restore them.

Everything else in the key hygiene runbooks assumes this split holds. Once a phrase is confined to hardware a person touches, and a raw keypair is confined to the host that runs the loop, the remaining questions become tractable: which medium holds which secret, what each medium survives, and how you replace a key without a gap in which nobody controls an account.

Where the secret can live

Every storage medium trades reachability against exposure. A secret that a program can read at three in the morning is by definition a secret that anything running as that user can read at three in the morning. The table below compares the media operators actually use, so you can place each secret deliberately rather than wherever the first tutorial put it.

Where the secret lives Readable by Survives machine loss Suitable for an unattended signer Main failure mode
Paper or metal backup in a safe Anyone with physical access to the safe Yes, it is the survival copy No, nothing can read it automatically Fire, flood, house move, or a photograph of it
Hardware wallet with a secure element Only the device, and only after a button press Yes, if the phrase backup exists separately No, every signature needs a human Lost device with no recorded backup
Plain keypair file on the operating host The process, the user account, and root No, unless it is backed up elsewhere Yes, and this is the common default Weak file permissions, backups, and shell history
Encrypted file with a passphrase given at start Whoever supplies the passphrase, then the process No, the file alone is useless without the passphrase Yes, but restarts need an operator or a stored passphrase The passphrase saved next to the file it protects
Operating system keychain or secret store The unlocked user session and anything inside it No, it is bound to the machine Yes, on a host with a real session model An unlocked session treated as an access grant
Environment variable in a process manager The process, its children, and anything reading its config No, it lives with the unit file Yes, and it is the easiest to leak Crash dumps, error reporters and process listings
Cloud note or synced password manager Every device on the sync, plus the account holder Yes, aggressively so No, never for raw signing material One account takeover exposes every synced copy

Read the fourth column first. Only three of those rows are honest candidates for a key a loop signs with, and all three have the same shape: the material ends up readable by the process. That is not a flaw you can design away, because a signer that cannot read its key cannot sign. What you can control is how narrow the readership is, how quickly you notice a change, and how little that one key is allowed to reach.

Notice also that the two media with the best survival properties, the safe and the sync, sit at opposite ends of the safety scale. Durability and secrecy pull against each other. Resolve that tension by splitting the roles rather than compromising on one artefact: the phrase for the wallet a person opens goes in the safe, and the automation keypair is generated fresh, never backed up to a synced service, and replaced rather than recovered when the host is rebuilt.

What encryption at rest actually buys

Encryption at rest is worth doing and routinely oversold. It answers a specific question: what happens when the bytes leave the machine without the machine noticing. Stolen laptops, decommissioned drives, cloud snapshots copied to a bucket, backups restored onto someone else test box, a disk image handed to a contractor. In every one of those cases an encrypted keypair file is inert and a plain one is a signing key.

The threat it stops and the threat it does not

Encryption at rest protects a key from a reader who has the storage but not the running system. It does nothing at all against a reader who has the running system. Once the passphrase has been supplied and the key material is decrypted into memory so the signer can use it, any process running as that user, any debugger attached to it, any core dump it writes and any operator with a shell can recover the key. If your model is a compromised dependency or a compromised host, encryption at rest is not the control that helps you; scope limits and rotation are.

There is also a practical cost to weigh. A passphrase supplied at start means an unattended restart cannot happen, because a crash at midnight leaves the signer waiting for a human. Most operators resolve this by storing the passphrase somewhere the machine can read it, at which point the encryption has become decoration: the disk and the key to the disk travel together. If you accept that trade knowingly, fine. What you must not do is record the result as protection you do not have.

Why an unattended signer cannot hold a hardware wallet

Hardware wallets solve a different problem well. The private key is generated inside a secure element and never leaves it, and the device requires a physical confirmation for each signature. That confirmation is the whole product. It means malware on the connected computer can request a signature but cannot obtain one, because a person has to look at the screen and press a button before anything is signed.

Automation removes the person, which removes the control. A loop that signs many transactions cannot wait on a button press, and any arrangement that makes the device sign without confirmation has thrown away the property you bought it for. Do not attempt to work around the confirmation step. Instead, give the hardware wallet the job it is suited to: holding the balance that funds the operation, and approving the deliberate, occasional transfers that move value into the hot wallets that do the work.

That gives you a clean division. Cold storage on hardware, signed by a human, holding the bulk of the value. Hot keypair files on the operating host, signed by software, holding only what a run needs. The segmentation runbook sets out how to size the second group so the loss of any one file is a nuisance rather than an event.

Multisig belongs on the treasury, not the hot path

Shared custody products such as Squads exist for approval flows, and they are the right tool for a balance that several people are accountable for. A withdrawal from the treasury requires a threshold of signers, so no single compromised device can drain it, and the approval trail is a record you can reconstruct later. For funding decisions, program upgrade authority and anything that moves a meaningful amount, that is a real improvement.

Multisig is not a replacement for a hot execution key, and it is worth being blunt about why. Automation that signs frequently cannot wait for a threshold of humans, so either the bot holds enough signing weight to act alone, which reproduces the single key you were trying to eliminate, or the run stalls. Keep the multisig where its latency is acceptable and its guarantee is meaningful: on approvals, on authority changes, and on the wallet that funds everything else.

Applied to key rotation, this arrangement is useful in a second way. If program authority sits behind a threshold rather than a single hot key, then rotating a compromised signer does not require an emergency authority transfer under pressure. The authority never lived on the machine that was breached, so the rotation is limited to replacing the execution key and re-funding it, which is a smaller and calmer job.

Storage hygiene you can audit

Hygiene rules are only useful if someone can check them without your help. Write them so each line is a yes or no question about a file, a permission or a location, not a statement of intent. Run through the list on the day a host is built, on the day a key is rotated, and on the day anyone new gets access to the machine.

  • Every automation key was generated independently, not derived from a phrase that also controls a personal wallet.
  • The keypair file is owned by the account that runs the signer, and no other user or group can read it.
  • No key material appears in the repository, in the container image, in a build artefact or in shell history.
  • No key material is sent to a log file, an error reporter, a crash handler or a metrics label.
  • No seed phrase exists in any file, note, chat, screenshot or password manager entry on any machine that runs automation.
  • Backups and snapshots of the operating host are either excluded from key paths or encrypted with a passphrase held elsewhere.
  • The account map names every live public key, its role, its cap and the date it was generated.
  • Each key has one job, and the number of accounts a single file can reach is written down rather than assumed.
  • The path to the key is configured explicitly, so no process silently falls back to a default keypair location.
  • Retired public keys stay in the map with a retirement date, and their private material is confirmed destroyed.
  • Someone other than the person who built the host can complete this list from the runbook alone.

The last line is the one that decays first. A key layout that only its author can explain is a layout that stops being maintained the moment that person is unavailable, which tends to coincide with the moment it matters. Keep the account map in the same place as the runbook and update it in the same change.

What should trigger a rotation

Calendar rotation is a habit, not a control. It has value, because it forces you to prove the procedure still works while nothing is on fire, but the rotations that protect you are the ones driven by events. Treat the following as immediate triggers rather than items for the next maintenance window.

  • Any suspicion that the key material has been read, including a dependency you no longer trust and a host you cannot fully account for.
  • Any exposure of the file itself, such as a commit, a paste, a shared screenshot, a support upload or an unencrypted backup that left the building.
  • A change in who has access to the operating host, in either direction, including the end of a contract or the removal of an administrator.
  • A machine rebuild, a migration to a new provider, or a restore from a snapshot whose chain of custody is not clean.
  • A balance movement you did not initiate, however small, on any account the key controls.

When the trigger is a suspected compromise rather than routine maintenance, rotation is not the first step and should not be. Containment comes first, because generating a new key while an attacker still holds the old one simply gives you two keys and one thief. The ordered response is set out in the incident response playbook; return to this procedure once the immediate exposure is contained.

A rotation that never leaves an account unreachable

The failure mode to design against is not a key that lives too long. It is the gap in which the old key has been discarded and the new one has not been proven, or in which value sits on an address whose authority has already moved elsewhere. The order below removes that gap by making destruction the last action rather than the first, and by proving the replacement signs before anything of consequence depends on it.

  1. Generate the replacement offline. Create the new keypair on a machine that is not running automation and is not exposed while you work. Write it to a path readable only by the operator account, and do not copy it anywhere that syncs.
  2. Register the public key before it holds value. Add the new address to the account map with its role, its intended cap and its creation date while the balance is still zero. An address that appears in the map only after it is funded is an address nobody reviewed.
  3. Fund minimally and prove it signs. Send the smallest useful amount, then have the new key sign one transaction of no consequence and confirm that signature on chain. A key that has not signed is a key you are assuming works.
  4. Move authority before you move value. Transfer program authority, update authority and any delegated role from the old address to the new one first. Control should follow the key, so that if you stop midway the balance is still held by an address whose authority is settled.
  5. Revoke delegations held by the old accounts. Revoke every outstanding token delegate on accounts the old key owns. A delegate that survives the sweep is a standing permission to move a balance you thought you had secured.
  6. Migrate balances in a fixed order. Move token balances first, close each emptied token account so its rent reserve is returned, and sweep native SOL last, because the old key is still paying the fee for every one of these transactions.
  7. Verify the old key controls nothing. Confirm a zero native balance, no remaining token accounts, no authority roles and no active delegates. Until each of those is checked, the rotation is unfinished no matter how the new key is behaving.
  8. Destroy the secret and keep the public key. Only after verification passes, destroy every copy of the old private material, then record the retirement date against the old public key in the account map so the history stays readable.
rotation commands
solana-keygen new -o <path/to/new-signer.json>
solana-keygen pubkey <path/to/new-signer.json>
solana address -k <path/to/new-signer.json>
solana balance <OLD_PUBKEY> --url <RPC_ENDPOINT>
spl-token revoke <TOKEN_ACCOUNT> --owner <path/to/old-signer.json>
spl-token close <TOKEN_MINT> --owner <path/to/old-signer.json>

Generation writes a phrase to the screen as well as a file. Decide in advance what happens to it: for an automation key that will be replaced rather than recovered, the phrase is an extra copy of a secret you do not want extra copies of, and the safest handling is not to record it at all. The command reference for these tools is maintained in the Agave client documentation, and the delegate model behind revoke is described in the SPL Token documentation.

Cadence, and the key count it produces

Rotation has a running cost, and it is worth sizing before you commit to a schedule you will quietly abandon. Two things accumulate: the lamports locked in newly created accounts, and the number of public keys your map has to explain. Neither is large, but both grow linearly with the fleet, and an operator who has not counted tends to discover the total at the worst moment.

Worked example: what a cadence costs over two years, illustrative

Take an illustrative fleet of 40 signing wallets rotated every 90 days, which is roughly 4 rotations a year and 8 over two years. Choose your own cadence; the arithmetic is what matters, not the interval. Each new system account must hold the rent exempt minimum of 890,880 lamports to exist, so one rotation across the fleet locks 40 multiplied by 890,880, which is 35,635,200 lamports, or 0.0356352 SOL. Eight rotations create 320 new accounts, which is 320 multiplied by 890,880, or 285,081,600 lamports, equal to 0.2850816 SOL. That reserve is returned when each account is closed, so the standing cost is only the reserve of the accounts you leave open. On top of that, every migration transaction costs the base fee of 5,000 lamports per signature whether it succeeds or fails. The key count grows faster than the money: with the old public keys retained for audit, the map holds 40 multiplied by 9, or 360 entries, of which only 40 have live private material and 320 are retired records.

That ratio is the real argument for keeping retired addresses in the map rather than deleting them. Nine out of ten entries will eventually be historical, and an explorer view of an old address is only interpretable if you can say what that key was for and when it stopped being used. Deleting the row saves nothing and costs you the ability to read your own transaction history a year later.

Retiring a key without erasing the record

Destruction and retirement are different acts. Destruction removes the private material so nobody can sign with it again. Retirement records that the address is no longer in service, when it left service, and what it was doing while it was live. Do both, in that order, and never let the second be a side effect of the first.

Practical destruction depends on where the material lived. A file on a host is deleted from the host, from every backup that captured it and from any snapshot retained by the provider, and the sensible assumption is that a snapshot you cannot enumerate still contains it, which is another reason to treat rotation as replacement rather than cleanup. A phrase on paper is shredded or burned. A key held in a keychain is removed from the keychain rather than merely unreferenced by the application that used it.

Retirement is a row in the account map: address, role, created, retired, reason, and the successor address. Written this way, the map answers the question you will actually be asked, which is not what happened but which key held that balance in a given month. Keep the map alongside the wallet threat model in the wallet security runbook, review it on the same schedule as your rotations, and treat any address you cannot explain as an open item until someone does explain it.

One closing point about pressure. Every step in this procedure is easy on a quiet afternoon and unpleasant during an incident, which is exactly why the routine rotation is worth doing even when nothing is wrong. The rotation you complete calmly is the rehearsal for the one you will have to complete badly, at speed, with a balance moving in a direction you did not choose.

Questions the desk gets asked

Is a keypair file the same thing as a seed phrase?

No. A seed phrase is a human backup that can regenerate every account derived from it along standard derivation paths. A keypair file is the raw private material for a single address, stored as a JSON array of sixty-four bytes. Compromising a keypair file costs you one account. Compromising a phrase costs you everything that phrase ever derived.

Can a bot sign with a hardware wallet?

Not in an unattended loop. A hardware wallet is designed so that each signature needs a physical confirmation on the device, which is exactly the property that makes it safe and exactly the property that makes it useless for automation that signs while nobody is watching. Use it for the funding wallet a person operates by hand.

How often should a signing key be rotated?

Pick a cadence you can actually complete and treat it as a floor rather than a ceiling. Event driven rotation matters more than calendar rotation: rotate immediately after a suspected exposure, a machine rebuild, a dependency you no longer trust, or the departure of anyone who had access to the operating host.

Does encrypting the keypair file protect a running bot?

It protects the file, not the process. Encryption at rest defeats someone who takes the disk, the backup or the snapshot. Once the passphrase has been supplied and the key is decrypted in memory, anything able to read that process or run as that user can obtain the key regardless of how it was stored.

Should the bot key come from the same seed phrase as my personal wallet?

No, and this is the single most expensive shortcut in the category. Deriving an automation key from a personal seed means the phrase that protects your savings is now the ancestor of a key sitting on an internet facing machine. Generate automation keys independently so their blast radius stops at the account they control.

What happens if I destroy the only copy of a key?

The accounts it controls become permanently unreachable. There is no recovery desk, no reset flow and no protocol mechanism to reassign control of an address. This is why the rotation order ends with destruction rather than starting with it, and why verification that the old key holds nothing has to pass first.