Skip to content

feat(address): proxy contract detection + Etherscan verification source#279

Merged
AugustoL merged 16 commits intodevfrom
feat/proxy-contract-detection
Mar 9, 2026
Merged

feat(address): proxy contract detection + Etherscan verification source#279
AugustoL merged 16 commits intodevfrom
feat/proxy-contract-detection

Conversation

@AugustoL
Copy link
Copy Markdown
Collaborator

@AugustoL AugustoL commented Mar 5, 2026

Description

Two related features for the contract address page:

  1. Proxy contract detection — automatically detects EIP-1967, EIP-1822 (UUPS), EIP-1967 Beacon, and EIP-1167 minimal proxy patterns. Shows the proxy type and implementation address, fetches verified ABI from the implementation, and provides an ABI tab switcher (Implementation / Proxy) when both have verified ABIs.

  2. Etherscan as parallel verification source — fetches contract verification data from Sourcify and Etherscan simultaneously. When an Etherscan API key is configured in Settings, both sources are queried in parallel and results are shown as linked tag badges on the status row (green Sourcify tag, blue Etherscan tag). The source field is an array: [] = unverified, ["sourcify"], ["etherscan"], or ["sourcify", "etherscan"] when both verify.

Related Issue

Type of Change

  • New feature
  • Bug fix
  • Documentation update
  • Refactoring
  • Performance improvement
  • Other (please describe):

Changes Made

Proxy detection:

  • src/utils/proxyDetection.ts — pure async function detecting EIP-1967, EIP-1822, Beacon, EIP-1167 via eth_getStorageAt / bytecode pattern matching
  • src/hooks/useProxyInfo.ts — React hook wrapping proxy detection
  • ContractInfoCard — shows Proxy Type row, Implementation Address row (with link + contract name badge), ABI tab switcher, and "implementation not verified" warning
  • Proxy detection added to all four display components (ContractDisplay, ERC20Display, ERC721Display, ERC1155Display)

Etherscan verification:

  • src/hooks/useEtherscan.ts — Etherscan V2 API hook; handles plain Solidity, single-brace, and double-brace JSON source formats; maps response to SourcifyContractDetails shape
  • src/hooks/useContractVerification.ts — unified hook running Sourcify + Etherscan in parallel; returns source: ("sourcify" | "etherscan")[]
  • ContractInfoCard — linked tag badges (following Kleros tag style from PR feat(address): show Kleros verified tag on address page #277) replacing the old text badges; URLs computed internally (correct partial_match path for Sourcify; per-chain explorer URL for Etherscan across 8 chains)
  • Etherscan API key field added to Settings page (after Alchemy, before AI keys)
  • ApiKeys type updated with etherscan?: string
  • All 5 locale files updated for both address and settings namespaces

Bug fix:

  • Fixed double separator between EVM Version row and Contract Details toggle (adjacent sibling CSS rule)

Screenshots (if applicable)

  • Verified on both: status row shows ✓ Verified [Sourcify ↗] [Etherscan ↗]
  • Proxy contract: shows Proxy Type, Implementation address with contract name badge, ABI tab switcher
  • Etherscan-only: status row shows ✓ Verified [Etherscan ↗]

Checklist

  • I have run npm run format:fix and npm run lint:fix
  • I have run npm run typecheck with no errors
  • I have run tests with npm run test:run
  • I have tested my changes locally
  • I have updated documentation if needed
  • My code follows the project's architecture patterns

Additional Notes

  • Etherscan is opt-in: without an API key configured, behaviour is identical to before (Sourcify only)
  • Sourcify data always takes priority when both sources verify (more canonical/trustless)
  • Etherscan V2 API supports 60+ chains with a single key via the chainid parameter

AugustoL added 7 commits March 5, 2026 08:01
…1167)

Automatically detects proxy patterns and shows the implementation address
as a clickable link. Merges the implementation ABI into the interaction
panel so users can call implementation functions directly.

- Add proxyDetection.ts utility supporting EIP-1967, EIP-1822 (UUPS),
  EIP-1967-Beacon, and EIP-1167 minimal proxy patterns
- Add useProxyInfo hook wrapping detection with AppContext RPC URLs
- Show proxy type and implementation address row in ContractInfoCard
- Add ABI tab switcher (Implementation / Proxy) when both ABIs exist
- Fetch implementation contract data from Sourcify when proxy detected
- Add abi-tab-switcher CSS styles
- Add i18n keys (proxyType, implementationAddress, implementationFunctions,
  proxyFunctions) to en, es, ja, pt-BR, zh locales

Closes #266
Proxy detection fixes:
- Add legacy OZ AdminUpgradeabilityProxy slot (keccak256("org.zeppelinos.proxy.implementation"))
  so Circle USDC (FiatTokenProxy) and other pre-EIP-1967 proxies are detected
- Use NetworkAdapter.getStorageAt/callContract instead of raw fetch calls,
  so the configured RPC strategy (fallback/parallel) is used for proxy checks
- Add slotToAddress validation: top 12 bytes must be zero (ABI-encoded address)
  to prevent false positives from contracts with incidental non-zero storage slots
- Run all four slot reads in parallel to reduce latency
- Add ProxyRpcClient interface satisfied by NetworkAdapter

EOA misclassification fixes:
- Pass pre-fetched addressData to fetchAddressWithType to avoid redundant
  eth_getCode call; secondary RPC failures no longer silently downgrade
  contracts to EOA
- When type detection fails or no RPC URL is configured, derive EOA/contract
  from the already-fetched code instead of defaulting to "account"

