Passer au contenu principal

Tutoriel pour frapper des NFT

solidityNFTalchemycontrats intelligentsfrontendPinata
Intermédiaire
smudgil
6 octobre 2021
30 minutes de lecture minute read

L'un des plus grands défis pour les développeurs venus du Web2 est de comprendre comment connecter son contrat intelligent à un projet d'interface et interagir avec lui.

En construisant un générateur de NFT — une interface simple où vous pouvez saisir un lien vers votre ressource numérique, un titre et une description — vous apprendrez à :

  • Vous connecter à MetaMask via votre interface
  • Appeler les méthodes du contrat intelligent depuis votre interface
  • Signer les transactions à l'aide de MetaMask

Dans ce tutoriel, nous utiliserons React(opens in a new tab) en tant que framework d'interface. Puisque ce tutoriel s'intéresse avant tout au développement Web3, nous ne nous attarderons pas à expliquer les bases de React. Au lieu de cela, nous nous concentrerons sur l'ajout de fonctionnalités à notre projet.

En prérequis, il vous faudra un niveau débutant en React — savoir comment fonctionnent les composants, les props, useState/useEffect, et les appels des fonctions de base. Si vous n'avez jamais entendu parler de ces termes auparavant, vous pouvez consulter ce tutoriel d'introduction à React(opens in a new tab). Pour les apprenants plus visuels, nous recommandons fortement cette excellente série vidéo Full Modern React Tutorial(opens in a new tab) par Net Ninja.

Et si vous ne l'avez pas déjà fait, vous aurez certainement besoin d'un compte Alchemy pour terminer ce tutoriel ainsi que pour construire quoi que ce soit sur la blockchain. Créez un compte gratuit ici(opens in a new tab).

Sans plus attendre, commençons !

Introduction à la fabrication de NFT

Avant même de commencer à regarder du code, il est important de comprendre comment la fabrication d'un NFT fonctionne. Elle comporte deux étapes :

Publier un contrat intelligent de NFT sur la blockchain Ethereum

La plus grande différence entre les deux normes de contrat intelligent NFT est que l'ERC-1155 est un standard multijeton et inclut la fonctionnalité de lot. Alors que l'ERC-721 est un standard à jeton unique et supporte donc uniquement le transfert d'un jeton à la fois.

Appeler la fonction de « frappe » (mint)

Habituellement, cette fonction de frappe nécessite que vous passiez deux variables en paramètres. Tout d'abord le recipient, qui spécifie l'adresse qui recevra votre NFT fraîchement frappé. Et la seconde qui est le tokenURIdu NFT : une chaîne de caractères qui pointe sur un document JSON décrivant les métadonnées du NFT.

Les métadonnées d'un NFT sont ce qui lui donne vie, lui permettant d'avoir des propriétés configurables, comme un nom, une description, une image et d'autres attributs. Voici un exemple de tokenURI(opens in a new tab) qui contient les métadonnées d'un NFT.

Dans ce tutoriel, nous allons nous concentrer sur la deuxième partie, en appelant la fonction existante de frappe d'un contrat intelligent de type NFT avec notre interface React.

Voici un lien(opens in a new tab) vers le contrat intelligent ERC-721 NFT que nous allons appeler dans ce tutoriel. Si vous souhaitez apprendre comment nous l'avons fait, nous vous recommandons fortement de consulter notre autre tutoriel, "Comment créer un NFT"(opens in a new tab).

Bien, maintenant que nous comprenons comment la fabrication de NFT fonctionne, clonons nos fichiers et démarrons  !

Cloner les fichiers de démarrage

Tout d'abord, rendez-vous sur le dépôt GitHub nft-minter-tutorial(opens in a new tab) pour obtenir les fichiers de démarrage de ce projet. Clonez ce dépôt dans votre environnement local.=

