Some checks failed
check / check (push) Has been cancelled
Three layers of defense against address poisoning attacks: 1. Known symbol verification: tokens claiming a symbol from the hardcoded top-250 list (e.g. "ETH", "USDT") but from an unrecognized contract are identified as spoofs and always hidden. Their contract addresses are auto-added to the fraud blocklist. 2. Low-holder filtering: tokens with <1000 holders are hidden from both transaction history and the send token selector. Controlled by the "Hide tokens with fewer than 1,000 holders" setting. 3. Fraud contract blocklist: a persistent local list of detected fraud contract addresses. Transactions involving these contracts are hidden. Controlled by the "Hide transactions from detected fraud contracts" setting. Both settings default to on and can be disabled in Settings. Fetching and filtering are separated: fetchRecentTransactions returns raw data, filterTransactions is a pure function applying heuristics. Token holder counts are now passed through from the Blockscout API.
107 lines
3.4 KiB
JavaScript
107 lines
3.4 KiB
JavaScript
// Send view: collect To, Amount, Token. Then go to confirmation.
|
|
|
|
const { $, showFlash, formatAddressHtml } = require("./helpers");
|
|
const { state, currentAddress } = require("../../shared/state");
|
|
const { getProvider } = require("../../shared/balances");
|
|
const { KNOWN_SYMBOLS } = require("../../shared/tokens");
|
|
|
|
function isSpoofedToken(t) {
|
|
const upper = (t.symbol || "").toUpperCase();
|
|
if (!KNOWN_SYMBOLS.has(upper)) return false;
|
|
const legit = KNOWN_SYMBOLS.get(upper);
|
|
if (legit === null) return true;
|
|
return t.address.toLowerCase() !== legit;
|
|
}
|
|
|
|
function renderSendTokenSelect(addr) {
|
|
const sel = $("send-token");
|
|
sel.innerHTML = '<option value="ETH">ETH</option>';
|
|
const fraudSet = new Set(
|
|
(state.fraudContracts || []).map((a) => a.toLowerCase()),
|
|
);
|
|
for (const t of addr.tokenBalances || []) {
|
|
if (isSpoofedToken(t)) continue;
|
|
if (fraudSet.has(t.address.toLowerCase())) continue;
|
|
if (state.hideLowHolderTokens && (t.holders || 0) < 1000) continue;
|
|
const opt = document.createElement("option");
|
|
opt.value = t.address;
|
|
opt.textContent = t.symbol;
|
|
sel.appendChild(opt);
|
|
}
|
|
}
|
|
|
|
function updateSendBalance() {
|
|
const addr = currentAddress();
|
|
if (!addr) return;
|
|
$("send-from").innerHTML = formatAddressHtml(
|
|
addr.address,
|
|
addr.ensName || null,
|
|
null,
|
|
);
|
|
const token = $("send-token").value;
|
|
if (token === "ETH") {
|
|
$("send-balance").textContent =
|
|
"Current balance: " + (addr.balance || "0") + " ETH";
|
|
} else {
|
|
const tb = (addr.tokenBalances || []).find(
|
|
(t) => t.address.toLowerCase() === token.toLowerCase(),
|
|
);
|
|
const symbol = tb ? tb.symbol : "?";
|
|
const bal = tb ? tb.balance || "0" : "0";
|
|
$("send-balance").textContent =
|
|
"Current balance: " + bal + " " + symbol;
|
|
}
|
|
}
|
|
|
|
function init(ctx) {
|
|
$("send-token").addEventListener("change", updateSendBalance);
|
|
|
|
$("btn-send-review").addEventListener("click", async () => {
|
|
const to = $("send-to").value.trim();
|
|
const amount = $("send-amount").value.trim();
|
|
if (!to) {
|
|
showFlash("Please enter a recipient address.");
|
|
return;
|
|
}
|
|
if (!amount || isNaN(parseFloat(amount)) || parseFloat(amount) <= 0) {
|
|
showFlash("Please enter a valid amount.");
|
|
return;
|
|
}
|
|
|
|
// Resolve ENS if needed
|
|
let resolvedTo = to;
|
|
let ensName = null;
|
|
if (to.includes(".") && !to.startsWith("0x")) {
|
|
try {
|
|
const provider = getProvider(state.rpcUrl);
|
|
const resolved = await provider.resolveName(to);
|
|
if (!resolved) {
|
|
showFlash("Could not resolve " + to);
|
|
return;
|
|
}
|
|
resolvedTo = resolved;
|
|
ensName = to;
|
|
} catch (e) {
|
|
showFlash("Failed to resolve ENS name.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
const token = $("send-token").value;
|
|
const addr = currentAddress();
|
|
|
|
ctx.showConfirmTx({
|
|
from: addr.address,
|
|
to: resolvedTo,
|
|
ensName: ensName,
|
|
amount: amount,
|
|
token: token,
|
|
balance: addr.balance,
|
|
});
|
|
});
|
|
|
|
$("btn-send-back").addEventListener("click", ctx.showAddressDetail);
|
|
}
|
|
|
|
module.exports = { init, updateSendBalance, renderSendTokenSelect };
|