feat(address): proxy contract detection + Etherscan verification source#279
Merged
feat(address): proxy contract detection + Etherscan verification source#279
Conversation
…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
|
🚀 Preview: https://pr-279--openscan.netlify.app |
7 tasks
MatiasOS
approved these changes
Mar 6, 2026
- 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)
Collaborator
|
Re-checked this PR after the latest fix ( The 2 issues I previously flagged are now resolved:
At this point I do not see any blockers. LGTM. One small non-blocking nit: Not a blocker for merge, just something worth expanding later. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Two related features for the contract address page:
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.
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
sourcefield is an array:[]= unverified,["sourcify"],["etherscan"], or["sourcify", "etherscan"]when both verify.Related Issue
Type of Change
Changes Made
Proxy detection:
src/utils/proxyDetection.ts— pure async function detecting EIP-1967, EIP-1822, Beacon, EIP-1167 viaeth_getStorageAt/ bytecode pattern matchingsrc/hooks/useProxyInfo.ts— React hook wrapping proxy detectionContractInfoCard— shows Proxy Type row, Implementation Address row (with link + contract name badge), ABI tab switcher, and "implementation not verified" warningEtherscan verification:
src/hooks/useEtherscan.ts— Etherscan V2 API hook; handles plain Solidity, single-brace, and double-brace JSON source formats; maps response toSourcifyContractDetailsshapesrc/hooks/useContractVerification.ts— unified hook running Sourcify + Etherscan in parallel; returnssource: ("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 (correctpartial_matchpath for Sourcify; per-chain explorer URL for Etherscan across 8 chains)ApiKeystype updated withetherscan?: stringBug fix:
Screenshots (if applicable)
✓ Verified [Sourcify ↗] [Etherscan ↗]✓ Verified [Etherscan ↗]Checklist
npm run format:fixandnpm run lint:fixnpm run typecheckwith no errorsnpm run test:runAdditional Notes
chainidparameter