Commit 72ae4ea7 authored by tom goriunov's avatar tom goriunov Committed by GitHub

Merge pull request #231 from blockscout/tx-revert-reason

tx revert reason
parents 00c7792b a313fd7e
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.868 1.55a.377.377 0 0 0-.378 0L3.188 6.363A.377.377 0 0 0 3 6.69v9.622c0 .135.072.26.188.327l8.302 4.811a.377.377 0 0 0 .378 0l8.302-4.811a.377.377 0 0 0 .188-.327V6.69a.377.377 0 0 0-.188-.327l-8.302-4.811ZM3.755 16.095V6.906l7.924-4.592 7.925 4.592v9.188l-7.925 4.592-7.924-4.592ZM8.66 6.972a.377.377 0 0 0-.754 0v6.354l-1.53-4.587a.377.377 0 0 0-.734.12v5.66a.377.377 0 0 0 .754 0v-3.335l1.529 4.586a.377.377 0 0 0 .735-.12V6.973Zm2.824-2.448a.377.377 0 0 1 .386-.003l2.262 1.32a.377.377 0 0 1-.38.652l-2.07-1.208-1.89 1.145v4.693h2.265a.377.377 0 0 1 0 .754H9.792v4.906a.377.377 0 0 1-.754 0V6.217c0-.132.069-.254.182-.323l2.264-1.37Zm3.195 2.06a.377.377 0 0 1 .515-.141l2.333 1.333a.377.377 0 1 1-.375.655l-.567-.324v7.544a.377.377 0 1 1-.755 0V7.676l-1.01-.578a.377.377 0 0 1-.141-.515Z" fill="currentColor"/>
</svg>
...@@ -13,7 +13,8 @@ const paths = { ...@@ -13,7 +13,8 @@ const paths = {
blocks: `${ BASE_PATH }/blocks`, blocks: `${ BASE_PATH }/blocks`,
block: `${ BASE_PATH }/block/[id]`, block: `${ BASE_PATH }/block/[id]`,
tokens: `${ BASE_PATH }/tokens`, tokens: `${ BASE_PATH }/tokens`,
token_index: `${ BASE_PATH }/token/[id]`, token_index: `${ BASE_PATH }/token/[hash]`,
token_instance_item: `${ BASE_PATH }/token/[hash]/instance/[id]`,
address_index: `${ BASE_PATH }/address/[id]`, address_index: `${ BASE_PATH }/address/[id]`,
address_contract_verification: `${ BASE_PATH }/address/[id]/contract_verifications/new`, address_contract_verification: `${ BASE_PATH }/address/[id]/contract_verifications/new`,
apps: `${ BASE_PATH }/apps`, apps: `${ BASE_PATH }/apps`,
......
...@@ -63,6 +63,9 @@ export const ROUTES = { ...@@ -63,6 +63,9 @@ export const ROUTES = {
pattern: PATHS.token_index, pattern: PATHS.token_index,
crossNetworkNavigation: true, crossNetworkNavigation: true,
}, },
token_instance_item: {
pattern: PATHS.token_instance_item,
},
// ADDRESSES // ADDRESSES
address_index: { address_index: {
......
import { defineStyle, defineStyleConfig } from '@chakra-ui/styled-system';
import { mode } from '@chakra-ui/theme-tools';
const baseStyle = defineStyle((props) => {
const { emptyColor, color } = props;
return {
borderColor: color || 'blue.500',
borderBottomColor: emptyColor || mode('blackAlpha.200', 'whiteAlpha.200')(props),
borderLeftColor: emptyColor || mode('blackAlpha.200', 'whiteAlpha.200')(props),
};
});
const Spinner = defineStyleConfig({
baseStyle,
defaultProps: {
size: 'md',
},
});
export default Spinner;
...@@ -12,6 +12,7 @@ import Modal from './Modal'; ...@@ -12,6 +12,7 @@ import Modal from './Modal';
import Popover from './Popover'; import Popover from './Popover';
import Radio from './Radio'; import Radio from './Radio';
import Skeleton from './Skeleton'; import Skeleton from './Skeleton';
import Spinner from './Spinner';
import Table from './Table'; import Table from './Table';
import Tabs from './Tabs'; import Tabs from './Tabs';
import Tag from './Tag'; import Tag from './Tag';
...@@ -34,6 +35,7 @@ const components = { ...@@ -34,6 +35,7 @@ const components = {
Popover, Popover,
Radio, Radio,
Skeleton, Skeleton,
Spinner,
Tabs, Tabs,
Table, Table,
Tag, Tag,
......
import type { AddressParam } from './addressParams'; import type { AddressParam } from './addressParams';
export interface TokenTransfer { export type ERC1155TotalPayload = {
type: string; value: string;
token_id: string;
}
export type TokenTransfer = (
{
token_type: 'ERC-20';
total: {
value: string;
};
} |
{
token_type: 'ERC-721';
total: {
token_id: string;
};
} |
{
token_type: 'ERC-1155';
total: ERC1155TotalPayload | Array<ERC1155TotalPayload>;
}
) & TokenTransferBase
interface TokenTransferBase {
type: 'token_transfer' | 'token_burning' | 'token_spawning' | 'token_minting';
txHash: string; txHash: string;
from: AddressParam; from: AddressParam;
to: AddressParam; to: AddressParam;
token_address: string; token_address: string;
token_symbol: string; token_symbol: string;
token_type: string;
total: {
value: string;
};
exchange_rate: string; exchange_rate: string;
} }
...@@ -3,13 +3,18 @@ import type { DecodedInput } from './decodedInput'; ...@@ -3,13 +3,18 @@ import type { DecodedInput } from './decodedInput';
import type { Fee } from './fee'; import type { Fee } from './fee';
import type { TokenTransfer } from './tokenTransfer'; import type { TokenTransfer } from './tokenTransfer';
export type TransactionRevertReason = {
raw: string;
decoded: string;
} | DecodedInput;
export interface Transaction { export interface Transaction {
hash: string; hash: string;
result: string; result: string;
confirmations: number; confirmations: number;
status: 'ok' | 'error' | null; status: 'ok' | 'error' | null;
block: number | null; block: number | null;
timestamp: string; timestamp: string | null;
confirmation_duration: Array<number>; confirmation_duration: Array<number>;
from: AddressParam; from: AddressParam;
to: AddressParam; to: AddressParam;
...@@ -18,7 +23,7 @@ export interface Transaction { ...@@ -18,7 +23,7 @@ export interface Transaction {
fee: Fee; fee: Fee;
gas_price: number; gas_price: number;
type: number; type: number;
gas_used: string; gas_used: string | null;
gas_limit: string; gas_limit: string;
max_fee_per_gas: number | null; max_fee_per_gas: number | null;
max_priority_fee_per_gas: number | null; max_priority_fee_per_gas: number | null;
...@@ -27,10 +32,7 @@ export interface Transaction { ...@@ -27,10 +32,7 @@ export interface Transaction {
tx_burnt_fee: number | null; tx_burnt_fee: number | null;
nonce: number; nonce: number;
position: number; position: number;
revert_reason: { revert_reason: TransactionRevertReason | null;
raw: string;
decoded: string;
} | null;
raw_input: string; raw_input: string;
decoded_input: DecodedInput | null; decoded_input: DecodedInput | null;
token_transfers: Array<TokenTransfer> | null; token_transfers: Array<TokenTransfer> | null;
......
...@@ -17,13 +17,11 @@ interface Props { ...@@ -17,13 +17,11 @@ interface Props {
} }
const BlocksTableItem = ({ data, isPending }: Props) => { const BlocksTableItem = ({ data, isPending }: Props) => {
const spinnerEmptyColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
return ( return (
<Tr> <Tr>
<Td fontSize="sm"> <Td fontSize="sm">
<Flex columnGap={ 2 } alignItems="center"> <Flex columnGap={ 2 } alignItems="center">
{ isPending && <Spinner size="sm" color="blue.500" emptyColor={ spinnerEmptyColor }/> } { isPending && <Spinner size="sm"/> }
<Link <Link
fontWeight={ 600 } fontWeight={ 600 }
href={ link('block', { id: String(data.height) }) } href={ link('block', { id: String(data.height) }) }
......
...@@ -33,12 +33,12 @@ const CurrencyValue = ({ value, currency = '', unit, exchangeRate, className, ac ...@@ -33,12 +33,12 @@ const CurrencyValue = ({ value, currency = '', unit, exchangeRate, className, ac
} }
usdContent = ( usdContent = (
<Text as="span" variant="secondary" whiteSpace="pre" fontWeight={ 400 }> (${ usdResult })</Text> <Text as="span" variant="secondary" fontWeight={ 400 }>(${ usdResult })</Text>
); );
} }
return ( return (
<Box as="span" className={ className }> <Box as="span" className={ className } display="inline-flex" rowGap={ 3 } columnGap={ 1 }>
<Text display="inline-block"> <Text display="inline-block">
{ valueResult }{ currency ? ` ${ currency }` : '' } { valueResult }{ currency ? ` ${ currency }` : '' }
</Text> </Text>
......
...@@ -12,7 +12,7 @@ interface Props { ...@@ -12,7 +12,7 @@ interface Props {
} }
const TokenSnippet = ({ symbol, hash, name, className }: Props) => { const TokenSnippet = ({ symbol, hash, name, className }: Props) => {
const url = link('token_index', { id: hash }); const url = link('token_index', { hash });
return ( return (
<Center className={ className } columnGap={ 1 }> <Center className={ className } columnGap={ 1 }>
......
...@@ -20,7 +20,7 @@ const AddressLink = ({ alias, type, className, truncation = 'dynamic', hash, id, ...@@ -20,7 +20,7 @@ const AddressLink = ({ alias, type, className, truncation = 'dynamic', hash, id,
if (type === 'transaction') { if (type === 'transaction') {
url = link('tx', { id: id || hash }); url = link('tx', { id: id || hash });
} else if (type === 'token') { } else if (type === 'token') {
url = link('token_index', { id: id || hash }); url = link('token_index', { hash: id || hash });
} else if (type === 'block') { } else if (type === 'block') {
url = link('block', { id: id || hash }); url = link('block', { id: id || hash });
} else { } else {
......
import { Flex, Link, Text, Icon, Box } from '@chakra-ui/react';
import React from 'react';
import nftIcon from 'icons/nft_shield.svg';
import link from 'lib/link/link';
import TokenSnippet from 'ui/shared/TokenSnippet';
interface Props {
value: string;
tokenId: string;
hash: string;
symbol: string;
}
const NftTokenTransferSnippet = (props: Props) => {
const num = props.value === '1' ? '' : props.value;
const url = link('token_instance_item', { hash: props.hash, id: props.tokenId });
return (
<Flex alignItems="center" columnGap={ 3 } rowGap={ 2 } flexWrap="wrap">
<Text fontWeight={ 500 } as="span">For { num } token ID:</Text>
<Box display="inline-flex" alignItems="center">
<Icon as={ nftIcon } boxSize={ 6 } mr={ 1 }/>
<Link href={ url } fontWeight={ 600 }>{ props.tokenId }</Link>
</Box>
<TokenSnippet symbol={ props.symbol } hash={ props.hash } name="Foo"/>
</Flex>
);
};
export default React.memo(NftTokenTransferSnippet);
...@@ -8,23 +8,71 @@ import { space } from 'lib/html-entities'; ...@@ -8,23 +8,71 @@ import { space } from 'lib/html-entities';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import CurrencyValue from 'ui/shared/CurrencyValue'; import CurrencyValue from 'ui/shared/CurrencyValue';
import TokenSnippet from 'ui/shared/TokenSnippet'; import TokenSnippet from 'ui/shared/TokenSnippet';
import NftTokenTransferSnippet from 'ui/tx/NftTokenTransferSnippet';
type Props = TTokenTransfer type Props = TTokenTransfer;
const TokenTransfer = (props: Props) => {
const isColumnLayout = props.token_type === 'ERC-1155' && Array.isArray(props.total);
const tokenSnippet = <TokenSnippet symbol={ props.token_symbol } hash={ props.token_address } name="Foo" ml={ 3 }/>;
const content = (() => {
switch (props.token_type) {
case 'ERC-20':
return (
<Flex>
<Text fontWeight={ 500 } as="span">For:{ space }
<CurrencyValue value={ props.total.value } unit="ether" exchangeRate={ props.exchange_rate } fontWeight={ 600 }/>
</Text>
{ tokenSnippet }
</Flex>
);
case 'ERC-721': {
return (
<NftTokenTransferSnippet
tokenId={ props.total.token_id }
value="1"
hash={ props.token_address }
symbol={ props.token_symbol }
/>
);
}
case 'ERC-1155': {
const items = Array.isArray(props.total) ? props.total : [ props.total ];
return items.map((item) => (
<NftTokenTransferSnippet
key={ item.token_id }
tokenId={ item.token_id }
value={ item.value }
hash={ props.token_address }
symbol={ props.token_symbol }
/>
));
}
}
})();
const TokenTransfer = ({ from, to, total, exchange_rate: exchangeRate, ...token }: Props) => {
return ( return (
<Flex alignItems="center" flexWrap="wrap" columnGap={ 3 } rowGap={ 3 }> <Flex
alignItems={ isColumnLayout ? 'flex-start' : 'center' }
flexWrap="wrap"
columnGap={ 3 }
rowGap={ 3 }
flexDir={ isColumnLayout ? 'column' : 'row' }
>
<Flex alignItems="center"> <Flex alignItems="center">
<AddressLink fontWeight="500" hash={ from.hash } truncation="constant"/> <AddressLink fontWeight="500" hash={ props.from.hash } truncation="constant"/>
<Icon as={ rightArrowIcon } boxSize={ 6 } mx={ 2 } color="gray.500"/> <Icon as={ rightArrowIcon } boxSize={ 6 } mx={ 2 } color="gray.500"/>
<AddressLink fontWeight="500" hash={ to.hash } truncation="constant"/> <AddressLink fontWeight="500" hash={ props.to.hash } truncation="constant"/>
</Flex>
<Flex flexDir="column" rowGap={ 5 }>
{ content }
</Flex> </Flex>
<Text fontWeight={ 500 } as="span">For:{ space }
<CurrencyValue value={ total.value.replaceAll(',', '') } unit="ether" exchangeRate={ exchangeRate } fontWeight={ 600 }/>
</Text>
<TokenSnippet symbol={ token.token_symbol } hash={ token.token_address } name="Foo"/>
</Flex> </Flex>
); );
}; };
export default TokenTransfer; export default React.memo(TokenTransfer);
import { Flex, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import type { TokenTransfer as TTokenTransfer } from 'types/api/tokenTransfer';
import TokenTransfer from 'ui/tx/TokenTransfer';
interface Props {
items: Array<TTokenTransfer>;
}
function getItemsNum(items: Array<TTokenTransfer>) {
const nonErc1155items = items.filter((item) => item.token_type !== 'ERC-1155').length;
const erc1155items = items
.filter((item) => item.token_type === 'ERC-1155')
.map((item) => {
if (Array.isArray(item.total)) {
return item.total.length;
}
return 1;
})
.reduce((sum, item) => sum + item, 0);
return nonErc1155items + erc1155items;
}
const TokenTransferList = ({ items }: Props) => {
const itemsNum = getItemsNum(items);
const hasScroll = itemsNum > 5;
const gradientStartColor = useColorModeValue('whiteAlpha.600', 'blackAlpha.600');
const gradientEndColor = useColorModeValue('whiteAlpha.900', 'blackAlpha.900');
return (
<Flex
flexDirection="column"
alignItems="flex-start"
rowGap={ 5 }
w="100%"
_after={ hasScroll ? {
position: 'absolute',
content: '""',
bottom: 0,
left: 0,
right: '20px',
height: '48px',
bgGradient: `linear(to-b, ${ gradientStartColor } 37.5%, ${ gradientEndColor } 77.5%)`,
} : undefined }
maxH={ hasScroll ? '200px' : 'auto' }
overflowY={ hasScroll ? 'scroll' : 'auto' }
pr={ hasScroll ? 5 : 0 }
pb={ hasScroll ? 10 : 0 }
>
{ items.map((item, index) => <TokenTransfer key={ index } { ...item }/>) }
</Flex>
);
};
export default React.memo(TokenTransferList);
...@@ -111,45 +111,49 @@ const TxDecodedInputData = ({ data }: Props) => { ...@@ -111,45 +111,49 @@ const TxDecodedInputData = ({ data }: Props) => {
{ data.method_call } { data.method_call }
</GridItem> </GridItem>
{ /* TABLE INSIDE OF BLOCK */ } { /* TABLE INSIDE OF BLOCK */ }
<GridItem { data.parameters.length > 0 && (
pl={ PADDING } <>
pr={ GAP } <GridItem
pt={ PADDING } pl={ PADDING }
pb={ 1 } pr={ GAP }
bgColor={ bgColor } pt={ PADDING }
fontWeight={ 600 } pb={ 1 }
> bgColor={ bgColor }
fontWeight={ 600 }
>
Name Name
</GridItem> </GridItem>
<GridItem <GridItem
pr={ GAP } pr={ GAP }
pt={ PADDING } pt={ PADDING }
pb={ 1 } pb={ 1 }
bgColor={ bgColor } bgColor={ bgColor }
fontWeight={ 600 } fontWeight={ 600 }
> >
Type Type
</GridItem> </GridItem>
{ hasIndexed && ( { hasIndexed && (
<GridItem <GridItem
pr={ GAP } pr={ GAP }
pt={ PADDING } pt={ PADDING }
pb={ 1 } pb={ 1 }
bgColor={ bgColor } bgColor={ bgColor }
fontWeight={ 600 } fontWeight={ 600 }
> >
Inde<wbr/>xed? Inde<wbr/>xed?
</GridItem> </GridItem>
) } ) }
<GridItem <GridItem
pr={ PADDING } pr={ PADDING }
pt={ PADDING } pt={ PADDING }
pb={ 1 } pb={ 1 }
bgColor={ bgColor } bgColor={ bgColor }
fontWeight={ 600 } fontWeight={ 600 }
> >
Data Data
</GridItem> </GridItem>
</>
) }
{ data.parameters.map(({ name, type, value, indexed }, index) => { { data.parameters.map(({ name, type, value, indexed }, index) => {
return ( return (
<TableRow key={ name } name={ name } type={ type } isLast={ index === data.parameters.length - 1 } indexed={ indexed }> <TableRow key={ name } name={ name } type={ type } isLast={ index === data.parameters.length - 1 } indexed={ indexed }>
...@@ -160,7 +164,7 @@ const TxDecodedInputData = ({ data }: Props) => { ...@@ -160,7 +164,7 @@ const TxDecodedInputData = ({ data }: Props) => {
</Address> </Address>
) : ( ) : (
<Flex alignItems="flex-start" justifyContent="space-between" whiteSpace="normal" wordBreak="break-all"> <Flex alignItems="flex-start" justifyContent="space-between" whiteSpace="normal" wordBreak="break-all">
<Text>{ value }</Text> <Text>{ String(value) }</Text>
<CopyToClipboard text={ value }/> <CopyToClipboard text={ value }/>
</Flex> </Flex>
) } ) }
......
This diff is collapsed.
import { Grid, GridItem, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import type { TransactionRevertReason } from 'types/api/transaction';
import TxDecodedInputData from 'ui/tx/TxDecodedInputData';
type Props = TransactionRevertReason;
const TxRevertReason = (props: Props) => {
const bgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
if ('raw' in props) {
return (
<Grid
bgColor={ bgColor }
p={ 4 }
fontSize="sm"
borderRadius="md"
templateColumns="auto minmax(0, 1fr)"
rowGap={ 2 }
columnGap={ 4 }
whiteSpace="normal"
>
<GridItem fontWeight={ 500 }>Raw:</GridItem>
<GridItem>{ props.raw }</GridItem>
<GridItem fontWeight={ 500 }>Decoded:</GridItem>
<GridItem>{ props.decoded }</GridItem>
</Grid>
);
}
return <TxDecodedInputData data={ props }/>;
};
export default React.memo(TxRevertReason);
...@@ -26,7 +26,7 @@ const TxInternalTableItem = ({ type, from, to, value, success, error }: Props) = ...@@ -26,7 +26,7 @@ const TxInternalTableItem = ({ type, from, to, value, success, error }: Props) =
<TxStatus status={ success ? 'ok' : 'error' } errorText={ error }/> <TxStatus status={ success ? 'ok' : 'error' } errorText={ error }/>
</Td> </Td>
<Td> <Td>
<Address> <Address display="inline-flex" maxW="100%">
<AddressIcon hash={ from.hash }/> <AddressIcon hash={ from.hash }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ from.hash } alias={ from.name } flexGrow={ 1 }/> <AddressLink ml={ 2 } fontWeight="500" hash={ from.hash } alias={ from.name } flexGrow={ 1 }/>
</Address> </Address>
...@@ -35,16 +35,16 @@ const TxInternalTableItem = ({ type, from, to, value, success, error }: Props) = ...@@ -35,16 +35,16 @@ const TxInternalTableItem = ({ type, from, to, value, success, error }: Props) =
<Icon as={ rightArrowIcon } boxSize={ 6 } color="gray.500"/> <Icon as={ rightArrowIcon } boxSize={ 6 } color="gray.500"/>
</Td> </Td>
<Td> <Td>
<Address> <Address display="inline-flex" maxW="100%">
<AddressIcon hash={ to.hash }/> <AddressIcon hash={ to.hash }/>
<AddressLink hash={ to.hash } alias={ to.name } fontWeight="500" ml={ 2 }/> <AddressLink hash={ to.hash } alias={ to.name } fontWeight="500" ml={ 2 }/>
</Address> </Address>
</Td> </Td>
<Td isNumeric> <Td isNumeric verticalAlign="middle">
{ value } { value }
</Td> </Td>
{ /* no gas limit in api yet */ } { /* no gas limit in api yet */ }
{ /* <Td isNumeric> { /* <Td isNumeric verticalAlign='middle'>
{ gasLimit.toLocaleString('en') } { gasLimit.toLocaleString('en') }
</Td> */ } </Td> */ }
</Tr> </Tr>
......
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