Vai al contenuto principale

Tutorial del Coniatore di NFT

solidityNFTalchemycontratti intelligentifrontendPinata
Intermedio
smudgil
6 ottobre 2021
28 minuti letti minute read

Una delle più grandi sfide per gli sviluppatori provenienti da Web2 è comprendere come connettere il proprio smart contract a un progetto di frontend, e come interagire con esso.

Creando un coniatore di NFT, una semplice UI in cui è possibile inserire un link alla risorsa digitale, un titolo e una descrizione, imparerai come:

  • Connetterti a MetaMask tramite il progetto del tuo frontend
  • Chiamare i metodi dello smart contract dal tuo frontend
  • Firmare le transazioni usando MetaMask

In questo tutorial, utilizzeremo React(opens in a new tab) come framework di frontend. Poiché questo tutorial è incentrato principalmente sullo sviluppo di Web3, non dedicheremo molto tempo ad analizzare i fondamenti di React. Al contrario, ci concentreremo sul portare funzionalità al nostro progetto.

Come prerequisito, dovresti avere conoscenze di base di React e sapere come funzionano i componenti, gli accessori, useState/useEffect e la chiamata delle funzioni di base. Se non hai mai sentito parlare di alcuno di questi termini prima d'ora, è consigliabile dare un'occhiata a questo tutorial d'introduzione a React(opens in a new tab). Per chi preferisce l'apprendimento visivo, consigliamo vivamente quest'eccellente serie di video Tutorial moderno e completo su React(opens in a new tab) di Net Ninja.

E se non lo hai già fatto, necessiterai decisamente di un conto di Alchemy, per completare questo tutorial, nonché per creare qualsiasi cosa sulla blockchain. Registra gratuitamente un conto,qui(opens in a new tab).

Iniziamo quindi!

Guida alla Creazione di NFT

Prima ancora d'iniziare ad esaminare qualsiasi codice, è importante comprendere come funziona la creazione di un NFT. Si articola in due fasi:

Pubblicare lo smart contract di un NFT sulla blockchain di Ethereum

La più grande differenza tra i due standard di smart contract di NFT è che ERC-1155 è uno standard multi-token e comprende funzionalità batch, mentre ERC-721 è uno standard a token singolo, supporta dunque solo il trasferimento di un token per volta.

Chiamare la funzione di conio

Solitamente, questa funzione di conio richiede di passare due variabili come parametri, prima recipient, che specifica l'indirizzo che riceverà il tuo NFT appena coniato e poi il tokenURI del NFT, una stringa che si risolve a un documento JSON che descrive i metadati del NFT.

I metadati di un NFT sono davvero ciò che lo porta in vita, consentendogli di avere proprietà, quali nome, descrizione, immagine (o altre risorse digitali) e altri attributi. Ecco un esempio di un tokenURI(opens in a new tab), contenente i metadati di un NFT.

In questo tutorial, ci concentreremo sulla parte 2: chiamare una funzione di conio dello smart contract del NFT esistente usando la nostra UI di React.

Ecco un link(opens in a new tab) allo smart contract del NFT dell'ERC-721 che chiameremo in questo tutorial. Se sei interessato a imparare come lo abbiamo creato, consigliamo vivamente di dare un'occhiata al nostro tutorial, "Come creare un NFT"(opens in a new tab).

Forte! Ora che sappiamo come funziona la creazione di un NFT, cloniamo i nostri file iniziali!

Clonare i file iniziali

Prima, vai al repository di GitHub nft-minter-tutorial(opens in a new tab) per ottenere i file iniziali per questo progetto. Clona questo repository nel tuo ambiente locale.

Quando apri questo repository nft-minter-tutorial clonato, noterai che contiene due cartelle: minter-starter-files e nft-minter.

  • minter-starter-files contiene i file iniziali (essenzialmente l'UI di React) per questo progetto. In questo tutorial, lavoreremo in questa cartella, mentre impari a dar vita a questa UI connettendola al tuo portafoglio di Ethereum e a uno smart contract di NFT.
  • nft-minter contiene l'intero tutorial completato e serve come riferimento se dovessi bloccarti.

Apri quindi la tua copia di minter-starter-files nel tuo editor di codice e poi vai alla cartella src.

Tutto il codice che scriveremo sarà sotto la cartella src. Modificheremo il componente Minter.js e scriveremo altri file in JavaScript per dare funzionalità al nostro progetto Web3.

Fase 2: dai un'occhiata ai nostri file iniziali

Prima di iniziare a programmare, è importante dare un'occhiata a ciò che è già disponibile nei file iniziali.

Metti in funzione il tuo progetto di React

Iniziamo eseguendo il progetto di React nel browser. La bellezza di React è che una volta eseguito il nostro progetto nel browser, ogni modifica che salviamo sarà aggiornata dal vivo nel browser.

Per mettere il progetto in funzione, vai alla cartella di root della cartella minter-starter-files ed esegui npm install nel terminale per installare le dipendenze del progetto:

cd minter-starter-files
npm install

Una volta terminata l'installazione, esegui npm start nel terminale:

npm start

Così facendo, dovrebbe aprirsi http://localhost:3000/ nel browser, dove vedrai il frontend per il nostro progetto. Dovrebbe consistere in 3 campi: un luogo per inserire un link alla tua risorsa NFT, uno per inserire il nome e uno per fornire una descrizione.

Se provi a cliccare i pulsanti "Connetti Portafoglio" o "Conia NFT", noterai che non funzionano, questo perché devi ancora programmarne la funzionalità! :)

