# OpenZeppelin Contracts — The Standard Library for Solidity
When building smart contracts, you shouldn't reinvent the wheel. OpenZeppelin Contracts is the industry-standard library providing secure, audited, and gas-optimized implementations of common patterns. From ERC20 tokens to access control, upgradeable contracts to security utilities, OpenZeppelin is the foundation for thousands of production contracts securing billions of dollars.
What is OpenZeppelin Contracts?
OpenZeppelin Contracts is an open-source library of reusable smart contract components for Ethereum and EVM-compatible chains. Developed and maintained by OpenZeppelin, the leading blockchain security firm, every contract is:
- Audited by multiple security firms
- Battle-tested in production by major protocols
- Gas-optimized for efficiency
- Well-documented with comprehensive guides
- Actively maintained with security updates
The library is modular — use only what you need and compose contracts from small, focused components.
Installation
Using npm
npm install @openzeppelin/contracts
Using Foundry
forge install OpenZeppelin/openzeppelin-contracts
Add to foundry.toml:
[dependencies]
openzeppelin-contracts = "5.2.0"
Import in Your Contracts
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
Solidity finds the package in node_modules.
Token Standards
ERC20 — Fungible Tokens
ERC20 is the standard for fungible tokens (cryptocurrencies, stablecoins, governance tokens).
Basic ERC20 Token:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MyToken is ERC20 {
constructor(uint256 initialSupply) ERC20("MyToken", "MTK") {
_mint(msg.sender, initialSupply);
}
}
That's it! You now have a fully functional ERC20 token with:
totalSupply()
balanceOf(address)
transfer(address, uint256)
approve(address, uint256)
transferFrom(address, address, uint256)
allowance(address, address)
Adding Minting and Burning:
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyToken is ERC20, Ownable {
constructor() ERC20("MyToken", "MTK") Ownable(msg.sender) {}
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
function burn(uint256 amount) public {
_burn(msg.sender, amount);
}
}
Adding Pausable:
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyToken is ERC20, Pausable, Ownable {
constructor() ERC20("MyToken", "MTK") Ownable(msg.sender) {}
function pause() public onlyOwner {
_pause();
}
function unpause() public onlyOwner {
_unpause();
}
function _update(address from, address to, uint256 value)
internal
override
whenNotPaused
{
super._update(from, to, value);
}
}
ERC721 — Non-Fungible Tokens (NFTs)
ERC721 is the standard for unique, non-fungible tokens (NFTs).
Basic NFT:
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyNFT is ERC721, Ownable {
uint256 private _tokenIdCounter;
constructor() ERC721("MyNFT", "MNFT") Ownable(msg.sender) {}
function mint(address to) public onlyOwner {
uint256 tokenId = _tokenIdCounter++;
_safeMint(to, tokenId);
}
}
Adding Metadata URI:
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
contract MyNFT is ERC721URIStorage, Ownable {
uint256 private _tokenIdCounter;
constructor() ERC721("MyNFT", "MNFT") Ownable(msg.sender) {}
function mint(address to, string memory uri) public onlyOwner {
uint256 tokenId = _tokenIdCounter++;
_safeMint(to, tokenId);
_setTokenURI(tokenId, uri);
}
}
Adding Enumerable:
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
contract MyNFT is ERC721Enumerable, Ownable {
constructor() ERC721("MyNFT", "MNFT") Ownable(msg.sender) {}
// Now you get:
// - totalSupply()
// - tokenByIndex(uint256 index)
// - tokenOfOwnerByIndex(address owner, uint256 index)
}
ERC1155 — Multi-Token Standard
ERC1155 supports both fungible and non-fungible tokens in a single contract.
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyMultiToken is ERC1155, Ownable {
uint256 public constant GOLD = 0;
uint256 public constant SILVER = 1;
uint256 public constant SWORD = 2;
constructor() ERC1155("https://game.example/api/item/{id}.json") Ownable(msg.sender) {}
function mint(address to, uint256 id, uint256 amount) public onlyOwner {
_mint(to, id, amount, "");
}
function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts) public onlyOwner {
_mintBatch(to, ids, amounts, "");
}
}
Access Control
Ownable — Single Owner
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyContract is Ownable {
constructor() Ownable(msg.sender) {}
function restrictedFunction() public onlyOwner {
// Only owner can call this
}
function transferOwnership(address newOwner) public override onlyOwner {
super.transferOwnership(newOwner);
}
}
AccessControl — Role-Based Access
For multi-role systems (admin, minter, pauser, etc.):
import "@openzeppelin/contracts/access/AccessControl.sol";
contract MyToken is AccessControl {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
constructor() {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(MINTER_ROLE, msg.sender);
}
function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {
_mint(to, amount);
}
function pause() public onlyRole(PAUSER_ROLE) {
_pause();
}
}
Ownable2Step — Safer Ownership Transfer
Prevents accidental ownership transfer to the wrong address:
import "@openzeppelin/contracts/access/Ownable2Step.sol";
contract MyContract is Ownable2Step {
constructor() Ownable(msg.sender) {}
// Owner calls transferOwnership(newOwner)
// New owner must call acceptOwnership() to confirm
}
Security Utilities
ReentrancyGuard — Prevent Reentrancy Attacks
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
contract Bank is ReentrancyGuard {
mapping(address => uint256) public balances;
function withdraw(uint256 amount) public nonReentrant {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount;
(bool success, ) = msg.sender.call{value: amount}("");
require(success);
}
}
The nonReentrant modifier prevents reentrant calls during execution.
Pausable — Emergency Stop
import "@openzeppelin/contracts/utils/Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyContract is Pausable, Ownable {
constructor() Ownable(msg.sender) {}
function criticalFunction() public whenNotPaused {
// This can't be called when paused
}
function pause() public onlyOwner {
_pause();
}
function unpause() public onlyOwner {
_unpause();
}
}
SafeERC20 — Safe Token Transfers
Some tokens don't follow the ERC20 standard correctly (USDT doesn't return bool). SafeERC20 handles these edge cases:
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
contract Vault {
using SafeERC20 for IERC20;
function deposit(IERC20 token, uint256 amount) public {
token.safeTransferFrom(msg.sender, address(this), amount);
// Reverts if transfer fails, even if token doesn't return bool
}
}
Upgradeable Contracts
OpenZeppelin provides upgradeable versions of all contracts using the proxy pattern.
Installation
npm install @openzeppelin/contracts-upgradeable @openzeppelin/hardhat-upgrades
Upgradeable ERC20
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
contract MyTokenUpgradeable is Initializable, ERC20Upgradeable, OwnableUpgradeable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize(uint256 initialSupply) public initializer {
__ERC20_init("MyToken", "MTK");
__Ownable_init(msg.sender);
_mint(msg.sender, initialSupply);
}
}
Key differences:
- Inherit from
*Upgradeableversions
- Use
initialize()instead ofconstructor()
- Call
__ContractName_init()in initializer
- Disable initializers in constructor
Deploying Upgradeable Contracts
With Hardhat:
const { ethers, upgrades } = require("hardhat");
async function main() {
const MyToken = await ethers.getContractFactory("MyTokenUpgradeable");
const token = await upgrades.deployProxy(MyToken, [1000000], {
initializer: "initialize"
});
await token.waitForDeployment();
console.log("Token deployed to:", await token.getAddress());
}
Upgrading:
const MyTokenV2 = await ethers.getContractFactory("MyTokenUpgradeableV2");
const upgraded = await upgrades.upgradeProxy(proxyAddress, MyTokenV2);
Utilities
Cryptography
ECDSA signature verification:
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
contract SignatureVerifier {
using ECDSA for bytes32;
using MessageHashUtils for bytes32;
function verify(address signer, bytes32 messageHash, bytes memory signature) public pure returns (bool) {
bytes32 ethSignedHash = messageHash.toEthSignedMessageHash();
address recoveredSigner = ethSignedHash.recover(signature);
return recoveredSigner == signer;
}
}
Merkle proofs:
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
contract Whitelist {
bytes32 public merkleRoot;
constructor(bytes32 _merkleRoot) {
merkleRoot = _merkleRoot;
}
function claim(bytes32[] calldata proof) public {
bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
require(MerkleProof.verify(proof, merkleRoot, leaf), "Invalid proof");
// Grant access
}
}
Counters (Deprecated, use native uint256)
In older versions, Counters was used for auto-incrementing IDs. Modern Solidity can do this natively:
contract NFT {
uint256 private _tokenIdCounter; // Cheaper than Counters
function mint() public {
uint256 tokenId = _tokenIdCounter++;
_safeMint(msg.sender, tokenId);
}
}
Math Utilities
import "@openzeppelin/contracts/utils/math/Math.sol";
contract Calculator {
function average(uint256 a, uint256 b) public pure returns (uint256) {
return Math.average(a, b); // Prevents overflow
}
function sqrt(uint256 x) public pure returns (uint256) {
return Math.sqrt(x);
}
}
Best Practices
1. Use Latest Stable Version
npm install @openzeppelin/contracts@latest
Check releases at github.com/OpenZeppelin/openzeppelin-contracts/releases
2. Understand What You Import
Don't blindly inherit contracts. Read the source code to understand behavior, especially for:
_update()hooks in tokens
- Virtual functions you can override
- Internal functions you can call
3. Override Carefully
When overriding functions, always call super:
function _update(address from, address to, uint256 value) internal override {
super._update(from, to, value); // Call parent implementation
// Your custom logic
}
4. Compose, Don't Modify
Instead of modifying OpenZeppelin code, compose contracts:
// Good
contract MyToken is ERC20, Ownable, Pausable {
// Compose existing contracts
}
// Bad - modifying OZ internals
contract MyToken is ERC20 {
function transfer() public override {
// Rewriting transfer logic = dangerous
}
}
5. Check for Updates
Subscribe to OpenZeppelin security advisories and update regularly.
Wizard — Generate Contracts Visually
OpenZeppelin Contracts Wizard generates ready-to-deploy contracts:
Visit wizard.openzeppelin.com
The wizard is perfect for:
- Learning which contracts to combine
- Bootstrapping new projects
- Understanding contract composition
Conclusion
OpenZeppelin Contracts is the foundation for secure smart contract development. By leveraging battle-tested, audited implementations, you avoid common vulnerabilities and focus on your unique business logic. From simple tokens to complex governance systems, OpenZeppelin provides the building blocks you need.
Start building with confidence — install OpenZeppelin Contracts and explore the library. Then sharpen your skills with Solingo's smart contract challenges to master secure development patterns.
Next steps:
- Deploy your first ERC20 token using OpenZeppelin
- Explore the AccessControl pattern for multi-role systems
- Learn about upgradeable contracts and the proxy pattern
- Read the OpenZeppelin documentation at docs.openzeppelin.com