メインコンテンツへスキップ

初心者のためのHello Worldスマート・コントラクト - フルスタック

Solidity
Hardhat
Alchemy
スマート・コントラクト
デプロイ
ブロックエクスプローラー
フロントエンド
トランザクション
フレームワーク
初級
nstrike2
2021年10月25日
72 分で読めます

ブロックチェーン開発が初めてで、どこから始めればよいか、あるいはスマート・コントラクトをどのようにデプロイして対話すればよいかわからない場合、このガイドはあなたのためのものです。メタマスク (opens in a new tab)Solidity (opens in a new tab)Hardhat (opens in a new tab)、およびAlchemy (opens in a new tab)を使用して、ゴエリのテストネットワーク上にシンプルなスマート・コントラクトを作成し、デプロイする手順を説明します。

このチュートリアルを完了するには、Alchemyのアカウントが必要です。無料アカウントに登録してください (opens in a new tab)

途中で質問がある場合は、Alchemyのディスコード (opens in a new tab)でお気軽にお問い合わせください!

パート1 - Hardhatを使用したスマート・コントラクトの作成とデプロイ

イーサリアムネットワークへの接続

イーサリアムチェーンにリクエストを送信する方法はたくさんあります。ここではシンプルにするため、ブロックチェーン開発者向けプラットフォームおよびAPIであるAlchemyの無料アカウントを使用します。これにより、自分でノードを実行することなくイーサリアムチェーンと通信できるようになります。Alchemyには監視や分析のための開発者ツールも備わっており、このチュートリアルではこれらを活用して、スマート・コントラクトのデプロイの内部で何が起こっているのかを理解します。

アプリとAPIキーの作成

Alchemyアカウントを作成したら、アプリを作成してAPIキーを生成できます。これにより、ゴエリテストネットへのリクエストが可能になります。テストネットに馴染みがない場合は、ネットワークの選択に関するAlchemyのガイド (opens in a new tab)をお読みください。

Alchemyのダッシュボードで、ナビゲーションバーのAppsドロップダウンを見つけ、Create Appをクリックします。

Hello world create app

アプリに「Hello World」という名前を付け、短い説明を書きます。環境(Environment)としてStagingを、ネットワークとしてGoerliを選択します。

create app view hello world

注: 必ずGoerliを選択してください。そうしないと、このチュートリアルは機能しません。

Create appをクリックします。アプリが下の表に表示されます。

イーサリアムアカウントの作成

トランザクションを送受信するには、イーサリアムアカウントが必要です。ここでは、ユーザーがブラウザ上でイーサリアムアカウントのアドレスを管理できる仮想ウォレットであるメタマスクを使用します。

メタマスクのアカウントはこちら (opens in a new tab)から無料でダウンロードして作成できます。アカウントを作成する際、またはすでにアカウントを持っている場合は、右上で「Goerli Test Network」に切り替えてください(実際の資金を扱わないようにするためです)。

ステップ4: フォーセットからイーサを追加する

テストネットワークにスマート・コントラクトをデプロイするには、テスト用のETHが必要です。ゴエリネットワークでETHを取得するには、ゴエリのフォーセットにアクセスし、ゴエリアカウントのアドレスを入力します。最近、ゴエリのフォーセットは少し不安定になることがあるため、試せるオプションのリストについてはテストネットワークのページを参照してください。

注: ネットワークの混雑状況により、これには少し時間がかかる場合があります。 ``

ステップ5: 残高を確認する

ウォレットにETHが入っていることを再確認するために、Alchemyのコンポーザーツール (opens in a new tab)を使用してeth_getBalance (opens in a new tab)リクエストを送信してみましょう。これにより、ウォレット内のETHの量が返されます。詳細については、コンポーザーツールの使用方法に関するAlchemyの短いチュートリアル (opens in a new tab)を確認してください。

メタマスクのアカウントアドレスを入力し、Send Requestをクリックします。以下のコードスニペットのようなレスポンスが表示されます。

{ "jsonrpc": "2.0", "id": 0, "result": "0x2B5E3AF16B1880000" }

注: この結果はETHではなくweiで表示されています。weiはイーサの最小単位として使用されます。

ふう!テスト用の資金がすべて揃いました。

ステップ6: プロジェクトを初期化する

まず、プロジェクト用のフォルダを作成する必要があります。コマンドラインに移動し、以下を入力します。

mkdir hello-world
cd hello-world

プロジェクトフォルダに入ったので、npm initを使用してプロジェクトを初期化します。

まだnpmをインストールしていない場合は、こちらの手順に従ってNode.jsとnpmをインストール (opens in a new tab)してください。

このチュートリアルの目的においては、初期化時の質問にどのように答えても問題ありません。参考までに、以下のように設定しました。

package.jsonを承認すれば準備完了です!

ステップ7: Hardhatをダウンロードする

Hardhatは、イーサリアムソフトウェアをコンパイル、デプロイ、テスト、およびデバッグするための開発環境です。ライブチェーンにデプロイする前に、ローカルでスマート・コントラクトや分散型アプリケーション (dapp) を構築する開発者を支援します。

hello-worldプロジェクト内で以下を実行します。

npm install --save-dev hardhat

インストール手順 (opens in a new tab)の詳細については、こちらのページを確認してください。

ステップ8: Hardhatプロジェクトを作成する

hello-worldプロジェクトフォルダ内で、以下を実行します。

npx hardhat

すると、ウェルカムメッセージと実行したい操作を選択するオプションが表示されます。「create an empty hardhat.config.js」を選択します。

これにより、プロジェクト内にhardhat.config.jsファイルが生成されます。このファイルは、チュートリアルの後半でプロジェクトの設定を指定するために使用します。

ステップ9: プロジェクトフォルダを追加する

プロジェクトを整理するために、2つの新しいフォルダを作成しましょう。コマンドラインでhello-worldプロジェクトのルートディレクトリに移動し、次のように入力します。

mkdir contracts
mkdir scripts
  • contracts/ は、Hello Worldスマート・コントラクトのコードファイルを保存する場所です
  • scripts/ は、コントラクトをデプロイして対話するためのスクリプトを保存する場所です

ステップ10: コントラクトを記述する

