Day9:DApp フロント(React + ethers)— ウォレット接続/ネットワーク切替/残高・送金UI

← 目次 前: Day8 次: Day10

学習目的

  • ブラウザから MetaMask 等に接続し、アカウント・ネットワークを取得できるようになる。
  • ネットワーク切替(Local/Sepolia/Optimism)とエラー処理の考え方を理解し、典型的な失敗を切り分けできるようになる。
  • ETH 残高、ERC‑20残高の表示と送金UIを動かして確認できるようになる。

まず docs/curriculum/index.md の「共通の前提(動作確認済みバージョン含む)」を確認してから進める。


0. 前提

  • フロントエンドは dapp/ に同梱してある(新規作成不要)。
  • Day5 で MyToken をデプロイして、アドレスを控えている。
  • ブラウザに MetaMask 等のウォレット拡張が入っている。
  • ミニプロジェクト(通しで作るもの):Day14 を“完成”としてつなぐ(全体像:docs/curriculum/Project.md
  • 先に読む付録:docs/appendix/glossary.md(用語に迷ったとき)
  • 触るファイル(主なもの):dapp/.env.local / dapp/src/App.tsx / dapp/src/lib/web3.ts
  • 今回触らないこと:UI/UXの作り込み(接続と切り分けが主題)
  • 最短手順(迷ったらここ):localhost を起動 → MyToken を deploy → 専用の学習用 wallet へ ETH / token を送る → .env.local に chainId / address を設定 → Connect / 残高 / Send を確認

0.1 どのチェーンで動かすか

  • ローカル(推奨):Hardhat node(chainId=31337)
  • テストネット:Sepolia(chainId=11155111)
  • L2:Optimism(chainId=10)

1. DApp の設定ファイルを用意する

npm run dapp:ci:safe
cp dapp/.env.example dapp/.env.local

dapp/.env.local を編集する:

VITE_CHAIN_ID=31337
VITE_TOKEN_ADDRESS=0x...   # MyToken(同じチェーン上)のアドレス
VITE_EVENT_TOKEN=          # 任意:Day10で使う

VITE_TOKEN_ADDRESS は 2章で deploy した後に設定する。このファイルは DApp の公開設定であり、秘密鍵を置かない。


2. ローカルで動かす(Hardhat node)

2.1 Hardhat node を起動する

npx hardhat node

期待される出力(最小例):

Started HTTP and WebSocket JSON-RPC server at http://127.0.0.1:8545/

Hardhat node は chain ID 31337 で、複数の unlocked development accounts を持つ。localhost deploy は先頭の unlocked account で署名されるため、リポジトリルートの .envPRIVATE_KEY も不要である。node が表示する開発用秘密鍵は公開済みの既知情報であり、コピーや browser wallet への import は行わない。

2.2 MetaMask にローカルチェーンを追加する

MetaMask のネットワーク追加で次を設定する:

  • RPC URL:http://127.0.0.1:8545
  • Chain ID:31337

専用のブラウザプロファイルで新しい学習用 account を作り、その公開 address を控える。実資産を持つ account や通常利用のブラウザプロファイルは使わない。

2.3 MyToken をローカルへデプロイする

別ターミナルでリポジトリルートから deploy する。localhost は unlocked account を使うため root .env は作らない。

npx hardhat run scripts/deploy-token.ts --network localhost

期待される出力(最小例):

MTK: 0x...

出力された MyToken のアドレスを dapp/.env.localVITE_TOKEN_ADDRESS に入れる。

2.4 学習用 wallet へ ETH / MyToken を送る

ブラウザ wallet で選択中の公開 address と、deploy 直後の token address を指定する。補助 script は --network localhost / chain ID 31337 以外では失敗し、秘密鍵を受け取らない。

LOCAL_WALLET_ADDRESS=0xYOUR_LEARNING_WALLET \
TOKEN_ADDRESS=0xDEPLOYED_MYTOKEN \
npm run fund:localhost

既定では unlocked deployer から 10 test ETH と 1,000 MTK を送る。量を変える場合だけ LOCAL_ETH_AMOUNT / LOCAL_TOKEN_AMOUNT を追加する。これらは localhost の test asset であり、価値を持たない。

2.5 設定と画面を順に確認する

  1. wallet の接続 network が Localhost 8545、chain ID が 31337 であることを確認する。
  2. dapp/.env.localVITE_CHAIN_ID=31337VITE_TOKEN_ADDRESS が deploy 結果に一致することを確認する。
  3. DApp を起動する。

    npm --prefix dapp run dev
    
  4. Connect Wallet 後に、画面の接続 address が資金を送った学習用 address と一致することを確認する。
  5. Refresh Balances で ETH と MTK が 0 でないことを確認してから、localhost 内だけで少額の Send を試す。

Vite が表示した URL(通常 http://localhost:5173/)を開く。ポートが使用中なら表示された別の URL を使う。


3. 実装の読みどころ

  • dapp/src/lib/web3.tsBrowserProvider 取得、eth_requestAccounts、chainId取得、wallet_switchEthereumChain
  • dapp/src/App.tsx:残高取得(getBalance / balanceOf)、ERC‑20 transfer、UI状態管理
  • dapp/src/hooks/useEvents.ts:イベント購読(Day10)

4. つまずきポイント

症状 原因 対処
No injected provider detected MetaMask 等が入っていない 拡張機能を入れて再読み込みする
Switch が失敗する チェーンが未追加 / 許可がない 先に MetaMask 側でネットワークを追加してから再実行する
Token contract not configured VITE_TOKEN_ADDRESS 未設定 .env.local を見直す
ETH / トークン残高が 0 学習用 wallet への送付前、address / chain 不一致 fund:localhost の宛先、chain ID、VITE_TOKEN_ADDRESS を順に確認する
call revert / execution reverted アドレスが違う / コントラクトが存在しない チェーン ID とアドレスの組み合わせを確認する

5. 追加課題

  • 送金フォームに入力検証(アドレス形式、数量、負数/空文字)を追加する。
  • UI のエラーハンドリングを alert からトーストに置き換える。
  • permit(EIP‑2612) を使った「署名→中継」送金のUI案を考える(実装は発展)。

6. まとめ

  • dapp/ を起動し、ウォレット接続→chainId確認→残高取得までの流れを動かした。
  • dapp/.env.local の chainId とコントラクトアドレスが、動作の成否を決める前提を押さえた。
  • 典型的なエラー(provider未検出、チェーン不一致、アドレス不一致)の切り分け観点を整理した。

理解チェック(3問)

  • Q1. DApp が injected provider(MetaMask 等)を使うとき、何が前提になるか?
  • Q2. VITE_CHAIN_ID と MetaMask 側のチェーンが不一致だと、どんな症状が起きやすいか?
  • Q3. dapp/.env.localVITE_* を分けて管理する利点は何か?

解答例(短く)

  • A1. ブラウザにウォレット拡張があり、ユーザーが接続/承認する前提になる(勝手に送金できない)。
  • A2. コントラクトが見つからない、残高が0に見える、イベントが読めないなど「別チェーンを見ている」症状が起きやすい。
  • A3. チェーン ID やアドレスなど環境依存の設定をコードから切り離し、切り替えミスやコミット事故を減らせる。

確認コマンド(最小)

# Terminal A(ローカルチェーン)
npx hardhat node

# Terminal B(MyToken をローカルへデプロイ)
npx hardhat run scripts/deploy-token.ts --network localhost

# Terminal B(表示された token address と学習用 wallet address を指定)
LOCAL_WALLET_ADDRESS=0x... TOKEN_ADDRESS=0x... npm run fund:localhost

# Terminal C(chain ID / token address を dapp/.env.local に設定済み)
npm run dapp:ci:safe
test -f dapp/.env.local || cp dapp/.env.example dapp/.env.local
npm --prefix dapp run dev

7. 提出物

  • 稼働中スクリーンショット(接続、chainId、残高表示、送金ログ)
  • 使用した VITE_CHAIN_ID とネットワーク名(鍵は伏せる)
  • 実行した送金TxHash(Explorerリンクがあるとよい)

8. 実行例