Commit 23155089 authored by tom's avatar tom

api for blocks

parent 24258d46
/* eslint-disable max-len */
export const block = {
height: 15006918,
size: 1754,
timestamp: 1662623567695,
transactionsNum: 99,
miner: {
name: 'Alex Emelyanov',
address: '0xdAd49e6CbDE849353ab27DeC6319E687BFc91A41',
},
minedIn: 20,
reward: {
'static': 2,
tx_fee: 0.1550895290904872,
},
burnt_fees: 0.15116230256264004,
gas_limit: 30000000,
gas_used: 12866397,
gas_target: 14266397,
base_fee_per_gas: 11.748611718,
data: {
hex: '0x7370657468303367c18300',
utf: 'speth03g��',
},
difficulty: '14,735,262,741,740,473',
totalDifficulty: '52,307,701,288,535,570,059,059',
hash: '0x1ea365d2144796f793883534aa51bf20d23292b19478994eede23dfc599e7c34',
parent_hash: '0x4585ac59fdfd84443d93c9216e5172f57ca204639d3e68d1908088b0ebd3e709',
parent_height: 29471346,
sha3_uncles: '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347',
state_root: '0x434fb3f234c8cffc1d915ad26ea360b0261d183ad37af89e31b839df4dd0b00f',
nonce: '0xde80e5b4dd984384',
};
/* eslint-disable max-len */
import { block } from './block';
export const blocks = [
{
...block,
timestamp: Date.now() - 25_000,
},
{
...block,
height: 15006917,
timestamp: Date.now() - 1_000 * 60 * 2,
miner: {
address: '0xdAd49e6CbDE849353ab27DeC6319E687BFc91A41',
name: undefined,
},
transactionsNum: 185,
size: 452,
gas_limit: 30000000,
gas_used: 15671326,
gas_target: 14671326,
burnt_fees: 0.3988042215537949,
},
{
...block,
height: 15006916,
timestamp: Date.now() - 1_000 * 60 * 60 * 17,
transactionsNum: 377,
size: 5222,
gas_limit: 30000000,
gas_used: 23856751,
gas_target: 28856751,
burnt_fees: 0.0000019660909367,
},
];
import type { NextApiRequest } from 'next';
import handler from 'lib/api/handler';
const getUrl = (req: NextApiRequest) => {
return `/v2/blocks?type=${ req.query.type }`;
};
const requestHandler = handler(getUrl, [ 'GET' ]);
export default requestHandler;
import type { AddressParam } from 'types/api/addressParams';
import type { Reward } from 'types/api/reward';
export type BlockType = 'block' | 'reorg' | 'uncle';
export interface Block {
height: number;
timestamp: string;
......@@ -23,12 +25,12 @@ export interface Block {
gas_target_percentage: number | null;
gas_used_percentage: number | null;
burnt_fees_percentage: number | null;
type: 'block' | 'reorg' | 'uncle';
type: BlockType;
tx_fees: string | null;
uncles_hashes: Array<string>;
}
export interface BlockResponse {
export interface BlocksResponse {
items: Array<Block>;
next_page_params: {
block_number: number;
......
import { Box, Text, Show } from '@chakra-ui/react';
import { Box, Text, Show, Alert } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import React from 'react';
import type { BlockType, BlocksResponse } from 'types/api/block';
import useFetch from 'lib/hooks/useFetch';
import BlocksList from 'ui/blocks/BlocksList';
import BlocksSkeletonMobile from 'ui/blocks/BlocksSkeletonMobile';
import BlocksTable from 'ui/blocks/BlocksTable';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import Pagination from 'ui/shared/Pagination';
import SkeletonTable from 'ui/shared/SkeletonTable';
interface Props {
type?: BlockType;
}
const BlocksContent = ({ type }: Props) => {
const fetch = useFetch();
const { data, isLoading, isError } = useQuery<unknown, unknown, BlocksResponse>(
[ 'blocks', type ],
async() => await fetch(`/api/blocks?type=${ type }`),
);
if (isLoading) {
return (
<>
<Show below="lg"><BlocksSkeletonMobile/></Show>
<Show above="lg">
<SkeletonTable columns={ [ '124px', '112px', '144px', '64px', '40%', '30%', '30%' ] }/>
</Show>
</>
);
}
if (isError) {
return <DataFetchAlert/>;
}
if (data.items.length === 0) {
return <Alert>There are no blocks.</Alert>;
}
const BlocksContent = () => {
return (
<>
<Text>Total of 15,044,883 blocks</Text>
<Show below="lg"><BlocksList/></Show>
<Show above="lg"><BlocksTable/></Show>
<Show below="lg"><BlocksList data={ data.items }/></Show>
<Show above="lg"><BlocksTable data={ data.items }/></Show>
<Box mx={{ base: 0, lg: 6 }} my={{ base: 6, lg: 3 }}>
<Pagination currentPage={ 1 }/>
</Box>
......
import { Box } from '@chakra-ui/react';
import React from 'react';
import { blocks } from 'data/blocks';
import type { Block } from 'types/api/block';
import BlocksListItem from 'ui/blocks/BlocksListItem';
const BlocksList = () => {
interface Props {
data: Array<Block>;
}
const BlocksList = ({ data }: Props) => {
return (
<Box mt={ 8 }>
{ blocks.map((item, index) => <BlocksListItem key={ item.height } data={ item } isPending={ index === 0 }/>) }
{ data.map((item, index) => <BlocksListItem key={ item.height } data={ item } isPending={ index === 0 }/>) }
</Box>
);
};
......
import { Flex, Link, Spinner, Text, Box, Icon, useColorModeValue } from '@chakra-ui/react';
import { utils } from 'ethers';
import React from 'react';
import type ArrayElement from 'types/utils/ArrayElement';
import type { Block } from 'types/api/block';
import type { blocks } from 'data/blocks';
import flameIcon from 'icons/flame.svg';
import getBlockReward from 'lib/block/getBlockReward';
import dayjs from 'lib/date/dayjs';
import useNetwork from 'lib/hooks/useNetwork';
import useLink from 'lib/link/useLink';
......@@ -14,7 +15,7 @@ import GasUsedToTargetRatio from 'ui/shared/GasUsedToTargetRatio';
import Utilization from 'ui/shared/Utilization';
interface Props {
data: ArrayElement<typeof blocks>;
data: Block;
isPending?: boolean;
}
......@@ -22,6 +23,7 @@ const BlocksListItem = ({ data, isPending }: Props) => {
const spinnerEmptyColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
const link = useLink();
const network = useNetwork();
const { totalReward, burntFees, txFees } = getBlockReward(data);
return (
<AccountListItemMobile rowGap={ 3 }>
......@@ -43,29 +45,29 @@ const BlocksListItem = ({ data, isPending }: Props) => {
</Flex>
<Flex columnGap={ 2 }>
<Text fontWeight={ 500 }>Miner</Text>
<AddressLink alias={ data.miner?.name } hash={ data.miner.address } truncation="constant"/>
<AddressLink alias={ data.miner.name } hash={ data.miner.hash } truncation="constant"/>
</Flex>
<Flex columnGap={ 2 }>
<Text fontWeight={ 500 }>Txn</Text>
<Text variant="secondary">{ data.transactionsNum }</Text>
<Text variant="secondary">{ data.tx_count }</Text>
</Flex>
<Box>
<Text fontWeight={ 500 }>Gas used</Text>
<Flex columnGap={ 4 }>
<Text variant="secondary">{ data.gas_used.toLocaleString('en') }</Text>
<Utilization colorScheme="gray" value={ data.gas_used / data.gas_limit }/>
<GasUsedToTargetRatio used={ data.gas_used } target={ data.gas_target }/>
<Text variant="secondary">{ utils.commify(data.gas_used) }</Text>
<Utilization colorScheme="gray" value={ utils.parseUnits(data.gas_used).mul(10_000).div(utils.parseUnits(data.gas_limit)).toNumber() / 10_000 }/>
<GasUsedToTargetRatio value={ data.gas_target_percentage || undefined }/>
</Flex>
</Box>
<Flex columnGap={ 2 }>
<Text fontWeight={ 500 }>Reward { network?.currency }</Text>
<Text variant="secondary">{ (data.reward.static + data.reward.tx_fee - data.burnt_fees).toLocaleString('en', { maximumFractionDigits: 5 }) }</Text>
<Text variant="secondary">{ utils.formatUnits(totalReward) }</Text>
</Flex>
<Flex>
<Text fontWeight={ 500 }>Burnt fees</Text>
<Icon as={ flameIcon } boxSize={ 5 } color="gray.500" ml={ 2 }/>
<Text variant="secondary" ml={ 1 }>{ data.burnt_fees.toLocaleString('en', { maximumFractionDigits: 6 }) }</Text>
<Utilization ml={ 4 } value={ data.burnt_fees / data.reward.tx_fee }/>
<Text variant="secondary" ml={ 1 }>{ utils.formatUnits(burntFees) }</Text>
<Utilization ml={ 4 } value={ burntFees.mul(10_000).div(txFees).toNumber() / 10_000 }/>
</Flex>
</AccountListItemMobile>
);
......
import { Skeleton, Flex, Box, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
const BlocksSkeletonMobile = () => {
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
return (
<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 } justifyContent="space-between">
<Skeleton w="75px"/>
<Skeleton w="90px"/>
</Flex>
<Skeleton h={ 6 } w="130px"/>
<Skeleton h={ 6 } w="180px"/>
<Skeleton h={ 6 } w="60px"/>
<Skeleton h={ 6 } w="100%"/>
<Skeleton h={ 6 } w="170px"/>
<Skeleton h={ 6 } w="100%"/>
</Flex>
)) }
</Box>
);
};
export default BlocksSkeletonMobile;
import { Table, Thead, Tbody, Tr, Th, TableContainer } from '@chakra-ui/react';
import React from 'react';
import { blocks } from 'data/blocks';
import type { Block } from 'types/api/block';
import useNetwork from 'lib/hooks/useNetwork';
import BlocksTableItem from 'ui/blocks/BlocksTableItem';
const BlocksTable = () => {
interface Props {
data: Array<Block>;
}
const BlocksTable = ({ data }: Props) => {
const network = useNetwork();
return (
......@@ -23,7 +28,7 @@ const BlocksTable = () => {
</Tr>
</Thead>
<Tbody>
{ blocks.map((item, index) => <BlocksTableItem key={ item.height } data={ item } isPending={ index === 0 }/>) }
{ data.map((item, index) => <BlocksTableItem key={ item.height } data={ item } isPending={ index === 0 }/>) }
</Tbody>
</Table>
</TableContainer>
......
import { Tr, Td, Text, Link, Flex, Box, Icon, Tooltip, Spinner, useColorModeValue } from '@chakra-ui/react';
import { utils } from 'ethers';
import React from 'react';
import type ArrayElement from 'types/utils/ArrayElement';
import type { Block } from 'types/api/block';
import type { blocks } from 'data/blocks';
import flameIcon from 'icons/flame.svg';
import getBlockReward from 'lib/block/getBlockReward';
import dayjs from 'lib/date/dayjs';
import useLink from 'lib/link/useLink';
import AddressLink from 'ui/shared/address/AddressLink';
......@@ -12,7 +13,7 @@ import GasUsedToTargetRatio from 'ui/shared/GasUsedToTargetRatio';
import Utilization from 'ui/shared/Utilization';
interface Props {
data: ArrayElement<typeof blocks>;
data: Block;
isPending?: boolean;
}
......@@ -20,6 +21,7 @@ const BlocksTableItem = ({ data, isPending }: Props) => {
const link = useLink();
const spinnerEmptyColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
const { totalReward, burntFees, txFees } = getBlockReward(data);
return (
<Tr>
......@@ -37,25 +39,28 @@ const BlocksTableItem = ({ data, isPending }: Props) => {
</Td>
<Td fontSize="sm">{ data.size.toLocaleString('en') } bytes</Td>
<Td fontSize="sm">
<AddressLink alias={ data.miner?.name } hash={ data.miner.address } truncation="constant"/>
<AddressLink alias={ data.miner.name } hash={ data.miner.hash } truncation="constant"/>
</Td>
<Td isNumeric fontSize="sm">{ data.transactionsNum }</Td>
<Td isNumeric fontSize="sm">{ data.tx_count }</Td>
<Td fontSize="sm">
<Box>{ data.gas_used.toLocaleString('en') }</Box>
<Box>{ utils.commify(data.gas_used) }</Box>
<Flex mt={ 2 }>
<Utilization colorScheme="gray" value={ data.gas_used / data.gas_limit }/>
<GasUsedToTargetRatio ml={ 2 } used={ data.gas_used } target={ data.gas_target }/>
<Utilization
colorScheme="gray"
value={ utils.parseUnits(data.gas_used).mul(10_000).div(utils.parseUnits(data.gas_limit)).toNumber() / 10_000 }
/>
<GasUsedToTargetRatio ml={ 2 } value={ data.gas_target_percentage || undefined }/>
</Flex>
</Td>
<Td fontSize="sm">{ (data.reward.static + data.reward.tx_fee - data.burnt_fees).toLocaleString('en', { maximumFractionDigits: 5 }) }</Td>
<Td fontSize="sm">{ utils.formatUnits(totalReward) }</Td>
<Td fontSize="sm">
<Flex alignItems="center" columnGap={ 1 }>
<Icon as={ flameIcon } boxSize={ 5 } color={ useColorModeValue('gray.500', 'inherit') }/>
{ data.burnt_fees.toLocaleString('en', { maximumFractionDigits: 6 }) }
{ utils.formatUnits(burntFees) }
</Flex>
<Tooltip label="Burnt fees / Txn fees * 100%">
<Box>
<Utilization mt={ 2 } value={ data.burnt_fees / data.reward.tx_fee }/>
<Utilization mt={ 2 } value={ burntFees.mul(10_000).div(txFees).toNumber() / 10_000 }/>
</Box>
</Tooltip>
</Td>
......
......@@ -9,8 +9,8 @@ import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs';
const TABS: Array<RoutedTab> = [
{ routeName: 'blocks', title: 'All', component: <BlocksContent/> },
{ routeName: 'blocks_reorgs', title: 'Forked', component: <BlocksContent/> },
{ routeName: 'blocks_uncles', title: 'Uncles', component: <BlocksContent/> },
{ routeName: 'blocks_reorgs', title: 'Forked', component: <BlocksContent type="reorg"/> },
{ routeName: 'blocks_uncles', title: 'Uncles', component: <BlocksContent type="uncle"/> },
];
export interface Props {
......
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