Учебное пособие по NFT Minter

Как создать DApp для выпуска NFT токенов
В этом руководстве вы создадите минтер NFT и узнаете, как создать полнофункциональное приложение с полным стеком, подключив свой смарт-контракт к веб-интерфейсу React с помощью инструментов Metamask и Web3.
Одна из самых больших проблем для разработчиков, имеющих опыт работы в Web2, - это выяснить, как подключить ваш смартконтакт к внешнему проекту и взаимодействовать с ним.

Создав NFT minter - простой пользовательский интерфейс, в котором вы можете ввести ссылку на свой цифровой актив, название и описание - вы научитесь:
  • Подключитесь к Metamask через свой интерфейсный проект
  • Вызов методов смарт-контракта из вашего интерфейса
  • Подписывать транзакции с помощью Metamask

В этом уроке мы будем использовать React качестве нашего внешнего фреймворка. Поскольку это руководство в первую очередь ориентировано на разработку Web3, мы не будем тратить много времени на изучение основ React. Вместо этого мы сосредоточимся на добавлении функциональности в наш проект.

В качестве предварительного условия вы должны иметь понимание React на начальном уровне:
- знать, как работают компоненты, свойства, useState / useEffect и вызов основных функций.

Если вы никогда раньше не слышали ни одного из этих терминов, возможно, вы захотите ознакомиться с этим учебным курсом Intro to React.

Мы настоятельно рекомендуем эту отличную серию видеороликов Full Modern React Tutorial от Net Ninja.

Шаг 0: создание NFT 101

Прежде чем мы даже начнем изучать какой-либо код, важно понять, как работает NFT. Он состоит из двух этапов:
1) Вы публикуете смарт-контракт NFT в блокчейне Ethereum. Обычно это смарт-контракт ERC-721 или ERC-1155.
Самая большая разница между двумя стандартами интеллектуальных контактов NFT заключается в том, что ERC-1155 является стандартом с несколькими токенами и включает в себя пакетные функции, тогда как ERC-721 является стандартом с одним токеном и, следовательно, поддерживает передачу только одного токена за раз.
2) Вы вызываете функцию чеканки смарт-контракта NFT для чеканки NFT.

Минтинг - это просто акт публикации уникального экземпляра вашего невзаимозаменяемого токена в блокчейне.

Обычно эта функция чеканки требует, чтобы вы передали две переменные в качестве параметров, во-первых , который указывает адрес, который будет получать ваш недавно созданный NFT, а во-вторых, NFT, строку recipienttokenURI, которая преобразуется в документ JSON, описывающий метаданные NFT.

Метаданные NFT - это действительно то, что оживляет их, позволяя им иметь такие свойства, как имя, описание, изображение (или другой цифровой актив) и другие атрибуты. Вот пример tokenURI, который содержит метаданные NFT.
В этом руководстве мы сосредоточимся на части 2, вызывая существующую функцию чеканки смарт-контрактов NFT с помощью нашего пользовательского интерфейса React.

Вот ссылка на ERC-721 NFT смарт контракта мы будем называть в этом учебнике. Если вы хотите узнать, как мы сделали это, мы настоятельно рекомендуем вам проверить наш другой учебник, «Как создать NFT»


Круто, теперь, когда мы понимаем, как работает NFT, давайте клонируем наши стартовые файлы!

Шаг 1. Клонируйте начальные файлы

Сначала перейдите в репозиторий github nft-minter-tutorial, чтобы получить стартовые файлы для этого проекта. Клонируйте этот репозиторий в свою локальную среду.
Не знаете, как клонировать репозиторий? Ознакомьтесь с этим руководством на Github.
Когда вы откроете этот клонированный репозиторий, вы заметите, что он содержит две папки:
  • minter-starter-files содержит стартовые файлы (по сути, пользовательский интерфейс React) для этого проекта. В этом руководстве мы будем работать в этом каталоге, пока вы узнаете, как воплотить в жизнь этот пользовательский интерфейс, подключив его к своему кошельку Ethereum и смарт-контракту NFT.
  • nft-minter содержит весь завершенный учебник и может быть использован в качестве справки, если вы застряли.

Затем откройте свою копию в своем любимом редакторе кода (в Alchemy мы большие поклонники VSCode), а затем перейдите в свою папку :src

Будем работать в папке "src"

Весь код, который мы напишем, будет находиться в папке.


Мы будем редактировать компонент Minter.js и писать дополнительные файлы javascript, чтобы дать нашему проекту функциональность Web3.src

Шаг 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
  • предоставьте описание.
Как должен выглядеть ваш пользовательский интерфейс
Если вы попытаетесь нажать кнопки «Подключить кошелек» или «Mint NFT», вы заметите, что они не работают - это потому, что нам все еще нужно запрограммировать их функциональность! :)

Компонент Minter.js

Вернемся в папку в нашем редакторе и откроем файл src Minter.js.

Очень важно, чтобы мы понимали все в этом файле, так как это основной компонент React, над которым мы будем работать.

ПРИМЕЧАНИЕ
Убедитесь, что вы находитесь в minter-starter-files папке, а не nft-minterfolder!
В верхней части нашего файла находятся переменные состояния, которые мы будем обновлять после определенных событий.

//State variables
const [walletAddress, setWallet] = useState("");
const [status, setStatus] = useState("");
const [name, setName] = useState("");
const [description, setDescription] = useState("");
const [url, setURL] = useState("");
Никогда не слышали о переменных состояния React или хуках состояния? Check out these docs.
Вот что представляет собой каждая из переменных:
walletAddress - строка, в которой хранится адрес кошелька пользователя
status - строка, которая содержит сообщение, отображаемое в нижней части пользовательского интерфейса
name - строка, в которой хранится имя NFT
description - строка, в которой хранится описание NFT
url - строка, которая является ссылкой на цифровой актив NFT.

После переменных состояния вы увидите три нереализованные функции:
  • useEffect,
  • connectWalletPressed
  • onMintPressed.

Вы заметите, что все эти функции асинхронные, потому что мы будем выполнять в них асинхронные вызовы API!
Их названия созвучны с их функциональностью:

useEffect(async () => { //TODO: implement
    
  }, []);

  const connectWalletPressed = async () => { //TODO: implement
   
  };

  const onMintPressed = async () => { //TODO: implement
    
  };
  • useEffect - это хук React, который вызывается после рендеринга вашего компонента. Поскольку в него передан пустой массив [] prop (см. строку 3), он будет вызван только при первом рендеринге компонента. Здесь мы вызовем wallet listener и еще одну функцию кошелька, чтобы обновить наш пользовательский интерфейс, чтобы отразить, подключен ли уже кошелек.
  • connectWalletPressed - эта функция будет вызвана для подключения кошелька Metamask пользователя к нашему dApp.
  • onMintPressed - эта функция будет вызвана, чтобы отчеканить пользовательский NFT.
В конце этого файла находится пользовательский интерфейс нашего компонента. Если вы внимательно просканируете этот код, то заметите, что мы обновляем переменные состояния url, name и description при изменении ввода в соответствующих текстовых полях.


Вы также увидите, что connectWalletPressed и onMintPressedare вызывается, когда кнопки с идентификаторами mintButton и walletButton нажимаются соответственно.

//the UI of our component
  return (
    <div className="Minter">
      <button id="walletButton" onClick={connectWalletPressed}>
        {walletAddress.length > 0 ? (
          "Connected: " +
          String(walletAddress).substring(0, 6) +
          "..." +
          String(walletAddress).substring(38)
        ) : (
          <span>Connect Wallet</span>
        )}
      </button>

      <br></br>
      <h1 id="title">????‍♂️ Alchemy NFT Minter</h1>
      <p>
        Simply add your asset's link, name, and description, then press "Mint."
      </p>
      <form>
        <h2>???? Link to asset: </h2>
        <input
          type="text"
          placeholder="e.g. https://gateway.pinata.cloud/ipfs/<hash>"
          onChange={(event) => setURL(event.target.value)}
        />
        <h2>???? Name: </h2>
        <input
          type="text"
          placeholder="e.g. My first NFT!"
          onChange={(event) => setName(event.target.value)}
        />
        <h2>✍️ Description: </h2>
        <input
          type="text"
          placeholder="e.g. Even cooler than cryptokitties ;)"
          onChange={(event) => setDescription(event.target.value)}
        />
      </form>
      <button id="mintButton" onClick={onMintPressed}>
        Mint NFT
      </button>
      <p id="status">
        {status}
      </p>
    </div>
  );
