Solidity has no floating-point numbers. There are no fractions, no 0.5, no 3.14. Everything is an integer. So how do you represent '2.5 tokens'? ERC-20 solves this with the decimals() function. Most tokens use 18 decimals, meaning the smallest unit is 10^-18 of one token. When the contract stores '2500000000000000000', that represents 2.5 tokens to a human.
1// The decimals function — usually returns 182function decimals() public pure returns (uint8) {3return 18;4}56// Internally, all amounts are stored in the smallest unit (like wei)7// 1 token = 1000000000000000000 (1e18)8// 0.5 tokens = 500000000000000000 (5e17)9// 0.01 token = 10000000000000000 (1e16)1011// The name(), symbol(), and decimals() form "token metadata"12function name() public pure returns (string memory) {13return "Effort Token";14}1516function symbol() public pure returns (string memory) {17return "EFFORT";18}
Not all tokens use 18 decimals. USDC and USDT use 6 decimals (matching the cent precision of USD). WBTC uses 8 decimals (matching Bitcoin's satoshi). Some tokens use 0 decimals for non-divisible tokens. This is critical: if you confuse decimals between tokens, you could send 1,000,000x too much or too little. Always check decimals() before doing math with token amounts.
Common mistake: Sending raw numbers without converting. If you call transfer(recipient, 100) on an 18-decimal token, you are sending 0.0000000000000001 tokens — not 100 tokens. You need to send 100 * 10^18 = 100000000000000000000.
1// Converting between human-readable and raw amounts2import { parseUnits, formatUnits, parseEther, formatEther } from "viem";34// For 18-decimal tokens (most common):5const rawAmount = parseEther("2.5");6// Result: 2500000000000000000n (BigInt)78const humanAmount = formatEther(2500000000000000000n);9// Result: "2.5"1011// For tokens with different decimals (e.g., USDC = 6):12const usdcRaw = parseUnits("100.50", 6);13// Result: 100500000n1415const usdcHuman = formatUnits(100500000n, 6);16// Result: "100.5"1718// DANGER: Using parseEther for USDC would give the wrong amount!19// parseEther("100") = 100000000000000000000 (way too large for USDC)
The name(), symbol(), and decimals() functions are technically optional in EIP-20 (they are in the 'optional' section of the spec), but in practice every modern token implements them. These three metadata functions let wallets and dApps display tokens correctly. Without decimals(), a wallet could not know whether '1000000' means one million tokens or one token with 6 decimals.
Connect your wallet to mark this lesson as complete
Earn 15 EFFORT tokens