fix: suppress USD display on testnet networks (#142)
All checks were successful
check / check (push) Successful in 24s
All checks were successful
check / check (push) Successful in 24s
## Summary Fixes USD prices still showing on the main view when connected to a testnet (e.g. Sepolia). The root cause was stale mainnet prices lingering in the in-memory price cache after switching networks. ### Root Cause PR #137 correctly made `refreshPrices()` skip fetching on testnets, but the cached prices from a prior mainnet session remained in the `prices` object. All display functions (`getPrice()`, `getAddressValueUsd()`, etc.) used whatever was cached without checking which network was active. ### Changes - **`src/shared/prices.js`** - `refreshPrices()` now clears the price cache when on a testnet instead of silently returning - New `clearPrices()` function empties the cache and resets the fetch timestamp - `getPrice()` returns null on testnets (defense-in-depth) - `getAddressValueUsd()`, `getWalletValueUsd()`, `getTotalValueUsd()` return null on testnets - **`src/popup/views/settings.js`** - Network switcher immediately clears prices when switching to a testnet, so the UI updates without waiting for the next refresh cycle closes #139 Co-authored-by: clawbot <clawbot@noreply.git.eeqj.de> Reviewed-on: #142 Co-authored-by: clawbot <clawbot@noreply.example.org> Co-committed-by: clawbot <clawbot@noreply.example.org>
This commit was merged in pull request #142.
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
const { DEFAULT_RPC_URL } = require("../shared/constants");
|
const { DEFAULT_RPC_URL } = require("../shared/constants");
|
||||||
const { SUPPORTED_CHAIN_IDS, networkByChainId } = require("../shared/networks");
|
const { SUPPORTED_CHAIN_IDS, networkByChainId } = require("../shared/networks");
|
||||||
|
const { onChainSwitch } = require("../shared/chainSwitch");
|
||||||
const { getBytes } = require("ethers");
|
const { getBytes } = require("ethers");
|
||||||
const {
|
const {
|
||||||
state,
|
state,
|
||||||
@@ -345,12 +346,8 @@ async function handleRpc(method, params, origin) {
|
|||||||
return { result: null };
|
return { result: null };
|
||||||
}
|
}
|
||||||
if (SUPPORTED_CHAIN_IDS.has(chainId)) {
|
if (SUPPORTED_CHAIN_IDS.has(chainId)) {
|
||||||
// Switch to the requested network
|
|
||||||
const target = networkByChainId(chainId);
|
const target = networkByChainId(chainId);
|
||||||
state.networkId = target.id;
|
await onChainSwitch(target.id);
|
||||||
state.rpcUrl = target.defaultRpcUrl;
|
|
||||||
state.blockscoutUrl = target.defaultBlockscoutUrl;
|
|
||||||
await saveState();
|
|
||||||
broadcastChainChanged(target.chainId);
|
broadcastChainChanged(target.chainId);
|
||||||
return { result: null };
|
return { result: null };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ function etherscanAddressLink(address) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function etherscanTokenLink(tokenContract, holderAddress) {
|
function etherscanTokenLink(tokenContract, holderAddress) {
|
||||||
return `https://etherscan.io/token/${tokenContract}?a=${holderAddress}`;
|
return `${currentNetwork().explorerUrl}/token/${tokenContract}?a=${holderAddress}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isoDate(timestamp) {
|
function isoDate(timestamp) {
|
||||||
@@ -168,7 +168,7 @@ function show() {
|
|||||||
`<a href="${addrLink}" target="_blank" rel="noopener" class="inline-flex items-center">${EXT_ICON}</a>`;
|
`<a href="${addrLink}" target="_blank" rel="noopener" class="inline-flex items-center">${EXT_ICON}</a>`;
|
||||||
|
|
||||||
// USD total for this token only
|
// USD total for this token only
|
||||||
const usdVal = price ? amount * price : 0;
|
const usdVal = price ? amount * price : null;
|
||||||
const usdStr = formatUsd(usdVal);
|
const usdStr = formatUsd(usdVal);
|
||||||
$("address-token-usd-total").innerHTML = usdStr || " ";
|
$("address-token-usd-total").innerHTML = usdStr || " ";
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ const { $, showView, showFlash, escapeHtml } = require("./helpers");
|
|||||||
const { applyTheme } = require("../theme");
|
const { applyTheme } = require("../theme");
|
||||||
const { state, saveState, currentNetwork } = require("../../shared/state");
|
const { state, saveState, currentNetwork } = require("../../shared/state");
|
||||||
const { NETWORKS, SUPPORTED_CHAIN_IDS } = require("../../shared/networks");
|
const { NETWORKS, SUPPORTED_CHAIN_IDS } = require("../../shared/networks");
|
||||||
|
const { onChainSwitch } = require("../../shared/chainSwitch");
|
||||||
const { log, debugFetch } = require("../../shared/log");
|
const { log, debugFetch } = require("../../shared/log");
|
||||||
const deleteWallet = require("./deleteWallet");
|
const deleteWallet = require("./deleteWallet");
|
||||||
|
|
||||||
@@ -220,14 +221,9 @@ function init(ctx) {
|
|||||||
if (networkSelect) {
|
if (networkSelect) {
|
||||||
networkSelect.addEventListener("change", async () => {
|
networkSelect.addEventListener("change", async () => {
|
||||||
const newId = networkSelect.value;
|
const newId = networkSelect.value;
|
||||||
const net = NETWORKS[newId];
|
const net = await onChainSwitch(newId);
|
||||||
if (!net) return;
|
|
||||||
state.networkId = newId;
|
|
||||||
state.rpcUrl = net.defaultRpcUrl;
|
|
||||||
state.blockscoutUrl = net.defaultBlockscoutUrl;
|
|
||||||
$("settings-rpc").value = state.rpcUrl;
|
$("settings-rpc").value = state.rpcUrl;
|
||||||
$("settings-blockscout").value = state.blockscoutUrl;
|
$("settings-blockscout").value = state.blockscoutUrl;
|
||||||
await saveState();
|
|
||||||
showFlash("Switched to " + net.name + ".");
|
showFlash("Switched to " + net.name + ".");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
57
src/shared/chainSwitch.js
Normal file
57
src/shared/chainSwitch.js
Normal file
@@ -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 };
|
||||||
@@ -8,9 +8,13 @@ const prices = {};
|
|||||||
let lastFetchedAt = 0;
|
let lastFetchedAt = 0;
|
||||||
|
|
||||||
async function refreshPrices() {
|
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");
|
const { currentNetwork } = require("./state");
|
||||||
if (currentNetwork().isTestnet) return;
|
if (currentNetwork().isTestnet) {
|
||||||
|
clearPrices();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (now - lastFetchedAt < PRICE_CACHE_TTL) return;
|
if (now - lastFetchedAt < PRICE_CACHE_TTL) return;
|
||||||
try {
|
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) {
|
function getPrice(symbol) {
|
||||||
|
const { currentNetwork } = require("./state");
|
||||||
|
if (currentNetwork().isTestnet) return null;
|
||||||
return prices[symbol] || null;
|
return prices[symbol] || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,6 +56,8 @@ function formatUsd(amount) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getAddressValueUsd(addr) {
|
function getAddressValueUsd(addr) {
|
||||||
|
const { currentNetwork } = require("./state");
|
||||||
|
if (currentNetwork().isTestnet) return null;
|
||||||
if (!prices.ETH) return null;
|
if (!prices.ETH) return null;
|
||||||
let total = 0;
|
let total = 0;
|
||||||
const ethBal = parseFloat(addr.balance || "0");
|
const ethBal = parseFloat(addr.balance || "0");
|
||||||
@@ -54,6 +72,8 @@ function getAddressValueUsd(addr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getWalletValueUsd(wallet) {
|
function getWalletValueUsd(wallet) {
|
||||||
|
const { currentNetwork } = require("./state");
|
||||||
|
if (currentNetwork().isTestnet) return null;
|
||||||
if (!prices.ETH) return null;
|
if (!prices.ETH) return null;
|
||||||
let total = 0;
|
let total = 0;
|
||||||
for (const addr of wallet.addresses) {
|
for (const addr of wallet.addresses) {
|
||||||
@@ -63,6 +83,8 @@ function getWalletValueUsd(wallet) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getTotalValueUsd(wallets) {
|
function getTotalValueUsd(wallets) {
|
||||||
|
const { currentNetwork } = require("./state");
|
||||||
|
if (currentNetwork().isTestnet) return null;
|
||||||
if (!prices.ETH) return null;
|
if (!prices.ETH) return null;
|
||||||
let total = 0;
|
let total = 0;
|
||||||
for (const wallet of wallets) {
|
for (const wallet of wallets) {
|
||||||
@@ -74,6 +96,7 @@ function getTotalValueUsd(wallets) {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
prices,
|
prices,
|
||||||
refreshPrices,
|
refreshPrices,
|
||||||
|
clearPrices,
|
||||||
getPrice,
|
getPrice,
|
||||||
formatUsd,
|
formatUsd,
|
||||||
getAddressValueUsd,
|
getAddressValueUsd,
|
||||||
|
|||||||
Reference in New Issue
Block a user