Commit 9a41d4eb authored by Igor Stuev's avatar Igor Stuev Committed by GitHub

Merge pull request #220 from blockscout/tx-page-api

tx page api integration
parents 8f833732 507a29af
/* eslint-disable max-len */ /* eslint-disable max-len */
export const tx = { export const tx = {
hash: '0x1ea365d2144796f793883534aa51bf20d23292b19478994eede23dfc599e7c34', hash: '0x1ea365d2144796f793883534aa51bf20d23292b19478994eede23dfc599e7c34',
status: 'success' as TxStatus, status: 'ok' as Transaction['status'],
block_num: 15006918, block_num: 15006918,
confirmation_num: 283, confirmation_num: 283,
confirmation_duration: 30, confirmation_duration: 30,
...@@ -52,4 +52,4 @@ export const tx = { ...@@ -52,4 +52,4 @@ export const tx = {
export type TxType = 'contract-call' | 'transaction' | 'token-transfer' | 'internal-tx' | 'multicall'; export type TxType = 'contract-call' | 'transaction' | 'token-transfer' | 'internal-tx' | 'multicall';
export type TxStatus = 'success' | 'failed' | 'pending'; import type { Transaction } from 'types/api/transaction';
import type { TxInternalsType } from 'types/api/tx';
export const data = [
{
id: 1,
type: 'call' as TxInternalsType,
status: 'success' as const,
from: { hash: '0x12E80C27BfFBB76b4A8d26FF2bfd3C9f310FFA01' },
to: { hash: '0xF7A558692dFB5F456e291791da7FAE8Dd046574e' },
value: 0.25207646303,
gasLimit: 369472,
},
{
id: 2,
type: 'delegate_call' as TxInternalsType,
status: 'success' as const,
from: { hash: '0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45' },
to: { hash: '0x12E80C27BfFBB76b4A8d26FF2bfd3C9f310FFA01' },
value: 0.5633333,
gasLimit: 340022,
},
{
id: 3,
type: 'static_call' as TxInternalsType,
status: 'failed' as const,
from: { hash: '0x97Aa2EfcF35c0f4c9AaDDCa8c2330fa7A9533830' },
to: { hash: '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',
},
];
import type { Transaction } from 'types/api/transaction';
import { tx } from './tx'; import { tx } from './tx';
import type { TxType, TxStatus } from './tx'; import type { TxType } from './tx';
export const txs = [ export const txs = [
{ {
...@@ -10,7 +12,7 @@ export const txs = [ ...@@ -10,7 +12,7 @@ export const txs = [
}, },
{ {
...tx, ...tx,
status: 'failed' as TxStatus, status: 'error' as Transaction['status'],
errorText: 'Error: (Awaiting internal transactions for reason)', errorText: 'Error: (Awaiting internal transactions for reason)',
txType: 'contract-call' as TxType, txType: 'contract-call' as TxType,
method: 'CommitHash CommitHash CommitHash CommitHash', method: 'CommitHash CommitHash CommitHash CommitHash',
...@@ -25,7 +27,7 @@ export const txs = [ ...@@ -25,7 +27,7 @@ export const txs = [
}, },
{ {
...tx, ...tx,
status: 'pending' as TxStatus, status: null,
txType: 'token-transfer' as TxType, txType: 'token-transfer' as TxType,
method: 'Multicall', method: 'Multicall',
address_from: { address_from: {
......
export const WEI = BigInt(10 ** 18);
export const GWEI = BigInt(10 ** 9);
export default function getConfirmationString(durations: Array<number>) {
if (durations.length === 0) {
return '';
}
const [ lower, upper ] = durations.map((time) => time / 1_000);
if (!upper) {
return `Confirmed within ${ lower } secs`;
}
if (lower === 0) {
return `Confirmed within <= ${ upper } secs`;
}
return `Confirmed within ${ lower } - ${ upper } secs`;
}
import type { NextApiRequest } from 'next';
import handler from 'lib/api/handler';
const getUrl = (req: NextApiRequest) => {
return `/v2/transactions/${ req.query.id }`;
};
const requestHandler = handler(getUrl, [ 'GET' ]);
export default requestHandler;
import type { NextApiRequest } from 'next';
import handler from 'lib/api/handler';
const getUrl = (req: NextApiRequest) => {
return `/v2/transactions/${ req.query.id }/internal-transactions`;
};
const requestHandler = handler(getUrl, [ 'GET' ]);
export default requestHandler;
import type { NextApiRequest } from 'next';
import handler from 'lib/api/handler';
const getUrl = (req: NextApiRequest) => {
return `/v2/transactions/${ req.query.id }/logs`;
};
const requestHandler = handler(getUrl, [ 'GET' ]);
export default requestHandler;
import type { NextApiRequest } from 'next';
import handler from 'lib/api/handler';
const getUrl = (req: NextApiRequest) => {
return `/v2/transactions/${ req.query.id }/raw-trace`;
};
const requestHandler = handler(getUrl, [ 'GET' ]);
export default requestHandler;
...@@ -8,4 +8,5 @@ export interface DecodedInputParams { ...@@ -8,4 +8,5 @@ export interface DecodedInputParams {
name: string; name: string;
type: string; type: string;
value: string; value: string;
indexed?: boolean;
} }
import type { AddressParam } from './addressParams'; import type { AddressParam } from './addressParams';
export type TxInternalsType = 'call' | 'delegatecall' | 'staticcall' | 'create' | 'create2' | 'selfdestruct' | 'reward'
export interface InternalTransaction { export interface InternalTransaction {
error: string | null; error: string | null;
success: boolean; success: boolean;
type: string; type: TxInternalsType;
transaction_hash: string; transaction_hash: string;
from: AddressParam; from: AddressParam;
to: AddressParam; to: AddressParam;
......
...@@ -11,5 +11,5 @@ export interface TokenTransfer { ...@@ -11,5 +11,5 @@ export interface TokenTransfer {
total: { total: {
value: string; value: string;
}; };
exchangeRate: number; exchange_rate: string;
} }
...@@ -7,7 +7,7 @@ export interface Transaction { ...@@ -7,7 +7,7 @@ export interface Transaction {
hash: string; hash: string;
result: string; result: string;
confirmations: number; confirmations: number;
status: string; status: 'ok' | 'error' | null;
block: number; block: number;
timestamp: string; timestamp: string;
confirmation_duration: Array<number>; confirmation_duration: Array<number>;
...@@ -18,8 +18,8 @@ export interface Transaction { ...@@ -18,8 +18,8 @@ export interface Transaction {
fee: Fee; fee: Fee;
gas_price: number; gas_price: number;
type: number; type: number;
gas_used: number; gas_used: string;
gas_limit: number; 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;
priority_fee: number | null; priority_fee: number | null;
...@@ -35,7 +35,7 @@ export interface Transaction { ...@@ -35,7 +35,7 @@ export interface Transaction {
decoded_input: DecodedInput | null; decoded_input: DecodedInput | null;
token_transfers: Array<TokenTransfer> | null; token_transfers: Array<TokenTransfer> | null;
token_transfers_overflow: boolean; token_transfers_overflow: boolean;
exchange_rate: number; exchange_rate: string;
} }
export interface TransactionsResponse { export interface TransactionsResponse {
......
export type TxInternalsType = 'call' | 'delegate_call' | 'static_call' | 'create' | 'create2' | 'self_destruct' | 'reward'
...@@ -13,13 +13,14 @@ import TxDetails from 'ui/tx/TxDetails'; ...@@ -13,13 +13,14 @@ import TxDetails from 'ui/tx/TxDetails';
import TxInternals from 'ui/tx/TxInternals'; import TxInternals from 'ui/tx/TxInternals';
import TxLogs from 'ui/tx/TxLogs'; import TxLogs from 'ui/tx/TxLogs';
import TxRawTrace from 'ui/tx/TxRawTrace'; import TxRawTrace from 'ui/tx/TxRawTrace';
import TxState from 'ui/tx/TxState'; // import TxState from 'ui/tx/TxState';
const TABS: Array<RoutedTab> = [ const TABS: Array<RoutedTab> = [
{ routeName: 'tx_index', title: 'Details', component: <TxDetails/> }, { routeName: 'tx_index', title: 'Details', component: <TxDetails/> },
{ routeName: 'tx_internal', title: 'Internal txn', component: <TxInternals/> }, { routeName: 'tx_internal', title: 'Internal txn', component: <TxInternals/> },
{ routeName: 'tx_logs', title: 'Logs', component: <TxLogs/> }, { routeName: 'tx_logs', title: 'Logs', component: <TxLogs/> },
{ routeName: 'tx_state', title: 'State', component: <TxState/> }, // will be implemented later, api is not ready
// { routeName: 'tx_state', title: 'State', component: <TxState/> },
{ routeName: 'tx_raw_trace', title: 'Raw trace', component: <TxRawTrace/> }, { routeName: 'tx_raw_trace', title: 'Raw trace', component: <TxRawTrace/> },
]; ];
......
import { Box, Text, chakra } from '@chakra-ui/react';
import { utils, constants } from 'ethers';
import React from 'react';
interface Props {
value: string;
unit?: 'wei' | 'gwei' | 'ether';
currency?: string;
exchangeRate?: string;
className?: string;
}
const CurrencyValue = ({ value, currency = '', unit = 'wei', exchangeRate, className }: Props) => {
const valueBn = utils.parseUnits(value, unit);
const exchangeRateBn = utils.parseUnits(exchangeRate || '0', 'ether');
const usdBn = valueBn.mul(exchangeRateBn).div(constants.WeiPerEther);
return (
<Box as="span" className={ className }>
<Text as="span">
{ Number(utils.formatUnits(valueBn)).toLocaleString() }{ currency ? ` ${ currency }` : '' }</Text>
{ exchangeRate !== undefined && exchangeRate !== null &&
<Text as="span" variant="secondary" whiteSpace="pre" fontWeight={ 400 }> (${ utils.formatUnits(usdBn) })</Text> }
</Box>
);
};
export default React.memo(chakra(CurrencyValue));
import { Tag, TagLabel, TagLeftIcon, Tooltip } from '@chakra-ui/react'; import { Tag, TagLabel, TagLeftIcon, Tooltip } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { Transaction } from 'types/api/transaction';
import errorIcon from 'icons/status/error.svg'; import errorIcon from 'icons/status/error.svg';
import pendingIcon from 'icons/status/pending.svg'; import pendingIcon from 'icons/status/pending.svg';
import successIcon from 'icons/status/success.svg'; import successIcon from 'icons/status/success.svg';
export interface Props { export interface Props {
status: 'success' | 'failed' | 'pending'; status: Transaction['status'];
errorText?: string; errorText?: string | null;
} }
const TxStatus = ({ status, errorText }: Props) => { const TxStatus = ({ status, errorText }: Props) => {
...@@ -16,17 +18,17 @@ const TxStatus = ({ status, errorText }: Props) => { ...@@ -16,17 +18,17 @@ const TxStatus = ({ status, errorText }: Props) => {
let colorScheme; let colorScheme;
switch (status) { switch (status) {
case 'success': case 'ok':
label = 'Success'; label = 'Success';
icon = successIcon; icon = successIcon;
colorScheme = 'green'; colorScheme = 'green';
break; break;
case 'failed': case 'error':
label = 'Failed'; label = 'Failed';
icon = errorIcon; icon = errorIcon;
colorScheme = 'red'; colorScheme = 'red';
break; break;
case 'pending': case null:
label = 'Pending'; label = 'Pending';
icon = pendingIcon; icon = pendingIcon;
// FIXME: it's not gray on mockups // FIXME: it's not gray on mockups
......
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 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';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import CurrencyValue from 'ui/shared/CurrencyValue';
import TokenSnippet from 'ui/shared/TokenSnippet'; import TokenSnippet from 'ui/shared/TokenSnippet';
interface Props { type Props = TTokenTransfer
from: string;
to: string;
amount: number;
usd: number;
token: {
symbol: string;
hash: string;
name: string;
};
}
const TokenTransfer = ({ from, to, amount, usd, token }: Props) => { const TokenTransfer = ({ from, to, total, exchange_rate: exchangeRate, ...token }: Props) => {
return ( return (
<Flex alignItems="center" flexWrap="wrap" columnGap={ 3 } rowGap={ 3 }> <Flex alignItems="center" flexWrap="wrap" columnGap={ 3 } rowGap={ 3 }>
<Flex alignItems="center"> <Flex alignItems="center">
<AddressLink fontWeight="500" hash={ from } 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={ to } truncation="constant"/> <AddressLink fontWeight="500" hash={ to.hash } truncation="constant"/>
</Flex> </Flex>
<Text fontWeight={ 500 } as="span">For:{ space } <Text fontWeight={ 500 } as="span">For:{ space }
<Text fontWeight={ 600 } as="span">{ amount }</Text>{ space } <CurrencyValue value={ total.value.replaceAll(',', '') } unit="ether" exchangeRate={ exchangeRate } fontWeight={ 600 }/>
<Text fontWeight={ 400 } variant="secondary" as="span">(${ usd.toFixed(2) })</Text>
</Text> </Text>
<TokenSnippet { ...token }/> <TokenSnippet symbol={ token.token_symbol } hash={ token.token_address } name="Foo"/>
</Flex> </Flex>
); );
}; };
......
import { Flex, Text, Grid, GridItem, useColorModeValue } from '@chakra-ui/react'; import { Flex, Text, Grid, GridItem, useColorModeValue } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { DecodedInput } from 'types/api/decodedInput';
import Address from 'ui/shared/address/Address'; import Address from 'ui/shared/address/Address';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import CopyToClipboard from 'ui/shared/CopyToClipboard'; import CopyToClipboard from 'ui/shared/CopyToClipboard';
...@@ -39,14 +41,16 @@ const TableRow = ({ isLast, name, type, children, indexed }: RowProps) => { ...@@ -39,14 +41,16 @@ const TableRow = ({ isLast, name, type, children, indexed }: RowProps) => {
> >
{ type } { type }
</GridItem> </GridItem>
<GridItem { indexed !== undefined && (
pr={ GAP } <GridItem
pt={ GAP } pr={ GAP }
pb={ isLast ? PADDING : 0 } pt={ GAP }
bgColor={ bgColor } pb={ isLast ? PADDING : 0 }
> bgColor={ bgColor }
{ indexed ? 'true' : 'false' } >
</GridItem> { indexed ? 'true' : 'false' }
</GridItem>
) }
<GridItem <GridItem
pr={ PADDING } pr={ PADDING }
pt={ GAP } pt={ GAP }
...@@ -60,14 +64,28 @@ const TableRow = ({ isLast, name, type, children, indexed }: RowProps) => { ...@@ -60,14 +64,28 @@ const TableRow = ({ isLast, name, type, children, indexed }: RowProps) => {
); );
}; };
const TxDecodedInputData = () => { interface Props {
data: DecodedInput;
}
const TxDecodedInputData = ({ data }: Props) => {
const bgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50'); const bgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
const hasIndexed = data.parameters.some(({ indexed }) => indexed !== undefined);
const gridTemplateColumns = hasIndexed ?
'minmax(80px, auto) minmax(80px, auto) minmax(80px, auto) minmax(0, 1fr)' :
'minmax(80px, auto) minmax(80px, auto) minmax(0, 1fr)';
const colNumber = hasIndexed ? 4 : 3;
return ( return (
<Grid gridTemplateColumns="minmax(80px, auto) minmax(80px, auto) minmax(80px, auto) minmax(0, 1fr)" fontSize="sm" lineHeight={ 5 } w="100%"> <Grid gridTemplateColumns={ gridTemplateColumns } fontSize="sm" lineHeight={ 5 } w="100%">
{ /* FIRST PART OF BLOCK */ } { /* FIRST PART OF BLOCK */ }
<GridItem fontWeight={ 600 } pl={{ base: 0, lg: PADDING }} pr={{ base: 0, lg: GAP }} colSpan={{ base: 4, lg: undefined }}>Method Id</GridItem> <GridItem fontWeight={ 600 } pl={{ base: 0, lg: PADDING }} pr={{ base: 0, lg: GAP }} colSpan={{ base: colNumber, lg: undefined }}>
<GridItem colSpan={{ base: 4, lg: 3 }} pr={{ base: 0, lg: PADDING }} mt={{ base: 2, lg: 0 }}>0xddf252ad</GridItem> Method Id
</GridItem>
<GridItem colSpan={{ base: colNumber, lg: colNumber - 1 }} pr={{ base: 0, lg: PADDING }} mt={{ base: 2, lg: 0 }}>
{ data.method_id }
</GridItem>
<GridItem <GridItem
py={ 2 } py={ 2 }
mt={ 2 } mt={ 2 }
...@@ -76,7 +94,7 @@ const TxDecodedInputData = () => { ...@@ -76,7 +94,7 @@ const TxDecodedInputData = () => {
fontWeight={ 600 } fontWeight={ 600 }
borderTopColor={ useColorModeValue('blackAlpha.200', 'whiteAlpha.200') } borderTopColor={ useColorModeValue('blackAlpha.200', 'whiteAlpha.200') }
borderTopWidth="1px" borderTopWidth="1px"
colSpan={{ base: 4, lg: undefined }} colSpan={{ base: colNumber, lg: undefined }}
> >
Call Call
</GridItem> </GridItem>
...@@ -84,13 +102,13 @@ const TxDecodedInputData = () => { ...@@ -84,13 +102,13 @@ const TxDecodedInputData = () => {
py={{ base: 0, lg: 2 }} py={{ base: 0, lg: 2 }}
mt={{ base: 0, lg: 2 }} mt={{ base: 0, lg: 2 }}
mb={{ base: 2, lg: 0 }} mb={{ base: 2, lg: 0 }}
colSpan={{ base: 4, lg: 3 }} colSpan={{ base: colNumber, lg: colNumber - 1 }}
pr={{ base: 0, lg: PADDING }} pr={{ base: 0, lg: PADDING }}
borderTopColor={ useColorModeValue('blackAlpha.200', 'whiteAlpha.200') } borderTopColor={ useColorModeValue('blackAlpha.200', 'whiteAlpha.200') }
borderTopWidth={{ base: '0px', lg: '1px' }} borderTopWidth={{ base: '0px', lg: '1px' }}
whiteSpace="normal" whiteSpace="normal"
> >
Transfer(address indexed from, address indexed to, uint256 indexed tokenId) { data.method_call }
</GridItem> </GridItem>
{ /* TABLE INSIDE OF BLOCK */ } { /* TABLE INSIDE OF BLOCK */ }
<GridItem <GridItem
...@@ -112,15 +130,17 @@ const TxDecodedInputData = () => { ...@@ -112,15 +130,17 @@ const TxDecodedInputData = () => {
> >
Type Type
</GridItem> </GridItem>
<GridItem { hasIndexed && (
pr={ GAP } <GridItem
pt={ PADDING } pr={ GAP }
pb={ 1 } pt={ PADDING }
bgColor={ bgColor } pb={ 1 }
fontWeight={ 600 } bgColor={ bgColor }
> fontWeight={ 600 }
Inde<wbr/>xed? >
</GridItem> Inde<wbr/>xed?
</GridItem>
) }
<GridItem <GridItem
pr={ PADDING } pr={ PADDING }
pt={ PADDING } pt={ PADDING }
...@@ -130,24 +150,23 @@ const TxDecodedInputData = () => { ...@@ -130,24 +150,23 @@ const TxDecodedInputData = () => {
> >
Data Data
</GridItem> </GridItem>
<TableRow name="from" type="address"> { data.parameters.map(({ name, type, value, indexed }, index) => {
<Address justifyContent="space-between"> return (
<AddressLink hash="0x0000000000000000000000000000000000000000"/> <TableRow key={ name } name={ name } type={ type } isLast={ index === data.parameters.length - 1 } indexed={ indexed }>
<CopyToClipboard text="0x0000000000000000000000000000000000000000"/> { type === 'address' ? (
</Address> <Address justifyContent="space-between">
</TableRow> <AddressLink hash={ value }/>
<TableRow name="to" type="address" indexed> <CopyToClipboard text={ value }/>
<Address justifyContent="space-between"> </Address>
<AddressLink hash="0xcf0c50b7ea8af37d57380a0ac199d55b0782c718"/> ) : (
<CopyToClipboard text="0xcf0c50b7ea8af37d57380a0ac199d55b0782c718"/> <Flex alignItems="flex-start" justifyContent="space-between" whiteSpace="normal" wordBreak="break-all">
</Address> <Text>{ value }</Text>
</TableRow> <CopyToClipboard text={ value }/>
<TableRow name="tokenId" type="uint256" isLast> </Flex>
<Flex alignItems="center" justifyContent="space-between"> ) }
<Text>116842</Text> </TableRow>
<CopyToClipboard text="116842"/> );
</Flex> }) }
</TableRow>
</Grid> </Grid>
); );
}; };
......
This diff is collapsed.
import { Box, Flex } from '@chakra-ui/react'; import { Box, Flex, Alert, Show } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import type { TxInternalsType } from 'types/api/tx'; import type { InternalTransactionsResponse, TxInternalsType, InternalTransaction } from 'types/api/internalTransaction';
import type ArrayElement from 'types/utils/ArrayElement';
import { data } from 'data/txInternal'; import useFetch from 'lib/hooks/useFetch';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import { apos } from 'lib/html-entities'; import { apos } from 'lib/html-entities';
import EmptySearchResult from 'ui/apps/EmptySearchResult'; import EmptySearchResult from 'ui/apps/EmptySearchResult';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import FilterInput from 'ui/shared/FilterInput'; import FilterInput from 'ui/shared/FilterInput';
import TxInternalsFilter from 'ui/tx/internals/TxInternalsFilter'; import TxInternalsFilter from 'ui/tx/internals/TxInternalsFilter';
import TxInternalsList from 'ui/tx/internals/TxInternalsList'; import TxInternalsList from 'ui/tx/internals/TxInternalsList';
import TxInternalsSkeletonDesktop from 'ui/tx/internals/TxInternalsSkeletonDesktop';
import TxInternalsSkeletonMobile from 'ui/tx/internals/TxInternalsSkeletonMobile';
import TxInternalsTable from 'ui/tx/internals/TxInternalsTable'; import TxInternalsTable from 'ui/tx/internals/TxInternalsTable';
import type { Sort, SortField } from 'ui/tx/internals/utils'; import type { Sort, SortField } from 'ui/tx/internals/utils';
...@@ -26,7 +30,7 @@ const getNextSortValue = (field: SortField) => (prevValue: Sort | undefined) => ...@@ -26,7 +30,7 @@ const getNextSortValue = (field: SortField) => (prevValue: Sort | undefined) =>
return sequence[nextIndex]; return sequence[nextIndex];
}; };
const sortFn = (sort: Sort | undefined) => (a: ArrayElement<typeof data>, b: ArrayElement<typeof data>) => { const sortFn = (sort: Sort | undefined) => (a: InternalTransaction, b: InternalTransaction) => {
switch (sort) { switch (sort) {
case 'value-desc': { case 'value-desc': {
const result = a.value > b.value ? -1 : 1; const result = a.value > b.value ? -1 : 1;
...@@ -38,22 +42,23 @@ const sortFn = (sort: Sort | undefined) => (a: ArrayElement<typeof data>, b: Arr ...@@ -38,22 +42,23 @@ const sortFn = (sort: Sort | undefined) => (a: ArrayElement<typeof data>, b: Arr
return a.value === b.value ? 0 : result; return a.value === b.value ? 0 : result;
} }
case 'gas-limit-desc': { // no gas limit in api yet
const result = a.gasLimit > b.gasLimit ? -1 : 1; // case 'gas-limit-desc': {
return a.gasLimit === b.gasLimit ? 0 : result; // const result = a.gasLimit > b.gasLimit ? -1 : 1;
} // return a.gasLimit === b.gasLimit ? 0 : result;
// }
case 'gas-limit-asc': { // case 'gas-limit-asc': {
const result = a.gasLimit > b.gasLimit ? 1 : -1; // const result = a.gasLimit > b.gasLimit ? 1 : -1;
return a.gasLimit === b.gasLimit ? 0 : result; // return a.gasLimit === b.gasLimit ? 0 : result;
} // }
default: default:
return 0; return 0;
} }
}; };
const searchFn = (searchTerm: string) => (item: ArrayElement<typeof data>): boolean => { const searchFn = (searchTerm: string) => (item: InternalTransaction): boolean => {
const formattedSearchTerm = searchTerm.toLowerCase(); const formattedSearchTerm = searchTerm.toLowerCase();
return item.type.toLowerCase().includes(formattedSearchTerm) || return item.type.toLowerCase().includes(formattedSearchTerm) ||
item.from.hash.toLowerCase().includes(formattedSearchTerm) || item.from.hash.toLowerCase().includes(formattedSearchTerm) ||
...@@ -61,9 +66,19 @@ const searchFn = (searchTerm: string) => (item: ArrayElement<typeof data>): bool ...@@ -61,9 +66,19 @@ const searchFn = (searchTerm: string) => (item: ArrayElement<typeof data>): bool
}; };
const TxInternals = () => { const TxInternals = () => {
const router = useRouter();
const fetch = useFetch();
const [ filters, setFilters ] = React.useState<Array<TxInternalsType>>([]); const [ filters, setFilters ] = React.useState<Array<TxInternalsType>>([]);
const [ searchTerm, setSearchTerm ] = React.useState<string>(''); const [ searchTerm, setSearchTerm ] = React.useState<string>('');
const [ sort, setSort ] = React.useState<Sort>(); const [ sort, setSort ] = React.useState<Sort>();
const { data, isLoading, isError } = useQuery<unknown, unknown, InternalTransactionsResponse>(
[ 'tx-internals', router.query.id ],
async() => await fetch(`/api/transactions/${ router.query.id }/internal-transactions`),
{
enabled: Boolean(router.query.id),
},
);
const isMobile = useIsMobile(); const isMobile = useIsMobile();
...@@ -77,8 +92,25 @@ const TxInternals = () => { ...@@ -77,8 +92,25 @@ const TxInternals = () => {
}; };
}, []); }, []);
if (isLoading) {
return (
<>
<Show below="lg"><TxInternalsSkeletonMobile/></Show>
<Show above="lg"><TxInternalsSkeletonDesktop/></Show>
</>
);
}
if (isError) {
return <DataFetchAlert/>;
}
if (data.items.length === 0) {
return <Alert>There are no internal transactions for this transaction.</Alert>;
}
const content = (() => { const content = (() => {
const filteredData = data const filteredData = data.items
.filter(({ type }) => filters.length > 0 ? filters.includes(type) : true) .filter(({ type }) => filters.length > 0 ? filters.includes(type) : true)
.filter(searchFn(searchTerm)) .filter(searchFn(searchTerm))
.sort(sortFn(sort)); .sort(sortFn(sort));
......
import { Box } from '@chakra-ui/react'; import { Box, Alert } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import { data } from 'data/txLogs'; import type { LogsResponse } from 'types/api/log';
import useFetch from 'lib/hooks/useFetch';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import TxLogItem from 'ui/tx/logs/TxLogItem'; import TxLogItem from 'ui/tx/logs/TxLogItem';
import TxLogSkeleton from 'ui/tx/logs/TxLogSkeleton';
const TxLogs = () => { const TxLogs = () => {
const router = useRouter();
const fetch = useFetch();
const { data, isLoading, isError } = useQuery<unknown, unknown, LogsResponse>(
[ 'tx-log', router.query.id ],
async() => await fetch(`/api/transactions/${ router.query.id }/logs`),
{
enabled: Boolean(router.query.id),
},
);
if (isError) {
return <DataFetchAlert/>;
}
if (isLoading) {
return (
<Box>
<TxLogSkeleton/>
<TxLogSkeleton/>
</Box>
);
}
if (data.items.length === 0) {
return <Alert>There are no logs for this transaction.</Alert>;
}
return ( return (
<Box> <Box>
{ data.map((item, index) => <TxLogItem key={ index } { ...item } index={ index }/>) } { data.items.map((item, index) => <TxLogItem key={ index } { ...item }/>) }
</Box> </Box>
); );
}; };
......
import { Flex, Textarea } from '@chakra-ui/react'; import { Flex, Textarea, Skeleton } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import type { RawTracesResponse } from 'types/api/rawTrace';
import useFetch from 'lib/hooks/useFetch';
import CopyToClipboard from 'ui/shared/CopyToClipboard'; import CopyToClipboard from 'ui/shared/CopyToClipboard';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
const data = [ const TxRawTrace = () => {
{ const router = useRouter();
action: { const fetch = useFetch();
callType: 'delegatecall',
from: '0x296033cb983747b68911244ec1a3f01d7708851b', const { data, isLoading, isError } = useQuery<unknown, unknown, RawTracesResponse>(
gas: '0x1AB35C9', [ 'tx-raw-trace', router.query.id ],
// eslint-disable-next-line max-len async() => await fetch(`/api/transactions/${ router.query.id }/raw-trace`),
input: '0x6a76120200000000000000000000000099759357a9923bb164a7ae8b85703a6882cb84ea0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000014466d2a64d000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000013ef0000000000000000000000000000000000000000000000000000000000001bfb000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000186b900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041000000000000000000000000f4e5b62da2eee3b5811dae1fae480f7623bd4cd000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000', {
to: '0x3e5c63644e683549055b9be8653de26e0b4cd36e', enabled: Boolean(router.query.id),
value: '0x0',
},
result: {
blockHash: '0x43dd926aa138a58d3f4740dae387bcff3c7bc525db2d0a449f323f8b8f92a229',
blockNumber: '0xa4f285',
from: '0xea8a7ef30f894bce23b42314613458d13f9d43ea',
gas: '0x30d40',
gasPrice: '0x2e90edd000',
hash: '0x72ee43a3784cc6749f64fad1ecf0bbd51a54dd6892ae0573f211566809e0d511',
input: '0x',
nonce: '0x1e7',
to: '0xbd064928cdd4fd67fb99917c880e6560978d7ca1',
transactionIndex: '0x0',
value: '0xde0b6b3a7640000',
v: '0x25',
r: '0x7e833413ead52b8c538001b12ab5a85bac88db0b34b61251bb0fc81573ca093f',
s: '0x49634f1e439e3760265888434a2f9782928362412030db1429458ddc9dcee995',
},
},
{
action: {
callType: 'delegatecall',
from: '0x296033cb983747b68911244ec1a3f01d7708851b',
gas: '0x1AB35C9',
// eslint-disable-next-line max-len
input: '0x6a76120200000000000000000000000099759357a9923bb164a7ae8b85703a6882cb84ea0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000014466d2a64d000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000013ef0000000000000000000000000000000000000000000000000000000000001bfb000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000186b900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041000000000000000000000000f4e5b62da2eee3b5811dae1fae480f7623bd4cd000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000',
to: '0x3e5c63644e683549055b9be8653de26e0b4cd36e',
value: '0x0',
},
result: {
blockHash: '0x43dd926aa138a58d3f4740dae387bcff3c7bc525db2d0a449f323f8b8f92a229',
blockNumber: '0xa4f285',
from: '0xea8a7ef30f894bce23b42314613458d13f9d43ea',
gas: '0x30d40',
gasPrice: '0x2e90edd000',
hash: '0x72ee43a3784cc6749f64fad1ecf0bbd51a54dd6892ae0573f211566809e0d511',
input: '0x',
nonce: '0x1e7',
to: '0xbd064928cdd4fd67fb99917c880e6560978d7ca1',
transactionIndex: '0x0',
value: '0xde0b6b3a7640000',
v: '0x25',
r: '0x7e833413ead52b8c538001b12ab5a85bac88db0b34b61251bb0fc81573ca093f',
s: '0x49634f1e439e3760265888434a2f9782928362412030db1429458ddc9dcee995',
}, },
}, );
];
if (isError) {
return <DataFetchAlert/>;
}
if (isLoading) {
return (
<>
<Flex justifyContent="end" mb={ 2 }>
<Skeleton w={ 5 } h={ 5 }/>
</Flex>
<Skeleton w="100%" h="500px"/>
</>
);
}
const TxRawTrace = () => {
const text = JSON.stringify(data, undefined, 4); const text = JSON.stringify(data, undefined, 4);
return ( return (
<> <>
<Flex justifyContent="end" mb={ 2 }> <Flex justifyContent="end" mb={ 2 }>
...@@ -69,7 +45,7 @@ const TxRawTrace = () => { ...@@ -69,7 +45,7 @@ const TxRawTrace = () => {
</Flex> </Flex>
<Textarea <Textarea
variant="filledInactive" variant="filledInactive"
height="570px" minHeight="500px"
p={ 4 } p={ 4 }
value={ text } value={ text }
/> />
......
import { Grid, GridItem, Skeleton, SkeletonCircle } from '@chakra-ui/react';
import React from 'react';
const SkeletonRow = ({ w = '100%' }: { w?: string }) => (
<>
<GridItem display="flex" columnGap={ 2 } w={{ base: '50%', lg: 'auto' }} _notFirst={{ mt: { base: 3, lg: 0 } }}>
<SkeletonCircle h={ 5 } w={ 5 }/>
<Skeleton flexGrow={ 1 } h={ 5 } borderRadius="full"/>
</GridItem>
<GridItem pl={{ base: 7, lg: 0 }}>
<Skeleton h={ 5 } borderRadius="full" w={{ base: '100%', lg: w }}/>
</GridItem>
</>
);
const TxDetailsSkeleton = () => {
const sectionGap = <GridItem colSpan={{ base: undefined, lg: 2 }} mt={{ base: 1, lg: 4 }}/>;
return (
<Grid columnGap={ 8 } rowGap={{ base: 5, lg: 7 }} templateColumns={{ base: '1fr', lg: '210px 1fr' }} maxW="1000px">
<SkeletonRow/>
<SkeletonRow w="20%"/>
<SkeletonRow w="50%"/>
<SkeletonRow/>
<SkeletonRow w="70%"/>
<SkeletonRow w="70%"/>
{ sectionGap }
<SkeletonRow w="40%"/>
<SkeletonRow w="40%"/>
<SkeletonRow w="40%"/>
<SkeletonRow w="40%"/>
<SkeletonRow w="40%"/>
{ sectionGap }
<GridItem colSpan={{ base: undefined, lg: 2 }}>
<Skeleton h={ 5 } borderRadius="full" w="100px"/>
</GridItem>
</Grid>
);
};
export default TxDetailsSkeleton;
import { Popover, PopoverTrigger, PopoverContent, PopoverBody, CheckboxGroup, Checkbox, Text, useDisclosure } from '@chakra-ui/react'; import { Popover, PopoverTrigger, PopoverContent, PopoverBody, CheckboxGroup, Checkbox, Text, useDisclosure } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { TxInternalsType } from 'types/api/tx'; import type { TxInternalsType } from 'types/api/internalTransaction';
import FilterButton from 'ui/shared/FilterButton'; import FilterButton from 'ui/shared/FilterButton';
import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils'; import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils';
......
import { Box } from '@chakra-ui/react'; import { Box } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { data as txData } from 'data/txInternal'; import type { InternalTransaction } from 'types/api/internalTransaction';
import useNetwork from 'lib/hooks/useNetwork'; import useNetwork from 'lib/hooks/useNetwork';
import TxInternalsListItem from 'ui/tx/internals/TxInternalsListItem'; import TxInternalsListItem from 'ui/tx/internals/TxInternalsListItem';
const TxInternalsList = ({ data }: { data: typeof txData}) => { const TxInternalsList = ({ data }: { data: Array<InternalTransaction>}) => {
const selectedNetwork = useNetwork(); const selectedNetwork = useNetwork();
return ( return (
<Box mt={ 6 }> <Box mt={ 6 }>
{ data.map((item) => <TxInternalsListItem key={ item.id } { ...item } currency={ selectedNetwork?.currency }/>) } { data.map((item) => <TxInternalsListItem key={ item.transaction_hash } { ...item } currency={ selectedNetwork?.currency }/>) }
</Box> </Box>
); );
}; };
......
import { Flex, Tag, Icon, Box, HStack, Text } from '@chakra-ui/react'; import { Flex, Tag, Icon, Box, HStack, Text } from '@chakra-ui/react';
import capitalize from 'lodash/capitalize';
import React from 'react'; import React from 'react';
import type ArrayElement from 'types/utils/ArrayElement'; import type { InternalTransaction } from 'types/api/internalTransaction';
import type { data } from 'data/txInternal';
import eastArrowIcon from 'icons/arrows/east.svg'; import eastArrowIcon from 'icons/arrows/east.svg';
import AccountListItemMobile from 'ui/shared/AccountListItemMobile'; import AccountListItemMobile from 'ui/shared/AccountListItemMobile';
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';
import TxStatus from 'ui/shared/TxStatus'; import TxStatus from 'ui/shared/TxStatus';
import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils';
type Props = ArrayElement<typeof data> & { currency?: string }; type Props = InternalTransaction & { currency?: string };
const TxInternalsListItem = ({ type, from, to, value, currency, success, error }: Props) => {
const typeTitle = TX_INTERNALS_ITEMS.find(({ id }) => id === type)?.title;
const TxInternalsListItem = ({ type, status, from, to, value, gasLimit, currency }: Props) => {
return ( return (
<AccountListItemMobile rowGap={ 3 }> <AccountListItemMobile rowGap={ 3 }>
<Flex> <Flex>
<Tag colorScheme="cyan" mr={ 2 }>{ capitalize(type) }</Tag> <Tag colorScheme="cyan" mr={ 2 }>{ typeTitle }</Tag>
<TxStatus status={ status }/> <TxStatus status={ success ? 'ok' : 'error' } errorText={ error }/>
</Flex> </Flex>
<Box w="100%" display="flex" columnGap={ 3 }> <Box w="100%" display="flex" columnGap={ 3 }>
<Address width="calc((100% - 48px) / 2)"> <Address width="calc((100% - 48px) / 2)">
...@@ -36,10 +37,11 @@ const TxInternalsListItem = ({ type, status, from, to, value, gasLimit, currency ...@@ -36,10 +37,11 @@ const TxInternalsListItem = ({ type, status, from, to, value, gasLimit, currency
<Text fontSize="sm" fontWeight={ 500 }>Value { currency }</Text> <Text fontSize="sm" fontWeight={ 500 }>Value { currency }</Text>
<Text fontSize="sm" variant="secondary">{ value }</Text> <Text fontSize="sm" variant="secondary">{ value }</Text>
</HStack> </HStack>
<HStack spacing={ 3 }> { /* no gas limit in api yet */ }
{ /* <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">{ gasLimit.toLocaleString('en') }</Text>
</HStack> </HStack> */ }
</AccountListItemMobile> </AccountListItemMobile>
); );
}; };
......
import { Skeleton, Flex } from '@chakra-ui/react';
import React from 'react';
import SkeletonTable from 'ui/shared/SkeletonTable';
const TxInternalsSkeletonDesktop = () => {
return (
<>
<Flex columnGap={ 3 } h={ 8 } mb={ 6 }>
<Skeleton w="78px"/>
<Skeleton w="360px"/>
</Flex>
<SkeletonTable columns={ [ '28%', '28%', '24px', '28%', '16%' ] }/>
</>
);
};
export default TxInternalsSkeletonDesktop;
import { Skeleton, SkeletonCircle, Flex, Box, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
const TxInternalsSkeletonMobile = () => {
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
return (
<>
<Flex columnGap={ 3 } h={ 8 } mb={ 6 }>
<Skeleton w="36px" flexShrink={ 0 }/>
<Skeleton w="100%"/>
</Flex>
<Box>
{ Array.from(Array(2)).map((item, index) => (
<Flex
key={ index }
rowGap={ 3 }
flexDirection="column"
paddingY={ 6 }
borderTopWidth="1px"
borderColor={ borderColor }
_last={{
borderBottomWidth: '1px',
}}
>
<Flex h={ 6 }>
<Skeleton w="100px" mr={ 2 }/>
<Skeleton w="90px"/>
</Flex>
<Flex h={ 6 }>
<SkeletonCircle size="6" mr={ 2 } flexShrink={ 0 }/>
<Skeleton flexGrow={ 1 } mr={ 3 }/>
<Skeleton w={ 6 } mr={ 3 }/>
<SkeletonCircle size="6" mr={ 2 } flexShrink={ 0 }/>
<Skeleton flexGrow={ 1 } mr={ 3 }/>
</Flex>
<Flex h={ 6 }>
<Skeleton w="70px" mr={ 2 }/>
<Skeleton w="30px"/>
</Flex>
</Flex>
)) }
</Box>
</>
);
};
export default TxInternalsSkeletonMobile;
import { Table, Thead, Tbody, Tr, Th, TableContainer, Link, Icon } from '@chakra-ui/react'; import { Table, Thead, Tbody, Tr, Th, TableContainer, Link, Icon } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { data as txData } from 'data/txInternal'; import type { InternalTransaction } from 'types/api/internalTransaction';
import arrowIcon from 'icons/arrows/east.svg'; import arrowIcon from 'icons/arrows/east.svg';
import useNetwork from 'lib/hooks/useNetwork'; import useNetwork from 'lib/hooks/useNetwork';
import TxInternalsTableItem from 'ui/tx/internals/TxInternalsTableItem'; import TxInternalsTableItem from 'ui/tx/internals/TxInternalsTableItem';
import type { Sort, SortField } from 'ui/tx/internals/utils'; import type { Sort, SortField } from 'ui/tx/internals/utils';
interface Props { interface Props {
data: typeof txData; data: Array<InternalTransaction>;
sort: Sort | undefined; sort: Sort | undefined;
onSortToggle: (field: SortField) => () => void; onSortToggle: (field: SortField) => () => void;
} }
...@@ -23,26 +24,27 @@ const TxInternalsTable = ({ data, sort, onSortToggle }: Props) => { ...@@ -23,26 +24,27 @@ const TxInternalsTable = ({ data, sort, onSortToggle }: Props) => {
<Thead> <Thead>
<Tr> <Tr>
<Th width="28%">Type</Th> <Th width="28%">Type</Th>
<Th width="20%">From</Th> <Th width="28%">From</Th>
<Th width="24px" px={ 0 }/> <Th width="24px" px={ 0 }/>
<Th width="20%">To</Th> <Th width="28%">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 { selectedNetwork?.currency } Value { selectedNetwork?.currency }
</Link> </Link>
</Th> </Th>
<Th width="16%" isNumeric> { /* no gas limit in api yet */ }
{ /* <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
</Link> </Link>
</Th> </Th> */ }
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
{ data.map((item) => ( { data.map((item) => (
<TxInternalsTableItem key={ item.id } { ...item }/> <TxInternalsTableItem key={ item.transaction_hash } { ...item }/>
)) } )) }
</Tbody> </Tbody>
</Table> </Table>
......
import { Tr, Td, Tag, Icon, Box } from '@chakra-ui/react'; import { Tr, Td, Tag, Icon, Box } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { InternalTransaction } from 'types/api/internalTransaction';
import rightArrowIcon from 'icons/arrows/east.svg'; import rightArrowIcon from 'icons/arrows/east.svg';
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';
...@@ -8,16 +10,9 @@ import AddressLink from 'ui/shared/address/AddressLink'; ...@@ -8,16 +10,9 @@ import AddressLink from 'ui/shared/address/AddressLink';
import TxStatus from 'ui/shared/TxStatus'; import TxStatus from 'ui/shared/TxStatus';
import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils'; import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils';
interface Props { type Props = InternalTransaction
type: string;
status: 'success' | 'failed' | 'pending';
from: { hash: string; alias?: string};
to: { hash: string; alias?: string};
value: number;
gasLimit: number;
}
const TxInternalTableItem = ({ type, status, from, to, value, gasLimit }: Props) => { const TxInternalTableItem = ({ type, from, to, value, success, error }: Props) => {
const typeTitle = TX_INTERNALS_ITEMS.find(({ id }) => id === type)?.title; const typeTitle = TX_INTERNALS_ITEMS.find(({ id }) => id === type)?.title;
return ( return (
...@@ -28,12 +23,12 @@ const TxInternalTableItem = ({ type, status, from, to, value, gasLimit }: Props) ...@@ -28,12 +23,12 @@ const TxInternalTableItem = ({ type, status, from, to, value, gasLimit }: Props)
<Tag colorScheme="cyan" mr={ 5 }>{ typeTitle }</Tag> <Tag colorScheme="cyan" mr={ 5 }>{ typeTitle }</Tag>
</Box> </Box>
) } ) }
<TxStatus status={ status }/> <TxStatus status={ success ? 'ok' : 'error' } errorText={ error }/>
</Td> </Td>
<Td> <Td>
<Address> <Address>
<AddressIcon hash={ from.hash }/> <AddressIcon hash={ from.hash }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ from.hash } alias={ from.alias } flexGrow={ 1 }/> <AddressLink ml={ 2 } fontWeight="500" hash={ from.hash } alias={ from.name } flexGrow={ 1 }/>
</Address> </Address>
</Td> </Td>
<Td px={ 0 }> <Td px={ 0 }>
...@@ -42,15 +37,16 @@ const TxInternalTableItem = ({ type, status, from, to, value, gasLimit }: Props) ...@@ -42,15 +37,16 @@ const TxInternalTableItem = ({ type, status, from, to, value, gasLimit }: Props)
<Td> <Td>
<Address> <Address>
<AddressIcon hash={ to.hash }/> <AddressIcon hash={ to.hash }/>
<AddressLink hash={ to.hash } alias={ to.alias } fontWeight="500" ml={ 2 }/> <AddressLink hash={ to.hash } alias={ to.name } fontWeight="500" ml={ 2 }/>
</Address> </Address>
</Td> </Td>
<Td isNumeric> <Td isNumeric>
{ value } { value }
</Td> </Td>
<Td isNumeric> { /* no gas limit in api yet */ }
{ /* <Td isNumeric>
{ gasLimit.toLocaleString('en') } { gasLimit.toLocaleString('en') }
</Td> </Td> */ }
</Tr> </Tr>
); );
}; };
......
import type { TxInternalsType } from 'types/api/tx'; import type { TxInternalsType } from 'types/api/internalTransaction';
export type Sort = 'value-asc' | 'value-desc' | 'gas-limit-asc' | 'gas-limit-desc'; export type Sort = 'value-asc' | 'value-desc' | 'gas-limit-asc' | 'gas-limit-desc';
export type SortField = 'value' | 'gas-limit'; export type SortField = 'value' | 'gas-limit';
...@@ -10,10 +10,10 @@ interface TxInternalsTypeItem { ...@@ -10,10 +10,10 @@ interface TxInternalsTypeItem {
export const TX_INTERNALS_ITEMS: Array<TxInternalsTypeItem> = [ export const TX_INTERNALS_ITEMS: Array<TxInternalsTypeItem> = [
{ title: 'Call', id: 'call' }, { title: 'Call', id: 'call' },
{ title: 'Delegate call', id: 'delegate_call' }, { title: 'Delegate call', id: 'delegatecall' },
{ title: 'Static call', id: 'static_call' }, { title: 'Static call', id: 'staticcall' },
{ title: 'Create', id: 'create' }, { title: 'Create', id: 'create' },
{ title: 'Create2', id: 'create2' }, { title: 'Create2', id: 'create2' },
{ title: 'Self-destruct', id: 'self_destruct' }, { title: 'Self-destruct', id: 'selfdestruct' },
{ title: 'Reward', id: 'reward' }, { title: 'Reward', id: 'reward' },
]; ];
import { Text, Grid, GridItem, Link, Tooltip, Button, Icon, useColorModeValue } from '@chakra-ui/react'; import { Text, Grid, GridItem, Link, Tooltip, Button, Icon, useColorModeValue } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { Log } from 'types/api/log';
import searchIcon from 'icons/search.svg'; import searchIcon from 'icons/search.svg';
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';
...@@ -8,12 +10,7 @@ import AddressLink from 'ui/shared/address/AddressLink'; ...@@ -8,12 +10,7 @@ import AddressLink from 'ui/shared/address/AddressLink';
import TxLogTopic from 'ui/tx/logs/TxLogTopic'; import TxLogTopic from 'ui/tx/logs/TxLogTopic';
import DecodedInputData from 'ui/tx/TxDecodedInputData'; import DecodedInputData from 'ui/tx/TxDecodedInputData';
interface Props { type Props = Log;
address: string;
topics: Array<{ hex: string }>;
data: string;
index: number;
}
const RowHeader = ({ children }: { children: React.ReactNode }) => ( const RowHeader = ({ children }: { children: React.ReactNode }) => (
<GridItem _notFirst={{ my: { base: 4, lg: 0 } }}> <GridItem _notFirst={{ my: { base: 4, lg: 0 } }}>
...@@ -21,7 +18,8 @@ const RowHeader = ({ children }: { children: React.ReactNode }) => ( ...@@ -21,7 +18,8 @@ const RowHeader = ({ children }: { children: React.ReactNode }) => (
</GridItem> </GridItem>
); );
const TxLogItem = ({ address, index, topics, data }: Props) => { 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');
...@@ -41,27 +39,31 @@ const TxLogItem = ({ address, index, topics, data }: Props) => { ...@@ -41,27 +39,31 @@ const TxLogItem = ({ address, index, topics, data }: Props) => {
<RowHeader>Address</RowHeader> <RowHeader>Address</RowHeader>
<GridItem display="flex" alignItems="center"> <GridItem display="flex" alignItems="center">
<Address> <Address>
<AddressIcon hash={ address }/> <AddressIcon hash={ address.hash }/>
<AddressLink hash={ address } ml={ 2 }/> <AddressLink hash={ address.hash } alias={ address.name } ml={ 2 }/>
</Address> </Address>
<Tooltip label="Find matches topic"> <Tooltip label="Find matches topic">
<Link ml={ 2 } 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={{ base: 9, lg: 'auto' }} size="sm" fontWeight={ 400 }> <Button variant="outline" colorScheme="gray" isActive ml="auto" size="sm" fontWeight={ 400 }>
{ index } { index }
</Button> </Button>
</Tooltip> </Tooltip>
</GridItem> </GridItem>
<RowHeader>Decode input data</RowHeader> { decoded && (
<GridItem> <>
<DecodedInputData/> <RowHeader>Decode input data</RowHeader>
</GridItem> <GridItem>
<DecodedInputData data={ decoded }/>
</GridItem>
</>
) }
<RowHeader>Topics</RowHeader> <RowHeader>Topics</RowHeader>
<GridItem> <GridItem>
{ topics.map((item, index) => <TxLogTopic key={ index } { ...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, Grid, GridItem, Skeleton, SkeletonCircle, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
const RowHeader = () => (
<GridItem _notFirst={{ my: { base: 4, lg: 0 } }} _first={{ alignSelf: 'center' }}>
<Skeleton h={ 6 } borderRadius="full" w="150px"/>
</GridItem>
);
const TopicRow = () => (
<Flex columnGap={ 3 }>
<SkeletonCircle size="6"/>
<Skeleton h={ 6 } w="70px" borderRadius="full"/>
<Skeleton h={ 6 } flexGrow={ 1 } borderRadius="full"/>
</Flex>
);
const TxLogSkeleton = () => {
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
return (
<Grid
gap={{ base: 2, lg: 8 }}
templateColumns={{ base: '1fr', lg: '200px 1fr' }}
py={ 8 }
_notFirst={{
borderTopWidth: '1px',
borderTopColor: borderColor,
}}
_first={{
pt: 0,
}}
>
<RowHeader/>
<GridItem display="flex" alignItems="center">
<SkeletonCircle size="6"/>
<Skeleton h={ 6 } flexGrow={ 1 } borderRadius="full" ml={ 2 } mr={ 9 }/>
<Skeleton h={ 8 } w={ 8 } borderRadius="base"/>
</GridItem>
<RowHeader/>
<GridItem>
<Skeleton h="150px" w="100%" borderRadius="base"/>
</GridItem>
<RowHeader/>
<GridItem display="flex" flexDir="column" rowGap={ 3 }>
<TopicRow/>
<TopicRow/>
<TopicRow/>
</GridItem>
<RowHeader/>
<GridItem>
<Skeleton h="60px" w="100%" borderRadius="base"/>
</GridItem>
</Grid>
);
};
export default TxLogSkeleton;
...@@ -23,22 +23,18 @@ const TxLogTopic = ({ hex, index }: Props) => { ...@@ -23,22 +23,18 @@ const TxLogTopic = ({ hex, index }: Props) => {
<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>
{ /* temporary condition juse to show different states of the component */ } <Select
{ /* delete when ther will be real data */ } size="sm"
{ index > 0 && ( borderRadius="base"
<Select value={ selectedDataType }
size="sm" onChange={ handleSelectChange }
borderRadius="base" focusBorderColor="none"
value={ selectedDataType } w="75px"
onChange={ handleSelectChange } mr={ 3 }
focusBorderColor="none" flexShrink={ 0 }
w="75px" >
mr={ 3 } { OPTIONS.map((option) => <option key={ option } value={ option }>{ option }</option>) }
flexShrink={ 0 } </Select>
>
{ OPTIONS.map((option) => <option key={ option } value={ option }>{ option }</option>) }
</Select>
) }
<Box overflow="hidden" whiteSpace="nowrap"> <Box overflow="hidden" whiteSpace="nowrap">
<HashStringShortenDynamic hash={ hex }/> <HashStringShortenDynamic hash={ hex }/>
</Box> </Box>
......
This diff is collapsed.
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