DEV Community

Low Key Builds
Low Key Builds

Posted on Originally published at contractlens.appin60.com

Who holds the keys to Base's top 250 tokens? An on-chain check

I built a small on-chain checker that answers one question about an ERC-20 token: who can change it after you buy it? To test it, I ran it over the 249 largest tokens in CoinGecko's Base-ecosystem category. Here is what came back, how the check works, and where it falls short.

Disclosure: I built the tool (ContractLens). The full per-token table and raw JSON are public: Who holds the keys to Base's top 250 tokens.

Results

  • 47 tokens are canonical bridged copies of L1 tokens (OptimismMintableERC20, minted only by the OP-stack bridge at 0x4200…0010). A naive scanner flags all of them as "mintable", so I excluded them. That leaves 202 native Base tokens.
  • 80 of 202 (40%) are upgradeable proxies. Upgrade control: 25 Safe multisig, 12 timelock, 13 a single EOA, 9 renounced, 7 another contract, 14 not readable.
  • 102 have a mint function, 53 can pause transfers, 15 can blacklist or freeze holders, and 4 have an owner-adjustable fee.
  • Owners: 76 Safe, 9 timelock, 20 single EOA, 13 renounced, 68 with no owner() at all (mostly AccessControl).
  • Upgradeable with a single EOA holding the upgrade key: USDC, CBBTC, USD0, EURC, GTBTC, CBXRP, AUSD, MAGIC, TGBP, HYPER, USUAL, VNX, CBADA.
  • 52 have no DEX pair on Base that DexScreener knows of, and 41 more have under $10k of Base liquidity.

None of this means "scam". USDC, EURC and cbBTC are meant to be issuer-controlled, and an issuer can guard an EOA with HSMs and approval processes that the chain cannot show. The point is to know who holds the keys, especially for the long tail.

How the check works (about 2 seconds, no source code needed)

  1. Proxy detection. Read the EIP-1967 implementation, admin and beacon slots, the older ZeppelinOS slots (USDC on Base still uses them: 0x7050c9e0… for the implementation, 0x10d6a54a… for the admin), and match the EIP-1167 minimal-proxy bytecode.
  2. Who is in charge. Try owner(), getOwner(), defaultAdmin(), getRoleMember(DEFAULT_ADMIN_ROLE, i). Classify each address: zero or 0x…dead means renounced, empty code means EOA, an EIP-7702 delegation prefix (0xef0100) means an EOA too, getThreshold() plus getOwners() means a Safe m-of-n, getMinDelay() means a timelock. If the proxy admin is an OpenZeppelin ProxyAdmin, follow it to its owner.
  3. What they can do. Walk the runtime bytecode, collect every PUSH4 operand and match it against the selectors of known admin functions (mint, blacklist, setBots, pause, setFee/setTaxes, enableTrading, setMaxWallet…). For proxies, scan the implementation.
  4. Bridged tokens. If BRIDGE()/bridge()/l2Bridge() returns a canonical bridge, mint is bridge-only and gets a low rating.
  5. Market. DexScreener for liquidity, 24h buys and sells, and pair age.

The verdict separates control risk (admin powers × who holds them) from market risk (liquidity, age, buys with zero sells).

Limits

  • A selector scan proves a function exists, not who may call it, and it misses admin functions with unusual names.
  • There is no buy/sell simulation, so it cannot rule out a honeypot. Pair it with a simulator.
  • AccessControl role holders that are not enumerable on-chain show as "not found". You would need RoleGranted event history.

Try it

  • Free web form: https://contractlens.appin60.com
  • Remote MCP server (free, 50 checks/day): https://contractlens-api.appin60.com/mcp, tool check_token
  • For agents: GET https://contractlens-api.appin60.com/token?chainId=8453&address=0x… costs $0.005 USDC per call via x402 on Base or Arbitrum. No API key: the agent pays and gets JSON back. Chains: Ethereum, Base, Arbitrum, Optimism, Polygon, BNB.

Key-change alerts (waitlist): I'm considering alerts that email you when a token you hold upgrades its implementation, changes owner or admin, or lowers its Safe threshold. The planned price is $9/month in USDC. If you'd pay for that, join the waitlist at the bottom of the study page. Nothing is charged now.

If a row in the table is wrong, tell me at contractlens@appin60.com and I will fix the checker.

Top comments (0)