tutoriales.com

Creando y Utilizando un Mercado NFT Descentralizado con IPFS y Smart Contracts 🌐

Este tutorial te guiará paso a paso en la creación de un mercado NFT completamente descentralizado. Cubriremos la implementación de smart contracts en Solidity, el uso de IPFS para almacenar metadatos de los NFTs, y cómo interactuar con ellos para crear, listar y comprar activos digitales únicos. ¡Prepárate para construir tu propio OpenSea en miniatura!

Intermedio20 min de lectura6 views9 de marzo de 2026Reportar error

Introducción al Mercado NFT Descentralizado ✨

Los NFTs (Tokens No Fungibles) han revolucionado la forma en que pensamos sobre la propiedad digital. Sin embargo, muchos mercados NFT populares aún dependen de infraestructuras centralizadas para almacenar metadatos o para la interfaz de usuario. En este tutorial, daremos un paso más allá para crear un mercado verdaderamente descentralizado, utilizando el poder de los smart contracts de Ethereum (o una EVM compatible) y el Sistema de Archivos InterPlanetario (IPFS).

¿Por qué Descentralizar un Mercado NFT? 💡

La descentralización ofrece múltiples beneficios:

  • Resistencia a la censura: Los datos y la lógica de la aplicación no pueden ser alterados o eliminados por una única entidad.
  • Transparencia: Todas las transacciones y la información del NFT son verificables en la blockchain.
  • Inmutabilidad: Una vez que un NFT se registra en la blockchain y sus metadatos en IPFS, no se pueden cambiar.
  • Confianza: Se reduce la necesidad de intermediarios, ya que las operaciones se rigen por código auditable.
🔥 Importante: Entender los principios básicos de la blockchain, Ethereum y Solidity es crucial para seguir este tutorial. Si eres nuevo en estos conceptos, te recomendamos familiarizarte con ellos antes de continuar.

Componentes Clave de Nuestro Mercado NFT 🛠️

Para construir nuestro mercado, necesitaremos los siguientes componentes:

  1. Smart Contract de NFT (ERC-721): Para definir la propiedad y las características de nuestros tokens no fungibles.
  2. Smart Contract del Mercado: Para gestionar la lógica de listado, compra y venta de NFTs.
  3. IPFS: Para almacenar los metadatos de los NFTs (nombre, descripción, URL de la imagen, etc.) de forma descentralizada.
  4. Una Cartera Web3 (ej. MetaMask): Para interactuar con la blockchain.
  5. Una Herramienta de Desarrollo (ej. Hardhat/Foundry): Para desplegar y probar nuestros contratos.
20% Preparación

Diagrama de Flujo del Mercado NFT

Aquí tienes un diagrama simplificado de cómo interactuarán los componentes:

Usuario Cartera Web3 IPFS Smart Contract NFT Smart Contract Mercado Blockchain Interactúa Firma Transacción Sube Metadatos Crea NFT Gestiona Listados Registra Transacciones Interfaz de Usuario (dApp) Solicita Operación

1. El Smart Contract ERC-721 para NFTs 🖼️

Comenzaremos creando nuestro propio contrato ERC-721. Utilizaremos las implementaciones estándar de OpenZeppelin por su seguridad y robustez.

Inicializando el Proyecto (Hardhat/Foundry) 🚀

Si usas Hardhat:

npx hardhat init
npm install @openzeppelin/contracts

Si usas Foundry:

forge init
forge install OpenZeppelin/openzeppelin-contracts

Código del Smart Contract ERC-721 (MyNFT.sol) 📝

Este contrato nos permitirá crear (mintear) NFTs y asignarles un URI de metadatos.

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

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

contract MyNFT is ERC721, Ownable {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIdCounter;

    constructor(address initialOwner) ERC721("My Awesome NFT", "MANFT") Ownable(initialOwner) {}

    function safeMint(address to, string memory uri) public onlyOwner returns (uint256) {
        _tokenIdCounter.increment();
        uint256 newItemId = _tokenIdCounter.current();
        _safeMint(to, newItemId);
        _setTokenURI(newItemId, uri);
        return newItemId;
    }

    // La función base URI puede ser útil para construir URIs si los metadatos tienen una base común.
    // En nuestro caso, pasamos el URI completo en `safeMint`.
    // function _baseURI() internal pure override returns (string memory) {
    //     return "ipfs://"; // Ejemplo si todos los NFTs usan el mismo prefijo IPFS
    // }
}
📌 Nota: La función `safeMint` está protegida por `onlyOwner`, lo que significa que solo el desplegador del contrato (o un rol autorizado) puede mintear nuevos NFTs. En un mercado real, esta lógica podría ser más compleja, permitiendo a los usuarios mintear sus propios NFTs a través del contrato del mercado.

