初心者のためのHello Worldスマート・コントラクト - フルスタック
ブロックチェーン開発が初めてで、どこから始めればよいか、あるいはスマート・コントラクトをどのようにデプロイして対話すればよいかわからない場合、このガイドはあなたのためのものです。メタマスク (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」という名前を付け、短い説明を書きます。環境(Environment)としてStagingを、ネットワークとしてGoerliを選択します。
注: 必ず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 name: (hello-world)
version: (1.0.0)
description: hello world smart contract
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to /Users/.../.../.../hello-world/package.json:
{
"name": "hello-world",
"version": "1.0.0",
"description": "hello world smart contract",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
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」を選択します。
888 888 888 888 888
888 888 888 888 888
888 888 888 888 888
8888888888 8888b. 888d888 .d88888 88888b. 8888b. 888888
888 888 "88b 888P" d88" 888 888 "88b "88b 888
888 888 .d888888 888 888 888 888 888 .d888888 888
888 888 888 888 888 Y88b 888 888 888 888 888 Y88b.
888 888 "Y888888 888 "Y88888 888 888 "Y888888 "Y888
👷 Welcome to Hardhat v2.0.11 👷
What do you want to do? …
Create a sample project
❯ Create an empty hardhat.config.js
Quit
これにより、プロジェクト内にhardhat.config.jsファイルが生成されます。このファイルは、チュートリアルの後半でプロジェクトの設定を指定するために使用します。
ステップ9: プロジェクトフォルダを追加する
プロジェクトを整理するために、2つの新しいフォルダを作成しましょう。コマンドラインでhello-worldプロジェクトのルートディレクトリに移動し、次のように入力します。
mkdir contracts
mkdir scripts
contracts/は、Hello Worldスマート・コントラクトのコードファイルを保存する場所ですscripts/は、コントラクトをデプロイして対話するためのスクリプトを保存する場所です
ステップ10: コントラクトを記述する
いつコードを書くのか疑問に思っているかもしれません。いよいよその時です!
お気に入りのエディタでhello-worldプロジェクトを開きます。スマート・コントラクトは一般的にSolidityで記述されるため、ここでもSolidityを使用してスマート・コントラクトを記述します。
contractsフォルダに移動し、HelloWorld.solという新しいファイルを作成します- 以下は、このチュートリアルで使用するHello Worldスマート・コントラクトのサンプルです。以下の内容を
HelloWorld.solファイルにコピーします。
注: コメントを読んで、このコントラクトが何を行うかを理解してください。
// セマンティックバージョニングを使用して、Solidityのバージョンを指定します。
// 詳細: https://solidity.readthedocs.io/en/v0.5.10/layout-of-source-files.html#pragma
pragma solidity >=0.7.3;
// `HelloWorld`という名前のコントラクトを定義します。
// コントラクトは、関数とデータ(その状態)の集合体です。デプロイされると、コントラクトはイーサリアムブロックチェーン上の特定のアドレスに配置されます。詳細: https://solidity.readthedocs.io/en/v0.5.10/structure-of-a-contract.html
contract HelloWorld {
// update関数が呼び出されたときに発行されます
// スマート・コントラクトのイベントは、ブロックチェーン上で何かが発生したことをアプリのフロントエンドに伝えるための手段です。フロントエンドは特定のイベントを「リッスン」し、それらが発生したときにアクションを実行できます。
event UpdatedMessages(string oldStr, string newStr);
// `string`型の状態変数`message`を宣言します。
// 状態変数は、その値がコントラクトのストレージに永続的に保存される変数です。`public`キーワードを使用すると、コントラクトの外部から変数にアクセスできるようになり、他のコントラクトやクライアントが値にアクセスするために呼び出せる関数が作成されます。
string public message;
// 多くのクラスベースのオブジェクト指向言語と同様に、コンストラクタはコントラクトの作成時にのみ実行される特別な関数です。
// コンストラクタは、コントラクトのデータを初期化するために使用されます。詳細: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#constructors
constructor(string memory initMessage) {
// 文字列の引数`initMessage`を受け取り、その値をコントラクトの`message`ストレージ変数に設定します。
message = initMessage;
}
// 文字列の引数を受け取り、`message`ストレージ変数を更新するパブリック関数です。
function update(string memory newMessage) public {
string memory oldMsg = message;
message = newMessage;
emit UpdatedMessages(oldMsg, newMessage);
}
}
これは、作成時にメッセージを保存する基本的なスマート・コントラクトです。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などの他の名前を付けないでください。
- 秘密鍵をエクスポートするには、こちらの手順 (opens in a new tab)に従ってください
- HTTP Alchemy API URLの取得については以下を参照してください
.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を次のように更新します。
/**
* @type import('hardhat/config').HardhatUserConfig
*/
require("dotenv").config()
require("@nomiclabs/hardhat-ethers")
const { API_URL, PRIVATE_KEY } = process.env
module.exports = {
solidity: "0.7.3",
defaultNetwork: "goerli",
networks: {
hardhat: {},
goerli: {
url: API_URL,
accounts: [`0x${PRIVATE_KEY}`],
},
},
}
ステップ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という新しいファイルを作成して、以下の内容を追加します。
async function main() {
const HelloWorld = await ethers.getコントラクトFactory("HelloWorld")
// デプロイを開始し、コントラクトオブジェクトに解決されるプロミスを返します
const hello_world = await HelloWorld.deploy("Hello World!")
console.log("Contract deployed to address:", hello_world.address)
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error)
process.exit(1)
})
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プラグインのContractFactoryとContractを使用する場合、インスタンスはデフォルトで最初の署名者(所有者)に接続されます。
const hello_world = await HelloWorld.deploy()
ContractFactoryでdeploy()を呼び出すとデプロイが開始され、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つの概念を扱う必要があります。
- プロバイダー (プロバイダー) - ブロックチェーンへの読み書きアクセスを提供するノードプロバイダー
- 署名者 (サイナー) - トランザクションに署名できるイーサリアムのアカウントを表す
- コントラクト (Contract) - オンチェーンにデプロイされた特定のコントラクトを表すEthers.jsオブジェクト
前のステップのコントラクトABIを使用して、コントラクトのインスタンスを作成します。
// interact.js
// Provider
const alchemyProvider = new ethers.providers.AlchemyProvider(
(network = "goerli"),
API_KEY
)
// Signer
const signer = new ethers.Wallet(PRIVATE_KEY, alchemyProvider)
// Contract
const helloWorldContract = new ethers.Contract(
CONTRACT_ADDRESS,
contract.abi,
signer
)
プロバイダー、署名者、コントラクトの詳細については、ethers.jsのドキュメント (opens in a new tab)を参照してください。
初期メッセージの読み取り
initMessage = "Hello world!"を使用してコントラクトをデプロイしたことを覚えていますか?これから、スマート・コントラクトに保存されているそのメッセージを読み取り、コンソールに出力します。
JavaScriptでは、ネットワークとやり取りする際に非同期関数が使用されます。非同期関数の詳細については、こちらのMedium記事 (opens in a new tab)をお読みください。
以下のコードを使用して、スマート・コントラクトのmessage関数を呼び出し、初期メッセージを読み取ります。
// interact.js
// ...
async function main() {
const message = await helloWorldContract.message()
console.log("The message is: " + message)
}
main()
ターミナルでnpx hardhat run scripts/interact.jsを使用してファイルを実行すると、次の応答が表示されるはずです。
メッセージ: Hello world!
おめでとうございます!イーサリアムのブロックチェーンからスマート・コントラクトのデータを正常に読み取ることができました。よくやりました!
メッセージの更新
メッセージを読み取るだけでなく、update関数を使用してスマート・コントラクトに保存されているメッセージを更新することもできます!素晴らしいですね。
メッセージを更新するには、インスタンス化されたContractオブジェクトでupdate関数を直接呼び出します。
// interact.js
// ...
async function main() {
const message = await helloWorldContract.message()
console.log("The message is: " + message)
console.log("Updating the message...")
const tx = await helloWorldContract.update("This is the new message.")
await tx.wait()
}
main()
11行目で、返されたトランザクションオブジェクトに対して.wait()を呼び出していることに注意してください。これにより、関数を終了する前に、スクリプトがブロックチェーン上でトランザクションがマイニングされるのを待機するようになります。.wait()の呼び出しが含まれていない場合、スクリプトはコントラクト内の更新されたmessageの値を認識できない可能性があります。
新しいメッセージの読み取り
前のステップを繰り返して、更新されたmessageの値を読み取ることができるはずです。少し時間を取って、その新しい値を出力するために必要な変更を加えられるか試してみてください!
ヒントが必要な場合、現時点での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
const contract = require("../artifacts/contracts/HelloWorld.sol/HelloWorld.json")
// プロバイダー - Alchemy
const alchemyProvider = new ethers.providers.AlchemyProvider(
(network = "goerli"),
API_KEY
)
// サイナー - あなた
const signer = new ethers.Wallet(PRIVATE_KEY, alchemyProvider)
// コントラクトインスタンス
const helloWorldContract = new ethers.Contract(
CONTRACT_ADDRESS,
contract.abi,
signer
)
async function main() {
const message = await helloWorldContract.message()
console.log("The message is: " + message)
console.log("Updating the message...")
const tx = await helloWorldContract.update("this is the new message")
await tx.wait()
const newMessage = await helloWorldContract.message()
console.log("The new message is: " + newMessage)
}
main()
あとはスクリプトを実行するだけで、古いメッセージ、更新ステータス、そして新しいメッセージがターミナルに出力されるのを確認できるはずです!
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の設定オプションを追加します。
// hardhat.config.js
require("dotenv").config()
require("@nomiclabs/hardhat-ethers")
require("@nomiclabs/hardhat-etherscan")
const { API_URL, PRIVATE_KEY, ETHERSCAN_API_KEY } = process.env
module.exports = {
solidity: "0.7.3",
defaultNetwork: "goerli",
networks: {
hardhat: {},
goerli: {
url: API_URL,
accounts: [`0x${PRIVATE_KEY}`],
},
},
etherscan: {
// EtherscanのAPI鍵
// https://etherscan.io/ で取得してください
apiKey: ETHERSCAN_API_KEY,
},
}
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-filesとcompletedの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
import React from "react"
import { useEffect, useState } from "react"
import {
helloWorldContract,
connectWallet,
updateMessage,
loadCurrentMessage,
getCurrentWalletConnected,
} from "./util/interact.js"
import alchemylogo from "./alchemylogo.svg"
次に、特定のイベントの後に更新する状態変数があります。
// 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つの関数(useEffect、addSmartContractListener、addWalletListener、connectWalletPressed、onUpdatePressed)があります。それぞれの機能について以下で説明します。
// HelloWorld.js
//一度だけ呼び出されます
useEffect(async () => {
//TODO: 実装する
}, [])
function addSmartContractListener() {
//TODO: 実装する
}
function addWalletListener() {
//TODO: 実装する
}
const connectWalletPressed = async () => {
//TODO: 実装する
}
const onUpdatePressed = async () => {
//TODO: 実装する
}
useEffect(opens in a new tab) - これはコンポーネントがレンダリングされた後に呼び出されるReactフックです。空の配列[]プロパティが渡されているため(4行目を参照)、コンポーネントの_最初_のレンダリング時にのみ呼び出されます。ここでは、スマート・コントラクトに保存されている現在のメッセージを読み込み、スマート・コントラクトとウォレットのリスナーを呼び出し、ウォレットがすでに接続されているかどうかを反映するようにUIを更新します。addSmartContractListener- この関数は、HelloWorldコントラクトのUpdatedMessagesイベントを監視し、スマート・コントラクト内のメッセージが変更されたときにUIを更新するリスナーを設定します。addWalletListener- この関数は、ユーザーがウォレットを切断したりアドレスを切り替えたりするなど、ユーザーのメタマスクウォレットの状態の変化を検出するリスナーを設定します。connectWalletPressed- この関数は、ユーザーのメタマスクウォレットをdappに接続するために呼び出されます。onUpdatePressed- この関数は、ユーザーがスマート・コントラクトに保存されているメッセージを更新したいときに呼び出されます。
このファイルの終わり近くに、コンポーネントのUIがあります。
// HelloWorld.js
//コンポーネントのUI
return (
<div id="container">
<img id="logo" src={alchemylogo}></img>
<button id="walletButton" onClick={connectWalletPressed}>
{walletAddress.length > 0 ? (
"Connected: " +
String(walletAddress).substring(0, 6) +
"..." +
String(walletAddress).substring(38)
) : (
<span>Connect Wallet</span>
)}
</button>
<h2 style={{ paddingTop: "50px" }}>Current Message:</h2>
<p>{message}</p>
<h2 style={{ paddingTop: "18px" }}>New Message:</h2>
<div>
<input
type="text"
placeholder="Update the message in your smart contract."
onChange={(e) => setNewMessage(e.target.value)}
value={newMessage}
/>
<p id="status">{status}</p>
<button id="publishButton" onClick={onUpdatePressed}>
Update
</button>
</div>
</div>
)
このコードを注意深く見ると、UIでさまざまな状態変数をどこで使用しているかがわかります。
- 6〜12行目では、ユーザーのウォレットが接続されている場合(つまり、
walletAddress.length > 0)、IDが「walletButton」のボタンにユーザーのwalletAddressの短縮版を表示します。そうでない場合は、単に「Connect Wallet」と表示します。 - 17行目では、スマート・コントラクトに保存されている現在のメッセージを表示します。これは
message文字列にキャプチャされています。 - 23〜26行目では、制御されたコンポーネント (opens in a new tab)を使用して、テキストフィールドの入力が変更されたときに
newMessage状態変数を更新します。
状態変数に加えて、IDがpublishButtonとwalletButtonのボタンがそれぞれクリックされたときに、connectWalletPressedとonUpdatePressed関数が呼び出されることもわかります。
最後に、この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というファイルが含まれていることに気づくでしょう。
// interact.js
//export const helloWorldContract;
export const loadCurrentMessage = async () => {}
export const connectWallet = async () => {}
const getCurrentWalletConnected = async () => {}
export const updateMessage = async (message) => {}
ファイルの上部で、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キーをインポートし、次にalchemyKeyをcreateAlchemyWeb3に渡して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行は次のようになります。
// interact.js
require("dotenv").config()
const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY
const { createAlchemyWeb3 } = require("@alch/alchemy-web3")
const web3 = createAlchemyWeb3(alchemyKey)
const contractABI = require("../contract-abi.json")
const contractAddress = "0x6f3f635A9762B47954229Ea479b4541eAF402A6A"
export const helloWorldContract = new web3.eth.Contract(
contractABI,
contractAddress
)
コントラクトが読み込まれたので、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.jsとinteract.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行目を参照)。
// HelloWorld.sol
// セマンティックバージョニングを使用して、Solidityのバージョンを指定します。
// 詳細: https://solidity.readthedocs.io/en/v0.5.10/layout-of-source-files.html#pragma
pragma solidity ^0.7.3;
// `HelloWorld`という名前のコントラクトを定義します。
// コントラクトは関数とデータ(その状態)の集合です。デプロイされると、コントラクトはイーサリアムブロックチェーン上の特定のアドレスに配置されます。詳細: https://solidity.readthedocs.io/en/v0.5.10/structure-of-a-contract.html
contract HelloWorld {
//update関数が呼び出されたときに発行されます
//スマート・コントラクトのイベントは、ブロックチェーン上で何かが発生したことをアプリのフロントエンドに伝えるための方法です。フロントエンドは特定のイベントを「リッスン」し、それらが発生したときにアクションを起こすことができます。
event UpdatedMessages(string oldStr, string newStr);
// `string`型の状態変数 `message` を宣言します。
// 状態変数は、その値がコントラクトのストレージに永続的に保存される変数です。`public`キーワードを使用すると、コントラクトの外部から変数にアクセスできるようになり、他のコントラクトやクライアントが値にアクセスするために呼び出すことができる関数が作成されます。
string public message;
// 多くのクラスベースのオブジェクト指向言語と同様に、コンストラクターはコントラクトの作成時にのみ実行される特別な関数です。
// コンストラクターはコントラクトのデータを初期化するために使用されます。詳細: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#constructors
constructor(string memory initMessage) {
// 文字列の引数 `initMessage` を受け取り、その値をコントラクトの `message` ストレージ変数に設定します)。
message = initMessage;
}
// 文字列の引数を受け取り、`message` ストレージ変数を更新するpublic関数です。
function update(string memory newMessage) public {
string memory oldMsg = message;
message = newMessage;
emit UpdatedMessages(oldMsg, newMessage);
}
}
スマート・コントラクトイベントは、ブロックチェーン上で何かが起こった(つまり、_イベント_があった)ことをフロントエンドアプリケーションに伝えるためのコントラクトの方法です。フロントエンドアプリケーションは特定のイベントを「リッスン」し、それらが発生したときにアクションを起こすことができます。
addSmartContractListener関数は、Hello Worldスマート・コントラクトのUpdatedMessagesイベントを特別にリッスンし、新しいメッセージを表示するようにUIを更新します。
addSmartContractListenerを次のように変更します。
// HelloWorld.js
function addSmartContractListener() {
helloWorldContract.events.UpdatedMessages({}, (error, data) => {
if (error) {
setStatus("😥 " + error.message)
} else {
setMessage(data.returnValues[1])
setNewMessage("")
setStatus("🎉 Your message has been updated!")
}
})
}
リスナーがイベントを検出したときに何が起こるかを分解してみましょう。
- イベントの発行時にエラーが発生した場合、
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を次のように変更しましょう。
// interact.js
export const connectWallet = async () => {
if (window.ethereum) {
try {
const addressArray = await window.ethereum.request({
method: "eth_requestAccounts",
})
const obj = {
status: "👆🏽 Write a message in the text-field above.",
address: addressArray[0],
}
return obj
} catch (err) {
return {
address: "",
status: "😥 " + err.message,
}
}
} else {
return {
address: "",
status: (
<span>
<p>
{" "}
🦊 <a target="_blank" href={`https://metamask.io/download`}>
You must install MetaMask, a virtual Ethereum wallet, in your
browser.
</a>
</p>
</span>
),
}
}
}
では、この巨大なコードブロックは正確に何をするのでしょうか?
まず、ブラウザで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.jsのconnectWalletPressed関数に移動し、次のように更新します。
// 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.jsとinteract.js)を保存し、これまでのUIをテストしてみましょう。
ブラウザでhttp://localhost:3000/ (opens in a new tab)ページを開き、右上の「Connect Wallet」ボタンを押します。
メタマスクがインストールされている場合は、ウォレットをdappに接続するように求められるはずです。接続の招待を承認します。
ウォレットボタンにアドレスが接続されていることが反映されるはずです!やったー 🔥
次に、ページを更新してみてください...これは奇妙です。すでに接続されているにもかかわらず、ウォレットボタンがメタマスクの接続を求めています...
しかし、恐れることはありません!getCurrentWalletConnectedを実装することで、アドレスがすでにdappに接続されているかどうかを確認し、それに応じてUIを更新することで、この問題には簡単に対処できます!
getCurrentWalletConnected関数
interact.jsファイルのgetCurrentWalletConnected関数を次のように更新します。
// interact.js
export const getCurrentWalletConnected = async () => {
if (window.ethereum) {
try {
const addressArray = await window.ethereum.request({
method: "eth_accounts",
})
if (addressArray.length > 0) {
return {
address: addressArray[0],
status: "👆🏽 Write a message in the text-field above.",
}
} else {
return {
address: "",
status: "🦊 Connect to MetaMask using the top right button.",
}
}
} catch (err) {
return {
address: "",
status: "😥 " + err.message,
}
}
} else {
return {
address: "",
status: (
<span>
<p>
{" "}
🦊 <a target="_blank" href={`https://metamask.io/download`}>
You must install MetaMask, a virtual Ethereum wallet, in your
browser.
</a>
</p>
</span>
),
}
}
}
このコードは、前のステップで記述したconnectWallet関数と_非常_によく似ています。
主な違いは、ユーザーがウォレットを接続するためにメタマスクを開くeth_requestAccountsメソッドを呼び出す代わりに、ここでは現在dappに接続されているメタマスクアドレスを含む配列を単に返すeth_accountsメソッドを呼び出すことです。
この関数の動作を確認するために、HelloWorld.jsコンポーネントのuseEffect関数で呼び出してみましょう。
// HelloWorld.js
useEffect(async () => {
const message = await loadCurrentMessage()
setMessage(message)
addSmartContractListener()
const { address, status } = await getCurrentWalletConnected()
setWallet(address)
setStatus(status)
}, [])
getCurrentWalletConnectedへの呼び出しの応答を使用して、walletAddressおよびstatus状態変数を更新していることに注意してください。
このコードを追加したので、ブラウザウィンドウを更新してみましょう。
素晴らしい!ボタンには接続されていることが表示され、更新した後でも接続されているウォレットのアドレスのプレビューが表示されるはずです!
addWalletListenerの実装
dappウォレット設定の最後のステップは、ユーザーが切断したりアカウントを切り替えたりするなど、ウォレットの状態が変化したときにUIが更新されるようにウォレットリスナーを実装することです。
HelloWorld.jsファイルで、addWalletListener関数を次のように変更します。
// HelloWorld.js
function addWalletListener() {
if (window.ethereum) {
window.ethereum.on("accountsChanged", (accounts) => {
if (accounts.length > 0) {
setWallet(accounts[0])
setStatus("👆🏽 Write a message in the text-field above.")
} else {
setWallet("")
setStatus("🦊 Connect to MetaMask using the top right button.")
}
})
} else {
setStatus(
<p>
{" "}
🦊 <a target="_blank" href={`https://metamask.io/download`}>
You must install MetaMask, a virtual Ethereum wallet, in your browser.
</a>
</p>
)
}
}
現時点では、ここで何が起こっているかを理解するのに私たちの助けは必要ないと思いますが、念のため、簡単に分解してみましょう。
- まず、関数は
window.ethereumが有効になっているか(つまり、メタマスクがインストールされているか)を確認します。- 有効になっていない場合は、
status状態変数を、ユーザーにメタマスクのインストールを促すJSX文字列に設定するだけです。 - 有効になっている場合は、3行目でメタマスクウォレットの状態の変化をリッスンするリスナー
window.ethereum.on("accountsChanged")を設定します。これには、ユーザーが追加のアカウントをdappに接続したとき、アカウントを切り替えたとき、またはアカウントを切断したときが含まれます。少なくとも1つのアカウントが接続されている場合、walletAddress状態変数は、リスナーによって返されるaccounts配列の最初のアカウントとして更新されます。それ以外の場合、walletAddressは空の文字列として設定されます。
- 有効になっていない場合は、
最後になりましたが、これをuseEffect関数で呼び出す必要があります。
// HelloWorld.js
useEffect(async () => {
const message = await loadCurrentMessage()
setMessage(message)
addSmartContractListener()
const { address, status } = await getCurrentWalletConnected()
setWallet(address)
setStatus(status)
addWalletListener()
}, [])
これで完了です!すべてのウォレット機能のプログラミングが正常に完了しました!それでは最後のタスク、スマート・コントラクトに保存されているメッセージの更新に進みましょう!
ステップ6: updateMessage関数の実装
さあ皆さん、いよいよ大詰めです!interact.jsファイルのupdateMessageで、以下のことを行います。
- スマート・コントラクトに公開したいメッセージが有効であることを確認する
- メタマスクを使用してトランザクションに署名する
HelloWorld.jsフロントエンドコンポーネントからこの関数を呼び出す
それほど時間はかかりません。このdappを完成させましょう!
入力エラー処理
当然のことながら、関数の開始時に何らかの入力エラー処理を行うことは理にかなっています。
メタマスク拡張機能がインストールされていない場合、ウォレットが接続されていない場合(つまり、渡されたaddressが空の文字列である場合)、またはmessageが空の文字列である場合は、関数が早期に返されるようにします。updateMessageに次のエラー処理を追加しましょう。
// interact.js
export const updateMessage = async (address, message) => {
if (!window.ethereum || address === null) {
return {
status:
"💡 Connect your MetaMask wallet to update the message on the blockchain.",
}
}
if (message.trim() === "") {
return {
status: "❌ Your message cannot be an empty string.",
}
}
}
適切な入力エラー処理が追加されたので、メタマスクを介してトランザクションに署名する時間です!
トランザクションへの署名
従来のWeb3イーサリアムトランザクションにすでに慣れている場合、次に記述するコードは非常に馴染みのあるものになります。入力エラー処理コードの下に、updateMessageに次を追加します。
// interact.js
//トランザクションのパラメーターを設定する
const transactionParameters = {
to: contractAddress, // コントラクトの公開時を除き必須です。
from: address, // ユーザーのアクティブなアドレスと一致する必要があります。
data: helloWorldContract.methods.update(message).encodeABI(),
}
//トランザクションに署名する
try {
const txHash = await window.ethereum.request({
method: "eth_sendTransaction",
params: [transactionParameters],
})
return {
status: (
<span>
✅{" "}
<a target="_blank" href={`https://goerli.etherscan.io/tx/${txHash}`}>
View the status of your transaction on Etherscan!
</a>
<br />
ℹ️ Once the transaction is verified by the network, the message will be
updated automatically.
</span>
),
}
} catch (error) {
return {
status: "😥 " + error.message,
}
}
何が起こっているかを分解してみましょう。まず、トランザクションパラメーターを設定します。ここで、
toは受信者アドレス(スマート・コントラクト)を指定しますfromはトランザクションの署名者、つまり関数に渡したaddress変数を指定しますdataには、Hello Worldスマート・コントラクトのupdateメソッドへの呼び出しが含まれており、入力としてmessage文字列変数を受け取ります
次に、await呼び出しwindow.ethereum.requestを行い、メタマスクにトランザクションへの署名を要求します。11行目と12行目で、ETHメソッドeth_sendTransactionを指定し、transactionParametersを渡していることに注意してください。
この時点で、ブラウザでメタマスクが開き、ユーザーにトランザクションへの署名または拒否を求めます。
- トランザクションが成功した場合、関数はJSONオブジェクトを返します。ここで、
statusJSX文字列は、トランザクションの詳細についてEtherscanを確認するようにユーザーに促します。 - トランザクションが失敗した場合、関数はJSONオブジェクトを返します。ここで、
status文字列はエラーメッセージを伝えます。
全体として、updateMessage関数は次のようになります。
// interact.js
export const updateMessage = async (address, message) => {
//入力エラーの処理
if (!window.ethereum || address === null) {
return {
status:
"💡 Connect your MetaMask wallet to update the message on the blockchain.",
}
}
if (message.trim() === "") {
return {
status: "❌ Your message cannot be an empty string.",
}
}
//トランザクションのパラメーターを設定する
const transactionParameters = {
to: contractAddress, // コントラクトの公開時を除き必須です。
from: address, // ユーザーのアクティブなアドレスと一致する必要があります。
data: helloWorldContract.methods.update(message).encodeABI(),
}
//トランザクションに署名する
try {
const txHash = await window.ethereum.request({
method: "eth_sendTransaction",
params: [transactionParameters],
})
return {
status: (
<span>
✅{" "}
<a target="_blank" href={`https://goerli.etherscan.io/tx/${txHash}`}>
View the status of your transaction on Etherscan!
</a>
<br />
ℹ️ Once the transaction is verified by the network, the message will
be updated automatically.
</span>
),
}
} catch (error) {
return {
status: "😥 " + error.message,
}
}
}
最後になりましたが、updateMessage関数をHelloWorld.jsコンポーネントに接続する必要があります。
updateMessageをHelloWorld.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)をタグ付けして、体験談やフィードバックをお知らせください!





