Commit 1cec1447 authored by tom goriunov's avatar tom goriunov Committed by GitHub

Merge pull request #264 from blockscout/tx-api-update

tx api update
parents 37c11d04 43f1e0dd
export interface AddressTag {
label: string;
display_name: string;
address_hash: string;
}
export interface WatchlistName {
label: string;
display_name: string;
}
export interface AddressParam { export interface AddressParam {
hash: string; hash: string;
implementation_name: string; implementation_name: string;
name: string; name: string | null;
is_contract: boolean; is_contract: boolean;
private_tags: Array<AddressTag> | null;
watchlist_names: Array<WatchlistName> | null;
public_tags: Array<AddressTag> | null;
} }
...@@ -10,10 +10,11 @@ export interface InternalTransaction { ...@@ -10,10 +10,11 @@ export interface InternalTransaction {
from: AddressParam; from: AddressParam;
to: AddressParam; to: AddressParam;
created_contract: AddressParam; created_contract: AddressParam;
value: number; value: string;
index: number; index: number;
block: number; block: number;
timestamp: string; timestamp: string;
gas_limit: string;
} }
export interface InternalTransactionsResponse { export interface InternalTransactionsResponse {
......
export interface Reward { export interface Reward {
reward: number; reward: string;
type: 'Miner Reward' | 'Validator Reward' | 'Emission Reward' | 'Chore Reward' | 'Uncle Reward'; type: 'Miner Reward' | 'Validator Reward' | 'Emission Reward' | 'Chore Reward' | 'Uncle Reward';
} }
export type TokenType = 'ERC-20' | 'ERC-721' | 'ERC-1155';
export interface TokenInfo {
address: string;
type: TokenType;
symbol: string | null;
name: string | null;
decimals: string | null;
holders: string | null;
exchange_rate: string | null;
}
export type TokenInfoGeneric<Type extends TokenType> = Omit<TokenInfo, 'type'> & { type: Type };
import type { AddressParam } from './addressParams'; import type { AddressParam } from './addressParams';
import type { TokenInfoGeneric } from './tokenInfo';
export type ERC1155TotalPayload = { export type Erc20TotalPayload = {
decimals: string | null;
value: string;
}
export type Erc721TotalPayload = {
token_id: string;
}
export type Erc1155TotalPayload = {
decimals: string | null;
value: string; value: string;
token_id: string; token_id: string;
} }
export type TokenTransfer = ( export type TokenTransfer = (
{ {
token_type: 'ERC-20'; token: TokenInfoGeneric<'ERC-20'>;
total: { total: Erc20TotalPayload;
value: string;
};
} | } |
{ {
token_type: 'ERC-721'; token: TokenInfoGeneric<'ERC-721'>;
total: { total: Erc721TotalPayload;
token_id: string;
};
} | } |
{ {
token_type: 'ERC-1155'; token: TokenInfoGeneric<'ERC-1155'>;
total: ERC1155TotalPayload | Array<ERC1155TotalPayload>; total: Erc1155TotalPayload | Array<Erc1155TotalPayload>;
} }
) & TokenTransferBase ) & TokenTransferBase
interface TokenTransferBase { interface TokenTransferBase {
type: 'token_transfer' | 'token_burning' | 'token_spawning' | 'token_minting'; type: 'token_transfer' | 'token_burning' | 'token_spawning' | 'token_minting';
txHash: string; tx_hash: string;
from: AddressParam; from: AddressParam;
to: AddressParam; to: AddressParam;
token_address: string;
token_symbol: string;
exchange_rate: string;
} }
...@@ -38,6 +38,7 @@ export interface Transaction { ...@@ -38,6 +38,7 @@ export interface Transaction {
token_transfers: Array<TokenTransfer> | null; token_transfers: Array<TokenTransfer> | null;
token_transfers_overflow: boolean; token_transfers_overflow: boolean;
exchange_rate: string; exchange_rate: string;
tx_tag: string | null;
} }
export interface TransactionsResponse { export interface TransactionsResponse {
......
...@@ -158,6 +158,19 @@ const BlockDetails = () => { ...@@ -158,6 +158,19 @@ const BlockDetails = () => {
) } ) }
</DetailsInfoItem> </DetailsInfoItem>
) } ) }
{ data.rewards
?.filter(({ type }) => type !== 'Validator Reward' && type !== 'Miner Reward')
.map(({ type, reward }) => (
<DetailsInfoItem
key={ type }
title={ type }
// is this text correct for validators?
hint={ `Amount of distributed reward. ${ capitalize(validatorTitle) }s receive a static block reward + Tx fees + uncle fees.` }
>
{ BigNumber(reward).dividedBy(WEI).toFixed() } { appConfig.network.currency.symbol }
</DetailsInfoItem>
))
}
{ sectionGap } { sectionGap }
...@@ -298,18 +311,6 @@ const BlockDetails = () => { ...@@ -298,18 +311,6 @@ const BlockDetails = () => {
> >
{ data.nonce } { data.nonce }
</DetailsInfoItem> </DetailsInfoItem>
{ data.rewards
?.filter(({ type }) => type !== 'Validator Reward' && type !== 'Miner Reward')
.map(({ type, reward }) => (
<DetailsInfoItem
key={ type }
title={ type }
// is this text correct for validators?
hint={ `Amount of distributed reward. ${ capitalize(validatorTitle) }s receive a static block reward + Tx fees + uncle fees.` }
>
{ BigNumber(reward).dividedBy(WEI).toFixed() } { appConfig.network.currency.symbol }
</DetailsInfoItem>
)) }
</> </>
) } ) }
</Grid> </Grid>
......
import { Flex, Link, Spinner, Text, Box, Icon, useColorModeValue } from '@chakra-ui/react'; import { Flex, Link, Spinner, Text, Box, Icon } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import capitalize from 'lodash/capitalize'; import capitalize from 'lodash/capitalize';
import React from 'react'; import React from 'react';
...@@ -7,8 +7,7 @@ import type { Block } from 'types/api/block'; ...@@ -7,8 +7,7 @@ import type { Block } from 'types/api/block';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import flameIcon from 'icons/flame.svg'; import flameIcon from 'icons/flame.svg';
import getBlockReward from 'lib/block/getBlockReward'; import { WEI, ZERO } from 'lib/consts';
import { WEI } from 'lib/consts';
import link from 'lib/link/link'; import link from 'lib/link/link';
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle'; import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
import BlockTimestamp from 'ui/blocks/BlockTimestamp'; import BlockTimestamp from 'ui/blocks/BlockTimestamp';
...@@ -24,14 +23,17 @@ interface Props { ...@@ -24,14 +23,17 @@ interface Props {
} }
const BlocksListItem = ({ data, isPending, enableTimeIncrement }: Props) => { const BlocksListItem = ({ data, isPending, enableTimeIncrement }: Props) => {
const spinnerEmptyColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200'); const totalReward = data.rewards
const { totalReward, burntFees, txFees } = getBlockReward(data); ?.map(({ reward }) => BigNumber(reward))
.reduce((result, item) => result.plus(item), ZERO) || ZERO;
const burntFees = BigNumber(data.burnt_fees || 0);
const txFees = BigNumber(data.tx_fees || 0);
return ( return (
<AccountListItemMobile rowGap={ 3 }> <AccountListItemMobile rowGap={ 3 }>
<Flex justifyContent="space-between" w="100%"> <Flex justifyContent="space-between" w="100%">
<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) }) }
......
...@@ -5,8 +5,7 @@ import React from 'react'; ...@@ -5,8 +5,7 @@ import React from 'react';
import type { Block } from 'types/api/block'; import type { Block } from 'types/api/block';
import flameIcon from 'icons/flame.svg'; import flameIcon from 'icons/flame.svg';
import getBlockReward from 'lib/block/getBlockReward'; import { WEI, ZERO } from 'lib/consts';
import { WEI } from 'lib/consts';
import link from 'lib/link/link'; import link from 'lib/link/link';
import BlockTimestamp from 'ui/blocks/BlockTimestamp'; import BlockTimestamp from 'ui/blocks/BlockTimestamp';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
...@@ -20,7 +19,11 @@ interface Props { ...@@ -20,7 +19,11 @@ interface Props {
} }
const BlocksTableItem = ({ data, isPending, enableTimeIncrement }: Props) => { const BlocksTableItem = ({ data, isPending, enableTimeIncrement }: Props) => {
const { totalReward, burntFees, txFees } = getBlockReward(data); const totalReward = data.rewards
?.map(({ reward }) => BigNumber(reward))
.reduce((result, item) => result.plus(item), ZERO) || ZERO;
const burntFees = BigNumber(data.burnt_fees || 0);
const txFees = BigNumber(data.tx_fees || 0);
return ( return (
<Tr> <Tr>
......
import { Flex, Link, Icon } from '@chakra-ui/react'; import { Flex, Link, Icon, Tag } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import type { Transaction } from 'types/api/transaction';
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 useFetch from 'lib/hooks/useFetch';
import isBrowser from 'lib/isBrowser';
import networkExplorers from 'lib/networks/networkExplorers'; 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';
...@@ -28,6 +31,15 @@ const TABS: Array<RoutedTab> = [ ...@@ -28,6 +31,15 @@ const TABS: Array<RoutedTab> = [
const TransactionPageContent = () => { const TransactionPageContent = () => {
const router = useRouter(); const router = useRouter();
const fetch = useFetch();
const { data } = useQuery<unknown, unknown, Transaction>(
[ 'tx', router.query.id ],
async() => await fetch(`/api/transactions/${ router.query.id }`),
{
enabled: Boolean(router.query.id),
},
);
const explorersLinks = networkExplorers const explorersLinks = networkExplorers
.filter((explorer) => explorer.paths.tx) .filter((explorer) => explorer.paths.tx)
...@@ -36,15 +48,19 @@ const TransactionPageContent = () => { ...@@ -36,15 +48,19 @@ const TransactionPageContent = () => {
return <ExternalLink key={ explorer.baseUrl } title={ `Open in ${ explorer.title }` } href={ url.toString() }/>; return <ExternalLink key={ explorer.baseUrl } title={ `Open in ${ explorer.title }` } href={ url.toString() }/>;
}); });
const hasGoBackLink = isBrowser() && window.document.referrer.includes('/txs');
return ( return (
<Page> <Page>
{ /* TODO should be shown only when navigating from txs list */ } { hasGoBackLink && (
<Link mb={ 6 } display="inline-flex" href={ link('txs') }> <Link mb={ 6 } display="inline-flex" href={ window.document.referrer }>
<Icon as={ eastArrowIcon } boxSize={ 6 } mr={ 2 } transform="rotate(180deg)"/> <Icon as={ eastArrowIcon } boxSize={ 6 } mr={ 2 } transform="rotate(180deg)"/>
Transactions Transactions
</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"/>
{ data?.tx_tag && <Tag my={ 2 } ml={ 3 }>{ data.tx_tag }</Tag> }
{ explorersLinks.length > 0 && ( { explorersLinks.length > 0 && (
<Flex <Flex
alignItems="center" alignItems="center"
......
...@@ -2,22 +2,18 @@ import { Box, Text, chakra } from '@chakra-ui/react'; ...@@ -2,22 +2,18 @@ import { Box, Text, chakra } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
import type { Unit } from 'types/unit';
import getValueWithUnit from 'lib/getValueWithUnit';
interface Props { interface Props {
value: string; value: string;
unit?: Unit;
currency?: string; currency?: string;
exchangeRate?: string | null; exchangeRate?: string | null;
className?: string; className?: string;
accuracy?: number; accuracy?: number;
accuracyUsd?: number; accuracyUsd?: number;
decimals?: string | null;
} }
const CurrencyValue = ({ value, currency = '', unit, exchangeRate, className, accuracy, accuracyUsd }: Props) => { const CurrencyValue = ({ value, currency = '', decimals, exchangeRate, className, accuracy, accuracyUsd }: Props) => {
const valueCurr = getValueWithUnit(value, unit); const valueCurr = BigNumber(value).div(BigNumber(10 ** Number(decimals || '18')));
const valueResult = accuracy ? valueCurr.dp(accuracy).toFormat() : valueCurr.toFormat(); const valueResult = accuracy ? valueCurr.dp(accuracy).toFormat() : valueCurr.toFormat();
let usdContent; let usdContent;
......
...@@ -7,7 +7,7 @@ const EmptyElement = () => null; ...@@ -7,7 +7,7 @@ const EmptyElement = () => null;
interface Props { interface Props {
hash: string; hash: string;
name?: string; name?: string | null;
className?: string; className?: string;
} }
......
...@@ -5,9 +5,9 @@ import link from 'lib/link/link'; ...@@ -5,9 +5,9 @@ import link from 'lib/link/link';
import TokenLogo from 'ui/shared/TokenLogo'; import TokenLogo from 'ui/shared/TokenLogo';
interface Props { interface Props {
symbol: string; symbol?: string | null;
hash: string; hash: string;
name: string; name?: string | null;
className?: string; className?: string;
} }
...@@ -20,7 +20,7 @@ const TokenSnippet = ({ symbol, hash, name, className }: Props) => { ...@@ -20,7 +20,7 @@ const TokenSnippet = ({ symbol, hash, name, className }: Props) => {
<Link href={ url } target="_blank"> <Link href={ url } target="_blank">
{ name } { name }
</Link> </Link>
<Text variant="secondary">({ symbol })</Text> { symbol && <Text variant="secondary">({ symbol })</Text> }
</Center> </Center>
); );
}; };
......
import { Link, chakra, shouldForwardProp, Tooltip, Box } from '@chakra-ui/react'; import { Link, chakra, shouldForwardProp, Tooltip, Box } from '@chakra-ui/react';
import type { HTMLAttributeAnchorTarget } from 'react';
import React from 'react'; import React from 'react';
import link from 'lib/link/link'; import link from 'lib/link/link';
...@@ -7,15 +8,16 @@ import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; ...@@ -7,15 +8,16 @@ import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
interface Props { interface Props {
type?: 'address' | 'transaction' | 'token' | 'block'; type?: 'address' | 'transaction' | 'token' | 'block';
alias?: string; alias?: string | null;
className?: string; className?: string;
hash: string; hash: string;
truncation?: 'constant' | 'dynamic'| 'none'; truncation?: 'constant' | 'dynamic'| 'none';
fontWeight?: string; fontWeight?: string;
id?: string; id?: string;
target?: HTMLAttributeAnchorTarget;
} }
const AddressLink = ({ alias, type, className, truncation = 'dynamic', hash, id, fontWeight }: Props) => { const AddressLink = ({ alias, type, className, truncation = 'dynamic', hash, id, fontWeight, target }: Props) => {
let url; let url;
if (type === 'transaction') { if (type === 'transaction') {
url = link('tx', { id: id || hash }); url = link('tx', { id: id || hash });
...@@ -49,7 +51,7 @@ const AddressLink = ({ alias, type, className, truncation = 'dynamic', hash, id, ...@@ -49,7 +51,7 @@ const AddressLink = ({ alias, type, className, truncation = 'dynamic', hash, id,
<Link <Link
className={ className } className={ className }
href={ url } href={ url }
target="_blank" target={ target || '_blank' }
overflow="hidden" overflow="hidden"
whiteSpace="nowrap" whiteSpace="nowrap"
> >
......
...@@ -3,27 +3,33 @@ import React from 'react'; ...@@ -3,27 +3,33 @@ import React from 'react';
import nftIcon from 'icons/nft_shield.svg'; import nftIcon from 'icons/nft_shield.svg';
import link from 'lib/link/link'; import link from 'lib/link/link';
import AddressLink from 'ui/shared/address/AddressLink';
import TokenSnippet from 'ui/shared/TokenSnippet'; import TokenSnippet from 'ui/shared/TokenSnippet';
interface Props { interface Props {
value: string; value: string;
tokenId: string; tokenId: string;
hash: string; hash: string;
symbol: string; name?: string | null;
symbol?: string | null;
} }
const NftTokenTransferSnippet = (props: Props) => { const NftTokenTransferSnippet = ({ value, name, hash, symbol, tokenId }: Props) => {
const num = props.value === '1' ? '' : props.value; const num = value === '1' ? '' : value;
const url = link('token_instance_item', { hash: props.hash, id: props.tokenId }); const url = link('token_instance_item', { hash: hash, id: tokenId });
return ( return (
<Flex alignItems="center" columnGap={ 3 } rowGap={ 2 } flexWrap="wrap"> <Flex alignItems="center" columnGap={ 3 } rowGap={ 2 } flexWrap="wrap">
<Text fontWeight={ 500 } as="span">For { num } token ID:</Text> <Text fontWeight={ 500 } as="span">For { num } token ID:</Text>
<Box display="inline-flex" alignItems="center"> <Box display="inline-flex" alignItems="center">
<Icon as={ nftIcon } boxSize={ 6 } mr={ 1 }/> <Icon as={ nftIcon } boxSize={ 6 } mr={ 1 }/>
<Link href={ url } fontWeight={ 600 }>{ props.tokenId }</Link> <Link href={ url } fontWeight={ 600 }>{ tokenId }</Link>
</Box> </Box>
<TokenSnippet symbol={ props.symbol } hash={ props.hash } name="Foo"/> { name ? (
<TokenSnippet symbol={ symbol } hash={ hash } name={ name }/>
) : (
<AddressLink hash={ hash } truncation="constant" type="token"/>
) }
</Flex> </Flex>
); );
}; };
......
import { Flex, Icon, Text } from '@chakra-ui/react'; import { Flex, Icon, Text } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { TokenTransfer as TTokenTransfer } from 'types/api/tokenTransfer'; import type { TokenTransfer as TTokenTransfer, Erc20TotalPayload, Erc721TotalPayload, Erc1155TotalPayload } from 'types/api/tokenTransfer';
import rightArrowIcon from 'icons/arrows/east.svg'; import rightArrowIcon from 'icons/arrows/east.svg';
import { space } from 'lib/html-entities'; import { space } from 'lib/html-entities';
...@@ -12,43 +12,47 @@ import NftTokenTransferSnippet from 'ui/tx/NftTokenTransferSnippet'; ...@@ -12,43 +12,47 @@ import NftTokenTransferSnippet from 'ui/tx/NftTokenTransferSnippet';
type Props = TTokenTransfer; type Props = TTokenTransfer;
const TokenTransfer = (props: Props) => { const TokenTransfer = ({ token, total, to, from }: Props) => {
const isColumnLayout = props.token_type === 'ERC-1155' && Array.isArray(props.total); const isColumnLayout = token.type === 'ERC-1155' && Array.isArray(total);
const tokenSnippet = <TokenSnippet symbol={ props.token_symbol } hash={ props.token_address } name="Foo" ml={ 3 }/>; const tokenSnippet = <TokenSnippet symbol={ token.symbol } hash={ token.address } name={ token.name } ml={ 3 }/>;
const content = (() => { const content = (() => {
switch (props.token_type) { switch (token.type) {
case 'ERC-20': case 'ERC-20': {
const payload = total as Erc20TotalPayload;
return ( return (
<Flex> <Flex>
<Text fontWeight={ 500 } as="span">For:{ space } <Text fontWeight={ 500 } as="span">For:{ space }
<CurrencyValue value={ props.total.value } unit="ether" exchangeRate={ props.exchange_rate } fontWeight={ 600 }/> <CurrencyValue value={ payload.value } exchangeRate={ token.exchange_rate } fontWeight={ 600 }/>
</Text> </Text>
{ tokenSnippet } { tokenSnippet }
</Flex> </Flex>
); );
}
case 'ERC-721': { case 'ERC-721': {
const payload = total as Erc721TotalPayload;
return ( return (
<NftTokenTransferSnippet <NftTokenTransferSnippet
tokenId={ props.total.token_id } tokenId={ payload.token_id }
value="1" value="1"
hash={ props.token_address } hash={ token.address }
symbol={ props.token_symbol } symbol={ token.symbol }
/> />
); );
} }
case 'ERC-1155': { case 'ERC-1155': {
const items = Array.isArray(props.total) ? props.total : [ props.total ]; const payload = total as Erc1155TotalPayload | Array<Erc1155TotalPayload>;
const items = Array.isArray(payload) ? payload : [ payload ];
return items.map((item) => ( return items.map((item) => (
<NftTokenTransferSnippet <NftTokenTransferSnippet
key={ item.token_id } key={ item.token_id }
tokenId={ item.token_id } tokenId={ item.token_id }
value={ item.value } value={ item.value }
hash={ props.token_address } hash={ token.address }
symbol={ props.token_symbol } symbol={ token.symbol }
/> />
)); ));
} }
...@@ -64,9 +68,9 @@ const TokenTransfer = (props: Props) => { ...@@ -64,9 +68,9 @@ const TokenTransfer = (props: Props) => {
flexDir={ isColumnLayout ? 'column' : 'row' } flexDir={ isColumnLayout ? 'column' : 'row' }
> >
<Flex alignItems="center"> <Flex alignItems="center">
<AddressLink fontWeight="500" hash={ props.from.hash } truncation="constant"/> <AddressLink fontWeight="500" hash={ 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={ props.to.hash } truncation="constant"/> <AddressLink fontWeight="500" hash={ to.hash } truncation="constant"/>
</Flex> </Flex>
<Flex flexDir="column" rowGap={ 5 }> <Flex flexDir="column" rowGap={ 5 }>
{ content } { content }
......
...@@ -10,9 +10,9 @@ interface Props { ...@@ -10,9 +10,9 @@ interface Props {
} }
function getItemsNum(items: Array<TTokenTransfer>) { function getItemsNum(items: Array<TTokenTransfer>) {
const nonErc1155items = items.filter((item) => item.token_type !== 'ERC-1155').length; const nonErc1155items = items.filter((item) => item.token.type !== 'ERC-1155').length;
const erc1155items = items const erc1155items = items
.filter((item) => item.token_type === 'ERC-1155') .filter((item) => item.token.type === 'ERC-1155')
.map((item) => { .map((item) => {
if (Array.isArray(item.total)) { if (Array.isArray(item.total)) {
return item.total.length; return item.total.length;
......
import { Grid, GridItem, Text, Box, Icon, Link, Spinner } from '@chakra-ui/react'; import { Grid, GridItem, Text, Box, Icon, Link, Spinner, Tag, Flex, Tooltip, chakra } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
...@@ -11,9 +11,9 @@ import { QueryKeys } from 'types/client/queries'; ...@@ -11,9 +11,9 @@ import { QueryKeys } from 'types/client/queries';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import clockIcon from 'icons/clock.svg'; import clockIcon from 'icons/clock.svg';
import flameIcon from 'icons/flame.svg'; import flameIcon from 'icons/flame.svg';
import errorIcon from 'icons/status/error.svg';
import successIcon from 'icons/status/success.svg';
import { WEI, WEI_IN_GWEI } from 'lib/consts'; import { WEI, WEI_IN_GWEI } from 'lib/consts';
// import errorIcon from 'icons/status/error.svg';
// import successIcon from 'icons/status/success.svg';
import dayjs from 'lib/date/dayjs'; import dayjs from 'lib/date/dayjs';
import useFetch from 'lib/hooks/useFetch'; import useFetch from 'lib/hooks/useFetch';
import getConfirmationDuration from 'lib/tx/getConfirmationDuration'; import getConfirmationDuration from 'lib/tx/getConfirmationDuration';
...@@ -73,6 +73,18 @@ const TxDetails = () => { ...@@ -73,6 +73,18 @@ const TxDetails = () => {
return <DataFetchAlert/>; return <DataFetchAlert/>;
} }
const addressFromTags = [
...data.from.private_tags || [],
...data.from.public_tags || [],
...data.from.watchlist_names || [],
].map((tag) => <Tag key={ tag.label }>{ tag.display_name }</Tag>);
const addressToTags = [
...data.to.private_tags || [],
...data.to.public_tags || [],
...data.to.watchlist_names || [],
].map((tag) => <Tag key={ tag.label }>{ tag.display_name }</Tag>);
return ( return (
<Grid columnGap={ 8 } rowGap={{ base: 3, lg: 3 }} templateColumns={{ base: 'minmax(0, 1fr)', lg: 'auto minmax(0, 1fr)' }}> <Grid columnGap={ 8 } rowGap={{ base: 3, lg: 3 }} templateColumns={{ base: 'minmax(0, 1fr)', lg: 'auto minmax(0, 1fr)' }}>
<DetailsInfoItem <DetailsInfoItem
...@@ -132,36 +144,51 @@ const TxDetails = () => { ...@@ -132,36 +144,51 @@ const TxDetails = () => {
<DetailsInfoItem <DetailsInfoItem
title="From" title="From"
hint="Address (external or contract) sending the transaction." hint="Address (external or contract) sending the transaction."
columnGap={ 3 }
> >
<Address> <Address>
<AddressIcon hash={ data.from.hash }/> <AddressIcon hash={ data.from.hash }/>
<AddressLink ml={ 2 } hash={ data.from.hash } alias={ data.from.name }/> <AddressLink ml={ 2 } hash={ data.from.hash }/>
<CopyToClipboard text={ data.from.hash }/> <CopyToClipboard text={ data.from.hash }/>
</Address> </Address>
{ data.from.name && <Text>{ data.from.name }</Text> }
{ addressFromTags.length > 0 && (
<Flex columnGap={ 3 }>
{ addressFromTags }
</Flex>
) }
</DetailsInfoItem> </DetailsInfoItem>
<DetailsInfoItem <DetailsInfoItem
title={ data.to.is_contract ? 'Interacted with contract' : 'To' } title={ data.to.is_contract ? 'Interacted with contract' : 'To' }
hint="Address (external or contract) receiving the transaction." hint="Address (external or contract) receiving the transaction."
flexWrap={{ base: 'wrap', lg: 'nowrap' }} flexWrap={{ base: 'wrap', lg: 'nowrap' }}
columnGap={ 3 }
> >
<Address mr={ 3 }> <Address>
<AddressIcon hash={ data.to.hash }/> <AddressIcon hash={ data.to.hash }/>
<AddressLink ml={ 2 } hash={ data.to.hash } alias={ data.to.name }/> <AddressLink ml={ 2 } hash={ data.to.hash }/>
<CopyToClipboard text={ data.to.hash }/> <CopyToClipboard text={ data.to.hash }/>
</Address> </Address>
{ /* todo_tom Nikita should add to api later */ } { data.to.name && <Text>{ data.to.name }</Text> }
{ /* <Tag colorScheme="orange" variant="solid" flexShrink={ 0 }>SANA</Tag> */ } { data.to.is_contract && data.result === 'success' && (
{ /* <Tooltip label="Contract execution completed"> <Tooltip label="Contract execution completed">
<chakra.span display="inline-flex"> <chakra.span display="inline-flex">
<Icon as={ successIcon } boxSize={ 4 } ml={ 2 } color="green.500" cursor="pointer"/> <Icon as={ successIcon } boxSize={ 4 } color="green.500" cursor="pointer"/>
</chakra.span> </chakra.span>
</Tooltip> */ } </Tooltip>
{ /* <Tooltip label="Error occured during contract execution"> ) }
<chakra.span display="inline-flex"> { data.to.is_contract && Boolean(data.status) && data.result !== 'success' && (
<Icon as={ errorIcon } boxSize={ 4 } ml={ 2 } color="red.500" cursor="pointer"/> <Tooltip label="Error occured during contract execution">
</chakra.span> <chakra.span display="inline-flex">
</Tooltip> */ } <Icon as={ errorIcon } boxSize={ 4 } color="red.500" cursor="pointer"/>
{ /* <TokenSnippet symbol="UP" name="User Pay" hash="0xA17ed5dFc62D0a3E74D69a0503AE9FdA65d9f212" ml={ 3 }/> */ } </chakra.span>
</Tooltip>
) }
{ addressToTags.length > 0 && (
<Flex columnGap={ 3 }>
{ addressToTags }
</Flex>
) }
</DetailsInfoItem> </DetailsInfoItem>
{ TOKEN_TRANSFERS.map(({ title, hint, type }) => { { TOKEN_TRANSFERS.map(({ title, hint, type }) => {
const items = data.token_transfers?.filter((token) => token.type === type) || []; const items = data.token_transfers?.filter((token) => token.type === type) || [];
......
...@@ -43,16 +43,15 @@ const sortFn = (sort: Sort | undefined) => (a: InternalTransaction, b: InternalT ...@@ -43,16 +43,15 @@ const sortFn = (sort: Sort | undefined) => (a: InternalTransaction, b: InternalT
return a.value === b.value ? 0 : result; return a.value === b.value ? 0 : result;
} }
// no gas limit in api yet case 'gas-limit-desc': {
// case 'gas-limit-desc': { const result = a.gas_limit > b.gas_limit ? -1 : 1;
// const result = a.gasLimit > b.gasLimit ? -1 : 1; return a.gas_limit === b.gas_limit ? 0 : result;
// return a.gasLimit === b.gasLimit ? 0 : result; }
// }
case 'gas-limit-asc': {
// case 'gas-limit-asc': { const result = a.gas_limit > b.gas_limit ? 1 : -1;
// const result = a.gasLimit > b.gasLimit ? 1 : -1; return a.gas_limit === b.gas_limit ? 0 : result;
// return a.gasLimit === b.gasLimit ? 0 : result; }
// }
default: default:
return 0; return 0;
......
import { Flex, Tag, Icon, Box, HStack, Text } from '@chakra-ui/react'; import { Flex, Tag, Icon, Box, HStack, Text } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
import type { InternalTransaction } from 'types/api/internalTransaction'; import type { InternalTransaction } from 'types/api/internalTransaction';
...@@ -14,7 +15,7 @@ import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils'; ...@@ -14,7 +15,7 @@ import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils';
type Props = InternalTransaction; type Props = InternalTransaction;
const TxInternalsListItem = ({ type, from, to, value, success, error }: Props) => { const TxInternalsListItem = ({ type, from, to, value, success, error, gas_limit: gasLimit }: Props) => {
const typeTitle = TX_INTERNALS_ITEMS.find(({ id }) => id === type)?.title; const typeTitle = TX_INTERNALS_ITEMS.find(({ id }) => id === type)?.title;
return ( return (
...@@ -38,11 +39,10 @@ const TxInternalsListItem = ({ type, from, to, value, success, error }: Props) = ...@@ -38,11 +39,10 @@ const TxInternalsListItem = ({ type, from, to, value, success, error }: Props) =
<Text fontSize="sm" fontWeight={ 500 }>Value { appConfig.network.currency.symbol }</Text> <Text fontSize="sm" fontWeight={ 500 }>Value { appConfig.network.currency.symbol }</Text>
<Text fontSize="sm" variant="secondary">{ value }</Text> <Text fontSize="sm" variant="secondary">{ value }</Text>
</HStack> </HStack>
{ /* no gas limit in api yet */ } <HStack spacing={ 3 }>
{ /* <HStack spacing={ 3 }>
<Text fontSize="sm" fontWeight={ 500 }>Gas limit</Text> <Text fontSize="sm" fontWeight={ 500 }>Gas limit</Text>
<Text fontSize="sm" variant="secondary">{ gasLimit.toLocaleString('en') }</Text> <Text fontSize="sm" variant="secondary">{ BigNumber(gasLimit).toFormat() }</Text>
</HStack> */ } </HStack>
</AccountListItemMobile> </AccountListItemMobile>
); );
}; };
......
...@@ -10,7 +10,7 @@ const TxInternalsSkeletonDesktop = () => { ...@@ -10,7 +10,7 @@ const TxInternalsSkeletonDesktop = () => {
<Skeleton w="78px"/> <Skeleton w="78px"/>
<Skeleton w="360px"/> <Skeleton w="360px"/>
</Flex> </Flex>
<SkeletonTable columns={ [ '28%', '28%', '24px', '28%', '16%' ] }/> <SkeletonTable columns={ [ '28%', '20%', '24px', '20%', '16%', '16%' ] }/>
</> </>
); );
}; };
......
...@@ -38,6 +38,10 @@ const TxInternalsSkeletonMobile = () => { ...@@ -38,6 +38,10 @@ const TxInternalsSkeletonMobile = () => {
<Skeleton w="70px" mr={ 2 }/> <Skeleton w="70px" mr={ 2 }/>
<Skeleton w="30px"/> <Skeleton w="30px"/>
</Flex> </Flex>
<Flex h={ 6 }>
<Skeleton w="70px" mr={ 2 }/>
<Skeleton w="60px"/>
</Flex>
</Flex> </Flex>
)) } )) }
</Box> </Box>
......
...@@ -23,22 +23,21 @@ const TxInternalsTable = ({ data, sort, onSortToggle }: Props) => { ...@@ -23,22 +23,21 @@ const TxInternalsTable = ({ data, sort, onSortToggle }: Props) => {
<Thead> <Thead>
<Tr> <Tr>
<Th width="28%">Type</Th> <Th width="28%">Type</Th>
<Th width="28%">From</Th> <Th width="20%">From</Th>
<Th width="24px" px={ 0 }/> <Th width="24px" px={ 0 }/>
<Th width="28%">To</Th> <Th width="20%">To</Th>
<Th width="16%" isNumeric> <Th width="16%" isNumeric>
<Link display="flex" alignItems="center" justifyContent="flex-end" onClick={ onSortToggle('value') } columnGap={ 1 }> <Link display="flex" alignItems="center" justifyContent="flex-end" onClick={ onSortToggle('value') } columnGap={ 1 }>
{ sort?.includes('value') && <Icon as={ arrowIcon } boxSize={ 4 } transform={ sortIconTransform }/> } { sort?.includes('value') && <Icon as={ arrowIcon } boxSize={ 4 } transform={ sortIconTransform }/> }
Value { appConfig.network.currency.symbol } Value { appConfig.network.currency.symbol }
</Link> </Link>
</Th> </Th>
{ /* no gas limit in api yet */ } <Th width="16%" isNumeric>
{ /* <Th width="16%" isNumeric>
<Link display="flex" alignItems="center" justifyContent="flex-end" onClick={ onSortToggle('gas-limit') } columnGap={ 1 }> <Link display="flex" alignItems="center" justifyContent="flex-end" onClick={ onSortToggle('gas-limit') } columnGap={ 1 }>
{ sort?.includes('gas-limit') && <Icon as={ arrowIcon } boxSize={ 4 } transform={ sortIconTransform }/> } { sort?.includes('gas-limit') && <Icon as={ arrowIcon } boxSize={ 4 } transform={ sortIconTransform }/> }
Gas limit Gas limit { appConfig.network.currency.symbol }
</Link> </Link>
</Th> */ } </Th>
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
......
import { Tr, Td, Tag, Icon, Box } from '@chakra-ui/react'; import { Tr, Td, Tag, Icon, Box } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
import type { InternalTransaction } from 'types/api/internalTransaction'; import type { InternalTransaction } from 'types/api/internalTransaction';
...@@ -12,7 +13,7 @@ import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils'; ...@@ -12,7 +13,7 @@ import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils';
type Props = InternalTransaction type Props = InternalTransaction
const TxInternalTableItem = ({ type, from, to, value, success, error }: Props) => { const TxInternalTableItem = ({ type, from, to, value, success, error, gas_limit: gasLimit }: Props) => {
const typeTitle = TX_INTERNALS_ITEMS.find(({ id }) => id === type)?.title; const typeTitle = TX_INTERNALS_ITEMS.find(({ id }) => id === type)?.title;
return ( return (
...@@ -43,10 +44,9 @@ const TxInternalTableItem = ({ type, from, to, value, success, error }: Props) = ...@@ -43,10 +44,9 @@ const TxInternalTableItem = ({ type, from, to, value, success, error }: Props) =
<Td isNumeric verticalAlign="middle"> <Td isNumeric verticalAlign="middle">
{ value } { value }
</Td> </Td>
{ /* no gas limit in api yet */ } <Td isNumeric verticalAlign="middle">
{ /* <Td isNumeric verticalAlign='middle'> { BigNumber(gasLimit).toFormat() }
{ gasLimit.toLocaleString('en') } </Td>
</Td> */ }
</Tr> </Tr>
); );
}; };
......
...@@ -60,6 +60,7 @@ const TxsListItem = ({ tx }: {tx: Transaction}) => { ...@@ -60,6 +60,7 @@ const TxsListItem = ({ tx }: {tx: Transaction}) => {
type="transaction" type="transaction"
fontWeight="700" fontWeight="700"
truncation="constant" truncation="constant"
target="_self"
/> />
</Address> </Address>
</Flex> </Flex>
......
...@@ -86,6 +86,7 @@ const TxsTableItem = ({ tx }: {tx: Transaction}) => { ...@@ -86,6 +86,7 @@ const TxsTableItem = ({ tx }: {tx: Transaction}) => {
hash={ tx.hash } hash={ tx.hash }
type="transaction" type="transaction"
fontWeight="700" fontWeight="700"
target="_self"
/> />
</Address> </Address>
<Text color="gray.500" fontWeight="400">{ dayjs(tx.timestamp).fromNow() }</Text> <Text color="gray.500" fontWeight="400">{ dayjs(tx.timestamp).fromNow() }</Text>
......
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