Crie um Marketplace de NFT na Linea

Aprenda a criar e implantar um marketplace de NFT descentralizado na Linea.

Leitura de 10 min
Crie um Marketplace de NFT na Linea

NFTs se tornaram uma parte central do ecossistema web3 desde o boom de 2021, evoluindo além dos colecionáveis para casos de uso no mundo real, como acesso a memberships, tokenização de ativos, itens de jogos e identidade digital.

Plataformas no-code como o Phosphor tornaram mais fácil do que nunca para criadores e desenvolvedores mintarem, personalizarem e implantarem NFTs por meio de um intuitivo Creator Studio e conjunto de APIs, eliminando a necessidade de construir contratos NFT personalizados do zero. No entanto, para desenvolvedores que desejam aprofundar seu conhecimento em Solidity e experiências nativas do web3, aprender a construir um marketplace de NFTs continua sendo uma habilidade fundamental.

Neste guia, vamos percorrer o processo de desenvolvimento e implantação de um marketplace de NFTs básico, mas totalmente funcional, na Linea. Ao final, você terá o conhecimento necessário para lançar seu próprio marketplace e obter uma compreensão mais sólida do desenvolvimento de smart contracts, aplicações descentralizadas e interações web3.

Pré-requisitos

  • Você deve ter um entendimento básico de Solidity.

  • Você deve ter o Nodejs e o Foundry instalados no seu computador.

Visão geral do contrato

Este marketplace de NFTs suportará as seguintes funcionalidades:

  1. Listagem de NFTs para venda: Os usuários podem definir um preço e listar seus NFTs, com os detalhes armazenados com segurança no smart contract.

  2. Compra de NFTs: Os compradores podem adquirir NFTs listados pagando o valor em Ether necessário, garantindo uma transação sem complicações.

  3. Compra em lote de NFTs: Múltiplos NFTs podem ser comprados em uma única transação para reduzir os custos de gas.

  4. Remoção de listagem de NFTs: Os proprietários podem remover seus NFTs da venda, devolvendo-os às suas carteiras.

Configuração do projeto

Usaremos o framework Foundry para este projeto. Com o Foundry instalado, execute o comando abaixo para iniciar um novo projeto:

forge init market

Abra a pasta market no Vscode ou no seu editor de código favorito e exclua os arquivos scripts/counter.s.sol, src/counter.sol e test/counter.t.sol

Instalar todas as dependências

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

Código completo do smart contract do marketplace de NFTs

Crie um arquivo market.sol na pasta src e adicione o código abaixo ao arquivo.

// 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);
    }
}
Copiar

Principais funcionalidades do código

Struct de listagem Isso é essencial para implementar funcionalidades como listagem, compra e remoção de listagem em um marketplace de NFTs descentralizado. Ele fornece uma maneira clara e eficiente de gerenciar listagens, garantindo que o marketplace mantenha um registro do vendedor e do preço de cada item — ambos cruciais para suas operações.

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

Mapping de listagem Esta linha de código define um mapping aninhado em Solidity, que organiza e armazena as listagens do marketplace de NFTs. O mapping externo usa o endereço do contrato NFT como chave, permitindo que o smart contract categorize as listagens por suas respectivas coleções de NFTs. Isso garante que o marketplace possa suportar múltiplas coleções de NFTs.

O mapping interno usa o token ID (um identificador único para cada NFT) como chave, permitindo o gerenciamento eficiente de múltiplas listagens em um único contrato NFT.

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

Listagem de NFT

A função listNFT permite que os usuários listem seus NFTs para venda no marketplace. Ela recebe três parâmetros: contrato NFT, token ID e preço. O contrato NFT refere-se ao endereço da coleção da qual o usuário deseja listar um NFT, o token ID é o identificador único do NFT dentro dessa coleção, e o preço é o valor pelo qual o usuário deseja vender o NFT.

A função primeiro valida que o usuário é o proprietário do NFT e garante que o preço seja maior que zero. Após essas verificações, o NFT é transferido do vendedor para o marketplace, o mapping de listagens é atualizado e o evento Listed é emitido com os parâmetros corretos.

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);
    }
Copiar

Compra de NFT

A função buyNFT permite que os usuários comprem um NFT listado no marketplace. Ela recebe dois parâmetros: nftContract, o endereço do smart contract do NFT, e tokenId, o identificador único do NFT sendo comprado.

A função primeiro verifica se o comprador enviou Ether suficiente para cobrir o preço do NFT. Uma vez validado, a listagem é removida do mapping de listagens, o pagamento é transferido com segurança para o vendedor e o NFT é transferido do contrato do marketplace para o endereço do comprador, finalizando a compra.

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);
    }
Copiar

Compra de múltiplos NFTs

A função buyMultipleNFT permite que um usuário compre múltiplos NFTs em uma única transação. Ela primeiro calcula o preço total somando os preços de todos os NFTs listados no array tokenIds. A função então verifica se o comprador enviou fundos suficientes para cobrir o valor total.

Uma vez validados os fundos, a função itera pelo array tokenIds, chamando a função buyNFT para cada NFT. Isso garante que cada NFT seja comprado individualmente, o pagamento seja transferido para o vendedor e os NFTs sejam enviados ao comprador — seguindo o mesmo processo da função 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]);   
        }
    }
Copiar

Remoção de listagem de NFTs

