Hello! Welcome back to your course on Ethereum development.
In our last lesson, we designed the front-end architecture for a DeFi application, focusing on how a user interface interacts with smart contracts. We're now going to switch gears and dive back into Solidity to explore a different, but equally important, part of the Ethereum ecosystem: Non-Fungible Tokens (NFTs).
Introduction
You're already familiar with ERC-20 tokens from our DeFi module—they are fungible, meaning each token is identical and interchangeable, like dollars in a bank account. This lesson introduces the ERC-721 standard, the foundation for NFTs. These tokens are non-fungible, meaning each one is unique and has a distinct identity. This uniqueness makes them perfect for representing ownership of digital art, collectibles, real estate, or any one-of-a-kind asset.
Learning Outcome: By the end of this lesson, you will be able to build an ERC-721 NFT contract with metadata URI management.
We will cover the core principles of the ERC-721 standard, use the industry-standard OpenZeppelin library to build a secure NFT contract, and, most importantly, tackle the critical task of managing the metadata (like images and attributes) that gives an NFT its character.
1. Understanding the ERC-721 Standard
The core idea of an ERC-721 token is that each one is identified by a unique uint256 called the tokenId. All operations—transferring, checking ownership, or approving someone else to move the token—revolve around this unique ID.
To understand the fundamental building blocks of an NFT contract, let's review the functions and events that define the standard.
How to create and deploy an ERC-721 (NFT)
This video from Quicknode provides a concise overview of the ERC-721 standard. It clearly explains the purpose of each required function and event.
Watch the section 'Understanding ERC-721 Standard' (from 01:33 to 05:20). As you watch, notice how functions like ownerOf(tokenId) and transferFrom(from, to, tokenId) are centered around the unique token ID, which is the key difference from the ERC-20 standard.
As the video explains, any compliant ERC-721 contract must implement an interface that includes:
- Ownership functions:
ownerOf(tokenId)andbalanceOf(owner). - Transfer functions:
transferFrom(...)andsafeTransferFrom(...). - Approval functions:
approve(...)andsetApprovalForAll(...). - Events:
Transfer,Approval, andApprovalForAllto allow off-chain applications to track state changes.
2. Building an NFT Contract with OpenZeppelin
While it's possible to implement the entire ERC-721 interface from scratch, modern Solidity development heavily relies on libraries like OpenZeppelin. Using their battle-tested, community-audited contracts saves time and significantly enhances security.
For NFTs, OpenZeppelin provides several helpful contracts. We will focus on two:
ERC721.sol: The base implementation of the standard.ERC721URIStorage.sol: An extension that allows us to associate a metadata URI with each individual token ID.
Let's look at how to combine these to create a simple but powerful NFT contract.
How to Create and Deploy an ERC-721 (NFT)
This guide from Quicknode provides a complete, well-explained code example for an NFT contract. It's an excellent text-based resource that walks through a modern implementation.
Please read the section titled 'Creating Our Own Token'. Study the provided MyToken.sol contract and the line-by-line explanation. Pay close attention to: The contracts being imported and inherited (ERC721, ERC721URIStorage, Ownable). The safeMint function, which calls _safeMint to create the token and _setTokenURI to link its metadata.
The key takeaway from the code is its simplicity. By inheriting from OpenZeppelin's contracts, we only need to write a small amount of custom logic:
- Constructor: Sets the token's name and symbol (e.g., "MyNFT", "MNFT") and establishes ownership for access control.
- Minting Function: A custom function, often called
mintorsafeMint, that controls how new tokens are created. It's crucial to protect this function (e.g., withonlyOwner) to control supply. The function performs two key actions:_safeMint(to, tokenId): Assigns a newtokenIdto thetoaddress._setTokenURI(tokenId, uri): Stores a string (the URI) and associates it with the newly createdtokenId.
This _setTokenURI function is the bridge between your on-chain token and its off-chain metadata. Now, let's explore what that metadata is and how to manage it.
3. Metadata and IPFS: The Soul of an NFT
An on-chain token is just a number (tokenId) in a smart contract. The art, name, description, and traits that we associate with an NFT are contained in a separate metadata file, usually in JSON format. The tokenURI function in the contract simply returns a URL pointing to this file.
Why is Metadata Off-Chain?
Storing data on Ethereum is extremely expensive. A single high-resolution image would cost thousands of dollars in gas fees to store directly on-chain. Therefore, the standard practice is to store the metadata off-chain and only store a link to it on-chain.
IPFS: Decentralized and Permanent Storage
You could use a regular web server (e.g., https://my-server.com/nfts/1.json) for your metadata, but this creates a problem. What if the server goes down or you stop paying the hosting bill? The NFT would still exist on-chain, but it would point to a dead link, effectively losing its art and properties.
The solution is the InterPlanetary File System (IPFS). Unlike a traditional URL that points to a location, an IPFS URI points to the content itself using a unique hash.
- Content-Addressing:
ipfs://Qm...The URI is a hash of the file's content. If the file changes even by one byte, the hash changes completely. - Immutability: This ensures that the metadata linked to your NFT can never be changed without creating a new URI.
- Decentralization: Anyone can "pin" (host) a copy of the content. As long as at least one node on the IPFS network is hosting the file, it will be available.
The workflow for managing metadata with IPFS is a two-step process:
- Upload the Asset: Upload your image (e.g.,
my-nft.png) to an IPFS pinning service (like Pinata, nft.storage, or QuickNode). You will get an IPFS URL for it. - Upload the Metadata: Create a JSON file (e.g.,
1.json) that includes the IPFS URL for your image. Then, upload this JSON file to IPFS. The resulting IPFS URL for the JSON file is what you will use as thetokenURIwhen you mint your NFT.
The following video demonstrates this entire workflow.
How to create and deploy an ERC-721 (NFT)
Let's return to the Quicknode video to see a practical demonstration of preparing and uploading assets to IPFS and then using the resulting URI to mint an NFT.
Watch the sections 'Hosting NFT Art and Metadata on IPFS' (13:28 - 16:30) and 'Minting an NFT with Metadata URI' (16:30 - 18:27). Focus on the two-step upload process: first the image, then the metadata file that points to the image.
For a deeper conceptual understanding of why IPFS is the standard for NFTs, the following video from RareSkills provides an excellent explanation.
ERC721 Tutorial. How ERC721 works, integrate with Opensea, use IPFS
This video offers a clear, conceptual breakdown of the problems with centralized storage for NFTs and how IPFS's content-addressing model solves them.
For a more theoretical perspective, watch the section 'Introduction to IPFS for NFT Assets' (48:52 - 56:34). This is optional but highly recommended for a full understanding of the 'why' behind using IPFS.
4. Putting It All Together: Deployment and Minting
You now have all the pieces:
- An ERC-721 smart contract built with OpenZeppelin.
- An IPFS URI for your NFT's metadata.
The final step is to deploy the contract and call your safeMint function. You can do this easily in an environment like Remix.
- Compile your contract.
- Deploy it to a testnet (e.g., Sepolia), passing any required constructor arguments (like the
initialOwner). - In the deployed contract interface in Remix, find your
safeMintfunction. - Provide the recipient's address, a unique
tokenId(e.g.,0or1), and the IPFS URI of your JSON metadata file. - Transact and approve the transaction in your wallet.
Once the transaction is confirmed, your NFT is live! You can then view it on a testnet-compatible marketplace like testnets.opensea.io by searching for your contract address. The marketplace will read the tokenURI from your contract, fetch the JSON from IPFS, and use it to display your NFT's image and properties.
Conclusion
In this lesson, we moved from the fungible world of ERC-20s to the unique, non-fungible world of ERC-721s. You've learned how to build a standard NFT contract and, crucially, how to manage the off-chain data that gives it value and identity.
Key Takeaways:
- ERC-721 is the standard for unique, non-fungible tokens on Ethereum, where each token is identified by a unique
tokenId. - OpenZeppelin's
ERC721URIStorageis the industry-standard starting point for building secure NFT contracts with per-token metadata. - Metadata is typically a JSON file stored off-chain due to the high cost of on-chain storage.
- IPFS is the preferred storage solution for metadata because its content-addressing system provides immutability and decentralization, ensuring the NFT's properties don't depend on a single server.
- The
tokenURIfunction in the smart contract is the critical link that connects the on-chain token to its off-chain metadata.
Next Steps:
While ERC-721 is perfect for one-of-a-kind items, what if you want to manage a collection that includes both unique items (like a legendary sword) and fungible items (like gold coins) within the same contract? The next lesson, "Implement an ERC-1155 multi-token contract," will introduce you to a powerful standard designed for exactly that purpose.
Can't find a good explanation? Sign up and we'll make it for you
Sign up