You're about to run forge script --broadcast. The command needs a private key. The options that come to mind first all share the same problem: paste it into the terminal and it ends up in .bash_history or .zsh_history. Put it in .env and it's one accidental git add away from the repo. Hardcode it in the deploy script and it's in version history the moment the file is committed. These aren't theoretical risks — they're how keys get exposed.
There is a better way built directly into Foundry.
Using Foundry's encrypted keystore
Import your private key into an encrypted keystore:
cast wallet import deployer --interactive
The --interactive flag prompts for your private key and a password. Foundry stores the key encrypted at ~/.foundry/keystores/deployer. Nothing touches shell history. The name deployer is arbitrary — use whatever you'll recognise.
Before running a deploy, confirm the import worked:
cast wallet list
This lists all keystores by name. If deployer appears, the import succeeded. Two seconds, one less thing to debug mid-deploy.
Now run the deploy referencing the keystore by name:
forge script script/Deploy.s.sol \
--rpc-url $RPC_URL \
--account deployer \
--broadcast \
--verify
--account deployer tells Foundry which keystore to use. It prompts for the password at runtime. The private key is not in the command, not in .env, not anywhere in the repository. The password prompt is the only moment the key decrypts, and it never leaves your machine.
What this protects against: shell history logs every command you run — .bash_history, .zsh_history, your terminal emulator's scrollback. .env files get committed. Command arguments show up in process listings. The key is encrypted at rest and decrypted only at deploy time. There is no passive exposure surface.
One note on the password
The password matters. A weak password on an encrypted keystore holding a deploy key for a contract with real funds is not meaningfully safer than .env. Use a password manager. The keystore adds a layer; the quality of your password determines what that layer is worth.
This is now the default for every remaining week of this series. First-time setup took about ninety seconds. The instinct — "I don't feel okay putting it in the terminal" — was right; the tooling already had the answer.
The build this came from: Week 1: Base — 56/60
Scoring methodology for the series: How I'm Scoring the Chains
Top comments (0)