Il componente Minter.js

NOTA: Assicurati di essere nella cartella minter-starter-files e non nella cartella nft-minter!

Torniamo alla cartella src nell'editor e apriamo il file Minter.js. È davvero importante comprendere tutto il contenuto di questo file, che è il componente principale di React su cui lavoreremo.

In cima al nostro file, abbiamo le nostre variabili di stato che aggiorneremo dopo eventi specifici.

1//State variables
2const [walletAddress, setWallet] = useState("")
3const [status, setStatus] = useState("")
4const [name, setName] = useState("")
5const [description, setDescription] = useState("")
6const [url, setURL] = useState("")

Mai sentito parlare di variabili di stato di React o di hook di stato? Dai un'occhiata a questa(opens in a new tab) documentazione.

Ecco cosa rappresenta ognuna delle variabili:

  • walletAddress - una stringa che memorizza l'indirizzo del portafoglio dell'utente
  • status - una stringa contenente un messaggio da mostrare in fondo all'UI
  • name - una stringa che memorizza il nome del NFT
  • description - una stringa che memorizza la descrizione del NFT
  • url - una stringa che rappresenta un link alla risorsa digitale del NFT

Dopo le variabili di stato, vedrai tre funzioni non implementate: useEffect, connectWalletPressed e onMintPressed. Noterai che tutte queste funzioni sono async, perché al loro interno effettueremo chiamate asincrone all'API! I nomi sono indicativi delle loro funzionalità:

1useEffect(async () => {
2 //TODO: implement
3}, [])
4
5const connectWalletPressed = async () => {
6 //TODO: implement
7}
8
9const onMintPressed = async () => {
10 //TODO: implement
11}
Mostra tutto

Vicino alla fine di questo file, abbiamo l'UI del nostro componente. Se esamini attentamente questo codice, noterai che aggiorniamo le nostre variabili di stato url, name e description, quando l'input nei relativi campi di testo cambia.

Vedrai anche che connectWalletPressed e onMintPressed vengono chiamate rispettivamente quando viene fatto clic sui pulsanti con ID mintButton e walletButton.

1//the UI of our component
2return (
3 <div className="Minter">
4 <button id="walletButton" onClick={connectWalletPressed}>
5 {walletAddress.length > 0 ? (
6 "Connected: " +
7 String(walletAddress).substring(0, 6) +
8 "..." +
9 String(walletAddress).substring(38)
10 ) : (
11 <span>Connect Wallet</span>
12 )}
13 </button>
14
15 <br></br>
16 <h1 id="title">🧙‍♂️ Alchemy NFT Minter</h1>
17 <p>
18 Simply add your asset's link, name, and description, then press "Mint."
19 </p>
20 <form>
21 <h2>🖼 Link to asset: </h2>
22 <input
23 type="text"
24 placeholder="e.g. https://gateway.pinata.cloud/ipfs/<hash>"
25 onChange={(event) => setURL(event.target.value)}
26 />
27 <h2>🤔 Name: </h2>
28 <input
29 type="text"
30 placeholder="e.g. My first NFT!"
31 onChange={(event) => setName(event.target.value)}
32 />
33 <h2>✍️ Description: </h2>
34 <input
35 type="text"
36 placeholder="e.g. Even cooler than cryptokitties ;)"
37 onChange={(event) => setDescription(event.target.value)}
38 />
39 </form>
40 <button id="mintButton" onClick={onMintPressed}>
41 Mint NFT
42 </button>
43 <p id="status">{status}</p>
44 </div>
45)
Mostra tutto

Infine, vediamo dove viene aggiunto questo componente del Coniatore.

Se vai al file App.js, che è il componente principale su React e che agisce come contenitore per tutti gli altri componenti, vedrai che il nostro componente del Coniatore è inserito alla riga 7.

In questo tutorial, modificheremo solo il file Minter.js e aggiungeremo i file alla nostra cartella src.

Ora che ci è chiaro con cosa stiamo lavorando, configuriamo il portafoglio di Ethereum!

Configura il tuo wallet Ethereum

Per poter interagire con il tuo smart contract, gli utenti dovranno connettere il proprio portafoglio di Ethereum alla tua dapp.

