Перейти до основного контенту

Посібник із карбування NFT

мова програмування
NFT
alchemy
Смарт-контракти
використання
Pinata
Середнячок
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 101

Перш ніж ми почнемо розглядати будь-який код, важливо зрозуміти, як працює створення 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 у вашому браузері, де ви побачите фронтенд нашого проєкту. Він повинен складатися з 3 полів: місце для введення посилання на актив вашого NFT, введення назви вашого NFT та надання опису.

Якщо ви спробуєте натиснути кнопки «Підключити гаманець» або «Викарбувати 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 користувача до нашого dapp.
  • 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 до вашого dapp.

Завантажте MetaMask

Для цього уроку ми будемо використовувати MetaMask, віртуальний гаманець в браузері, який використовується для керування адресою облікового запису Ethereum. Якщо ви хочете дізнатися більше про те, як працюють транзакції в Ethereum, перегляньте цю сторінку.

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

Додайте ефір із крана (Faucet)

Щоб карбувати наші NFT (або підписувати будь-які транзакції в блокчейні Ethereum), нам знадобиться трохи тестового Eth. Щоб отримати Eth, ви можете перейти до крана Ropstenopens in a new tab, ввести адресу свого облікового запису Ropsten, а потім натиснути «Надіслати Ropsten Eth». Незабаром ви маєте побачити Eth у своєму обліковому записі MetaMask!

Перевірте свій баланс

Щоб перевірити наявність балансу, давайте зробимо запит eth_getBalanceopens in a new tab за допомогою інструмента-композитора від Alchemyopens in a new tab. Це поверне кількість Eth у нашому гаманці. Після введення вашої адреси облікового запису MetaMask і натисніть кнопку "Відправити запит", ви повинні побачити таку відповідь:

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

ПРИМІТКА: Цей результат у wei, а не в eth. Wei використовується в якості найменшого номіналу ether. Перетворення з wei в eth: 1 eth = 10¹⁸ wei. Отже, якщо ми перетворимо 0xde0b6b3a7640000 у десяткове число, ми отримаємо 1*10¹⁸, що дорівнює 1 eth.

Фух! Наші підроблені гроші усі там!

Підключіть MetaMask до свого інтерфейсу користувача

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

Оскільки ми хочемо дотримуватися парадигми MVCopens in a new tab, ми створимо окремий файл, який міститиме наші функції для керування логікою, даними та правилами нашого dapp, а потім передамо ці функції нашому фронтенду (компоненту 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 </a>
28 </p>
29 </span>
30 ),
31 }
32 }
33}
Показати все

Давайте розберемо, що робить цей код:

По-перше, наша функція перевіряє, чи ввімкнено window.ethereum у вашому браузері.

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

Якщо window.ethereum не присутній, це означає, що MetaMask не встановлено. Це призводить до повернення об'єкта JSON, де повернута address є порожнім рядком, а об'єкт JSX status повідомляє, що користувач повинен встановити MetaMask.

Більшість функцій, які ми пишемо, повертатимуть об'єкти JSON, які ми можемо використовувати для оновлення наших змінних стану та інтерфейсу користувача.

Тепер, якщо window.ethereum присутній, ось тут і починається найцікавіше.

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

  • Якщо користувач вирішить підключитися, method: "eth_requestAccounts" поверне масив, що містить усі адреси облікових записів користувача, підключені до dapp. Загалом, наша функція connectWallet поверне об'єкт JSON, який містить першу address у цьому масиві (див. рядок 9) та повідомлення status, яке пропонує користувачеві написати повідомлення для смарт-контракту.
  • Якщо користувач відхиляє підключення, то об'єкт JSON міститиме порожній рядок для повернутої address та повідомлення status, що відображає відхилення підключення користувачем.

Додайте функцію connectWallet до вашого компонента Minter.js UI

Тепер, коли ми написали цю функцію 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-C!

У connectWalletPressed ми просто робимо await-виклик нашої імпортованої функції connectWallet і, використовуючи її відповідь, оновлюємо наші змінні status та walletAddress за допомогою їхніх хуків стану.

Тепер збережімо обидва файли, Minter.js та interact.js, і протестуємо наш інтерфейс користувача.

Відкрийте свій браузер за адресою localhost:3000 і натисніть кнопку «Підключити гаманець» у верхньому правому куті сторінки.

Якщо у вас встановлено MetaMask, вам буде запропоновано підключити свій гаманець до вашого dapp. Прийміть запрошення на підключення.

Ви маєте побачити, що кнопка гаманця тепер відображає, що ваша адреса підключена.

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

Але не хвилюйтеся! Ми можемо легко це виправити, реалізувавши функцію під назвою getCurrentWalletConnected, яка перевірить, чи адреса вже підключена до нашого dapp, і відповідно оновить наш інтерфейс користувача!

Функція 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 </a>
34 </p>
35 </span>
36 ),
37 }
38 }
39}
Показати все

Цей код дуже схожий на функцію connectWallet, яку ми щойно написали.

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

Щоб побачити цю функцію в дії, давайте викличемо її у функції 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

Останній крок у налаштуванні гаманця нашого dapp — це реалізація прослуховувача гаманця, щоб наш інтерфейс користувача оновлювався при зміні стану нашого гаманця, наприклад, коли користувач відключається або перемикає облікові записи.

У вашому файлі 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, зокрема, коли користувач підключає додатковий обліковий запис до dapp, перемикає облікові записи або відключає обліковий запис. Якщо підключено хоча б один обліковий запис, змінна стану walletAddress оновлюється як перший обліковий запис у масиві accounts, що повертається прослуховувачем. В іншому випадку walletAddress встановлюється як порожній рядок.

Нарешті, ми повинні викликати її в нашій функції useEffect:

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

І вуаля! Ми завершили програмування всієї функціональності нашого гаманця! Тепер, коли наш гаманець налаштовано, давайте розберемося, як викарбувати наш NFT!

Метадані NFT 101

Отже, пам'ятаєте метадані 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 у вашому каталозі проєкту.

Відкрийте нову вкладку у вашому терміналі (окрему від тієї, де запущено localhost) і переконайтеся, що ви перебуваєте в папці minter-starter-files, а потім виконайте наступну команду у вашому терміналі:

1npm install dotenv --save

Далі створіть файл .env у кореневому каталозі вашого minter-starter-files, ввівши наступне в командному рядку:

1vim.env

Це відкриє ваш файл .env у vim (текстовому редакторі). Щоб зберегти його, натисніть «esc» + «:» + «q» на клавіатурі в такому порядку.

Далі у VSCode перейдіть до файлу .env і додайте до нього свій ключ API та секрет API Pinata, ось так:

1REACT_APP_PINATA_KEY = <pinata-api-key>
2REACT_APP_PINATA_SECRET = <pinata-api-secret>

Збережіть файл, і тоді ви будете готові почати писати функцію для завантаження ваших метаданих JSON в IPFS!

opens in a new tabРеалізуйте 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-запит до їхнього API pinJSONToIPFS.

  • Якщо цей POST-запит успішний, то наша функція повертає об'єкт JSON з булевим значенням success, встановленим як true, і pinataUrl, де були закріплені наші метадані. Ми будемо використовувати цей повернутий pinataUrl як вхідні дані tokenURI для функції карбування нашого смарт-контракту.
  • Якщо цей POST-запит не вдається, то наша функція повертає об'єкт 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» для вашої мережі.

Натисніть "Створити додаток", ось і все! Ваш додаток повинен з'явитися у таблиці нижче.

Чудово, тепер, коли ми створили нашу URL-адресу HTTP Alchemy API, скопіюйте її в буфер обміну...

…а потім додамо його до нашого файлу .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 та інші важливі переваги, щоб полегшити ваше життя як розробника web3. Він розроблений так, щоб вимагати мінімальної конфігурації, щоб ви могли одразу почати використовувати його у своєму додатку!

Далі додамо ABI та адресу нашого контракту до нашого файлу.

1require("dotenv").config()
2const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY
3const { createAlchemyWeb3 } = require("@alch/alchemy-web3")
4const web3 = createAlchemyWeb3(alchemyKey)
5
6const contractABI = require("../contract-abi.json")
7const contractAddress = "0x4C4a07F737Bf57F6632B6CAB089B78f62385aCaE"

Коли ми маємо обидва, ми готові почати кодувати нашу функцію карбування!

Реалізуйте функцію 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 повідомляє, що всі поля в нашому інтерфейсі користувача повинні бути заповнені.

opens in a new tabЗавантажте метадані в 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, як вхідні дані
  • Потім ми робимо await-виклик, 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, викарбувані через ваш dapp, у своєму гаманці — тож обов'язково ознайомтеся з нашим коротким посібником Як переглянути свій NFT у своєму гаманціopens in a new tab!

І, як завжди, якщо у вас є які-небудь питання, ми тут, щоб допомогти в Alchemy Discordopens in a new tab. Ми з нетерпінням чекаємо, щоб побачити, як ви застосуєте концепції з цього посібника у своїх майбутніх проєктах!

Останні оновлення сторінки: 22 жовтня 2025 р.

Чи була ця інструкція корисною?