2. El Smart Contract del Mercado NFT 🛒

Este contrato gestionará la lógica de listado, compra y cancelación de NFTs. Actuará como un escrow (depósito de garantía) para los NFTs y los fondos.

Código del Smart Contract (NFTMarketplace.sol) 💰

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

import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract NFTMarketplace is Ownable, IERC721Receiver {
    struct Listing {
        uint256 itemId;
        address nftContract;
        uint256 tokenId;
        uint256 price;
        address seller;
        bool isSold;
    }

    mapping(uint256 => Listing) public listings;
    uint256 private _listingIdCounter; // Usaremos esto para generar IDs únicos para cada listado

    event ItemListed(uint256 indexed listingId, address indexed nftContract, uint256 indexed tokenId, address seller, uint256 price);
    event ItemBought(uint256 indexed listingId, address indexed nftContract, uint256 indexed tokenId, address buyer, uint256 price);
    event ListingCancelled(uint256 indexed listingId, address indexed nftContract, uint256 indexed tokenId, address seller);

    constructor(address initialOwner) Ownable(initialOwner) {}

    function listItem(address _nftContract, uint256 _tokenId, uint256 _price) public {
        require(_price > 0, "Price must be greater than zero");

        IERC721 nft = IERC721(_nftContract);
        require(nft.ownerOf(_tokenId) == msg.sender, "You do not own this NFT");

        // Transferir la propiedad del NFT al contrato del marketplace
        nft.transferFrom(msg.sender, address(this), _tokenId);

        _listingIdCounter++;
        uint256 newListingId = _listingIdCounter;

        listings[newListingId] = Listing({
            itemId: newListingId,
            nftContract: _nftContract,
            tokenId: _tokenId,
            price: _price,
            seller: msg.sender,
            isSold: false
        });

        emit ItemListed(newListingId, _nftContract, _tokenId, msg.sender, _price);
    }

    function buyItem(uint256 _listingId) public payable {
        Listing storage listing = listings[_listingId];

        require(listing.itemId != 0, "Listing does not exist");
        require(!listing.isSold, "Item already sold");
        require(msg.value == listing.price, "Please submit the asking price");
        require(listing.seller != msg.sender, "You cannot buy your own item");

        // Marcar como vendido
        listing.isSold = true;

        // Transferir el NFT al comprador
        IERC721(listing.nftContract).transferFrom(address(this), msg.sender, listing.tokenId);

        // Enviar el pago al vendedor
        payable(listing.seller).transfer(msg.value);

        emit ItemBought(_listingId, listing.nftContract, listing.tokenId, msg.sender, msg.value);
    }

    function cancelListing(uint256 _listingId) public {
        Listing storage listing = listings[_listingId];

        require(listing.itemId != 0, "Listing does not exist");
        require(!listing.isSold, "Item already sold");
        require(listing.seller == msg.sender, "You are not the seller of this item");

        // Transferir el NFT de vuelta al vendedor
        IERC721(listing.nftContract).transferFrom(address(this), msg.sender, listing.tokenId);

        // Eliminar o invalidar el listado
        delete listings[_listingId]; // O marcar como cancelado

        emit ListingCancelled(_listingId, listing.nftContract, listing.tokenId, msg.sender);
    }

    // Función para recibir NFTs (requerida por IERC721Receiver)
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external pure override returns (bytes4) {
        return IERC777Receiver.onERC721Received.selector;
    }

    // Función para retirar fondos en caso de emergencia o para el propietario
    function withdraw() public onlyOwner {
        payable(owner()).transfer(address(this).balance);
    }
}
⚠️ Advertencia: Este es un contrato simplificado para fines educativos. Un contrato de mercado real necesitaría más validaciones, manejo de errores, comisiones, funcionalidades de pausado, etc., y pasaría por varias auditorías de seguridad.

Tabla de Funciones del Marketplace 📋

FunciónDescripciónPermisosEvento Emitido
listItemPermite a un propietario listar su NFT para la venta. El NFT se transfiere al contrato.Propietario del NFTItemListed
buyItemPermite a un comprador adquirir un NFT listado, pagando el precio especificado.Cualquier usuarioItemBought
cancelListingPermite al vendedor retirar su NFT del listado si no ha sido vendido.Vendedor del NFTListingCancelled
onERC721ReceivedInterfaz requerida para que el contrato pueda recibir NFTs.Llamado por transferFromNinguno
withdrawPermite al propietario del contrato retirar cualquier Ether accidentalmente enviado.onlyOwnerNinguno

3. Almacenando Metadatos en IPFS 🌍

