Stored state had no version and no structural validation, so a corrupt blob produced a completely blank popup with no message and no recovery control, and made every dApp RPC call from every page answer a generic -32603. There was no reset or wipe control anywhere in the UI. saveState() now stamps a schema version and loadState() validates the shape. A version it does not understand, or a wallets array it cannot parse, lands on a recovery screen that names the problem, offers the stored record verbatim for export, and offers a destructive reset behind a typed confirmation. Unversioned but valid state -- which every existing install has -- migrates in place and keeps working; it is never shown a wipe prompt. A dApp call against unusable state answers -32007, which EIP-1474 leaves unassigned, rather than -32603. networkById() refuses an unknown id loudly instead of returning mainnet, and networkId is validated so a corrupt value cannot be used as an object key. Fields the gate does not refuse are floored by type, container and entries both: a malformed trackedTokens or tokenBalances entry is dropped rather than dereferenced. Verified by an independent sweep of 1152 corrupt blobs producing no blank popup, with the same harness showing 9 blanks against the previous revision.
94 lines
3.2 KiB
JavaScript
94 lines
3.2 KiB
JavaScript
// Network definitions for supported Ethereum networks.
|
|
// Each network specifies its chain ID, default RPC and Blockscout endpoints,
|
|
// and the block explorer base URL used for address/tx/token/block links.
|
|
|
|
const NETWORKS = {
|
|
mainnet: {
|
|
id: "mainnet",
|
|
name: "Ethereum Mainnet",
|
|
chainId: "0x1",
|
|
networkVersion: "1",
|
|
nativeCurrency: "ETH",
|
|
defaultRpcUrl: "https://ethereum-rpc.publicnode.com",
|
|
defaultBlockscoutUrl: "https://eth.blockscout.com/api/v2",
|
|
explorerUrl: "https://etherscan.io",
|
|
isTestnet: false,
|
|
},
|
|
sepolia: {
|
|
id: "sepolia",
|
|
name: "Sepolia Testnet",
|
|
chainId: "0xaa36a7",
|
|
networkVersion: "11155111",
|
|
nativeCurrency: "SepoliaETH",
|
|
defaultRpcUrl: "https://ethereum-sepolia-rpc.publicnode.com",
|
|
defaultBlockscoutUrl: "https://eth-sepolia.blockscout.com/api/v2",
|
|
explorerUrl: "https://sepolia.etherscan.io",
|
|
isTestnet: true,
|
|
},
|
|
};
|
|
|
|
const SUPPORTED_CHAIN_IDS = new Set(
|
|
Object.values(NETWORKS).map((n) => n.chainId),
|
|
);
|
|
|
|
// Thrown rather than defaulted. An id this build does not know used to answer
|
|
// with MAINNET, so a stored `{networkId:"base"}` rendered the selector as
|
|
// Ethereum Mainnet with no banner and answered eth_chainId 0x1, while rpcUrl
|
|
// still pointed at Base — the wallet telling the user and the page one chain
|
|
// while transacting on another. Nothing in this codebase has an unknown id to
|
|
// offer: stored state is validated against this table before it is loaded
|
|
// (src/shared/stateSchema.js), and every other caller passes an id it took
|
|
// from here. So an unknown id is a defect, and it says so, the same way
|
|
// getProvider() (src/shared/balances.js) already refuses one.
|
|
class UnknownNetworkError extends Error {
|
|
constructor(id) {
|
|
super(
|
|
"AutistMask does not know the network " +
|
|
JSON.stringify(id) +
|
|
"; it supports " +
|
|
Object.keys(NETWORKS).join(", "),
|
|
);
|
|
this.name = "UnknownNetworkError";
|
|
this.networkId = id;
|
|
}
|
|
}
|
|
|
|
// Own properties only: NETWORKS inherits from Object.prototype, so
|
|
// NETWORKS["constructor"] and NETWORKS["__proto__"] both answer with something
|
|
// truthy that is not a network. A stored id is untrusted input, and this is
|
|
// the test the validator uses to decide whether it may be adopted at all.
|
|
function isKnownNetworkId(id) {
|
|
return (
|
|
typeof id === "string" &&
|
|
Object.prototype.hasOwnProperty.call(NETWORKS, id)
|
|
);
|
|
}
|
|
|
|
function networkById(id) {
|
|
if (!isKnownNetworkId(id)) throw new UnknownNetworkError(id);
|
|
return NETWORKS[id];
|
|
}
|
|
|
|
function networkByChainId(chainId) {
|
|
for (const net of Object.values(NETWORKS)) {
|
|
if (net.chainId === chainId) return net;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Build a block explorer link for the given path type and value.
|
|
// type: "address" | "tx" | "token" | "block"
|
|
function explorerLink(network, type, value) {
|
|
return `${network.explorerUrl}/${type}/${value}`;
|
|
}
|
|
|
|
module.exports = {
|
|
NETWORKS,
|
|
SUPPORTED_CHAIN_IDS,
|
|
UnknownNetworkError,
|
|
isKnownNetworkId,
|
|
networkById,
|
|
networkByChainId,
|
|
explorerLink,
|
|
};
|