IERC6093.sol
EIP-6093 standardized custom errors inherited by EffortToken. Every error explained.
Because developers of the future should know the code they interact with.
SPDX-MIT license and the OpenZeppelin version comment. The path interfaces/draft-IERC6093.sol tells you this is an interface file and the standard is still in draft. The "I" prefix is a naming convention borrowed from C#/Java indicating this is an interface, not a concrete contract.
- ▸MIT license allows unrestricted use, modification, and distribution
- ▸The "draft-" prefix will be removed if/when EIP-6093 reaches Final status in the EIP process
- ▸This is the same v5.0.0 used by all other contracts in the EffortToken dependency tree
Same ^0.8.20 pragma as EffortToken and Ownable. All files in a compilation unit must be compatible with the same compiler version. Custom errors (used throughout this file) require Solidity 0.8.4+, so the 0.8.20 floor is more than sufficient.
Standardized Errors: Why EIP-6093 Exists
EIP-6093 defines standardized custom error types for token contracts. Before this standard, every project invented its own error messages (or worse, used raw require strings), making it impossible for wallets, block explorers, and debugging tools to provide consistent error decoding. This file lives in OpenZeppelin's interfaces/ directory and carries a "draft" prefix because EIP-6093 was still in draft status when v5.0.0 shipped. The interface keyword means these are pure declarations with no implementation code — they define WHAT errors exist, not WHEN they are thrown.
Standardized errors are the foundation of a good developer experience. When EffortToken reverts with ERC20InsufficientBalance, any wallet (MetaMask, Rabby, etc.) or block explorer can decode that error into a human-readable message without needing the contract's ABI. Before EIP-6093, a transfer() failure might show as a raw hex revert, leaving users and developers guessing.
The "draft" status does NOT mean the code is unsafe — OpenZeppelin shipped it as production-ready in v5.0.0. "Draft" in EIP terms means the standard is still open for community feedback, not that the implementation is experimental. The code itself is fully audited.
- ▸Custom errors were introduced in Solidity 0.8.4 — they cost roughly 50 gas less than require("string message") because they don't store the string in bytecode.
- ▸The file path interfaces/draft-IERC6093.sol follows OpenZeppelin's naming convention: "I" prefix for interfaces, "draft-" prefix for standards still in draft.
- ▸This is one of the few OpenZeppelin files with zero executable code — it's pure type definitions that other contracts inherit.
- ✗Defining your own custom errors for standard operations like insufficient balance — use the EIP-6093 errors instead so tooling can decode them.
- ✗Thinking "draft" means unstable or untested — in EIP terminology it just means the standard hasn't been finalized, but the code is production-quality.
- EIP-6093
- An Ethereum Improvement Proposal that standardizes custom error types for ERC-20, ERC-721, and ERC-1155 tokens. Provides consistent error signatures across all conforming contracts.
- Custom Error
- A Solidity feature (since 0.8.4) that replaces require() string messages with structured, gas-efficient error types. Errors are encoded as 4-byte selectors plus ABI-encoded parameters.
- Interface
- A Solidity construct containing only function signatures, error declarations, or event declarations — no state variables, no implementations, no constructors.
Before EIP-6093, a failed ERC-20 transfer might show as "execution reverted: ERC20: transfer amount exceeds balance" (a raw string costing ~200+ gas per character in bytecode) or just raw hex. EIP-6093 replaced all of these with structured errors that cost less gas AND are machine-readable.
The documentation block links directly to the EIP-6093 specification page. The NatSpec uses Solidity's documentation comment style (/** */), and the link syntax https://...[ERC-6093] is a standard NatSpec URL reference format.
- ▸The link points to the official EIP page on ethereum.org
- ▸"Standard" in the title means these errors are the CANONICAL set — all EIP-6093 compliant tokens use these exact definitions
Declares a Solidity interface containing only error declarations. Interfaces cannot have state variables or function implementations. The "I" prefix is a naming convention indicating this is an interface, not a concrete contract. ERC20.sol inherits this to get all 6 error types.
- ▸Interfaces compile to zero bytecode — they only define type signatures
- ▸Any contract that is IERC20Errors can use these error types in revert statements
- ▸EffortToken gets these through its ERC20 inheritance chain: EffortToken → ERC20 → IERC20Errors
Thrown when a transfer or transferFrom is attempted with an amount exceeding the sender's balance. The three parameters give full context: WHO tried to send, HOW MUCH they have, and HOW MUCH was needed. This is the error you'll see most often when testing EffortToken.
- ▸Triggered in ERC20._update() when the from-address balance is less than the amount
- ▸In EffortToken: try to transfer 100 EFFORT when you only have 50, and you'll get ERC20InsufficientBalance(yourAddress, 50, 100)
- ▸The "needed" parameter is the total amount required, not the shortfall
Thrown when the "from" address in a transfer is the zero address (address(0)). This prevents minting from being disguised as a transfer. The ERC-20 standard treats transfers from address(0) as invalid — minting has its own separate logic.
- ▸Guards against: transfer(from: address(0), to: someAddress, amount)
- ▸This is NOT about unauthorized senders — it's specifically about the zero address
- ▸Minting uses _mint() which explicitly handles the from=address(0) case separately
Thrown when the "to" address in a transfer is the zero address. This prevents accidental token burning via transfer. If you want to burn tokens, use the explicit _burn() function instead.
- ▸Guards against: transfer(from: someAddress, to: address(0), amount)
- ▸Prevents accidental loss: sending tokens to address(0) is irreversible
- ▸EffortToken's spendTokensForContent uses _burn() which handles the to=address(0) case explicitly
Thrown when transferFrom() is called but the spender hasn't been approved for enough tokens. Parameters show the spender, their current allowance, and the amount they tried to spend. This is the second most common ERC-20 error after InsufficientBalance.
- ▸Triggered when: allowance[owner][spender] < amount in transferFrom()
- ▸Fix: the token owner must call approve(spender, amount) first
- ▸The "needed" parameter is the full transfer amount, not just the shortfall beyond the current allowance
Thrown when the address calling approve() is the zero address. This is a sanity check that prevents approval records from being created with an invalid owner.
- ▸Catches edge cases in internal _approve() calls where the owner parameter is address(0)
- ▸In normal usage, the approver is msg.sender and this error rarely fires
- ▸Different from InvalidSender — this is about approval, not transfer
Thrown when approve() is called with the spender set to the zero address. You cannot approve the zero address to spend tokens — it would create a meaningless allowance record.
- ▸Guards: approve(spender: address(0), amount) from succeeding
- ▸Prevents wasted gas on approvals that could never be used (no one controls address(0))
- ▸This is the approve-side counterpart to ERC20InvalidReceiver on the transfer side
Six errors cover every ERC-20 failure mode. Two for balance/allowance insufficiency (with amounts), two for invalid sender/receiver (transfer guards), and two for invalid approver/spender (approval guards). This symmetry is intentional — the EIP was designed so every operation has a matching error.
The Six Errors Your ERC-20 Token Can Throw
IERC20Errors defines exactly 6 custom errors covering every failure mode in the ERC-20 standard: insufficient balance on transfer, invalid sender (zero address), invalid receiver (zero address), insufficient allowance on transferFrom, invalid approver, and invalid spender. EffortToken's ERC20 base contract inherits all 6 of these. When you call transfer() and don't have enough tokens, the contract reverts with ERC20InsufficientBalance(yourAddress, yourBalance, amountNeeded) — giving you structured, decodable failure data.
These are the errors you will actually encounter when building dApps. Every failed transfer(), transferFrom(), or approve() on EffortToken reverts with one of these 6 errors. Understanding them is essential for building frontend error handling. With these standardized errors, wagmi's error decoding can automatically show users "You tried to send 100 EFFORT but only have 50" instead of a raw hex revert.
The error parameters intentionally expose internal state (like the user's current balance and the required amount). This is safe because all of this data is already public on the blockchain. The parameters are designed for debugging, not secrecy.
- ▸In your frontend, decode these errors with viem's decodeErrorResult or wagmi's built-in error handling — the 4-byte selector uniquely identifies each error.
- ▸Each error is named with an ERC20 prefix, making it impossible to confuse with ERC-721 or ERC-1155 errors even in multi-token contracts.
- ▸The parameter names (sender, balance, needed) match the EIP specification exactly — they serve as self-documentation.
- ▸InsufficientBalance and InsufficientAllowance both include the current amount AND the required amount, so users can see exactly how much they're short.
- ✗Not handling ERC20InsufficientAllowance separately from ERC20InsufficientBalance in your UI — they require different user actions (approve vs. acquire more tokens).
- ✗Confusing ERC20InvalidSender with ERC20InvalidApprover — InvalidSender is about transfer-from addresses, InvalidApprover is about who's calling approve().
- ✗Ignoring the error parameters and showing generic "transaction failed" — the whole point of EIP-6093 is to provide actionable error data.
- ERC20InsufficientBalance
- Thrown when transfer() or transferFrom() is called but the sender's balance is less than the amount. Parameters: sender address, current balance, needed amount.
- ERC20InsufficientAllowance
- Thrown when transferFrom() is called but the spender's allowance from the token owner is insufficient. Parameters: spender address, current allowance, needed amount.
- 4-byte selector
- The first 4 bytes of the keccak256 hash of the error signature. Used by EVM clients to identify which error was thrown. Each of the 21 errors in this file has a unique selector.
ERC20InsufficientBalance is probably the single most-triggered custom error in all of Ethereum. Every time anyone on any EIP-6093-compliant token tries to send more than they have, this exact error fires. On mainnet alone, it happens thousands of times per day.
Same documentation pattern as IERC20Errors, linking to the EIP-6093 specification. The comment explicitly says "ERC721 tokens" to differentiate from the ERC-20 and ERC-1155 sections.
Eight error declarations for all ERC-721 failure modes. Two more than ERC-20 because NFTs need per-token-ID error reporting and have the concept of operators (from setApprovalForAll).
Thrown when an NFT operation would result in the zero address becoming an owner. Similar to ERC20InvalidSender but specific to NFT ownership context.
- ▸Used in _mint() and _transfer() when the intended owner is address(0)
- ▸NOT about checking if someone IS the owner — that's IncorrectOwner
Thrown when referencing a tokenId that hasn't been minted or has been burned. This error has no ERC-20 equivalent because fungible tokens don't have individual IDs.
- ▸Common in NFT marketplaces when a token is burned between listing and purchase
- ▸The uint256 tokenId tells you exactly which token was looked up and not found
Thrown when the caller claims to own a specific token but doesn't. Three parameters provide full context: who tried (sender), which token (tokenId), and who actually owns it (owner).
- ▸The most parameter-rich error in the ERC-721 set — three pieces of data
- ▸Enables frontend messages like: "Token #42 is owned by 0xABC, not by you"
Same zero-address guards as the ERC-20 equivalents, but for NFT transfers. InvalidSender prevents transfers from address(0); InvalidReceiver prevents transfers to address(0).
- ▸Mirror the ERC-20 pattern exactly — consistent naming across standards
Thrown when an operator tries to transfer an NFT they haven't been approved for. Unlike ERC-20's allowance-based system, NFT approval is per-token or per-operator, so the parameters are operator and tokenId (no "amount").
- ▸Two ways to get approved: approve(operator, tokenId) for one NFT, or setApprovalForAll(operator, true) for all
- ▸No "amount" parameter because NFTs are indivisible — either you're approved or you're not
Two approval-side guards. InvalidApprover catches approve() from address(0). InvalidOperator catches setApprovalForAll() with an invalid operator address.
- ▸InvalidOperator is unique to ERC-721 — ERC-20 has no equivalent because it doesn't have the operator/setApprovalForAll concept
Eight errors covering ownership, existence, transfers, and approvals. The tokenId parameter threading is the key differentiator from ERC-20 errors.
NFT Errors: Token IDs Change Everything
IERC721Errors defines 8 custom errors for ERC-721 (NFT) operations. The key difference from ERC-20 errors is the presence of tokenId parameters — because NFTs are non-fungible, errors must identify WHICH specific token is involved. New error types like NonexistentToken and IncorrectOwner exist because NFTs have individual ownership tracking that fungible tokens don't need.
Even though EffortToken is an ERC-20, understanding ERC-721 errors builds a complete mental model of the token error ecosystem. Many dApps combine fungible and non-fungible tokens (e.g., an NFT marketplace that uses ERC-20 for payments). EIP-6093 ensures ALL token errors across ALL standards follow the same conventions, so your frontend error-handling code can be reused.
ERC721IncorrectOwner takes three parameters (sender, tokenId, owner) — this is critical for frontends to distinguish between "you don't own this NFT" and "this NFT doesn't exist at all" (NonexistentToken). Confusing these two in your UI can mislead users.
- ▸Notice the pattern: ERC-721 has 8 errors vs ERC-20's 6. The extra two (InvalidOwner and NonexistentToken) exist because NFTs have per-token ownership that fungible tokens lack.
- ▸ERC721InsufficientApproval replaces ERC20InsufficientAllowance — NFT approvals are per-token, not per-amount.
- ▸InvalidOperator is specific to ERC-721's setApprovalForAll — the equivalent concept doesn't exist in ERC-20.
- ✗Confusing ERC721InvalidOwner (owner is zero address) with ERC721IncorrectOwner (caller expected to own the token but doesn't).
- ✗Not checking for NonexistentToken before displaying NFT data — querying a burned or unminted tokenId triggers this error.
- ERC-721
- The standard for non-fungible tokens (NFTs). Each token has a unique tokenId and exactly one owner.
- NonexistentToken
- Thrown when an operation references a tokenId that hasn't been minted or has been burned.
- setApprovalForAll
- An ERC-721 function that approves an operator to manage ALL of an owner's NFTs, unlike single-token approve().
The ERC721IncorrectOwner error with its three parameters (sender, tokenId, owner) was one of the most debated additions in EIP-6093. The community argued about whether to include the actual owner in the error data — ultimately they decided the debugging benefit outweighed any privacy concern, since ownership is public on-chain anyway.
Third and final NatSpec block, linking to the same EIP-6093 spec. ERC-1155 is the newest of the three token standards, finalized in 2018.
Seven errors for the most complex token standard. The error count (7) falls between ERC-20 (6) and ERC-721 (8), but includes the unique InvalidArrayLength for batch validation.
Like ERC20InsufficientBalance but with a fourth parameter: tokenId. In ERC-1155, each token ID has its own balance, so the error must specify WHICH token's balance is insufficient. Parameters: sender, current balance, needed amount, token ID.
- ▸This is the only 4-parameter error in all of EIP-6093
- ▸In a game: "You have 3 of Sword#42 but tried to send 5" → ERC1155InsufficientBalance(you, 3, 5, 42)
Same zero-address guards as ERC-20 and ERC-721. The consistency across all three standards is intentional — tooling can handle all three with the same pattern matching on the error prefix.
Thrown when an operator tries to transfer tokens without setApprovalForAll permission. Unlike ERC-721's per-token InsufficientApproval, ERC-1155 only has the blanket "all tokens" approval model.
- ▸Parameters: (operator, owner) — tells you WHO tried and WHOSE tokens they tried to move
- ▸No tokenId parameter because the approval covers ALL token IDs
- ▸Named "Missing" instead of "Insufficient" because approval is boolean (yes/no), not an amount
Approval-side zero-address guards. Same pattern as ERC-721 — prevents setting approvals with invalid addresses.
The most unique error in all of EIP-6093. Thrown when batch functions (safeBatchTransferFrom, etc.) receive ids[] and values[] arrays of different lengths. This is a structural validation error — not about balances or permissions, but about input correctness.
- ▸Parameters: idsLength and valuesLength — shows the mismatch directly
- ▸Example: passing 3 token IDs but 5 amounts → ERC1155InvalidArrayLength(3, 5)
- ▸This error catches bugs at the contract boundary, before any state is modified
- ▸No equivalent exists in ERC-20 (no batching) or ERC-721 (batching isn't in the base standard)
The file is complete. Three interfaces, 21 total custom errors, covering every failure mode across ERC-20, ERC-721, and ERC-1155. This is the entire error vocabulary of the token ecosystem.
Multi-Token Errors: When One Standard Handles Both
IERC1155Errors defines 7 custom errors for ERC-1155, the multi-token standard that can represent both fungible and non-fungible tokens in a single contract. The key additions are: InsufficientBalance now includes a tokenId parameter (because ERC-1155 tracks balances per-token-ID), MissingApprovalForAll replaces per-token approval with a blanket operator model, and InvalidArrayLength catches batch operation mismatches — a new error type not present in either ERC-20 or ERC-721.
ERC-1155 represents the evolution of token standards. It combines ERC-20's balance tracking with ERC-721's token IDs, and adds batch operations on top. Understanding its error types shows how the same error patterns (insufficient balance, invalid sender/receiver, missing approval) scale across increasingly complex token standards. The InvalidArrayLength error specifically highlights a new class of input validation that only exists in batch-capable contracts.
The ERC1155InvalidArrayLength error is a critical batch safety check. Without it, mismatched ids[] and values[] arrays in batch transfers could lead to out-of-bounds access or silent data corruption. Always validate array lengths before iterating in batch operations.
- ▸Compare across all three interfaces: InsufficientBalance has 3 params in ERC-20, 4 in ERC-1155 (adds tokenId). The pattern is consistent but adapted to each standard's needs.
- ▸ERC-1155 has no per-token approval — only setApprovalForAll. That's why it's MissingApprovalForAll instead of InsufficientApproval.
- ▸InvalidArrayLength is the only "structural" error in all of EIP-6093 — every other error is about authorization or balances.
- ▸Notice there's no NonexistentToken equivalent — ERC-1155 allows querying balance for any tokenId (returns 0 if never minted).
- ✗Passing mismatched ids and values arrays to batch functions — InvalidArrayLength will revert the entire batch.
- ✗Expecting per-token approval in ERC-1155 like ERC-721 — ERC-1155 only supports setApprovalForAll, which is all-or-nothing.
- ✗Thinking ERC-1155 balance of 0 means the token doesn't exist — unlike ERC-721, ERC-1155 doesn't track existence, only balances.
- ERC-1155
- The multi-token standard. A single contract can manage multiple token types (fungible and non-fungible) with batch transfer support.
- Batch Operations
- ERC-1155's ability to transfer multiple token types in a single transaction using arrays of IDs and amounts. Saves gas compared to individual ERC-20/721 transfers.
- setApprovalForAll
- The ONLY approval mechanism in ERC-1155. Approves an operator for ALL of an owner's tokens across all token IDs — there's no per-token-ID approval.
ERC-1155 was originally designed for gaming (by Enjin in 2018) where a single contract might manage thousands of item types — swords, shields, potions, currencies — each with different quantities. The InvalidArrayLength error is a direct consequence of this batch-first design philosophy.
IERC6093.sol — OpenZeppelin v5.0.0 on OP Sepolia
View on Etherscan