Scarica MetaMask

Per questo tutorial, utilizzeremo MetaMask, un portafoglio virtuale nel browser, utilizzato per gestire l'indirizzo del tuo conto di Ethereum. Se vuoi capire di più su come funzionano le transazioni su Ethereum, dai un'occhiata a questa pagina.

Puoi scaricare e creare gratuitamente un conto di MetaMask qui(opens in a new tab). Quando stai creando un conto, o se ne hai già uno, assicurati di passare alla "Rete di Prova di Ropsten" in alto a destra (così da non avere a che fare con denaro reale).

Aggiungere ether da un Faucet

Per coniare i nostri NFT (o firmare qualsiasi transazione sulla blockchain di Ethereum), avremo bisogno di qualche finto Eth. Per ottenere degli Eth puoi andare al faucet di Ropsten(opens in a new tab) e inserire l'indirizzo del tuo conto di Ropsten, poi cliccare “Invia Eth a Ropsten.” Poco dopo, dovresti vedere gli Eth nel tuo conto di MetaMask!

Controlla il tuo saldo

Per ricontrollare che ci sia il saldo, facciamo una richiesta eth_getBalance(opens in a new tab) usando lo strumento compositore di Alchemy(opens in a new tab). Questo restituirà l'importo di Eth nel tuo portafoglio. Dopo aver inserito l'indirizzo del tuo conto di MetaMask e aver cliccato "Invia Richiesta", dovresti visualizzare una risposta simile alla seguente:

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

NOTA: Questo risultato è in wei non in eth. Wei è usato come taglio più piccolo dell'ether. La conversione da wei a eth è: 1 eth = 10¹⁸ wei. Quindi se convertiamo 0xde0b6b3a7640000 in decimali, otteniamo 1*10¹⁸, pari a 1 eth.

Meno male! I nostri soldi finti ci sono tutti!

Connettere MetaMask alla UI

Ora che il nostro portafoglio di MetaMask è configurato, connettiamo la nostra dapp!

Poiché vogliamo prescrivere al paradigma del MVC(opens in a new tab), creeremo un file separato che contiene le nostre funzioni per gestire la logica, i dati e le regole della nostra dapp e poi passeremo tali funzioni al nostro frontend (il nostro componente Minter.js).

La funzione connectWallet

Per farlo, creiamo una nuova cartella chiamata utils nella nostra cartella src e aggiungiamo al suo interno un file chiamato interact.js, che conterrà tutte le funzioni d'interazione del nostro portafoglio e del nostro smart contract.

Nel nostro file interact.js, scriveremo una funzione connectWallet, che poi importeremo e chiameremo nel nostro componente Minter.js.

