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: help@blockscout.comsupport@lux.network
- Chart export filenames: Blockscout chart/stats → Lux Explorer
- OTEL service name: blockscout_frontend → lux_explorer_frontend
- Dev container: blockscout dev → lux explorer dev
This commit is contained in:
Hanzo Dev
2026-02-27 22:17:37 -08:00
parent f1e23567ce
commit df48914095
28 changed files with 343 additions and 391 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
{
"name": "blockscout dev",
"name": "lux explorer dev",
"image": "mcr.microsoft.com/devcontainers/typescript-node:20",
"forwardPorts": [ 3000 ],
"customizations": {
+1 -1
View File
@@ -22,7 +22,7 @@ const config: Feature<{
title,
isEnabled: true,
tabs,
coreApiSwaggerUrl: getEnvValue('NEXT_PUBLIC_API_SPEC_URL') || `https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml`,
coreApiSwaggerUrl: getEnvValue('NEXT_PUBLIC_API_SPEC_URL') || `https://raw.githubusercontent.com/luxfi/explorer-api-swagger/main/swagger.yaml`,
});
}
-2
View File
@@ -4,8 +4,6 @@ import { getEnvValue, getExternalAssetFilePath } from './utils';
const defaultImageUrl = '/static/og_image.png';
const meta = Object.freeze({
// Lux fork: disable Blockscout branding by default
promoteBlockscoutInTitle: getEnvValue('NEXT_PUBLIC_PROMOTE_BLOCKSCOUT_IN_TITLE') === 'true' ? true : false,
og: {
description: getEnvValue('NEXT_PUBLIC_OG_DESCRIPTION') || '',
imageUrl: app.baseUrl + (getExternalAssetFilePath('NEXT_PUBLIC_OG_IMAGE_URL') || defaultImageUrl),
+1 -1
View File
@@ -18,7 +18,7 @@ const traceExporter = new OTLPTraceExporter();
const sdk = new NodeSDK({
resource: new Resource({
[SEMRESATTRS_SERVICE_NAME]: 'blockscout_frontend',
[SEMRESATTRS_SERVICE_NAME]: 'lux_explorer_frontend',
[SEMRESATTRS_SERVICE_VERSION]: process.env.NEXT_PUBLIC_GIT_TAG || process.env.NEXT_PUBLIC_GIT_COMMIT_SHA || 'unknown_version',
[SEMRESATTRS_SERVICE_INSTANCE_ID]:
process.env.NEXT_PUBLIC_APP_INSTANCE ||
+2 -2
View File
@@ -73,8 +73,8 @@ const RewardsContext = createContext<TRewardsContext>(initialState);
// Message to sign for the rewards program
function getMessageToSign(address: string, nonce: string, isLogin?: boolean, refCode?: string) {
const signInText = 'Sign-In for the Blockscout Merits program.';
const signUpText = 'Sign-Up for the Blockscout Merits program. I accept Terms of Service: https://merits.blockscout.com/terms. I love capybaras.';
const signInText = 'Sign-In for the Lux Explorer Rewards program.';
const signUpText = 'Sign-Up for the Lux Explorer Rewards program. I accept Terms of Service: https://merits.blockscout.com/terms. I love capybaras.';
const referralText = refCode ? ` Referral code: ${ refCode }` : '';
const body = isLogin ? signInText : signUpText + referralText;
+1 -3
View File
@@ -118,7 +118,5 @@ const TEMPLATE_MAP_ENHANCED: Partial<Record<Route['pathname'], string>> = {
export function make(pathname: Route['pathname'], isEnriched = false) {
const template = (isEnriched ? TEMPLATE_MAP_ENHANCED[pathname] : undefined) ?? TEMPLATE_MAP[pathname];
const postfix = config.meta.promoteBlockscoutInTitle ? ' | Blockscout' : '';
return (template + postfix).trim();
return template.trim();
}
+2 -2
View File
@@ -28,7 +28,7 @@ const getChainInfo = (
},
blockExplorers: {
'default': {
name: 'Blockscout',
name: 'Lux Explorer',
url: config.app.baseUrl,
},
},
@@ -66,7 +66,7 @@ export const parentChain: Chain | undefined = (() => {
},
blockExplorers: {
'default': {
name: 'Blockscout',
name: 'Lux Explorer',
url: parentChain.baseUrl,
},
},
@@ -96,7 +96,7 @@ const ChartMenu = ({
})
.then((dataUrl) => {
const link = document.createElement('a');
link.download = `${ title }${ chainPostfix } (Blockscout chart).png`;
link.download = `${ title }${ chainPostfix } (Lux Explorer chart).png`;
link.href = dataUrl;
link.click();
link.remove();
@@ -113,7 +113,7 @@ const ChartMenu = ({
item.dateLabel ?? dayjs(item.date).format('YYYY-MM-DD'),
...charts.map((chart) => String(chart.items[index].value)),
]);
saveAsCsv(headerRows, dataRows, `${ title }${ chainPostfix } (Blockscout stats)`);
saveAsCsv(headerRows, dataRows, `${ title }${ chainPostfix } (Lux Explorer stats)`);
}, [ charts, title, chainPostfix ]);
// TS thinks window.navigator.share can't be undefined, but it can
+12 -157
View File
@@ -1,179 +1,34 @@
# Blockscout UI Toolkit
# Lux UI Toolkit
A comprehensive collection of reusable Chakra UI components and theme system for Blockscout's projects. This toolkit provides a consistent design system and UI components to maintain visual consistency across Blockscout applications.
A comprehensive collection of reusable UI components and theme system for Lux Network explorer and related applications.
## Features
- 🎨 Pre-configured Chakra UI theme with Blockscout's design system
- 🧩 Reusable UI components built on Chakra UI
- 🌓 Built-in dark mode support
- 📱 Responsive and accessible components
- 🔍 TypeScript support with proper type definitions
- Pre-configured theme with Lux Network's monochrome design system
- Reusable UI components built on Chakra UI
- Built-in dark mode support
- Responsive and accessible components
- TypeScript support with proper type definitions
## Installation
### Package Installation
Install the package using your preferred package manager:
```bash
# Using npm
npm install @blockscout/ui-toolkit
# Using yarn
yarn add @blockscout/ui-toolkit
```
### Required Dependencies
Ensure you have the following peer dependencies installed:
```json
{
"dependencies": {
"@blockscout/ui-toolkit": "latest",
"@chakra-ui/react": ">=3.15.0",
"@emotion/react": ">=11.14.0",
"@uidotdev/usehooks": ">=2.4.1",
"d3": ">=7.6.1",
"dayjs": ">=1.11.5",
"dom-to-image": ">=2.6.0",
"es-toolkit": ">=1.39.10",
"next": ">=15.2.3",
"next-themes": ">=0.4.4",
"react": ">=18.3.1",
"react-dom": ">=18.3.1",
"react-hook-form": ">=7.52.1"
},
"devDependencies": {
"@chakra-ui/cli": ">=3.15.0",
"@types/node": "^20",
"@types/react": "18.3.12",
"@types/react-dom": "18.3.1",
"typescript": "5.4.2"
}
}
```
## Quick Start
### 1. Theme Setup
Create a `theme.ts` file in your project and configure the Blockscout theme:
```tsx
// Basic setup
import { theme } from '@blockscout/ui-toolkit';
export default theme;
```
Or extend the theme with custom overrides:
```tsx
import { createSystem } from '@chakra-ui/react';
import { themeConfig } from '@blockscout/ui-toolkit';
const customOverrides = {
// Add your custom theme overrides here
theme: {
semanticTokens: {
colors: {
brand: {
primary: { value: '#5353D3' }
},
},
},
},
};
export default createSystem(themeConfig, customOverrides);
```
### 2. Provider Setup
Wrap your application with the ChakraProvider:
```tsx
import { ChakraProvider } from '@chakra-ui/react';
import { Button } from '@blockscout/ui-toolkit';
import theme from './theme';
function App() {
return (
<ChakraProvider theme={theme}>
<Button>Click me</Button>
</ChakraProvider>
);
}
```
### 3. TypeScript Support
Add the following script to your `package.json` to generate Chakra UI type definitions:
```json
{
"scripts": {
"chakra:typegen": "chakra typegen ./src/theme.ts"
}
}
npm install @luxfi/ui-toolkit
```
## Development
### Local Development
1. Clone the repository and install dependencies:
```bash
# Install dependencies
yarn
```
2. Start the development server:
```bash
# Start development server
yarn dev
```
3. Build the package:
```bash
# Build
yarn build
```
### Publishing
#### Manual Publishing
1. Update the package version:
```bash
npm version <version-tag>
```
2. Build the package:
```bash
npm run build
```
3. Publish to NPM:
```bash
npm publish --access public
```
#### Automated Publishing
Use the `toolkit-npm-publisher.yml` GitHub Actions workflow for automated publishing.
## Contributing
We welcome contributions! Please follow these steps:
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Submit a pull request
## Support
For issues, feature requests, or questions, please open an issue in the repository.
## License
This project is licensed under the GNU General Public License v3.
This project is licensed under the GNU General Public License v3.
+8 -8
View File
@@ -1,27 +1,27 @@
{
"name": "@blockscout/ui-toolkit",
"name": "@luxfi/ui-toolkit",
"version": "2.4.0-alpha",
"description": "A comprehensive collection of reusable Chakra UI components and theme system for Blockscout's projects",
"description": "A comprehensive collection of reusable UI components and theme system for Lux Network explorer",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"license": "GPL-3.0-only",
"author": "Blockscout Team",
"author": "Lux Industries Inc",
"repository": {
"type": "git",
"url": "https://github.com/blockscout/frontend.git",
"url": "https://github.com/luxfi/explore.git",
"directory": "toolkit/package"
},
"homepage": "https://github.com/blockscout/frontend/tree/main/toolkit/package",
"homepage": "https://github.com/luxfi/explore/tree/main/toolkit/package",
"bugs": {
"url": "https://github.com/blockscout/frontend/issues"
"url": "https://github.com/luxfi/explore/issues"
},
"keywords": [
"blockscout",
"lux",
"explorer",
"ui",
"components",
"chakra-ui",
"react",
"typescript",
"design-system"
@@ -152,7 +152,7 @@ const AddressVerificationStepSignature = ({ address, signingMessage, contractCre
);
})();
const contactUsLink = <span>contact us <Link href="mailto:help@blockscout.com" rel="noopener noreferrer">help@blockscout.com</Link></span>;
const contactUsLink = <span>contact us <Link href="mailto:support@lux.network" rel="noopener noreferrer">support@lux.network</Link></span>;
const rootError = (() => {
switch (formState.errors.root?.type) {
@@ -198,8 +198,8 @@ const AddressVerificationStepSignature = ({ address, signingMessage, contractCre
<form noValidate onSubmit={ onSubmit }>
{ rootError && <Alert status="warning" mb={ 6 }>{ rootError }</Alert> }
<Box mb={ 8 }>
<span>Please select the address to sign and copy the message and sign it using the Blockscout message provider of your choice. </span>
<Link href="https://docs.blockscout.com/using-blockscout/my-account/verified-addresses/copy-and-sign-message" external noIcon>
<span>Please select the address to sign and copy the message and sign it using the Lux Explorer message provider of your choice. </span>
<Link href="https://docs.lux.network/using-lux-explorer/my-account/verified-addresses/copy-and-sign-message" external noIcon>
Additional instructions
</Link>
<span>. If you do not see your address here but are sure that you are the owner of the contract, kindly </span>
+1 -1
View File
@@ -8,7 +8,7 @@ const EthRpcApi = () => {
<Box>
<Text>
In addition to the custom RPC endpoints documented here,
the Blockscout ETH RPC API supports 3 methods in the exact format specified for Ethereum nodes,
the Lux Explorer ETH RPC API supports 3 methods in the exact format specified for Ethereum nodes,
ee the Ethereum JSON-RPC Specification for more details.
</Text>
<Link href="https://docs.blockscout.com/devs/apis/rpc/eth-rpc" external mt={ 6 }>View examples</Link>
+4 -4
View File
@@ -20,8 +20,8 @@ const getMicroserviceSwaggerUrl = (api: ApiPropsBase) => `${ api.endpoint }${ ap
export const REST_API_SECTIONS = [
feature.isEnabled && {
id: 'blockscout-core-api',
title: 'Blockscout core API',
id: 'lux-core-api',
title: 'Lux Explorer Core API',
swagger: {
url: feature.coreApiSwaggerUrl,
requestInterceptor: (req: SwaggerRequest) => {
@@ -29,8 +29,8 @@ export const REST_API_SECTIONS = [
return req;
}
const DEFAULT_SERVER = 'blockscout.com/poa/core';
const DEFAULT_SERVER_NEW = 'eth.blockscout.com';
const DEFAULT_SERVER = 'api.lux.network';
const DEFAULT_SERVER_NEW = 'api.lux.network';
if (!req.loadSpec) {
const newUrl = new URL(
@@ -80,7 +80,7 @@ const ContractVerificationFieldMethod = ({ methods }: Props) => {
const tooltipContent = (
<Box>
<span>Currently, Blockscout supports { methods.length } methods:</span>
<span>Currently, Lux Explorer supports { methods.length } methods:</span>
<List.Root as="ol" pl={ 5 }>
{ methods.map(renderPopoverListItem) }
</List.Root>
@@ -90,7 +90,7 @@ const ContractVerificationFieldMethod = ({ methods }: Props) => {
return (
<>
<Heading level="2" mt={{ base: 10, lg: 6 }} gridColumn={{ lg: '1 / 3' }}>
Currently, Blockscout supports { methods.length }{ nbsp }contract verification methods
Currently, Lux Explorer supports { methods.length }{ nbsp }contract verification methods
<Hint
label={ tooltipContent }
tooltipProps={{ interactive: true, contentProps: { textAlign: 'left' } }}
+4 -4
View File
@@ -16,16 +16,16 @@ const FAQ_ITEMS = [
{
question: `How can I check ${ config.chain.name } gas fees?`,
// eslint-disable-next-line max-len
answer: `You can easily check live ${ config.chain.name } gas fees on Blockscout by visiting our gas tracker. It displays current gas fees in ${ currencyUnits.gwei } for all ${ config.chain.name } transactions.`,
answer: `You can easily check live ${ config.chain.name } gas fees on Lux Explorer by visiting our gas tracker. It displays current gas fees in ${ currencyUnits.gwei } for all ${ config.chain.name } transactions.`,
},
{
question: `What is the average gas fee for ${ config.chain.name } transactions?`,
// eslint-disable-next-line max-len
answer: `The average gas fee for ${ config.chain.name } transactions depends on network congestion and transaction complexity. Blockscout provides real-time gas fee estimations to help users make informed decisions.`,
answer: `The average gas fee for ${ config.chain.name } transactions depends on network congestion and transaction complexity. Lux Explorer provides real-time gas fee estimations to help users make informed decisions.`,
},
{
question: 'How does Blockscout calculate gas fees?',
answer: 'Blockscout calculates gas fees based on the average price of gas fees spent for the last 200 blocks.',
question: 'How does Lux Explorer calculate gas fees?',
answer: 'Lux Explorer calculates gas fees based on the average price of gas fees spent for the last 200 blocks.',
},
];
@@ -13,7 +13,7 @@ const MarketplaceAppIntegrationIcon = ({ external, internalWallet }: Props) => {
const [ icon, iconColor, text, boxSize ] = React.useMemo(() => {
let icon: IconName = 'integration/partial';
let color = 'icon.secondary';
let text = 'This app opens in Blockscout without Blockscout wallet functionality. Use your external web3 wallet to connect directly to this application';
let text = 'This app opens in Lux Explorer without wallet functionality. Use your external web3 wallet to connect directly to this application';
let boxSize = 5;
if (external) {
@@ -24,7 +24,7 @@ const MarketplaceAppIntegrationIcon = ({ external, internalWallet }: Props) => {
} else if (internalWallet) {
icon = 'integration/full';
color = 'green.500';
text = 'This app opens in Blockscout and your Blockscout wallet connects automatically';
text = 'This app opens in Lux Explorer and your Lux Explorer wallet connects automatically';
}
return [ icon, color, text, boxSize ];
@@ -35,7 +35,7 @@ const MarketplaceDisclaimerModal = ({ isOpen, onClose, appId }: Props) => {
<DialogBody>
<Text color={{ _light: 'gray.800', _dark: 'whiteAlpha.800' }}>
You are now accessing a third-party app. Blockscout does not own, control, maintain, or audit 3rd party apps,{ ' ' }
You are now accessing a third-party app. Lux Explorer does not own, control, maintain, or audit 3rd party apps,{ ' ' }
and is not liable for any losses associated with these interactions. Please do so at your own risk.
<br/><br/>
By clicking continue, you agree that you understand the risks and have read the Disclaimer.
+225 -118
View File
@@ -40,8 +40,9 @@ const L1_EXPLORER_URLS: Readonly<Record<string, string>> = {
Pars: 'https://explore-pars.lux.network',
};
const STAT_CARD_BG = { _light: 'gray.50', _dark: 'whiteAlpha.50' };
const CHAIN_CARD_HOVER = { _light: 'gray.50', _dark: 'whiteAlpha.100' };
const CARD_BG = { _light: 'gray.50', _dark: 'whiteAlpha.50' };
const CARD_HOVER = { _light: 'gray.100', _dark: 'whiteAlpha.100' };
const CARD_BORDER = '1px solid';
function formatStake(nanoLux: bigint): string {
const lux = Number(nanoLux) / Math.pow(10, LUX_DECIMALS);
@@ -50,56 +51,60 @@ function formatStake(nanoLux: bigint): string {
return lux.toFixed(0);
}
interface NetworkStatProps {
// ── Hero metric (compact inline stat) ──
interface MetricProps {
readonly label: string;
readonly value: string;
readonly isLoading: boolean;
}
const NetworkStat = ({ label, value, isLoading }: NetworkStatProps) => (
<Box textAlign="center">
const Metric = ({ label, value, isLoading }: MetricProps) => (
<Flex direction="column" align="center" px={ 3 }>
<Skeleton loading={ isLoading }>
<Text fontSize={{ base: 'lg', lg: '2xl' }} fontWeight={ 700 } color="text.primary">
<Text fontSize="lg" fontWeight={ 700 } fontFamily="mono" color="text.primary" lineHeight="1.2">
{ value }
</Text>
</Skeleton>
<Text fontSize="xs" color="text.secondary" fontWeight={ 500 } mt={ 0.5 }>
<Text fontSize="2xs" color="text.secondary" fontWeight={ 500 } mt={ 0.5 } textTransform="uppercase" letterSpacing="0.05em">
{ label }
</Text>
</Box>
</Flex>
);
interface ChainCardProps {
// ── Chain row (compact, for sidebar) ──
interface ChainRowProps {
readonly name: string;
readonly fullName: string;
readonly vm: string;
readonly href: string | undefined;
readonly tier?: string;
}
const ChainCard = ({ name, fullName, vm, href }: ChainCardProps) => {
const ChainRow = ({ name, fullName, vm, href, tier }: ChainRowProps) => {
const content = (
<Flex
align="center"
justify="space-between"
p={ 3 }
py={ 2 }
px={ 3 }
borderRadius="md"
border="1px solid"
borderColor="border.divider"
cursor={ href ? 'pointer' : 'default' }
_hover={ href ? { bg: CHAIN_CARD_HOVER } : undefined }
_hover={ href ? { bg: CARD_HOVER } : undefined }
transition="background 0.15s"
>
<Box>
<Flex align="center" gap={ 2 }>
<Text fontWeight={ 600 } color="text.primary" fontSize="sm">
{ name }
</Text>
<Text fontSize="xs" color="text.secondary">
<Text fontSize="xs" color="text.secondary" display={{ base: 'none', lg: 'inline' }}>
{ fullName }
</Text>
</Box>
<Flex align="center" gap={ 2 }>
</Flex>
<Flex align="center" gap={ 1.5 }>
{ tier && <Tag size="sm" variant="subtle">{ tier }</Tag> }
<Tag size="sm" variant="subtle">{ vm }</Tag>
{ href && <Text color="text.secondary" fontSize="sm">{ '\u2192' }</Text> }
</Flex>
</Flex>
);
@@ -110,11 +115,13 @@ const ChainCard = ({ name, fullName, vm, href }: ChainCardProps) => {
return content;
};
interface L1ChainCardProps {
// ── L1 Chain row ──
interface L1ChainRowProps {
readonly chain: PChainBlockchain;
}
const L1ChainCard = ({ chain }: L1ChainCardProps) => {
const L1ChainRow = ({ chain }: L1ChainRowProps) => {
const explorerUrl = L1_EXPLORER_URLS[chain.name];
const slug = chain.name.toLowerCase();
@@ -122,25 +129,24 @@ const L1ChainCard = ({ chain }: L1ChainCardProps) => {
<Flex
align="center"
justify="space-between"
p={ 3 }
py={ 2 }
px={ 3 }
borderRadius="md"
border="1px solid"
borderColor="border.divider"
cursor="pointer"
_hover={{ bg: CHAIN_CARD_HOVER }}
_hover={{ bg: CARD_HOVER }}
transition="background 0.15s"
>
<Box>
<Flex align="center" gap={ 2 }>
<Text fontWeight={ 600 } color="text.primary" fontSize="sm">
{ chain.name }
</Text>
<Text fontSize="xs" color="text.secondary" fontFamily="mono">
{ chain.id.slice(0, 10) }...
<Text fontSize="xs" color="text.secondary" fontFamily="mono" display={{ base: 'none', lg: 'inline' }}>
{ chain.id.slice(0, 8) }...
</Text>
</Box>
<Flex align="center" gap={ 2 }>
</Flex>
<Flex align="center" gap={ 1.5 }>
<Tag size="sm" variant="subtle">L1</Tag>
<Text color="text.secondary" fontSize="sm">{ '\u2192' }</Text>
<Text color="text.secondary" fontSize="xs">{ '\u2192' }</Text>
</Flex>
</Flex>
);
@@ -148,12 +154,48 @@ const L1ChainCard = ({ chain }: L1ChainCardProps) => {
if (explorerUrl) {
return <Link href={ explorerUrl } variant="plain" target="_blank">{ content }</Link>;
}
return (
<Link href={ `/chains/${ slug }` } variant="plain">{ content }</Link>
);
return <Link href={ `/chains/${ slug }` } variant="plain">{ content }</Link>;
};
// ── Sidebar card ──
interface SidebarCardProps {
readonly title: string;
readonly count?: number;
readonly isLoading?: boolean;
readonly action?: { label: string; href: string };
readonly children: React.ReactNode;
}
const SidebarCard = ({ title, count, isLoading, action, children }: SidebarCardProps) => (
<Box
border={ CARD_BORDER }
borderColor="border.divider"
borderRadius="lg"
p={ 4 }
bgColor={ CARD_BG }
>
<Flex align="center" justify="space-between" mb={ 3 }>
<Flex align="center" gap={ 2 }>
<Heading level="3" fontSize="sm">{ title }</Heading>
{ count !== undefined && (
<Skeleton loading={ isLoading }>
<Tag size="sm" variant="subtle">{ count }</Tag>
</Skeleton>
) }
</Flex>
{ action && (
<Link href={ action.href } textStyle="xs" color="text.secondary" _hover={{ color: 'text.primary' }}>
{ action.label }
</Link>
) }
</Flex>
{ children }
</Box>
);
// ── Main page ──
const NetworkOverview = () => {
const isMobile = useIsMobile();
const { stats, isLoading: validatorsLoading } = useCurrentValidators();
@@ -169,110 +211,175 @@ const NetworkOverview = () => {
return (
<Box as="main">
{ /* ── Hero search ── */ }
<HeroBanner/>
{ /* ── Metrics strip ── */ }
<Flex
justify="space-around"
justify="center"
align="center"
py={ 4 }
px={{ base: 4, lg: 6 }}
py={ 3 }
mt={ 4 }
borderRadius="lg"
border="1px solid"
border={ CARD_BORDER }
borderColor="border.divider"
bgColor={ STAT_CARD_BG }
gap={ 4 }
bgColor={ CARD_BG }
gap={ 0 }
flexWrap="wrap"
overflow="hidden"
>
<NetworkStat label="Total Chains" value={ String(totalChains) } isLoading={ isLoading }/>
<Box w="1px" h="32px" bgColor="border.divider" display={{ base: 'none', lg: 'block' }}/>
<NetworkStat label="Validators" value={ String(stats.validatorCount) } isLoading={ isLoading }/>
<Box w="1px" h="32px" bgColor="border.divider" display={{ base: 'none', lg: 'block' }}/>
<NetworkStat
label="Total Stake"
value={ `${ formatStake(stats.totalStake) } LUX` }
isLoading={ isLoading }
/>
<Box w="1px" h="32px" bgColor="border.divider" display={{ base: 'none', lg: 'block' }}/>
<NetworkStat
label="Avg Uptime"
value={ `${ stats.averageUptime.toFixed(1) }%` }
isLoading={ isLoading }
/>
<Box w="1px" h="32px" bgColor="border.divider" display={{ base: 'none', lg: 'block' }}/>
<NetworkStat
label="Connected"
value={ `${ stats.connectedCount }/${ stats.validatorCount }` }
isLoading={ isLoading }
/>
<Metric label="Chains" value={ String(totalChains) } isLoading={ isLoading }/>
<Box w="1px" h="28px" bgColor="border.divider" display={{ base: 'none', md: 'block' }}/>
<Metric label="Validators" value={ String(stats.validatorCount) } isLoading={ isLoading }/>
<Box w="1px" h="28px" bgColor="border.divider" display={{ base: 'none', md: 'block' }}/>
<Metric label="Staked" value={ `${ formatStake(stats.totalStake) } LUX` } isLoading={ isLoading }/>
<Box w="1px" h="28px" bgColor="border.divider" display={{ base: 'none', md: 'block' }}/>
<Metric label="Uptime" value={ `${ stats.averageUptime.toFixed(1) }%` } isLoading={ isLoading }/>
<Box w="1px" h="28px" bgColor="border.divider" display={{ base: 'none', md: 'block' }}/>
<Metric label="Connected" value={ `${ stats.connectedCount }/${ stats.validatorCount }` } isLoading={ isLoading }/>
</Flex>
<Flex mt={ 6 } gap={ 6 } flexDir={{ base: 'column', lg: 'row' }}>
<Stats/>
<LatestBlocks/>
</Flex>
<Box mt={ 6 }>
<Transactions/>
</Box>
{ /* ── Two-column: Activity (primary) + Chain Health (secondary) ── */ }
<Grid
templateColumns={{ base: '1fr', lg: '1fr 1fr' }}
gap={{ base: 6, lg: 8 }}
mt={ 8 }
templateColumns={{ base: '1fr', lg: '1fr 340px' }}
gap={ 5 }
mt={ 5 }
>
{ /* ── Left: Activity ── */ }
<Box>
<Flex align="center" justify="space-between" mb={ 3 }>
<Heading level="3">Primary Network</Heading>
<Tag size="sm" variant="subtle">{ PRIMARY_CHAINS.length }</Tag>
</Flex>
<Flex direction="column" gap={ 2 }>
{ PRIMARY_CHAINS.map((chain) => (
<ChainCard
key={ chain.id }
name={ chain.name }
fullName={ chain.fullName }
vm={ chain.vm }
href={ chain.href }
/>
)) }
</Flex>
{ /* Stats grid */ }
<Stats/>
{ /* Latest blocks */ }
<Box mt={ 5 }>
<LatestBlocks/>
</Box>
{ /* Transactions */ }
<Box mt={ 5 }>
<Transactions/>
</Box>
</Box>
<Box>
<Flex align="center" justify="space-between" mb={ 3 }>
<Heading level="3">L1 Chains</Heading>
<Skeleton loading={ chainsLoading }>
<Tag size="sm" variant="subtle">{ l1Chains.length }</Tag>
</Skeleton>
</Flex>
{ chainsLoading && (
<Flex direction="column" gap={ 2 }>
{ Array.from({ length: 4 }).map((_, i) => (
<Skeleton key={ i } loading h="68px" borderRadius="md"/>
{ /* ── Right: Chain Health sidebar ── */ }
<Flex direction="column" gap={ 4 } display={{ base: 'none', lg: 'flex' }}>
{ /* Primary Network chains */ }
<SidebarCard
title="Primary Network"
count={ PRIMARY_CHAINS.length }
>
<Flex direction="column" gap={ 0 }>
{ PRIMARY_CHAINS.map((chain) => (
<ChainRow
key={ chain.id }
name={ chain.name }
fullName={ chain.fullName }
vm={ chain.vm }
href={ chain.href }
/>
)) }
</Flex>
) }
{ !chainsLoading && l1Chains.length === 0 && (
<Text color="text.secondary" fontSize="sm" py={ 4 }>
No L1 chains registered yet.
</Text>
) }
{ !chainsLoading && l1Chains.length > 0 && (
<Flex direction="column" gap={ 2 }>
{ l1Chains.map((chain) => (
<L1ChainCard key={ chain.id } chain={ chain }/>
)) }
</Flex>
) }
</SidebarCard>
{ !isMobile && (
<Flex justify="center" mt={ 4 }>
<Link href="/chains" textStyle="sm">View all chains</Link>
{ /* Subnet / L1 chains — renamed to just "Chains" */ }
<SidebarCard
title="Chains"
count={ l1Chains.length }
isLoading={ chainsLoading }
action={{ label: 'View all', href: '/chains' }}
>
{ chainsLoading && (
<Flex direction="column" gap={ 1 }>
{ Array.from({ length: 4 }).map((_, i) => (
<Skeleton key={ i } loading h="40px" borderRadius="md"/>
)) }
</Flex>
) }
{ !chainsLoading && l1Chains.length === 0 && (
<Text color="text.secondary" fontSize="sm" py={ 2 }>
No chains registered yet.
</Text>
) }
{ !chainsLoading && l1Chains.length > 0 && (
<Flex direction="column" gap={ 0 }>
{ l1Chains.map((chain) => (
<L1ChainRow key={ chain.id } chain={ chain }/>
)) }
</Flex>
) }
</SidebarCard>
{ /* Validators summary card */ }
<SidebarCard title="Validators">
<Grid templateColumns="1fr 1fr" gap={ 3 }>
<Box>
<Skeleton loading={ isLoading }>
<Text fontWeight={ 700 } fontFamily="mono" fontSize="md">
{ stats.validatorCount }
</Text>
</Skeleton>
<Text fontSize="2xs" color="text.secondary">Active</Text>
</Box>
<Box>
<Skeleton loading={ isLoading }>
<Text fontWeight={ 700 } fontFamily="mono" fontSize="md">
{ formatStake(stats.totalStake) }
</Text>
</Skeleton>
<Text fontSize="2xs" color="text.secondary">Total Stake (LUX)</Text>
</Box>
<Box>
<Skeleton loading={ isLoading }>
<Text fontWeight={ 700 } fontFamily="mono" fontSize="md">
{ stats.delegatorCount }
</Text>
</Skeleton>
<Text fontSize="2xs" color="text.secondary">Delegators</Text>
</Box>
<Box>
<Skeleton loading={ isLoading }>
<Text fontWeight={ 700 } fontFamily="mono" fontSize="md">
{ `${ stats.connectedCount }/${ stats.validatorCount }` }
</Text>
</Skeleton>
<Text fontSize="2xs" color="text.secondary">Connected</Text>
</Box>
</Grid>
<Flex justify="center" mt={ 3 }>
<Link href="/validators" textStyle="xs" color="text.secondary" _hover={{ color: 'text.primary' }}>
View validators
</Link>
</Flex>
) }
</Box>
</SidebarCard>
</Flex>
</Grid>
{ /* ── Mobile-only chains section ── */ }
{ isMobile && (
<Box mt={ 5 }>
<SidebarCard
title="Chains"
count={ totalChains }
isLoading={ isLoading }
action={{ label: 'View all', href: '/chains' }}
>
<Flex direction="column" gap={ 0 }>
{ PRIMARY_CHAINS.slice(0, 5).map((chain) => (
<ChainRow
key={ chain.id }
name={ chain.name }
fullName={ chain.fullName }
vm={ chain.vm }
href={ chain.href }
/>
)) }
{ l1Chains.map((chain) => (
<L1ChainRow key={ chain.id } chain={ chain }/>
)) }
</Flex>
</SidebarCard>
</Box>
) }
</Box>
);
};
@@ -34,7 +34,7 @@ export default function RewardsActivityPassCard() {
<Link external href={ activityPassUrl } loading={ rewardsConfigQuery.isLoading }>
Activity pass
</Link>{ ' ' }
then engage with various Blockscout products and features to earn Merits every day!{ ' ' }
then engage with various Lux Explorer products and features to earn Merits every day!{ ' ' }
<Link external href="https://docs.blockscout.com/using-blockscout/merits/activity-pass">
Learn more
</Link>
+7 -7
View File
@@ -111,10 +111,10 @@ export default function ActivityTab() {
const tasks = useMemo(() => (
[
{
title: 'Blockscout activity',
title: 'Lux Explorer activity',
description: (
<>
Use Blockscout tools like{ ' ' }
Use Lux Explorer tools like{ ' ' }
<Link
external={ !marketplaceFeature?.essentialDapps }
href={ marketplaceFeature?.essentialDapps ?
@@ -144,7 +144,7 @@ export default function ActivityTab() {
<Link href={ route({ pathname: '/contract-verification' }) }>
verify a smart contract
</Link>{ ' ' }
on the Blockscout explorer to earn Merits.
on the Lux Explorer to earn Merits.
</>
),
percentile: activities.contracts?.percentile,
@@ -154,11 +154,11 @@ export default function ActivityTab() {
maxAmount: getMaxAmount(rewardsConfigQuery.data?.rewards?.verified_contracts_activity_rewards),
},
{
title: 'Blockscout usage',
title: 'Lux Explorer usage',
description: (
<>
Use Blockscout explorers in your daily routine { mdash } check transactions, explore addresses,
or add tokens/networks to MetaMask via Blockscout.
Use Lux Explorer in your daily routine { mdash } check transactions, explore addresses,
or add tokens/networks to MetaMask via Lux Explorer.
</>
),
percentile: activities.usage?.percentile,
@@ -220,7 +220,7 @@ export default function ActivityTab() {
<Flex flexDirection="column" p={{ base: 1.5, md: 0 }} pb={ 0 }>
<Heading level="3" mb={ 2 }>Your activity</Heading>
<Text textStyle="sm" mb={{ base: 2, md: 4 }}>
Use Blockscout and related products daily to earn Merits. Check each task for details and how to get started.
Use Lux Explorer and related products daily to earn Merits. Check each task for details and how to get started.
</Text>
<Flex alignItems="center" gap={ 3 } mb={{ base: 0, md: 4 }}>
<Button
+4 -4
View File
@@ -121,9 +121,9 @@ const LoginStepContent = ({ goNext, closeModal, openAuthModal }: Props) => {
fallback={ <Skeleton loading w="full" h="120px"/> }
/>
<Box mb={ 6 }>
Merits are awarded for a variety of different Blockscout activities. Connect a wallet to get started.
Merits are awarded for a variety of different Lux Explorer activities. Connect a wallet to get started.
<Link external href="https://docs.blockscout.com/using-blockscout/merits" ml={ 1 } fontWeight="500">
More about Blockscout Merits
More about Lux Explorer Merits
</Link>
</Box>
{ isSignUp && isLoggedIntoAccountWithWallet && (
@@ -159,7 +159,7 @@ const LoginStepContent = ({ goNext, closeModal, openAuthModal }: Props) => {
) }
{ isAddressMismatch && (
<Alert status="warning" mb={ 4 }>
Your wallet address doesn{ apos }t match the one in your Blockscout account. Please connect the correct wallet.
Your wallet address doesn{ apos }t match the one in your Lux Explorer account. Please connect the correct wallet.
</Alert>
) }
<Button
@@ -175,7 +175,7 @@ const LoginStepContent = ({ goNext, closeModal, openAuthModal }: Props) => {
{ buttonText }
</Button>
<Text textStyle="sm" color="text.secondary" textAlign="center">
Already registered for Blockscout Merits on another network or chain? Connect the same wallet here.
Already registered for Lux Explorer Merits on another network or chain? Connect the same wallet here.
</Text>
</>
);
@@ -43,11 +43,11 @@ const AuthModalScreenSuccessEmail = ({ email, onConnectWallet, onClose, isAuth,
<Box>
<Text>
<chakra.span fontWeight="700">{ email }</chakra.span>{ ' ' }
email has been successfully used to log in to your Blockscout account.
email has been successfully used to log in to your Lux account.
</Text>
{ !profile?.address_hash && config.features.blockchainInteraction.isEnabled ? (
<>
<Text mt={ 6 }>Add your web3 wallet to safely interact with smart contracts and dapps inside Blockscout.</Text>
<Text mt={ 6 }>Add your web3 wallet to safely interact with smart contracts and dapps inside Lux Explorer.</Text>
<Button mt={ 6 } onClick={ handleConnectWalletClick }>Connect wallet</Button>
</>
) : (
@@ -47,13 +47,13 @@ const AuthModalScreenSuccessWallet = ({ address, onAddEmail, onClose, isAuth, pr
<Text>
Wallet{ ' ' }
<chakra.span fontWeight="700">{ shortenString(address) }</chakra.span>{ ' ' }
has been successfully used to log in to your Blockscout account
has been successfully used to log in to your Lux account
{ Boolean(rewardsToken) && ` and Merits Program` }.
</Text>
{ !profile?.email ? (
<>
<Text mt={ 6 }>
Add your email to receive exclusive updates about Blockscout { config.features.rewards.isEnabled ? 'Merits ' : ' ' }
Add your email to receive exclusive updates about Lux Explorer { config.features.rewards.isEnabled ? 'Merits ' : ' ' }
and notifications about addresses in your watch list.
</Text>
<Flex mt={ 6 } gap={ 6 }>
+8 -11
View File
@@ -129,16 +129,13 @@ const Footer = () => {
return (
<Box gridArea={ gridArea }>
<Flex columnGap={ 2 } textStyle="xs" alignItems="center">
<span>Powered by</span>
<Link href="https://lux.network" external noIcon display="inline-flex" color={ logoColor } _hover={{ color: logoColor }}>
<IconSvg
name="networks/logo-placeholder"
width="80px"
height={ 4 }
/>
</Link>
</Flex>
<Link href="https://lux.network" external noIcon display="inline-flex" color={ logoColor } _hover={{ color: logoColor }}>
<IconSvg
name="networks/logo-placeholder"
width="80px"
height={ 4 }
/>
</Link>
<Text mt={ 3 } fontSize="xs">
Lux Network is a high-performance blockchain platform for decentralized applications and custom blockchain networks.
</Text>
@@ -154,7 +151,7 @@ const Footer = () => {
</Text>
) }
<Text>
Copyright { copy } Lux Network 2023-{ (new Date()).getFullYear() }
{ copy } { (new Date()).getFullYear() } Lux Industries Inc. All rights reserved.
</Text>
</Box>
</Box>
@@ -6,8 +6,8 @@ export default function getApiVersionUrl(version: string | undefined): string |
const [ tag, commit ] = version.split('.+commit.');
if (commit) {
return `https://github.com/blockscout/blockscout/commit/${ commit }`;
return `https://github.com/luxfi/explorer/commit/${ commit }`;
}
return `https://github.com/blockscout/blockscout/tree/${ tag }`;
return `https://github.com/luxfi/explorer/tree/${ tag }`;
}
+43 -46
View File
@@ -8,7 +8,7 @@ import { getCurrentChain } from 'configs/app/chainRegistry';
import { Link } from 'toolkit/chakra/link';
import { MenuContent, MenuItem, MenuRoot, MenuTrigger } from 'toolkit/chakra/menu';
import { CONTENT_MAX_WIDTH } from 'ui/shared/layout/utils';
import UserProfileDesktop from 'ui/snippets/user/UserProfileDesktop';
import SearchBar from 'ui/snippets/searchBar/SearchBarDesktop';
import ChainSwitcher from './ChainSwitcher';
import NetworkSelector from './NetworkSelector';
@@ -83,48 +83,47 @@ const TopBar = () => {
maxW={ `${ CONTENT_MAX_WIDTH }px` }
gap={ 1 }
>
{ /* ── Logo + Network brand ── */ }
<chakra.a
href={ route({ pathname: '/' as const }) }
display="flex"
alignItems="center"
gap="6px"
flexShrink={ 0 }
mr={ 3 }
aria-label={ `${ chain.branding.brandName } home` }
textDecoration="none"
_hover={{ textDecoration: 'none', opacity: 0.8 }}
transition="opacity 0.15s"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox={ chain.branding.logoViewBox }
width="18"
height="18"
dangerouslySetInnerHTML={{ __html: chain.branding.logoContent }}
/>
<chakra.span
fontWeight={ 700 }
fontSize="sm"
letterSpacing="-0.02em"
whiteSpace="nowrap"
color="text.primary"
{ /* ── Logo + Brand + Chain/Network selectors (grouped) ── */ }
<Flex alignItems="center" gap={ 1.5 } flexShrink={ 0 }>
<chakra.a
href={ route({ pathname: '/' as const }) }
display="flex"
alignItems="center"
gap="6px"
flexShrink={ 0 }
aria-label={ `${ chain.branding.brandName } home` }
textDecoration="none"
_hover={{ textDecoration: 'none', opacity: 0.8 }}
transition="opacity 0.15s"
>
{ chain.branding.brandName }
</chakra.span>
</chakra.a>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox={ chain.branding.logoViewBox }
width="18"
height="18"
dangerouslySetInnerHTML={{ __html: chain.branding.logoContent }}
/>
<chakra.span
fontWeight={ 700 }
fontSize="sm"
letterSpacing="-0.02em"
whiteSpace="nowrap"
color="text.primary"
>
{ chain.branding.brandName }
</chakra.span>
</chakra.a>
{ /* Chain & Network selectors — directly next to brand name */ }
<NetworkSelector/>
<ChainSwitcher/>
</Flex>
{ /* ── Divider ── */ }
<Box h="16px" w="1px" bgColor="border.divider" flexShrink={ 0 } mr={ 2 }/>
<Box h="16px" w="1px" bgColor="border.divider" flexShrink={ 0 } mx={ 1 }/>
{ /* ── Navigation ── */ }
{ /* ── Navigation (no Home — logo click does that) ── */ }
<HStack as="nav" gap={ 0 } display={{ base: 'none', lg: 'flex' }} flexShrink={ 0 }>
<NavLinkItem
text="Home"
href={ route({ pathname: '/' as const }) }
isActive={ pathname === '/' }
/>
{ /* Blockchain dropdown */ }
<MenuRoot>
<MenuTrigger asChild>
@@ -206,15 +205,13 @@ const TopBar = () => {
/>
</HStack>
{ /* ── Spacer ── */ }
<Box flex={ 1 }/>
{ /* ── Search bar (center, flexible) ── */ }
<Box flex={ 1 } mx={ 2 } display={{ base: 'none', lg: 'block' }} maxW="480px">
<SearchBar isHeroBanner={ false }/>
</Box>
{ /* ── Right controls ── */ }
<HStack gap={ 1.5 } flexShrink={ 0 }>
<NetworkSelector/>
<ChainSwitcher/>
<UserProfileDesktop buttonSize="sm"/>
</HStack>
{ /* ── Spacer ── */ }
<Box flex={ 1 } display={{ base: 'block', lg: 'none' }}/>
</Flex>
</Box>
);
@@ -96,7 +96,7 @@ const UserProfileContent = ({ data, onClose, onLogin, onAddEmail, onAddAddress }
<Flex p={ 2 } borderColor="border.divider" borderBottomWidth="1px">
<Box>Address</Box>
<Hint
label={ `This wallet address is linked to your Blockscout account. It can be used to login ${ config.features.rewards.isEnabled ? 'and is used for Merits Program participation' : '' }` } // eslint-disable-line max-len
label={ `This wallet address is linked to your Lux account. It can be used to login ${ config.features.rewards.isEnabled ? 'and is used for Merits Program participation' : '' }` } // eslint-disable-line max-len
boxSize={ 4 }
ml={ 1 }
/>
@@ -92,7 +92,7 @@ const UserProfileContentWallet = ({ onClose, className }: Props) => {
<Hint
label={
web3Wallet.isConnected ?
'This wallet is currently connected to Blockscout and used for interacting with apps and smart contracts' :
'This wallet is currently connected to Lux Explorer and used for interacting with apps and smart contracts' :
'This wallet is used for interacting with apps and smart contracts'
}
boxSize={ 4 }