Hardhat Projects skill for Codex

Config, deploy and verify without leaking a key. Codex detects whether a repo is on Hardhat 2 or 3 and keeps to that version's idioms, moves secrets into configVariable and the encrypted keystore, writes tests with viem and node:test, and deploys through resumable Ignition modules with parameter files. Verification uses the same build profile as the deploy, and mainnet deploys wait for your confirmation.

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- hardhat-projects

Installs to ~/.agents/skills/hardhat-projects/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $hardhat-projects, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Sets up and maintains Hardhat 3 Solidity projects (and legacy Hardhat 2) - defineConfig with build profiles and networks, configVariable and the keystore for secrets, Solidity and node:test tests with viem, Hardhat Ignition deployment modules, and contract verification on Etherscan, Blockscout and Sourcify. Use when asked to scaffold a Hardhat project, deploy to a testnet or mainnet, verify a contract, migrate from Hardhat 2 or ethers scripts, or fix a broken hardhat.config.ts.

Use it when

  • You are starting or maintaining a Hardhat-based Solidity repo
  • A contract needs deploying to a testnet or mainnet with reproducible records
  • Etherscan, Blockscout or Sourcify verification keeps failing
  • Secrets are sitting in hardhat.config or a committed .env

Use something else when

How to check the result

npx hardhat test passes on a clean clone, Ignition deployment state is committed, and the explorer shows the contract as verified with matching constructor arguments.

The mistake it prevents

Building the deploy with the default profile and verifying with the production profile, so the bytecode never matches and verification fails.

~/.agents/skills/hardhat-projects/SKILL.md

--- name: hardhat-projects description: Sets up and maintains Hardhat 3 Solidity projects (and legacy Hardhat 2) - defineConfig with build profiles and networks, configVariable and the… license: MIT ---

A Hardhat project is in good shape when secrets never sit in the config or .env that gets committed, deployments are declarative and resumable (Ignition), the verify step uses the same build profile as the deploy, and anyone can run npx hardhat test on a fresh clone.

Inspect first

  1. Read AGENTS.md, package.json and hardhat.config.*. The hardhat version decides everything below: 3.x uses defineConfig, ESM and plugins arrays; 2.x uses module.exports/HardhatUserConfig and require side-effect plugins.
  2. Note the package manager from the lockfile and use it (npm, pnpm, yarn, bun).
  3. Hardhat 3 needs Node.js 22+ and "type": "module" in package.json. Check node -v.
  4. Run npx hardhat build (3.x) or npx hardhat compile (2.x) and npx hardhat test to get a baseline before changing anything.

Installing packages needs network; in a Codex sandbox ask for approval first.

Version decision

SituationDo
New projectHardhat 3: npx hardhat --init, choose the viem + node:test template
Existing Hardhat 2 project, workingStay on 2.x unless the user asks to migrate; do not mix idioms
Need fast Solidity unit testsHardhat 3 Solidity tests (forge-std compatible) or Foundry alongside
Repo already uses Foundry for testsKeep Foundry for tests; Hardhat only if deployment tooling needs it

Hardhat 3 config

// hardhat.config.ts
import { configVariable, defineConfig } from "hardhat/config";
import hardhatToolboxViem from "@nomicfoundation/hardhat-toolbox-viem";

export default defineConfig({
  plugins: [hardhatToolboxViem],
  solidity: {
    profiles: {
      default: { version: "0.8.28" },
      production: {
        version: "0.8.28",
        settings: { optimizer: { enabled: true, runs: 200 } },
      },
    },
  },
  networks: {
    hardhatMainnet: { type: "edr-simulated", chainType: "l1" },
    hardhatOp: { type: "edr-simulated", chainType: "op" },
    sepolia: {
      type: "http",
      chainType: "l1",
      url: configVariable("SEPOLIA_RPC_URL"),
      accounts: [configVariable("SEPOLIA_PRIVATE_KEY")],
    },
  },
  verify: {
    etherscan: { apiKey: configVariable("ETHERSCAN_API_KEY") },
  },
});
  • configVariable("X") is resolved lazily, only when a task needs it, from environment variables or the encrypted keystore. A missing variable fails the task that uses it, not every task.
  • Store secrets in the keystore, never in the file: npx hardhat keystore set SEPOLIA_PRIVATE_KEY (prompts for a password; the user must type the secret themselves, so ask them to run it).
  • chainType: "op" makes the simulated network behave like OP Stack chains (gas, L1 fees).
  • Plugins are listed in plugins; importing a plugin file for its side effects (the 2.x way) does nothing in 3.x.

Tests

Hardhat 3 runs Solidity tests and TypeScript tests from one command:

npx hardhat test            # everything
npx hardhat test solidity   # *.t.sol files, forge-std style
npx hardhat test nodejs     # node:test files in test/

TypeScript test with viem:

import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { network } from "hardhat";

