Aller au contenu

Créer un marché NFT sur Linea

Apprenez à créer et déployer une marketplace NFT décentralisée sur Linea.

10 min de lecture
Créer un marché NFT sur Linea

Les NFT sont devenus un élément central de l'écosystème web3 depuis le boom de 2021, évoluant au-delà des objets de collection vers des cas d'usage concrets tels que l'accès aux adhésions, la tokenisation d'actifs, les objets de jeu et l'identité numérique.

Des plateformes no-code comme Phosphor ont facilité plus que jamais la création, la personnalisation et le déploiement de NFT pour les créateurs et les développeurs, grâce à un Creator Studio intuitif et une suite d'API, supprimant ainsi la nécessité de construire des contrats NFT personnalisés de zéro. Cependant, pour les développeurs souhaitant approfondir leur compréhension de Solidity et des expériences natives web3, apprendre à construire une marketplace NFT reste une compétence fondamentale.

Dans ce guide, nous allons parcourir le processus de développement et de déploiement d'une marketplace NFT basique mais entièrement fonctionnelle sur Linea. À la fin, vous aurez les connaissances nécessaires pour lancer votre propre marketplace et acquérir une meilleure maîtrise du développement de smart contracts, des applications décentralisées et des interactions web3.

Prérequis

  • Vous devez avoir une compréhension de base de Solidity.

  • Vous devez avoir Nodejs et Foundry installés sur votre PC.

Présentation du contrat

Cette marketplace NFT prendra en charge les fonctionnalités suivantes :

  1. Mise en vente de NFT : Les utilisateurs peuvent définir un prix et mettre en vente leurs NFT, avec les détails stockés de manière sécurisée dans le smart contract.

  2. Achat de NFT : Les acheteurs peuvent acquérir des NFT mis en vente en payant l'Ether requis, garantissant une transaction fluide.

  3. Achat groupé de NFT : Plusieurs NFT peuvent être achetés en une seule transaction pour réduire les frais de gas.

  4. Retrait de la vente de NFT : Les propriétaires peuvent retirer leurs NFT de la vente, les renvoyant dans leurs portefeuilles.

Configuration du projet

Nous utiliserons le framework Foundry pour ce projet. Une fois Foundry installé, exécutez la commande ci-dessous pour initier un nouveau projet :

forge init market

Ouvrez le dossier market sur Vscode ou votre éditeur de code favori, et supprimez les fichiers scripts/counter.s.sol, src/counter.sol et test/counter.t.sol.

Installer toutes les dépendances

forge install foundry-rs/forge-std --no-commit && forge install OpenZeppelin/openzeppelin-contracts --no-commit

Code complet du smart contract de la marketplace NFT

Créez un fichier market.sol dans le dossier src et ajoutez le code ci-dessous au fichier.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol";
import {Test, console} from "forge-std/Test.sol";
contract NFTMarketplace  {
    struct Listing {
        address seller;
        uint256 price;
    }
    // NFT Contract => Token ID => Listing
    mapping(address => mapping(uint256 => Listing)) public listings;
    event Listed(address indexed nftContract, uint256 indexed tokenId, address seller, uint256 price);
    event Purchase(address indexed nftContract, uint256 indexed tokenId, address buyer, uint256 price);
    event Delisted(address indexed nftContract, uint256 indexed tokenId, address seller);
    modifier isSeller(address nftContract, uint256 tokenId, address spender) {
        require(listings[address(nftContract)][tokenId].seller == spender, "Not the seller");
        _;
    }
    modifier isListed(address nftContract, uint256 tokenId) {
        require(listings[nftContract][tokenId].price > 0, "Not listed");
        _;
    }
    modifier notListed(address nftContract, uint256 tokenId) {
        require(listings[nftContract][tokenId].price == 0, "Already listed");
        _;
    }
    /**
     * @dev List an NFT for sale.
     * @param nftContract The address of the NFT contract.
     * @param tokenId The ID of the token to list.
     * @param price The price of the NFT in wei.
     */
    function listNFT(address nftContract, uint256 tokenId, uint256 price)
        external
        notListed(nftContract, tokenId)
    {
        require(price > 0, "Price must be greater than zero");
        require(IERC721(nftContract).ownerOf(tokenId) == msg.sender, "not owner");
        IERC721(nftContract).transferFrom(msg.sender, address(this), tokenId);
        listings[nftContract][tokenId] = Listing(msg.sender, price);
        emit Listed(nftContract, tokenId, msg.sender, price);
    }
    /**
     * @dev Buy a listed NFT.
     * @param nftContract The address of the NFT contract.
     * @param tokenId The ID of the token to buy.
     */
    function buyNFT(address nftContract, uint256 tokenId)
        public
        payable
        isListed(nftContract, tokenId)
    {
        Listing memory listing = listings[nftContract][tokenId];
        require(msg.value >= listing.price, "Insufficient funds");
        // Remove listing
        delete listings[nftContract][tokenId];
        // Transfer payment to the seller
        (bool sent, ) = listing.seller.call{value: listing.price}("");
        require(sent, "Failed to send Ether");
        // Transfer NFT to the buyer
        IERC721(nftContract).transferFrom(address(this), msg.sender, tokenId);
        emit Purchase(nftContract, tokenId, msg.sender, listing.price);
    }

    function buyMultipleNFT(address nftContract, uint256 [] memory tokenIds)
        external
        payable
    {
        uint256 totalAmount;
        for (uint256 i; i < tokenIds.length; i++){
           Listing memory listing = listings[nftContract][tokenIds[i]];
           totalAmount += listing.price;
        }
        require(msg.value >= totalAmount, "Insufficient funds");
        for (uint256 i; i < tokenIds.length; i++){
           buyNFT(nftContract, tokenIds[i]);   
        }
    }
    /**
     * @dev Delist an NFT from the marketplace.
     * @param nftContract The address of the NFT contract.
     * @param tokenId The ID of the token to delist.
     */
    function delistNFT(address nftContract, uint256 tokenId)
        external
        isListed(nftContract, tokenId)
        isSeller(nftContract, tokenId, msg.sender)
    {
        Listing memory listing = listings[nftContract][tokenId];

        // Remove listing
        delete listings[nftContract][tokenId];
        // Return NFT to the seller
        IERC721(nftContract).transferFrom(address(this), listing.seller, tokenId);
        emit Delisted(nftContract, tokenId, listing.seller);
    }
}
Copier

Fonctionnalités clés du code

Struct Listing Cet élément est essentiel pour implémenter des fonctionnalités telles que la mise en vente, l'achat et le retrait de la vente dans une marketplace NFT décentralisée. Il offre un moyen clair et efficace de gérer les annonces tout en garantissant que la marketplace conserve un enregistrement du vendeur et du prix de chaque article — deux éléments cruciaux pour son fonctionnement.

struct Listing {
        address seller; // NFT seller
        uint256 price;  // NFT price
    }
Copier

Mapping des annonces Cette ligne de code définit un mapping imbriqué en Solidity, qui organise et stocke les annonces pour la marketplace NFT. Le mapping externe utilise l'adresse du contrat NFT comme clé, permettant au smart contract de catégoriser les annonces par leurs collections NFT respectives. Cela garantit que la marketplace peut prendre en charge plusieurs collections NFT.

Le mapping interne utilise l'ID du token (un identifiant unique pour chaque NFT) comme clé, permettant une gestion efficace de plusieurs annonces sous un seul contrat NFT.

 // NFT Contract => Token ID => Listing
    mapping(address => mapping(uint256 => Listing)) public listings;
Copier

Mise en vente de NFT

La fonction listNFT permet aux utilisateurs de mettre en vente leurs NFT sur la marketplace. Elle prend trois paramètres : le contrat NFT, l'ID du token et le prix. Le contrat NFT désigne l'adresse de la collection depuis laquelle l'utilisateur souhaite mettre en vente un NFT, l'ID du token est l'identifiant unique du NFT au sein de cette collection, et le prix est le montant auquel l'utilisateur souhaite vendre le NFT.

La fonction vérifie d'abord que l'utilisateur est bien le propriétaire du NFT et s'assure que le prix est supérieur à zéro. Une fois ces vérifications effectuées, le NFT est transféré du vendeur vers la marketplace, le mapping des annonces est mis à jour, et l'événement Listed est émis avec les paramètres corrects.

function listNFT(address nftContract, uint256 tokenId, uint256 price)
        external
        notListed(nftContract, tokenId)
    {
        require(price > 0, "Price must be greater than zero");
        require(IERC721(nftContract).ownerOf(tokenId) == msg.sender, "not owner");
        IERC721(nftContract).transferFrom(msg.sender, address(this), tokenId);
        listings[nftContract][tokenId] = Listing(msg.sender, price);
        emit Listed(nftContract, tokenId, msg.sender, price);
    }
Copier

Achat de NFT

La fonction buyNFT permet aux utilisateurs d'acheter un NFT mis en vente sur la marketplace. Elle prend deux paramètres : nftContract, l'adresse du smart contract du NFT, et tokenId, l'identifiant unique du NFT en cours d'achat.

La fonction vérifie d'abord que l'acheteur a envoyé suffisamment d'Ether pour couvrir le prix du NFT. Une fois validé, l'annonce est supprimée du mapping des annonces, le paiement est transféré de manière sécurisée au vendeur, et le NFT est transféré du contrat de la marketplace vers l'adresse de l'acheteur, finalisant ainsi l'achat.

function buyNFT(address nftContract, uint256 tokenId)
        public
        payable
        isListed(nftContract, tokenId)
    {
        Listing memory listing = listings[nftContract][tokenId];
        require(msg.value >= listing.price, "Insufficient funds");
        // Remove listing
        delete listings[nftContract][tokenId];
        // Transfer payment to the seller
        (bool sent, ) = listing.seller.call{value: listing.price}("");
        require(sent, "Failed to send Ether");
        // Transfer NFT to the buyer
        IERC721(nftContract).transferFrom(address(this), msg.sender, tokenId);
        emit Purchase(nftContract, tokenId, msg.sender, listing.price);
    }
Copier

Achat de plusieurs NFT

La fonction buyMultipleNFT permet à un utilisateur d'acheter plusieurs NFT en une seule transaction. Elle calcule d'abord le prix total en additionnant les prix de tous les NFT listés dans le tableau tokenIds. La fonction vérifie ensuite que l'acheteur a envoyé suffisamment de fonds pour couvrir le montant total.

Une fois les fonds validés, la fonction itère à travers le tableau tokenIds, appelant la fonction buyNFT pour chaque NFT. Cela garantit que chaque NFT est acheté individuellement, que le paiement est transféré au vendeur et que les NFT sont envoyés à l'acheteur — en suivant le même processus que la fonction buyNFT.

function buyMultipleNFT(address nftContract, uint256 [] memory tokenIds)
        external
        payable
    {
        uint256 totalAmount;
        for (uint256 i; i < tokenIds.length; i++){
           Listing memory listing = listings[nftContract][tokenIds[i]];
           totalAmount += listing.price;
        }
        require(msg.value >= totalAmount, "Insufficient funds");
        for (uint256 i; i < tokenIds.length; i++){
           buyNFT(nftContract, tokenIds[i]);   
        }
    }
Copier

Retrait de la vente de NFT

La fonction delistNFT permet à un vendeur de retirer son NFT de la marketplace. Elle vérifie d'abord que le NFT est bien en vente et que l'appelant en est le propriétaire légitime. Une fois validé, l'annonce est supprimée de la marketplace, retirant effectivement le NFT de la vente.

La fonction transfère ensuite le NFT au vendeur, lui garantissant de récupérer la propriété de son actif. Enfin, l'événement Delisted est émis pour confirmer que le NFT a été retiré avec succès de la marketplace.

function delistNFT(address nftContract, uint256 tokenId)
        external
        isListed(nftContract, tokenId)
        isSeller(nftContract, tokenId, msg.sender)
    {
        Listing memory listing = listings[nftContract][tokenId];

        // Remove listing
        delete listings[nftContract][tokenId];
        // Return NFT to the seller
        IERC721(nftContract).transferFrom(address(this), listing.seller, tokenId);
        emit Delisted(nftContract, tokenId, listing.seller);
    }
Copier

Événements clés

Le contrat émet des événements pour les actions essentielles de la marketplace :

  • Listed : Déclenché lorsqu'un NFT est mis en vente, enregistrant des détails tels que le vendeur, le prix et les informations du NFT.

  • Purchase : Émis lorsqu'un acheteur acquiert un NFT, garantissant la traçabilité des transactions.

  • Delisted : Indique qu'un NFT a été retiré avec succès de la marketplace, assurant la transparence.

