Collectibles NFTs proyects that attract more buyers have a website for mint. In this video we launch a NFT collection smart contract on Goerli and then the required UI containing the mint button.
https://www.youtube.com/watch?v=6m17YQWHq-M
Dependencies
For this tutorial you will need NodeJs, I recommend downloading it from Linux via NVM, a RPC URL, I recommend using INFURA or Alchemy, Metamask with Goerli Testnet funds that you can get from the Faucet.
1. Initial configuration
mkdir MyNFT
cd MyNFT
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: "*",
},
goerli: {
provider: function () {
return new HDWalletProvider(process.env.PRIVATE_KEY, process.env.GOERLI_RPC_URL);
},
network_id: 5,
gas: 4000000,
networkCheckTimeout: 10000
}
},
mocha: {
},
compilers: {
solc: {
version: "0.8.16",
}
},
db: {
enabled: false
}
};
2. Contract launch
Create and edit the contract at your convenience.
contracts/MyNFT.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
contract MyNFT 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/1_my_deploy.js
const MyNFT = artifacts.require("MyNFT")
module.exports = async function (deployer) {
await deployer.deploy(MyNFT)
}
.env
GOERLI_RPC_URL=YOURRPCKEY
PRIVATE_KEY=YOURPRIVATEKEY
npx truffle migrate --network goerli
3. Frontend
Create the client/contracts
directory and copypaste build/MyNFT.json
on it. Also, add the necessary html and js and edit them at your convenience.
client/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<input id="connect_button" type="button" value="Connect" onclick="connectWallet()" style="display: none"></input>
<p id="account_address" style="display: none"></p>
<p id="web3_message"></p>
<p id="contract_state"></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 id="nft_price"></p>
<p id="nft_balance"></p>
<br>
<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>
<script>
function _mint()
{
mint_amount = document.getElementById("mint_amount").value
mint(mint_amount)
}
</script>
client/src/contract_interaction.js
const NETWORK_ID = 5
const MY_NFT_CONTRACT_ADDRESS = "0xc7c463b90B393b4A0d3650Aa70a08211a6D5fB79"
const MY_NFT_CONTRACT_ABI_PATH = "./MyNFTABI.json"
var my_nft_contract
var price
var accounts
var web3
function metamaskReloadCallback() {
window.ethereum.on('accountsChanged', (accounts) => {
document.getElementById("web3_message").textContent="Network changed, refreshing...";
window.location.reload()
})
window.ethereum.on('networkChanged', (accounts) => {
document.getElementById("web3_message").textContent="Network chainged, refreshing...";
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: Connect to 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, address, abi_path) => {
const response = await fetch(abi_path);
const data = await response.json();
const netId = await web3.eth.net.getId();
contract = new web3.eth.Contract(
data,
address
);
return contract
}
async function loadDapp() {
metamaskReloadCallback()
document.getElementById("web3_message").textContent="Please connect to Metamask"
var awaitWeb3 = async function () {
web3 = await getWeb3()
web3.eth.net.getId((err, netId) => {
if (netId == NETWORK_ID) {
var awaitContract = async function () {
my_nft_contract = await getContract(web3, MY_NFT_CONTRACT_ADDRESS, MY_NFT_CONTRACT_ABI_PATH)
document.getElementById("web3_message").textContent="You are connected to Metamask"
onContractInitCallback()
web3.eth.getAccounts(function(err, _accounts){
accounts = _accounts
if (err != null)
{
console.error("An error occurred: "+err)
} else if (accounts.length > 0)
{
onWalletConnectedCallback()
document.getElementById("account_address").style.display = "block"
} else
{
document.getElementById("connect_button").style.display = "block"
}
});
};
awaitContract();
} else {
document.getElementById("web3_message").textContent="Please connect to Goerli";
}
});
};
awaitWeb3();
}
async function connectWallet() {
await window.ethereum.request({ method: "eth_requestAccounts" })
accounts = await web3.eth.getAccounts()
onWalletConnectedCallback()
}
loadDapp()
const onContractInitCallback = async () => {
price = await my_nft_contract.methods.PRICE().call()
document.getElementById("nft_price").textContent = "NFT Price: " + web3.utils.fromWei(price) + " ETH";
}
const onWalletConnectedCallback = async () => {
balance = await my_nft_contract.methods.balanceOf(accounts[0]).call()
document.getElementById("nft_balance").textContent= "Your balance: " + balance;
}
//// Functions ////
const mint = async (amount) => {
const result = await my_nft_contract.methods.mint(accounts[0], amount)
.send({ from: accounts[0], gas: 0, value: price * amount })
.on('transactionHash', function(hash){
document.getElementById("web3_message").textContent="Executing...";
})
.on('receipt', function(receipt){
document.getElementById("web3_message").textContent="Success."; })
.catch((revertReason) => {
console.log("ERROR! Transaction reverted: " + revertReason.receipt.transactionHash)
});
}
4. Interact via web
npm install -g lite-server
cd client
lite-server
The result is in http://localhost:3000.
Now you can do changes to the contract and then adapt the web UI. I recommend Netlify as a deploying plataform, is free and simple.
Feel free to use the [Github public template]https://github.com/FilosofiaCodigo/NFTCollectionTemplate) as a reference.
Thanks for watching this tutorial!