// Address-token detail view: shows a single token's balance and // filtered transactions for the selected address. const { $, showView, showFlash, flashCopyFeedback, addressDotHtml, addressTitle, escapeHtml, truncateMiddle, balanceLine, } = require("./helpers"); const { state, currentAddress, saveState, currentNetwork, } = require("../../shared/state"); const { TOKEN_BY_ADDRESS, resolveSymbol } = require("../../shared/tokenList"); const { formatUsd, getPrice, getAddressValueUsd, } = require("../../shared/prices"); const { fetchRecentTransactions, filterTransactions, } = require("../../shared/transactions"); const { resolveEnsNames } = require("../../shared/ens"); const { updateSendBalance, renderSendTokenSelect, resetSendValidation, } = require("./send"); const { log } = require("../../shared/log"); const makeBlockie = require("ethereum-blockies-base64"); let ctx; const EXT_ICON = `` + ``; function etherscanAddressLink(address) { return `${currentNetwork().explorerUrl}/address/${address}`; } function etherscanTokenLink(tokenContract, holderAddress) { return `${currentNetwork().explorerUrl}/token/${tokenContract}?a=${holderAddress}`; } function isoDate(timestamp) { const d = new Date(timestamp * 1000); const pad = (n) => String(n).padStart(2, "0"); if (state.utcTimestamps) { return ( d.getUTCFullYear() + "-" + pad(d.getUTCMonth() + 1) + "-" + pad(d.getUTCDate()) + "T" + pad(d.getUTCHours()) + ":" + pad(d.getUTCMinutes()) + ":" + pad(d.getUTCSeconds()) + "Z" ); } const offsetMin = -d.getTimezoneOffset(); const sign = offsetMin >= 0 ? "+" : "-"; const absOff = Math.abs(offsetMin); const tzStr = sign + pad(Math.floor(absOff / 60)) + ":" + pad(absOff % 60); return ( d.getFullYear() + "-" + pad(d.getMonth() + 1) + "-" + pad(d.getDate()) + "T" + pad(d.getHours()) + ":" + pad(d.getMinutes()) + ":" + pad(d.getSeconds()) + tzStr ); } function timeAgo(timestamp) { const seconds = Math.floor(Date.now() / 1000 - timestamp); if (seconds < 60) return seconds + " seconds ago"; const minutes = Math.floor(seconds / 60); if (minutes < 60) return minutes + " minute" + (minutes !== 1 ? "s" : "") + " ago"; const hours = Math.floor(minutes / 60); if (hours < 24) return hours + " hour" + (hours !== 1 ? "s" : "") + " ago"; const days = Math.floor(hours / 24); if (days < 30) return days + " day" + (days !== 1 ? "s" : "") + " ago"; const months = Math.floor(days / 30); if (months < 12) return months + " month" + (months !== 1 ? "s" : "") + " ago"; const years = Math.floor(days / 365); return years + " year" + (years !== 1 ? "s" : "") + " ago"; } let loadedTxs = []; let ensNameMap = new Map(); let currentSymbol = null; function show() { const wallet = state.wallets[state.selectedWallet]; const addr = wallet.addresses[state.selectedAddress]; const ai = state.selectedAddress; const tokenId = state.selectedToken; // Determine token symbol and balance let symbol, amount, price; const knownToken = TOKEN_BY_ADDRESS.get(tokenId.toLowerCase()); if (tokenId === "ETH") { symbol = "ETH"; amount = parseFloat(addr.balance || "0"); price = getPrice("ETH"); } else { const tb = (addr.tokenBalances || []).find( (t) => t.address.toLowerCase() === tokenId.toLowerCase(), ); symbol = resolveSymbol( tokenId, addr.tokenBalances, state.trackedTokens, ); amount = tb ? parseFloat(tb.balance || "0") : 0; price = getPrice(symbol); } currentSymbol = symbol; $("address-token-title").textContent = wallet.name + " \u2014 Address " + (ai + 1) + " \u2014 " + symbol; // Blockie const blockieEl = $("address-token-jazzicon"); blockieEl.innerHTML = ""; const img = document.createElement("img"); img.src = makeBlockie(addr.address); img.width = 48; img.height = 48; img.style.imageRendering = "pixelated"; img.style.borderRadius = "50%"; blockieEl.appendChild(img); // Address line $("address-token-dot").innerHTML = addressDotHtml(addr.address); $("address-token-full").dataset.full = addr.address; $("address-token-full").textContent = addr.address; const addrLink = tokenId !== "ETH" ? etherscanTokenLink(tokenId, addr.address) : etherscanAddressLink(addr.address); $("address-token-etherscan-link").innerHTML = `${EXT_ICON}`; // USD total for this token only const usdVal = price ? amount * price : null; const usdStr = formatUsd(usdVal); $("address-token-usd-total").innerHTML = usdStr || " "; // Single token balance line (no tokenId — not clickable here) $("address-token-balance").innerHTML = balanceLine(symbol, amount, price); // Token contract details (ERC-20 only) const contractInfo = $("address-token-contract-info"); if (tokenId !== "ETH") { const tb = (addr.tokenBalances || []).find( (t) => t.address.toLowerCase() === tokenId.toLowerCase(), ); const tracked = (state.trackedTokens || []).find( (t) => t.address.toLowerCase() === tokenId.toLowerCase(), ); const rawName = (tb && tb.name) || (tracked && tracked.name) || (knownToken && knownToken.name) || null; const rawSymbol = (tb && tb.symbol) || (tracked && tracked.symbol) || (knownToken && knownToken.symbol) || null; const tokenName = rawName ? escapeHtml(rawName) : null; const tokenSymbol = rawSymbol ? escapeHtml(rawSymbol) : null; const tokenDecimals = tb && tb.decimals != null ? tb.decimals : tracked && tracked.decimals != null ? tracked.decimals : knownToken && knownToken.decimals != null ? knownToken.decimals : null; const tokenHolders = tb && tb.holders != null ? tb.holders : null; const dot = addressDotHtml(tokenId); const tokenLink = `${currentNetwork().explorerUrl}/token/${escapeHtml(tokenId)}`; const projectUrl = knownToken && knownToken.url ? knownToken.url : null; let infoHtml = `