非代替性トークン(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とツールキットを使用します。 次のステップでは、この方法を具体的に説明します。
Pinataを使用してメタデータをIPFSに固定化
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)にアップロードする関数を書き始める準備が整いました。
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オブジェクトを返します。
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)でお問い合わせください。 このチュートリアルのコンセプトが、今後のプロジェクトでどのように応用されるのか楽しみでなりません。
最終編集者: @wackerow(opens in a new tab), 2024年5月7日