Créer sa collection de NFT sur Ethereum

par

Pierre

Pierre Crouillebois

16 minutes de lecture

Créer sa collection de NFT sur Ethereum

Dans l'article précédent, nous avons étudié les différentes caractéristiques et notions du Web 3.

Nous allons aujourd'hui créer notre première collection de NFT de A à Z sur la blockchain Ethereum accompagnée d'une dApp (Decentralized Application).

Créer sa collection de NFT accompagnée d'une dApp

Avant de commencer, il est important de préciser qu'il existe différents procédés pour créer sa collection de NFT :

  1. Des outils existent pour simplifier différentes tâches nécessaires au bon développement de notre collection.

Nous pouvons citer par exemple HashLips qui est un outil utilisé pour créer plusieurs œuvres d'art à partir de différentes couches (layers), qui seront ensuite superposées dans le calque de façon à générer des NFT uniques.

HashLips Demo

Il existe aussi des outils pour créer facilement notre propre dApp (application web qui intéragit avec un ou plusieurs smarts contracts).

Comme par exemple Moralis, qui fournit une dépendance et une API complète via un SDK très simple d'utilisation. L'API de Moralis nous permet d'obtenir :

  • Les prix des NFT
  • Données de propriété des NFT
  • Métadonnées NFT
  • Transférer un NFT
  • et bien plus encore...
A savoir que toutes les fonctionnalités fournies par Moralis sont cross-chain par défaut 🤯
C'est à dire que les fonctionnalités sont compatibles avec différentes blockchains.
  1. Autrement, nous pouvons tout réaliser nous-même, et c'est ce que nous allons aborder dans la prochaine section de cet article.

Création du NFT

Pour cet exemple, nous allons nous focaliser sur la partie technique en utilisant le logo de Premier Octet comme image pour notre collection de NFT.

Image examples

Avant de nous lancer dans le développement du smart-contract il va falloir créer et héberger nos métadonnées sur un IPFS.

IPFS qui veut dire InterPlanetary File System en anglais, est un système distribué de fichiers pair à pair qui ne dépend pas de serveurs centralisés. Son but est de connecter un ensemble d'équipements informatiques avec le même système de fichiers. D'une certaine manière, c'est ce qui va remplacer les bases de données couramment utilisées dans le Web 2.

Pour cela, il nous faudra créer un compte sur un NFT Storage pour héberger nos métadonnées, et utiliser la version gratuite de Pinata Cloud.

Une fois notre compte créé, nous pouvons nous rendre dans notre manager et cliquer sur le bouton Upload et sélectionner Folder :

Pinata upload

Pour le dossier contenant toutes nos images ou métadonnées, il est important de respecter un index unique dans le nom de chaque fichier en démarrant de 0 et en incrémentant de + 1 pour chaque image.

Nous pouvons ensuite indiquer le chemin d'accès à nos images que nous avons créées précédemment :

Folder select

Une fois les images sauvegardées dans Pinata, nous obtiendrons un numéro CID qui correspond au chemin d'accès à nos fichiers dans IPFS.

CID Pinata

En se rendant sur cet URL, nous obtenons bien nos images.

Maintenant que nous avons nos images, nous pouvons créer les métadonnées des NFT qui sont en JSON, voici le modèle que nous allons utiliser pour notre exemple :

{
  "name": "Premier Octet #0",
  "description": "Premier Octet est une agence de développement d'applications mobiles basée à Paris.",
  "attributes": [
    {
      "trait_type": "Techno",
      "value": "React"
    }
  ],
  "image": "https://gateway.pinata.cloud/ipfs/QmYigrbExnVjTqvjy116zaSLMPhTxB8YXL3Jc8kMKRUELm/0.png"
}

Même manipulation que pour les images, nous pouvons désormais héberger notre dossier contenant nos métadonnées sur Pinata :

Metadata upload

Metadata Final

