mirror of
https://github.com/luxfi/explore.git
synced 2026-07-27 05:54:15 +00:00
fix(explore/validators): render real P-chain data in light mode + brand currency symbol
The /validators view fetched real validators (5, 2.5e18 weight) but rendered white-on-white in light mode — data present in DOM, invisible to users. Set text-[var(--color-text-primary)] on each of the three lux validator component roots so all text inherits the theme-flipping color token. Also replace the hardcoded "LUX" stake unit with config.chain.currency.symbol (AI on Hanzo), matching NetworkStats. No decimals change (6dp is the codebase convention, consistent with P-chain getMinStake).
This commit is contained in:
@@ -166,6 +166,81 @@ All symlinks reference this single source of truth.
|
||||
|
||||
## Recent Changes
|
||||
|
||||
### Investor-grade data: real validators + honest-empty stats/DEX (v1.0.11)
|
||||
Removed every fabricated placeholder; data is now real-from-chain or honestly
|
||||
empty, never faked. Root causes + fixes:
|
||||
- **Validators/Stake = 0 was a proxy bug, not missing data.** P-chain
|
||||
`platform.getCurrentValidators` returns real validators (5, total weight
|
||||
2.5e18 on hanzo). `pages/api/pchain.ts` derived its base URL by stripping
|
||||
ONLY `/ext/bc/C/rpc` via regex; brand RPCs are `…/ext/bc/<chain>/rpc`
|
||||
(hanzo: `/ext/bc/hanzo/rpc`), so the regex no-op'd and the proxy POSTed to
|
||||
`…/ext/bc/hanzo/rpc/ext/bc/P` → HTML 404 → "Unexpected non-whitespace
|
||||
character after JSON at position 4" → 502. Fix: `new URL(rpcUrl).origin`
|
||||
(works for every brand) + defensive text→JSON parse with a clear error.
|
||||
`ui/stats/lux/NetworkStats.tsx` (Network Overview: Total Chains / Validators
|
||||
/ Connected / Total Stake / Avg Uptime) already had honest `—` fallbacks and
|
||||
now renders REAL values. `lib/api/luxnet/instance.ts` was already safe (uses
|
||||
`new URL().hostname`), so B/D-Chain SDK calls were unaffected.
|
||||
- **"Placeholder Counter 9.074M ×8"** came from `stubs/stats.ts` `STATS_COUNTER`
|
||||
(`value:'9074405', title:'Placeholder Counter'`) used as React-Query
|
||||
`placeholderData` in `ui/stats/NumberWidgetsList.tsx`. When the Blockscout
|
||||
**stats microservice** is absent (hanzo has NO `NEXT_PUBLIC_STATS_API_HOST`;
|
||||
every `api-explore.hanzo.network/api/v1|v2/*` 404s) the stub leaked as "real".
|
||||
Fix: neutralize the stub (`title:'', value:'0'`) AND gate the counters/charts
|
||||
in `ui/pages/Stats.tsx` on `config.features.stats.isEnabled` — show the live
|
||||
chain-sourced Network Overview always, with an honest "being indexed" note
|
||||
when no stats service. Aggregate counters (total txns/addresses, gas-used-
|
||||
today, history charts) genuinely need a stats indexer that isn't deployed.
|
||||
- **DEX showed static demo numbers.** `lib/api/dchain/useDexData.ts` had
|
||||
hardcoded `DEMO_SYMBOLS/ORDERS/TRADES/POOLS/OVERVIEW` (LUX/USDT 24.85, etc.)
|
||||
served when the D-Chain (DexVM) is unavailable. Deleted ALL demo data → real
|
||||
D-Chain data or honest-empty; `ui/dex/DexPage.tsx` renders an honest empty/
|
||||
error message per tab.
|
||||
- **Bridge signers = 0 is already honest** (`lib/api/bchain/useBridgeData.ts`
|
||||
returns real B-Chain signer set or `EMPTY_STATS`; B-Chain not yet reporting).
|
||||
- **Homepage is already investor-grade**: real RPC blocks (#8–#13), tx hashes,
|
||||
gas (2.5 Gwei), Total blocks/txns/wallets — `ui/home/Stats.tsx` degrades to
|
||||
RPC when the general indexer errors; `HOMEPAGE_STATS` stub is placeholder-only.
|
||||
- **Blockchain dropdown clipped its last item** ("Verified Contracts"):
|
||||
`@luxfi/ui` `Tooltip` `variant="popover"` Content defaults to
|
||||
`overflow-hidden px-3 py-2`, clipping a tall menu `<ul>`. Fixed at the
|
||||
consumer (`ui/snippets/navigation/horizontal/NavLinkGroup.tsx`) via
|
||||
`contentProps={{ className:'overflow-visible px-1.5 py-2' }}` (twMerge wins)
|
||||
+ `placement:'bottom-start'`. No node_modules patch.
|
||||
- Still needs a real indexer (honest-empty until then): aggregate Stats
|
||||
counters + history charts (Blockscout stats microservice), DEX live
|
||||
markets/orders/trades (DexVM indexer), Bridge signer set (B-Chain).
|
||||
- Built v1.0.11 via the kaniko Job pattern (NO GHA) → `ghcr.io/{hanzoai,zooai,
|
||||
luxfi}/explore:v1.0.11`; deploy = image patch of `explore-fe-*` Deployments
|
||||
on do-sfo3-lux-k8s ns `lux-mainnet` (plain Deployments, not operator-managed).
|
||||
|
||||
### Validators page: visible in light mode + brand currency (v1.0.12)
|
||||
v1.0.11 fetched REAL P-chain validators (5, total weight 2.5e18 on hanzo) but
|
||||
a Playwright pass found the `/validators` view rendered **white-on-white in
|
||||
light mode** — the data was present in the DOM yet invisible to users (only the
|
||||
tab bar showed because it alone used `text-[var(--color-text-primary)]`). The
|
||||
three custom Lux components (`ui/validators/lux/{ValidatorsDashboard,
|
||||
ValidatorsList,DelegatorsList}.tsx`) set NO text color, so they inherited a
|
||||
white color that doesn't flip with the theme. Fixes:
|
||||
- Set `text-[var(--color-text-primary)]` on each component's root container —
|
||||
one place per component; the token flips per `styles/tokens.css` (light
|
||||
`rgba(16,17,18,.80)` / dark `rgba(255,255,255,.80)`), so all descendants now
|
||||
render the mode-correct color (dark on light, light on dark).
|
||||
- Stake unit was hardcoded `LUX` on every brand; now `config.chain.currency.
|
||||
symbol || 'LUX'` (= `AI` on Hanzo) — matches `NetworkStats.tsx`'s pattern,
|
||||
DRY across all 6 stake labels in the three components.
|
||||
- NOT changed: `formatStake` uses `LUX_DECIMALS = 6`, the codebase-wide
|
||||
convention (NetworkStats + ChainDetailPage use the same); P-chain
|
||||
`getMinStake` returns `minValidatorStake:2000000000` (= 2000 at 6dp),
|
||||
consistent with 6. (Three copies of this helper is a latent DRY violation to
|
||||
consolidate later, but the value is correct — left as-is to avoid drift.)
|
||||
- `next.config.js` has `typescript.ignoreBuildErrors:true`; repo carries ~224
|
||||
pre-existing tsc errors (all in `*.pw.tsx` / unrelated) — build is unaffected.
|
||||
Dockerfile uses **pnpm** (not yarn). Edited files lint+typecheck clean.
|
||||
- Built v1.0.12 via the kaniko Job (NO GHA) → `ghcr.io/{hanzoai,zooai,luxfi}/
|
||||
explore:v1.0.12`; deployed by image patch of `explore-fe-{hanzo,lux,zoo}` on
|
||||
do-sfo3-lux-k8s ns `lux-mainnet`. (Built on do-sfo3-hanzo-k8s ns `hanzo`.)
|
||||
|
||||
### Multi-brand hostname-driven header logo (explore v1.0.10)
|
||||
- ONE image white-labels by request Host: `configs/app/chainRegistry.ts`
|
||||
`getCurrentChain()` resolves the chain (and its brand) from the hostname,
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { Skeleton } from '@luxfi/ui/skeleton';
|
||||
import React from 'react';
|
||||
|
||||
import config from 'configs/app';
|
||||
import type { PChainDelegator, PChainValidator } from 'lib/api/pchain';
|
||||
import dayjs from 'lib/date/dayjs';
|
||||
import { Skeleton } from '@luxfi/ui/skeleton';
|
||||
|
||||
import { formatStake, truncateNodeId } from './utils';
|
||||
|
||||
const CURRENCY = config.chain.currency.symbol || 'LUX';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Types
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -69,7 +72,7 @@ const DelegatorsList = ({ validators, isLoading }: DelegatorsListProps) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="overflow-hidden"
|
||||
className="overflow-hidden text-[var(--color-text-primary)]"
|
||||
>
|
||||
{ /* Header */ }
|
||||
<div className="flex">
|
||||
@@ -125,7 +128,7 @@ const DelegatorsList = ({ validators, isLoading }: DelegatorsListProps) => {
|
||||
{ truncateNodeId(d.validatorNodeID) }
|
||||
</div>
|
||||
<div className="text-left lg:text-right">
|
||||
{ formatStake(d.stakeAmount) } LUX
|
||||
{ formatStake(d.stakeAmount) } { CURRENCY }
|
||||
</div>
|
||||
<div>
|
||||
{ formatTimestamp(d.startTime) }
|
||||
@@ -134,7 +137,7 @@ const DelegatorsList = ({ validators, isLoading }: DelegatorsListProps) => {
|
||||
{ formatTimestamp(d.endTime) }
|
||||
</div>
|
||||
<div className="text-left lg:text-right">
|
||||
{ formatStake(d.potentialReward) } LUX
|
||||
{ formatStake(d.potentialReward) } { CURRENCY }
|
||||
</div>
|
||||
</div>
|
||||
)) }
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { Skeleton } from '@luxfi/ui/skeleton';
|
||||
import React from 'react';
|
||||
|
||||
import config from 'configs/app';
|
||||
import type { PChainValidator, ValidatorStats } from 'lib/api/pchain';
|
||||
import { Skeleton } from '@luxfi/ui/skeleton';
|
||||
|
||||
import { formatStake, formatUptime, truncateNodeId } from './utils';
|
||||
|
||||
const CURRENCY = config.chain.currency.symbol || 'LUX';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -61,13 +64,13 @@ const StakeBreakdown = ({ stats, isLoading }: StakeBreakdownProps) => {
|
||||
</div>
|
||||
<Skeleton loading={ isLoading }>
|
||||
<div className="font-bold">
|
||||
{ formatStake(stats.totalStake) } LUX
|
||||
{ formatStake(stats.totalStake) } { CURRENCY }
|
||||
</div>
|
||||
</Skeleton>
|
||||
<Skeleton loading={ isLoading }>
|
||||
<div className="flex gap-6 flex-wrap">
|
||||
<div>Validators: { formatStake(validatorStake) } LUX ({ validatorPct }%)</div>
|
||||
<div>Delegated: { formatStake(stats.totalDelegatedStake) } LUX ({ delegationPct }%)</div>
|
||||
<div>Validators: { formatStake(validatorStake) } { CURRENCY } ({ validatorPct }%)</div>
|
||||
<div>Delegated: { formatStake(stats.totalDelegatedStake) } { CURRENCY } ({ delegationPct }%)</div>
|
||||
</div>
|
||||
</Skeleton>
|
||||
</div>
|
||||
@@ -140,7 +143,7 @@ const ActiveValidatorsTable = ({ validators, isLoading }: ActiveValidatorsTableP
|
||||
{ truncateNodeId(v.nodeID) }
|
||||
</div>
|
||||
<div className="flex-[2] text-right">
|
||||
{ formatStake(v.stakeAmount ?? v.weight) } LUX
|
||||
{ formatStake(v.stakeAmount ?? v.weight) } { CURRENCY }
|
||||
</div>
|
||||
<div className="flex-1 text-right">
|
||||
{ v.delegationFee }%
|
||||
@@ -169,11 +172,11 @@ interface ValidatorsDashboardProps {
|
||||
|
||||
const ValidatorsDashboard = ({ validators, stats, isLoading }: ValidatorsDashboardProps) => {
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="flex flex-col gap-6 text-[var(--color-text-primary)]">
|
||||
{ /* Stat cards */ }
|
||||
<div
|
||||
className="grid gap-4"
|
||||
style={{ gridTemplateColumns: "repeat(4, 1fr)" }}
|
||||
style={{ gridTemplateColumns: 'repeat(4, 1fr)' }}
|
||||
>
|
||||
<StatCard
|
||||
label="Validators"
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import React from 'react';
|
||||
|
||||
import type { PChainValidator } from 'lib/api/pchain';
|
||||
import { Input } from '@luxfi/ui/input';
|
||||
import { Skeleton } from '@luxfi/ui/skeleton';
|
||||
import React from 'react';
|
||||
|
||||
import config from 'configs/app';
|
||||
import type { PChainValidator } from 'lib/api/pchain';
|
||||
|
||||
import { formatStake, formatUptime, truncateNodeId } from './utils';
|
||||
|
||||
const CURRENCY = config.chain.currency.symbol || 'LUX';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -48,7 +51,7 @@ const ValidatorsList = ({ validators, isLoading }: ValidatorsListProps) => {
|
||||
}, [ validators, search ]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-4 text-[var(--color-text-primary)]">
|
||||
{ /* Search */ }
|
||||
<div>
|
||||
<Input
|
||||
@@ -112,7 +115,7 @@ const ValidatorsList = ({ validators, isLoading }: ValidatorsListProps) => {
|
||||
{ truncateNodeId(v.nodeID) }
|
||||
</div>
|
||||
<div className="flex-[2] text-right">
|
||||
{ formatStake(v.stakeAmount ?? v.weight) } LUX
|
||||
{ formatStake(v.stakeAmount ?? v.weight) } { CURRENCY }
|
||||
</div>
|
||||
<div className="flex-1 text-right">
|
||||
{ v.delegationFee }%
|
||||
|
||||
Reference in New Issue
Block a user