Наконец, давайте рассмотрим, куда добавляется этот компонент Minter.

Если вы перейдете к файлу App.js, который является основным компонентом React, выступающим в качестве контейнера для всех остальных компонентов, вы увидите, что наш компонент Minter внедрен в строке 7.

В этом руководстве мы будем редактировать только файл Minter.js и добавлять файлы в папку src.

Теперь, когда мы понимаем, с чем работаем, давайте настроим наш кошелек Ethereum.

Шаг 3. Настройте свой кошелек Ethereum

Чтобы пользователи могли взаимодействовать с вашим смарт-контрактом, им необходимо подключить свой кошелек Ethereum к вашему dApp.

Скачать Metamask

В этом руководстве мы будем использовать Metamask, виртуальный кошелек в браузере, который используется для управления адресом вашей учетной записи Ethereum. Если вы хотите узнать больше о том, как работают транзакции в Ethereum, посетите эту страницу Ethereum Foundation.

Вы можете бесплатно скачать и создать учетную запись Metamask здесь . Когда вы создаете учетную запись или если у вас уже есть учетная запись, обязательно переключитесь на «Тестовую сеть Ropsten» в правом верхнем углу (чтобы мы не имели дело с реальными деньгами).

Образец кошелька Metamask

Добавить эфир из сборщика

Чтобы чеканить наши NFT (или подписать любые транзакции в блокчейне Ethereum), нам понадобится фальшивая Eth. Чтобы получить Eth, вы можете перейти к крану Ropsten и ввести адрес своей учетной записи Ropsten, а затем нажать «Отправить Ropsten Eth». Вскоре после этого вы должны увидеть Eth в своей учетной записи Metamask!

Проверьте свой баланс

Чтобы дважды проверить наш баланс, давайте сделаем запрос eth_getBalance с помощью инструмента композитора Alchemy . Это вернет количество Eth в нашем кошельке. После того, как вы введете адрес своей учетной записи Metamask и нажмете «Отправить запрос», вы должны увидеть такой ответ:


{"jsonrpc": "2.0", "id": 0, "result": "0xde0b6b3a7640000"}
ПРИМЕЧАНИЕ. Это результат в wei, а не в eth. Вэй используется как наименьшее обозначение эфира. Преобразование wei в eth: 1 eth = 10¹⁸ wei. Итак, если мы преобразуем 0xde0b6b3a7640000 в десятичное число, мы получим 1 * 10¹⁸, что равно 1 eth.
Уф! Все наши фальшивые деньги!

Шаг 4. Подключите Metamask к своему пользовательскому интерфейсу

Теперь, когда наш кошелек Metamask настроен, давайте подключим к нему наше приложение!
Поскольку мы хотим придерживаться парадигмы MVC , мы собираемся создать отдельный файл, содержащий наши функции для управления логикой, данными и правилами нашего dApp, а затем передать эти функции нашему интерфейсу (наш компонент Minter.js ).

Функция connectWallet

To do so, let's create a new folder called utilsin your src directory and add a file called interact.js inside it, which will contain all of our wallet and smart contract interaction functions.
In our interact.jsfile, we will write a connectWallet function, which we will then import and call in our Minter.js component.

In your interact.js file, add the following


export const connectWallet = async () => {
  if (window.ethereum) {
    try {
      const addressArray = await window.ethereum.request({
        method: "eth_requestAccounts",
      });
      const obj = {
        status: "???????? Write a message in the text-field above.",
        address: addressArray[0],
      };
      return obj;
    } catch (err) {
      return {
        address: "",
        status: "???? " + err.message,
      };
    }
  } else {
    return {
      address: "",
      status: (
        <span>
          <p>
            {" "}
            ????{" "}
            <a target="_blank" href={`https://metamask.io/download.html`}>
              You must install Metamask, a virtual Ethereum wallet, in your
              browser.
            </a>
          </p>
        </span>
      ),
    };
  }
};
Let's breakdown what this code does:
First, our function checks if it window.ethereum is enabled in your browser.
If window.ethereumis not present, then that means Metamask is not installed. This results in a JSON object being returned, where address returned is an empty string, and the status JSX object relays that the user must install Metamask.
Most of the functions we write will be returning JSON objects that we can use to update our state variables and UI.
Now if window.ethereumis present, then that's when things get interesting.
Using a try/catch loop, we'll try to connect to Metamask by callingwindow.ethereum.request({ method: "eth_requestAccounts" }); Calling this function will open up Metamask in the browser, whereby the user will be prompted to connect their wallet to your dApp.
  • If the user chooses to connect, method: "eth_requestAccounts" will return an array that contains all of the user's account addresses that connected to the dApp. Altogether, our connectWallet function will return a JSON object that contains the firstaddress in this array (see line 9) and a status message that prompts the user to write a message to the smart contract.
  • If the user rejects the connection, then the JSON object will contain an empty string for the address returned and a status message that reflects that the user rejected the connection.

