🏠 Home
Beginner
01 — Introduction to Solidity 02 — Setting Up Your Environment 03 — Your First Smart Contract 04 — Data Types & Variables 05 — Functions & Visibility 06 — Control Flow 07 — Arrays & Mappings 08 — Structs & Enums
Intermediate
09 — Events & Logging 10 — Modifiers 11 — Inheritance 12 — Interfaces & Abstract Contracts 13 — Error Handling 14 — Ether & Wei 15 — Payable Functions 16 — msg.sender & msg.value 17 — Storage vs Memory vs Stack
Advanced
18 — Gas Optimization 19 — ERC-20 Tokens 20 — ERC-721 NFT Standard 21 — Contract Security 22 — Reentrancy Attacks 23 — Oracles & Chainlink 24 — Upgradeable Contracts 25 — Deploying to Mainnet
SolidityMaster / Lesson 20
Lesson 20 of 25

ERC-721 NFT Standard

Mint, transfer, and manage non-fungible tokens with the ERC-721 interface.

Advanced

What Makes NFTs Non-Fungible?

Unlike ERC-20 tokens (all identical), every ERC-721 token has a unique tokenId. NFTs represent unique ownership — digital art, game items, real estate, or any unique asset.
Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SimpleNFT {
    string public name   = "MyNFT";
    string public symbol = "MNFT";

    uint256 private _nextTokenId;

    mapping(uint256 => address) public ownerOf;
    mapping(address => uint256) public balanceOf;
    mapping(uint256 => address) public getApproved;
    mapping(uint256 => string)  private _tokenURIs;

    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    function mint(address to, string memory uri) public returns (uint256 id) {
        id = _nextTokenId++;
        ownerOf[id]    = to;
        _tokenURIs[id] = uri;
        balanceOf[to]++;
        emit Transfer(address(0), to, id);
    }

    function tokenURI(uint256 id) public view returns (string memory) {
        require(ownerOf[id] != address(0), "Nonexistent token");
        return _tokenURIs[id];
    }

    function transferFrom(address from, address to, uint256 id) public {
        require(ownerOf[id] == from, "Not owner");
        require(msg.sender == from || msg.sender == getApproved[id],
            "Not authorized");
        ownerOf[id] = to;
        balanceOf[from]--;
        balanceOf[to]++;
        delete getApproved[id];
        emit Transfer(from, to, id);
    }

    function approve(address to, uint256 id) public {
        require(msg.sender == ownerOf[id], "Not owner");
        getApproved[id] = to;
        emit Approval(msg.sender, to, id);
    }
}