いつコードを書くのか疑問に思っているかもしれません。いよいよその時です!

お気に入りのエディタでhello-worldプロジェクトを開きます。スマート・コントラクトは一般的にSolidityで記述されるため、ここでもSolidityを使用してスマート・コントラクトを記述します。‌

  1. contractsフォルダに移動し、HelloWorld.solという新しいファイルを作成します
  2. 以下は、このチュートリアルで使用するHello Worldスマート・コントラクトのサンプルです。以下の内容をHelloWorld.solファイルにコピーします。

注: コメントを読んで、このコントラクトが何を行うかを理解してください。

これは、作成時にメッセージを保存する基本的なスマート・コントラクトです。update関数を呼び出すことで更新できます。

ステップ11: メタマスクとAlchemyをプロジェクトに接続する

メタマスクウォレット、Alchemyアカウントを作成し、スマート・コントラクトを記述しました。次はこれら3つを接続します。

ウォレットから送信されるすべてのトランザクションには、固有の秘密鍵を使用した署名が必要です。プログラムにこの権限を与えるために、秘密鍵を環境ファイルに安全に保存できます。また、AlchemyのAPIキーもここに保存します。

トランザクションの送信について詳しくは、Web3を使用したトランザクションの送信に関するこちらのチュートリアル (opens in a new tab)を確認してください。

まず、プロジェクトディレクトリにdotenvパッケージをインストールします。

npm install dotenv --save

次に、プロジェクトのルートディレクトリに.envファイルを作成します。そこにメタマスクの秘密鍵とHTTP Alchemy API URLを追加します。

環境ファイルの名前は.envである必要があります。そうでない場合、環境ファイルとして認識されません。

process.env.env-customなどの他の名前を付けないでください。

Animated walkthrough of getting an Alchemy API key

.envファイルは次のようになります。

API_URL = "https://eth-goerli.alchemyapi.io/v2/your-api-key"
PRIVATE_KEY = "your-metamask-private-key"

これらを実際にコードに接続するために、ステップ13でhardhat.config.jsファイル内のこれらの変数を参照します。

ステップ12: Ethers.jsをインストールする

Ethers.jsは、標準のJSON-RPCメソッド (opens in a new tab)をよりユーザーフレンドリーなメソッドでラップすることで、イーサリアムとの対話やリクエストの送信を容易にするライブラリです。

Hardhatでは、追加のツールや拡張機能のためにプラグイン (opens in a new tab)を統合できます。ここでは、コントラクトのデプロイにEthersプラグイン (opens in a new tab)を活用します。

プロジェクトディレクトリで次のように入力します。

npm install --save-dev @nomiclabs/hardhat-ethers "ethers@^5.0.0"

ステップ13: hardhat.config.jsを更新する

これまでにいくつかの依存関係とプラグインを追加しました。次に、プロジェクトがそれらすべてを認識できるようにhardhat.config.jsを更新する必要があります。

hardhat.config.jsを次のように更新します。

ステップ14: コントラクトをコンパイルする

ここまでがすべて正常に機能していることを確認するために、コントラクトをコンパイルしましょう。compileタスクは、Hardhatに組み込まれているタスクの1つです。

コマンドラインから以下を実行します。

npx hardhat compile

SPDX license identifier not provided in source fileに関する警告が表示されるかもしれませんが、心配する必要はありません。他はすべて問題ないはずです!もし問題がある場合は、いつでもAlchemyのディスコード (opens in a new tab)でメッセージを送ることができます。

ステップ15: デプロイスクリプトを記述する

コントラクトの記述が完了し、設定ファイルの準備も整ったので、コントラクトのデプロイスクリプトを記述します。

scripts/フォルダに移動し、deploy.jsという新しいファイルを作成して、以下の内容を追加します。

Hardhatは、コントラクトのチュートリアル (opens in a new tab)でこれらのコードの各行が何を行うかを非常にわかりやすく説明しています。ここではその説明を採用しています。

const HelloWorld = await ethers.getContractFactory("HelloWorld")

ethers.jsのContractFactoryは、新しいスマート・コントラクトをデプロイするために使用される抽象化です。したがって、ここでのHelloWorldは、Hello Worldコントラクトのインスタンスのファクトリ (opens in a new tab)です。hardhat-ethersプラグインのContractFactoryContractを使用する場合、インスタンスはデフォルトで最初の署名者(所有者)に接続されます。

const hello_world = await HelloWorld.deploy()

ContractFactorydeploy()を呼び出すとデプロイが開始され、Contractオブジェクトに解決されるPromiseが返されます。これは、スマート・コントラクトの各関数のメソッドを持つオブジェクトです。

ステップ16: コントラクトをデプロイする

ついにスマート・コントラクトをデプロイする準備が整いました!コマンドラインに移動して以下を実行します。

npx hardhat run scripts/deploy.js --network goerli

すると、次のような出力が表示されるはずです。

Contract deployed to address: 0x6cd7d44516a20882cEa2DE9f205bF401c0d23570

このアドレスを保存してください。チュートリアルの後半で使用します。

ゴエリのEtherscan (opens in a new tab)にアクセスしてコントラクトのアドレスを検索すると、正常にデプロイされたことが確認できるはずです。トランザクションは次のようになります。

Fromのアドレスはメタマスクのアカウントアドレスと一致し、ToのアドレスにはContract Creationと表示されます。トランザクションをクリックすると、Toフィールドにコントラクトのアドレスが表示されます。

おめでとうございます!イーサリアムのテストネットにスマート・コントラクトをデプロイしました。

内部で何が起こっているのかを理解するために、Alchemyダッシュボード (opens in a new tab)のExplorerタブに移動しましょう。複数のAlchemyアプリがある場合は、アプリでフィルタリングしてHello Worldを選択してください。

ここでは、.deploy()関数を呼び出したときに、Hardhat/Ethersが内部で実行したいくつかのJSON-RPCメソッドを確認できます。ここで重要な2つのメソッドは、ゴエリチェーンにコントラクトを書き込むためのリクエストであるeth_sendRawTransaction (opens in a new tab)と、ハッシュを指定してトランザクションに関する情報を読み取るためのリクエストであるeth_getTransactionByHash (opens in a new tab)です。トランザクションの送信について詳しくは、Web3を使用したトランザクションの送信に関するチュートリアルを確認してください。

