Skip to content

ERC721 Standard Implementation

1. System Overview

The ERC721 standard implementation is a Non-Fungible Token (NFT) contract implemented in Solidity that fully complies with the ERC721 standard interface specification. This implementation includes basic token functionality, metadata functionality, and necessary security mechanisms.

1.1 Main Features

  • Standard compliance: Fully compliant with ERC721 standard
  • Basic functions: Minting, transfer, burning, and other basic operations
  • Metadata functionality: Support for token metadata management
  • Event tracking: Complete event logging
  • Security checks: Comprehensive security validation
  • Authorization management: Support for token approval and operator approval

2. Contract Implementation

solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/Context.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";

/**
 * @title ERC721 Token
 * @dev ERC721 Non-Fungible Token standard implementation
 */
contract ERC721 is Context, ERC165 {
    using Address for address;
    using Strings for uint256;

    // Events
    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);

    // State variables
    string private _name;
    string private _symbol;
    string private _baseURI;

    // Token data
    mapping(uint256 => address) private _owners;
    mapping(address => uint256) private _balances;
    mapping(uint256 => address) private _tokenApprovals;
    mapping(address => mapping(address => bool)) private _operatorApprovals;
    mapping(uint256 => string) private _tokenURIs;

    /**
     * @dev Constructor
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev Implement ERC165 interface
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return
            interfaceId == type(IERC721).interfaceId ||
            interfaceId == type(IERC721Metadata).interfaceId ||
            super.supportsInterface(interfaceId);
    }

    /**
     * @dev Get token name
     */
    function name() public view returns (string memory) {
        return _name;
    }

    /**
     * @dev Get token symbol
     */
    function symbol() public view returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Get token URI
     */
    function tokenURI(uint256 tokenId) public view returns (string memory) {
        require(_exists(tokenId), "ERC721: URI query for nonexistent token");

        string memory _tokenURI = _tokenURIs[tokenId];
        string memory base = baseURI();

        if (bytes(base).length == 0) {
            return _tokenURI;
        }
        if (bytes(_tokenURI).length > 0) {
            return string(abi.encodePacked(base, _tokenURI));
        }
        return string(abi.encodePacked(base, tokenId.toString()));
    }

    /**
     * @dev Get base URI
     */
    function baseURI() public view returns (string memory) {
        return _baseURI;
    }

    /**
     * @dev Get token balance
     */
    function balanceOf(address owner) public view returns (uint256) {
        require(owner != address(0), "ERC721: balance query for the zero address");
        return _balances[owner];
    }

    /**
     * @dev Get token owner
     */
    function ownerOf(uint256 tokenId) public view returns (address) {
        address owner = _owners[tokenId];
        require(owner != address(0), "ERC721: owner query for nonexistent token");
        return owner;
    }

    /**
     * @dev Transfer token
     */
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public virtual {
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");
        _transfer(from, to, tokenId);
    }

    /**
     * @dev Safe transfer token
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
    ) public virtual {
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");
        _safeTransfer(from, to, tokenId, _data);
    }

    /**
     * @dev Approve token
     */
    function approve(address to, uint256 tokenId) public virtual {
        address owner = ownerOf(tokenId);
        require(to != owner, "ERC721: approval to current owner");
        require(
            _msgSender() == owner || isApprovedForAll(owner, _msgSender()),
            "ERC721: approve caller is not owner nor approved for all"
        );
        _approve(to, tokenId);
    }

    /**
     * @dev Get approved address for token
     */
    function getApproved(uint256 tokenId) public view returns (address) {
        require(_exists(tokenId), "ERC721: approved query for nonexistent token");
        return _tokenApprovals[tokenId];
    }

    /**
     * @dev Set operator approval
     */
    function setApprovalForAll(address operator, bool approved) public virtual {
        require(operator != _msgSender(), "ERC721: approve to caller");
        _operatorApprovals[_msgSender()][operator] = approved;
        emit ApprovalForAll(_msgSender(), operator, approved);
    }

    /**
     * @dev Check if approved for all
     */
    function isApprovedForAll(address owner, address operator) public view returns (bool) {
        return _operatorApprovals[owner][operator];
    }

    /**
     * @dev Internal safe transfer function
     */
    function _safeTransfer(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
    ) internal virtual {
        _transfer(from, to, tokenId);
        require(
            _checkOnERC721Received(from, to, tokenId, _data),
            "ERC721: transfer to non ERC721Receiver implementer"
        );
    }

    /**
     * @dev Internal transfer function
     */
    function _transfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {
        require(ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
        require(to != address(0), "ERC721: transfer to the zero address");

        _beforeTokenTransfer(from, to, tokenId);

        // Clear approvals
        _approve(address(0), tokenId);

        _balances[from] -= 1;
        _balances[to] += 1;
        _owners[tokenId] = to;

        emit Transfer(from, to, tokenId);

        _afterTokenTransfer(from, to, tokenId);
    }

    /**
     * @dev Internal mint function
     */
    function _mint(address to, uint256 tokenId) internal virtual {
        require(to != address(0), "ERC721: mint to the zero address");
        require(!_exists(tokenId), "ERC721: token already minted");

        _beforeTokenTransfer(address(0), to, tokenId);

        _balances[to] += 1;
        _owners[tokenId] = to;

        emit Transfer(address(0), to, tokenId);

        _afterTokenTransfer(address(0), to, tokenId);
    }

    /**
     * @dev Internal burn function
     */
    function _burn(uint256 tokenId) internal virtual {
        address owner = ownerOf(tokenId);

        _beforeTokenTransfer(owner, address(0), tokenId);

        // Clear approvals
        _approve(address(0), tokenId);

        _balances[owner] -= 1;
        delete _owners[tokenId];

        emit Transfer(owner, address(0), tokenId);

        _afterTokenTransfer(owner, address(0), tokenId);
    }

    /**
     * @dev Internal approve function
     */
    function _approve(address to, uint256 tokenId) internal virtual {
        _tokenApprovals[tokenId] = to;
        emit Approval(ownerOf(tokenId), to, tokenId);
    }

    /**
     * @dev Check if token exists
     */
    function _exists(uint256 tokenId) internal view virtual returns (bool) {
        return _owners[tokenId] != address(0);
    }

    /**
     * @dev Check if caller is owner or approved
     */
    function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
        require(_exists(tokenId), "ERC721: operator query for nonexistent token");
        address owner = ownerOf(tokenId);
        return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender));
    }

    /**
     * @dev Hook before token transfer
     */
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {}

    /**
     * @dev Hook after token transfer
     */
    function _afterTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {}

    /**
     * @dev Check ERC721 receiver
     */
    function _checkOnERC721Received(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
    ) private returns (bool) {
        if (to.isContract()) {
            try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data) returns (bytes4 retval) {
                return retval == IERC721Receiver.onERC721Received.selector;
            } catch (bytes memory reason) {
                if (reason.length == 0) {
                    revert("ERC721: transfer to non ERC721Receiver implementer");
                } else {
                    assembly {
                        revert(add(32, reason), mload(reason))
                    }
                }
            }
        } else {
            return true;
        }
    }
}

