Skip to content

Managing config values and secrets safely

Hardhat projects often need values that shouldn’t be committed to shared repositories, like private keys or API keys.

Hardhat 3 offers Configuration Variables to handle this in a secure way, either encrypted (using hardhat-keystore) or not, depending on what you need.

By default, Configuration Variables are read from environment variables, but plugins can define alternative ways to obtain their values.

This guide covers how to use Configuration Variables and the hardhat-keystore plugin to securely store sensitive values.

A common example of a value that you don’t want to hardcode is an RPC URL that includes an API key, such as one from Alchemy or Infura:

hardhat.config.ts
import { defineConfig } from "hardhat/config";
export default defineConfig({
3 collapsed lines
solidity: {
version: "0.8.28",
},
networks: {
sepolia: {
type: "http",
url: "https://eth-sepolia.g.alchemy.com/v2/ABC123...",
},
},
});

Instead of this, you can call the configVariable function to get the value at runtime:

hardhat.config.ts
import { configVariable, defineConfig } from "hardhat/config";
export default defineConfig({
3 collapsed lines
solidity: {
version: "0.8.28",
},
networks: {
sepolia: {
type: "http",
url: configVariable("SEPOLIA_RPC_URL"),
},
},
});

In the snippet above, Hardhat will look for an environment variable named SEPOLIA_RPC_URL when it needs the value. You can define it inline when running a task:

Terminal window
SEPOLIA_RPC_URL='https://eth-sepolia.g.alchemy.com/v2/ABC123' npx hardhat run ./my-script.ts --network sepolia

Configuration Variables are lazy, meaning that they’re only resolved when actually needed. This allows you to use Hardhat without having to define all of them upfront.

You can provide a second argument to configVariable called format. This is a template string where all instances of {variable} get replaced with the actual config variable value.

For example, instead of storing an entire URL, you can store just the API key part.

hardhat.config.ts
import { configVariable, defineConfig } from "hardhat/config";
export default defineConfig({
3 collapsed lines
solidity: {
version: "0.8.28",
},
networks: {
mainnet: {
type: "http",
url: configVariable(
"ALCHEMY_API_KEY",
"https://eth-mainnet.g.alchemy.com/v2/{variable}",
),
},
sepolia: {
type: "http",
url: configVariable(
"ALCHEMY_API_KEY",
"https://eth-sepolia.g.alchemy.com/v2/{variable}",
),
},
},
});

You can then run it like this:

Terminal window
ALCHEMY_API_KEY=foo npx hardhat run ./my-script.ts --network sepolia

Managing secrets with the hardhat-keystore plugin

Section titled “Managing secrets with the hardhat-keystore plugin”

Resolving Configuration Variables from environment variables isn’t safe (due to command history storage, among other things) nor convenient. The hardhat-keystore plugin lets you store sensitive values in an encrypted file and use them as Configuration Variables. This way, you don’t have to type them every time or commit them to disk in plain text, where they can be stolen.

If you are using a Hardhat Toolbox or created a sample project using Hardhat 3, you already have the plugin installed.

Otherwise, check out the hardhat-keystore documentation to install it.

Use the keystore set task to store an encrypted secret:

Terminal window
npx hardhat keystore set SEPOLIA_RPC_URL

When you run this task for the first time, you’ll be prompted to create a password for your keystore. After that, you’ll need to enter that password to confirm the operation every time you add a new value. The secret is then stored encrypted in a file in your home directory.

Once a value is stored in the keystore, you can use it in your configuration:

hardhat.config.ts
import { configVariable, defineConfig } from "hardhat/config";
export default defineConfig({
3 collapsed lines
solidity: {
version: "0.8.28",
},
networks: {
sepolia: {
type: "http",
url: configVariable("SEPOLIA_RPC_URL"),
},
},
});

Hardhat will prompt you to enter the password when the configuration variable is needed.

To manage your keystore, the plugin provides the following self-explanatory tasks:

Terminal window
npx hardhat keystore list
npx hardhat keystore get <key>
npx hardhat keystore delete <key>
npx hardhat keystore change-password
npx hardhat keystore path

Improving UX when using keystore values during development

Section titled “Improving UX when using keystore values during development”

To avoid repeatedly writing the keystore password when working locally with values that aren’t sensitive, you can use the Development Keystore. This is a separate keystore that doesn’t require a password when accessing values, allowing you to keep the keystore workflow even when security isn’t critical.

To use it, just store the values with the --dev flag:

Terminal window
npx hardhat keystore set --dev <key>

To learn more about how Configuration Variables work, you can read their explanation article.