Commit 0ad366ce authored by tom's avatar tom

internal txs tab

parent b5cb5982
import type { TxInternalsType } from 'types/api/tx';
export const data = [
{
id: 1,
type: 'call' as TxInternalsType,
status: 'ok' as const,
from: { hash: '0x12E80C27BfFBB76b4A8d26FF2bfd3C9f310FFA01' },
to: { hash: '0xF7A558692dFB5F456e291791da7FAE8Dd046574e' },
value: 0.25207646303,
gasLimit: 369472,
},
{
id: 2,
type: 'delegate_call' as TxInternalsType,
status: 'error' as const,
from: { hash: '0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45' },
to: { hash: '0x12E80C27BfFBB76b4A8d26FF2bfd3C9f310FFA01' },
value: 0.5633333,
gasLimit: 340022,
},
{
id: 3,
type: 'static_call' as TxInternalsType,
status: 'error' as const,
from: { hash: '0x97Aa2EfcF35c0f4c9AaDDCa8c2330fa7A9533830' },
to: { hash: '0x35317007D203b8a86CA727ad44E473E40450E378' },
value: 0.421152366,
gasLimit: 509333,
},
];
import type { Transaction } from 'types/api/transaction';
import { tx } from './tx';
import type { TxType, TxStatus } from './tx';
import type { TxType } from './tx';
export const txs = [
{
......@@ -10,7 +12,7 @@ export const txs = [
},
{
...tx,
status: 'failed' as TxStatus,
status: 'error' as Transaction['status'],
errorText: 'Error: (Awaiting internal transactions for reason)',
txType: 'contract-call' as TxType,
method: 'CommitHash CommitHash CommitHash CommitHash',
......@@ -25,7 +27,7 @@ export const txs = [
},
{
...tx,
status: 'pending' as TxStatus,
status: null,
txType: 'token-transfer' as TxType,
method: 'Multicall',
address_from: {
......
export const WEI = BigInt(10 ** 18);
export const GWEI = BigInt(10 ** 9);
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 { AddressParam } from './addressParams';
export type TxInternalsType = 'call' | 'delegatecall' | 'staticcall' | 'create' | 'create2' | 'selfdestruct' | 'reward'
export interface InternalTransaction {
error?: string;
success: boolean;
type: string;
type: TxInternalsType;
transaction_hash: string;
from: AddressParam;
to: AddressParam;
......
export type TxInternalsType = 'call' | 'delegate_call' | 'static_call' | 'create' | 'create2' | 'self_destruct' | 'reward'
......@@ -136,7 +136,8 @@ const TxDecodedInputData = ({ data }: Props) => {
>
Data
</GridItem>
{ data.parameters.map(({ name, type, value }, index) => (
{ data.parameters.map(({ name, type, value }, index) => {
return (
<TableRow key={ name } name={ name } type={ type } isLast={ index === data.parameters.length - 1 }>
{ type === 'address' ? (
<Address justifyContent="space-between">
......@@ -144,13 +145,14 @@ const TxDecodedInputData = ({ data }: Props) => {
<CopyToClipboard text={ value }/>
</Address>
) : (
<Flex alignItems="center" justifyContent="space-between">
<Text>116842</Text>
<CopyToClipboard text="116842"/>
<Flex alignItems="flex-start" justifyContent="space-between" whiteSpace="normal" wordBreak="break-all">
<Text>{ value }</Text>
<CopyToClipboard text={ value }/>
</Flex>
) }
</TableRow>
)) }
);
}) }
</Grid>
);
};
......
......@@ -109,7 +109,7 @@ const TxDetails = () => {
>
<Address>
<AddressIcon hash={ data.from.hash }/>
<AddressLink ml={ 2 } hash={ data.from.hash }/>
<AddressLink ml={ 2 } hash={ data.from.hash } alias={ data.from.name }/>
<CopyToClipboard text={ data.from.hash }/>
</Address>
</DetailsInfoItem>
......@@ -123,7 +123,7 @@ const TxDetails = () => {
<AddressLink ml={ 2 } hash={ data.to.hash } alias={ data.to.name }/>
<CopyToClipboard text={ data.to.hash }/>
</Address>
{ /* todo_tom ask Nikita about tags and contract execution */ }
{ /* todo_tom ask Nikita about tags */ }
{ /* <Tag colorScheme="orange" variant="solid" flexShrink={ 0 }>SANA</Tag> */ }
{ /* <Tooltip label="Contract execution completed">
<chakra.span display="inline-flex">
......@@ -135,7 +135,6 @@ const TxDetails = () => {
<Icon as={ errorIcon } boxSize={ 4 } ml={ 2 } color="red.500" cursor="pointer"/>
</chakra.span>
</Tooltip> */ }
{ /* todo_tom ask Nikita about token */ }
{ /* <TokenSnippet symbol="UP" name="User Pay" hash="0xA17ed5dFc62D0a3E74D69a0503AE9FdA65d9f212" ml={ 3 }/> */ }
</DetailsInfoItem>
{ (data.token_transfers?.length || 0) > 0 && (
......
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 type { TxInternalsType } from 'types/api/tx';
import type ArrayElement from 'types/utils/ArrayElement';
import type { InternalTransactionsResponse, TxInternalsType, InternalTransaction } from 'types/api/internalTransaction';
import { data } from 'data/txInternal';
import useFetch from 'lib/hooks/useFetch';
import useIsMobile from 'lib/hooks/useIsMobile';
import { apos } from 'lib/html-entities';
import EmptySearchResult from 'ui/apps/EmptySearchResult';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import FilterInput from 'ui/shared/FilterInput';
import TxInternalsFilter from 'ui/tx/internals/TxInternalsFilter';
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 type { Sort, SortField } from 'ui/tx/internals/utils';
......@@ -26,7 +30,7 @@ const getNextSortValue = (field: SortField) => (prevValue: Sort | undefined) =>
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) {
case 'value-desc': {
const result = a.value > b.value ? -1 : 1;
......@@ -38,22 +42,23 @@ const sortFn = (sort: Sort | undefined) => (a: ArrayElement<typeof data>, b: Arr
return a.value === b.value ? 0 : result;
}
case 'gas-limit-desc': {
const result = a.gasLimit > b.gasLimit ? -1 : 1;
return a.gasLimit === b.gasLimit ? 0 : result;
}
// no gas limit in api yet
// case 'gas-limit-desc': {
// const result = a.gasLimit > b.gasLimit ? -1 : 1;
// return a.gasLimit === b.gasLimit ? 0 : result;
// }
case 'gas-limit-asc': {
const result = a.gasLimit > b.gasLimit ? 1 : -1;
return a.gasLimit === b.gasLimit ? 0 : result;
}
// case 'gas-limit-asc': {
// const result = a.gasLimit > b.gasLimit ? 1 : -1;
// return a.gasLimit === b.gasLimit ? 0 : result;
// }
default:
return 0;
}
};
const searchFn = (searchTerm: string) => (item: ArrayElement<typeof data>): boolean => {
const searchFn = (searchTerm: string) => (item: InternalTransaction): boolean => {
const formattedSearchTerm = searchTerm.toLowerCase();
return item.type.toLowerCase().includes(formattedSearchTerm) ||
item.from.hash.toLowerCase().includes(formattedSearchTerm) ||
......@@ -61,9 +66,19 @@ const searchFn = (searchTerm: string) => (item: ArrayElement<typeof data>): bool
};
const TxInternals = () => {
const router = useRouter();
const fetch = useFetch();
const [ filters, setFilters ] = React.useState<Array<TxInternalsType>>([]);
const [ searchTerm, setSearchTerm ] = React.useState<string>('');
const [ sort, setSort ] = React.useState<Sort>();
const { data, isLoading, isError } = useQuery<unknown, unknown, InternalTransactionsResponse>(
[ 'tx', router.query.id ],
async() => await fetch(`/api/transactions/${ router.query.id }/internal-transactions`),
{
enabled: Boolean(router.query.id),
},
);
const isMobile = useIsMobile();
......@@ -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 filteredData = data
const filteredData = data.items
.filter(({ type }) => filters.length > 0 ? filters.includes(type) : true)
.filter(searchFn(searchTerm))
.sort(sortFn(sort));
......
import { Popover, PopoverTrigger, PopoverContent, PopoverBody, CheckboxGroup, Checkbox, Text, useDisclosure } from '@chakra-ui/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 { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils';
......
import { Box } from '@chakra-ui/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 TxInternalsListItem from 'ui/tx/internals/TxInternalsListItem';
const TxInternalsList = ({ data }: { data: typeof txData}) => {
const TxInternalsList = ({ data }: { data: Array<InternalTransaction>}) => {
const selectedNetwork = useNetwork();
return (
<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>
);
};
......
import { Flex, Tag, Icon, Box, HStack, Text } from '@chakra-ui/react';
import capitalize from 'lodash/capitalize';
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 AccountListItemMobile from 'ui/shared/AccountListItemMobile';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
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 (
<AccountListItemMobile rowGap={ 3 }>
<Flex>
<Tag colorScheme="cyan" mr={ 2 }>{ capitalize(type) }</Tag>
<TxStatus status={ status }/>
<Tag colorScheme="cyan" mr={ 2 }>{ typeTitle }</Tag>
<TxStatus status={ success ? 'ok' : 'error' } errorText={ error }/>
</Flex>
<Box w="100%" display="flex" columnGap={ 3 }>
<Address width="calc((100% - 48px) / 2)">
......@@ -36,10 +37,11 @@ const TxInternalsListItem = ({ type, status, from, to, value, gasLimit, currency
<Text fontSize="sm" fontWeight={ 500 }>Value { currency }</Text>
<Text fontSize="sm" variant="secondary">{ value }</Text>
</HStack>
<HStack spacing={ 3 }>
{ /* no gas limit in api yet */ }
{ /* <HStack spacing={ 3 }>
<Text fontSize="sm" fontWeight={ 500 }>Gas limit</Text>
<Text fontSize="sm" variant="secondary">{ gasLimit.toLocaleString('en') }</Text>
</HStack>
</HStack> */ }
</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 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 useNetwork from 'lib/hooks/useNetwork';
import TxInternalsTableItem from 'ui/tx/internals/TxInternalsTableItem';
import type { Sort, SortField } from 'ui/tx/internals/utils';
interface Props {
data: typeof txData;
data: Array<InternalTransaction>;
sort: Sort | undefined;
onSortToggle: (field: SortField) => () => void;
}
......@@ -23,26 +24,27 @@ const TxInternalsTable = ({ data, sort, onSortToggle }: Props) => {
<Thead>
<Tr>
<Th width="28%">Type</Th>
<Th width="20%">From</Th>
<Th width="28%">From</Th>
<Th width="24px" px={ 0 }/>
<Th width="20%">To</Th>
<Th width="28%">To</Th>
<Th width="16%" isNumeric>
<Link display="flex" alignItems="center" justifyContent="flex-end" onClick={ onSortToggle('value') } columnGap={ 1 }>
{ sort?.includes('value') && <Icon as={ arrowIcon } boxSize={ 4 } transform={ sortIconTransform }/> }
Value { selectedNetwork?.currency }
</Link>
</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 }>
{ sort?.includes('gas-limit') && <Icon as={ arrowIcon } boxSize={ 4 } transform={ sortIconTransform }/> }
Gas limit
</Link>
</Th>
</Th> */ }
</Tr>
</Thead>
<Tbody>
{ data.map((item) => (
<TxInternalsTableItem key={ item.id } { ...item }/>
<TxInternalsTableItem key={ item.transaction_hash } { ...item }/>
)) }
</Tbody>
</Table>
......
import { Tr, Td, Tag, Icon, Box } from '@chakra-ui/react';
import React from 'react';
import type { InternalTransaction } from 'types/api/internalTransaction';
import rightArrowIcon from 'icons/arrows/east.svg';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
......@@ -8,16 +10,9 @@ import AddressLink from 'ui/shared/address/AddressLink';
import TxStatus from 'ui/shared/TxStatus';
import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils';
interface Props {
type: string;
status: 'ok' | 'error' | null;
from: { hash: string; alias?: string};
to: { hash: string; alias?: string};
value: number;
gasLimit: number;
}
type Props = InternalTransaction
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;
return (
......@@ -28,12 +23,12 @@ const TxInternalTableItem = ({ type, status, from, to, value, gasLimit }: Props)
<Tag colorScheme="cyan" mr={ 5 }>{ typeTitle }</Tag>
</Box>
) }
<TxStatus status={ status }/>
<TxStatus status={ success ? 'ok' : 'error' } errorText={ error }/>
</Td>
<Td>
<Address>
<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>
</Td>
<Td px={ 0 }>
......@@ -42,15 +37,16 @@ const TxInternalTableItem = ({ type, status, from, to, value, gasLimit }: Props)
<Td>
<Address>
<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>
</Td>
<Td isNumeric>
{ value }
</Td>
<Td isNumeric>
{ /* no gas limit in api yet */ }
{ /* <Td isNumeric>
{ gasLimit.toLocaleString('en') }
</Td>
</Td> */ }
</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 SortField = 'value' | 'gas-limit';
......@@ -10,10 +10,10 @@ interface TxInternalsTypeItem {
export const TX_INTERNALS_ITEMS: Array<TxInternalsTypeItem> = [
{ title: 'Call', id: 'call' },
{ title: 'Delegate call', id: 'delegate_call' },
{ title: 'Static call', id: 'static_call' },
{ title: 'Delegate call', id: 'delegatecall' },
{ title: 'Static call', id: 'staticcall' },
{ title: 'Create', id: 'create' },
{ title: 'Create2', id: 'create2' },
{ title: 'Self-destruct', id: 'self_destruct' },
{ title: 'Self-destruct', id: 'selfdestruct' },
{ title: 'Reward', id: 'reward' },
];
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