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
130 lines
4.5 KiB
JavaScript
130 lines
4.5 KiB
JavaScript
// Address warning module.
|
|
// Provides local and async (RPC-based) warning checks for Ethereum addresses.
|
|
// Returns arrays of {type, message, severity} objects.
|
|
|
|
const { isScamAddress } = require("./scamlist");
|
|
const { isBurnAddress } = require("./constants");
|
|
const { checkEtherscanLabel } = require("./etherscanLabels");
|
|
const { log } = require("./log");
|
|
|
|
/**
|
|
* Check an address against local-only lists (scam, burn, self-send).
|
|
* Synchronous — no network calls.
|
|
*
|
|
* @param {string} address - The target address to check.
|
|
* @param {object} [options] - Optional context.
|
|
* @param {string} [options.fromAddress] - Sender address (for self-send check).
|
|
* @returns {Array<{type: string, message: string, severity: string}>}
|
|
*/
|
|
function getLocalWarnings(address, options = {}) {
|
|
const warnings = [];
|
|
const addr = address.toLowerCase();
|
|
|
|
if (isScamAddress(addr)) {
|
|
warnings.push({
|
|
type: "scam",
|
|
message:
|
|
"This address is on a known scam/fraud list. Do not send funds to this address.",
|
|
severity: "critical",
|
|
});
|
|
}
|
|
|
|
if (isBurnAddress(addr)) {
|
|
warnings.push({
|
|
type: "burn",
|
|
message:
|
|
"This is a known null/burn address. Funds sent here are permanently destroyed and cannot be recovered.",
|
|
severity: "critical",
|
|
});
|
|
}
|
|
|
|
if (options.fromAddress && addr === options.fromAddress.toLowerCase()) {
|
|
warnings.push({
|
|
type: "self-send",
|
|
message: "You are sending to your own address.",
|
|
severity: "warning",
|
|
});
|
|
}
|
|
|
|
return warnings;
|
|
}
|
|
|
|
/**
|
|
* Check an address against local lists AND via RPC queries.
|
|
* Async — performs network calls to check contract status, tx count and
|
|
* balance.
|
|
*
|
|
* @param {string} address - The target address to check.
|
|
* @param {object} provider - An ethers.js provider instance.
|
|
* @param {object} [options] - Optional context.
|
|
* @param {string} [options.fromAddress] - Sender address (for self-send check).
|
|
* @param {string} [options.chainId] - Chain id of the network whose explorer
|
|
* the Etherscan label check asks; the active network's when omitted.
|
|
* @returns {Promise<Array<{type: string, message: string, severity: string}>>}
|
|
*/
|
|
async function getFullWarnings(address, provider, options = {}) {
|
|
const warnings = getLocalWarnings(address, options);
|
|
|
|
let isContract = false;
|
|
try {
|
|
const code = await provider.getCode(address);
|
|
if (code && code !== "0x") {
|
|
isContract = true;
|
|
warnings.push({
|
|
type: "contract",
|
|
message:
|
|
"This address is a smart contract, not a regular wallet.",
|
|
severity: "warning",
|
|
});
|
|
}
|
|
} catch (e) {
|
|
log.errorf("contract check failed:", e.shortMessage || e.message);
|
|
}
|
|
|
|
// The transaction count is the number of transactions the address has
|
|
// sent, so an address that has only ever received has a count of zero.
|
|
// Native token it received stays in its balance until it sends a
|
|
// transaction, so the warning also needs a zero balance. A contract is
|
|
// skipped: it has its own warning above.
|
|
if (!isContract) {
|
|
try {
|
|
const txCount = await provider.getTransactionCount(address);
|
|
if (txCount === 0) {
|
|
const balance = await provider.getBalance(address);
|
|
if (balance === 0n) {
|
|
warnings.push({
|
|
type: "new-address",
|
|
message:
|
|
"This address has never sent a transaction and holds none of the network's native token. Double-check it is correct.",
|
|
severity: "info",
|
|
});
|
|
}
|
|
}
|
|
} catch (e) {
|
|
log.errorf(
|
|
"tx count or balance check failed:",
|
|
e.shortMessage || e.message,
|
|
);
|
|
}
|
|
}
|
|
|
|
// Etherscan label check (best-effort async — network failures are silent).
|
|
// Runs for ALL addresses including contracts, since many dangerous
|
|
// flagged addresses on Etherscan (drainers, phishing contracts) are contracts.
|
|
try {
|
|
const etherscanWarning = await checkEtherscanLabel(
|
|
address,
|
|
options.chainId,
|
|
);
|
|
if (etherscanWarning) {
|
|
warnings.push(etherscanWarning);
|
|
}
|
|
} catch (e) {
|
|
log.errorf("etherscan label check failed:", e.message);
|
|
}
|
|
|
|
return warnings;
|
|
}
|
|
|
|
module.exports = { getLocalWarnings, getFullWarnings };
|