Let’s experiment with NFTs

Deepanshi Sharma
Coinmonks
8 min readJun 5, 2023

--

Imagine you buy a chocolate bar from the market and get a unique token proving your ownership over the chocolate. Wouldn’t it be great? Nobody can steal your part of joy now. Similarly, NFTs are those chocolate bars with ownership rights.

NFT stands for Non-Fungible Tokens. Non-Fungible stands for the uniqueness of these digital assets. Physical currency or even cryptocurrency is fungible but NFTs are not. They can neither be interchanged nor replaced.

Anybody can create NFTs, because it requires very minimal or zero programming skills, and they can be made out of any artwork, digital art, photos or even videos.

The first 5000 days NFT-sold for $69.3M

NFTs are programmed in a very similar way to cryptocurrencies, like Bitcoin. They are tokenized via blockchain and assigned unique identification codes and some metadata which distinguishes them from others. The key difference between cryptocurrencies and NFTs is that two cryptocurrencies from an identical blockchain can be exchanged with each other but two NFTs can’t be, they are not mutually exchangeable.

How did NFTs come into existence?

In 2014, A guy’s wife made a video clip, he registered the video on a blockchain(Namecoin) and sold it to Dash for $4. They called it Monetized Graphics at that time.

Quantum-World’s First NFT By Kevin McCoy

In October 2015, the first NFT Project, Etheria was launched at Ethereum’s first developer conference, just 3 months after the launch of Ethereum. Soon ERC-721 was also proposed in 2017.

Later on, in early 2020, NFT Market saw rapid growth and wider usage.

How do NFTs work?

NFTs are individual tokens with valuable information in them, they reside on any blockchain. ( A Blockchain is nothing but a public ledger that holds information about transactions.) Their unique data makes it easier to verify, validate or transfer ownership. When anyone purchases an NFT, they buy a piece of data that points to a server that hosts that image or digital asset.

NFTs are created by a process called minting, which basically involves the creation of a block, validating the information, recording it on blockchain and closing the block.

Meme by me ( I can steal memes even if I can’t steal NFTs)

As tokens are minted, they are assigned a unique identifier address that is linked to exactly one blockchain address. Each token has an owner and a unique identifier through which it can be distinguished from others.

Why should you mint an NFT?

I hope this clears all the doubts.

What is the easiest way to make your own NFT(for free)?

I would suggest exploring various marketplaces such as OpenSea, Rarible, NFTically and many more.

For this blog, we will be using Open Sea. Hop on to OpenSea and connect your Metamask wallet( If you don’t have one, just create one, very easy piecey).

Go to create, upload your file (it can be anything image, drawing, audio, video or even 3d model), name it and add a description. For blockchain choose Polygon (if you want to create it for free), and once you have completed all these steps, click on Create.

Voila! you created your first NFT.

How to mint multiple NFTs on a Testnet?

In this blog, we will be using Polgon Mumbai Testnet, IPFS and NFT.Storage API.

Polygon is a layer-2 blockchain, built on Ethereum. Hence, it helps in scalability and enables access to such a big market of Ethereum. NFT.Storage provides cost-effective storage solutions and easy integration. IPFS(Interplanetary File System) ensures that files are immutable and verifiable as it identifies files using unique cryptographic hashes.

Step 1: Setting up Pre-requisites

Download and Install Node.js.

Download and Install Metamask.

Receive Matic Tokens using Faucet.

Copy your Private Key from your Metamask wallet.

Install an IDE like Visual Studio Code with Solidity Extension enabled.

Step 2: Getting an API Key from NFT.Storage

Hop on to NFT.Storage and login with your e-mail id. It will send a magic link to login and use it.

NFT.Storage Interface

After that go on to API-Keys on the navbar and create one for yourself.

Step 3: Setting up Workspace

Installing Node.js Dependencies:

npm install hardhat @openzeppelin/contracts nft.storage dotenv @nomiclabs/hardhat-ethers

Initialising Hardhat:

npx hardhat

When it will prompt, choose Create an Empty hardhat.config.js

Now create a file .env and store your API-Key(from NFT-Storage) and Private Key(from Wallet) in the following format:

PRIVATE_KEY=”Your private key”. 
NFT_STORAGE_API_KEY="Your api key"

Step 4: Modify the Hardhat-config file to support Mumbai Testnet

Import Modules for Hardhat integration with Ether.js Library and dotenv to retrieve data from the .env file(where we can store our API key and our private key).

Retrieve the private key from the environment using const{PRIVATE_KEY}=process.end;

Then in exports, we are specifying our network and its configurations, i.e. Configuring it to support Polygon Mumbai Testnet. Then some info for the solidity compiler.

/**
* @type import('hardhat/config').HardhatUserConfig
*/
require("@nomiclabs/hardhat-ethers");
require('dotenv').config();
const { PRIVATE_KEY } = process.env;
module.exports = {
defaultNetwork: "PolygonMumbai",
networks: {
hardhat: {
},
PolygonMumbai: {
url: "https://rpc-mumbai.maticvigil.com",
accounts: [PRIVATE_KEY]
}
},
solidity: {
version: "0.8.12",
settings: {
optimizer: {
enabled: true,
runs: 200
}
}
},
}

Step 5: Create Folders using mkdir command for keeping track of all files

Try to create them using mkdir only, not manually. You can dodge a few errors just because of this change.

mkdir contracts assets scripts

Download the assets from here.

File Structure at the end of the project

Step 6: Create a script: store-content.mjs in Scripts Directory.

Import Required Modules: NFT-Storage, File and dotenv

Retrieve the value of API-KEY from .env.