describe("Vault", async () => {
  const { viem } = await network.connect();
  const [owner, alice] = await viem.getWalletClients();

  it("records deposits", async () => {
    const token = await viem.deployContract("ERC20Mock");
    const vault = await viem.deployContract("Vault", [token.address, owner.account.address]);
    await token.write.mint([alice.account.address, 100n]);
    await token.write.approve([vault.address, 100n], { account: alice.account });
    await vault.write.deposit([100n], { account: alice.account });
    assert.equal(await vault.read.balanceOf([alice.account.address]), 100n);
  });
});
  • Each network.connect() returns a fresh, isolated simulated chain; connect once per describe and use fixtures for repeated setup.
  • Pass network.connect("hardhatOp") to test against OP-style gas accounting.
  • For deep fuzzing and invariants, Foundry is stronger; see $foundry-testing.

Deploy with Ignition

// ignition/modules/Vault.ts
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";

export default buildModule("VaultModule", (m) => {
  const owner = m.getParameter("owner");
  const token = m.getParameter("token");
  const vault = m.contract("Vault", [token, owner]);
  return { vault };
});
// ignition/parameters/sepolia.json
{ "VaultModule": { "owner": "0xYourSafe...", "token": "0xToken..." } }
npx hardhat ignition deploy ignition/modules/Vault.ts \
  --network sepolia --parameters ignition/parameters/sepolia.json
  • Ignition records state under ignition/deployments/chain-<id>/. Re-running the same command resumes a partial deployment instead of redeploying. Commit that folder for production deployments; it is the source of truth for addresses.
  • Deploy to the simulated network first (omit --network), then a testnet, then mainnet.
  • Mainnet or any production chain: show the user the module, parameters, deployer address and estimated cost, and get explicit confirmation before running the deploy. Ownership should go to a multisig, not the deployer EOA.

Verify

npx hardhat build --build-profile production
npx hardhat verify --network sepolia 0xDeployed "0xToken..." "0xOwner..."
npx hardhat verify blockscout --network sepolia 0xDeployed "0xToken..." "0xOwner..."
  • verify uses the production build profile by default. If the deploy was built with default, bytecode will not match: build and deploy with the same profile you verify.
  • Sourcify verification runs without an API key. Etherscan uses one key for all supported chains (the v2 API); chains not built in need a chainDescriptors entry.
  • Constructor arguments must be passed exactly as deployed, in order. Complex arguments (structs, arrays) are easier through the programmatic verifyContract helper from @nomicfoundation/hardhat-verify/verify.

Hardhat 2 (legacy) equivalents

Hardhat 3Hardhat 2
defineConfig({ plugins: [...] })require("@nomicfoundation/hardhat-toolbox")
configVariable("KEY") + keystorevars.get("KEY") with npx hardhat vars set KEY
npx hardhat buildnpx hardhat compile
network.connect() per test fileGlobal hre.network, loadFixture from network-helpers
verify.etherscan.apiKeyetherscan: { apiKey } (needs a recent hardhat-verify for the v2 API)

Chai matchers in 2.x (ethers v6): await expect(tx).to.be.revertedWithCustomError(vault, "ZeroAmount") and .to.emit(vault, "Deposited").withArgs(alice.address, 100n).

Migrating 2 to 3

- [ ] 1. Branch; record current test results and deployed addresses
- [ ] 2. Node 22+, "type": "module", rename config to hardhat.config.ts with defineConfig
- [ ] 3. Replace side-effect plugin imports with the plugins array (toolbox-viem or mocha-ethers)
- [ ] 4. Move secrets from .env / vars to configVariable + keystore
- [ ] 5. Port tests: network.connect() at the top of each file; fix ESM imports
- [ ] 6. Port deploy scripts to Ignition modules; keep old deployment records
- [ ] 7. Run the full suite; diff compiled bytecode for unchanged contracts

Gotchas

  • Hard-coded private keys in hardhat.config.* or a committed .env are a leak. Check git log -p -- hardhat.config.ts .env if one was ever there and tell the user to rotate.
  • bigint everywhere in viem: 100n, not 100. parseEther("1") for ETH amounts.
  • Gas estimation on a fork of an L2 can differ from the real chain; leave headroom.
  • Contract names that exist in two files need fully qualified names: viem.deployContract("contracts/a/Vault.sol:Vault").
  • Do not run npx hardhat clean in the middle of an Ignition deployment you intend to resume without checking the build-info it relies on.

Report

**Hardhat:** 3.x, toolbox-viem, solc 0.8.28 (production profile: optimizer 200 runs)
**Tests:** 42 passing (31 Solidity, 11 node:test)
**Deployment:** VaultModule on sepolia, Vault at 0x..., state in ignition/deployments/chain-11155111
**Verification:** Etherscan and Sourcify verified (links)
**Secrets:** SEPOLIA_PRIVATE_KEY, ETHERSCAN_API_KEY in keystore; none in repo

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Hardhat Projects"
  short_description: "Config, deploy and verify without leaking a key"
  default_prompt: "$hardhat-projects Set up a Hardhat 3 project with viem tests and a Sepolia network"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem