पाठ 11 / 25

ERC-721 NFTs

Create non-fungible tokens with metadata and safe transfers.

Unique tokens

ERC-721 defines non-fungible tokens (NFTs): each token has a unique tokenId and one owner. The core interface includes ownerOf(tokenId), balanceOf(owner), transferFrom, safeTransferFrom (which checks that a receiving contract implements onERC721Received, preventing tokens from being locked in contracts that cannot handle them), approve for a single token and setApprovalForAll for an operator such as a marketplace. The optional metadata extension provides name, symbol and tokenURI(tokenId), which points to a JSON document with a name, description, image and attributes, usually stored on IPFS or Arweave, or generated on chain. Storing images on chain is expensive, so most NFTs store only a URI; if that URI points to a centralised server, the NFT's content can change or disappear. NFTs are used for tickets, certificates and credentials, memberships, in-game items, domain names (ENS) and art. ERC-2981 standardises royalty information, but marketplaces choose whether to enforce it.

A certificate NFT

Minting with an incrementing id and per-token metadata.

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

import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {ERC721URIStorage} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

contract CourseCertificate is ERC721URIStorage, Ownable {
    uint256 private _nextId = 1;
    mapping(address => mapping(bytes32 => bool)) public hasCertificate;   // learner => course => issued

    error AlreadyIssued(address learner, bytes32 courseId);

    constructor(address issuer) ERC721("Course Certificate", "CERT") Ownable(issuer) {}

    function issue(address learner, bytes32 courseId, string calldata metadataUri)
        external
        onlyOwner
        returns (uint256 tokenId)
    {
        if (hasCertificate[learner][courseId]) revert AlreadyIssued(learner, courseId);
        hasCertificate[learner][courseId] = true;
        tokenId = _nextId++;
        _safeMint(learner, tokenId);           // checks contract receivers
        _setTokenURI(tokenId, metadataUri);    // e.g. "ipfs://bafy.../42.json"
    }
}

// Example metadata JSON at the URI:
// { "name": "Solidity Basics", "description": "Completed 2026-10-03",
//   "image": "ipfs://bafy.../cert.png",
//   "attributes": [{ "trait_type": "Grade", "value": "A" }] }

A numbered concert ticket

An ERC-20 token is like cash: any ten-rupee note is as good as another. An ERC-721 token is a numbered concert ticket: seat 42 is different from seat 43, and the ticket's details are printed on a card stored elsewhere (the token URI).

त्वरित जाँच: What does safeTransferFrom check that transferFrom does not?

  • The gas price
  • That a receiving contract implements onERC721Received, so tokens are not locked
  • The token's price
  • The sender's balance in ether
Answer

That a receiving contract implements onERC721Received, so tokens are not locked — Safe transfers prevent sending NFTs to contracts that cannot handle them.