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

初心者向けのHello Worldスマートコントラクト - フルスタック

SolidityHardhatAlchemyスマートコントラクトデプロイブロックエクスプローラフロントエンドトランザクション
初級
nstrike2
2021年10月25日
70 分の読書 minute read

このガイドはブロックチェーンの開発の初心者で、どこから始めたらよいか分からなかったり、スマートコントラクトのデプロイやインタラクト方法について分からない方向けのものです。 これから一緒に、Goerliテストネットワーク上で簡単なスマートコントラクトを作成してデプロイする方法を順を追ってたどりましょう。その際、MetaMask(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 Discord(opens in a new tab)でお問い合わせください。

パート1: Hardhatを利用してスマートコントラクトを作りデプロイする

イーサリアムネットワークに接続する

イーサリアムチェーンにリクエストを行う方法はたくさんあります。 簡略化のため、ここではAlchemyの無料アカウントを使用します。このブロックチェーンのデベロッパープラットフォームとAPIにより、独自のノードを実行することなく、イーサリアムチェーンとの通信が可能になります。 Alchemyには、スマートコントラクトのデプロイメントにおいて内部で何が起こっているのかを把握するためにこのチュートリアルで利用する、監視と分析のためのデベロッパーツールも備わっています。

アプリのAPIキーの作成

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

Alchemyダッシュボード上にあるナビゲーションバーでAppsドロップダウンがあります。そこで、Create Appをクリックします。

Hello WorldのCreate App

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

Hello WorldのCreate App画面

注意: 必ずGoerliを選択してください。そうしないと、このチュートリアルどおり行きません。

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

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

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

Metamaskのアカウントはこちら(opens in a new tab)から無料でダウンロード、作成できます。 アカウントを作成後、またはすでにアカウントをお持ちの場合は(実際に支払いが発生しないように)右上の「Goerli Test Network」に切り替えてください。

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

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

注意: ネットワークの混雑状況によっては、時間がかかる場合があります。

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

あなたのウォレットにETHがあることをダブルチェックし、eth_getBalance(opens in a new tab)リクエストをAlchemyのコンポーザーツール(opens in a new tab)を使って出してみましょう。 リクエストすると、ウォレット内のETHの量が返却されます。 詳細については、Alchemyの短いチュートリアルにあるコンポーザーツールの使用方法(opens in a new tab)をご覧ください。

MetaMaskアカウントのアドレスを入力し、Send Requestをクリックします。 以下のコードスニペットのようなレスポンスが来ます。

1{ "jsonrpc": "2.0", "id": 0, "result": "0x2B5E3AF16B1880000" }
コピー

注意: この結果の単位はweiであり、ETHではありません。 weiはETHの最小単位として使われています。

ご安心ください。 私たちの偽物のお金はすべてそこにあります。

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

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

1mkdir hello-world
2cd hello-world

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

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

このチュートリアルでは、初期化における質問にどのように答えるかには重点を置いていません。 参考までに、私たちは次のように行いました。

1package name: (hello-world)
2version: (1.0.0)
3description: hello world smart contract
4entry point: (index.js)
5test command:
6git repository:
7keywords:
8author:
9license: (ISC)
10
11About to write to /Users/.../.../.../hello-world/package.json:
12
13{
14 "name": "hello-world",
15 "version": "1.0.0",
16 "description": "hello world smart contract",
17 "main": "index.js",
18 "scripts": {
19 "test": "echo \"Error: no test specified\" && exit 1"
20 },
21 "author": "",
22 "license": "ISC"
23}
すべて表示

package.jsonを承認すれば完了です。

ステップ7: Hardhatのダウンロード

Hardhatは、イーサリアムのソフトウェアをコンパイル、デプロイ、テスト、デバッグするための開発環境です。 デベロッパーがライブチェーンにデプロイする前に、スマートコントラクトや分散型アプリケーション(Dapp)をローカルに構築する際に役立ちます。

先ほど作成したhello-worldプロジェクト内で、以下を実行します。

1npm install --save-dev hardhat

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

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

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

1npx hardhat

ウェルカムメッセージと、次に何をするのかを選択できるオプションが表示されます。 「Create an empty hardhat.config.js」を選択します。

1888 888 888 888 888
2888 888 888 888 888
3888 888 888 888 888
48888888888 8888b. 888d888 .d88888 88888b. 8888b. 888888
5888 888 "88b 888P" d88" 888 888 "88b "88b 888
6888 888 .d888888 888 888 888 888 888 .d888888 888
7888 888 888 888 888 Y88b 888 888 888 888 888 Y88b.
8888 888 "Y888888 888 "Y88888 888 888 "Y888888 "Y888
9
10👷 Welcome to Hardhat v2.0.11 👷‍
11
12What do you want to do? …
13Create a sample project
14❯ Create an empty hardhat.config.js
15Quit
すべて表示

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

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

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

1mkdir contracts
2mkdir scripts
  • contracts/は、Hello Worldスマートコントラクトのコードファイルを格納する場所です。
  • scripts/は、コントラクトをデプロイして対話するスクリプトを保持する場所です。

ステップ10: コントラクトを作成する

一体いつになったらコードを書くのだろうと疑問をお持ちではないでしょうか 。 まさに、その時です!

あなたのお気に入りのエディターでhello-worldプロジェクトを開きます。 スマートコントラクトは、最も一般的にはSolidityで書かれています。そのため、Solidityでスマートコントラクトを作成します。

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

注意: 必ずコメントを読み、このコントラクトの処理内容を理解してください。

1// Specifies the version of Solidity, using semantic versioning.
2// Learn more: https://solidity.readthedocs.io/en/v0.5.10/layout-of-source-files.html#pragma
3pragma solidity >=0.7.3;
4
5// Defines a contract named `HelloWorld`.
6// A contract is a collection of functions and data (its state). Once deployed, a contract resides at a specific address on the Ethereum blockchain. Learn more: https://solidity.readthedocs.io/en/v0.5.10/structure-of-a-contract.html
7contract HelloWorld {
8
9 //Emitted when update function is called
10 //Smart contract events are a way for your contract to communicate that something happened on the blockchain to your app front-end, which can be 'listening' for certain events and take action when they happen.
11 event UpdatedMessages(string oldStr, string newStr);
12
13 // Declares a state variable `message` of type `string`.
14 // State variables are variables whose values are permanently stored in contract storage. The keyword `public` makes variables accessible from outside a contract and creates a function that other contracts or clients can call to access the value.
15 string public message;
16
17 // Similar to many class-based object-oriented languages, a constructor is a special function that is only executed upon contract creation.
18 // Constructors are used to initialize the contract's data. Learn more:https://solidity.readthedocs.io/en/v0.5.10/contracts.html#constructors
19 constructor(string memory initMessage) {
20
21 // Accepts a string argument `initMessage` and sets the value into the contract's `message` storage variable).
22 message = initMessage;
23 }
24
25 // A public function that accepts a string argument and updates the `message` storage variable.
26 function update(string memory newMessage) public {
27 string memory oldMsg = message;
28 message = newMessage;
29 emit UpdatedMessages(oldMsg, newMessage);
30 }
31}
すべて表示

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

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

ここまでで、MetaMaskウォレットとAlchemyアカウントを作成し、スマートコントラクトも作成しました。次はこの3つを接続しましょう。

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

トランザクションの送信の詳細については、こちらのチュートリアル(opens in a new tab)のweb3使ったトランザクションの送信に関する内容をご覧ください。

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

1npm install dotenv --save

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

環境ファイルの名前は、必ず.envにしてください。そうしないと環境ファイルとして認識されません。

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

  • 秘密鍵をエクスポートするには、こちらの手順(opens in a new tab)に従ってください。
  • HTTP Alchemy APIのURLを取得するには、以下を参照してください。

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

1API_URL = "https://eth-goerli.alchemyapi.io/v2/your-api-key"
2PRIVATE_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を以下のように更新します。

1/**
2 * @type import('hardhat/config').HardhatUserConfig
3 */
4
5require("dotenv").config()
6require("@nomiclabs/hardhat-ethers")
7
8const { API_URL, PRIVATE_KEY } = process.env
9
10module.exports = {
11 solidity: "0.7.3",
12 defaultNetwork: "goerli",
13 networks: {
14 hardhat: {},
15 goerli: {
16 url: API_URL,
17 accounts: [`0x${PRIVATE_KEY}`],
18 },
19 },
20}
すべて表示

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

ここまででしっかりと動作していることを確認するため、コントラクトをコンパイルしてみましょう。 compileタスクは、組み込みのHardhatタスクの1つです。

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

npx hardhat compile

SPDX license identifier not provided in source fileという警告が表示される場合がありますが、心配する必要はありません。警告が表示されないのがベストですが、 表示された場合は、いつでもAlchemy discord(opens in a new tab)でメッセージを送信できます。

ステップ15: デプロイスクリプトを書く

コントラクトの作成と設定ファイルの作成が完了したら、いよいよコントラクトのデプロイのためのスクリプトを作成します。

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

1async function main() {
2 const HelloWorld = await ethers.getContractFactory("HelloWorld")
3
4 // Start deployment, returning a promise that resolves to a contract object
5 const hello_world = await HelloWorld.deploy("Hello World!")
6 console.log("Contract deployed to address:", hello_world.address)
7}
8
9main()
10 .then(() => process.exit(0))
11 .catch((error) => {
12 console.error(error)
13 process.exit(1)
14 })
すべて表示

Hardhatがコードの各行で行っている驚くべき内容については、Hardhatのコントラクトチュートリアル(opens in a new tab)で説明されています。以下では、その説明を採用しています。

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

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

1const hello_world = await HelloWorld.deploy()

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

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

ようやく、スマートコントラクトをデプロイする準備が整いました。 コマンドラインに移動し、以下を実行します。

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

次のような画面が表示されるはずです。

Contract deployed to address: 0x6cd7d44516a20882cEa2DE9f205bF401c0d23570

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

Goerli etherscan(opens in a new tab)に移動し、コントラクトアドレスを検索すると、コントラクトが正常にデプロイされていることを確認できるはずです。 トランザクションは以下のようなものになります。

FromアドレスはMetaMaskアカウントのアドレスと一致し、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)です。これは、Goerliチェーンにコントラクトを書き込むリクエストです。次にeth_getTransactionByHash(opens in a new tab)は、ハッシュを指定してトランザクションに関する情報を読み取るリクエストです。 トランザクションの送信の詳細については、こちらのチュートリアルにあるWeb3を使用したトランザクションの送信をご覧ください。

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