Los metadatos del NFT son cruciales: definen el nombre, la descripción, la imagen y otras propiedades del activo digital. Almacenarlos en IPFS garantiza que sean inmutables y resistentes a la censura.

¿Qué es IPFS? 🤔

IPFS es un protocolo peer-to-peer para almacenar y compartir datos en un sistema de archivos distribuido. A diferencia de HTTP, que localiza contenido por su ubicación (un servidor específico), IPFS lo hace por su contenido (un hash criptográfico). Esto significa que si el contenido está disponible en cualquier nodo de la red IPFS, se puede acceder a él.

Pasos para Subir a IPFS 🚀

  1. Crea un archivo JSON de metadatos: Sigue el estándar de metadatos de OpenSea/ERC-721.
  2. Sube la imagen: Sube el archivo de imagen de tu NFT a IPFS para obtener su ipfs:// URI.
  3. Sube el JSON: Sube el archivo JSON de metadatos (que ya contiene la URI de la imagen) a IPFS para obtener su ipfs:// URI final.

Ejemplo de Archivo JSON de Metadatos (my_nft_metadata.json)

{
  "name": "Atardecer en la Playa Descentralizada",
  "description": "Una obra de arte digital que captura la belleza de un atardecer imaginario, almacenada de forma inmutable en la red IPFS.",
  "image": "ipfs://QmTfQfHwE8Y32d4R5u6gX3n2m1j7pL9cK0oV6zA1b2c3d4/sunset.jpg",
  "attributes": [
    {
      "trait_type": "Atmósfera",
      "value": "Pacífica"
    },
    {
      "trait_type": "Color Dominante",
      "value": "Naranja"
    }
  ]
}
💡 Consejo: Puedes usar servicios como Pinata, Infura IPFS, o ejecutar tu propio nodo IPFS para subir archivos. Pinata ofrece una capa amigable para desarrolladores para interactuar con IPFS.
Cómo usar Pinata para subir archivos a IPFS
  1. Regístrate en Pinata: Crea una cuenta en pinata.cloud.
  2. Crea una API Key: Ve a 'API Keys' y genera una nueva clave.
  3. Sube archivos: Puedes usar la interfaz web o su SDK/API.
    • Subir imagen: Sube tu archivo sunset.jpg. Pinata te devolverá un CID (Content Identifier). Tu URI de imagen será ipfs://<CID>/sunset.jpg.
    • Actualizar JSON: Modifica my_nft_metadata.json con la URI de la imagen obtenida.
    • Subir JSON: Sube el archivo JSON actualizado. Pinata te dará otro CID. Tu URI final para el NFT será ipfs://<CID>/my_nft_metadata.json.

Este URI final es el que pasarás a la función safeMint de tu contrato MyNFT.


4. Despliegue y Pruebas 🧪

Una vez que tenemos los contratos y entendemos cómo manejar los metadatos, es hora de desplegar y probar.

Script de Despliegue (ej. Hardhat) ⚙️

Crea un archivo de despliegue, por ejemplo, scripts/deploy.js:

// scripts/deploy.js
const hre = require("hardhat");