event Listed(address indexed nftContract, uint256 indexed tokenId, address seller, uint256 price);
    event Purchase(address indexed nftContract, uint256 indexed tokenId, address buyer, uint256 price);
    event Delisted(address indexed nftContract, uint256 indexed tokenId, address seller
Copier

Présentation de la suite de tests

Créez un fichier NFT.sol dans le dossier src et ajoutez le code ci-dessous au fichier.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "lib/openzeppelin-contracts/contracts/token/ERC721/ERC721.sol";
import "lib/openzeppelin-contracts/contracts/access/Ownable.sol";
contract NFT is ERC721, Ownable {
    uint256 private _tokenIdCounter;
    string private _baseTokenURI;
    event Minted(address indexed to, uint256 tokenId);
    constructor(string memory name, string memory symbol, string memory baseTokenURI) ERC721(name, symbol) Ownable(msg.sender) {
        _baseTokenURI = baseTokenURI;
    }
    /// @dev Function to mint a new NFT
    function mint(address to) external onlyOwner {
        uint256 tokenId = _tokenIdCounter;
        _tokenIdCounter += 1;
        _safeMint(to, tokenId);
        emit Minted(to, tokenId);
    }
    /// @dev Override base token URI for metadata
    function _baseURI() internal view override returns (string memory) {
        return _baseTokenURI;
    }
    /// @dev Function to update the base token URI
    function setBaseTokenURI(string memory newBaseTokenURI) external onlyOwner {
        _baseTokenURI = newBaseTokenURI;
    }
    /// @dev Function to retrieve the current token ID count
    function getTokenCount() external view returns (uint256) {
        return _tokenIdCounter;
    }
}
Copier

Créez un fichier market.t.sol dans le dossier test et ajoutez le code ci-dessous au fichier.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Test, console} from "forge-std/Test.sol";
import {NFTMarketplace} from "../src/Market.sol";
import {NFT} from "../src/NFT.sol";
contract MarketTest is Test {
    NFTMarketplace public market;
    NFT public nft;
    address public user1 = makeAddr("user1");
    address public user2 = makeAddr("user2");
    address public user3 = makeAddr("user3");
    function setUp() public {
        nft = new NFT("TestNFT", "tnft", "");
        market = new NFTMarketplace();
        nft.mint(user1);
        nft.mint(user1);
        nft.mint(user1);
    }
    function test_Listing() public {
        vm.startPrank(user1);
        nft.approve(address(market), 0);
        market.listNFT(address(nft), 0, 1 ether);

    }

     function test_BuyingNFT() public {
        vm.startPrank(user1);
        nft.approve(address(market), 0);
        market.listNFT(address(nft), 0, 1 ether);

        vm.startPrank(user2);
        deal(user2, 10 ether);
        market.buyNFT{value: 1 ether}(address(nft), 0);
        assert(nft.ownerOf(0) == user2);

    }

      function test_BuyingMultipleNFT() public {
        vm.startPrank(user1);
        nft.approve(address(market), 0);
        nft.approve(address(market), 1);
        nft.approve(address(market), 2);
        market.listNFT(address(nft), 0, 1 ether);
        market.listNFT(address(nft), 1, 1 ether);
        market.listNFT(address(nft), 2, 1 ether);

        vm.startPrank(user2);
        deal(user2, 10 ether);
        uint256 [] memory nfts = new uint256[] (3);
        nfts[0] = 0;
        nfts[1] = 1;
        nfts[2] = 2; 
        market.buyMultipleNFT{value: 3 ether}(address(nft), nfts);     
    }

     function test_DelistingNFT() public {
        vm.startPrank(user1);
        nft.approve(address(market), 0);
        market.listNFT(address(nft), 0, 1 ether);
        assert(nft.ownerOf(0) == address(market));
        market.delistNFT(address(nft), 0);
        assert(nft.ownerOf(0) == address(user1));

    }
}
Copier

La suite de tests ci-dessus garantit que le contrat de la marketplace NFT fonctionne comme prévu dans l'ensemble de ses fonctionnalités principales. Voici une présentation détaillée des tests :

Configuration de l'environnement de test

La fonction setUp initialise l'environnement en déployant les contrats NFTMarketplace et NFT. Elle crée trois NFT pour user1 à des fins de test. Un contrat NFT fictif (NFT) est déployé avec une fonction mint pour générer des NFT, tandis que le contrat de la marketplace (NFTMarketplace) est instancié. User1 possède tous les NFT créés, permettant de tester les fonctionnalités de mise en vente et de retrait de la vente.

Test de la mise en vente de NFT

