Comment créer une collection de NFT sur Ethereum ? (Contrat + Mint)

Publié le 26 février 2022
·
Par Equipe CryptoLearn

Ces derniers mois, la popularité des « Non Fungible Token », également appelés NFTs, a explosé. Ces derniers sont des « jetons » non fongibles, c’est-à-dire qu’ils sont uniques et qu’ils ne peuvent pas être interchangés. Dans le monde réel, un actif fongible serait par exemple un billet de 10€ qui est indifférenciable d’un autre billet de même valeur. A l’inverse, un tableau de Picasso est non-fongible. Il est unique et ne peut être échangé ou remplacé par un élément de même caractéristiques et de mêmes valeurs. Les NFTs sont donc la transcription de cet aspect d’unicité dans le monde du numérique en utilisant la blockchain.

Aujourd’hui les NFTs sont principalement utilisés pour proposer aux investisseurs des œuvres d’art numérique, bien souvent sous la forme de collection. Une collection regroupe un certain nombre de NFTs, identifiés par un numéro unique et relié à un smart-contract, qui lui est déployé sur la blockchain.

Aujourd’hui, vous allez découvrir comment créer une collection de NFTs sur une blockchain EVM (Ethereum, Avax, Polygon, etc…) en utilisant le standard ERC-721 d’OpenZeppelin, ainsi qu’une dApp permettant à l’utilisateur de Mint les NFTs.

Etape 1 – Créer les visuels

Pour proposer une collection, nous avons tout d’abord besoin d’images, au format JPEG ou PNG. La plupart des collections utilisent un outil de génération comme Hashlips. Le principe est relativement simple : Vous définissez des attributs, et pour chaque attribut vous définissez une ou plusieurs variantes. Par exemple, si l’on souhaite créer un NFT d’un dessin de singe, on peut définir comme attribut la couleur des yeux, un collier, un chapeau. En guise de variante sur l’attribut « couleur des yeux » on peut mettre bleu, noir, rouge, vert, etc…

Hashlips va venir prendre une variante aléatoire sur chaque attribut, assembler toutes les variantes et ainsi générer un certain nombre de NFT.

Avant d’installer Hashlips, vous devrez :

Ensuite, placer vous dans un dossier vide avec le CMD de Windows, et entrez la commande:

git clone https://github.com/HashLips/hashlips_art_engine.git

Placez vous ensuite dans le dossier "hashlips_art_engine" en faisant

cd hashlips_art_engine

Puis entrez la commande suivante pour installer les dépendances:

npm install

Les fichiers et dossiers nécessaires au bon fonctionnement de Hashlips seront alors copiés dans votre répertoire.

  • Le dossier « layers » va contenir au format PNG toutes les variantes de tous les attributs. Dans ce dossier, vous devrez créer un dossier par attribut et placer chaque variante sous la forme « NomVariante#%apparition ». Par exemple, si mon NFT a un attribut sur le couleur des yeux, dans mon dossier « layers » je vais créer un dossier « Eye Color », et dans ce dossier je vais par exemple mettre « Blue#50.png » et « Red#50.png ». Chaque image PNG affichant uniquement l’attribut en question.
  • Le fichier « config.js » dans « src » contient tous les paramètres de Hashlips, dans ce fichier vous trouverez :
  1. Le nombre de NFT à générer à la ligne growEditionSizeTo: 5, (remplacez 5 par le nombre en question)
  2. La liste de vos attributs à la ligne layersOrder: […]. Vous devrez completer cette liste avec vos attributs, en les nommant de la même manière que les dossiers de src
  3. Le nom et la description de la collection à la ligne 8 et 9.

Hashlips propose également d’autres paramètres directement que vous retrouverez directement dans la documentation ici.

Après avoir entré tous vos layers, et après avoir configuré proprement le fichier config.js, vous pouvez générer vos images en utilisant la commande:

npm run build

Pour l'exemple, nous allons ici utiliser les visuels de base que propose Hashlips. En ne touchant rien aux layers et aux paramètres, voici ce que Hashlips nous propose:

Hashlips a généré 5 images en utilisant les attributs présents dans le dossier "layers"

Les images générées sont présentes dans le dossier "build/images"