Nel tuo file interact.js, aggiungi quanto segue

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: "👆🏽 Write a message in the text-field above.",
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.html`}>
26 You must install MetaMask, a virtual Ethereum wallet, in your
27 browser.
28 </a>
29 </p>
30 </span>
31 ),
32 }
33 }
34}
Mostra tutto

Analizziamo cosa fa questo codice:

Per prima cosa, la nostra funzione verifica se window.ethereum è abilitato nel browser.

window.ethereum è un'API globale, iniettata da MetaMask e altri fornitori di portafogli, che consente ai siti web di richiedere i conti di Ethereum degli utenti. Se approvata, può leggere i dati dalle blockchain a cui è connesso l'utente e suggerire all'utente di firmare messaggi e transazioni. Dai un'occhiata alla documentazione di MetaMask(opens in a new tab) per ulteriori informazioni!

Se window.ethereum non è presente, significa che MetaMask non è installato. Verrà quindi restituito un oggetto JSON in cui l'address restituito è una stringa vuota e l'oggetto JSX di status indica che l'utente deve installare MetaMask.

Gran parte delle funzioni che scriveremo restituiranno oggetti JSON che possiamo usare per aggiornare le nostre variabili di stato e l'UI.

Ora, se window.ethereum è presente, le cose cominciano a farsi interessanti.

Utilizzando un ciclo try/catch, proveremo a connetterci a MetaMask chiamando [window.ethereum.request({ method: "eth_requestAccounts" });](https://docs.metamask.io/guide/rpc-api.html#eth-requestaccounts). Chiamare questa funzione aprirà MetaMask nel browser, dove sarà richiesto all'utente di connettere il proprio portafoglio alla tua dapp.

  • Se l'utente sceglie di connettersi, method: "eth_requestAccounts" restituirà un insieme contenente tutti gli indirizzi del conto dell'utente, connessi alla dapp. Nel complesso, la nostra funzione connectWallet restituirà un oggetto JSON contenente il primo address in questo array (vedi la riga 9) e un messaggio di status che richiede all'utente di scrivere un messaggio nello smart contract.
  • Se l'utente rifiuta la connessione, allora l'oggetto JSON conterrà una stringa vuota per l'address restituito e un messaggio di status che indica che l'utente ha rifiutato la connessione.

Aggiungi la funzione connectWallet al tuo componente UI Minter.js

Ora che abbiamo scritto questa funzione connectWallet, connettiamola al nostro componente Minter.js..

Prima, dovremo importare la nostra funzione nel file Minter.js, aggiungendo import { connectWallet } from "./utils/interact.js"; in cima al file Minter.js. Le tue prime 11 righe di Minter.js dovrebbero somigliare a questo:

1import { useEffect, useState } from "react";
2import { connectWallet } from "./utils/interact.js";
3
4const Minter = (props) => {
5
6 //State variables
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("");
Mostra tutto

Poi, nella nostra funzione connectWalletPressed, chiameremo la funzione connectWallet importata, come quella che segue:

1const connectWalletPressed = async () => {
2 const walletResponse = await connectWallet()
3 setStatus(walletResponse.status)
4 setWallet(walletResponse.address)
5}

Nota come gran parte della nostra funzionalità è esterna al nostro componente Minter.js dal file interact.js? Questo perché stiamo seguendo il modello M-V-C!

In connectWalletPressed, creiamo semplicemente una chiamata d'attesa alla nostra funzione connectWallet importata e, usando la sua risposta, aggiorniamo le nostre variabili status e walletAddress tramite i loro hook di stato.

Ora, salviamo entrambi i file Minter.js e interact.js e testiamo la nostra UI.

Apri il browser su localhost:3000 e premi il pulsante "Connetti Portafoglio" in alto a destra alla pagina.

Se hai MetaMask installato, ti dovrebbe essere richiesto di connettere il tuo portafoglio alla tua dapp. Accetta l'invito a connetterti.

Dovresti vedere ora che il pulsante del portafoglio indica che l'indirizzo è connesso.

Prova quindi a ricaricare la pagina... questo è strano. Il nostro pulsante del portafoglio ci sta richiedendo di connetterci a MetaMask, anche se è già connesso...

Non preoccuparti! Possiamo risolverlo facilmente implementando una funzione chiamata getCurrentWalletConnected, che verificherà se un indirizzo è già connesso alla nostra dapp e aggiornerà l'UI di conseguenza!

La funzione getCurrentWalletConnected

Nel file interact.js, aggiungi la seguente funzione 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: "👆🏽 Write a message in the text-field above.",
11 }
12 } else {
13 return {
14 address: "",
15 status: "🦊 Connect to MetaMask using the top right button.",
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.html`}>
32 You must install MetaMask, a virtual Ethereum wallet, in your
33 browser.
34 </a>
35 </p>
36 </span>
37 ),
38 }
39 }
40}
Mostra tutto

Questo codice è molto simile alla funzione connectWallet che abbiamo scritto poco fa.

La differenza principale è che, invece di chiamare il metodo eth_requestAccounts, che apre MetaMask perché l'utente connetta il proprio portafoglio, qui chiamiamo il metodo eth_accounts che, semplicemente, restituisce un insieme contenente gli indirizzi di MetaMask correntemente connessi alla nostra dapp.

Per vedere questa funzione in azione, chiamiamola nella funzione useEffect del nostro componente Minter.js.

Come abbiamo fatto per connectWallet, dobbiamo importare questa funzione dal file interact.js al file Minter.js, come segue:

1import { useEffect, useState } from "react"
2import {
3 connectWallet,
4 getCurrentWalletConnected, //import here
5} from "./utils/interact.js"

Ora, semplicemente, chiamiamola nella nostra funzione useEffect:

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

Nota che stiamo usando la risposta alla nostra chiamata a getCurrentWalletConnected per aggiornare le nostre variabili di stato walletAddress e status.

Una volta aggiunto questo codice, prova a ricaricare la nostra finestra del browser. Il pulsante dovrebbe dire che sei connesso e mostrare un'anteprima dell'indirizzo del tuo portafoglio connesso, anche dopo un refresh!

Implementare addWalletListener

Il passaggio finale della configurazione del portafoglio della nostra dapp è implementare l'ascoltatore del portafoglio, così che la nostra UI si aggiorni al cambiamento dello stato del nostro portafoglio, ad esempio, quando l'utente si disconnette o cambia conto.

Nel file Minter.js, aggiungi una funzione addWalletListener, simile a quanto segue:

1function addWalletListener() {
2 if (window.ethereum) {
3 window.ethereum.on("accountsChanged", (accounts) => {
4 if (accounts.length > 0) {
5 setWallet(accounts[0])
6 setStatus("👆🏽 Write a message in the text-field above.")
7 } else {
8 setWallet("")
9 setStatus("🦊 Connect to MetaMask using the top right button.")
10 }
11 })
12 } else {
13 setStatus(
14 <p>
15 {" "}
16 🦊 <a target="_blank" href={`https://metamask.io/download.html`}>
17 You must install MetaMask, a virtual Ethereum wallet, in your browser.
18 </a>
19 </p>
20 )
21 }
22}
Mostra tutto

