Compare commits
3 Commits
2f69ad0361
...
d22c83e6dd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d22c83e6dd | ||
| 18e431ffcf | |||
| a138a36710 |
@@ -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 };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,12 @@
|
|||||||
// Loads state, initializes views, triggers first render.
|
// Loads state, initializes views, triggers first render.
|
||||||
|
|
||||||
const { DEBUG } = require("../shared/constants");
|
const { DEBUG } = require("../shared/constants");
|
||||||
const { state, saveState, loadState } = require("../shared/state");
|
const {
|
||||||
|
state,
|
||||||
|
saveState,
|
||||||
|
loadState,
|
||||||
|
currentNetwork,
|
||||||
|
} = require("../shared/state");
|
||||||
const { refreshPrices } = require("../shared/prices");
|
const { refreshPrices } = require("../shared/prices");
|
||||||
const { refreshBalances } = require("../shared/balances");
|
const { refreshBalances } = require("../shared/balances");
|
||||||
const { $, showView } = require("./views/helpers");
|
const { $, showView } = require("./views/helpers");
|
||||||
@@ -167,18 +172,25 @@ function fallbackView() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
if (DEBUG) {
|
await loadState();
|
||||||
|
applyTheme(state.theme);
|
||||||
|
|
||||||
|
const net = currentNetwork();
|
||||||
|
if (DEBUG || net.isTestnet) {
|
||||||
const banner = document.createElement("div");
|
const banner = document.createElement("div");
|
||||||
banner.id = "debug-banner";
|
banner.id = "debug-banner";
|
||||||
banner.textContent = "DEBUG / INSECURE";
|
if (DEBUG && net.isTestnet) {
|
||||||
|
banner.textContent = "DEBUG / INSECURE [TESTNET]";
|
||||||
|
} else if (net.isTestnet) {
|
||||||
|
banner.textContent = "[TESTNET]";
|
||||||
|
} else {
|
||||||
|
banner.textContent = "DEBUG / INSECURE";
|
||||||
|
}
|
||||||
banner.style.cssText =
|
banner.style.cssText =
|
||||||
"background:#c00;color:#fff;text-align:center;font-size:10px;padding:1px 0;font-family:monospace;position:sticky;top:0;z-index:9999;";
|
"background:#c00;color:#fff;text-align:center;font-size:10px;padding:1px 0;font-family:monospace;position:sticky;top:0;z-index:9999;";
|
||||||
document.body.prepend(banner);
|
document.body.prepend(banner);
|
||||||
}
|
}
|
||||||
|
|
||||||
await loadState();
|
|
||||||
applyTheme(state.theme);
|
|
||||||
|
|
||||||
// Auto-default active address
|
// Auto-default active address
|
||||||
if (
|
if (
|
||||||
state.activeAddress === null &&
|
state.activeAddress === 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 || " ";
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const {
|
|||||||
getPrice,
|
getPrice,
|
||||||
getAddressValueUsd,
|
getAddressValueUsd,
|
||||||
} = require("../../shared/prices");
|
} = require("../../shared/prices");
|
||||||
const { state, saveState } = require("../../shared/state");
|
const { state, saveState, currentNetwork } = require("../../shared/state");
|
||||||
|
|
||||||
// When views are added, removed, or transitions between them change,
|
// When views are added, removed, or transitions between them change,
|
||||||
// update the view-navigation documentation in README.md to match.
|
// update the view-navigation documentation in README.md to match.
|
||||||
@@ -59,10 +59,18 @@ function showView(name) {
|
|||||||
clearFlash();
|
clearFlash();
|
||||||
state.currentView = name;
|
state.currentView = name;
|
||||||
saveState();
|
saveState();
|
||||||
if (DEBUG) {
|
const net = currentNetwork();
|
||||||
|
if (DEBUG || net.isTestnet) {
|
||||||
const banner = document.getElementById("debug-banner");
|
const banner = document.getElementById("debug-banner");
|
||||||
if (banner) {
|
if (banner) {
|
||||||
banner.textContent = "DEBUG / INSECURE (" + name + ")";
|
if (DEBUG && net.isTestnet) {
|
||||||
|
banner.textContent =
|
||||||
|
"DEBUG / INSECURE [TESTNET] (" + name + ")";
|
||||||
|
} else if (net.isTestnet) {
|
||||||
|
banner.textContent = "[TESTNET]";
|
||||||
|
} else {
|
||||||
|
banner.textContent = "DEBUG / INSECURE (" + name + ")";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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