Add connectWallet function to your Minter.js UI Component

Now that we've written this connectWallet function, let's connect it to our Minter.js. component.
First, we'll have to import our function into our Minter.js file by adding import { connectWallet } from "./utils/interact.js"; to the top of the Minter.js file. Your first 11 lines of Minter.js should now look like this:

import { useEffect, useState } from "react";
import { connectWallet } from "./utils/interact.js";

const Minter = (props) => {

  //State variables
  const [walletAddress, setWallet] = useState("");
  const [status, setStatus] = useState("");
  const [name, setName] = useState("");
  const [description, setDescription] = useState("");
  const [url, setURL] = useState("");
Then, inside our connectWalletPressed function, we'll call our imported connectWallet function, like so:

const connectWalletPressed = async () => {
    const walletResponse = await connectWallet();
    setStatus(walletResponse.status);
    setWallet(walletResponse.address);
  };
Notice how most of our functionality is abstracted away from our Minter.js component from the interact.js file? This is so we comply with the M-V-C paradigm!
In connectWalletPressed, we simply make an await call to our imported connectWallet function, and using its response, we update our status and walletAddress variables via their state hooks.
Now, let's save both files (Minter.js and interact.js) and test out our UI so far.
Open your browser on the http://localhost:3000/ page, and press the "Connect Wallet" button on the top right of the page.
If you have Metamask installed, you should be prompted to connect your wallet to your dApp. Accept the invitation to connect.
You should see that the wallet button now reflects that your address is connected! Yasssss ????
Next, try refreshing the page... this is strange. Our wallet button is prompting us to connect Metamask, even though it is already connected...
The problem on page reload
Don't worry though! We easily can fix that by implementing a function calledgetCurrentWalletConnected, which will check if an address is already connected to our dApp and update our UI accordingly!

The getCurrentWalletConnected function

In your interact.js file, add the following getCurrentWalletConnected function:

export const getCurrentWalletConnected = async () => {
  if (window.ethereum) {
    try {
      const addressArray = await window.ethereum.request({
        method: "eth_accounts",
      });
      if (addressArray.length > 0) {
        return {
          address: addressArray[0],
          status: "???????? Write a message in the text-field above.",
        };
      } else {
        return {
          address: "",
          status: "???? Connect to Metamask using the top right button.",
        };
      }
    } catch (err) {
      return {
        address: "",
        status: "???? " + err.message,
      };
    }
  } else {
    return {
      address: "",
      status: (
        <span>
          <p>
            {" "}
            ????{" "}
            <a target="_blank" href={`https://metamask.io/download.html`}>
              You must install Metamask, a virtual Ethereum wallet, in your
              browser.
            </a>
          </p>
        </span>
      ),
    };
  }
};
This code is very similar to the connectWallet function we just wrote earlier.
The main difference is that instead of calling the method eth_requestAccounts, which opens Metamask for the user to connect their wallet, here we call the method eth_accounts, which simply returns an array containing the Metamask addresses currently connected to our dApp.
To see this function in action, let's call it in the useEffect function of our Minter.js component.
Like we did for connectWallet, we must import this function from our interact.js file into our Minter.js file like so:

import { useEffect, useState } from "react";
import {
  connectWallet,
  getCurrentWalletConnected //import here
} from "./utils/interact.js";
Now, we simply call it in our useEffect function:

useEffect(async () => {
    const {address, status} = await getCurrentWalletConnected();
    setWallet(address)
    setStatus(status); 
}, []);
Notice, we use the response of our call to getCurrentWalletConnected to update our walletAddress and status state variables.
Once you've added this code, try refreshing our browser window. The button should say that you're connected, and show a preview of your connected wallet's address - even after you refresh! ????

Implement addWalletListener

The final step in our dApp wallet setup is implementing the wallet listener so our UI updates when our wallet's state changes, such as when the user disconnects or switches accounts.
In your Minter.js file, add a function addWalletListener that looks like the following:

function addWalletListener() {
  if (window.ethereum) {
    window.ethereum.on("accountsChanged", (accounts) => {
      if (accounts.length > 0) {
        setWallet(accounts[0]);
        setStatus("???????? Write a message in the text-field above.");
      } else {
        setWallet("");
        setStatus("???? Connect to Metamask using the top right button.");
      }
    });
  } else {
    setStatus(
      <p>
        {" "}
        ????{" "}
        <a target="_blank" href={`https://metamask.io/download.html`}>
          You must install Metamask, a virtual Ethereum wallet, in your
          browser.
        </a>
      </p>
    );
  }
}
Let's quickly break down what's happening here:
  • First, our function checks if window.ethereum is enabled (i.e. Metamask is installed).
  • If it's not, we simply set our status state variable to a JSX string that prompts the user to install Metamask.
  • If it is enabled, we set up the listener window.ethereum.on("accountsChanged") on line 3 that listens for state changes in the Metamask wallet, which include when the user connects an additional account to the dApp, switches accounts, or disconnects an account. If there is at least one account connected, the walletAddress state variable is updated as the first account in the accounts array returned by the listener. Otherwise, walletAddress is set as an empty string.
Finally, we must call it in our useEffect function:

useEffect(async () => {
    const {address, status} = await getCurrentWalletConnected();
    setWallet(address)
    setStatus(status);
    
    addWalletListener(); 
}, []);
And voila! We've completed programming all of our wallet functionality! Now that our wallet is set up, let's figure out how to mint our NFT!

Step 5: NFT Metadata 101

So remember the NFT metadata we just talked about in Step 0 of this tutorial—it brings an NFT to life, allowing it to have properties, such as a digital asset, name, description, and other attributes.
We're going to need to configure this metadata as a JSON object and store it, so we can pass it in as the tokenURI parameter when calling our smart contract's mintNFT function.
The text in the "Link to Asset", "Name", "Description" fields will comprise the different properties of our NFT's metadata. We'll format this metadata as a JSON object, but there are a couple options for where we can store this JSON object:
  • We could store it on the Ethereum blockchain; however, doing so would be SUPER expensive (we're talking upwards of hundreds of dollars) due to the nature of Ethereum. ❌
  • We could store it on a centralized server, like AWS or Firebase. But that would defeat our decentralization ethos. ❌
  • We could use IPFS, a decentralized protocol and peer-to-peer network for storing and sharing data in a distributed file system. As this protocol as decentralized and free, it is our best option! ✅
To store our metadata on IPFS, we will use Pinata, a convenient IPFS API and toolkit. In the next step, we'll explain exactly how to do this!

Step 6: Use Pinata to pin your metadata to IPFS

If you don't have a Pinata account, sign up for a free account here and complete the steps to verify your email and account.

Create your Pinata API key

Navigate to the https://pinata.cloud/keys page, then select the "New Key" button at the top, set the Admin widget as enabled, and name your key.
Create your Pinata API key
You'll then be shown a popup with your API info. Make sure to put this somewhere safe.
Make sure to save your API key and secret in a safe place
Now that our key is set up, let's add it to our project so we can use it.

Create a .env file

We can safely store our Pinata key and secret in an environment file. Let's install the dotenv package in your project directory.
Open up a new tab in your terminal (separate from the one running local host) and make sure you are in the minter-starter-files folder, then run the following command in your terminal:

npm install dotenv --save
Next, create a .env file in the root directory of your minter-starter-files by entering the following on your command line:

vim .env
This will pop open your .env file in vim (a text editor). To save it hit "esc" + ":" + "q" on your keyboard in that order.
Next, in VSCode, navigate to your .env file and add your Pinata API key and API secret to it, like so:

REACT_APP_PINATA_KEY = <pinata-api-key>
REACT_APP_PINATA_SECRET = <pinata-api-secret>
Save the file, and then you're ready to start writing the function to upload your JSON metadata to IPFS!

Implement pinJSONToIPFS

Fortunately for us, Pinata has an API specifically for uploading JSON data to IPFS and a convenient JavaScript with axios example that we can use, with some slight modifications.
In your utils folder, let's create another file called pinata.js and then import our Pinata secret and key from the .env file like so:

require('dotenv').config();
const key = process.env.REACT_APP_PINATA_KEY;
const secret = process.env.REACT_APP_PINATA_SECRET;
Next, paste the additional code from below into your pinata.js file. Don't worry, we'll break down what everything means!

require('dotenv').config();
const key = process.env.REACT_APP_PINATA_KEY;
const secret = process.env.REACT_APP_PINATA_SECRET;

const axios = require('axios');

export const pinJSONToIPFS = async(JSONBody) => {
    const url = `https://api.pinata.cloud/pinning/pinJSONToIPFS`;
    //making axios POST request to Pinata ⬇️
    return axios 
        .post(url, JSONBody, {
            headers: {
                pinata_api_key: key,
                pinata_secret_api_key: secret,
            }
        })
        .then(function (response) {
           return {
               success: true,
               pinataUrl: "https://gateway.pinata.cloud/ipfs/" + response.data.IpfsHash
           };
        })
        .catch(function (error) {
            console.log(error)
            return {
                success: false,
                message: error.message,
            }
           
    });
};
So what does this code do exactly?
First, it imports axios, a promise based HTTP client for the browser and node.js, which we will use to make a request to Pinata.
Then we have our asynchronous function pinJSONToIPFS, which takes a JSONBody as its input and the Pinata api key and secret in its header, all to make a POST request to theirpinJSONToIPFS API.
  • If this POST request is successful, then our function returns an JSON object with the success boolean as true and the pinataUrl where our metadata was pinned. We will use this pinataUrl returned as the tokenURI input to our smart contract's mint function.
  • If this post request fails, then our function returns an JSON object with the success boolean as false and a message string that relays our error.
As with our connectWalletfunction return types, we're returning JSON objects so we can use their parameters to update our state variables and UI.

Step 7: Load your smart contract

Now that we have a way to upload our NFT metadata to IPFS via our pinJSONToIPFS function, we're going to need a way to load an instance of our smart contract so we can call its mintNFT function.
As we mentioned earlier, in this tutorial we will be using this existing NFT smart contract; however, if you'd like to learn how we made it, or make one yourself, we highly recommend you check out our other tutorial, "How to Create an NFT."

The contract ABI

If you examined our files closely, you'll have noticed that in our src directory, there's a contract-abi.json file. An ABI is necessary for specifying which function a contract will invoke as well ensuring that the function will return data in the format you're expecting.
We're also going to need an Alchemy API key and the Alchemy Web3 API to connect to the Ethereum blockchain and load our smart contract.

Create your Alchemy API key

If you don't already have an Alchemy account, sign up for free here.
Once you’ve created an Alchemy account, you can generate an API key by creating an app. This will allow us to make requests to the Ropsten test network.
Navigate to the “Create App” page in your Alchemy Dashboard by hovering over “Apps” in the nav bar and clicking “Create App”
Create a new app
Name your app (we chose "My First NFT!"), offer a short description, select “Staging” for the Environment (used for your app bookkeeping), and choose “Ropsten” for your network.
Configure your app details
Click “Create app” and that’s it! Your app should appear in the table below.
Awesome so now that we've created our HTTP Alchemy API URL, copy it to your clipboard like so…
Copy your Alchemy API key
…and then let's add it to our .env file. Altogether, your .env file should look like this:

REACT_APP_PINATA_KEY = <pinata-key>
REACT_APP_PINATA_SECRET = <pinata-secret>
REACT_APP_ALCHEMY_KEY = https://eth-ropsten.alchemyapi.io/v2/<alchemy-key>
Now that we have our contract ABI and our Alchemy API key, we're ready to load our smart contract using Alchemy Web3.

Set up your Alchemy Web3 endpoint and contract

First, if you don't have it already, you'll need to install Alchemy Web3 by navigating to the home directory: nft-minter-tutorialin the terminal:

cd ..
npm install @alch/alchemy-web3
Next let's go back to our interact.jsfile. At the top of the file, add the following code to import your Alchemy key from your .env file and set up your Alchemy Web3 endpoint:

require('dotenv').config();
const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY;
const { createAlchemyWeb3 } = require("@alch/alchemy-web3");
const web3 = createAlchemyWeb3(alchemyKey); 
Alchemy Web3 is a wrapper around Web3.js, providing enhanced API methods and other crucial benefits to make your life as a web3 developer easier. It is designed to require minimal configuration so you can start using it in your app right away!
Next, let's add our contract ABI and contract address to our file.

require('dotenv').config();
const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY;
const { createAlchemyWeb3 } = require("@alch/alchemy-web3");
const web3 = createAlchemyWeb3(alchemyKey); 

const contractABI = require('../contract-abi.json')
const contractAddress = "0x4C4a07F737Bf57F6632B6CAB089B78f62385aCaE";
Once we have both of those, we're ready to start coding our mint function!

Step 8: Implement the mintNFT function

Inside your interact.js file, let's define our function, mintNFT , which eponymously will mint our NFT.
Because we will be making numerous asynchronous calls (to Pinata to pin our metadata to IPFS, Alchemy Web3 to load our smart contract, and Metamask to sign our transactions), our function will also be asynchronous.
The three inputs to our function will be the url of our digital asset, name, and description. Add the following function signature below the connectWallet function:

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

Input error handling

Naturally, it makes sense to have some sort of input error handling at the start of the function, so we exit this function if our input parameters aren't correct. Inside our function, let's add the following code:

export const mintNFT = async(url, name, description) => {
 //error handling
 if (url.trim() == "" || (name.trim() == "" || description.trim() == "")) { 
   return {
    success: false,
    status: "❗Please make sure all fields are completed before minting.",
   }
  }
}
Essentially, if any of the input parameters are an empty string, then we return a JSON object where the success boolean is false, and the status string relays that all fields in our UI must be complete.

Upload the metadata to IPFS

Once we know our metadata is formatted properly, the next step is to wrap it into a JSON object and upload it to IPFS via the pinJSONToIPFS we wrote!
To do so, we first we need to import the pinJSONToIPFSfunction into our interact.js file. At the very top of the interact.js, let's add:

import {pinJSONToIPFS} from './pinata.js'
Recall that pinJSONToIPFS takes in a JSON body. So before we make a call to it, we're going to need to format our url, name, and description parameters into a JSON object.
Let's update our code to create a JSON object called metadata and then make a call to pinJSONToIPFS with this metadata parameter:

export const mintNFT = async(url, name, description) => {
 //error handling
 if (url.trim() == "" || (name.trim() == "" || description.trim() == "")) { 
        return {
            success: false,
            status: "❗Please make sure all fields are completed before minting.",
        }
  }

  //make metadata
  const metadata = new Object();
  metadata.name = name;
  metadata.image = url;
  metadata.description = description;

  //make pinata call
  const pinataResponse = await pinJSONToIPFS(metadata);
  if (!pinataResponse.success) {
      return {
          success: false,
          status: "???? Something went wrong while uploading your tokenURI.",
      }
  } 
  const tokenURI = pinataResponse.pinataUrl;  
}
Notice, we store the response of our call to pinJSONToIPFS(metadata) in the pinataResponse object. Then, we parse this object for any errors.
If there's an error, we return a JSON object where the success boolean is false and our status string relays that our call failed. Otherwise, we extract the pinataURL from the pinataResponse and store it as our tokenURIvariable.
Now it's time to load our smart contract using the Alchemy Web3 API that we initialized at the top of our file. Add the following line of code to the bottom of the mintNFT function to set the contract at the window.contract global variable:

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

The last thing to add in our mintNFT function is our Ethereum transaction:

//set up your Ethereum transaction
 const transactionParameters = {
        to: contractAddress, // Required except during contract publications.
        from: window.ethereum.selectedAddress, // must match user's active address.
        'data': window.contract.methods.mintNFT(window.ethereum.selectedAddress, tokenURI).encodeABI()//make call to NFT smart contract 
 };

//sign the transaction via Metamask
 try {
    const txHash = await window.ethereum
        .request({
            method: 'eth_sendTransaction',
            params: [transactionParameters],
        });
    return {
        success: true,
        status: "✅ Check out your transaction on Etherscan: https://ropsten.etherscan.io/tx/" + txHash
    }
 } catch (error) {
    return {
        success: false,
        status: "???? Something went wrong: " + error.message
    }

 }
If you're already familiar with Ethereum transactions, you'll notice that the structure is pretty similar to what you've seen.
  • First, we set up our transactions parameters.
  • to specifies the recipient address (our smart contract)
  • from specifies the signer of the transaction (the user's connected address to Metamask: window.ethereum.selectedAddress)
  • data contains the call to our smart contract mintNFT method, which receives our tokenURI and the user's wallet address, window.ethereum.selectedAddress, as inputs
  • Then, we make an await call, window.ethereum.request, where we ask Metamask to sign the transaction. Notice, in this request, we're specifying our eth method (eth_SentTransaction) and passing in our transactionParameters. At this point, Metamask will open up in the browser, and prompt the user to sign or reject the transaction.
  • If the transaction is successful, the function will return a JSON object where the boolean success is set to true and the status string prompts the user to check out Etherscan for more information about their transaction.
  • If the transaction fails, the function will return a JSON object where the success boolean is set to false, and the status string relays the error message.
Altogether, our mintNFT function should look like this:

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

    //error handling
    if (url.trim() == "" || (name.trim() == "" || description.trim() == "")) { 
        return {
            success: false,
            status: "❗Please make sure all fields are completed before minting.",
        }
    }

    //make metadata
    const metadata = new Object();
    metadata.name = name;
    metadata.image = url;
    metadata.description = description;

    //pinata pin request
    const pinataResponse = await pinJSONToIPFS(metadata);
    if (!pinataResponse.success) {
        return {
            success: false,
            status: "???? Something went wrong while uploading your tokenURI.",
        }
    } 
    const tokenURI = pinataResponse.pinataUrl;  

    //load smart contract
    window.contract = await new web3.eth.Contract(contractABI, contractAddress);//loadContract();

    //set up your Ethereum transaction
    const transactionParameters = {
        to: contractAddress, // Required except during contract publications.
        from: window.ethereum.selectedAddress, // must match user's active address.
        'data': window.contract.methods.mintNFT(window.ethereum.selectedAddress, tokenURI).encodeABI() //make call to NFT smart contract 
    };

    //sign transaction via Metamask
    try {
        const txHash = await window.ethereum
            .request({
                method: 'eth_sendTransaction',
                params: [transactionParameters],
            });
        return {
            success: true,
            status: "✅ Check out your transaction on Etherscan: https://ropsten.etherscan.io/tx/" + txHash
        }
    } catch (error) {
        return {
            success: false,
            status: "???? Something went wrong: " + error.message
        }
    }
}
That's one giant function! Now, we just need to connect our mintNFT function to our Minter.js component...

Step 9: Connect mintNFT to our Minter.js frontend

Open up your Minter.js file and update the import { connectWallet, getCurrentWalletConnected } from "./utils/interact.js"; line at the top to be:

 import { connectWallet, getCurrentWalletConnected, mintNFT } from "./utils/interact.js";

Finally, implement the onMintPressed function to make await call to your imported mintNFTfunction and update the status state variable to reflect whether our transaction succeeded or failed:

const onMintPressed = async () => {
    const { status } = await mintNFT(url, name, description);
    setStatus(status);
};

Step 10: Deploy your NFT to a Live Website

Ready to take your project live for users to interact with? Check out the tutorial below for deploying your Minter to a live website:

Step 11: Take the blockchain world by storm

JK, you made it to the end of the tutorial! To recap, by building an NFT minter, you successfully learned how to:
  • Connect to Metamask via your frontend project
  • Call smart contract methods from your frontend
  • Sign transactions using Metamask
Presumably, you'd like to be able to show off the NFTs minted via your dApp in your wallet — so be sure to check out our quick tutorial How to View Your NFT in Your Wallet!
And, as always, if you have any questions, we're here to help in the Alchemy Discord. We can't wait to see how you apply the concepts from this tutorial to your future projects!

Bonus: Put your NFT Minter to work!

Ready to mint an NFT using your minter? Check out this demo video!
NOTE: The transactions sent through Metamask will use Metamask's default provider (not necessarily your Alchemy endpoint). If you'd like to send those transactions through Alchemy to unlock debugging tools and the Alchemy Notify suite, check out this guide for connecting Metamask to Alchemy.

Поддержите блог, перечислив небольшие чаевые

Если у вас нет кошелька MetaMask, вы можете получить его здесь
Made on
Tilda