fix: LiveStats: remove floor price from landing, fix ETH reserve data pipeline, strip browser RPC (#196)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
d398c7667d
commit
7219f1b21c
4 changed files with 64 additions and 94 deletions
|
|
@ -4,7 +4,7 @@
|
|||
<span class="live-dot" :class="{ 'live-dot-error': error }"></span>
|
||||
<span class="live-text">Live</span>
|
||||
</div>
|
||||
<div class="stats-grid" :class="{ 'has-floor': showFloorPrice }">
|
||||
<div class="stats-grid">
|
||||
<div class="stat-item" :class="{ 'stat-changed': changedStats.has('ethReserve') }">
|
||||
<div class="stat-label">ETH Reserve</div>
|
||||
<div class="stat-value">{{ ethReserveDisplay }}</div>
|
||||
|
|
@ -13,18 +13,13 @@
|
|||
<svg v-if="ethReserveSpark.length > 1" class="sparkline" viewBox="0 0 80 24" preserveAspectRatio="none">
|
||||
<polyline :points="toSvgPoints(ethReserveSpark)" fill="none" stroke="rgba(96,165,250,0.5)" stroke-width="1.5" stroke-linejoin="round" stroke-linecap="round" />
|
||||
</svg>
|
||||
<span v-else-if="stats" class="spark-placeholder">Gathering data...</span>
|
||||
</div>
|
||||
<div class="stat-item" :class="{ 'stat-changed': changedStats.has('ethPerToken') }">
|
||||
<div class="stat-label">ETH / Token</div>
|
||||
<div class="stat-value">{{ ethPerTokenDisplay }}</div>
|
||||
<div v-if="ethPerTokenSecondary" class="stat-secondary">{{ ethPerTokenSecondary }}</div>
|
||||
</div>
|
||||
<div v-if="showFloorPrice" class="stat-item" :class="{ 'stat-changed': changedStats.has('floorPrice') }">
|
||||
<div class="stat-label">Floor Price</div>
|
||||
<div class="stat-value">{{ floorPriceDisplay }}</div>
|
||||
<div v-if="floorPriceSecondary" class="stat-secondary">{{ floorPriceSecondary }}</div>
|
||||
<div v-if="floorDistanceText" class="floor-distance">{{ floorDistanceText }}</div>
|
||||
</div>
|
||||
<div class="stat-item" :class="{ 'stat-changed': changedStats.has('supply') }">
|
||||
<div class="stat-label">Supply (7d)</div>
|
||||
<div class="stat-value">{{ totalSupply }}</div>
|
||||
|
|
@ -32,6 +27,7 @@
|
|||
<svg v-if="supplySpark.length > 1" class="sparkline" viewBox="0 0 80 24" preserveAspectRatio="none">
|
||||
<polyline :points="toSvgPoints(supplySpark)" fill="none" stroke="rgba(74,222,128,0.5)" stroke-width="1.5" stroke-linejoin="round" stroke-linecap="round" />
|
||||
</svg>
|
||||
<span v-else-if="stats" class="spark-placeholder">Gathering data...</span>
|
||||
</div>
|
||||
<div class="stat-item" :class="{ 'stat-changed': changedStats.has('holders') }">
|
||||
<div class="stat-label">Holders</div>
|
||||
|
|
@ -40,6 +36,7 @@
|
|||
<svg v-if="holdersSpark.length > 1" class="sparkline" viewBox="0 0 80 24" preserveAspectRatio="none">
|
||||
<polyline :points="toSvgPoints(holdersSpark)" fill="none" stroke="rgba(251,191,36,0.5)" stroke-width="1.5" stroke-linejoin="round" stroke-linecap="round" />
|
||||
</svg>
|
||||
<span v-else-if="stats" class="spark-placeholder">Gathering data...</span>
|
||||
</div>
|
||||
<div class="stat-item" :class="{ 'pulse': isRecentRebalance, 'stat-changed': changedStats.has('rebalances') }">
|
||||
<div class="stat-label">Rebalances</div>
|
||||
|
|
@ -76,10 +73,6 @@ interface Stats {
|
|||
burnedLastWeek: string;
|
||||
netSupplyChangeWeek: string;
|
||||
ethReserveGrowthBps: number | null;
|
||||
feesEarned7dEth: string | null;
|
||||
floorPriceWei: string | null;
|
||||
floorDistanceBps: number | null;
|
||||
currentPriceWei: string | null;
|
||||
ringBuffer: string[] | null;
|
||||
ringBufferPointer: number | null;
|
||||
}
|
||||
|
|
@ -362,35 +355,6 @@ const totalSupply = computed(() => {
|
|||
return `${(supply / 1000).toFixed(1)}K KRK`;
|
||||
});
|
||||
|
||||
// Floor price: only show when data is available
|
||||
const showFloorPrice = computed(() => {
|
||||
return !!(stats.value?.floorPriceWei && stats.value.floorPriceWei !== '0');
|
||||
});
|
||||
|
||||
const floorPriceAmount = computed(() => {
|
||||
if (!showFloorPrice.value || !stats.value?.floorPriceWei) return null;
|
||||
return weiToEth(stats.value.floorPriceWei);
|
||||
});
|
||||
|
||||
const floorPriceDisplay = computed(() => {
|
||||
if (floorPriceAmount.value == null) return '—';
|
||||
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 => {
|
||||
if (!stats.value || stats.value.floorDistanceBps == null) return null;
|
||||
const distPct = Number(stats.value.floorDistanceBps) / 100;
|
||||
const aboveBelow = distPct >= 0 ? 'above' : 'below';
|
||||
return `(${Math.abs(distPct).toFixed(0)}% ${aboveBelow})`;
|
||||
});
|
||||
|
||||
async function fetchStats() {
|
||||
try {
|
||||
const endpoint = `${window.location.origin}/api/graphql`;
|
||||
|
|
@ -413,10 +377,6 @@ async function fetchStats() {
|
|||
burnedLastWeek
|
||||
netSupplyChangeWeek
|
||||
ethReserveGrowthBps
|
||||
feesEarned7dEth
|
||||
floorPriceWei
|
||||
floorDistanceBps
|
||||
currentPriceWei
|
||||
ringBuffer
|
||||
ringBufferPointer
|
||||
}
|
||||
|
|
@ -434,47 +394,6 @@ async function fetchStats() {
|
|||
|
||||
if (data.data?.statss?.items?.[0]) {
|
||||
const s = data.data.statss.items[0];
|
||||
// If ETH reserve is 0 from Ponder (EthScarcity/EthAbundance events never emitted),
|
||||
// read WETH balance of the Uniswap V3 pool directly via RPC
|
||||
if (s.lastEthReserve === '0' || !s.lastEthReserve) {
|
||||
try {
|
||||
const rpc = `${window.location.origin}/api/rpc`;
|
||||
const deployResp = await fetch(`${window.location.origin}/app/deployments-local.json`);
|
||||
if (deployResp.ok) {
|
||||
const deployments = await deployResp.json();
|
||||
const krkAddr = deployments.contracts?.Kraiken;
|
||||
if (krkAddr) {
|
||||
const wethAddr = '0x4200000000000000000000000000000000000006';
|
||||
const factoryAddr = '0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24';
|
||||
const fee = 10000; // 1% fee tier
|
||||
|
||||
// Step 1: factory.getPool(weth, kraiken, fee) → pool address
|
||||
// selector: 0x1698ee82
|
||||
const wethPad = wethAddr.slice(2).padStart(64, '0');
|
||||
const krkPad = krkAddr.slice(2).padStart(64, '0');
|
||||
const feePad = fee.toString(16).padStart(64, '0');
|
||||
const poolRes = await fetch(rpc, { method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'eth_call',
|
||||
params: [{ to: factoryAddr, data: '0x1698ee82' + wethPad + krkPad + feePad }, 'latest'] }) });
|
||||
const poolJson = await poolRes.json();
|
||||
const poolAddr = '0x' + (poolJson.result || '').slice(26);
|
||||
|
||||
if (poolAddr.length === 42 && poolAddr !== '0x' + '0'.repeat(40)) {
|
||||
// Step 2: weth.balanceOf(pool) → ETH reserve in pool
|
||||
const poolPad = poolAddr.slice(2).padStart(64, '0');
|
||||
const balRes = await fetch(rpc, { method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ jsonrpc: '2.0', id: 2, method: 'eth_call',
|
||||
params: [{ to: wethAddr, data: '0x70a08231' + poolPad }, 'latest'] }) });
|
||||
const balJson = await balRes.json();
|
||||
const wethBal = BigInt(balJson.result || '0x0');
|
||||
if (wethBal > 0n) {
|
||||
s.lastEthReserve = wethBal.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch { /* ignore RPC fallback errors */ }
|
||||
}
|
||||
// Detect changed fields for flash animation
|
||||
const prev = stats.value;
|
||||
if (prev) {
|
||||
|
|
@ -487,7 +406,6 @@ async function fetchStats() {
|
|||
changed.add('supply');
|
||||
changed.add('ethPerToken');
|
||||
}
|
||||
if (s.floorPriceWei !== prev.floorPriceWei) changed.add('floorPrice');
|
||||
if (s.holderCount !== prev.holderCount) changed.add('holders');
|
||||
if (s.recentersLastWeek !== prev.recentersLastWeek) changed.add('rebalances');
|
||||
if (changed.size > 0) {
|
||||
|
|
@ -539,9 +457,6 @@ onUnmounted(() => {
|
|||
margin: 0 auto
|
||||
padding: 0 32px
|
||||
|
||||
&.has-floor
|
||||
max-width: 1020px
|
||||
|
||||
@media (min-width: 640px)
|
||||
grid-template-columns: repeat(2, 1fr)
|
||||
|
||||
|
|
@ -549,9 +464,6 @@ onUnmounted(() => {
|
|||
grid-template-columns: repeat(3, 1fr)
|
||||
gap: 32px
|
||||
|
||||
&.has-floor
|
||||
grid-template-columns: repeat(3, 1fr)
|
||||
|
||||
.stat-item
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
|
@ -608,10 +520,11 @@ onUnmounted(() => {
|
|||
color: rgba(240, 240, 240, 0.45)
|
||||
letter-spacing: 0.3px
|
||||
|
||||
.floor-distance
|
||||
.spark-placeholder
|
||||
font-size: 11px
|
||||
color: rgba(240, 240, 240, 0.5)
|
||||
color: rgba(240, 240, 240, 0.3)
|
||||
letter-spacing: 0.3px
|
||||
margin-top: 4px
|
||||
|
||||
.pulse
|
||||
animation: pulse-glow 2s ease-in-out infinite
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue