paint-brush
How to Create Your NFT on Ethereum Blockchainby@talktomaruf
3,539 reads
3,539 reads

How to Create Your NFT on Ethereum Blockchain

by Abubakar MarufJune 21st, 2022
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

An NFT is a unique and special digital asset that is non-fungible—unequalled and unmatched. The ERC-721 token standard that allows for the creation of NFTs on the. Ethereum blockchain is a decentralized, permissionless blockchain with the additional function of a smart contract. NFT tokens are a unique token that can't be exchanged for another, i.e., a particular NFT can't. be exchanged with another token even if both NFT are from a collection of a single creator. Examples of non-Fungible tokens are Cryptopunks, Bored Ape, and Cryptokitties.
featured image - How to Create Your NFT on Ethereum Blockchain
Abubakar Maruf HackerNoon profile picture

NFT is an acronym term for Non-fungible tokens. An NFT is a unique and special digital asset that is non-fungible—unequalled and unmatched. NFTs are unique and non-fungible because each NFT has a special or rare property or feature that is not present in other NFTs. This rarity feature applies to all NFTs. It is locked in a smart contract; this is why it is usually created or minted on blockchains that have a smart contract. One such blockchain is the Ethereum blockchain.


The Ethereum blockchain is a decentralized, permissionless blockchain with the additional function of a smart contract. Smart contracts are predefined conditions or terms of agreement embedded in programming codes that self-execute once the predefined conditions have been met. One of the several use cases of a smart contract on a particular blockchain extends to allowing other tokens to be built or created on the blockchain. This is why Ethereum can house other tokens being launched on its ecosystem. Such tokens can be fungible—ERC-20— or Non-fungible—ERC-721.

Distinguishing between Fungible and Non-fungible tokens

Fungible and non-fungible tokens can be distinguished with two distinct features: exchangeability—fungibility—and amount—quantity.

Fungible Tokens: Fungibility and Quantity

Fungibility or exchangeability is the ability to exchange or substitute assets for another. For example, Kelly and George were both given separate $100 bills by their uncle. Since both bills represent the same value, they can be exchanged while still retaining their respective value. That is fungibility!


Now imagine if Kelly and George agreed to add their money together to buy a movie ticket for two.


Their total money is now $200, right?


Quantity or amount of fungible tokens can be added to have a greater amount while still having the money.


The ERC-20 tokens are the fungible tokens that can be exchanged for their kind or another token of its equivalent. ERC-20 token standard is exploited for the creation of fungible tokens. Examples of ERC-20 tokens include ETH, BNB, DAI, USDT, and USDC, among others.

Non-fungible Tokens: Non-Fungibility and Quantity

A non-fungible token—NFT— is a unique token that can't be exchanged for another token. Likewise, you can't add up NFTs to get a greater or higher quantity. Like you can't exchange a sofa with a waste bin, you can't also replace one NFT with another because each has its unmatched properties.


The Ethereum token standard that allows for the creation of NFTs on the Ethereum blockchain is the ERC-721 token standard. The ERC-721 tokens are non-fungible tokens that can't be exchanged for another, i.e., a particular NFT can't be exchanged with another NFT even if both NFTs were from a collection of a single creator. Examples of non-fungible tokens are Cryptopunks, Bored Ape, and Cryptokitties.

ERC-721 Standard

The ERC-721 standard applies a set of mechanisms for recognizing and engaging with Non-Fungible Tokens. The mechanism entails three layers: Core, Extensions, and Convenience.



The EIPs—Ethereum Improvement proposals— have three interfaces; IERC721IERC721Metadata, and IERCEnumerable. For a contract to be ERC721 compliant, just the IERC721 is needed. However, all of them are implemented in ERC721.


The IERC is the token's contract Interface, while ERC is the token's contract implementation. The IERC interface has two subsets which are:


  • Events
  • Functions


The events category includes Transfer, Approval, and ApprovalForAll.

The Functions are:

balanceOf: Supplies the balance of NFTs in the owner’s account

ownerOf: Supplies the owner—address—of the given token ID

safeTransferFrom: Will transfer a particular NFT from one address to another having checked and ascertain that the recipient’s address can accept the NFT so that it is not lost or burn by sending it to the address.

transferFrom: Will transfer NFT from one address to another—using this function is not encouraged. If the recipient’s address is not from the approved address, it should either be approved through approve or setApprovalForAll.

approve: Will approve any other address to send NFT to from the owner’s address to any other approved address.

setApprovalForAll: Permits or prevents an operator—any address, primarily wallets, and exchanges—from sending NFTs from the owner's address to any other address.

getApproved: Return an address that NFTs can be transferred to for owners. If no address is set, return 0.

isApprovedForAll: Return true if the address is approved by the particular owner.


pragma solidity ^0.4.20;

interface ERC721 /* is ERC165 */ {

event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);

event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);

event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);

function balanceOf(address _owner) external view returns (uint256);

function ownerOf(uint256 _tokenId) external view returns (address);

function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;

function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;

function transferFrom(address _from, address _to, uint256 _tokenId) external payable;

function approve(address _approved, uint256 _tokenId) external payable;

function setApprovalForAll(address _operator, bool _approved) external;

function getApproved(uint256 _tokenId) external view returns (address);

function isApprovedForAll(address _owner, address _operator) external view returns (bool);

}


Creating an ERC-721 Token—NFT— with OpenZeppelin and Truffle

Before we can create or develop an NFT with OpenZeppelin library and Truffle we need to setup Truffle first:


mkdir simple

truffle init

npm install openzeppelin-solidity


Now, let’s create a new contract for our token:


pragma solidity ^0.4.24;

 

import "/openzeppelin-solidity/contracts/token/ERC721/ERC721Full.sol";

import "/openzeppelin-solidity/contracts/ownership/Ownable.sol";

contract SIMPLEToken is ERC721Full, Ownable{

  

  constructor() 

  ERC721Full("SIMPLE", "SMPL")

  public {}

  

function mint(address to, uint256 tokenId) public onlyOwner {

   _mint(to, tokenId);

  }

function _mint(address to) public onlyOwner{

   mint(to, totalSupply().add(1));

  }

}


From the above, we're inheriting two contracts simultaneously; ERC721FULL and Ownable.

Ownable: With this contract, we’ll be able to manage ownership of our contract and also mint NFTs from the contract owner’s address only.


ERC721FULL: This is a standard implementation of the ERC-721 interface mentioned earlier. Let's have an internal look at this contract:


pragma solidity ^0.4.24;

 

import "./ERC721.sol";

import "./ERC721Enumerable.sol";

import "./ERC721Metadata.sol";

contract ERC721Full is ERC721, ERC721Enumerable, ERC721Metadata {

  constructor(string name, string symbol) ERC721Metadata(name, symbol)

    public

  {

  }

}


Internally, ERC721FULL inherits 3 contracts—ERC721, ERC721Enumerable, and ERC721Metadata. Let's focus on ERC721 to understand better how it works.

Before going through the main function one after the other, let’s understand are tokens are stored first:

// Mapping from token ID to owner

  mapping (uint256 => address) private _tokenOwner;

  

 

// Mapping from token ID to approved address

  mapping (uint256 => address) private _tokenApprovals;

  

// Mapping from owner to number of owned token

  mapping (address => uint256) private _ownedTokensCount;

  

// Mapping from owner to operator approvals

  mapping (address => mapping (address => bool)) private _operatorApprovals;


_tokenOwner: This mapping is required for the storage of the token against its owner. We can find out who owns a particular token ID by using this.

_tokenApprovals: This mapping is required for the storage of token ID against an address that has been approved by the token owner to transfer a token on behalf of the owner.

_ownedTokenCount: This mapping is required to determine the number of tokens an address holds. If this mapping is not created, we'll have to loop to get this token count, which consumes a lot of gas on EVM.

_operatorApprovals: Mapping of an owner and an operator—any address, primarily wallets and exchanges— to determine whether the owner has approved or not.

Now, we can go through the functions

balanceOf: This will return an address's balance. It verifies for a valid address first, and then uses _ownedTokensCount to return the token's count.


function balanceOf(address owner) public view returns (uint256) {

    require(owner != address(0));

    return _ownedTokensCount[owner];

  }


OwnerOf: Using the _tokenOwner mapping, this will return the owner address for a specific token.


function ownerOf(uint256 tokenId) public view returns (address) {

    address owner = _tokenOwner[tokenId];

    require(owner != address(0));

    return owner;

  }


approve: This authorizes an address to transfer a token on behalf of the owner. The function initially verifies whether the function was called by the owner or if the call was approved by the owner to send all tokens. If everything is correct, it will update _tokenApprovals


function approve(address to, uint256 tokenId) public {

    address owner = ownerOf(tokenId);

    require(to != owner);

    require(msg.sender == owner || isApprovedForAll(owner, msg.sender));

    

_tokenApprovals[tokenId] = to;

    emit Approval(owner, to, tokenId);

  }


safeTransferFrom: There are two similar functions with varying arguments. These functions internally call the transferFrom function has another essential task they perform. They verify whether or not the recipient’s address is authorized to get the token. This contributes to the token's security.


function safeTransferFrom(

    address from,

    address to,

    uint256 tokenId,

    bytes _data

  )

    public

  {

    transferFrom(from, to, tokenId);

    require(_checkOnERC721Received(from, to, tokenId, _data));

  }


transferFrom: This is the primary function of transferring a token from a particular address to another. It does the following:


  1. Determine whether the token is owned by called, or approved to the caller. It also verifies if an address is valid.
  2. Clear approval, remove existing ownership and lower the current owner's token count.
  3. Add a token to the recipient's account and increase the recipient's token count.


function transferFrom(

    address from,

    address to,

    uint256 tokenId

  )

    public

  {

    require(_isApprovedOrOwner(msg.sender, tokenId));

    require(to != address(0));

_clearApproval(from, tokenId);

    _removeTokenFrom(from, tokenId);

    _addTokenTo(to, tokenId);

emit Transfer(from, to, tokenId);

  }


setApprovalForAll: This function authorizes the address to send all tokens on the owner's behalf. It checks to see if the called and to addresses are different before updating the _operatorApprovals mapping.


function setApprovalForAll(address to, bool approved) public {

    require(to != msg.sender);

    _operatorApprovals[msg.sender][to] = approved;

    emit ApprovalForAll(msg.sender, to, approved);

  }


isApprovedForAll: This function verifies whether the owner has permitted the operator to transfer tokens or not.


function isApprovedForAll(

    address owner,

    address operator

  )

    public

    view

    returns (bool)

  {

    return _operatorApprovals[owner][operator];

  }

getApproved: Returns the approved address for a certain token ID.


function getApproved(uint256 tokenId) public view returns (address) {

    require(_exists(tokenId));

    return _tokenApprovals[tokenId];

  }

Conclusion

We just used Solidity on Ethereum to create a Non-Fungible Token. NFTs mostly serve as collectibles, but they can also be used to represent real estate, artwork, certifications, and loans, among other things. NFTs are used as items and collections in gaming, metaverse, and other platforms.


Isn't it fun?