Etape 2 - Envoyer les images sur IPFS

Pour héberger nos images nous allons utiliser un service appelé IPFS. Il permet le stockage et le partage de données en peer-to-peer. En clair, nous allons héberger nos images sur IPFS. Pour cela, nous allons utiliser Pinata.cloud, un plateforme nous permettant de faire le lien entre nous et IPFS.

  • Dans un premier temps, créez un compte sur Pinata.
  • Cliquez ensuite sur "Upload" et selectionnez "Folder"
  • Choisissez ensuite le dossier contenant les images générés par Hashlips,

Une fois l'upload finalisé, vous devrez récuperer le hash de votre dossier. Il s'agit d'un identifiant unique, qui indique comment retrouver votre dossier sur le réseau IPFS.

Vous le trouverez ici:

Ensuite, nous allons devoir créer les métadonnées de nos NFTs. Ces données, au format JSON, indique pour chaque NFT son nom (souvent "NomDeLaCollection #ID"), ses attributs ainsi l'URL IPFS de son image.

Heuresement, Hashlips génère à notre place les fichiers JSON de nos meta. Ils sont visibles dans le dossier "build/json" après la génération. Cependant, ces fichiers JSON sont incomplets. Nous devons leurs indiquer l'ID IPFS des images que nous venons d'uploader. Voici ce que vous devez faire:

  • Copiez le CID
  • Entrez votre CID dans le fichier de configuration de Hashlips à la ligne 10, par exemple
const baseUri = "ipfs://QmYgKeq1FeE64FyHaJZyp2jMy3ceLGryio9qhvYhGBrCcU";
  • Lancez la commande:
npm run update_info

Vos fichiers JSON seront alors automatiquement mis à jours avec l'ID IPFS indiqué.

Ensuite, vous devrez uploader vos fichiers JSON sur IPFS, de la même manière qu'avec les images, en indiquant cette fois-ci le dossier "json".

Etape 3 - Créer et déployer le contrat

Pour que nos NFTs puissent exister sur la blockchain, nous devons les rattacher à un contrat. Il s'agit d'un programme écrit en Solidity, dans lequel on retrouvera des fonctions permettant l'achat, la vente, ou encore le transfert de nos NFTs.

Nous utiliserons ici le standard ERC-721 proposé par OpenZeppelin. Ainsi, nous n'aurons pas à écrire la plupart des fonctions de bases du NFT, tel que l'achat ou la vente. Nous viendrons simplement rajouter la logique propre à notre collection, que nous appellerons ici "The Learn Collection"

Pour écrire votre contrat vous pouvez utiliser l'IDE Remix, ou alors utiliser Hardhat pour travailler dans un premier temps en local. Voici un contrat assez simple, vous pouvez le copier-coller directement.

//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract TheLearnCollection is ERC721, Ownable {

    using Strings for uint256;
    using Counters for Counters.Counter;

    constructor() ERC721("The Learn Collection", "TLC") {
    }

    string public baseExtension = ".json";
    uint256 public cost = 0.1 ether;
    uint256 public maxSupply = 5;
    bool public saleIsActive = true;
    Counters.Counter private _tokenIds;

    // Low Res Base URI
    string baseURI;

    function _baseURI() internal view virtual override returns (string memory) {
        return baseURI;
    }

    function setBaseURI(string memory _newBaseURI) public onlyOwner {
        baseURI = _newBaseURI;
    }

    function withdraw() public onlyOwner {
        payable(msg.sender).transfer(address(this).balance);
    }

    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
        string memory base = _baseURI();
        // If there is a baseURI but no tokenURI, concatenate the tokenID to the baseURI.
        return string(abi.encodePacked(base, tokenId.toString(), ".json"));

    }

    function mintToken() public payable {
        uint supply = _tokenIds.current() + 1;
        require(saleIsActive, "Sale must be active to mint token");
        require(supply <= maxSupply, "Purchase would exceed max supply of tokens");
        require(msg.value >= cost, "Ether value sent is not correct");
        _safeMint(msg.sender, supply);
        _tokenIds.increment();

    }

    function setCost(uint256 _newCost) public onlyOwner() {
        cost = _newCost;
    }


    function toString(uint256 value) internal pure returns (string memory) {
    // Inspired by OraclizeAPI's implementation - MIT license
    // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol

        if (value == 0) {
            return "0";
        }
        uint256 temp = value;
        uint256 digits;
        while (temp != 0) {
            digits++;
            temp /= 10;
        }
        bytes memory buffer = new bytes(digits);
        while (value != 0) {
            digits -= 1;
            buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
            value /= 10;
        }
        return string(buffer);
    }
}

