DEV Community

Cover image for Botón de "Mint" NFTs en tu página web
Ahmed Castro
Ahmed Castro

Posted on • Updated on

Botón de "Mint" NFTs en tu página web

Los proyectos de NFTs coleccionables que atraen mas compradores cuentan con un sitio web para mintear. En este video lanzamos un smart contract de una colección de NFTs en Rinkeby y luego la interfaz web necesaria con el botón de "Mint".

Dependencias

Para este tutorial ocuparás NodeJs que les recomendará descargarlo el Linux via NVM , un URL de RPC te recomiendo usar INFURA, Metamask con fondos de Rinkeby Testnet que puedes conseguir desde el Faucet.

1. Configuración inicial

mkdir MyNFTCollection
cd MyNFTCollection
npm install --save-dev truffle dotenv @truffle/hdwallet-provider @openzeppelin/contracts
npx truffle init
Enter fullscreen mode Exit fullscreen mode

truffle-config.js

require('dotenv').config()
const HDWalletProvider = require('@truffle/hdwallet-provider');

const fs = require('fs');

module.exports = {
  networks: {
    development: {
      host: "127.0.0.1",
      port: 8545,
      network_id: "*",
    },
    rinkeby: {
      provider: function () {
        return new HDWalletProvider(process.env.PRIVATE_KEY, process.env.RINKEBY_RPC_URL);
      },
      network_id: 4,
      gas: 4000000
    }
  },
  mocha: {
  },
  compilers: {
    solc: {
      version: "0.8.8",
    }
  },
  db: {
    enabled: false
  }
};
Enter fullscreen mode Exit fullscreen mode

2. Lanzamos el contrato

Crea y edita el contrato a tu conveniencia.

contracts/MyNFTCollection.sol

// contracts/GameItem.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.8;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract MyNFTCollection is ERC721Enumerable {
    uint256 public MAX_ELEMENTS = 5;
    uint256 public PRICE = 0.01 ether;
    address public CREATOR = 0x0000000000000000000000000000000000000000;
    uint256 public token_count;
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIdTracker;

    constructor() ERC721("My NFT", "MNFT") {}

    function _baseURI() internal view virtual override returns (string memory) {
        return "MIURL";
    }

    function _totalSupply() internal view returns (uint) {
        return _tokenIdTracker.current();
    }

    function totalMint() public view returns (uint256) {
        return _totalSupply();
    }

    function mint(address _to, uint256 _count) public payable {
        uint256 total = _totalSupply();
        require(total + _count <= MAX_ELEMENTS, "Max limit");
        require(total <= MAX_ELEMENTS, "Sale end");
        require(msg.value >= PRICE*_count, "Value below price");

        for (uint256 i = 0; i < _count; i++) {
            _mintAnElement(_to);
        }
    }

    function _mintAnElement(address _to) private {
        uint id = _totalSupply();
        _tokenIdTracker.increment();
        _safeMint(_to, id);
    }

    function withdrawAll() public {
        (bool success, ) = CREATOR.call{value:address(this).balance}("");
        require(success, "Transfer failed.");
    }
}
Enter fullscreen mode Exit fullscreen mode

migrations/2_my_deploy.js

const MyNFTCollection = artifacts.require("MyNFTCollection")

module.exports = async function (deployer) {
  await deployer.deploy(MyNFTCollection)
}
Enter fullscreen mode Exit fullscreen mode

.env

RINKEBY_RPC_URL=TULLAVERPC
PRIVATE_KEY=TULLAVEPRIVADA
Enter fullscreen mode Exit fullscreen mode
npx truffle migrate --network rinkeby
Enter fullscreen mode Exit fullscreen mode

3. El frontend

Crea la carpeta client/contracts y copypastea dentro build/MyNFTCollection.json. También agrega el html y js necesario y edítalos a tu conveniencia.

client/index.html

<p><span id="web3_message">Loading web3...</span></p>
<select id="mint_amount">
  <option value="1">1</option>
  <option value="2">2</option>
  <option value="3">3</option>
  <option value="4">4</option>
</select>
<input type="button" value="mint!" onclick="mint()">
<p>You have <span id="nft_balance"></span> NFTs</p>

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

client/src/contract_interaction.js

const NETWORK_ID = 4
var contract
var accounts
var web3
var balance
var price