スマートコントラクトをGoerliネットワークに正常にデプロイできました。それでは、スマートコントラクトとやり取りする方法について学びましょう。

interact.jsファイルの作成

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

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

1// interact.js
2
3const API_KEY = process.env.API_KEY
4const PRIVATE_KEY = process.env.PRIVATE_KEY
5const 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ファイルに次のコードを追加して、コンテンツをパースする必要があります。

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

ABIを表示したい場合は、次のコードを追加することでコンソールに出力できます:

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

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

npx hardhat run scripts/interact.js

コントラクトのインスタンスを作成する

コントラクトを操作するには、コード内にコントラクトのインスタンスを作成する必要があります。 Ethers.jsでこれを行うには、次の3つのコンセプトを機能させる必要があります。

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

前の手順で取得したコントラクABIを使って、コントラクトのインスタンスを作成します。

1// interact.js
2
3// Provider
4const alchemyProvider = new ethers.providers.AlchemyProvider(
5 (network = "goerli"),
6 API_KEY
7)
8
9// Signer
10const signer = new ethers.Wallet(PRIVATE_KEY, alchemyProvider)
11
12// Contract
13const helloWorldContract = new ethers.Contract(
14 CONTRACT_ADDRESS,
15 contract.abi,
16 signer
17)
すべて表示