Esaminiamo rapidamente cosa sta succedendo qui:

  • Per prima cosa, la nostra funzione verifica se window.ethereum è abilitata (cioè se MetaMask è installato).
    • Se non lo è, impostiamo semplicemente la nostra variabile di stato statusa una stringa JSX che richiede all'utente di installare MetaMask.
    • Se è abilitato, configuriamo l'ascoltatore window.ethereum.on("accountsChanged") alla riga 3, affinché ascolti i cambiamenti di stato nel portafoglio di MetaMask, tra cui, quando l'utente connette un ulteriore conto alla dapp, cambia conto, o ne disconnette uno. Se è connesso almeno un conto, la variabile di stato walletAddress è aggiornata come primo conto nell'insieme accounts, restituito dall'ascoltatore. Altrimenti, walletAddress è impostato come una stringa vuota.

Infine, dobbiamo chiamarlo nella nostra funzione useEffect:

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

E voilà! Abbiamo completato la programmazione di tutte le funzionalità del nostro portafoglio! Ora che il nostro portafoglio è configurato, cerchiamo di capire come coniare il nostro NFT!

Guida di base ai Metadati del NFT

Ricorda quindi che i metadati del NFT di cui abbiamo appena parlato al Passaggio 0 di questo tutorial, portano in vita un NFT, consentendogli di avere proprietà quali una risorsa digitale, un nome, una descrizione e altri attributi.

Dovremo configurare questi metadati come un oggetto JSON e memorizzarli, quindi potremo passarli come parametro tokenURI, chiamando la nostra funzione mintNFT dello smart contract.

Il testo nei campi "Link to Asset", "Name", "Description" comprenderà le diverse proprietà dei metadati del nostro NFT. Formatteremo questi metadati come un oggetto JSON, ma esistono un paio di opzioni per dove possiamo memorizzare questo oggetto:

  • Potremmo memorizzarlo sulla blockchain di Ethereum; ma farlo sarebbe molto costoso.
  • Potremmo memorizzarlo su un server centralizzato, come AWS o Firebase. Ma questo sarebbe contrario alla nostra etica di decentralizzazione.
  • Potremmo usare IPFS, un protocollo decentralizzato e rete peer-to-peer per memorizzare e condividere dati in un sistema di file distribuito. Poiché questo protocollo è decentralizzato e libero, è la nostra opzione preferita!

Per memorizzare i nostri metadati su IPFS, useremo Pinata(opens in a new tab), una comoda API e un toolkit per IPFS. Al prossimo passaggio, spiegheremo esattamente come farlo!

(opens in a new tab)Utilizza Pinata per fissare i tuoi metadati su IPFS

Se non hai un conto di Pinata(opens in a new tab), registrane gratuitamente uno qui(opens in a new tab) e completa i passaggi per verificare la tua email e il tuo conto.

Crea la tua chiave API di Pinata

Vai alla pagina https://pinata.cloud/keys(opens in a new tab), quindi seleziona il pulsante "Nuova Chiave" in alto, abilita il widget Admin e assegna un nome alla tua chiave.

Ti sarà poi mostrato un popup con le informazioni sulla tua API. Assicurati di conservarle da qualche parte al sicuro.

Ora che la nostra chiave è configurata, aggiungiamola al nostro progetto così da poterla usare.

Crea un file .env

Possiamo memorizzare in sicurezza la nostra chiave e il codice segreto di Pinata in un file di ambiente. Installiamo il pacchetto dotenv(opens in a new tab) nella cartella del progetto.

