DEV Community

Cover image for Lancé una colección de NFTs
Ahmed Castro
Ahmed Castro

Posted on

Lancé una colección de NFTs

Lancé una colección de 66 NFTs para los que estuvieron desde el inicio formando parte de la comunidad de Filosofía Código. Si te ha gustado el contenido del canal y quieres contribuir de una manera diferente, te invito a que mintees uno en og.filosofiacodigo.com. Están en la red de Polygon y cuestan 4 Matic. Quienes minteen tendran trato especial en los futuros proyectos que cree a través del canal. Gracias!

En este video explico cómo creé este token y el botón de mint con ayuda únicamente de Remix y Javascript. Normalmente uso Truffle o Hardhat pero quize hacerlo diferente esta vez para demostrar que hay varios caminos para hacer realidad nuestros proyectos.

Antes de empezar

Para realizar este tutorial ocupas la billetera Metamask que la puedes conseguir como una extensión de tu navegador. Adicionalmente necesitaras fondos de Rinkeby Testnet que puedes conseguir desde el Faucet. Para hacer el último paso en este video también ocuparás NodeJs que les recomiendo descargarlo el Linux via NVM.

1. Lanzamos el Smart Contract

// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract MyNFT is ERC721, ERC721Enumerable, Ownable {
  enum Color {Black, White, Purple, Cyan, Yellow, Orange}
  uint public SUPPLY;
  uint public MAX_SUPPLY = 66;
  uint public PRICE = 4 ether;
  mapping (uint=>Color) public token_color;
  mapping (Color=>string) public color_uri;

  constructor() ERC721("OG Filosofia Codigo", "OGFC") {}

  function tokenURI(uint256 token_id) public view virtual override returns (string memory) {
    require(_exists(token_id), "ERC721Metadata: URI query for nonexistent token");
    return color_uri[token_color[token_id]];
  }

  function setTokenURIs(string[] memory uris) public onlyOwner
  {
    color_uri[Color.Black]  = uris[0];
    color_uri[Color.White]  = uris[1];
    color_uri[Color.Purple] = uris[2];
    color_uri[Color.Cyan]   = uris[3];
    color_uri[Color.Yellow] = uris[4];
    color_uri[Color.Orange] = uris[5];
  }

  function setTokenColor(uint token_id, Color color) public
  {
    require(msg.sender == ownerOf(token_id), "Must be token owner.");
    token_color[token_id] = color;
  }

  function mint() public payable
  {
    require(msg.value >= PRICE,  "Must pay price.");
    require(SUPPLY < MAX_SUPPLY, "Max supply must not be reached.");
    _mint(msg.sender, SUPPLY);
    SUPPLY  += 1;
  }

  function withdraw() public
  {
    (bool sent, bytes memory data) = address(owner()).call{value: address(this).balance}("");
    require(sent, "Failed to send Ether");
    data;
  }

  function setPrice(uint _price) public onlyOwner
  {
    PRICE = _price;
  }

  function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721Enumerable) returns (bool)
  {
    return super.supportsInterface(interfaceId);
  }

  function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal override(ERC721, ERC721Enumerable)
  {
    super._beforeTokenTransfer(from, to, tokenId);
  }
}
Enter fullscreen mode Exit fullscreen mode

Botón de mint

Colocamos los siguientes 2 archivos en un servidor web público. Adicionalmente colocamos un tercer archivo llamado ContractABI.json que contiene el ABI del contrato generado por Remix.

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Ejemplo</title>
</head>
<body>
  <h1 class="title">
    Token Ejemplo
  </h1>
  <p class="subtitle">
    Para quienes formaron parte <strong>desde el inicio</strong>
  </p>
  <button onclick="mint()" class="button is-primary">mintea!</button><br>
  <p id="web3_message"></p>
  <p id="available_message"></p>
  <br>
  <br>
  <h2 class="subtitle">Mintea tokens y elige tu color favorito</h2>
  <div id="color_selectors" class="columns is-desktop"></div>

  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/web3/1.3.5/web3.min.js"></script>
  <script type="text/javascript" src="blockchain_stuff.js"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

blockchain_stuff.js

const NETWORK_ID = 4
const CONTRACT_ADDRESS = "0xD9BcFd43E6BA76b1468D0a66325C0c06D6DACf33"
const JSON_CONTRACT_ABI_PATH = "./ContractABI.json"
var contract
var accounts
var web3
var balance
var SUPPLY
var MAX_SUPPLY
var nft_ids = []
var token_colors = []
var PRICE

