Commit c256c241 authored by tom goriunov's avatar tom goriunov Committed by GitHub

Merge pull request #227 from blockscout/decode-tx-data

decode tx data
parents ca51d193 ca1a7da6
export default function hexToAddress(hex: string) {
const shortenHex = hex.slice(0, 66);
return shortenHex.slice(0, 2) + shortenHex.slice(26);
}
export default function hexToBytes(hex: string) {
const bytes = [];
for (let c = 0; c < hex.length; c += 2) {
bytes.push(parseInt(hex.substring(c, c + 2), 16));
}
return bytes;
}
import hexToBytes from 'lib/hexToBytes';
export default function hexToUtf8(hex: string) {
const utf8decoder = new TextDecoder();
const bytes = new Uint8Array(hexToBytes(hex));
return utf8decoder.decode(bytes);
}
...@@ -73,6 +73,10 @@ export const ROUTES = { ...@@ -73,6 +73,10 @@ export const ROUTES = {
pattern: `${ BASE_PATH }/address/[id]`, pattern: `${ BASE_PATH }/address/[id]`,
crossNetworkNavigation: true, crossNetworkNavigation: true,
}, },
address_contract_verification: {
pattern: `${ BASE_PATH }/address/[id]/contract_verifications/new`,
crossNetworkNavigation: true,
},
// APPS // APPS
apps: { apps: {
......
import { Box, Flex, Select, Textarea } from '@chakra-ui/react'; import { Box, Flex, Select, Textarea } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import hexToUtf8 from 'lib/hexToUtf8';
import CopyToClipboard from 'ui/shared/CopyToClipboard'; import CopyToClipboard from 'ui/shared/CopyToClipboard';
type DataType = 'Hex' | 'UTF-8' type DataType = 'Hex' | 'UTF-8'
...@@ -26,7 +27,7 @@ const RawInputData = ({ hex }: Props) => { ...@@ -26,7 +27,7 @@ const RawInputData = ({ hex }: Props) => {
<CopyToClipboard text={ hex }/> <CopyToClipboard text={ hex }/>
</Flex> </Flex>
<Textarea <Textarea
value={ selectedDataType === 'Hex' ? hex : 'UTF-8 equivalent' } value={ selectedDataType === 'Hex' ? hex : hexToUtf8(hex) }
w="100%" w="100%"
maxH="220px" maxH="220px"
mt={ 2 } mt={ 2 }
...@@ -38,4 +39,4 @@ const RawInputData = ({ hex }: Props) => { ...@@ -38,4 +39,4 @@ const RawInputData = ({ hex }: Props) => {
); );
}; };
export default RawInputData; export default React.memo(RawInputData);
import { Text, Grid, GridItem, Link, Tooltip, Button, Icon, useColorModeValue } from '@chakra-ui/react'; import { Text, Grid, GridItem, Tooltip, Button, useColorModeValue, Alert, Link } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { Log } from 'types/api/log'; import type { Log } from 'types/api/log';
import searchIcon from 'icons/search.svg'; // import searchIcon from 'icons/search.svg';
import { space } from 'lib/html-entities';
import useLink from 'lib/link/useLink';
import Address from 'ui/shared/address/Address'; import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon'; import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
...@@ -22,6 +24,7 @@ const TxLogItem = ({ address, index, topics, data, decoded }: Props) => { ...@@ -22,6 +24,7 @@ const TxLogItem = ({ address, index, topics, data, decoded }: Props) => {
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200'); const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
const dataBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50'); const dataBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
const link = useLink();
return ( return (
<Grid <Grid
...@@ -36,17 +39,26 @@ const TxLogItem = ({ address, index, topics, data, decoded }: Props) => { ...@@ -36,17 +39,26 @@ const TxLogItem = ({ address, index, topics, data, decoded }: Props) => {
pt: 0, pt: 0,
}} }}
> >
{ !decoded && (
<GridItem colSpan={{ base: 1, lg: 2 }}>
<Alert status="warning" display="inline-table" whiteSpace="normal">
To see accurate decoded input data, the contract must be verified.{ space }
<Link href={ link('address_contract_verification', { id: address.hash }) }>Verify the contract here</Link>
</Alert>
</GridItem>
) }
<RowHeader>Address</RowHeader> <RowHeader>Address</RowHeader>
<GridItem display="flex" alignItems="center"> <GridItem display="flex" alignItems="center">
<Address> <Address mr={{ base: 9, lg: 0 }}>
<AddressIcon hash={ address.hash }/> <AddressIcon hash={ address.hash }/>
<AddressLink hash={ address.hash } alias={ address.name } ml={ 2 }/> <AddressLink hash={ address.hash } alias={ address.name } ml={ 2 }/>
</Address> </Address>
<Tooltip label="Find matches topic"> { /* api doesn't have find topic feature yet */ }
{ /* <Tooltip label="Find matches topic">
<Link ml={ 2 } mr={{ base: 9, lg: 0 }} display="inline-flex"> <Link ml={ 2 } mr={{ base: 9, lg: 0 }} display="inline-flex">
<Icon as={ searchIcon } boxSize={ 5 }/> <Icon as={ searchIcon } boxSize={ 5 }/>
</Link> </Link>
</Tooltip> </Tooltip> */ }
<Tooltip label="Log index"> <Tooltip label="Log index">
<Button variant="outline" colorScheme="gray" isActive ml="auto" size="sm" fontWeight={ 400 }> <Button variant="outline" colorScheme="gray" isActive ml="auto" size="sm" fontWeight={ 400 }>
{ index } { index }
...@@ -63,7 +75,13 @@ const TxLogItem = ({ address, index, topics, data, decoded }: Props) => { ...@@ -63,7 +75,13 @@ const TxLogItem = ({ address, index, topics, data, decoded }: Props) => {
) } ) }
<RowHeader>Topics</RowHeader> <RowHeader>Topics</RowHeader>
<GridItem> <GridItem>
{ topics.filter(Boolean).map((item, index) => <TxLogTopic key={ index } hex={ item } index={ index }/>) } { topics.filter(Boolean).map((item, index) => (
<TxLogTopic
key={ index }
hex={ item }
index={ index }
/>
)) }
</GridItem> </GridItem>
<RowHeader>Data</RowHeader> <RowHeader>Data</RowHeader>
<GridItem p={ 4 } fontSize="sm" borderRadius="md" bgColor={ dataBgColor }> <GridItem p={ 4 } fontSize="sm" borderRadius="md" bgColor={ dataBgColor }>
......
import { Flex, Button, Select, Box } from '@chakra-ui/react'; import { Flex, Button, Select, Box } from '@chakra-ui/react';
import capitalize from 'lodash/capitalize';
import React from 'react'; import React from 'react';
import hexToAddress from 'lib/hexToAddress';
import hexToUtf8 from 'lib/hexToUtf8';
import Address from 'ui/shared/address/Address';
import AddressLink from 'ui/shared/address/AddressLink';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
interface Props { interface Props {
...@@ -8,36 +14,71 @@ interface Props { ...@@ -8,36 +14,71 @@ interface Props {
index: number; index: number;
} }
type DataType = 'Hex' | 'Dec' type DataType = 'hex' | 'text' | 'address' | 'number';
const OPTIONS: Array<DataType> = [ 'Hex', 'Dec' ];
const VALUE_CONVERTERS: Record<DataType, (hex: string) => string> = {
hex: (hex) => hex,
text: hexToUtf8,
address: hexToAddress,
number: (hex) => BigInt(hex).toString(),
};
const OPTIONS: Array<DataType> = [ 'hex', 'address', 'text', 'number' ];
const TxLogTopic = ({ hex, index }: Props) => { const TxLogTopic = ({ hex, index }: Props) => {
const [ selectedDataType, setSelectedDataType ] = React.useState<DataType>('Hex'); const [ selectedDataType, setSelectedDataType ] = React.useState<DataType>('hex');
const handleSelectChange = React.useCallback((event: React.ChangeEvent<HTMLSelectElement>) => { const handleSelectChange = React.useCallback((event: React.ChangeEvent<HTMLSelectElement>) => {
setSelectedDataType(event.target.value as DataType); setSelectedDataType(event.target.value as DataType);
}, []); }, []);
const value = VALUE_CONVERTERS[selectedDataType.toLowerCase() as Lowercase<DataType>](hex);
const content = (() => {
switch (selectedDataType) {
case 'hex':
case 'number':
case 'text': {
return (
<>
<Box overflow="hidden" whiteSpace="nowrap">
<HashStringShortenDynamic hash={ value }/>
</Box>
<CopyToClipboard text={ value }/>
</>
);
}
case 'address': {
return (
<Address>
<AddressLink hash={ value }/>
<CopyToClipboard text={ value }/>
</Address>
);
}
}
})();
return ( return (
<Flex alignItems="center" px={{ base: 0, lg: 3 }} _notFirst={{ mt: 3 }} overflow="hidden" maxW="100%"> <Flex alignItems="center" px={{ base: 0, lg: 3 }} _notFirst={{ mt: 3 }} overflow="hidden" maxW="100%">
<Button variant="outline" colorScheme="gray" isActive size="xs" fontWeight={ 400 } mr={ 3 } w={ 6 }> <Button variant="outline" colorScheme="gray" isActive size="xs" fontWeight={ 400 } mr={ 3 } w={ 6 }>
{ index } { index }
</Button> </Button>
<Select { index !== 0 && (
size="sm" <Select
borderRadius="base" size="sm"
value={ selectedDataType } borderRadius="base"
onChange={ handleSelectChange } value={ selectedDataType }
focusBorderColor="none" onChange={ handleSelectChange }
w="75px" focusBorderColor="none"
mr={ 3 } mr={ 3 }
flexShrink={ 0 } flexShrink={ 0 }
> w="auto"
{ OPTIONS.map((option) => <option key={ option } value={ option }>{ option }</option>) } >
</Select> { OPTIONS.map((option) => <option key={ option } value={ option }>{ capitalize(option) }</option>) }
<Box overflow="hidden" whiteSpace="nowrap"> </Select>
<HashStringShortenDynamic hash={ hex }/> ) }
</Box> { content }
</Flex> </Flex>
); );
}; };
......
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