Перейти к основному содержанию

Руководство по NFT Minter

Solidity
NFT
alchemy
смарт-контракты
интерфейс
Pinata
Intermediate
smudgil
6 октября 2021 г.
26 минута прочтения

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

Создавая NFT-минтер — простой пользовательский интерфейс, в который вы сможете ввести ссылку на свой цифровой актив, название и описание — вы узнаете, как:

  • Подключаться к MetaMask через ваш фронтенд-проект
  • Вызывать методы смарт-контракта из вашего интерфейса
  • Подписывать транзакции с помощью MetaMask

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

Изначально вы уже должны иметь начальное понимание React — знать, как работают компоненты, реквизиты, useState/useEffect и вызов базовых функций. Если вы никогда раньше не слышали ни об одном из этих терминов, рекомендуем ознакомиться с этим руководством «Введение в React»opens in a new tab. Для тех, кто лучше воспринимает информацию визуально, мы настоятельно рекомендуем эту превосходную серию видеороликов «Полное руководство по современному React»opens in a new tab от Net Ninja.

И если вы еще этого не сделали, вам обязательно понадобится создать аккаунт Alchemy, чтобы пройти это руководство, а также создать что-нибудь на блокчейне. Зарегистрируйте бесплатный аккаунт здесьopens in a new tab.

Без лишних слов, давайте начнем!

Создание NFT для начинающих

Прежде чем мы начнем изучать какой-либо код, важно понять, как работает создание NFT. Он состоит из двух шагов:

Публикация смарт-контракта NFT в блокчейне Ethereum

Самая большая разница между двумя стандартами смарт-контрактов NFT заключается в том, что ERC-1155 является стандартом с несколькими токенами и включает в себя пакетную функциональность, тогда как ERC-721 является стандартом на один токен и, следовательно, поддерживает передачу только одного токена за раз.

Вызов функции минтинга

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

Метаданные NFT на самом деле оживляют его, позволяя ему иметь такие свойства, как имя, описание, изображение (или другой цифровой актив) и другие атрибуты. Вот пример tokenURIopens in a new tab, который содержит метаданные NFT.

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

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

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

Клонируйте стартовые файлы

Для начала перейдите в репозиторий nft-minter-tutorial на GitHubopens in a new tab, чтобы получить стартовые файлы для этого проекта. Клонируйте этот репозиторий в свою локальную среду.

Открыв этот клонированный репозиторий nft-minter-tutorial, вы заметите, что он содержит две папки: minter-starter-files и nft-minter.

  • minter-starter-files содержит стартовые файлы (по сути, пользовательский интерфейс React) для этого проекта. В этом руководстве мы будем работать в этой директории, и вы научитесь оживлять этот пользовательский интерфейс, подключив его к вашему кошельку Ethereum и смарт-контракту NFT.
  • 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, где вы увидите интерфейс нашего проекта. Он должен состоять из трех полей: место для ввода ссылки на ваш NFT-ресурс, имя вашего NFT и описание.

Если вы попытаетесь нажать кнопки «Connect Wallet» или «Mint NFT», вы заметите, что они не работают - это потому, что нам все еще нужно их запрограммировать! :)

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

ПРИМЕЧАНИЕ: Убедитесь, что вы находитесь в папке minter-starter-files, а не в nft-minter!

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

В верхней части этого файла находятся переменные состояния, которые мы будем обновлять после определенных событий.

1//Переменные состояния
2const [walletAddress, setWallet] = useState("")
3const [status, setStatus] = useState("")
4const [name, setName] = useState("")
5const [description, setDescription] = useState("")
6const [url, setURL] = useState("")

Никогда не слышали о переменных состояния React или перехватах состояния? Ознакомьтесь с этойopens in a new tab документацией.

Вот что представляет собой каждая из переменных:

  • walletAddress — строка, в которой хранится адрес кошелька пользователя
  • status — строка, содержащая сообщение для отображения в нижней части пользовательского интерфейса
  • name — строка, в которой хранится имя NFT
  • description — строка, в которой хранится описание NFT
  • url — строка, которая является ссылкой на цифровой актив NFT

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

