---
title: "Hardhatとは？Hardhat 3の使い方：導入・テスト・デプロイとFoundryとの違い【2026年11月】"
url: "https://www.issoh.co.jp/tech/details/12466/"
published: 2026-06-11
updated: 2026-11-13
categories: ["ブロックチェーン"]
publisher: "株式会社一創"
---

# Hardhatとは？Hardhat 3の使い方：導入・テスト・デプロイとFoundryとの違い【2026年11月】

Hardhatは、Ethereumなど EVM 互換チェーン向けのスマートコントラクト開発環境です。Solidityのコンパイル、テスト、デバッグ、デプロイを1つのコマンドラインツールで扱えます。開発元は非営利団体のNomic Foundationで、最新版は2026年11月12日公開の3.22.0です。この記事では、Hardhat 3でプロジェクトを作り、SolidityテストとTypeScriptテストを書き、OpenZeppelinを使ったトークンをデプロイして検証するまでを解説します。プロジェクト作成からテスト、ローカルへのデプロイまでは、2026年11月13日にmacOS（x86\_64）・Node.js v26.5.0・npm 11.17.0・Hardhat 3.22.0で実行した結果を載せ、Sepoliaの手順は公式ドキュメントに沿って示します。次のAmsterdamハードフォークでガス使用量がどれだけ変わるかも、同じ環境で測りました。検索で見つかる日本語の入門記事はHardhat 2時代の手順（設定が `hardhat.config.js`、テストネットが終了済みのRopsten）のままのものが多いので、2系との違いとサポート終了日、Foundryとの選び方もまとめます。

## まとめ：Hardhat 3で押さえる要点

- **Hardhat 3が現行版**：3.0.0は2025年8月13日にベータとして公開され、2026年5月28日の3.7.0でベータを抜けた（公式ブログでの正式版発表は6月1日）。Node.jsはv22.13.0以上が必要。
- **Hardhat 2はサポート終了が決まっている**：Hegotaハードフォークのメインネット有効化か2027年6月1日の早いほうで終了し、以後はセキュリティ修正も出ない。新規はHardhat 3で始める。
- **テストはSolidityとTypeScriptの両方で書ける**：Solidityテストはforge-std互換で、同じ `.t.sol` がFoundry 1.9.0の `forge test` でも同じ結果になった。TypeScriptテストは `network.create()` で接続する（`network.connect()` は3.4.0で非推奨）。
- **ガス計測とカバレッジは本体に入った**：`--gas-stats` と `--coverage` を付けるだけで動く。2系の `hardhat-gas-reporter` と `solidity-coverage` は移行時に外す。
- **Amsterdamのガス変更を先に試せる**：`hardfork: "amsterdam"` のネットワークで測ると、Counterのデプロイは263,088から1,714,624ガスへ約6.5倍になった。ガス量を固定値で書いたコードとテストは要点検。
- **秘密鍵は環境変数でなくkeystoreに置く**：`configVariable()` と `hardhat-keystore` で暗号化して保存し、デプロイはHardhat Ignition、検証は `hardhat verify` で行う。
- **Foundryとの選択はチームの言語で決める**：TypeScriptでフロントエンドやスクリプトを書くチームはHardhat 3、Solidityだけで完結させたいならFoundry。

## Hardhatの役割とHardhat 3で変わった前提

Hardhatの仕事は、Solidityのソースをコンパイルし、手元の模擬チェーンでテストを回し、本番やテストネットへデプロイするまでの作業をタスクとしてまとめることです。言語そのものの書き方は[Solidity入門｜開発環境の作り方・基本構文・Sepoliaデプロイ手順【2026年10月】](/tech/details/8629/)で扱っているので、ここではツールとしてのHardhatに絞ります。

### Hardhat 3を構成する3つの部品

Hardhat 3は、タスクを実行するCLI本体、テストとローカル実行を担うシミュレーター、デプロイ管理のHardhat Ignitionで構成されます。シミュレーターはRustで書かれたEDR（Ethereum Development Runtime）で、2系の「Hardhat Network」に当たります。3.22.0に同梱されるEDRは0.25.1です。

EDRは、どの種類のチェーンを模擬するかを `chainType` で選べます。2系の開発用ネットワークは常にEthereumメインネットとして振る舞いましたが、3系ではEthereum（`l1`）とOP Mainnet系（`op`）を指定でき、それ以外のチェーンには汎用の `generic` を使います。

### Hardhat 2との違いとサポート終了日

Hardhat 3は2系の全面的な書き直しで、2系のプロジェクトはそのままでは動きません。主な違いは次のとおりです。

