Add total portfolio value, cached prices and balances
All checks were successful
check / check (push) Successful in 16s
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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user