// State management and extension storage persistence. const { DEFAULT_RPC_URL, DEFAULT_BLOCKSCOUT_URL } = require("./constants"); const { networkById } = require("./networks"); // Dependency-free constant module; safe to pull into a background bundle. const { RESTORABLE_VIEWS } = require("../popup/restorableViews"); const { storageGet, storageSet } = require("./browserApi"); const DEFAULT_STATE = { hasWallet: false, wallets: [], trackedTokens: [], networkId: "mainnet", rpcUrl: DEFAULT_RPC_URL, blockscoutUrl: DEFAULT_BLOCKSCOUT_URL, lastBalanceRefresh: 0, activeAddress: null, allowedSites: {}, deniedSites: {}, rememberSiteChoice: true, showZeroBalanceTokens: true, hideSpoofedSymbols: true, hideLowHolderTokens: true, hideFraudContracts: true, hideDustTransactions: true, dustThresholdGwei: 100000, utcTimestamps: false, fraudContracts: [], tokenHolderCache: {}, theme: "system", debugMode: false, }; const state = { ...DEFAULT_STATE, currentView: null, selectedWallet: null, selectedAddress: null, selectedToken: null, viewData: {}, viewStack: [], }; // Keep only the leading run of stored views the popup is willing to render. // // restoreView() refuses to reopen ONTO a non-restorable view, but the stack // behind it used to be restored verbatim, so Back could walk onto a screen // whose content is deliberately never re-rendered — and "show-phrase" has no // Back control to leave by. Truncating at the first such entry instead of // splicing it out keeps the result a prefix of the stored stack, so every // surviving entry's Back target is exactly the one it had; splicing would // silently re-point the entry above the hole at a different screen. // // Filtering happens here on load rather than in saveState(): the live // in-session stack is legitimate (the screen really is rendered while the // popup is open), and only a load-side filter also repairs the stacks // already in storage, including ones written before a view left the set. function restorableStack(stored, currentView) { // A stored stack that is missing or not an array keeps nothing, but it // still goes through the never-empty rule below rather than returning // early: otherwise a corrupt stack would depend on exactly the goBack() // fallback that the explicit ["main"] exists in order not to depend on. const source = Array.isArray(stored) ? stored : []; const cut = source.findIndex((view) => !RESTORABLE_VIEWS.has(view)); const kept = cut === -1 ? source.slice() : source.slice(0, cut); // A view restored below the root still needs somewhere for Back to go. if ( kept.length === 0 && currentView !== "main" && RESTORABLE_VIEWS.has(currentView) ) { return ["main"]; } return kept; } // Return the network configuration for the currently selected network. function currentNetwork() { return networkById(state.networkId); } async function saveState() { const persisted = { hasWallet: state.hasWallet, wallets: state.wallets, trackedTokens: state.trackedTokens, networkId: state.networkId, rpcUrl: state.rpcUrl, blockscoutUrl: state.blockscoutUrl, lastBalanceRefresh: state.lastBalanceRefresh, activeAddress: state.activeAddress, allowedSites: state.allowedSites, deniedSites: state.deniedSites, rememberSiteChoice: state.rememberSiteChoice, showZeroBalanceTokens: state.showZeroBalanceTokens, hideSpoofedSymbols: state.hideSpoofedSymbols, hideLowHolderTokens: state.hideLowHolderTokens, hideFraudContracts: state.hideFraudContracts, hideDustTransactions: state.hideDustTransactions, dustThresholdGwei: state.dustThresholdGwei, utcTimestamps: state.utcTimestamps, fraudContracts: state.fraudContracts, tokenHolderCache: state.tokenHolderCache, theme: state.theme, debugMode: state.debugMode, currentView: state.currentView, selectedWallet: state.selectedWallet, selectedAddress: state.selectedAddress, selectedToken: state.selectedToken, viewData: state.viewData, viewStack: state.viewStack, }; await storageSet({ autistmask: persisted }); } async function loadState() { const result = await storageGet("autistmask"); if (result.autistmask) { const saved = result.autistmask; state.wallets = saved.wallets || []; // Derived, never read from storage: a profile persisted with the flag // out of step with the wallet list would otherwise stay broken on // every load. Nothing depends on the two disagreeing. state.hasWallet = state.wallets.length > 0; state.trackedTokens = saved.trackedTokens || []; state.networkId = saved.networkId || DEFAULT_STATE.networkId; state.rpcUrl = saved.rpcUrl || DEFAULT_STATE.rpcUrl; state.blockscoutUrl = saved.blockscoutUrl || DEFAULT_STATE.blockscoutUrl; state.lastBalanceRefresh = saved.lastBalanceRefresh || 0; state.activeAddress = saved.activeAddress || null; state.allowedSites = saved.allowedSites && !Array.isArray(saved.allowedSites) ? saved.allowedSites : {}; state.deniedSites = saved.deniedSites && !Array.isArray(saved.deniedSites) ? saved.deniedSites : {}; state.rememberSiteChoice = saved.rememberSiteChoice !== undefined ? saved.rememberSiteChoice : true; state.showZeroBalanceTokens = saved.showZeroBalanceTokens !== undefined ? saved.showZeroBalanceTokens : true; // A profile written before this setting existed has no key for it. // It is a safety filter, so absent must load as on, not as undefined. state.hideSpoofedSymbols = saved.hideSpoofedSymbols !== undefined ? saved.hideSpoofedSymbols : true; state.hideLowHolderTokens = saved.hideLowHolderTokens !== undefined ? saved.hideLowHolderTokens : true; state.hideFraudContracts = saved.hideFraudContracts !== undefined ? saved.hideFraudContracts : true; state.hideDustTransactions = saved.hideDustTransactions !== undefined ? saved.hideDustTransactions : true; state.dustThresholdGwei = saved.dustThresholdGwei !== undefined ? saved.dustThresholdGwei : 100000; state.utcTimestamps = saved.utcTimestamps !== undefined ? saved.utcTimestamps : false; state.fraudContracts = saved.fraudContracts || []; state.tokenHolderCache = saved.tokenHolderCache || {}; state.theme = saved.theme || "system"; state.debugMode = saved.debugMode !== undefined ? saved.debugMode : false; state.currentView = saved.currentView || null; state.selectedWallet = saved.selectedWallet !== undefined ? saved.selectedWallet : null; state.selectedAddress = saved.selectedAddress !== undefined ? saved.selectedAddress : null; state.selectedToken = saved.selectedToken || null; state.viewData = saved.viewData || {}; state.viewStack = restorableStack(saved.viewStack, state.currentView); } } function currentAddress() { if (state.selectedWallet === null || state.selectedAddress === null) { return null; } return state.wallets[state.selectedWallet].addresses[state.selectedAddress]; } module.exports = { state, saveState, loadState, currentAddress, currentNetwork, };