const { $, showView, showFlash, balanceLinesForAddress } = require("./helpers"); const { state, currentAddress } = require("../../shared/state"); const { formatUsd, getAddressValueUsd } = require("../../shared/prices"); const { fetchRecentTransactions } = require("../../shared/transactions"); const { updateSendBalance } = require("./send"); const { log } = require("../../shared/log"); const QRCode = require("qrcode"); function show() { const wallet = state.wallets[state.selectedWallet]; const addr = wallet.addresses[state.selectedAddress]; const wi = state.selectedWallet; const ai = state.selectedAddress; $("address-title").textContent = wallet.name + " \u2014 Address " + (wi + 1) + "." + (ai + 1); $("address-full").textContent = addr.address; $("address-usd-total").textContent = formatUsd(getAddressValueUsd(addr)); const ensEl = $("address-ens"); if (addr.ensName) { ensEl.textContent = addr.ensName; ensEl.classList.remove("hidden"); } else { ensEl.classList.add("hidden"); } $("address-balances").innerHTML = balanceLinesForAddress(addr); renderSendTokenSelect(addr); $("tx-list").innerHTML = '
Loading...
'; showView("address"); loadTransactions(addr.address); } function isoDate(timestamp) { const d = new Date(timestamp * 1000); const pad = (n) => String(n).padStart(2, "0"); return ( d.getFullYear() + "-" + pad(d.getMonth() + 1) + "-" + pad(d.getDate()) + " " + pad(d.getHours()) + ":" + pad(d.getMinutes()) + ":" + pad(d.getSeconds()) ); } 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"; } function escapeHtml(s) { const div = document.createElement("div"); div.textContent = s; return div.innerHTML; } let loadedTxs = []; async function loadTransactions(address) { try { const txs = await fetchRecentTransactions(address, state.blockscoutUrl); loadedTxs = txs; renderTransactions(txs); } catch (e) { log.errorf("loadTransactions failed:", e.message); $("tx-list").innerHTML = '
Failed to load transactions.
'; } } function renderTransactions(txs) { const list = $("tx-list"); if (txs.length === 0) { list.innerHTML = '
No transactions found.
'; return; } list.innerHTML = ""; txs.forEach((tx, i) => { const counterparty = tx.direction === "sent" ? tx.to : tx.from; const dirLabel = tx.direction === "sent" ? "Sent" : "Received"; const errorStyle = tx.isError ? " opacity:0.5" : ""; const row = document.createElement("div"); row.className = "py-2 border-b border-border-light text-xs cursor-pointer hover:bg-hover"; if (errorStyle) row.style.cssText = errorStyle; const line1 = document.createElement("div"); line1.className = "flex justify-between"; const age = document.createElement("span"); age.className = "text-muted"; age.textContent = timeAgo(tx.timestamp); age.title = isoDate(tx.timestamp); const dir = document.createElement("span"); dir.className = tx.isError ? "text-muted" : ""; dir.textContent = dirLabel + (tx.isError ? " (failed)" : ""); line1.appendChild(age); line1.appendChild(dir); const line2 = document.createElement("div"); line2.className = "flex justify-between"; const addr = document.createElement("span"); addr.className = "break-all pr-2"; addr.textContent = counterparty; const amount = document.createElement("span"); amount.className = "shrink-0"; amount.textContent = tx.value + " " + tx.symbol; line2.appendChild(addr); line2.appendChild(amount); row.appendChild(line1); row.appendChild(line2); row.addEventListener("click", () => { state.selectedTx = i; showTxDetail(tx); }); list.appendChild(row); }); } function showTxDetail(tx) { $("tx-detail-hash").textContent = tx.hash; $("tx-detail-from").textContent = tx.from; $("tx-detail-to").textContent = tx.to; $("tx-detail-value").textContent = tx.value + " " + tx.symbol; $("tx-detail-time").textContent = isoDate(tx.timestamp); $("tx-detail-status").textContent = tx.isError ? "Failed" : "Success"; showView("transaction"); } function renderSendTokenSelect(addr) { const sel = $("send-token"); sel.innerHTML = ''; for (const t of addr.tokenBalances || []) { const opt = document.createElement("option"); opt.value = t.address; opt.textContent = t.symbol; sel.appendChild(opt); } } function init(ctx) { $("address-full").addEventListener("click", () => { const addr = $("address-full").textContent; if (addr) { navigator.clipboard.writeText(addr); showFlash("Copied!"); } }); $("btn-address-back").addEventListener("click", () => { ctx.renderWalletList(); showView("main"); }); $("btn-send").addEventListener("click", () => { const addr = state.wallets[state.selectedWallet].addresses[ state.selectedAddress ]; if (!addr.balance || parseFloat(addr.balance) === 0) { showFlash("Cannot send \u2014 zero balance."); return; } $("send-to").value = ""; $("send-amount").value = ""; updateSendBalance(); showView("send"); }); $("btn-receive").addEventListener("click", () => { const addr = currentAddress(); const address = addr ? addr.address : ""; $("receive-address").textContent = address; if (address) { QRCode.toCanvas($("receive-qr"), address, { width: 200, margin: 2, color: { dark: "#000000", light: "#ffffff" }, }); } showView("receive"); }); $("btn-add-token").addEventListener("click", ctx.showAddTokenView); $("btn-tx-back").addEventListener("click", () => { show(); }); } module.exports = { init, show };