Provider、Signer、Contractの詳細については、ethers.jsドキュメント(opens in a new tab)をご覧ください。

initメッセージの読み取り

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

JavaScriptでは、ネットワークとのやり取りで非同期関数を使います。 非同期関数の詳細については、この記事の中ほど(opens in a new tab)をご覧ください。

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

1// interact.js
2
3// ...
4
5async function main() {
6 const message = await helloWorldContract.message()
7 console.log("The message is: " + message)
8}
9main()
すべて表示

ターミナルでnpx hardware run scripts/interact.jsを入力してファイルを実行すると、次のレスポンスが表示されるはずです。

1The message is: Hello world!

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

メッセージの更新

メッセージを読み取るだけでなく、update関数を使ってスマートコントラクトに保存されたメッセージを更新することもできます。 かなりイケてますよね?

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

1// interact.js
2
3// ...
4
5async function main() {
6 const message = await helloWorldContract.message()
7 console.log("The message is: " + message)
8
9 console.log("Updating the message...")
10 const tx = await helloWorldContract.update("This is the new message.")
11 await tx.wait()
12}
13main()
すべて表示

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

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

前の手順を繰り返して、更新されたmessageの値を読み取ることができるのに違いありません。 その新しい値を出力するために必要となる変更を、少し考えてみましょう!

ヒントが必要ですか?この時点で、あなたのinteract.jsファイルは次のようになるはずです。

1// interact.js
2
3const API_KEY = process.env.API_KEY
4const PRIVATE_KEY = process.env.PRIVATE_KEY
5const CONTRACT_ADDRESS = process.env.CONTRACT_ADDRESS
6
7const contract = require("../artifacts/contracts/HelloWorld.sol/HelloWorld.json")
8
9// provider - Alchemy
10const alchemyProvider = new ethers.providers.AlchemyProvider(
11 (network = "goerli"),
12 API_KEY
13)
14
15// signer - you
16const signer = new ethers.Wallet(PRIVATE_KEY, alchemyProvider)
17
18// contract instance
19const helloWorldContract = new ethers.Contract(
20 CONTRACT_ADDRESS,
21 contract.abi,
22 signer
23)
24
25async function main() {
26 const message = await helloWorldContract.message()
27 console.log("The message is: " + message)
28
29 console.log("Updating the message...")
30 const tx = await helloWorldContract.update("this is the new message")
31 await tx.wait()
32
33 const newMessage = await helloWorldContract.message()
34 console.log("The new message is: " + newMessage)
35}
36
37main()
すべて表示

このスクリプトを実行するだけで、古いメッセージ、更新ステータス、および新しいメッセージがコンソールに出力されるのを確認できるはずです。

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

1The message is: Hello World!
2Updating the message...
3The new message is: This is the new message.