Lorsque vous ouvrez ce dépôt nft-minter-tutorial , vous remarquerez qu'il contient deux dossiers : minter-starter-files et nft-minter.

  • minter-starter-files contient les fichiers de démarrage (essentiellement l'interface utilisateur en React) pour ce projet. Dans ce tutoriel, nous travaillerons dans ce répertoire. Au fur et à mesure, vous apprendrez à donner vie à cette interface utilisateur en la connectant à votre portefeuille Ethereum et à un contrat intelligent NFT.
  • nft-minter contient l'intégralité du tutoriel et vous servira de référence si vous êtes coincé.

Ensuite, ouvrez votre copie de minter-starter-files dans votre éditeur de code, puis naviguez dans votre dossier src.

Tout le code que nous allons écrire restera dans le dossier src. Nous allons modifier le composant Minter.js et écrire des fichiers javascript supplémentaires pour ajouter des fonctionnalités à notre projet Web3.

Étape 2 : Vérifier nos fichiers de démarrage

Avant de commencer à coder, il est important de connaître ce qui est déjà fourni dans les fichiers de base.

Faites tourner votre projet de React

Commençons par exécuter le projet React dans notre navigateur. La beauté de React est qu'une fois que notre projet est en cours d'exécution dans notre navigateur, toutes les modifications que nous sauvegardons seront mises à jour en direct dans notre navigateur.

Pour faire fonctionner le projet, accédez au répertoire racine du dossier minter-starter-files et exécutez npm install dans votre terminal pour installer les dépendances du projet :

cd minter-starter-files
npm install

Une fois l'installation terminée, exécutez npm start dans votre terminal :

npm start

Cela devrait ouvrir http://localhost:3000/ dans votre navigateur, où vous verrez le frontend pour notre projet. Il devrait se composer de 3 champs : un pour renseigner le lien vers l'actif de votre NFT, un pour le nom de votre NFT, et un pour fournir une description.

Si vous essayez de cliquer sur les boutons « Connect Wallet » (connecter le portefeuille) ou « Mint NFT » (frapper un NFT), vous remarquerez qu'ils ne fonctionnent pas. C'est parce qu'il nous faut encore programmer leur fonctionnalité ! :)

Le composant Minter.js

REMARQUE : Assurez-vous d'être dans le dossier minter-starter-files et non le dossier nft-minter !

Revenons dans le dossier src de notre éditeur et ouvrons le fichier Minter.js. Il est très important de comprendre tout ce qui se trouve dans ce fichier, car c'est le composant principal de React sur lequel nous allons travailler.

En haut de ce fichier, nous avons nos variables d'état que nous mettrons à jour après des événements spécifiques.

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

Vous n'avez jamais entendu parler de variables d'état React ou de hooks d'état ? Jetez un œil à cette documentation(opens in a new tab).

Voici ce que chacune des variables représente :

  • walletAddress - une chaîne de caractère qui stocke l'adresse du portefeuille de l'utilisateur
  • status - une chaîne de caractère qui contient un message à afficher en bas de l'interface utilisateur
  • name - une chaîne de caractère qui stocke le nom du NFT
  • description - une chaîne de caractère qui stocke la description du NFT
  • url - une chaîne de caractères qui est un lien vers l'actif numérique du NFT

Après les variables d'état, vous verrez trois fonctions non implémentées : useEffect, connectWalletPressed, et onMintPressed. Vous remarquerez que toutes ces fonctions sont async, c'est parce que nous allons faire des appels d'API asynchrones ! Leurs noms correspondent à leurs fonctionnalités :

1useEffect(async () => {
2 //TODO: implement
3}, [])
4
5const connectWalletPressed = async () => {
6 //TODO: implement
7}
8
9const onMintPressed = async () => {
10 //TODO: implement
11}
Afficher tout
  • useEffect(opens in a new tab) - Il s'agit d'un hook React qui est appelé après que votre composant est affiché. Parce qu'une prop tableau vide [] lui est passée (voir la ligne 3), elle ne sera appelée qu'au premier affichage du composant. Ici, nous appellerons notre écouteur de portefeuille et une autre fonction de portefeuille pour mettre à jour notre interface utilisateur afin de déterminer si un portefeuille est déjà connecté.
  • connectWalletPressed - cette fonction sera appelée pour connecter le portefeuille MetaMask de l'utilisateur à notre dApp.
  • onMintPressed - cette fonction sera appelée pour frapper le NFT de l'utilisateur.

Vers la fin de ce fichier, nous avons l'interface utilisateur de notre composant. Si vous scannez ce code attentivement, vous remarquerez que nous mettons à jour nos variables d'état url, name, et description lorsque le contenu entré dans leurs champs de texte change.

Vous verrez également que connectWalletPressed et onMintPressed sont appelées lorsque les boutons portant les IDs respectifs mintButton et walletButton sont cliqués.

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)
Afficher tout

