Hardhatで学ぶEthereumトランザクション入門
はじめに
ブロックチェーン初心者向けの記事は数多くありますが、その多くはスマートコントラクトに焦点を当てています。
一方でアカウント間の送金や送金後の残高確認といった、基本的なトランザクションの流れを扱った記事は意外と少ないように思います。
本記事では「Webエンジニアとしてスマートコントラクトに興味はあるが、まずトランザクションの仕組みを知りたい」という方を対象に、ローカル環境で1ETHの送金 → ブロック承認 → 残高変化の確認までを解説します。
トランザクションとは?
イーサリアムにおけるトランザクションは送金だけに限りません。ネットワーク上でアカウントが何かアクションを起こすときには必ずトランザクションが発生します。
例:
- 送金:ETHを別のアドレスへ送る
- スマートコントラクトの呼び出し:関数を実行して状態を変更する
- データの記録:チェーン上に任意のデータを書き込む
つまり「ブロックチェーンに対して何かを書き込む処理」はすべてトランザクションです。
逆に残高を読む、コントラクトの状態を参照する、といった読み取り処理はトランザクションには含まれません。
トランザクションの中身
Transaction objectには次のような情報が含まれます。
- nonce: 送信者がこれまでに送ったトランザクションの数
- to: 送金先アドレス
- value: 送金するETHの量
- gasLimit: この処理に使える最大ガス量
- gasPrice: 1ガスあたりのETH支払額
- v / r / s: 秘密鍵による署名データ
普通のWebアプリに例えると「誰が、どこに、いくら送るか + 署名」と考えるとわかりやすいです。
Hardhatとは?
Hardhat(ハードハット)は、Ethereumアプリケーションを開発するための開発環境/フレームワークです。
通常、Ethereumにトランザクションを送るには「実際のネットワーク(メインネットやテストネット)に接続して、ETHを使って実行する」必要があります。これは初心者が学ぶにはハードルが高く、ちょっとしたミスでも資金を失うリスクがあります。
Hardhatを使うと、以下のようなことが可能になります。
ローカルにEthereumノードを立ち上げられる
→ 自分のPC上でEthereumネットワークを再現できる
テスト用アカウントが自動生成される
→ たくさんのアカウントに十分なETHが配布された状態からスタートできる
トランザクションやスマートコントラクトを安全に実験できる
→ 無料で失敗してもOK
TypeScript/JavaScriptから簡単に扱える
→ 普段のWeb開発スキルをそのまま活かせる
要するに、HardhatはWebエンジニアにとってのnpm run devで立ち上げるローカルサーバーのようなものです。
本番環境にデプロイする前に、ローカルで何度でも試行錯誤できるので、Ethereumやスマートコントラクト学習の入口として最適です。
Hardhat3の公式ドキュメント:
環境構築
では実際にトランザクションをローカル環境で実行してみましょう。
Node.js v22
Hardhat v3.0.6
まずプロジェクトを作成し、Hardhatを初期化します。
mkdir eth-tx-tutorial && cd eth-tx-tutorial
npm init -y
npx hardhat --init
Need to install the following packages:
hardhat@3.0.6
Ok to proceed? (y) y
█████ █████ ███ ███ ███ ██████
░░███ ░░███ ░███ ░███ ░███ ███░░███
░███ ░███ ██████ ████████ ███████ ░███████ ██████ ███████ ░░░ ░███
░██████████ ░░░░░███░░███░░███ ███░░███ ░███░░███ ░░░░░███░░░███░ ████░
░███░░░░███ ███████ ░███ ░░░ ░███ ░███ ░███ ░███ ███████ ░███ ░░░░███
░███ ░███ ███░░███ ░███ ░███ ░███ ░███ ░███ ███░░███ ░███ ███ ███ ░███
█████ █████░░███████ █████ ░░███████ ████ █████░░███████ ░░█████ ░░██████
░░░░░ ░░░░░ ░░░░░░░ ░░░░░ ░░░░░░░ ░░░░ ░░░░░ ░░░░░░░ ░░░░░ ░░░░░░
👷 Welcome to Hardhat v3.0.6 👷
✔ Which version of Hardhat would you like to use? · hardhat-3
✔ Where would you like to initialize the project?
Please provide either a relative or an absolute path: · .
✔ What type of project would you like to initialize? · mocha-ethers
✨ Template files copied ✨
✔ You need to install the necessary dependencies using the following command:
npm install --save-dev "hardhat@^3.0.6" "@nomicfoundation/hardhat-toolbox-mocha-ethers@^3.0.0" "@nomicfoundation/hardhat-ignition@^3.0.0" "@types/chai@^4.2.0" "@types/chai-as-promised@^8.0.1" "@types/mocha@>=10.0.10" "@types/node@^22.8.5" "chai@^5.1.2" "ethers@^6.14.0" "forge-std@foundry-rs/forge-std#v1.9.4" "mocha@^11.0.0" "typescript@~5.8.0"
Do you want to run it now? (Y/n) · false
Give Hardhat a star on Github if you're enjoying it! ⭐️✨
セットアップ中にいくつか質問されます。今回は以下を選択しました。
- Which version of Hardhat would you like to use?: hardhat3
- Where would you like to initialize the project?: ./
- What type of project would you like to initialize?: mocha-ethers
- Do you want to run it now? (Y/n): n
次に依存パッケージをインストールします。
npm install --save-dev "ethers hardhat@^3.0.6" "@nomicfoundation/hardhat-toolbox-mocha-ethers@^3.0.0" "@nomicfoundation/hardhat-ignition@^3.0.0" "@types/chai@^4.2.0" "@types/chai-as-promised@^8.0.1" "@types/mocha@>=10.0.10" "@types/node@^22.8.5" "chai@^5.1.2" "ethers@^6.14.0" "forge-std@foundry-rs/forge-std#v1.9.4" "mocha@^11.0.0" "typescript@~5.8.0"
初期化が終わると以下のディレクトリが作成されます(Hardhat v3の場合)。
hardhat
├── artifacts
├── cache
├── contracts
├── ignition
├── node_modules
├── scripts
├── test
├── .gitignore
├── hardhat.config.js
├── package-lock.json
├── package.json
├── README.md
└── tsconfig.json
今回はこのscriptsディレクトリの中にトランザクション用のスクリプトを作成して、npx hardhat runコマンドで実行させようと思います。
scripts配下にsend-tx.tsを作成して下記のコードをコピペしてください。
touch ./scripts/send-tx.ts
// 1ETHを送る処理
import { JsonRpcProvider, Wallet, parseEther, formatEther } from "ethers";
const RPC_URL = "http://127.0.0.1:8545";
const SENDER_PK = "0x<送信者の秘密鍵>";
const TO = "0x<受信者のアドレス>";
async function main() {
const provider = new JsonRpcProvider(RPC_URL);
const sender = new Wallet(SENDER_PK, provider);
console.log("送信者:", sender.address);
console.log("受信者:", TO);
// --- 残高を表示(送信前)---
const b0s = await provider.getBalance(sender.address);
const b0r = await provider.getBalance(TO);
console.log("送信前 → 送信者:", formatEther(b0s), "ETH");
console.log("送信前 → 受信者:", formatEther(b0r), "ETH");
// --- 1 ETH を送信 ---
const tx = await sender.sendTransaction({ to: TO, value: parseEther("1.0") });
console.log("トランザクション送信:", tx.hash);
// --- トランザクションがブロックに入るまで待機 ---
const receipt = await tx.wait();
if (!receipt) throw new Error("トランザクションに失敗");
console.log("ブロックNo.", receipt.blockNumber);
// --- 残高を表示(送信後)---
const b1s = await provider.getBalance(sender.address, receipt.blockNumber);
const b1r = await provider.getBalance(TO, receipt.blockNumber);
console.log("送信後 → 送信者:", formatEther(b1s), "ETH");
console.log("送信後 → 受信者:", formatEther(b1r), "ETH");
}
main().catch((e) => { console.error(e); process.exit(1); });
コードの流れはシンプルで、以下の手順になります。
- 送信前に送信者と受信者の残高をログ表示
- 1ETHを送金するトランザクションを発行
- トランザクションがブロックに含まれるまで待機
- 承認後の残高を再度ログ表示し、差分を確認
provider.getBalance の引数に receipt.blockNumber を渡しているのは、最新ブロック番号を指定しないと送金前の残高が返ってしまうためです。
残高確認だけを行いたい場合は、専用のスクリプトを用意すると便利です。
touch ./scripts/check-balance.ts
import { JsonRpcProvider, formatEther } from "ethers";
const provider = new JsonRpcProvider("http://127.0.0.1:8545");
const ADDRESSES = [
// 残高を確認したいアドレス
"0x<account_address>",
"0x<account_address>",
];
async function main() {
for (const addr of ADDRESSES) {
const balance = await provider.getBalance(addr);
console.log(addr, ":", formatEther(balance), "ETH");
}
}
main().catch(console.error);
実行方法
スクリプトが準備できたら、ローカルノードを立ち上げます。
npx hardhat node
起動すると20個のテストアカウントが自動生成され、各アカウントに10000ETHが割り当てられます。これはあくまでローカル専用のテスト用資金であり、本番ネットワークでは絶対に使わないでください。
Started HTTP and WebSocket JSON-RPC server at http://127.0.0.1:8545/
Accounts
========
WARNING: Funds sent on live network to accounts with publicly known private keys WILL BE LOST.
Account #0: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 (10000 ETH)
Private Key: 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
Account #1: 0x70997970c51812dc3a010c7d01b50e0d17dc79c8 (10000 ETH)
Private Key: 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
.
.
.
Account #19: 0x8626f6940e2eb28930efb4cef49b2d1f2c9c1199 (10000 ETH)
Private Key: 0xdf57089febbacf7ba0bc227dafbffa9fc08a93fdc68e1e42411a14efcf23656e
WARNING: Funds sent on live network to accounts with publicly known private keys WILL BE LOST.
上記のログで表示されているように、JSON-RPCサーバーがlocalhostのポート8545に立ち上がります。このhttp://127.0.0.1:8545/がRPCエンドポイントになります。
テスト用アカウントのアドレスと秘密鍵も表示されるので、これらを先ほどのsend-tx.tsにあるSENDER_PKとTOに設定します。
今回はAccount #0 → Account #1への送金としましょう。
// Account #0の秘密鍵
const SENDER_PK = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
// Account #1のアドレス
const TO = "0x70997970c51812dc3a010c7d01b50e0d17dc79c8";
今回の例ではローカル環境なので全てハードコーディングしていますが、実際のトランザクションでは送信者が秘密鍵で署名したうえで、受信者のアドレスに送金する必要があります。
ただし、仕組み自体はローカルでもメインネットでも同じで、秘密鍵を持っていれば他のアカウントに送金できる点は変わりません。
秘密鍵とアドレスを設定できたら、あとは実際にトランザクションを実行してみましょう。HardhatではCLI上でTypeScriptをそのまま動かすことができます。
設定後、スクリプトを実行します。
npx hardhat run scripts/send-tx.ts
出力例:
送信者: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
受信者: 0x70997970c51812dc3a010c7d01b50e0d17dc79c8
送信前 → 送信者: 10000.0 ETH
送信前 → 受信者: 10000.0 ETH
トランザクション送信: 0x76c1179848f6d7802b1eb7c37ad8dafa170bdbc3035dd31b80a4559987788c82
ブロックNo. 1
送信後 → 送信者: 9998.999960625 ETH
送信後 → 受信者: 10001.0 ETH
うまく実行できると、送信元から1ETHが減り、受信者の残高が増えていることを確認できます。
(送信者の残高: 9998.999960625 ETH、受信者の残高: 10001.0 ETH)
まとめ
今回の例を通して、トランザクションはアカウント間の送金や署名といった情報をひとまとめにしたデータであることを学びました。Hardhatのローカル環境を使えば、安全に手元でトランザクションを検証できます。まずは送金トランザクションを理解することで、その先にあるスマートコントラクトの呼び出しや応用的な使い方にもつながっていきます。
次回は、スマートコントラクトを呼び出すトランザクションについて解説していきます。
Discussion