Lancé una colección de NFTs
Tags: blockchain, solidity, nfts, web3
Published: 2021-12-18 15:19:25

---
title: Lancé una colección de NFTs
published: true
description: ¡Cambian de color!
tags: blockchain, solidity, nfts, web3
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/09jfsa436tjxxhbv6057.png
--- 

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](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](https://metamask.io/) que la puedes conseguir como una extensión de tu navegador. Adicionalmente necesitaras fondos de Rinkeby Testnet que puedes conseguir desde el [Faucet](https://faucet.rinkeby.io/). Para hacer el último paso en este video también ocuparás [NodeJs](https://nodejs.org/es/) que les recomiendo descargarlo el Linux via [NVM](https://github.com/nvm-sh/nvm#installing-and-updating).

## 1. Lanzamos el Smart Contract

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

## 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`

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

`blockchain_stuff.js`

```javascript
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()
```

## 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](http://localhost:3000).

**¡Gracias por ver este tutorial!**

Sígueme en dev.to y en [Youtube](https://www.youtube.com/channel/UCNRB4tgwp09z4391JRjEsRA) para todo lo relacionado al desarrollo en Blockchain en Español.