mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
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]>
227 lines
7.7 KiB
TypeScript
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);
|