非代替性トークン(NFT)ミンターチュートリアル
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-files
とnft-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-filesnpm 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 variables2const [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)の後に、useEffect
、connectWalletPressed
、onMintPressed
という 3 つの未実装の関数があります。 これらの関数は、すべてasync
になっています。これは、それぞれの関数で非同期 API 呼び出しを行うためです。 それぞれの関数の名前は、その機能を示しています。
1useEffect(async () => {2 //TODO: implement3}, [])45const connectWalletPressed = async () => {6 //TODO: implement7}89const onMintPressed = async () => {10 //TODO: implement11}すべて表示
useEffect
(opens in a new tab) - コンポーネントがレンダリングされた後に呼び出される React フックです。 空の配列[]
の prop が渡される(3 行目を参照)ため、コンポーネントの最初のレンダリングでのみ呼び出されます。 ここでは、ウォレットリスナーと別のウォレット関数を呼び出し、ウォレットが接続されているかどうかに応じた UI の更新をします。connectWalletPressed
- この関数は、ユーザーの MataMask ウォレットを分散型アプリケーション(Dapp)に接続するために呼び出されます。onMintPressed
- この関数は、ユーザーの非代替性トークン(NFT)をミントするために呼び出されます。
このファイルの終盤には、コンポーネントの UI があります。 このコードを注意深く読んでいくと、状態変数のurl
、name
、description
に対応するテキストフィールドの入力が変更された場合、これらの変数を更新していることが分かります。
さらに、walletButton
またはmintButton
という ID を持つボタンがクリックされると、それぞれconnectWalletPressed
またはonMintPressed
が呼び出されることも分かります。
1//the UI of our component2return (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>1415 <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 <input23 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 <input29 type="text"30 placeholder="e.g. My first NFT!"31 onChange={(event) => setName(event.target.value)}32 />33 <h2>✍️ Description: </h2>34 <input35 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 NFT42 </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 obj12 } 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 your27 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 をインストールする必要があることを伝えるstatus
JSX オブジェクトが入った 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";34const Minter = (props) => {56 //State variables7 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 呼び出しを行っています。さらに、そのレスポンスを使用し、status
とwalletAddress
変数を状態フックを介して更新しています。
それでは、 Minter.js
と interact.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 your33 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 here5} 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
に空の文字列が設定されます。
- 有効になっていない場合、ユーザーに MetaMask のインストールを求める JSX 文字列を
最後に、useEffect
関数で次のように呼び出す必要があります。
1useEffect(async () => {2 const { address, status } = await getCurrentWalletConnected()3 setWallet(address)4 setStatus(status)56 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_KEY3const secret = process.env.REACT_APP_PINATA_SECRET
次に、pinata.js
ファイルに以下の追加コードを貼り付けます。 コードの意味はこれから説明しますので、心配する必要はありません。
1require("dotenv").config()2const key = process.env.REACT_APP_PINATA_KEY3const secret = process.env.REACT_APP_PINATA_SECRET45const axios = require("axios")67export const pinJSONToIPFS = async (JSONBody) => {8 const url = `https://api.pinata.cloud/pinning/pinJSONToIPFS`9 //making axios POST request to Pinata ⬇️10 return axios11 .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_KEY3const { 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_KEY3const { createAlchemyWeb3 } = require("@alch/alchemy-web3")4const web3 = createAlchemyWeb3(alchemyKey)56const contractABI = require("../contract-abi.json")7const contractAddress = "0x4C4a07F737Bf57F6632B6CAB089B78f62385aCaE"
これで両方を追加できたので、mint 関数のコーディングを始める準備ができました。
mintNFT 関数の実装
interact.js
ファイル内に、mintNFT
関数を定義しましょう。この関数は、名前が示すとおりに非代替性トークン(NFT)をミントします。
多数の非同期呼び出しを(メタデータを IPFS にピン留めするために Pinata に対して、スマートコントラクトをロードするために Alchemy Web3 に対して、トランザクションに署名するために MetaMask に対して)行うため、この関数もまた非同期になります。
この関数への 3 つの入力は、デジタル資産のurl
、name
、description
になります。 connectWallet
関数の下に、次の関数シグネチャを追加してください。
1export const mintNFT = async (url, name, description) => {}
入力エラー処理
当然のこととして、関数の開始時に何らかの入力エラー処理を行うことは理にかなっています。入力パラメータが正しくない場合は、関数を終了するようにします。 関数の内部に次のコードを追加しましょう。
1export const mintNFT = async (url, name, description) => {2 //error handling3 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 本体を取ることを思い出してください。 そのため、呼び出す前にurl
、name
、description
パラメータを JSON オブジェクトにフォーマットする必要があります。
次のようにコードを更新して、metadata
という JSON オブジェクトを作成し、このmetadata
パラメータを使用してpinJSONToIPFS
を呼び出します。
1export const mintNFT = async (url, name, description) => {2 //error handling3 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 }910 //make metadata11 const metadata = new Object()12 metadata.name = name13 metadata.image = url14 metadata.description = description1516 //make pinata call17 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.pinataUrl25}すべて表示
pinJSONToIPFS(metadata)
の呼び出しのレスポンスを、pinataResponse
オブジェクトに格納していることに注目してください。 次に、このオブジェクトにエラーがないか解析します。
エラーがある場合、false に設定されたsuccess
ブール値と、呼び出しが失敗したことを伝えるstatus
文字列が入った JSON オブジェクトを返します。 それ以外の場合は、pinataURL
をpinataResponse
から抽出し、それをtokenURI
変数として格納します。
では、ファイルの先頭で初期化した Alchemy Web3 API を使用して、スマートコントラクトをロードしてみましょう。 mintNFT
関数の下部に次のコードの行を追加して、window.contract
グローバル変数にコントラクトを設定します。
1window.contract = await new web3.eth.Contract(contractABI, contractAddress)
mintNFT
関数に最後に追加するのは、イーサリアムのトランザクションです。
1//set up your Ethereum transaction2const transactionParameters = {3 to: contractAddress, // Required except during contract publications.4 from: window.ethereum.selectedAddress, // must match user's active address.5 data: window.contract.methods6 .mintNFT(window.ethereum.selectedAddress, tokenURI)7 .encodeABI(), //make call to NFT smart contract8}910//sign the transaction via MetaMask11try {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 オブジェクトを返します。
- トランザクションが成功した場合、この関数は、true に設定された
mintNFT
関数全体は、次のようになります。
1export const mintNFT = async (url, name, description) => {2 //error handling3 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 }910 //make metadata11 const metadata = new Object()12 metadata.name = name13 metadata.image = url14 metadata.description = description1516 //pinata pin request17 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.pinataUrl2526 //load smart contract27 window.contract = await new web3.eth.Contract(contractABI, contractAddress) //loadContract();2829 //set up your Ethereum transaction30 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.methods34 .mintNFT(window.ethereum.selectedAddress, tokenURI)35 .encodeABI(), //make call to NFT smart contract36 }3738 //sign transaction via MetaMask39 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