diff --git a/src/background/index.js b/src/background/index.js index cb925f3..9a4879f 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -4,6 +4,7 @@ const { DEFAULT_RPC_URL } = require("../shared/constants"); const { SUPPORTED_CHAIN_IDS, networkByChainId } = require("../shared/networks"); +const { onChainSwitch } = require("../shared/chainSwitch"); const { getBytes } = require("ethers"); const { state, @@ -345,12 +346,8 @@ async function handleRpc(method, params, origin) { return { result: null }; } if (SUPPORTED_CHAIN_IDS.has(chainId)) { - // Switch to the requested network const target = networkByChainId(chainId); - state.networkId = target.id; - state.rpcUrl = target.defaultRpcUrl; - state.blockscoutUrl = target.defaultBlockscoutUrl; - await saveState(); + await onChainSwitch(target.id); broadcastChainChanged(target.chainId); return { result: null }; } diff --git a/src/popup/views/addressToken.js b/src/popup/views/addressToken.js index bb9a4e2..9614f61 100644 --- a/src/popup/views/addressToken.js +++ b/src/popup/views/addressToken.js @@ -51,7 +51,7 @@ function etherscanAddressLink(address) { } function etherscanTokenLink(tokenContract, holderAddress) { - return `https://etherscan.io/token/${tokenContract}?a=${holderAddress}`; + return `${currentNetwork().explorerUrl}/token/${tokenContract}?a=${holderAddress}`; } function isoDate(timestamp) { @@ -168,7 +168,7 @@ function show() { `${EXT_ICON}`; // USD total for this token only - const usdVal = price ? amount * price : 0; + const usdVal = price ? amount * price : null; const usdStr = formatUsd(usdVal); $("address-token-usd-total").innerHTML = usdStr || " "; diff --git a/src/popup/views/settings.js b/src/popup/views/settings.js index 9bae55a..07b42ac 100644 --- a/src/popup/views/settings.js +++ b/src/popup/views/settings.js @@ -2,6 +2,7 @@ const { $, showView, showFlash, escapeHtml } = require("./helpers"); const { applyTheme } = require("../theme"); const { state, saveState, currentNetwork } = require("../../shared/state"); const { NETWORKS, SUPPORTED_CHAIN_IDS } = require("../../shared/networks"); +const { onChainSwitch } = require("../../shared/chainSwitch"); const { log, debugFetch } = require("../../shared/log"); const deleteWallet = require("./deleteWallet"); @@ -220,14 +221,9 @@ function init(ctx) { if (networkSelect) { networkSelect.addEventListener("change", async () => { const newId = networkSelect.value; - const net = NETWORKS[newId]; - if (!net) return; - state.networkId = newId; - state.rpcUrl = net.defaultRpcUrl; - state.blockscoutUrl = net.defaultBlockscoutUrl; + const net = await onChainSwitch(newId); $("settings-rpc").value = state.rpcUrl; $("settings-blockscout").value = state.blockscoutUrl; - await saveState(); showFlash("Switched to " + net.name + "."); }); } diff --git a/src/shared/chainSwitch.js b/src/shared/chainSwitch.js new file mode 100644 index 0000000..96d7bd3 --- /dev/null +++ b/src/shared/chainSwitch.js @@ -0,0 +1,57 @@ +// Consolidated chain-switch handler. +// +// Every state change required when the active network changes is +// performed here so that callers (settings UI, background +// wallet_switchEthereumChain, future chain additions) all go +// through a single code path. +// +// Adding a new chain (e.g. ETC) requires only a new entry in +// networks.js — no per-caller wiring is needed. + +const { networkById } = require("./networks"); +const { clearPrices } = require("./prices"); + +// Switch the active chain and reset all chain-specific cached state. +// Returns the network configuration object for the new chain. +async function onChainSwitch(newNetworkId) { + const { state, saveState } = require("./state"); + + const net = networkById(newNetworkId); + + // --- core identity --- + state.networkId = net.id; + state.rpcUrl = net.defaultRpcUrl; + state.blockscoutUrl = net.defaultBlockscoutUrl; + + // --- price cache --- + // Prices are chain-specific (testnet tokens are worthless, + // ETC has different pricing, etc.). + clearPrices(); + + // --- balance / refresh state --- + // Reset last-refresh timestamp so the next polling cycle + // triggers an immediate balance refresh on the new chain. + state.lastBalanceRefresh = 0; + + // Clear per-address balances and token balances so stale data + // from the previous chain is never displayed while the first + // refresh on the new chain is in flight. + for (const wallet of state.wallets) { + for (const addr of wallet.addresses) { + addr.balance = "0"; + addr.tokenBalances = []; + } + } + + // --- chain-specific caches --- + // Token holder counts and fraud contract lists are + // chain-specific and must not carry over. + state.tokenHolderCache = {}; + state.fraudContracts = []; + + await saveState(); + + return net; +} + +module.exports = { onChainSwitch }; diff --git a/src/shared/prices.js b/src/shared/prices.js index 363f40c..ac45dd1 100644 --- a/src/shared/prices.js +++ b/src/shared/prices.js @@ -8,9 +8,13 @@ const prices = {}; let lastFetchedAt = 0; async function refreshPrices() { - // Testnet tokens have no real market value — skip price fetching. + // Testnet tokens have no real market value — skip price fetching + // and clear any stale mainnet prices so the UI shows no USD values. const { currentNetwork } = require("./state"); - if (currentNetwork().isTestnet) return; + if (currentNetwork().isTestnet) { + clearPrices(); + return; + } const now = Date.now(); if (now - lastFetchedAt < PRICE_CACHE_TTL) return; try { @@ -22,7 +26,19 @@ async function refreshPrices() { } } +// Clear all cached prices and reset the fetch timestamp so the +// next refreshPrices() call will fetch fresh data. +function clearPrices() { + for (const key of Object.keys(prices)) { + delete prices[key]; + } + lastFetchedAt = 0; +} + +// Return the USD price for a symbol, or null on testnet / unknown. function getPrice(symbol) { + const { currentNetwork } = require("./state"); + if (currentNetwork().isTestnet) return null; return prices[symbol] || null; } @@ -40,6 +56,8 @@ function formatUsd(amount) { } function getAddressValueUsd(addr) { + const { currentNetwork } = require("./state"); + if (currentNetwork().isTestnet) return null; if (!prices.ETH) return null; let total = 0; const ethBal = parseFloat(addr.balance || "0"); @@ -54,6 +72,8 @@ function getAddressValueUsd(addr) { } function getWalletValueUsd(wallet) { + const { currentNetwork } = require("./state"); + if (currentNetwork().isTestnet) return null; if (!prices.ETH) return null; let total = 0; for (const addr of wallet.addresses) { @@ -63,6 +83,8 @@ function getWalletValueUsd(wallet) { } function getTotalValueUsd(wallets) { + const { currentNetwork } = require("./state"); + if (currentNetwork().isTestnet) return null; if (!prices.ETH) return null; let total = 0; for (const wallet of wallets) { @@ -74,6 +96,7 @@ function getTotalValueUsd(wallets) { module.exports = { prices, refreshPrices, + clearPrices, getPrice, formatUsd, getAddressValueUsd,