A função delistNFT permite que um vendedor remova seu NFT do marketplace. Ela primeiro verifica se o NFT está listado e se o chamador é o legítimo proprietário. Uma vez validado, a listagem é excluída do marketplace, removendo efetivamente o NFT da venda.

A função então transfere o NFT de volta para o vendedor, garantindo que ele recupere a propriedade do seu ativo. Por fim, o evento Delisted é emitido para confirmar que o NFT foi removido com sucesso do 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);
    }
Copiar

Eventos principais

O contrato emite eventos para ações essenciais do marketplace:

  • Listed: Acionado quando um NFT é listado, registrando detalhes como o vendedor, preço e informações do NFT.

  • Purchase: Emitido quando um comprador adquire um NFT, garantindo a rastreabilidade da transação.

  • Delisted: Indica que um NFT foi removido com sucesso do marketplace, mantendo a transparência.

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
Copiar

Visão geral do conjunto de testes

Crie um arquivo NFT.sol na pasta src e adicione o código abaixo ao arquivo.

// 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;
    }
}
Copiar

Crie um arquivo market.t.sol na pasta test e adicione o código abaixo ao arquivo.

// 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));

    }
}
Copiar

O conjunto de testes acima garante que o contrato do marketplace de NFTs funcione conforme o esperado em suas funcionalidades principais. Veja um resumo dos testes:

Configuração do ambiente de testes

A função setUp inicializa o ambiente implantando os contratos NFTMarketplace e NFT. Ela minta três NFTs para o user1 para fins de teste. Um contrato NFT simulado (NFT) é implantado com uma função mint para gerar NFTs, enquanto o contrato do marketplace (NFTMarketplace) é instanciado. O user1 possui todos os NFTs mintados, permitindo o teste das funcionalidades de listagem e remoção de listagem.

Testando a listagem de NFT

A função test_Listing garante que o user1 possa listar um NFT para venda no marketplace.

  • O user1 aprova o marketplace para transferir o tokenId 0.

  • O user1 lista o NFT a um preço de 1 Ether.

  • O teste confirma que a função listNFT funciona corretamente e que o NFT é transferido para o marketplace.

Testando a compra de um único NFT

A função test_BuyingNFT simula a compra de um NFT listado pelo user2.

  • O user1 lista o tokenId 0 por 1 Ether.

  • O user2 recebe 10 Ether e chama a função buyNFT, enviando 1 Ether para comprar o NFT.

  • Após a compra, o teste verifica que o user2 é o novo proprietário do tokenId 0.

Testando a compra de múltiplos NFTs

A função test_BuyingMultipleNFT testa a capacidade de comprar múltiplos NFTs em uma única transação.

  • O user1 lista três NFTs (tokenId 0, 1, 2) por 1 Ether cada.

  • O user2 chama buyMultipleNFT, passando os token IDs e enviando 3 Ether.

  • O teste garante que o user2 se torne o proprietário dos três NFTs e que o marketplace processe corretamente as múltiplas compras.

Testando a remoção de listagem de NFT

A função test_DelistingNFT verifica que o user1 pode remover seu NFT do marketplace.

  • O user1 lista o tokenId 0 por 1 Ether e chama delistNFT.

  • O NFT é devolvido ao user1, e o teste confirma que a propriedade foi revertida para o user1.

Executar testes

forge test

Implantação e verificação na Linea

Adicione as variáveis necessárias ao arquivo .env, garantindo que ele esteja localizado na raiz do projeto:

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
Copiar

Adicione os detalhes abaixo ao foundry.toml

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

Crie um Makefile e adicione os detalhes abaixo:

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
Copiar

Implantação

make deploy

Verificação

make verify

Conclusão

Construir um marketplace de NFTs na Linea proporciona aos desenvolvedores experiência prática em desenvolvimento de smart contracts, programação em Solidity e interações web3. Neste guia, cobrimos as funcionalidades principais de um marketplace descentralizado, incluindo listagem, compra, compra em lote e remoção de listagem de NFTs, garantindo transações seguras e eficientes.

Também percorremos a implementação e os testes do smart contract usando o Foundry, bem como o processo de implantação e verificação na Linea. Seguindo este guia, você agora tem o conhecimento fundamental para construir, testar e implantar seu próprio marketplace de NFTs.

Para mais tutoriais práticos e análises aprofundadas sobre desenvolvimento blockchain, confira o canal do YouTube do MetaMask Developer e fique atualizado com nossos insights mais recentes nos seguindo no Twitter.

Traduzido por IA. Pode conter erros. Por favor, verifique sempre as informações.

Avalie a tradução
  • Kingsley Okonkwo
    Kingsley Okonkwo

    Kingsley, desenvolvedor certificado de Blockchain Ethereum, atua como redator técnico na Consensys, com foco especial em ferramentas para desenvolvedores. Antes de ingressar na Consensys, Kingsley trabalhou como desenvolvedor backend freelance em diversas plataformas, incluindo Gigster e Braintrust. Com 5 anos de experiência em desenvolvimento de software, ele domina ferramentas como JavaScript (Node.js e React), GoLang, SQL e Docker. Kingsley se destaca na criação de tutoriais passo a passo e guias práticos, utilizando o que há de mais moderno em tecnologia web3 para ajudar outros desenvolvedores a criar dapps melhores. Atualmente radicado em Dubai, Kingsley aproveita seu tempo livre jogando basquete e futebol.

    Ler todos os artigos