Apri una nuova scheda nel terminale (separata da quella che sta eseguendo l'host locale) e assicurati di essere nella cartella minter-starter-files, poi esegui il seguente comando nel terminale:

1npm install dotenv --save

Crea quindi un file .env nella cartella di root del tuo minter-starter-files inserendo quanto segue a riga di comando:

1vim.env

Questo aprirà il file .env in vim (un editor di testo). Per salvarlo, clicca "esc" + ":" + "q" sulla tua tastiera in questa sequenza.

Poi, su VSCode, vai al file .env e aggiungi al suo interno la tua chiave API di Pinata e il codice segreto dell'API, come segue:

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

Salva il file: sei pronto ora per scrivere la funzione per caricare i tuoi metadati di JSON su IPFS!

(opens in a new tab)Implementa pinJSONToIPFS

Per nostra fortuna, Pinata ha un'API specifica per caricare i dati JSON su IPFS(opens in a new tab) e un comodo JavaScript con esempio di axios che possiamo usare, con alcune lievi modifiche.

Nella cartella utils creiamo un altro file denominato pinata.js e poi importiamo il nostro codice segreto di Pinata e la chiave dal file .env, come segue:

1require("dotenv").config()
2const key = process.env.REACT_APP_PINATA_KEY
3const secret = process.env.REACT_APP_PINATA_SECRET

Incolla quindi il codice aggiuntivo seguente nel file pinata.js. Non preoccuparti, analizzeremo per bene cosa significa!

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 //making axios POST request to 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}
Mostra tutto

Quindi, cosa fa esattamente questo codice?

Prima di tutto, importa axios(opens in a new tab), un client HTTP basato su Promise per il browser e node.js, che useremo per creare una richiesta a Pinata.

Poi abbiamo la nostra funzione asincrona pinJSONToIPFS, che prende un JSONBody come input e la chiave API e il codice segreto di Pinata nell'intestazione, tutto per creare una richiesta di POST all'API pinJSONToIPFS.

  • Se questa richiesta di POST riesce, allora la nostra funzione restituisce un oggetto JSON con il booleano success impostato a true e il pinataUrl in cui i nostri metadati sono stati fissati. Useremo il pinataUrl restituito come l'input del tokenURI alla funzione di conio del nostro smart contract.
  • Se questa richiesta di POST fallisce, allora la nostra funzione restituisce un oggetto JSON con il booleano success impostato false e una stringa message che comunica l'errore.

Come con i tipi restituiti dalla nostra funzione connectWallet, stiamo restituendo oggetti JSON, così da poterne usare i parametri per aggiornare le nostre variabili di stato e l'UI.

Carica il tuo smart contract

Ora che abbiamo un modo per caricare i metadati del nostro NFT su IPFS tramite la nostra funzione pinJSONToIPFS, avremo bisogno di un modo per caricare un'istanza del nostro smart contract, così da poterne chiamare la funzione mintNFT.

Come menzionato prima, in questo tutorial useremo questo smart contract NFT esistente(opens in a new tab); se invece sei interessato a sapere come lo abbiamo creato, o se vuoi crearne uno tuo, consigliamo vivamente di dare un'occhiata all'altro nostro tutorial, "Come Creare un NFT."(opens in a new tab).

L'ABI del contratto

Se hai esaminato attentamente i nostri file, avrai notato che nella nostra cartella src si trova un file contract-abi.json. Un'ABI serve per specificare quale funzione invocherà un contratto, oltre che per garantire che la funzione restituirà i dati nel formato previsto.

Avremo anche bisogno di una chiave API di Alchemy e dell'API Alchemy Web3 per connetterci alla blockchain di Ethereum e caricare il nostro smart contract.

Crea la tua chiave API di Alchemy

Se non hai già un conto di Alchemy, registrane gratuitamente uno qui.(opens in a new tab)

Una volta creato un conto di Alchemy, puoi generare una chiave API creando un'app. Questo ci consentirà di effettuare richieste alla rete di prova di Ropsten.

Vai alla pagina “Crea App” nella tua dashboard di Alchemy passando su “App” nella barra di navigazione e cliccando “Crea App”.

Dai un nome alla tua app (noi abbiamo scelto “Il mio primo NFT!", aggiungi una breve descrizione, seleziona “Staging” come Ambiente) serve per la contabilità della tua app e scegli "Ropsten" come rete.

Clicca “Crea app” ed è tutto! La tua app dovrebbe apparire nella tabella seguente.

Fantastico, ora che abbiamo creato il nostro URL dell'API di Alchemy HTTP, copiamolo negli appunti...

…e poi aggiungiamolo al nostro file .env. Nel complesso, il file .env dovrebbe somigliare a questo:

1REACT_APP_PINATA_KEY = <pinata-key>
2REACT_APP_PINATA_SECRET = <pinata-secret>
3REACT_APP_ALCHEMY_KEY = https://eth-ropsten.alchemyapi.io/v2/<alchemy-key>

Ora che abbiamo l'ABI del nostro contratto e la nostra chiave API di Alchemy, siamo pronti a caricare il nostro smart contract usando Alchemy Web3(opens in a new tab).

Configura l'endpoint e il contratto di Web3 di Alchemy

Prima di tutto, se non lo hai già fatto, dovrai installare Alchemy Web3(opens in a new tab) navigando alla cartella home: nft-minter-tutorial nel terminale:

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

Torniamo quindi al nostro file interact.js. In cima al file, aggiungi il seguente codice per importare la tua chiave di Alchemy dal file .env e configurare il tuo endpoint di Alchemy Web3:

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

Alchemy Web3(opens in a new tab) è un wrapper intorno aWeb3.js(opens in a new tab) che fornisce metodi API migliorati e altri benefici fondamentale per semplificare la tua vita a uno sviluppatore web3. È progettato per richiedere una configurazione minima, così da poter iniziare a usarlo immediatamente nella tua app!

In seguito, aggiungiamo l'ABI del nostro contratto e l'indirizzo del contratto al nostro file.

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"

Una volta che abbiamo entrambi, siamo pronti a iniziare a programmare la nostra funzione di conio!

Implementa la funzione mintNFT

Nel file interact.js, definiamo la nostra funzione, mintNFT, che conierà il nostro omonimo NFT.

Poiché effettueremo numerose chiamate asincrone (a Pinata per fissare i nostri metadati su IPFS, a Alchemy Web3 per caricare il nostro smart contract e a MetaMask per firmare le nostre transazioni), anche la nostra funzione sarà asincrona.

I tre input alla nostra funzione saranno l'url della nostra risorsa digitale, il name e la description. Aggiungi la seguente firma della funzione sotto la funzione connectWallet:

1export const mintNFT = async (url, name, description) => {}

Gestione degli errori d'input

Naturalmente, è utile avere una certa gestione degli errori di input all'inizio della funzione, uscendo dalla funzione se i nostri parametri di input sono errati. Nella nostra funzione, aggiungiamo il seguente codice:

1export const mintNFT = async (url, name, description) => {
2 //error handling
3 if (url.trim() == "" || name.trim() == "" || description.trim() == "") {
4 return {
5 success: false,
6 status: "❗Please make sure all fields are completed before minting.",
7 }
8 }
9}
Mostra tutto

Essenzialmente, se uno qualsiasi dei parametri d'input è una stringa vuota, restituiamo un oggetto JSON in cui il booleano success è false e la stringa status indica che tutti i campi nella nostra UI devono esser completi.

(opens in a new tab)Carica i metadati su IPFS

Una volta che sappiamo che i nostri metadati sono correttamente formattati, il prossimo passaggio è avvolgerli in un oggetto JSON e caricarli su IPFS tramite il pinJSONToIPFS che abbiamo scritto!

Per farlo, prima dobbiamo importare la funzione pinJSONToIPFS nel nostro file interact.js. In cima al interact.js, aggiungiamo:

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

Ricorda che pinJSONToIPFS riceve in un body JSON. Quindi, prima di effettuare una chiamata a esso, dovremo formattare i nostri parametri url, name e description in un oggetto JSON.

Aggiorniamo il nostro codice per creare un oggetto JSON chiamato metadata e poi effettuiamo una chiamata a pinJSONToIPFS con questo parametro metadata:

1export const mintNFT = async (url, name, description) => {
2 //error handling
3 if (url.trim() == "" || name.trim() == "" || description.trim() == "") {
4 return {
5 success: false,
6 status: "❗Please make sure all fields are completed before minting.",
7 }
8 }
9
10 //make metadata
11 const metadata = new Object()
12 metadata.name = name
13 metadata.image = url
14 metadata.description = description
15
16 //make pinata call
17 const pinataResponse = await pinJSONToIPFS(metadata)
18 if (!pinataResponse.success) {
19 return {
20 success: false,
21 status: "😢 Something went wrong while uploading your tokenURI.",
22 }
23 }
24 const tokenURI = pinataResponse.pinataUrl
25}
Mostra tutto

Nota che memorizziamo la risposta della nostra chiamata a pinJSONToIPFS(metadata) nell'oggetto pinataResponse. Analizziamo quindi questo oggetto alla ricerca di eventuali errori.

Se è presente un errore, restituiamo un oggetto JSON in cui il booleano success è impostato a false e la nostra stringa status indica che la nostra chiamata non è andata a buon fine. Altrimenti, estraiamo pinataURL dal pinataResponse e lo memorizziamo come la nostra variabile tokenURI.

È arrivato il momento di caricare il nostro smart contract usando l'API Alchemy Web3 che abbiamo inizializzato in cima al nostro file. Aggiungi la seguente riga di codice in fondo alla funzione mintNFT per impostare il contratto alla variabile globale window.contract:

1window.contract = await new web3.eth.Contract(contractABI, contractAddress)

L'ultima cosa da aggiungere alla nostra funzione mintNFT è la nostra transazione di Ethereum:

1//set up your Ethereum transaction
2const transactionParameters = {
3 to: contractAddress, // Required except during contract publications.
4 from: window.ethereum.selectedAddress, // must match user's active address.
5 data: window.contract.methods
6 .mintNFT(window.ethereum.selectedAddress, tokenURI)
7 .encodeABI(), //make call to NFT smart contract
8}
9
10//sign the transaction via 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 "✅ Check out your transaction on Etherscan: https://ropsten.etherscan.io/tx/" +
20 txHash,
21 }
22} catch (error) {
23 return {
24 success: false,
25 status: "😥 Something went wrong: " + error.message,
26 }
27}
Mostra tutto