La fonction test_Listing vérifie que user1 peut mettre en vente un NFT sur la marketplace.

  • User1 autorise la marketplace à transférer le tokenId 0.

  • User1 met en vente le NFT à un prix de 1 Ether.

  • Le test confirme que la fonction listNFT fonctionne correctement et que le NFT est transféré vers la marketplace.

Test d'achat d'un seul NFT

La fonction test_BuyingNFT simule l'achat d'un NFT mis en vente par user2.

  • User1 met en vente le tokenId 0 pour 1 Ether.

  • User2 reçoit 10 Ether et appelle la fonction buyNFT, en envoyant 1 Ether pour acheter le NFT.

  • Après l'achat, le test vérifie que user2 est le nouveau propriétaire du tokenId 0.

Test d'achat de plusieurs NFT

La fonction test_BuyingMultipleNFT teste la capacité d'acheter plusieurs NFT en une seule transaction.

  • User1 met en vente trois NFT (tokenId 0, 1, 2) à 1 Ether chacun.

  • User2 appelle buyMultipleNFT, en passant les IDs des tokens et en envoyant 3 Ether.

  • Le test s'assure que user2 devient propriétaire des trois NFT et que la marketplace traite correctement les achats multiples.

Test du retrait de la vente de NFT

La fonction test_DelistingNFT vérifie que user1 peut retirer son NFT de la marketplace.

  • User1 met en vente le tokenId 0 pour 1 Ether et appelle delistNFT.

  • Le NFT est renvoyé à user1, et le test confirme que la propriété a été restituée à user1.

Exécuter les tests

forge test

Déploiement et vérification sur Linea

Ajoutez les variables requises au fichier .env, en vous assurant qu'il se trouve à la racine du projet :

LINEA_RPC_URL=https://linea-mainnet.infura.io/v3/ // Infura RPC endpoint for connecting to Linea 
PRIVATE_KEY=************************************************* // Wallet private key for signing transactions LINEA_API_KEY=************************* // API key for accessing Linea services via Infura
Copier

Ajoutez les détails ci-dessous à foundry.toml

[etherscan]
linea = { key = "${LINEA_API_KEY}", url = "https://api-sepolia.lineascan.build/api" }
Copier

Créez un Makefile et ajoutez les détails ci-dessous :

deploy:
    forge create src/Market.sol:NFTMarketplace --rpc-url $(LINEA_RPC_URL) --private-key $(PRIVATE_KEY)

verify:; forge verify-contract --rpc-url $(LINEA_RPC_URL) --chain linea <contract address> src/Market.sol:NFTMarketplace
Copier

Déploiement

make deploy

Vérification

make verify

Conclusion

Construire une marketplace NFT sur Linea offre aux développeurs une expérience pratique en développement de smart contracts, en programmation Solidity et en interactions web3. Dans ce guide, nous avons couvert les fonctionnalités principales d'une marketplace décentralisée, notamment la mise en vente, l'achat, l'achat groupé et le retrait de la vente de NFT, tout en garantissant des transactions sécurisées et efficaces.

Nous avons également parcouru l'implémentation et les tests du smart contract avec Foundry, ainsi que le processus de déploiement et de vérification sur Linea. En suivant ce guide, vous disposez désormais des connaissances fondamentales pour construire, tester et déployer votre propre marketplace NFT.

Pour d'autres tutoriels pratiques et des analyses approfondies du développement blockchain, consultez la chaîne YouTube MetaMask Developer, et restez informé de nos dernières actualités en nous suivant sur Twitter.

Traduit par IA. Peut contenir des erreurs. Veuillez toujours vérifier les informations.

Évaluer la traduction

Cet article a été rédigé par :

  • Kingsley Okonkwo

    Kingsley, développeur blockchain Ethereum certifié, occupe le poste de rédacteur technique chez Consensys, avec une spécialisation dans les outils destinés aux développeurs. Avant de rejoindre Consensys, Kingsley a travaillé en tant que développeur backend freelance sur plusieurs plateformes, dont Gigster et Braintrust. Fort de 5 ans d'expérience en développement logiciel, il maîtrise des outils tels que JavaScript (Node.js & React), GoLang, SQL et Docker. Kingsley excelle dans la création de tutoriels pas à pas et de guides pratiques, en s'appuyant sur les dernières technologies web3 pour aider les développeurs à créer de meilleures dapps. Actuellement basé à Dubaï, Kingsley aime jouer au basketball et au football pendant son temps libre.

    Lire tous les articles