Saltar al contenido

Crea un mercado de NFT en Linea

Aprende a crear e implementar un mercado NFT descentralizado en Linea.

10 min de lectura
Crea un mercado de NFT en Linea

Los NFT se han convertido en una parte fundamental del ecosistema web3 desde el boom de 2021, evolucionando más allá de los coleccionables hacia casos de uso en el mundo real, como acceso a membresías, tokenización de activos, objetos de videojuegos e identidad digital.

Las plataformas sin código como Phosphor han facilitado más que nunca que creadores y desarrolladores acuñen, personalicen e implementen NFT a través de un intuitivo Creator Studio y un conjunto de APIs, eliminando la necesidad de construir contratos NFT personalizados desde cero. Sin embargo, para los desarrolladores que buscan profundizar su comprensión de Solidity y las experiencias nativas de web3, aprender a construir un marketplace de NFT sigue siendo una habilidad fundamental.

En esta guía, recorreremos el proceso de desarrollo e implementación de un marketplace de NFT básico pero completamente funcional en Linea. Al finalizar, tendrás los conocimientos necesarios para lanzar tu propio marketplace y adquirir un dominio más sólido del desarrollo de contratos inteligentes, aplicaciones descentralizadas e interacciones web3.

Requisitos previos

Descripción general del contrato

Este marketplace de NFT admitirá las siguientes funcionalidades:

  1. Listar NFT en venta: Los usuarios pueden establecer un precio y listar sus NFT, con los detalles almacenados de forma segura en el contrato inteligente.

  2. Comprar NFT: Los compradores pueden adquirir NFT listados pagando el Ether requerido, garantizando una transacción fluida.

  3. Compra por lotes de NFT: Se pueden comprar múltiples NFT en una sola transacción para reducir los costos de gas.

  4. Retirar NFT del listado: Los propietarios pueden eliminar sus NFT de la venta, devolviéndolos a sus billeteras.

Configuración del proyecto

Utilizaremos el framework Foundry para este proyecto. Con Foundry instalado, ejecuta el siguiente comando para iniciar un nuevo proyecto:

forge init market

Abre la carpeta market en Vscode o tu editor de código favorito, y elimina los archivos scripts/counter.s.sol, src/counter.sol y test/counter.t.sol.

Instalar todas las dependencias

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

Código completo del contrato inteligente del marketplace de NFT

Crea un archivo market.sol en la carpeta src y añade el código a continuación al archivo.

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

Características clave del código

Struct de listado Esto es esencial para implementar funcionalidades como listar, comprar y retirar del listado en un marketplace de NFT descentralizado. Proporciona una forma clara y eficiente de gestionar los listados, asegurando que el marketplace mantenga un registro del vendedor y el precio de cada artículo, ambos cruciales para su funcionamiento.

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

Mapping de listados Esta línea de código define un mapping anidado en Solidity, que organiza y almacena los listados del marketplace de NFT. El mapping externo utiliza la dirección del contrato NFT como clave, lo que permite al contrato inteligente categorizar los listados según sus respectivas colecciones de NFT. Esto garantiza que el marketplace pueda admitir múltiples colecciones de NFT.

El mapping interno utiliza el token ID (un identificador único para cada NFT) como clave, lo que permite una gestión eficiente de múltiples listados bajo un mismo contrato NFT.

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

Listado de NFT

La función listNFT permite a los usuarios listar sus NFT en venta en el marketplace. Recibe tres parámetros: el contrato NFT, el token ID y el precio. El contrato NFT hace referencia a la dirección de la colección desde la cual el usuario desea listar un NFT, el token ID es el identificador único del NFT dentro de esa colección, y el precio es el monto por el que el usuario desea vender el NFT.

La función primero valida que el usuario sea el propietario del NFT y verifica que el precio sea mayor que cero. Una vez superadas estas comprobaciones, el NFT se transfiere del vendedor al marketplace, se actualiza el mapping de listados y se emite el evento Listed con los parámetros correctos.

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

La función buyNFT permite a los usuarios comprar un NFT listado en el marketplace. Recibe dos parámetros: nftContract, la dirección del contrato inteligente del NFT, y tokenId, el identificador único del NFT que se está comprando.

La función primero verifica que el comprador haya enviado suficiente Ether para cubrir el precio del NFT. Una vez validado, el listado se elimina del mapping de listados, el pago se transfiere de forma segura al vendedor y el NFT se transfiere desde el contrato del marketplace a la dirección del comprador, finalizando la 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últiples NFT

La función buyMultipleNFT permite a un usuario comprar múltiples NFT en una sola transacción. Primero calcula el precio total sumando los precios de todos los NFT listados en el array tokenIds. Luego verifica que el comprador haya enviado fondos suficientes para cubrir el monto total.

Una vez validados los fondos, la función itera a través del array tokenIds, llamando a la función buyNFT para cada NFT. Esto garantiza que cada NFT se compre de forma individual, el pago se transfiera al vendedor y los NFT se envíen al comprador, siguiendo el mismo proceso que la función 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

Retirar NFT del listado

La función delistNFT permite a un vendedor eliminar su NFT del marketplace. Primero verifica que el NFT esté listado y que quien realiza la llamada sea el propietario legítimo. Una vez validado, el listado se elimina del marketplace, retirando efectivamente el NFT de la venta.

