Add address color dots and cached ENS reverse lookups
Some checks failed
check / check (push) Has been cancelled

Deterministic colored dots derived from address bytes (16-color palette)
displayed before every address. ENS reverse resolution for transaction
counterparties with 12-hour localStorage cache.
This commit is contained in:
2026-02-26 03:26:52 +07:00
parent fbff44ade6
commit d28d5a5a51
5 changed files with 155 additions and 11 deletions

View File

@@ -1,7 +1,14 @@
const { $, showView, showFlash, balanceLinesForAddress } = require("./helpers");
const {
$,
showView,
showFlash,
balanceLinesForAddress,
addressDotHtml,
} = require("./helpers");
const { state, currentAddress } = require("../../shared/state");
const { formatUsd, getAddressValueUsd } = require("../../shared/prices");
const { fetchRecentTransactions } = require("../../shared/transactions");
const { resolveEnsNames } = require("../../shared/ens");
const { updateSendBalance } = require("./send");
const { log } = require("../../shared/log");
const QRCode = require("qrcode");
@@ -13,11 +20,13 @@ function show() {
const ai = state.selectedAddress;
$("address-title").textContent =
wallet.name + " \u2014 Address " + (wi + 1) + "." + (ai + 1);
$("address-dot").innerHTML = addressDotHtml(addr.address);
$("address-full").textContent = addr.address;
$("address-usd-total").textContent = formatUsd(getAddressValueUsd(addr));
const ensEl = $("address-ens");
if (addr.ensName) {
ensEl.textContent = addr.ensName;
ensEl.innerHTML =
addressDotHtml(addr.address) + escapeHtml(addr.ensName);
ensEl.classList.remove("hidden");
} else {
ensEl.classList.add("hidden");
@@ -80,10 +89,30 @@ function escapeHtml(s) {
let loadedTxs = [];
let ensNameMap = new Map();
async function loadTransactions(address) {
try {
const txs = await fetchRecentTransactions(address, state.blockscoutUrl);
loadedTxs = txs;
// Collect unique counterparty addresses for ENS resolution.
const counterparties = [
...new Set(
txs.map((tx) => (tx.direction === "sent" ? tx.to : tx.from)),
),
];
if (counterparties.length > 0) {
try {
ensNameMap = await resolveEnsNames(
counterparties,
state.rpcUrl,
);
} catch {
ensNameMap = new Map();
}
}
renderTransactions(txs);
} catch (e) {
log.errorf("loadTransactions failed:", e.message);
@@ -103,17 +132,20 @@ function renderTransactions(txs) {
let i = 0;
for (const tx of txs) {
const counterparty = tx.direction === "sent" ? tx.to : tx.from;
const ensName = ensNameMap.get(counterparty) || null;
const dirLabel = tx.direction === "sent" ? "Sent" : "Received";
const amountStr = escapeHtml(tx.value + " " + tx.symbol);
const maxAddr = Math.max(10, 38 - Math.max(0, amountStr.length - 10));
const addrStr = escapeHtml(truncateMiddle(counterparty, maxAddr));
const displayAddr = ensName || truncateMiddle(counterparty, maxAddr);
const addrStr = escapeHtml(displayAddr);
const dot = addressDotHtml(counterparty);
const err = tx.isError ? " (failed)" : "";
const opacity = tx.isError ? " opacity:0.5;" : "";
const ago = escapeHtml(timeAgo(tx.timestamp));
const iso = escapeHtml(isoDate(tx.timestamp));
html += `<div class="tx-row py-2 border-b border-border-light text-xs cursor-pointer hover:bg-hover" data-tx="${i}" style="${opacity}">`;
html += `<div class="flex justify-between"><span class="text-muted" title="${iso}">${ago}</span><span>${dirLabel}${err}</span></div>`;
html += `<div class="flex justify-between"><span>${addrStr}</span><span>${amountStr}</span></div>`;
html += `<div class="flex justify-between"><span class="flex items-center">${dot}${addrStr}</span><span>${amountStr}</span></div>`;
html += `</div>`;
i++;
}
@@ -126,10 +158,23 @@ function renderTransactions(txs) {
});
}
function txDetailAddressHtml(address) {
const ensName = ensNameMap.get(address) || null;
const dot = addressDotHtml(address);
if (ensName) {
return (
dot +
escapeHtml(ensName) +
`<div class="break-all">${escapeHtml(address)}</div>`
);
}
return dot + escapeHtml(address);
}
function showTxDetail(tx) {
$("tx-detail-hash").textContent = tx.hash;
$("tx-detail-from").textContent = tx.from;
$("tx-detail-to").textContent = tx.to;
$("tx-detail-from").innerHTML = txDetailAddressHtml(tx.from);
$("tx-detail-to").innerHTML = txDetailAddressHtml(tx.to);
$("tx-detail-value").textContent = tx.value + " " + tx.symbol;
$("tx-detail-time").textContent =
isoDate(tx.timestamp) + " (" + timeAgo(tx.timestamp) + ")";