このスクリプトの実行中、新しいメッセージが読み込まれる前に、 Updating the message...のステップの読み込みにしばらく時間がかかることに気づくかもしれません。 これはマイニングプロセスによるものです。マイニング中のトランザクションの追跡に興味があるならば、Alchemy mempool(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」ボタンを押します。

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

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

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

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

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

hardhat-etherscanのインストール

あなたのコントラクトをEtherescanへ公開するのは、Hardhatを使って簡単にできます。 はじめに、まずhardhat-etherscanプラグインをインストールしてください。 hardhat-etherscanは、スマートコントラクトのソースコードとEtherscan上のABIを自動的に検証します。 インストールするには、hello-worldディレクトリで次のコマンドを実行します。

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

インストールをしたら、hardhat.config.jsの先頭に次のステートメントを含んだEtherscan構成オプションを追加します。

1// hardhat.config.js
2
3require("dotenv").config()
4require("@nomiclabs/hardhat-ethers")
5require("@nomiclabs/hardhat-etherscan")
6
7const { API_URL, PRIVATE_KEY, ETHERSCAN_API_KEY } = process.env
8
9module.exports = {
10 solidity: "0.7.3",
11 defaultNetwork: "goerli",
12 networks: {
13 hardhat: {},
14 goerli: {
15 url: API_URL,
16 accounts: [`0x${PRIVATE_KEY}`],
17 },
18 },
19 etherscan: {
20 // Your API key for Etherscan
21 // Obtain one at https://etherscan.io/
22 apiKey: ETHERSCAN_API_KEY,
23 },
24}
すべて表示

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

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

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

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

DEPLOYED_CONTRACT_ADDRESSがGoerliテストネットワーク上にデプロイされたスマートコントラクトのアドレスであることを確認してください。 また、最後の引数 ('Hello World!') は、 パート1のデプロイ手順で使われたの文字列値と同じでなければなりません。

順調に行けば、コンソールに次のメッセージが表示されます。

1Successfully submitted source code for contract
2contracts/HelloWorld.sol:HelloWorld at 0xdeployed-contract-address
3for verification on Etherscan. Waiting for verification result...
4
5
6Successfully verified contract HelloWorld on Etherscan.
7https://goerli.etherscan.io/address/<contract-address>#contracts

おめでとうございます! これで、あなたのスマートコントラクトコードは、Etherscan上にあります。

Etherscanであなたのスマートコントラクトを確認する

コンソールに表示されているリンクに移動すると、Etherscanで公開されているスマートコントラクトコードとABIが表示されます。

ヤッホー!栄冠を手にしました。 これで、誰でもスマートコントラクトを呼び出したり、書き込んだりできるようになりました。 次にあなたが何を構築するか楽しみにしています。

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

このチュートリアルを終えると、次の方法がわかるようになります。

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

この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コンポーネントと JavaScriptファイルであるutil/interact.jsを編集して、プロジェクトに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のロゴがあります

1// HelloWorld.js
2
3import React from "react"
4import { useEffect, useState } from "react"
5import {
6 helloWorldContract,
7 connectWallet,
8 updateMessage,
9 loadCurrentMessage,
10 getCurrentWalletConnected,
11} from "./util/interact.js"
12
13import alchemylogo from "./alchemylogo.svg"
すべて表示

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

1// HelloWorld.js
2
3//State variables
4const [walletAddress, setWallet] = useState("")
5const [status, setStatus] = useState("")
6const [message, setMessage] = useState("No connection to the network.")
7const [newMessage, setNewMessage] = useState("")

それぞれの変数は以下の用途で使われます。

  • walletAddress - ユーザーのウォレットアドレスを格納する文字列
  • status - ユーザーにdappの操作方法を案内する補助メッセージを文字列として格納する
  • message - スマートコントラクトの現在のメッセージを格納する文字列
  • newMessage - スマートコントラクトに書き込まれる新しいメッセージを格納する文字列

ステート変数の後に、useEffectaddSmartContractListeneraddWalletListenerconnectWalletPressedonUpdatePressedの未実装の5つの関数があります。 次に、それらが何をするのかを説明します。

1// HelloWorld.js
2
3//called only once
4useEffect(async () => {
5 //TODO: implement
6}, [])
7
8function addSmartContractListener() {
9 //TODO: implement
10}
11
12function addWalletListener() {
13 //TODO: implement
14}
15
16const connectWalletPressed = async () => {
17 //TODO: implement
18}
19
20const onUpdatePressed = async () => {
21 //TODO: implement
22}
すべて表示
  • useEffect(opens in a new tab)- コンポーネントがレンダリングされた後に呼び出されるReactフックです。 空の配列[]のプロップが渡されているため (4行目を参照)、コンポーネントの最初のレンダリングでのみ呼び出されます。 ここでは、スマートコントラクトに保存されている現在のメッセージのロード、スマートコントラクトとウォレットリスナーの呼び出し、ウォレットが既に接続されているかどうかを反映してUIを更新するのに使います。
  • addSmartContractListener - この関数では、HelloWorldコントラクトのUpdatedMessagesイベントを監視し、スマートコントラクトでメッセージが変更されたときにUIを更新するリスナーを設定します。
  • addWalletListener - この関数では、ユーザーがウォレットを切断したときやアドレスを切り替えたときなど、ユーザーのMetaMaskウォレットのステートの変化を検出するリスナーを設定します。
  • connectWalletPressed - この関数は、ユーザーのMetaMaskウォレットをdappに接続するのに呼び出されます。
  • onUpdatePressed - この関数は、ユーザーがスマートコントラクトに保存されているメッセージを更新したいときに呼び出されます。

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

1// HelloWorld.js
2
3//the UI of our component
4return (
5 <div id="container">
6 <img id="logo" src={alchemylogo}></img>
7 <button id="walletButton" onClick={connectWalletPressed}>
8 {walletAddress.length > 0 ? (
9 "Connected: " +
10 String(walletAddress).substring(0, 6) +
11 "..." +
12 String(walletAddress).substring(38)
13 ) : (
14 <span>Connect Wallet</span>
15 )}
16 </button>
17
18 <h2 style={{ paddingTop: "50px" }}>Current Message:</h2>
19 <p>{message}</p>
20
21 <h2 style={{ paddingTop: "18px" }}>New Message:</h2>
22
23 <div>
24 <input
25 type="text"
26 placeholder="Update the message in your smart contract."
27 onChange={(e) => setNewMessage(e.target.value)}
28 value={newMessage}
29 />
30 <p id="status">{status}</p>
31
32 <button id="publishButton" onClick={onUpdatePressed}>
33 Update
34 </button>
35 </div>
36 </div>
37)
すべて表示

このコードを注意深く読むと、さまざまなステート変数がUIのどの場所で使用されているかがわかります。

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

ステート変数に加えて、IDがpublishButtonwalletButtonである、それぞれがクリックされるとconnectWalletPressedおよびonUpdatePressed関数が呼び出されることがわります。

最後に、このHelloWorld.jsコンポーネントがどこに加えられるかについて説明します。

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

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

interact.jsファイル

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

👆🏽まさにこれがinteract.jsファイルの目的です。

srcディレクトリのutilフォルダに移動すると、interact.jsというファイルが含まれていることがわかります。これには、スマートコントラクトとのやり取り、ウォレット関数と変数が含まれています。

1// interact.js
2
3//export const helloWorldContract;
4
5export const loadCurrentMessage = async () => {}
6
7export const connectWallet = async () => {}
8
9const getCurrentWalletConnected = async () => {}
10
11export const updateMessage = async (message) => {}
すべて表示

ファイルの先頭で、helloWorldContractオブジェクトがコメントアウトされていることがわかります。 このチュートリアルの後半で、このオブジェクトのコメントを外し、この変数でスマートコントラクトをインスタンス化し、それをHelloWorld.jsコンポーネントにエクスポートします。

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

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

何をするか理解したので、スマートコントラクトから読み取る方法を解明していきましょう。

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

スマートコントラクトから読み取るには、次の設定を正しく行う必要があります。

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

手順がたくさんあるように感じますが、心配しないでください! それぞれの方法を1つずつ説明していきます。 :)

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

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

