Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
F
frontend
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
vicotor
frontend
Commits
ac627348
Commit
ac627348
authored
Oct 07, 2022
by
tom
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
block data
parent
95d1bcbb
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
185 additions
and
59 deletions
+185
-59
getBlockReward.ts
lib/block/getBlockReward.ts
+17
-0
index.ts
pages/api/blocks/[id]/index.ts
+11
-0
block.ts
types/api/block.ts
+4
-4
reward.ts
types/api/reward.ts
+1
-1
BlockDetails.tsx
ui/block/BlockDetails.tsx
+106
-50
BlockDetailsSkeleton.tsx
ui/block/details/BlockDetailsSkeleton.tsx
+41
-0
GasUsedToTargetRatio.tsx
ui/shared/GasUsedToTargetRatio.tsx
+5
-4
No files found.
lib/block/getBlockReward.ts
0 → 100644
View file @
ac627348
import
{
utils
}
from
'
ethers
'
;
import
type
{
Block
}
from
'
types/api/block
'
;
export
default
function
getBlockReward
(
block
:
Block
)
{
const
txFees
=
utils
.
parseUnits
(
block
.
tx_fees
||
'
0
'
,
'
wei
'
);
const
burntFees
=
utils
.
parseUnits
(
String
(
block
.
burnt_fees
||
'
0
'
),
'
wei
'
);
const
totalReward
=
utils
.
parseUnits
(
String
(
block
.
rewards
?.
find
(({
type
})
=>
type
===
'
Miner Reward
'
||
type
===
'
Validator Reward
'
)?.
reward
)
||
'
0
'
,
'
wei
'
);
const
staticReward
=
totalReward
.
sub
(
txFees
).
add
(
burntFees
);
return
{
totalReward
,
staticReward
,
txFees
,
burntFees
,
};
}
pages/api/blocks/[id]/index.ts
0 → 100644
View file @
ac627348
import
type
{
NextApiRequest
}
from
'
next
'
;
import
handler
from
'
lib/api/handler
'
;
const
getUrl
=
(
req
:
NextApiRequest
)
=>
{
return
`/v2/blocks/
${
req
.
query
.
id
}
`
;
};
const
requestHandler
=
handler
(
getUrl
,
[
'
GET
'
]);
export
default
requestHandler
;
types/api/block.ts
View file @
ac627348
...
...
@@ -9,10 +9,10 @@ export interface Block {
size
:
number
;
hash
:
string
;
parent_hash
:
string
;
difficulty
:
number
;
total_difficulty
:
number
;
gas_used
:
number
;
gas_limit
:
number
;
difficulty
:
string
;
total_difficulty
:
string
;
gas_used
:
string
;
gas_limit
:
string
;
nonce
:
number
;
base_fee_per_gas
:
number
|
null
;
burnt_fees
:
number
|
null
;
...
...
types/api/reward.ts
View file @
ac627348
export
interface
Reward
{
reward
:
number
;
type
:
'
Miner Reward
'
|
'
Emission Reward
'
|
'
Chore Reward
'
|
'
Uncle Reward
'
;
type
:
'
Miner Reward
'
|
'
Validator Reward
'
|
'
Emission Reward
'
|
'
Chore Reward
'
|
'
Uncle Reward
'
;
}
ui/block/BlockDetails.tsx
View file @
ac627348
import
{
Grid
,
GridItem
,
Text
,
Icon
,
Link
,
Box
,
Tooltip
}
from
'
@chakra-ui/react
'
;
import
{
useQuery
}
from
'
@tanstack/react-query
'
;
import
{
utils
}
from
'
ethers
'
;
import
{
useRouter
}
from
'
next/router
'
;
import
React
from
'
react
'
;
import
{
scroller
,
Element
}
from
'
react-scroll
'
;
import
{
block
}
from
'
data/block
'
;
import
type
{
Block
}
from
'
types/api/block
'
;
import
clockIcon
from
'
icons/clock.svg
'
;
import
flameIcon
from
'
icons/flame.svg
'
;
import
getBlockReward
from
'
lib/block/getBlockReward
'
;
import
dayjs
from
'
lib/date/dayjs
'
;
import
useFetch
from
'
lib/hooks/useFetch
'
;
import
useNetwork
from
'
lib/hooks/useNetwork
'
;
import
{
space
}
from
'
lib/html-entities
'
;
import
useLink
from
'
lib/link/useLink
'
;
import
BlockDetailsSkeleton
from
'
ui/block/details/BlockDetailsSkeleton
'
;
import
AddressLink
from
'
ui/shared/address/AddressLink
'
;
import
CopyToClipboard
from
'
ui/shared/CopyToClipboard
'
;
import
DataFetchAlert
from
'
ui/shared/DataFetchAlert
'
;
import
DetailsInfoItem
from
'
ui/shared/DetailsInfoItem
'
;
import
GasUsedToTargetRatio
from
'
ui/shared/GasUsedToTargetRatio
'
;
import
HashStringShortenDynamic
from
'
ui/shared/HashStringShortenDynamic
'
;
...
...
@@ -24,6 +31,15 @@ const BlockDetails = () => {
const
link
=
useLink
();
const
router
=
useRouter
();
const
network
=
useNetwork
();
const
fetch
=
useFetch
();
const
{
data
,
isLoading
,
isError
}
=
useQuery
<
unknown
,
unknown
,
Block
>
(
[
'
block
'
,
router
.
query
.
id
],
async
()
=>
await
fetch
(
`/api/blocks/
${
router
.
query
.
id
}
`
),
{
enabled
:
Boolean
(
router
.
query
.
id
),
},
);
const
handleCutClick
=
React
.
useCallback
(()
=>
{
setIsExpanded
((
flag
)
=>
!
flag
);
...
...
@@ -33,7 +49,16 @@ const BlockDetails = () => {
});
},
[]);
if
(
isLoading
)
{
return
<
BlockDetailsSkeleton
/>;
}
if
(
isError
)
{
return
<
DataFetchAlert
/>;
}
const
sectionGap
=
<
GridItem
colSpan=
{
{
base
:
undefined
,
lg
:
2
}
}
mt=
{
{
base
:
1
,
lg
:
4
}
}
/>;
const
{
totalReward
,
staticReward
,
burntFees
,
txFees
}
=
getBlockReward
(
data
);
return
(
<
Grid
columnGap=
{
8
}
rowGap=
{
{
base
:
3
,
lg
:
3
}
}
templateColumns=
{
{
base
:
'
minmax(0, 1fr)
'
,
lg
:
'
auto minmax(0, 1fr)
'
}
}
overflow=
"hidden"
>
...
...
@@ -41,30 +66,30 @@ const BlockDetails = () => {
title=
"Block height"
hint=
"The block height of a particular block is defined as the number of blocks preceding it in the blockchain."
>
{
block
.
height
}
{
data
.
height
}
<
PrevNext
ml=
{
6
}
/>
</
DetailsInfoItem
>
<
DetailsInfoItem
title=
"Size"
hint=
"Size of the block in bytes."
>
{
block
.
size
.
toLocaleString
(
'
en
'
)
}
{
data
.
size
.
toLocaleString
(
'
en
'
)
}
</
DetailsInfoItem
>
<
DetailsInfoItem
title=
"Timestamp"
hint=
"Date & time at which block was produced."
>
<
Icon
as=
{
clockIcon
}
boxSize=
{
5
}
color=
"gray.500"
/>
<
Text
ml=
{
1
}
>
{
dayjs
(
block
.
timestamp
).
fromNow
()
}
</
Text
>
<
Text
ml=
{
1
}
>
{
dayjs
(
data
.
timestamp
).
fromNow
()
}
</
Text
>
<
TextSeparator
/>
<
Text
whiteSpace=
"normal"
>
{
dayjs
(
block
.
timestamp
).
format
(
'
LLLL
'
)
}
</
Text
>
<
Text
whiteSpace=
"normal"
>
{
dayjs
(
data
.
timestamp
).
format
(
'
LLLL
'
)
}
</
Text
>
</
DetailsInfoItem
>
<
DetailsInfoItem
title=
"Transactions"
hint=
"The number of transactions in the block."
>
<
Link
href=
{
link
(
'
block_txs
'
,
{
id
:
router
.
query
.
id
})
}
>
{
block
.
transactionsNum
}
transactions
{
data
.
tx_count
}
transactions
</
Link
>
</
DetailsInfoItem
>
<
DetailsInfoItem
...
...
@@ -72,9 +97,10 @@ const BlockDetails = () => {
hint=
"A block producer who successfully included the block onto the blockchain."
columnGap=
{
1
}
>
<
AddressLink
hash=
{
block
.
miner
.
address
}
/>
{
block
.
miner
.
name
&&
<
Text
>
(Miner:
{
block
.
miner
.
name
}
)
</
Text
>
}
<
Text
>
{
dayjs
.
duration
(
block
.
minedIn
,
'
second
'
).
humanize
(
true
)
}
</
Text
>
<
AddressLink
hash=
{
data
.
miner
.
hash
}
/>
{
data
.
miner
.
name
&&
<
Text
>
(Miner:
{
data
.
miner
.
name
}
)
</
Text
>
}
{
/* api doesn't return the block processing time yet */
}
{
/* <Text>{ dayjs.duration(block.minedIn, 'second').humanize(true) }</Text> */
}
</
DetailsInfoItem
>
<
DetailsInfoItem
title=
"Block reward"
...
...
@@ -84,18 +110,18 @@ const BlockDetails = () => {
}
columnGap=
{
1
}
>
<
Text
>
{
block
.
reward
.
static
+
block
.
reward
.
tx_fee
-
block
.
burnt_fees
}
</
Text
>
<
Text
>
{
utils
.
formatUnits
(
totalReward
)
}
{
network
?.
currency
}
</
Text
>
<
Text
variant=
"secondary"
whiteSpace=
"break-spaces"
>
(
<
Tooltip
label=
"Static block reward"
>
<
span
>
{
block
.
reward
.
static
}
</
span
>
<
span
>
{
utils
.
formatUnits
(
staticReward
)
}
</
span
>
</
Tooltip
>
{
space
}
+
{
space
}
<
Tooltip
label=
"Txn fees"
>
<
span
>
{
block
.
reward
.
tx_fee
}
</
span
>
<
span
>
{
utils
.
formatUnits
(
txFees
)
}
</
span
>
</
Tooltip
>
{
space
}
-
{
space
}
<
Tooltip
label=
"Burnt fees"
>
<
span
>
{
block
.
burnt_fees
}
</
span
>
<
span
>
{
utils
.
formatUnits
(
burntFees
)
}
</
span
>
</
Tooltip
>
)
</
Text
>
</
DetailsInfoItem
>
...
...
@@ -106,42 +132,62 @@ const BlockDetails = () => {
title=
"Gas used"
hint=
"The total gas amount used in the block and its percentage of gas filled in the block."
>
<
Text
>
{
block
.
gas_used
.
toLocaleString
(
'
en
'
)
}
</
Text
>
<
Utilization
ml=
{
4
}
mr=
{
5
}
colorScheme=
"gray"
value=
{
block
.
gas_used
/
block
.
gas_limit
}
/>
<
GasUsedToTargetRatio
used=
{
block
.
gas_used
}
target=
{
block
.
gas_target
}
/>
<
Text
>
{
utils
.
commify
(
data
.
gas_used
)
}
</
Text
>
<
Utilization
ml=
{
4
}
mr=
{
5
}
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
}
/>
</
DetailsInfoItem
>
<
DetailsInfoItem
title=
"Gas limit"
hint=
"Total gas limit provided by all transactions in the block."
>
<
Text
>
{
block
.
gas_limit
.
toLocaleString
(
'
en
'
)
}
</
Text
>
<
Text
>
{
utils
.
commify
(
data
.
gas_limit
)
}
</
Text
>
</
DetailsInfoItem
>
<
DetailsInfoItem
title=
"Base fee per gas"
hint=
"Minimum fee required per unit of gas. Fee adjusts based on network congestion."
>
<
Text
>
{
(
block
.
base_fee_per_gas
/
10
**
9
).
toLocaleString
(
'
en
'
,
{
minimumFractionDigits
:
18
})
}
{
network
?.
currency
}
</
Text
>
<
Text
variant=
"secondary"
whiteSpace=
"pre"
>
{
space
}
(
{
block
.
base_fee_per_gas
.
toLocaleString
(
'
en
'
,
{
minimumFractionDigits
:
9
})
}
Gwei)
</
Text
>
</
DetailsInfoItem
>
<
DetailsInfoItem
title=
"Burnt fees"
hint=
{
`Amount of ${ network?.currency || 'native token' } burned from transactions included in the block. Equals Block Base Fee per Gas * Gas Used.`
}
>
<
Icon
as=
{
flameIcon
}
boxSize=
{
5
}
color=
"gray.500"
/>
<
Text
ml=
{
1
}
>
{
block
.
burnt_fees
.
toLocaleString
(
'
en
'
,
{
minimumFractionDigits
:
18
})
}
{
network
?.
currency
}
</
Text
>
<
Tooltip
label=
"Burnt fees / Txn fees * 100%"
>
<
Box
>
<
Utilization
ml=
{
4
}
value=
{
block
.
burnt_fees
/
block
.
reward
.
tx_fee
}
/>
</
Box
>
</
Tooltip
>
</
DetailsInfoItem
>
<
DetailsInfoItem
{
data
.
base_fee_per_gas
&&
(
<
DetailsInfoItem
title=
"Base fee per gas"
hint=
"Minimum fee required per unit of gas. Fee adjusts based on network congestion."
>
<
Text
>
{
utils
.
formatUnits
(
utils
.
parseUnits
(
String
(
data
.
base_fee_per_gas
),
'
wei
'
))
}
{
network
?.
currency
}
</
Text
>
<
Text
variant=
"secondary"
whiteSpace=
"pre"
>
{
space
}
(
{
utils
.
formatUnits
(
utils
.
parseUnits
(
String
(
data
.
base_fee_per_gas
),
'
wei
'
),
'
gwei
'
)
}
Gwei)
</
Text
>
</
DetailsInfoItem
>
)
}
{
data
.
burnt_fees
&&
(
<
DetailsInfoItem
title=
"Burnt fees"
hint=
{
`Amount of ${ network?.currency || 'native token' } burned from transactions included in the block.
Equals Block Base Fee per Gas * Gas Used.`
}
>
<
Icon
as=
{
flameIcon
}
boxSize=
{
5
}
color=
"gray.500"
/>
<
Text
ml=
{
1
}
>
{
utils
.
formatUnits
(
burntFees
)
}
{
network
?.
currency
}
</
Text
>
{
data
.
tx_fees
&&
(
<
Tooltip
label=
"Burnt fees / Txn fees * 100%"
>
<
Box
>
<
Utilization
ml=
{
4
}
value=
{
burntFees
.
mul
(
10
_000
).
div
(
txFees
).
toNumber
()
/
10
_000
}
/>
</
Box
>
</
Tooltip
>
)
}
</
DetailsInfoItem
>
)
}
{
/* api doesn't support extra data yet */
}
{
/* <DetailsInfoItem
title="Extra data"
hint="Any data that can be included by the miner in the block."
>
<
Text
whiteSpace=
"pre"
>
{
block
.
data
.
utf
}
</
Text
>
<
Text
variant=
"secondary"
>
(Hex:
{
block
.
data
.
hex
}
)
</
Text
>
</
DetailsInfoItem
>
<Text whiteSpace="pre">{
data.extra_data
} </Text>
<Text variant="secondary">(Hex: {
data.extra_data
})</Text>
</DetailsInfoItem>
*/
}
{
/* CUT */
}
<
GridItem
colSpan=
{
{
base
:
undefined
,
lg
:
2
}
}
>
...
...
@@ -168,13 +214,13 @@ const BlockDetails = () => {
title=
"Difficulty"
hint=
"Block difficulty for miner, used to calibrate block generation time."
>
{
block
.
difficulty
}
{
utils
.
commify
(
data
.
difficulty
)
}
</
DetailsInfoItem
>
<
DetailsInfoItem
title=
"Total difficulty"
hint=
"Total difficulty of the chain until this block."
>
{
block
.
totalDifficulty
}
{
utils
.
commify
(
data
.
total_difficulty
)
}
</
DetailsInfoItem
>
{
sectionGap
}
...
...
@@ -185,30 +231,40 @@ const BlockDetails = () => {
flexWrap=
"nowrap"
>
<
Box
overflow=
"hidden"
>
<
HashStringShortenDynamic
hash=
{
block
.
hash
}
/>
<
HashStringShortenDynamic
hash=
{
data
.
hash
}
/>
</
Box
>
<
CopyToClipboard
text=
{
block
.
hash
}
/>
<
CopyToClipboard
text=
{
data
.
hash
}
/>
</
DetailsInfoItem
>
<
DetailsInfoItem
title=
"Parent hash"
hint=
"The hash of the block from which this block was generated."
flexWrap=
"nowrap"
>
<
AddressLink
hash=
{
block
.
parent_hash
}
type=
"block"
id=
{
String
(
block
.
parent_height
)
}
/>
<
CopyToClipboard
text=
{
block
.
hash
}
/>
<
AddressLink
hash=
{
data
.
parent_hash
}
type=
"block"
id=
{
String
(
data
.
height
-
1
)
}
/>
<
CopyToClipboard
text=
{
data
.
parent_
hash
}
/>
</
DetailsInfoItem
>
<
DetailsInfoItem
{
/* api doesn't support state root yet */
}
{
/* <DetailsInfoItem
title="State root"
hint="The root of the state trie."
>
<
Text
wordBreak=
"break-all"
whiteSpace=
"break-spaces"
>
{
block
.
state_root
}
</
Text
>
</
DetailsInfoItem
>
<Text wordBreak="break-all" whiteSpace="break-spaces">{
data
.state_root }</Text>
</DetailsInfoItem>
*/
}
<
DetailsInfoItem
title=
"Nonce"
hint=
"Block nonce is a value used during mining to demonstrate proof of work for a block."
>
{
block
.
nonce
}
{
data
.
nonce
}
</
DetailsInfoItem
>
{
data
.
rewards
?.
map
(({
type
,
reward
})
=>
(
<
DetailsInfoItem
key=
{
type
}
title=
{
type
}
hint=
"Amount of distributed reward. Miners receive a static block reward + Tx fees + uncle fees."
>
{
utils
.
formatUnits
(
utils
.
parseUnits
(
String
(
reward
),
'
wei
'
))
}
{
network
?.
currency
}
</
DetailsInfoItem
>
))
}
</>
)
}
</
Grid
>
...
...
ui/block/details/BlockDetailsSkeleton.tsx
0 → 100644
View file @
ac627348
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
BlockDetailsSkeleton
=
()
=>
{
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
w=
"25%"
/>
<
SkeletonRow
w=
"25%"
/>
<
SkeletonRow
w=
"65%"
/>
<
SkeletonRow
w=
"25%"
/>
<
SkeletonRow
/>
<
SkeletonRow
/>
{
sectionGap
}
<
SkeletonRow
w=
"50%"
/>
<
SkeletonRow
w=
"25%"
/>
<
SkeletonRow
w=
"50%"
/>
<
SkeletonRow
w=
"50%"
/>
<
SkeletonRow
w=
"50%"
/>
{
sectionGap
}
<
GridItem
colSpan=
{
{
base
:
undefined
,
lg
:
2
}
}
>
<
Skeleton
h=
{
5
}
borderRadius=
"full"
w=
"100px"
/>
</
GridItem
>
</
Grid
>
);
};
export
default
BlockDetailsSkeleton
;
ui/shared/GasUsedToTargetRatio.tsx
View file @
ac627348
...
...
@@ -2,13 +2,14 @@ import { Stat, StatArrow, Text, chakra } from '@chakra-ui/react';
import
React
from
'
react
'
;
interface
Props
{
used
:
number
;
target
:
number
;
used
?:
number
;
target
?:
number
;
value
?:
number
;
className
?:
string
;
}
const
GasUsedToTargetRatio
=
({
used
,
target
,
className
}:
Props
)
=>
{
const
percentage
=
(
used
/
target
-
1
)
*
100
;
const
GasUsedToTargetRatio
=
({
used
,
target
,
value
,
className
}:
Props
)
=>
{
const
percentage
=
value
||
((
used
||
0
)
/
(
target
||
0
)
-
1
)
*
100
;
return
(
<
Stat
className=
{
className
}
>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment