Commit 9dad0728 authored by tom goriunov's avatar tom goriunov Committed by GitHub

Merge pull request #144 from blockscout/tx-page-details

tx page: details tab
parents 82221691 460d6349
const RESTRICTED_MODULES = {
paths: [
{ name: 'dayjs', message: 'Please use lib/date/dayjs.ts' },
],
};
module.exports = {
env: {
es6: true,
......@@ -195,6 +200,8 @@ module.exports = {
},
],
'no-restricted-imports': [ 'error', RESTRICTED_MODULES ],
'react/jsx-key': 'error',
'react/jsx-no-bind': [ 'error', {
ignoreRefs: true,
......
/* eslint-disable max-len */
export const tx = {
hash: '0x1ea365d2144796f793883534aa51bf20d23292b19478994eede23dfc599e7c34',
status: 'success',
block_num: 15006918,
confirmation_num: 283,
confirmation_duration: 30,
timestamp: 1662623567695,
address_from: '0x97Aa2EfcF35c0f4c9AaDDCa8c2330fa7A9533830',
address_to: '0x35317007D203b8a86CA727ad44E473E40450E378',
amount: {
value: 0.03,
value_usd: 35.5,
},
fee: {
value: 0.002395904453623692,
value_usd: 2.84,
},
gas_price: 0.000000017716513811,
gas_limit: 208420,
gas_used: 159319,
gas_fees: {
base: 13.538410068,
max: 20.27657523,
max_priority: 1.5,
},
burnt_fees: {
value: 0.002156925953623692,
value_usd: 2.55,
},
type: {
value: '2',
eip: 'EIP-1559',
},
nonce: 4,
position: 342,
input_hex: '0x42842e0e0000000000000000000000007767dac225a233ea1055d79fb227b1696d538b75000000000000000000000000fc3017c31fe752fc48e904050ea5d6edfc38a1b00000000000000000000000000000000000000000000000000000000000000e3b',
transferred_tokens: [
{ from: '0x12E80C27BfFBB76b4A8d26FF2bfd3C9f310FFA01', to: '0xF7A558692dFB5F456e291791da7FAE8Dd046574e', token: 'USDT', amount: 192.7, usd: 194.05 },
{ from: '0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45', to: '0x12E80C27BfFBB76b4A8d26FF2bfd3C9f310FFA01', token: 'TOKE', amount: 76.1851851851846, usd: 194.05 },
],
};
export const data = [
{
id: 1,
type: 'call',
status: 'success' as const,
from: '0x12E80C27BfFBB76b4A8d26FF2bfd3C9f310FFA01',
to: '0xF7A558692dFB5F456e291791da7FAE8Dd046574e',
value: 0.25207646303,
gasLimit: 369472,
},
{
id: 2,
type: 'delegate call',
status: 'error' as const,
from: '0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45',
to: '0x12E80C27BfFBB76b4A8d26FF2bfd3C9f310FFA01',
value: 0.5633333,
gasLimit: 340022,
},
{
id: 3,
type: 'static call',
status: 'success' as const,
from: '0x97Aa2EfcF35c0f4c9AaDDCa8c2330fa7A9533830',
to: '0x35317007D203b8a86CA727ad44E473E40450E378',
value: 0.421152366,
gasLimit: 509333,
},
];
export const data = [
{
address: '0x12E80C27BfFBB76b4A8d26FF2bfd3C9f310FFA01',
topics: [
{ hex: '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef' },
{ hex: '0x000000000000000000000000def171fe48cf0115b1d80b88dc8eab59176fee57' },
{ hex: '0x000000000000000000000000c465c0a16228ef6fe1bf29c04fdb04bb797fd537' },
],
data: '0x000000000000000000000000000000000000000000000000019faae14eb88000',
},
{
address: '0x73968b9a57c6e53d41345fd57a6e6ae27d6cdb2f',
topics: [
{ hex: '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef' },
{ hex: '0x000000000000000000000000c465c0a16228ef6fe1bf29c04fdb04bb797fd537' },
{ hex: '0x0000000000000000000000008453d9385af5f49edad9905345cd2411b5c5831b' },
],
data: '0x000000000000000000000000000000000000000000000013b6ee62022c95ced4',
},
];
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.758 11c.89 0 1.337-1.077.707-1.707l-2.95-2.95a1 1 0 1 1 1.414-1.414l6.364 6.364a1 1 0 0 1 0 1.414l-6.364 6.364a1 1 0 1 1-1.414-1.414l2.95-2.95c.63-.63.184-1.707-.707-1.707H5a1 1 0 1 1 0-2h8.758Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.5 11a7.5 7.5 0 1 1 15 0 7.5 7.5 0 0 1-15 0ZM11 1C5.477 1 1 5.477 1 11s4.477 10 10 10 10-4.477 10-10S16.523 1 11 1Zm1.25 5a1.25 1.25 0 1 0-2.5 0v5c0 .69.56 1.25 1.25 1.25h5a1.25 1.25 0 1 0 0-2.5h-3.75V6Z" fill="currentColor" stroke="transparent" stroke-width=".6" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.5 14.167c0 .46.373.833.833.833H11a.833.833 0 1 0 0-1.667H9.333a.833.833 0 0 0-.833.834ZM3.5 5a.833.833 0 0 0 0 1.667h13.333a.833.833 0 0 0 0-1.667H3.5Zm1.667 5c0 .46.373.833.833.833h8.333a.833.833 0 0 0 0-1.666H6a.833.833 0 0 0-.833.833Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.976 20a6.977 6.977 0 0 0 6.977-6.977c0-.805-.214-1.578-.465-2.297-1.55 1.532-2.729 2.297-3.535 2.297C16.669 6.512 14.627 3.721 9.046 0c.465 4.651-2.601 6.767-3.85 7.941A6.977 6.977 0 0 0 9.977 20Zm.66-16.526c3.016 2.559 3.03 4.546.701 8.627-.708 1.24.188 2.783 1.616 2.783.64 0 1.287-.186 1.971-.554a5.116 5.116 0 1 1-8.453-5.034c.117-.11.712-.637.738-.66.394-.354.719-.668 1.04-1.01 1.144-1.227 1.967-2.587 2.388-4.152Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.034 3H6.965C4.587 3 3 4.703 3 7.141v5.718C3 15.299 4.583 17 6.965 17h6.068C15.416 17 17 15.3 17 12.859V7.14C17 4.701 15.416 3 13.034 3ZM6.965 4.05h6.069c1.785 0 2.916 1.214 2.916 3.091v5.718c0 1.877-1.13 3.091-2.917 3.091H6.966c-1.786 0-2.916-1.214-2.916-3.091V7.14c0-1.874 1.134-3.091 2.915-3.091ZM10 6.818a.525.525 0 0 1 .072 1.045l-.079.005a.525.525 0 0 1-.07-1.045L10 6.818Zm-.007 2.221c.266 0 .486.198.52.454l.005.071v3.093a.525.525 0 0 1-1.045.072l-.005-.072V9.564c0-.29.235-.525.525-.525Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 10A5 5 0 1 0 5 0a5 5 0 0 0 0 10ZM3.567 2.683a.625.625 0 1 0-.884.884L4.116 5 2.683 6.433a.625.625 0 1 0 .884.884L5 5.884l1.433 1.433a.625.625 0 1 0 .884-.884L5.884 5l1.433-1.433a.625.625 0 1 0-.884-.884L5 4.116 3.567 2.683Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 10A5 5 0 1 0 5 0a5 5 0 0 0 0 10Zm2.552-6.652a.625.625 0 0 0-1.083-.625L4.492 6.148l-1.34-.954a.625.625 0 0 0-.725 1.018l1.9 1.353a.625.625 0 0 0 .903-.196l2.322-4.02Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path fill="url(#toke_svg__a)" d="M0 0h20v20H0z"/>
<defs>
<pattern id="toke_svg__a" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#toke_svg__b" transform="scale(.03125)"/>
</pattern>
<image id="toke_svg__b" width="32" height="32" xlink:href="data:image/png;base64,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"/>
</defs>
</svg>
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path fill="url(#usdt_svg__a)" d="M0 0h20v20H0z"/>
<defs>
<pattern id="usdt_svg__a" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#usdt_svg__b" transform="scale(.03125)"/>
</pattern>
<image id="usdt_svg__b" width="32" height="32" xlink:href="data:image/png;base64,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"/>
</defs>
</svg>
// eslint-disable-next-line no-restricted-imports
import dayjs from 'dayjs';
import localizedFormat from 'dayjs/plugin/localizedFormat';
import relativeTime from 'dayjs/plugin/relativeTime';
import updateLocale from 'dayjs/plugin/updateLocale';
dayjs.locale('en');
dayjs.extend(relativeTime);
dayjs.extend(updateLocale);
dayjs.extend(localizedFormat);
dayjs.updateLocale('en', {
formats: {
LLLL: 'MMMM-DD-YYYY HH:mm:ss A Z UTC',
},
});
export default dayjs;
......@@ -13,6 +13,14 @@ import getDefaultTransitionProps from '../utils/getDefaultTransitionProps';
import getOutlinedFieldStyles from '../utils/getOutlinedFieldStyles';
const size = {
xs: defineStyle({
fontSize: 'md',
lineHeight: '24px',
px: '4px',
py: '12px',
h: '32px',
borderRadius: 'base',
}),
sm: defineStyle({
fontSize: 'md',
lineHeight: '24px',
......@@ -55,6 +63,10 @@ const variantOutline = definePartsStyle((props) => {
});
const sizes = {
xs: definePartsStyle({
field: size.xs,
addon: size.xs,
}),
sm: definePartsStyle({
field: size.sm,
addon: size.sm,
......
......@@ -11,6 +11,7 @@ const variantPrimary = defineStyle((props) => {
color: mode('blue.600', 'blue.300')(props),
_hover: {
color: mode('blue.400', 'blue.200')(props),
textDecorationStyle: props.textDecorationStyle || 'solid',
},
};
});
......
import { Textarea as TextareaComponent } from '@chakra-ui/react';
import { defineStyle, defineStyleConfig } from '@chakra-ui/styled-system';
import { mode } from '@chakra-ui/theme-tools';
import getOutlinedFieldStyles from '../utils/getOutlinedFieldStyles';
const variantFilledInactive = defineStyle((props) => {
return {
bgColor: mode('blackAlpha.50', 'whiteAlpha.50')(props),
};
});
const sizes = {
md: defineStyle({
fontSize: 'md',
......@@ -24,6 +31,7 @@ const Textarea = defineStyleConfig({
sizes,
variants: {
outline: defineStyle(getOutlinedFieldStyles),
filledInactive: variantFilledInactive,
},
defaultProps: {
variant: 'outline',
......
......@@ -35,7 +35,7 @@ const baseStyle = defineStyle((props) => {
[$bg.variable]: `colors.${ bg }`,
[$fg.reference]: `colors.${ fg }`,
[$arrowBg.variable]: $bg.reference,
maxWidth: 'unset',
maxWidth: props.maxWidth || props.maxW || 'unset',
};
});
......
......@@ -12,6 +12,8 @@ import useBasePath from 'lib/hooks/useBasePath';
import Page from 'ui/shared/Page';
import PageHeader from 'ui/shared/PageHeader';
import TxDetails from 'ui/tx/TxDetails';
import TxInternals from 'ui/tx/TxInternals';
import TxLogs from 'ui/tx/TxLogs';
interface Tab {
type: 'details' | 'internal_txn' | 'logs' | 'raw_trace' | 'state';
......@@ -22,8 +24,8 @@ interface Tab {
const TABS: Array<Tab> = [
{ type: 'details', path: '', name: 'Details', component: <TxDetails/> },
{ type: 'internal_txn', path: '/internal-transactions', name: 'Internal txn' },
{ type: 'logs', path: '/logs', name: 'Logs' },
{ type: 'internal_txn', path: '/internal-transactions', name: 'Internal txn', component: <TxInternals/> },
{ type: 'logs', path: '/logs', name: 'Logs', component: <TxLogs/> },
{ type: 'state', path: '/state', name: 'State' },
{ type: 'raw_trace', path: '/raw-trace', name: 'Raw trace' },
];
......
import { HStack, Link } from '@chakra-ui/react';
import { Flex, Link } from '@chakra-ui/react';
import type { HTMLChakraProps } from '@chakra-ui/system';
import React from 'react';
import useBasePath from 'lib/hooks/useBasePath';
......@@ -8,33 +9,38 @@ import CopyToClipboard from './CopyToClipboard';
const FONT_WEIGHT = '600';
type Props = {
interface Props extends HTMLChakraProps<'div'> {
address: string;
type?: 'address' | 'transaction';
type?: 'address' | 'transaction' | 'token';
fontWeight?: string;
truncated?: boolean;
withCopy?: boolean;
}
const AddressLinkWithTooltip = ({ address, type = 'address' }: Props) => {
const AddressLinkWithTooltip = ({ address, type = 'address', truncated, withCopy = true, ...styles }: Props) => {
const basePath = useBasePath();
let url;
if (type === 'transaction') {
url = basePath + '/tx/' + address;
} else {
} else if (type === 'token') {
url = basePath + '/address/' + address + '/tokens#address-tabs';
} else {
url = basePath + '/address/' + address;
}
return (
<HStack spacing={ 2 } alignContent="center" overflow="hidden" maxW="100%">
<Flex columnGap={ 2 } alignItems="center" overflow="hidden" maxW="100%" { ...styles }>
<Link
href={ url }
target="_blank"
overflow="hidden"
fontWeight={ FONT_WEIGHT }
fontWeight={ styles.fontWeight || FONT_WEIGHT }
lineHeight="24px"
whiteSpace="nowrap"
>
<AddressWithDots address={ address } fontWeight={ FONT_WEIGHT }/>
<AddressWithDots address={ address } fontWeight={ styles.fontWeight || FONT_WEIGHT } truncated={ truncated }/>
</Link>
<CopyToClipboard text={ address }/>
</HStack>
{ withCopy && <CopyToClipboard text={ address }/> }
</Flex>
);
};
......
......@@ -19,12 +19,20 @@ import { BODY_TYPEFACE } from 'theme/foundations/typography';
const TAIL_LENGTH = 4;
const HEAD_MIN_LENGTH = 4;
const AddressWithDots = ({ address, fontWeight }: {address: string; fontWeight: FontFace['weight']}) => {
interface Props {
address: string;
fontWeight: string | number;
truncated?: boolean;
}
const shortenAddress = (address: string) => address.slice(0, 4) + '...' + address.slice(-4);
const AddressWithDots = ({ address, fontWeight, truncated }: Props) => {
const addressRef = useRef<HTMLSpanElement>(null);
const [ displayedAddress, setAddress ] = React.useState(address);
const [ displayedAddress, setAddress ] = React.useState(truncated ? shortenAddress(address) : address);
const isFontFaceLoaded = useFontFaceObserver([
{ family: BODY_TYPEFACE, weight: fontWeight },
{ family: BODY_TYPEFACE, weight: String(fontWeight) as FontFace['weight'] },
]);
const calculateString = useCallback(() => {
......@@ -65,19 +73,23 @@ const AddressWithDots = ({ address, fontWeight }: {address: string; fontWeight:
// but we don't want to create more resize event listeners
// that's why there are separate useEffect hooks
useEffect(() => {
calculateString();
}, [ calculateString, isFontFaceLoaded ]);
!truncated && calculateString();
}, [ calculateString, isFontFaceLoaded, truncated ]);
useEffect(() => {
const resizeHandler = _debounce(calculateString, 50);
window.addEventListener('resize', resizeHandler);
return function cleanup() {
window.removeEventListener('resize', resizeHandler);
};
}, [ calculateString ]);
if (!truncated) {
const resizeHandler = _debounce(calculateString, 100);
const resizeObserver = new ResizeObserver(resizeHandler);
resizeObserver.observe(document.body);
return function cleanup() {
resizeObserver.unobserve(document.body);
};
}
}, [ calculateString, truncated ]);
const content = <span ref={ addressRef }>{ displayedAddress }</span>;
const isTruncated = address.length !== displayedAddress.length;
const isTruncated = truncated || address.length !== displayedAddress.length;
if (isTruncated) {
return (
......
import { GridItem, Icon, Flex, Tooltip, Box, Text } from '@chakra-ui/react';
import type { HTMLChakraProps } from '@chakra-ui/system';
import React from 'react';
import infoIcon from 'icons/info.svg';
interface Props extends HTMLChakraProps<'div'> {
title: string;
hint: string;
children: React.ReactNode;
}
const DetailsInfoItem = ({ title, hint, children, ...styles }: Props) => {
return (
<>
<GridItem py={ 2 } lineHeight={ 5 } { ...styles } whiteSpace="nowrap">
<Flex columnGap={ 2 } alignItems="center">
<Tooltip
label={ hint }
placement="top"
maxW="320px"
>
<Box cursor="pointer" display="inherit">
<Icon as={ infoIcon } boxSize={ 5 }/>
</Box>
</Tooltip>
<Text fontWeight={ 500 }>{ title }</Text>
</Flex>
</GridItem>
<GridItem display="flex" alignItems="center" py={ 2 } lineHeight={ 5 } whiteSpace="nowrap" { ...styles }>
{ children }
</GridItem>
</>
);
};
export default DetailsInfoItem;
import { SearchIcon } from '@chakra-ui/icons';
import { Flex, Icon, Button, Circle, InputGroup, InputLeftElement, Input, useColorModeValue } from '@chakra-ui/react';
import type { ChangeEvent } from 'react';
import React from 'react';
import filterIcon from 'icons/filter.svg';
const FilterIcon = <Icon as={ filterIcon } boxSize={ 5 }/>;
const Filters = () => {
const [ isActive, setIsActive ] = React.useState(false);
const [ value, setValue ] = React.useState('');
const handleClick = React.useCallback(() => {
setIsActive(flag => !flag);
}, []);
const handleChange = React.useCallback((event: ChangeEvent<HTMLInputElement>) => {
setValue(event.target.value);
}, []);
const badgeColor = useColorModeValue('white', 'black');
const badgeBgColor = useColorModeValue('blue.700', 'gray.50');
const searchIconColor = useColorModeValue('blackAlpha.600', 'whiteAlpha.600');
const inputBorderColor = useColorModeValue('blackAlpha.100', 'whiteAlpha.200');
return (
<Flex>
<Button
leftIcon={ FilterIcon }
rightIcon={ isActive ? <Circle bg={ badgeBgColor } size={ 5 } color={ badgeColor }>2</Circle> : undefined }
size="sm"
variant="outline"
colorScheme="gray-dark"
borderWidth="1px"
onClick={ handleClick }
isActive={ isActive }
px={ 1.5 }
>
Filter
</Button>
<InputGroup size="xs" ml={ 3 } maxW="360px">
<InputLeftElement ml={ 1 }>
<SearchIcon w={ 5 } h={ 5 } color={ searchIconColor }/>
</InputLeftElement>
<Input
paddingInlineStart="38px"
placeholder="Search by addresses, hash, method..."
ml="1px"
onChange={ handleChange }
borderColor={ inputBorderColor }
value={ value }
size="xs"
/>
</InputGroup>
</Flex>
);
};
export default Filters;
import { Box, Flex, Select, Textarea } from '@chakra-ui/react';
import React from 'react';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
type DataType = 'Hex' | 'UTF-8'
const OPTIONS: Array<DataType> = [ 'Hex', 'UTF-8' ];
interface Props {
hex: string;
}
const RawInputData = ({ hex }: Props) => {
const [ selectedDataType, setSelectedDataType ] = React.useState<DataType>('Hex');
const handleSelectChange = React.useCallback((event: React.ChangeEvent<HTMLSelectElement>) => {
setSelectedDataType(event.target.value as DataType);
}, []);
return (
<Box w="100%">
<Flex justifyContent="space-between" alignItems="center">
<Select size="sm" borderRadius="base" value={ selectedDataType } onChange={ handleSelectChange } focusBorderColor="none" w="auto">
{ OPTIONS.map((option) => <option key={ option } value={ option }>{ option }</option>) }
</Select>
<CopyToClipboard text={ hex }/>
</Flex>
<Textarea
value={ selectedDataType === 'Hex' ? hex : 'UTF-8 equivalent' }
w="100%"
maxH="220px"
mt={ 2 }
p={ 4 }
variant="filledInactive"
fontSize="sm"
/>
</Box>
);
};
export default RawInputData;
import { Center, Icon, Link, Text, chakra } from '@chakra-ui/react';
import React from 'react';
import tokeIcon from 'icons/tokens/toke.svg';
import usdtIcon from 'icons/tokens/usdt.svg';
import useBasePath from 'lib/hooks/useBasePath';
// temporary solution
// don't know where to get icons and addresses yet
const TOKENS = {
USDT: {
fullName: 'Tether USD',
symbol: 'USDT',
icon: usdtIcon,
address: '0x9bD35A17C9C7c8820f89e0277e2046CDC57aCB15',
},
TOKE: {
fullName: 'Tokemak',
symbol: 'TOKE',
icon: tokeIcon,
address: '0x9bD35A17C9C7c8820f89e0277e2046CDC57aCB15',
},
};
interface Props {
symbol: string;
className?: string;
}
const Token = ({ symbol, className }: Props) => {
const token = TOKENS[symbol as keyof typeof TOKENS];
const basePath = useBasePath();
if (!token) {
return null;
}
const url = basePath + '/token/' + token.address;
return (
<Center className={ className }>
<Icon as={ token.icon } boxSize={ 5 }/>
<Link href={ url } target="_blank" ml={ 1 }>
{ token.fullName }
</Link>
<Text ml={ 1 }>({ token.symbol })</Text>
</Center>
);
};
export default chakra(Token);
import { Box, Flex, Text, chakra } from '@chakra-ui/react';
import React from 'react';
interface Props {
className?: string;
value: number;
}
const WIDTH = 50;
const Utilization = ({ className, value }: Props) => {
const valueString = (value * 100).toFixed(2) + '%';
return (
<Flex className={ className } alignItems="center">
<Box bg="gray.100" w={ `${ WIDTH }px` } h="4px" borderRadius="full" overflow="hidden">
<Box bg="green.500" w={ valueString } h="100%"/>
</Box>
<Text color="green.500" ml="10px" fontWeight="bold">{ valueString }</Text>
</Flex>
);
};
export default chakra(Utilization);
import { Center, Icon, Text } from '@chakra-ui/react';
import React from 'react';
import rightArrowIcon from 'icons/arrows/right.svg';
import { space } from 'lib/html-entities';
import AddressLinkWithTooltip from 'ui/shared/AddressLinkWithTooltip';
import Token from 'ui/shared/Token';
interface Props {
from: string;
to: string;
token: string;
amount: number;
usd: number;
}
const TokenTransfer = ({ from, to, amount, usd, token }: Props) => {
return (
<Center>
<AddressLinkWithTooltip address={ from } fontWeight="500" truncated withCopy={ false }/>
<Icon as={ rightArrowIcon } boxSize={ 6 } mx={ 2 } color="gray.500"/>
<AddressLinkWithTooltip address={ to } fontWeight="500" truncated withCopy={ false }/>
<Text fontWeight={ 500 } as="span" ml={ 4 }>For:{ space }
<Text fontWeight={ 600 } as="span">{ amount }</Text>{ space }
<Text fontWeight={ 400 } variant="secondary" as="span">(${ usd.toFixed(2) })</Text>
</Text>
<Token symbol={ token } ml={ 3 }/>
</Center>
);
};
export default TokenTransfer;
import { Flex, Text, Grid, GridItem, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import AddressLinkWithTooltip from 'ui/shared/AddressLinkWithTooltip';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
interface RowProps {
children: React.ReactNode;
isLast?: boolean;
name: string;
type: string;
}
const PADDING = 4;
const GAP = 5;
const TableRow = ({ isLast, name, type, children }: RowProps) => {
const bgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
return (
<>
<GridItem
pl={ PADDING }
pr={ GAP }
pt={ GAP }
pb={ isLast ? PADDING : 0 }
bgColor={ bgColor }
borderBottomLeftRadius={ isLast ? 'md' : 'none' }
>
{ name }
</GridItem>
<GridItem
pr={ GAP }
pt={ GAP }
pb={ isLast ? PADDING : 0 }
bgColor={ bgColor }
>
{ type }
</GridItem>
<GridItem
pr={ PADDING }
pt={ GAP }
pb={ isLast ? PADDING : 0 }
bgColor={ bgColor }
borderBottomRightRadius={ isLast ? 'md' : 'none' }
>
{ children }
</GridItem>
</>
);
};
const TxDecodedInputData = () => {
const bgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
return (
<Grid gridTemplateColumns="minmax(80px, auto) minmax(80px, auto) 1fr" fontSize="sm" lineHeight={ 5 } w="100%">
{ /* FIRST PART OF BLOCK */ }
<GridItem fontWeight={ 600 } pl={ PADDING } pr={ GAP }>Method Id</GridItem>
<GridItem colSpan={ 2 } pr={ PADDING }>0xddf252ad</GridItem>
<GridItem
py={ 2 }
mt={ 2 }
pl={ PADDING }
pr={ GAP }
fontWeight={ 600 }
borderTopColor={ useColorModeValue('blackAlpha.200', 'whiteAlpha.200') }
borderTopWidth="1px"
>
Call
</GridItem>
<GridItem
py={ 2 }
mt={ 2 }
colSpan={ 2 }
pr={ PADDING }
borderTopColor={ useColorModeValue('blackAlpha.200', 'whiteAlpha.200') }
borderTopWidth="1px"
>
Transfer(address indexed from, address indexed to, uint256 indexed tokenId)
</GridItem>
{ /* TABLE INSIDE OF BLOCK */ }
<GridItem
pl={ PADDING }
pr={ GAP }
pt={ PADDING }
pb={ 1 }
bgColor={ bgColor }
fontWeight={ 600 }
>
Name
</GridItem>
<GridItem
pr={ GAP }
pt={ PADDING }
pb={ 1 }
bgColor={ bgColor }
fontWeight={ 600 }
>
Type
</GridItem>
<GridItem
pr={ PADDING }
pt={ PADDING }
pb={ 1 }
bgColor={ bgColor }
fontWeight={ 600 }
>
Data
</GridItem>
<TableRow name="from" type="address">
<AddressLinkWithTooltip address="0x0000000000000000000000000000000000000000" columnGap={ 0 } justifyContent="space-between" fontWeight="400"/>
</TableRow>
<TableRow name="from" type="address">
<AddressLinkWithTooltip address="0xcf0c50b7ea8af37d57380a0ac199d55b0782c718" columnGap={ 0 } justifyContent="space-between" fontWeight="400"/>
</TableRow>
<TableRow name="tokenId" type="uint256" isLast>
<Flex alignItems="center" justifyContent="space-between">
<Text>116842</Text>
<CopyToClipboard text="116842"/>
</Flex>
</TableRow>
</Grid>
);
};
export default TxDecodedInputData;
import { Grid, GridItem, Text, Box, Icon, Link, Tag, Flex } from '@chakra-ui/react';
import React from 'react';
import { scroller, Element } from 'react-scroll';
import { tx } from 'data/tx';
import clockIcon from 'icons/clock.svg';
import flameIcon from 'icons/flame.svg';
import successIcon from 'icons/status/success.svg';
import dayjs from 'lib/date/dayjs';
import AddressIcon from 'ui/shared/AddressIcon';
import AddressLinkWithTooltip from 'ui/shared/AddressLinkWithTooltip';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import RawInputData from 'ui/shared/RawInputData';
import Token from 'ui/shared/Token';
import Utilization from 'ui/shared/Utilization';
import TokenTransfer from 'ui/tx/TokenTransfer';
import TxDecodedInputData from 'ui/tx/TxDecodedInputData';
import type { Props as TxStatusProps } from 'ui/tx/TxStatus';
import TxStatus from 'ui/tx/TxStatus';
const TxDetails = () => {
return <div>TxDetails component</div>;
const [ isExpanded, setIsExpanded ] = React.useState(false);
const leftSeparatorStyles = {
ml: 3,
pl: 3,
borderLeftWidth: '1px',
borderLeftColor: 'gray.700',
};
const handleCutClick = React.useCallback(() => {
setIsExpanded((flag) => !flag);
scroller.scrollTo('TxDetails__cutLink', {
duration: 500,
smooth: true,
});
}, []);
return (
<Grid columnGap={ 8 } rowGap={ 3 } templateColumns="auto 1fr">
<DetailsInfoItem
title="Transaction hash"
hint="Unique character string (TxID) assigned to every verified transaction."
>
{ tx.hash }
<CopyToClipboard text={ tx.hash }/>
</DetailsInfoItem>
<DetailsInfoItem
title="Status"
hint="Current transaction state: Success, Failed (Error), or Pending (In Process)"
>
<TxStatus status={ tx.status as TxStatusProps['status'] }/>
</DetailsInfoItem>
<DetailsInfoItem
title="Block"
hint="Block number containing the transaction."
>
<Text>{ tx.block_num }</Text>
<Text { ...leftSeparatorStyles } borderLeftColor="gray.500" variant="secondary">
{ tx.confirmation_num } Block confirmations
</Text>
</DetailsInfoItem>
<DetailsInfoItem
title="Timestamp"
hint="Date & time of transaction inclusion, including length of time for confirmation."
>
<Icon as={ clockIcon } boxSize={ 5 } color="gray.500"/>
<Text ml={ 1 }>{ dayjs(tx.timestamp).fromNow() }</Text>
<Text { ...leftSeparatorStyles }>{ dayjs(tx.timestamp).format('LLLL') }</Text>
<Text { ...leftSeparatorStyles } borderLeftColor="gray.500" variant="secondary">
Confirmed within { tx.confirmation_duration } secs
</Text>
</DetailsInfoItem>
<DetailsInfoItem
title="From"
hint="Address (external or contract) sending the transaction."
mt={ 8 }
>
<AddressIcon address={ tx.address_from }/>
<AddressLinkWithTooltip address={ tx.address_from } columnGap={ 0 } ml={ 2 } fontWeight="400"/>
</DetailsInfoItem>
<DetailsInfoItem
title="Interacted with contract"
hint="Address (external or contract) receiving the transaction."
>
<AddressIcon address={ tx.address_to }/>
<AddressLinkWithTooltip address={ tx.address_to } columnGap={ 0 } ml={ 2 } fontWeight="400"/>
<Tag colorScheme="orange" variant="solid" ml={ 3 }>SANA</Tag>
<Icon as={ successIcon } boxSize={ 4 } ml={ 2 } color="green.500"/>
<Token symbol="USDT" ml={ 3 }/>
</DetailsInfoItem>
<DetailsInfoItem
title="Token transferred"
hint="List of token transferred in the transaction."
>
<Flex flexDirection="column" alignItems="flex-start" rowGap={ 5 }>
{ tx.transferred_tokens.map((item) => <TokenTransfer key={ item.token } { ...item }/>) }
</Flex>
</DetailsInfoItem>
<DetailsInfoItem
title="Value"
hint="Value sent in the native token (and USD) if applicable."
mt={ 8 }
>
<Text>{ tx.amount.value } Ether</Text>
<Text variant="secondary" ml={ 1 }>(${ tx.amount.value_usd.toFixed(2) })</Text>
</DetailsInfoItem>
<DetailsInfoItem
title="Transaction fee"
hint="Total transaction fee."
>
<Text>{ tx.fee.value } Ether</Text>
<Text variant="secondary" ml={ 1 }>(${ tx.fee.value_usd.toFixed(2) })</Text>
</DetailsInfoItem>
<DetailsInfoItem
title="Gas price"
hint="Price per unit of gas specified by the sender. Higher gas prices can prioritize transaction inclusion during times of high usage."
>
<Text>{ tx.gas_price.toLocaleString('en', { minimumFractionDigits: 18 }) } Ether</Text>
<Text variant="secondary" ml={ 1 }>({ (tx.gas_price * Math.pow(10, 18)).toFixed(0) } Gwei)</Text>
</DetailsInfoItem>
<DetailsInfoItem
title="Gas limit & usage by txn"
hint="Actual gas amount used by the transaction."
>
<Text>{ tx.gas_used.toLocaleString('en') }</Text>
<Text { ...leftSeparatorStyles }>{ tx.gas_limit.toLocaleString('en') }</Text>
<Utilization ml={ 4 } value={ tx.gas_used / tx.gas_limit }/>
</DetailsInfoItem>
<DetailsInfoItem
title="Gas fees (Gwei)"
// eslint-disable-next-line max-len
hint="Base Fee refers to the network Base Fee at the time of the block, while Max Fee & Max Priority Fee refer to the max amount a user is willing to pay for their tx & to give to the miner respectively."
>
<Box>
<Text as="span" fontWeight="500">Base: </Text>
<Text fontWeight="600" as="span">{ tx.gas_fees.base }</Text>
</Box>
<Box { ...leftSeparatorStyles }>
<Text as="span" fontWeight="500">Max: </Text>
<Text fontWeight="600" as="span">{ tx.gas_fees.max }</Text>
</Box>
<Box { ...leftSeparatorStyles }>
<Text as="span" fontWeight="500">Max priority: </Text>
<Text fontWeight="600" as="span">{ tx.gas_fees.max_priority }</Text>
</Box>
</DetailsInfoItem>
<DetailsInfoItem
title="Burnt fees"
hint="Amount of ETH burned for this transaction. Equals Block Base Fee per Gas * Gas Used."
>
<Icon as={ flameIcon } boxSize={ 5 } color="gray.500"/>
<Text ml={ 1 }>{ tx.burnt_fees.value.toLocaleString('en', { minimumFractionDigits: 18 }) } Ether</Text>
<Text variant="secondary" ml={ 1 }>(${ tx.burnt_fees.value_usd.toFixed(2) })</Text>
</DetailsInfoItem>
<GridItem colSpan={ 2 }>
<Element name="TxDetails__cutLink">
<Link
mt={ 6 }
display="inline-block"
fontSize="sm"
textDecorationLine="underline"
textDecorationStyle="dashed"
onClick={ handleCutClick }
>
{ isExpanded ? 'Hide details' : 'View details' }
</Link>
</Element>
</GridItem>
{ isExpanded && (
<>
<DetailsInfoItem
mt={ 4 }
title="Other"
hint="Other data related to this transaction."
>
<Box>
<Text as="span" fontWeight="500">Txn type: </Text>
<Text fontWeight="600" as="span">{ tx.type.value }</Text>
<Text fontWeight="400" as="span" ml={ 1 }>({ tx.type.eip })</Text>
</Box>
<Box { ...leftSeparatorStyles }>
<Text as="span" fontWeight="500">Nonce: </Text>
<Text fontWeight="600" as="span">{ tx.nonce }</Text>
</Box>
<Box { ...leftSeparatorStyles }>
<Text as="span" fontWeight="500">Position: </Text>
<Text fontWeight="600" as="span">{ tx.position }</Text>
</Box>
</DetailsInfoItem>
<DetailsInfoItem
title="Raw input"
hint="Binary data included with the transaction. See logs tab for additional info."
>
<RawInputData hex={ tx.input_hex }/>
</DetailsInfoItem>
<DetailsInfoItem
title="Decoded input data"
hint="hmmmmmmmmmmm"
>
<TxDecodedInputData/>
</DetailsInfoItem>
</>
) }
</Grid>
);
};
export default TxDetails;
import { Box, Table, Thead, Tbody, Tr, Th, TableContainer } from '@chakra-ui/react';
import React from 'react';
import { data } from 'data/txInternal';
import Filters from 'ui/shared/Filters';
import TxInternalsTableItem from 'ui/tx/internals/TxInternalsTableItem';
const TxInternals = () => {
return (
<Box>
<Filters/>
<TableContainer width="100%" mt={ 6 }>
<Table variant="simple" minWidth="950px">
<Thead>
<Tr>
<Th width="20%">Type</Th>
<Th width="calc(20% + 40px)" pr="0">From</Th>
<Th width="calc(20% - 40px)" pl="0">To</Th>
<Th width="20%" isNumeric>Value</Th>
<Th width="20%" isNumeric>Gas limit</Th>
</Tr>
</Thead>
<Tbody>
{ data.map((item) => (
<TxInternalsTableItem
key={ item.id }
{ ...item }
/>
)) }
</Tbody>
</Table>
</TableContainer>
</Box>
);
};
export default TxInternals;
import { Box } from '@chakra-ui/react';
import React from 'react';
import { data } from 'data/txLogs';
import TxLogItem from 'ui/tx/logs/TxLogItem';
const TxLogs = () => {
return (
<Box>
{ data.map((item, index) => <TxLogItem key={ index } { ...item } index={ index }/>) }
</Box>
);
};
export default TxLogs;
import { Tag, TagLabel, TagLeftIcon } from '@chakra-ui/react';
import React from 'react';
import errorIcon from 'icons/status/error.svg';
import successIcon from 'icons/status/success.svg';
export interface Props {
status: 'success' | 'error';
}
const TxStatus = ({ status }: Props) => {
const label = status === 'success' ? 'Success' : 'Error';
const icon = status === 'success' ? successIcon : errorIcon;
const colorScheme = status === 'success' ? 'green' : 'red';
return (
<Tag colorScheme={ colorScheme }>
<TagLeftIcon boxSize={ 2.5 } as={ icon }/>
<TagLabel>{ label }</TagLabel>
</Tag>
);
};
export default TxStatus;
import { Tr, Td, Tag, Flex, Icon } from '@chakra-ui/react';
import capitalize from 'lodash/capitalize';
import React from 'react';
import rightArrowIcon from 'icons/arrows/right.svg';
import AddressIcon from 'ui/shared/AddressIcon';
import AddressLinkWithTooltip from 'ui/shared/AddressLinkWithTooltip';
import TxStatus from 'ui/tx/TxStatus';
interface Props {
type: string;
status: 'success' | 'error';
from: string;
to: string;
value: number;
gasLimit: number;
}
const TxInternalTableItem = ({ type, status, from, to, value, gasLimit }: Props) => {
return (
<Tr alignItems="top">
<Td>
<Tag colorScheme="cyan" mr={ 2 }>{ capitalize(type) }</Tag>
<TxStatus status={ status }/>
</Td>
<Td pr="0">
<Flex alignItems="center">
<AddressIcon address={ from }/>
<AddressLinkWithTooltip address={ from } fontWeight="500" withCopy={ false } ml={ 2 }/>
<Icon as={ rightArrowIcon } boxSize={ 6 } mx={ 2 } color="gray.500"/>
</Flex>
</Td>
<Td pl="0">
<Flex alignItems="center">
<AddressIcon address={ to }/>
<AddressLinkWithTooltip address={ to } fontWeight="500" withCopy={ false } ml={ 2 }/>
</Flex>
</Td>
<Td isNumeric>
{ value }
</Td>
<Td isNumeric>
{ gasLimit.toLocaleString('en') }
</Td>
</Tr>
);
};
export default React.memo(TxInternalTableItem);
import { SearchIcon } from '@chakra-ui/icons';
import { Text, Grid, GridItem, Link, Tooltip, Button, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import AddressIcon from 'ui/shared/AddressIcon';
import AddressLinkWithTooltip from 'ui/shared/AddressLinkWithTooltip';
import TxLogTopic from 'ui/tx/logs/TxLogTopic';
import DecodedInputData from 'ui/tx/TxDecodedInputData';
interface Props {
address: string;
topics: Array<{ hex: string }>;
data: string;
index: number;
}
const RowHeader = ({ children }: { children: React.ReactNode }) => <GridItem><Text fontWeight={ 500 }>{ children }</Text></GridItem>;
const TxLogItem = ({ address, index, topics, data }: Props) => {
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
const dataBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
return (
<Grid gridTemplateColumns="200px 1fr" gap={ 8 } py={ 8 } _notFirst={{ borderTopWidth: '1px', borderTopColor: borderColor }}>
<RowHeader>Address</RowHeader>
<GridItem display="flex" alignItems="center">
<AddressIcon address={ address }/>
<AddressLinkWithTooltip address={ address } columnGap={ 0 } ml={ 2 } fontWeight="400" withCopy={ false }/>
<Tooltip label="Find matches topic">
<Link ml={ 2 }>
<SearchIcon w={ 5 } h={ 5 }/>
</Link>
</Tooltip>
<Tooltip label="Log index">
<Button variant="outline" isActive ml="auto" size="sm" fontWeight={ 400 }>
{ index }
</Button>
</Tooltip>
</GridItem>
<RowHeader>Decode input data</RowHeader>
<GridItem>
<DecodedInputData/>
</GridItem>
<RowHeader>Topics</RowHeader>
<GridItem>
{ topics.map((item, index) => <TxLogTopic key={ index } { ...item } index={ index }/>) }
</GridItem>
<RowHeader>Data</RowHeader>
<GridItem p={ 4 } fontSize="sm" borderRadius="md" bgColor={ dataBgColor }>
{ data }
</GridItem>
</Grid>
);
};
export default React.memo(TxLogItem);
import { Flex, Button, Text, Select } from '@chakra-ui/react';
import React from 'react';
interface Props {
hex: string;
index: number;
}
type DataType = 'Hex' | 'Dec'
const OPTIONS: Array<DataType> = [ 'Hex', 'Dec' ];
const TxLogTopic = ({ hex, index }: Props) => {
const [ selectedDataType, setSelectedDataType ] = React.useState<DataType>('Hex');
const handleSelectChange = React.useCallback((event: React.ChangeEvent<HTMLSelectElement>) => {
setSelectedDataType(event.target.value as DataType);
}, []);
return (
<Flex alignItems="center" px={ 3 } _notFirst={{ mt: 3 }}>
<Button variant="outline" isActive size="xs" fontWeight={ 400 } mr={ 3 } w={ 6 }>
{ index }
</Button>
{ /* temporary condition juse to show different states of the component */ }
{ /* delete when ther will be real data */ }
{ index > 0 && (
<Select size="sm" borderRadius="base" value={ selectedDataType } onChange={ handleSelectChange } focusBorderColor="none" w="75px" mr={ 3 }>
{ OPTIONS.map((option) => <option key={ option } value={ option }>{ option }</option>) }
</Select>
) }
<Text>{ hex }</Text>
</Flex>
);
};
export default React.memo(TxLogTopic);
......@@ -2485,6 +2485,11 @@ data-uri-to-buffer@^4.0.0:
resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz#b5db46aea50f6176428ac05b73be39a57701a64b"
integrity sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==
dayjs@^1.11.5:
version "1.11.5"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.5.tgz#00e8cc627f231f9499c19b38af49f56dc0ac5e93"
integrity sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA==
debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
......
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