ERC-20 is an interface, not an implementation. Six mandatory functions (totalSupply, balanceOf, transfer, transferFrom, approve, allowance) and two events (Transfer, Approval). Everything else is implementation details that matter. Our experience — over 50 ERC-20 tokens launched into production — shows that these details determine whether the token will be compatible with DeFi protocols or will require a rewrite.
Which approach to ERC-20 token development to choose?
The first dilemma — upgradeable (Proxy + Implementation) or immutable. Proxy provides flexibility for changes, but each transaction via delegatecall costs about 150,000 gas compared to 45,000 for a regular contract. Immutable contracts are three times cheaper for users and simpler to audit. Recommendation: for utility tokens and governance — immutable; for DeFi vaults and complex protocols — upgradeable with caution.
The second issue — minting control. If the minter is an EOA, this is a centralization risk: the key might leak or the owner could mint an unlimited amount. Solution — use a multisig or a smart contract with the MINTER role. Our projects always use AccessControl for distributed governance.
The third — gas optimization. Not using ERC20Permit (EIP-2612) forces the user to spend two transactions instead of one for the first interaction with DeFi.
Why use OpenZeppelin instead of writing from scratch?
OpenZeppelin is the industry standard. Their code has undergone hundreds of audits, used in millions of contracts. Do not write ERC-20 from scratch — it increases the risk of errors and reduces integrator trust. Our solutions are based on OpenZeppelin with additional custom modules.
Basic Implementation
// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; import "@openzeppelin/contracts/access/Ownable2Step.sol"; contract MyToken is ERC20, ERC20Burnable, ERC20Permit, Ownable2Step { uint256 public constant MAX_SUPPLY = 100_000_000 * 10**18; // 100M tokens constructor( address initialOwner, address treasury ) ERC20("My Token", "MTK") ERC20Permit("My Token") Ownable2Step() { _transferOwnership(initialOwner); _mint(treasury, MAX_SUPPLY); // entire supply at deploy } } ERC20Permit is an important extension: it allows approval via off-chain signatures. This improves UX (one transaction instead of two) and reduces gas for the user. Ownable2Step instead of Ownable protects against accidentally transferring control to an incorrect address.
Mintable Token with Access Control
If the token needs to be minted after deployment, use AccessControl:
import "@openzeppelin/contracts/access/AccessControl.sol"; contract MintableToken is ERC20, ERC20Burnable, ERC20Permit, AccessControl { bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); uint256 public immutable maxSupply; constructor( string memory name, string memory symbol, uint256 _maxSupply, address admin ) ERC20(name, symbol) ERC20Permit(name) { maxSupply = _maxSupply; _grantRole(DEFAULT_ADMIN_ROLE, admin); _grantRole(MINTER_ROLE, admin); } function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) { require(totalSupply() + amount <= maxSupply, "Exceeds max supply"); _mint(to, amount); } } MINTER_ROLE should be assigned to a smart contract (staking reward, vesting), not an EOA.
Comparison of Approaches
| Criterion | Immutable Contract | Upgradeable Proxy |
|---|---|---|
| Gas (per transaction) | ~45,000 gas | ~150,000 gas (due to delegatecall) |
| Flexibility | No changes | Upgradeable logic |
| Risk | Only logical errors | Proxy errors, storage collisions |
| Audit | Simpler | More complex (two contracts) |
| Recommendation | Utility, governance | DeFi vaults, complex protocols |
Why decimals Should Be 18 (Usually)
By default, decimals = 18 (like ETH). Exception: USDC/USDT use 6. If creating a stablecoin or wrap — check the original's decimals. Never use 0 decimals for tokens that will trade on a DEX — AMM works poorly with integers. We've encountered projects where incorrect decimals caused incompatibility issues with protocols.
Common Mistakes
Transfer tax: every transfer takes a percentage — breaks DeFi protocols. If still needed, use a whitelist for Uniswap, Aave, Compound contracts. Centralized blacklist without timelock: bad for community tokens. Reentrancy in transfer hooks: if adding _beforeTokenTransfer or _afterTokenTransfer, ensure you don't call external code.
Work Stages
| Stage | Duration | Result |
|---|---|---|
| Analytics | 1-2 days | Token specification, architecture selection |
| Implementation | 2-5 days | Smart contract, tests, deployment scripts |
| Audit | 1-3 weeks | Vulnerability report, fixes |
| Deployment and verification | 1 day | Contract on chain, verified on Etherscan |
| Support | 1 month | Free fixes after launch |
Verification and Deployment
# Tests forge test -vvv # Deploy with verification forge script script/Deploy.s.sol \ --rpc-url $RPC_URL \ --private-key $PRIVATE_KEY \ --broadcast \ --verify \ --etherscan-api-key $ETHERSCAN_KEY After deployment — verify the contract on Etherscan/Polygonscan. An unverified token raises legitimate suspicion from exchanges and users.
What's Included in Turnkey ERC-20 Token Development
- Smart contract in Solidity with custom functions (mint, burn, permit, pause, blacklist)
- Unit tests (Foundry/Hardhat) with coverage >90%
- Deployment scripts configured for your network (Ethereum, Polygon, Arbitrum, BNB Chain)
- Verification on blockchain explorer (Etherscan, Polygonscan)
- Developer and user documentation
- Consultation on DeFi protocol integration
- Guarantee — 1 month free fixes after launch
We will assess your project within 1 business day. We are a team of senior blockchain developers: 5+ years on the market, 50+ tokens delivered. Contact us to discuss details and order turnkey ERC-20 token development.







