はじめに
当記事は、「Claude Codeを使って デジタルアセットを学ぶ」シリーズのStep1です。Step4まであるのでぜひご覧になってください。
Ethereumの実機での稼働確認を始めると、最初にぶつかる壁の1つが「環境構築」です。
まずはローカル環境でスマートコントラクトを作成・デプロイ・実行できる状態を作りたいと思います。
今回は、Macbook M1環境でHardhatを使い、シンプルなCounterコントラクトをローカルにデプロイし、トランザクションを送信してブロックに反映される流れを確認します。
この記事では、各手順で何をセットアップしているのか、そしてそれぞれが何に使われるのかを説明します。
開発環境の構成
以下は、今回構築するローカル開発環境のイメージです。
1. 環境準備とは何をするのか
最初に行うのは、Node.jsとnpmの確認、そしてHardhatの導入です。
何をセットアップするのか
- Node.js
- JavaScriptの実行環境
- HardhatはNode.js上で動作するため必要
- npm
- Node.js向けのパッケージ管理ツール
- Hardhatや関連ライブラリをインストールするために使う
- Hardhat
- Ethereum開発を効率よく行うための開発フレームワーク
- コントラクトのコンパイル、テスト、ローカルノードの起動、デプロイを支援
- Hardhat Toolbox
- Hardhatをより実用的にするための拡張パッケージ
- 開発に必要な便利機能をまとめて提供
何に使うのか
この準備ができることで、次のような作業ができるようになります。
- Solidityのコードをコンパイルする
- ローカルEthereumノードを立ち上げる
- コントラクトをデプロイする
- トランザクションを送信して状態を変更する
つまり、開発環境の土台を作る工程です。
2. Hardhatプロジェクトを作る意味
次に、Hardhatのプロジェクト構成を作ります。
何をセットアップするのか
- package.json
- プロジェクトの設定ファイル
- 依存パッケージや実行スクリプトを管理
- hardhat.config.js
- Hardhatの動作設定を記述するファイル
- Solidityのバージョンやネットワーク設定を定義
何に使うのか
この設定があることで、Hardhatが「どのコントラクトを扱い、どのネットワークに接続するか」を理解できます。
ローカル開発では、特に次の2点が重要です。
- ローカルノードに接続する設定
- Solidityのコンパイル設定
この工程によって、Hardhatが開発を始めるための準備が整います。
3. Solidityとは何か
ここで使うSolidityは、Ethereum上でスマートコントラクトを書くための専用言語です。
Solidityの役割
- Ethereum上で自動実行されるルールをコードとして記述する
- ブロックチェーン上で状態を保持し、関数を通してその状態を変更する
- コントラクトの挙動を、他の人が読み取り・検証できる形で表現する
つまり、Solidityは「Ethereumで動くプログラムを書くための言語」です。
たとえばCounterコントラクトでは
- 数字を保存するための変数を用意する
-
increment()で値を増やす -
getCount()で現在の値を読む
このような処理をSolidityで記述します。
なぜ重要なのか
Solidityを理解しておくと、Hardhatでコンパイルする対象が何か、またデプロイ後にどのような処理が実行されるのかを自然に理解できます。
Solidityで書かれたコードは、EVM(Ethereum Virtual Machine)上で実行されるため、ブロックチェーンの世界で動く「ルール」として扱われます。
4. コントラクトとは何か
ここで扱う「コントラクト」という言葉は、一般的には「契約」や「ルールを定義したもの」という意味で使われます。
通常のコントラクトとの違い
従来の契約では、紙や契約書の中にルールが書かれ、当事者がその内容を守ることを前提にしていました。
一方、ブロックチェーン上のコントラクトは、コードとしてルールを記述し、その内容がネットワーク上で自動的に実行される仕組みです。
スマートコントラクトとは
スマートコントラクトとは、ブロックチェーン上で自動実行されるプログラムです。
たとえば、次のようなルールをコードで表現できます。
- 条件を満たしたら自動で処理を実行する
- 特定の関数を呼ぶと状態を変更する
- 誰がどの操作をできるかを制御する
このため、スマートコントラクトは単なる「コード」ではなく、ブロックチェーン上で動く自動的な契約(プログラム)のようなものです。
具体例
今回作るCounterコントラクトでは、次のようなルールを定義しています。
- カウントの値を保存する
-
increment()を呼ぶと値を増やす -
getCount()で現在の値を確認できる
このように、スマートコントラクトは「状態を持ち、関数を通じてその状態を変える」仕組みとして理解すると分かりやすいです。
なぜ重要なのか
スマートコントラクトがあることで、中央管理者なしに、一定のルールに従って処理を実行できます。
そのため、送金、保管、証明、投票、NFTの発行など、さまざまな応用に使われています。
5. Counterコントラクトを作る意味
ここで、実際にスマートコントラクトを書きます。今回作る contracts/Counter.sol の全文は次のとおりです。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract Counter {
uint256 private count;
event CountChanged(uint256 newCount);
function increment() public {
count += 1;
emit CountChanged(count);
}
function decrement() public {
require(count > 0, 'Counter: count is zero');
count -= 1;
emit CountChanged(count);
}
function getCount() public view returns (uint256) {
return count;
}
}
このコードの読みどころを補足します。
-
// SPDX-License-Identifierとpragma solidity… ライセンス表記と、対象とするSolidityのバージョン指定。お決まりの2行です。 -
uint256 private count;… 0以上の整数を保存する状態変数。privateは「外部から直接は読めない」という意味で、値はgetCount()を通して取得します。 -
event CountChanged/emit… 状態が変わったことをイベント(ログ)として記録する仕組み。あとから「いつ値が変わったか」を追跡できます。 -
increment()/decrement()… 値を増減させる関数。decrement()のrequire(count > 0, ...)は「0より小さくしようとしたら処理を中止する」という**ガード(条件チェック)**です。 -
getCount()のview… 「状態を読むだけで変更しない」という宣言。view関数の呼び出しはトランザクションにならず、ガス(手数料)もかかりません。ここが後半で出てくる「読み取り」と「状態変更」の違いに繋がります。
何をセットアップするのか
Counterコントラクトは、シンプルな状態変数と関数を持つ構成です。
- count
- コントラクト上に保存される状態変数
- これはブロックチェーン上に永続的に記録されるデータ
- increment()
- カウントを1増やす関数
- decrement()
- カウントを1減らす関数
- getCount()
- 現在の値を読み取る関数
何に使うのか
このコントラクトの役割は、スマートコントラクトの基本動作を確認することです。
特に以下の概念を体験できます。
- 状態を保存する
- 関数で状態を変更する
- 関数を呼ぶことでブロックチェーン上のデータを操作する
ここは「実際にブロックチェーン上で動くコード」を書く最初のステップです。
6. デプロイスクリプトとは何か
コントラクトを作っただけでは、まだブロックチェーン上には存在しません。
そこで、デプロイ用のスクリプトを用意します。
何をセットアップするのか
- deploy.js
- コントラクトをネットワークに送信するためのスクリプト
- Hardhatに対して「このコントラクトをデプロイして」と指示
何に使うのか
このスクリプトの役割は、スマートコントラクトをチェーン上に展開することです。
デプロイとは、簡単に言えば「コードをブロックチェーンに登録する行為」です。
実際には、次のような流れになります。
- コントラクトのバイトコードを生成
- トランザクションとして送信
- ノードがそれを受け取り、チェーン上に配置
この工程で、コントラクトは「利用可能なオンチェーンの資産」になります。
7. トランザクション実行とは何か
コントラクトをデプロイしたら、次に関数を呼び出して状態変更を行います。
何をセットアップするのか
- interact.js
- すでにデプロイ済みのコントラクトに対して、関数を呼び出すスクリプト
何に使うのか
このスクリプトでは、例えばincrement()を実行して、カウントを増やします。
ここで重要なのは、関数呼び出しが単なるローカル処理ではなく、トランザクションとして送信される点です。
つまり、次のことが起きます。
- 送信者の署名が付く
- ノードに伝わる
- ブロックに取り込まれる
- 状態が更新される
この流れを体験することで、Ethereumでの「状態変更」がどういう仕組みかを理解しやすくなります。
8. ブロック確認とは何をするのか
トランザクションを送信したら、実際にブロックに含まれたかどうかを確認します。
何をセットアップするのか
- ブロック番号
- トランザクションが取り込まれたブロックの位置
- トランザクションハッシュ
- そのトランザクションを一意に識別するID
- ブロックハッシュ
- ブロックそのものを識別するID
何に使うのか
これらを確認することで、次のことが分かります。
- トランザクションが実際に処理されたか
- どのブロックに含まれたか
- ブロックチェーン上で記録されたか
この工程によって、単なる「関数呼び出し」ではなく、ブロックチェーンに記録された事実として扱われたことを確認できます。
9. 実際に動かして検証する
ここまでは「何をするか」を説明してきました。ここからは、実際にMac上でコマンドを実行し、出力を見ながら一連の流れを確認します。この記事の出力は、すべて実際に動かして取得したものです。
環境メモ:
今回の検証は Node.js v25 / Hardhat 2 系で実施しました。Hardhat 2 系が公式にサポートするのは Node.js の偶数バージョン(18 / 20 / 22)です。新しすぎる Node では起動時に警告が出ますが、今回の範囲では問題なく動作しました。安定して使いたい場合は Node.js 20 LTS を推奨します。
9-1. コントラクトをコンパイルする
まず、Solidityのソースコードを EVMが実行できるバイトコード に変換します。
npx hardhat compile
contracts/Counter.sol がコンパイルされ、結果(ABIとバイトコード)が artifacts/ フォルダに生成されます。
ここで言う ABI(Application Binary Interface) とは、「このコントラクトにはどんな関数があり、どう呼び出すか」を定義した説明書のようなものです。後でスクリプトから関数を呼ぶときに使われます。
9-2. ローカルEthereumノードを起動する
別のターミナルで、自分のPCの中だけで動く「練習用のEthereum」を立ち上げます。
npx hardhat node
起動すると、http://127.0.0.1:8545 で待ち受けるノードが立ち上がり、テスト用のアカウント(それぞれ10000 ETHを持つ)が複数表示されます。本物のETHではないので、いくら使っても問題ありません。
このノードが、これから送るトランザクションを受け取り、ブロックに取り込む役割を担います。
9-3. コントラクトをデプロイする
ノードを起動したまま、もう一方のターミナルでデプロイを実行します。
npx hardhat run scripts/deploy.js --network localhost
実際の出力:
Counter deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Transaction hash: 0x31fb031d0c6068ac38a51210302f4e007d7fa29a26f5ce5299ff62d725523eb7
Deployment info saved to: .../deployments/localhost.json
ここで分かることは次の3点です。
- Counter deployed to … コントラクトがチェーン上に配置された住所(アドレス)。今後このアドレス宛に関数を呼び出します。
- Transaction hash … デプロイという行為自体も1つのトランザクションであり、固有のハッシュ(ID)を持ちます。
- デプロイ情報を
deployments/localhost.jsonに保存しているので、次のスクリプトはこのアドレスを読み込んで再利用できます。
ポイント: デプロイも「トランザクションの一種」 です。違いは「新しいコントラクトを作るトランザクション」である点だけです。
補足:
アドレスやトランザクションハッシュは実行のたびに変わります。この記事の値はあくまで一例なので、ご自身の環境では別の値が表示されます。
9-4. 関数を呼び出してトランザクションを送る
デプロイ済みのコントラクトに対して increment() を実行し、カウントを増やします。
npx hardhat run scripts/interact.js --network localhost
実際の出力:
Before: 0
After: 1
Transaction hash: 0xe4eca5d94b504e83e8706afa93edfdbab1ba9585edf363916883eb51a4a56098
Latest block number: 2
Latest block hash: 0xcc2d871a72867e636d604635b3d0713e2cd28200c0dfa149cad7aa7d57cfb847
読み取れることは次のとおりです。
-
Before: 0 → After: 1 …
increment()によって、チェーン上に保存されたcountが確かに変化しました。 - Transaction hash … この状態変更も1つのトランザクションとして記録されました。
-
Latest block number / hash … そのトランザクションが「ブロック番号2」に取り込まれたことが分かります。デプロイがブロック1、
increment()がブロック2、という順番です。
gasの比較
| 処理 | Gas used |
|---|---|
| deploy(コード全体を保存) | 213,802 |
| increment()(数字を+1するだけ) | 44,836 |
同じ「トランザクション」でも、実行するタスクが重いほどgasを多く消費することが確認できました。デプロイはコード丸ごと保存で重い、increment()は変数を1増やすだけで軽い。これがgasの本質です。
9-5. ブロックの中身を生のRPCで覗いてみる
「本当にブロックに入っているのか?」を、Hardhatを介さず、Ethereumの標準API(JSON-RPC)で直接確認します。eth_getBlockByNumber は「指定した番号のブロックを取り出す」RPCメソッドです(番号は16進数で渡します)。
RPCメソッド:
ネットワークで繋がれた別のコンピュータ(サーバー)上にある関数やメソッドを、まるで自分のプログラム内にあるローカルな関数のように呼び出して実行するための仕組み
ブロック1(デプロイのトランザクション)
curl -s -X POST http://127.0.0.1:8545 \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["0x1",true],"id":1}'
返ってきたブロック1の中身(抜粋):
{
"result": {
"number": "0x1",
"hash": "0x1f2d7efc69d0995d43abc53e4ca2d676aeef6014a2ea90528a8161e412225041",
"transactions": [
{
"hash": "0x31fb031d0c6068ac38a51210302f4e007d7fa29a26f5ce5299ff62d725523eb7",
"from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"to": null,
"input": "0x60806040...(コントラクトのバイトコード全体が続く)"
}
]
}
}
ここで注目すべきは to: null です。通常のトランザクションには宛先(to)がありますが、**デプロイのトランザクションだけは to が空(null)**になります。「宛先がない=新しいコントラクトを生成するトランザクション」という意味で、これがデプロイと普通の関数呼び出しを見分ける決定的な違いです。input には、デプロイするコントラクトのバイトコードがまるごと入っています。
ブロック2(increment() のトランザクション)
curl -s -X POST http://127.0.0.1:8545 \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["0x2",true],"id":1}'
返ってきたブロック2の中身(抜粋):
{
"result": {
"number": "0x2",
"hash": "0xcc2d871a72867e636d604635b3d0713e2cd28200c0dfa149cad7aa7d57cfb847",
"parentHash": "0x1f2d7efc69d0995d43abc53e4ca2d676aeef6014a2ea90528a8161e412225041",
"gasUsed": "0xaf24",
"transactions": [
{
"hash": "0xe4eca5d94b504e83e8706afa93edfdbab1ba9585edf363916883eb51a4a56098",
"from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"to": "0x5fbdb2315678afecb367f032d93f642f64180aa3",
"value": "0x0",
"input": "0xd09de08a",
"chainId": "0x7a69"
}
]
}
}
この生データから、ブログ全体で説明してきたことが事実として裏付けられます。
-
number: "0x2"… 16進数の0x2= 10進数の 2。interact.jsが表示した「ブロック番号2」と一致 -
transactions[].hash… 先ほどのトランザクションハッシュと完全に一致 -
from… 送信者(テスト用アカウント)。to… 呼び出し先(デプロイしたCounterのアドレス) ←今度はちゃんと宛先がある -
value: "0x0"… 送金額0。今回はETHを送らず、関数を呼んだだけであるためゼロ -
input: "0xd09de08a"… どの関数を呼んだか を表すデータ。これはincrement()という文字列をハッシュ化した先頭4バイト(関数セレクタ)で、increment()を呼んだ証拠 -
chainId: "0x7a69"…0x7a69= 31337。Hardhatローカルネットワークの識別番号 -
gasUsed: "0xaf24"… このトランザクションの処理に使われた手数料の単位(ガス)。0xaf24= 44836。状態を書き換える処理には必ずガスがかかる
gasとは
処理の手間賃を測るための単位」 です。あなたがノードに何か処理をお願いすると、その計算量に応じて gas を消費し、その分の手数料を払います。
なぜこんな仕組みがあるのか 🧠
Ethereumは世界中のコンピュータ(ノード)が同じ計算を実行する仕組みです。もし手数料がタダだったら…
- 「無限ループ」みたいな悪質なコードを送られてネットワークが止まる
- スパムだらけになる
これを防ぐため、「重い処理ほど gas をたくさん払え」 というルールにしています。計算を実行してくれるノードへの報酬にもなります。
実際の手数料は?
手数料 = gas used × gas price(1ガスあたりの単価)
支払いは ETH で行われます。ただし今回はローカルのテスト用ETHなので、いくら使っても現実のお金は減りません。本番ネットワーク(メインネット)では本物のETHが必要で、混雑すると gas price が上がって手数料が高くなります。
「チェーン(鎖)」を自分の目で確認する
2つのブロックの値をよく見比べてみると、、、↓
- ブロック1の
hash:0x1f2d7efc…412225041 - ブロック2の
parentHash:0x1f2d7efc…412225041
ブロック2の parentHash が、ブロック1の hash と完全に一致しています。 各ブロックは「1つ前のブロックのハッシュ」を自分の中に持っており、こうしてブロック同士が鎖(chain)のように前へ前へと繋がっています。これこそが ブロックチェーン という名前の由来です。もし過去のブロックを誰かが改ざんすれば、そのブロックのハッシュが変わり、後続ブロックの parentHash と食い違ってしまうため、改ざんがすぐに発覚する——という仕組みの土台にもなっています。
10. なぜこの流れで動くのか
ここが最も重要なポイントです。
仕組みを簡単に説明すると
- Solidityでコントラクトを書く
- Hardhatがそれをコンパイルする
- デプロイスクリプトでトランザクションを送信する
- ノードがそのトランザクションを受け取る
- ブロックに取り込まれる
- コントラクトの状態が更新される
重要な概念
- Solidity
- スマートコントラクトを書くための言語
- EVM
- Ethereum仮想マシン
- コントラクトのコードはEVM上で実行される
- トランザクション
- 状態変更を要求する記録
- ブロック
- 複数のトランザクションをまとめて記録する単位
つまり、スマートコントラクトは「コード」だけではなく、ブロックチェーン上で実行・記録される処理そのものです。
11. まとめ
今回は、Macbook M1環境でHardhatを使い、ローカルEthereum開発環境を構築する流れを確認しました。
各手順で何をセットアップしているかを整理すると、次のようになります。
- 環境準備
- 開発に必要な実行環境とツールを導入する
- Hardhatプロジェクト作成
- 開発設定を整える
- Counterコントラクト作成
- 実際のスマートコントラクトを書く
- デプロイスクリプト
- コントラクトをチェーン上に登録する
- トランザクション実行
- 状態変更を発生させる
- ブロック確認
- その変更がブロックチェーンに記録されたことを確認する
今回実際に自分のMacbook上で環境をセットアップし、コントラクトの作成、デプロイ、そしてトランザクションの実行をして、ブロックに情報が書き込まれることを確認できました。そして連続するブロックはハッシュで繋がっていて、そのブロックの連鎖がブロックチェーンの由来であり、データ変更履歴の担保であることを理解できたので非常に有意義な検証となりました。
今後、この技術が浸透して、今の中央集権的な情報管理から分散型の情報管理へと変遷していくと世の中の仕組みが大きく変わりそうですね。既にこの技術を使ったサービスが誕生していて、広く浸透してはいないが実際に使われているのはエキサイティングだと思います。その波に乗れるように検証を続けていこうと思います。
デジタルアセットを学ぶロードマップ