{"id":"ERC-20","name":"Token Standard","status":"Final","chain":"both","category":{"id":"defi","name":"DeFi Patterns","description":"Vaults, bonds, and financial primitives"},"journeyStages":[{"id":"asset-display","name":"Asset Discovery & Display","description":"Seeing tokens, NFTs, and balances"},{"id":"approving","name":"Approving & Permissioning","description":"Granting permissions for actions"},{"id":"executing","name":"Executing Transactions","description":"Performing on-chain actions"}],"uxImpact":"ERC-20 defines a common interface for fungible token balances, transfers, and approvals. A client can integrate a token through these functions, but metadata methods, return values, fee behavior, and compatibility depend on the deployed contract.","officialUrl":"https://ercs.ethereum.org/ERCS/erc-20","lastReviewed":"2026-09-18","lastUpdated":"2026-09-18","kind":"proposal","hasDetailedContent":true,"content":{"id":"ERC-20","summary":"ERC-20 defines a common interface for fungible token balances, transfers, and approvals. A client can integrate a token through these functions, but metadata methods, return values, fee behavior, and compatibility depend on the deployed contract.","designerTakeaways":["You can format balances with decimals() when the token exposes it, with an explicit raw-unit fallback when metadata is absent.","You can default approvals to the exact spend for this action, not unlimited.","You can add an approvals screen where users review and revoke active spenders."],"applicability":{"whenToUse":["Wallets or apps display fungible token balances, sends, or swaps.","DEX, lending, or payment flows need approve + transferFrom.","You integrate contracts that assume the common ERC-20 interface; verify the target token's deployed behavior."],"whenToAvoid":["Each asset must be unique by id (use ERC-721 or ERC-1155).","Users only move native chain currency with no token layer.","Tokens are non-transferable or soulbound with no standard wallet UX."]},"designDecisions":[{"question":"What is the default approval amount?","recommendation":"Default to the exact amount needed for the current action; make unlimited opt-in with a warning.","rationale":"A compromised spender can use any remaining allowance, so unlimited approvals increase the amount at risk."},{"question":"How do you show token amounts?","recommendation":"Use decimals() when available; otherwise label and display the raw-unit amount explicitly.","rationale":"Wrong decimals misstate balances and break trust at confirmation time."},{"question":"Do users get an approvals management surface?","recommendation":"Provide a list of active allowances with revoke actions and risk labels.","rationale":"Users cannot clean up risky approvals they granted months ago without this UI."},{"question":"How does MAX behave for the gas token?","recommendation":"Reserve a small buffer when the ERC-20 is also used to pay gas on that chain.","rationale":"Send-max otherwise fails when the wallet needs leftover balance for fees."}],"statesToDesign":[{"state":"Balance loading","trigger":"Token list or balance query is in flight.","userNeed":"Know whether funds are still syncing.","designResponse":"Skeleton rows; avoid showing zero until the query settles."},{"state":"Insufficient balance","trigger":"User enters more than wallet balance.","userNeed":"Understand why send or swap is blocked.","designResponse":"Inline error with max available and a one-tap fill-max that respects gas reserve."},{"state":"Approval required","trigger":"Spender allowance is lower than the action amount.","userNeed":"Understand why an extra step appears before swap.","designResponse":"Explain approval in plain language with amount strategy choices."},{"state":"Unlimited allowance active","trigger":"User views approvals or connects to a high-risk spender.","userNeed":"See elevated risk clearly.","designResponse":"Warning badge on unlimited rows; offer revoke and safer limited re-approval."},{"state":"Transfer pending","trigger":"transfer() submitted but not finalized.","userNeed":"Track progress without refreshing.","designResponse":"Pending row with explorer link; update balance on confirmation."}],"problemsSolved":[{"problem":"Every token had different interfaces","oldWay":"Each token contract did things differently, wallets couldn't support them all","newWay":"A common core interface helps wallets and DEXs integrate, with optional metadata handled defensively","impact":"critical"},{"problem":"No standard way to check balances","oldWay":"Each token invented its own balance function","newWay":"balanceOf(address) works the same everywhere","impact":"critical"},{"problem":"Approving contracts to spend tokens was inconsistent","oldWay":"Random approval mechanisms, security risks","newWay":"approve() + transferFrom() form a common allowance pattern; verify return behavior and spender terms","impact":"high"},{"problem":"Token metadata (name, symbol) not standardized","oldWay":"Some had name(), some had getName(), some had nothing","newWay":"Optional name(), symbol(), and decimals() methods when implemented, with a raw-unit fallback","impact":"medium"}],"uxPatterns":[{"name":"Token Balance Display","description":"Show user's token holdings with proper formatting","mockup":"generic/balance-display","userFlow":["App queries known token contracts","Calls balanceOf(userAddress) on each","Uses decimals() when available; otherwise labels the raw-unit amount explicitly","Fetches price data for fiat conversion","Displays formatted list"]},{"name":"Token Approval Flow","description":"Let user approve token spending with clear limits","userFlow":["DEX detects insufficient allowance","Shows approval modal","User selects amount strategy","Calls approve(spender, amount)","User signs transaction","Handles a false return before treating approval as successful","Allowance updated"]},{"name":"Token Transfer","description":"Simple send tokens to another address","mockup":"generic/token-transfer","userFlow":["User enters recipient (address or ENS)","Enters amount","App validates against balance","Shows fee estimate","User confirms send","Calls transfer(to, amount) and handles a false return before treating it as successful"]},{"name":"Allowance Management","description":"View and revoke token approvals","userFlow":["User opens approvals page","App scans Approval events","Shows all active allowances","Highlights risky (unlimited, unknown)","User can revoke individually or batch"]}],"uiComponents":[{"name":"TokenBalanceDisplay","description":"Shows token amount with proper decimal formatting","states":["loading","loaded","zero","error"],"props":["balance","decimals","symbol","showFiat","price"]},{"name":"TokenAmountInput","description":"Input field for token amounts with MAX button","states":["empty","valid","exceeds-balance","invalid"],"props":["value","max","decimals","onChange"]},{"name":"ApprovalSelector","description":"Choose approval amount strategy","states":["exact","unlimited","custom"],"props":["suggestedAmount","onSelect"]},{"name":"AllowanceDisplay","description":"Shows current approval amount for a spender","states":["loading","none","limited","unlimited"],"props":["token","spender","amount"]},{"name":"TokenSelector","description":"Dropdown/modal to select from available tokens","states":["closed","open","searching","selected"],"props":["tokens[]","selected","onSelect"]}],"antiPatterns":[{"pattern":"Defaulting to unlimited approvals","why":"Exposes user to maximum risk if contract is compromised","instead":"Default to exact amount needed, make unlimited opt-in","severity":"critical"},{"pattern":"Not checking decimals before displaying","why":"Token decimals vary; wrong math misstates the amount shown","instead":"Feature-detect decimals(); format with it when available and label raw units when absent","severity":"critical"},{"pattern":"Showing raw uint256 values to users","why":"\"1000000000000000000\" is 1 ETH, not a billion ETH","instead":"Format with decimals() when available; otherwise show the raw units with an explicit label","severity":"critical"},{"pattern":"No allowance management UI","why":"Users have no way to see or revoke old approvals","instead":"Provide approvals page with revoke functionality","severity":"high"},{"pattern":"Not explaining what approval means","why":"Users approve without understanding the risk","instead":"Clear explanation: \"This lets them transfer without asking\"","severity":"high"},{"pattern":"MAX button without dust accounting","why":"Sending MAX may fail if gas is needed from same balance","instead":"Reserve small amount for gas when token is gas token","severity":"medium"}],"onMonad":[{"aspect":"Token balance and native gas","ethereum":"An ERC-20 token balance is separate from the ETH used for transaction fees.","monad":"An ERC-20 token balance is separate from native MON. Reserve eligibility concerns native account balances; it is not a 10-token deduction from every ERC-20 balance.","designImplication":"Keep the token amount, native fee, and native-balance eligibility separate. Calculate MAX for the selected asset and simulate the complete transaction."}],"keyTakeaways":["Treat ERC-20 as a common contract interface, not a guarantee of uniform token behavior","Check decimals() when available; provide an explicit fallback when metadata is absent","Default to exact approval amounts, not unlimited","Provide allowance management UI","Format amounts for humans, store raw for contracts"],"technicalNotes":"ERC-20 specifies 6 functions (totalSupply, balanceOf, transfer, allowance, approve, transferFrom) and 2 events (Transfer, Approval). Optional usability methods: name(), symbol(), decimals(); feature-detect them and fall back to the address/raw units when absent. Callers must handle false returns from bool-returning token calls and must not assume false is never returned. Approval pattern: user approves spender for X tokens, spender can then transferFrom. Common extensions: ERC-2612 (permit), ERC-1363 (payable).","specConstraints":[{"id":"erc20-false-return","constraint":"Callers must handle a `false` return from bool-returning token methods and must not assume it never occurs.","appliesTo":"application","normativity":"normative","specQuote":"- Callers MUST handle `false` from `returns (bool success)`.  Callers MUST NOT assume that `false` is never returned!","specSection":"Token — Methods","specUrl":"https://ercs.ethereum.org/ERCS/erc-20#methods","designImplication":"Keep a failed-token-call state distinct from a network or wallet error and do not show a transfer as complete from submission alone."},{"id":"erc20-optional-metadata","constraint":"Token name, symbol, and decimals are optional usability methods.","appliesTo":"application","normativity":"normative","specQuote":"OPTIONAL - This method can be used to improve usability,\nbut interfaces and other contracts MUST NOT expect these values to be present.","specSection":"Token — Methods — name","specUrl":"https://ercs.ethereum.org/ERCS/erc-20#name","designImplication":"Use explicit unknown metadata and raw-unit fallback when optional methods are absent."},{"id":"erc20-transfer-events","constraint":"Successful transfers emit `Transfer`, including zero-value transfers.","appliesTo":"application","normativity":"normative","specQuote":"Transfers `_value` amount of tokens to address `_to`, and MUST fire the `Transfer` event.\nThe function SHOULD `throw` if the message caller's account balance does not have enough tokens to spend.","specSection":"Token — Methods — transfer","specUrl":"https://ercs.ethereum.org/ERCS/erc-20#transfer","designImplication":"Refresh balances and activity from the receipt or event, and do not hide zero-value transfers when they are part of the token history."},{"id":"erc20-zero-value-transfers","constraint":"Zero-value transfers are normal transfers and still emit `Transfer`.","appliesTo":"application","normativity":"normative","specQuote":"*Note* Transfers of 0 values MUST be treated as normal transfers and fire the `Transfer` event.","specSection":"Token — Methods — transfer","specUrl":"https://ercs.ethereum.org/ERCS/erc-20#transfer","designImplication":"Do not treat a zero amount as an absent transaction when displaying token activity."},{"id":"erc20-approval-events","constraint":"A successful approval emits `Approval`.","appliesTo":"application","normativity":"normative","specQuote":"MUST trigger on any successful call to `approve(address _spender, uint256 _value)`.","specSection":"Token — Events — Approval","specUrl":"https://ercs.ethereum.org/ERCS/erc-20#approval","designImplication":"Update allowance UI from the approval result and show the approved spender and amount."},{"id":"erc20-transferfrom-authorization","constraint":"transferFrom SHOULD throw unless the source account deliberately authorized the message sender.","appliesTo":"application","normativity":"normative","specSection":"Token — Methods — transferFrom","specUrl":"https://ercs.ethereum.org/ERCS/erc-20#transferfrom","designImplication":"Show the source account, spender, and current allowance before a delegated transfer, and distinguish missing authorization from generic transaction failure.","specQuote":"The `transferFrom` method is used for a withdraw workflow, allowing contracts to transfer tokens on your behalf.\nThis can be used for example to allow a contract to transfer tokens on your behalf and/or to charge fees in sub-currencies.\nThe function SHOULD `throw` unless the `_from` account has deliberately authorized the sender of the message via some mechanism."},{"id":"erc20-approval-reset","constraint":"Clients SHOULD set an allowance to 0 before setting a different value for the same spender.","appliesTo":"wallet","normativity":"normative","specSection":"Token — Methods — approve","specUrl":"https://ercs.ethereum.org/ERCS/erc-20#approve","designImplication":"Use a visible reset-then-set approval flow when changing an allowance and explain the extra confirmation rather than silently submitting a replacement.","specQuote":"**NOTE**: To prevent attack vectors like the one [described here](https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM/) and discussed [here](https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729),\nclients SHOULD make sure to create user interfaces in such a way that they set the allowance first to `0` before setting it to another value for the same spender.\nTHOUGH The contract itself shouldn't enforce it, to allow backwards compatibility with contracts deployed before"},{"id":"erc20-mint-event","constraint":"A token contract that creates tokens SHOULD emit Transfer with the from address set to 0x0.","appliesTo":"application","normativity":"normative","specSection":"Token — Events — Transfer","specUrl":"https://ercs.ethereum.org/ERCS/erc-20#transfer","designImplication":"Index minting as a Transfer from the zero address and show it in token history so new supply is not mistaken for an unexplained balance change.","specQuote":"A token contract which creates new tokens SHOULD trigger a Transfer event with the `_from` address set to `0x0` when tokens are created."}],"sources":[{"label":"Monad reserve-balance rules","url":"https://docs.monad.xyz/developer-essentials/reserve-balance","type":"reference"},{"label":"Official ERC-20 specification","url":"https://ercs.ethereum.org/ERCS/erc-20","type":"official-spec"}],"lastUpdated":"2026-09-18","lastReviewed":"2026-09-18"},"sources":[{"label":"Official specification","url":"https://ercs.ethereum.org/ERCS/erc-20","type":"official-spec"},{"label":"Discussion search","url":"https://ethereum-magicians.org/search?q=ERC-20","type":"discussion"},{"label":"Monad reserve-balance rules","url":"https://docs.monad.xyz/developer-essentials/reserve-balance","type":"reference"}],"urls":{"canonical":"https://www.eipsfordesigners.com/standards/ERC-20","markdown":"https://www.eipsfordesigners.com/standards/ERC-20/content.md","agent":"https://www.eipsfordesigners.com/standards/ERC-20/agent.md","api":"https://www.eipsfordesigners.com/api/standards/ERC-20","official":"https://ercs.ethereum.org/ERCS/erc-20","discussion":"https://ethereum-magicians.org/search?q=ERC-20"},"freshness":{"lastReviewed":"2026-09-18","lastUpdated":"2026-09-18"}}