Enfin, occupons-nous de l'endroit où ajouter ce composant Minter.

Si vous ouvrez le fichier App.js, qui est le composant principal en React agissant comme un conteneur pour tous les autres composants, vous verrez que notre composant Minter est injecté à la ligne 7.

Dans ce tutoriel, nous allons seulement modifier le fichier Minter.js et ajouter des fichiers dans notre dossier src.

Maintenant que nous comprenons ce avec quoi nous travaillons, mettons en place notre portefeuille Ethereum !

Configurez votre portefeuille Ethereum

Pour que les utilisateurs puissent interagir avec votre contrat intelligent, ils devront connecter leur portefeuille Ethereum à votre dApp.

Téléchargez MetaMask

Pour ce tutoriel, nous utiliserons MetaMask, un portefeuille virtuel utilisable dans le navigateur servant à gérer les adresses Ethereum. Si vous voulez en savoir plus sur le fonctionnement des transactions sur Ethereum, consultez cette page.

Vous pouvez télécharger et créer un compte MetaMask gratuitement ici(opens in a new tab). Lorsque vous créez un compte, ou si vous en avez déjà un, assurez-vous de basculer sur « Réseau de test Ropsten » en haut à droite (afin de ne pas utiliser d'argent réel).

Ajoutez de l'ether depuis un Robinet

Afin de frapper nos NFT (ou de signer des transactions sur la blockchain Ethereum), nous aurons besoin de faux Eth. Pour obtenir des ETH, vous pouvez vous rendre sur le robinet Ropsten(opens in a new tab) et entrer votre adresse Ropsten, puis cliquer sur « Send Ropsten ETH. » Vous devriez voir les ETH dans votre compte MetaMask peu de temps après !

Vérifiez votre solde

Pour revérifier que votre solde est correct, faisons une requête eth_getBalance(opens in a new tab) en utilisant l'outil Alchemy Composer(opens in a new tab). Cela va retourner la quantité d'ETH que contient votre portefeuille. Après avoir entré l'adresse de votre compte MetaMask et cliqué sur « Send Request », vous devriez voir une réponse comme celle-ci :

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

REMARQUE : Ce résultat est en wei et non pas en ETH. Le wei est utilisé comme la plus petite unité d'ether. La conversion de wei vers eth est : 1 eth = 10¹⁸ wei. Donc si on convertit 0xde0b6b3a7640000 en nombre décimal, nous obtenons 1*10¹⁸ ce qui correspond à 1 eth.

Ouf ! Notre faux argent est bien là !

Connecter MetaMask à votre interface utilisateur

Maintenant que notre portefeuille MetaMask est configuré, connectons-y notre dApp !

Pour respecter le paradigme MVC(opens in a new tab) , nous allons créer un fichier séparé qui contient nos fonctions pour gérer la logique, les données, et les règles de notre dApp, puis passer ces fonctions à notre interface (notre composant Minter.js).

La fonction connectWallet

Pour cela, créons un nouveau dossier appelé utils dans votre dossier src et ajoutons-y un fichier appelé interact.js, qui contiendra toutes les fonctions de notre portefeuille et les interactions avec le contrat intelligent.

Dans notre fichier interact.js, nous écrirons une fonction connectWallet, que nous importerons et appellerons dans notre composant Minter.js.

Ajoutez ce qui suit dans le fichier 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: "👆🏽 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}
Afficher tout

Décomposons ce que fait ce code :

Premièrement, notre fonction vérifie si window.ethereum est activé dans votre navigateur.

window.ethereum est une API globale injectée par MetaMask et d'autres fournisseurs de portefeuille qui permet aux sites web de faire des requêtes vers les comptes Ethereum des utilisateurs. S'il est approuvé, un site peut lire les données des blockchains auxquels l'utilisateur est connecté et proposer à l'utilisateur de signer des messages et des transactions. Consultez la documentation MetaMask(opens in a new tab) pour plus d'infos !

Si window.ethereum n'est pas présent, alors cela signifie que Metamask n'est pas installé. Cela se traduit par un objet JSON retourné, où l'attribut adresse retourné est une chaîne vide, et le status de l'objet JSX indique que l'utilisateur doit installer MetaMask.

La plupart des fonctions que nous écrivons retourneront des objets JSON que nous pouvons utiliser pour mettre à jour nos variables d'état et notre interface utilisateur.

Maintenant, si window.ethereum est présent, alors c'est là que les choses deviennent intéressantes.

En utilisant une boucle try/catch, nous allons essayer de nous connecter à MetaMask en appelant[window.ethereum.request({ method: "eth_requestAccounts" });](https://docs.metamask.io/guide/rpc-api.html#eth-requestaccounts). L'appel de cette fonction ouvrira MetaMask dans le navigateur, où l'utilisateur sera invité à connecter son portefeuille à votre dApp.

  • Si l'utilisateur choisit de se connecter, method : "eth_requestAccounts" retournera une table qui contient toutes les adresses du compte de l'utilisateur qui sont connectées à la dApp. Au final, notre fonction connectWallet retourne un objet JSON qui contient la première address dans cette table (voir ligne 9\) et un message status qui invite l'utilisateur à écrire un message sur le contrat intelligent.
  • Si l'utilisateur rejette la connexion, alors l'objet JSON contiendra une chaîne vide pour l'address retournée et un message status qui indique que l'utilisateur a rejeté la connexion.

Ajouter une fonction connectWallet à votre composant Minter.js

Maintenant que nous avons écrit cette fonction connectWallet, connectons-la à notre composant Minter.js..

Tout d'abord, nous allons devoir importer notre fonction dans notre fichier Minter.js en ajoutant import { connectWallet } from "./utils/interact.js"; en haut du fichier Minter.js. Les 11 premières lignes de votre Minter.js devraient maintenant ressembler à ceci :

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("");
Afficher tout

Puis, dans notre fonction connectWalletPressed, nous appellerons notre fonction importée connectWallet, comme suit :

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

Vous avez remarqué comment la plupart de nos fonctionnalités sont sorties de notre composant Minter.js depuis le fichier interact.js ? C'est ainsi que nous respectons le paradigme M-V-C !

Dans connectWalletPressed, nous faisons simplement un appel await à notre fonction importée connectWallet, et en utilisant sa réponse, nous mettons à jour nos variables status et walletAddress via leurs hooks d'états.

Maintenant, enregistrons à la fois les fichiers Minter.js et interact.js et testons notre interface utilisateur.

Ouvrez votre navigateur sur localhost:3000, et cliquez sur le bouton « Connect Wallet » en haut à droite de la page.

Si MetaMask est installé, vous devriez être invité à connecter votre portefeuille à votre dApp. Accepter l'invitation à se connecter.

Vous devriez voir que le bouton du portefeuille précise maintenant que votre adresse est connectée.

Ensuite, essayez de rafraîchir la page... c'est étrange. Notre bouton de portefeuille nous invite à connecter MetaMask bien qu'il soit déjà connecté...

Mais ne vous inquiétez pas ! Nous pouvons facilement corriger cela en implémentant une fonction appelée getCurrentWalletConnected, qui vérifiera si une adresse est déjà connectée à notre dApp, et mettra à jour notre interface en conséquence !

La fonction getCurrentWalletConnected

Dans votre fichier interact.js, ajoutez la fonction suivante getCurrentWalletted :

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}
Afficher tout

Ce code est très similaire à la fonction connectWallet que nous venons d'écrire plus tôt.

La différence principale est qu'au lieu d'appeler la méthode eth_requestAccounts, qui ouvre MetaMask pour que l'utilisateur puisse connecter son portefeuille, ici nous appelons la méthode eth_accounts, qui renvoie simplement un tableau contenant les adresses MetaMask actuellement connectées à notre dApp.

Pour voir cette fonction en action, appelons-la dans la fonction useEffect de notre composant Minter.js.

Comme nous l'avons fait pour connectWallet, nous devons importer cette fonction de notre fichier interact.js dans notre fichier Minter.js comme ceci :

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

Maintenant, nous l'appelons simplement dans notre fonction useEffect :

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

Remarquez que nous utilisons la réponse de notre appel à getCurrentWalletConnected pour mettre à jour nos variables d'état walletAddress et status.

Une fois que vous avez ajouté ce code, essayez de rafraîchir votre fenêtre de navigateur. Le bouton devrait indiquer que vous êtes connecté et afficher un aperçu de l'adresse de votre portefeuille connecté, même après avoir été actualisé !

Implémenter addWalletListener

La dernière étape de la configuration de notre dApp de portefeuille consiste à mettre en place le listener de portefeuille afin que notre interface utilisateur soit mise à jour lorsque l'état de notre portefeuille change, par exemple lorsque l'utilisateur se déconnecte ou change de compte.

Dans votre fichier Minter.js, ajoutez une fonction addWalletListener qui ressemble à ce qui suit :

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}
Afficher tout

Décomposons rapidement ce qui se passe ici :

  • Premièrement, notre fonction vérifie si window.ethereum est activé (ex. : MetaMask est installé).
    • Si ce n'est pas le cas, nous fixons simplement notre variable d'état status à une chaîne de caractères JSX qui invite l'utilisateur à installer MetaMask.
    • S'il est activé, nous configurons le listener window.ethereum.on("accountsChanged") à la ligne 3 qui écoute les changements d'état dans le portefeuille MetaMask, qui les incluent lorsque l'utilisateur connecte un compte additionnel à la dApp, change de compte ou déconnecte un compte. S'il existe au moins un compte connecté, la variable d'état walletAddress est mise à jour comme premier compte dans le tableau des comptes accounts retourné par l'écouteur. Sinon, walletAdresse est défini comme une chaîne de caractères vide.

Enfin, nous devons l'appeler dans notre fonction useEffect :

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

Et voilà ! Nous avons terminé la programmation de toutes les fonctionnalités de notre portefeuille ! Maintenant que notre portefeuille est configuré, regardons comment créer notre NFT !

Métadonnées NFT 101

Rappelez-vous que les métadonnées NFT dont nous venons de parler à l'étape 0 de ce tutoriel, donnent vie à un NFT, lui permettant d'avoir des propriétés, comme un actif numérique, un nom, une description et d'autres attributs.

Nous allons devoir configurer ces métadonnées sous forme d'objet JSON et les stocker, afin de pouvoir les transmettre en tant que paramètre tokenURI lors de l'appel de la fonction mintNFT de notre contrat intelligent.

Le texte des champs « Lien vers l'actif », « Nom » et « Description » comprendra les différentes propriétés des métadonnées de notre NFT. Nous allons formater ces métadonnées sous la forme d'un objet JSON, mais il existe plusieurs options pour le stockage de cet objet JSON :

  • Nous pourrions la stocker sur la blockchain Ethereum, mais cela serait très coûteux.
  • Nous pourrions le stocker sur un serveur centralisé, comme AWS ou Firebase. Mais cela irait à l'encontre de notre philosophie de décentralisation.
  • Nous pourrions utiliser IPFS, un protocole décentralisé et un réseau peer-to-peer pour stocker et partager des données dans un système de fichiers distribué. Comme ce protocole est décentralisé et gratuit, c'est notre meilleure option !

Pour stocker nos métadonnées sur IPFS, nous allons utiliser Pinata(opens in a new tab), une API et une boîte à outils IPFS très pratique. Dans l'étape suivante, nous vous expliquerons exactement comment faire !

(opens in a new tab)Utiliser Pinata pour épingler vos métadonnées sur IPFS

Si vous n'avez pas de compte Pinata(opens in a new tab), créez-vous un compte gratuit ici(opens in a new tab) et suivez les étapes pour vérifier votre mail et votre compte.

Créer votre clé API Pinata

Naviguez vers la page https://pinata.cloud/keys(opens in a new tab), puis sélectionnez le bouton « New Key » en haut, activez le widget Admin et nommez votre clé.