Se conosci già le transazioni di Ethereum, noterai che la struttura è abbastanza simile a quella che hai visto.

  • Prima, configuriamo i parametri delle nostre transazioni.
    • to specifica l'indirizzo del destinatario (il nostro smart contract)
    • from specifica il firmatario della transazione (l'indirizzo dell'utente connesso a MetaMask: window.ethereum.selectedAddress)
    • data contiene la chiamata al metodo mintNFT del nostro smart contract, che riceve come input il nostro tokenURI e l'indirizzo del portafoglio dell'utente, window.ethereum.selectedAddress.
  • Creiamo quindi una chiamata d'attesa, window.ethereum.request, in cui chiediamo a MetaMask di firmare la transazione. Nota che, in questa richiesta, stiamo specificando il nostro metodo eth (eth_SentTransaction) e passando il nostro transactionParameters. A questo punto, MetaMask si aprirà nel browser e richiederà all'utente di firmare o rifiutare la transazione.
    • Se la transazione va a buon fine, la funzione restituirà un oggetto JSON in cui il booleano success è impostato a true e la stringa status richiede all'utente di controllare Etherscan per ulteriori informazioni sulla sua transazione.
    • Se la transazione non va a buon fine, la funzione restituirà un oggetto JSON in cui il booleano success è impostato a false e la stringa status trasmette il messaggio d'errore.

