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

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

Solidity
NFT
Alchemy
スマートコントラクト
フロントエンド
Pinata
中級
smudgil
2021年10月6日
45 分の読書

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つのステップがあります。

Ethereumブロックチェーン上に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-minter-tutorialのGitHubリポジトリ (opens in a new tab)にアクセスして、このプロジェクトのスターターファイルを入手します。 このリポジトリをローカル環境にクローンします。

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

  • minter-starter-filesには、このプロジェクトのスターターファイル(本質的にはReactのUI)が含まれています。 このチュートリアルでは、Ethereumウォレットと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/が開き、プロジェクトのフロントエンドが表示されます。 (opens in a new tab) フロントエンドは3つのフィールドで構成されており、それぞれ、非代替性トークン(NFT)資産へのリンク、非代替性トークン(NFT)の名前、非代替性トークン(NFT)の説明を入力する場所になっています。

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

Minter.jsコンポーネント

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

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

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

1//状態変数
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のデジタル資産へのリンクである文字列

状態変数の後には、useEffectconnectWalletPressedonMintPressedという3つの未実装の関数があります。 これらの関数はすべてasyncであることにお気づきでしょう。これは、これらの関数内で非同期API呼び出しを行うためです。 それぞれの関数の名前は、その機能を示しています。

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

このファイルの終盤には、コンポーネントのUIがあります。 このコードを注意深く見ると、対応するテキストフィールドの入力が変更されたときに、urlnamedescriptionの状態変数が更新されることがわかります。

また、mintButtonwalletButtonのIDを持つボタンがクリックされると、それぞれconnectWalletPressedonMintPressedが呼び出されることもわかります。

1//コンポーネントのUI
2return (
3 <div className="Minter">
4 <button id="walletButton" onClick={connectWalletPressed}>
5 {walletAddress.length > 0 ? (
6 "接続済み: " +
7 String(walletAddress).substring(0, 6) +
8 "..." +
9 String(walletAddress).substring(38)
10 ) : (
11 <span>ウォレットを接続</span>
12 )}
13 </button>
14
15 <br></br>
16 <h1 id="title">🧙‍♂️ Alchemy NFTミンター</h1>
17 <p>
18 アセットのリンク、名前、説明を追加し、「ミント」を押すだけです。
19 </p>
20 <form>
21 <h2>🖼 アセットへのリンク: </h2>
22 <input
23 type="text"
24 placeholder="例: https://gateway.pinata.cloud/ipfs/<hash>"
25 onChange={(event) => setURL(event.target.value)}
26 />
27 <h2>🤔 名前: </h2>
28 <input
29 type="text"
30 placeholder="例: はじめてのNFT!"
31 onChange={(event) => setName(event.target.value)}
32 />
33 <h2>✍️ 説明: </h2>
34 <input
35 type="text"
36 placeholder="例: Cryptokittiesよりもクール ;)"
37 onChange={(event) => setDescription(event.target.value)}
38 />
39 </form>
40 <button id="mintButton" onClick={onMintPressed}>
41 NFTをミント
42 </button>
43 <p id="status">{status}</p>
44</div>
45)
すべて表示

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

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

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

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

Ethereumウォレットを設定する

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

MetaMaskをダウンロードする

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

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

フォーセットからEtherを追加する

非代替性トークン(NFT)をミントする(または、イーサリアムのブロックチェーンのトランザクションに署名する)には、偽のETHが必要です。 Ethを取得するには、Ropstenフォーセット (opens in a new tab)にアクセスしてRopstenのアカウントアドレスを入力し、「Send Ropsten Eth」をクリックします。 MetamaskアカウントにETHが表示されるはずです。

残高を確認する

