OP Superchain: local views for chains in the cluster (#2864)

* update styles for chain widgets on home page

* update latest txs styles and slug creation algorithm

* list of local txs

* list of local blocks

* local view for address internal tx tab

* local view for address coin history

* local view for address logs

* local view for address token transfers

* address details, kind of

* [wip] local view for address contract tab

* user ops views

* chain popover

* csv-export page

* advanced filter page

* block countdown pages

* fixes for contract tab

* refactor address txs and transfers tabs

* refactor getServerSideProps

* internal txs page

* tokens and token transfers pages

* simple tag for address page and details info placeholders

* add chain icon to the local lists

* show creator in contract tab

* OP Superchain: local views for chains in the cluster

Fixes #2827

* highlight current chain in the title of local entities

* tokens nfts tab for address

* allow entity icon to be an image

* adjust cross-chain txs table and add placeholder to home page

* cross-chain txs for address

* fetch multichain config from microservice

* fix skeleton in token transfers table

* tests, pt.1

* wtf with gh

* verified contracts page

* change chain info in block and token page header

* fix some tests

* update styles for token icon with chain logo in header

* fix links and address internal txs tab

* update screenshots

* fix margins for tx info button and chain icon in tx tables

* add some tests

* fix screenshots
This commit is contained in:
tom goriunov
2025-08-15 15:32:47 +02:00
committed by GitHub
parent 28d2a602ed
commit 7505f154e6
314 changed files with 5557 additions and 1812 deletions
+1
View File
@@ -113,6 +113,7 @@ const multichainApi = (() => {
return Object.freeze({
endpoint: apiHost,
socketEndpoint: `wss://${ url.host }`,
basePath: stripTrailingSlash(getEnvValue('NEXT_PUBLIC_MULTICHAIN_AGGREGATOR_BASE_PATH') || ''),
});
} catch (error) {
return;
+8 -5
View File
@@ -19,6 +19,7 @@ NEXT_PUBLIC_NETWORK_ID=10
# TODO @tom2drum New ENVs (add to docs)
NEXT_PUBLIC_MULTICHAIN_AGGREGATOR_API_HOST=https://multichain-aggregator.k8s-dev.blockscout.com
NEXT_PUBLIC_MULTICHAIN_AGGREGATOR_BASE_PATH=/api/v1/clusters/interop
NEXT_PUBLIC_OP_SUPERCHAIN_ENABLED=true
# TODO @tom2drum remove this
@@ -30,10 +31,10 @@ NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-c
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=none
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs', 'coin_price', 'market_cap', 'secondary_coin_price']
NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG={'background':['linear-gradient(90deg, rgb(232, 52, 53) 0%, rgb(139, 28, 232) 100%)'],'text_color':['rgb(255, 255, 255)']}
NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/optimism-mainnet-light.svg
NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/optimism-mainnet-dark.svg
NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/optimism.svg
NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/optimism.svg
NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/optimism-superchain.svg
NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/optimism-superchain.svg
NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/optimism-superchain.svg
NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/optimism-superchain-dark.svg
NEXT_PUBLIC_NETWORK_NAME=OP Superchain
NEXT_PUBLIC_NETWORK_SHORT_NAME=OP Superchain
NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/optimism-mainnet.png
@@ -43,4 +44,6 @@ NEXT_PUBLIC_HIDE_INDEXING_ALERT_BLOCKS=true
NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS=true
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=false
NEXT_PUBLIC_IS_TESTNET=true
NEXT_PUBLIC_USE_NEXT_JS_PROXY=false
NEXT_PUBLIC_USE_NEXT_JS_PROXY=true
NEXT_PUBLIC_HAS_USER_OPS=true
NEXT_PUBLIC_ADVANCED_FILTER_ENABLED=false
+1
View File
@@ -52,6 +52,7 @@ NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=http://localhost:3006
NEXT_PUBLIC_METADATA_SERVICE_API_HOST=http://localhost:3007
NEXT_PUBLIC_NAME_SERVICE_API_HOST=http://localhost:3008
NEXT_PUBLIC_REWARDS_SERVICE_API_HOST=http://localhost:3009
NEXT_PUBLIC_MULTICHAIN_AGGREGATOR_API_HOST=http://localhost:3010
NEXT_PUBLIC_TAC_OPERATION_LIFECYCLE_API_HOST=http://localhost:3100
NEXT_PUBLIC_USER_OPS_INDEXER_API_HOST=http://localhost:3110
NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=xxx
@@ -6,13 +6,19 @@ import { Worker } from 'node:worker_threads';
const currentFilePath = fileURLToPath(import.meta.url);
const currentDir = dirname(currentFilePath);
const EXPLORER_URLS = [
'https://optimism-interop-alpha-0.blockscout.com',
'https://optimism-interop-alpha-1.blockscout.com',
];
// const EXPLORER_URLS = [
// 'https://optimism.blockscout.com',
// 'https://base.blockscout.com',
// 'https://arbitrum.blockscout.com',
// 'https://unichain.blockscout.com',
// 'https://explorer.redstone.xyz',
// 'https://matchscan.io'
// // 'https://optimism-interop-alpha-0.blockscout.com',
// // 'https://optimism-interop-alpha-1.blockscout.com',
// ];
function getSlug(url: string) {
return new URL(url).hostname.replace('.blockscout.com', '').replace('.k8s-dev', '');
function getSlug(chainName: string) {
return chainName.toLowerCase().replace(/ /g, '-').replace(/[^a-z0-9-]/g, '');
}
async function computeChainConfig(url: string): Promise<unknown> {
@@ -41,6 +47,22 @@ async function computeChainConfig(url: string): Promise<unknown> {
});
}
async function getExplorerUrls() {
try {
const basePath = (process.env.NEXT_PUBLIC_MULTICHAIN_AGGREGATOR_BASE_PATH ?? '') + '/chains';
const url = new URL(basePath, process.env.NEXT_PUBLIC_MULTICHAIN_AGGREGATOR_API_HOST);
const response = await fetch(url.toString());
const data = await response.json();
console.log(`💡 Found ${ data.items.length } chains in cluster.`);
return data.items.map((item: { explorer_url: string }) => item.explorer_url);
} catch (error) {
return [];
}
}
async function run() {
try {
if (!process.env.NEXT_PUBLIC_MULTICHAIN_AGGREGATOR_API_HOST) {
@@ -49,12 +71,20 @@ async function run() {
}
console.log('🌀 Generating multichain config...');
const configs = await Promise.all(EXPLORER_URLS.map(computeChainConfig));
const explorerUrls = await getExplorerUrls();
if (!explorerUrls.length) {
throw new Error('No chains found in the cluster.');
}
const configs = await Promise.all(explorerUrls.map(computeChainConfig));
const config = {
chains: configs.map((config, index) => {
const chainName = (config as { chain: { name: string } })?.chain?.name ?? `Chain ${ index + 1 }`;
return {
slug: getSlug(EXPLORER_URLS[index]),
slug: getSlug(chainName),
config,
};
}),
@@ -172,6 +172,10 @@ module.exports = {
};
},
additionalPaths: async(config) => {
if(process.env.NEXT_PUBLIC_OP_SUPERCHAIN_ENABLED === 'true'){
return;
}
const addresses = fetchResource(
`${ apiUrl }/addresses`,
(data) => data.items.map(({ hash }) => `/address/${ hash }`),
+6
View File
@@ -0,0 +1,6 @@
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.303 13.995A7.988 7.988 0 0 1 8 16 8.015 8.015 0 0 1 .006 8.59h7.887l5.41 5.404Zm2.692-5.404a7.97 7.97 0 0 1-1.917 4.619L9.452 8.59h6.543Z" fill="#7B67E9"/>
<path d="M15.994 8.59a7.972 7.972 0 0 1-1.915 4.623L9.452 8.59h6.542Z" fill="#FAA135"/>
<path d="M8.535 0A8.013 8.013 0 0 1 16 7.456H8.535V0Z" fill="#64CE55"/>
<path d="M7.469 7.46H0A8.012 8.012 0 0 1 7.469 0v7.46Z" fill="#C455CE"/>
</svg>

After

Width:  |  Height:  |  Size: 490 B

+2 -1
View File
@@ -10,7 +10,7 @@ import { GENERAL_API_RESOURCES } from './services/general';
import type { GeneralApiResourceName, GeneralApiResourcePayload, GeneralApiPaginationFilters, GeneralApiPaginationSorting } from './services/general';
import type { MetadataApiResourceName, MetadataApiResourcePayload } from './services/metadata';
import { METADATA_API_RESOURCES } from './services/metadata';
import type { MultichainApiResourceName, MultichainApiResourcePayload } from './services/multichain';
import type { MultichainApiPaginationFilters, MultichainApiResourceName, MultichainApiResourcePayload } from './services/multichain';
import { MULTICHAIN_API_RESOURCES } from './services/multichain';
import type { RewardsApiResourceName, RewardsApiResourcePayload } from './services/rewards';
import { REWARDS_API_RESOURCES } from './services/rewards';
@@ -91,6 +91,7 @@ export type PaginationFilters<R extends ResourceName> =
R extends BensApiResourceName ? BensApiPaginationFilters<R> :
R extends GeneralApiResourceName ? GeneralApiPaginationFilters<R> :
R extends ContractInfoApiResourceName ? ContractInfoApiPaginationFilters<R> :
R extends MultichainApiResourceName ? MultichainApiPaginationFilters<R> :
R extends TacOperationLifecycleApiResourceName ? TacOperationLifecycleApiPaginationFilters<R> :
never;
/* eslint-enable @stylistic/indent */
+14 -1
View File
@@ -3,9 +3,14 @@ import type * as multichain from '@blockscout/multichain-aggregator-types';
export const MULTICHAIN_API_RESOURCES = {
interop_messages: {
path: '/api/v1/interop/messages',
path: '/messages',
filterFields: [ 'address' as const ],
paginated: true,
},
interop_messages_count: {
path: '/messages/count',
filterFields: [ 'address' as const ],
},
} satisfies Record<string, ApiResource>;
export type MultichainApiResourceName = `multichain:${ keyof typeof MULTICHAIN_API_RESOURCES }`;
@@ -13,5 +18,13 @@ export type MultichainApiResourceName = `multichain:${ keyof typeof MULTICHAIN_A
/* eslint-disable @stylistic/indent */
export type MultichainApiResourcePayload<R extends MultichainApiResourceName> =
R extends 'multichain:interop_messages' ? multichain.ListInteropMessagesResponse :
R extends 'multichain:interop_messages_count' ? multichain.CountInteropMessagesResponse :
never;
/* eslint-enable @stylistic/indent */
/* eslint-disable @stylistic/indent */
export type MultichainApiPaginationFilters<R extends MultichainApiResourceName> =
R extends 'multichain:interop_messages' ? Partial<multichain.ListInteropMessagesRequest> :
R extends 'multichain:interop_messages_count' ? Partial<multichain.CountInteropMessagesRequest> :
never;
/* eslint-enable @stylistic/indent */
+1 -1
View File
@@ -41,7 +41,7 @@ export default function useApiFetch() {
const url = buildUrl(resourceName, pathParams, queryParams, undefined, chain);
const withBody = isBodyAllowed(fetchParams?.method);
const headers = pickBy({
'x-endpoint': api.endpoint && apiName !== 'general' && isNeedProxy() ? api.endpoint : undefined,
'x-endpoint': isNeedProxy() ? api.endpoint : undefined,
Authorization: [ 'admin', 'contractInfo' ].includes(apiName) ? apiToken : undefined,
'x-csrf-token': withBody && csrfToken ? csrfToken : undefined,
...resource.headers,
+1 -1
View File
@@ -1,7 +1,7 @@
import React, { createContext, useContext } from 'react';
import type { Route } from 'nextjs-routes';
import type { Props as PageProps } from 'nextjs/getServerSideProps';
import type { Props as PageProps } from 'nextjs/getServerSideProps/handlers';
type Props = {
children: React.ReactNode;
+12 -2
View File
@@ -6,22 +6,27 @@ import type { ChainConfig } from 'types/multichain';
import multichainConfig from 'configs/multichain';
import getQueryParamString from 'lib/router/getQueryParamString';
export type ContextLevel = 'page' | 'tab';
interface MultichainProviderProps {
children: React.ReactNode;
chainSlug?: string;
level?: ContextLevel;
}
export interface TMultichainContext {
chain: ChainConfig;
level?: ContextLevel;
}
export const MultichainContext = React.createContext<TMultichainContext | null>(null);
export function MultichainProvider({ children, chainSlug: chainSlugProp }: MultichainProviderProps) {
export function MultichainProvider({ children, chainSlug: chainSlugProp, level: levelProp }: MultichainProviderProps) {
const router = useRouter();
const chainSlugQueryParam = router.pathname.includes('chain-slug') ? getQueryParamString(router.query['chain-slug']) : undefined;
const [ chainSlug, setChainSlug ] = React.useState<string | undefined>(chainSlugProp ?? chainSlugQueryParam);
const [ level, setLevel ] = React.useState<ContextLevel | undefined>(levelProp);
React.useEffect(() => {
if (chainSlugProp) {
@@ -29,6 +34,10 @@ export function MultichainProvider({ children, chainSlug: chainSlugProp }: Multi
}
}, [ chainSlugProp ]);
React.useEffect(() => {
setLevel(levelProp);
}, [ levelProp ]);
const chain = React.useMemo(() => {
const config = multichainConfig();
if (!config) {
@@ -49,8 +58,9 @@ export function MultichainProvider({ children, chainSlug: chainSlugProp }: Multi
return {
chain,
level,
};
}, [ chain ]);
}, [ chain, level ]);
return (
<MultichainContext.Provider value={ value }>
+8 -4
View File
@@ -36,13 +36,13 @@ export default function useNavItems(): ReturnType {
text: 'Blocks',
nextRoute: { pathname: '/blocks' as const },
icon: 'block',
isActive: pathname === '/blocks' || pathname === '/block/[height_or_hash]',
isActive: pathname === '/blocks' || pathname === '/block/[height_or_hash]' || pathname === '/chain/[chain-slug]/block/[height_or_hash]',
};
const txs: NavItem | null = {
text: 'Transactions',
nextRoute: { pathname: '/txs' as const },
icon: 'transactions',
isActive: pathname === '/txs' || pathname === '/tx/[hash]',
isActive: pathname === '/txs' || pathname === '/tx/[hash]' || pathname === '/chain/[chain-slug]/tx/[hash]',
};
const operations: NavItem | null = config.features.tac.isEnabled ? {
text: 'Operations',
@@ -60,7 +60,7 @@ export default function useNavItems(): ReturnType {
text: 'User operations',
nextRoute: { pathname: '/ops' as const },
icon: 'user_op',
isActive: pathname === '/ops' || pathname === '/op/[hash]',
isActive: pathname === '/ops' || pathname === '/op/[hash]' || pathname === '/chain/[chain-slug]/op/[hash]',
} : null;
const verifiedContracts: NavItem | null =
@@ -245,7 +245,11 @@ export default function useNavItems(): ReturnType {
} : null;
const otherNavItems: Array<NavItem> | Array<Array<NavItem>> = [
{
config.features.opSuperchain.isEnabled ? {
text: 'Verify contract',
// TODO @tom2drum adjust URL to Vera
url: 'https://vera.blockscout.com',
} : {
text: 'Verify contract',
nextRoute: { pathname: '/contract-verification' as const },
isActive: pathname.startsWith('/contract-verification'),
+12 -4
View File
@@ -62,13 +62,21 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = {
'/pools': 'Root page',
'/pools/[hash]': 'Regular page',
'/interop-messages': 'Root page',
'/chain/[chain-slug]/accounts/label/[slug]': 'Root page',
'/chain/[chain-slug]/address/[hash]': 'Regular page',
'/chain/[chain-slug]/block/[height_or_hash]': 'Regular page',
'/chain/[chain-slug]/tx/[hash]': 'Regular page',
'/operations': 'Root page',
'/operation/[id]': 'Regular page',
// multichain routes
'/chain/[chain-slug]/accounts/label/[slug]': 'Root page',
'/chain/[chain-slug]/advanced-filter': 'Regular page',
'/chain/[chain-slug]/block/[height_or_hash]': 'Regular page',
'/chain/[chain-slug]/block/countdown': 'Regular page',
'/chain/[chain-slug]/block/countdown/[height]': 'Regular page',
'/chain/[chain-slug]/csv-export': 'Regular page',
'/chain/[chain-slug]/op/[hash]': 'Regular page',
'/chain/[chain-slug]/token/[hash]': 'Regular page',
'/chain/[chain-slug]/token/[hash]/instance/[id]': 'Regular page',
'/chain/[chain-slug]/tx/[hash]': 'Regular page',
// service routes, added only to make typescript happy
'/login': 'Regular page',
'/sprite': 'Regular page',
+12 -4
View File
@@ -65,13 +65,21 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/pools': DEFAULT_TEMPLATE,
'/pools/[hash]': DEFAULT_TEMPLATE,
'/interop-messages': DEFAULT_TEMPLATE,
'/chain/[chain-slug]/accounts/label/[slug]': DEFAULT_TEMPLATE,
'/chain/[chain-slug]/address/[hash]': DEFAULT_TEMPLATE,
'/chain/[chain-slug]/block/[height_or_hash]': DEFAULT_TEMPLATE,
'/chain/[chain-slug]/tx/[hash]': DEFAULT_TEMPLATE,
'/operations': DEFAULT_TEMPLATE,
'/operation/[id]': DEFAULT_TEMPLATE,
// multichain routes
'/chain/[chain-slug]/accounts/label/[slug]': DEFAULT_TEMPLATE,
'/chain/[chain-slug]/advanced-filter': DEFAULT_TEMPLATE,
'/chain/[chain-slug]/block/[height_or_hash]': DEFAULT_TEMPLATE,
'/chain/[chain-slug]/block/countdown': DEFAULT_TEMPLATE,
'/chain/[chain-slug]/block/countdown/[height]': DEFAULT_TEMPLATE,
'/chain/[chain-slug]/csv-export': DEFAULT_TEMPLATE,
'/chain/[chain-slug]/op/[hash]': DEFAULT_TEMPLATE,
'/chain/[chain-slug]/token/[hash]': DEFAULT_TEMPLATE,
'/chain/[chain-slug]/token/[hash]/instance/[id]': DEFAULT_TEMPLATE,
'/chain/[chain-slug]/tx/[hash]': DEFAULT_TEMPLATE,
// service routes, added only to make typescript happy
'/login': DEFAULT_TEMPLATE,
'/sprite': DEFAULT_TEMPLATE,
+12 -4
View File
@@ -62,13 +62,21 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/pools': '%network_name% DEX pools',
'/pools/[hash]': '%network_name% pool details',
'/interop-messages': '%network_name% interop messages',
'/chain/[chain-slug]/accounts/label/[slug]': '%network_name% addresses search by label',
'/chain/[chain-slug]/address/[hash]': '%network_name% address details for %hash%',
'/chain/[chain-slug]/block/[height_or_hash]': '%network_name% block %height_or_hash% details',
'/chain/[chain-slug]/tx/[hash]': '%network_name% transaction %hash% details',
'/operations': '%network_name% operations',
'/operation/[id]': '%network_name% operation %id%',
// multichain routes
'/chain/[chain-slug]/accounts/label/[slug]': '%network_name% addresses search by label',
'/chain/[chain-slug]/advanced-filter': '%network_name% advanced filter',
'/chain/[chain-slug]/block/[height_or_hash]': '%network_name% block %height_or_hash% details',
'/chain/[chain-slug]/block/countdown': '%network_name% block countdown index',
'/chain/[chain-slug]/block/countdown/[height]': '%network_name% block %height% countdown',
'/chain/[chain-slug]/csv-export': '%network_name% export data to CSV',
'/chain/[chain-slug]/op/[hash]': '%network_name% user operation %hash% details',
'/chain/[chain-slug]/token/[hash]': '%network_name% token details',
'/chain/[chain-slug]/token/[hash]/instance/[id]': '%network_name% token NFT instance',
'/chain/[chain-slug]/tx/[hash]': '%network_name% transaction %hash% details',
// service routes, added only to make typescript happy
'/login': '%network_name% login',
'/sprite': '%network_name% SVG sprite',
+12 -4
View File
@@ -60,13 +60,21 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = {
'/pools': 'DEX pools',
'/pools/[hash]': 'Pool details',
'/interop-messages': 'Interop messages',
'/chain/[chain-slug]/accounts/label/[slug]': 'Chain addresses search by label',
'/chain/[chain-slug]/address/[hash]': 'Chain address details',
'/chain/[chain-slug]/block/[height_or_hash]': 'Chain block details',
'/chain/[chain-slug]/tx/[hash]': 'Chain transaction details',
'/operations': 'Operations',
'/operation/[id]': 'Operation details',
// multichain routes
'/chain/[chain-slug]/accounts/label/[slug]': 'Chain addresses search by label',
'/chain/[chain-slug]/advanced-filter': 'Chain advanced filter',
'/chain/[chain-slug]/block/[height_or_hash]': 'Chain block details',
'/chain/[chain-slug]/block/countdown': 'Chain block countdown index',
'/chain/[chain-slug]/block/countdown/[height]': 'Chain block countdown',
'/chain/[chain-slug]/csv-export': 'Chain export data to CSV',
'/chain/[chain-slug]/op/[hash]': 'Chain user operation details',
'/chain/[chain-slug]/token/[hash]': 'Chain token details',
'/chain/[chain-slug]/token/[hash]/instance/[id]': 'Chain token NFT instance',
'/chain/[chain-slug]/tx/[hash]': 'Chain transaction details',
// service routes, added only to make typescript happy
'/login': 'Login',
'/sprite': 'Sprite',
+6
View File
@@ -0,0 +1,6 @@
import type { TMultichainContext } from 'lib/contexts/multichain';
export function getChainDataForList(multichainContext: TMultichainContext | null) {
// for now we only show chain icon in the list with chain selector (not in the entire local pages)
return multichainContext?.chain && multichainContext.level !== 'page' ? multichainContext.chain : undefined;
}
+5
View File
@@ -0,0 +1,5 @@
import type { ChainConfig } from 'types/multichain';
export default function getChainTooltipText(chain: ChainConfig, prefix: string = '') {
return `${ prefix }${ chain.config.chain.name } (Chain ID ${ chain.config.chain.id })`;
}
+14
View File
@@ -0,0 +1,14 @@
import type { Router } from 'next/router';
import multichainConfig from 'configs/multichain';
import getQueryParamString from 'lib/router/getQueryParamString';
export default function getChainValueFromQuery(query: Router['query']) {
const config = multichainConfig();
if (!config) {
return undefined;
}
const queryParam = getQueryParamString(query['chain-slug']);
return queryParam || config.chains[0].slug;
}
+6
View File
@@ -0,0 +1,6 @@
import multichainConfig from 'configs/multichain';
export default function getCurrencySymbol() {
const nativeCurrency = multichainConfig()?.chains[0]?.config.chain.currency;
return nativeCurrency?.symbol;
}
+8
View File
@@ -0,0 +1,8 @@
import type { ChainConfig } from 'types/multichain';
export default function getIconUrl(config: ChainConfig) {
const appUrl = config.config.app.baseUrl;
const iconPath = config.config.UI.navigation.icon.default;
return `${ appUrl }${ iconPath }`;
}
+35
View File
@@ -0,0 +1,35 @@
import { useRouter } from 'next/router';
import React from 'react';
import getChainValueFromQuery from './getChainValueFromQuery';
interface Props {
persistedParams?: Array<string>;
}
export default function useRoutedChainSelect(props?: Props) {
const router = useRouter();
const [ value, setValue ] = React.useState<Array<string> | undefined>(
[ getChainValueFromQuery(router.query) ].filter(Boolean),
);
const onValueChange = React.useCallback(({ value }: { value: Array<string> }) => {
setValue(value);
const nextQuery = props?.persistedParams ? props.persistedParams.reduce((acc, param) => ({ ...acc, [param]: router.query[param] }), {}) : router.query;
router.push({
pathname: router.pathname,
query: {
...nextQuery,
'chain-slug': value[0],
},
});
}, [ props?.persistedParams, router ]);
return React.useMemo(() => ({
value,
onValueChange,
}), [ value, onValueChange ]);
}
@@ -1,7 +1,7 @@
import config from 'configs/app';
export default function getNetworkValidationActionText() {
switch (config.chain.verificationType) {
export default function getNetworkValidationActionText(chainConfig = config) {
switch (chainConfig.chain.verificationType) {
case 'validation': {
return 'validated';
}
+15 -11
View File
@@ -2,33 +2,37 @@ import React from 'react';
import type { AddEthereumChainParameter } from 'viem';
import config from 'configs/app';
import { useMultichainContext } from 'lib/contexts/multichain';
import { SECOND } from 'toolkit/utils/consts';
import useRewardsActivity from '../hooks/useRewardsActivity';
import useProvider from './useProvider';
import { getHexadecimalChainId } from './utils';
function getParams(): AddEthereumChainParameter {
if (!config.chain.id) {
function getParams(chainConfig: typeof config): AddEthereumChainParameter {
if (!chainConfig.chain.id) {
throw new Error('Missing required chain config');
}
return {
chainId: getHexadecimalChainId(Number(config.chain.id)),
chainName: config.chain.name ?? '',
chainId: getHexadecimalChainId(Number(chainConfig.chain.id)),
chainName: chainConfig.chain.name ?? '',
nativeCurrency: {
name: config.chain.currency.name ?? '',
symbol: config.chain.currency.symbol ?? '',
decimals: config.chain.currency.decimals ?? 18,
name: chainConfig.chain.currency.name ?? '',
symbol: chainConfig.chain.currency.symbol ?? '',
decimals: chainConfig.chain.currency.decimals ?? 18,
},
rpcUrls: config.chain.rpcUrls,
blockExplorerUrls: [ config.app.baseUrl ],
rpcUrls: chainConfig.chain.rpcUrls,
blockExplorerUrls: [ chainConfig.app.baseUrl ],
};
}
export default function useAddChain() {
const { wallet, provider } = useProvider();
const { trackUsage } = useRewardsActivity();
const multichainContext = useMultichainContext();
const chainConfig = multichainContext?.chain.config ?? config;
return React.useCallback(async() => {
if (!wallet || !provider) {
@@ -39,12 +43,12 @@ export default function useAddChain() {
await provider.request({
method: 'wallet_addEthereumChain',
params: [ getParams() ],
params: [ getParams(chainConfig) ],
});
// if network is already added, the promise resolves immediately
if (Date.now() - start > SECOND) {
await trackUsage('add_network');
}
}, [ wallet, provider, trackUsage ]);
}, [ wallet, provider, chainConfig, trackUsage ]);
}
+41
View File
@@ -0,0 +1,41 @@
import React from 'react';
import * as mixpanel from 'lib/mixpanel/index';
import { toaster } from 'toolkit/chakra/toaster';
import useAddChain from './useAddChain';
import useProvider from './useProvider';
import useSwitchChain from './useSwitchChain';
export default function useAddChainClick() {
const { provider, wallet } = useProvider();
const addChain = useAddChain();
const switchChain = useSwitchChain();
return React.useCallback(async() => {
if (!wallet || !provider) {
return;
}
try {
await addChain();
await switchChain();
toaster.success({
title: 'Success',
description: 'Successfully added network to your wallet',
});
mixpanel.logEvent(mixpanel.EventTypes.ADD_TO_WALLET, {
Target: 'network',
Wallet: wallet,
});
} catch (error) {
toaster.error({
title: 'Error',
description: (error as Error)?.message || 'Something went wrong',
});
}
}, [ addChain, provider, wallet, switchChain ]);
}
+11 -6
View File
@@ -1,23 +1,28 @@
import React from 'react';
import { getFeaturePayload } from 'configs/app/features/types';
import type { WalletType } from 'types/client/wallets';
import type { WalletProvider } from 'types/web3';
import config from 'configs/app';
const feature = config.features.web3Wallet;
import { useMultichainContext } from 'lib/contexts/multichain';
export default function useProvider() {
const [ provider, setProvider ] = React.useState<WalletProvider>();
const [ wallet, setWallet ] = React.useState<WalletType>();
const multichainContext = useMultichainContext();
const feature = (multichainContext?.chain.config ?? config).features.web3Wallet;
const wallets = getFeaturePayload(feature)?.wallets;
const initializeProvider = React.useMemo(() => async() => {
if (!feature.isEnabled) {
if (!feature.isEnabled || !wallets) {
return;
}
if (!('ethereum' in window && window.ethereum)) {
if (feature.wallets.includes('metamask') && window.navigator.userAgent.includes('Firefox')) {
if (wallets.includes('metamask') && window.navigator.userAgent.includes('Firefox')) {
const { WindowPostMessageStream } = (await import('@metamask/post-message-stream'));
const { initializeProvider } = (await import('@metamask/providers'));
@@ -50,7 +55,7 @@ export default function useProvider() {
// if user has only one wallet, the provider is injected in the window.ethereum directly
const providers = Array.isArray(window.ethereum.providers) ? window.ethereum.providers : [ window.ethereum ];
for (const wallet of feature.wallets) {
for (const wallet of wallets) {
const provider = providers.find((provider) => {
return (
// some wallets (e.g TokenPocket, Liquality, etc) try to look like MetaMask but they are not (not even close)
@@ -70,7 +75,7 @@ export default function useProvider() {
break;
}
}
}, []);
}, [ feature.isEnabled, wallets ]);
React.useEffect(() => {
initializeProvider();
+9 -5
View File
@@ -1,20 +1,24 @@
import React from 'react';
import config from 'configs/app';
import { useMultichainContext } from 'lib/contexts/multichain';
import useProvider from './useProvider';
import { getHexadecimalChainId } from './utils';
function getParams(): { chainId: string } {
if (!config.chain.id) {
function getParams(chainConfig: typeof config): { chainId: string } {
if (!chainConfig.chain.id) {
throw new Error('Missing required chain config');
}
return { chainId: getHexadecimalChainId(Number(config.chain.id)) };
return { chainId: getHexadecimalChainId(Number(chainConfig.chain.id)) };
}
export default function useSwitchChain() {
const { wallet, provider } = useProvider();
const multichainContext = useMultichainContext();
const chainConfig = multichainContext?.chain.config ?? config;
return React.useCallback(() => {
if (!wallet || !provider) {
@@ -23,7 +27,7 @@ export default function useSwitchChain() {
return provider.request({
method: 'wallet_switchEthereumChain',
params: [ getParams() ],
params: [ getParams(chainConfig) ],
});
}, [ wallet, provider ]);
}, [ wallet, provider, chainConfig ]);
}
+7
View File
@@ -1,5 +1,12 @@
import type { AddressCounters } from 'types/api/address';
export const forAddress: AddressCounters = {
gas_usage_count: '319340525',
token_transfers_count: '420',
transactions_count: '5462',
validations_count: '0',
};
export const forContract: AddressCounters = {
gas_usage_count: '319340525',
token_transfers_count: '0',
+44
View File
@@ -0,0 +1,44 @@
import type { ChainConfig } from 'types/multichain';
export const chainDataA = {
slug: 'op-mainnet',
config: {
app: {
baseUrl: 'https://op-mainnet.com',
},
chain: {
name: 'OP Mainnet',
id: '420',
currency: {
name: 'Ether',
symbol: 'ETH',
decimals: 18,
},
},
UI: {
navigation: {
icon: {
'default': '/logo.png',
},
},
},
apis: {
general: {
host: 'localhost',
protocol: 'http',
port: '4003',
endpoint: 'http://localhost:4003',
socketEndpoint: 'ws://localhost:3200',
},
},
features: {
csvExport: {
isEnabled: true,
reCaptcha: { siteKey: 'xxx' },
},
advancedFilter: {
isEnabled: true,
},
},
},
} as ChainConfig;
+1 -1
View File
@@ -2,7 +2,7 @@ import Head from 'next/head';
import React from 'react';
import type { Route } from 'nextjs-routes';
import type { Props as PageProps } from 'nextjs/getServerSideProps';
import type { Props as PageProps } from 'nextjs/getServerSideProps/handlers';
import config from 'configs/app';
import * as metadata from 'lib/metadata';
+1 -1
View File
@@ -1,7 +1,7 @@
import React from 'react';
import type { Route } from 'nextjs-routes';
import type { Props as PageProps } from 'nextjs/getServerSideProps';
import type { Props as PageProps } from 'nextjs/getServerSideProps/handlers';
import PageMetadata from 'nextjs/PageMetadata';
import useAdblockDetect from 'lib/hooks/useAdblockDetect';
-440
View File
@@ -1,440 +0,0 @@
import type { GetServerSideProps, GetServerSidePropsContext, GetServerSidePropsResult } from 'next';
import type { AdBannerProviders } from 'types/client/adProviders';
import type { RollupType } from 'types/client/rollup';
import type { Route } from 'nextjs-routes';
import config from 'configs/app';
import multichainConfig from 'configs/multichain';
import isNeedProxy from 'lib/api/isNeedProxy';
import * as cookies from 'lib/cookies';
import type * as metadata from 'lib/metadata';
import detectBotRequest from './utils/detectBotRequest';
const rollupFeature = config.features.rollup;
const adBannerFeature = config.features.adsBanner;
export interface Props<Pathname extends Route['pathname'] = never> {
query: Route['query'];
cookies: string;
referrer: string;
adBannerProvider: AdBannerProviders | null;
// if apiData is undefined, Next.js will complain that it is not serializable
// so we force it to be always present in the props but it can be null
apiData: metadata.ApiData<Pathname> | null;
uuid: string;
}
export const base = async <Pathname extends Route['pathname'] = never>({ req, res, query }: GetServerSidePropsContext):
Promise<GetServerSidePropsResult<Props<Pathname>>> => {
const adBannerProvider = (() => {
if (adBannerFeature.isEnabled) {
if ('additionalProvider' in adBannerFeature && adBannerFeature.additionalProvider) {
// we need to get a random ad provider on the server side to keep it consistent with the client side
const randomIndex = Math.round(Math.random());
return [ adBannerFeature.provider, adBannerFeature.additionalProvider ][randomIndex];
} else {
return adBannerFeature.provider;
}
}
return null;
})();
let uuid = cookies.getFromCookieString(req.headers.cookie || '', cookies.NAMES.UUID);
if (!uuid) {
uuid = crypto.randomUUID();
res.setHeader('Set-Cookie', `${ cookies.NAMES.UUID }=${ uuid }`);
}
const isTrackingDisabled = process.env.DISABLE_TRACKING === 'true';
const isBot = Boolean(detectBotRequest(req));
if (!isTrackingDisabled && !isBot) {
// log pageview
const hostname = req.headers.host;
const timestamp = new Date().toISOString();
const chainId = process.env.NEXT_PUBLIC_NETWORK_ID;
const chainName = process.env.NEXT_PUBLIC_NETWORK_NAME;
const publicRPC = process.env.NEXT_PUBLIC_NETWORK_RPC_URL;
fetch('https://monitor.blockscout.com/count', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
hostname,
timestamp,
chainId,
chainName,
publicRPC,
uuid,
}),
});
}
return {
props: {
query,
cookies: req.headers.cookie || '',
referrer: req.headers.referer || '',
adBannerProvider: adBannerProvider,
apiData: null,
uuid,
},
};
};
export const account: GetServerSideProps<Props> = async(context) => {
if (!config.features.account.isEnabled) {
return {
notFound: true,
};
}
return base(context);
};
export const verifiedAddresses: GetServerSideProps<Props> = async(context) => {
if (!config.features.addressVerification.isEnabled) {
return {
notFound: true,
};
}
return account(context);
};
const DEPOSITS_ROLLUP_TYPES: Array<RollupType> = [ 'optimistic', 'shibarium', 'zkEvm', 'arbitrum', 'scroll' ];
export const deposits: GetServerSideProps<Props> = async(context) => {
if (!(rollupFeature.isEnabled && DEPOSITS_ROLLUP_TYPES.includes(rollupFeature.type))) {
return {
notFound: true,
};
}
return base(context);
};
const WITHDRAWALS_ROLLUP_TYPES: Array<RollupType> = [ 'optimistic', 'shibarium', 'zkEvm', 'arbitrum', 'scroll' ];
export const withdrawals: GetServerSideProps<Props> = async(context) => {
if (
!config.features.beaconChain.isEnabled &&
!(rollupFeature.isEnabled && WITHDRAWALS_ROLLUP_TYPES.includes(rollupFeature.type))
) {
return {
notFound: true,
};
}
return base(context);
};
export const txnWithdrawals: GetServerSideProps<Props> = async(context) => {
if (!(rollupFeature.isEnabled && rollupFeature.type === 'arbitrum')) {
return {
notFound: true,
};
}
return base(context);
};
export const rollup: GetServerSideProps<Props> = async(context) => {
if (!config.features.rollup.isEnabled) {
return {
notFound: true,
};
}
return base(context);
};
export const outputRoots: GetServerSideProps<Props> = async(context) => {
if (!(rollupFeature.isEnabled && rollupFeature.outputRootsEnabled)) {
return {
notFound: true,
};
}
return base(context);
};
const BATCH_ROLLUP_TYPES: Array<RollupType> = [ 'zkEvm', 'zkSync', 'arbitrum', 'optimistic', 'scroll' ];
export const batch: GetServerSideProps<Props> = async(context) => {
if (!(rollupFeature.isEnabled && BATCH_ROLLUP_TYPES.includes(rollupFeature.type))) {
return {
notFound: true,
};
}
return base(context);
};
export const batchCelestia: GetServerSideProps<Props> = async(context) => {
if (!(rollupFeature.isEnabled && (rollupFeature.type === 'arbitrum' || rollupFeature.type === 'optimistic'))) {
return {
notFound: true,
};
}
return base(context);
};
export const marketplace = async <Pathname extends Route['pathname'] = never>(context: GetServerSidePropsContext):
Promise<GetServerSidePropsResult<Props<Pathname>>> => {
if (!config.features.marketplace.isEnabled) {
return {
notFound: true,
};
}
return base<Pathname>(context);
};
export const apiDocs: GetServerSideProps<Props> = async(context) => {
if (!config.features.apiDocs.isEnabled) {
return {
notFound: true,
};
}
return base(context);
};
export const csvExport: GetServerSideProps<Props> = async(context) => {
if (!config.features.csvExport.isEnabled) {
return {
notFound: true,
};
}
return base(context);
};
export const stats: GetServerSideProps<Props> = async(context) => {
if (!config.features.stats.isEnabled) {
return {
notFound: true,
};
}
return base(context);
};
export const suave: GetServerSideProps<Props> = async(context) => {
if (!config.features.suave.isEnabled) {
return {
notFound: true,
};
}
return base(context);
};
export const nameService: GetServerSideProps<Props> = async(context) => {
if (!config.features.nameService.isEnabled) {
return {
notFound: true,
};
}
return base(context);
};
export const accounts: GetServerSideProps<Props> = async(context) => {
if (config.UI.views.address.hiddenViews?.top_accounts) {
return {
notFound: true,
};
}
return base(context);
};
export const accountsLabelSearch: GetServerSideProps<Props> = async(context) => {
if (!config.features.addressMetadata.isEnabled || !context.query.tagType) {
return {
notFound: true,
};
}
return base(context);
};
export const userOps: GetServerSideProps<Props> = async(context) => {
if (!config.features.userOps.isEnabled) {
return {
notFound: true,
};
}
return base(context);
};
export const validators: GetServerSideProps<Props> = async(context) => {
if (!config.features.validators.isEnabled) {
return {
notFound: true,
};
}
return base(context);
};
export const validatorDetails: GetServerSideProps<Props> = async(context) => {
const feature = config.features.validators;
if (!feature.isEnabled || feature.chainType !== 'zilliqa') {
return {
notFound: true,
};
}
return base(context);
};
export const gasTracker: GetServerSideProps<Props> = async(context) => {
if (!config.features.gasTracker.isEnabled) {
return {
notFound: true,
};
}
return base(context);
};
export const advancedFilter: GetServerSideProps<Props> = async(context) => {
if (!config.features.advancedFilter.isEnabled) {
return {
notFound: true,
};
}
return base(context);
};
export const dataAvailability: GetServerSideProps<Props> = async(context) => {
if (!config.features.dataAvailability.isEnabled) {
return {
notFound: true,
};
}
return base(context);
};
export const login: GetServerSideProps<Props> = async(context) => {
if (!isNeedProxy()) {
return {
notFound: true,
};
}
return base(context);
};
export const dev: GetServerSideProps<Props> = async(context) => {
if (!config.app.isDev) {
return {
notFound: true,
};
}
return base(context);
};
export const publicTagsSubmit: GetServerSideProps<Props> = async(context) => {
if (!config.features.publicTagsSubmission.isEnabled) {
return {
notFound: true,
};
}
return base(context);
};
export const disputeGames: GetServerSideProps<Props> = async(context) => {
if (!config.features.faultProofSystem.isEnabled) {
return {
notFound: true,
};
}
return base(context);
};
export const mud: GetServerSideProps<Props> = async(context) => {
if (!config.features.mudFramework.isEnabled) {
return {
notFound: true,
};
}
return base(context);
};
export const tac: GetServerSideProps<Props> = async(context) => {
if (!config.features.tac.isEnabled) {
return {
notFound: true,
};
}
return base(context);
};
export const celo: GetServerSideProps<Props> = async(context) => {
if (!config.features.celo.isEnabled) {
return {
notFound: true,
};
}
return base(context);
};
export const interopMessages: GetServerSideProps<Props> = async(context) => {
const rollupFeature = config.features.rollup;
if (!rollupFeature.isEnabled || !rollupFeature.interopEnabled) {
return {
notFound: true,
};
}
return base(context);
};
export const opSuperchain: GetServerSideProps<Props> = async(context) => {
if (!config.features.opSuperchain.isEnabled) {
return {
notFound: true,
};
}
return base(context);
};
export const opSuperchainAccountsLabelSearch: GetServerSideProps<Props> = async(context) => {
const chainSlug = context.params?.['chain-slug'];
const chain = multichainConfig()?.chains.find((chain) => chain.slug === chainSlug);
if (!chain?.config.features.addressMetadata.isEnabled || !context.query.tagType) {
return {
notFound: true,
};
}
return opSuperchain(context);
};
export const pools: GetServerSideProps<Props> = async(context) => {
if (!config.features.pools.isEnabled) {
return {
notFound: true,
};
}
return base(context);
};
+299
View File
@@ -0,0 +1,299 @@
import type { GetServerSidePropsContext, GetServerSidePropsResult } from 'next';
import type { RollupType } from 'types/client/rollup';
import type { Route } from 'nextjs-routes';
import type { Props } from 'nextjs/getServerSideProps/handlers';
import config from 'configs/app';
import isNeedProxy from 'lib/api/isNeedProxy';
export type Guard = (chainConfig: typeof config) => <Pathname extends Route['pathname'] = never>(context: GetServerSidePropsContext) =>
Promise<GetServerSidePropsResult<Props<Pathname>> | undefined>;
export const account: Guard = (chainConfig: typeof config) => async() => {
if (!chainConfig.features.account.isEnabled) {
return {
notFound: true,
};
}
};
export const verifiedAddresses: Guard = (chainConfig: typeof config) => async() => {
if (!chainConfig.features.addressVerification.isEnabled) {
return {
notFound: true,
};
}
};
export const userOps: Guard = (chainConfig: typeof config) => async() => {
if (!chainConfig.features.userOps.isEnabled) {
return {
notFound: true,
};
}
};
export const marketplace: Guard = (chainConfig: typeof config) => async() => {
if (!chainConfig.features.marketplace.isEnabled) {
return {
notFound: true,
};
}
};
export const apiDocs: Guard = (chainConfig: typeof config) => async() => {
if (!chainConfig.features.apiDocs.isEnabled) {
return {
notFound: true,
};
}
};
export const csvExport: Guard = (chainConfig: typeof config) => async() => {
if (!chainConfig.features.csvExport.isEnabled) {
return {
notFound: true,
};
}
};
export const stats: Guard = (chainConfig: typeof config) => async() => {
if (!chainConfig.features.stats.isEnabled) {
return {
notFound: true,
};
}
};
export const suave: Guard = (chainConfig: typeof config) => async() => {
if (!chainConfig.features.suave.isEnabled) {
return {
notFound: true,
};
}
};
export const nameService: Guard = (chainConfig: typeof config) => async() => {
if (!chainConfig.features.nameService.isEnabled) {
return {
notFound: true,
};
}
};
export const accounts: Guard = (chainConfig: typeof config) => async() => {
if (chainConfig.UI.views.address.hiddenViews?.top_accounts) {
return {
notFound: true,
};
}
};
export const accountsLabelSearch: Guard = (chainConfig: typeof config) => async(context) => {
if (!chainConfig.features.addressMetadata.isEnabled || !context.query.tagType) {
return {
notFound: true,
};
}
};
export const validators: Guard = (chainConfig: typeof config) => async() => {
if (!chainConfig.features.validators.isEnabled) {
return {
notFound: true,
};
}
};
export const validatorDetails: Guard = (chainConfig: typeof config) => async() => {
const feature = chainConfig.features.validators;
if (!feature.isEnabled || feature.chainType !== 'zilliqa') {
return {
notFound: true,
};
}
};
export const gasTracker: Guard = (chainConfig: typeof config) => async() => {
if (!chainConfig.features.gasTracker.isEnabled) {
return {
notFound: true,
};
}
};
export const advancedFilter: Guard = (chainConfig: typeof config) => async() => {
if (!chainConfig.features.advancedFilter.isEnabled) {
return {
notFound: true,
};
}
};
export const dataAvailability: Guard = (chainConfig: typeof config) => async() => {
if (!chainConfig.features.dataAvailability.isEnabled) {
return {
notFound: true,
};
}
};
export const login: Guard = () => async() => {
if (!isNeedProxy()) {
return {
notFound: true,
};
}
};
export const dev: Guard = (chainConfig: typeof config) => async() => {
if (!chainConfig.app.isDev) {
return {
notFound: true,
};
}
};
export const publicTagsSubmit: Guard = (chainConfig: typeof config) => async() => {
if (!chainConfig.features.publicTagsSubmission.isEnabled) {
return {
notFound: true,
};
}
};
export const pools: Guard = (chainConfig: typeof config) => async() => {
if (!chainConfig.features.pools.isEnabled) {
return {
notFound: true,
};
}
};
// ROLLUPS
export const rollup: Guard = (chainConfig: typeof config) => async() => {
if (!chainConfig.features.rollup.isEnabled) {
return {
notFound: true,
};
}
};
const DEPOSITS_ROLLUP_TYPES: Array<RollupType> = [ 'optimistic', 'shibarium', 'zkEvm', 'arbitrum', 'scroll' ];
export const deposits: Guard = (chainConfig: typeof config) => async() => {
const rollupFeature = chainConfig.features.rollup;
if (!(rollupFeature.isEnabled && DEPOSITS_ROLLUP_TYPES.includes(rollupFeature.type))) {
return {
notFound: true,
};
}
};
const WITHDRAWALS_ROLLUP_TYPES: Array<RollupType> = [ 'optimistic', 'shibarium', 'zkEvm', 'arbitrum', 'scroll' ];
export const withdrawals: Guard = (chainConfig: typeof config) => async() => {
const rollupFeature = chainConfig.features.rollup;
if (
!chainConfig.features.beaconChain.isEnabled &&
!(rollupFeature.isEnabled && WITHDRAWALS_ROLLUP_TYPES.includes(rollupFeature.type))
) {
return {
notFound: true,
};
}
};
const BATCH_ROLLUP_TYPES: Array<RollupType> = [ 'zkEvm', 'zkSync', 'arbitrum', 'optimistic', 'scroll' ];
export const batch: Guard = (chainConfig: typeof config) => async() => {
const rollupFeature = chainConfig.features.rollup;
if (!(rollupFeature.isEnabled && BATCH_ROLLUP_TYPES.includes(rollupFeature.type))) {
return {
notFound: true,
};
}
};
export const batchCelestia: Guard = (chainConfig: typeof config) => async() => {
const rollupFeature = chainConfig.features.rollup;
if (!(rollupFeature.isEnabled && (rollupFeature.type === 'arbitrum' || rollupFeature.type === 'optimistic'))) {
return {
notFound: true,
};
}
};
export const txnWithdrawals: Guard = (chainConfig: typeof config) => async() => {
const rollupFeature = chainConfig.features.rollup;
if (!(rollupFeature.isEnabled && rollupFeature.type === 'arbitrum')) {
return {
notFound: true,
};
}
};
export const outputRoots: Guard = (chainConfig: typeof config) => async() => {
const rollupFeature = chainConfig.features.rollup;
if (!(rollupFeature.isEnabled && rollupFeature.outputRootsEnabled)) {
return {
notFound: true,
};
}
};
export const disputeGames: Guard = (chainConfig: typeof config) => async() => {
if (!chainConfig.features.faultProofSystem.isEnabled) {
return {
notFound: true,
};
}
};
export const mud: Guard = (chainConfig: typeof config) => async() => {
if (!chainConfig.features.mudFramework.isEnabled) {
return {
notFound: true,
};
}
};
export const tac: Guard = (chainConfig: typeof config) => async() => {
if (!chainConfig.features.tac.isEnabled) {
return {
notFound: true,
};
}
};
export const celo: Guard = (chainConfig: typeof config) => async() => {
if (!chainConfig.features.celo.isEnabled) {
return {
notFound: true,
};
}
};
export const interopMessages: Guard = (chainConfig: typeof config) => async() => {
const rollupFeature = chainConfig.features.rollup;
if (!rollupFeature.isEnabled || !rollupFeature.interopEnabled) {
return {
notFound: true,
};
}
};
export const opSuperchain: Guard = () => async() => {
if (!config.features.opSuperchain.isEnabled) {
return {
notFound: true,
};
}
};
export const notOpSuperchain: Guard = () => async() => {
if (config.features.opSuperchain.isEnabled) {
return {
notFound: true,
};
}
};
+82
View File
@@ -0,0 +1,82 @@
import type { GetServerSidePropsContext, GetServerSidePropsResult } from 'next';
import type { AdBannerProviders } from 'types/client/adProviders';
import type { Route } from 'nextjs-routes';
import config from 'configs/app';
import * as cookies from 'lib/cookies';
import type * as metadata from 'lib/metadata';
import detectBotRequest from '../utils/detectBotRequest';
const adBannerFeature = config.features.adsBanner;
export interface Props<Pathname extends Route['pathname'] = never> {
query: Route['query'];
cookies: string;
referrer: string;
adBannerProvider: AdBannerProviders | null;
// if apiData is undefined, Next.js will complain that it is not serializable
// so we force it to be always present in the props but it can be null
apiData: metadata.ApiData<Pathname> | null;
uuid: string;
}
export const base = async <Pathname extends Route['pathname'] = never>({ req, res, query }: GetServerSidePropsContext):
Promise<GetServerSidePropsResult<Props<Pathname>>> => {
const adBannerProvider = (() => {
if (adBannerFeature.isEnabled) {
if ('additionalProvider' in adBannerFeature && adBannerFeature.additionalProvider) {
// we need to get a random ad provider on the server side to keep it consistent with the client side
const randomIndex = Math.round(Math.random());
return [ adBannerFeature.provider, adBannerFeature.additionalProvider ][randomIndex];
} else {
return adBannerFeature.provider;
}
}
return null;
})();
let uuid = cookies.getFromCookieString(req.headers.cookie || '', cookies.NAMES.UUID);
if (!uuid) {
uuid = crypto.randomUUID();
res.setHeader('Set-Cookie', `${ cookies.NAMES.UUID }=${ uuid }`);
}
const isTrackingDisabled = process.env.DISABLE_TRACKING === 'true';
const isBot = Boolean(detectBotRequest(req));
if (!isTrackingDisabled && !isBot) {
// log pageview
const hostname = req.headers.host;
const timestamp = new Date().toISOString();
const chainId = process.env.NEXT_PUBLIC_NETWORK_ID;
const chainName = process.env.NEXT_PUBLIC_NETWORK_NAME;
const publicRPC = process.env.NEXT_PUBLIC_NETWORK_RPC_URL;
fetch('https://monitor.blockscout.com/count', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
hostname,
timestamp,
chainId,
chainName,
publicRPC,
uuid,
}),
});
}
return {
props: {
query,
cookies: req.headers.cookie || '',
referrer: req.headers.referer || '',
adBannerProvider: adBannerProvider,
apiData: null,
uuid,
},
};
};
+40
View File
@@ -0,0 +1,40 @@
import * as guards from './guards';
import { factory } from './utils';
export const base = factory([ ]);
export const block = factory([ guards.notOpSuperchain ]);
export const account = factory([ guards.account ]);
export const verifiedAddresses = factory([ guards.account, guards.verifiedAddresses ]);
export const userOps = factory([ guards.userOps ]);
export const marketplace = factory([ guards.marketplace ]);
export const apiDocs = factory([ guards.apiDocs ]);
export const csvExport = factory([ guards.csvExport ]);
export const stats = factory([ guards.stats ]);
export const suave = factory([ guards.suave ]);
export const nameService = factory([ guards.nameService ]);
export const accounts = factory([ guards.accounts ]);
export const accountsLabelSearch = factory([ guards.accountsLabelSearch ]);
export const validators = factory([ guards.validators ]);
export const validatorDetails = factory([ guards.validatorDetails ]);
export const gasTracker = factory([ guards.gasTracker ]);
export const advancedFilter = factory([ guards.advancedFilter ]);
export const dataAvailability = factory([ guards.dataAvailability ]);
export const login = factory([ guards.login ]);
export const dev = factory([ guards.dev ]);
export const publicTagsSubmit = factory([ guards.publicTagsSubmit ]);
export const pools = factory([ guards.pools ]);
// ROLLUPS
export const rollup = factory([ guards.rollup ]);
export const deposits = factory([ guards.deposits ]);
export const withdrawals = factory([ guards.withdrawals ]);
export const batch = factory([ guards.batch ]);
export const batchCelestia = factory([ guards.batchCelestia ]);
export const txnWithdrawals = factory([ guards.txnWithdrawals ]);
export const outputRoots = factory([ guards.outputRoots ]);
export const disputeGames = factory([ guards.disputeGames ]);
export const mud = factory([ guards.mud ]);
export const tac = factory([ guards.tac ]);
export const celo = factory([ guards.celo ]);
export const interopMessages = factory([ guards.interopMessages ]);
export const opSuperchain = factory([ guards.opSuperchain ]);
+8
View File
@@ -0,0 +1,8 @@
import * as guards from './guards';
import { factoryMultichain } from './utils';
export const base = factoryMultichain([ guards.opSuperchain ]);
export const userOps = factoryMultichain([ guards.opSuperchain, guards.userOps ]);
export const accountsLabelSearch = factoryMultichain([ guards.opSuperchain, guards.accountsLabelSearch ]);
export const advancedFilter = factoryMultichain([ guards.opSuperchain, guards.advancedFilter ]);
export const csvExport = factoryMultichain([ guards.opSuperchain, guards.csvExport ]);
+49
View File
@@ -0,0 +1,49 @@
import type { GetServerSidePropsContext, GetServerSidePropsResult } from 'next';
import type { Route } from 'nextjs-routes';
import config from 'configs/app';
import multichainConfig from 'configs/multichain';
import type { Guard } from './guards';
import * as handlers from './handlers';
import type { Props } from './handlers';
export const factory = (guards: Array<Guard | Array<Guard>>, chainConfig = config) => {
return async<Pathname extends Route['pathname'] = never>(context: GetServerSidePropsContext): Promise<GetServerSidePropsResult<Props<Pathname>>> => {
for (const guard of guards) {
if (Array.isArray(guard)) {
// When a guard is an array, it works as OR conditional statement
// If any of the guards returns nothing, we continue to the next guard
// If all of the guards return something, we return the result of the first one that returns something
const results = await Promise.all(guard.map((subGuard) => subGuard(chainConfig)(context)));
if (results.some((result) => !result)) {
continue;
} else {
return results.find((result) => result) as GetServerSidePropsResult<Props<Pathname>>;
}
} else {
const result = await guard(chainConfig)(context);
if (result) {
return result;
}
}
}
return handlers.base<Pathname>(context);
};
};
export const factoryMultichain = (guards: Array<Guard>) => {
return async(context: GetServerSidePropsContext) => {
const chainSlug = context.params?.['chain-slug'];
const chain = multichainConfig()?.chains.find((chain) => chain.slug === chainSlug);
if (!chain?.config) {
return {
notFound: true,
};
}
return factory(guards, chain.config)(context);
};
};
+7 -1
View File
@@ -39,8 +39,14 @@ declare module "nextjs-routes" {
| StaticRoute<"/block/countdown">
| StaticRoute<"/blocks">
| DynamicRoute<"/chain/[chain-slug]/accounts/label/[slug]", { "chain-slug": string; "slug": string }>
| DynamicRoute<"/chain/[chain-slug]/address/[hash]", { "chain-slug": string; "hash": string }>
| DynamicRoute<"/chain/[chain-slug]/advanced-filter", { "chain-slug": string }>
| DynamicRoute<"/chain/[chain-slug]/block/[height_or_hash]", { "chain-slug": string; "height_or_hash": string }>
| DynamicRoute<"/chain/[chain-slug]/block/countdown/[height]", { "chain-slug": string; "height": string }>
| DynamicRoute<"/chain/[chain-slug]/block/countdown", { "chain-slug": string }>
| DynamicRoute<"/chain/[chain-slug]/csv-export", { "chain-slug": string }>
| DynamicRoute<"/chain/[chain-slug]/op/[hash]", { "chain-slug": string; "hash": string }>
| DynamicRoute<"/chain/[chain-slug]/token/[hash]", { "chain-slug": string; "hash": string }>
| DynamicRoute<"/chain/[chain-slug]/token/[hash]/instance/[id]", { "chain-slug": string; "hash": string; "id": string }>
| DynamicRoute<"/chain/[chain-slug]/tx/[hash]", { "chain-slug": string; "hash": string }>
| StaticRoute<"/chakra">
| StaticRoute<"/contract-verification">
+1 -1
View File
@@ -43,7 +43,7 @@
},
"dependencies": {
"@blockscout/bens-types": "1.4.1",
"@blockscout/multichain-aggregator-types": "1.6.0-alpha.0",
"@blockscout/multichain-aggregator-types": "1.6.0-alpha.2",
"@blockscout/points-types": "1.3.0-alpha.2",
"@blockscout/stats-types": "^2.9.0",
"@blockscout/tac-operation-lifecycle-types": "0.0.1-alpha.6",
+1 -1
View File
@@ -1,7 +1,7 @@
import NextErrorComponent from 'next/error';
import React from 'react';
import type { Props as ServerSidePropsCommon } from 'nextjs/getServerSideProps';
import type { Props as ServerSidePropsCommon } from 'nextjs/getServerSideProps/handlers';
import * as cookies from 'lib/cookies';
+1 -1
View File
@@ -16,4 +16,4 @@ const Page: NextPage = () => {
export default Page;
export { account as getServerSideProps } from 'nextjs/getServerSideProps';
export { account as getServerSideProps } from 'nextjs/getServerSideProps/main';
+1 -1
View File
@@ -16,4 +16,4 @@ const Page: NextPage = () => {
export default Page;
export { account as getServerSideProps } from 'nextjs/getServerSideProps';
export { account as getServerSideProps } from 'nextjs/getServerSideProps/main';
+1 -1
View File
@@ -16,4 +16,4 @@ const Page: NextPage = () => {
export default Page;
export { account as getServerSideProps } from 'nextjs/getServerSideProps';
export { account as getServerSideProps } from 'nextjs/getServerSideProps/main';
+1 -1
View File
@@ -16,4 +16,4 @@ const Page: NextPage = () => {
export default Page;
export { account as getServerSideProps } from 'nextjs/getServerSideProps';
export { account as getServerSideProps } from 'nextjs/getServerSideProps/main';
+1 -1
View File
@@ -16,4 +16,4 @@ const Page: NextPage = () => {
export default Page;
export { verifiedAddresses as getServerSideProps } from 'nextjs/getServerSideProps';
export { verifiedAddresses as getServerSideProps } from 'nextjs/getServerSideProps/main';
+1 -1
View File
@@ -16,4 +16,4 @@ const Page: NextPage = () => {
export default Page;
export { account as getServerSideProps } from 'nextjs/getServerSideProps';
export { account as getServerSideProps } from 'nextjs/getServerSideProps/main';
+10 -2
View File
@@ -4,7 +4,15 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs';
const Accounts = dynamic(() => import('ui/pages/Accounts'), { ssr: false });
import config from 'configs/app';
const Accounts = dynamic(() => {
if (config.features.opSuperchain.isEnabled) {
return import('ui/optimismSuperchain/accounts/OpSuperchainAccounts');
}
return import('ui/pages/Accounts');
}, { ssr: false });
const Page: NextPage = () => {
return (
@@ -16,4 +24,4 @@ const Page: NextPage = () => {
export default Page;
export { accounts as getServerSideProps } from 'nextjs/getServerSideProps';
export { accounts as getServerSideProps } from 'nextjs/getServerSideProps/main';
+1 -1
View File
@@ -16,4 +16,4 @@ const Page: NextPage = () => {
export default Page;
export { accountsLabelSearch as getServerSideProps } from 'nextjs/getServerSideProps';
export { accountsLabelSearch as getServerSideProps } from 'nextjs/getServerSideProps/main';
@@ -1,7 +1,7 @@
import type { NextPage } from 'next';
import React from 'react';
import type { Props } from 'nextjs/getServerSideProps';
import type { Props } from 'nextjs/getServerSideProps/handlers';
import PageNextJs from 'nextjs/PageNextJs';
import ContractVerificationForAddress from 'ui/pages/ContractVerificationForAddress';
@@ -16,4 +16,4 @@ const Page: NextPage<Props> = (props: Props) => {
export default Page;
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
export { base as getServerSideProps } from 'nextjs/getServerSideProps/main';
+4 -4
View File
@@ -2,15 +2,15 @@ import type { GetServerSideProps, NextPage } from 'next';
import React from 'react';
import type { Route } from 'nextjs-routes';
import type { Props } from 'nextjs/getServerSideProps';
import * as gSSP from 'nextjs/getServerSideProps';
import type { Props } from 'nextjs/getServerSideProps/handlers';
import * as gSSP from 'nextjs/getServerSideProps/main';
import PageNextJs from 'nextjs/PageNextJs';
import detectBotRequest from 'nextjs/utils/detectBotRequest';
import fetchApi from 'nextjs/utils/fetchApi';
import config from 'configs/app';
import getQueryParamString from 'lib/router/getQueryParamString';
import AddressOpSuperchain from 'ui/optimismSuperchain/address/AddressOpSuperchain';
import OpSuperchainAddress from 'ui/optimismSuperchain/address/OpSuperchainAddress';
import Address from 'ui/pages/Address';
const pathname: Route['pathname'] = '/address/[hash]';
@@ -18,7 +18,7 @@ const pathname: Route['pathname'] = '/address/[hash]';
const Page: NextPage<Props<typeof pathname>> = (props: Props<typeof pathname>) => {
return (
<PageNextJs pathname={ pathname } query={ props.query } apiData={ props.apiData }>
{ config.features.opSuperchain.isEnabled ? <AddressOpSuperchain/> : <Address/> }
{ config.features.opSuperchain.isEnabled ? <OpSuperchainAddress/> : <Address/> }
</PageNextJs>
);
};
+1 -1
View File
@@ -15,4 +15,4 @@ const Page: NextPage = () => {
export default Page;
export { advancedFilter as getServerSideProps } from 'nextjs/getServerSideProps';
export { advancedFilter as getServerSideProps } from 'nextjs/getServerSideProps/main';
+1 -1
View File
@@ -16,4 +16,4 @@ const Page: NextPage = () => {
export default Page;
export { apiDocs as getServerSideProps } from 'nextjs/getServerSideProps';
export { apiDocs as getServerSideProps } from 'nextjs/getServerSideProps/main';
+2 -2
View File
@@ -7,8 +7,8 @@ import type { NextPageWithLayout } from 'nextjs/types';
import type { MarketplaceApp } from 'types/client/marketplace';
import type { Route } from 'nextjs-routes';
import * as gSSP from 'nextjs/getServerSideProps';
import type { Props } from 'nextjs/getServerSideProps';
import type { Props } from 'nextjs/getServerSideProps/handlers';
import * as gSSP from 'nextjs/getServerSideProps/main';
import PageNextJs from 'nextjs/PageNextJs';
import detectBotRequest from 'nextjs/utils/detectBotRequest';
import fetchApi from 'nextjs/utils/fetchApi';
+1 -1
View File
@@ -14,4 +14,4 @@ const Page: NextPage = () => (
export default Page;
export { marketplace as getServerSideProps } from 'nextjs/getServerSideProps';
export { marketplace as getServerSideProps } from 'nextjs/getServerSideProps/main';
+1 -1
View File
@@ -15,4 +15,4 @@ const Page: NextPage = () => {
export default Page;
export { account as getServerSideProps } from 'nextjs/getServerSideProps';
export { account as getServerSideProps } from 'nextjs/getServerSideProps/main';
+2 -2
View File
@@ -2,7 +2,7 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import type { Props } from 'nextjs/getServerSideProps';
import type { Props } from 'nextjs/getServerSideProps/handlers';
import PageNextJs from 'nextjs/PageNextJs';
import config from 'configs/app';
@@ -39,4 +39,4 @@ const Page: NextPage<Props> = (props: Props) => {
export default Page;
export { batch as getServerSideProps } from 'nextjs/getServerSideProps';
export { batch as getServerSideProps } from 'nextjs/getServerSideProps/main';
@@ -2,7 +2,7 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import type { Props } from 'nextjs/getServerSideProps';
import type { Props } from 'nextjs/getServerSideProps/handlers';
import PageNextJs from 'nextjs/PageNextJs';
import config from 'configs/app';
@@ -33,4 +33,4 @@ const Page: NextPage<Props> = (props: Props) => {
export default Page;
export { batchCelestia as getServerSideProps } from 'nextjs/getServerSideProps';
export { batchCelestia as getServerSideProps } from 'nextjs/getServerSideProps/main';
+1 -1
View File
@@ -37,4 +37,4 @@ const Page: NextPage = () => {
export default Page;
export { rollup as getServerSideProps } from 'nextjs/getServerSideProps';
export { rollup as getServerSideProps } from 'nextjs/getServerSideProps/main';
+2 -2
View File
@@ -2,7 +2,7 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import type { Props } from 'nextjs/getServerSideProps';
import type { Props } from 'nextjs/getServerSideProps/handlers';
import PageNextJs from 'nextjs/PageNextJs';
const Blob = dynamic(() => import('ui/pages/Blob'), { ssr: false });
@@ -17,4 +17,4 @@ const Page: NextPage<Props> = (props: Props) => {
export default Page;
export { dataAvailability as getServerSideProps } from 'nextjs/getServerSideProps';
export { dataAvailability as getServerSideProps } from 'nextjs/getServerSideProps/main';
+2 -2
View File
@@ -2,7 +2,7 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import type { Props } from 'nextjs/getServerSideProps';
import type { Props } from 'nextjs/getServerSideProps/handlers';
import PageNextJs from 'nextjs/PageNextJs';
const Block = dynamic(() => import('ui/pages/Block'), { ssr: false });
@@ -17,4 +17,4 @@ const Page: NextPage<Props> = (props: Props) => {
export default Page;
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
export { block as getServerSideProps } from 'nextjs/getServerSideProps/main';
+2 -2
View File
@@ -2,7 +2,7 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import type { Props } from 'nextjs/getServerSideProps';
import type { Props } from 'nextjs/getServerSideProps/handlers';
import PageNextJs from 'nextjs/PageNextJs';
const BlockCountdown = dynamic(() => import('ui/pages/BlockCountdown'), { ssr: false });
@@ -17,4 +17,4 @@ const Page: NextPage<Props> = (props: Props) => {
export default Page;
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
export { block as getServerSideProps } from 'nextjs/getServerSideProps/main';
+2 -2
View File
@@ -2,7 +2,7 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import type { Props } from 'nextjs/getServerSideProps';
import type { Props } from 'nextjs/getServerSideProps/handlers';
import PageNextJs from 'nextjs/PageNextJs';
const BlockCountdownIndex = dynamic(() => import('ui/pages/BlockCountdownIndex'), { ssr: false });
@@ -17,4 +17,4 @@ const Page: NextPage<Props> = (props: Props) => {
export default Page;
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
export { block as getServerSideProps } from 'nextjs/getServerSideProps/main';
+10 -2
View File
@@ -4,7 +4,15 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs';
const Blocks = dynamic(() => import('ui/pages/Blocks'), { ssr: false });
import config from 'configs/app';
const Blocks = dynamic(() => {
if (config.features.opSuperchain.isEnabled) {
return import('ui/optimismSuperchain/blocks/OpSuperchainBlocks');
}
return import('ui/pages/Blocks');
}, { ssr: false });
const Page: NextPage = () => {
return (
@@ -16,4 +24,4 @@ const Page: NextPage = () => {
export default Page;
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
export { base as getServerSideProps } from 'nextjs/getServerSideProps/main';
@@ -11,7 +11,7 @@ const AccountsLabelSearch = dynamic(() => import('ui/pages/AccountsLabelSearch')
const Page: NextPage = () => {
return (
<PageNextJs pathname="/chain/[chain-slug]/accounts/label/[slug]">
<MultichainProvider>
<MultichainProvider level="page">
<AccountsLabelSearch/>
</MultichainProvider>
</PageNextJs>
@@ -20,4 +20,4 @@ const Page: NextPage = () => {
export default Page;
export { opSuperchainAccountsLabelSearch as getServerSideProps } from 'nextjs/getServerSideProps';
export { accountsLabelSearch as getServerSideProps } from 'nextjs/getServerSideProps/multichain';
@@ -0,0 +1,21 @@
import type { NextPage } from 'next';
import React from 'react';
import PageNextJs from 'nextjs/PageNextJs';
import { MultichainProvider } from 'lib/contexts/multichain';
import AdvancedFilter from 'ui/pages/AdvancedFilter';
const Page: NextPage = () => {
return (
<PageNextJs pathname="/chain/[chain-slug]/advanced-filter">
<MultichainProvider level="page">
<AdvancedFilter/>
</MultichainProvider>
</PageNextJs>
);
};
export default Page;
export { advancedFilter as getServerSideProps } from 'nextjs/getServerSideProps/multichain';
@@ -2,7 +2,7 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import type { Props } from 'nextjs/getServerSideProps';
import type { Props } from 'nextjs/getServerSideProps/handlers';
import PageNextJs from 'nextjs/PageNextJs';
import { MultichainProvider } from 'lib/contexts/multichain';
@@ -12,7 +12,7 @@ const Block = dynamic(() => import('ui/pages/Block'), { ssr: false });
const Page: NextPage<Props> = (props: Props) => {
return (
<PageNextJs pathname="/chain/[chain-slug]/block/[height_or_hash]" query={ props.query }>
<MultichainProvider>
<MultichainProvider level="page">
<Block/>
</MultichainProvider>
</PageNextJs>
@@ -21,4 +21,4 @@ const Page: NextPage<Props> = (props: Props) => {
export default Page;
export { opSuperchain as getServerSideProps } from 'nextjs/getServerSideProps';
export { base as getServerSideProps } from 'nextjs/getServerSideProps/multichain';
@@ -0,0 +1,24 @@
import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import type { Props } from 'nextjs/getServerSideProps/handlers';
import PageNextJs from 'nextjs/PageNextJs';
import { MultichainProvider } from 'lib/contexts/multichain';
const BlockCountdown = dynamic(() => import('ui/pages/BlockCountdown'), { ssr: false });
const Page: NextPage<Props> = (props: Props) => {
return (
<PageNextJs pathname="/chain/[chain-slug]/block/countdown/[height]" query={ props.query }>
<MultichainProvider level="page">
<BlockCountdown/>
</MultichainProvider>
</PageNextJs>
);
};
export default Page;
export { base as getServerSideProps } from 'nextjs/getServerSideProps/multichain';
@@ -0,0 +1,24 @@
import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import type { Props } from 'nextjs/getServerSideProps/handlers';
import PageNextJs from 'nextjs/PageNextJs';
import { MultichainProvider } from 'lib/contexts/multichain';
const BlockCountdownIndex = dynamic(() => import('ui/pages/BlockCountdownIndex'), { ssr: false });
const Page: NextPage<Props> = (props: Props) => {
return (
<PageNextJs pathname="/chain/[chain-slug]/block/countdown" query={ props.query }>
<MultichainProvider level="page">
<BlockCountdownIndex/>
</MultichainProvider>
</PageNextJs>
);
};
export default Page;
export { base as getServerSideProps } from 'nextjs/getServerSideProps/multichain';
@@ -0,0 +1,21 @@
import type { NextPage } from 'next';
import React from 'react';
import PageNextJs from 'nextjs/PageNextJs';
import { MultichainProvider } from 'lib/contexts/multichain';
import CsvExport from 'ui/pages/CsvExport';
const Page: NextPage = () => {
return (
<PageNextJs pathname="/chain/[chain-slug]/csv-export">
<MultichainProvider level="page">
<CsvExport/>
</MultichainProvider>
</PageNextJs>
);
};
export default Page;
export { csvExport as getServerSideProps } from 'nextjs/getServerSideProps/multichain';
+24
View File
@@ -0,0 +1,24 @@
import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import type { Props } from 'nextjs/getServerSideProps/handlers';
import PageNextJs from 'nextjs/PageNextJs';
import { MultichainProvider } from 'lib/contexts/multichain';
const UserOp = dynamic(() => import('ui/pages/UserOp'), { ssr: false });
const Page: NextPage<Props> = (props: Props) => {
return (
<PageNextJs pathname="/chain/[chain-slug]/op/[hash]" query={ props.query }>
<MultichainProvider level="page">
<UserOp/>
</MultichainProvider>
</PageNextJs>
);
};
export default Page;
export { userOps as getServerSideProps } from 'nextjs/getServerSideProps/multichain';
@@ -2,16 +2,16 @@ import type { NextPage } from 'next';
import React from 'react';
import type { Route } from 'nextjs-routes';
import type { Props } from 'nextjs/getServerSideProps';
import type { Props } from 'nextjs/getServerSideProps/handlers';
import PageNextJs from 'nextjs/PageNextJs';
import multichainConfig from 'configs/multichain';
import getSocketUrl from 'lib/api/getSocketUrl';
import { MultichainProvider } from 'lib/contexts/multichain';
import { SocketProvider } from 'lib/socket/context';
import Address from 'ui/pages/Address';
import Token from 'ui/pages/Token';
const pathname: Route['pathname'] = '/chain/[chain-slug]/address/[hash]';
const pathname: Route['pathname'] = '/token/[hash]';
const Page: NextPage<Props<typeof pathname>> = (props: Props<typeof pathname>) => {
const chainSlug = props.query?.['chain-slug'];
@@ -20,8 +20,8 @@ const Page: NextPage<Props<typeof pathname>> = (props: Props<typeof pathname>) =
return (
<PageNextJs pathname={ pathname } query={ props.query } apiData={ props.apiData }>
<SocketProvider url={ getSocketUrl(chainData?.config) }>
<MultichainProvider>
<Address/>
<MultichainProvider level="page">
<Token/>
</MultichainProvider>
</SocketProvider>
</PageNextJs>
@@ -30,4 +30,4 @@ const Page: NextPage<Props<typeof pathname>> = (props: Props<typeof pathname>) =
export default Page;
export { opSuperchain as getServerSideProps } from 'nextjs/getServerSideProps';
export { base as getServerSideProps } from 'nextjs/getServerSideProps/multichain';
@@ -0,0 +1,33 @@
import type { NextPage } from 'next';
import React from 'react';
import type { Route } from 'nextjs-routes';
import type { Props } from 'nextjs/getServerSideProps/handlers';
import PageNextJs from 'nextjs/PageNextJs';
import multichainConfig from 'configs/multichain';
import getSocketUrl from 'lib/api/getSocketUrl';
import { MultichainProvider } from 'lib/contexts/multichain';
import { SocketProvider } from 'lib/socket/context';
import TokenInstance from 'ui/pages/TokenInstance';
const pathname: Route['pathname'] = '/token/[hash]/instance/[id]';
const Page: NextPage<Props<typeof pathname>> = (props: Props<typeof pathname>) => {
const chainSlug = props.query?.['chain-slug'];
const chainData = multichainConfig()?.chains.find(chain => chain.slug === chainSlug);
return (
<PageNextJs pathname={ pathname } query={ props.query } apiData={ props.apiData }>
<SocketProvider url={ getSocketUrl(chainData?.config) }>
<MultichainProvider level="page">
<TokenInstance/>
</MultichainProvider>
</SocketProvider>
</PageNextJs>
);
};
export default Page;
export { base as getServerSideProps } from 'nextjs/getServerSideProps/multichain';
+3 -3
View File
@@ -2,7 +2,7 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import type { Props } from 'nextjs/getServerSideProps';
import type { Props } from 'nextjs/getServerSideProps/handlers';
import PageNextJs from 'nextjs/PageNextJs';
import { MultichainProvider } from 'lib/contexts/multichain';
@@ -12,7 +12,7 @@ const Transaction = dynamic(() => import('ui/pages/Transaction'), { ssr: false }
const Page: NextPage<Props> = (props: Props) => {
return (
<PageNextJs pathname="/chain/[chain-slug]/tx/[hash]" query={ props.query }>
<MultichainProvider>
<MultichainProvider level="page">
<Transaction/>
</MultichainProvider>
</PageNextJs>
@@ -21,4 +21,4 @@ const Page: NextPage<Props> = (props: Props) => {
export default Page;
export { opSuperchain as getServerSideProps } from 'nextjs/getServerSideProps';
export { base as getServerSideProps } from 'nextjs/getServerSideProps/multichain';
+1 -1
View File
@@ -15,4 +15,4 @@ const Page: NextPage = () => {
export default Page;
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
export { base as getServerSideProps } from 'nextjs/getServerSideProps/main';
+2 -2
View File
@@ -1,7 +1,7 @@
import type { NextPage } from 'next';
import React from 'react';
import type { Props } from 'nextjs/getServerSideProps';
import type { Props } from 'nextjs/getServerSideProps/handlers';
import PageNextJs from 'nextjs/PageNextJs';
import ContractVerification from 'ui/pages/ContractVerification';
@@ -16,4 +16,4 @@ const Page: NextPage<Props> = (props: Props) => {
export default Page;
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
export { base as getServerSideProps } from 'nextjs/getServerSideProps/main';
+1 -1
View File
@@ -15,4 +15,4 @@ const Page: NextPage = () => {
export default Page;
export { csvExport as getServerSideProps } from 'nextjs/getServerSideProps';
export { csvExport as getServerSideProps } from 'nextjs/getServerSideProps/main';
+1 -1
View File
@@ -41,4 +41,4 @@ const Page: NextPage = () => {
export default Page;
export { deposits as getServerSideProps } from 'nextjs/getServerSideProps';
export { deposits as getServerSideProps } from 'nextjs/getServerSideProps/main';
+1 -1
View File
@@ -16,4 +16,4 @@ const Page: NextPage = () => {
export default Page;
export { disputeGames as getServerSideProps } from 'nextjs/getServerSideProps';
export { disputeGames as getServerSideProps } from 'nextjs/getServerSideProps/main';
+2 -2
View File
@@ -2,7 +2,7 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import type { Props } from 'nextjs/getServerSideProps';
import type { Props } from 'nextjs/getServerSideProps/handlers';
import PageNextJs from 'nextjs/PageNextJs';
const Epoch = dynamic(() => import('ui/pages/Epoch'), { ssr: false });
@@ -17,4 +17,4 @@ const Page: NextPage<Props> = (props: Props) => {
export default Page;
export { celo as getServerSideProps } from 'nextjs/getServerSideProps';
export { celo as getServerSideProps } from 'nextjs/getServerSideProps/main';
+1 -1
View File
@@ -16,4 +16,4 @@ const Page: NextPage = () => {
export default Page;
export { celo as getServerSideProps } from 'nextjs/getServerSideProps';
export { celo as getServerSideProps } from 'nextjs/getServerSideProps/main';
+1 -1
View File
@@ -16,4 +16,4 @@ const Page: NextPage = () => {
export default Page;
export { gasTracker as getServerSideProps } from 'nextjs/getServerSideProps';
export { gasTracker as getServerSideProps } from 'nextjs/getServerSideProps/main';
+3 -3
View File
@@ -5,14 +5,14 @@ import type { NextPageWithLayout } from 'nextjs/types';
import PageNextJs from 'nextjs/PageNextJs';
import config from 'configs/app';
import HomeOpSuperchain from 'ui/optimismSuperchain/home/HomeOpSuperchain';
import OpSuperchainHome from 'ui/optimismSuperchain/home/OpSuperchainHome';
import Home from 'ui/pages/Home';
import LayoutHome from 'ui/shared/layout/LayoutHome';
const Page: NextPageWithLayout = () => {
return (
<PageNextJs pathname="/">
{ config.features.opSuperchain.isEnabled ? <HomeOpSuperchain/> : <Home/> }
{ config.features.opSuperchain.isEnabled ? <OpSuperchainHome/> : <Home/> }
</PageNextJs>
);
};
@@ -27,4 +27,4 @@ Page.getLayout = function getLayout(page: React.ReactElement) {
export default Page;
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
export { base as getServerSideProps } from 'nextjs/getServerSideProps/main';
+10 -2
View File
@@ -4,7 +4,15 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs';
const InternalTxs = dynamic(() => import('ui/pages/InternalTxs'), { ssr: false });
import config from 'configs/app';
const InternalTxs = dynamic(() => {
if (config.features.opSuperchain.isEnabled) {
return import('ui/optimismSuperchain/internalTxs/OpSuperchainInternalTxs');
}
return import('ui/pages/InternalTxs');
}, { ssr: false });
const Page: NextPage = () => {
return (
@@ -16,4 +24,4 @@ const Page: NextPage = () => {
export default Page;
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
export { base as getServerSideProps } from 'nextjs/getServerSideProps/main';
+1 -1
View File
@@ -16,4 +16,4 @@ const Page: NextPage = () => {
export default Page;
export { interopMessages as getServerSideProps } from 'nextjs/getServerSideProps';
export { interopMessages as getServerSideProps } from 'nextjs/getServerSideProps/main';
+1 -1
View File
@@ -15,4 +15,4 @@ const Page: NextPage = () => {
export default Page;
export { login as getServerSideProps } from 'nextjs/getServerSideProps';
export { login as getServerSideProps } from 'nextjs/getServerSideProps/main';
+1 -1
View File
@@ -16,4 +16,4 @@ const Page: NextPage = () => {
export default Page;
export { mud as getServerSideProps } from 'nextjs/getServerSideProps';
export { mud as getServerSideProps } from 'nextjs/getServerSideProps/main';
+2 -2
View File
@@ -2,7 +2,7 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import type { Props } from 'nextjs/getServerSideProps';
import type { Props } from 'nextjs/getServerSideProps/handlers';
import PageNextJs from 'nextjs/PageNextJs';
const NameDomain = dynamic(() => import('ui/pages/NameDomain'), { ssr: false });
@@ -17,4 +17,4 @@ const Page: NextPage<Props> = (props: Props) => {
export default Page;
export { nameService as getServerSideProps } from 'nextjs/getServerSideProps';
export { nameService as getServerSideProps } from 'nextjs/getServerSideProps/main';
+1 -1
View File
@@ -16,4 +16,4 @@ const Page: NextPage = () => {
export default Page;
export { nameService as getServerSideProps } from 'nextjs/getServerSideProps';
export { nameService as getServerSideProps } from 'nextjs/getServerSideProps/main';
+2 -2
View File
@@ -2,7 +2,7 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import type { Props } from 'nextjs/getServerSideProps';
import type { Props } from 'nextjs/getServerSideProps/handlers';
import PageNextJs from 'nextjs/PageNextJs';
const UserOp = dynamic(() => import('ui/pages/UserOp'), { ssr: false });
@@ -17,4 +17,4 @@ const Page: NextPage<Props> = (props: Props) => {
export default Page;
export { userOps as getServerSideProps } from 'nextjs/getServerSideProps';
export { userOps as getServerSideProps } from 'nextjs/getServerSideProps/main';
+2 -2
View File
@@ -2,7 +2,7 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import type { Props } from 'nextjs/getServerSideProps';
import type { Props } from 'nextjs/getServerSideProps/handlers';
import PageNextJs from 'nextjs/PageNextJs';
const TacOperation = dynamic(() => import('ui/pages/TacOperation'), { ssr: false });
@@ -17,4 +17,4 @@ const Page: NextPage<Props> = (props: Props) => {
export default Page;
export { tac as getServerSideProps } from 'nextjs/getServerSideProps';
export { tac as getServerSideProps } from 'nextjs/getServerSideProps/main';
+1 -1
View File
@@ -16,4 +16,4 @@ const Page: NextPage = () => {
export default Page;
export { tac as getServerSideProps } from 'nextjs/getServerSideProps';
export { tac as getServerSideProps } from 'nextjs/getServerSideProps/main';
+10 -2
View File
@@ -4,7 +4,15 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs';
const UserOps = dynamic(() => import('ui/pages/UserOps'), { ssr: false });
import config from 'configs/app';
const UserOps = dynamic(() => {
if (config.features.opSuperchain.isEnabled) {
return import('ui/optimismSuperchain/userOps/OpSuperchainUserOps');
}
return import('ui/pages/UserOps');
}, { ssr: false });
const Page: NextPage = () => {
return (
@@ -16,4 +24,4 @@ const Page: NextPage = () => {
export default Page;
export { userOps as getServerSideProps } from 'nextjs/getServerSideProps';
export { userOps as getServerSideProps } from 'nextjs/getServerSideProps/main';
+1 -1
View File
@@ -16,4 +16,4 @@ const Page: NextPage = () => {
export default Page;
export { outputRoots as getServerSideProps } from 'nextjs/getServerSideProps';
export { outputRoots as getServerSideProps } from 'nextjs/getServerSideProps/main';
+2 -2
View File
@@ -2,7 +2,7 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import type { Props } from 'nextjs/getServerSideProps';
import type { Props } from 'nextjs/getServerSideProps/handlers';
import PageNextJs from 'nextjs/PageNextJs';
const Pool = dynamic(() => import('ui/pages/Pool'), { ssr: false });
@@ -17,4 +17,4 @@ const Page: NextPage<Props> = (props: Props) => {
export default Page;
export { pools as getServerSideProps } from 'nextjs/getServerSideProps';
export { pools as getServerSideProps } from 'nextjs/getServerSideProps/main';
+1 -1
View File
@@ -16,4 +16,4 @@ const Page: NextPage = () => {
export default Page;
export { pools as getServerSideProps } from 'nextjs/getServerSideProps';
export { pools as getServerSideProps } from 'nextjs/getServerSideProps/main';
+1 -1
View File
@@ -15,4 +15,4 @@ const Page: NextPage = () => {
export default Page;
export { publicTagsSubmit as getServerSideProps } from 'nextjs/getServerSideProps';
export { publicTagsSubmit as getServerSideProps } from 'nextjs/getServerSideProps/main';
+2 -2
View File
@@ -3,7 +3,7 @@ import React from 'react';
import type { NextPageWithLayout } from 'nextjs/types';
import type { Props } from 'nextjs/getServerSideProps';
import type { Props } from 'nextjs/getServerSideProps/handlers';
import PageNextJs from 'nextjs/PageNextJs';
import LayoutSearchResults from 'ui/shared/layout/LayoutSearchResults';
@@ -28,4 +28,4 @@ Page.getLayout = function getLayout(page: React.ReactElement) {
export default Page;
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
export { base as getServerSideProps } from 'nextjs/getServerSideProps/main';
+1 -1
View File
@@ -15,4 +15,4 @@ const Page: NextPage = () => {
export default Page;
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
export { base as getServerSideProps } from 'nextjs/getServerSideProps/main';
+2 -2
View File
@@ -3,8 +3,8 @@ import dynamic from 'next/dynamic';
import React from 'react';
import type { Route } from 'nextjs-routes';
import * as gSSP from 'nextjs/getServerSideProps';
import type { Props } from 'nextjs/getServerSideProps';
import type { Props } from 'nextjs/getServerSideProps/handlers';
import * as gSSP from 'nextjs/getServerSideProps/main';
import PageNextJs from 'nextjs/PageNextJs';
import detectBotRequest from 'nextjs/utils/detectBotRequest';
import fetchApi from 'nextjs/utils/fetchApi';
+1 -1
View File
@@ -15,4 +15,4 @@ const Page: NextPage = () => {
export default Page;
export { stats as getServerSideProps } from 'nextjs/getServerSideProps';
export { stats as getServerSideProps } from 'nextjs/getServerSideProps/main';

Some files were not shown because too many files have changed in this diff Show More