function metamaskReloadCallback()
{
  window.ethereum.on('accountsChanged', (accounts) => {
    document.getElementById("web3_message").textContent="Se cambió el account, refrescando...";
    window.location.reload()
  })
  window.ethereum.on('networkChanged', (accounts) => {
    document.getElementById("web3_message").textContent="Se el network, refrescando...";
    window.location.reload()
  })
}

const getWeb3 = async () => {
  return new Promise((resolve, reject) => {
    if(document.readyState=="complete")
    {
      if (window.ethereum) {
        const web3 = new Web3(window.ethereum)
        window.location.reload()
        resolve(web3)
      } else {
        reject("must install MetaMask")
        document.getElementById("web3_message").textContent="Error: Porfavor conéctate a Metamask";
      }
    }else
    {
      window.addEventListener("load", async () => {
        if (window.ethereum) {
          const web3 = new Web3(window.ethereum)
          resolve(web3)
        } else {
          reject("must install MetaMask")
          document.getElementById("web3_message").textContent="Error: Please install Metamask";
        }
      });
    }
  });
};

const getContract = async (web3) => {
  const response = await fetch(JSON_CONTRACT_ABI_PATH);
  const data = await response.json();

  const netId = await web3.eth.net.getId();
  contract = new web3.eth.Contract(
    data,
    CONTRACT_ADDRESS
    );
  return contract
}

async function loadDapp() {
  metamaskReloadCallback()
  document.getElementById("web3_message").textContent="Por favor conéctate a Metamask"
  var awaitWeb3 = async function () {
    web3 = await getWeb3()
    web3.eth.net.getId((err, netId) => {
      if (netId == NETWORK_ID) {
        var awaitContract = async function () {
          contract = await getContract(web3);
          await window.ethereum.request({ method: "eth_requestAccounts" })
          accounts = await web3.eth.getAccounts()
          balance = await contract.methods.balanceOf(accounts[0]).call()
          MAX_SUPPLY = await contract.methods.MAX_SUPPLY().call()
          SUPPLY = await contract.methods.SUPPLY().call()
          PRICE = await contract.methods.PRICE().call()
          for(i=0; i<balance; i++)
          {
            nft_ids.push(await contract.methods.tokenOfOwnerByIndex(accounts[0],i).call())
          }
          console.log(nft_ids)
          for(i=0; i<nft_ids.length; i++)
          {
            token_color = await contract.methods.token_color(nft_ids[i]).call()
            token_colors.push(token_color)
            addColorSelector(token_color, nft_ids[i])
          }
          console.log(token_colors)
          if(balance == 1)
            document.getElementById("web3_message").textContent="Tienes 1 token"
          else
            document.getElementById("web3_message").textContent="Tienes " + balance + " tokens"
          document.getElementById("available_message").textContent="" + (MAX_SUPPLY-SUPPLY) + "/" + MAX_SUPPLY + " disponibles (Precio: " + web3.utils.fromWei(PRICE) + " MATIC)"
        };
        awaitContract();
      } else {
        document.getElementById("web3_message").textContent="Por favor conectate a Polygon";
      }
    });
  };
  awaitWeb3();
}

function getTokenUrl(token_color)
{
  if(token_color==0)
    return "https://raw.githubusercontent.com/FilosofiaCodigo/OGToken/master/assets/Black.png"
  if(token_color==1)
    return "https://raw.githubusercontent.com/FilosofiaCodigo/OGToken/master/assets/White.png"
  if(token_color==2)
    return "https://raw.githubusercontent.com/FilosofiaCodigo/OGToken/master/assets/Purple.png"
  if(token_color==3)
    return "https://raw.githubusercontent.com/FilosofiaCodigo/OGToken/master/assets/Cyan.png"
  if(token_color==4)
    return "https://raw.githubusercontent.com/FilosofiaCodigo/OGToken/master/assets/Yellow.png"
  if(token_color==5)
    return "https://raw.githubusercontent.com/FilosofiaCodigo/OGToken/master/assets/Orange.png"
}