残高を確認するために、Alchemyのcomposerツール (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: "👆🏽 上のテキストフィールドにメッセージを書いてください。",
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`}>
26 ブラウザに仮想EthereumウォレットであるMetaMaskをインストールする必要があります。
27 </a>
28 </p>
29 </span>
30 ),
31 }
32 }
33}
すべて表示

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

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

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

window.ethereumが_存在しない_場合、それはMetaMaskがインストールされていないことを意味します。 これにより、返されるaddressが空の文字列で、status JSXオブジェクトがユーザーにMetaMaskをインストールするよう促すJSONオブジェクトが返されます。

私たちが書く関数のほとんどは、状態変数とUIを更新するために使用できるJSONオブジェクトを返します。

さて、window.ethereumが_存在する_場合、ここからが面白くなります。

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

  • ユーザーが接続を選択した場合、method: "eth_requestAccounts"は、dappに接続されているすべてのユーザーのアカウントアドレスを含む配列を返します。 まとめると、connectWallet関数は、この配列の_最初の_address(9行目参照)と、ユーザーにスマートコントラクトへのメッセージを書き込むよう促すstatusメッセージを含むJSONオブジェクトを返します。
  • ユーザーが接続を拒否した場合、JSONオブジェクトには返されるaddressの空文字列と、ユーザーが接続を拒否したことを示すstatusメッセージが含まれます。

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

この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 //状態変数
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: "👆🏽 上のテキストフィールドにメッセージを書いてください。",
11 }
12 } else {
13 return {
14 address: "",
15 status: "🦊 右上のボタンを使ってMetaMaskに接続してください。",
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`}>
32 ブラウザに仮想EthereumウォレットであるMetaMaskをインストールする必要があります。
33 </a>
34 </p>
35 </span>
36 ),
37 }
38 }
39}
すべて表示

このコードは、先ほど書いた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, //ここでインポート
5} from "./utils/interact.js"

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

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

walletAddressstatusの状態変数を更新するのに、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("👆🏽 上のテキストフィールドにメッセージを書いてください。")
7 } else {
8 setWallet("")
9 setStatus("🦊 右上のボタンを使ってMetaMaskに接続してください。")
10 }
11 })
12 } else {
13 setStatus(
14 <p>
15 {" "}
16 🦊 <a target="_blank" href={`https://metamask.io/download`}>
17 ブラウザに仮想EthereumウォレットであるMetaMaskをインストールする必要があります。
18 </a>
19 </p>
20 )
21 }
22}
すべて表示

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

  • まず、この関数はwindow.ethereumが有効になっているか(つまり、MetaMaskがインストールされているか)をチェックします。
    • 有効でない場合、status状態変数を、ユーザーにMetaMaskのインストールを促すJSX文字列に設定するだけです。
    • 有効になっている場合、3行目のリスナーwindow.ethereum.on("accountsChanged")を設定します。これはMetaMaskウォレットの状態変更をリッスンします。これには、ユーザーがdappに追加のアカウントを接続した場合、アカウントを切り替えた場合、アカウントを切断した場合が含まれます。 少なくとも1つのアカウントが接続されていれば、walletAddress状態変数は、リスナーから返されたaccounts配列の最初のアカウントとして更新されます。 それ以外の場合、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に保存するには、便利なIPFS APIおよびツールキットであるPinata (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ウィジェットを有効に設定してからキーに名前を付けます。

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には、JSONデータをIPFSにアップロードするための専用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 //Pinataへのaxios POSTリクエストを作成 ⬇️
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非同期関数があります。この関数は、JSONBodyを入力として取り、PinataのAPIキーとシークレットをヘッダーに入れて、pinJSONToIPFS APIへのPOSTリクエストを行います。

  • このPOSTリクエストが成功した場合、この関数は、successブール値がtrueで、メタデータがピン留めされたpinataUrlが入ったJSONオブジェクトを返します。 ここで返されたpinataUrlは、スマートコントラクトのmint関数のtokenURIの入力として使用されます。
  • このPOSTリクエストが失敗した場合、この関数は、successブール値がfalseで、エラーを伝える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 //エラー処理
3 if (url.trim() == "" || name.trim() == "" || description.trim() == "") {
4 return {
5 success: false,
6 status: "❗ミントする前にすべてのフィールドに入力してください。",
7 }
8 }
9}
すべて表示

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

