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
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
}
};
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.");
}
}
migrations/2_my_deploy.js
const MyNFTCollection = artifacts.require("MyNFTCollection")
module.exports = async function (deployer) {
await deployer.deploy(MyNFTCollection)
}
.env
RINKEBY_RPC_URL=TULLAVERPC
PRIVATE_KEY=TULLAVEPRIVADA
npx truffle migrate --network rinkeby
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>
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);
});
}
4. Interactuar vía web
npm install -g lite-server
cd client
lite-server
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.
Top comments (0)