パート2: スマート・コントラクトとのやり取り

ゴエリ・ネットワークへのスマート・コントラクトのデプロイに成功したので、次はそれとやり取りする方法を学びましょう。

interact.jsファイルの作成

これは、やり取りのためのスクリプトを記述するファイルです。パート1でインストールしたEthers.jsライブラリを使用します。

scripts/フォルダ内に、interact.jsという名前の新しいファイルを作成し、以下のコードを追加します。

// interact.js

const API_KEY = process.env.API_KEY
const PRIVATE_KEY = process.env.PRIVATE_KEY
const CONTRACT_ADDRESS = process.env.CONTRACT_ADDRESS

.envファイルの更新

新しい環境変数を使用するため、以前に作成した.envファイルでそれらを定義する必要があります。

AlchemyのAPI_KEYと、スマート・コントラクトがデプロイされたCONTRACT_ADDRESSの定義を追加する必要があります。

.envファイルは次のようになります。

# .env

API_URL = "https://eth-goerli.alchemyapi.io/v2/<your-api-key>"
API_KEY = "<your-api-key>"
PRIVATE_KEY = "<your-metamask-private-key>"
CONTRACT_ADDRESS = "0x<your contract address>"

コントラクトABIの取得

コントラクトのは、スマート・コントラクトとやり取りするためのインターフェースです。Hardhatは自動的にABIを生成し、HelloWorld.jsonに保存します。ABIを使用するには、interact.jsファイルに以下のコード行を追加して、内容を解析する必要があります。

// interact.js
const contract = require("../artifacts/contracts/HelloWorld.sol/HelloWorld.json")

ABIを確認したい場合は、コンソールに出力できます。

console.log(JSON.stringify(contract.abi))

コンソールに出力されたABIを確認するには、ターミナルに移動して以下を実行します。

npx hardhat run scripts/interact.js

コントラクトのインスタンスの作成

コントラクトとやり取りするには、コード内でコントラクトのインスタンスを作成する必要があります。Ethers.jsでこれを行うには、3つの概念を扱う必要があります。

  1. プロバイダー (プロバイダー) - ブロックチェーンへの読み書きアクセスを提供するノードプロバイダー
  2. 署名者 (サイナー) - トランザクションに署名できるイーサリアムのアカウントを表す
  3. コントラクト (Contract) - オンチェーンにデプロイされた特定のコントラクトを表すEthers.jsオブジェクト

前のステップのコントラクトABIを使用して、コントラクトのインスタンスを作成します。

プロバイダー、署名者、コントラクトの詳細については、ethers.jsのドキュメント (opens in a new tab)を参照してください。

初期メッセージの読み取り

initMessage = "Hello world!"を使用してコントラクトをデプロイしたことを覚えていますか?これから、スマート・コントラクトに保存されているそのメッセージを読み取り、コンソールに出力します。

JavaScriptでは、ネットワークとやり取りする際に非同期関数が使用されます。非同期関数の詳細については、こちらのMedium記事 (opens in a new tab)をお読みください。

以下のコードを使用して、スマート・コントラクトのmessage関数を呼び出し、初期メッセージを読み取ります。

ターミナルでnpx hardhat run scripts/interact.jsを使用してファイルを実行すると、次の応答が表示されるはずです。

メッセージ: Hello world!

おめでとうございます!イーサリアムのブロックチェーンからスマート・コントラクトのデータを正常に読み取ることができました。よくやりました!

メッセージの更新

メッセージを読み取るだけでなく、update関数を使用してスマート・コントラクトに保存されているメッセージを更新することもできます!素晴らしいですね。

メッセージを更新するには、インスタンス化されたContractオブジェクトでupdate関数を直接呼び出します。

11行目で、返されたトランザクションオブジェクトに対して.wait()を呼び出していることに注意してください。これにより、関数を終了する前に、スクリプトがブロックチェーン上でトランザクションがマイニングされるのを待機するようになります。.wait()の呼び出しが含まれていない場合、スクリプトはコントラクト内の更新されたmessageの値を認識できない可能性があります。

新しいメッセージの読み取り

前のステップを繰り返して、更新されたmessageの値を読み取ることができるはずです。少し時間を取って、その新しい値を出力するために必要な変更を加えられるか試してみてください!

ヒントが必要な場合、現時点でのinteract.jsファイルは次のようになります。

あとはスクリプトを実行するだけで、古いメッセージ、更新ステータス、そして新しいメッセージがターミナルに出力されるのを確認できるはずです!

npx hardhat run scripts/interact.js --network goerli

メッセージ: Hello World!
メッセージを更新中...
新しいメッセージ: This is the new message.

スクリプトの実行中、新しいメッセージが読み込まれる前にUpdating the message...のステップで少し時間がかかることに気づくかもしれません。これはマイニングプロセスによるものです。マイニング中のトランザクションの追跡に興味がある場合は、Alchemyのメンプール (opens in a new tab)にアクセスしてトランザクションのステータスを確認してください。トランザクションがドロップされた場合は、Goerli Etherscan (opens in a new tab)を確認し、トランザクションハッシュを検索するのも役立ちます。

パート3: スマート・コントラクトをEtherscanに公開する

スマート・コントラクトを完成させるための大変な作業はすべて終わりました。次はそれを世界と共有する番です!

Etherscanでスマート・コントラクトを検証することで、誰でもソースコードを閲覧し、スマート・コントラクトとやり取りできるようになります。さあ、始めましょう!

ステップ1: EtherscanアカウントでAPIキーを生成する

公開しようとしているスマート・コントラクトの所有者であることを検証するために、EtherscanのAPIキーが必要です。

まだEtherscanアカウントを持っていない場合は、アカウントを登録してください (opens in a new tab)

ログインしたら、ナビゲーションバーでユーザー名を見つけ、その上にカーソルを合わせてMy profileボタンを選択します。

プロフィールページにサイドナビゲーションバーが表示されます。サイドナビゲーションバーからAPI Keysを選択します。次に、「Add」ボタンを押して新しいAPIキーを作成し、アプリにhello-worldと名付け、Create New API Keyボタンを押します。