もしなければ、最初に、 starter-filesのルートディレクトリへ移動して、次のコマンドをコンソールで実行してAlchemy Web3(opens in a new tab)をインストールしてください。

1npm 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キーを取得した後に安全な場所に保管できるようになります。

1npm install dotenv --save

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

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

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

これで、私たちのdappにAlchemy Web3エンドポイントを設定する準備が整いました。 utilフォルダー内に入っているinteract.jsに戻り、ファイルの先頭に次のコードを加えてください。

1// interact.js
2
3require("dotenv").config()
4const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY
5const { createAlchemyWeb3 } = require("@alch/alchemy-web3")
6const web3 = createAlchemyWeb3(alchemyKey)
7
8//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をコピーしたら、それをcontract-abi.jsonという名前のJSONファイルとしてsrcディレクトリに保存しましょう。

contract-abi.jsonは、srcフォルダーに格納されている必要があります。

コントラクトアドレス、ABI、Alchemy Web3エンドポイントを用意することで、コントラクトメソッド(opens in a new tab)を使ってスマートコントラクトのインスタンスをロードすることができます。 コントラクトABIをinteract.jsファイルにインポートし、コントラクトアドレスを加えます。

1// interact.js
2
3const contractABI = require("../contract-abi.json")
4const contractAddress = "0x6f3f635A9762B47954229Ea479b4541eAF402A6A"

ついに、helloWorldContract変数のコメントを外し、AlchemyWeb3エンドポイントを使用してスマートコントラクトをロードできるようになりました。

1// interact.js
2export const helloWorldContract = new web3.eth.Contract(
3 contractABI,
4 contractAddress
5)

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

1// interact.js
2
3require("dotenv").config()
4const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY
5const { createAlchemyWeb3 } = require("@alch/alchemy-web3")
6const web3 = createAlchemyWeb3(alchemyKey)
7
8const contractABI = require("../contract-abi.json")
9const contractAddress = "0x6f3f635A9762B47954229Ea479b4541eAF402A6A"
10
11export const helloWorldContract = new web3.eth.Contract(
12 contractABI,
13 contractAddress
14)
すべて表示

私たちのコントラクトがロードされたので、loadCurrentMessage関数を実装できます!

interact.jsファイルにloadCurrentMessageを実装する

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

interact.jsファイルの loadCurrentMessageを次のように更新してください。

1// interact.js
2
3export const loadCurrentMessage = async () => {
4 const message = await helloWorldContract.methods.message().call()
5 return message
6}

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

1// HelloWorld.js
2
3//called only once
4useEffect(async () => {
5 const message = await loadCurrentMessage()
6 setMessage(message)
7}, [])

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

リスナーについて詳しく説明する前に、これまでの内容を確認してみましょう! HelloWorld.jsファイルとinteract.jsファイルを保存し、http://localhost: 3000/(opens in a new tab)へアクセスしてください。

現在、「ネットワークに接続されていません」というメッセージが表示されなくなっていることがわかります。 代わりに、スマート コントラクトに保存されているメッセージが反映されます。 カッコイイ!

今や、UIにスマートコントラクトに保存されたメッセージが反映されるようになりました。

それでは、リスナーについて説明していきます。

addSmartContractListenerを実装する

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

1// HelloWorld.sol
2
3// Specifies the version of Solidity, using semantic versioning.
4// Learn more: https://solidity.readthedocs.io/en/v0.5.10/layout-of-source-files.html#pragma
5pragma solidity ^0.7.3;
6
7// Defines a contract named `HelloWorld`.
8// A contract is a collection of functions and data (its state). Once deployed, a contract resides at a specific address on the Ethereum blockchain. Learn more: https://solidity.readthedocs.io/en/v0.5.10/structure-of-a-contract.html
9contract HelloWorld {
10
11 //Emitted when update function is called
12 //Smart contract events are a way for your contract to communicate that something happened on the blockchain to your app front-end, which can be 'listening' for certain events and take action when they happen.
13 event UpdatedMessages(string oldStr, string newStr);
14
15 // Declares a state variable `message` of type `string`.
16 // State variables are variables whose values are permanently stored in contract storage. The keyword `public` makes variables accessible from outside a contract and creates a function that other contracts or clients can call to access the value.
17 string public message;
18
19 // Similar to many class-based object-oriented languages, a constructor is a special function that is only executed upon contract creation.
20 // Constructors are used to initialize the contract's data. Learn more:https://solidity.readthedocs.io/en/v0.5.10/contracts.html#constructors
21 constructor(string memory initMessage) {
22
23 // Accepts a string argument `initMessage` and sets the value into the contract's `message` storage variable).
24 message = initMessage;
25 }
26
27 // A public function that accepts a string argument and updates the `message` storage variable.
28 function update(string memory newMessage) public {
29 string memory oldMsg = message;
30 message = newMessage;
31 emit UpdatedMessages(oldMsg, newMessage);
32 }
33}
すべて表示