Maintenant que nos métadonnées sont bien hébergées sur un IPFS, nous pouvons commencer le développement du smart-contract !

Créer un smart contract

Avant de nous lancer dans le code, les smarts contracts sont par définition un programme automatisé ou littéralement un “contrat intelligent”. Ces contrats ou programmes sont des protocoles informatiques qui visent à automatiser une action lorsque les conditions prérequises sont remplies.

Dans notre cas, notre smart-contract va nous permettre de générer nos NFT directement sur la blockchain Ethereum. Nous allons utiliser le protocole ERC721A qui est une amélioration du protocole IERC721 pour permettre de générer plusieurs NFT en ayant un faible coût en frais de gas. C'est le protocole le plus répandu dans toutes les grandes collections NFT du moment.

Pour en savoir plus sur ce protocole, je vous invite à lire cet article.

Nous allons aussi utiliser OpenZeppelin qui fournit des produits sécurisés pour construire, automatiser et exploiter des applications décentralisées. Nous allons donc utiliser des smart-contract venant de OpenZeppelin pour développer notre contrat.

Dans cet exemple nous allons développer le smart-contract en Solidity car nous sommes sur la blockchain Ethereum. En Solana, nous aurions dû développer le smart-contract en Rust.

Pour tester, développer et déployer des smarts-contracts nous pouvons utiliser Remix IDE qui est un IDE Solidity en ligne ou bien, développer directement dans votre IDE favoris.

Pour ce projet, nous allons utiliser le smart-contract suivant :

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;

/// @author Premier Octet https://www.premieroctet.com/
/// @title Premier Octet

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/finance/PaymentSplitter.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "./ERC721A.sol";

contract PremierOctetERC721A is Ownable, ERC721A, PaymentSplitter {
    using Strings for uint;

    // Toute les étapes de ventes
    enum Step {
        Before,
        PublicSale,
        SoldOut
    }

    // Phase de vente actuel
    // 0 => Avant la vente, mint désactivé
    // 1 => Vente active, mint activé
    // 2 => Soldout, vente terminée
    Step public sellingStep;

    // Nombre total de NFT
    uint private constant MAX_SUPPLY = 3;

    // Prix de mint en Wei == 0.02 ETH
    uint public publicSalePrice = 20000000000000000;

    // l'url de votre IPFS correspondant à vos métadonnées (Pinata)
    string public baseURI;

    // Nombre de wallet dans la team
    uint private teamLength;

    // Lors du déploiement du contrat il faudra fournir les informations présentes dans le constructeur
    constructor(
        address[] memory _team,
        uint[] memory _teamShares,
        string memory _baseURI)
    ERC721A("Premier Octet", "PO")
    PaymentSplitter(_team, _teamShares) {
        baseURI = _baseURI;
        teamLength = _team.length;
    }

    // On vérifie que l'appel viens bien du bon contrat autrement on bloque la transaction
    modifier callerIsUser() {
        require(tx.origin == msg.sender, "The caller is another contract");
        _;
    }

    // Mint un NFT
    function publicSaleMint(address _account, uint _quantity) external payable callerIsUser {
        uint price = publicSalePrice;
        require(price != 0, "Price is 0");
        require(sellingStep == Step.PublicSale, "Public sale is not activated");
        require(totalSupply() + _quantity <= MAX_SUPPLY, "Max supply exceeded");
        require(msg.value >= price * _quantity, "Not enought funds");
        _safeMint(_account, _quantity);
    }

    // Modifier le prix de Mint
    function setPublicSalePrice(uint _publicSalePrice) external onlyOwner {
        publicSalePrice = _publicSalePrice;
    }

    // Obtenir l'URI du jeton d'un NFT par son ID
    function tokenURI(uint _tokenId) public view virtual override returns (string memory) {
        require(_exists(_tokenId), "URI query for nonexistent token");

        return string(abi.encodePacked(baseURI, _tokenId.toString(), ".json"));
    }

    // Changer la phase de vente
    function setStep(uint _step) external onlyOwner {
        sellingStep = Step(_step);
    }

    // Envoyer tout les Ethereum présents dans le contrat sur les adresses des membres de la team
    function releaseAll() external onlyOwner  {
        for(uint i = 0 ; i < teamLength ; i++) {
            release(payable(payee(i)));
        }
    }
}