新しいAPIキーがAPIキーのテーブルに表示されるはずです。APIキーをクリップボードにコピーします。

次に、EtherscanのAPIキーを.envファイルに追加する必要があります。

追加すると、.envファイルは次のようになります。

API_URL = "https://eth-goerli.alchemyapi.io/v2/your-api-key"
PUBLIC_KEY = "your-public-account-address"
PRIVATE_KEY = "your-private-account-address"
CONTRACT_ADDRESS = "your-contract-address"
ETHERSCAN_API_KEY = "your-etherscan-key"

Hardhatでデプロイされたスマート・コントラクト

hardhat-etherscanのインストール

Hardhatを使用してコントラクトをEtherscanに公開するのは簡単です。始めるには、まずhardhat-etherscanプラグインをインストールする必要があります。hardhat-etherscanは、Etherscan上でスマート・コントラクトのソースコードとABIを自動的に検証します。これを追加するには、hello-worldディレクトリで以下を実行します。

npm install --save-dev @nomiclabs/hardhat-etherscan

インストールが完了したら、hardhat.config.jsの先頭に以下の文を含め、Etherscanの設定オプションを追加します。

Etherscanでスマート・コントラクトを検証する

すべてのファイルが保存され、すべての.env変数が正しく設定されていることを確認します。

コントラクトのアドレスとデプロイ先のネットワークを渡して、verifyタスクを実行します。

npx hardhat verify --network goerli DEPLOYED_CONTRACT_ADDRESS 'Hello World!'

DEPLOYED_CONTRACT_ADDRESSが、ゴエリのテストネットにデプロイされたスマート・コントラクトのアドレスであることを確認してください。また、最後の引数('Hello World!')は、パート1のデプロイ手順で使用したのと同じ文字列値である必要があります。

すべてがうまくいけば、ターミナルに次のメッセージが表示されます。

Successfully submitted source code for contract
contracts/HelloWorld.sol:HelloWorld at 0xdeployed-contract-address
for verification on Etherscan. Waiting for verification result...


Successfully verified contract HelloWorld on Etherscan.
https://goerli.etherscan.io/address/<contract-address>#contracts

おめでとうございます!あなたのスマート・コントラクトのコードがEtherscanに公開されました!

Etherscanでスマート・コントラクトを確認しましょう!

ターミナルに表示されたリンクにアクセスすると、Etherscanに公開されたスマート・コントラクトのコードとABIを確認できるはずです!

やったね、大成功です!これで誰でもあなたのスマート・コントラクトを呼び出したり、書き込んだりできるようになりました!次にあなたが何を構築するのか、楽しみにしています!

パート4 - スマート・コントラクトとフロントエンドの統合

このチュートリアルを終えると、以下のことができるようになります。

  • メタマスクウォレットを分散型アプリケーション (dapp) に接続する
  • Alchemy Web3 (opens in a new tab) APIを使用してスマート・コントラクトからデータを読み取る
  • メタマスクを使用してイーサリアムのトランザクションに署名する

このdappでは、フロントエンドフレームワークとしてReact (opens in a new tab)を使用します。ただし、主にプロジェクトにWeb3機能をもたらすことに焦点を当てるため、Reactの基礎を解説することに多くの時間を費やさない点に注意してください。

前提条件として、Reactの初心者レベルの理解が必要です。そうでない場合は、公式のReact入門チュートリアル (opens in a new tab)を完了することをお勧めします。

スターターファイルのクローン

まず、hello-world-part-fourのGitHubリポジトリ (opens in a new tab)にアクセスしてこのプロジェクトのスターターファイルを取得し、このリポジトリをローカルマシンにクローンします。

クローンしたリポジトリをローカルで開きます。starter-filescompletedの2つのフォルダーが含まれていることに注意してください。

  • starter-files - このディレクトリで作業します。UIをイーサリアムウォレットと、パート3でEtherscanに公開したスマート・コントラクトに接続します。
  • completedには完成したチュートリアル全体が含まれており、行き詰まった場合の参考としてのみ使用してください。

次に、お気に入りのコードエディターでstarter-filesのコピーを開き、srcフォルダーに移動します。

記述するコードはすべてsrcフォルダー内に配置されます。HelloWorld.jsコンポーネントとutil/interact.js JavaScriptファイルを編集して、プロジェクトにWeb3機能を追加します。

スターターファイルの確認

コーディングを始める前に、スターターファイルで提供されているものを確認しましょう。

Reactプロジェクトの実行

まずはブラウザでReactプロジェクトを実行してみましょう。Reactの素晴らしい点は、プロジェクトをブラウザで実行すると、保存した変更がブラウザ上でリアルタイムに更新されることです。

プロジェクトを実行するには、starter-filesフォルダーのルートディレクトリに移動し、ターミナルでnpm installを実行してプロジェクトの依存関係をインストールします。

cd starter-files
npm install

インストールが完了したら、ターミナルでnpm startを実行します。

npm start

これにより、ブラウザでhttp://localhost:3000/ (opens in a new tab)が開き、プロジェクトのフロントエンドが表示されるはずです。1つのフィールド(スマート・コントラクトに保存されているメッセージを更新する場所)、「Connect Wallet(ウォレットを接続)」ボタン、「Update(更新)」ボタンで構成されているはずです。

どちらかのボタンをクリックしてみると、機能しないことに気づくでしょう。これは、まだその機能をプログラミングする必要があるためです。

HelloWorld.jsコンポーネント

エディターでsrcフォルダーに戻り、HelloWorld.jsファイルを開きましょう。これは私たちが作業する主要なReactコンポーネントであるため、このファイル内のすべてを理解することが非常に重要です。

このファイルの上部には、Reactライブラリ、useEffectおよびuseStateフック、./util/interact.jsからのいくつかのアイテム(これらについては後で詳しく説明します!)、Alchemyのロゴなど、プロジェクトを実行するために必要なインポート文がいくつかあることに気づくでしょう。

次に、特定のイベントの後に更新する状態変数があります。

// HelloWorld.js

//状態変数
const [walletAddress, setWallet] = useState("")
const [status, setStatus] = useState("")
const [message, setMessage] = useState("No connection to the network.")
const [newMessage, setNewMessage] = useState("")