Contract Details panel fix:
- Show the Contract Details section (ABI + interaction) whenever a proxy is
  detected with a verified implementation ABI, even when the proxy contract
  itself has no Sourcify entry
ERC20 tokens that are also proxies (e.g. USDC/FiatTokenProxy) now
detect the proxy pattern and pass proxyInfo + implementationContractData
to ContractInfoCard, enabling the implementation ABI tab switcher.
…on Sourcify

When a proxy is detected but its implementation contract is not verified
on Sourcify (e.g. Aave V3 Pool on Optimism), show a clear note explaining
why no implementation ABI tab is available.
- Add useEtherscan hook fetching from Etherscan V2 API (single key, 60+ chains)
- Add useContractVerification hook running Sourcify and Etherscan simultaneously;
  source is an array — empty means unverified, ["sourcify"], ["etherscan"], or both
- Replace useSourcify calls in ContractDisplay, ERC20Display, ERC721Display,
  ERC1155Display with useContractVerification; add proxy detection to ERC721/ERC1155
- Show Sourcify and Etherscan as linked tag badges on the status row in
  ContractInfoCard, following the same style as Kleros tags (PR #277)
- Compute verification URLs internally in ContractInfoCard (correct partial_match
  path for Sourcify; per-chain explorer URL for Etherscan); remove sourcifyUrl prop
- Add Etherscan API key field to Settings (after Alchemy, before AI keys)
- Add etherscan key to ApiKeys type and all 5 locale files
- Fix double separator between EVM Version and Contract Details rows
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 5, 2026

🚀 Preview: https://pr-279--openscan.netlify.app
📝 Commit: e21ac2b06a2fff5c6cb58dbe8897d6882dd59a87

AugustoL added 5 commits March 6, 2026 11:55
- Distinguish EIP-1967 Transparent vs UUPS by checking admin slots
  (both EIP-1967 admin and legacy OZ admin for Aave V2 compatibility)
- Pass implSourcifyData unconditionally so unverified impl names still show;
  gate ABI interaction separately via implIsVerified prop
- Fall back to implementation contract name when proxy itself is unverified
- Fix contract name flickering by keeping display components mounted during
  background re-fetches (only show loader when addressData is not yet loaded)
- Remove unused hasContractCode import
Initialize addressType as null and reset it on each new fetch. The loader
is now shown until both addressData and addressType are determined (first
load only). Subsequent re-fetches from dataService reference changes no
longer unmount the active display component, preserving all hook state
(proxy detection, Sourcify data, contract name, etc.).
- useSourcify: add SourcifyV2Raw internal type and mapV2Response() to
  correctly extract compilation fields (name, compilerVersion, evmVersion,
  language, optimizer) from the nested 'compilation' object returned by
  V2 API — these were previously undefined because the raw JSON was
  assigned directly without mapping
- useSourcify: normalize match values ('exact_match' -> 'perfect')
- useSourcify: add proxyResolution field (isProxy, proxyType, implementations)
- useSourcify: remove dead code — SOURCIFY_API_BASE (V1 URL) and
  useSourcifyFiles hook (never imported anywhere)
- ContractDisplay: merge Sourcify proxyResolution with RPC detection;
  Sourcify provides the implementation address reliably, RPC provides
  accurate Transparent vs UUPS distinction
- ContractDisplay: expose sourcifyImplName from proxyResolution so the
  implementation contract name is available immediately without waiting
  for a second verification fetch
- ContractDisplay: rename sourcifyData -> contractVerifiedData (also in
  ERC20/721/1155Display) to reflect it may come from Etherscan fallback
- ContractInfoCard: add language, optimizerEnabled, optimizerRuns fields
  and display rows; use sourcifyImplName as immediate name fallback
- index.tsx: only reset addressType when navigating to a new address
  (use prevAddressRef); keeps display component mounted on background
  re-fetches, preserving all hook state
- useProxyInfo: keep last known proxyInfo on RPC failure instead of
  resetting to null, preventing verified impl data from disappearing
- i18n: add language, optimizer, optimizerEnabled, optimizerDisabled
  keys in all 5 locales
…contract tabs

- Derive sourceFiles from activeSourceData (same as activeABI) so
  implementation source code shows when on the Implementation tab
- Suppress bottom bytecode section when contract-details-section
  already renders bytecode (prevents duplication for unverified proxy
  with verified implementation)
- Add runtimeBytecode field to SourcifyContractDetails and map it
  from Sourcify V2 API response so bytecode switches with active tab
- Add NetworkAdapter.getCode() for direct eth_getCode calls
- Fetch implementation bytecode via RPC in ContractDisplay as fallback
  when Sourcify runtimeBytecode is unavailable (Etherscan-only contracts)
@josealoha666
Copy link
Copy Markdown
Collaborator

Re-checked this PR after the latest fix (c0a19d5).

The 2 issues I previously flagged are now resolved:

  • the Implementation tab now uses the correct implementation source files
  • the duplicate bytecode section for unverified proxy + verified implementation is gone

At this point I do not see any blockers. LGTM.

One small non-blocking nit: useEtherscan supports many chains through the V2 API, but the ETHERSCAN_EXPLORERS map in ContractInfoCard.tsx only covers a small subset of networks. That means a contract could be verified through Etherscan on a supported chain, but still not show an Etherscan badge/link in the UI because the explorer base URL is missing from the map.

Not a blocker for merge, just something worth expanding later.

@AugustoL AugustoL merged commit 7ace785 into dev Mar 9, 2026
6 of 8 checks passed
@AugustoL AugustoL deleted the feat/proxy-contract-detection branch March 11, 2026 19:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants