fix: Post-purchase holder dashboard on landing page (#150)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
openhands 2026-02-24 15:02:22 +00:00
parent af10dcf4c6
commit fad6486152
5 changed files with 203 additions and 49 deletions

View file

@ -0,0 +1,65 @@
import { ref, onMounted, onUnmounted } from 'vue';
const ETH_PRICE_CACHE_MS = 5 * 60 * 1000; // 5 minutes
// Module-level cache shared across all composable instances on the same page
let _cachedPrice: number | null = null;
let _cacheTime = 0;
export function formatUsd(usd: number): string {
if (usd >= 1000) return `$${(usd / 1000).toFixed(1)}k`;
if (usd >= 1) return `$${usd.toFixed(2)}`;
if (usd >= 0.01) return `$${usd.toFixed(3)}`;
return `$${usd.toFixed(4)}`;
}
export function formatEthCompact(eth: number): string {
if (eth === 0) return '0 ETH';
if (eth >= 1) return `${eth.toFixed(2)} ETH`;
if (eth >= 0.01) return `${eth.toFixed(4)} ETH`;
if (eth >= 0.0001) return `${eth.toFixed(6)} ETH`;
return `${eth.toPrecision(4)} ETH`;
}
export function useEthPrice() {
const ethUsdPrice = ref<number | null>(_cachedPrice);
async function fetchEthPrice() {
const now = Date.now();
if (_cachedPrice !== null && now - _cacheTime < ETH_PRICE_CACHE_MS) {
ethUsdPrice.value = _cachedPrice;
return;
}
try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
const resp = await fetch(
'https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd',
{ signal: controller.signal },
);
clearTimeout(timeout);
if (!resp.ok) throw new Error('ETH price fetch failed');
const data = await resp.json();
if (data.ethereum?.usd) {
_cachedPrice = data.ethereum.usd;
_cacheTime = now;
ethUsdPrice.value = _cachedPrice;
}
} catch {
// Keep existing cached price or null; ETH fallback will be used
}
}
let interval: ReturnType<typeof setInterval> | null = null;
onMounted(async () => {
await fetchEthPrice();
interval = setInterval(() => void fetchEthPrice(), ETH_PRICE_CACHE_MS);
});
onUnmounted(() => {
if (interval) clearInterval(interval);
});
return { ethUsdPrice, fetchEthPrice };
}

View file

@ -5,8 +5,8 @@ const POLL_INTERVAL_MS = 30_000;
function formatTokenAmount(rawWei: string, decimals = 18): number {
try {
const big = BigInt(rawWei);
const divisor = 10 ** decimals;
return Number(big) / divisor;
// Use BigInt arithmetic to avoid float64 precision loss at high values
return Number(big * 10000n / (10n ** BigInt(decimals))) / 10000;
} catch {
return 0;
}