👌

Viemの基礎学習1日目(Ethereum(Anvil)チェーンとの対話)

に公開

Viemライブラリの学習と実践

日付: 2025年10月25日
学習内容: Viemライブラリを使用したEthereumブロックチェーンとの対話、JavaScriptからTypeScriptへの移行

1. Viemライブラリの概要

1.1 Viemとは

Viemは、Ethereumブロックチェーンとやり取りするためのTypeScriptライブラリです。主な特徴は以下の通りです:

  • Ethereumクライアント: ブロックチェーンとの通信
  • ウォレット統合: MetaMaskなどのウォレットとの連携
  • スマートコントラクト操作: コントラクトの読み取り・書き込み
  • トランザクション処理: トランザクションの送信・確認
  • TypeScript対応: 型安全性を提供

1.2 他のライブラリとの比較

  • ethers.js: より軽量でモダンな代替
  • web3.js: より新しいAPI設計
  • wagmi: React用のフックベースライブラリ(Viemを内部で使用)

2. 前提条件とセットアップ

2.1 前提条件

  1. Node.jsとnpmがインストール済み
  2. Foundryをインストール済み
# Foundryのインストール(まだの場合)
curl -L https://foundry.paradigm.xyz | bash
foundryup

2.2 プロジェクトの初期化

# Node.jsプロジェクトの初期化
npm init -y

# Viemのインストール
npm install viem

3. Anvilローカルチェーンの起動

3.1 Anvilの起動

# Anvilを起動(ローカルEthereumノード)
anvil

3.2 Anvilの情報

Anvilが起動すると、以下のような情報が表示されます:

  • デフォルトポート: http://127.0.0.1:8545
  • 10個のテストアカウント(各10,000 ETH)
  • 各アカウントの秘密鍵

4. JavaScriptでの実装

4.1 balance.jsの作成

残高確認用のスクリプトを作成:

// Viemのインポート
import { createPublicClient, http } from 'viem';
import { foundry } from 'viem/chains';

// Public Clientの作成(読み取り専用)
const publicClient = createPublicClient({
  chain: foundry,
  transport: http('http://127.0.0.1:8545')
});

// アカウントの残高を取得
// AnvilのAccount #0のアドレスを使用
const address = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266';

const balance = await publicClient.getBalance({ 
  address: address 
});

console.log('Balance:', balance.toString(), 'Wei');
console.log('Balance:', (balance / BigInt(10**18)).toString(), 'ETH');

4.2 send-transaction.jsの作成

トランザクション送信用のスクリプトを作成:

import { createWalletClient, createPublicClient, http, parseEther } from 'viem';
import { foundry } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';

// Anvilから取得した秘密鍵を使用(Account #0)
const account = privateKeyToAccount('0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80');

// Wallet Clientの作成(書き込み用)
const walletClient = createWalletClient({
  account,
  chain: foundry,
  transport: http('http://127.0.0.1:8545')
});

// Public Clientの作成(読み取り用)
const publicClient = createPublicClient({
  chain: foundry,
  transport: http('http://127.0.0.1:8545')
});

// Account #0 から Account #1 に 1 ETH送信
const hash = await walletClient.sendTransaction({
  to: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', // Account #1
  value: parseEther('1') // 1 ETH
});

console.log('Transaction Hash:', hash);

// トランザクションの確認を待つ
const receipt = await publicClient.waitForTransactionReceipt({ hash });
console.log('Transaction Status:', receipt.status);

4.3 index.jsの作成

包括的なデモスクリプトを作成:

// index.js
import { createPublicClient, createWalletClient, http, parseEther } from 'viem';
import { foundry } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';

// Public Client(読み取り用)
const publicClient = createPublicClient({
  chain: foundry,
  transport: http('http://127.0.0.1:8545')
});

// AnvilのAccount #0の秘密鍵
const account = privateKeyToAccount('0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80');

// Wallet Client(書き込み用)
const walletClient = createWalletClient({
  account,
  chain: foundry,
  transport: http('http://127.0.0.1:8545')
});

async function main() {
  console.log('=== Viem + Anvil Tutorial ===\n');

  // Account #0 と #1 のアドレス
  const account0 = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266';
  const account1 = '0x70997970C51812dc3A010C7d01b50e0d17dc79C8';

  // 初期残高を確認
  console.log('初期残高:');
  const balance0Before = await publicClient.getBalance({ address: account0 });
  const balance1Before = await publicClient.getBalance({ address: account1 });
  
  console.log(`Account #0: ${balance0Before / BigInt(10**18)} ETH`);
  console.log(`Account #1: ${balance1Before / BigInt(10**18)} ETH\n`);

  // 1 ETHを送信
  console.log('1 ETH を Account #0 から Account #1 に送信中...');
  const hash = await walletClient.sendTransaction({
    to: account1,
    value: parseEther('1')
  });
  
  console.log('Transaction Hash:', hash);

  // トランザクションの確認
  const receipt = await publicClient.waitForTransactionReceipt({ hash });
  console.log('Transaction Status:', receipt.status, '\n');

  // 送信後の残高を確認
  console.log('送信後の残高:');
  const balance0After = await publicClient.getBalance({ address: account0 });
  const balance1After = await publicClient.getBalance({ address: account1 });
  
  console.log(`Account #0: ${balance0After / BigInt(10**18)} ETH`);
  console.log(`Account #1: ${balance1After / BigInt(10**18)} ETH`);
}

main().catch(console.error);

5. ES Module対応

5.1 package.jsonの設定

{
  "type": "module"
}

5.2 import文への変更

// CommonJSからES Moduleへ
import { createPublicClient, http } from 'viem';
import { foundry } from 'viem/chains';

6. JavaScriptからTypeScriptへの移行

6.1 必要なパッケージのインストール

# TypeScript関連パッケージのインストール
npm install --save-dev @types/node ts-node tsx typescript

6.2 tsconfig.jsonの作成

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "esModuleInterop": true,
    "strict": true,
    "skipLibCheck": true,
    "resolveJsonModule": true,
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"],
  "ts-node": {
    "esm": true
  }
}

6.3 srcフォルダの作成とTypeScriptファイルの作成

balance_ts.ts

// src/balance.ts
import { createPublicClient, http } from 'viem'
import { foundry } from 'viem/chains'
import type { Address } from 'viem'

// Public Clientの作成(読み取り専用)
const publicClient = createPublicClient({
  chain: foundry,
  transport: http('http://127.0.0.1:8545')
})

// アカウントの残高を取得
// AnvilのAccount #0のアドレスを使用
const address: Address = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'

const balance = await publicClient.getBalance({ 
  address: address 
})

console.log('Balance:', balance.toString(), 'Wei')
console.log('Balance:', (balance / BigInt(10**18)).toString(), 'ETH')

send-transaction_ts.ts

import { createWalletClient, createPublicClient, http, parseEther } from 'viem'
import { foundry } from 'viem/chains'
import { privateKeyToAccount } from 'viem/accounts'
import type { Address } from 'viem'

// Anvilから取得した秘密鍵を使用(Account #0)
const account = privateKeyToAccount('0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80')

// Wallet Clientの作成(書き込み用)
const walletClient = createWalletClient({
  account,
  chain: foundry,
  transport: http('http://127.0.0.1:8545')
})

// Public Clientの作成(読み取り用)
const publicClient = createPublicClient({
  chain: foundry,
  transport: http('http://127.0.0.1:8545')
})

// Account #0 から Account #1 に 1 ETH送信
const hash = await walletClient.sendTransaction({
  to: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8' as Address, // Account #1
  value: parseEther('1') // 1 ETH
})

console.log('Transaction Hash:', hash)

// トランザクションの確認を待つ
const receipt = await publicClient.waitForTransactionReceipt({ hash })
console.log('Transaction Status:', receipt.status)

6.4 実行方法

# tsxを使用してTypeScriptファイルを実行
npx tsx src/balance_ts.ts
npx tsx src/send-transaction_ts.ts
npx tsx src/index_ts.ts

7. 重要な概念の理解

7.1 Public Client vs Wallet Client

  • Public Client: 読み取り専用(残高確認、ブロック情報取得など)
  • Wallet Client: 書き込み専用(トランザクション送信、スマートコントラクト実行など)

7.2 非同期処理とawait

// 非同期関数の定義
async function main() {
  const balance = await publicClient.getBalance({ address });
  // awaitでPromiseの完了を待機
}

7.3 ES6の短縮記法

// プロパティ名と変数名が同じ場合
const walletClient = createWalletClient({
  account,    // { account: account }の短縮
  chain,     // { chain: chain }の短縮
  transport  // { transport: transport }の短縮
});

7.4 テンプレートリテラル

// バッククォート(`)で囲む
console.log(`Account #0: ${balance / BigInt(10**18)} ETH`);

8. エラーハンドリング

8.1 .catch()メソッド

main().catch(console.error);

8.2 try/catch文

try {
  await main();
} catch (error) {
  console.error(error);
}

9. TypeScriptの型システム

9.1 型のインポート

import type { Address } from 'viem';

9.2 型注釈

const address: Address = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266';

9.3 型安全性の利点

  • コンパイル時のエラー検出
  • IDEサポートの向上
  • コードの可読性向上

10. 実行結果の例

10.1 balance.jsの実行結果

Balance: 9992999750922420627000 Wei
Balance: 9992 ETH

10.2 send-transaction.jsの実行結果

Transaction Hash: 0x4e7f4dc278b4fc13ebd47728c0cbb42f14f3b373b9a5574174ed4a69d7bd69ab
Transaction Status: success

10.3 index.jsの実行結果

=== Viem + Anvil Tutorial ===

初期残高:
Account #0: 9994.999813140300722 ETH
Account #1: 10005 ETH

1 ETH を Account #0 から Account #1 に送信中...
Transaction Hash: 0x0261231904ae9f37e1a2c755765e6a33260b9ea135b2671d746d146669b5e7d4
Transaction Status: success 

送信後の残高:
Account #0: 9993.999781358437651 ETH
Account #1: 10006 ETH

11. 学習のポイント

11.1 重要な理解事項

  1. Viemライブラリの基本概念
  2. Public ClientとWallet Clientの使い分け
  3. 非同期処理とPromiseの理解
  4. ES ModuleとCommonJSの違い
  5. TypeScriptの型システム

11.2 実践的なスキル

  1. ブロックチェーンとの対話
  2. トランザクションの送信と確認
  3. エラーハンドリング
  4. JavaScriptからTypeScriptへの移行

12. まとめ

Viemライブラリを使用することで、Ethereumブロックチェーンとの対話が簡単になりました。JavaScriptからTypeScriptへの移行により、型安全性とコードの可読性が向上しました。Anvilローカルチェーンを使用することで、安全な環境でブロックチェーン開発の基礎を学習できました。

12.1 習得した技術

  • Viemライブラリの使用方法
  • Ethereumブロックチェーンとの対話
  • 非同期処理の理解
  • TypeScriptの型システム
  • ES Moduleの使用

12.2 今後の学習方向

  • スマートコントラクトのデプロイと実行
  • より複雑なトランザクション処理
  • イベントの監視と処理
  • ガス最適化の理解

Discussion