diff --git a/src/popup/views/confirmTx.js b/src/popup/views/confirmTx.js index 522e9e5..fb40850 100644 --- a/src/popup/views/confirmTx.js +++ b/src/popup/views/confirmTx.js @@ -25,6 +25,7 @@ const { decryptWithPassword } = require("../../shared/vault"); const { formatUsd, getPrice } = require("../../shared/prices"); const { getProvider } = require("../../shared/balances"); const { isScamAddress } = require("../../shared/scamlist"); +const { hasTransactionHistory } = require("../../shared/transactions"); const { ERC20_ABI } = require("../../shared/constants"); const { log } = require("../../shared/log"); const makeBlockie = require("ethereum-blockies-base64"); @@ -244,6 +245,7 @@ function show(txInfo) { showView("confirm-tx"); estimateGas(txInfo); + checkRecipientHistory(txInfo); } async function estimateGas(txInfo) { @@ -286,6 +288,31 @@ async function estimateGas(txInfo) { } } +async function checkRecipientHistory(txInfo) { + try { + const hasHistory = await hasTransactionHistory( + txInfo.to, + state.blockscoutUrl, + ); + if (hasHistory === false) { + const warningsEl = $("confirm-warnings"); + const warningDiv = document.createElement("div"); + warningDiv.className = + "border border-dashed p-2 mb-1 text-xs font-bold"; + warningDiv.style.color = "#dc2626"; + warningDiv.style.borderColor = "#dc2626"; + warningDiv.textContent = + "WARNING: This address has ZERO transaction history on-chain. " + + "It has never sent or received any transactions. " + + "Double-check the address before sending."; + warningsEl.appendChild(warningDiv); + warningsEl.classList.remove("hidden"); + } + } catch (e) { + log.errorf("recipient history check failed:", e.message); + } +} + function init(ctx) { $("btn-confirm-send").addEventListener("click", async () => { const password = $("confirm-tx-password").value; diff --git a/src/shared/transactions.js b/src/shared/transactions.js index ad45698..21fa964 100644 --- a/src/shared/transactions.js +++ b/src/shared/transactions.js @@ -251,4 +251,36 @@ function filterTransactions(txs, filters = {}) { return { transactions: filtered, newFraudContracts: newFraud }; } -module.exports = { fetchRecentTransactions, filterTransactions }; +async function hasTransactionHistory(address, blockscoutUrl) { + try { + const resp = await debugFetch(blockscoutUrl + "/addresses/" + address); + if (!resp.ok) { + // If Blockscout returns 404, the address has never been seen on-chain. + if (resp.status === 404) return false; + log.errorf( + "blockscout address check:", + resp.status, + resp.statusText, + ); + return null; // unknown + } + const data = await resp.json(); + // Blockscout v2 address endpoint returns tx counts. + // An address with no history may still exist (e.g. received ETH once + // but shows 0 outgoing). We check both transactions_count and + // token_transfers_count to be thorough. + const txCount = + (parseInt(data.transactions_count, 10) || 0) + + (parseInt(data.token_transfers_count, 10) || 0); + return txCount > 0; + } catch (e) { + log.errorf("hasTransactionHistory error:", e.message); + return null; // unknown, don't block the user + } +} + +module.exports = { + fetchRecentTransactions, + filterTransactions, + hasTransactionHistory, +};