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