diff --git a/landing/src/components/LiveStats.vue b/landing/src/components/LiveStats.vue
index 93d0ceb..6807e37 100644
--- a/landing/src/components/LiveStats.vue
+++ b/landing/src/components/LiveStats.vue
@@ -3,7 +3,8 @@
ETH Reserve
-
{{ ethReserveAmount }}
+
{{ ethReserveDisplay }}
+
{{ ethReserveSecondary }}
{{ growthIndicator }}
ETH / Token
-
{{ ethPerToken }}
+
{{ ethPerTokenDisplay }}
+
{{ ethPerTokenSecondary }}
Floor Price
-
{{ floorPriceAmount }}
+
{{ floorPriceDisplay }}
+
{{ floorPriceSecondary }}
{{ floorDistanceText }}
@@ -78,7 +81,11 @@ interface Stats {
const stats = ref(null);
const error = ref(false);
+const ethUsdPrice = ref(null);
let refreshInterval: number | null = null;
+let ethPriceInterval: number | null = null;
+let ethPriceCacheTime = 0;
+const ETH_PRICE_CACHE_MS = 5 * 60 * 1000; // 5 minutes
// Helper: safely convert a Wei BigInt string to ETH float
function weiToEth(wei: string | null | undefined): number {
@@ -191,10 +198,53 @@ const holderGrowthClass = computed(() => {
return 'growth-flat';
});
-const ethReserveAmount = computed(() => {
- if (!stats.value) return '0.00 ETH';
+// Format tiny ETH values without truncating to zero
+function formatSmallEth(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`;
+ const s = eth.toPrecision(4);
+ return `${s} ETH`;
+}
+
+// Format USD with tiered precision
+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)}`;
+}
+
+// Fetch ETH/USD price from CoinGecko with 5-min cache
+async function fetchEthPrice() {
+ const now = Date.now();
+ if (ethUsdPrice.value !== null && (now - ethPriceCacheTime) < ETH_PRICE_CACHE_MS) return;
+ try {
+ const resp = await fetch('https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd');
+ if (!resp.ok) throw new Error('ETH price fetch failed');
+ const data = await resp.json();
+ if (data.ethereum?.usd) {
+ ethUsdPrice.value = data.ethereum.usd;
+ ethPriceCacheTime = now;
+ }
+ } catch {
+ // Keep existing cached price or null; ETH fallback will be used
+ }
+}
+
+// Primary/secondary display helpers
+const ethReserveDisplay = computed(() => {
+ if (!stats.value) return '$0.00';
const eth = weiToEth(stats.value.lastEthReserve);
- return `${eth.toFixed(2)} ETH`;
+ if (ethUsdPrice.value) return formatUsd(eth * ethUsdPrice.value);
+ return formatSmallEth(eth);
+});
+
+const ethReserveSecondary = computed((): string | null => {
+ if (!stats.value || !ethUsdPrice.value) return null;
+ const eth = weiToEth(stats.value.lastEthReserve);
+ return `(${eth.toFixed(2)} ETH)`;
});
// Growth indicator: null = hide (< 7 days history or missing)
@@ -222,9 +272,22 @@ const ethPerToken = computed(() => {
const supply = Number(stats.value.kraikenTotalSupply) / 1e18;
if (supply === 0) return '—';
const ratio = reserve / supply;
- if (ratio >= 0.01) return `${ratio.toFixed(4)} ETH`;
- if (ratio >= 0.0001) return `${ratio.toFixed(6)} ETH`;
- return `${ratio.toExponential(2)} ETH`;
+ return ratio;
+});
+
+const ethPerTokenDisplay = computed(() => {
+ if (!stats.value) return '—';
+ const ratio = ethPerToken.value;
+ if (typeof ratio === 'string') return ratio;
+ if (ethUsdPrice.value) return formatUsd(ratio * ethUsdPrice.value);
+ return formatSmallEth(ratio);
+});
+
+const ethPerTokenSecondary = computed((): string | null => {
+ if (!stats.value || !ethUsdPrice.value) return null;
+ const ratio = ethPerToken.value;
+ if (typeof ratio === 'string') return null;
+ return `(${formatSmallEth(ratio).replace(' ETH', '')} ETH)`;
});
// Net supply change indicator (7d)
@@ -292,8 +355,19 @@ const showFloorPrice = computed(() => {
const floorPriceAmount = computed(() => {
if (!showFloorPrice.value || !stats.value?.floorPriceWei) return null;
- const eth = weiToEth(stats.value.floorPriceWei);
- return `${eth.toFixed(4)} ETH`;
+ return weiToEth(stats.value.floorPriceWei);
+});
+
+const floorPriceDisplay = computed(() => {
+ if (floorPriceAmount.value == null) return null;
+ const eth = floorPriceAmount.value;
+ if (ethUsdPrice.value) return formatUsd(eth * ethUsdPrice.value);
+ return formatSmallEth(eth);
+});
+
+const floorPriceSecondary = computed((): string | null => {
+ if (floorPriceAmount.value == null || !ethUsdPrice.value) return null;
+ return `(${formatSmallEth(floorPriceAmount.value)})`;
});
const floorDistanceText = computed((): string | null => {
@@ -401,13 +475,14 @@ async function fetchStats() {
onMounted(() => {
fetchStats();
+ fetchEthPrice();
refreshInterval = window.setInterval(fetchStats, 30000); // Refresh every 30 seconds
+ ethPriceInterval = window.setInterval(fetchEthPrice, ETH_PRICE_CACHE_MS); // Refresh ETH price every 5 min
});
onUnmounted(() => {
- if (refreshInterval) {
- clearInterval(refreshInterval);
- }
+ if (refreshInterval) clearInterval(refreshInterval);
+ if (ethPriceInterval) clearInterval(ethPriceInterval);
});
@@ -489,6 +564,11 @@ onUnmounted(() => {
&.growth-flat
color: rgba(240, 240, 240, 0.45)
+.stat-secondary
+ font-size: 12px
+ color: rgba(240, 240, 240, 0.45)
+ letter-spacing: 0.3px
+
.floor-distance
font-size: 11px
color: rgba(240, 240, 240, 0.5)