Pour le déployer nous allons initialiser notre projet avec Hardhat en exécutant ces commandes :

mkdir my-nft-project
cd my-nft-project
npm init --yes
npm install --save-dev hardhat

Dans le même dossier où nous avons installé Hardhat, exécutons la commande suivante :

npx hardhat

Puis sélectionnons Create an empty hardhat.config.js :

Init Hardhat

Un fichier hardhat.config.js est généré, nous pouvons le modifier dès maintenant en assignant ces valeurs :

require('@nomiclabs/hardhat-waffle')
require('@nomiclabs/hardhat-etherscan')
require('hardhat-gas-reporter')
require('dotenv').config()

const { API_URL, PRIVATE_KEY, ETHERSCAN_API_KEY } = process.env

/**
 * @type import('hardhat/config').HardhatUserConfig
 */
module.exports = {
  solidity: {
    version: '0.8.12',
    settings: {
      optimizer: {
        enabled: true,
        runs: 200,
      },
    },
  },
  paths: {
    artifacts: './artifacts',
  },
  defaultNetwork: 'rinkeby',
  networks: {
    hardhat: {
      chainId: 137,
    },
    rinkeby: {
      url: API_URL,
      accounts: [`0x${PRIVATE_KEY}`],
    },
  },
  gasReporter: {
    currency: 'EUR',
    gasPrice: 21,
    enabled: true,
  },
  etherscan: {
    apiKey: ETHERSCAN_API_KEY,
  },
}

Il est nécessaire d'installer les dépendances suivantes :

yarn add -D @nomiclabs/hardhat-waffle @nomiclabs/hardhat-etherscan hardhat-gas-reporter dotenv

Il faudra aussi ajouter les variables d'environnement :

  • API_URL : Un compte sur Infura doit être créé pour obtenir une clé API.
  • PRIVATE_KEY : Correspond à la clé secrète du wallet, propriétaire du contrat qui sera déployé. Cette clé doit absolument rester secrète !
  • ETHERSCAN_API_KEY : Un compte sur Etherscan doit aussi être créé pour obtenir une clé API.

Nous y sommes presque ! Il reste à ajouter le script de déploiement, nous pouvons créer un dossier script à la racine du projet et ajouter à l'intérieur de ce dossier un fichier deploy.js :

const hre = require('hardhat')

async function main() {
  // Wallet des membres de la team
  let team = ['0x2939cd2d52D6aB2B3cBD3966dA800E7dea69e955']
  // Pourcentage d'ETH que l'addresse va récupérer après la vente
  let teamShares = [100]
  // Lien des métadonnées IPFS (Pinata)
  let baseURI = 'https://gateway.pinata.cloud/ipfs/QmX7KKoebxvcpjLqhQ2FhX7bQkTe8uea7iaisYWfy1gWxW/'

  // Déploiement du contrat
  const Raffle = await hre.ethers.getContractFactory('PremierOctetERC721A')
  const raffle = await Raffle.deploy(team, teamShares, baseURI)

  await raffle.deployed()

  console.log('Premier Octet Contract ERC721A deployed to :' + raffle.address)
  console.log(team, teamShares, baseURI)
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error)
    process.exit(1)
  })

Ajoutons maintenant le smart-contract à la racine du projet dans un dossier contracts de façon à avoir une structure de fichier comme l'exemple ci-dessous :

Exemple structure

Sans oublier les dépendances nécessaires au déploiement du contrat :

yarn add -D @nomiclabs/hardhat-ethers @nomiclabs/hardhat-etherscan @nomiclabs/hardhat-waffle @openzeppelin/contracts

