Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add confirmations next to rewards and other entry that could benefits from such details #1032

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 50 additions & 35 deletions explorer/src/components/Consensus/Account/AccountLatestRewards.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { StatusIcon } from '@/components/common/StatusIcon'
import { Tooltip } from '@/components/common/Tooltip'
import { INTERNAL_ROUTES } from 'constants/routes'
import { AccountByIdQuery } from 'gql/graphql'
import useIndexers from 'hooks/useIndexers'
import Link from 'next/link'
import { useParams, useRouter } from 'next/navigation'
import { FC } from 'react'
import { useConsensusStates } from 'states/consensus'
import { AccountIdParam } from 'types/app'
import { bigNumberToNumber } from 'utils/number'

Expand All @@ -15,6 +18,7 @@ interface AccountLatestRewardsProps {
export const AccountLatestRewards: FC<AccountLatestRewardsProps> = ({ rewards }) => {
const { network, section, tokenSymbol } = useIndexers()
const { accountId } = useParams<AccountIdParam>()
const lastBlockNumber = useConsensusStates((state) => state.lastBlockNumber)
const { push } = useRouter()

return (
Expand All @@ -33,42 +37,53 @@ export const AccountLatestRewards: FC<AccountLatestRewardsProps> = ({ rewards })
</div>
<div className='w-full'>
<ol className='relative w-full border-l border-purpleLight dark:border-purpleLighterAccent'>
{rewards.map(({ id, rewardType, blockHeight, amount }, index) => (
<li
key={`${id}-account-rewards-block`}
className={`flex w-full justify-between ${
index !== rewards.length - 1 && 'mb-[26px]'
}`}
>
<div className='w-full flex-1 grow'>
<div
className={`absolute -left-1.5 size-3 rounded-full ${
index === 0
? 'bg-primaryAccent dark:bg-primaryAccent'
: 'bg-purpleLight dark:bg-purpleLighterAccent'
}`}
></div>
<div className='-mt-1 ml-4 flex-1 grow text-[13px] font-normal text-grayDark dark:text-white '>
<Link
key={`${id}-account-index`}
className='hover:text-primaryAccent'
href={INTERNAL_ROUTES.blocks.id.page(network, section, blockHeight)}
>
{blockHeight}
</Link>
{rewards.map(({ id, rewardType, blockHeight, amount }, index) => {
const confirmations = lastBlockNumber ? Math.max(0, lastBlockNumber - blockHeight) : 0
return (
<li
key={`${id}-account-rewards-block`}
className={`flex w-full justify-between ${
index !== rewards.length - 1 && 'mb-[26px]'
}`}
>
<div className='w-full flex-1 grow'>
<div
className={`absolute -left-1.5 size-3 rounded-full ${
index === 0
? 'bg-primaryAccent dark:bg-primaryAccent'
: 'bg-purpleLight dark:bg-purpleLighterAccent'
}`}
></div>
<div className='-mt-1 ml-4 flex flex-1 grow items-center gap-2 text-[13px] font-normal text-grayDark dark:text-white'>
<Link
key={`${id}-account-index`}
className='hover:text-primaryAccent'
href={INTERNAL_ROUTES.blocks.id.page(network, section, blockHeight)}
>
{blockHeight}
</Link>
<Tooltip
text={
<span className='whitespace-nowrap'>{`${confirmations} confirmations`}</span>
}
direction='top'
>
<StatusIcon status={confirmations >= 10} isPending={confirmations < 10} />
</Tooltip>
</div>
</div>
</div>
<div className='-mt-1 w-full flex-1 grow text-center text-[13px] font-normal text-grayDark dark:text-white'>
{rewardType
.split('.')[1]
.split(/(?=[A-Z])/)
.join(' ')}
</div>
<div className='-mt-1 w-full flex-1 grow text-end text-[13px] font-normal text-grayDark dark:text-white'>
{bigNumberToNumber(amount)} {tokenSymbol}
</div>
</li>
))}
<div className='-mt-1 w-full flex-1 grow text-center text-[13px] font-normal text-grayDark dark:text-white'>
{rewardType
.split('.')[1]
.split(/(?=[A-Z])/)
.join(' ')}
</div>
<div className='-mt-1 w-full flex-1 grow text-end text-[13px] font-normal text-grayDark dark:text-white'>
{bigNumberToNumber(amount)} {tokenSymbol}
</div>
</li>
)
})}
</ol>
</div>
</div>
Expand Down
27 changes: 25 additions & 2 deletions explorer/src/components/Consensus/Account/AccountRewardList.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client'

import { StatusIcon } from '@/components/common/StatusIcon'
import { useApolloClient } from '@apollo/client'
import { shortString } from '@autonomys/auto-utils'
import { sendGAEvent } from '@next/third-parties/google'
Expand All @@ -22,6 +23,7 @@ import Link from 'next/link'
import { useParams } from 'next/navigation'
import { FC, useCallback, useEffect, useMemo, useState } from 'react'
import { useInView } from 'react-intersection-observer'
import { useConsensusStates } from 'states/consensus'
import { hasValue, isLoading, useQueryStates } from 'states/query'
import { AccountIdParam } from 'types/app'
import type { Cell } from 'types/table'
Expand All @@ -48,7 +50,7 @@ export const AccountRewardList: FC = () => {
const isLargeLaptop = useMediaQuery('(min-width: 1440px)')
const { accountId } = useParams<AccountIdParam>()
const inFocus = useWindowFocus()

const lastBlockNumber = useConsensusStates((state) => state.lastBlockNumber)
const orderBy = useMemo(
() =>
sorting && sorting.length > 0
Expand Down Expand Up @@ -171,8 +173,29 @@ export const AccountRewardList: FC = () => {
</div>
),
},
{
accessorKey: 'block_height',
header: 'Confirmation',
enableSorting: true,
cell: ({ row }: Cell<Row>) => {
const confirmations = lastBlockNumber
? Math.max(0, lastBlockNumber - row.original.block_height)
: 0
return (
<div
key={`${row.original.id}-account-confirmation`}
className='flex items-center gap-2'
>
<StatusIcon status={confirmations >= 10} isPending={confirmations < 10} />
<span className={confirmations < 10 ? 'text-orange-500' : 'text-green-500'}>
{confirmations}
</span>
</div>
)
},
},
],
[network, section, isLargeLaptop, tokenSymbol],
[network, section, isLargeLaptop, tokenSymbol, lastBlockNumber],
)

const pageCount = useMemo(
Expand Down
36 changes: 24 additions & 12 deletions explorer/src/components/common/OutOfSyncBanner.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { useQuery } from '@apollo/client'
import { blockNumber } from '@autonomys/auto-consensus'
import { activate, NetworkId } from '@autonomys/auto-utils'
import { EXTERNAL_ROUTES } from 'constants/routes'
import { LastBlockQuery } from 'gql/graphql'
import useIndexers from 'hooks/useIndexers'
import Link from 'next/link'
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'
import React, { FC, useCallback, useEffect, useMemo } from 'react'
import { useConsensusStates } from 'states/consensus'
import { LAST_BLOCK } from './query'

const NORMAL_BLOCKS_DIVERGENCE = 120
const POLL_INTERVAL = 12000

const OutOfSyncBanner: FC = () => {
const { network } = useIndexers()
Expand Down Expand Up @@ -47,35 +50,44 @@ const OutOfSyncBanner: FC = () => {

export const useOutOfSyncBanner = () => {
const { network } = useIndexers()
const [lastChainBlock, setLastChainBlock] = useState<number | null>(null)

const lastBlockNumber = useConsensusStates((state) => state.lastBlockNumber)
const setLastBlockNumber = useConsensusStates((state) => state.setLastBlockNumber)

const { data } = useQuery<LastBlockQuery>(LAST_BLOCK, {
pollInterval: 30000,
pollInterval: POLL_INTERVAL,
})

const getChainLastBlock = useCallback(async () => {
const api = await activate({ networkId: network })

const block = await api.rpc.chain.getBlock()

setLastChainBlock(block.block.header.number.toNumber())
}, [network])
try {
const api = await activate({ networkId: network })
setLastBlockNumber(await blockNumber(api))
await api.disconnect()
} catch (error) {
console.error('Error getting chain last block', error)
}
}, [network, setLastBlockNumber])

const lastBlock = useMemo(() => data && parseInt(data.lastBlock[0].height), [data])

const outOfSyncBanner = useMemo(
() =>
data &&
lastBlock &&
lastChainBlock !== null &&
lastBlock + NORMAL_BLOCKS_DIVERGENCE < lastChainBlock ? (
lastBlockNumber !== null &&
lastBlock + NORMAL_BLOCKS_DIVERGENCE < lastBlockNumber ? (
<OutOfSyncBanner />
) : null,
[data, lastBlock, lastChainBlock],
[data, lastBlock, lastBlockNumber],
)

useEffect(() => {
getChainLastBlock()
const interval = setInterval(() => {
getChainLastBlock()
}, POLL_INTERVAL)

return () => clearInterval(interval)
}, [getChainLastBlock])

return outOfSyncBanner
Expand Down
10 changes: 9 additions & 1 deletion explorer/src/states/consensus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ interface ConsensusDefaultState {
successfulBundles: SuccessfulBundle[]
deposits: Deposit[]
withdrawals: Withdrawal[]

// last block number
lastBlockNumber: number | null
}

interface ConsensusState extends ConsensusDefaultState {
Expand All @@ -63,6 +66,7 @@ interface ConsensusState extends ConsensusDefaultState {
setSuccessfulBundles: (successfulBundles: SuccessfulBundle[]) => void
setDeposits: (deposits: Deposit[]) => void
setWithdrawals: (withdrawals: Withdrawal[]) => void
setLastBlockNumber: (lastBlockNumber: number) => void
clear: () => void
}

Expand All @@ -89,6 +93,9 @@ const initialState: ConsensusDefaultState = {
successfulBundles: [],
deposits: [],
withdrawals: [],

// last block number
lastBlockNumber: null,
}

export const useConsensusStates = create<ConsensusState>()(
Expand Down Expand Up @@ -116,11 +123,12 @@ export const useConsensusStates = create<ConsensusState>()(
setSuccessfulBundles: (successfulBundles) => set(() => ({ successfulBundles })),
setDeposits: (deposits) => set(() => ({ deposits })),
setWithdrawals: (withdrawals) => set(() => ({ withdrawals })),
setLastBlockNumber: (lastBlockNumber) => set(() => ({ lastBlockNumber })),
clear: () => set(() => ({ ...initialState })),
}),
{
name: 'consensus-storage',
version: 2,
version: 3,
storage: createJSONStorage(() => localStorage),
serialize: (state) => JSON.stringify(state, bigIntSerializer),
deserialize: (str) => JSON.parse(str, bigIntDeserializer),
Expand Down
Loading