スマートコントラクトイベントは、ブロックチェーンで何かが起こったこと (すなわち、イベントの発生 ) をフロントエンドアプリケーションに伝える方法です。特定のイベントを「リスニング」して、それが起きた時にアクションを実行します。

具体的には、addSmartContractListener関数がHello WorldスマートコントラクトのUpdatedMessagesイベントをリッスンしており、新しいメッセージを表示するようにUIの更新をします。

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

1// HelloWorld.js
2
3function addSmartContractListener() {
4 helloWorldContract.events.UpdatedMessages({}, (error, data) => {
5 if (error) {
6 setStatus("😥 " + error.message)
7 } else {
8 setMessage(data.returnValues[1])
9 setNewMessage("")
10 setStatus("🎉 Your message has been updated!")
11 }
12 })
13}
すべて表示

リスナーがイベントを検出したときに何が起こるかを詳しく解説します。

  • イベントの発行時にエラーが発生した場合、そのエラーはstatusステート変数を介してUIに反映する。
  • それ以外の場合は、返されたdataオブジェクトを使う。 data.returnValuesは、配列にある最初のエレメントがインデックスの0に格納されている配列です。配列の最初のエレメントには前のメッセージが格納され、2番目のエレメントには更新されたメッセージが格納されます。 つまり、イベントが成功すると、message文字列を更新されたメッセージに設定し、newMessage文字列をクリアし、statusステート変数を更新します。 これにより、新しいメッセージがスマートコントラクトに公開されたことを反映しています。

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

1// HelloWorld.js
2
3useEffect(async () => {
4 const message = await loadCurrentMessage()
5 setMessage(message)
6 addSmartContractListener()
7}, [])

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

それでは、次にイーサリアムウォレット (MetaMask) を設定し、それをdappに接続することに取り組んでいきましょう!

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

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

イーサリアムのトランザクションの仕組みの詳細については、イーサリアム・ファウンデーションのこちらのページをご覧ください。

MetaMaskをダウンロード

Metamaskのアカウントはこちら(opens in a new tab)から無料でダウンロード、作成できます。 アカウントを作成後、またはすでにアカウントをお持ちの場合は( 実際に支払いが発生しないように )右上の「Goerli Test Network」に切り替えてください。

フォーセットからイーサ(ETH)を追加

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

残高の確認

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

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

注: この結果の単位は、ETHではなくweiです。 weiはETHの最小単位として使われています。 weiからETHへ変換すると、1 eth = 10¹⁸ weiになります。 つまり、0xde0b6b3a7640000を10進数に変換すると、1*10¹⁸となり、1 ETHに相当します。

ご安心ください。 これで、偽のお金を手に入れました。 🤑

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

MetaMaskウォレットが設定されたので、分散型アプリケーション(Dapp)を接続しましょう。

connectWallet関数

interact.jsファイルのconnectWallet関数を実装します。この関数は、HelloWorld.jsコンポーネントで呼び出します。

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

1// interact.js
2
3export const connectWallet = async () => {
4 if (window.ethereum) {
5 try {
6 const addressArray = await window.ethereum.request({
7 method: "eth_requestAccounts",
8 })
9 const obj = {
10 status: "👆🏽 Write a message in the text-field above.",
11 address: addressArray[0],
12 }
13 return obj
14 } catch (err) {
15 return {
16 address: "",
17 status: "😥 " + err.message,
18 }
19 }
20 } else {
21 return {
22 address: "",
23 status: (
24 <span>
25 <p>
26 {" "}
27 🦊 <a target="_blank" href={`https://metamask.io/download.html`}>
28 You must install MetaMask, a virtual Ethereum wallet, in your
29 browser.
30 </a>
31 </p>
32 </span>
33 ),
34 }
35 }
36}
すべて表示

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

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

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

window.ethereum存在しない場合は、MeTaMaskがインストールされていないことを意味します。 その結果、空の文字列に設定された、返されるaddressと、ユーザーがMetaMaskをインストールする必要があることを伝えるstatusJSXオブジェクトが入ったJSONオブジェクトが返されます。

window.ethereum存在する場合、興味深いことが起こります。

try/catch ループを使用して、window.ethereum.request({ method: "eth_requestAccounts" });(opens in a new tab)を呼び出すことでMetaMaskに接続しようとしています。 この関数を呼び出すと、ブラウザでMetaMaskが開き、ユーザーはウォレットを分散型アプリケーション(Dapp)に接続するように求められます。

  • ユーザーが接続を選んだ場合、method: "eth_requestAccounts"は、分散型アプリケーション(Dapp)に接続されているすべてのユーザーのアカウントアドレスを含む配列を返します。 connectWallet関数は、配列内の最初のaddress(9 行目参照)、ユーザーにスマートコントラクトにメッセージを書き込むように促すstatusメッセージが入ったJSONオブジェクトを返します。
  • ユーザーが接続を拒否した場合、JSONオブジェクトには、返されるaddressに入る空の文字列と、ユーザーが接続を拒否したことを示すstatusメッセージが入ることになります。