| 項目              | Hardhat 2      | Hardhat 3          |
| --------------- | -------------- | ------------------ |
| 最新版（2026-11-13） | 2.29.2         | 3.22.0             |
| 設定ファイル          | CommonJS可      | ESM必須              |
| プラグインの読み込み      | importの副作用     | plugins配列に明記       |
| JS/TSテストの実行     | Mocha固定        | node:testかMochaを選択 |
| Solidityテスト     | なし             | 標準搭載               |
| 模擬チェーン          | L1のみ           | l1・op・generic      |
| ガス計測・カバレッジ      | 外部プラグイン        | 本体のフラグ             |
| サポート            | 2027年6月1日までに終了 | 継続                 |

2系のサポート終了は、公式の「[Hardhat 2 end of life](https://hardhat.org/docs/reference/hardhat-2-end-of-life)」に次のように定められています。2系は次のGlamsterdamハードフォークには対応するものの、その次のHegotaには対応しません。終了日はHegotaのメインネット有効化と2027年6月1日のどちらか早いほうで、それ以降はセキュリティ修正を含めてリリースが止まります。npmでは2系が `hh2` タグで配布され続けているので、`npm i hardhat` を実行すると3系が入る点にも注意してください。

## Hardhat 3プロジェクトの作り方

### Node.jsの要件とテンプレート3種

公式のGetting startedが求めるNode.jsはv22.13.0以上です。サポート対象は、Node.jsチームが保守している偶数のメジャーバージョンに限られます。空のディレクトリで次を実行すると、対話形式でテンプレートを選べます。

```
mkdir hardhat-example && cd hardhat-example
npx hardhat@3.22.0 --init
```

選べるテンプレートは3つです。`--templates` で一覧を出し、`--template` で指定すれば質問なしで作成と依存パッケージのインストールまで進みます。CIや手順書ではこちらが向きます。

```
$ npx hardhat --init --templates
Available templates:
  - node-test-runner-viem
  - mocha-ethers
  - minimal

$ npx hardhat --init --template node-test-runner-viem
```

迷ったら `node-test-runner-viem` を選びます。公式がNode.js標準のテストランナーを推奨しているためで、Mochaとethers.jsの組み合わせは2系から移行するプロジェクト向けです。`minimal` は設定ファイルだけの空のプロジェクトになります。

インストールの最後に `npm audit` が「12 high severity vulnerabilities」と表示しました。内訳を見ると、12件はすべてzodの1件の勧告（[GHSA-mrhx-c7xc-m3w5](https://github.com/advisories/GHSA-mrhx-c7xc-m3w5)、件数や長さに上限を付けた配列などの検証で巨大な入力を渡すとメモリを使い果たす）が、zod自身と、zodに依存するHardhatのパッケージごとに数えられたものでした。導入されたzodは3.25.76で、GitHubの勧告が示す対象範囲（4.6.6未満）に入っています。ただし、Zodの作者は2026年10月12日に[GitHubのissue](https://github.com/colinhacks/zod/issues/6653)で、この挙動を脆弱性とは考えておらず、v3の新しい版はもう公開しないと述べています。`npm audit fix --dry-run` で確かめると、通常の修正では解消されず、表示どおり `--force` を付けると「Will install hardhat@2.29.2, which is a breaking change」と、サポート終了が決まった2系へ入れ替えようとします。`--force` は実行しないでください。auditの検出結果と、使用する処理が巨大な入力を受け付けるかという影響評価は分けて判断します。

### 生成されるファイルとhardhat.config.ts

`node-test-runner-viem` では、`contracts/`（`Counter.sol` とSolidityテストの `Counter.t.sol`）、`test/`（TypeScriptテスト）、`ignition/modules/`（デプロイ定義）、`scripts/` と設定ファイルが作られます。3.22.0では、AIコーディングエージェント向けに `CLAUDE.md` と `AGENTS.md`、さらに `.claude/skills/` と `.agents/skills/` に `hardhat` と `hardhat-toolbox-viem` の2つのスキル（`SKILL.md`）も置かれました。テストの書き分けや `network.create()` の使い方が書かれているので、エージェントに古い2系の書き方をさせないための材料になります。生成された設定ファイルは次のとおりです。

```
import hardhatToolboxViemPlugin from "@nomicfoundation/hardhat-toolbox-viem";
import { configVariable, defineConfig } from "hardhat/config";

export default defineConfig({
  plugins: [hardhatToolboxViemPlugin],
  solidity: {
    profiles: {
      default: {
        version: "0.8.34",
      },
      production: {
        version: "0.8.34",
        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")],
    },
  },
});
```

2系から来た人が戸惑うのは、プラグインが `plugins` 配列に入れないと有効にならない点です。`solidity.profiles` はビルドプロファイルで、普段のテストは最適化なしの `default`、デプロイと検証は最適化ありの `production` が使われます。ネットワークは `type` で区別し、`edr-simulated` は手元の模擬チェーン、`http` は外部のRPCです。

`npx hardhat build`（`compile` は別名）を初めて実行すると、指定したsolcが自動でダウンロードされます。後述の金庫コントラクトを加えた状態では `Compiled 4 Solidity files with solc 0.8.34 (evm target: osaka)` と表示されました。EVMの既定ターゲットがosakaである点は、未追随のL2へデプロイするときに効いてきます。

## SolidityテストとTypeScriptテストの書き分け

`npx hardhat test` は両方のテストをまとめて実行し、`test solidity` と `test nodejs` で片方だけを回せます。テンプレートのままなら `5 passing (3 solidity, 2 nodejs)` と表示されます。使い分けの目安は、コントラクト単体の振る舞いとファズテストはSolidity、イベントの集計や複数コントラクトをまたぐ手順、フロントエンドと同じviemのコードで確かめたい処理はTypeScriptです。

ここからは、残高を超える引き出しをカスタムエラーで止める小さな金庫コントラクトを例にします。コントラクトは `contracts/Vault.sol`、Solidityテストは `contracts/Vault.t.sol`、TypeScriptテストは `test/Vault.ts` に保存します。

```
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.34;

import "hardhat/console.sol";

contract Vault {
  error InsufficientBalance(uint256 requested, uint256 available);

  mapping(address => uint256) public balanceOf;

  function deposit() external payable {
    balanceOf[msg.sender] += msg.value;
  }

  function withdraw(uint256 amount) external {
    console.log("withdraw: requested=%s available=%s", amount, balanceOf[msg.sender]);
    if (amount > balanceOf[msg.sender]) {
      revert InsufficientBalance(amount, balanceOf[msg.sender]);
    }
    balanceOf[msg.sender] -= amount;
    (bool ok, ) = payable(msg.sender).call{value: amount}("");
    require(ok, "transfer failed");
  }
}
```

送金に `transfer` を使うと、solc 0.8.34は「’transfer’ is deprecated and scheduled for removal」と警告を出します。`transfer` は転送先に2,300ガスしか渡さないため、後述のAmsterdamのガス変更で壊れる書き方の代表でもあります。古い入門記事を写すとまず出会う警告なので、`call` で書いておきます。

### forge-std互換のSolidityテスト

Solidityテストは `contracts/` に `.t.sol` として置くか、`test/` に置きます。`forge-std` の `Test` を継承し、`vm.expectRevert` などのチートコードがそのまま使えます。3.22.0のテンプレートが入れるforge-stdはv1.17.0です。

```
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.34;

import {Vault} from "./Vault.sol";
import {Test} from "forge-std/Test.sol";

contract VaultTest is Test {
  Vault vault;

  function setUp() public {
    vault = new Vault();
    vault.deposit{value: 100}();
  }

  function test_RevertWhenOverWithdraw() public {
    vm.expectRevert(abi.encodeWithSelector(Vault.InsufficientBalance.selector, 500, 100));
    vault.withdraw(500);
  }

  receive() external payable {}
}
```

引数を取るテスト関数はファズテストになり、テンプレートの `testFuzz_Inc(uint8)` は既定で256回のランダム入力で実行されました。

### node:testとviemで書くTypeScriptテスト

TypeScriptテストでは、まず `network.create()` で模擬チェーンへの接続を作り、そこからviemのクライアントとテスト用ヘルパーを取り出します。同じ初期状態を何度も使う場合は `loadFixture` に関数を渡すと、2回目以降はデプロイをやり直さずスナップショットから復元されます。

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

describe("Vault", async function () {
  const { viem, networkHelpers } = await network.create();

  async function deployVaultFixture() {
    const vault = await viem.deployContract("Vault");
    await vault.write.deposit({ value: 100n });
    return { vault };
  }

  it("残高を超える引き出しはカスタムエラーで止まる", async function () {
    const { vault } = await networkHelpers.loadFixture(deployVaultFixture);
    await viem.assertions.revertWithCustomErrorWithArgs(
      vault.write.withdraw([500n]),
      vault,
      "InsufficientBalance",
      [500n, 100n],
    );
  });

  it("残高の範囲なら引き出せる", async function () {
    const { vault } = await networkHelpers.loadFixture(deployVaultFixture);
    const [wallet] = await viem.getWalletClients();
    await vault.write.withdraw([40n]);
    assert.equal(await vault.read.balanceOf([wallet.account.address]), 60n);
    assert.equal(await (await viem.getPublicClient()).getBalance({ address: vault.address }), 60n);
  });
});
```

`npx hardhat test nodejs test/Vault.ts` で2件とも通り、`tsc --noEmit` でも型エラーは出ませんでした。`vault.read.balanceOf` の引数と戻り値に型が付くのは、Hardhat 3がコンパイル時にコントラクトの型定義を生成するためです。2系でTypeChainのようなコード生成ツールが担っていた役割を、本体が受け持っています。

## console.logとSolidityスタックトレースでのデバッグ

コントラクトに `import "hardhat/console.sol";` を書くと、Solidityの中から `console.log` で値を出力できます。上のSolidityテストに、残高を超えて引き出すだけの `test_BrokenOnPurpose()` を足してわざと落とすと、ログに続いてSolidityのどの行でrevertしたかが表示されます。

```
withdraw: requested=500 available=100
  1) VaultTest#test_BrokenOnPurpose()
    Error: reverted with custom error 'InsufficientBalance(500, 100)'
      at Vault.withdraw (contracts/Vault.sol:18)
      at VaultTest.test_BrokenOnPurpose (contracts/Vault.t.sol:21)
```

エラー名と引数が復号されて出るので、どの条件で落ちたかをイベントやトランザクションの中身から推測する必要がありません。呼び出しの階層とガスまで見たいときは `-vvvv` のように `-v` を重ねると、Solidityテストの呼び出しトレースが表示されます。`console.log` は実チェーンでは何もしない呼び出しになりますが、ガスは消費するので、デプロイ前に消しておきます。

## ガス計測とカバレッジの取り方

どちらもテストコマンドにフラグを付けるだけです。`--gas-stats` では関数ごとの最小・平均・中央値・最大ガスと、デプロイのガス、バイトコードのサイズが表になります。テンプレートの `Counter` では、`inc` の中央値が27,674、デプロイが263,076ガス、バイトコードが975バイトでした。`--gas-stats-json` でJSONにも書き出せるので、プルリクエストごとに比べたい場合に使えます。

```
npx hardhat test solidity --gas-stats
npx hardhat test --coverage
```

`--coverage` は、SolidityテストとTypeScriptテストの両方を合算して行カバレッジを出し、HTMLのレポートを `coverage/html` に保存します。金庫のSolidityテストを上の残高不足の1件だけにし、TypeScriptテストの `test/Vault.ts` を一時的にプロジェクト外へ退避したうえで、テンプレートの `Counter` のテストと合わせて測ると、次のように未実行の行番号が出ました。

```
║ File Path             │ Line % │ Statement % │ Uncovered Lines ║
║ contracts/Vault.sol   │ 57.14  │ 62.50       │ 20-22           ║
║ contracts/Counter.sol │ 100.00 │ 100.00      │ -               ║
```

20〜22行は引き出しが成功する経路で、送金部分が一度もテストされていないことがわかります。`Uncovered Lines` に送金や権限チェックの行が残っていたら、その経路をテストに加えます。ただし、実行済みの行でも、期待結果を確かめるアサーションが足りなければ不具合は見逃します。なお3.22.0からは、`--no-compile` を付けて計測用のコードが入らない状態で `--coverage` を実行すると、エラーで止まります。

## Amsterdamハードフォークに備えたガスのテスト

Ethereumの次のアップグレードGlamsterdamは、実行層のAmsterdamと合意層のGloasをまとめたものです。テストネットのSepoliaでは2026年10月6日の有効化が[Ethereum Foundationのブログで告知](https://blog.ethereum.org/en/2026/09/17/glamsterdam-testnet-announcement)され、Hoodiは10月26日に予定されていましたが[延期](https://blog.ethereum.org/en/2026/10/22/glamsterdam-hoodi-delay)されました。さらに11月6日には、状態の作成と読み書きのガスを引き上げるEIP-8037とEIP-8038で前提が崩れるコントラクトが見つかったとして、Hoodiでの有効化を問題の解決まで見送り、開発者に自分のコントラクトの点検を求める[呼びかけ](https://blog.ethereum.org/en/2026/11/06/glamsterdam-8037-8038)が出ています。メインネットの日程は2026年11月13日時点で決まっていません。

### hardfork: “amsterdam” のネットワーク設定

Hardhat 3はAmsterdamのEIPを段階的に実装しており（3.20.0でEIP-2780とEIP-8246、3.21.0でEIP-7954、3.22.0でEIP-8038を追加）、公式の[Amsterdam support](https://hardhat.org/docs/reference/amsterdam-support)には13のEIPが並んでいます。対応は実験的で、既定のハードフォークはosakaのままです。試すには、`networks` に次の1件を足します。

```
edrAmsterdam: {
  type: "edr-simulated",
  chainType: "l1",
  hardfork: "amsterdam",
},
```

TypeScriptのスクリプトやテストは `--network edrAmsterdam` で、Solidityテストも同じ指定で、このネットワークのハードフォークで実行されます。実行すると「”amsterdam” hardfork, which is experimental and not yet finalized」という警告が出ます。

### osakaとamsterdamで測ったgasUsedの差

同じ操作を `hardhatMainnet`（osaka）と `edrAmsterdam` で実行し、レシートの `gasUsed` を比べました。どちらもテンプレートの初期状態（テスト用アカウントに10,000 ETH）から始め、コントラクトは `default` プロファイル（最適化なし）でビルドしています。次の `scripts/compare-gas.ts` を `npx hardhat run scripts/compare-gas.ts` で実行すると、送金の行を再現できます。`Counter` の行は、同じループの中で `viem.deployContract("Counter")` と `counter.write.inc()` を2回続けて呼び、各トランザクションのハッシュを同じように調べて測りました。

```
import { network } from "hardhat";

for (const name of ["hardhatMainnet", "edrAmsterdam"]) {
  const { viem, provider } = await network.create({ network: name });
  const publicClient = await viem.getPublicClient();
  const [sender] = await viem.getWalletClients();

  // まだ使われていないアドレスへ1 ETHを送る
  const hash = await sender.sendTransaction({
    to: "0x000000000000000000000000000000000000beef",
    value: 10n ** 18n,
  });
  const receipt = await publicClient.waitForTransactionReceipt({ hash });
  const trace = await provider.request({
    method: "debug_traceTransaction",
    params: [hash, { tracer: "stateGasTracer" }],
  });
  console.log(name, receipt.gasUsed, trace);
}
```

| 操作                  | osaka   | amsterdam | 倍率    |
| ------------------- | ------- | --------- | ----- |
| Counterのデプロイ        | 263,088 | 1,714,624 | 6.5倍  |
| Vaultのデプロイ          | 479,150 | 3,264,862 | 6.8倍  |
| inc（初回・空のスロットへ書き込み） | 44,774  | 126,794   | 2.8倍  |
| inc（2回目以降）          | 27,674  | 28,874    | 1.04倍 |
| 新しいアドレスへの1 ETH送金    | 21,000  | 204,600   | 9.7倍  |
| 自分宛ての1 wei送金        | 21,000  | 12,000    | 0.57倍 |

増え方が大きいのは、新しい状態を作る操作です。EIP-8037ではガスが「状態ガス」と「実行ガス」に分かれ、状態ガスは新しいアカウント、空のストレージスロットへの書き込み、デプロイされるコードに課されます。`debug_traceTransaction` に `stateGasTracer` を指定すると内訳が取れ、初回の `inc` は実行ガス28,874と状態ガス97,920、新しいアドレスへの送金は実行ガス21,000と状態ガス183,600でした。反対に、自分宛ての送金はEIP-2780で一律21,000だった基本料が12,000に下がっています。なお、ブロックの `gasUsed` は2つのうち大きいほうだけを数えるので、初回の `inc` だけを含むブロックは97,920と、トランザクションの合計より小さく表示されます。

この結果から点検すべき箇所は3つあります。`gasUsed` を21,000などの固定値で比べているテスト、`transfer` や `send` のように渡すガスが2,300に固定された送金、デプロイやバッチ処理のガス上限をコードや運用スクリプトに書き込んでいる箇所です。ガス量の固定値を検証するテストは期待値との不一致で失敗し、送金先の処理に必要なガスや固定したガス上限によっては、送金やデプロイも失敗する可能性があります。ただし、EIP-8037とEIP-8038は上の呼びかけのとおり仕様が見直される可能性があり、表の数値もHardhatの更新で変わりえます。メインネットの日程が出るまでは、数値を確定値として扱わず、CIにamsterdamのジョブを1本足して差分を追う使い方が現実的です。

## ローカルノード・フォーク・OPチェーンの模擬

### ローカルノードの起動とウォレット接続

テストはプロセス内の模擬チェーンで完結しますが、MetaMaskやフロントエンドから接続したいときは `npx hardhat node` でJSON-RPCサーバーを立てます。`http://127.0.0.1:8545/` で待ち受け、10,000 ETHを持つテスト用アカウントと秘密鍵が表示されます。この秘密鍵は誰でも知っている値なので、メインネットで送金に使うと資金を失います。別のターミナルから `--network localhost` を付ければ、デプロイやスクリプトをこのノードに向けられます。

### メインネットのフォーク設定とブロック番号の固定

実在するコントラクト（DEXやトークン）とのやり取りを試すときは、本物のネットワークの状態を手元に複製するフォークを使います。TypeScriptテストとSolidityテストで設定場所が違う点に注意してください。

```
export default defineConfig({
  plugins: [hardhatToolboxViemPlugin],
  solidity: "0.8.34",
  networks: {
    mainnetFork: {
      type: "edr-simulated",
      forking: {
        url: configVariable("MAINNET_RPC_URL"),
        blockNumber: 23819000,
      },
    },
  },
  test: {
    solidity: {
      forking: {
        url: configVariable("MAINNET_RPC_URL"),
        blockNumber: 23819000n,
      },
    },
  },
});
```

TypeScriptテストは `npx hardhat test nodejs --network mainnetFork` か、テスト内の `network.create("mainnetFork")` でフォークに接続します。Solidityテストは `test.solidity.forking` の設定で動き、ブロック番号は `23819000n` とBigIntで書きます（ネットワーク側は数値）。ブロック番号を固定すると結果が毎回同じになり、取得した状態がキャッシュされて2回目以降も速くなります。

### OP Stackの模擬とL1データ手数料

OP Stack系のL2では、手数料にL1へデータを書く分が加わります。Hardhat 3が模擬できるのは標準のOP Stackの動作までで、独自に改造したチェーン固有の挙動は対象外です。テンプレートの `hardhatOp` ネットワーク（`chainType: "op"`）で付属の `scripts/send-op-tx.ts` を実行すると、viemの `estimateL1Gas` が `Estimated L1 gas: 1600n` を返しました。同じスクリプトを `chainType: "l1"` のネットワークに向けると、viemのクライアントに `estimateL1Gas` が無いため実行時エラーで止まります。

3.21.0（2026年10月22日）からは、模擬OPネットワークのトランザクションが実際にL1データ手数料を払い、レシートにも載るようになりました（それまでは課金されていませんでした）。自分宛てに1 weiを送ったレシートには、`l1GasUsed` が1,600、`l1GasPrice` が10 gwei、`l1Fee` が1,097,845,000,000 wei（約0.0000011 ETH）と記録されました。L2の残高の増減を厳密に確かめるテストは、3.21.0以降ではこのL1手数料を差し引いて計算します。ただし、これは模擬チェーン上の値なので、本番の見積もりはフォークか実チェーンで取り直してください。L2の手数料の内訳は[ブロックチェーンのロールアップとは？レイヤー2の仕組み・種類と手数料・出金日数の比較【2026年版】](/tech/details/8797/)、OP Stackに乗るBaseの最近の変更は[Base Azulとは？マルチプルーフと出金日数・ノード移行、Denimで変わった前提【2026年11月】](/tech/details/11906/)で解説しています。

## Sepoliaへのデプロイと検証の手順

### keystoreによるRPCのURLと秘密鍵の管理

設定ファイルの `configVariable("SEPOLIA_PRIVATE_KEY")` は、値が必要になった時点で読み込まれる「設定変数」です。既定では同名の環境変数から読みますが、公式ガイドはコマンド履歴に残るなどの理由で環境変数を安全でないとしています。テンプレートに含まれる `hardhat-keystore` を使えば、パスワードで暗号化したファイルに保存できます。

```
npx hardhat keystore set SEPOLIA_RPC_URL
npx hardhat keystore set SEPOLIA_PRIVATE_KEY
npx hardhat keystore list
```

実行するとパスワードと値の入力を求められます。保存先は `npx hardhat keystore path` で確認でき、macOSでは `~/Library/Preferences/hardhat-nodejs/keystore.json` とプロジェクトの外でした。プロジェクト内の `.env` に書く場合と違い、`git add .` でうっかり秘密鍵をコミットする経路が無くなります。`keystore set` に `--dev` を付けると、本番用とは別の開発用keystoreに保存されます。開発用はパスワードなしで読み出せるので、公式ガイドの想定どおり、ローカル用のRPCのURLのような機密でない値だけに使います。

### OpenZeppelinのトークン実装とIgnitionのデプロイ定義

実務では、ERC-20などの標準実装をOpenZeppelin Contractsから読み込むのが一般的です。Hardhat 3ではnpmで入れるだけで、リマッピングの設定は要りません。今回入ったのは5.6.1でした。以下のコントラクトは `contracts/MyToken.sol` に、続くIgnitionモジュールは `ignition/modules/MyToken.ts` に保存します。

```
npm i -D @openzeppelin/contracts
```

```
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.34;

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MyToken is ERC20 {
  constructor(uint256 initialSupply) ERC20("MyToken", "MTK") {
    _mint(msg.sender, initialSupply);
  }
}
```

Hardhat Ignitionは、デプロイの手順を「モジュール」として宣言するしくみです。コンストラクタ引数は `m.getParameter()` で受け取ると、コードを書き換えずにネットワークごとに値を変えられます。

```
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";

export default buildModule("MyTokenModule", (m) => {
  const initialSupply = m.getParameter("initialSupply", 1_000_000n * 10n ** 18n);
  const token = m.contract("MyToken", [initialSupply]);
  return { token };
});
```

値を変えるときは、`{"MyTokenModule": {"initialSupply": "500000000000000000000000n"}}` のようなJSONファイルを作り、`--parameters` で渡します。BigIntは末尾に `n` を付けた数字の文字列で書き、Ignitionが読み込み時にBigIntへ戻します。手元の模擬チェーンで50万トークン分の値を渡すと、`totalSupply()` は `500000000000000000000000n` を返しました。テンプレートの `Counter` 用モジュールのように、`m.call()` でデプロイ後の関数呼び出しまで手順に含めることもできます。

### Sepoliaへのデプロイとソースコード検証

実行の前に、送信元アカウントにSepoliaのテストETH（フォーセットで入手）を入れておきます。Etherscanで検証する場合は、テンプレートの設定に無いAPIキーの項目を `defineConfig` に足し、値はkeystoreに入れます。

```
verify: {
  etherscan: {
    apiKey: configVariable("ETHERSCAN_API_KEY"),
  },
},
```

準備ができたら、Sepoliaへは次のコマンドで出します。進捗は `ignition/deployments/chain-11155111/` に記録されるので、途中でRPCが落ちても同じコマンドで続きから再開でき、完了済みの手順は二重に実行されません。

```
npx hardhat ignition deploy ignition/modules/MyToken.ts --network sepolia --verify
```

`--verify` を付けると、コンストラクタ引数も含めてデプロイ後の検証まで続けて行います。Ignitionを使わずにデプロイしたコントラクトは `npx hardhat verify --network sepolia <アドレス> <コンストラクタ引数>` のように、アドレスの後ろに引数を並べて検証します。引数が多い場合や配列を含む場合は、引数を書き出したモジュールを `--constructor-args-path` で渡します。対応先はEtherscan、Blockscout、Sourcifyの3つで、サブタスクを付けずに実行すると3つすべてに送ります。EtherscanはAPI v2に対応しているので、APIキー1つを対応チェーン間で共用でき、BlockscoutとSourcifyはキーが要りません。検証は既定で `production` プロファイルのビルドを使うため、別のプロファイルでデプロイした場合は `--build-profile` で合わせます。トークンの送金を署名1つで代行させる拡張は[EIP-3009（ERC-3009）とは？transferWithAuthorizationの仕組みと署名実装・チェーン別対応表【2026年11月】](/tech/details/9717/)で扱っています。

## FoundryとHardhat 3の違いと選び方

ローカルでプロジェクトを管理する開発ツールとして、ここではHardhat 3とFoundry（2026年11月2日公開のv1.9.0が最新）を比べます。かつての定番だったTruffleとGanacheは、2023年9月にConsensysが開発終了を発表し、GitHubのリポジトリもアーカイブされています。保守されないため、新規の採用は勧めません。

| 観点          | Hardhat 3            | Foundry                    |
| ----------- | -------------------- | -------------------------- |
| 本体の言語       | TypeScript＋Rust（EDR） | Rust                       |
| 導入          | npm                  | foundryupかバイナリ             |
| Solidityテスト | forge-std互換          | forge test                 |
| TS/JSテスト    | node:testかMocha      | なし                         |
| デプロイ        | Ignition（宣言型）        | forge script               |
| ローカルノード     | hardhat node         | anvil                      |
| OP系の模擬      | chainType: op        | `anvil --network optimism` |

両者の差は、Solidityテストの互換によって小さくなっています。Hardhatのテンプレートで作った `Counter.t.sol` と上の `Vault.t.sol`（わざと落とすテストを含む）を、ソースを1行も変えずにFoundry 1.9.0の `forge test` で実行したところ、成功4件・失敗1件と同じ結果になりました。用意した設定は、`foundry.toml` でソースとテストの場所を `contracts` にしたことと、`remappings.txt` の2行で `forge-std/` と `hardhat/` の参照先を指定したことです。このときforgeはpragmaを満たすsolc 0.8.38を自動で選び、Hardhat側の設定（0.8.34）とは別の版でコンパイルしました。ガスやバイトコードを両者で比べるなら、`foundry.toml` の `solc_version` で版を揃えます。確認できたのはこの規模のSolidityテストまでで、TypeScriptテストやデプロイの手順は移せません。乗り換えるときは、チートコードの対応状況と設定ファイルの差を公式の[Foundry互換性のドキュメント](https://hardhat.org/docs/reference/foundry-compatibility)で確かめてください。

判断はチームが書く言語で決めます。フロントエンドやバックエンドをTypeScriptで書いていて、viemやethers.jsのコードをテストとデプロイにも使い回したいならHardhat 3です。逆に、コントラクトだけを書くチームで、Node.jsの依存（今回のテンプレートで139パッケージ）を持ち込みたくないならFoundryが合います。Hardhat 3を選ばないほうがよいのは、社内のCIやサーバーがNode.js 20以前に固定されていて更新できない場合です。v22.13.0未満では動かないので、先にNode.jsの更新計画を立ててください。

## Hardhat 2から3への移行で詰まる箇所

公式の移行ガイドは、既存の設定を書き換えるのではなく、新しい設定ファイルを作って機能を1つずつ移す方法を勧めています。手順は、Node.jsの確認、`npx hardhat clean`、2系の依存（`hardhat`、`@nomiclabs/` や `@nomicfoundation/` で始まるもの、`solidity-coverage` と `hardhat-gas-reporter`）の削除、旧設定のリネーム、`package.json` への `"type": "module"` の追加という順です。実際に止まりやすいのは次の4点です。

- **プラグインを入れたのに効かない**：2系はimportするだけで有効になったが、3系は `plugins` 配列に入れないと何も起きない。
- **カスタムタスクが動かない**：`task("accounts", ..., async () => {})` の書き方は使えず、`setInlineAction()` で処理を渡して `.build()` し、設定の `tasks` 配列に登録する。
- **`hre.network` がそのまま使えない**：3系では接続を自分で作る。3.4.0（2026年4月16日）で `network.connect()` が非推奨になり、3.22.0でも実行すると「hre.network.connect() is deprecated and will be removed in a future version」と警告が出る。`network.create()` か `network.getOrCreate()` に置き換える。
- **`extendEnvironment` と `extendConfig` が無い**：フックの仕組みに置き換わった。HREにフィールドを足したい場合は、プラグインとして `hre` の `created` フックで値を入れ、型拡張で型を宣言する形に書き直す。設定ファイルの中で直接HREを書き換えていた自作の拡張があるプロジェクトは、ここが移行の最大の作業になる。

3.3.0までは公式テンプレートのテストとスクリプト自体が `network.connect()` を使っていました。そのため、Hardhat 3を扱った記事でも3.4.0より前に書かれたものはこの書き方になっています。写したコードで非推奨の警告が出たら、`network.create()` に読み替えてください。

## よくある質問

### Hardhatとは何ですか？

Ethereumなど EVM 互換チェーンのスマートコントラクトを、コンパイル、テスト、デバッグ、デプロイするための開発環境です。Nomic Foundationが開発するオープンソースで、npmで配布されています。2026年11月13日時点の最新版はHardhat 3.22.0です。

### Hardhatの読み方は？

「ハードハット」と読みます。英語の hard hat（工事用のヘルメット）と同じ綴りです。英語の検索結果では作業用ヘルメットの商品も混ざるので、開発ツールを探すときは「hardhat solidity」のように言語名を足すと絞り込めます。

### Hardhat 2はいつまで使えますか？

Hegotaハードフォークのメインネット有効化か2027年6月1日のどちらか早いほうでサポートが終了し、以後はセキュリティ修正も出ません。それまではGlamsterdamハードフォークへの対応とバグ修正が続きます。2系の最新は2026年11月11日公開の2.29.2です。

### HardhatとRemixはどう使い分けますか？

Remixはブラウザだけで動くIDEで、インストールなしで数十行のコントラクトを試すのに向きます。コードをGitで管理し、テストを自動で回し、CIからデプロイするようになったらHardhatかFoundryに移ります。

### Hardhatの利用にお金はかかりますか？

Hardhat自体は無料のオープンソースです。費用がかかるのは外部サービスで、メインネットへのデプロイにはETHのガス代が必要です。SepoliaのテストETHは無料のフォーセットで手に入りますが、RPCプロバイダーやEtherscanのAPIは利用量によって有料プランが必要になります。

## 関連記事

- [Solidity入門｜開発環境の作り方・基本構文・Sepoliaデプロイ手順【2026年10月】](/tech/details/8629/)
- [ブロックチェーンのロールアップとは？レイヤー2の仕組み・種類と手数料・出金日数の比較【2026年版】](/tech/details/8797/)
- [Base Azulとは？マルチプルーフと出金日数・ノード移行、Denimで変わった前提【2026年11月】](/tech/details/11906/)
- [EIP-3009（ERC-3009）とは？transferWithAuthorizationの仕組みと署名実装・チェーン別対応表【2026年11月】](/tech/details/9717/)
- [EVMbenchとは？AIのスマートコントラクト監査力を測る3モードと最新の結果](/tech/details/11080/)

---

出典: [Hardhatとは？Hardhat 3の使い方：導入・テスト・デプロイとFoundryとの違い【2026年11月】](<https://www.issoh.co.jp/tech/details/12466/>)（株式会社一創）
