All checks were successful
check / check (push) Successful in 25s
Add support for the Sepolia testnet alongside Ethereum mainnet: - New src/shared/networks.js with network definitions (mainnet + sepolia) including chain IDs, default RPC/Blockscout endpoints, and explorer URLs - State now tracks networkId; defaults to mainnet for backward compatibility - Network selector in Settings lets users switch between mainnet and Sepolia - Switching networks updates RPC URL, Blockscout URL, and chain ID - All hardcoded etherscan.io URLs replaced with dynamic explorer links from the current network config (sepolia.etherscan.io for Sepolia) - Background handles wallet_switchEthereumChain for both supported chains and broadcasts chainChanged events to connected dApps - Inpage provider fetches chain ID on init and updates dynamically via chainChanged events (no more hardcoded 0x1) - Blockscout API uses eth-sepolia.blockscout.com for Sepolia - Etherscan label/phishing checks use the correct explorer per network - Price fetching skipped on testnets (tokens have no real value) - RPC validation checks against the selected network's chain ID - getProvider() uses the correct ethers Network for Sepolia API endpoints verified: - Etherscan: sepolia.etherscan.io - Blockscout: eth-sepolia.blockscout.com/api/v2 - RPC: ethereum-sepolia-rpc.publicnode.com closes #110
108 lines
3.8 KiB
JavaScript
108 lines
3.8 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.
|
|
|
|
// 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 current network's explorer URL so the lookup works on both
|
|
* mainnet (etherscan.io) and Sepolia (sepolia.etherscan.io).
|
|
*
|
|
* @param {string} address - Ethereum address to check.
|
|
* @returns {Promise<{type: string, message: string, severity: string}|null>}
|
|
*/
|
|
async function checkEtherscanLabel(address) {
|
|
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 etherscanBase = currentNetwork().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 };
|