Commit 6eb3313d authored by Igor Stuev's avatar Igor Stuev Committed by GitHub

Merge pull request #255 from blockscout/other-explorers

links to other explorers
parents 2c6b0b0f 4b843492
...@@ -22,3 +22,4 @@ NEXT_PUBLIC_APP_HOST=APP_NEXT_NEXT_PUBLIC_APP_HOST ...@@ -22,3 +22,4 @@ NEXT_PUBLIC_APP_HOST=APP_NEXT_NEXT_PUBLIC_APP_HOST
NEXT_PUBLIC_APP_PORT=APP_NEXT_NEXT_PUBLIC_APP_PORT NEXT_PUBLIC_APP_PORT=APP_NEXT_NEXT_PUBLIC_APP_PORT
NEXT_PUBLIC_API_ENDPOINT=APP_NEXT_NEXT_PUBLIC_API_ENDPOINT NEXT_PUBLIC_API_ENDPOINT=APP_NEXT_NEXT_PUBLIC_API_ENDPOINT
NEXT_PUBLIC_API_BASE_PATH=APP_NEXT_NEXT_PUBLIC_API_BASE_PATH NEXT_PUBLIC_API_BASE_PATH=APP_NEXT_NEXT_PUBLIC_API_BASE_PATH
NEXT_PUBLIC_NETWORK_EXPLORERS=APP_NEXT_NEXT_PUBLIC_NETWORK_EXPLORERS
...@@ -66,6 +66,7 @@ The app instance could be customized by passing following variables to NodeJS en ...@@ -66,6 +66,7 @@ The app instance could be customized by passing following variables to NodeJS en
| NEXT_PUBLIC_FOOTER_TELEGRAM_LINK | `string` *(optional)* | Link to Telegram in the footer | `https://t.me/poa_network` | | NEXT_PUBLIC_FOOTER_TELEGRAM_LINK | `string` *(optional)* | Link to Telegram in the footer | `https://t.me/poa_network` |
| NEXT_PUBLIC_FOOTER_STAKING_LINK | `string` *(optional)* | Link to staking dashboard in the footer | `https://duneanalytics.com/maxaleks/xdai-staking` | | NEXT_PUBLIC_FOOTER_STAKING_LINK | `string` *(optional)* | Link to staking dashboard in the footer | `https://duneanalytics.com/maxaleks/xdai-staking` |
| NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM | `string` | Link to form where authors can submit their dapps to the marketplace | `https://airtable.com/shrqUAcjgGJ4jU88C` | | NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM | `string` | Link to form where authors can submit their dapps to the marketplace | `https://airtable.com/shrqUAcjgGJ4jU88C` |
| NEXT_PUBLIC_NETWORK_EXPLORERS | `Array<NetworkExplorer>` where `NetworkExplorer` can have following [properties](#network-explorer-configuration-properties) | Used to build up links to transactions, blocks, addresses in other chain explorers. | `[{'title':'Anyblock','baseUrl':'https://explorer.anyblock.tools','paths':{'tx':'/ethereum/poa/core/tx'}}]` |
### App configuration ### App configuration
...@@ -93,6 +94,16 @@ The app instance could be customized by passing following variables to NodeJS en ...@@ -93,6 +94,16 @@ The app instance could be customized by passing following variables to NodeJS en
| group | `mainnets \| testnets \| other` | Indicates in which tab network appears in the menu | `'mainnets'` | | group | `mainnets \| testnets \| other` | Indicates in which tab network appears in the menu | `'mainnets'` |
| icon | `string` *(optional)* | Network icon; if not provided, will fallback to icon predefined in the project; if the project doesn't have icon for such network then the common placeholder will be shown; *Note* that icon size should be 30px by 30px | `'https://www.fillmurray.com/60/60'` | | icon | `string` *(optional)* | Network icon; if not provided, will fallback to icon predefined in the project; if the project doesn't have icon for such network then the common placeholder will be shown; *Note* that icon size should be 30px by 30px | `'https://www.fillmurray.com/60/60'` |
### Network explorer configuration properties
| Property | Type | Description | Example value
| --- | --- | --- | --- |
| title | `string` | Displayed name of the explorer | `'Anyblock'` |
| baseUrl | `string` | Base url of the explorer | `'https://explorer.anyblock.tools'` |
| paths | `Record<'tx' \| 'block' \| 'address', string>` | Map of explorer entities and their paths | `'paths':{'tx':'/ethereum/poa/core/tx'}` |
*Note* The url of an entity will be constructed as `<baseUrl><paths[<entity-type>]><entity-id>`, e.g `https://explorer.anyblock.tools/ethereum/poa/core/tx/<tx-id>`
### External services configuration ### External services configuration
| Variable | Type | Description | Default value | Variable | Type | Description | Default value
......
...@@ -22,6 +22,7 @@ const config = Object.freeze({ ...@@ -22,6 +22,7 @@ const config = Object.freeze({
assetsPathname: process.env.NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME, assetsPathname: process.env.NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME,
nativeTokenAddress: process.env.NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS, nativeTokenAddress: process.env.NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS,
basePath: '/' + [ process.env.NEXT_PUBLIC_NETWORK_TYPE, process.env.NEXT_PUBLIC_NETWORK_SUBTYPE ].filter(Boolean).join('/'), basePath: '/' + [ process.env.NEXT_PUBLIC_NETWORK_TYPE, process.env.NEXT_PUBLIC_NETWORK_SUBTYPE ].filter(Boolean).join('/'),
explorers: process.env.NEXT_PUBLIC_NETWORK_EXPLORERS?.replaceAll('\'', '"'),
}, },
footerLinks: { footerLinks: {
github: process.env.NEXT_PUBLIC_FOOTER_GITHUB_LINK, github: process.env.NEXT_PUBLIC_FOOTER_GITHUB_LINK,
......
...@@ -4,7 +4,7 @@ NEXT_PUBLIC_APP_HOST=localhost ...@@ -4,7 +4,7 @@ NEXT_PUBLIC_APP_HOST=localhost
NEXT_PUBLIC_APP_PORT=3000 NEXT_PUBLIC_APP_PORT=3000
NEXT_PUBLIC_APP_INSTANCE=local NEXT_PUBLIC_APP_INSTANCE=local
# nav and footer config # ui config
NEXT_PUBLIC_BLOCKSCOUT_VERSION=v4.1.7-beta NEXT_PUBLIC_BLOCKSCOUT_VERSION=v4.1.7-beta
NEXT_PUBLIC_FOOTER_GITHUB_LINK=https://github.com/blockscout/blockscout NEXT_PUBLIC_FOOTER_GITHUB_LINK=https://github.com/blockscout/blockscout
NEXT_PUBLIC_FOOTER_TWITTER_LINK=https://www.twitter.com/blockscoutcom NEXT_PUBLIC_FOOTER_TWITTER_LINK=https://www.twitter.com/blockscoutcom
......
# nav and footer config # ui config
NEXT_PUBLIC_FOOTER_TELEGRAM_LINK=https://t.me/poa_network NEXT_PUBLIC_FOOTER_TELEGRAM_LINK=https://t.me/poa_network
NEXT_PUBLIC_FOOTER_STAKING_LINK=https://duneanalytics.com/maxaleks/xdai-staking NEXT_PUBLIC_FOOTER_STAKING_LINK=https://duneanalytics.com/maxaleks/xdai-staking
NEXT_PUBLIC_FEATURED_NETWORKS=[{'title':'Gnosis Chain','basePath':'/xdai/mainnet','group':'mainnets'},{'title':'Optimism on Gnosis Chain','basePath':'/xdai/optimism','group':'mainnets','icon':'https://www.fillmurray.com/60/60'},{'title':'Arbitrum on xDai','basePath':'/xdai/aox','group':'mainnets'},{'title':'Ethereum','basePath':'/eth/mainnet','group':'mainnets'},{'title':'Ethereum Classic','basePath':'/etx/mainnet','group':'mainnets'},{'title':'POA','basePath':'/poa/core','group':'mainnets'},{'title':'RSK','basePath':'/rsk/mainnet','group':'mainnets'},{'title':'Gnosis Chain Testnet','basePath':'/xdai/testnet','group':'testnets'},{'title':'POA Sokol','basePath':'/poa/sokol','group':'testnets'},{'title':'ARTIS Σ1','basePath':'/artis/sigma1','group':'other'},{'title':'LUKSO L14','basePath':'/lukso/l14','group':'other'},{'title':'Astar','basePath':'/astar','group':'other'}] NEXT_PUBLIC_FEATURED_NETWORKS=[{'title':'Gnosis Chain','basePath':'/xdai/mainnet','group':'mainnets'},{'title':'Optimism on Gnosis Chain','basePath':'/xdai/optimism','group':'mainnets','icon':'https://www.fillmurray.com/60/60'},{'title':'Arbitrum on xDai','basePath':'/xdai/aox','group':'mainnets'},{'title':'Ethereum','basePath':'/eth/mainnet','group':'mainnets'},{'title':'Ethereum Classic','basePath':'/etx/mainnet','group':'mainnets'},{'title':'POA','basePath':'/poa/core','group':'mainnets'},{'title':'RSK','basePath':'/rsk/mainnet','group':'mainnets'},{'title':'Gnosis Chain Testnet','basePath':'/xdai/testnet','group':'testnets'},{'title':'POA Sokol','basePath':'/poa/sokol','group':'testnets'},{'title':'ARTIS Σ1','basePath':'/artis/sigma1','group':'other'},{'title':'LUKSO L14','basePath':'/lukso/l14','group':'other'},{'title':'Astar','basePath':'/astar','group':'other'}]
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Anyblock','baseUrl':'https://explorer.anyblock.tools','paths':{'tx':'/ethereum/poa/core/tx'}}]
# current network config # current network config
NEXT_PUBLIC_NETWORK_NAME=POA NEXT_PUBLIC_NETWORK_NAME=POA
......
import _compose from 'lodash/fp/compose';
import _mapValues from 'lodash/mapValues';
import type { NetworkExplorer } from 'types/networks';
import appConfig from 'configs/app/config';
// for easy .env update
// const NETWORK_EXPLORERS = JSON.stringify([
// {
// title: 'Anyblock',
// baseUrl: 'https://explorer.anyblock.tools',
// paths: {
// tx: '/ethereum/poa/core/tx',
// },
// },
// ]).replaceAll('"', '\'');
function parseNetworkExplorers() {
try {
return JSON.parse(appConfig.network.explorers || '[]');
} catch (error) {
return [];
}
}
const stripTrailingSlash = (str: string) => str.at(-1) === '/' ? str.slice(0, -1) : str;
const addLeadingSlash = (str: string) => str.at(0) === '/' ? str : '/' + str;
const networkExplorers: Array<NetworkExplorer> = (() => {
const explorers: Array<NetworkExplorer> = parseNetworkExplorers();
return explorers.map((explorer) => ({
...explorer,
baseUrl: stripTrailingSlash(explorer.baseUrl),
paths: _mapValues(explorer.paths, _compose(stripTrailingSlash, addLeadingSlash)),
}));
})();
export default networkExplorers;
...@@ -8,3 +8,11 @@ export interface FeaturedNetwork { ...@@ -8,3 +8,11 @@ export interface FeaturedNetwork {
group: NetworkGroup; group: NetworkGroup;
icon?: FunctionComponent<SVGAttributes<SVGElement>> | string; icon?: FunctionComponent<SVGAttributes<SVGElement>> | string;
} }
export interface NetworkExplorer {
title: string;
baseUrl: string;
paths: {
tx: string;
};
}
import { Flex, Link, Icon } from '@chakra-ui/react'; import { Flex, Link, Icon } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import type { RoutedTab } from 'ui/shared/RoutedTabs/types'; import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import eastArrowIcon from 'icons/arrows/east.svg'; import eastArrowIcon from 'icons/arrows/east.svg';
import link from 'lib/link/link'; import link from 'lib/link/link';
import networkExplorers from 'lib/networks/networkExplorers';
import ExternalLink from 'ui/shared/ExternalLink'; import ExternalLink from 'ui/shared/ExternalLink';
import Page from 'ui/shared/Page/Page'; import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
...@@ -25,6 +27,15 @@ const TABS: Array<RoutedTab> = [ ...@@ -25,6 +27,15 @@ const TABS: Array<RoutedTab> = [
]; ];
const TransactionPageContent = () => { const TransactionPageContent = () => {
const router = useRouter();
const explorersLinks = networkExplorers
.filter((explorer) => explorer.paths.tx)
.map((explorer) => {
const url = new URL(explorer.paths.tx + '/' + router.query.id, explorer.baseUrl);
return <ExternalLink key={ explorer.baseUrl } title={ `Open in ${ explorer.title }` } href={ url.toString() }/>;
});
return ( return (
<Page> <Page>
{ /* TODO should be shown only when navigating from txs list */ } { /* TODO should be shown only when navigating from txs list */ }
...@@ -34,19 +45,19 @@ const TransactionPageContent = () => { ...@@ -34,19 +45,19 @@ const TransactionPageContent = () => {
</Link> </Link>
<Flex alignItems="flex-start" flexDir={{ base: 'column', lg: 'row' }}> <Flex alignItems="flex-start" flexDir={{ base: 'column', lg: 'row' }}>
<PageTitle text="Transaction details"/> <PageTitle text="Transaction details"/>
<Flex { explorersLinks.length > 0 && (
alignItems="center" <Flex
flexWrap="wrap" alignItems="center"
columnGap={ 6 } flexWrap="wrap"
rowGap={ 3 } columnGap={ 6 }
ml={{ base: 'initial', lg: 'auto' }} rowGap={ 3 }
mb={{ base: 6, lg: 'initial' }} ml={{ base: 'initial', lg: 'auto' }}
py={ 2.5 } mb={{ base: 6, lg: 'initial' }}
> py={ 2.5 }
<ExternalLink title="Open in Tenderly" href="#"/> >
<ExternalLink title="Open in Blockchair" href="#"/> { explorersLinks }
<ExternalLink title="Open in Etherscan" href="#"/> </Flex>
</Flex> ) }
</Flex> </Flex>
<RoutedTabs <RoutedTabs
tabs={ TABS } tabs={ TABS }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment