[Solidityのはじめ方] Part25: web3.js や ethers.js の基本操作

Solidity

スマートコントラクトをデプロイしたら、次はそのコントラクトと対話するアプリケーション(DApp: Decentralized Application)を構築します。WebベースのDAppでは、フロントエンド(ユーザーが見る画面)からEthereumネットワーク上のスマートコントラクトを操作するために、JavaScriptライブラリが不可欠です。今回は、その代表的なライブラリである web3.jsethers.js の基本的な使い方を学びましょう! 🤝

注意: これらのライブラリを使用するには、Node.js と npm (または yarn) が開発環境にインストールされている必要があります。また、ブラウザでDAppを動作させる場合は、MetaMaskのようなブラウザ拡張ウォレットが必要になります。

web3.js の基本操作

web3.js は、Ethereumエコシステムで最も古くから使われているライブラリの一つです。Ethereumノードと通信するための豊富な機能を提供します。

1. インストール

まず、プロジェクトに web3.js をインストールします。

npm install web3

または

yarn add web3

2. 初期化とプロバイダ設定

web3.js を使用するには、Ethereumネットワークへの接続点となる「プロバイダ」を設定する必要があります。ブラウザ環境では MetaMask が提供するプロバイダ (`window.ethereum`) を使うのが一般的です。Node.js環境などでは、Infura のようなノードプロバイダサービスのエンドポイントURLを指定します。

// ブラウザ環境 (MetaMaskなど)
import Web3 from 'web3';

let web3;

if (window.ethereum) {
  web3 = new Web3(window.ethereum);
  try {
    // アカウントへのアクセスを要求
    await window.ethereum.request({ method: 'eth_requestAccounts' });
  } catch (error) {
    console.error("User denied account access");
  }
} else if (window.web3) { // 古い DApp ブラウザの場合
  web3 = new Web3(window.web3.currentProvider);
} else {
  // MetaMaskなどがインストールされていない場合のフォールバック
  const provider = new Web3.providers.HttpProvider('YOUR_INFURA_ENDPOINT'); // 例: InfuraのURL
  web3 = new Web3(provider);
  console.log('Non-Ethereum browser detected. You should consider trying MetaMask!');
}

// Node.js 環境の例
// const Web3 = require('web3'); // CommonJSの場合
// const web3 = new Web3('YOUR_INFURA_ENDPOINT');

3. 基本的な操作例

アカウント取得

async function getAccounts() {
  try {
    const accounts = await web3.eth.getAccounts();
    console.log('Accounts:', accounts);
    return accounts;
  } catch (error) {
    console.error('Error fetching accounts:', error);
  }
}

getAccounts();

ネットワークID取得

async function getNetworkId() {
  try {
    const networkId = await web3.eth.net.getId();
    console.log('Network ID:', networkId);
    return networkId;
  } catch (error) {
    console.error('Error fetching network ID:', error);
  }
}

getNetworkId();

スマートコントラクトの操作

コントラクトを操作するには、コントラクトのABI(Application Binary Interface)とデプロイされたアドレスが必要です。ABIはコンパイル時に生成されるJSONファイルで、コントラクトの関数やイベントの定義情報が含まれています。

// 例: シンプルなカウンターコントラクト
const contractABI = [ /* ... コントラクトのABIをここに貼り付け ... */ ];
const contractAddress = 'YOUR_CONTRACT_ADDRESS'; // デプロイしたコントラクトのアドレス

// コントラクトインスタンスの作成
const contract = new web3.eth.Contract(contractABI, contractAddress);

// --- データの読み取り (call) ---
async function getCount() {
  try {
    // 'getCount' はコントラクト内の view または pure 関数の名前
    const count = await contract.methods.getCount().call();
    console.log('Current count:', count);
    return count;
  } catch (error) {
    console.error('Error calling getCount:', error);
  }
}

// --- データの書き込み (send) ---
async function incrementCount() {
  try {
    const accounts = await web3.eth.getAccounts();
    if (accounts.length === 0) {
        console.error("No accounts found. Make sure MetaMask is connected.");
        return;
    }
    const senderAccount = accounts[0];

    // 'increment' はコントラクト内の状態を変更する関数の名前
    // send() はトランザクションを送信し、ガス代が必要
    const receipt = await contract.methods.increment().send({ from: senderAccount });
    console.log('Transaction receipt:', receipt);
    // トランザクション成功後、再度カウントを取得して確認
    await getCount();
  } catch (error) {
    console.error('Error sending increment transaction:', error);
  }
}

// 実行例
getCount();
// incrementCount(); // 実行するとMetaMaskが起動し、トランザクションの承認を求められます
call() は状態を変更しない読み取り専用の関数呼び出しに使用され、ガス代はかかりません。一方、send() は状態を変更する関数呼び出し(トランザクションの発行)に使用され、ガス代がかかり、ウォレットでの承認が必要です。

ethers.js の基本操作

ethers.js は、web3.js と同様の目的を持つライブラリですが、よりモダンで軽量、そしてシンプルで一貫性のあるAPIを提供することを目指しています。ENS(Ethereum Name Service)のネイティブサポートなども特徴です。近年、多くの新しいプロジェクトで採用されています。 🎉

1. インストール

npm install ethers

または

yarn add ethers

2. 初期化とプロバイダ/サイナー設定

ethers.js では、「プロバイダ(Provider)」と「サイナー(Signer)」という概念が重要です。

  • Provider: Ethereumネットワークへの読み取り専用の接続を提供します。アカウントの状態を知る必要はありません。
  • Signer: Providerの機能に加え、特定のアカウント(秘密鍵を持つ)としてトランザクションに署名し、送信することができます。
// ブラウザ環境 (MetaMaskなど)
import { ethers } from 'ethers';

let provider;
let signer;

if (window.ethereum) {
  // MetaMask が提供するプロバイダを使用
  provider = new ethers.BrowserProvider(window.ethereum);
  try {
    // アカウントへの接続と署名者の取得
    signer = await provider.getSigner();
    console.log('Signer obtained:', await signer.getAddress());
  } catch (error) {
    console.error("Error getting signer:", error);
  }
} else {
  // MetaMaskなどがインストールされていない場合 (読み取り専用)
  // 例: Infura を使う場合
  provider = new ethers.InfuraProvider('mainnet', 'YOUR_INFURA_API_KEY'); // または JsonRpcProvider
  console.log('Connected to Infura (read-only)');
  // この場合、signer は取得できないため、書き込み操作は不可
}

// Node.js 環境の例 (読み取り専用)
// import { InfuraProvider } from 'ethers'; // ES Modules
// const provider = new InfuraProvider('mainnet', 'YOUR_INFURA_API_KEY');

// Node.js 環境の例 (秘密鍵を使って書き込み可能)
// import { Wallet, JsonRpcProvider } from 'ethers';
// const provider = new JsonRpcProvider('YOUR_RPC_ENDPOINT');
// const privateKey = 'YOUR_PRIVATE_KEY'; // 注意: 秘密鍵の管理には十分気をつけてください
// const wallet = new Wallet(privateKey, provider);
// const signer = wallet;

3. 基本的な操作例

アカウント(署名者のアドレス)取得

async function getSignerAddress() {
  if (!signer) {
    console.error('Signer not available.');
    // 必要に応じて再度接続を試みるか、ユーザーに接続を促す
    // await provider.send("eth_requestAccounts", []); // 再度MetaMaskに接続要求
    // signer = await provider.getSigner();
    return;
  }
  try {
    const address = await signer.getAddress();
    console.log('Signer Address:', address);
    return address;
  } catch (error) {
    console.error('Error getting signer address:', error);
  }
}

getSignerAddress();

ネットワーク情報取得

async function getNetworkInfo() {
  if (!provider) {
    console.error('Provider not available.');
    return;
  }
  try {
    const network = await provider.getNetwork();
    console.log('Network Name:', network.name);
    console.log('Network Chain ID:', network.chainId);
  } catch (error) {
    console.error('Error getting network info:', error);
  }
}

getNetworkInfo();

スマートコントラクトの操作

web3.js と同様に、コントラクトのABIとアドレスが必要です。ethers.js では ethers.Contract クラスを使用します。

// 例: シンプルなカウンターコントラクト
const contractABI = [ /* ... コントラクトのABIをここに貼り付け ... */ ];
const contractAddress = 'YOUR_CONTRACT_ADDRESS';

// 読み取り専用のコントラクトインスタンス (Providerを使用)
const readOnlyContract = new ethers.Contract(contractAddress, contractABI, provider);

// 書き込み可能なコントラクトインスタンス (Signerを使用)
// Signerが取得できている場合のみ作成可能
const writableContract = signer ? new ethers.Contract(contractAddress, contractABI, signer) : null;

// --- データの読み取り ---
async function getCountEthers() {
  try {
    // 'getCount' はコントラクト内の view または pure 関数の名前
    const count = await readOnlyContract.getCount(); // Provider経由で呼び出し
    console.log('Current count (ethers):', Number(count)); // BigIntで返ってくることが多いのでNumber()で変換
    return count;
  } catch (error) {
    console.error('Error calling getCount (ethers):', error);
  }
}

// --- データの書き込み ---
async function incrementCountEthers() {
  if (!writableContract) {
    console.error('Writable contract instance not available. Check if wallet is connected.');
    return;
  }
  try {
    // 'increment' はコントラクト内の状態を変更する関数の名前
    // Signer経由で呼び出すとトランザクションが送信される
    const tx = await writableContract.increment();
    console.log('Transaction sent:', tx.hash);

    // トランザクションがブロックに含まれるのを待つ (任意)
    const receipt = await tx.wait();
    console.log('Transaction confirmed:', receipt);

    // トランザクション成功後、再度カウントを取得して確認
    await getCountEthers();
  } catch (error) {
    console.error('Error sending increment transaction (ethers):', error);
  }
}

// 実行例
getCountEthers();
// if (writableContract) {
//   incrementCountEthers(); // 実行するとMetaMaskが起動し、トランザクションの承認を求められます
// }
ethers.js では、コントラクトインスタンスを生成する際に Provider を渡すと読み取り専用、Signer を渡すと書き込み可能(トランザクション署名可能)なインスタンスになります。APIがより明確に分かれているのが特徴です。

web3.js vs ethers.js どっちを選ぶ? 🤔

どちらのライブラリも Ethereum との対話を実現する強力なツールですが、いくつかの違いがあります。

特徴web3.jsethers.js
歴史と普及度古くからあり、多くの既存プロジェクトで使用されている。情報量も多い。比較的新しいが、急速に普及。モダンなプロジェクトでの採用例が増加。
APIデザイン機能は豊富だが、API がやや複雑で一貫性に欠ける部分があるとの指摘も。シンプルで一貫性のあるAPIを目指している。Provider/Signer の概念が明確。
ライブラリサイズ比較的多機能な分、サイズが大きめ。より軽量。
TypeScript対応対応しているが、型定義のメンテナンスが追いついていない場合がある。ネイティブでTypeScriptをサポートしており、型定義が充実。
ENSサポート基本的なサポートあり。ネイティブで強力なENSサポートを提供。
コミュニティとドキュメント長年の実績があり、コミュニティは大きい。活発なコミュニティと、整理された公式ドキュメント。

結論として:

  • これから新しいプロジェクトを始める場合、ethers.js はシンプルさ、軽量さ、TypeScriptとの親和性の高さから、有力な選択肢となります。
  • 既存のプロジェクトで web3.js が使われている場合や、豊富な情報量を重視する場合は、web3.js も引き続き良い選択です。

どちらのライブラリも基本的な操作(アカウント取得、ネットワーク接続、コントラクト呼び出し)は可能です。まずは両方のドキュメントやサンプルコードを見て、自分に合った方を選んでみましょう!

まとめ

今回は、フロントエンドとスマートコントラクトを繋ぐための重要なライブラリである web3.js と ethers.js の基本的な使い方を紹介しました。

  • プロバイダ/サイナーの設定方法
  • アカウントやネットワーク情報の取得
  • スマートコントラクトの読み取り (call/読み取り専用) と書き込み (send/Signer経由)

これらの基本をマスターすれば、いよいよ本格的なDApp開発に進むことができます。実際に簡単なDAppを作りながら、これらのライブラリの使い方に慣れていきましょう!🚀

コメント

タイトルとURLをコピーしました