Add total portfolio value, cached prices and balances
All checks were successful
check / check (push) Successful in 16s

Total USD value displayed in 2x type above wallet list on Home.
Value aggregation: getAddressValueUsd (ETH + all tokens) →
getWalletValueUsd → getTotalValueUsd. Price API cached for 5
minutes, balance fetches cached for 60 seconds. Both caches
are app-wide — repeated calls to refreshPrices/refreshBalances
are no-ops within the TTL.
This commit is contained in:
2026-02-25 18:44:29 +07:00
parent 64bd541013
commit 2a8c051377
2 changed files with 62 additions and 13 deletions

View File

@@ -129,16 +129,27 @@ function addressFromPrivateKey(key) {
return w.address;
}
// -- price fetching --
// -- caching layer --
const PRICE_CACHE_TTL = 300000; // 5 minutes
const BALANCE_CACHE_TTL = 60000; // 60 seconds
const cache = {
prices: { data: {}, fetchedAt: 0 },
balances: { fetchedAt: 0 },
};
// { "ETH": 1234.56, "LINK": 8.60, ... }
const prices = {};
async function refreshPrices() {
const now = Date.now();
if (now - cache.prices.fetchedAt < PRICE_CACHE_TTL) return;
try {
const fetched = await getTopTokenPrices(25);
Object.assign(prices, fetched);
cache.prices.fetchedAt = now;
} catch (e) {
// prices stay empty on error
// prices stay stale on error
}
}
@@ -187,11 +198,12 @@ function formatBalance(wei) {
}
async function refreshBalances() {
const now = Date.now();
if (now - cache.balances.fetchedAt < BALANCE_CACHE_TTL) return;
const provider = getProvider();
const updates = [];
for (const wallet of state.wallets) {
for (const addr of wallet.addresses) {
// Fetch ETH balance
updates.push(
provider
.getBalance(addr.address)
@@ -200,7 +212,6 @@ async function refreshBalances() {
})
.catch(() => {}),
);
// Reverse ENS lookup
updates.push(
provider
.lookupAddress(addr.address)
@@ -214,15 +225,54 @@ async function refreshBalances() {
}
}
await Promise.all(updates);
cache.balances.fetchedAt = now;
await saveState();
}
function getAddressValueUsd(addr) {
let total = 0;
const ethBal = parseFloat(addr.balance || "0");
if (prices.ETH) {
total += ethBal * prices.ETH;
}
for (const token of addr.tokens || []) {
const tokenBal = parseFloat(token.balance || "0");
if (tokenBal > 0 && prices[token.symbol]) {
total += tokenBal * prices[token.symbol];
}
}
return total;
}
function getWalletValueUsd(wallet) {
let total = 0;
for (const addr of wallet.addresses) {
total += getAddressValueUsd(addr);
}
return total;
}
function getTotalValueUsd() {
let total = 0;
for (const wallet of state.wallets) {
total += getWalletValueUsd(wallet);
}
return total;
}
function renderTotalValue() {
const el = $("total-value");
if (!el) return;
el.textContent = formatUsd(getTotalValueUsd());
}
// -- render wallet list on main view --
function renderWalletList() {
const container = $("wallet-list");
if (state.wallets.length === 0) {
container.innerHTML =
'<p class="text-muted py-2">No wallets yet. Add one to get started.</p>';
renderTotalValue();
return;
}
@@ -244,12 +294,8 @@ function renderWalletList() {
html += `<div class="text-xs break-all">${addr.address}</div>`;
html += `<div class="flex justify-between items-center">`;
html += `<span class="text-xs">${addr.balance} ETH</span>`;
const ethUsd = prices.ETH
? parseFloat(addr.balance) * prices.ETH
: null;
if (ethUsd !== null) {
html += `<span class="text-xs text-muted">${formatUsd(ethUsd)}</span>`;
}
const addrUsd = getAddressValueUsd(addr);
html += `<span class="text-xs text-muted">${formatUsd(addrUsd)}</span>`;
html += `</div>`;
html += `</div>`;
});
@@ -285,6 +331,8 @@ function renderWalletList() {
renderWalletList();
});
});
renderTotalValue();
}
function showAddressDetail() {
@@ -294,9 +342,7 @@ function showAddressDetail() {
$("address-full").textContent = addr.address;
$("address-copied-msg").textContent = "";
$("address-eth-balance").textContent = addr.balance;
const ethUsd = prices.ETH ? parseFloat(addr.balance) * prices.ETH : null;
$("address-usd-value").textContent =
ethUsd !== null ? formatUsd(ethUsd) : "";
$("address-usd-value").textContent = formatUsd(getAddressValueUsd(addr));
const ensEl = $("address-ens");
if (addr.ensName) {
ensEl.textContent = addr.ensName;