function metamaskReloadCallback()
{
  window.ethereum.on('accountsChanged', (accounts) => {
    document.getElementById("web3_message").textContent="Accounts changed, realoading...";
    window.location.reload()
  })
  window.ethereum.on('networkChanged', (accounts) => {
    document.getElementById("web3_message").textContent="Network changed, realoading...";
    window.location.reload()
  })
}

const getWeb3 = async () => {
  return new Promise((resolve, reject) => {
    if(document.readyState=="complete")
    {
      if (window.ethereum) {
        const web3 = new Web3(window.ethereum)
        metamaskReloadCallback()
        try {
          // ask user permission to access his accounts
          (async function(){
            await window.ethereum.request({ method: "eth_requestAccounts" })
          })()
          resolve(web3)
        } catch (error) {
          reject(error)
        }
      } else {
        reject("must install MetaMask")
        document.getElementById("web3_message").textContent="Error: Please install Metamask";
      }
    }else
    {
      window.addEventListener("load", async () => {
        if (window.ethereum) {
          const web3 = new Web3(window.ethereum)
          metamaskReloadCallback()
          try {
            // ask user permission to access his accounts
            await window.ethereum.request({ method: "eth_requestAccounts" })
            resolve(web3);
          } catch (error) {
            reject(error);
          }
        } else {
          reject("must install MetaMask")
          document.getElementById("web3_message").textContent="Error: Please install Metamask";
        }
      });
    }
  });
};

function handleRevertError(message) {
  alert(message)
}

async function getRevertReason(txHash) {
  const tx = await web3.eth.getTransaction(txHash)
  await web3.eth
    .call(tx, tx.blockNumber)
    .then((result) => {
      throw Error("unlikely to happen")
    })
    .catch((revertReason) => {
      var str = "" + revertReason
      json_reason = JSON.parse(str.substring(str.indexOf("{")))
      handleRevertError(json_reason.message)
    });
}

const getContract = async (web3) => {
  const data = await getJSON("./contracts/MyNFTCollection.json")

  const netId = await web3.eth.net.getId()
  const deployedNetwork = data.networks[netId]
  const contract = new web3.eth.Contract(
    data.abi,
    deployedNetwork && deployedNetwork.address
  )
  return contract
}

function getJSON(url) {
  return new Promise(resolve => {
    var xhr = new XMLHttpRequest()
    xhr.open("GET", url, true)
    xhr.responseType = "json"
    xhr.onload = function () {
      resolve(xhr.response)
    };
    xhr.send()
  });
}

async function loadApp() {
  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);
          var awaitAccounts = async function () {
            accounts = await web3.eth.getAccounts()
            document.getElementById("web3_message").textContent="Connected";
            balance = await contract.methods.balanceOf(accounts[0]).call()
            document.getElementById("nft_balance").textContent=balance;
            price = await contract.methods.PRICE().call()
          };
          awaitAccounts();
        };
        awaitContract();
      } else {
        document.getElementById("web3_message").textContent="Please connect to Rinkeby Testnet";
      }
    });
  };
  awaitWeb3();
}

loadApp()

const mint = async () => {
  let mint_amount = document.getElementById("mint_amount").value
  const result = await contract.methods.mint(accounts[0], mint_amount)
    .send({ from: accounts[0], gas: 0, value: price * mint_amount })
    .on('transactionHash', function(hash){
      document.getElementById("web3_message").textContent="Minting...";
    })
    .on('receipt', function(receipt){
      document.getElementById("web3_message").textContent="Success! Minting finished.";    })
    .catch((revertReason) => {
      getRevertReason(revertReason.receipt.transactionHash);
    });
}
Enter fullscreen mode Exit fullscreen mode

4. Interactuar vía web

npm install -g lite-server
cd client
lite-server
Enter fullscreen mode Exit fullscreen mode

El resultado se encuentra en http://localhost:3000.

A partir de aquí puedes hacer cambios en el contrato y luego en la web para reflejarlos. También considera cambiar la configuración y el frontend para migrar a otra red (mainnet, L2, polygon, avalanche, etc...). Y para lanzar tu interfaz web te recomendaría Netlify, es grátis y sencillo.

También puedes trabajar en base a la template pública en Github.

¡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)