On peut ajouter les scripts dans le fichier package.json :

"scripts": {
	"deploy-contract": "npx hardhat run script/deploy.js --network rinkeby",
	"verify": "npx hardhat verify --network rinkeby --constructor-args arguments.js contractAddress"
},

Pour pouvoir exécuter la commande suivante :

yarn deploy-contract

Et voilà le smart-contract est déployé sur le réseau de test Rinkeby ! Nous pouvons copier coller l'adresse et la retrouver dans Etherscan Rinkeby.

Pour vérifier le contrat sur Etherscan, remplaçons contractAddress dans le script verify du fichier package.json par l'adresse du contrat qui vient d'être publiée :

"verify": "npx hardhat verify --network rinkeby --constructor-args arguments.js 0x6B5067c2F4FbFa3711d4EF76237219CBe554A1B2"

Créons un fichier arguments.js à la racine du projet contenant les paramètres passés dans le constructeur lors du déploiement du contrat :

module.exports = [
  ['0x2939cd2d52D6aB2B3cBD3966dA800E7dea69e955'],
  [100],
  'https://gateway.pinata.cloud/ipfs/QmX7KKoebxvcpjLqhQ2FhX7bQkTe8uea7iaisYWfy1gWxW/',
]

Exécutons ensuite cette commande afin de vérifier le smart-contract pour pouvoir accéder au code source depuis Etherscan :

yarn verify

Une fois le smart-contract validé, une petite bulle verte apparaîtra dans Etherscan :

Verify contract

Le smart contract est désormais correctement déployé et vérifié, nous pouvons directement passer à la prochaine étape qui consistera à créer une app en React qui va communiquer avec notre smart-contract.

Création d'une app NextJS

Pour initialiser notre app NextJS nous pouvons exécuter la commande suivante à la racine de votre projet :

yarn create next-app --typescript

Organisons les dossiers et fichiers de façon à pouvoir les retrouver facilement à la racine du projet comme le montre l'exemple ci-dessous :

Nextjs folder

Installons ensuite ChakraUI dans le projet afin de créer des jolis designs ✨

yarn add @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^6

Une fois ChakraUI d'installé, il faudra mettre en place le provider dans le fichier _app.tsx comme ceci :

import type { AppProps } from 'next/app'
import { ChakraProvider } from '@chakra-ui/react'
import '../styles/globals.css'

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <ChakraProvider>
      <Component {...pageProps} />
    </ChakraProvider>
  )
}

export default MyApp

Nous pouvons maintenant créer notre UI et une Navbar dans un dossier components :

import { Button, Flex, Image, Spacer, Text } from '@chakra-ui/react'

const Navbar = () => {
  return (
    <Flex bgColor="black" py={5} px={[5, 5, 20, 20]} w="100%" align="center" justify="center">
      <Image src="/logo.jpg" borderRadius={5} alt="PremierOctet logo" width={[45, 45, 75, 75]} />
      <Text ml={5} color="white" fontSize={[15, 15, 25, 25]} fontWeight={800}>
        Premier Octet NFT
      </Text>
      <Spacer />
      <Button colorScheme="blue" fontWeight={800} size="md">
        Connect Wallet
      </Button>
    </Flex>
  )
}

export default Navbar

Un affichage semblable à cette image apparaît :

Navbar demo

Connecter son wallet sur son app en React

Nous allons maintenant ajouter la possibilité de connecter son wallet en cliquant sur le bouton Connect Wallet créé précédemment.

Il existe différentes façon de connecter son wallet. L'option basique est d'utiliser la librairie ethers qui fonctionnera seulement avec Metamask qui est un provider directement installé dans le navigateur web.

L'option plus complète consiste à utiliser ethers ainsi que WalletConnect accompagné de la Web3Modal. Cette option offre la possibilité de connecter tout type de Wallet et pas seulement Metamask, citons par exemple Coinbase Wallet ou Torus.