Vous verrez ensuite une popup avec vos infos d'API. Assurez-vous de mettre cela dans un endroit sûr.

Maintenant que notre clé est configurée, ajoutons-la à notre projet pour que nous puissions l'utiliser.

Créer un fichier .env

Nous pouvons stocker en toute sécurité notre clé et notre secret Pinata dans un fichier d'environnement. Installons le paquet dotenv(opens in a new tab) dans le répertoire de votre projet.

Ouvrez un nouvel onglet dans votre terminal (distinct de celui qui exécute l'hôte local) et assurez-vous que vous êtes dans le dossier starter, puis exécutez la commande suivante dans votre terminal :

1npm install dotenv --save

Ensuite, créez un fichier .env dans le répertoire racine de vos minter-starter-files en entrant ce qui suit sur votre ligne de commande :

1vim.env

Ceci ouvrira votre fichier .env dans vim (un éditeur de texte). Pour l'enregistrer, appuyez sur « esc » + « : » + « q » sur votre clavier et dans cet ordre.

Ensuite, dans VSCode, accédez à votre fichier .env et ajoutez votre clé API Pinata et votre secret API Secret, comme ceci :

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

Enregistrez le fichier et vous êtes prêt à commencer à écrire la fonction pour télécharger vos métadonnées JSON sur IPFS !

(opens in a new tab)Implémenter pinJSONToIPFS

Heureusement pour nous, Pinata a une API spécifique pour télécharger des données JSON sur IPFS(opens in a new tab) et un JavaScript pratique avec un exemple d'axios que nous pouvons utiliser en opérant juste quelques petites modifications.

Dans votre dossier utils, créons un autre fichier appelé pinata.js puis importez notre clé secrète Pinata à partir du fichier .env comme suit :

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

Ensuite, collez le code supplémentaire ci-dessous dans votre fichier pinata.js. Ne vous inquiétez pas, nous allons expliquer ce que tout cela signifie !

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}
Afficher tout

Alors, que fait ce code exactement ?

Tout d'abord, il importe axios(opens in a new tab), un client basé HTTP en devenir pour le navigateur et le node.js, que nous utiliserons pour réaliser une requête à Pinata.

Ensuite, nous avons notre fonction asynchrone pinJSONToIPFS, qui prend un JSONBody en entrée et la clé API Pinata et secret dans son en-tête, pour faire une requête POST à leur API pinJSONToIPFS.

  • Si cette requête POST réussie, alors notre fonction retourne un objet JSON avec le booléen success comme true et la pinataUrl où nos métadonnées ont été épinglées. Nous utiliserons cette pinataUrl retournée comme entrée tokenURI de la fonction de mint de notre contrat intelligent.
  • Si cette requête POST échoue, alors notre fonction retourne un objet JSON avec le booléen success comme false et une chaîne de caractères message qui relaie notre erreur.

Comme avec notre fonction de types retournés connectWallet, nous retournons des objets JSON afin que nous puissions utiliser leurs paramètres pour mettre à jour nos variables d'état et notre interface utilisateur.

Charger votre contrat intelligent

Maintenant que nous avons un moyen d'envoyer nos métadonnées NFT vers IPFS via notre fonction pinJSONToIPFS, nous allons avoir besoin d'un moyen de charger une instance de notre contrat intelligent afin que nous puissions appeler sa fonction mintNFT.

Comme nous l'avons mentionné précédemment dans ce tutoriel, nous utiliserons ce contrat intelligent NFT existant(opens in a new tab). Cependant, si vous souhaitez apprendre comment nous avons fait, en faire un par vous-même, nous vous recommandons vivement de consulter notre autre tutoriel, "Comment créer un NFT(opens in a new tab).

Le contrat ABI

Si vous avez examiné en détail nos fichiers, vous aurez remarqué que dans notre répertoire src, il existe un fichier contract-abi.json. Une ABI est nécessaire pour spécifier quelle fonction un contrat invoquera en s'assurant également que la fonction retournera des données dans le format que vous attendez.

Nous allons également avoir besoin d'une clé API Alchemy et de l'API Alchemy Web3 pour nous connecter à la blockchain Ethereum et charger notre contrat intelligent.

