If you are a developer interested in blockchain development, you should know something about NFTs or non-fungible tokens. In this article, we'll take a look at the engineering behind them so you can start building your own.
At the end of the project, you will have your own Ethereum wallet containing a new NFT. This tutorial is suitable for beginners and does not require any prior knowledge of the Ethereum network or smart contracts.
What are NFTs?
NFT stands for non-fungible token. This quote from ethereum.org explains it well :
NFTs are tokens that we can use to represent ownership of unique items. They let us tokenize things like art, collectibles, and even real estate. They can only have one official owner at a time and are protected by the Ethereum blockchain - no one can modify ownership records or copy/paste new NFTs.
What is the NFT standard or ERC-721?
ERC-721 is the most common NFT standard. If your smart contract implements certain standardized API methods, it can be called an ERC-721 non-fungible token contract.
These methods are specified in EIP-721 . Open source projects like OpenZeppelin simplify the development process by implementing the most common ERC standards as reusable libraries.
What is a minted NFT?
By minting an NFT, you issue a unique token on the blockchain. This token is an instance of your smart contract.
Each token has a unique tokenURI that contains your asset's metadata in a JSON file that conforms to a specific pattern. Metadata is where you store information about your NFT, such as name, image, description, and other attributes.
An example JSON file for "ERC721 Metadata Schema" looks like this:
{
"attributes": [
{
"trait_type": "Shape",
"value": "Circle"
},
{
"trait_type": "Mood",
"value": "Sad"
}
],
"description": "A sad circle.",
"image": "https://i.https://img.chengxuka.comur.com/Qkw9N0A.jpeg",
"name": "Sad Circle"
}
How to store metadata of NFT?
There are three main ways to store NFT metadata.
First, you can store information on-chain. In other words, you could extend your ERC-721 and store the metadata on the blockchain, but that could be expensive.
The second way is to use IPFS .
A third approach is to have your API return a JSON file.
The first and second methods are usually preferred because you cannot adjust the underlying JSON file. For the scope of this project, we will choose the third approach.
For an excellent tutorial on using NFTs in IPFS, read this post from the Alchemy team .
what will we build
In this article, we will create and mint our own NFT. It is beginner-friendly and does not require any prior knowledge of the Ethereum network or smart contracts. Still, a good understanding of these concepts will help you understand what's going on behind the scenes.
In what follows, we'll build a fully functional React web application where you can display and sell your NFTs.
This project is intentionally written in easy-to-understand code and is not intended for production use.
prerequisites
MetaMask
We need an Ethereum address to interact with our smart contract. We will use Metamask as our wallet. This is a free virtual wallet that manages your Ethereum addresses. We will need it to send and receive transactions ( read more here ). For example, minting an NFT is a transaction.
Download the Chrome plugin and mobile app. We need both because the Chrome extension won't reveal your NFT.
For ease of development, change the network to "Ropsten Test Network". You will need some Eth to pay for deploying and minting the NFT. Go to the Ropsten Ethereum faucet and enter your address. You should see some test Eth in your Metamask account shortly.
Alchemy
To interact with the Ethereum network, you need to connect to an Ethereum node.
Running your own node and maintaining the infrastructure is a project in itself. Fortunately, there are Node-as-a-Service providers who host the infrastructure for you. There are many options such as Infura, BlockDaemon, and Moralis. We will use Alchemy as our node provider.
Head over to their website, create an account, choose Ethereum as your network and create your application. Select Ropsten as your network.
On your panel, click "view details" on the app, then click "view key". Save your http key somewhere as we'll need it later.
NodeJS/NPM
We will use NodeJS in our project. If you don't have it installed, follow this simple tutorial from freeCodeCamp .
Initialize the project
In your terminal, run the following command to create a new directory for your project:
mkdir nft-project
cd nft-project
Now, let's create another directory under nft-project/
the directory ethereum/
and initialize it with Hardhat . Hardhat is a development tool that allows you to easily deploy and test your Ethereum software.
mkdir ethereum
cd ethereum
npm init
Then, run these commands to create a Hardhat project:
npm install --save-dev hardhat
npx hardhat
You will see this prompt:
888 888 888 888 888
888 888 888 888 888
888 888 888 888 888
8888888888 8888b. 888d888 .d88888 88888b. 8888b. 888888
888 888 "88b 888P" d88" 888 888 "88b "88b 888
888 888 .d888888 888 888 888 888 888 .d888888 888
888 888 888 888 888 Y88b 888 888 888 888 888 Y88b.
888 888 "Y888888 888 "Y88888 888 888 "Y888888 "Y888
Welcome to Hardhat v2.0.8
? What do you want to do? …
Create a sample project
❯ Create an empty hardhat.config.js
Quit
Choose to create an empty hardhat.config.js. This will generate an empty hardhat.config.js
file, which we will update later.
For the web application, we'll use Next.js to initialize a fully functional web application. Go back to the root directory nft-project/
, and initialize a directory called web, and the Next.js application:
cd ..
mkdir web
cd web
npx create-next-app@latest
Your project now looks like this:
nft-project/
ethereum/
web/
We're ready to dive into some real coding.
How to define our .env variables
Remember the Alchemy key we got from the test project earlier? We'll use this to interact with the blockchain along with our Metamask account's public and private keys.
Run the following commands to create a file named in your ethereum/
directory .env
and install dotenv . We'll use them later.
cd ..
cd ethereum
touch .env
npm install dotenv --save
For your .env
file, enter the key you exported from Alchemy, then follow these instructions to get the private key for Metamask.
Here is your .env file:
DEV_API_URL = YOUR_ALCHEMY_KEY
PRIVATE_KEY = YOUR_METAMASK_PRIVATE_KEY
PUBLIC_KEY = YOUR_METAMASK_ADDRESS
Smart contract for NFT
Go to that ethereum/
folder and create two more directories: contracts and scripts. A simple hardhat project contains these folders.
contracts/
Contains the source file of the contractscripts/
Contains scripts to deploy and mint our NFT
mkdir contracts
mkdir scripts
Then, install OpenZeppelin. OpenZeppelin Contract is an open-source library with pre-tested reusable code that makes smart contract development easier.
npm install @openzeppelin/contracts
Finally, we will write a smart contract for the NFT. Go back to your contracts directory and create a file named EmotionalShapes.sol
. You can name your NFT whatever you want.
This .sol
extension refers to the Solidity language, which we will use to write our smart contracts. We'll only write 14 lines of code using Solidity, so don't worry if you haven't seen it before.
cd contracts
touch EmotionalShapes.sol
Here is our smart contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
contract EmotionalShapes is ERC721 {
using Counters for Counters.Counter;
Counters.Counter private _tokenIdCounter;
constructor() ERC721("EmotionalShapes", "ESS") {}
function _baseURI() internal pure override returns (string memory) {
return "YOUR_API_URL/api/erc721/";
}
function mint(address to)
public returns (uint256)
{
require(_tokenIdCounter.current() < 3);
_tokenIdCounter.increment();
_safeMint(to, _tokenIdCounter.current());
return _tokenIdCounter.current();
}
}
Let's walk through the code and understand what's going on.
- At the top of the file, we specify the OpenZeppelin modules to import. We need the ERC721 contract as it is the "base" of our smart contract. It already implements all the methods specified in EIP-721 , so we can use it safely.
- Counters are useful for generating incremental IDs for our NFTs. We name the variable as
_tokenIdCounter
- In the constructor we initialize our ERC721 with its name and symbol. I chose EmotionalShapes and ESS.
- We override the default
_baseURI
function by returning our own function. We'll build this in a second. All in all, it will be added as a "prefix" to URLs in all of our tokenURIs. In the example above, our NFT's metadata will be saved in a JSON file atYOUR_API_URL/api/erc721/1
. - We implemented the "mint" function. This function allows you to publish an instance of this smart contract on the blockchain. I require
_tokenIdCounter
the variables to be less than 3 because I'm only going to create three instances of the NFT. You can delete it if you want to mint more. - Finally, in the mint function, we
_tokenIdCounter
increment the variable by 1, so our id will be 1, then 2, then 3. Then, we call a function provided by OpenZeppelin_safeMint
to issue tokens.
How to build metadata for NFT
As mentioned earlier, there are three main ways to store the tokenURI. We'll build a simple API endpoint that parses the NFT's information into JSON.
Our Next.js project provides us with a convenient way to develop API routes. Go to web/
the directory, find api/
the directory , pages/
under the directory, erc721/
and create our dynamic [id].js
route in the directory ([here](https://www.freecodecamp.org/news/p/18513919-9e93-4ab3 -9f52-2448aafa8835/develop API routes) read more about routing):
// web/pages/api/erc721/[id].js
const metadata = {
1: {
attributes: [
{
trait_type: "Shape",
value: "Circle",
},
{
trait_type: "Mood",
value: "Sad",
},
],
description: "A sad circle.",
image: "https://i.https://img.chengxuka.comur.com/Qkw9N0A.jpeg",
name: "Sad Circle",
},
2: {
attributes: [
{
trait_type: "Shape",
value: "Rectangle",
},
{
trait_type: "Mood",
value: "Angry",
},
],
description: "An angry rectangle.",
image: "https://i.https://img.chengxuka.comur.com/SMneO6k.jpeg",
name: "Angry Rectangle",
},
3: {
attributes: [
{
trait_type: "Shape",
value: "Triangle",
},
{
trait_type: "Mood",
value: "Bored",
},
],
description: "An bored triangle.",
image: "https://i.https://img.chengxuka.comur.com/hMVRFoJ.jpeg",
name: "Bored Triangle",
},
};
export default function handler(req, res) {
res.status(200).json(metadata[req.query.id] || {
});
}
For this project, I made the code as easy to understand as possible. This is definitely not suitable for production (please don't use https://img.chengxuka.comur url for your NFT). Make sure to define metadata for any NFTs you intend to mint.
Now, change to the web directory and start the Next.js application with the following command:
npm run dev
Your application should be running on localhost:3000. To make sure our endpoint is working, visit http://localhost:3000/api/erc721/1, which should parse with a JSON object of your first NFT's metadata.
How to expose metadata for NFTs
Since your application is hosted locally, other applications cannot access it. Using tools like ngrok , we can expose our localhost to publicly accessible URLs.
- Visit ngrok.com and complete the registration process
- Unzip the downloaded package
- In your terminal, make sure you cd into the folder where you extracted the ngrok package
- Follow the instructions on the panel to run
./ngrok authtoken YOUR_AUTH_TOKEN
- Then, run this command to create a tunnel to the web application hosted on localhost:3000
./ngrok http 3000
- about there! On your terminal, you should see something like this:
ngrok by @inconshreveable (Ctrl+C to quit)
Session Status online
Account YOUR_ACCOUNT (Plan: Free)
Version 2.3.40
Region United States (us)
Web Interface http://127.0.0.1:4040
Forwarding http://YOUR_NGROK_ADDRESS -> http://localhost:3000
Forwarding https://YOUR_NGROK_ADDRESS -> http://localhost:3000
Go to YOUR_NGROK_ADDRESS/api/erc721/1
to make sure your endpoint is working.
How to deploy our NFT
Now that we've done all the groundwork (oof), let's go back to our ethereum/
folder, ready to deploy our NFT.
ethreum/contracts/YOUR_NFT_NAME.sol
Change _baseURI
the function in the file to return your ngrok address.
// ethereum/conrtacts/EmotionalShapes.sol
contract EmotionalShapes is ERC721 {
...
function _baseURI() internal pure override returns (string memory) {
return "https://YOUR_NGROK_ADDRESS/api/erc721/";
}
...
}
To deploy our NFT, we first need to compile it with Hardhat . To make this process easier, we'll install ethers.js .
npm install @nomiclabs/hardhat-ethers --save-dev
Let's update our hardhat.config.js:
require("dotenv").config();
require("@nomiclabs/hardhat-ethers");
module.exports = {
solidity: "0.8.0",
defaultNetwork: "ropsten",
networks: {
hardhat: {
},
ropsten: {
url: process.env.DEV_API_URL,
accounts: [`0x${
process.env.PRIVATE_KEY}`],
},
},
};
To learn more about hardhat configuration files, check out their documentation . We've configured the ropsten network with our Alchemy URL and provided it with your MetaMask account's private key.
Finally, run:
npx hardhat compile
This makes hardhat generate two files for each compiled contract. We should see a newly created artifacts/
folder containing the contracts you compiled in the folder contracts/
. To learn more about how it works, read this tutorial from the Hardhat team .
Now, let's write a script that will finally deploy our NFT to the testnet. In your scripts/
folder, create a file named deploy.js
.
// ethereum/scripts/deploy.js
async function main() {
const EmotionalShapes = await ethers.getContractFactory("EmotionalShapes");
const emotionalShapes = await EmotionalShapes.deploy();
console.log("EmotionalShapes deployed:", emotionalShapes.address);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
This code was inspired by the hardhat deployment tutorial .
In ethers.js
ContractFactory
is the abstraction for deploying new smart contracts, soEmotionalShapes
here is our factory for token contract instances.ContractFactory
Calling ondeploy()
will start the deployment and return a resolveContract
toPromise
. This is the object that provides methods for each of your smart contract functions.
How to View NFTs on the Blockchain
Run the deployment script:
node ./scripts/deploy.js
You should see this in the terminal EmotionalShapes deployed: SOME_ADDRESS
. This is the address where your smart contract will be deployed on the ropsten testnet.
If you go https://ropsten.etherscan.io/address/SOME_ADDRESS
, you should see the newly deployed NFT.
How to mint your NFT
Now that you've deployed your NFT, it's time to mint it for yourself! Create a new file mint.js
called . We'll use ethers.js to help us.
First add ethers.js
the package:
npm install --save ethers
Then, write mint.js
the file:
require("dotenv").config();
const {
ethers } = require("ethers");
const contract = require("../artifacts/contracts/EmotionalShapes.sol/EmotionalShapes.json");
const contractInterface = contract.abi;
// https://docs.ethers.io/v5/api/providers
const provider = ethers.getDefaultProvider("ropsten", {
alchemy: process.env.DEV_API_URL,
});
// https://docs.ethers.io/v5/api/signer/#Wallet
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);
//https://docs.ethers.io/v5/api/contract/contract
const emotionalShapes = new ethers.Contract(
YOUR_NFT_ADDRESS,
contractInterface,
wallet
);
const main = () => {
emotionalShapes
.mint(process.env.PUBLIC_KEY)
.then((transaction) => console.log(transaction))
.catch((e) => console.log("something went wrong", e));
};
main();
We first get the interface (ABI) of the contract. From ethereum.org:
The Application Binary Interface (ABI) is the standard way to interact with contracts in the Ethereum ecosystem , both from outside the blockchain and for contract-to-contract interactions.
Your ABI defines how others can interact with your contract. We then created our provider using Alchemy (remember Node as a Service). Finally, we initialize our wallet with our private key.
In main()
the function call mint
method, the method in the smart contract we just deployed. This mint
method accepts only one parameter, to
, which represents the recipient of the token. Since we're minting for ourselves, we'll put our Metamask account's public address.
If all goes well, you should see the transaction logged in the terminal. Get hash
the attribute, and go https://ropsten.etherscan.io/tx/YOUR_HASH
. You should see the minting transaction there!
How to view NFT in Metamask wallet
You need to download the mobile version of Metamask first. Then, log into your account.
You should see an NFT tab and an Add NFT button. Click the button and enter the address of your smart contract along with your minted id. If you're following the tutorial, you should start with the id 1
.
at last
Congratulations! You just minted your own NFT. In the next part of the project, we'll build the front-end React application to interact with our contract. The ultimate goal is to build a fully functional web application where you can sell your own NFTs.
Original link: https://www.freecodecamp.org/news/how-to-make-an-nft/