La función luego transfiere el NFT de vuelta al vendedor, asegurando que recupere la propiedad de su activo. Finalmente, se emite el evento Delisted para confirmar que el NFT ha sido eliminado exitosamente del 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 clave

El contrato emite eventos para las acciones esenciales del marketplace:

  • Listed: Se activa cuando se lista un NFT, registrando detalles como el vendedor, el precio y la información del NFT.

  • Purchase: Se emite cuando un comprador adquiere un NFT, garantizando la trazabilidad de la transacción.

  • Delisted: Indica que un NFT ha sido eliminado exitosamente del marketplace, manteniendo la transparencia.

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

Descripción general del conjunto de pruebas

Crea un archivo NFT.sol en la carpeta src y añade el código a continuación al archivo.

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

Crea un archivo market.t.sol en la carpeta test y añade el código a continuación al archivo.

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

El conjunto de pruebas anterior garantiza que el contrato del marketplace de NFT funcione según lo previsto en todas sus funcionalidades principales. A continuación, se detalla cada prueba:

Configuración del entorno de pruebas

La función setUp inicializa el entorno desplegando tanto el contrato NFTMarketplace como el contrato NFT. Acuña tres NFT para user1 con fines de prueba. Se despliega un contrato NFT simulado (NFT) con una función mint para generar NFT, mientras que el contrato del marketplace (NFTMarketplace) se instancia. User1 es propietario de todos los NFT acuñados, lo que permite probar las funcionalidades de listado y retiro del listado.

Prueba de listado de NFT

La función test_Listing verifica que user1 pueda listar un NFT en venta en el marketplace.

  • User1 aprueba al marketplace para transferir el tokenId 0.

  • User1 lista el NFT a un precio de 1 Ether.

  • La prueba confirma que la función listNFT funciona correctamente y que el NFT se transfiere al marketplace.

Prueba de compra de un solo NFT

La función test_BuyingNFT simula la compra de un NFT listado por parte de user2.

  • User1 lista el tokenId 0 por 1 Ether.

  • User2 recibe 10 Ether y llama a la función buyNFT, enviando 1 Ether para comprar el NFT.

  • Tras la compra, la prueba verifica que user2 es el nuevo propietario del tokenId 0.

Prueba de compra de múltiples NFT

La función test_BuyingMultipleNFT prueba la capacidad de comprar múltiples NFT en una sola transacción.

  • User1 lista tres NFT (tokenId 0, 1, 2) por 1 Ether cada uno.

  • User2 llama a buyMultipleNFT, pasando los token IDs y enviando 3 Ether.

  • La prueba verifica que user2 se convierte en propietario de los tres NFT y que el marketplace procesa correctamente las compras múltiples.

Prueba de retiro de NFT del listado

La función test_DelistingNFT verifica que user1 pueda retirar su NFT del marketplace.

  • User1 lista el tokenId 0 por 1 Ether y llama a delistNFT.

  • El NFT se devuelve a user1, y la prueba confirma que la propiedad ha vuelto a user1.

Ejecutar pruebas

forge test

Despliegue y verificación en Linea

Añade las variables requeridas al archivo .env, asegurándote de que esté ubicado en el nivel raíz del proyecto:

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

Añade los detalles a continuación en foundry.toml

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

Crea un Makefile y añade los detalles a continuación:

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

Despliegue

make deploy

Verificación

make verify

Conclusión

Construir un marketplace de NFT en Linea brinda a los desarrolladores experiencia práctica en el desarrollo de contratos inteligentes, programación en Solidity e interacciones web3. En esta guía, cubrimos las funcionalidades principales de un marketplace descentralizado, incluyendo el listado, la compra, la compra por lotes y el retiro del listado de NFT, garantizando transacciones seguras y eficientes.

También recorrimos la implementación y las pruebas del contrato inteligente utilizando Foundry, así como el proceso de despliegue y verificación en Linea. Siguiendo esta guía, ahora cuentas con los conocimientos fundamentales para construir, probar e implementar tu propio marketplace de NFT.

Para más tutoriales prácticos y análisis en profundidad sobre el desarrollo blockchain, visita el canal de YouTube de MetaMask Developer y mantente al día con nuestras últimas novedades siguiéndonos en Twitter.

Traducido por IA. Puede contener errores. Por favor, verifique siempre la información.

Califica la traducción

Este artículo fue escrito por:

  • Kingsley Okonkwo

    Kingsley, desarrollador certificado de Ethereum Blockchain, trabaja como escritor técnico en Consensys, con un enfoque especial en herramientas para desarrolladores. Antes de unirse a Consensys, Kingsley trabajó como desarrollador backend freelance en varias plataformas, entre ellas Gigster y Braintrust. Con 5 años de experiencia en desarrollo de software, domina herramientas como JavaScript (Node.js y React), GoLang, SQL y Docker. Kingsley destaca en la creación de tutoriales paso a paso y guías prácticas, aprovechando las últimas tecnologías web3 para ayudar a otros desarrolladores a construir mejores dapps. Actualmente reside en Dubái y en su tiempo libre disfruta jugando al baloncesto y al fútbol.

    Leer todos los artículos