async function main() {
  const [deployer] = await hre.ethers.getSigners();

  console.log("Desplegando contratos con la cuenta:", deployer.address);

  const MyNFT = await hre.ethers.getContractFactory("MyNFT");
  const myNFT = await MyNFT.deploy(deployer.address);

  await myNFT.waitForDeployment();
  console.log("MyNFT desplegado en:", await myNFT.getAddress());

  const NFTMarketplace = await hre.ethers.getContractFactory("NFTMarketplace");
  const nftMarketplace = await NFTMarketplace.deploy(deployer.address);

  await nftMarketplace.waitForDeployment();
  console.log("NFTMarketplace desplegado en:", await nftMarketplace.getAddress());
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Ejecuta el script de despliegue en tu red de desarrollo o testnet:

npx hardhat run scripts/deploy.js --network localhost # o goerli, sepolia, etc.

Interacción con los Contratos (Hardhat Console / Ethers.js) 🌐

Ahora vamos a interactuar paso a paso:

Paso 1: Mintear un NFT 🎨
Primero, el dueño del contrato `MyNFT` mintea un nuevo token, apuntando a nuestros metadatos IPFS.
Paso 2: Aprobar el Marketplace
El propietario del NFT debe aprobar que el contrato del Marketplace pueda transferir su NFT. Esto es crucial para la función `listItem`.
Paso 3: Listar el NFT 🛍️
El propietario del NFT lista el token en el Marketplace con un precio. El NFT se transfiere al contrato del Marketplace.
Paso 4: Comprar el NFT 🤝
Otro usuario (comprador) paga el precio especificado para adquirir el NFT. El Marketplace transfiere el NFT al comprador y el Ether al vendedor.

Ejemplo de Interacción en Hardhat Console

// Asegúrate de haber desplegado los contratos y tener sus direcciones
// npx hardhat console --network localhost

const MyNFT = await ethers.getContractFactory("MyNFT");
const myNFT = await MyNFT.attach("DIRECCION_DE_MYNFT"); // Reemplaza con la dirección real

const NFTMarketplace = await ethers.getContractFactory("NFTMarketplace");
const nftMarketplace = await NFTMarketplace.attach("DIRECCION_DE_MARKETPLACE"); // Reemplaza con la dirección real

const [deployer, buyer] = await ethers.getSigners(); // Obtener diferentes cuentas

// Paso 1: Mintear un NFT (deployer es el owner en este ejemplo)
const metadataURI = "ipfs://bafyreigv62o7qg5m32p45l7v5k2k6c2x6z4q7g1h3j5k6l7m8n9o0p1q2r3s4t5u6v7w8x9y0z/"; // Tu URI de metadatos IPFS
let tx = await myNFT.connect(deployer).safeMint(deployer.address, metadataURI);
await tx.wait();
const tokenId = (await myNFT.tokenOfOwnerByIndex(deployer.address, 0)).toNumber();
console.log(`NFT minteado con ID: ${tokenId} al owner: ${deployer.address}`);

// Paso 2: Aprobar el Marketplace para que pueda mover el NFT
tx = await myNFT.connect(deployer).approve(await nftMarketplace.getAddress(), tokenId);
await tx.wait();
console.log("Marketplace aprobado para mover el NFT.");

// Paso 3: Listar el NFT por el deployer (vendedor)
const price = ethers.parseEther("0.1"); // 0.1 ETH
tx = await nftMarketplace.connect(deployer).listItem(await myNFT.getAddress(), tokenId, price);
await tx.wait();
console.log("NFT listado en el marketplace.");

// Recuperar el listingId del evento
const filter = nftMarketplace.filters.ItemListed(null, await myNFT.getAddress(), tokenId);
const events = await nftMarketplace.queryFilter(filter);
const listingId = events[0].args.listingId;
console.log(`Listing ID del NFT: ${listingId}`);

// Paso 4: Comprar el NFT por el buyer
tx = await nftMarketplace.connect(buyer).buyItem(listingId, { value: price });
await tx.wait();
console.log(`NFT con ID ${tokenId} comprado por ${buyer.address}.`);

// Verificar el nuevo propietario del NFT
const newOwner = await myNFT.ownerOf(tokenId);
console.log(`El nuevo propietario del NFT ${tokenId} es: ${newOwner}`);
// Debería ser buyer.address

// Intentar cancelar (debería fallar si ya está vendido)
// await nftMarketplace.connect(deployer).cancelListing(listingId);
100% Completo

Consideraciones Adicionales y Próximos Pasos 🌟

  • Interfaz de Usuario (Frontend): Para que este mercado sea realmente usable, necesitarías una interfaz de usuario (dApp) construida con React, Vue, Angular o Next.js, utilizando bibliotecas como Ethers.js o Web3.js para interactuar con los contratos.
  • Moneda de Pago: Este ejemplo usa Ether. Podrías modificar el contrato para aceptar tokens ERC-20 como USDC o DAI.
  • Comisiones: Un mercado real aplicaría una comisión por venta, que se distribuiría al propietario del contrato o a una DAO. Esto implicaría una lógica adicional en buyItem.
  • Búsqueda y Filtrado: Para una experiencia de usuario completa, necesitarías indexar los eventos de la blockchain y los metadatos de IPFS en una base de datos centralizada (o una solución descentralizada como The Graph) para permitir búsquedas y filtros eficientes.
  • Seguridad: Auditar los contratos es fundamental antes de desplegarlos en una red principal. Considera patrones de seguridad como Re-entrancy Guard.
¿Qué es The Graph y por qué es útil aquí?

The Graph es un protocolo descentralizado para indexar y consultar datos de blockchain. En lugar de que tu frontend tenga que leer directamente de la blockchain o de IPFS (lo cual puede ser lento o complejo para datos agregados), puedes desplegar un subgraph que escucha eventos de tus contratos (como ItemListed, ItemBought) y los guarda en un formato consultable (GraphQL). Esto permite que tu interfaz de usuario muestre listas de NFTs, historial de ventas, etc., de manera rápida y eficiente.

¡Felicidades! 🎉

Has aprendido los fundamentos para construir un mercado NFT descentralizado, desde los smart contracts hasta el almacenamiento de metadatos en IPFS y la lógica de interacción básica. ¡Este es un gran paso en tu viaje Web3!

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!