mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
* refactor widgets on home page * coming soon placeholder component * top accounts page * contract data * address ERC-20 tokens list * chain filter for address ERC-20 tokens * token select for address detailed view * address details fields * tokens table * stats widgets on home page * chain indicators graph on the home page * quick search * search results all tab * tabs and chain select on search results page * search results mobile view * add contract info to search and address details * add text filter for tokens and search improvements * load all results inside tab on search results page * token balance info * add support for domains in the full search * Multichain explorer Resolves #2989 * add envs validator for multichain * add some tests * fix search bar on search results page on mobile * home stats fixes * fix ts * optimize amount of queries on address page * add more contract info on address page * add enter key hint to search input * fix tests * fix test * update bg.primary css-variable on color theme change * sitemap for multichain * txs page: render first tab if blob tab is not available on the chain * filter out in active chains from chain select on address page and user ops page * add chain icon to advanced filter table * fix tooltip on entity with shield * fix ts * show contract implementations * fixes * add chain icon to all lists * refactor zetachain chains config type * use chain id as primary option instead of chain slug * refactor chain config type for multichain and essential dapps * refactor chain icon and chain select * rename chain-slug in chain_slug * change default radius of chain icon and update screenshots for multichain tests * change conditions for "isContract" and "isVerified" * test fixes * make shield optional for external chain entities * add chain icon to approvals lists * add stats page for multichain * add chain postfix to chart .png and .csv assets name * fix tests * sync verified contracts language filter and fix loading state in the search results page * fix sol2uml diagram and csv export * regcred from helmfile for review * link fixes * fix state for unknown address * fix incorrect chain in the context during spa transition to the same route on different chain * add links to stats widgets * add mixpanel to L2 demo values * show message of on-demand contract verification * fix screenshots --------- Co-authored-by: Nick Zenchik <[email protected]>
266 lines
8.9 KiB
TypeScript
266 lines
8.9 KiB
TypeScript
import type { BoxProps } from '@chakra-ui/react';
|
|
import { Box, chakra } from '@chakra-ui/react';
|
|
import BigNumber from 'bignumber.js';
|
|
import React from 'react';
|
|
|
|
import type { AddressParam } from 'types/api/addressParams';
|
|
import type {
|
|
TxInterpretationSummary,
|
|
TxInterpretationVariable,
|
|
TxInterpretationVariableString,
|
|
} from 'types/api/txInterpretation';
|
|
import type { ClusterChainConfig } from 'types/multichain';
|
|
|
|
import { route } from 'nextjs-routes';
|
|
|
|
import config from 'configs/app';
|
|
import dayjs from 'lib/date/dayjs';
|
|
import * as mixpanel from 'lib/mixpanel/index';
|
|
import { currencyUnits } from 'lib/units';
|
|
import { Badge } from 'toolkit/chakra/badge';
|
|
import { useColorModeValue } from 'toolkit/chakra/color-mode';
|
|
import { Image } from 'toolkit/chakra/image';
|
|
import { Link } from 'toolkit/chakra/link';
|
|
import { Skeleton } from 'toolkit/chakra/skeleton';
|
|
import { Tooltip } from 'toolkit/chakra/tooltip';
|
|
import { SECOND } from 'toolkit/utils/consts';
|
|
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
|
import EnsEntity from 'ui/shared/entities/ens/EnsEntity';
|
|
import TokenEntity from 'ui/shared/entities/token/TokenEntity';
|
|
import ChainIcon from 'ui/shared/externalChains/ChainIcon';
|
|
import getChainTooltipText from 'ui/shared/externalChains/getChainTooltipText';
|
|
import IconSvg from 'ui/shared/IconSvg';
|
|
|
|
import {
|
|
extractVariables,
|
|
getStringChunks,
|
|
fillStringVariables,
|
|
checkSummary,
|
|
NATIVE_COIN_SYMBOL_VAR_NAME,
|
|
WEI_VAR_NAME,
|
|
} from './utils';
|
|
|
|
const nameServicesFeature = config.features.nameServices;
|
|
|
|
interface Props extends BoxProps {
|
|
summary?: TxInterpretationSummary;
|
|
isLoading?: boolean;
|
|
addressDataMap?: Record<string, AddressParam>;
|
|
className?: string;
|
|
isNoves?: boolean;
|
|
chainData?: ClusterChainConfig;
|
|
};
|
|
|
|
type NonStringTxInterpretationVariable = Exclude<TxInterpretationVariable, TxInterpretationVariableString>;
|
|
|
|
const TxInterpretationElementByType = (
|
|
{ variable, addressDataMap }: { variable?: NonStringTxInterpretationVariable; addressDataMap?: Record<string, AddressParam> },
|
|
) => {
|
|
const onAddressClick = React.useCallback(() => {
|
|
mixpanel.logEvent(mixpanel.EventTypes.TX_INTERPRETATION_INTERACTION, { Type: 'Address click' });
|
|
}, []);
|
|
|
|
const onTokenClick = React.useCallback(() => {
|
|
mixpanel.logEvent(mixpanel.EventTypes.TX_INTERPRETATION_INTERACTION, { Type: 'Token click' });
|
|
}, []);
|
|
|
|
const onDomainClick = React.useCallback(() => {
|
|
mixpanel.logEvent(mixpanel.EventTypes.TX_INTERPRETATION_INTERACTION, { Type: 'Domain click' });
|
|
}, []);
|
|
|
|
if (!variable) {
|
|
return null;
|
|
}
|
|
|
|
const { type, value } = variable;
|
|
switch (type) {
|
|
case 'address': {
|
|
return (
|
|
<chakra.span display="inline-block" verticalAlign="top" _notFirst={{ marginLeft: 1 }}>
|
|
<AddressEntity
|
|
address={ addressDataMap?.[value.hash] || value }
|
|
icon={{ marginRight: 1 }}
|
|
truncation="constant"
|
|
onClick={ onAddressClick }
|
|
whiteSpace="initial"
|
|
/>
|
|
</chakra.span>
|
|
);
|
|
}
|
|
case 'token':
|
|
return (
|
|
<chakra.span display="inline-block" verticalAlign="top" _notFirst={{ marginLeft: 1 }}>
|
|
<TokenEntity
|
|
token={ value }
|
|
icon={{ marginRight: 1 }}
|
|
onlySymbol
|
|
noCopy
|
|
width="fit-content"
|
|
_notFirst={{ marginLeft: 1 }}
|
|
mr={ 2 }
|
|
whiteSpace="initial"
|
|
onClick={ onTokenClick }
|
|
/>
|
|
</chakra.span>
|
|
);
|
|
case 'domain': {
|
|
if (nameServicesFeature.isEnabled && nameServicesFeature.ens.isEnabled) {
|
|
return (
|
|
<chakra.span display="inline-block" verticalAlign="top" _notFirst={{ marginLeft: 1 }}>
|
|
<EnsEntity
|
|
domain={ value }
|
|
icon={{ marginRight: 1 }}
|
|
width="fit-content"
|
|
_notFirst={{ marginLeft: 1 }}
|
|
whiteSpace="initial"
|
|
onClick={ onDomainClick }
|
|
/>
|
|
</chakra.span>
|
|
);
|
|
}
|
|
return <chakra.span color="text.secondary" whiteSpace="pre">{ value + ' ' }</chakra.span>;
|
|
}
|
|
case 'currency': {
|
|
let numberString = '';
|
|
if (BigNumber(value).isLessThan(0.1)) {
|
|
numberString = BigNumber(value).toPrecision(2);
|
|
} else if (BigNumber(value).isLessThan(10000)) {
|
|
numberString = BigNumber(value).dp(2).toFormat();
|
|
} else if (BigNumber(value).isLessThan(1000000)) {
|
|
numberString = BigNumber(value).dividedBy(1000).toFormat(2) + 'K';
|
|
} else {
|
|
numberString = BigNumber(value).dividedBy(1000000).toFormat(2) + 'M';
|
|
}
|
|
return <chakra.span>{ numberString + ' ' }</chakra.span>;
|
|
}
|
|
case 'timestamp': {
|
|
return <chakra.span color="text.secondary" whiteSpace="pre">{ dayjs(Number(value) * SECOND).format('MMM DD YYYY') }</chakra.span>;
|
|
}
|
|
case 'external_link': {
|
|
return <Link external href={ value.link }>{ value.name }</Link>;
|
|
}
|
|
case 'method': {
|
|
return (
|
|
<Badge
|
|
colorPalette={ value === 'Multicall' ? 'teal' : 'gray' }
|
|
truncated
|
|
ml={ 1 }
|
|
mr={ 2 }
|
|
verticalAlign="text-top"
|
|
>
|
|
{ value }
|
|
</Badge>
|
|
);
|
|
}
|
|
case 'dexTag': {
|
|
const icon = value.app_icon || value.icon;
|
|
const name = (() => {
|
|
if (value.app_id && config.features.marketplace.isEnabled) {
|
|
return (
|
|
<Link
|
|
href={ route({ pathname: '/apps/[id]', query: { id: value.app_id } }) }
|
|
>
|
|
{ value.name }
|
|
</Link>
|
|
);
|
|
}
|
|
if (value.url) {
|
|
return (
|
|
<Link external href={ value.url }>
|
|
{ value.name }
|
|
</Link>
|
|
);
|
|
}
|
|
return value.name;
|
|
})();
|
|
|
|
return (
|
|
<chakra.span display="inline-flex" alignItems="center" verticalAlign="top" _notFirst={{ marginLeft: 1 }} gap={ 1 } mr={ 2 }>
|
|
{ icon && <Image src={ icon } alt={ value.name } width={ 5 } height={ 5 }/> }
|
|
{ name }
|
|
</chakra.span>
|
|
);
|
|
}
|
|
case 'link': {
|
|
return <Link external href={ value.url }>{ value.name }</Link>;
|
|
}
|
|
}
|
|
};
|
|
|
|
const TxInterpretation = ({ summary, isLoading, addressDataMap, className, chainData, isNoves, ...rest }: Props) => {
|
|
const novesLogoUrl = useColorModeValue('/static/noves-logo.svg', '/static/noves-logo-dark.svg');
|
|
if (!summary) {
|
|
return null;
|
|
}
|
|
|
|
const template = summary.summary_template;
|
|
const variables = summary.summary_template_variables;
|
|
|
|
if (!checkSummary(template, variables)) {
|
|
return null;
|
|
}
|
|
|
|
const intermediateResult = fillStringVariables(template, variables);
|
|
|
|
const variablesNames = extractVariables(intermediateResult);
|
|
const chunks = getStringChunks(intermediateResult);
|
|
|
|
const tooltipContent = 'Transaction summary' + (chainData ? `\n${ getChainTooltipText(chainData) }` : '');
|
|
|
|
return (
|
|
<Skeleton loading={ isLoading } className={ className } fontWeight={ 500 } whiteSpace="pre-wrap" { ...rest }>
|
|
<Tooltip content={ tooltipContent } contentProps={{ whiteSpace: 'pre-wrap' }}>
|
|
<Box display="inline-flex" position="relative" mr={ chainData ? '14px' : 1 } verticalAlign="text-top">
|
|
<IconSvg name="lightning" boxSize={ 5 } color="icon.primary"/>
|
|
{ chainData && (
|
|
<ChainIcon
|
|
data={ chainData }
|
|
boxSize="18px"
|
|
position="absolute"
|
|
top="6px"
|
|
left="12px"
|
|
borderRadius="full"
|
|
borderWidth="1px"
|
|
borderStyle="solid"
|
|
borderColor="bg.primary"
|
|
backgroundColor="bg.primary"
|
|
noTooltip
|
|
/>
|
|
) }
|
|
</Box>
|
|
</Tooltip>
|
|
{ chunks.map((chunk, index) => {
|
|
let content = null;
|
|
if (variablesNames[index] === NATIVE_COIN_SYMBOL_VAR_NAME) {
|
|
content = <chakra.span>{ currencyUnits.ether + ' ' }</chakra.span>;
|
|
} else if (variablesNames[index] === WEI_VAR_NAME) {
|
|
content = <chakra.span>{ currencyUnits.wei + ' ' }</chakra.span>;
|
|
} else {
|
|
content = (
|
|
<TxInterpretationElementByType
|
|
variable={ variables[variablesNames[index]] as NonStringTxInterpretationVariable }
|
|
addressDataMap={ addressDataMap }
|
|
/>
|
|
);
|
|
}
|
|
return (
|
|
<chakra.span key={ chunk + index }>
|
|
<chakra.span color="text.secondary">{ chunk.trim() + (chunk.trim() && variablesNames[index] ? ' ' : '') }</chakra.span>
|
|
{ index < variablesNames.length && content }
|
|
</chakra.span>
|
|
);
|
|
}) }
|
|
{ isNoves && (
|
|
<Tooltip content="Human readable transaction provided by Noves.fi">
|
|
<Badge ml={ 2 } verticalAlign="unset" transform="translateY(-2px)">
|
|
by
|
|
<Image src={ novesLogoUrl } alt="Noves logo" h="12px" ml={ 1.5 } display="inline"/>
|
|
</Badge>
|
|
</Tooltip>
|
|
) }
|
|
</Skeleton>
|
|
);
|
|
};
|
|
|
|
export default chakra(TxInterpretation);
|