Pour cela, il faudra installer les dépendances suivantes :

yarn add ethers @walletconnect/web3-provider web3modal

Pour pouvoir facilement utiliser le Web3 dans notre projet, créons un dossier context à la racine du projet qui contiendra ethersProviderContext.tsx :

import React, { useCallback, useEffect, useState } from 'react'
import Web3Modal from 'web3modal'
import WalletConnectProvider from '@walletconnect/web3-provider'
import { useRouter } from 'next/router'
import { providers } from 'ethers'

interface AppContext {
  address: string
  provider: providers.Web3Provider
  disconnect: any
  connect: any
  chainId: number
  loading: boolean
}

const EthersContext = React.createContext<AppContext | null>(null)

export const EthersProvider = ({ children }: any) => {
  const [address, setAddress] = useState<null | string>(null)
  const [provider, setProvider] = useState<any>(null)
  const [loading, setLoading] = useState<boolean>(false)
  const [web3Provider, setWeb3Provider] = useState<null | providers.Web3Provider>(null)
  const [chainId, setChainId] = useState<null | number>(null)
  const router = useRouter()

  let web3Modal: Web3Modal
  if (typeof window !== 'undefined') {
    const providerOptions = {
      walletconnect: {
        display: {
          name: 'Mobile',
        },
        package: WalletConnectProvider,
        options: {
          infuraId: process.env.NEXT_PUBLIC_INFURA_API_KEY,
        },
      },
    }
    web3Modal = new Web3Modal({
      network: 'rinkeby',
      cacheProvider: true,
      providerOptions,
      disableInjectedProvider: false,
    })
  }

  const connect = useCallback(async function () {
    try {
      setLoading(true)
      const provider = await web3Modal.connect()
      const web3Result = new providers.Web3Provider(provider)
      const signer = web3Result.getSigner()
      const address = await signer.getAddress()
      const network = await web3Result.getNetwork()

      setProvider(provider)
      setWeb3Provider(web3Result)
      setAddress(address)
      setChainId(network.chainId)
      setLoading(false)
    } catch {
      console.log('error')
    }
  }, [])

  const disconnect = useCallback(
    async function () {
      setLoading(true)

      await web3Modal.clearCachedProvider()
      if (provider) {
        if (provider.disconnect && typeof provider.disconnect === 'function') {
          await provider.disconnect()
        }
      }

      setAddress(null)
      setProvider(null)
      setWeb3Provider(null)
      setChainId(null)
      setLoading(false)
    },
    [provider]
  )

  useEffect(() => {
    if (provider?.on) {
      const handleAccountsChanged = (accounts: any) => {
        console.log('accountsChanged', accounts)
        setAddress(accounts[0])
      }

      const handleChainChanged = (_hexChainId: any) => {
        router.reload()
      }

      const handleDisconnect = (error: any) => {
        console.log('disconnect', error)
        disconnect()
      }

      provider.on('accountsChanged', handleAccountsChanged)
      provider.on('chainChanged', handleChainChanged)
      provider.on('disconnect', handleDisconnect)

      return () => {
        if (provider.removeListener) {
          provider.removeListener('accountsChanged', handleAccountsChanged)
          provider.removeListener('chainChanged', handleChainChanged)
          provider.removeListener('disconnect', handleDisconnect)
        }
      }
    }
  }, [provider, disconnect])

  return (
    <EthersContext.Provider
      value={{
        address: address!,
        provider: web3Provider!,
        disconnect: disconnect,
        connect: connect,
        chainId: chainId!,
        loading: loading,
      }}
    >
      {children}
    </EthersContext.Provider>
  )
}

export default EthersContext
Pensez à bien ajouter votre clé API Infura dans les variables d'environnement (NEXT_PUBLIC_INFURA_API_KEY)

