Files
explore/ui/bridge/BridgePage.tsx
T
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

227 lines
7.7 KiB
TypeScript

// Cross-chain bridge/teleporter page for the Lux multi-chain explorer.
// Shows cross-chain transfers between Primary Network chains and sovereign L1s.
import { Skeleton } from '@luxfi/ui/skeleton';
import { Tag } from '@luxfi/ui/tag';
import React from 'react';
import config from 'configs/app';
import { useBridgeData } from 'lib/api/bchain';
import { useBlockchains, useCurrentValidators } from 'lib/api/pchain';
import type { PChainBlockchain } from 'lib/api/pchain';
import PageTitle from 'ui/shared/Page/PageTitle';
// ── Constants ──
const PRIMARY_NETWORK_ID = '11111111111111111111111111111111LpoYY';
// All 14 primary network chains from ~/work/lux/node/node/vms_allvms.go
const CHAIN_LABELS: Readonly<Record<string, string>> = {
C: 'C-Chain',
P: 'P-Chain',
X: 'X-Chain',
D: 'D-Chain',
A: 'A-Chain',
B: 'B-Chain',
Q: 'Q-Chain',
T: 'T-Chain',
Z: 'Z-Chain',
G: 'G-Chain',
K: 'K-Chain',
O: 'O-Chain',
R: 'R-Chain',
I: 'I-Chain',
};
// ── Sub-components ──
interface StatCardProps {
readonly label: string;
readonly value: string;
readonly isLoading: boolean;
}
const StatCard = ({ label, value, isLoading }: StatCardProps) => (
<div className="border border-[var(--color-border-divider)] rounded-lg p-5 bg-[var(--color-gray-50)] dark:bg-[var(--color-whiteAlpha-50)]">
<div className="text-xs text-[var(--color-text-secondary)] font-semibold uppercase tracking-wider mb-1">
{ label }
</div>
<Skeleton loading={ isLoading }>
<div className="text-2xl font-bold text-[var(--color-text-primary)]">
{ value }
</div>
</Skeleton>
</div>
);
interface ChainPairCardProps {
readonly source: string;
readonly destination: string;
readonly status: 'active' | 'coming_soon';
}
const ChainPairCard = ({ source, destination, status }: ChainPairCardProps) => (
<div className={ `
flex items-center py-3 px-4 border-b border-[var(--color-border-divider)] hover:bg-[var(--color-gray-50)]
dark:hover:bg-[var(--color-whiteAlpha-50)] transition-colors duration-150 gap-4 flex-wrap lg:flex-nowrap
` }>
<div className="min-w-[160px] shrink-0">
<span className="font-medium text-sm text-[var(--color-text-primary)]">
{ source }
</span>
</div>
<span className="text-[var(--color-text-secondary)] text-sm shrink-0">
{ '\u2192' }
</span>
<div className="min-w-[160px] shrink-0">
<span className="font-medium text-sm text-[var(--color-text-primary)]">
{ destination }
</span>
</div>
<div className="ml-0 lg:ml-auto flex items-center">
{ status === 'active' ? (
<Tag size="sm" variant="subtle" className="bg-good/10 text-good dark:bg-good/10 dark:text-good">Active</Tag>
) : (
<Tag size="sm" variant="subtle">Coming Soon</Tag>
) }
</div>
</div>
);
// ── Main component ──
const BridgePage = () => {
const { blockchains, isLoading: chainsLoading } = useBlockchains();
const { stats, isLoading: validatorsLoading } = useCurrentValidators();
const { stats: bridgeStats, isLoading: bridgeLoading } = useBridgeData();
const l1Chains = React.useMemo<ReadonlyArray<PChainBlockchain>>(
() => blockchains.filter((c) => c.netID !== PRIMARY_NETWORK_ID),
[ blockchains ],
);
const isLoading = chainsLoading || validatorsLoading || bridgeLoading;
// Build bridge pairs: Primary chain <-> sovereign L1s
const bridgePairs = React.useMemo(() => {
const pairs: Array<{ source: string; destination: string; status: 'active' | 'coming_soon' }> = [];
// C-Chain <-> each L1
for (const chain of l1Chains) {
pairs.push({
source: CHAIN_LABELS.C ?? 'C-Chain',
destination: chain.name,
status: 'coming_soon',
});
pairs.push({
source: chain.name,
destination: CHAIN_LABELS.C ?? 'C-Chain',
status: 'coming_soon',
});
}
// X-Chain <-> C-Chain (atomic swaps)
pairs.unshift(
{ source: 'X-Chain', destination: 'C-Chain', status: 'active' },
{ source: 'C-Chain', destination: 'X-Chain', status: 'active' },
{ source: 'P-Chain', destination: 'C-Chain', status: 'active' },
{ source: 'C-Chain', destination: 'P-Chain', status: 'active' },
);
return pairs;
}, [ l1Chains ]);
return (
<>
<PageTitle
title="Bridge"
secondRow={ (
<div className="text-sm text-[var(--color-text-secondary)]">
Cross-chain transfers between { config.chain.name || 'network' } chains
</div>
) }
/>
{ /* Stats */ }
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3 mb-6">
<StatCard
label="Connected Chains"
value={ String(l1Chains.length + Object.keys(CHAIN_LABELS).length) }
isLoading={ isLoading }
/>
<StatCard
label="Bridge Routes"
value={ String(bridgePairs.length) }
isLoading={ isLoading }
/>
<StatCard
label="Bridge Signers"
value={ bridgeStats.signerCount > 0 ? String(bridgeStats.signerCount) : String(stats.validatorCount) }
isLoading={ isLoading }
/>
<StatCard
label="Active Routes"
value={ String(bridgePairs.filter((p) => p.status === 'active').length) }
isLoading={ isLoading }
/>
</div>
{ /* Bridge routes table */ }
<div className="border border-[var(--color-border-divider)] rounded-lg overflow-hidden">
<div className="px-4 py-3 font-semibold text-sm text-[var(--color-text-primary)] border-b border-[var(--color-border-divider)]">
Bridge Routes
</div>
{ /* Table header */ }
<div className="hidden lg:flex px-4 py-2 gap-4 border-b border-[var(--color-border-divider)]">
<div className="min-w-[160px] shrink-0 text-[var(--color-text-secondary)] font-semibold text-xs uppercase tracking-wider">
Source
</div>
<div className="w-5 shrink-0"/>
<div className="min-w-[160px] shrink-0 text-[var(--color-text-secondary)] font-semibold text-xs uppercase tracking-wider">
Destination
</div>
<div className="ml-auto text-[var(--color-text-secondary)] font-semibold text-xs uppercase tracking-wider">
Status
</div>
</div>
{ /* Loading */ }
{ isLoading && (
<div className="px-4 py-6">
<Skeleton loading h="16px" mb={ 3 }/>
<Skeleton loading h="16px" mb={ 3 }/>
<Skeleton loading h="16px" mb={ 3 }/>
<Skeleton loading h="16px"/>
</div>
) }
{ /* Routes */ }
{ !isLoading && bridgePairs.map((pair, idx) => (
<ChainPairCard
key={ `${ pair.source }-${ pair.destination }-${ idx }` }
source={ pair.source }
destination={ pair.destination }
status={ pair.status }
/>
)) }
</div>
{ /* Info card */ }
<div className="mt-6 p-5 border border-[var(--color-border-divider)] rounded-lg bg-[var(--color-gray-50)] dark:bg-[var(--color-whiteAlpha-50)]">
<span className="font-semibold text-sm text-[var(--color-text-primary)] mb-2 block">
{ config.chain.name || 'Network' } Bridge
</span>
<span className="text-sm text-[var(--color-text-secondary)] leading-relaxed">
The bridge enables cross-chain asset transfers between the Primary Network
chains and sovereign L1 chains. Atomic swaps between core chains (C, P, X) are
currently active. Transfers to sovereign L1s are
coming soon via the B-Chain bridge relay.
</span>
</div>
</>
);
};
export default React.memo(BridgePage);