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

非代替性トークン(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}
すべて表示

このファイルの終盤には、コンポーネントの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)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)にアップロードする関数を書き始める準備が整いました。

(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)でお問い合わせください。 このチュートリアルのコンセプトが、今後のプロジェクトでどのように応用されるのか楽しみでなりません。

最終編集者: @wackerow(opens in a new tab), 2024年5月7日

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