Interfaces & Libraries
Solidity interfaces: contract APIs without implementation. The standards (ERC-20, ERC-721, ERC-1155) and how to design your own.
Web3 — Solidity interfaces
EXAMPLE
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
// ===== Interface basics =====
interface IGreeter {
function greet(address who) external view returns (string memory);
event Greeted(address indexed who);
}
contract Greeter is IGreeter {
function greet(address who) external pure returns (string memory) {
// pure: no state read or write
return "hello";
}
}
// ===== Why interfaces =====
// 1. Standards: ERC-20 / ERC-721 / ERC-1155 are interfaces
// 2. Composition: import only the interface; call any deployed contract that implements it
// 3. Testing: mock contracts conform to the interface
// 4. Smaller bytecode: interfaces have no implementation
// ===== ERC-20 (fungible tokens) =====
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
// Implement using OpenZeppelin:
// import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
// contract MyToken is ERC20 { constructor() ERC20("My", "MY") {} }
// ===== ERC-721 (non-fungible tokens) =====
interface IERC721 {
function balanceOf(address owner) external view returns (uint256);
function ownerOf(uint256 tokenId) external view returns (address);
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function transferFrom(address from, address to, uint256 tokenId) external;
function approve(address to, uint256 tokenId) external;
function setApprovalForAll(address operator, bool approved) external;
function getApproved(uint256 tokenId) external view returns (address);
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
// ===== ERC-1155 (multi-token) =====
// Supports both fungible + non-fungible IDs in one contract; gas-efficient batch ops.
// ===== ERC-165 (interface detection) =====
interface IERC165 {
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
// Use to check if a contract implements a given interface BEFORE calling it.
// ===== Designing your own =====
interface ISubscription {
function isActive(address user) external view returns (bool);
function subscribe(uint256 tierId) external payable;
function cancel() external;
event Subscribed(address indexed user, uint256 indexed tierId);
event Cancelled(address indexed user);
}
// Implementers can vary internals; consumers depend on the interface.
// ===== Visibility rules =====
// In interfaces, all functions MUST be external (callable from outside).
// They cannot have state variables, modifiers, or constructors.
// Events ARE allowed.
// ===== Calling external contracts via interface =====
contract Vault {
function depositTo(IERC20 token, address to, uint256 amount) external {
// Pull tokens from the caller (must have approved this contract first)
token.transferFrom(msg.sender, to, amount);
}
}
// ===== Patterns to internalise =====
// - Standard interfaces (ERC-20/721/1155) wherever applicable
// - Small custom interfaces for your contracts; consumers depend on them
// - ERC-165 supportsInterface for runtime checks
// - OpenZeppelin implementations as the default (audited + battle-tested)
// ===== Pitfalls =====
// - Reinventing ERC-20 / 721 instead of using OpenZeppelin
// - Forgetting that interface functions are external (cannot be called internally without 'this.')
// - Calling unverified contracts via interface assumes they behave; check supportsInterface
// - Skipping events on state-changing functions (no indexers can catch up)
Why it matters
Solidity interfaces are the API contracts of Web3. Use the standards (ERC-20, ERC-721, ERC-1155) where you can, OpenZeppelin implementations for audited code, and your own small interfaces for composition. Interfaces enable mocking, indexing, and cross-contract calls — and keep the bytecode small.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
interface IERC20 {
function transfer(address to, uint256 amount) external returns (bool);
function balanceOf(address owner) external view returns (uint256);
}
library Math {
function max(uint256 a, uint256 b) internal pure returns (uint256) { return a > b ? a : b; }
}
Try it Yourself »
Discussion
Loading…