Compare commits

..

3 Commits

Author SHA1 Message Date
clawbot
d22c83e6dd fix: move banner creation after loadState() to read correct networkId
All checks were successful
check / check (push) Successful in 13s
2026-03-01 11:36:00 -08:00
18e431ffcf feat: show debug banner on testnet or debug mode, add TESTNET tag
Display the red debug banner when on a testnet OR when DEBUG is enabled.
When on a testnet, a 'TESTNET' label is shown on the far right side of
the banner. The banner label shows the network name when not in debug
mode, and 'DEBUG / INSECURE' when debug is on.

closes #140
2026-03-01 11:36:00 -08:00
a138a36710 fix: suppress USD display on testnet networks (#142)
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>
2026-03-01 20:31:45 +01:00
6 changed files with 91 additions and 18 deletions

View File

@@ -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 };
}

View File

@@ -172,6 +172,9 @@ function fallbackView() {
}
async function init() {
await loadState();
applyTheme(state.theme);
const net = currentNetwork();
if (DEBUG || net.isTestnet) {
const banner = document.createElement("div");
@@ -188,9 +191,6 @@ async function init() {
document.body.prepend(banner);
}
await loadState();
applyTheme(state.theme);
// Auto-default active address
if (
state.activeAddress === null &&

View File

@@ -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() {
`<a href="${addrLink}" target="_blank" rel="noopener" class="inline-flex items-center">${EXT_ICON}</a>`;
// 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 || "&nbsp;";

View File

@@ -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 + ".");
});
}

57
src/shared/chainSwitch.js Normal file
View 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 };

View File

@@ -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,