Setting Up Hardhat for Development
Hardhat without setup is just a Solidity compiler with a local node. Hardhat with proper configuration is a full development environment where a developer doesn't waste time on routine: incremental compilation, tests running in parallel, deployment is idempotent, CI/CD knows what to do without manual instructions.
Basic Configuration
Minimal hardhat.config.ts for a production project:
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox"; // ethers, waffle, chai, gas reporter
import "@openzeppelin/hardhat-upgrades";
import "hardhat-deploy";
import "hardhat-contract-sizer";
const config: HardhatUserConfig = {
solidity: {
version: "0.8.24",
settings: {
optimizer: { enabled: true, runs: 200 },
viaIR: true, // reduces gas, needed for complex contracts
},
},
networks: {
hardhat: {
forking: { url: process.env.ALCHEMY_MAINNET_URL! },
chainId: 1,
},
polygon: {
url: process.env.ALCHEMY_POLYGON_URL!,
accounts: [process.env.DEPLOYER_PRIVATE_KEY!],
gasPrice: "auto",
},
},
gasReporter: {
enabled: process.env.REPORT_GAS === "true",
currency: "USD",
coinmarketcap: process.env.CMC_API_KEY,
},
};
viaIR: true is important for contracts that the Solidity compiler can't compile without it (Stack too deep). With the IR compiler, this error doesn't occur, but compilation time increases by 30-50%.
Plugins That Really Matter
hardhat-deploy — manages deployments with support for named accounts, fixtures, and tagging. Deployment scripts store artifacts in deployments/ and are idempotent: re-running doesn't redeploy what's already deployed. Essential when working with multiple networks.
hardhat-contract-sizer — checks contract size. EIP-170 limits bytecode to 24576 bytes. Getting this limit unexpectedly on mainnet deployment is unpleasant. The plugin shows size after every compilation.
@nomicfoundation/hardhat-toolbox — a meta-plugin that includes hardhat-ethers, hardhat-waffle, hardhat-chai-matchers, hardhat-network-helpers, hardhat-verify. One dependency instead of six.
@openzeppelin/hardhat-upgrades — if the project uses UUPS or Transparent Proxy. The plugin checks storage compatibility before deployment: if the new contract breaks storage layout, you'll know before losing data on mainnet.
Test Organization and Fixtures
Slow tests are a result of improper fixtures. The loadFixture pattern from hardhat-network-helpers lets you snapshot network state after deployment and revert to it before each test instead of redeploying:
async function deployTokenFixture() {
const [owner, alice, bob] = await ethers.getSigners();
const Token = await ethers.getContractFactory("MyToken");
const token = await Token.deploy(ethers.parseEther("1000000"));
return { token, owner, alice, bob };
}
it("transfers tokens", async () => {
const { token, alice } = await loadFixture(deployTokenFixture);
// state is always clean, without redeployment
});
On a large test suite (200+ tests), the difference between loadFixture and beforeEach with deployment — 5 minutes vs 30 seconds.
CI/CD Integration
GitHub Actions for automatic test runs and verification:
- name: Run tests
run: npx hardhat test --network hardhat
env:
ALCHEMY_MAINNET_URL: ${{ secrets.ALCHEMY_URL }}
- name: Verify contract
run: npx hardhat verify --network polygon ${{ steps.deploy.outputs.address }}
env:
POLYGONSCAN_API_KEY: ${{ secrets.POLYGONSCAN_KEY }}
Verification in CI requires that constructor arguments are deterministic or saved as deployment artifacts. hardhat-deploy saves arguments in deployments/polygon/ContractName.json automatically.
Setup takes 2-3 hours. Cost is calculated individually.