Nel complesso, la nostra funzione mintNFT dovrebbe somigliare a questa:

1export const mintNFT = async (url, name, description) => {
2 //error handling
3 if (url.trim() == "" || name.trim() == "" || description.trim() == "") {
4 return {
5 success: false,
6 status: "❗Please make sure all fields are completed before minting.",
7 }
8 }
9
10 //make metadata
11 const metadata = new Object()
12 metadata.name = name
13 metadata.image = url
14 metadata.description = description
15
16 //pinata pin request
17 const pinataResponse = await pinJSONToIPFS(metadata)
18 if (!pinataResponse.success) {
19 return {
20 success: false,
21 status: "😢 Something went wrong while uploading your tokenURI.",
22 }
23 }
24 const tokenURI = pinataResponse.pinataUrl
25
26 //load smart contract
27 window.contract = await new web3.eth.Contract(contractABI, contractAddress) //loadContract();
28
29 //set up your Ethereum transaction
30 const transactionParameters = {
31 to: contractAddress, // Required except during contract publications.
32 from: window.ethereum.selectedAddress, // must match user's active address.
33 data: window.contract.methods
34 .mintNFT(window.ethereum.selectedAddress, tokenURI)
35 .encodeABI(), //make call to NFT smart contract
36 }
37
38 //sign transaction via 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 "✅ Check out your transaction on Etherscan: https://ropsten.etherscan.io/tx/" +
48 txHash,
49 }
50 } catch (error) {
51 return {
52 success: false,
53 status: "😥 Something went wrong: " + error.message,
54 }
55 }
56}
Mostra tutto

Questa è una funzione gigante! Ora, dobbiamo solo connettere la nostra funzione mintNFT al nostro componente Minter.js...

Connetti mintNFT al nostro frontend di Minter.js

Apri il file Minter.js e aggiorna la riga import { connectWallet, getCurrentWalletConnected } from "./utils/interact.js"; in alto affinché sia:

1import {
2 connectWallet,
3 getCurrentWalletConnected,
4 mintNFT,
5} from "./utils/interact.js"

Infine, implementa la funzione onMintPressed per effettuare la chiamata d'attesa alla tua funzione mintNFT importata e aggiornare la variabile di stato status affinché rifletta se la nostra transazione è andata o meno a buon fine:

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

Distribuisci il tuo NFT a un sito web live

Pronto a portare in vita il tuo progetto affinché gli utenti vi interagiscano? Dai un'occhiata a questo tutorial(opens in a new tab) per distribuire il tuo Coniatore su un sito web live.

Un ultimo passaggio...

Prendi d'assalto il mondo della blockchain

Stiamo scherzando, sei arrivato alla fine del tutorial!

Per ricapitolare, creando un coniatore di NFT, hai imparato correttamente come:

  • Connetterti a MetaMask tramite il progetto del tuo frontend
  • Chiamare i metodi dello smart contract dal tuo frontend
  • Firmare le transazioni usando MetaMask

Molto probabilmente vorrai mostrare gli NFT coniati tramite la tua dapp nel tuo portafoglio, dai quindi un'occhiata al nostro rapido tutorial Come visualizzare il tuo NFT nel tuo Portafoglio(opens in a new tab)!

E, come sempre, se hai qualsiasi domanda, siamo qui per aiutare sul Discord di Alchemy(opens in a new tab). Non vediamo l'ora di vedere come applicherai i concetti di questo tutorial ai tuoi progetti futuri!

Ultima modifica: @wackerow(opens in a new tab), 7 maggio 2024

Questo tutorial è stato utile?