function addColorSelector(token_color, token_id)
{
  var parent = document.getElementById("color_selectors");
  var column = document.createElement("column");
  column.className = "column is-one-quarter-desktop"
  var card = document.createElement("div");
  card.className = "card"
  column.appendChild(card);
  parent.appendChild(column);

  //Img
  var img = document.createElement("img");
  var img_field_div = document.createElement("div");
  var figure = document.createElement("figure");
  img_field_div.className = "card-image"
  figure.className = "image is-150by150"
  img.src = getTokenUrl(token_color);
  img.width = "150"
  img_field_div.appendChild(figure);
  figure.appendChild(img);
  card.appendChild(img_field_div);

  //Card content
  var card_content = document.createElement("div");
  card_content.className = "card-content"
  card.appendChild(card_content);

  //Title
  var title = document.createElement("p");
  title.className = "title"
  title.innerHTML = "OG Filosofía Código #" + token_id
  card_content.appendChild(title);

  //Select
  var array = ["Black", "White", "Purple", "Cyan", "Yellow", "Orange"];
  var field = document.createElement("div");
  field.className = "field"
  var label = document.createElement("label");
  label.className = "label"
  label.innerHTML = "Color"
  var control = document.createElement("div");
  control.className = "control"
  var select_div = document.createElement("div");
  select_div.className = "select"
  var select_list = document.createElement("select");
  select_div.appendChild(select_list);  
  select_list.id = "color_select_" + token_id;
  field.appendChild(label); 
  field.appendChild(control); 
  control.appendChild(select_div); 
  card_content.appendChild(field); 
  for (var i = 0; i < array.length; i++) {
      var option = document.createElement("option");
      option.value = ""+i;
      option.text = array[i];
      select_list.appendChild(option);
  }

  //Button
  var btn_field_div = document.createElement("footer");
  btn_field_div.className = "card-footer"
  var btn = document.createElement("a");
  btn.innerHTML = "Cambia el color";
  btn.className = "card-footer-item"
  btn.onclick = function () {
    var color_select_element = document.getElementById("color_select_" + token_id);
    var selected_color = color_select_element.options[color_select_element.selectedIndex].value;
    setColor(token_id, selected_color)
  };
  btn_field_div.appendChild(btn); 
  card.appendChild(btn_field_div);
}

const mint = async () => {
  const result = await contract.methods.mint()
    .send({ from: accounts[0], gas: 0, value: PRICE })
    .on('transactionHash', function(hash){
      document.getElementById("web3_message").textContent="Minteando...";
    })
    .on('receipt', function(receipt){
      document.getElementById("web3_message").textContent="Éxito! El minteo se ha completado.";    })
    .catch((revertReason) => {
      console.log("ERROR! Transaction reverted: " + revertReason.receipt.transactionHash)
    });
}

const setColor = async (token_id, color) => {
  const result = await contract.methods.setTokenColor(token_id, color)
    .send({ from: accounts[0], gas: 0, value: 0 })
    .on('transactionHash', function(hash){
      document.getElementById("web3_message").textContent="Minteando...";
    })
    .on('receipt', function(receipt){
      document.getElementById("web3_message").textContent="Éxito! Has cambiado el color.";    })
    .catch((revertReason) => {
      console.log("ERROR! Transaction reverted: " + revertReason.receipt.transactionHash)
    });
}

/*
await setTokenURIs(
  ["https://raw.githubusercontent.com/FilosofiaCodigo/OGToken/master/assets/Black.json",
   "https://raw.githubusercontent.com/FilosofiaCodigo/OGToken/master/assets/White.json",
   "https://raw.githubusercontent.com/FilosofiaCodigo/OGToken/master/assets/Purple.json",
   "https://raw.githubusercontent.com/FilosofiaCodigo/OGToken/master/assets/Cyan.json",
   "https://raw.githubusercontent.com/FilosofiaCodigo/OGToken/master/assets/Yellow.json",
   "https://raw.githubusercontent.com/FilosofiaCodigo/OGToken/master/assets/Orange.json"])
*/
const setTokenURIs = async (uris) => {
  const result = await contract.methods.setTokenURIs(uris)
    .send({ from: accounts[0], gas: 0, value: 0 })
    .on('transactionHash', function(hash){
      document.getElementById("web3_message").textContent="Estableciendo los URIs...";
    })
    .on('receipt', function(receipt){
      document.getElementById("web3_message").textContent="Éxito! Has cambiado los URIs.";    })
    .catch((revertReason) => {
      console.log("ERROR! Transaction reverted: " + revertReason.receipt.transactionHash)
    });
}

loadDapp()
Enter fullscreen mode Exit fullscreen mode

3. Probarlo de manera local

Primero instala lite-server npm i -g lite-server. Y luego escribe lite-server en la terminal para iniciar el servidor en http://localhost:3000.

¡Gracias por ver este tutorial!

Sígueme en dev.to y en Youtube para todo lo relacionado al desarrollo en Blockchain en Español.

Discussion (0)