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

非代替性トークン(NFT)ミンターチュートリアル

SolidityNFTalchemyスマートコントラクトフロントエンドPinata
中級
✍️smudgil
📆 2021年10月6日
⏱️45 分の読書 minute read

Web2 のバックグラウンドを持つデベロッパーの最大の課題の 1 つは、スマートコントラクトをフロントエンドのプロジェクトに接続し、やり取りを行う方法を理解することです。

ここでは、デジタル資産へのリンク、タイトル、説明を入力できるシンプルな UI を備えた非代替性トークン(NFT)ミンターを構築することで、次の方法を学びます。

  • フロントエンドのプロジェクト経由で MetaMask に接続する
  • フロントエンドからスマートコントラクトメソッドを呼び出す
  • MetaMask を使用してトランザクションに署名する

このチュートリアルでは、React(opens in a new tab)をフロントエンドフレームワークとして使用します。 このチュートリアルは Web3 開発に焦点を当てているので、React の基礎についての説明に多くの時間を費やせません。 代わりに、プロジェクトの機能性を高めることに注力します。

前提条件として、React に関する初級レベルの知識を有している必要があります。つまり、コンポーネント、プロパティ(props)、useState および useEffect、基本関数の呼び出しなどの仕組みを理解している必要があります。 これらの中に初めて耳にする用語がある場合は、React の入門チュートリアル(opens in a new tab)をご覧ください。 より視覚的な学習を好む方には、Net Ninja による素晴らしいフルモダン React チュートリアル(opens in a new tab)のビデオシリーズをお勧めします。

まだ Alchemy アカウントをお持ちでない場合、このチュートリアルを完了したり、ブロックチェーンで何かを構築したりするために必ず必要になりますので、 こちらから(opens in a new tab)無料アカウントに登録してください。

それでは、さっそく始めましょう!

非代替性トークン(NFT)作成入門

コードを見始める前に、非代替性トークン(NFT)作成の仕組みを理解することが重要です。 それには、次の 2 つのステップがあります。

イーサリアムブロックチェーン上で非代替性トークン(NFT)スマートコントラクトを公開

ERC-1155 と ERC-721 の 2 つのスマートコントラクト規格の最大の違いは、ERC-1155 はマルチトークン規格でありバッチ機能を備えているのに対し、ERC-721 はシングルトークン規格であり一度に 1 つのトークンの送信しかサポートしていないことです。

ミント関数の呼び出し

通常、このミント関数は、パラメータとして 2 つの変数を渡す必要があります。1 つ目は、新しくミントされた非代替性トークン(NFT)を受け取るアドレスを指定するrecipientです。2 つ目は、非代替性トークン(NFT)のメタデータを記述する JSON ドキュメントに解決される文字列である非代替性トークン(NFT)のtokenURIです。

非代替性トークン(NFT)のメタデータは、非代替性トークン(NFT)に名前、説明、画像(または別のデジタル資産)、その他の属性などのプロパティを持たせ、非代替性トークン(NFT)を利用できるようにします。 非代替性トークン(NFT)のメタデータが含まれているtokenURI の例(opens in a new tab)をご覧ください。

このチュートリアルでは、React UI を使用して既存の非代替性トークン(NFT)のスマートコントラクトのミント関数を呼び出すパート 2(後半)の方に焦点を当てています。

このチュートリアルで呼び出す ERC-721 非代替性トークン(NFT)スマートコントラクトへのリンクは、こちら(opens in a new tab)です。 この作成方法について知りたい場合は、非代替性トークン(NFT)の作り方(opens in a new tab)という別のチュートリアルを確認することを強くお勧めします。

非代替性トークン(NFT)作成の仕組みを理解したところで、スターターファイルをクローンしましょう。

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

最初に、非代替性トークン(NFT)ミンターチュートリアル(nft-minter-tutorial)の GitHub リポジトリ(opens in a new tab)にアクセスし、このプロジェクトのスターターファイルを取得します。 リポジトリをローカル環境にクローンします。