Create an async function named store-asset, and inside it, create a new client to store NFT and its metadata.

Read the images using File and parse through our CSV files, and store them as attributes on IPFS.

dotenv.config()
import { NFTStorage, File } from "nft.storage"
import fs from 'fs'
import dotenv from 'dotenv'
import { parse } from 'csv-parse';
const API_KEY = process.env.NFT_STORAGE_API_KEY
const POKEMON_CSV_PATH = 'assets/pokemon_metadata.csv'
const POKEMON_PHOTO_PATH = 'assets/original_150_pokemon_photos/'

// Process CSV file
var parser = await parse({columns: true}, async function (err, records) {
for (let index = 0; index < 50; index++) {
var element = records[index]
// Each row of the CSV represents a single Pokemon extract the
// name, description, type, attack, defense, speed, and number.
var name = element['Pokemon']
var description = element['Description']
var type = element['Type 1']
var hp = element['HP']
var attack = element['Attack']
var defense = element['Defense']
var speed = element['Speed']
var number = element['Number']
var picture = `${POKEMON_PHOTO_PATH}${number}.PNG`

var attributes = createAttributes(type, hp, attack, defense, speed)
// store the metadata for this Pokemon
await storeAsset(name, description, attributes, picture)
}
});

fs.createReadStream(POKEMON_CSV_PATH).pipe(parser);

function createAttributes(type, hp, attack, defense, speed){
let type_attr = JSON.parse(`{ "trait_type": "Type", "value": "${type}" }`)
let hp_attr = JSON.parse(`{ "trait_type": "HP", "value": ${hp} }`)
let attack_attr = JSON.parse(`{ "trait_type": "Attack", "value": ${attack} }`)
let defense_attr = JSON.parse(`{ "trait_type": "Defense", "value": ${defense} }`)
let speed_attr = JSON.parse(`{ "trait_type": "Speed", "value": ${speed} }`)
return [type_attr, hp_attr, attack_attr, defense_attr, speed_attr]
}

// Store metadata for one Pokemon on IPFS
async function storeAsset(name, description, attributes, picture_path) {
const client = new NFTStorage({ token: API_KEY })
const metadata = await client.store({
name: `BlockchainBob ${name}`,
description: description,
attributes: attributes,
image: new File(
[await fs.promises.readFile(picture_path)],
`${name}Photo.png`,
{ type: 'image/png' }
),
})
console.log(`${name}: ${metadata.url}`)
}

When we run it, we would be able to see its URL on the console. Run it using:

Node scripts/store-content.mjs

Step 7: Create a Smart Contract, nft.sol in Contracts Directory

Import various contracts from open zeppelin library:

ERC-721: for basic implementation of ERC-721 Standard.

Counters: for managing counters, generate unique token ids

Ownable: Allows certain functions to be accessible only to the contract owner.

Create a constructor which gets called when the contract is deployed.The constructor initializes the contract by calling the constructor if ERC-721 contract and passing in the desired name and symbol for NFT. The Function is for the contract owner to mint new NFTs and assign them to recipients and associating metadata URIs with each token.

// Contract based on https://docs.openzeppelin.com/contracts/4.x/erc721
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract PokemonNFT is ERC721URIStorage, Ownable {
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;

constructor() ERC721("PokemonNFT", "PokeNFT") {}

function mintNFT(address recipient, string memory tokenURI)
public onlyOwner
returns (uint256)
{
_tokenIds.increment();

uint256 newItemId = _tokenIds.current();
_mint(recipient, newItemId);
_setTokenURI(newItemId, tokenURI);

return newItemId;
}
}

Step 8: Deploy Smart Contract on Polygon

Create a script deploy.js in the Scripts Directory

async function deployContract() {
const PokemonNFT = await ethers.getContractFactory("PokemonNFT")
const exampleNFT = await PokemonNFT.deploy()
await exampleNFT.deployed()
const txHash = exampleNFT.deployTransaction.hash
const txReceipt = await ethers.provider.waitForTransaction(txHash)
const contractAddress = txReceipt.contractAddress
console.log("Contract deployed to address:", contractAddress)
}

deployContract()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});

Step 9: Minting NFTs on Polygon

Create a script Mint-NFT.mjs in the Scripts Directory

import { parse } from 'csv-parse'
const CONTRACT_ADDRESS = "xxxxxxxxxxxxx"
import fs from 'fs'
const METADATA_URLS_PATH = 'assets/metadata_urls.csv'

// Process CSV file
var parser = parse({columns: true}, async function (err, records) {
console.log(records.length)
for (let index = 0; index < records.length; index++) {
var record = records[index]
console.log(`Minting:${record['POKEMON']}`)
await mintNFT(CONTRACT_ADDRESS, record['IPFS_URL'])
}
})

fs.createReadStream(METADATA_URLS_PATH).pipe(parser)

async function mintNFT(contractAddress, metaDataURL) {
const ExampleNFT = await ethers.getContractFactory("PokemonNFT")
const [owner] = await ethers.getSigners()
await ExampleNFT.attach(contractAddress).mintNFT(owner.address, metaDataURL)
console.log("NFT minted to: ", owner.address)
}

Run the script using the following Command:

npx hardhat run scripts/mint-nft.mjs --network PolygonMumbai

Step 10: Verify your NFTs

Go to Opensea Testnets and connect your wallet. Bingo! You will find all your NFTs there.

If it was this easy,

Why did I write this blog?

Because I never got all the information when I was learning about the same. No blogs, and no videos clearly explaining this. I realised the need for an informative blog so that it’s easier for other people to learn. Credits to Chirag too, for figuring this out with me.

Thank You!

Meanwhile, people who actually read the blog.

--

--