IERC20Metadata.sol
The metadata extension interface adding name(), symbol(), and decimals() to ERC-20. Every line explained.
Because developers of the future should know the code they interact with.
The path token/ERC20/extensions/IERC20Metadata.sol tells you this is an extension of the base ERC-20 standard. The "I" prefix marks it as an interface, not a concrete implementation.
^0.8.20 matches all other contracts in the EffortToken compilation. Interfaces don't use any Solidity features beyond basic declarations, so they could technically work with much older compilers — but OpenZeppelin keeps them aligned for consistency.
Imports the base ERC-20 interface from one directory up (../). This import enables the 'is IERC20' inheritance on line 11, making IERC20Metadata a superset of IERC20. Any contract implementing IERC20Metadata automatically satisfies the IERC20 type as well.
An Interface That Inherits an Interface
IERC20Metadata lives in the extensions/ subdirectory, indicating it's an add-on to the base ERC-20 standard. The key import is IERC20 — this interface inherits from the base interface, meaning any contract that implements IERC20Metadata must ALSO implement all of IERC20's functions (transfer, approve, transferFrom, balanceOf, totalSupply, allowance). This is interface composition: building richer interfaces by layering on top of simpler ones.
The import path "../IERC20.sol" reveals the file structure: IERC20Metadata.sol sits one directory deeper than IERC20.sol (in extensions/). This pattern is how OpenZeppelin organizes optional features — the base standard lives at the root, extensions live in subdirectories. When ERC20.sol declares `is IERC20Metadata`, it's promising to implement both the metadata functions AND all of IERC20's functions.
Interface inheritance is purely a type-checking mechanism — it doesn't add any code or storage. It simply tells the Solidity compiler: "any contract implementing IERC20Metadata must also satisfy the IERC20 interface." This is enforced at compile time, not at runtime.
- ▸The file path extensions/IERC20Metadata.sol follows OpenZeppelin's convention: base standards at the root, optional extensions in subdirectories.
- ▸Interface inheritance (is IERC20) creates a type hierarchy. A variable of type IERC20Metadata can be used anywhere an IERC20 is expected — it's a superset.
- ▸This file compiles to zero bytecode — interfaces are pure type declarations.
- ✗Thinking that importing IERC20 adds code to this interface — interfaces have no bytecode, no storage, no runtime cost.
- ✗Confusing interface inheritance with contract inheritance — interface inheritance adds type constraints, contract inheritance adds actual code.
- Interface Inheritance
- When an interface uses 'is' to extend another interface. The child interface includes all function declarations from the parent. Implementors must provide bodies for ALL functions from both interfaces.
- extensions/
- OpenZeppelin's directory convention for optional features that build on top of a base standard. Keeps the base standard clean while providing commonly-needed extras.
IERC20Metadata started life as an entirely separate EIP proposal. The original ERC-20 standard (EIP-20) only required transfer(), approve(), transferFrom(), balanceOf(), totalSupply(), and allowance(). Name, symbol, and decimals were listed as "optional" methods. But every single token in practice implemented them, so OpenZeppelin formalized them into a dedicated interface.
Short and direct: "Interface for the optional metadata functions from the ERC20 standard." The word "optional" is technically accurate per EIP-20 but practically misleading — these functions are universally expected by all ERC-20 tooling.
Declares a new interface that extends IERC20. This single line means: any contract claiming to implement IERC20Metadata must implement ALL of IERC20's 6 functions PLUS the 3 metadata functions declared below. ERC20.sol satisfies this by declaring 'is IERC20Metadata' in its inheritance list.
- ▸IERC20 brings: transfer, approve, transferFrom, balanceOf, totalSupply, allowance
- ▸IERC20Metadata adds: name, symbol, decimals
- ▸Total: 9 function signatures that implementing contracts must provide
"Optional" Metadata — That Every Token Needs
The NatSpec calls these "optional metadata functions from the ERC20 standard." The word "optional" is technically correct — EIP-20 doesn't require them. But in practice, every ERC-20 token implements name(), symbol(), and decimals() because wallets, exchanges, and DeFi protocols depend on them. OpenZeppelin made them a formal interface so the compiler can enforce their presence. The declaration `interface IERC20Metadata is IERC20` means: this interface includes everything from IERC20 PLUS three additional functions.
This small interface is the reason MetaMask can display " EFFORT" next to your balance instead of just a raw address. Without IERC20Metadata, wallets would have to hardcode token names or use external registries. By standardizing these three functions in an interface, any wallet can call name(), symbol(), and decimals() on any compliant token and get consistent results.
Malicious tokens can implement this interface with misleading values — returning name="USD Coin" and symbol="USDC" on a scam token. Never trust token metadata alone for identity verification. Always verify the contract address against official sources.
- ▸The keyword 'interface' (not 'abstract contract') means: no constructor, no state variables, no function bodies — only function signatures.
- ▸All functions in an interface are implicitly virtual and must be overridden by implementing contracts.
- ▸The 'is IERC20' clause is what makes this a superset — implementing IERC20Metadata requires implementing all 9 functions (6 from IERC20 + 3 metadata).
- ✗Thinking "optional" means tokens don't need these functions — virtually every production token implements them.
- ✗Implementing IERC20Metadata without implementing IERC20's functions — the compiler will reject this because of the interface inheritance.
- interface
- A Solidity type containing only function declarations (no bodies), event declarations, and error declarations. Cannot have state variables, constructors, or implemented functions. All functions are implicitly external and virtual.
- EIP-20 Optional Methods
- The original ERC-20 specification listed name(), symbol(), and decimals() as OPTIONAL. In practice they became universally expected, leading OpenZeppelin to formalize them in IERC20Metadata.
The decision to make name/symbol/decimals "optional" in EIP-20 was controversial even in 2015. Fabian Vogelsteller (EIP-20 author) wanted to keep the standard minimal. The community argued that tokens without names are unusable in wallets. The compromise was labeling them "optional" — and then everyone implemented them anyway.
Returns the full human-readable name of the token. For EffortToken, this returns "EffortToken". Declared as external view returning string memory. The implementation in ERC20.sol simply returns the private _name variable set in the constructor.
- ▸Called by wallets to display the token name in portfolio views
- ▸Called by block explorers to show the token name on transaction pages
- ▸Returns a dynamic string — no length limit enforced by the interface
Returns the short symbol/ticker for the token. For EffortToken, this returns " EFFORT". By convention, symbols are uppercase and 3-5 characters, but the interface enforces no constraints — it's just a string.
- ▸Displayed next to balances in wallets: "50.0 EFFORT"
- ▸Used as the trading pair identifier on exchanges: EFFORT/ETH
- ▸Convention is uppercase, but not enforced — some tokens use lowercase or emoji
Returns the number of decimal places for human-readable formatting. Returns uint8 (not uint256) because 255 is more than enough precision. For EffortToken (and most tokens), this returns 18. The closing brace ends the entire interface.
- ▸18 decimals: 1 token = 1000000000000000000 raw units (like ETH/Wei)
- ▸6 decimals: 1 token = 1000000 raw units (like USDC)
- ▸Your frontend divides raw balances by 10^decimals to display human amounts
- ▸Has ZERO effect on the contract's arithmetic — purely a frontend formatting hint
Three Functions That Give Tokens Their Identity
These three function declarations define the complete metadata interface for ERC-20 tokens. name() returns the full token name (e.g., "EffortToken"). symbol() returns the trading ticker (e.g., " EFFORT"). decimals() returns the number of decimal places for display formatting (e.g., 18). All three are external view — they only read data, never modify state, and are called from outside the contract. The implementations live in ERC20.sol; here we only see the signatures.
Every time you see a token in MetaMask, Etherscan, or a DeFi dashboard, these three functions are being called. name() provides the full display name. symbol() provides the ticker shown next to balances. decimals() tells the frontend how to format raw uint256 amounts into human-readable numbers. Without these, tokens would be nameless numbers at cryptic addresses.
These functions return arbitrary strings/numbers set by the token deployer. They provide NO guarantee of uniqueness or accuracy. Multiple tokens can have the same name and symbol. The only reliable identifier for a token is its contract address.
- ▸All three use 'external' visibility (not 'public') because interfaces can only declare external functions. When ERC20.sol implements them, it upgrades them to 'public' which satisfies the interface requirement.
- ▸name() and symbol() return 'string memory' — dynamically-sized strings allocated in memory. This costs more gas than returning bytes32, but allows arbitrary-length names.
- ▸decimals() returns uint8 — a single byte, max value 255. The most common values are 18 (standard), 6 (USDC/USDT), and 8 (WBTC).
- ✗Hardcoding decimals=18 in your frontend without checking — USDC uses 6, WBTC uses 8. Always call decimals() for each token.
- ✗Using name() or symbol() for token identification or comparison — use the contract address instead. Names and symbols can collide and can be misleading.
- ✗Thinking decimals() affects the contract's math — it's purely a display hint. The contract always operates on raw integer values.
- external
- The most restrictive function visibility for calls. External functions can only be called from outside the contract (or via this.functionName()). They cannot be called internally without 'this'. More gas-efficient than 'public' for functions that accept calldata.
- view
- A function modifier guaranteeing the function reads state but never modifies it. View functions cost zero gas when called from off-chain (e.g., from your frontend via wagmi).
- string memory
- A dynamically-sized UTF-8 string stored in memory (not storage). The 'memory' keyword indicates the string is created fresh for each call and not persisted between calls.
Some creative tokens use name() and symbol() dynamically. For example, a token called "Unisocks" (SOCKS) changes its symbol based on the current price. Another token returns a different name() depending on the block number. While unusual, the interface allows this because it returns a computed value, not necessarily a stored constant.
IERC20Metadata.sol — OpenZeppelin v5.0.0 on OP Sepolia
View on Etherscan