Руководство по NFT Minter
Одна из самых больших проблем для разработчиков, имеющих опыт работы с 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-filesnpm 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— строка, в которой хранится имя NFTdescription— строка, в которой хранится описание NFTurl— строка, которая является ссылкой на цифровой актив NFT
После переменных состояния вы увидите три нереализованные функции: useEffect, connectWalletPressed и onMintPressed. Вы заметите, что все эти функции являются async (асинхронными), потому что в них мы будем делать асинхронные вызовы API! Их названия соответствуют их функциям:
1useEffect(async () => {2 //TODO: реализовать3}, [])45const connectWalletPressed = async () => {6 //TODO: реализовать7}89const 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>1415 <br></br>16 <h1 id="title">🧙♂️ Минтер NFT от Alchemy</h1>17 <p>18 Просто добавьте ссылку на ваш актив, имя и описание, а затем нажмите «Минтить».19 </p>20 <form>21 <h2>🖼 Ссылка на актив: </h2>22 <input23 type="text"24 placeholder="например, https://gateway.pinata.cloud/ipfs/<hash>"25 onChange={(event) => setURL(event.target.value)}26 />27 <h2>🤔 Имя: </h2>28 <input29 type="text"30 placeholder="например, Мой первый NFT!"31 onChange={(event) => setName(event.target.value)}32 />33 <h2>✍️ Описание: </h2>34 <input35 type="text"36 placeholder="например, Даже круче, чем CryptoKitties ;)"37 onChange={(event) => setDescription(event.target.value)}38 />39 </form>40 <button id="mintButton" onClick={onMintPressed}>41 Сминтить NFT42 </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 obj12 } catch (err) {13 return {14 address: "",15 status: "😥 " + err.message,16 }17 }18 } else {19 return {20 address: "",21 status: (22 <span>23 <p>24 {" "}25 🦊 <a target="_blank" href={`https://metamask.io/download`}>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";34const Minter = (props) => {56 //Переменные состояния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)56 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_KEY3const secret = process.env.REACT_APP_PINATA_SECRETЗатем вставьте дополнительный код ниже в файл pinata.js. Не волнуйтесь, сейчас мы разберем, что все означает!
1require("dotenv").config()2const key = process.env.REACT_APP_PINATA_KEY3const secret = process.env.REACT_APP_PINATA_SECRET45const axios = require("axios")67export const pinJSONToIPFS = async (JSONBody) => {8 const url = `https://api.pinata.cloud/pinning/pinJSONToIPFS`9 //делаем POST-запрос axios к Pinata ⬇️10 return axios11 .post(url, JSONBody, {12 headers: {13 pinata_api_key: key,14 pinata_secret_api_key: secret,15 },16 })17 .then(function (response) {18 return {19 success: true,20 pinataUrl:21 "https://gateway.pinata.cloud/ipfs/" + response.data.IpfsHash,22 }23 })24 .catch(function (error) {25 console.log(error)26 return {27 success: false,28 message: error.message,29 }30 })31}Показать всеТак что же именно делает этот код?
Во-первых, он импортирует 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_KEY3const { 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_KEY3const { createAlchemyWeb3 } = require("@alch/alchemy-web3")4const web3 = createAlchemyWeb3(alchemyKey)56const 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 }910 //создание метаданных11 const metadata = new Object()12 metadata.name = name13 metadata.image = url14 metadata.description = description1516 //вызов pinata17 const pinataResponse = await pinJSONToIPFS(metadata)18 if (!pinataResponse.success) {19 return {20 success: false,21 status: "😢 Что-то пошло не так при загрузке вашего tokenURI.",22 }23 }24 const tokenURI = pinataResponse.pinataUrl25}Показать всеОбратите внимание: мы сохраняем ответ на наш вызов 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//настройка транзакции Ethereum2const transactionParameters = {3 to: contractAddress, // Обязательно, кроме случаев публикации контракта.4 from: window.ethereum.selectedAddress, // должен совпадать с активным адресом пользователя.5 data: window.contract.methods6 .mintNFT(window.ethereum.selectedAddress, tokenURI)7 .encodeABI(), //вызов смарт-контракта NFT8}910//подписание транзакции через MetaMask11try {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передает сообщение об ошибке.
- Если транзакция прошла успешно, функция вернет объект 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 }910 //создание метаданных11 const metadata = new Object()12 metadata.name = name13 metadata.image = url14 metadata.description = description1516 //запрос на закрепление в pinata17 const pinataResponse = await pinJSONToIPFS(metadata)18 if (!pinataResponse.success) {19 return {20 success: false,21 status: "😢 Что-то пошло не так при загрузке вашего tokenURI.",22 }23 }24 const tokenURI = pinataResponse.pinataUrl2526 //загрузка смарт-контракта27 window.contract = await new web3.eth.Contract(contractABI, contractAddress) //loadContract();2829 //настройка транзакции Ethereum30 const transactionParameters = {31 to: contractAddress, // Обязательно, кроме случаев публикации контракта.32 from: window.ethereum.selectedAddress, // должен совпадать с активным адресом пользователя.33 data: window.contract.methods34 .mintNFT(window.ethereum.selectedAddress, tokenURI)35 .encodeABI(), //вызов смарт-контракта NFT36 }3738 //подписание транзакции через MetaMask39 try {40 const txHash = await window.ethereum.request({41 method: "eth_sendTransaction",42 params: [transactionParameters],43 })44 return {45 success: true,46 status:47 "✅ Проверьте свою транзакцию в 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 г.