Grâce à ce contexte, nous pourrons nous connecter, nous déconnecter ou bien détecter un changement de réseau.

Notre fichier useEthersProvider.ts doit être sauvegardé dans un dossier hooks :

import { useContext } from 'react'
import EthersContext from '../context/ethersProviderContext'

export default function useEthersProvider() {
  const context = useContext(EthersContext)

  if (!context) {
    throw new Error('useEthersProvider must be used within a EthersProvider')
  }

  return context
}

Nous pouvons maintenant ajouter EthersProvider dans notre fichier _app.tsx :

import type { AppProps } from 'next/app'
import { ChakraProvider } from '@chakra-ui/react'
import { EthersProvider } from '../context/ethersProviderContext'
import '../styles/globals.css'

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <EthersProvider>
      <ChakraProvider>
        <Component {...pageProps} />
      </ChakraProvider>
    </EthersProvider>
  )
}

export default MyApp

Voilà ! L'implémentation Web 3 est bien ajoutée au projet, on peut dés à présent importer les functions connect et disconnect dans le composant Navbar créé précédemment :

import { Button, Flex, Image, Spacer, Text } from '@chakra-ui/react'
import useEthersProvider from '../../hooks/useEthersProvider'

const Navbar = () => {
  const { connect, address, disconnect } = useEthersProvider()

  return (
    <Flex bgColor="black" py={5} px={[5, 5, 20, 20]} w="100%" align="center" justify="center">
      <Image src="/logo.jpg" borderRadius={5} alt="PremierOctet logo" width={[45, 45, 75, 75]} />
      <Text ml={5} color="white" fontSize={[15, 15, 25, 25]} fontWeight={800}>
        Premier Octet NFT
      </Text>
      <Spacer />
      {address ? (
        <Button onClick={() => disconnect()} fontWeight={800} colorScheme="blue">
          {address.substring(0, 6)}...
          {address.substring(address.length - 4, address.length)}
        </Button>
      ) : (
        <Button onClick={() => connect()} fontWeight={800} colorScheme="blue">
          Connect Wallet
        </Button>
      )}
    </Flex>
  )
}

export default Navbar

Connect wallet demo

Le wallet se connecte bien à notre site web ! 🥳

Intéragir avec le smart-contract

Une fois notre wallet connecté, il va falloir le faire communiquer avec notre smart-contract pour générer un NFT.

Lorsque nous avons déployé le contrat dans la section précédente, un dossier artifacts a été généré à la racine du projet contenant toutes les informations relatives à notre contrat, c'est cette partie qui va nous être utile.

Nous allons récupérer le contract ABI (Application Binary Interface) qui correspond à toutes les fonctions / inputs présents dans le contrat pour que nous puissons communiquer avec.

Importons le contract ABI dans le fichier index.tsx de cette façon :

import contractABI from '../artifacts/contracts/PremierOctetERC721A.sol/PremierOctetERC721A.json'

Nous pouvons instancier notre contrat dans notre code comme ceci :

const { provider } = useEthersProvider()

const mint = async () => {
  const signer = provider.getSigner()
  const contract = new ethers.Contract(
    '0x6B5067c2F4FbFa3711d4EF76237219CBe554A1B2',
    contractABI.abi,
    signer
  )
}

La signature numérique du wallet est obligatoire pour autoriser un wallet à exécuter une transaction.

Génération d'un NFT

Une fois notre contrat instancié nous pouvons commencer à communiquer avec, et compléter la function suivante :

const [isLoading, setIsLoading] = useState<boolean>(false);

const mint = async () => {
    setIsLoading(true)
    const signer = provider.getSigner();
	const contract = new ethers.Contract(
		"0x6B5067c2F4FbFa3711d4EF76237219CBe554A1B2",
		contractABI.abi,
		signer
	);
    const overrides = {
		from: address,
        // Doit correspondre au prix à payer pour exécuter la transaction en Wei
        // => soit 0.02 ETH
		value: "20000000000000000",
	};

    try {
		const mintNft = await contract.publicSaleMint(address, 1, overrides);
		await mintNft.wait();
        setIsLoading(false)
	} catch (err) {
		console.log(err);
        setIsLoading(false)
	}
};

Ici nous appelons la function publicSaleMint présente dans notre smart contract, en passant en paramètre l'adresse de l'utilisateur ainsi que la quantité de NFT à générer.

Nous pouvons désormais ajouter un bouton sur la page d'accueil pour générer un NFT si le wallet est bien connecté :

<Flex w="100%" align="center" my={20} justify="center">
  {isLoading ? (
    <Spinner colorScheme="blue" />
  ) : address ? (
    <Button onClick={() => mint()} fontWeight={800} colorScheme="blue">
      Mint an NFT
    </Button>
  ) : (
    <Text fontSize={30} fontWeight="bold">
      Vous devez connecter votre wallet
    </Text>
  )}
</Flex>

Voilà nous avons déjà une très bonne base ! Nous pouvons maintenant ajouter une fonction pour changer l'étape de vente du smart-contract pour autoriser le mint d'un NFT, car actuellement la vente n'est pas autorisée.

Nous pouvons ajouter cette fonction :

const activateSale = async () => {
  setIsLoading(true)
  const signer = provider.getSigner()
  const contract = new ethers.Contract(
    '0x6B5067c2F4FbFa3711d4EF76237219CBe554A1B2',
    contractABI.abi,
    signer
  )

  try {
    const activeSale = await contract.setStep(1)
    await activeSale.wait()
    setIsLoading(false)
  } catch (err) {
    console.log(err)
    setIsLoading(false)
  }
}

Accompagné d'un bouton pour l'appeler :

<Button onClick={() => activateSale()} fontWeight={800} colorScheme="blue" mx={10}>
  Start Sale
</Button>

Pour ce test, nous sommes sur le réseau de test Rinkeby, des ETH de test Rinkeby sont disponibles gratuitement ici.

Voici une démo des démarches à suivre :

Demo setStep

Une fois la vente activée nous pouvons apercevoir une nouvelle entrée sur Etherscan qui indique que nous avons bien appelé la méthode setStep :

Etherscan setStep

Nous pouvons dès maintenant générer notre premier NFT !

Un clique sur le bouton Mint an NFT comme le montre l'exemple ci-dessous :

Mint demo

Pour cet exemple, nous allons répéter l'opération deux fois pour générer les deux NFT restants, car nous avons assigné un maximum de 3 NFT dans le smart-contract.

Une fois tous les NFT générés, ils apparaisssent dans le Opensea réservé au Testnet Rinkeby juste ici.

Opensea

On y retrouve aussi les métadonnées que nous avons créées auparavant dans les propriétés du NFT :

Opensea properties

Mission réussie ! Nous avons créé notre première collection NFT de A à Z 😍

L'avenir des NFT

Depuis plusieurs mois, et malgré quelques initiatives comme le récent lancement d’une collection officielle imaginée par le Vatican, le marché des NFT connaît un sérieux coup de mou. Entre février et mars 2022, les transactions enregistrées sur la blockchain sont passées de 3,9 milliards à seulement 964 millions de dollars.

Cependant, de plus en plus de plateformes permettent aux utilisateurs de créer, d’acquérir, de vendre ou de collectionner des NFT. Parallèlement, le nombre de nouveaux acquéreurs de NFT augmente également. Tout semble donc possible.

Tout le code source du projet est disponible sur Github ainsi que la collection générée sur Opensea.

Vous avez maintenant toutes les ressources nécessaires pour créer votre propre collection de NFT !

À vous de jouer ! 🤠

👋🏼

Continuer la discussion sur Twitter

18 avenue Parmentier
75011 Paris
+33 1 43 57 39 11
hello@premieroctet.com

Suivez nos aventures

GitHub

Naviguez à vue