// The known-symbol spoof rule, in one place. // // A token that borrows a known symbol from a contract that is not the one // that symbol belongs to is a spoof, and the wallet hides it. Three surfaces // ask that question — the transaction history, the Send token selector and // the balance list — and they must answer it identically: a token the history // calls fake while the balance list lists it as a holding is worse than // either verdict alone, because the balance list is where the user forms // their belief about what they own (issue #235). // // KNOWN_SYMBOLS maps a symbol to the lowercased contract address that may // bear it, or to null. Null means the symbol belongs to the native asset, // which has no contract at all, so no contract may bear it and every one // that does is a spoof. "ETH" is the only such entry today; the rule is // written so that a second one needs no change here or at any call site. const { KNOWN_SYMBOLS } = require("./tokenList"); // Ethereum addresses are case-insensitive: EIP-55 mixed case is a checksum // over the address, not part of its identity. function normalizeAddress(addr) { return (addr || "").toLowerCase(); } // True when a token bearing `symbol` from contract `contractAddress` is // impersonating a known symbol. // // An empty contract address is the native asset, which is never a spoof: // this is what keeps the user's real ETH out of the rule, and it holds for // any symbol that becomes null-mapped later, not just for ETH. function isSpoofedSymbol(symbol, contractAddress) { const contract = normalizeAddress(contractAddress); if (!contract) return false; const sym = (symbol || "").toUpperCase(); if (!KNOWN_SYMBOLS.has(sym)) return false; const legit = KNOWN_SYMBOLS.get(sym); if (legit === null) return true; return contract !== normalizeAddress(legit); } module.exports = { isSpoofedSymbol, };