クローンされたnft-minter-tutorialリポジトリを開くと、minter-starter-filesnft-minterという 2 つのフォルダが含まれています。

  • minter-starter-filesには、このプロジェクトのスターターファイル(基本的には React UI)が含まれています。 このチュートリアルでは、イーサリアムウォレットと非代替性トークン(NFT)スマートコントラクトに接続することで、この UI を利用できるようにする方法を学ぶ際に、こちらのディレクトリで作業します
  • nft-minterには、完成したチュートリアル全体が含まれており、困ったときに**リファレンス**として利用できます。

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

これから作成するすべてのコードは、srcフォルダに保存されます。 後ほどMinter.jsコンポーネントを編集し、追加の javascript ファイルを書くことで、このプロジェクトに Web3 機能を追加します。

ステップ 2: スターターファイルの確認

コーディングを始める前に、スターターファイルで既に提供されるものを確認することが重要です。

React プロジェクトの実行

まずは、ブラウザで React プロジェクトを実行しましょう。 React の素晴らしいところは、一度ブラウザでプロジェクトを実行すると、保存した変更がブラウザでも同時に更新されることです。

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

cd minter-starter-files
npm install

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

npm start

これにより、ブラウザで http://localhost:3000/が開き、プロジェクトのフロントエンドが表示されます。 フロントエンドは 3 つのフィールドで構成されており、それぞれ、非代替性トークン(NFT)資産へのリンク、非代替性トークン(NFT)の名前、非代替性トークン(NFT)の説明を入力する場所になっています。

「Connect Wallet」や「Mint NFT」ボタンをクリックしても、動作しません。これらの機能は、これからプログラムする必要があります。 :)

Minter.js コンポーネント

注: minter-starter-filesフォルダにいることを確認してください。nft-minterフォルダではないことを確認します。

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

このファイルの上部には、特定のイベントの後に更新される状態変数(State Variable)があります。

1//State variables
2const [walletAddress, setWallet] = useState("")
3const [status, setStatus] = useState("")
4const [name, setName] = useState("")
5const [description, setDescription] = useState("")
6const [url, setURL] = useState("")

React の状態変数や状態フック(State Hook)を聞いたことがない場合は、 こちらの(opens in a new tab)ドキュメントをご覧ください。

それぞれの変数は以下を示します。

  • walletAddress - ユーザーのウォレットアドレスを格納する文字列
  • status - UI の下部に表示するメッセージを含む文字列
  • name - 非代替性トークン(NFT)の名前を格納する文字列
  • description - 非代替性トークン(NFT)の説明を格納する文字列
  • url - 非代替性トークン(NFT)のデジタル資産へのリンクを含んだ文字列

状態変数(State Variable)の後に、useEffectconnectWalletPressedonMintPressedという 3 つの未実装の関数があります。 これらの関数は、すべてasyncになっています。これは、それぞれの関数で非同期 API 呼び出しを行うためです。 それぞれの関数の名前は、その機能を示しています。

1useEffect(async () => {
2 //TODO: implement
3}, [])
4
5const connectWalletPressed = async () => {
6 //TODO: implement
7}
8
9const onMintPressed = async () => {
10 //TODO: implement
11}
すべて表示
  • useEffect(opens in a new tab) - コンポーネントがレンダリングされた後に呼び出される React フックです。 空の配列[]の prop が渡される(3 行目を参照)ため、コンポーネントの最初のレンダリングでのみ呼び出されます。 ここでは、ウォレットリスナーと別のウォレット関数を呼び出し、ウォレットが接続されているかどうかに応じた UI の更新をします。
  • connectWalletPressed - この関数は、ユーザーの MataMask ウォレットを分散型アプリケーション(Dapp)に接続するために呼び出されます。
  • onMintPressed - この関数は、ユーザーの非代替性トークン(NFT)をミントするために呼び出されます。

このファイルの終盤には、コンポーネントの UI があります。 このコードを注意深く読んでいくと、状態変数のurlnamedescriptionに対応するテキストフィールドの入力が変更された場合、これらの変数を更新していることが分かります。

さらに、walletButtonまたはmintButtonという ID を持つボタンがクリックされると、それぞれconnectWalletPressedまたはonMintPressedが呼び出されることも分かります。