各変数が表すものは以下の通りです。

  • walletAddress - ユーザーのウォレットアドレスを保存する文字列
  • status - ユーザーにdappとの対話方法を案内する役立つメッセージを保存する文字列
  • message - スマート・コントラクト内の現在のメッセージを保存する文字列
  • newMessage - スマート・コントラクトに書き込まれる新しいメッセージを保存する文字列

状態変数の後には、未実装の5つの関数(useEffectaddSmartContractListeneraddWalletListenerconnectWalletPressedonUpdatePressed)があります。それぞれの機能について以下で説明します。

  • useEffect (opens in a new tab) - これはコンポーネントがレンダリングされた後に呼び出されるReactフックです。空の配列[]プロパティが渡されているため(4行目を参照)、コンポーネントの_最初_のレンダリング時にのみ呼び出されます。ここでは、スマート・コントラクトに保存されている現在のメッセージを読み込み、スマート・コントラクトとウォレットのリスナーを呼び出し、ウォレットがすでに接続されているかどうかを反映するようにUIを更新します。
  • addSmartContractListener - この関数は、HelloWorldコントラクトのUpdatedMessagesイベントを監視し、スマート・コントラクト内のメッセージが変更されたときにUIを更新するリスナーを設定します。
  • addWalletListener - この関数は、ユーザーがウォレットを切断したりアドレスを切り替えたりするなど、ユーザーのメタマスクウォレットの状態の変化を検出するリスナーを設定します。
  • connectWalletPressed - この関数は、ユーザーのメタマスクウォレットをdappに接続するために呼び出されます。
  • onUpdatePressed - この関数は、ユーザーがスマート・コントラクトに保存されているメッセージを更新したいときに呼び出されます。

このファイルの終わり近くに、コンポーネントのUIがあります。

このコードを注意深く見ると、UIでさまざまな状態変数をどこで使用しているかがわかります。

  • 6〜12行目では、ユーザーのウォレットが接続されている場合(つまり、walletAddress.length > 0)、IDが「walletButton」のボタンにユーザーのwalletAddressの短縮版を表示します。そうでない場合は、単に「Connect Wallet」と表示します。
  • 17行目では、スマート・コントラクトに保存されている現在のメッセージを表示します。これはmessage文字列にキャプチャされています。
  • 23〜26行目では、制御されたコンポーネント (opens in a new tab)を使用して、テキストフィールドの入力が変更されたときにnewMessage状態変数を更新します。

状態変数に加えて、IDがpublishButtonwalletButtonのボタンがそれぞれクリックされたときに、connectWalletPressedonUpdatePressed関数が呼び出されることもわかります。

最後に、このHelloWorld.jsコンポーネントがどこに追加されるかを確認しましょう。

他のすべてのコンポーネントのコンテナとして機能するReactのメインコンポーネントであるApp.jsファイルを見ると、7行目にHelloWorld.jsコンポーネントが注入されていることがわかります。

最後になりましたが、提供されているもう1つのファイルであるinteract.jsファイルを確認しましょう。

interact.jsファイル

M-V-C (opens in a new tab)パラダイムに従うため、dappのロジック、データ、ルールを管理するすべての関数を含む別のファイルを用意し、それらの関数をフロントエンド(HelloWorld.jsコンポーネント)にエクスポートできるようにします。

👆🏽これこそがinteract.jsファイルの目的です!

srcディレクトリ内のutilフォルダーに移動すると、スマート・コントラクトとの対話やウォレットの関数と変数をすべて含むinteract.jsというファイルが含まれていることに気づくでしょう。

ファイルの上部で、helloWorldContractオブジェクトがコメントアウトされていることに気づくでしょう。このチュートリアルの後半で、このオブジェクトのコメントを解除し、この変数にスマート・コントラクトをインスタンス化して、HelloWorld.jsコンポーネントにエクスポートします。

helloWorldContractオブジェクトの後の4つの未実装の関数は、以下のことを行います。

  • loadCurrentMessage - この関数は、スマート・コントラクトに保存されている現在のメッセージを読み込むロジックを処理します。Alchemy Web3 API (opens in a new tab)を使用して、Hello Worldスマート・コントラクトへの_読み取り_呼び出しを行います。
  • connectWallet - この関数は、ユーザーのメタマスクをdappに接続します。
  • getCurrentWalletConnected - この関数は、ページ読み込み時にイーサリアムアカウントがすでにdappに接続されているかどうかを確認し、それに応じてUIを更新します。
  • updateMessage - この関数は、スマート・コントラクトに保存されているメッセージを更新します。Hello Worldスマート・コントラクトへの_書き込み_呼び出しを行うため、ユーザーのメタマスクウォレットはメッセージを更新するためにイーサリアムのトランザクションに署名する必要があります。

作業内容が理解できたところで、スマート・コントラクトから読み取る方法を見ていきましょう!

ステップ3: スマート・コントラクトからの読み取り

スマート・コントラクトから読み取るには、以下を正常に設定する必要があります。

  • イーサリアムチェーンへのAPI接続
  • 読み込まれたスマート・コントラクトのインスタンス
  • スマート・コントラクトの関数を呼び出すための関数
  • スマート・コントラクトから読み取っているデータが変更されたときに更新を監視するリスナー

手順が多いように聞こえるかもしれませんが、心配しないでください!それぞれの手順を段階的に説明します! :)

イーサリアムチェーンへのAPI接続の確立

このチュートリアルのパート2で、Alchemy Web3キーを使用してスマート・コントラクトから読み取った (opens in a new tab)ことを覚えていますか?チェーンから読み取るには、dappでもAlchemy Web3キーが必要になります。

まだ持っていない場合は、まずstarter-filesのルートディレクトリに移動し、ターミナルで以下を実行してAlchemy Web3 (opens in a new tab)をインストールします。

npm install @alch/alchemy-web3

Alchemy Web3 (opens in a new tab)Web3.js (opens in a new tab)のラッパーであり、拡張されたAPIメソッドやその他の重要な利点を提供して、Web3開発者としての作業を容易にします。最小限の構成で済むように設計されているため、アプリですぐに使用を開始できます!

