Compare commits

..

2 Commits

Author SHA1 Message Date
user
b799686cd4 fix: zero-tx warning layout shift and contract address false positive
All checks were successful
check / check (push) Successful in 22s
- Reserve space for the warning upfront using visibility:hidden instead
  of display:none, preventing layout shift per README policy
- Move warning HTML to index.html as a static element rather than
  injecting dynamically
- Skip warning for contract addresses (check getCode first) since
  getTransactionCount only returns outgoing tx nonce
- Collapse reserved space when warning is not needed (address has
  history, is a contract, or on RPC error)
2026-02-28 14:18:28 -08:00
user
9e177f04a4 feat: show red warning when sending to address with zero tx history
On the confirm-tx view, asynchronously check the recipient address
transaction count via getTransactionCount(). If zero, display a
prominent red warning advising the user to double-check the address.

Closes #82
2026-02-28 14:18:28 -08:00
3 changed files with 12 additions and 18 deletions

View File

@@ -246,6 +246,7 @@ function show(txInfo) {
// Reset recipient warning: reserve space (visibility:hidden) while // Reset recipient warning: reserve space (visibility:hidden) while
// the async check runs, preventing layout shift per README policy. // the async check runs, preventing layout shift per README policy.
const recipientWarning = $("confirm-recipient-warning"); const recipientWarning = $("confirm-recipient-warning");
recipientWarning.style.display = "";
recipientWarning.style.visibility = "hidden"; recipientWarning.style.visibility = "hidden";
estimateGas(txInfo); estimateGas(txInfo);
@@ -301,18 +302,22 @@ async function checkRecipientHistory(txInfo) {
// the nonce, i.e. sent-tx count only). // the nonce, i.e. sent-tx count only).
const code = await provider.getCode(txInfo.to); const code = await provider.getCode(txInfo.to);
if (code && code !== "0x") { if (code && code !== "0x") {
// Contract address — no warning needed, keep space reserved // Contract address — hide the reserved space entirely
// but invisible to prevent layout shift el.style.display = "none";
return; return;
} }
const txCount = await provider.getTransactionCount(txInfo.to); const txCount = await provider.getTransactionCount(txInfo.to);
if (txCount === 0) { if (txCount === 0) {
el.style.visibility = "visible"; el.style.visibility = "visible";
} else {
// Address has history — collapse the reserved space
el.style.display = "none";
} }
// If txCount > 0, leave visibility:hidden — space stays reserved
} catch (e) { } catch (e) {
log.errorf("recipient history check failed:", e.message); log.errorf("recipient history check failed:", e.message);
// On error, leave visibility:hidden — no layout shift, no false warning // On error, collapse the reserved space rather than showing a
// false warning or leaving an empty gap
el.style.display = "none";
} }
} }

View File

@@ -158,9 +158,8 @@ function render() {
loadCalldata(tx.hash, tx.to); loadCalldata(tx.hash, tx.to);
} }
const isoStr = isoDate(tx.timestamp); $("tx-detail-time").textContent =
$("tx-detail-time").innerHTML = isoDate(tx.timestamp) + " (" + timeAgo(tx.timestamp) + ")";
copyableHtml(isoStr) + " (" + escapeHtml(timeAgo(tx.timestamp)) + ")";
$("tx-detail-status").textContent = tx.isError ? "Failed" : "Success"; $("tx-detail-status").textContent = tx.isError ? "Failed" : "Success";
showView("transaction"); showView("transaction");

View File

@@ -59,16 +59,6 @@ function txHashHtml(hash) {
); );
} }
function blockNumberHtml(blockNumber) {
const num = String(blockNumber);
const link = `https://etherscan.io/block/${num}`;
const extLink = `<a href="${link}" target="_blank" rel="noopener" class="inline-flex items-center">${EXT_ICON}</a>`;
return (
`<span class="underline decoration-dashed cursor-pointer" data-copy="${escapeHtml(num)}">${escapeHtml(num)}</span>` +
extLink
);
}
function attachCopyHandlers(viewId) { function attachCopyHandlers(viewId) {
document document
.getElementById(viewId) .getElementById(viewId)
@@ -199,7 +189,7 @@ function renderSuccess() {
$("success-tx-to").innerHTML = toAddressHtml(d.to); $("success-tx-to").innerHTML = toAddressHtml(d.to);
} }
$("success-tx-block").innerHTML = blockNumberHtml(d.blockNumber); $("success-tx-block").textContent = String(d.blockNumber);
$("success-tx-hash").innerHTML = txHashHtml(d.hash); $("success-tx-hash").innerHTML = txHashHtml(d.hash);
// Show decoded calldata details if present // Show decoded calldata details if present