Ici nous importons des contrats provenant d'OpenZeppelin, et qui comportent la plupart de la logique.

Nous allons ensuite créer un nouveau contrat héritant de l'ERC-721 et de Ownable. Ownable permet de définir un propriétaire à notre contrat, et de rendre certaines fonctions uniquement appelables par le propriétaire. Nous l'utiliserons par exemple pour récupérer le solde des mints. Dans le constructeur du contrat, vous devrez indiquer le nom de la collection ainsi que son symbole.

Nous définissions ensuite quelques attributs propre à notre collection, comme par exemple le prix du mint ou encore le supply (qui est ici de 5 NFTs).

le baseUri correspond au CID du dossier dans lequel nous avons les JSON de nos NFTs. Vous n’êtes pas obligé de l'indiquer lors du déploiement du contrat, puisque la fonction setBaseUri() va nous permettre de le mettre à jour après le déploiement (utile par exemple pour un système de reveal).

La fonction tokenUri() permet d'obtenir les meta d'un token dont l'ID est passé en paramètre. Pour cela, la fonction va simplement concaténer le baseUri() avec l'ID du token et l'extension JSON pour donner quelque chose de la forme:

ipfs://QmS7m9kyvcu98pFX11Z5hKWYuDrLUg4aV6udgJvzfE7KFe/1.json

Enfin, le mintToken() contient la logique de notre mint. C'est cette fonction qui va être appelé lorsque l'utilisateur va vouloir mint notre NFT. D'abord on récupère l'ID du token à mint, puis on regarde si la vente est bien active, on regarde ensuite si l'ID du token à mint n'est pas plus grand que le maxSupply de la collection, et enfin on vérifie que l'utilisateur ait bien payé le prix du mint. Si toutes ces conditions sont verifiées, alors la fonction _safeMint() est appelé et le NFT est assigné à l'utilisateur ayant envoyé la transaction.

Vous pouvez ensuite déployer le contrat en utilisant Remix IDE, sur le testnet Rinkeby par exemple.

Vous devrez ensuite indiquer le baseUri de votre contrat en appelant la fonction setBaseUri() depuis remix, et en lui passant en paramètre le CID du dossier JSON de Pinata avec le prefix d'IPFS, donc sous la forme

ipfs://QmZyfc82Jkhk4Y15mcypmC7AKGLsSYA2GEjFxVwQvqkZ7d

Votre collection de NFTs est à présent déployé sur la blockchain, vous n'avez plus qu'a créer une dApp Web3 en utilisant React pour permettre aux utilisateurs de mint votre collection.

Etape 4 - Créer la dApp de mint

Pour permettre à vos utilisateurs le mint de vos NFTs, vous devrez créer une application web relié à votre contrat. Ces apps sont généralement faites avec le framework React. Si vous ne savez pas comment utiliser React, vous pouvez réutiliser des applications web open-source, dans lesquels vous viendrez simplement remplacer les informations du contrat par ce que vous venez de déployer.

Voici une application React que vous pouvez réutiliser.

Le repo Github est accompagné d'un tutoriel, vous expliquant le fonctionnement de l'app, et vous permettant ainsi de modifier l'application à votre guise.

Etape 5 - Deployer l'application

Le déploiement d'une application React est extrêmement facile. La solution la plus simple est d'utiliser Netlify. Sur Netlify, vous pourrez déployer une application directement à partir d'un dépôt Github. En clair, vous n'aurez qu'a push votre application React sur Github, puis sur Netlify vous n'aurrez qu'a indiquer votre dépôt Github et Netlify se charge du reste. Vous aurez également la possibilité d'associer votre application à un nom de domaine.