次に、プロジェクトディレクトリにdotenv (opens in a new tab)パッケージをインストールして、取得したAPIキーを安全に保存する場所を確保します。

npm install dotenv --save

このdappでは、HTTP APIキーの代わりにWebsockets APIキーを使用します。これにより、スマート・コントラクトに保存されているメッセージが変更されたときに検出するリスナーを設定できるようになります。

APIキーを取得したら、ルートディレクトリに.envファイルを作成し、そこにAlchemy WebsocketsのURLを追加します。その後、.envファイルは次のようになります。

REACT_APP_ALCHEMY_KEY = wss://eth-goerli.ws.alchemyapi.io/v2/<鍵>

これで、dappにAlchemy Web3エンドポイントを設定する準備が整いました!utilフォルダー内にネストされているinteract.jsに戻り、ファイルの上部に次のコードを追加しましょう。

// interact.js

require("dotenv").config()
const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY
const { createAlchemyWeb3 } = require("@alch/alchemy-web3")
const web3 = createAlchemyWeb3(alchemyKey)

//export const helloWorldContract;

上記では、まず.envファイルからAlchemyキーをインポートし、次にalchemyKeycreateAlchemyWeb3に渡してAlchemy Web3エンドポイントを確立しました。

このエンドポイントの準備ができたら、スマート・コントラクトを読み込みましょう!

Hello Worldスマート・コントラクトの読み込み

Hello Worldスマート・コントラクトを読み込むには、そのコントラクトアドレスとABIが必要です。これらは両方とも、このチュートリアルのパート3を完了していればEtherscanで見つけることができます。

EtherscanからコントラクトABIを取得する方法

このチュートリアルのパート3をスキップした場合は、アドレス0x6f3f635A9762B47954229Ea479b4541eAF402A6A (opens in a new tab)のHelloWorldコントラクトを使用できます。そのABIはこちら (opens in a new tab)にあります。

コントラクトABIは、コントラクトがどの関数を呼び出すかを指定し、関数が期待する形式でデータを返すことを保証するために必要です。コントラクトABIをコピーしたら、srcディレクトリにcontract-abi.jsonというJSONファイルとして保存しましょう。

contract-abi.jsonはsrcフォルダーに保存する必要があります。

コントラクトアドレス、ABI、およびAlchemy Web3エンドポイントが揃ったので、contractメソッド (opens in a new tab)を使用してスマート・コントラクトのインスタンスを読み込むことができます。コントラクトABIをinteract.jsファイルにインポートし、コントラクトアドレスを追加します。

// interact.js

const contractABI = require("../contract-abi.json")
const contractAddress = "0x6f3f635A9762B47954229Ea479b4541eAF402A6A"

これでようやくhelloWorldContract変数のコメントを解除し、AlchemyWeb3エンドポイントを使用してスマート・コントラクトを読み込むことができます。

// interact.js
export const helloWorldContract = new web3.eth.Contract(
  contractABI,
  contractAddress
)

まとめると、interact.jsの最初の12行は次のようになります。

コントラクトが読み込まれたので、loadCurrentMessage関数を実装できます!

interact.jsファイルでのloadCurrentMessageの実装

この関数は非常にシンプルです。コントラクトから読み取るために、シンプルな非同期Web3呼び出しを行います。この関数は、スマート・コントラクトに保存されているメッセージを返します。

interact.jsファイルのloadCurrentMessageを次のように更新します。

// interact.js

export const loadCurrentMessage = async () => {
  const message = await helloWorldContract.methods.message().call()
  return message
}

このスマート・コントラクトをUIに表示したいため、HelloWorld.jsコンポーネントのuseEffect関数を次のように更新しましょう。

// HelloWorld.js

//一度だけ呼び出されます
useEffect(async () => {
  const message = await loadCurrentMessage()
  setMessage(message)
}, [])

なお、loadCurrentMessageはコンポーネントの最初のレンダリング時に1回だけ呼び出されるようにします。スマート・コントラクト内のメッセージが変更された後にUIを自動的に更新するaddSmartContractListenerをすぐに実装します。

リスナーに入る前に、これまでの内容を確認しましょう!HelloWorld.jsinteract.jsファイルを保存し、http://localhost:3000/ (opens in a new tab)にアクセスします。

現在のメッセージが「No connection to the network.(ネットワークに接続されていません)」ではなくなっていることに気づくでしょう。代わりに、スマート・コントラクトに保存されているメッセージが反映されています。素晴らしいですね!

UIにスマート・コントラクトに保存されているメッセージが反映されるはずです

さて、そのリスナーについてですが...

addSmartContractListenerの実装

このチュートリアルシリーズのパート1 (opens in a new tab)で記述したHelloWorld.solファイルを思い出すと、スマート・コントラクトのupdate関数が呼び出された後に発行されるUpdatedMessagesというスマート・コントラクトイベントがあることを思い出すでしょう(9行目と27行目を参照)。

スマート・コントラクトイベントは、ブロックチェーン上で何かが起こった(つまり、_イベント_があった)ことをフロントエンドアプリケーションに伝えるためのコントラクトの方法です。フロントエンドアプリケーションは特定のイベントを「リッスン」し、それらが発生したときにアクションを起こすことができます。

addSmartContractListener関数は、Hello Worldスマート・コントラクトのUpdatedMessagesイベントを特別にリッスンし、新しいメッセージを表示するようにUIを更新します。

addSmartContractListenerを次のように変更します。

リスナーがイベントを検出したときに何が起こるかを分解してみましょう。

  • イベントの発行時にエラーが発生した場合、status状態変数を介してUIに反映されます。
  • それ以外の場合は、返されたdataオブジェクトを使用します。data.returnValuesはゼロからインデックス付けされた配列で、配列の最初の要素には前のメッセージが保存され、2番目の要素には更新されたメッセージが保存されます。全体として、イベントが成功すると、message文字列を更新されたメッセージに設定し、newMessage文字列をクリアして、新しいメッセージがスマート・コントラクトに公開されたことを反映するようにstatus状態変数を更新します。

最後に、HelloWorld.jsコンポーネントの最初のレンダリング時に初期化されるように、useEffect関数でリスナーを呼び出しましょう。全体として、useEffect関数は次のようになります。