Créer votre clé API Alchemy

Si vous n'avez pas déjà un compte Alchemy, vous pouvez vous inscrire gratuitement ici(opens in a new tab).

Une fois votre compte Alchemy créé, vous pouvez générer une clé API en créant une application. Cela nous permettra de réaliser des requêtes sur le réseau de test Ropsten.

Accédez à la page "Create App" dans votre Tableau de bord Alchemy, en survolant "Apps" dans la barre de navigation et en cliquant sur "Create App".

Nommez votre application. Ici nous avons choisi "My First NFT!", donnez une brève description, sélectionnez "Staging" pour l'environnement utilisé pour la comptabilité de votre application, et choisissez "Ropsten" pour votre réseau.

Cliquez sur « Create app », et voilà ! Votre application devrait apparaître dans le tableau ci-dessous.

Génial ! Maintenant que nous avons créé notre URL pour l'API HTTP Alchemy, copiez-la dans votre presse-papiers...

…puis ajoutons-la à notre fichier .env. Dans l'ensemble, votre fichier .env devrait ressembler à ceci :

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

Maintenant que nous avons notre contrat ABI et notre clé API Alchemy, nous sommes prêts à charger notre contrat intelligent en utilisant Alchemy Web3(opens in a new tab).

Configurer votre point de terminaison Alchemy Web3 et votre contrat

Tout d'abord, si vous ne l'avez pas déjà fait, vous devrez installer Alchemy Web3(opens in a new tab) en naviguant dans le répertoire principal : nft-minter-tutorial dans le terminal :

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

Ensuite, revenons à notre fichier interact.js. En haut du fichier, ajoutez le code suivant pour importer votre clé Alchemy à partir de votre fichier .env et configurez votre point de terminaison 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) est un wrapper autour de Web3.js(opens in a new tab), fournissant des méthodes API améliorées et d'autres avantages pour faciliter votre vie en tant que développeur Web3. Il est conçu pour nécessiter une configuration minimale afin que vous puissiez commencer à l'utiliser immédiatement dans votre application !

Ensuite, ajoutons notre contrat ABI et l'adresse de notre contrat à notre fichier.

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"

Une fois que nous avons les deux, nous sommes prêts à commencer à coder notre fonction de frappage !

Implémenter la fonction mintNFT

Dans votre fichier interact.js, définissons notre fonction, mintNFT, qui comme son nom l'indique va frapper notre NFT.

Parce que nous allons réaliser de nombreux appels asynchrones (à Pinata pour épingler nos métadonnées à IPFS, Alchemy Web3 pour charger notre contrat intelligent, et MetaMask pour signer nos transactions), notre fonction sera également asynchrone.

Les trois entrées de notre fonction seront l'url de notre actif numérique, name, et description. Ajoutez la signature de fonction suivante sous la fonction connectWallet :

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

Gestion des erreurs d'entrée

Naturellement, il est logique d'avoir une sorte de gestion des erreurs d'entrée au début de la fonction et ainsi, nous quitterons cette fonction si nos paramètres d'entrée ne sont pas corrects. Dans notre fonction, ajoutons le code suivant :

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}
Afficher tout

Si l'un des paramètres d'entrée est une chaîne de caractères vide, alors nous retournons un objet JSON où le booléen succes est false, et les relais de chaîne de caractère status signalent que tous les champs de notre interface utilisateur doivent être complétés.

(opens in a new tab)Télécharger les métadonnées sur IPFS

Une fois que nous savons que nos métadonnées sont correctement formatées, la prochaine étape est de l'envelopper dans un objet JSON et de le charger sur IPFS via le pinJSONToIPFS que nous avons écrit !

Pour ce faire, nous devons d'abord importer la fonction pinJSONToIPFS dans notre fichier interact.js. Tout en haut de interact.js, ajoutons :

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

Rappelez-vous que pinJSONToIPFS prend dans un corps JSON. Ainsi, avant de passer un appel, nous allons devoir formater nos paramètres url, name, et description dans un objet JSON.

Mettons à jour notre code pour créer un objet JSON appelé metadata puis appelons pinJSONToIPFS avec ce paramètre 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}
Afficher tout

