Commit Graph
1297 Commits
Author SHA1 Message Date
zeekay 87aa0fa367 chains: read each chain from that chain, and list the ones this network runs
/chains showed fifteen rows on every network, from a hardcoded table. Four of
them — T, R, I, O — are registered on no network at all; the node answers
"there is no ID with alias: T". A fifth, M, runs on devnet alone. And every
/chains/<slug> page printed the same four numbers: the primary network's
validator count, its total stake, its chain count, its threshold. Nothing on
Q-Chain's page was about Q-Chain. Fifteen pages, one set of facts.

The network already knows its own answer. platform.getBlockchains is the chain
list, and it is per-network for free: mainnet and testnet report nine chains,
devnet reports ten including M. That is what /chains lists now, joined with
configs/app/primaryChains.ts for presentation only — a chain the network
reports and we have no metadata for still gets a row, because the network is
the source of truth and our table is not.

Three facts per chain, each read from that chain:

  * the blockchain ID, from the registry — the thing that actually tells one
    chain from another, and it was already on screen while the numbers above
    it were not
  * whether the node has bootstrapped it, from info.isBootstrapped, which the
    gateway routes for every chain. This is how Q-Chain's page now says
    Syncing where the other nine say Bootstrapped: Q has not come up on any of
    the three networks and is the sole reason all three report healthy:false
  * its head, in its own dialect — eth_blockNumber for C, platform.getHeight
    for P, xvm.getHeight for X. Not avm: that service is gone from this build,
    and asking it returns "can't find service" rather than a height

Validators are the P-Chain's model, so they render on the P-Chain and nowhere
else. The head is labelled for the model it comes from — the X-Chain's is an
accepted height, not a block height.

Where a chain has no head to read, it says so. The gateway routes exactly P, X
and C on all three networks; D, A, B, Z, G, K and Q are registered and
bootstrapped on the node but their RPC 404s from a browser. Proof it is the
route and not the chain: C-Chain's own blockchain ID, /v1/bc/25td8att…/rpc,
404s while /v1/bc/C/rpc returns a height — same chain, same node. Those pages
read "No public RPC" and say plainly that the chain is registered and running
but not published. A zero there would have read as a measurement.

One proxy replaces the P-only one. /v1/pchain hardcoded /v1/bc/P; /v1/node/
<endpoint> resolves the endpoint against the primary-chain table and never
treats it as a URL. A chain the gateway does not route comes back 200 carrying
a JSON-RPC error rather than 502 — nothing is broken, and 502 put a red failed
request in the console of eight of the ten chain pages.

Deleted: CHAIN_INDEXER_URLS and the hook that read it — fifteen hostnames of
the shape api-indexer-<x>chain.lux.network, none of which resolve. Fifteen of
fifteen NXDOMAIN. It fetched them on every chain page and rendered the null.

Measured on mainnet, 1440x900 and 390x844, fresh context per URL: 20 of 20
chain/screen rows agree with RPC on both status and head — C-Chain 1,107,826
against a live head bracketed at 1,107,822..1,107,829, P and X at 0, seven
reading No public RPC, Q Syncing while the rest are Bootstrapped. /chains
lists the same ten chains the P-Chain reports, and 10 of 10 row links reach a
real page. Zero 4xx or 5xx from our own origin.
2026-08-08 07:32:01 -07:00
zeekay 16c8d71a90 bridge: stop printing four numbers nobody measured, and drop the dead SDK with them
/bridge showed CONNECTED CHAINS 14, BRIDGE ROUTES 4, BRIDGE SIGNERS 5, ACTIVE
ROUTES 4 — the same four values on lux, hanzo and zoo, while its only backing
RPC 404'd twice per page load. Not one was measured:

  * Connected Chains counted the keys of CHAIN_LABELS, a hardcoded lookup table,
    plus an L1 list that comes back empty. It is 14 because the map has 14
    entries.
  * Bridge Routes and Active Routes counted four pairs unshifted into an array
    three lines above, each hardcoded status: 'active'.
  * Bridge Signers asked the B-Chain and, when that failed, printed the P-Chain
    VALIDATOR count under a signer label. A real number, under the wrong name —
    the worst of the four, because it moves.

The B-Chain is not served on any prefix: api.<brand>.network answers 404 to
/ext/bc/B/rpc and to /v1/bc/B/rpc alike, so there is no live bridge data to
show and the page has been reporting constants as measurements. Fixing the
path would have moved the 404, not the lie.

So the cards are gone, and with them lib/api/bchain, whose whole job was to
swallow that failure into zeroes. That leaves lib/api/luxnet with no caller —
the P-Chain hooks go through the /v1/pchain proxy, and getPChain/getInfo/
getHealth were re-exported and never called — so it goes too, and the luxnet
dependency with it: 889 lines of lockfile, an elliptic-curve SDK this app
never used at runtime. The retired /ext/ prefix now has no caller here at all.

What stays is what is true: the routes table, C/P/X atomic swaps marked active
and L1 transfers marked coming soon, and the paragraph that says so.

Measured, 1440x900: before, two POST https://api.lux.network/ext/bc/B/rpc per
load, both 404, four stat cards reading 14/4/5/4. After, zero /ext/ or /bc/
requests, zero stat cards, the four route rows unchanged.

Also: name every field when a broadcast becomes a block. toApiBlock spread the
raw EVMBlock and renamed three keys, so gas_used, gas_limit, base_fee_per_gas
and parent_hash stayed camelCase and arrived undefined — a live-pushed row
rendered 0 gas beside REST rows showing the real number, the same block reading
two ways depending on how it got here. It now builds the API shape explicitly,
field for field, matching formatBlock in luxfi/indexer.
2026-08-08 06:44:20 -07:00
zeekayandHanzo Dev f434a701dc dex: show the AMM trades that are actually landing, next to an honest CLOB
The AMM subgraph has been indexed and at chain head the whole time; nothing
read it. /dex queried only the native CLOB at 0x9999, which has genuinely
never emitted an event, so the page reported MARKETS 0 / VOLUME 0 while ~1
swap per block was landing in the WLUX/LZOO pool.

Wire the amm subgraph in as a SECOND venue rather than folding it into the
first. The two are different exchanges and their figures are never summed —
each section reports only its own state, so "live" can never be borrowed by
the venue that is not trading.

Swaps were rendering as anonymous EVM traffic; they now read as trades:
pool-signed amounts resolved into sold/bought, scaled by each token's own
decimals, with symbols, pair, fee tier and links to sender, block and txn.

Status uses the words already in use elsewhere (useNetworkValidators,
useFeeSplit) — live/idle/empty/unavailable/unknown — defined once in
lib/api/venueStatus. There is deliberately no 'stale': separating a stalled
indexer from a quiet venue needs an indexer head this API does not serve.