// HelloWorld.js

useEffect(async () => {
  const message = await loadCurrentMessage()
  setMessage(message)
  addSmartContractListener()
}, [])

スマート・コントラクトから読み取ることができるようになったので、書き込む方法もわかると素晴らしいですね!ただし、dappに書き込むには、まずイーサリアムウォレットを接続する必要があります。

そこで、次はイーサリアムウォレット(メタマスク)を設定し、それをdappに接続することに取り組みます!

ステップ4: イーサリアムウォレットの設定

イーサリアムチェーンに何かを書き込むには、ユーザーは仮想ウォレットの秘密鍵を使用してトランザクションに署名する必要があります。このチュートリアルでは、イーサリアムアカウントアドレスを管理するために使用されるブラウザ内の仮想ウォレットであるメタマスク (opens in a new tab)を使用します。これにより、エンドユーザーにとってこのトランザクションの署名が非常に簡単になります。

イーサリアムでのトランザクションの仕組みについてさらに詳しく知りたい場合は、イーサリアム財団のこちらのページを確認してください。

メタマスクのダウンロード

こちら (opens in a new tab)から無料でメタマスクをダウンロードしてアカウントを作成できます。アカウントを作成する際、またはすでにアカウントを持っている場合は、右上の「Goerli Test Network」に切り替えてください(実際のお金を扱わないようにするためです)。

フォーセットからイーサを追加する

イーサリアムブロックチェーンでトランザクションに署名するには、偽のETHが必要です。ETHを取得するには、FaucETH (opens in a new tab)にアクセスしてゴエリアカウントアドレスを入力し、「Request funds」をクリックして、ドロップダウンで「Ethereum Testnet Goerli」を選択し、最後に「Request funds」ボタンをもう一度クリックします。すぐにメタマスクアカウントにETHが表示されるはずです!

残高の確認

残高があることを再確認するために、Alchemyのコンポーザーツール (opens in a new tab)を使用してeth_getBalance (opens in a new tab)リクエストを行いましょう。これにより、ウォレット内のETHの量が返されます。メタマスクアカウントアドレスを入力して「Send Request」をクリックすると、次のような応答が表示されるはずです。

{"jsonrpc": "2.0", "id": 0, "result": "0xde0b6b3a7640000"}

注: この結果はETHではなくwei単位です。weiはイーサの最小単位として使用されます。weiからETHへの変換は、1 ETH = 10¹⁸ weiです。したがって、0xde0b6b3a7640000を10進数に変換すると1*10¹⁸となり、1 ETHに等しくなります。

ふう!偽のお金がすべて揃いました! 🤑

ステップ5: メタマスクをUIに接続する

メタマスクウォレットの設定が完了したので、dappを接続しましょう!

connectWallet関数

interact.jsファイルで、connectWallet関数を実装しましょう。これをHelloWorld.jsコンポーネントで呼び出すことができます。

connectWalletを次のように変更しましょう。

では、この巨大なコードブロックは正確に何をするのでしょうか?

まず、ブラウザでwindow.ethereumが有効になっているかどうかを確認します。

window.ethereumは、メタマスクやその他のウォレットプロバイダーによって注入されるグローバルAPIであり、Webサイトがユーザーのイーサリアムアカウントを要求できるようにします。承認されると、ユーザーが接続しているブロックチェーンからデータを読み取り、ユーザーにメッセージやトランザクションへの署名を提案できます。詳細については、メタマスクのドキュメント (opens in a new tab)を確認してください!

window.ethereumが存在_しない_場合、それはメタマスクがインストールされていないことを意味します。これによりJSONオブジェクトが返され、返されるaddressは空の文字列になり、status JSXオブジェクトはユーザーがメタマスクをインストールする必要があることを伝えます。

一方、window.ethereumが存在_する_場合、ここからが面白くなります。

try/catchループを使用して、window.ethereum.request({ method: "eth_requestAccounts" }); (opens in a new tab)を呼び出すことでメタマスクへの接続を試みます。この関数を呼び出すとブラウザでメタマスクが開き、ユーザーはウォレットをdappに接続するように求められます。

  • ユーザーが接続を選択した場合、method: "eth_requestAccounts"はdappに接続されたユーザーのすべてのアカウントアドレスを含む配列を返します。全体として、connectWallet関数は、この配列の_最初_のaddress(9行目を参照)と、ユーザーにスマート・コントラクトへのメッセージの書き込みを促すstatusメッセージを含むJSONオブジェクトを返します。
  • ユーザーが接続を拒否した場合、JSONオブジェクトには返されるaddressの空の文字列と、ユーザーが接続を拒否したことを反映するstatusメッセージが含まれます。

このconnectWallet関数を記述したので、次のステップはそれをHelloWorld.jsコンポーネントで呼び出すことです。

HelloWorld.js UIコンポーネントへのconnectWallet関数の追加

HelloWorld.jsconnectWalletPressed関数に移動し、次のように更新します。

// HelloWorld.js

const connectWalletPressed = async () => {
  const walletResponse = await connectWallet()
  setStatus(walletResponse.status)
  setWallet(walletResponse.address)
}

機能の大部分がinteract.jsファイルからHelloWorld.jsコンポーネントへと抽象化されていることに気づきましたか?これはM-V-Cパラダイムに準拠するためです!

connectWalletPressedでは、インポートされたconnectWallet関数へのawait呼び出しを単に行い、その応答を使用して、状態フックを介してstatusおよびwalletAddress変数を更新します。

それでは、両方のファイル(HelloWorld.jsinteract.js)を保存し、これまでのUIをテストしてみましょう。

ブラウザでhttp://localhost:3000/ (opens in a new tab)ページを開き、右上の「Connect Wallet」ボタンを押します。

メタマスクがインストールされている場合は、ウォレットをdappに接続するように求められるはずです。接続の招待を承認します。

ウォレットボタンにアドレスが接続されていることが反映されるはずです!やったー 🔥

次に、ページを更新してみてください...これは奇妙です。すでに接続されているにもかかわらず、ウォレットボタンがメタマスクの接続を求めています...