メタデータを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 //エラー処理
3 if (url.trim() == "" || name.trim() == "" || description.trim() == "") {
4 return {
5 success: false,
6 status: "❗ミントする前にすべてのフィールドに入力してください。",
7 }
8 }
9
10 //メタデータを作成
11 const metadata = new Object()
12 metadata.name = name
13 metadata.image = url
14 metadata.description = description
15
16 //pinata呼び出しを作成
17 const pinataResponse = await pinJSONToIPFS(metadata)
18 if (!pinataResponse.success) {
19 return {
20 success: false,
21 status: "😢 tokenURIのアップロード中に問題が発生しました。",
22 }
23 }
24 const tokenURI = pinataResponse.pinataUrl
25}
すべて表示

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

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

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

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

mintNFT関数に最後に追加するのは、Ethereumのトランザクションです。

1//Ethereumトランザクションを設定
2const transactionParameters = {
3 to: contractAddress, // コントラクト公開時以外は必須
4 from: window.ethereum.selectedAddress, // ユーザーのアクティブなアドレスと一致する必要あり
5 data: window.contract.methods
6 .mintNFT(window.ethereum.selectedAddress, tokenURI)
7 .encodeABI(), //NFTスマートコントラクトを呼び出し
8}
9
10//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 "✅ Etherscanでトランザクションを確認: https://ropsten.etherscan.io/tx/" +
20 txHash,
21 }
22} catch (error) {
23 return {
24 success: false,
25 status: "😥 問題が発生しました: " + 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が開かれ、ユーザーにトランザクションの署名または拒否を求めます。
    • トランザクションが成功した場合、この関数は、ブール値successがtrueに設定され、status文字列がユーザーにトランザクションの詳細についてEtherscanを確認するよう促すJSONオブジェクトを返します。
    • トランザクションが失敗した場合、この関数は、successブール値がfalseに設定され、status文字列がエラーメッセージを伝えるJSONオブジェクトを返します。

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

1export const mintNFT = async (url, name, description) => {
2 //エラー処理
3 if (url.trim() == "" || name.trim() == "" || description.trim() == "") {
4 return {
5 success: false,
6 status: "❗ミントする前にすべてのフィールドに入力してください。",
7 }
8 }
9
10 //メタデータを作成
11 const metadata = new Object()
12 metadata.name = name
13 metadata.image = url
14 metadata.description = description
15
16 //pinataピン留めリクエスト
17 const pinataResponse = await pinJSONToIPFS(metadata)
18 if (!pinataResponse.success) {
19 return {
20 success: false,
21 status: "😢 tokenURIのアップロード中に問題が発生しました。",
22 }
23 }
24 const tokenURI = pinataResponse.pinataUrl
25
26 //スマートコントラクトを読み込む
27 window.contract = await new web3.eth.Contract(contractABI, contractAddress) //loadContract();
28
29 //Ethereumトランザクションを設定
30 const transactionParameters = {
31 to: contractAddress, // コントラクト公開時以外は必須
32 from: window.ethereum.selectedAddress, // ユーザーのアクティブなアドレスと一致する必要あり
33 data: window.contract.methods
34 .mintNFT(window.ethereum.selectedAddress, tokenURI)
35 .encodeABI(), //NFTスマートコントラクトを呼び出す
36 }
37
38 //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 "✅ Etherscanでトランザクションを確認: https://ropsten.etherscan.io/tx/" +
48 txHash,
49 }
50 } catch (error) {
51 return {
52 success: false,
53 status: "😥 問題が発生しました: " + 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を稼働中のWebサイトにデプロイする

プロジェクトを稼働させてユーザーが使える準備ができましたでしょうか? ミンターを稼働中のWebサイトにデプロイする方法については、こちらのチュートリアル (opens in a new tab)をご覧ください。

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

ブロックチェーンの世界に旋風を巻き起こす

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

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

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

おそらく、dappを介してミントされたNFTをウォレットで披露したいと思うでしょうから、簡単なチュートリアルウォレットでNFTを表示する方法 (opens in a new tab)をぜひご覧ください。

そして、いつものように、何か質問があれば、Alchemy Discord (opens in a new tab)でお手伝いします。 このチュートリアルのコンセプトが、今後のプロジェクトでどのように応用されるのか楽しみでなりません。

最終更新: 2026年2月25日

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