Roadmap › Solidity & Smart Contract Development › Module: Object-Oriented Solidity
Lessons in this course (11)

6. ERC-20 and ERC-721 with OpenZeppelin

Intermediate★ Must knowLesson 6 of 11
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

contract DivakarToken is ERC20, ERC20Permit, Ownable {
    constructor(address initialOwner) ERC20("Divakar Token", "DVK") ERC20Permit("Divakar Token") Ownable(initialOwner) {
        _mint(initialOwner, 1_000_000 * 10 ** decimals());   // 18 decimals by default
    }
    function mint(address to, uint256 amount) external onlyOwner { _mint(to, amount); }
}
solidity
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";

contract CourseCertificate is ERC721, AccessControl {
    bytes32 public constant ISSUER_ROLE = keccak256("ISSUER_ROLE");
    uint256 private _nextId = 1;
    string private _base;

    constructor(string memory baseUri) ERC721("Course Certificate", "CERT") {
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _grantRole(ISSUER_ROLE, msg.sender);
        _base = baseUri;                                     // e.g. ipfs://<CID>/
    }
    function issue(address student) external onlyRole(ISSUER_ROLE) returns (uint256 id) {
        id = _nextId++;
        _safeMint(student, id);
    }
    function _baseURI() internal view override returns (string memory) { return _base; }
    function supportsInterface(bytes4 id) public view override(ERC721, AccessControl) returns (bool) {
        return super.supportsInterface(id);
    }
}

Use OpenZeppelin instead of writing token logic yourself — audited, standard-compliant. Ownable (single owner) vs AccessControl (roles) vs AccessManager (centralised permissions, OZ 5).