Lesson 4.4: Non-Fungible Tokens (NFTs) 🚀
Welcome back, digital pioneers!
In this lesson, we’re exploring the fascinating world of Non-Fungible Tokens (NFTs). We’ll dive into the ERC721 standard, learn how to create and manage NFTs, and develop a marketplace for trading them. Ready to create your own unique digital assets? Let’s get started!
Understanding the ERC721 Standard
The ERC721 standard defines a set of rules for creating non-fungible tokens (NFTs) on the Ethereum blockchain. Unlike ERC20 tokens, each ERC721 token is unique and can’t be exchanged on a one-to-one basis with another token.
1. Key Features of ERC721:
- Uniqueness: Each token has a unique identifier.
- Ownership: Allows for the tracking and transfer of ownership.
- Metadata: Can include additional data like a link to an image or description.
2. ERC721 Interface: Here’s a simplified version of the ERC721 interface:
interface IERC721 {
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
function balanceOf(address owner) external view returns (uint256 balance);
function ownerOf(uint256 tokenId) external view returns (address owner);
function transferFrom(address from, address to, uint256 tokenId) external;
function approve(address to, uint256 tokenId) external;
}
Creating and Managing NFTs
Let’s create our own NFT contract using OpenZeppelin’s implementation of the ERC721 standard.
1. Define Your NFT Contract:
pragma solidity ^0.8.0;
import “@openzeppelin/contracts/token/ERC721/ERC721.sol”;
import “@openzeppelin/contracts/access/Ownable.sol”;
contract ArtGalleryNFT is ERC721, Ownable {
uint256 public nextTokenId;
mapping(uint256 => string) private _tokenURIs;
constructor() ERC721(“ArtGalleryNFT”, “ART”) {}
function mint(string memory tokenURI) public onlyOwner {
uint256 tokenId = nextTokenId;
_safeMint(msg.sender, tokenId);
_setTokenURI(tokenId, tokenURI);
nextTokenId++;
}
function _setTokenURI(uint256 tokenId, string memory tokenURI) internal virtual {
_tokenURIs[tokenId] = tokenURI;
}
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
return _tokenURIs[tokenId];
}
}
Explanation:
- ERC721: Inherits the standard ERC721 functionality.
- Ownable: Adds ownership control to the contract.
- mint Function: Allows the contract owner to mint new tokens with a specified URI.
- _setTokenURI and tokenURI Functions: Manage and retrieve the metadata for each token.
Developing a Marketplace for Trading NFTs
Now, here’s a fun project! Let’s create a simple marketplace contract where users can buy and sell NFTs.
1. Define Your Marketplace Contract:
pragma solidity ^0.8.0;
import “@openzeppelin/contracts/token/ERC721/IERC721.sol”;
import “@openzeppelin/contracts/access/Ownable.sol”;
contract NFTMarketplace is Ownable {
struct Listing {
address seller;
uint256 price;
}
mapping(address => mapping(uint256 => Listing)) public listings;
event TokenListed(address indexed nftContract, uint256 indexed tokenId, address indexed seller, uint256 price);
event TokenPurchased(address indexed nftContract, uint256 indexed tokenId, address indexed buyer);
function listToken(address nftContract, uint256 tokenId, uint256 price) public {
IERC721 nft = IERC721(nftContract);
require(nft.ownerOf(tokenId) == msg.sender, “Not the token owner”);
require(nft.getApproved(tokenId) == address(this), “Marketplace not approved”);
listings[nftContract][tokenId] = Listing(msg.sender, price);
emit TokenListed(nftContract, tokenId, msg.sender, price);
}
function purchaseToken(address nftContract, uint256 tokenId) public payable {
Listing memory listing = listings[nftContract][tokenId];
require(msg.value == listing.price, “Incorrect price”);
IERC721(nftContract).transferFrom(listing.seller, msg.sender, tokenId);
payable(listing.seller).transfer(msg.value);
delete listings[nftContract][tokenId];
emit TokenPurchased(nftContract, tokenId, msg.sender);
}
}
Explanation:
- Listing Struct: Stores the seller’s address and the token price.
- listToken Function: Allows users to list their NFTs for sale.
- purchaseToken Function: Enables users to purchase listed NFTs.
Real-World Example: Building an NFT-Based Art Gallery
Let’s put it all together and build an NFT-based art gallery where users can mint, buy, and sell unique digital artworks.
- Deploy Your NFT Contract: Deploy your
ArtGalleryNFT
contract and mint a few NFTs with unique URIs. - Deploy Your Marketplace Contract: Deploy your
NFTMarketplace
contract and list the minted NFTs for sale. - Interact with the Marketplace: Use the marketplace functions to list and purchase NFTs, simulating a digital art gallery where users can trade unique artworks.