← ClaudeAtlas

evm-gotchaslisted

Use when reading ERC-20 balances, pricing tokens, or hashing Ethereum data in JS/TS/Python/Solidity. Covers per-chain token decimal drift with runtime decimals() lookup and WAD normalization, and the Node sha3-256 vs Keccak-256 mismatch with correct ethers/viem/web3 APIs.
Mixard/fable-pack · ★ 1 · AI & Automation · score 74
Install: claude install-skill Mixard/fable-pack
# EVM Gotchas ## Token decimals vary per chain and bridge The same symbol does not imply the same decimals across chains or bridge deployments. Hardcoding a divisor (e.g. `1_000_000` because a stablecoin has 6 decimals on one chain) produces balances and USD values off by orders of magnitude with no error thrown. Bridged and wrapped tokens can change precision relative to the origin asset. Rules: - Query `decimals()` at runtime, never assume by symbol - Cache keyed by `(chain_id, token_address)`, not by symbol - Use exact math (`Decimal`, `BigInt`), not floats - Re-query decimals after bridging or wrapper changes - Normalize internal accounting to one precision before comparison or pricing ### Runtime lookup (Python / web3.py) ```python from decimal import Decimal from web3 import Web3 ERC20_ABI = [ {"name": "decimals", "type": "function", "inputs": [], "outputs": [{"type": "uint8"}], "stateMutability": "view"}, {"name": "balanceOf", "type": "function", "inputs": [{"name": "account", "type": "address"}], "outputs": [{"type": "uint256"}], "stateMutability": "view"}, ] def get_token_balance(w3: Web3, token_address: str, wallet: str) -> Decimal: contract = w3.eth.contract( address=Web3.to_checksum_address(token_address), abi=ERC20_ABI) decimals = contract.functions.decimals().call() raw = contract.functions.balanceOf(Web3.to_checksum_address(wallet)).call() return Decimal(raw) / Decimal(10 ** decimals) ``` ### Cache by (cha