Hardhat Setup Skill
This skill provides templates, scripts, and best practices for setting up Hardhat-based Solidity projects.
When to Use
Use this skill when:
- Initializing a new Hardhat project
- Adding Hardhat to an existing Solidity codebase
- Configuring Hardhat settings (networks, plugins, etc.)
- Setting up Hardhat in a hybrid Foundry/Hardhat project
- Updating Hardhat configuration
Prerequisites: Node.js and npm/yarn/pnpm must be installed
Integration with Framework Detection
Before using this skill, reference the framework-detection skill to:
- Check if Hardhat is already configured
- Determine if this is a hybrid setup
- Avoid overwriting existing configuration
Quick Setup
Basic Initialization
# Initialize new Hardhat project
npm init -y
npm install --save-dev hardhat
npx hardhat init
# Or with yarn
yarn init -y
yarn add --dev hardhat
npx hardhat init
Project Structure
Hardhat creates this structure:
project/
├── hardhat.config.js # Configuration
├── .env # Environment variables
├── package.json # Dependencies
├── contracts/ # Contract source files
│ └── Lock.sol # Example contract
├── test/ # Test files
│ └── Lock.js # Example test
├── scripts/ # Deployment scripts
│ └── deploy.js # Example script
└── ignition/ # Hardhat Ignition (deployment modules)
Configuration Templates
hardhat.config.js
See ./templates/hardhat.config.js for JavaScript configuration template.
Key Configuration Sections:
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();
module.exports = {
solidity: {
version: "0.8.30",
settings: {
optimizer: {
enabled: true,
runs: 200
}
}
},
networks: {
hardhat: {},
sepolia: {
url: process.env.SEPOLIA_RPC_URL || "",
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : []
}
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY
}
};
hardhat.config.ts
See ./templates/hardhat.config.ts for TypeScript configuration template.
TypeScript Configuration:
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
import "dotenv/config";
const config: HardhatUserConfig = {
solidity: "0.8.30",
networks: {
sepolia: {
url: process.env.SEPOLIA_RPC_URL || "",
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : []
}
}
};
export default config;
Environment Variables
See ./templates/.env.example for complete environment variable template.
Essential Variables:
# RPC URLs
MAINNET_RPC_URL=
SEPOLIA_RPC_URL=
# Private Keys (NEVER commit actual keys)
PRIVATE_KEY=
# Etherscan API Keys
ETHERSCAN_API_KEY=
# Gas Settings
GAS_PRICE=
Essential Plugins
Hardhat Toolbox (Recommended)
Includes all essential plugins:
npm install --save-dev @nomicfoundation/hardhat-toolbox
Includes:
@nomicfoundation/hardhat-ethers- Ethers.js integration@nomicfoundation/hardhat-chai-matchers- Chai matchers for testing@nomicfoundation/hardhat-verify- Contract verificationhardhat-gas-reporter- Gas usage reportingsolidity-coverage- Code coverage@typechain/hardhat- TypeScript types for contracts
Individual Plugins
# Contract verification
npm install --save-dev @nomicfoundation/hardhat-verify
# Gas reporting
npm install --save-dev hardhat-gas-reporter
# Code coverage
npm install --save-dev solidity-coverage
# OpenZeppelin Upgrades
npm install --save-dev @openzeppelin/hardhat-upgrades
Common Configurations
1. Multiple Networks
networks: {
mainnet: {
url: process.env.MAINNET_RPC_URL,
accounts: [process.env.PRIVATE_KEY],
chainId: 1
},
sepolia: {
url: process.env.SEPOLIA_RPC_URL,
accounts: [process.env.PRIVATE_KEY],
chainId: 11155111
},
arbitrum: {
url: process.env.ARBITRUM_RPC_URL,
accounts: [process.env.PRIVATE_KEY],
chainId: 42161
}
}
2. High Optimization for Production
solidity: {
version: "0.8.30",
settings: {
optimizer: {
enabled: true,
runs: 10000
},
viaIR: true
}
}
3. Multiple Solidity Versions
solidity: {
compilers: [
{
version: "0.8.30",
settings: {
optimizer: { enabled: true, runs: 200 }
}
},
{
version: "0.8.20",
settings: {
optimizer: { enabled: true, runs: 200 }
}
}
]
}
4. Gas Reporter Configuration
gasReporter: {
enabled: process.env.REPORT_GAS === "true",
currency: "USD",
coinmarketcap: process.env.COINMARKETCAP_API_KEY,
outputFile: "gas-report.txt",
noColors: true
}
Dependencies Management
Installing OpenZeppelin
# OpenZeppelin Contracts
npm install @openzeppelin/contracts
# OpenZeppelin Upgrades Plugin
npm install --save-dev @openzeppelin/hardhat-upgrades
Installing Common Libraries
# Ethers.js (usually included with hardhat-toolbox)
npm install ethers
# Chai for testing
npm install --save-dev chai
# Dotenv for environment variables
npm install --save-dev dotenv
Initialization Script
See ./scripts/init-hardhat.sh for automated setup.
Usage:
# Basic initialization
./scripts/init-hardhat.sh
# With project name
./scripts/init-hardhat.sh my-project
# With TypeScript
./scripts/init-hardhat.sh my-project --typescript
What the script does:
- Checks if Node.js is installed
- Initializes npm/yarn project
- Installs Hardhat and essential plugins
- Creates configuration files
- Sets up .gitignore
- Installs OpenZeppelin contracts
- Creates example contract and test
Hybrid Setup (Hardhat + Foundry)
When adding Hardhat to an existing Foundry project:
1. Initialize Hardhat Without Overwriting
# Initialize npm project
npm init -y
# Install Hardhat
npm install --save-dev hardhat
npx hardhat init # Choose "Create an empty hardhat.config.js"
2. Configure Shared Directories
// hardhat.config.js
module.exports = {
paths: {
sources: "./src", // Use Foundry's src dir
tests: "./test", // Shared test dir
cache: "./cache_hardhat", // Separate cache
artifacts: "./artifacts"
},
solidity: "0.8.30"
};
3. Update .gitignore
# Hardhat
artifacts/
cache/
cache_hardhat/
node_modules/
coverage/
coverage.json
typechain-types/
# Foundry
out/
lib/
# Environment
.env
.env.local
4. Shared Dependencies
# Install OpenZeppelin via npm (for Hardhat)
npm install @openzeppelin/contracts
# Foundry can reference node_modules
# Add to foundry.toml:
# libs = ["node_modules", "lib"]
Testing Setup
Basic Test Structure (JavaScript)
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("MyContract", function () {
let myContract;
let owner;
beforeEach(async function () {
[owner] = await ethers.getSigners();
const MyContract = await ethers.getContractFactory("MyContract");
myContract = await MyContract.deploy();
});
it("Should work correctly", async function () {
// Test implementation
expect(await myContract.someFunction()).to.equal(expectedValue);
});
});
Basic Test Structure (TypeScript)
import { expect } from "chai";
import { ethers } from "hardhat";
import { MyContract } from "../typechain-types";
describe("MyContract", function () {
let myContract: MyContract;
beforeEach(async function () {
const MyContract = await ethers.getContractFactory("MyContract");
myContract = await MyContract.deploy();
});
it("Should work correctly", async function () {
expect(await myContract.someFunction()).to.equal(expectedValue);
});
});
Running Tests
# Run all tests
npx hardhat test
# Run specific test
npx hardhat test test/MyContract.test.js
# With gas reporting
REPORT_GAS=true npx hardhat test
# With coverage
npx hardhat coverage
Security Best Practices for Private Keys
⚠️ CRITICAL: Never store production private keys in .env files!
Recommended Approaches (in order of preference)
1. Hardware Wallets (Most Secure - Production)
Install the Ledger plugin:
npm install --save-dev @nomicfoundation/hardhat-ledger
Configure in hardhat.config.js:
require("@nomicfoundation/hardhat-ledger");
module.exports = {
networks: {
mainnet: {
url: process.env.MAINNET_RPC_URL,
ledgerAccounts: [
"0xYourLedgerAddress"
]
}
}
};
Deploy with Ledger:
npx hardhat run scripts/deploy.js --network mainnet
2. Hardhat Configuration Variables (Recommended - Built-in)
Hardhat 2.x+ includes built-in support for secure configuration variables:
# Set a configuration variable (prompts for value, stores encrypted)
npx hardhat vars set PRIVATE_KEY
# List all configuration variables
npx hardhat vars list
# Delete a variable
npx hardhat vars delete PRIVATE_KEY
Use in hardhat.config.js:
const { vars } = require("hardhat/config");
module.exports = {
networks: {
sepolia: {
url: vars.get("SEPOLIA_RPC_URL"),
accounts: [vars.get("PRIVATE_KEY")]
}
}
};
This stores encrypted values in your home directory and prompts for decryption when needed.
3. hardhat-keystore Plugin (Encrypted Keystore)
Similar to Foundry's cast wallet, provides encrypted keystore management:
npm install --save-dev hardhat-keystore
Configure in hardhat.config.js:
require("hardhat-keystore");
module.exports = {
networks: {
sepolia: {
url: process.env.SEPOLIA_RPC_URL,
accounts: {
mnemonic: keystore.getMnemonic(),
// or
privateKey: keystore.getPrivateKey("deployer")
}
}
}
};
4. hardhat-secure-accounts Plugin (Third-Party Alternative)
npm install --save-dev hardhat-secure-accounts
Creates encrypted keystore files:
# Create new encrypted account
npx hardhat accounts:create
# Import existing private key
npx hardhat accounts:import --name deployer
Use in scripts:
const { getSecureAccount } = require("hardhat-secure-accounts");
async function main() {
const signer = await getSecureAccount("deployer");
// Use signer for deployment
}
5. Interactive Environment Variable (Development Only)
Prompt for private key at runtime (doesn't store on disk):
const readline = require("readline");
async function getPrivateKey() {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve) => {
rl.question("Enter private key: ", (answer) => {
rl.close();
resolve(answer);
});
});
}
async function main() {
const privateKey = await getPrivateKey();
const wallet = new ethers.Wallet(privateKey, ethers.provider);
// Use wallet for deployment
}
6. .env Variables (Development/Testing ONLY)
⚠️ Use ONLY for local development or testnet testing with non-production keys!
require("dotenv").config();
module.exports = {
networks: {
sepolia: {
url: process.env.SEPOLIA_RPC_URL,
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : []
}
}
};
If using .env:
- ✅ Only use accounts created specifically for development/testing
- ✅ Never reuse production private keys
- ✅ Keep test funds minimal
- ✅ Add .env to .gitignore
- ❌ Never commit .env to version control
- ❌ Never use for mainnet deployments
Deployment Setup
Basic Deployment Script
// scripts/deploy.js
const hre = require("hardhat");
async function main() {
const MyContract = await hre.ethers.getContractFactory("MyContract");
const myContract = await MyContract.deploy();
await myContract.waitForDeployment();
console.log("MyContract deployed to:", await myContract.getAddress());
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
Deploy Commands
# Deploy to local network
npx hardhat run scripts/deploy.js
# Deploy to Sepolia with hardware wallet (RECOMMENDED for production)
npx hardhat run scripts/deploy.js --network sepolia
# (Ledger will prompt for approval)
# Deploy to Sepolia with Hardhat vars (RECOMMENDED for all deployments)
# First time: npx hardhat vars set PRIVATE_KEY
npx hardhat run scripts/deploy.js --network sepolia
# Development only: with .env private key
npx hardhat run scripts/deploy.js --network sepolia
# Verify contract
npx hardhat verify --network sepolia DEPLOYED_CONTRACT_ADDRESS
Best Practices
- Secure private key management - Use hardware wallets or Hardhat Configuration Variables for all deployments; never store production keys in .env
- Use hardhat-toolbox - Includes all essential plugins
- TypeScript for large projects - Better type safety and IDE support
- Comprehensive .env.example - Document all required environment variables (but discourage private keys)
- Separate networks - Configure all networks you'll deploy to
- Gas reporting - Enable gas reports for optimization
- Code coverage - Run coverage regularly
- Contract verification - Always verify deployed contracts
- Version lock dependencies - Use exact versions in package.json
Troubleshooting
Issue: "hardhat: command not found"
# Use npx
npx hardhat compile
# Or install globally (not recommended)
npm install -g hardhat
Issue: Module not found errors
# Clear cache and reinstall
rm -rf node_modules package-lock.json
npm install
Issue: Compilation errors with OpenZeppelin
# Ensure correct import paths
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
Issue: Network connection errors
# Check RPC URL in .env
# Test with curl
curl -X POST -H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
$SEPOLIA_RPC_URL
Quick Reference
| Task | Command | Notes |
|---|---|---|
| Init project | npx hardhat init |
Creates new project |
| Add dependency | npm install <package> |
Uses npm |
| Compile | npx hardhat compile |
Compiles contracts |
| Test | npx hardhat test |
Runs tests |
| Coverage | npx hardhat coverage |
Test coverage |
| Gas report | REPORT_GAS=true npx hardhat test |
Gas usage |
| Deploy | npx hardhat run scripts/deploy.js |
Run deployment |
| Verify | npx hardhat verify |
Verify on Etherscan |
| Clean | npx hardhat clean |
Clean artifacts |
Template Files
This skill provides the following templates:
./templates/hardhat.config.js- JavaScript configuration./templates/hardhat.config.ts- TypeScript configuration./templates/.env.example- Environment variables template./templates/deploy-script.js- Deployment script template
Scripts
This skill provides the following scripts:
./scripts/init-hardhat.sh- Automated project initialization
Next Steps After Setup:
- Configure
hardhat.config.jsfor your networks - Copy
.env.exampleto.envand fill in values - Install required dependencies with
npm install - Write contracts in
contracts/ - Write tests in
test/ - Run
npx hardhat testto verify setup
Converted and distributed by TomeVault — claim your Tome and manage your conversions.