When the background saves a network switch after the user approves a site's request, or another popup page saves one, an open popup takes the new network from the stored record inside a save, holding the lock, and checks the record once at start-up for a switch saved before it listened. The network it takes is not counted as the popup's own change, even when that save fails to write. It then leaves the page as a switch in Settings does. A switch in Settings is now made inside its own save too, so one picked while a follow waits for the lock is made after it. The popup never switches the network itself. Model: opus-5-5
109 lines
4.8 KiB
JavaScript
109 lines
4.8 KiB
JavaScript
// The field mutations a chain switch performs, applied to a state record
|
|
// handed in rather than to the module-level `state` singleton.
|
|
//
|
|
// Split out of chainSwitch.js so the background can perform a chain switch
|
|
// without the singleton being reachable from its bundle at all. The popup
|
|
// still goes through onChainSwitch() (chainSwitch.js), which applies this to
|
|
// the singleton and saves; the background applies it to the detached record of
|
|
// its own read-modify-write (src/background/state.js).
|
|
//
|
|
// Everything here is synchronous and touches nothing but the objects it is
|
|
// given: no storage, no caches, no imports beyond the network table. That is
|
|
// what makes it usable on a record that has been read fresh from storage
|
|
// microseconds earlier and is about to be written back.
|
|
|
|
const { networkById } = require("./networks");
|
|
|
|
// Switch `s` to `newNetworkId` and reset every piece of chain-specific state
|
|
// it carries. Returns the network configuration object for the new chain.
|
|
function applyChainSwitchFields(s, newNetworkId) {
|
|
const net = networkById(newNetworkId);
|
|
|
|
// --- core identity ---
|
|
// Endpoints are remembered per network rather than reset to the
|
|
// defaults, because a user who points the wallet at their own node has
|
|
// no way to get that URL back once it is gone: overwriting it moved
|
|
// every address and every transaction onto a third-party endpoint
|
|
// silently and permanently.
|
|
//
|
|
// s.rpcUrl / s.blockscoutUrl stay the live endpoints of the active
|
|
// network, so nothing that reads them changes. The invariant is that for
|
|
// the ACTIVE network those two fields are authoritative and the map entry
|
|
// may be stale (Settings writes the fields directly); for every other
|
|
// network the map is authoritative. Snapshotting the outgoing network
|
|
// here, before the switch, is what reconciles them.
|
|
if (!s.networkEndpoints) s.networkEndpoints = {};
|
|
s.networkEndpoints[s.networkId] = {
|
|
rpcUrl: s.rpcUrl,
|
|
blockscoutUrl: s.blockscoutUrl,
|
|
};
|
|
const remembered = s.networkEndpoints[net.id] || {};
|
|
|
|
// Tracked tokens are kept per network the same way, because a contract
|
|
// address names a token on one network only. s.trackedTokens stays the
|
|
// active network's list, so nothing that reads it changes: the outgoing
|
|
// list is put away under its network, and the incoming network's list, or
|
|
// an empty one, takes its place.
|
|
if (!s.networkTrackedTokens) s.networkTrackedTokens = {};
|
|
s.networkTrackedTokens[s.networkId] = s.trackedTokens;
|
|
s.trackedTokens = s.networkTrackedTokens[net.id] || [];
|
|
|
|
s.networkId = net.id;
|
|
s.rpcUrl = remembered.rpcUrl || net.defaultRpcUrl;
|
|
s.blockscoutUrl = remembered.blockscoutUrl || net.defaultBlockscoutUrl;
|
|
|
|
// --- balance / refresh state ---
|
|
// Reset last-refresh timestamp so the next polling cycle
|
|
// triggers an immediate balance refresh on the new chain.
|
|
s.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 s.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.
|
|
s.tokenHolderCache = {};
|
|
s.fraudContracts = [];
|
|
|
|
return net;
|
|
}
|
|
|
|
// Give `s` every field applyChainSwitchFields() sets, as `switched` holds it:
|
|
// the popup takes a switch the background saved from the stored record this
|
|
// way, rather than performing it again (takeStoredNetwork() in
|
|
// src/shared/state.js). Balances are taken by address, and an address
|
|
// `switched` does not have keeps none.
|
|
function copyChainSwitchFields(s, switched) {
|
|
s.networkEndpoints = switched.networkEndpoints;
|
|
s.networkTrackedTokens = switched.networkTrackedTokens;
|
|
s.trackedTokens = switched.trackedTokens;
|
|
s.networkId = switched.networkId;
|
|
s.rpcUrl = switched.rpcUrl;
|
|
s.blockscoutUrl = switched.blockscoutUrl;
|
|
s.lastBalanceRefresh = switched.lastBalanceRefresh;
|
|
s.tokenHolderCache = switched.tokenHolderCache;
|
|
s.fraudContracts = switched.fraudContracts;
|
|
|
|
const stored = new Map();
|
|
for (const wallet of switched.wallets) {
|
|
for (const addr of wallet.addresses) stored.set(addr.address, addr);
|
|
}
|
|
for (const wallet of s.wallets) {
|
|
for (const addr of wallet.addresses) {
|
|
const got = stored.get(addr.address);
|
|
addr.balance = got ? got.balance : "0";
|
|
addr.tokenBalances = got ? got.tokenBalances : [];
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = { applyChainSwitchFields, copyChainSwitchFields };
|