Skip to content

Honor evmScanApiKey on etherscan.io - #1078

Open
j0ntz wants to merge 1 commit into
masterfrom
jon/fix/evmscan-api-key-fallback
Open

Honor evmScanApiKey on etherscan.io#1078
j0ntz wants to merge 1 commit into
masterfrom
jon/fix/evmscan-api-key-fallback

Conversation

@j0ntz

@j0ntz j0ntz commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

CHANGELOG

Does this branch warrant an entry to the CHANGELOG?

  • Yes
  • No

Dependencies

none

Description

Asana task

getEvmScanApiKey requires the deprecated etherscanApiKey init option whenever the server is api.etherscan.io, and throws before it ever considers evmScanApiKey, the option the function itself tells callers to use:

if (serverUrl.includes('etherscan.io')) {
  if (etherscanApiKey == null)
    throw new Error(`Missing etherscanApiKey for etherscan.io`)
  return etherscanApiKey
}

if (evmScanApiKey != null) return evmScanApiKey   // never reached for etherscan.io

An app configured with only evmScanApiKey therefore gets no key at all. Etherscan V2 rejects keyless requests:

$ curl 'https://api.etherscan.io/v2/api?chainid=1&module=account&action=txlist&address=0x742d...'
{"status":"0","message":"NOTOK","result":"Missing/Invalid API Key"}

The impact is larger than fee estimation. EvmScanAdapter is the ONLY adapter with a non-null fetchTxs on the 15 EVM networks whose networkAdapterConfigs point at api.etherscan.io (ethereum, polygon, base, arbitrum, optimism, avalanche, binancesmartchain, celo, zksync, sonic, opbnb, monad, hyperEvm, holesky, sepolia). RpcAdapter, BlockbookAdapter and AmberdataAdapter all set fetchTxs = null. So the failed key lookup means acquireTxs never receives a tokenTxs update, setHistoryRatios(_, 1) never fires, and transaction history stays unsynced forever.

makeTokenSyncTracker averages balance and history progress per token, so a wallet whose balance is complete and whose history is stuck at zero reports exactly 0.5:

totalRatio += ((balanceStatus + txStatus) / 2) * perTokenSlice

which surfaces in the app as a wallet permanently stuck at 50% sync. This was reported against a white-label build whose env.json carries only evmScanApiKey.

This regressed in 0296a76c "Upgrade to Etherscan V2" (first released in v4.47.0), which moved the evmScanApiKey check below the new etherscan.io branch. Before that commit, evmScanApiKey was checked first and such a build worked.

The fix: check evmScanApiKey first for every server, etherscan.io included. Treat an empty string or empty array as unconfigured, so a caller that defaults the option to [] (edge-react-gui does) still falls through to the deprecated per-network keys instead of sending an empty key. The hard error for etherscan.io is kept for the case where no usable key exists at all, and the deprecation warning now fires on that path too.

Testing

  • New unit tests in test/ethereum/fees/getEvmScanApiKey.test.ts cover: evmScanApiKey resolving for etherscan.io and for other scan hosts, an empty array falling back to the deprecated key, an absent evmScanApiKey falling back, and no usable key still throwing.
  • npm test: passing.
  • Verified end to end in edge-react-gui on an iOS simulator with an env.json carrying only evmScanApiKey. Before: an ETH wallet resync left the app at "Loading Transactions... 49.9% Complete" with an empty transaction list. After: the transaction list fully repopulated and the sync indicator cleared.

Note

Medium Risk
Changes init-option resolution for all EvmScan and gas-oracle calls on etherscan.io hosts across many EVM networks; behavior is intentionally different but fixes a production sync regression with targeted tests.

Overview
Fixes EVM wallet sync stuck at 50% when the app is configured with only the supported evmScanApiKey init option (no deprecated etherscanApiKey).

getEvmScanApiKey now checks evmScanApiKey first for every scan host, including api.etherscan.io. After the Etherscan V2 upgrade, the etherscan.io branch ran before that check and required etherscanApiKey, so builds with only evmScanApiKey sent no API key. Etherscan V2 rejects those requests, which broke EvmScanAdapter transaction history on the networks that rely on that host—history never advanced while balance could still sync, so overall progress averaged to half.

A new hasApiKey helper treats absent values, empty strings, and empty arrays as unconfigured (matching GUI defaults like evmScanApiKey: []), so those cases fall through to deprecated per-network keys instead of a keyless call. The hard error for etherscan.io with no usable key now references evmScanApiKey and logs the deprecation warning when falling back to etherscanApiKey.

Unit tests in test/ethereum/fees/getEvmScanApiKey.test.ts cover the etherscan.io and other hosts paths, empty-array fallback, and the throw when nothing is configured.

Reviewed by Cursor Bugbot for commit 2bd8113. Bugbot is set up for automated code reviews on this repo. Configure here.

The Etherscan V2 upgrade made key lookup for api.etherscan.io require the
deprecated etherscanApiKey option and throw before it ever considered
evmScanApiKey, the supported option. A build configured with only
evmScanApiKey therefore sent a keyless request, which Etherscan V2 rejects
with 'Missing/Invalid API Key'.

EvmScan is the only network adapter with a non-null fetchTxs on the 15 EVM
networks that route through api.etherscan.io, so the failed lookup left
transaction history permanently unsynced. With balance complete and history
at zero, the token sync tracker reports exactly 0.5, which surfaces as a
wallet stuck at 50%.

Check evmScanApiKey first for every server, and treat an empty string or
empty array as unconfigured so it still falls through to the deprecated
per-network keys.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

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.

1 participant