🙆
Viemの基礎学習4日目(ブラウザからの対話(JavaScript))
ブラウザからスマートコントラクトとインタラクション
日付: 2025年10月28日
学習内容: ブラウザの開発者コンソールからViemを使用してスマートコントラクトとインタラクションする方法、forge createとforge scriptの違い、ESMモジュールを使用したHTMLファイルでのコントラクト操作
1. forge createとforge scriptの違い
1.1 forge create(コントラクトのデプロイ専用)
コントラクトをデプロイする際に使用するシンプルなコマンド:
forge create src/SomeContract.sol:SomeContract \
--rpc-url http://localhost:8545 \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
特徴:
-
--broadcastは不要(デフォルトで実際にトランザクションを送信) - シンプルで直接的なデプロイに使用
- 1つのコントラクトをデプロイするだけ
- デプロイ専用のコマンド
実行時の注意点:
- 実行ディレクトリは
foundry/内であること - パスは
src/SomeContract.sol:SomeContractの形式 -
--broadcastは不要だが、実際にネットワークに送信される
1.2 forge script(スクリプト実行)
より柔軟で複雑な操作を行うためのコマンド:
forge script script/Deploy.s.sol \
--rpc-url http://localhost:8545 \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
--broadcast # ← これが必要!
特徴:
-
--broadcastが必要(必須ではないが重要) -
--broadcastなし = シミュレーションのみ(dry run) -
--broadcastあり = 実際にトランザクション送信 - 複雑なデプロイやインタラクションに使用
- デプロイスクリプトを使った運用に適している
1.3 使い分けの指針
| 項目 | forge create | forge script |
|---|---|---|
| 用途 | シンプルなデプロイ | 複雑な操作やデプロイ |
| broadcast | 不要 | 必要 |
| デプロイ | 可能 | 可能 |
| 複雑な操作 | 不可 | 可能 |
| スクリプト | 不要 | 必要 |
選択の目安:
- 単にコントラクトを1つデプロイしたい →
forge create - 複数のコントラクトをデプロイ、初期化、セットアップが必要 →
forge script
2. ブラウザからコントラクトとインタラクション
2.1 環境構築
2.1.1 Anvilの起動
anvil
確認事項:
- Chain ID: 31337
- Listening on 127.0.0.1:8545
- デフォルトアカウントが10個作成される(各10000 ETH)
2.1.2 コントラクトのデプロイ
cd foundry
forge create src/SomeContract.sol:SomeContract \
--rpc-url http://localhost:8545 \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
デプロイ結果の例:
- コントラクトアドレス:
0x5FbDB2315678afecb367f032d93F642f64180aa3 - デプロイアカウント:
0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
2.1.3 HTTPサーバーの起動
python3 -m http.server 8000
2.2 HTMLファイルの作成
2.2.1 基本的な構造
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>Viem Contract Interaction</title>
</head>
<body>
<h1>Viem + Foundry Demo</h1>
<script type="module">
// Viemのコードをここに記述
</script>
</body>
</html>
2.2.2 チェーン設定の重要性
注意点: AnvilのChain IDは 31337 だが、viemのデフォルト localhost chainは 1337 を使用する。
正しいチェーン設定:
import { createPublicClient, createWalletClient, http, parseAbi, defineChain } from 'https://esm.sh/viem@2.x'
import { privateKeyToAccount } from 'https://esm.sh/viem@2.x/accounts'
// Anvilのチェーン設定(chain ID: 31337)
const anvil = defineChain({
id: 31337, // ← これが重要!
name: 'Anvil Localhost',
nativeCurrency: {
decimals: 18,
name: 'Ether',
symbol: 'ETH',
},
rpcUrls: {
default: {
http: ['http://127.0.0.1:8545'],
},
},
})
エラー例:
invalid chain id for signer
このエラーは、Chain IDが一致していない場合に発生する。
2.2.3 クライアントの作成
// コントラクト設定
const CONTRACT_ADDRESS = '0x5FbDB2315678afecb367f032d93F642f64180aa3'
const contractABI = parseAbi([
'function myUint() view returns (uint256)',
'function setUint(uint256 _myUint) public'
])
// アカウント設定
const account = privateKeyToAccount(
'0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'
)
// Public Client(読み取り専用)
const publicClient = createPublicClient({
chain: anvil, // ← カスタムチェーンを使用
transport: http('http://localhost:8545')
})
// Wallet Client(書き込み用)
const walletClient = createWalletClient({
account,
chain: anvil, // ← カスタムチェーンを使用
transport: http('http://localhost:8545')
})
2.3 関数の実装
2.3.1 読み取り関数
// myUintを読み取る関数
window.readMyUint = async () => {
try {
const result = await publicClient.readContract({
address: CONTRACT_ADDRESS,
abi: contractABI,
functionName: 'myUint'
})
console.log('myUint value:', result.toString())
return result
} catch (error) {
console.error('Error reading myUint:', error)
}
}
ポイント:
-
windowオブジェクトに関数を割り当てることで、ブラウザコンソールから呼び出し可能に - 戻り値は
bigintなので、toString()で文字列に変換 - エラーハンドリングを実装
2.3.2 書き込み関数
// myUintを更新する関数
window.updateMyUint = async (newValue) => {
try {
console.log(`Updating myUint to ${newValue}...`)
// トランザクション送信
const hash = await walletClient.writeContract({
address: CONTRACT_ADDRESS,
abi: contractABI,
functionName: 'setUint',
args: [BigInt(newValue)]
})
console.log('Transaction hash:', hash)
// トランザクションの確認を待つ
const receipt = await publicClient.waitForTransactionReceipt({ hash })
console.log('Transaction confirmed!', receipt)
// 更新された値を読み取る
const updatedValue = await window.readMyUint()
return updatedValue
} catch (error) {
console.error('Error updating myUint:', error)
}
}
ポイント:
-
writeContractでトランザクションを送信 -
waitForTransactionReceiptでトランザクションの確認を待つ - 引数は
BigIntに変換 - トランザクション後、自動的に更新された値を読み取る
2.3.3 アカウント情報取得関数
// アカウント情報を取得
window.getAccounts = async () => {
try {
const accounts = await publicClient.request({ method: 'eth_accounts' })
console.log('Available accounts:', accounts)
console.log('Using account:', account.address)
return accounts
} catch (error) {
console.error('Error getting accounts:', error)
console.log('Using account:', account.address)
}
}
注意点:
-
publicClient.getAddresses()は存在しない -
publicClient.request({ method: 'eth_accounts' })を使用する - エラーハンドリングを実装
2.4 ブラウザでの使用方法
2.4.1 ページを開く
http://localhost:8000/index.html
2.4.2 開発者コンソールを開く
- Windows:
F12またはCtrl + Shift + I - Mac:
Cmd + Option + I
2.4.3 コマンドの実行
// 1. 値を読み取る
await readMyUint()
// 出力例:
// myUint value: 10n
// 2. 値を更新する
await updateMyUint(50)
// 出力例:
// Updating myUint to 50...
// Transaction hash: 0xbbc48cd71d5367f48512d30de2cbaaded637dc610ba5f258afede69619e395c5
// Transaction confirmed! { ... }
// myUint value: 50n
// 3. 再度読み取って確認
await readMyUint()
// 出力例:
// myUint value: 50n
// 4. アカウント情報を取得
await getAccounts()
2.5 ESMモジュールの使用
<script type="module">
import { createPublicClient, createWalletClient, http, parseAbi, defineChain } from 'https://esm.sh/viem@2.x'
import { privateKeyToAccount } from 'https://esm.sh/viem@2.x/accounts'
// コード...
</script>
ポイント:
-
type="module"でES6モジュールを有効化 - CDNから直接インポート可能(
https://esm.sh/viem@2.x) - ローカルにインストール不要
- 最新版が自動的に読み込まれる
3. トラブルシューティング
3.1 よくあるエラーと解決方法
エラー1: invalid chain id for signer
原因: Chain IDが一致していない
解決方法:
// カスタムチェーンを定義
const anvil = defineChain({
id: 31337, // AnvilのChain ID
// ...
})
エラー2: publicClient.getAddresses is not a function
原因: getAddresses() メソッドが存在しない
解決方法:
// 正しい方法
const accounts = await publicClient.request({ method: 'eth_accounts' })
エラー3: Connection refused
原因: Anvilが起動していない
解決方法:
anvil
エラー4: ファイルが見つからない
原因: forge create を間違ったディレクトリで実行
解決方法:
cd foundry
forge create src/SomeContract.sol:SomeContract --rpc-url http://localhost:8545 --private-key ...
3.2 デバッグのコツ
- ブラウザコンソールを確認: エラーメッセージを確認
- Anvilのログを確認: トランザクションが到達しているか確認
- Networkタブを確認: HTTPリクエストが正しく送信されているか確認
-
チェーンIDを確認:
31337が正しく設定されているか確認
4. まとめ
4.1 重要なポイント
-
forge create vs forge script:
-
forge create: シンプルなデプロイに使用、--broadcast不要 -
forge script: 複雑な操作に使用、--broadcast必要
-
-
チェーン設定:
- AnvilのChain IDは
31337 -
defineChainでカスタムチェーンを定義する必要がある
- AnvilのChain IDは
-
ブラウザからの操作:
-
windowオブジェクトに関数を割り当てる - ESMモジュールを使用(
type="module") -
publicClient.requestを使用してRPCメソッドを呼び出し
-
-
実用的な開発フロー:
- Anvilを起動
- コントラクトをデプロイ
- HTTPサーバーを起動
- ブラウザでページを開く
- 開発者コンソールでコマンドを実行
4.2 学習成果
- ブラウザからスマートコントラクトとインタラクションする方法を習得
- forge createとforge scriptの使い分けを理解
- ESMモジュールを使用したモダンなJavaScriptの記述方法を習得
- エラーハンドリングとデバッグの技術を習得
4.3 今後の展開
- より複雑なコントラクト操作
- UI要素を追加したブラウザアプリケーション
- 複数のコントラクト間でのインタラクション
- イベント監視の実装
Discussion