1useEffect(async () => {
2 //TODO: реализовать
3}, [])
4
5const connectWalletPressed = async () => {
6 //TODO: реализовать
7}
8
9const onMintPressed = async () => {
10 //TODO: реализовать
11}
Показать все
  • useEffectopens in a new tab — это хук React, который вызывается после рендеринга вашего компонента. Поскольку в него передается пустой массив [] в качестве свойства (см. строку 3), он будет вызываться только при первом рендеринге компонента. Здесь мы вызовем наш прослушиватель кошелька и другую функцию кошелька, чтобы обновить наш пользовательский интерфейс и указать, подключен ли он.
  • connectWalletPressed — эта функция будет вызываться для подключения кошелька MetaMask пользователя к нашему децентрализованному приложению.
  • onMintPressed — эта функция будет вызываться для минтинга NFT пользователя.

Ближе к концу этого файла находится пользовательский интерфейс нашего компонента. Если вы внимательно изучите этот код, вы заметите, что мы обновляем наши переменные состояния url, name и description, когда изменяется ввод в соответствующих текстовых полях.

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

1//Пользовательский интерфейс нашего компонента
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">🧙‍♂️ Минтер NFT от Alchemy</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.

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

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

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

Настройте ваш кошелек Ethereum

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

Загрузите MetaMask

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

Вы можете бесплатно скачать и создать аккаунт MetaMask здесьopens in a new tab. Если вы создаете аккаунт или если у вас уже есть аккаунт, обязательно переключитесь на «Тестовую сеть Ropsten» в правом верхнем углу \ (чтобы мы не имели дело с реальными деньгами ).

Получите ether из крана

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

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

Чтобы дважды проверить наш баланс, давайте сделаем запрос eth_getBalanceopens in a new tab, используя инструмент для составления запросов от Alchemyopens in a new tab. Так сумма Eth вернется в наш кошелек. После ввода адреса вашего аккаунта MetaMask и нажатия «Send Request» вы должны увидеть примерно такой ответ:

1{"jsonrpc": "2.0", "id": 0, "result": "0xde0b6b3a7640000"}

ПРИМЕЧАНИЕ: этот результат указан в wei, а не в ETH. Wei это наименьшая единица измерения эфира. Преобразование wei в eth: 1 eth = 10¹⁸ wei. Итак, если мы преобразуем 0xde0b6b3a7640000 в десятичное число, мы получим 1*10¹⁸, что равно 1 eth.

Фух! Наши ненастоящие деньги уже все там!

Подключите MetaMask к вашему пользовательскому интерфейсу

Теперь, когда наш кошелек MetaMask настроен, давайте подключим к нему наше децентрализованное приложение!

Поскольку мы хотим придерживаться парадигмы MVCopens in a new tab, мы создадим отдельный файл, который будет содержать наши функции для управления логикой, данными и правилами нашего децентрализованного приложения, а затем передадим эти функции нашему фронтенду (компоненту Minter.js).

Функция connectWallet

Для этого создадим новую папку с именем utils в каталоге src и добавим в нее файл 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 Вы должны установить MetaMask, виртуальный кошелек Ethereum, в вашем
27 браузере.
28 </a>
29 </p>
30 </span>
31 ),
32 }
33 }
34}
Показать все

Давайте разберем, что делает этот код:

Сначала наша функция проверяет, включен ли window.ethereum в вашем браузере.

window.ethereum — это глобальный API, внедряемый MetaMask и другими поставщиками кошельков, который позволяет веб-сайтам запрашивать аккаунты пользователей Ethereum. В случае одобрения он может считывать данные из блокчейнов, к которым подключен пользователь, и предлагать пользователю подписывать сообщения и транзакции. Для получения дополнительной информации см. документацию MetaMaskopens in a new tab!

Если window.ethereum отсутствует, это означает, что MetaMask не установлен. В результате возвращается объект JSON, где возвращаемый address представляет собой пустую строку, а объект status JSX сообщает, что пользователь должен установить MetaMask.

Большинство написанных нами функций будут возвращать объекты JSON, которые мы можем использовать для обновления переменных состояния и пользовательского интерфейса.

Если же window.ethereum присутствует, то здесь начинается самое интересное.

Используя цикл try/catch, мы попытаемся подключиться к MetaMask, вызвав window.ethereum.request({ method: "eth_requestAccounts" });opens in a new tab. Вызов этой функции откроет MetaMask в браузере, и пользователю будет предложено подключить свой кошелек к вашему децентрализованному приложению.

  • Если пользователь решит подключиться, method: "eth_requestAccounts" вернет массив, содержащий все адреса аккаунтов пользователя, подключенных к децентрализованному приложению. В целом, наша функция connectWallet вернет объект JSON, который содержит первый address в этом массиве (см. строку 9) и сообщение status, предлагающее пользователю написать сообщение в смарт-контракт.
  • Если пользователь отклоняет подключение, то объект JSON будет содержать пустую строку для возвращаемого address и сообщение status, которое отражает, что пользователь отклонил подключение.

Добавление функции connectWallet в компонент пользовательского интерфейса Minter.js

Теперь, когда мы написали функцию connectWallet, давайте подключим ее к нашему компоненту Minter.js..

Во-первых, нам нужно импортировать нашу функцию в наш файл Minter.js, добавив import { connectWallet } from "./utils/interact.js"; вверху файла Minter.js. Ваши первые 11 строк Minter.js теперь должны выглядеть так:

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}

Заметили, как большая часть нашей функциональности абстрагирована от компонента Minter.js в файл interact.js? Это значит, что мы соответствуем парадигме M-V-С!

В connectWalletPressed мы просто делаем вызов await к нашей импортированной функции connectWallet и, используя ее ответ, обновляем наши переменные status и walletAddress через их хуки состояния.

Теперь давайте сохраним оба файла Minter.js и interact.js и протестируем наш пользовательский интерфейс.

Откройте браузер на локальном хосте: 3000 и нажмите кнопку «Connect Wallet» в правом верхнем углу страницы.

Если у вас установлен MetaMask, вам будет предложено подключить кошелек к вашему децентрализованному приложению. Примите приглашение на подключение.

Вы должны увидеть, что кнопка кошелька теперь показывает, что ваш адрес подключен.

Далее попробуйте обновить страницу... это странно. Кнопка нашего кошелька предлагает нам подключить MetaMask, хотя он уже подключен...

Однако не стоит беспокоиться! Мы легко можем это исправить, реализовав функцию под названием getCurrentWalletConnected, которая проверит, подключен ли уже адрес к нашему децентрализованному приложению, и соответствующим образом обновит наш пользовательский интерфейс!

Функция 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 Вы должны установить MetaMask, виртуальный кошелек Ethereum, в вашем
33 браузере.
34 </a>
35 </p>
36 </span>
37 ),
38 }
39 }
40}
Показать все

Этот код очень похож на функцию connectWallet, которую мы написали ранее.

Основное отличие состоит в том, что вместо вызова метода eth_requestAccounts, который открывает MetaMask для подключения пользователя к кошельку, мы вызываем метод eth_accounts, который просто возвращает массив с адресами MetaMask, которые в данный момент подключены к нашему децентрализованному приложению.

Чтобы увидеть эту функцию в действии, давайте вызовем ее в функции useEffect нашего компонента Minter.js.

Как и в случае с 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}, [])

Обратите внимание: мы используем ответ на наш вызов getCurrentWalletConnected для обновления наших переменных состояния walletAddress и status.

После добавления этого кода попробуйте обновить окно браузера. На кнопке должно быть указано, что вы подключены, и показан предварительный просмотр адреса вашего подключенного кошелька — даже после обновления!

Реализация addWalletListener

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

В ваш файл 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 Вы должны установить MetaMask, виртуальный кошелек Ethereum, в вашем браузере.
18 </a>
19 </p>
20 )
21 }
22}
Показать все

Давайте быстро разберем, что здесь происходит:

  • Сначала наша функция проверяет, включен ли window.ethereum (т. е. установлен ли MetaMask).
    • Если нет, мы просто устанавливаем для нашей переменной состояния status строку JSX, которая предлагает пользователю установить MetaMask.
    • Если он включен, мы устанавливаем прослушиватель window.ethereum.on("accountsChanged") в строке 3, который прослушивает изменения состояния в кошельке MetaMask, включая подключение пользователем дополнительного аккаунта к децентрализованному приложению, переключение аккаунтов или отключение аккаунта. Если подключен хотя бы один аккаунт, переменная состояния 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, о которых мы только что говорили в шаге 0 этого руководства — они оживляют NFT, позволяя ему иметь такие свойства, как цифровой актив, имя, описание и другие характеристики.

Нам нужно будет настроить эти метаданные в виде объекта JSON и сохранить их, чтобы мы могли передать их в качестве параметра tokenURI при вызове функции mintNFT нашего смарт-контракта.

Текст в полях «Ссылка на актив», «Имя», «Описание» будет содержать различные свойства метаданных нашего NFT. Мы отформатируем эти метаданные в виде объекта JSON, но есть несколько вариантов хранения этого объекта JSON:

  • Мы могли бы хранить его в блокчейне Ethereum; однако это будет очень дорого.
  • Мы могли бы хранить его на централизованном сервере, например AWS или Firebase. Но это подорвет суть нашей децентрализации.
  • Мы могли бы использовать IPFS, децентрализованный протокол и одноранговую сеть для хранения и обмена данными в распределенной файловой системе. Поскольку это децентрализованный и бесплатный протокол, он подходит нам идеально!

Для хранения наших метаданных в IPFS мы будем использовать Pinataopens in a new tab, удобный API и набор инструментов IPFS. На следующем шаге мы объясним, как именно это сделать!

Использование Pinata для закрепления ваших метаданных в IPFS

Если у вас нет аккаунта Pinataopens in a new tab, зарегистрируйте бесплатный аккаунт здесьopens in a new tab и выполните действия по подтверждению электронной почты и аккаунта.

Создайте свой ключ API Pinata

Перейдите на страницу https://pinata.cloud/keysopens in a new tab, затем нажмите кнопку «New Key» (Новый ключ) вверху, включите виджет Admin и дайте имя своему ключу.

Затем у вас появится всплывающее окно с информацией о вашем API. Обязательно поместите его в безопасное место.

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

Создайте файл .env

Мы можем безопасно хранить ключ Pinata и секрет в файле среды. Давайте установим пакет dotenvopens in a new tab в вашем каталоге проекта.

Откройте новую вкладку в своем терминале (отдельную от той, на которой запущен локальный хост) и убедитесь, что вы находитесь в папке minter-starter-files, затем выполните следующую команду в своем терминале:

1npm install dotenv --save

Далее создайте файл .env в корневом каталоге minter-starter-files, введя в командной строке следующее:

1vim .env

При этом ваш файл .env откроется в vim (текстовом редакторе). Для сохранения нажмите «esc» + «:» + «q» на клавиатуре в указанном порядке.

Затем в VSCode перейдите к файлу .env и добавьте в него ключ API Pinata и секрет API, как показано ниже:

1REACT_APP_PINATA_KEY = <ключ-api-pinata>
2REACT_APP_PINATA_SECRET = <секрет-api-pinata>

Сохраните файл, и после этого вы сможете приступить к написанию функции для загрузки метаданных JSON в IPFS!

Реализация pinJSONToIPFS

К счастью для нас, Pinata имеет API специально для загрузки данных JSON в IPFSopens in a new tab и удобный пример на JavaScript с axios, который мы можем использовать с небольшими изменениями.

Давайте создадим в вашей папке utils еще один файл с именем pinata.js, а затем импортируем секрет и ключ Pinata из файла .env следующим образом:

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 //делаем POST-запрос axios к 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}
Показать все

Так что же именно делает этот код?

Во-первых, он импортирует axiosopens in a new tab, HTTP-клиент на основе промисов для браузера и node.js, который мы будем использовать для отправки запроса в Pinata.

Затем у нас есть асинхронная функция pinJSONToIPFS, которая принимает JSONBody в качестве входных данных, а также ключ и секрет API Pinata в своем заголовке, и все это для выполнения POST-запроса к их pinJSONToIPFS API.

  • Если этот POST-запрос оказывается успешным, наша функция возвращает объект JSON с логическим значением success как true и pinataUrl, где были закреплены наши метаданные. Мы будем использовать этот возвращенный pinataUrl в качестве входных данных tokenURI для функции минтинга нашего смарт-контракта.
  • Если этот запрос на отправку завершается неудачей, наша функция возвращает объект JSON с логическим значением success как false и строку message, которая передает нашу ошибку.