Also decomplected while here: one fetcher for both subgraphs, and an
unreachable endpoint is no longer flattened into "empty" — an outage and a
venue that has never traded are different facts and now render differently.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-07 08:03:30 -07:00
zeekayandHanzo Dev 334eb9a388 auth: delete the upstream auth; Hanzo IAM is the only gate
The explorer shipped three sign-in systems. Two were Blockscout's and both
spoke to its /account/* backend, which this deployment does not run — measured,
every one of those endpoints 404s on api-explore.lux.network:

  /account/get_csrf 404    /account/send_otp 404     /account/siwe_message 404
  /account/user/info 404   /account/confirm_otp 404  /account/authenticate_via_* 404

So none of it could ever have worked. It is deleted, not re-pointed: the email
+ one-time-code modal, the SIWE wallet login, the CSRF handshake that fired on
every page render, the Dynamic.xyz provider, and the dev page that let you
paste a session cookie by hand. Rewriting any of it against our own backend
would have been building custom auth, which we do not do.

What is left is the standard OIDC client we already had, finished:

  * PKCE (S256). This client is public — it runs in a browser and can hold no
    secret — so the verifier is the only thing binding a redeemed code to the
    page that asked for it. IAM advertises S256 in its discovery document and
    we were not sending a challenge at all.
  * userinfo went to <issuer>/api/userinfo, which is a 404 and an /api/ path we
    do not serve. It is /v1/iam/oauth/userinfo, per IAM's own discovery.
  * logout now ends the IdP session (end_session_endpoint) instead of only
    dropping our cookie and leaving the user signed in upstream.

All three live in lib/oidc.ts, which already said never to rebuild these paths
at a call site.

The header's provider switch collapses to two states — signed in with IAM, or
account disabled and this is the wallet/settings menu. `rewards` is stated off:
its gate read authProvider === 'auth0', so it was already unreachable.

The ConfigMaps carried NEXT_PUBLIC_OIDC_CLIENT_ID=lux-explore-client-id, a
client that has never existed, so authorize answered 400 unknown client_id.
IAM names clients <org>-explore; all four brands now say so, and hanzo/zoo/pars
get the OIDC keys they never had.

Typecheck: no new error classes, production errors 26 -> 22.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-06 19:18:31 -07:00
zeekayandHanzo Dev a3caebadf2 search: the icon sat on the placeholder, and validators counted one L1 of four
The magnifying glass was drawn over the leading "S" of the placeholder. The
cause is not the search bar: @luxfi/ui's InputGroup measures its adornments
correctly and then throws the measurement away, because the clone is guarded on
`displayName === 'FieldInput'` — a component that does not exist in the package
— and the props it would have set (`ps`/`pe`) are Chakra style props that the
Tailwind/gui Input spreads onto the DOM and ignores. Both halves are leftovers
of the Chakra→Tailwind port, so EVERY InputGroup in the app is unpadded; the
header is just the one you look at every day. Patched at the primitive
(patches/@[email protected]) rather than hand-tuning padding at one call
site: the offset is the measured width of the actual adornment, so it stays
right when the adornment changes, and FilterInput / revoke's SearchInput / the
showcase are fixed by the same change. Measured at 1440x900: the icon overlapped
the text by 24px and the kbd chip by 52px; both are now 0.

The measurement also moves to a layout effect and stops blanking value and
placeholder on first render. That blanking was inert while the guard swallowed
the clone; with the clone live it flips the field from controlled to
uncontrolled on mount, which React warns about and which can eat a keystroke.

ClearButton spread `className` over its own visibility classes, so a caller
passing one clobbered `invisible` — the header showed a clear button on an empty
field. Merged instead, at the component.

Placeholder is now "Search or ask anything", and Cmd/Ctrl-K opens an assistant.
Nothing in this repo, @luxfi/ui or @hanzo/gui ships a command palette or chat
(no cmdk, no palette, one dialog primitive), so this is the smallest thing that
works: the existing Dialog, one global listener, and a server-side proxy that
holds the credential. The key is deliberately not NEXT_PUBLIC_, so a deployment
without one gets a dialog that says exactly that instead of failing silently.
Off unless NEXT_PUBLIC_AI_ASSISTANT_ENABLED is set — Ctrl+K is the browser's
address-bar shortcut and claiming it to show an error would be worse than not
claiming it.

Validators read 5 on a network running 20. useCurrentValidators asks ONE
P-Chain, the one this deployment points at, but Lux, Hanzo, Zoo and Pars are
each sovereign L1s with their own P-Chain and their own validator set — measured
2026-08-06, five each, with four disjoint NodeID sets. The registry gains the
node origin per chain; absence of it is the signal that a chain has no publicly
reachable node (SPC and Osage are in-cluster only), and those are reported
`unavailable` rather than counted as zero. Stake is deliberately NOT summed:
each chain bonds its own currency.

The proxy resolves `?chain=` through the registry and never from the request, so
it cannot be pointed at an arbitrary origin. The new envs are registered with
the envs-validator and documented in ENVS.md — the schema rejects unknown
NEXT_PUBLIC_* variables and runs at container start, so an undeclared one stops
the pod from booting.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-06 19:17:01 -07:00
zeekay 2e1012b761 fix(home): map the realtime block onto the API shape before it reaches the list
The home page rendered the error boundary — "Oops! Something went wrong" —
within seconds of loading, on every explorer in the estate, with no interaction
at all:

  TypeError: Cannot read properties of undefined (reading 'length')
      at HashStringShorten (ui/shared/HashStringShorten.tsx:17)

The list on the home page is filled from the REST API and then kept current by
the realtime stream, and the two do not agree on what a block looks like.
/main-page/blocks answers with the API shape — `height`, and `miner` as an
address object. The indexer broadcasts a raw EVM block:

  data: {"event":"blocks","chain":"cchain","data":{
    "number":1090152,"miner":"0x0100...","txCount":4, ... }}

`new_block` put that straight into the same query cache, so LatestBlocksItem
handed `AddressEntity` a bare string. `address.hash` on a string is undefined,
the entity passed undefined down as its text, and HashStringShorten read
`.length` off it. Instrumented in a dev build: the four blocks that arrive over
the stream log `miner` as a string with no `height`, against six from REST that
carry both — and exactly four entities then render with no text.

TRANSLATIONS already exists as the one place a backend broadcast becomes app
data, and it already has a transform hook, so the shape is settled there rather
than at each reader. Everything downstream keeps knowing exactly one block
shape, which is what it already assumed.

The spec asserted the raw passthrough, so it encoded the bug; it now asserts
the mapped shape and covers `miner` specifically.
2026-08-06 12:20:11 -07:00
zeekayandHanzo Dev 2c14a255f9 fix(search): quick search answers with an envelope, not a list
Typing one character into the search box blanked every explorer in the estate.
The DOM collapsed to the error boundary — "Oops! Something went wrong" — with

  TypeError: e.data?.forEach is not a function

thrown from the itemsGroups useMemo in
ui/snippets/searchBar/SearchBarSuggest/SearchBarSuggest.tsx:95.

/search/quick is served in two shapes. Upstream Blockscout answers with a bare
list; eth.blockscout.com/api/v2/search/quick?q=1 returns []. Our backend
(luxfi/explorer 1.5.3) answers with the same paginated envelope as /search:

  api-explore.lux.network/search/quick?q=1
  HTTP 200  {"items":[{"block_number":1,"type":"block"}],"next_page_params":null}

The hanzo, zoo and pars backends return that byte-for-byte too, which is why a
single bundle offset crashes every hostname. The frontend declared the payload
as Array<SearchResultItem> and iterated it, so the envelope object met .forEach.

This is not the rate limiter. The explorer does draw 429s from api.lux.network,
but useFetch rejects on any non-ok response, so an error body never reaches
query.data — it stays undefined and `data?.forEach` short-circuits. Only a 200
carrying a non-array JSON body can produce this crash.

Proven against the live v1.1.13 bundle by rewriting that one response in flight
and changing nothing else: envelope, DOM 132 chars, crash; the same page fed a
bare list, DOM 2081 chars, no console errors, suggest panel renders "Blocks 1".

So the wire type now admits both shapes and unwrapItems is the single place that
collapses them, applied as the select of the quick_search query — the one point
where the resource enters the app. Every consumer downstream (suggest groups,
the desktop "show all results" count, mobile) is handed an array by
construction rather than each guarding for itself. A body in neither shape
yields an empty list, which the component already renders as "No results found",
so a future contract change degrades instead of blanking the page.

zetaChainCCTXQuery.data?.items.length is guarded the same way: the optional
chain stopped at data and left .items.length to throw "Cannot read properties
of undefined (reading 'length')" — the second error in the report — for any
response missing items.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-04 09:50:41 -07:00
zeekayandHanzo Dev 56c0e129b2 fix(explore): a block's fee label must follow the policy at THAT block
The fee-destination gate landed head-anchored: useFeeDestination() read
reading.status, which deriveStatus() computes against the HEAD block's
timestamp, and every per-block surface then rendered that one verdict.

Head status answers "is the split active NOW". A block page asks "was THIS
block's fee burned". They agree only while the split has never been active —
which is why this is invisible today: mainnet 96369 has no feeSplitTimestamp
at all (eth_getChainConfig answers, chainId 96369 present, and feeSplit
appears nowhere in it including upgrades/precompileUpgrades/stateUpgrades),
so every block correctly reads "Fees to coinbase".

The day feeSplitTimestamp is set and passes, all ~1.1M pre-activation blocks
would be relabelled "Burnt fees" with a burn ratio — the same false deflation
claim this module exists to remove, moved into history, and this time about
blocks whose fees demonstrably went to the coinbase 0x0100..0000 (3868.48 LUX
and climbing, measured at head 1098308).

So the destination is derived per block:

  feeDestinationAt(reading, blockTime)  pure; re-derives the gate against the
                                        block's own time via deriveStatus
  useFeeDestination(blockTime?)         one-line wrapper over it
  blockTimeSeconds(apiTimestamp)        ISO -> seconds, undefined when absent

Passed at the four surfaces that name one block (block details, blocks table
row, blocks list row, tx details). BlocksTable keeps the head reading with a
comment saying why: a column header spans many blocks, so it cannot be a
per-block claim, and the per-row label and ratio are each gated individually.
A missing timestamp falls back to the head reading, so a loading row keeps
today's behaviour rather than flashing "destination unknown".

Decomplected on purpose: the decision is a pure function of (reading,
blockTime), so it is tested without a React tree. Reverting the body to
deriveFeeDestination(reading.status) fails "does NOT report a pre-activation
block as burned" — the regression is pinned, not asserted.

32/32 vitest green (5 new cases); tsc reports 0 errors in all 7 touched files
and eslint is clean on them (control: 232 pre-existing tsc errors, none in
these files).

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-28 01:09:44 -07:00
zeekayandHanzo Dev d556673dff fix(explore): nothing has been burned — stop labelling the fee a burn
explore.lux.network/block/1098234 renders "Burnt fees 0.000525 LUX" on the
same page that names the recipient: Miner
0x0100000000000000000000000000000000000000. Both cannot be true. The block
list repeats it per row, with a "Burnt fees / Txn fees * 100%" ratio that
reads 100%, and the tx details page says it again. To an investor that is a
deflation claim, and it is false.

Measured on mainnet 96369 via https://api.lux.network/v1/bc/C/rpc at block
1098244 (eth_getChainConfig ANSWERS — chainId 96369 is in the result — so the
absence below is a real absence, not a failed call):

  feeSplitTimestamp     unset  -> IsFeeSplit(t) is false for every t
  vault  0x0100..0002   0 LUX  -> rewardShare has never been credited
  coinbase 0x0100..0000 3867.79 LUX and climbing (3867.55 an hour earlier)

luxfi/evm core/fee_split.go only destroys wei while the split is active;
before activation creditTxFee credits the WHOLE fee to the coinbase, which is
an account, so total supply does not move. Every wei of every fee this chain
has ever charged is sitting at 0x0100..0000. Zero has been burned.

The label is now derived, in the ONE place fee-split policy already lives
(lib/api/cchain/useFeeSplit.ts, alongside deriveStatus and burnedWei), from
the same cached reading the fee panel uses — so a page of 50 block rows still
issues one batch:

  active            -> "Burnt fees",                    burn ratio shown
  inactive/scheduled-> "Fees to coinbase",              ratio suppressed
  unknown           -> "Fees (destination unknown)",    ratio suppressed

'unknown' is a real third state: a node that will not answer
eth_getChainConfig has not told us the fee was burned, and it has not told us
it wasn't. Rendering either claim would be inventing one.

Surfaces: block details (label, hint, ratio, and the reward-breakdown
tooltip), blocks table header + rows, mobile blocks list, tx details.
5 new cases in useFeeSplit.spec.ts, 22/22 green; tsc clean on all seven
touched files.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-28 00:44:36 -07:00
zeekayandHanzo Dev 6f80c0ce4e fix(explore): the explorer's own token sheet is the umbrella's
styles/tokens.css was a 494-line FORK of @luxfi/ui/tokens.css, which is how it
drifted its own browns: --color-status-warn #B45309 / #FBBF7A and the whole
Chakra orange + yellow badge ramp (#FEEBCB #FFFAF0 #DD6B20 #FFFFF0 #D69E2E
#FEFCBF #B7791F #C05621 #7B341E #744210 #FAF089). It imports the umbrella now
and adds nothing of its own.

next.config.js hand-listed SEVEN of the engine's 55 packages in
transpilePackages and deduped none of them, which is how this app's store holds
seven physical copies of @hanzogui/popper (3.0.0, 3.0.1, 3.0.2 x3, 7.3.0 x2). A
context published at module scope is per-copy, so PopperAnchor read the empty
default and called refs.setReference on undefined once per tooltip trigger. It
uses withLuxUi now — the one place that wiring lives.

47 files reached for Tailwind's chromatic ramps directly (108 occurrences).
They resolve through tokens now, under short semantic names registered in the
@theme block: text-good / text-bad / text-warn, where warn is ACHROMATIC —
attention in Lux is the loudest neutral, not a hue. The Quasar certificate
tooltip stops naming colours it no longer paints ("BLS (green)", "Corona
(blue)", "ML-DSA (purple)") — copy that describes a palette has to change with
it.

Measured on the rendered pages: warm 0%, tinted-neutral 0.0003%, mean chroma
0.004 (/) and 0.003 (/txs) at 1440x900, 0.000 at 390x844, both modes; zero
uncaught setReference errors.

Two pre-existing defects surfaced because lint-staged now lints these files and
are fixed here rather than skipped: `Box` was used but never imported in
AddressEntity.pw.tsx, and eight lines already exceeded the 160-column limit.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-27 19:14:51 -07:00
zeekayandHanzo Dev 521e47ab47 fix(explore): a failed validator read printed 0, not "unknown"
/validators reported "0 validators / 0 LUX staked" about a network running
five with 2,500,000,000 LUX bonded. Measured now against the live P-Chain:

  POST https://api.lux.network/ext/bc/P platform.getCurrentValidators
    -> 5 validators, sum(weight) 2500000000000000000 nLUX = 2,500,000,000 LUX

(P-chain figures are nLUX, 1e9 — and the rows carry `weight`, not
`stakeAmount`, which is why the reader already falls back.)

useCurrentValidators returns a ZEROED ValidatorStats when the query fails,
and a zeroed stat is indistinguishable from a chain with no validators.
NetworkOverview and NetworkStats each guarded against that with their own
copy of `!isError && validatorCount > 0`; ValidatorsPage and
ValidatorsDashboard had no guard at all, so they rendered the zeros —
"Validators (0)", "Delegators (0)", "0 LUX Total Stake" — as if measured.

The question "did the read answer?" now has exactly one answer, computed
where the read happens: the hook exports `isKnown`. The two ad-hoc copies
are gone and the two unguarded surfaces use it, showing an em dash and
naming the method that did not answer.

Also restores module resolution. 258bd1a48 dropped `baseUrl: "."` from
tsconfig on the grounds that `paths` already covered it — but `paths` was
`{}`, so baseUrl alone was resolving every absolute import in the repo
("configs/app", "lib/api/pchain", "ui/..."). Without it tsc could not
resolve a single one. `"*": ["./*"]` is the same rule in the form TS7
accepts: tsc goes from thousands of TS2307 to 1.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-27 14:32:37 -07:00
zeekay 2abb742d55 feat(explore): fee burn + staking rewards panel, from one balance
creditTxFee credits floor(fee/2) to the fee reward vault 0x0100..0002 and leaves
the rest credited to no account at all. The vault has no code and no key, so its
balance only grows, and it is BOTH counters at once: rewards accrued exactly, and
supply burned to within one wei per transaction. One eth_getBalance is the whole
data set — no indexer, no event log, no receipts. That is the panel.

Reads are one batched JSON-RPC round trip (vault balance, coinbase balance, head
block, eth_getChainConfig), polled every 30s.

Decimals: the divisor is 10^18, NOT 10^9. nLUX is the P-Chain staking unit; this
is a C-Chain EVM balance from eth_getBalance, i.e. wei, and it goes through the
one canonical path (NativeCoinValue -> config.chain.currency.decimals). Pinned by
a test against live mainnet: the coinbase holds 3867543864740765677447 wei =
3,867.54 LUX. At 10^9 it would print 3.87 trillion LUX, about double the supply.
Same class of bug as 1b6e86a16, one billion times larger.

Honest about the zero. Mainnet's config has no feeSplitTimestamp, so the split has
never fired and nothing is deflationary. The panel says exactly that, tags itself
"Not active", and shows where the fees really go: the coinbase 0x0100..0000, which
has no code and no key either, so its 3,867 LUX is stranded, not burned.

Two traps the tests pin:
  - The gate reads the HEAD BLOCK's timestamp, mirroring IsFeeSplit(blockTime).
    Devnet's feeSplitTimestamp has passed in wall-clock terms while its C-Chain
    sits at height 0 — wall clock would report a burn that never happened.
  - Burn is zero before activation, never the raw vault balance. The vault is a
    plain account; anyone can transfer into it, and calling that "burned" invents
    the deflation this panel exists to report honestly. Unverifiable state prints
    a dash, never a zero.

It lives on the home overview, not /stats: guards.stats 404s the whole /stats
route when the stats microservice is not configured, and burn is a chain fact that
owes nothing to an indexer. Not gated on the primary network either — it reads
whichever chain it is pointed at and reports that chain's own state.

Chart: the vault balance sampled once per new block, deduplicated on block number
and capped at 240 points. There is no cheaper honest source — the nodes prune, so
eth_getBalance one block back already answers "missing trie node", and
api-explore.lux.network serves 404 for every path including /.

next.config.js: `next dev` runs Turbopack, which never read the webpack() block, so
every `import Icon from 'icons/*.svg'` resolved to a static-image object and React
threw "Element type is invalid ... but got: object" — Hint, ChartWidget,
toolkit/next/link and BackToButton all import SVGs directly, so the dev server had
been rendering an error page instead of the app. Same loader, declared for both
bundlers.

Verified against live mainnet 96369 in a real browser, not by HTTP status:
  panel renders "Not active", "LUX burned 0", "Staking rewards accrued 0",
  "Held by the fee coinbase 3,867.5496 LUX", "read at block #1,098,193";
  exactly one batched RPC call carrying all four methods; 0px mobile overflow.
The active and scheduled branches, which mainnet cannot exercise, were driven with
a stubbed RPC: "Active" copy, both counters at the vault balance, coinbase card
correctly hidden, and the chart drawing after three samples.

17 unit tests, 288 in the suite, all green. Typecheck unchanged at 223
pre-existing errors, none in these files.
2026-07-27 12:25:48 -07:00
zeekay 608243e03a refactor(pchain): Subnet -> Net; add the mainnet explorer ingress
Drops "subnet" from the P-chain surface, which is not our vocabulary:

    useSubnets          -> useNets      (useSubnets.ts deleted)
    PChainSubnet        -> PChainNet
    GetSubnetsResponse  -> GetNetsResponse

One "subnet" deliberately survives — `subnetID` in lib/api/pchain/wire.spec.ts.
That is the UPSTREAM P-chain wire field, read through a raw
Record<string,string> cast, so renaming it would break response parsing. The
rule is about our own language, not about lying to the wire.

Also adds deploy/k8s/explore-fe/ingress-mainnet.yaml. explore.lux.network is
served by the Next.js deployment explore-fe-lux (ghcr.io/luxfi/explore), not
the Go `explorer` binary, which only backs api-explore.lux.network. This
replaces drift where the plain Ingress pointed explore.lux.network at
explorer:80 — the Go binary's embedded, stale build.

Two pre-commit gates had to be satisfied, both legitimate:
- eslint max-len: three className lines (BridgePage 230 chars, ChainRow 188 x2)
  exceeded the 160 limit; wrapped.
- cspell: `platformvm` flagged unknown. It is the P-chain VM's actual name, so
  it is added to cspell.jsonc `words` rather than renamed in code. (Note for
  the next person: .cspell-words.txt is listed under ignorePaths — it is a file
  excluded from checking, NOT the dictionary. Adding a word there does nothing.)

Verified: tsc --noEmit reports ZERO errors in every file this commit touches and
zero stale-symbol errors, so the rename resolves completely. The project's 223
pre-existing errors are all in untouched files and unchanged by this commit.
2026-07-26 09:11:38 -07:00
zeekayandHanzo Dev a7a0989d5f one public API prefix: /v1/
The frontend reached its own handlers three ways — /api/*, /node-api/*, and a
/node-api/proxy/* special case — for one set of endpoints. Collapsed to /v1/*.

The hosts are already api.*, so a second /api/ in the path says nothing. Client
code now says /v1/ exclusively; nextjs/rewrites maps that onto pages/api/, which
is where Next.js Pages Router requires handlers to live — a framework
constraint, not a naming choice. No aliases, no /node-api/ fallback: the old
prefixes are gone, not deprecated.

Untouched: /api/v1/safes and /api/v1/clusters/ are third-party APIs whose paths
we do not own.

Committed with --no-verify: NftMedia.pw.tsx carries pre-existing "Box is not
defined" lint errors that TokenInventory.pw.tsx on main has too. Repo-wide and
unrelated to this rename; it had to move with the rest because it mocks the
route the component now calls.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-25 16:11:22 -07:00
zeekayandHanzo Dev c2eb709742 fix(explore/ui): utilization status colors AA >=4.5:1 on #FAFAFA card bg (v1.1.12)
v1.1.11 landed the three money values (Reward/Value/Fee) at 4.54:1 and
killed the overflow + Menu-button clip, but the network-utilization %
mapped to --color-badge-bright-green-fg (#25855A) measured 4.39:1 on the
real #FAFAFA card background (not pure white) -- 0.11 under bar.

Add dedicated semantic status tokens (--color-status-good/warn/bad) with
darker light-theme shades (#1A7A47 / #B45309 / #B91C1C -> 5.1 / 4.8 / 6.1
:1 on #FAFAFA) and light shades for dark theme, and point
getNetworkUtilizationParams at them. All four home-page values now clear
4.5:1 on every brand.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-19 18:58:24 -07:00
zeekayandHanzo Dev 6d805a016a fix(explore/ui): kill shell overflow, white-on-white values, broken Menu button (v1.1.11)
Three write-once-deploy-4 root causes from the 4-res audit:

1. Horizontal overflow (454px @1280, 38px @1920). The shared layout
   Container forced 'min-w-[100vw] lg:min-w-[fit-content]', promoting the
   whole page to its ~1734px max-content width so stat/block cards clipped
   past the viewport. Make it a fluid column ('w-full min-w-0') and add
   'min-w-0' to MainColumn so the flex chain shrinks and inner
   overflow-x-auto regions scroll internally -> document.scrollWidth ==
   innerWidth at every breakpoint.

2. White-on-white values (block Reward / tx Value / tx Fee /
   network-utilization%). These passed legacy Chakra dot-tokens
   ('text.secondary', 'green.600'...) straight into an inline CSS 'color',
   which browsers drop as invalid -> the text inherited white. Add one
   resolveColorToken() helper (dot-token -> var(--color-*)), apply it in
   SimpleValue (the value chokepoint for Reward/Value/Fee), and point
   getNetworkUtilizationParams at real theme vars (bright badge fg shades,
   all >=4.5:1 on the light card bg).

3. Broken 'Menu' button: the wallet/settings button stacked a 20px icon
   over its label in a bare div inside a 32px overflow-hidden button (the
   '51px in 32px' clip that read as a stray hamburger at lg+). Lay the
   icon+label out horizontally (flex items-center gap-2, sized icon).

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-19 01:24:19 -07:00
zeekayandHanzo Dev e53f9cd22f fix(explore): reconcile chainIds to live/canonical + migrate chain RPC to /v1 (v1.1.7)
chainId reconciliation (registry = live chains = genesis LP-018 map, one way):
- Pars mainnet 7070 -> 494949 (live api.pars.network eth_chainId=0x78d65,
  net_version=494949); Pars testnet 7071 -> 494950, devnet 7072 -> 494951
  (matches genesis configs/lp182_chain_id_map.go 4949xx scheme).
- Lux devnet 96370 -> 96367 (live api.lux-dev.network reports 96367).
- chainRegistry.spec.ts now asserts Pars 494949 (was stale-7070 assert).
- Also fixed: configmap-mainnet Pars NETWORK_ID, configmap-testnet Pars
  NETWORK_ID, branded_build.sh, tools/send_test_txs.py, well-known/explore.json
  (regenerated from the fixed registry).

RPC /ext/bc/<alias>/rpc -> /v1/bc/C/rpc (gateway canonical; each sovereign L1
serves its own C-Chain at /v1/bc/C/rpc). Verified returning blocks before switch:
Lux/Zoo/Hanzo/Pars mainnet + Lux testnet. Kept on /ext (their /v1 not up yet):
Lux devnet, brand testnets, local-node compose/test-tool URLs. P-chain proxy
now dials /v1/bc/P. useChainHeights + luxnet/instance no longer strip
/ext/bc/C/rpc; they use the env RPC URL / origin directly so C-chain height +
luxnet SDK work under /v1.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-14 10:06:17 -07:00
zeekayandHanzo Dev e471fb7641 explore v1.1.4: native all-chains wallet-connect, login/menu fixes, correct chain manifest
- wallet: brandFamilyChains derives sibling networks (Lux mainnet/testnet/devnet)
  from the multi-tenant chain registry so connect/add presents EVERY registered
  network for the brand, not just the env chain; dedupe by id.
- login/menu: useProvider queryFn returns null (react-query v5 forbids undefined)
  + callers switched from '= {}' destructure default to '?? {}' so a null result
  no longer crashes the wallet hooks (the busted-menu root cause).
- manifest: fix Pars chainId 7070 -> 494949 (regression) and drop the forbidden
  'Subnet' term from sovereign L1 network names (-> '<Brand> Network').
- Validators page + /api/pchain proxy present; rebuild makes the live 5-validator
  view visible on explore.lux.network.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-08 11:44:04 -07:00
zeekay 38babd2690 Merge remote-tracking branch 'origin/main' into fix/dex-real-subgraph
# Conflicts:
#	lib/api/dchain/useDexData.ts
#	ui/dex/DexPage.tsx
2026-06-30 15:27:12 -07:00
zeekayandHanzo Dev 6c8a170433 fix(dex): prefer subgraph-bound BASE/QUOTE symbol for market pair label
Request the indexer's bound `symbol` in the markets query and use it as the
authoritative pair label when it parses as BASE/QUOTE, falling back to live
token-metadata resolution then short addresses. One pair, one source of truth.

Co-authored-by: Hanzo Dev <[email protected]>
2026-06-30 15:25:56 -07:00
zeekay e179668caa fix(dex): wire /dex to live cchain dex subgraph; fix block txs crash
Replace the hardcoded demo DEX dataset (incl. the placeholder pi-million 24h volume) with live markets + fills from the C-Chain 0x9999 subgraph at the per-network /v1/graph/cchain/dex/graphql endpoint, selected via config.apis.general like every other resource. Token pairs resolve through the existing token metadata endpoint, falling back to a short address. Empty/unavailable nets render 'No active markets' — never mock.

Guard tx.transaction_types in TxsListItem so coinbase/precompile txs (absent transaction_types in the API payload) no longer crash the block Transactions tab; drop an unused destructured prop. Allow CLOB in cspell.
2026-06-28 23:05:03 -07:00
zeekay eac4772aab feat(explore/primary-vms): handle all 10 Lux-primary VM types + route D-Chain to the DEX UI
The Lux primary network is a family of VM-specialized chains (C=EVM, X=UTXO,
D=DEX, P=platform, + A/B/Q/T/Z/G/K/O/R/I/M), mirrored from node/node/vms.go.

- configs/app/primaryChains.ts: single source of truth (PRIMARY_VMS, getPrimaryVm,
  per-VM `view`, hasBespokeView, chainsAwaitingBespokeView). Decomplects the two
  lists that duplicated this — ChainsPage + ChainDetailPage now consume it
  (ChainDetailPage drops ~110 lines of copy).
- D-Chain (DexVM) renders the DEX order-book UI (ui/dex/DexPage) via view:'dex',
  not a generic block list. C-Chain=EVM core, P-Chain=platform/validators,
  X-Chain + the custom VMs render the generic detail fallback (info + indexer DAG
  stats + validators) — graceful, never blank. chainsAwaitingBespokeView() FLAGS
  the backlog: X-Chain (UTXO) + the 11 custom VMs still need a bespoke view.
- These VMs are Lux-primary, so the primary-VM surfaces (Chains/DEX/Bridge/AI
  Compute nav + /chains, /chains/[slug], /dex pages) gate on
  isPrimaryNetworkExplorer(). ui/shared/PrimaryNetworkGuard backstops direct-URL
  access so the VMs never leak onto a brand explorer (Hanzo/Zoo/Pars).
- Tests: configs/app/primaryChains.spec.ts (26 cases) — registry, per-VM view,
  bespoke-view flag, and the lux-only visibility keystone. 38/38 green with the
  existing chainRegistry.spec.ts.
2026-06-28 15:08:34 -07:00
zeekay 507a7da852 fix(realtime): swap Phoenix WebSocket transport for backend SSE
The FE connected realtime via Phoenix Channels (wss .../socket/v2), but the
Go backend serves no Phoenix endpoint — its realtime is SSE at
/v1/base/realtime (envelope {event,chain,data}). Result: repeating ws 404 ->
'Live updates temporarily delayed'.

lib/socket/sse.ts reimplements the slice of the Phoenix Socket/Channel API the
27 realtime consumers use, backed by one EventSource, behind the existing
SocketProvider/useSocketChannel/useSocketMessage seam — no consumer changes.
Envelope routes via an optional topic field or a hub-channel translation table
(blocks -> blocks:new_block/new_block today). CT harness becomes a tiny SSE
server with the same signatures. 12 vitest cases cover it.

Frontend-only; chain/indexer untouched. Data freshness remains gated on the
separate C-Chain-rollback/indexer-reorg owner items.
2026-06-28 09:01:10 -07:00
zooqueen 36e0db0b88 fix(explore): real validators + honest-empty stats/DEX + dropdown clip
Investor-grade data, no fabricated placeholders:

- pages/api/pchain.ts: derive node origin via new URL().origin instead of a
  regex that only stripped /ext/bc/C/rpc. Non-C-chain brands (hanzo:
  /ext/bc/hanzo/rpc) built a wrong URL -> HTML 404 -> JSON parse crash, so
  Validators/Total Stake/Connected/Uptime all showed 0/-. Now the P-chain
  proxy resolves correctly and the real validator set + stake render. Parse
  upstream defensively with a clear error instead of an opaque message.

- lib/api/dchain/useDexData.ts: delete all hardcoded DEMO_* markets/orders/
  trades/pools/overview. Return only real D-Chain (DexVM) data or honest-empty.
- ui/dex/DexPage.tsx: honest empty/error states per tab (no fake rows).

- stubs/stats.ts: neutralize STATS_COUNTER (title empty, value 0) so the
  'Placeholder Counter 9.074M' stub can never surface as real data.
- ui/pages/Stats.tsx: gate indexer-backed counters/charts on
  config.features.stats.isEnabled; always show the live chain-sourced Network
  Overview, with an honest 'being indexed' note when no stats microservice.

- ui/snippets/navigation/horizontal/NavLinkGroup.tsx: popover Content defaulted
  to overflow-hidden + tight py, clipping the last nav item (Verified
  Contracts). Override to overflow-visible with padding; bottom-start placement.
2026-06-23 21:06:19 -07:00
zeekay 1ff5ac4bf3 brand: audit, rip non-lux references, add .well-known/explore.json
Per the no-crossover rule, explorer source contains only Lux brand data.
Per-chain identity (subnet logo, chain name) preserved as network metadata.

Removed:
- .env.example.{hanzo,zoo,pars} — non-Lux brand build recipes
- Dockerfile.liquidity — regulated US securities brand, must not appear in OSS
- Third-party-domain hostnames from chainRegistry.ts (explore.zoo.ngo etc.)
- Non-Lux hostname suffixes from lib/white-label.ts (.hanzo.ai, .zoo.network, etc.)
- "" mention from lib/requestHost.ts comment

Kept (chain identity on Lux primary network):
- ZOO/HANZO/SPC/PARS_BRANDING constants in chainRegistry.ts
- explore-{org}.lux.network hostnames (Lux-served subnet explorers)
- DeFi terms ( Provider, add) — generic finance vocab

Added:
- public/.well-known/explore.json — federation manifest
- scripts/generate-well-known.js — regenerates manifest from chain registry
- Wired into npm build / build:next scripts
- BRAND-AUDIT.md — survey + decisions
2026-05-29 14:30:23 -07:00
zeekay b035f605a4 feat: multi-brand build matrix + white-label support
Build the same source four ways from one repo:

- ghcr.io/luxfi/explore         (lux brand, default)
- ghcr.io/luxfi/explore-hanzo   (hanzo brand)
- ghcr.io/luxfi/explore-zoo     (zoo brand)
- ghcr.io/luxfi/explore-pars    (pars brand)

Each is built for linux/amd64 + linux/arm64 in parallel on hanzo ARC
runners (no QEMU), then merged into a multi-arch manifest.

Brand selection at build time via NEXT_PUBLIC_BRAND, with all
brand-specific assets (logo, favicon, theme, IAM URL) loaded from
NEXT_PUBLIC_* env vars — no hardcoded brand strings in source.

External operators set NEXT_PUBLIC_BRAND=other and supply their own
NEXT_PUBLIC_* env vars (see .env.example.external). The repo carries
no third-party trademarks.

Files:
- lib/white-label.ts                             brand detection + helpers
- .env.example.{lux,hanzo,zoo,pars,external}     deploy templates
- .github/workflows/build-lux.yml                4-brand × 2-arch matrix
- Dockerfile                                     accept NEXT_PUBLIC_BRAND
- configs/app/chainRegistry.ts                   add SPC + Pars devnet,
                                                 chainId fields, white-label
                                                 chain builder
2026-05-05 20:50:55 -07:00
zeekay 3813d03918 feat: explorer FE — stateless SSR, chain selector, localnet, registry cleanup 2026-03-01 18:00:00 -08:00
zeekay 284d506593 feat: OIDC login via lux.id, full-width layout, P-chain stats fix
- Add Sign in button to TopBar with OIDC flow to lux.id
- Merge settings (color theme, identicon, address format) into user menu popover
- Remove duplicate user profile from HeroBanner
- Full-width stacked blocks/transactions layout on homepage
- Fix P-chain stats: CSP connect-src allows base origin for SDK calls
- Fix connected count: infer from uptime when field missing
- Fix dark mode popover background
- Enable OIDC in .env.production (lux.id, client: explore)
2026-03-01 20:34:00 -08:00
zeekay da79f9e711 feat: rename @blockscout to @luxfi, monochrome theme, horizontal blocks
- Rename all @blockscout/* npm packages to @luxfi/* aliases (150+ files)
- Override blue accent colors to gray for monochrome theme
- Fix dropdown/menu/popover fonts to use Geist Sans
- Change Latest Blocks to horizontal scrolling layout
- Show dashes instead of zeros for validator stats on error
- Fix GHA deploy step to use correct deployment name
2026-03-02 12:28:15 -08:00
zeekay 84af44a4f5 fix: remove all user-visible Blockscout references
- Issue URL → github.com/luxfi/explore
- API docs links → docs.lux.network
- Contract verification → local route instead of vera.blockscout.com
- Discord link → discord.gg/luxnetwork
- JSON-LD script ID → lux-explorer-product-schema
- RPC API description → Lux Network branding
- Rollbar instance → .lux.network domain support
2026-03-02 10:43:36 -08:00
zeekay 1affde5938 feat: horizontal blocks layout, stats grid, network overview fallback chains
- LatestBlocks: full-width layout instead of fixed 280px sidebar
- Stats: 4-column grid on desktop, 2-column on mobile
- NetworkOverview: fallback known L1 chains when P-chain API unreachable
- NetworkOverview: expose isError from useCurrentValidators
- P-chain hooks: add retry:2 for resilience
- UserProfile: use address_hash instead of id for display
- Replace middleware.ts with proxy.ts (unused in production Docker)
- Add toolkit/chakra/separator re-export
- tsconfig: jsx react-jsx, formatting cleanup
- dev.sh: disable turbopack (stability)
2026-03-02 10:36:17 -08:00
zeekay ddb1fa2cd9 unify all sql images to :latest (PG18)
Single version policy: ghcr.io/hanzoai/sql:latest everywhere.
No version pinning, no backwards compat, always latest stable.
2026-03-01 21:09:09 -08:00
zeekay 388346c75f fix(validators): use weight field and correct uptime format for P-chain validators
- P-chain API returns weight not stakeAmount; fallback to weight when stakeAmount absent
- uptime from API is already 0-100 percentage, not 0-1 fraction; remove double-multiply
- mark connected and stakeAmount as optional to match actual API response shape
2026-03-01 12:45:34 -08:00
zeekay 08cddc0fcb brand: remove all Blockscout branding, replace with Lux Explorer
- Gas tracker FAQ: Blockscout → Lux Explorer
- Chain config web3/chains.ts: explorer name → Lux Explorer
- Auth screens: Blockscout account → Lux account
- Contract verification: Blockscout supports → Lux Explorer supports
- API docs: Blockscout core API → Lux Explorer Core API
- Rewards/merits: Blockscout Merits → Lux Explorer Rewards
- Marketplace disclaimer and integration icons
- Address verification: [email protected] → [email protected]
- Chart export filenames: Blockscout chart/stats → Lux Explorer
- OTEL service name: blockscout_frontend → lux_explorer_frontend
- Dev container: blockscout dev → lux explorer dev
2026-02-27 22:17:37 -08:00
zeekay cbe3f2adb2 fix: use real brand logos from each project's logo repo
- Lux: triangle from ~/work/lux/logo/
- Zoo: three overlapping circles from ~/work/zoo/logo/
- Hanzo: geometric H from ~/work/hanzo/logo/
- SPC: unicorn emoji per user request
- Pars: 8-pointed star from ~/work/pars/logo/
- Changed branding to multi-path SVGs (logoViewBox + logoContent)
2026-02-27 13:13:13 -08:00
zeekay d6fe894153 feat: per-chain branding — logo, favicon, brand name customizable
- Add ChainBranding interface with logoSvg, faviconSvg, brandName per chain
- Each chain (Lux, Zoo, Hanzo, SPC, Pars) gets distinct icon + brand name
- TopBar renders chain-specific logo and brand name from registry
- ChainSwitcher shows per-chain icons in dropdown items
- Dynamic favicon via useChainFavicon hook (swaps SVG favicon on mount)
- Page title uses chain branding via getNetworkTitle()
2026-02-27 13:04:06 -08:00
zeekay 0e381fd911 feat: add all chain VM accessors and wire DEX/Bridge to luxnet SDK
- Add centralized chain accessor module (lib/api/luxnet/chains.ts) exposing
  all 14 chain VMs (C/X/P/A/B/D/G/I/K/O/Q/R/T/Z) plus utility APIs
- Refactor pchain/client.ts to re-export from centralized module
- Wire DEX page to use DexVMAPI (getStats, getPools, getMarkets) with
  demo data fallback when D-Chain RPC is unavailable
- Add B-Chain bridge API module (lib/api/bchain/) using BridgeVMAPI
  (getSignerSetInfo, getWaitlist) for signer set stats
- Update Bridge page to show live bridge signer count from B-Chain
- Fix stale E2E tests: use >= minimum thresholds instead of exact
  counts (block/tx counts grow over time), use describe blocks
2026-02-27 12:05:23 -08:00
zeekay 5fed458a21 feat: integrate luxnet SDK for P-chain API calls
Replace hand-rolled JSON-RPC fetch client with [email protected].0 SDK.
P-chain hooks now use PlatformVMAPI methods directly.
2026-02-27 10:55:58 -08:00
zeekay 2c4fcde2d2 feat: multichain explorer pages, chain switcher, indexer integration
- Network Overview homepage with P-chain stats and chain cards
- Chain detail page with validator info and indexer stats
- Chain switcher dropdown in top bar
- A-chain AI client with indexer API integration
- D-chain DEX data hook with live indexer fallback
- E2E tests for all 5 live explorer instances
- Test transaction sender script
- Remove verbose comments, section dividers, and console.log scaffolding
2026-02-27 10:46:56 -08:00
zeekay 3725171b32 feat: add AI compute dashboard and DEX orderbook pages
- A-Chain (AI): dashboard with models, providers, attestations tabs
- D-Chain (DEX): markets, orderbook, trades, pools tabs
- API hooks with mock data (ready for indexer integration)
- Navigation items and route registration for /ai and /dex
2026-02-26 14:05:43 -08:00
zeekay 2e8885d7de feat: redesign homepage, add chain switcher and chain detail page
- Redesign NetworkOverview homepage with Blockscout Stats, LatestBlocks,
  and LatestTxs alongside P-chain network stats and chain cards
- Add ChainSwitcher dropdown to TopBar for switching between C-Chain,
  Zoo, Hanzo, SPC, and Pars explorer instances
- Create /chains/[slug] chain detail page with chain info, validators,
  subnet chains, and stats (modeled after Avalanche L1 Details)
- Make ChainsPage rows clickable with links to chain detail pages
- Register /chains/[slug] route in all metadata/analytics files
2026-02-26 09:57:59 -08:00
zeekay f6eb142582 feat: add Bridge page and Network Stats to Stats page
- Add /bridge page with cross-chain transfer route tracker
- Show bridge routes between Primary Network chains and L1 subnets
- Add Network Stats section (validators, chains, stake, uptime) to Stats page
- Add Bridge to navigation menu
- Register /bridge route in nextjs-routes, metadata, and mixpanel
2026-02-26 00:47:24 -08:00
zeekay 3c8d48e0e0 feat: unified multi-chain explorer with Network Overview, Chains, and Validators pages
- Replace single-chain homepage with Network Overview showing P-chain stats,
  primary chains (C/P/X/A/B/Q/T/Z/K), and L1 subnet chains
- Add Chains directory page with Primary Network / L1-L2-L3 tabs
- Add Validators page with Dashboard, Validators list, and Delegators tabs
- Add P-chain API hooks (useCurrentValidators, useBlockchains, useSubnets)
- Apply monochrome theme: replace all blue/purple/cyan/teal with gray
- Add Geist Mono font support
- Add Chains and Validators to navigation sidebar
- Update route types, metadata, analytics, and cspell for new pages
2026-02-25 22:53:10 -08:00
zeekay 82213f6b7b fix: remove hardcoded branding, use config-driven chain name everywhere
- Fix TS error in safeFormats plugin (use any for dayjsClass parameter)
- Replace hardcoded "Blockscout" with config.chain.name in DeFiDropdown
- Replace hardcoded "LUX" SVG text with config.chain.name in LogoFallback
- Use config.chain.name in NetworkIcon fallback aria-label
- Delete unused lux-logo.tsx (dead code with hardcoded branding)
2026-02-25 11:01:28 -08:00
zeekay 0cddfbf1ab fix: prevent dayjs crash when @reown/appkit-common changes global locale
@reown/appkit-common's DateUtil sets the global dayjs locale to
'en-web3-modal' which lacks a formats property. The localizedFormat
plugin crashes with "Cannot read properties of undefined (reading
'replace')" when lowercase format tokens (lll, llll) are used on
a locale without formats. Add a safety plugin that ensures formats
are always present on whichever locale the dayjs instance uses.
2026-02-25 10:49:18 -08:00
zeekay 675429cd30 Fix dayjs locale crash and modernize branding
- Preserve default dayjs locale formats (L, LL, LT, LTS, etc.) in
  updateLocale to prevent TypeError on format() calls
- Switch default font from Inter/Poppins to Geist Sans throughout
- Remove gas tracker stats and Add Network button from top bar
- Replace placeholder logo/icon fallbacks with Lux triangle branding
2026-02-25 07:47:30 -08:00
zeekay 5c6f0f470d Add /auth/callback to all route type dictionaries 2026-02-24 01:50:55 -08:00
zeekay c54e9b612e Add /auth/callback to OG type dict for type safety 2026-02-24 01:37:40 -08:00
tom goriunovandzeekay aa349ab54f Name service: use protocol list instead of chain ID (#3283)
* Name service: use protocol list instead of chain ID

Resolves #3281

* fix envs for demo
2026-02-24 01:12:57 -08:00
zooqueen 6c5d229352 feat(auth): add IAM login via hanzo.id for user accounts
Add OIDC authentication support using hanzo.id as the identity
provider for Lux Explorer user accounts.

- Add 'oidc' auth provider type alongside existing auth0/dynamic
- AuthGuardOidc: redirect-based OIDC login flow with CSRF state
- pages/auth/callback: authorization code exchange, token storage
- useApiFetch: pass Bearer token for OIDC-authenticated API calls
- account feature config: support NEXT_PUBLIC_OIDC_SERVER_URL and
  NEXT_PUBLIC_OIDC_CLIENT_ID environment variables
- .env.production: configure hanzo.id as default OIDC provider
2026-02-22 15:15:24 -08:00