Посібник із карбування NFT
Однією з найбільших проблем для розробників, які працюють із 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-filesnpm 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— рядок, у якому зберігається назва 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 користувача до нашого 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>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 до вашого 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 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 </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";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-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)56 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_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-запит до їхнього 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_KEY3const { 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_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 повідомляє, що всі поля в нашому інтерфейсі користувача повинні бути заповнені.
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 }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, як вхідні дані
- Потім ми робимо await-виклик,
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, викарбувані через ваш dapp, у своєму гаманці — тож обов'язково ознайомтеся з нашим коротким посібником Як переглянути свій NFT у своєму гаманціopens in a new tab!
І, як завжди, якщо у вас є які-небудь питання, ми тут, щоб допомогти в Alchemy Discordopens in a new tab. Ми з нетерпінням чекаємо, щоб побачити, як ви застосуєте концепції з цього посібника у своїх майбутніх проєктах!
Останні оновлення сторінки: 22 жовтня 2025 р.