1//the UI of our component
2return (
3 <div className="Minter">
4 <button id="walletButton" onClick={connectWalletPressed}>
5 {walletAddress.length > 0 ? (
6 "Connected: " +
7 String(walletAddress).substring(0, 6) +
8 "..." +
9 String(walletAddress).substring(38)
10 ) : (
11 <span>Connect Wallet</span>
12 )}
13 </button>
14
15 <br></br>
16 <h1 id="title">🧙‍♂️ Alchemy NFT Minter</h1>
17 <p>
18 Simply add your asset's link, name, and description, then press "Mint."
19 </p>
20 <form>
21 <h2>🖼 Link to asset: </h2>
22 <input
23 type="text"
24 placeholder="e.g. https://gateway.pinata.cloud/ipfs/<hash>"
25 onChange={(event) => setURL(event.target.value)}
26 />
27 <h2>🤔 Name: </h2>
28 <input
29 type="text"
30 placeholder="e.g. My first NFT!"
31 onChange={(event) => setName(event.target.value)}
32 />
33 <h2>✍️ Description: </h2>
34 <input
35 type="text"
36 placeholder="e.g. Even cooler than cryptokitties ;)"
37 onChange={(event) => setDescription(event.target.value)}
38 />
39 </form>
40 <button id="mintButton" onClick={onMintPressed}>
41 Mint NFT
42 </button>
43 <p id="status">{status}</p>
44 </div>
45)
すべて表示

最後に、このミンター(Minter)コンポーネントがどこに加えられるかについて説明します。

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

このチュートリアルでは、Minter.jsファイルの編集と、srcフォルダへのファイルの追加のみを行います。

これから取り組む内容を理解したところで、イーサリアムウォレットを設定しましょう。

イーサリアムウォレットの設定

ユーザーがスマートコントラクトとやり取りできるようにするには、自分のイーサリアムウォレットを分散型アプリケーション(Dapp)に接続する必要があります。

MetaMask をダウンロード

このチュートリアルでは、イーサリアムアカウントアドレスを管理するためにブラウザの仮想ウォレットである Metamask を使用します。 イーサリアムのトランザクションの仕組みの詳細については、こちらのページをご覧ください。

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

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

非代替性トークン(NFT)をミントする(または、イーサリアムのブロックチェーンのトランザクションに署名する)には、偽の ETH が必要です。 ETH を取得するには、Ropsten フォーセット(opens in a new tab)にアクセスして、Ropsten アカウントアドレスを入力し、「Send Ropsten ETH」をクリックします。 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 に相当します。

ふう! これで、偽のお金を手に入れました。 🤑

MetaMask を UI に接続

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

モデルビューコントローラ(MVC)(opens in a new tab)パラダイムを実践したいので、別のファイルを作成し、分散型アプリケーション(Dapp)のロジック、データ、ルールを管理する関数を含めます。次に、それらの関数をフロントエンド(Minter.js コンポーネント)に渡します。

connectWallet関数

これを行うには、srcディレクトリにutilsという新しいフォルダを作成して、そこにinteract.jsというファイルを追加します。このファイルには、ウォレットとスマートコントラクトがやり取りする関数がすべて含まれます。

interact.jsファイルにconnectWallet関数を記述し、この関数をMinter.jsコンポーネントにインポートして呼び出します。

interact.jsファイルに以下を追加します。

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

このコードが何をしているのか見てみましょう。

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

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

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

これから記述するほとんどの関数は、状態変数(State Variable)と UI の更新に使用できる JSON オブジェクトを返します。

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

