Commit 58df71fe authored by tom's avatar tom

base implementation

parent edad6594
......@@ -36,6 +36,7 @@ import type { TokensResponse, TokensFilters, TokenInstanceTransferResponse } fro
import type { TokenTransferResponse, TokenTransferFilters } from 'types/api/tokenTransfer';
import type { TransactionsResponseValidated, TransactionsResponsePending, Transaction } from 'types/api/transaction';
import type { TTxsFilters } from 'types/api/txsFilters';
import type { TxStateChanges } from 'types/api/txStateChanges';
import type { VisualizedContract } from 'types/api/visualization';
import type ArrayElement from 'types/utils/ArrayElement';
......@@ -158,6 +159,10 @@ export const RESOURCES = {
path: '/api/v2/transactions/:hash/raw-trace',
pathParams: [ 'hash' as const ],
},
tx_state_changes: {
path: '/api/v2/transactions/:hash/state-changes',
pathParams: [ 'hash' as const ],
},
// ADDRESSES
addresses: {
......@@ -449,6 +454,7 @@ Q extends 'tx_internal_txs' ? InternalTransactionsResponse :
Q extends 'tx_logs' ? LogsResponseTx :
Q extends 'tx_token_transfers' ? TokenTransferResponse :
Q extends 'tx_raw_trace' ? RawTracesResponse :
Q extends 'tx_state_changes' ? TxStateChanges :
Q extends 'addresses' ? AddressesResponse :
Q extends 'address' ? Address :
Q extends 'address_counters' ? AddressCounters :
......
......@@ -31,6 +31,8 @@ export type TokenTransfer = (
}
) & TokenTransferBase
export type TokenTotal = Erc20TotalPayload | Erc721TotalPayload | Erc1155TotalPayload | Array<Erc1155TotalPayload>;
interface TokenTransferBase {
type: 'token_transfer' | 'token_burning' | 'token_spawning' | 'token_minting';
tx_hash: string;
......
import type { AddressParam } from './addressParams';
import type { TokenInfo } from './token';
import type { TokenTotal } from './tokenTransfer';
export type TxStateChange = (TxStateChangeCoin | TxStateChangeToken) & {
address: AddressParam;
token: TokenInfo | null;
is_miner: boolean;
balance_before: string | null;
balance_after: string | null;
storage: Array<unknown> | null;
}
export interface TxStateChangeCoin {
type: 'coin';
change: string;
}
export interface TxStateChangeToken {
type: 'token';
change: {
direction: 'from' | 'to';
total: TokenTotal;
};
}
export type TxStateChanges = Array<TxStateChange>;
......@@ -17,16 +17,15 @@ import TxDetails from 'ui/tx/TxDetails';
import TxInternals from 'ui/tx/TxInternals';
import TxLogs from 'ui/tx/TxLogs';
import TxRawTrace from 'ui/tx/TxRawTrace';
import TxState from 'ui/tx/TxState';
import TxTokenTransfer from 'ui/tx/TxTokenTransfer';
// import TxState from 'ui/tx/TxState';
const TABS: Array<RoutedTab> = [
{ id: 'index', title: 'Details', component: <TxDetails/> },
{ id: 'token_transfers', title: 'Token transfers', component: <TxTokenTransfer/> },
{ id: 'internal', title: 'Internal txns', component: <TxInternals/> },
{ id: 'logs', title: 'Logs', component: <TxLogs/> },
// will be implemented later, api is not ready
// { id: 'state', title: 'State', component: <TxState/> },
{ id: 'state', title: 'State', component: <TxState/> },
{ id: 'raw_trace', title: 'Raw trace', component: <TxRawTrace/> },
];
......
import { Accordion, Text } from '@chakra-ui/react';
import { Accordion, Hide, Show, Skeleton, Text } from '@chakra-ui/react';
import React from 'react';
import useIsMobile from 'lib/hooks/useIsMobile';
import useApiQuery from 'lib/api/useApiQuery';
import { SECOND } from 'lib/consts';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import SkeletonList from 'ui/shared/skeletons/SkeletonList';
import SkeletonTable from 'ui/shared/skeletons/SkeletonTable';
import TxStateList from 'ui/tx/state/TxStateList';
import TxStateTable from 'ui/tx/state/TxStateTable';
import useFetchTxInfo from 'ui/tx/useFetchTxInfo';
import TxPendingAlert from './TxPendingAlert';
import TxSocketAlert from './TxSocketAlert';
const TxState = () => {
const isMobile = useIsMobile();
const list = isMobile ? <TxStateList/> : <TxStateTable/>;
const txInfo = useFetchTxInfo({ updateDelay: 5 * SECOND });
const { data, isLoading, isError } = useApiQuery('tx_state_changes', {
pathParams: { hash: txInfo.data?.hash },
queryOptions: {
enabled: Boolean(txInfo.data?.hash) && Boolean(txInfo.data?.status),
},
});
if (!txInfo.isLoading && !txInfo.isError && !txInfo.data.status) {
return txInfo.socketStatus ? <TxSocketAlert status={ txInfo.socketStatus }/> : <TxPendingAlert/>;
}
if (isError || txInfo.isError) {
return <DataFetchAlert/>;
}
if (isLoading || txInfo.isLoading) {
return (
<>
<Show below="lg" ssr={ false }>
<Skeleton h={ 4 } borderRadius="full" w="100%"/>
<Skeleton h={ 4 } borderRadius="full" w="100%" mt={ 2 }/>
<Skeleton h={ 4 } borderRadius="full" w="100%" mt={ 2 }/>
<Skeleton h={ 4 } borderRadius="full" w="50%" mt={ 2 } mb={ 6 }/>
<SkeletonList/>
</Show>
<Hide below="lg" ssr={ false }>
<Skeleton h={ 6 } borderRadius="full" w="90%" mb={ 6 }/>
<SkeletonTable columns={ [ '28%', '20%', '24px', '20%', '16%', '16%' ] }/>
</Hide>
</>
);
}
return (
<>
......@@ -15,7 +54,12 @@ const TxState = () => {
A set of information that represents the current state is updated when a transaction takes place on the network. The below is a summary of those changes
</Text>
<Accordion allowMultiple defaultIndex={ [] }>
{ list }
<Hide below="lg" ssr={ false }>
<TxStateTable data={ data }/>
</Hide>
<Show below="lg" ssr={ false }>
<TxStateList data={ data }/>
</Show>
</Accordion>
</>
);
......
import { Box } from '@chakra-ui/react';
import React from 'react';
import { data } from 'data/txState';
import type { TxStateChanges } from 'types/api/txStateChanges';
import TxStateListItem from 'ui/tx/state/TxStateListItem';
const TxStateList = () => {
interface Props {
data: TxStateChanges;
}
const TxStateList = ({ data }: Props) => {
return (
<Box mt={ 6 }>
{ data.map((item, index) => <TxStateListItem key={ index } { ...item }/>) }
{ data.map((item, index) => <TxStateListItem key={ index } data={ item }/>) }
</Box>
);
};
......
import { AccordionItem, AccordionButton, AccordionIcon, Button, Box, Flex, Text, Link, StatArrow, Stat, AccordionPanel } from '@chakra-ui/react';
import { AccordionItem, AccordionButton, AccordionIcon, Button, Box, Flex, Text, StatArrow, Stat } from '@chakra-ui/react';
import capitalize from 'lodash/capitalize';
import React from 'react';
import type ArrayElement from 'types/utils/ArrayElement';
import type { TxStateChange } from 'types/api/txStateChanges';
import appConfig from 'configs/app/config';
import type { data } from 'data/txState';
import { nbsp } from 'lib/html-entities';
// import { nbsp } from 'lib/html-entities';
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
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 ListItemMobile from 'ui/shared/ListItemMobile';
import TxStateStorageItem from './TxStateStorageItem';
// import TxStateStorageItem from './TxStateStorageItem';
import { formatData } from './utils';
type Props = ArrayElement<typeof data>;
interface Props {
data: TxStateChange;
}
const TxStateListItem = ({ storage, address, miner, after, before, diff }: Props) => {
const TxStateListItem = ({ data }: Props) => {
const hasStorageData = Boolean(storage?.length);
const hasStorageData = false;
const { balanceBefore, balanceAfter, difference, isIncrease } = formatData(data);
return (
<ListItemMobile>
......@@ -46,50 +49,51 @@ const TxStateListItem = ({ storage, address, miner, after, before, diff }: Props
// AccordionButton has its own opacity rule when disabled
_disabled={{ opacity: 1 }}
>
{ storage?.length || '0' }
{ data.storage?.length || '0' }
</Button>
<AccordionIcon color="blue.600" width="30px"/>
</AccordionButton>
<Address flexGrow={ 1 }>
{ /* ??? */ }
{ /* <AddressIcon hash={ address }/> */ }
<AddressLink type="address" hash={ address } ml={ 2 }/>
<AddressIcon address={ data.address }/>
<AddressLink type="address" hash={ data.address.hash } ml={ 2 }/>
</Address>
</Flex>
{ hasStorageData && (
{ /* { hasStorageData && (
<AccordionPanel fontWeight={ 500 } p={ 0 }>
{ storage?.map((storageItem, index) => <TxStateStorageItem key={ index } storageItem={ storageItem }/>) }
{ data.storage?.map((storageItem, index) => <TxStateStorageItem key={ index } storageItem={ storageItem }/>) }
</AccordionPanel>
) }
) } */ }
<Flex rowGap={ 2 } flexDir="column" fontSize="sm" whiteSpace="pre" fontWeight={ 500 }>
{ data.is_miner && (
<Box>
<Text as="span">{ capitalize(getNetworkValidatorTitle()) }</Text>
<Link>{ miner }</Link>
<span> -</span>
</Box>
) }
<Box>
<Text as="span">Before { appConfig.network.currency.symbol } </Text>
<Text as="span" variant="secondary">{ before.balance }</Text>
<Text as="span" variant="secondary">{ balanceBefore }</Text>
</Box>
{ typeof before.nonce !== 'undefined' && (
{ /* { typeof before.nonce !== 'undefined' && (
<Box>
<Text as="span">Nonce:</Text>
<Text as="span" fontWeight={ 600 }>{ nbsp }{ before.nonce }</Text>
</Box>
) }
) } */ }
<Box>
<Text as="span">After { appConfig.network.currency.symbol } </Text>
<Text as="span" variant="secondary">{ after.balance }</Text>
<Text as="span" variant="secondary">{ balanceAfter }</Text>
</Box>
{ typeof after.nonce !== 'undefined' && (
{ /* { typeof after.nonce !== 'undefined' && (
<Box>
<Text as="span">Nonce:</Text>
<Text as="span" fontWeight={ 600 }>{ nbsp }{ after.nonce }</Text>
</Box>
) }
) } */ }
<Text>State difference { appConfig.network.currency.symbol }</Text>
<Stat>
{ diff }
<StatArrow ml={ 2 } type={ Number(diff) > 0 ? 'increase' : 'decrease' }/>
{ difference }
<StatArrow ml={ 2 } type={ isIncrease ? 'increase' : 'decrease' }/>
</Stat>
</Flex>
</>
......
......@@ -7,13 +7,18 @@ import {
import capitalize from 'lodash/capitalize';
import React from 'react';
import type { TxStateChanges } from 'types/api/txStateChanges';
import appConfig from 'configs/app/config';
import { data } from 'data/txState';
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
import { default as Thead } from 'ui/shared/TheadSticky';
import TxStateTableItem from 'ui/tx/state/TxStateTableItem';
const TxStateTable = () => {
interface Props {
data: TxStateChanges;
}
const TxStateTable = ({ data }: Props) => {
return (
<Table variant="simple" minWidth="950px" size="sm" w="auto" mt={ 6 }>
<Thead top={ 0 }>
......@@ -21,13 +26,13 @@ const TxStateTable = () => {
<Th width="92px">Storage</Th>
<Th width="146px">Address</Th>
<Th width="120px">{ capitalize(getNetworkValidatorTitle()) }</Th>
<Th width="33%" isNumeric>{ `After ${ appConfig.network.currency.symbol }` }</Th>
<Th width="33%" isNumeric>{ `Before ${ appConfig.network.currency.symbol }` }</Th>
<Th width="33%" isNumeric>{ `After ${ appConfig.network.currency.symbol }` }</Th>
<Th width="33%" isNumeric>{ `State difference ${ appConfig.network.currency.symbol }` }</Th>
</Tr>
</Thead>
<Tbody>
{ data.map((item, index) => <TxStateTableItem txStateItem={ item } key={ index }/>) }
{ data.map((item, index) => <TxStateTableItem data={ item } key={ index }/>) }
</Tbody>
</Table>
);
......
import {
AccordionItem,
AccordionButton,
AccordionPanel,
// AccordionPanel,
AccordionIcon,
Text,
Box,
Tr,
Td,
Stat,
StatArrow,
Portal,
Link,
// Portal,
Button,
} from '@chakra-ui/react';
import React, { useRef } from 'react';
import type { TTxStateItem } from 'data/txState';
import { nbsp } from 'lib/html-entities';
import type { TxStateChange } from 'types/api/txStateChanges';
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 { nbsp } from 'lib/html-entities';
// import TxStateStorageItem from './TxStateStorageItem';
import { formatData } from './utils';
import TxStateStorageItem from './TxStateStorageItem';
interface Props {
data: TxStateChange;
}
const TxStateTableItem = ({ txStateItem }: { txStateItem: TTxStateItem }) => {
const TxStateTableItem = ({ data }: Props) => {
const ref = useRef<HTMLTableDataCellElement>(null);
const hasStorageData = Boolean(txStateItem.storage?.length);
const hasStorageData = false;
const { balanceBefore, balanceAfter, difference, isIncrease } = formatData(data);
return (
<>
......@@ -51,39 +57,40 @@ const TxStateTableItem = ({ txStateItem }: { txStateItem: TTxStateItem }) => {
// AccordionButton has its own opacity rule when disabled
_disabled={{ opacity: 1 }}
>
{ txStateItem.storage?.length || '0' }
{ data.storage?.length || '0' }
</Button>
<AccordionIcon color="blue.600" width="30px"/>
</AccordionButton>
</Td>
<Td border={ 0 }>
<Address height="30px">
{ /* ??? */ }
{ /* <AddressIcon hash={ txStateItem.address }/> */ }
<AddressLink type="address" hash={ txStateItem.address } fontWeight="500" truncation="constant" ml={ 2 }/>
<AddressIcon address={ data.address }/>
<AddressLink type="address" hash={ data.address.hash } fontWeight="500" truncation="constant" ml={ 2 }/>
</Address>
</Td>
<Td border={ 0 } lineHeight="30px"><Link>{ txStateItem.miner }</Link></Td>
<Td border={ 0 } lineHeight="30px">
{ data.is_miner ? 'Validator' : '-' }
</Td>
<Td border={ 0 } isNumeric lineHeight="30px">
<Box>{ txStateItem.after.balance }</Box>
{ typeof txStateItem.after.nonce !== 'undefined' && (
<Box>{ balanceBefore }</Box>
{ /* { typeof txStateItem.after.nonce !== 'undefined' && (
<Box justifyContent="end" display="inline-flex">Nonce: <Text fontWeight={ 600 }>{ nbsp + txStateItem.after.nonce }</Text></Box>
) }
) } */ }
</Td>
<Td border={ 0 } isNumeric lineHeight="30px">{ txStateItem.before.balance }</Td>
<Td border={ 0 } isNumeric lineHeight="30px">{ balanceAfter }</Td>
<Td border={ 0 } isNumeric lineHeight="30px">
<Stat>
{ txStateItem.diff }
<StatArrow ml={ 2 } type={ Number(txStateItem.diff) > 0 ? 'increase' : 'decrease' }/>
{ difference }
<StatArrow ml={ 2 } type={ isIncrease ? 'increase' : 'decrease' }/>
</Stat>
</Td>
{ hasStorageData && (
{ /* { hasStorageData && (
<Portal containerRef={ ref }>
<AccordionPanel fontWeight={ 500 }>
{ txStateItem.storage?.map((storageItem, index) => <TxStateStorageItem key={ index } storageItem={ storageItem }/>) }
{ data.storage?.map((storageItem, index) => <TxStateStorageItem key={ index } storageItem={ storageItem }/>) }
</AccordionPanel>
</Portal>
) }
) } */ }
</>
) }
</AccordionItem>
......
import BigNumber from 'bignumber.js';
import type { TxStateChange } from 'types/api/txStateChanges';
import appConfig from 'configs/app/config';
export function formatData(data: TxStateChange) {
if (data.type === 'coin') {
const beforeBn = BigNumber(data.balance_before || '0').div(10 ** appConfig.network.currency.decimals);
const afterBn = BigNumber(data.balance_after || '0').div(10 ** appConfig.network.currency.decimals);
const differenceBn = afterBn.minus(beforeBn);
return {
balanceBefore: beforeBn.toFormat(),
balanceAfter: afterBn.toFormat(),
difference: differenceBn.toFormat(),
isIncrease: beforeBn.lte(afterBn),
};
}
const difference = Number(data.balance_after) - Number(data.balance_before);
return {
balanceBefore: data.balance_before,
balanceAfter: data.balance_after,
difference,
isIncrease: difference > 0,
};
}
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