Как и в случае с типами возвращаемых значений функции connectWallet, мы возвращаем объекты JSON, чтобы можно было использовать их параметры для обновления переменных состояния и пользовательского интерфейса.

Загрузите свой смарт-контракт

Теперь, когда у нас есть способ загрузить метаданные NFT в IPFS с помощью функции pinJSONToIPFS, нам нужно создать способ загрузки экземпляра нашего смарт-контракта, чтобы мы могли вызвать его функцию mintNFT.

Как мы упоминали ранее, в этом руководстве мы будем использовать этот существующий смарт-контракт NFTopens in a new tab; однако, если вы хотите узнать, как мы его создали, или создать его самостоятельно, мы настоятельно рекомендуем вам ознакомиться с другим нашим руководством, «Как создать NFT»opens in a new tab.

ABI контракта

Если вы внимательно изучили наши файлы, то заметили, что в нашем каталоге src есть файл contract-abi.json. ABI необходим для указания того, какую функцию будет вызывать контракт, а также для обеспечения того, чтобы функция возвращала данные в ожидаемом формате.

Нам также понадобится ключ API Alchemy и API Alchemy Web3 для подключения к блокчейну Ethereum и загрузки нашего смарт-контракта.

Создайте свой ключ API Alchemy

Если у вас еще нет аккаунта Alchemy, зарегистрируйтесь бесплатно здесь.opens in a new tab

Как только регистрация в Alchemy завершена, можно создать приложение и таким образом сгенерировать ключ API. Это позволит нам делать запросы к тестовой сети Ropsten.

Перейдите на страницу «Create App» (Создать приложение) в вашей панели инструментов Alchemy, наведя курсор на «Apps» (Приложения) в навигационной панели и нажав «Create App» (Создать приложение).

Назовите свое приложение, которое мы выбрали, «My First NFT!», дайте краткое описание, выберите «Staging» для среды, используемой для учета вашего приложения, и выберите «Ropsten» для своей сети.

Нажмите "Create app" и все готово! Ваше приложение должно появиться в таблице ниже.

Отлично, теперь, когда мы создали URL-адрес API HTTP Alchemy, скопируйте его в буфер обмена...

…а затем добавьте его в наш файл .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 контракта и ключ API Alchemy, мы готовы загрузить наш смарт-контракт с помощью Alchemy Web3opens in a new tab.

Настройка конечной точки Alchemy Web3 и контракта

Во-первых, если у вас его еще нет, вам необходимо установить Alchemy Web3opens in a new tab, перейдя в домашний каталог: nft-minter-tutorial в терминале:

1cd ..
2npm install @alch/alchemy-web3

Теперь вернемся к нашему файлу interact.js. В верхней части файла добавьте следующий код, чтобы импортировать ключ Alchemy из файла .env и настроить конечную точку Alchemy Web3:

1require("dotenv").config()
2const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY
3const { createAlchemyWeb3 } = require("@alch/alchemy-web3")
4const web3 = createAlchemyWeb3(alchemyKey)

Alchemy Web3opens in a new tab — это оболочка для Web3.jsopens in a new tab, предоставляющая расширенные методы API и другие важные преимущества, облегчающие жизнь веб-разработчика. Он разработан с требованием минимальной настройки, поэтому вы можете сразу начать использовать его в своем приложении!

Далее давайте добавим в наш файл 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"

После того, как мы создали и то, и другое, мы готовы начать кодировать нашу функцию монетного двора!

Реализация функции mintNFT

Внутри вашего файла interact.js давайте определим нашу функцию mintNFT, которая, как следует из названия, будет минтить наш NFT.

Поскольку мы будем выполнять множество асинхронных вызовов (Pinata для закрепления наших метаданных в IPFS, Alchemy Web3 для загрузки нашего смарт-контракта и MetaMask для подписи наших транзакций), наша функция также будет асинхронной.

Тремя входными данными для нашей функции будут url нашего цифрового актива, name и description. Добавьте следующую сигнатуру функции под функцией 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}
Показать все