これで、connectWallet関数を作成できたので、次のステップでは、この関数をHelloWorld.jsコンポーネントに呼び出します。

connect Wallet関数をHello World.jsUIコンポーネントに加える

HelloWorld.jsにある connectWalletPressed関数に移動し、次のように更新します。

1// HelloWorld.js
2
3const connectWalletPressed = async () => {
4 const walletResponse = await connectWallet()
5 setStatus(walletResponse.status)
6 setWallet(walletResponse.address)
7}

interact.jsファイルによって、機能の大部分がHelloWorld.jsコンポーネントからどのように抽象化されているかに注目してください。 これは、モデルビューコントローラ(M-V-C)パラダイムに準拠しているためです。

connectWalletPressedでは、単にインポートされたconnectWallet関数のawait呼び出しを行っています。さらに、そのレスポンスを使用し、statuswalletAddress変数を状態フックを介して更新しています。

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

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

MetaMaskがインストールされている場合は、ウォレットを分散型アプリケーション(Dapp)に接続するように求められます。 接続リクエストを承認します。

ウォレットボタンに、接続した自分のアドレスが表示されているはずです。 やりましたね🔥

次に、ページを更新してみてください。変ですね。 ウォレットボタンによって、すでに接続しているにもかかわらずMetaMaskに接続するよう求められます。

しかし、恐れるに足りません。 getCurrentWalletConnectedを実装することで、簡単にこれを修正できます。この関数は、アドレスが分散型アプリケーション(Dapp) にすでに接続されているかどうかを確認し、それに応じてUIを更新します。

getCurrentWalletConnected関数

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

1// interact.js
2
3export const getCurrentWalletConnected = async () => {
4 if (window.ethereum) {
5 try {
6 const addressArray = await window.ethereum.request({
7 method: "eth_accounts",
8 })
9 if (addressArray.length > 0) {
10 return {
11 address: addressArray[0],
12 status: "👆🏽 Write a message in the text-field above.",
13 }
14 } else {
15 return {
16 address: "",
17 status: "🦊 Connect to MetaMask using the top right button.",
18 }
19 }
20 } catch (err) {
21 return {
22 address: "",
23 status: "😥 " + err.message,
24 }
25 }
26 } else {
27 return {
28 address: "",
29 status: (
30 <span>
31 <p>
32 {" "}
33 🦊 <a target="_blank" href={`https://metamask.io/download.html`}>
34 You must install MetaMask, a virtual Ethereum wallet, in your
35 browser.
36 </a>
37 </p>
38 </span>
39 ),
40 }
41 }
42}
すべて表示

このコードは、前述のconnectWallet関数に非常に似ています。

主な違いとしては、ユーザーがウォレットに接続するためにMetaMaskを開くeth_requestAccountsメソッドを呼び出す代わりに、 ここではeth_accountsメソッドを呼び出しています。これは、現在、分散型アプリケーション(Dapp)に接続されているMetaMaskのアドレスを含む配列を単に返すだけです。

この関数を動作させるため、HelloWorld.jsコンポーネントのuseEffect関数で呼び出しましょう。

1// HelloWorld.js
2
3useEffect(async () => {
4 const message = await loadCurrentMessage()
5 setMessage(message)
6 addSmartContractListener()
7
8 const { address, status } = await getCurrentWalletConnected()
9 setWallet(address)
10 setStatus(status)
11}, [])
すべて表示

walletAddress状態変数とstatus状態変数を更新するのに、呼び出したgetCurrentWalletConnectedのレスポンスを使用していることに注目してください。

このコードを加えたら、ブラウザウィンドウを更新してみてください。

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

addWalletListenerの実装

分散型アプリケーション(Dapp)ウォレットの設定の最終ステップは、ウォレットリスナーを実装することです。これにより、ユーザーが接続を切断したり、アカウントを切り替えたりした場合など、ウォレットの状態が変更されたときにUIが更新されます。

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

1// HelloWorld.js
2
3function addWalletListener() {
4 if (window.ethereum) {
5 window.ethereum.on("accountsChanged", (accounts) => {
6 if (accounts.length > 0) {
7 setWallet(accounts[0])
8 setStatus("👆🏽 Write a message in the text-field above.")
9 } else {
10 setWallet("")
11 setStatus("🦊 Connect to MetaMask using the top right button.")
12 }
13 })
14 } else {
15 setStatus(
16 <p>
17 {" "}
18 🦊 <a target="_blank" href={`https://metamask.io/download.html`}>
19 You must install MetaMask, a virtual Ethereum wallet, in your browser.
20 </a>
21 </p>
22 )
23 }
24}
すべて表示

この時点で何が起こっているかを理解するのに私たちの助けは必要ないと思いますが、完璧な理解を目指しているので簡単に説明します。

  • まず、ブラウザでwindow.ethereumが有効になっているか(すなわち MetaMaskがインストールされているか)を関数がチェックしています。
    • 有効になっていない場合、ユーザーにMetaMaskのインストールを求めるJSX文字列をstatus状態変数に設定します。
    • 有効になっている場合、MetaMaskウォレットの状態変更をリッスンしている3行目のwindow.ethereum.on("accountsChanged")リスナーを設定します。この状態変更には、ユーザーが追加のアカウントを分散型アプリケーション(Dapp)に接続した場合、アカウントを切り替えた場合、アカウントを切断した場合が含まれます。 少なくとも1つのアカウントが接続されていれば、accounts配列の最初のアカウントがリスナーから返されたときに、walletAddress状態変数が更新されます。 それ以外の場合は、walletAddressに空の文字列が設定されます。

最後に、useEffect関数で次のように呼び出す必要があります。

1// HelloWorld.js
2
3useEffect(async () => {
4 const message = await loadCurrentMessage()
5 setMessage(message)
6 addSmartContractListener()
7
8 const { address, status } = await getCurrentWalletConnected()
9 setWallet(address)
10 setStatus(status)
11
12 addWalletListener()
13}, [])
すべて表示

完成です! ウォレットのすべての機能をプログラミングしました。 次は最後のタスクです。スマートコントラクトに保存されているメッセージを更新します。

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

友よ!最終段階にたどり着きました。 interact.jsファイルのupdateMessageで、次のことを実行します。

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

これには、それほど時間を要しません。dappを完成させましょう!

入力エラー処理

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

MetaMaskエクステンションがインストールされていない場合や接続されているウォレットがない場合 (つまり、渡された addressが空の文字列)messageは空の文字列になります。 次のエラー処理をupdateMessageに追加しましょう。

1// interact.js
2
3export const updateMessage = async (address, message) => {
4 if (!window.ethereum || address === null) {
5 return {
6 status:
7 "💡 Connect your MetaMask wallet to update the message on the blockchain.",
8 }
9 }
10
11 if (message.trim() === "") {
12 return {
13 status: "❌ Your message cannot be an empty string.",
14 }
15 }
16}
すべて表示

入力エラーを適切に処理できるようなりました。それでは、MetaMaskを介してトランザクションに署名をします。

トランザクションへ署名する

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

1// interact.js
2
3//set up transaction parameters
4const transactionParameters = {
5 to: contractAddress, // Required except during contract publications.
6 from: address, // must match user's active address.
7 data: helloWorldContract.methods.update(message).encodeABI(),
8}
9
10//sign the transaction
11try {
12 const txHash = await window.ethereum.request({
13 method: "eth_sendTransaction",
14 params: [transactionParameters],
15 })
16 return {
17 status: (
18 <span>
19{" "}
20 <a target="_blank" href={`https://goerli.etherscan.io/tx/${txHash}`}>
21 View the status of your transaction on Etherscan!
22 </a>
23 <br />
24 ℹ️ Once the transaction is verified by the network, the message will be
25 updated automatically.
26 </span>
27 ),
28 }
29} catch (error) {
30 return {
31 status: "😥 " + error.message,
32 }
33}
すべて表示

何をしているか、説明していきましょう。 まず、次のようにトランザクションパラメータを設定します。

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

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

この時点で、ブラウザでMetaMaskが開かれ、ユーザーにトランザクションの署名または拒否を求めます。

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

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

1// interact.js
2
3export const updateMessage = async (address, message) => {
4 //input error handling
5 if (!window.ethereum || address === null) {
6 return {
7 status:
8 "💡 Connect your MetaMask wallet to update the message on the blockchain.",
9 }
10 }
11
12 if (message.trim() === "") {
13 return {
14 status: "❌ Your message cannot be an empty string.",
15 }
16 }
17
18 //set up transaction parameters
19 const transactionParameters = {
20 to: contractAddress, // Required except during contract publications.
21 from: address, // must match user's active address.
22 data: helloWorldContract.methods.update(message).encodeABI(),
23 }
24
25 //sign the transaction
26 try {
27 const txHash = await window.ethereum.request({
28 method: "eth_sendTransaction",
29 params: [transactionParameters],
30 })
31 return {
32 status: (
33 <span>
34{" "}
35 <a target="_blank" href={`https://goerli.etherscan.io/tx/${txHash}`}>
36 View the status of your transaction on Etherscan!
37 </a>
38 <br />
39 ℹ️ Once the transaction is verified by the network, the message will
40 be updated automatically.
41 </span>
42 ),
43 }
44 } catch (error) {
45 return {
46 status: "😥 " + error.message,
47 }
48 }
49}
すべて表示

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

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

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

1// HelloWorld.js
2
3const onUpdatePressed = async () => {
4 const { status } = await updateMessage(walletAddress, newMessage)
5 setStatus(status)
6}

とても綺麗ででシンプルです。 そして、なんということでしょう。 dappの完成です。

更新ボタンを試してみてください!

自分自身でカスタムdappを作る

おめでとう!あなたは、このチュートリアルを最後までやりきりました! おさらいすると、以下の方法を学びました。

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

これで、このチュートリアルのスキルを応用して独自のカスタムdappプロジェクトを構築するための準備が整いました。 何かご質問がございましたら、Alchemy Discord(opens in a new tab)でいつでもお気軽にお問い合わせください。 🧙‍♂️

このチュートリアルを通して体験したことやフィードバックがあれば、Twitter@alchemyplatform(opens in a new tab)でタグ付けしてお知らせください。

最終編集者: , 2024年4月1日

このチュートリアルは役に立ちましたか?