Attention, nous stockons la réponse de notre appel à pinJSONToIPFS(metadata) dans l'objet pinataResponse. Ensuite, nous analysons cet objet pour vérifier les erreurs.

S'il existe une erreur, nous retournons un objet JSON où le booléen success est false et que notre chaîne de caractères status nous signale que notre appel a échoué. Sinon, nous extrayons pinataURL de pinataResponse et la stockons comme notre variable tokenURI.

Maintenant, il est temps de charger notre contrat intelligent en utilisant l'API Alchemy Web3 que nous avons initialisée en haut de notre fichier. Ajoutez la ligne de code suivante au bas de la fonction mintNFT pour définir le contrat sur la variable globale window.contract :

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

La dernière chose à ajouter à notre fonction mintNFT est notre transaction 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}
Afficher tout

Si vous êtes déjà familier avec les transactions Ethereum, vous remarquerez que la structure est assez similaire à ce que vous avez déjà vu.

  • Tout d'abord, nous configurons nos paramètres de transactions.
    • to spécifie l'adresse du destinataire (notre contrat intelligent)
    • from spécifie le signataire de la transaction (l'adresse de l'utilisateur connectée à MetaMask : window.ethereum.selectedAddress)
    • data contient l'appel à la méthode mintNFT de notre contrat intelligent , qui reçoit notre tokenURI et l'adresse du portefeuille de l'utilisateur, window.ethereum.selectedAddress, comme des entrées.
  • Ensuite, nous faisons un appel en attente, window.ethereum.request, où nous demandons à MetaMask de signer la transaction. Remarquez que dans cette requête, nous spécifions notre méthode ETH (eth_SentTransaction) et en la passant dans nos transactionParameters. À ce stade, MetaMask s'ouvrira dans le navigateur, et demandera à l'utilisateur de signer ou rejeter la transaction.
    • Si la transaction est réussie, la fonction retournera un objet JSON où le booléen success sera défini comme vrai et la chaîne status invitera l'utilisateur à consulter Etherscan pour plus d'informations sur sa transaction.
    • Si la transaction échoue, la fonction retournera un objet JSON où le booléen success sera défini comme faux, et la chaîne de caractères status renverra un message d'erreur.

Dans l'ensemble, notre fonction mintNFT devrait ressembler à ceci :

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}
Afficher tout

C'est une fonction géante ! Maintenant, nous avons juste besoin de connecter notre fonction mintNFT à notre composant Minter.js...

Connecter mintNFT à notre frontend Minter.js

Ouvrez votre fichier Minter.js et mettez à jour la ligne du haut import{ connectWallet, getCurrentWalletConnected } from "./utils/interact.js"; pour devenir :

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

Enfin, implémentez la fonction onMintPressed pour faire attendre l'appel à votre fonction importée mintNFT et mettez à jour la variable d'état status pour refléter si notre transaction a réussi ou a échoué :

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

Déployer votre NFT sur un site Web en ligne

Prêt à mettre en ligne votre projet pour que les utilisateurs puissent interagir avec ? Consultez ce tutoriel(opens in a new tab) pour déployer votre Minter sur un site Web directement.

Encore une dernière étape...

Prendre d'assaut le monde de la blockchain

Je plaisante, vous êtes arrivé à la fin du tutoriel !

Pour récapituler, en construisant un Minter de NFT, vous avez appris avec succès à :

  • Vous connecter à MetaMask via votre projet en frontend
  • Appeler les méthodes du contrat intelligent depuis votre interface en frontend
  • Signer des transactions à l'aide de MetaMask

Sans doute vous aimeriez pouvoir montrer les NFT mis à jour via votre dApp dans votre portefeuille — alors n'oubliez pas de consulter notre bref tutoriel : Comment consulter votre NFT dans votre portefeuille(opens in a new tab) !

Et, comme toujours, si vous avez des questions, nous sommes là pour vous aider dans le Discord Alchemy(opens in a new tab). Nous avons hâte de voir comment vous appliquez les concepts de ce tutoriel à vos futurs projets !

Ce tutoriel vous a été utile ?