Files
AutistMask/src/shared/etherscanLabels.js
T
clawbot 1f5cdeb7b4
check / check (push) Waiting to run
e2e / e2e-chrome (push) Waiting to run
e2e / e2e-firefox (push) Waiting to run
fix: a confirmation screen drawn again keeps the network it was reviewed for (closes #586)
ConfirmTx set the transaction's chain id to the active network's every
time it drew the screen, so a popup reopened onto a saved confirmation
after a network switch in Settings described the reviewed transfer as
the new network's and sent it there.

show() now sets the chain id only on a transaction Send hands over, and
the screen describes the transaction entirely on that network: native
currency, USD values, explorer links, and the node and explorer its
checks ask. explorerUrl(), renderAddressHtml(), getPrice(), formatFee()
and checkEtherscanLabel() take an optional chain id for this. A saved
transaction whose chain id names no network is not restored.

Model: opus-5-5
2026-10-19 07:38:15 +02:00

113 lines
4.1 KiB
JavaScript

// Etherscan address label lookup via page scraping.
// Extension users make the requests directly to Etherscan — no proxy needed.
// This is a best-effort enrichment: network failures return null silently.
const { networkByChainId } = require("./networks");
// Patterns in the page title that indicate a flagged address.
// Title format: "Fake_Phishing184810 | Address: 0x... | Etherscan"
const PHISHING_LABEL_PATTERNS = [/^Fake_Phishing/i, /^Phish:/i, /^Exploiter/i];
// Patterns in the page body that indicate a scam/phishing warning.
const SCAM_BODY_PATTERNS = [
/used in a\s+(?:\w+\s+)?phishing scam/i,
/used in a\s+(?:\w+\s+)?scam/i,
/wallet\s+drainer/i,
];
/**
* Parse the Etherscan address page HTML to extract label info.
* Exported for unit testing (no fetch needed).
*
* @param {string} html - Raw HTML of the Etherscan address page.
* @returns {{ label: string|null, isPhishing: boolean, warning: string|null }}
*/
function parseEtherscanPage(html) {
// Extract <title> content
const titleMatch = html.match(/<title[^>]*>([^<]+)<\/title>/i);
let label = null;
let isPhishing = false;
let warning = null;
if (titleMatch) {
const title = titleMatch[1].trim();
// Title: "LABEL | Address: 0x... | Etherscan" or "Address: 0x... | Etherscan"
const labelMatch = title.match(/^(.+?)\s*\|\s*Address:/);
if (labelMatch) {
const candidate = labelMatch[1].trim();
// Only treat as a label if it's not just "Address" (unlabeled addresses)
if (candidate.toLowerCase() !== "address") {
label = candidate;
}
}
}
// Check label against phishing patterns
if (label) {
for (const pat of PHISHING_LABEL_PATTERNS) {
if (pat.test(label)) {
isPhishing = true;
warning = `Etherscan labels this address as "${label}" (Phish/Hack).`;
break;
}
}
}
// Check page body for scam warning banners
if (!isPhishing) {
for (const pat of SCAM_BODY_PATTERNS) {
if (pat.test(html)) {
isPhishing = true;
warning = label
? `Etherscan labels this address as "${label}" and reports it was used in a scam.`
: "Etherscan reports this address was flagged for phishing/scam activity.";
break;
}
}
}
return { label, isPhishing, warning };
}
/**
* Fetch an address page from Etherscan and check for scam/phishing labels.
* Returns a warning object if the address is flagged, or null.
* Network failures return null silently (best-effort check).
*
* Uses the explorer of the network with `chainId`, or of the active network
* when it is omitted: etherscan.io on mainnet, sepolia.etherscan.io on
* Sepolia.
*
* @param {string} address - Ethereum address to check.
* @param {string} [chainId] - Chain id of the network to look it up on.
* @returns {Promise<{type: string, message: string, severity: string}|null>}
*/
async function checkEtherscanLabel(address, chainId) {
try {
// Lazy require to avoid pulling in chrome.storage at module scope
// (which breaks unit tests that only exercise parseEtherscanPage).
const { currentNetwork } = require("./state");
const network = chainId ? networkByChainId(chainId) : currentNetwork();
const etherscanBase = network.explorerUrl + "/address/";
const resp = await fetch(etherscanBase + address, {
headers: { Accept: "text/html" },
});
if (!resp.ok) return null;
const html = await resp.text();
const result = parseEtherscanPage(html);
if (result.isPhishing) {
return {
type: "etherscan-phishing",
message: result.warning,
severity: "critical",
};
}
return null;
} catch {
// Network errors are expected — Etherscan may rate-limit or block.
return null;
}
}
module.exports = { parseEtherscanPage, checkEtherscanLabel };