try/catch ループを使用して、[window.ethereum.request({ method: "eth_requestAccounts" });](https://docs.metamask.io/guide/rpc-api.html#eth-requestaccounts)を呼び出すことで、MetaMask への接続を試みます。 この関数を呼び出すと、ブラウザで MetaMask が開き、ユーザーはウォレットを分散型アプリケーション(Dapp)に接続するように求められます。

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

Minter.js UI コンポーネントに connectWallet 関数を追加

connectWallet関数を記述したので、 Minter.jsコンポーネントに接続しましょう。

まず、Minter.jsファイルの上部にimport { connectWallet } from "./utils/interact.js";を追加して、Minter.jsファイルに関数をインポートする必要があります。 Minter.jsの最初の 11 行は、次のようになります。

1import { useEffect, useState } from "react";
2import { connectWallet } from "./utils/interact.js";
3
4const Minter = (props) => {
5
6 //State variables
7 const [walletAddress, setWallet] = useState("");
8 const [status, setStatus] = useState("");
9 const [name, setName] = useState("");
10 const [description, setDescription] = useState("");
11 const [url, setURL] = useState("");
すべて表示

次に、connectWalletPressed関数の中で、インポートされたconnectWallet関数を、以下のように呼び出します。

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

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

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

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

localhost:3000 でブラウザを開き、ページ右上にある「Connect Wallet」ボタンを押します。

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

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

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

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

getCurrentWalletConnected 関数

interact.jsファイルに、以下のgetCurrentWalletConnected関数を追加します。

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

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

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

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

connectWalletで行ったのと同様に、この関数をinteract.jsファイルから Minter.jsファイルへ次のようにインポートする必要があります。

1import { useEffect, useState } from "react"
2import {
3 connectWallet,
4 getCurrentWalletConnected, //import here
5} from "./utils/interact.js"

ここでは、useEffect関数で次のように呼び出します。

1useEffect(async () => {
2 const { address, status } = await getCurrentWalletConnected()
3 setWallet(address)
4 setStatus(status)
5}, [])

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

このコードを追加したら、ブラウザウィンドウを更新してみてください。 リフレッシュ後も、ボタンには接続されていることが示されており、接続されたウォレットのアドレスのプレビューが表示されているはずです。

addWalletListener の実装

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

Minter.jsファイルで、次のようなaddWalletListener関数を追加してください。

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

ここで何が起きているか、簡単に見ていきましょう。

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

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

1useEffect(async () => {
2 const { address, status } = await getCurrentWalletConnected()
3 setWallet(address)
4 setStatus(status)
5
6 addWalletListener()
7}, [])

これで完了です。 ウォレットのすべての機能をプログラミングしました。 ウォレットが設定されたので、非代替性トークン(NFT)をミントする方法を理解しましょう!

非代替性トークン(NFT)メタデータ入門

このチュートリアルの最初の方で説明した非代替性トークン(NFT)のメタデータを思い出してください。非代替性トークン(NFT)メタデータは、非代替性トークン(NFT)にデジタル資産、名前、説明、その他の属性などのプロパティーを持たせ、非代替性トークン(NFT)を利用できるようにします。

JSON オブジェクトとしてメタデータを設定し、保存する必要があります。これで、スマートコントラクトのmintNFT関数呼び出すときにtokenURIパラメータとして渡すことができます。

「Link to Asset」、「Name」、「Description」フィールドのテキストは、非代替性トークン(NFT)のメタデータで別々のプロパティになります。 メタデータを JSON オブジェクトとしてフォーマットしますが、この JSON オブジェクトの格納には、以下のような複数のオプションがあります。

  • イーサリアムブロックチェーンに格納することができますが、これは非常に高価です。
  • AWS や Firebase などの中央集権型サーバーに保存できます。 しかし、これは分散化の信念に反するものです。
  • 惑星間ファイルシステム(IPFS)という、分散型ファイルシステムでデータを保存、共有するための、分散型プロトコルおよびピアツーピア・ネットワークを使用できます。 このプロトコルは、分散化されており無料のため、最良のオプションです。

惑星間ファイルシステム(IPFS)にメタデータを保存するには、Pinata(opens in a new tab)という便利な惑星間ファイルシステム(IPFS) API とツールキットを使用します。 次のステップでは、この方法を具体的に説明します。

(opens in a new tab)メタデータを惑星間ファイルシステム(IPFS)にピン留めする Pintata の使用

Pinata(opens in a new tab)アカウントをお持ちでない場合は、こちら(opens in a new tab)から無料のアカウントにサインアップし、メールアドレスとアカウントの認証手順を完了してください。

Pinata API キーの作成

https://pinata.cloud/keys(opens in a new tab)ページに移動して、上部にある「New Key」ボタンを選択し、Admin ウィジェットを有効(Enabled)に設定してからキーに名前を付けます。

API 情報を含むポップアップが表示されます。 この情報は、必ず安全な場所に保存してください。

キーの設定が完了したので、プロジェクトに追加して使用できるようにしましょう。

.env ファイルの作成

環境ファイルに Pinata キーとシークレットを安全に保存できます。 dotenv パッケージ(opens in a new tab)をプロジェクトディレクトリにインストールしましょう。

ターミナルで(ローカルホストを実行しているタブとは別の)新しいタブを開き、minter-starter-filesフォルダにいることを確認してください。次に、ターミナルで以下のコマンドを実行します。

1npm install dotenv --save

次に、コマンドラインで次のように入力し、.envファイルをminter-starter-filesのルートディレクトリに作成します。

1vim.env

vim(テキストエディタ).envファイルが開きます。 保存するには、キーボードで「esc」+「:」+「q」をこの順序で押します。

次に、VSCode で.envファイルに移動し、次のようにして Pinata API キーと API シークレットを追加します。

1REACT_APP_PINATA_KEY = <pinata-api-key>
2REACT_APP_PINATA_SECRET = <pinata-api-secret>

ファイルを保存します。これで、JSON メタデータを惑星間ファイルシステム(IPFS)にアップロードする関数を書き始める準備が整いました。

(opens in a new tab)pinJSONToIPFS の実装

幸いにも Pinata では、惑星間ファイルシステム(IPFS)への JSON データのアップロードに特化した API(opens in a new tab)と、少しの変更を加えるだけで使用できる axios のサンプルを備えた便利な JavaScript を使用できます。

utilsフォルダーにpinata.jsという別のファイルを作成し、.env ファイルから Pinata のシークレットとキーをインポートしましょう。

1require("dotenv").config()
2const key = process.env.REACT_APP_PINATA_KEY
3const secret = process.env.REACT_APP_PINATA_SECRET

次に、pinata.jsファイルに以下の追加コードを貼り付けます。 コードの意味はこれから説明しますので、心配する必要はありません。

1require("dotenv").config()
2const key = process.env.REACT_APP_PINATA_KEY
3const secret = process.env.REACT_APP_PINATA_SECRET
4
5const axios = require("axios")
6
7export const pinJSONToIPFS = async (JSONBody) => {
8 const url = `https://api.pinata.cloud/pinning/pinJSONToIPFS`
9 //making axios POST request to Pinata ⬇️
10 return axios
11 .post(url, JSONBody, {
12 headers: {
13 pinata_api_key: key,
14 pinata_secret_api_key: secret,
15 },
16 })
17 .then(function (response) {
18 return {
19 success: true,
20 pinataUrl:
21 "https://gateway.pinata.cloud/ipfs/" + response.data.IpfsHash,
22 }
23 })
24 .catch(function (error) {
25 console.log(error)
26 return {
27 success: false,
28 message: error.message,
29 }
30 })
31}
すべて表示

では、このコードは何をしているのでしょうか?

最初に、ブラウザと node.js のための Promise ベースの HTTP クライアントであるaxios(opens in a new tab)をインポートしています。axios は、Pinata へのリクエストで使用します。

その下に、pinJSONToIPFS非同期関数があります。この関数は、pinJSONToIPFS API への POST リクエストを行うために、JSONBodyを入力として取り、Pinata の API キーとシークレットをヘッダーに入れます。

  • POST リクエストが成功した場合、この関数は、true に設定されたsuccessブール値と、メタデータがピン留めされたpinataUrlが入った JSON オブジェクトを返します。 ここで返されたpinataUrlは、スマートコントラクトの mint 関数のtokenURIの入力として使用されます。
  • POST リクエストが失敗した場合、この関数は、false に設定されたsuccessブール値と、エラーを伝えるmessage文字列が入った JSON オブジェクトを返します。

connectWallet関数の戻り値の型と同様に、JSON オブジェクトが返されるので、そのパラメータを状態変数と UI の更新に使用できます。

スマートコントラクトのロード

これで、pinJSONToIPFS関数を介して非代替性トークン(NFT)メタデータを惑星間ファイルシステム(IPFS)にアップロードする手段を手に入れました。次は、mintNFT関数を呼び出せるように、スマートコントラクトのインスタンスをロードする手段が必要です。

前述したように、このチュートリアルでは、こちらの既存の非代替性トークン(NFT)スマートコントラクト(opens in a new tab)を使用します。ただし、この作成方法を学びたい、もしくは自分で作成したい場合は、「非代替性トークン(NFT)の作り方」(opens in a new tab)という別のチュートリアルを確認することを強くお勧めします。

コントラクトアプリケーションバイナリインターフェース(ABI)

ファイルを詳しく調べてみると、srcディレクトリにcontract-abi.jsonファイルがあることが分かります。 アプリケーションバイナリインターフェース(ABI)は、コントラクトが呼び出す関数を指定し、関数が確実に意図しているフォーマットでデータを返すようにするために必要です。

さらに、イーサリアムブロックチェーンに接続してスマートコントラクトをロードするための、Alchemy API キーと Alchemy Web3 API も必要になります。

Alchemy API キーの作成

Alchemy のアカウントをお持ちでない場合は、こちら(opens in a new tab)から無料で登録できます。

Alchemy のアカウントを作成した後、アプリを作成することで API キーを生成することができます。 これにより、Ropsten テストネットワークへのリクエストが可能になります。

ナビゲーションバーの「Apps」にマウスを合わせて、「Create App」をクリックし、Alchemy ダッシュボードの「Create App」ページに移動してください。

アプリに名前を付け(私たちは「My First NFT!」にしました)、簡単な説明を記述し、環境に「Staging」を選択(アプリのブックキーピングに使用)し、ネットワークに「Ropsten」を選択します。

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

HTTP Alchemy API URL を作成したので、クリップボードにコピーします。

それを.envファイルに追加してみましょう。 これで.env ファイル全体は、次のようになります。

1REACT_APP_PINATA_KEY = <pinata-key>
2REACT_APP_PINATA_SECRET = <pinata-secret>
3REACT_APP_ALCHEMY_KEY = https://eth-ropsten.alchemyapi.io/v2/<alchemy-key>

コントラクトアプリケーションバイナリインターフェース(ABI)と Alchemy API キーが用意できたので、Alchemy Web3(opens in a new tab)を使用してスマートコントラクトをロードする準備ができました。

Alchemy Web3 エンドポイントとコントラクトの設定

まず、Alchemy Web3(opens in a new tab)がインストールされていない場合は、ターミナルで次のようにホームディレクトリであるnft-minter-tutorialに移動してインストールする必要があります。

1cd ..
2npm install @alch/alchemy-web3

次に、interact.jsファイルに戻りましょう。 .env ファイルから Alchemy キーがインポートされ、Alchemy Web3 エンドポイントが設定されるように、ファイルの上部に次のコードを追加します。

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

Alchemy Web3(opens in a new tab)は、Web3.js(opens in a new tab)のラッパーであり、強化された API メソッドや重要なメリットを提供し、Web3 デベロッパーの負担を軽減します。 最小限の設定で使えるように設計されているので、アプリですぐに使用可能です。

次に、コントラクトアプリケーションバイナリインターフェース(ABI)とコントラクトアドレスをファイルに追加しましょう。

1require("dotenv").config()
2const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY
3const { createAlchemyWeb3 } = require("@alch/alchemy-web3")
4const web3 = createAlchemyWeb3(alchemyKey)
5
6const contractABI = require("../contract-abi.json")
7const contractAddress = "0x4C4a07F737Bf57F6632B6CAB089B78f62385aCaE"

これで両方を追加できたので、mint 関数のコーディングを始める準備ができました。

mintNFT 関数の実装

interact.jsファイル内に、mintNFT関数を定義しましょう。この関数は、名前が示すとおりに非代替性トークン(NFT)をミントします。

多数の非同期呼び出しを(メタデータを IPFS にピン留めするために Pinata に対して、スマートコントラクトをロードするために Alchemy Web3 に対して、トランザクションに署名するために MetaMask に対して)行うため、この関数もまた非同期になります。

この関数への 3 つの入力は、デジタル資産のurlnamedescriptionになります。 connectWallet関数の下に、次の関数シグネチャを追加してください。

1export const mintNFT = async (url, name, description) => {}

入力エラー処理

当然のこととして、関数の開始時に何らかの入力エラー処理を行うことは理にかなっています。入力パラメータが正しくない場合は、関数を終了するようにします。 関数の内部に次のコードを追加しましょう。

1export const mintNFT = async (url, name, description) => {
2 //error handling
3 if (url.trim() == "" || name.trim() == "" || description.trim() == "") {
4 return {
5 success: false,
6 status: "❗Please make sure all fields are completed before minting.",
7 }
8 }
9}
すべて表示

基本的に、入力パラメーターのいずれかが空の文字列である場合、false に設定されたsuccessブール値と、UI のすべてのフィールドに入力する必要があることを伝えるstatus文字列が入った JSON オブジェクトを返します。

(opens in a new tab)IPFS にメタデータをアップロード

メタデータが適切にフォーマットされていることを確認したら、次のステップは、それを JSON オブジェクトにラップし、作成したpinJSONToIPFSを介して惑星間ファイルシステム(IPFS)にアップロードすることです。

そのためにはまず、pinJSONToIPFS関数をinteract.jsファイルにインポートする必要があります。 interact.jsの最上部に、次の行を追加してください。

1import { pinJSONToIPFS } from "./pinata.js"

pinJSONToIPFSが、JSON 本体を取ることを思い出してください。 そのため、呼び出す前にurlnamedescriptionパラメータを JSON オブジェクトにフォーマットする必要があります。

次のようにコードを更新して、metadataという JSON オブジェクトを作成し、このmetadataパラメータを使用してpinJSONToIPFSを呼び出します。

1export const mintNFT = async (url, name, description) => {
2 //error handling
3 if (url.trim() == "" || name.trim() == "" || description.trim() == "") {
4 return {
5 success: false,
6 status: "❗Please make sure all fields are completed before minting.",
7 }
8 }
9
10 //make metadata
11 const metadata = new Object()
12 metadata.name = name
13 metadata.image = url
14 metadata.description = description
15
16 //make pinata call
17 const pinataResponse = await pinJSONToIPFS(metadata)
18 if (!pinataResponse.success) {
19 return {
20 success: false,
21 status: "😢 Something went wrong while uploading your tokenURI.",
22 }
23 }
24 const tokenURI = pinataResponse.pinataUrl
25}
すべて表示

pinJSONToIPFS(metadata)の呼び出しのレスポンスを、pinataResponseオブジェクトに格納していることに注目してください。 次に、このオブジェクトにエラーがないか解析します。

エラーがある場合、false に設定されたsuccessブール値と、呼び出しが失敗したことを伝えるstatus文字列が入った JSON オブジェクトを返します。 それ以外の場合は、pinataURLpinataResponseから抽出し、それをtokenURI変数として格納します。

では、ファイルの先頭で初期化した Alchemy Web3 API を使用して、スマートコントラクトをロードしてみましょう。 mintNFT関数の下部に次のコードの行を追加して、window.contractグローバル変数にコントラクトを設定します。

1window.contract = await new web3.eth.Contract(contractABI, contractAddress)

mintNFT関数に最後に追加するのは、イーサリアムのトランザクションです。

1//set up your Ethereum transaction
2const transactionParameters = {
3 to: contractAddress, // Required except during contract publications.
4 from: window.ethereum.selectedAddress, // must match user's active address.
5 data: window.contract.methods
6 .mintNFT(window.ethereum.selectedAddress, tokenURI)
7 .encodeABI(), //make call to NFT smart contract
8}
9
10//sign the transaction via MetaMask
11try {
12 const txHash = await window.ethereum.request({
13 method: "eth_sendTransaction",
14 params: [transactionParameters],
15 })
16 return {
17 success: true,
18 status:
19 "✅ Check out your transaction on Etherscan: https://ropsten.etherscan.io/tx/" +
20 txHash,
21 }
22} catch (error) {
23 return {
24 success: false,
25 status: "😥 Something went wrong: " + error.message,
26 }
27}
すべて表示

イーサリアムトランザクションをすでによくご存知ならば、構造が今まで見てきたものとかなり似ていることに気付くでしょう。

  • まず、トランザクションパラメータを設定します。
    • toに受取人のアドレス(スマートコントラクト)を設定します 。
    • fromにトランザクションの署名者(MetaMask に接続されているユーザーのアドレス: window.ethereum.selectedAddress)を指定します。
    • dataには、スマートコントラクトのmintNFTメソッド呼び出しが含まれ、tokenURIとユーザーのウォレットのアドレスwindow.ethereum.selectedAddressを入力として受け取ります。
  • 次に、window.ethereum.requestを await で呼び出して、MetaMask にトランザクションの署名を依頼します。 このリクエストで、eth メソッド(eth_sendTransaction)を指定し、transactionParametersを渡していることに注目してください。 この時点で、ブラウザで MetaMask が開かれ、ユーザーにトランザクションの署名または拒否を求めます。
    • トランザクションが成功した場合、この関数は、true に設定されたsuccessブール値と、Etherscan でトランザクションについての詳細を確認するようユーザーに求めるstatus文字列が入った JSON オブジェクトを返します。
    • トランザクションが失敗した場合、この関数は、false に設定されたsuccessブール値と、エラーメッセージを伝えるstatus文字列が入った JSON オブジェクトを返します。

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

1export const mintNFT = async (url, name, description) => {
2 //error handling
3 if (url.trim() == "" || name.trim() == "" || description.trim() == "") {
4 return {
5 success: false,
6 status: "❗Please make sure all fields are completed before minting.",
7 }
8 }
9
10 //make metadata
11 const metadata = new Object()
12 metadata.name = name
13 metadata.image = url
14 metadata.description = description
15
16 //pinata pin request
17 const pinataResponse = await pinJSONToIPFS(metadata)
18 if (!pinataResponse.success) {
19 return {
20 success: false,
21 status: "😢 Something went wrong while uploading your tokenURI.",
22 }
23 }
24 const tokenURI = pinataResponse.pinataUrl
25
26 //load smart contract
27 window.contract = await new web3.eth.Contract(contractABI, contractAddress) //loadContract();
28
29 //set up your Ethereum transaction
30 const transactionParameters = {
31 to: contractAddress, // Required except during contract publications.
32 from: window.ethereum.selectedAddress, // must match user's active address.
33 data: window.contract.methods
34 .mintNFT(window.ethereum.selectedAddress, tokenURI)
35 .encodeABI(), //make call to NFT smart contract
36 }
37
38 //sign transaction via MetaMask
39 try {
40 const txHash = await window.ethereum.request({
41 method: "eth_sendTransaction",
42 params: [transactionParameters],
43 })
44 return {
45 success: true,
46 status:
47 "✅ Check out your transaction on Etherscan: https://ropsten.etherscan.io/tx/" +
48 txHash,
49 }
50 } catch (error) {
51 return {
52 success: false,
53 status: "😥 Something went wrong: " + error.message,
54 }
55 }
56}
すべて表示

巨大な関数でしたね! あとは、mintNFT関数をMinter.jsコンポーネントに接続するだけです。

mintNFT を Minter.js フロントエンドに接続

Minter.jsファイルを開いて、上部のimport { connectWallet, getCurrentWalletConnected } from "./utils/interact.js";の行を次のように更新してください。

1import {
2 connectWallet,
3 getCurrentWalletConnected,
4 mintNFT,
5} from "./utils/interact.js"

最後に、次のようにonMintPressed関数を実装し、インポートしたmintNFT関数を await で呼び出します。さらに、status状態変数を更新し、トランザクションが成功したか失敗したかを反映させるようにします。

1const onMintPressed = async () => {
2 const { status } = await mintNFT(url, name, description)
3 setStatus(status)
4}

稼働中のウェブサイトに非代替性トークン(NFT)をデプロイ

プロジェクトを稼働させてユーザーが使える準備ができましたでしょうか? 稼働しているウェブサイトへ Minter をデプロイするチュートリアル(opens in a new tab)をご覧ください。

次は最後のステップです。

ブロックチェーンの世界を席巻しよう!

これは冗談です。あなたは、このチュートリアルを最後までやりきりました!

要約すると、非代替性トークン(NFT)ミンターを構築することで次の方法を学ぶことが出来ました。

  • フロントエンドのプロジェクト経由で MetaMask へアクセス
  • フロントエンドからスマートコントラクトメソッドの呼び出し
  • MetaMask を使ったトランザクションの署名

ウォレットに分散型アプリケーション(Dapp)を介してミントされた非代替性トークン(NFT)を表示する方法については、ウォレットに非代替性トークン(NFT)を表示する方法(opens in a new tab)という簡単なチュートリアルをご覧ください。

ご不明な点がありましたら、いつでもAlchemy Discord(opens in a new tab)でお問い合わせください。 このチュートリアルのコンセプトが、今後のプロジェクトでどのように応用されるのか楽しみでなりません。

最終編集者: @HiroyukiNaito(opens in a new tab), Invalid DateTime

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