NFTs Explained Quick: From Zero to Hero
This Day 11 post of Merpay Tech Openness Month 2022 is brought to you by David Mohl. David worked as an Engineering Manager in Merpay as part of the Pointback team and was recently involved with the launch of Mercaris joint NFT project.
NFTs came out of nowhere and took the world by storm. No matter where on the internet you look, you will likely see NFTs—from music based NFTs to cartoon images of apes.
Mercari has also recently partnered with Pacific League to launch a joint NFT project called パ・リーグ Exciting Moments, which gives users the chance to purchase exciting moments of baseball history as digital collectibles.
For today’s post, let’s take a look at what NFTs actually are, how they work, and how we could make our own NFT collection from scratch. Let’s go!
What really is an NFT?
We know that NFTs are some kind of digital collectibles that facilitate a modern way for users to buy art, or in other words: things you can own on the internet. But it gets a bit more high-level than that.
NFT stands for [N]on-[F]ungible [T]oken. If we were to look up what fungible means in a dictionary, we’d see an explanation like this:
So, a fungible item is an item that can be replaced by another identical item and keeps its function.
Fungibility and non-fungibility
By reading the explanation in the previous paragraph I think you can already guess what fungibility is, but let’s take a look at some more examples.
The easiest example of a fungible thing is something we use on a daily basis: money. Each bill and each coin is fungible, meaning it can be replaced with another identical item and keep its function. The 1000 yen bills I have in my wallet are pretty much identical when it comes to function in society. They are all worth exactly 1000 yen and I can use them for buying different things—they are fungible.
Bitcoin (and most cryptocurrencies for that matter) is equally fungible. 1 BTC is always 1 BTC in worth and function. If my wallet holds 3 BTC, all those BTC will be usable in the same way, there is no difference between them.
Non-fungibility on the other hand is when something can’t be replaced with another identical item. Each item is unique and has a different function and worth. An easy example is Diamonds! Each diamond, with all its purities and impurities, exists only once on this planet. I can’t just replace a diamond with another one and pretend they are identical, because they are not. Their unique traits make up their worth.
Trading cards are also non-fungible (or semi-non-fungible) if we assume that each card exists only once. There may be many Charizard pokemon cards out there, but some are rarer than others based on traits such as season, release date, holo or not, and sparsity.
So we could summarize: NFT = a unique token that exists only once.
NFTs and non-fungibility on the blockchain
A blockchain is, simply put: a public ledger that records transactions like “User A sent User B 1 BTC” and “User C sent User B 0.2 BTC”. If we were to replay that entire ledger from its first block (the “genesis block”) to its last, then each user would end up an exact balance—e.g., User B would have 1.2 BTC in their wallet. Because bitcoin and those tokens are fungible, we can just summarize and say: User B owns 1.2 BTC. It doesn’t matter if they received it from five different people or one—they still have 1.2 BTC.
Now to NFTs. A simple way to think of an NFT is a token that exists only once. So, instead of having a maximum supply of tokens, an NFT has a maximum supply of, well, 1 (simplified). If User A sends this token to User B, user B now owns the only copy of this token. There is no balance we can summarize, because the token exists only once, and if we were to do a lookup at where that token is, the ledger would tell us that it’s in User B’s wallet, which we can’t really do with Bitcoin.
What we often see are collections and series of NFTs. An artist creates multiple images that are similar to each other and distributes each of them as separate tokens that are non-fungible, but part of the same collection. Even though all of those images are in the same collection (eg. "Bored Ape Yacht Club"), each individual token is still cryptographically unique and exists only once on the entire blockchain.
It’s kind of like having digital diamonds or trading cards!
Sometimes artists decide that they want to distribute the same image multiple times. Here as well, even though those images exist multiple times on the blockchain, each individual token is unique and exists only once.
Going deeper: What actually is an NFT?
Now that we’ve covered the base-concept of what an NFT is, let’s go into some of the technical aspects. How NFTs are implemented varies from blockchain to blockchain, but for the rest of this post, we’ll assume that we are on Ethereum and dealing with the Ethereum Virtual Machine (EVM).
Here’s the first shocker: NFTs, at least in the majority of cases, don’t actually contain any art, images, or videos! Storing things on the blockchain is very expensive and costs gas (small amounts of Ethereum that need to be— paid for the transaction to be included in the next block). Everything we store on the blockchain would have to be replicated onto the entire cluster of nodes and will become available forever. The cost of doing that for a collection of high quality images would be absurdly high.
Instead, NFTs on Ethereum follow the EIP-721 standard (EIP stands for Ethereum Improvement Proposal). This standard specifies what a smart contract must implement to be officially considered an ERC-721 compliant NFT. Mainly, there are two interfaces that need to be implemented: ERC-721 and ERC-165 (+ optional ERC721Metadata and ERC721TokenReceiver).
ERC721: How NFTs are implemented
All these standards and proposals may sound complicated at first, but the ERC-721 interface is actually very simple and looks like this:
balanceOf(owner)
ownerOf(tokenId)
safeTransferFrom(from, to, tokenId)
transferFrom(from, to, tokenId)
approve(to, tokenId)
getApproved(tokenId)
setApprovalForAll(operator, _approved)
isApprovedForAll(owner, operator)
safeTransferFrom(from, to, tokenId, data)
Reading through these method names, it should already give you a hint how NFTs, at least on Ethereum, actually work.
Similar to ERC20, the standard that is used for creating tokens, ERC721 keeps track of everything that happens with NFTs within a small smart contract. Think of it as a Class
that is getting initialized and then keeps track of its internal state within class properties (this.foo = "bar"
).
Let’s look at some examples:
balanceOf
: Your program should return how many of your NFTs the target address ownstransferFrom(from, to, tokenId)
: Your program should update it’s internal state so thatto
now owns the nft with tokenIdtokenId
, and no longerfrom
ownerOf(tokenId)
: Your program should return the address that currently holds the token with idtokenId
And that’s it! It’s really that simple—a class that just updates its internal state (this.tokens[123].owner = 'xxx'
) via methods matching an interface. How this class does this internally does not matter as long as it conforms to spec, neat!
What about the artwork?
If you paid attention to the standards I posted in the previous section you might have noticed that none of these methods have anything to do with art, images or videos. It’s just state tracking. What gives?
Standard NFTs, in their simplest form, are unique tokens that exist only once. That’s it, nothing else. What you do with those tokens is up to you and your smart contract.
To make things a bit easier, ERC721 supports adding additional metadata to tokens through extensions, but this is not part of the original ERC721 proposal. To support metadata for our NFT, we’ll have to implement ERC721Metadata as well:
name()
symbol()
tokenURI(tokenId)
And that’s it! This time for real, I promise.
name()
takes no arguments and just returns the name of our NFT, for example "Mercari NFT"symbol()
also takes no arguments and returns the symbol that we want our NFT to associate with, for example "MFT"tokenUri()
on the other hand does take an argument and has to return a string, more specifically, as the name implies: a URI of where to find the actual metadata
As I mentioned earlier, storing data on the chain directly is expensive. ERC721 solves this by not having it on the blockchain at all, but instead only storing the URI of where the metadata resides.
It does not matter where we host this metadata, but to make integration with websites easier, a few standards like the opensea metadata standard have popped up, which nowadays all NFTs implement. These standards specify a set of JSON keys that should be present:
{
"description": "My awesome NFT",
"image": "https://mercari.com/1.jpg",
"name": "Token 1",
"attributes": [ ... ],
}
And now we’ve arrived at the last part of the puzzle: the image! The metadata JSON is the place where we specify where to actually find the asset that the NFT represents!
Quick recap
So far we have learned that:
- NFTs are tokens that exist only once
- On Ethereum, if a smart contract implements ERC721, it’s considered an NFT
- Smart Contracts internally track who owns which tokens, similar to a Class with class properties
- To add assets (such as images) to an NFT, we have to implement ERC721Metadata as well
- Images are (usually) not stored on-chain due to cost, but instead somewhere else. The metadata json describes where it’s stored
Or, in even simpler terms: An on-chain NFT is kind of like a digital note/certificate that points to where the actual data is
It is up to the creator of the NFT to define what this digital certificate then actually means, and whether the buyer receives the copyright and/or intellectual property of the asset(s) that the NFT points to (usually it stays with the creator of the artwork and does not transfer to the buyer).
Bonus round: Let’s make an NFT:
For this example, to make our lives easier, we’ll be using libraries provided by openzeppelin.
Let’s start by creating a new contract that extends ERC721
(or, more specifically, ERC721URIStorage
, which includes ERC721Metadata
)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
contract MercariNFT is ERC721URIStorage {
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
constructor() ERC721("MercariNFT", "MFT") {}
function mintToken(address receiver, string memory tokenURI)
public
returns (uint256)
{
_tokenIds.increment();
uint256 newItemId = _tokenIds.current();
_mint(receiver, newItemId);
_setTokenURI(newItemId, tokenURI);
return newItemId;
}
}
contract MercariNFT is ERC721URIStorage
This creates a new contract, MercariNFT
, which extends ERC721URIStorage
(which in return extends ERC721
).
constructor() ERC721("MercariNFT", "MFT") {}
We don’t have to do anything in our constructor besides calling the parent ERC721 constructor with the name
and symbol
we want to use.
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
This creates a simple Counter (also provided by openzeppelin) that does only one thing: counting with increment
or decrement
.
function mintToken(address receiver, string memory tokenURI)
public
returns (uint256)
{
_tokenIds.increment();
uint256 newItemId = _tokenIds.current();
_mint(receiver, newItemId);
_setTokenURI(newItemId, tokenURI);
return newItemId;
}
Finally, in our mint function we:
- Increment the tokenId counter by 1, to get the next id
- Call the parent
_mint()
method (docs here) which creates a new NFT token with tokenIdnewItemId
internally and sends it toreceiver
- Set the URI passed as
tokenURI
parameter as the token URI with_setTokenURI
- Return the new tokenId
Once we deploy this with something like Remix, we can call mintToken
and our contract will create new NFTs with the specified URI!
mintToken()
: (note the receiver and tokenURI input)
balanceOf()
:
⚠️ This is not production ready ⚠️
This example is a very simple demonstration. mintToken
has no guard, so anyone can just create new NFTs with this contract as they please.
It’s also not advisable to create new tokens this way. Instead, have all resources be ready beforehand and upload them to something like IPFS (decentralized storage) so that they become immutable. Then upon contract creation, write your logic so that mint()
is using the next available image (if available), and error when all available images have already been minted. A common way to do this is to have images paths be predictable, eg: Token 1 => ipfs.xxxxx/1. Even better: have the starting-point randomized so that it doesn’t start from 1, 2, 3… 100 but maybe from 76, 77, 78… 75.
You can still use a mintToken
method like above with a guard that only the admin account is able to create new tokens, but make sure to add restrictions or a way to revoke this privilege to give your users peace of mind once all tokens are prepared.
NFT Next
There is no doubt that NFTs are the current hot topic in tech. Current use-cases like art and collectibles catapulted NFTs into the spotlight but that is far from the end of it.
Cryptographically unique tokens can be used for much more than just collecting art digitally. From ideas about supply-chain optimizations, tracking and tracing of goods, exclusive membership tokens or even proof of ownership for high-grade physical goods—I am convinced that applications of NFT technology will only grow from here on.
I’m excited to see where NFTs will be used next and strongly believe they will play an important role in the coming years, outside of the current collectibles use-case.