diff --git a/src/Block.tsx b/src/Block.tsx index 3064c99..a515036 100644 --- a/src/Block.tsx +++ b/src/Block.tsx @@ -1,92 +1,35 @@ -import React, { useEffect, useState, useMemo, useContext } from "react"; +import React, { useEffect, useMemo, useContext } from "react"; import { useParams, NavLink } from "react-router-dom"; -import { ethers, BigNumber } from "ethers"; +import { BigNumber, ethers } from "ethers"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faBurn } from "@fortawesome/free-solid-svg-icons"; import StandardFrame from "./StandardFrame"; import StandardSubtitle from "./StandardSubtitle"; import NavBlock from "./block/NavBlock"; import ContentFrame from "./ContentFrame"; +import InfoRow from "./components/InfoRow"; import Timestamp from "./components/Timestamp"; import GasValue from "./components/GasValue"; +import PercentageBar from "./components/PercentageBar"; import BlockLink from "./components/BlockLink"; import DecoratedAddressLink from "./components/DecoratedAddressLink"; import TransactionValue from "./components/TransactionValue"; +import FormattedBalance from "./components/FormattedBalance"; import HexValue from "./components/HexValue"; import { RuntimeContext } from "./useRuntime"; import { useLatestBlockNumber } from "./useLatestBlock"; import { blockTxsURL } from "./url"; +import { useBlockData } from "./useErigonHooks"; type BlockParams = { blockNumberOrHash: string; }; -interface ExtendedBlock extends ethers.providers.Block { - blockReward: BigNumber; - unclesReward: BigNumber; - feeReward: BigNumber; - size: number; - sha3Uncles: string; - stateRoot: string; - totalDifficulty: BigNumber; -} - const Block: React.FC = () => { const { provider } = useContext(RuntimeContext); const params = useParams(); - const [block, setBlock] = useState(); - useEffect(() => { - if (!provider) { - return; - } - - const readBlock = async () => { - let blockPromise: Promise; - if (ethers.utils.isHexString(params.blockNumberOrHash, 32)) { - blockPromise = provider.send("eth_getBlockByHash", [ - params.blockNumberOrHash, - false, - ]); - } else { - blockPromise = provider.send("eth_getBlockByNumber", [ - params.blockNumberOrHash, - false, - ]); - } - const [_rawBlock, _rawIssuance, _rawReceipts] = await Promise.all([ - blockPromise, - provider.send("erigon_issuance", [params.blockNumberOrHash]), - provider.send("eth_getBlockReceipts", [params.blockNumberOrHash]), - ]); - const receipts = (_rawReceipts as any[]).map((r) => - provider.formatter.receipt(r) - ); - const fees = receipts.reduce( - (acc, r) => acc.add(r.effectiveGasPrice.mul(r.gasUsed)), - BigNumber.from(0) - ); - - const _block = provider.formatter.block(_rawBlock); - const extBlock: ExtendedBlock = { - blockReward: provider.formatter.bigNumber( - _rawIssuance.blockReward ?? 0 - ), - unclesReward: provider.formatter.bigNumber( - _rawIssuance.uncleReward ?? 0 - ), - feeReward: fees, - size: provider.formatter.number(_rawBlock.size), - sha3Uncles: _rawBlock.sha3Uncles, - stateRoot: _rawBlock.stateRoot, - totalDifficulty: provider.formatter.bigNumber( - _rawBlock.totalDifficulty - ), - ..._block, - }; - setBlock(extBlock); - }; - readBlock(); - }, [provider, params.blockNumberOrHash]); - + const block = useBlockData(provider, params.blockNumberOrHash); useEffect(() => { if (block) { document.title = `Block #${block.number} | Otterscan`; @@ -101,6 +44,11 @@ const Block: React.FC = () => { console.error(err); } }, [block]); + const burntFees = + block?.baseFeePerGas && block.baseFeePerGas.mul(block.gasUsed); + const netFeeReward = block && block.feeReward.sub(burntFees ?? 0); + const gasUsedPerc = + block && block.gasUsed.mul(10000).div(block.gasLimit).toNumber() / 100; const latestBlockNumber = useLatestBlockNumber(provider); @@ -143,38 +91,77 @@ const Block: React.FC = () => { - + {!block.feeReward.isZero() && ( <> {" "} ( +{" "} - ) + + ) )} - - {ethers.utils.commify(block.difficulty)} - - - {ethers.utils.commify(block.totalDifficulty.toString())} - {ethers.utils.commify(block.size)} bytes - - - - - + {block.baseFeePerGas && ( + + + {" "} + Gwei ( + {" "} + wei) + + + )} + {burntFees && ( + +
+ + + + + + + + {" "} + Ether + + +
+
+ )} + +
+
+ /{" "} + +
+ +
{extraStr} (Hex:{" "} {block.extraData}) N/A + + {ethers.utils.commify(block.difficulty)} + + + {ethers.utils.commify(block.totalDifficulty.toString())} + @@ -196,15 +183,4 @@ const Block: React.FC = () => { ); }; -type InfoRowProps = { - title: string; -}; - -const InfoRow: React.FC = ({ title, children }) => ( -
-
{title}:
-
{children}
-
-); - export default React.memo(Block); diff --git a/src/Transaction.tsx b/src/Transaction.tsx index 55de7ac..ad940fe 100644 --- a/src/Transaction.tsx +++ b/src/Transaction.tsx @@ -92,12 +92,14 @@ const Transaction: React.FC = () => { value: _response.value, tokenTransfers, tokenMetas, + type: _response.type ?? 0, fee: _response.gasPrice!.mul(_receipt.gasUsed), + blockBaseFeePerGas: _block.baseFeePerGas, + maxFeePerGas: _response.maxFeePerGas, + maxPriorityFeePerGas: _response.maxPriorityFeePerGas, gasPrice: _response.gasPrice!, - gasLimit: _response.gasLimit, gasUsed: _receipt.gasUsed, - gasUsedPerc: - _receipt.gasUsed.toNumber() / _response.gasLimit.toNumber(), + gasLimit: _response.gasLimit, nonce: _response.nonce, data: _response.data, logs: _receipt.logs, diff --git a/src/components/ExternalLink.tsx b/src/components/ExternalLink.tsx new file mode 100644 index 0000000..51769a8 --- /dev/null +++ b/src/components/ExternalLink.tsx @@ -0,0 +1,23 @@ +import React from "react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons"; + +type ExternalLinkProps = { + href: string; +}; + +const ExternalLink: React.FC = ({ href, children }) => ( + + + {children} + + + +); + +export default ExternalLink; diff --git a/src/components/InfoRow.tsx b/src/components/InfoRow.tsx index d3758b3..4651923 100644 --- a/src/components/InfoRow.tsx +++ b/src/components/InfoRow.tsx @@ -1,7 +1,7 @@ import React from "react"; type InfoRowProps = React.PropsWithChildren<{ - title: string; + title: React.ReactNode; }>; const InfoRow: React.FC = ({ title, children }) => ( diff --git a/src/components/PercentageBar.tsx b/src/components/PercentageBar.tsx new file mode 100644 index 0000000..a868784 --- /dev/null +++ b/src/components/PercentageBar.tsx @@ -0,0 +1,21 @@ +import React from "react"; + +type PercentageBarProps = { + perc: number; +}; + +const PercentageBar: React.FC = ({ perc }) => ( +
+
+
+
+ {perc}% +
+
+
+); + +export default React.memo(PercentageBar); diff --git a/src/components/PercentageGauge.tsx b/src/components/PercentageGauge.tsx new file mode 100644 index 0000000..23af8f7 --- /dev/null +++ b/src/components/PercentageGauge.tsx @@ -0,0 +1,34 @@ +import React from "react"; + +type PercentageGaugeProps = { + perc: number; + bgFull: string; + bgPerc: string; + textPerc: string; +}; + +const PercentageGauge: React.FC = ({ + perc, + bgFull, + bgPerc, + textPerc, +}) => ( +
+
+
+
+
+
+
+
+ {perc}% +
+
+); + +export default React.memo(PercentageGauge); diff --git a/src/components/TransactionType.tsx b/src/components/TransactionType.tsx new file mode 100644 index 0000000..e7faa6c --- /dev/null +++ b/src/components/TransactionType.tsx @@ -0,0 +1,39 @@ +import React from "react"; +import ExternalLink from "./ExternalLink"; + +type TransactionTypeProps = { + type: number; +}; + +const TransactionType: React.FC = ({ type }) => { + let description: React.ReactNode; + switch (type) { + case 0: + description = "legacy"; + break; + case 1: + description = ( + + EIP-2930 + + ); + break; + case 2: + description = ( + + EIP-1559 + + ); + break; + default: + description = "unknown"; + } + + return ( + + {type} ({description}) + + ); +}; + +export default React.memo(TransactionType); diff --git a/src/transaction/Details.tsx b/src/transaction/Details.tsx index 30bcbbd..7346b0c 100644 --- a/src/transaction/Details.tsx +++ b/src/transaction/Details.tsx @@ -15,10 +15,14 @@ import Copy from "../components/Copy"; import Timestamp from "../components/Timestamp"; import InternalTransactionOperation from "../components/InternalTransactionOperation"; import MethodName from "../components/MethodName"; +import TransactionType from "../components/TransactionType"; +import RewardSplit from "./RewardSplit"; import GasValue from "../components/GasValue"; import FormattedBalance from "../components/FormattedBalance"; import TokenTransferItem from "../TokenTransferItem"; import { TransactionData, InternalOperation } from "../types"; +import PercentageBar from "../components/PercentageBar"; +import ExternalLink from "../components/ExternalLink"; type DetailsProps = { txData: TransactionData; @@ -30,144 +34,213 @@ const Details: React.FC = ({ txData, internalOps, sendsEthToMiner, -}) => ( - - -
- {txData.transactionHash} - -
-
- - {txData.status ? ( - - - Success - - ) : ( - - - Fail - - )} - - -
- - -
-
- - - - -
- - - - -
-
- - {txData.to ? ( -
- - - - -
- ) : ( -
- - - - -
- )} - {internalOps && ( -
- {internalOps.map((op, i) => ( - - ))} -
- )} -
- - - - {txData.tokenTransfers.length > 0 && ( - -
- {txData.tokenTransfers.map((t, i) => ( - - ))} +}) => { + const hasEIP1559 = + txData.blockBaseFeePerGas !== undefined && + txData.blockBaseFeePerGas !== null; + + return ( + + +
+ {txData.transactionHash} +
- )} - - - {ethers.utils.formatEther(txData.value)} Ether - - - - Ether - - -
- - Ether ( - Gwei) - - {sendsEthToMiner && ( - - Flashbots + + {txData.status ? ( + + + Success + + ) : ( + + + Fail )} -
-
- N/A - - - - - ( - {(txData.gasUsedPerc * 100).toFixed(2)}%) - - {txData.nonce} - - - {txData.transactionIndex} - - - -