Samouczek: Minter NFT
Jednym z największych wyzwań dla programistów wywodzących się ze środowiska Web2 jest zrozumienie, jak połączyć inteligentny kontrakt z projektem frontendowym i wchodzić z nim w interakcje.
Budując minter NFT — prosty interfejs użytkownika (UI), w którym można wprowadzić link do swojego aktywa cyfrowego, tytuł i opis — dowiesz się, jak:
- Połączyć się z MetaMask przez swój projekt frontendowy
- Wywoływać metody inteligentnego kontraktu z frontendu
- Podpisywać transakcje za pomocą MetaMask
W tym samouczku użyjemy React (opens in a new tab) jako naszego frameworka frontendowego. Ponieważ ten samouczek skupia się głównie na programowaniu w Web3, nie będziemy poświęcać dużo czasu na omawianie podstaw React. Zamiast tego skupimy się na dodaniu funkcjonalności do naszego projektu.
Wymagane jest podstawowe zrozumienie React — znajomość działania komponentów, propsów, useState/useEffect oraz podstawowego wywoływania funkcji. Jeśli nigdy wcześniej nie słyszałeś o żadnym z tych terminów, warto zapoznać się z tym samouczkiem wprowadzającym do React (opens in a new tab). Wzrokowcom gorąco polecamy tę doskonałą serię filmów Full Modern React Tutorial (opens in a new tab) autorstwa Net Ninja.
Jeśli jeszcze tego nie zrobiłeś, na pewno będziesz potrzebować konta Alchemy, aby ukończyć ten samouczek, a także zbudować cokolwiek na blockchainie. Zarejestruj darmowe konto tutaj (opens in a new tab).
Bez zbędnych ceregieli, zaczynajmy!
Podstawy tworzenia NFT
Zanim w ogóle zaczniemy przeglądać jakikolwiek kod, ważne jest, aby zrozumieć, jak działa tworzenie NFT. Obejmuje to dwa kroki:
Wdrożenie inteligentnego kontraktu NFT na blockchainie Ethereum
Największą różnicą między dwoma standardami inteligentnych kontraktów NFT jest to, że ERC-1155 to standard wielotokenowy (multi-token) i obejmuje funkcjonalność wsadową (batch), podczas gdy ERC-721 to standard pojedynczego tokena i dlatego obsługuje przesyłanie tylko jednego tokena naraz.
Wywołanie funkcji wybijania
Zazwyczaj ta funkcja wybijania wymaga przekazania dwóch zmiennych jako parametrów: po pierwsze recipient, który określa adres, na który trafi nowo wybite NFT, a po drugie tokenURI NFT, czyli ciąg znaków (string) wskazujący na dokument JSON opisujący metadane NFT.
Metadane NFT to tak naprawdę to, co ożywia token, pozwalając mu posiadać właściwości, takie jak nazwa, opis, obraz (lub inne aktywo cyfrowe) i inne atrybuty. Oto przykład tokenURI (opens in a new tab), który zawiera metadane NFT.
W tym samouczku skupimy się na części 2, czyli wywołaniu funkcji wybijania istniejącego inteligentnego kontraktu NFT za pomocą naszego interfejsu użytkownika w React.
Oto link (opens in a new tab) do inteligentnego kontraktu NFT ERC-721, który będziemy wywoływać w tym samouczku. Jeśli chcesz dowiedzieć się, jak go stworzyliśmy, gorąco polecamy zapoznanie się z naszym innym samouczkiem: „Jak stworzyć NFT” (opens in a new tab).
Świetnie, skoro już rozumiemy, jak działa tworzenie NFT, sklonujmy nasze pliki startowe!
Klonowanie plików startowych
Najpierw przejdź do repozytorium nft-minter-tutorial na GitHubie (opens in a new tab), aby pobrać pliki startowe dla tego projektu. Sklonuj to repozytorium do swojego środowiska lokalnego.
Kiedy otworzysz to sklonowane repozytorium nft-minter-tutorial, zauważysz, że zawiera ono dwa foldery: minter-starter-files i nft-minter.
minter-starter-fileszawiera pliki startowe (zasadniczo interfejs użytkownika w React) dla tego projektu. W tym samouczku będziemy pracować w tym katalogu, ucząc się, jak ożywić ten interfejs, łącząc go z portfelem Ethereum i inteligentnym kontraktem NFT.nft-minterzawiera cały ukończony samouczek i służy jako punkt odniesienia, jeśli utkniesz.
Następnie otwórz swoją kopię minter-starter-files w edytorze kodu, a potem przejdź do folderu src.
Cały kod, który napiszemy, będzie znajdował się w folderze src. Będziemy edytować komponent Minter.js i pisać dodatkowe pliki JavaScript, aby nadać naszemu projektowi funkcjonalność Web3.
Krok 2: Sprawdzenie plików startowych
Zanim zaczniemy kodować, ważne jest, aby sprawdzić, co już zostało dla nas przygotowane w plikach startowych.
Uruchomienie projektu w React
Zacznijmy od uruchomienia projektu React w naszej przeglądarce. Piękno React polega na tym, że gdy nasz projekt jest uruchomiony w przeglądarce, wszelkie zapisane przez nas zmiany będą aktualizowane na żywo.
Aby uruchomić projekt, przejdź do katalogu głównego folderu minter-starter-files i uruchom npm install w terminalu, aby zainstalować zależności projektu:
cd minter-starter-files
npm install
Po zakończeniu instalacji uruchom npm start w terminalu:
npm start
Spowoduje to otwarcie http://localhost:3000/ (opens in a new tab) w przeglądarce, gdzie zobaczysz frontend naszego projektu. Powinien składać się z 3 pól: miejsca na wprowadzenie linku do aktywa NFT, nazwy NFT oraz opisu.
Jeśli spróbujesz kliknąć przyciski „Connect Wallet” (Połącz portfel) lub „Mint NFT” (Wybij NFT), zauważysz, że nie działają — to dlatego, że wciąż musimy zaprogramować ich funkcjonalność! :)
Komponent Minter.js
UWAGA: Upewnij się, że jesteś w folderze minter-starter-files, a nie w folderze nft-minter!
Wróćmy do folderu src w naszym edytorze i otwórzmy plik Minter.js. To bardzo ważne, abyśmy zrozumieli wszystko w tym pliku, ponieważ jest to główny komponent React, nad którym będziemy pracować.
Na górze tego pliku znajdują się nasze zmienne stanu, które będziemy aktualizować po określonych zdarzeniach.
//Zmienne stanu
const [walletAddress, setWallet] = useState("")
const [status, setStatus] = useState("")
const [name, setName] = useState("")
const [description, setDescription] = useState("")
const [url, setURL] = useState("")
Nigdy nie słyszałeś o zmiennych stanu lub hookach stanu w React? Sprawdź tę (opens in a new tab) dokumentację.
Oto co reprezentuje każda ze zmiennych:
walletAddress- ciąg znaków przechowujący adres portfela użytkownikastatus- ciąg znaków zawierający wiadomość do wyświetlenia na dole interfejsu użytkownikaname- ciąg znaków przechowujący nazwę NFTdescription- ciąg znaków przechowujący opis NFTurl- ciąg znaków będący linkiem do aktywa cyfrowego NFT
Po zmiennych stanu zobaczysz trzy niezaimplementowane funkcje: useEffect, connectWalletPressed i onMintPressed. Zauważysz, że wszystkie te funkcje to async (asynchroniczne), ponieważ będziemy w nich wykonywać asynchroniczne wywołania API! Ich nazwy odpowiadają ich funkcjonalnościom:
useEffect(async () => {
//TODO: zaimplementuj
}, [])
const connectWalletPressed = async () => {
//TODO: zaimplementuj
}
const onMintPressed = async () => {
//TODO: zaimplementuj
}
useEffect(opens in a new tab) - to hook React, który jest wywoływany po wyrenderowaniu komponentu. Ponieważ przekazano do niego pustą tablicę[](zobacz linię 3), zostanie on wywołany tylko przy pierwszym renderowaniu komponentu. Tutaj wywołamy nasz nasłuchiwacz portfela (wallet listener) i inną funkcję portfela, aby zaktualizować nasz interfejs użytkownika i odzwierciedlić, czy portfel jest już podłączony.connectWalletPressed- ta funkcja zostanie wywołana, aby połączyć portfel MetaMask użytkownika z naszą zdecentralizowaną aplikacją (dapp).onMintPressed- ta funkcja zostanie wywołana, aby wybić NFT użytkownika.
Pod koniec tego pliku znajduje się interfejs użytkownika naszego komponentu. Jeśli uważnie przeanalizujesz ten kod, zauważysz, że aktualizujemy nasze zmienne stanu url, name i description, gdy zmieniają się dane wejściowe w odpowiadających im polach tekstowych.
Zobaczysz również, że connectWalletPressed i onMintPressed są wywoływane po kliknięciu przycisków o identyfikatorach odpowiednio mintButton i walletButton.
//interfejs użytkownika naszego komponentu
return (
<div className="Minter">
<button id="walletButton" onClick={connectWalletPressed}>
{walletAddress.length > 0 ? (
"Connected: " +
String(walletAddress).substring(0, 6) +
"..." +
String(walletAddress).substring(38)
) : (
<span>Connect Wallet</span>
)}
</button>
<br></br>
<h1 id="title">🧙♂️ Alchemy NFT Minter</h1>
<p>
Simply add your asset's link, name, and description, then press "Mint."
</p>
<form>
<h2>🖼 Link to asset: </h2>
<input
type="text"
placeholder="e.g., https://gateway.pinata.cloud/ipfs/<hash>"
onChange={(event) => setURL(event.target.value)}
/>
<h2>🤔 Name: </h2>
<input
type="text"
placeholder="e.g., My first NFT!"
onChange={(event) => setName(event.target.value)}
/>
<h2>✍️ Description: </h2>
<input
type="text"
placeholder="e.g., Even cooler than cryptokitties ;)"
onChange={(event) => setDescription(event.target.value)}
/>
</form>
<button id="mintButton" onClick={onMintPressed}>
Mint NFT
</button>
<p id="status">{status}</p>
</div>
)
Na koniec omówmy, gdzie dodawany jest ten komponent Minter.
Jeśli przejdziesz do pliku App.js, który jest głównym komponentem w React działającym jako kontener dla wszystkich innych komponentów, zobaczysz, że nasz komponent Minter jest wstrzykiwany w linii 7.
W tym samouczku będziemy edytować tylko Minter.js file i dodawać pliki w naszym folderze src.
Teraz, gdy rozumiemy, z czym pracujemy, skonfigurujmy nasz portfel Ethereum!
Konfiguracja portfela Ethereum
Aby użytkownicy mogli wchodzić w interakcje z Twoim inteligentnym kontraktem, będą musieli połączyć swój portfel Ethereum z Twoją zdecentralizowaną aplikacją (dapp).
Pobranie MetaMask
W tym samouczku użyjemy MetaMask, wirtualnego portfela w przeglądarce, służącego do zarządzania adresem konta Ethereum. Jeśli chcesz dowiedzieć się więcej o tym, jak działają transakcje w Ethereum, sprawdź tę stronę.
Możesz pobrać i utworzyć konto MetaMask za darmo tutaj (opens in a new tab). Podczas tworzenia konta lub jeśli już je posiadasz, upewnij się, że przełączyłeś się na „Ropsten Test Network” w prawym górnym rogu (abyśmy nie operowali prawdziwymi pieniędzmi).
Dodanie etheru z kranika
Aby wybić nasze NFT (lub podpisać jakiekolwiek transakcje na blockchainie Ethereum), będziemy potrzebować trochę fałszywego ETH. Aby zdobyć ETH, możesz przejść do kranika Ropsten (opens in a new tab) i wprowadzić adres swojego konta Ropsten, a następnie kliknąć „Send Ropsten Eth”. Wkrótce potem powinieneś zobaczyć ETH na swoim koncie MetaMask!
Sprawdzenie salda
Aby upewnić się, że nasze saldo tam jest, wykonajmy żądanie eth_getBalance (opens in a new tab) za pomocą narzędzia composer od Alchemy (opens in a new tab). Zwróci to ilość ETH w naszym portfelu. Po wprowadzeniu adresu konta MetaMask i kliknięciu „Send Request” (Wyślij żądanie), powinieneś zobaczyć odpowiedź podobną do tej:
{"jsonrpc": "2.0", "id": 0, "result": "0xde0b6b3a7640000"}
UWAGA: Ten wynik jest w wei, a nie w ETH. Wei jest używane jako najmniejszy nominał etheru. Przelicznik z wei na ETH to: 1 ETH = 10¹⁸ wei. Więc jeśli przekonwertujemy 0xde0b6b3a7640000 na system dziesiętny, otrzymamy 1*10¹⁸, co równa się 1 ETH.
Uff! Nasze fałszywe pieniądze są na miejscu!
Połączenie MetaMask z interfejsem użytkownika
Teraz, gdy nasz portfel MetaMask jest skonfigurowany, połączmy z nim naszą zdecentralizowaną aplikację (dapp)!
Ponieważ chcemy trzymać się paradygmatu MVC (opens in a new tab), utworzymy osobny plik zawierający nasze funkcje do zarządzania logiką, danymi i regułami naszej dapp, a następnie przekażemy te funkcje do naszego frontendu (naszego komponentu Minter.js).
Funkcja connectWallet
Aby to zrobić, utwórzmy nowy folder o nazwie utils w katalogu src i dodajmy do niego plik o nazwie interact.js, który będzie zawierał wszystkie nasze funkcje interakcji z portfelem i inteligentnym kontraktem.
W naszym pliku interact.js napiszemy funkcję connectWallet, którą następnie zaimportujemy i wywołamy w naszym komponencie Minter.js.
W pliku interact.js dodaj następujący kod:
export const connectWallet = async () => {
if (window.ethereum) {
try {
const addressArray = await window.ethereum.request({
method: "eth_requestAccounts",
})
const obj = {
status: "👆🏽 Write a message in the text-field above.",
address: addressArray[0],
}
return obj
} catch (err) {
return {
address: "",
status: "😥 " + err.message,
}
}
} else {
return {
address: "",
status: (
<span>
<p>
{" "}
🦊 <a target="_blank" href={`https://metamask.io/download`}>
You must install MetaMask, a virtual Ethereum wallet, in your
browser.
</a>
</p>
</span>
),
}
}
}
Przeanalizujmy, co robi ten kod:
Po pierwsze, nasza funkcja sprawdza, czy window.ethereum jest włączone w Twojej przeglądarce.
window.ethereum to globalne API wstrzykiwane przez MetaMask i innych dostawców portfeli, które pozwala stronom internetowym na żądanie dostępu do kont Ethereum użytkowników. Po zatwierdzeniu może odczytywać dane z blockchainów, z którymi połączony jest użytkownik, i sugerować mu podpisywanie wiadomości oraz transakcji. Sprawdź dokumentację MetaMask (opens in a new tab), aby uzyskać więcej informacji!
Jeśli window.ethereum nie jest obecne, oznacza to, że MetaMask nie jest zainstalowany. Skutkuje to zwróceniem obiektu JSON, w którym zwrócony address jest pustym ciągiem znaków, a obiekt JSX status przekazuje informację, że użytkownik musi zainstalować MetaMask.
Większość funkcji, które napiszemy, będzie zwracać obiekty JSON, których możemy użyć do aktualizacji naszych zmiennych stanu i interfejsu użytkownika.
Jeśli jednak window.ethereum jest obecne, wtedy robi się ciekawie.
Używając bloku try/catch, spróbujemy połączyć się z MetaMask, wywołując window.ethereum.request({ method: "eth_requestAccounts" }); (opens in a new tab). Wywołanie tej funkcji otworzy MetaMask w przeglądarce, po czym użytkownik zostanie poproszony o połączenie swojego portfela z Twoją dapp.
- Jeśli użytkownik zdecyduje się połączyć,
method: "eth_requestAccounts"zwróci tablicę zawierającą wszystkie adresy kont użytkownika, które są połączone z dapp. W sumie nasza funkcjaconnectWalletzwróci obiekt JSON, który zawiera pierwszyaddressw tej tablicy (zobacz linię 9) oraz wiadomośćstatus, która zachęca użytkownika do napisania wiadomości do inteligentnego kontraktu. - Jeśli użytkownik odrzuci połączenie, obiekt JSON będzie zawierał pusty ciąg znaków dla zwróconego
addressoraz wiadomośćstatusodzwierciedlającą fakt, że użytkownik odrzucił połączenie.
Dodanie funkcji connectWallet do komponentu interfejsu użytkownika Minter.js
Teraz, gdy napisaliśmy tę funkcję connectWallet, połączmy ją z naszym komponentem Minter.js..
Najpierw będziemy musieli zaimportować naszą funkcję do pliku Minter.js, dodając import { connectWallet } from "./utils/interact.js"; na samej górze pliku Minter.js. Twoje pierwsze 11 linii pliku Minter.js powinno teraz wyglądać tak:
import { useEffect, useState } from "react";
import { connectWallet } from "./utils/interact.js";
const Minter = (props) => {
//Zmienne stanu
const [walletAddress, setWallet] = useState("");
const [status, setStatus] = useState("");
const [name, setName] = useState("");
const [description, setDescription] = useState("");
const [url, setURL] = useState("");
Następnie wewnątrz naszej funkcji connectWalletPressed wywołamy zaimportowaną funkcję connectWallet w ten sposób:
const connectWalletPressed = async () => {
const walletResponse = await connectWallet()
setStatus(walletResponse.status)
setWallet(walletResponse.address)
}
Zauważ, jak większość naszej funkcjonalności jest wyabstrahowana z naszego komponentu Minter.js do pliku interact.js? Robimy to, aby zachować zgodność z paradygmatem M-V-C!
W connectWalletPressed po prostu wykonujemy wywołanie await do naszej zaimportowanej funkcji connectWallet i używając jej odpowiedzi, aktualizujemy nasze zmienne status i walletAddress za pomocą ich hooków stanu.
Teraz zapiszmy oba pliki Minter.js i interact.js i przetestujmy nasz dotychczasowy interfejs użytkownika.
Otwórz przeglądarkę na localhost:3000 i naciśnij przycisk „Connect Wallet” w prawym górnym rogu strony.
Jeśli masz zainstalowany MetaMask, powinieneś zostać poproszony o połączenie portfela z Twoją dapp. Zaakceptuj zaproszenie do połączenia.
Powinieneś zobaczyć, że przycisk portfela odzwierciedla teraz fakt, że Twój adres jest połączony.
Następnie spróbuj odświeżyć stronę... to dziwne. Nasz przycisk portfela prosi nas o połączenie MetaMask, mimo że jest już połączony...
Nie martw się jednak! Możemy to łatwo naprawić, implementując funkcję o nazwie getCurrentWalletConnected, która sprawdzi, czy adres jest już połączony z naszą dapp i odpowiednio zaktualizuje nasz interfejs użytkownika!
Funkcja getCurrentWalletConnected
W pliku interact.js dodaj następującą funkcję getCurrentWalletConnected:
export const getCurrentWalletConnected = async () => {
if (window.ethereum) {
try {
const addressArray = await window.ethereum.request({
method: "eth_accounts",
})
if (addressArray.length > 0) {
return {
address: addressArray[0],
status: "👆🏽 Write a message in the text-field above.",
}
} else {
return {
address: "",
status: "🦊 Connect to MetaMask using the top right button.",
}
}
} catch (err) {
return {
address: "",
status: "😥 " + err.message,
}
}
} else {
return {
address: "",
status: (
<span>
<p>
{" "}
🦊 <a target="_blank" href={`https://metamask.io/download`}>
You must install MetaMask, a virtual Ethereum wallet, in your
browser.
</a>
</p>
</span>
),
}
}
}
Ten kod jest bardzo podobny do funkcji connectWallet, którą napisaliśmy wcześniej.
Główna różnica polega na tym, że zamiast wywoływać metodę eth_requestAccounts, która otwiera MetaMask, aby użytkownik mógł połączyć swój portfel, tutaj wywołujemy metodę eth_accounts, która po prostu zwraca tablicę zawierającą adresy MetaMask aktualnie połączone z naszą dapp.
Aby zobaczyć tę funkcję w akcji, wywołajmy ją w funkcji useEffect naszego komponentu Minter.js.
Podobnie jak w przypadku connectWallet, musimy zaimportować tę funkcję z naszego pliku interact.js do pliku Minter.js w ten sposób:
import { useEffect, useState } from "react"
import {
connectWallet,
getCurrentWalletConnected, //importuj tutaj
} from "./utils/interact.js"
Teraz po prostu wywołujemy ją w naszej funkcji useEffect:
useEffect(async () => {
const { address, status } = await getCurrentWalletConnected()
setWallet(address)
setStatus(status)
}, [])
Zauważ, że używamy odpowiedzi z naszego wywołania getCurrentWalletConnected do aktualizacji naszych zmiennych stanu walletAddress i status.
Po dodaniu tego kodu spróbuj odświeżyć okno przeglądarki. Przycisk powinien informować, że jesteś połączony, i pokazywać podgląd adresu połączonego portfela - nawet po odświeżeniu!
Implementacja addWalletListener
Ostatnim krokiem w konfiguracji portfela naszej dapp jest zaimplementowanie nasłuchiwacza portfela (wallet listener), aby nasz interfejs użytkownika aktualizował się, gdy zmienia się stan naszego portfela, na przykład gdy użytkownik się rozłącza lub przełącza konta.
W pliku Minter.js dodaj funkcję addWalletListener, która wygląda następująco:
function addWalletListener() {
if (window.ethereum) {
window.ethereum.on("accountsChanged", (accounts) => {
if (accounts.length > 0) {
setWallet(accounts[0])
setStatus("👆🏽 Write a message in the text-field above.")
} else {
setWallet("")
setStatus("🦊 Connect to MetaMask using the top right button.")
}
})
} else {
setStatus(
<p>
{" "}
🦊 <a target="_blank" href={`https://metamask.io/download`}>
You must install MetaMask, a virtual Ethereum wallet, in your browser.
</a>
</p>
)
}
}
Szybko przeanalizujmy, co się tutaj dzieje:
- Po pierwsze, nasza funkcja sprawdza, czy
window.ethereumjest włączone (tj. czy MetaMask jest zainstalowany).- Jeśli nie jest, po prostu ustawiamy naszą zmienną stanu
statusna ciąg JSX, który zachęca użytkownika do zainstalowania MetaMask. - Jeśli jest włączone, konfigurujemy nasłuchiwacz
window.ethereum.on("accountsChanged")w linii 3, który nasłuchuje zmian stanu w portfelu MetaMask, co obejmuje sytuacje, gdy użytkownik łączy dodatkowe konto z dapp, przełącza konta lub odłącza konto. Jeśli podłączone jest co najmniej jedno konto, zmienna stanuwalletAddressjest aktualizowana jako pierwsze konto w tablicyaccountszwróconej przez nasłuchiwacz. W przeciwnym raziewalletAddressjest ustawiane jako pusty ciąg znaków.
- Jeśli nie jest, po prostu ustawiamy naszą zmienną stanu
Na koniec musimy wywołać ją w naszej funkcji useEffect:
useEffect(async () => {
const { address, status } = await getCurrentWalletConnected()
setWallet(address)
setStatus(status)
addWalletListener()
}, [])
I voila! Zakończyliśmy programowanie całej funkcjonalności naszego portfela! Teraz, gdy nasz portfel jest skonfigurowany, dowiedzmy się, jak wybić nasze NFT!
Podstawy metadanych NFT
Pamiętasz metadane NFT, o których mówiliśmy w Kroku 0 tego samouczka — to one ożywiają NFT, pozwalając mu posiadać właściwości, takie jak aktywo cyfrowe, nazwa, opis i inne atrybuty.
Będziemy musieli skonfigurować te metadane jako obiekt JSON i zapisać je, abyśmy mogli przekazać je jako parametr tokenURI podczas wywoływania funkcji mintNFT naszego inteligentnego kontraktu.
Tekst w polach „Link to Asset” (Link do aktywa), „Name” (Nazwa), „Description” (Opis) będzie stanowić różne właściwości metadanych naszego NFT. Sformatujemy te metadane jako obiekt JSON, ale istnieje kilka opcji, gdzie możemy przechowywać ten obiekt JSON:
- Moglibyśmy przechowywać go na blockchainie Ethereum; jednak byłoby to bardzo drogie.
- Moglibyśmy przechowywać go na scentralizowanym serwerze, takim jak AWS lub Firebase. Ale to zniweczyłoby nasz etos decentralizacji.
- Moglibyśmy użyć IPFS, zdecentralizowanego protokołu i sieci peer-to-peer do przechowywania i udostępniania danych w rozproszonym systemie plików. Ponieważ ten protokół jest zdecentralizowany i darmowy, jest to nasza najlepsza opcja!
Aby przechowywać nasze metadane w IPFS, użyjemy Pinata (opens in a new tab), wygodnego API i zestawu narzędzi IPFS. W następnym kroku wyjaśnimy dokładnie, jak to zrobić!
Użycie Pinata do przypięcia metadanych do IPFS
Jeśli nie masz konta Pinata (opens in a new tab), zarejestruj darmowe konto tutaj (opens in a new tab) i wykonaj kroki w celu weryfikacji adresu e-mail i konta.
Utworzenie klucza API Pinata
Przejdź na stronę https://pinata.cloud/keys (opens in a new tab), a następnie wybierz przycisk „New Key” (Nowy klucz) na górze, ustaw widżet Admin jako włączony i nazwij swój klucz.
Następnie zobaczysz wyskakujące okienko z informacjami o Twoim API. Upewnij się, że przechowujesz je w bezpiecznym miejscu.
Teraz, gdy nasz klucz jest skonfigurowany, dodajmy go do naszego projektu, abyśmy mogli z niego korzystać.
Utworzenie pliku .env
Możemy bezpiecznie przechowywać nasz klucz i sekret Pinata w pliku środowiskowym. Zainstalujmy pakiet dotenv (opens in a new tab) w katalogu Twojego projektu.
Otwórz nową kartę w terminalu (oddzielną od tej, na której działa localhost) i upewnij się, że jesteś w folderze minter-starter-files, a następnie uruchom następujące polecenie w terminalu:
npm install dotenv --save
Następnie utwórz plik .env w katalogu głównym minter-starter-files, wpisując w wierszu poleceń następujące polecenie:
vim.env
Spowoduje to otwarcie pliku .env w vimie (edytorze tekstu). Aby go zapisać, naciśnij na klawiaturze kolejno „esc” + „:” + „q”.
Następnie w VSCode przejdź do pliku .env i dodaj do niego swój klucz API Pinata oraz sekret API, w ten sposób:
REACT_APP_PINATA_KEY = <pinata-api-key>
REACT_APP_PINATA_SECRET = <pinata-api-secret>
Zapisz plik, a następnie będziesz gotowy do rozpoczęcia pisania funkcji przesyłającej metadane JSON do IPFS!
Implementacja pinJSONToIPFS
Na szczęście dla nas, Pinata posiada API specjalnie do przesyłania danych JSON do IPFS (opens in a new tab) oraz wygodny przykład w JavaScript z użyciem axios, którego możemy użyć z niewielkimi modyfikacjami.
W folderze utils utwórzmy kolejny plik o nazwie pinata.js, a następnie zaimportujmy nasz sekret i klucz Pinata z pliku .env w ten sposób:
require("dotenv").config()
const key = process.env.REACT_APP_PINATA_KEY
const secret = process.env.REACT_APP_PINATA_SECRET
Następnie wklej poniższy dodatkowy kod do pliku pinata.js. Nie martw się, przeanalizujemy, co to wszystko oznacza!
require("dotenv").config()
const key = process.env.REACT_APP_PINATA_KEY
const secret = process.env.REACT_APP_PINATA_SECRET
const axios = require("axios")
export const pinJSONToIPFS = async (JSONBody) => {
const url = `https://api.pinata.cloud/pinning/pinJSONToIPFS`
//wykonywanie żądania POST axios do Pinata ⬇️
return axios
.post(url, JSONBody, {
headers: {
pinata_api_key: key,
pinata_secret_api_key: secret,
},
})
.then(function (response) {
return {
success: true,
pinataUrl:
"https://gateway.pinata.cloud/ipfs/" + response.data.IpfsHash,
}
})
.catch(function (error) {
console.log(error)
return {
success: false,
message: error.message,
}
})
}
Więc co dokładnie robi ten kod?
Po pierwsze, importuje axios (opens in a new tab), opartego na obietnicach (promises) klienta HTTP dla przeglądarki i Node.js, którego użyjemy do wykonania żądania do Pinata.
Następnie mamy naszą asynchroniczną funkcję pinJSONToIPFS, która przyjmuje JSONBody jako dane wejściowe oraz klucz i sekret API Pinata w nagłówku, a wszystko to w celu wykonania żądania POST do ich API pinJSONToIPFS.
- Jeśli to żądanie POST zakończy się sukcesem, nasza funkcja zwraca obiekt JSON z wartością logiczną
successustawioną na true orazpinataUrl, gdzie przypięto nasze metadane. Użyjemy tego zwróconegopinataUrljako danych wejściowychtokenURIdla funkcji wybijania naszego inteligentnego kontraktu. - Jeśli to żądanie POST się nie powiedzie, nasza funkcja zwraca obiekt JSON z wartością logiczną
successustawioną na false oraz ciągiem znakówmessage, który przekazuje nasz błąd.
Podobnie jak w przypadku typów zwracanych przez naszą funkcję connectWallet, zwracamy obiekty JSON, abyśmy mogli użyć ich parametrów do aktualizacji naszych zmiennych stanu i interfejsu użytkownika.
Załadowanie inteligentnego kontraktu
Teraz, gdy mamy sposób na przesłanie naszych metadanych NFT do IPFS za pomocą naszej funkcji pinJSONToIPFS, będziemy potrzebować sposobu na załadowanie instancji naszego inteligentnego kontraktu, abyśmy mogli wywołać jego funkcję mintNFT.
Jak wspomnieliśmy wcześniej, w tym samouczku użyjemy tego istniejącego inteligentnego kontraktu NFT (opens in a new tab); jednak jeśli chcesz dowiedzieć się, jak go stworzyliśmy, lub stworzyć własny, gorąco polecamy zapoznanie się z naszym innym samouczkiem: „Jak stworzyć NFT” (opens in a new tab).
ABI kontraktu
Jeśli uważnie przyjrzałeś się naszym plikom, zauważyłeś, że w naszym katalogu src znajduje się plik contract-abi.json. ABI jest niezbędne do określenia, którą funkcję wywoła kontrakt, a także do upewnienia się, że funkcja zwróci dane w oczekiwananym formacie.
Będziemy również potrzebować klucza API Alchemy oraz API Alchemy Web3, aby połączyć się z blockchainem Ethereum i załadować nasz inteligentny kontrakt.
Utworzenie klucza API Alchemy
Jeśli nie masz jeszcze konta Alchemy, zarejestruj się za darmo tutaj. (opens in a new tab)
Po utworzeniu konta Alchemy możesz wygenerować klucz API, tworząc aplikację. Pozwoli nam to na wykonywanie żądań do sieci testowej Ropsten.
Przejdź do strony „Create App” (Utwórz aplikację) w panelu Alchemy, najechawszy na „Apps” (Aplikacje) na pasku nawigacyjnym i klikając „Create App”.
Nazwij swoją aplikację (my wybraliśmy „My First NFT!”), podaj krótki opis, wybierz „Staging” jako środowisko (Environment) używane do księgowości aplikacji i wybierz „Ropsten” jako swoją sieć (Network).
Kliknij „Create app” i to wszystko! Twoja aplikacja powinna pojawić się w tabeli poniżej.
Niesamowite, więc teraz, gdy utworzyliśmy nasz adres URL HTTP API Alchemy, skopiuj go do schowka...
…a następnie dodajmy go do naszego pliku .env. W sumie Twój plik .env powinien wyglądać tak:
REACT_APP_PINATA_KEY = <pinata-key>
REACT_APP_PINATA_SECRET = <pinata-secret>
REACT_APP_ALCHEMY_KEY = https://eth-ropsten.alchemyapi.io/v2/<alchemy-key>
Teraz, gdy mamy nasze ABI kontraktu i nasz klucz API Alchemy, jesteśmy gotowi do załadowania naszego inteligentnego kontraktu za pomocą Alchemy Web3 (opens in a new tab).
Konfiguracja punktu końcowego Alchemy Web3 i kontraktu
Po pierwsze, jeśli jeszcze tego nie zrobiłeś, będziesz musiał zainstalować Alchemy Web3 (opens in a new tab), przechodząc do katalogu domowego: nft-minter-tutorial w terminalu:
cd ..
npm install @alch/alchemy-web3
Następnie wróćmy do naszego pliku interact.js. Na górze pliku dodaj następujący kod, aby zaimportować klucz Alchemy z pliku .env i skonfigurować punkt końcowy Alchemy Web3:
require("dotenv").config()
const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY
const { createAlchemyWeb3 } = require("@alch/alchemy-web3")
const web3 = createAlchemyWeb3(alchemyKey)
Alchemy Web3 (opens in a new tab) to wrapper dla Web3.js (opens in a new tab), zapewniający ulepszone metody API i inne kluczowe korzyści, które ułatwią Ci życie jako programiście Web3. Został zaprojektowany tak, aby wymagał minimalnej konfiguracji, dzięki czemu możesz od razu zacząć go używać w swojej aplikacji!
Następnie dodajmy do naszego pliku ABI kontraktu i adres kontraktu.
require("dotenv").config()
const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY
const { createAlchemyWeb3 } = require("@alch/alchemy-web3")
const web3 = createAlchemyWeb3(alchemyKey)
const contractABI = require("../contract-abi.json")
const contractAddress = "0x4C4a07F737Bf57F6632B6CAB089B78f62385aCaE"
Gdy mamy już oba te elementy, jesteśmy gotowi do rozpoczęcia kodowania naszej funkcji wybijania!
Implementacja funkcji mintNFT
Wewnątrz pliku interact.js zdefiniujmy naszą funkcję mintNFT, która, jak sama nazwa wskazuje, wybije nasze NFT.
Ponieważ będziemy wykonywać liczne wywołania asynchroniczne (do Pinata, aby przypiąć nasze metadane do IPFS, do Alchemy Web3, aby załadować nasz inteligentny kontrakt, oraz do MetaMask, aby podpisać nasze transakcje), nasza funkcja również będzie asynchroniczna.
Trzema danymi wejściowymi do naszej funkcji będą url naszego aktywa cyfrowego, name oraz description. Dodaj następującą sygnaturę funkcji poniżej funkcji connectWallet:
export const mintNFT = async (url, name, description) => {}
Obsługa błędów danych wejściowych
Naturalnie, sensowne jest posiadanie jakiejś formy obsługi błędów danych wejściowych na początku funkcji, abyśmy mogli z niej wyjść, jeśli nasze parametry wejściowe nie są poprawne. Wewnątrz naszej funkcji dodajmy następujący kod:
export const mintNFT = async (url, name, description) => {
//obsługa błędów
if (url.trim() == "" || name.trim() == "" || description.trim() == "") {
return {
success: false,
status: "❗Please make sure all fields are completed before minting.",
}
}
}
Zasadniczo, jeśli którykolwiek z parametrów wejściowych jest pustym ciągiem znaków, zwracamy obiekt JSON, w którym wartość logiczna success to false, a ciąg znaków status przekazuje informację, że wszystkie pola w naszym interfejsie użytkownika muszą być wypełnione.
Przesłanie metadanych do IPFS
Gdy wiemy już, że nasze metadane są sformatowane poprawnie, następnym krokiem jest opakowanie ich w obiekt JSON i przesłanie do IPFS za pomocą napisanej przez nas funkcji pinJSONToIPFS!
Aby to zrobić, najpierw musimy zaimportować funkcję pinJSONToIPFS do naszego pliku interact.js. Na samej górze pliku interact.js dodajmy:
import { pinJSONToIPFS } from "./pinata.js"
Przypomnijmy, że pinJSONToIPFS przyjmuje ciało JSON. Zanim więc ją wywołamy, będziemy musieli sformatować nasze parametry url, name i description w obiekt JSON.
Zaktualizujmy nasz kod, aby utworzyć obiekt JSON o nazwie metadata, a następnie wywołajmy pinJSONToIPFS z tym parametrem metadata:
export const mintNFT = async (url, name, description) => {
//obsługa błędów
if (url.trim() == "" || name.trim() == "" || description.trim() == "") {
return {
success: false,
status: "❗Please make sure all fields are completed before minting.",
}
}
//utwórz metadane
const metadata = new Object()
metadata.name = name
metadata.image = url
metadata.description = description
//wykonaj wywołanie do Pinata
const pinataResponse = await pinJSONToIPFS(metadata)
if (!pinataResponse.success) {
return {
success: false,
status: "😢 Something went wrong while uploading your tokenURI.",
}
}
const tokenURI = pinataResponse.pinataUrl
}
Zauważ, że przechowujemy odpowiedź z naszego wywołania pinJSONToIPFS(metadata) w obiekcie pinataResponse. Następnie parsujemy ten obiekt w poszukiwaniu błędów.
Jeśli wystąpi błąd, zwracamy obiekt JSON, w którym wartość logiczna success to false, a nasz ciąg znaków status przekazuje informację, że nasze wywołanie się nie powiodło. W przeciwnym razie wyodrębniamy pinataURL z pinataResponse i przechowujemy go jako naszą zmienną tokenURI.
Teraz nadszedł czas, aby załadować nasz inteligentny kontrakt za pomocą API Alchemy Web3, które zainicjowaliśmy na górze naszego pliku. Dodaj następującą linię kodu na dole funkcji mintNFT, aby ustawić kontrakt w zmiennej globalnej window.contract:
window.contract = await new web3.eth.Contract(contractABI, contractAddress)
Ostatnią rzeczą do dodania w naszej funkcji mintNFT jest nasza transakcja Ethereum:
//skonfiguruj swoją transakcję Ethereum
const transactionParameters = {
to: contractAddress, // Wymagane z wyjątkiem publikacji kontraktu.
from: window.ethereum.selectedAddress, // musi pasować do aktywnego adresu użytkownika.
data: window.contract.methods
.mintNFT(window.ethereum.selectedAddress, tokenURI)
.encodeABI(), //wykonaj wywołanie do inteligentnego kontraktu NFT
}
//podpisz transakcję przez MetaMask
try {
const txHash = await window.ethereum.request({
method: "eth_sendTransaction",
params: [transactionParameters],
})
return {
success: true,
status:
"✅ Check out your transaction on Etherscan: https://ropsten.etherscan.io/tx/" +
txHash,
}
} catch (error) {
return {
success: false,
status: "😥 Something went wrong: " + error.message,
}
}
Jeśli znasz już transakcje Ethereum, zauważysz, że struktura jest dość podobna do tego, co już widziałeś.
- Po pierwsze, konfigurujemy parametry naszej transakcji.
tookreśla adres odbiorcy (nasz inteligentny kontrakt)fromokreśla osobę podpisującą transakcję (adres użytkownika połączony z MetaMask:window.ethereum.selectedAddress)datazawiera wywołanie metodymintNFTnaszego inteligentnego kontraktu, która otrzymuje naszetokenURIoraz adres portfela użytkownika,window.ethereum.selectedAddress, jako dane wejściowe
- Następnie wykonujemy wywołanie await,
window.ethereum.request,, w którym prosimy MetaMask o podpisanie transakcji. Zauważ, że w tym żądaniu określamy naszą metodę eth (eth_SentTransaction) i przekazujemy naszetransactionParameters. W tym momencie MetaMask otworzy się w przeglądarce i poprosi użytkownika o podpisanie lub odrzucenie transakcji.- Jeśli transakcja zakończy się sukcesem, funkcja zwróci obiekt JSON, w którym wartość logiczna
successjest ustawiona na true, a ciąg znakówstatuszachęca użytkownika do sprawdzenia Etherscan w celu uzyskania dodatkowych informacji o transakcji. - Jeśli transakcja się nie powiedzie, funkcja zwróci obiekt JSON, w którym wartość logiczna
successjest ustawiona na false, a ciąg znakówstatusprzekazuje komunikat o błędzie.
- Jeśli transakcja zakończy się sukcesem, funkcja zwróci obiekt JSON, w którym wartość logiczna
W sumie nasza funkcja mintNFT powinna wyglądać tak:
export const mintNFT = async (url, name, description) => {
//obsługa błędów
if (url.trim() == "" || name.trim() == "" || description.trim() == "") {
return {
success: false,
status: "❗Please make sure all fields are completed before minting.",
}
}
//utwórz metadane
const metadata = new Object()
metadata.name = name
metadata.image = url
metadata.description = description
//żądanie przypięcia Pinata
const pinataResponse = await pinJSONToIPFS(metadata)
if (!pinataResponse.success) {
return {
success: false,
status: "😢 Something went wrong while uploading your tokenURI.",
}
}
const tokenURI = pinataResponse.pinataUrl
//załaduj inteligentny kontrakt
window.contract = await new web3.eth.Contract(contractABI, contractAddress) //loadContract();
//skonfiguruj swoją transakcję Ethereum
const transactionParameters = {
to: contractAddress, // Wymagane z wyjątkiem publikacji kontraktu.
from: window.ethereum.selectedAddress, // musi pasować do aktywnego adresu użytkownika.
data: window.contract.methods
.mintNFT(window.ethereum.selectedAddress, tokenURI)
.encodeABI(), //wykonaj wywołanie do inteligentnego kontraktu NFT
}
//podpisz transakcję przez MetaMask
try {
const txHash = await window.ethereum.request({
method: "eth_sendTransaction",
params: [transactionParameters],
})
return {
success: true,
status:
"✅ Check out your transaction on Etherscan: https://ropsten.etherscan.io/tx/" +
txHash,
}
} catch (error) {
return {
success: false,
status: "😥 Something went wrong: " + error.message,
}
}
}
To jedna gigantyczna funkcja! Teraz musimy tylko połączyć naszą funkcję mintNFT z naszym komponentem Minter.js...
Połączenie mintNFT z naszym frontendem Minter.js
Otwórz plik Minter.js i zaktualizuj linię import { connectWallet, getCurrentWalletConnected } from "./utils/interact.js"; na górze, aby wyglądała tak:
import {
connectWallet,
getCurrentWalletConnected,
mintNFT,
} from "./utils/interact.js"
Na koniec zaimplementuj funkcję onMintPressed, aby wykonać wywołanie await do zaimportowanej funkcji mintNFT i zaktualizować zmienną stanu status, aby odzwierciedlić, czy nasza transakcja zakończyła się sukcesem, czy niepowodzeniem:
const onMintPressed = async () => {
const { status } = await mintNFT(url, name, description)
setStatus(status)
}
Wdrożenie NFT na działającej stronie internetowej
Gotowy, aby udostępnić swój projekt użytkownikom do interakcji? Sprawdź ten samouczek (opens in a new tab) dotyczący wdrażania Mintera na działającej stronie internetowej.
Jeszcze jeden, ostatni krok...
Podbij świat blockchaina
Żartuję, dotarłeś do końca samouczka!
Podsumowując, budując minter NFT, z powodzeniem nauczyłeś się, jak:
- Połączyć się z MetaMask przez swój projekt frontendowy
- Wywoływać metody inteligentnego kontraktu z frontendu
- Podpisywać transakcje za pomocą MetaMask
Prawdopodobnie chciałbyś móc pochwalić się w swoim portfelu NFT wybitymi za pośrednictwem Twojej dapp — więc koniecznie sprawdź nasz krótki samouczek Jak wyświetlić swoje NFT w portfelu (opens in a new tab)!
I jak zawsze, jeśli masz jakiekolwiek pytania, jesteśmy tutaj, aby pomóc na Discordzie Alchemy (opens in a new tab). Nie możemy się doczekać, aby zobaczyć, jak zastosujesz koncepcje z tego samouczka w swoich przyszłych projektach!