In the context of the Mutt@42 Art Challenge we decided to extend and use the challenge to learn about and create our own NFTs. Below you can find a Step by Step guide on how to mint your Mutt@42 NFT on the test environment of OpenSea (NFT Marketplace). We are looking into minting NFTs and offering them on a libertarian marketplace that does not charge service fees. We will update this post when we are ready.
Also Evangelos is planning to offer a How to session - we will share the community meeting.
NOTE: all NFTs minted in the Mutt@42 Challenge should be named “Mutt@42 your title”
The bigger picture here is:
1 -- We're going to write a smart contract. That contract complies with the “ERC721” standard and has all the logic around our NFTs.
2 -- Our smart contract will be deployed to the Testnet named Rinkeby of Ethereum blockchain. This way, anyone in the world will be able to access and buy our NFT.
We're going to be using a tool called Hardhat which let us quickly compile smart contracts and test them locally. Is built on top of Node.js. follow this guide (Linux, Mac, Windows) for installing Node:
Ethereum development environment for professionals by Nomic Labs
Then, we can install hardhat with npm (Node.js Package Manager):
mkdir <nameOfFolder>
cd <nameOfFolder>
npm init --yes
npm install --save-dev hardhat
Then we need ‘contracts’ library from OpenZeppelin, ether.js (JavaScript library to interact with Ethereum) and Waffle (simple smart contract testing library built on top of ether.js)
npm install --save-dev @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers
npm install @openzeppelin/contracts
Run npx (Node.js package execute) to initialize a hardhat project:
npx hardhat
// Choose "Create a basic sample project"
// yes/ enter to all prompts
Go ahead and delete the file ’sample-test.js’ under test. Also, delete ’sample-script.js’ under scripts. Then, delete ’Greeter.sol’ under contracts. Don't delete the actual folders!
Open VSCode and install the Solidity Language extension:
touch .sol under contracts and run.js under scripts Check the Folder/file structure.
.sol is the actual smart contract, and what really makes an NFT ‘NFT’ is the compliance with the ERC721 standards (want more info? Check this link from OpenZeppelin). https://docs.openzeppelin.com/contracts/2.x/api/token/erc721
//SPDX Licence-Identifier: MIT
pragma solidity ^0.8.0;
import "hardhat/console.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
//We inherit the imported Contracts.
contract mutt42 is ERC721URIStorage
{
//Counters imported for keeping track of TokenIDs.
//_tokenIds is 'state variable', change is stored on the contract directly.
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
//Set Name and symbol of our NFT
constructor() ERC721 ("R.Mutt@Desk0", "Mutt@42Desk0"){
console.log("Constructor called on mutt42.sol");
}
//Function used by the caller to get the NFT
// Gets the current tokenID (starts at 0 in line 15)
function buildNFT() public{
uint256 newId = _tokenIds.current();
//Mint the NFT to the Sender using msg.sender
// msg.sender is the public address of the person calling the contract
_safeMint(msg.sender, newId);
//Set the NFT data.
_setTokenURI(newId, "<https://ipfs.io/ipfs/><json_CID_here>");
//Confirm NFT is minted
console.log("NFT with id: %s Has been minted to %s", newId, msg.sender);
//Finally increment the Counter
_tokenIds.increment();
}
}
Line 1 indicates the Licence: MIT in this case. Check the link below for various versions:
pragma keyword enables compiler features or checks and prevents compatibility issues with feature versions of Solidity. ‘ ^ ’ means “this version or any newer”. Use pragma without ‘ ^ ’ to use specific version only.
Check the hardhat.config.js file! Version of language must be the same!
Also useful to note in this file:
url: is the link to Alchemy and in this case is for Rinkeby TestNet. Why use Alchemy? As we are not ‘node’ (miners/validators) to broadcast the smart contract to Ethereum Blockchain we need to use an external service. More info:
https://ethereum.org/en/developers/docs/ethereum-stack/#ethereum-nodes
https://docs.alchemy.com/alchemy/introduction/getting-started
Make a new Project, change from Mainnet to Rinkeby and copy your key:
public function buildNFT() can have any name you want, check run.js, name of calling function must be the same.
We take the newId (with .current) and we call _safemint() with the public address of the wallet of the caller and the id. ) _safemint() reverts if the tokenId is already used.
_setTokenURI holds the Metadata of the NFT with the .json file containing the name, description and address of the image.
First we need to save our image. This can be done ‘on-chain’, meaning that we can save the image on Blockchain, but gas fees will dramatically increase. Another solution is to save our image for the NFT on the IPFS peer-to-peer hypermedia protocol. Again similarly to Nodes and Alchemy we can use a provider to store in IPFS. We tried with NFT.Storage which provides free account. Firstly, you must upload your image and get the Content ID.
Then you create your .json file which contains this CID:
{
"name": "Mutt@42 <**your title>**",
"description": "Mutt@42",
"image": "<https://ipfs.io/ipfs/bafkreiag5zvjutor6oen7uruehkhotcxsmrrn3wofufauk2g2zd3oi7qxe>"
}
Remember that the name, description and address (the Metadata of NFT) will be pulled by the Marketplace.
Then you upload the .json file to IPFS with NFT.Storage (or any other service you prefer). My ‘Files’ page looks like this:
then the CID of the json will be imported to _setTokenURI in the form of “https://ipfs.io/ipfs/<CID_here>”);
Overall : address of the image in the .json and address of the .json in the _setTokenURI function.
Lastly (ignoring the printing), we increment the TokenId.
Lets see now the run.js:
const nftContractFactory = await hre.ethers.getContractFactory('mutt42');
Check that the calling contract is the one indicated in .sol
contract mutt42 is ERC721URIStorage
await nftContract.deployed();
We'll wait until our contract is officially mined and deployed to the blockchain! Our constructor runs when we actually are fully deployed!
console.log("Contract deployed to:", nftContract.address);
Finally, once it's deployed nftContract.address
will basically give us the address of the deployed contract. This address is how can actually find our contract on the blockchain. Also this address is different from msg.sender, which is the address of the person calling our contract
After the deployment of the Contract we will mint the NFT. Make sure you have the correct function name [ buildNFT() in this case ]:
let txn = await nftContract.buildNFT();
const main = async () => {
const nftContractFactory = await hre.ethers.getContractFactory('mutt42');
const nftContract = await nftContractFactory.deploy();
await nftContract.deployed();
console.log("Contract deployed to:", nftContract.address);
let txn = await nftContract.buildNFT();
// Wait for it to be mined.
await txn.wait()
console.log("NFT minted successfully.");
};
const runMain = async () => {
try {
await main();
process.exit(0);
} catch (error) {
console.log(error);
process.exit(1);
}
};
runMain();
First, we need to connect our Wallet with OpenSea Market:
Then we need Ether to pay for the deployment of the contract (deployment is actually a transaction we ask miners to do). Thankfully, we can use a testnet to learn and experiment. Here we used Rinkeby. You can ask for fake ETH on (https://faucet.rinkeby.io/)
npx hardhat run scripts/run.js --network rinkeby
Note the “Contract deployed to ” output. This is the address that holds the contract, and you can check this transaction on etherscan