По сути, если какой-либо из входных параметров оказывается пустой строкой, мы возвращаем объект JSON, в котором логическое значение success имеет значение false, а строка status сообщает, что все поля в нашем пользовательском интерфейсе должны быть заполнены.

Загрузка метаданных в IPFS

Как только мы узнаем, что наши метаданные отформатированы правильно, следующим шагом будет обернуть их в объект JSON и загрузить в IPFS через написанный нами pinJSONToIPFS!

Для этого нам сначала нужно импортировать функцию pinJSONToIPFS в наш файл interact.js. В самом верху interact.js добавим:

1import { pinJSONToIPFS } from "./pinata.js"

Напомним, что pinJSONToIPFS принимает тело JSON. Поэтому, прежде чем мы вызовем его, нам нужно отформатировать наши параметры url, name и description в объект JSON.

Давайте обновим наш код, чтобы создать объект JSON с именем metadata, а затем выполним вызов pinJSONToIPFS с этим параметром metadata:

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. Затем мы анализируем этот объект на наличие ошибок.

Если возникает ошибка, мы возвращаем объект JSON, где логическое значение success имеет значение false, а наша строка status сообщает, что наш вызов не удался. В противном случае мы извлекаем pinataURL из pinataResponse и сохраняем его как переменную tokenURI.

Теперь пришло время загрузить наш смарт-контракт с помощью API Alchemy Web3, который мы инициализировали в верхней части нашего файла. Добавьте следующую строку кода в конец функции 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}
Показать все

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

  • Сначала мы настраиваем параметры наших транзакций.
    • to указывает адрес получателя (наш смарт-контракт)
    • from указывает на подписавшего транзакцию (адрес пользователя, подключенного к MetaMask: window.ethereum.selectedAddress)
    • data содержит вызов метода mintNFT нашего смарт-контракта, который получает наш tokenURI и адрес кошелька пользователя, window.ethereum.selectedAddress, в качестве входных данных
  • Затем мы делаем ожидающий вызов, window.ethereum.request, где просим MetaMask подписать транзакцию. Обратите внимание: в этом запросе мы указываем наш метод ETH (eth_SentTransaction) и передаем наши transactionParameters. На этом этапе MetaMask откроется в браузере и предложит пользователю подписать или отклонить транзакцию.
    • Если транзакция прошла успешно, функция вернет объект JSON, в котором логическое значение success установлено в true, а строка status предлагает пользователю проверить Etherscan для получения дополнительной информации о его транзакции.
    • Если транзакция не удалась, функция вернет объект JSON, в котором для логического значения success установлено значение false, а строка status передает сообщение об ошибке.

В целом наша функция 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, чтобы выполнить вызов await к импортированной функции mintNFT, и обновите переменную состояния status, чтобы отразить, была ли наша транзакция успешной или неудачной:

1const onMintPressed = async () => {
2 const { status } = await mintNFT(url, name, description)
3 setStatus(status)
4}

Развертывание вашего NFT на действующем веб-сайте

Готовы опубликовать свой проект, чтобы пользователи могли с ним взаимодействовать? Ознакомьтесь с этим руководствомopens in a new tab по развертыванию вашего минтера на действующем веб-сайте.

Самый последний шаг...

Покорите мир блокчейна

Шучу, вы дошли до конца руководства!

Подводя итог, создав компонент NFT, вы научились:

  • Подключаться к MetaMask через ваш фронтенд-проект
  • Вызывать методы смарт-контракта из вашего интерфейса
  • Подписывать транзакции с помощью MetaMask

Вероятно, вы хотели бы иметь возможность демонстрировать NFT, выпущенные через ваше децентрализованное приложение, в своем кошельке — поэтому обязательно ознакомьтесь с нашим кратким руководством «Как просмотреть свой NFT в своем кошельке»opens in a new tab!

И, как всегда, если у вас есть вопросы, мы готовы помочь в Discord-канале Alchemyopens in a new tab. Нам не терпится увидеть, как вы примените концепции из этого руководства в своих будущих проектах!

Последнее обновление страницы: 25 февраля 2026 г.

Было ли это руководство полезным?