3. Function Description

3.1 Basic Functions

  • Token information query: name, symbol, URI
  • Balance and ownership query: check token balance and ownership
  • Token existence check: verify if a token exists
  • Token URI management: get and set token metadata URI

3.2 Core Functions

  • Transfer: transfer tokens between accounts
  • Safe Transfer: transfer with receiver verification
  • Approve: authorize other addresses to transfer specific tokens
  • Set Approval For All: authorize operator for all tokens

3.3 Internal Functions

  • Mint: create new tokens
  • Burn: destroy existing tokens
  • Transfer hooks: before and after transfer operations
  • Safety checks: validate transfers and approvals

4. Security Mechanisms

4.1 Access Control

  • Owner verification
  • Approval validation
  • Operator authorization
  • Zero address check

4.2 State Management

  • Token ownership tracking
  • Balance management
  • Approval tracking
  • URI management

4.3 Safety Features

  • Safe transfer checks
  • Receiver verification
  • Existence validation
  • Reentrance protection

5. Usage Examples

5.1 Deploy Contract

javascript
const name = "My NFT";
const symbol = "MNFT";
const nft = await ERC721.deploy(name, symbol);

5.2 Mint Token

javascript
const tokenId = 1;
await nft.mint(recipient.address, tokenId);

5.3 Transfer Token

javascript
// Direct transfer
await nft.transferFrom(owner.address, recipient.address, tokenId);

// Safe transfer
await nft.safeTransferFrom(owner.address, recipient.address, tokenId);

6. Summary

The ERC721 standard implementation provides:

  • Complete NFT functionality
  • Secure token operations
  • Metadata management
  • Comprehensive approval system
  • Event tracking

Frequently Asked Questions (FAQ)

1. Basic Concepts

Q: What is ERC721? A: ERC721 is a standard for non-fungible tokens (NFTs) that defines:

  • Unique token identification
  • Ownership tracking
  • Transfer mechanisms
  • Metadata handling
  • Approval systems

Q: Why use ERC721? A: Benefits include:

  • Unique asset representation
  • Proven standard
  • Wide compatibility
  • Secure ownership
  • Rich metadata support

Q: How to handle metadata? A: Metadata handling:

javascript
// Set base URI
await nft.setBaseURI("https://api.example.com/token/");

// Get token URI
const uri = await nft.tokenURI(tokenId);

Q: How to manage approvals? A: Approval management:

  • Use approve for single token
  • Use setApprovalForAll for all tokens
  • Check approval status before transfers
  • Clear approvals after transfers

Q: What are common security risks? A: Main risks include:

  • Unauthorized transfers
  • Metadata manipulation
  • Approval exploits
  • Receiver contract issues
  • Front-running attacks

Q: How to ensure transfer safety? A: Safety measures:

  • Use safeTransferFrom
  • Verify receiver compatibility
  • Check approvals
  • Validate ownership
  • Handle errors properly

4. Best Practices

Q: What are deployment recommendations? A: Key considerations:

  • Proper initialization
  • Base URI setup
  • Access control
  • Testing verification
  • Gas optimization

Q: How to handle large collections? A: Management strategies:

  • Batch operations
  • Efficient storage
  • Metadata optimization
  • Gas usage planning
  • Event monitoring

Released under the MIT License by Vogeb.