しかし、恐れることはありません!getCurrentWalletConnectedを実装することで、アドレスがすでにdappに接続されているかどうかを確認し、それに応じてUIを更新することで、この問題には簡単に対処できます!

getCurrentWalletConnected関数

interact.jsファイルのgetCurrentWalletConnected関数を次のように更新します。

このコードは、前のステップで記述したconnectWallet関数と_非常_によく似ています。

主な違いは、ユーザーがウォレットを接続するためにメタマスクを開くeth_requestAccountsメソッドを呼び出す代わりに、ここでは現在dappに接続されているメタマスクアドレスを含む配列を単に返すeth_accountsメソッドを呼び出すことです。

この関数の動作を確認するために、HelloWorld.jsコンポーネントのuseEffect関数で呼び出してみましょう。

getCurrentWalletConnectedへの呼び出しの応答を使用して、walletAddressおよびstatus状態変数を更新していることに注意してください。

このコードを追加したので、ブラウザウィンドウを更新してみましょう。

素晴らしい!ボタンには接続されていることが表示され、更新した後でも接続されているウォレットのアドレスのプレビューが表示されるはずです!

addWalletListenerの実装

dappウォレット設定の最後のステップは、ユーザーが切断したりアカウントを切り替えたりするなど、ウォレットの状態が変化したときにUIが更新されるようにウォレットリスナーを実装することです。

HelloWorld.jsファイルで、addWalletListener関数を次のように変更します。

現時点では、ここで何が起こっているかを理解するのに私たちの助けは必要ないと思いますが、念のため、簡単に分解してみましょう。

  • まず、関数はwindow.ethereumが有効になっているか(つまり、メタマスクがインストールされているか)を確認します。
    • 有効になっていない場合は、status状態変数を、ユーザーにメタマスクのインストールを促すJSX文字列に設定するだけです。
    • 有効になっている場合は、3行目でメタマスクウォレットの状態の変化をリッスンするリスナーwindow.ethereum.on("accountsChanged")を設定します。これには、ユーザーが追加のアカウントをdappに接続したとき、アカウントを切り替えたとき、またはアカウントを切断したときが含まれます。少なくとも1つのアカウントが接続されている場合、walletAddress状態変数は、リスナーによって返されるaccounts配列の最初のアカウントとして更新されます。それ以外の場合、walletAddressは空の文字列として設定されます。

最後になりましたが、これをuseEffect関数で呼び出す必要があります。

これで完了です!すべてのウォレット機能のプログラミングが正常に完了しました!それでは最後のタスク、スマート・コントラクトに保存されているメッセージの更新に進みましょう!

ステップ6: updateMessage関数の実装

さあ皆さん、いよいよ大詰めです!interact.jsファイルのupdateMessageで、以下のことを行います。

  1. スマート・コントラクトに公開したいメッセージが有効であることを確認する
  2. メタマスクを使用してトランザクションに署名する
  3. HelloWorld.jsフロントエンドコンポーネントからこの関数を呼び出す

それほど時間はかかりません。このdappを完成させましょう!

入力エラー処理

当然のことながら、関数の開始時に何らかの入力エラー処理を行うことは理にかなっています。

メタマスク拡張機能がインストールされていない場合、ウォレットが接続されていない場合(つまり、渡されたaddressが空の文字列である場合)、またはmessageが空の文字列である場合は、関数が早期に返されるようにします。updateMessageに次のエラー処理を追加しましょう。

適切な入力エラー処理が追加されたので、メタマスクを介してトランザクションに署名する時間です!

トランザクションへの署名

従来のWeb3イーサリアムトランザクションにすでに慣れている場合、次に記述するコードは非常に馴染みのあるものになります。入力エラー処理コードの下に、updateMessageに次を追加します。

何が起こっているかを分解してみましょう。まず、トランザクションパラメーターを設定します。ここで、

  • toは受信者アドレス(スマート・コントラクト)を指定します
  • fromはトランザクションの署名者、つまり関数に渡したaddress変数を指定します
  • dataには、Hello Worldスマート・コントラクトのupdateメソッドへの呼び出しが含まれており、入力としてmessage文字列変数を受け取ります

次に、await呼び出しwindow.ethereum.requestを行い、メタマスクにトランザクションへの署名を要求します。11行目と12行目で、ETHメソッドeth_sendTransactionを指定し、transactionParametersを渡していることに注意してください。

この時点で、ブラウザでメタマスクが開き、ユーザーにトランザクションへの署名または拒否を求めます。

  • トランザクションが成功した場合、関数はJSONオブジェクトを返します。ここで、status JSX文字列は、トランザクションの詳細についてEtherscanを確認するようにユーザーに促します。
  • トランザクションが失敗した場合、関数はJSONオブジェクトを返します。ここで、status文字列はエラーメッセージを伝えます。

全体として、updateMessage関数は次のようになります。

最後になりましたが、updateMessage関数をHelloWorld.jsコンポーネントに接続する必要があります。

updateMessageHelloWorld.jsフロントエンドに接続する

onUpdatePressed関数は、インポートされたupdateMessage関数へのawait呼び出しを行い、トランザクションが成功したか失敗したかを反映するようにstatus状態変数を変更する必要があります。

// HelloWorld.js

const onUpdatePressed = async () => {
  const { status } = await updateMessage(walletAddress, newMessage)
  setStatus(status)
}

非常にクリーンでシンプルです。そしてなんと...あなたのdappが完成しました!!!

さあ、Updateボタンをテストしてみてください!

独自のカスタムdappの作成

おめでとうございます、チュートリアルの最後までやり遂げました!まとめると、以下の方法を学びました。

  • メタマスクウォレットをdappプロジェクトに接続する
  • Alchemy Web3 (opens in a new tab) APIを使用してスマート・コントラクトからデータを読み取る
  • メタマスクを使用してイーサリアムのトランザクションに署名する

これで、このチュートリアルで得たスキルを応用して、独自のカスタムdappプロジェクトを構築する準備が完全に整いました!いつものように、質問がある場合は、遠慮なくAlchemyのディスコード (opens in a new tab)で助けを求めてください。 🧙‍♂️

このチュートリアルを完了したら、ツイッターで@alchemyplatform (opens in a new tab)をタグ付けして、体験談やフィードバックをお知らせください!