diff --git a/src/BlockTransactions.tsx b/src/BlockTransactions.tsx
index 30d39c0..2ce687b 100644
--- a/src/BlockTransactions.tsx
+++ b/src/BlockTransactions.tsx
@@ -63,10 +63,18 @@ const BlockTransactions: React.FC = () => {
to: t.to,
createdContractAddress: _receipts[i].contractAddress,
value: t.value,
- fee: provider.formatter
- .bigNumber(_receipts[i].gasUsed)
- .mul(t.gasPrice!),
- gasPrice: t.gasPrice!,
+ fee:
+ t.type !== 2
+ ? provider.formatter
+ .bigNumber(_receipts[i].gasUsed)
+ .mul(t.gasPrice!)
+ : provider.formatter
+ .bigNumber(_receipts[i].gasUsed)
+ .mul(t.maxPriorityFeePerGas!.add(_block.baseFeePerGas!)),
+ gasPrice:
+ t.type !== 2
+ ? t.gasPrice!
+ : t.maxPriorityFeePerGas!.add(_block.baseFeePerGas!),
data: t.data,
status: provider.formatter.number(_receipts[i].status),
};
diff --git a/src/Home.tsx b/src/Home.tsx
index d4f3afa..c7a7a3f 100644
--- a/src/Home.tsx
+++ b/src/Home.tsx
@@ -1,6 +1,8 @@
import React, { useState, useContext } from "react";
import { NavLink, useHistory } from "react-router-dom";
import { ethers } from "ethers";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faBurn } from "@fortawesome/free-solid-svg-icons";
import Logo from "./Logo";
import Timestamp from "./components/Timestamp";
import { RuntimeContext } from "./useRuntime";
@@ -54,6 +56,19 @@ const Home: React.FC = () => {
>
Search
+
+
+
+
+
+
+ Check the special dashboard for EIP-1559
+
+
+
+
+
+
{latestBlock && (
= ({ timestamp }) => {
- const now = Date.now() / 1000;
+const TimestampAge: React.FC = ({ now, timestamp }) => {
+ if (now === undefined) {
+ now = Date.now() / 1000;
+ }
let diff = now - timestamp;
let desc = "";
diff --git a/src/special/london/BlockRow.tsx b/src/special/london/BlockRow.tsx
new file mode 100644
index 0000000..6331cdb
--- /dev/null
+++ b/src/special/london/BlockRow.tsx
@@ -0,0 +1,60 @@
+import { ethers } from "ethers";
+import React from "react";
+import BlockLink from "../../components/BlockLink";
+import TimestampAge from "../../components/TimestampAge";
+import { ExtendedBlock } from "../../useErigonHooks";
+
+const ELASTICITY_MULTIPLIER = 2;
+
+type BlockRowProps = {
+ now: number;
+ block: ExtendedBlock;
+};
+
+const BlockRow: React.FC = ({ now, block }) => {
+ const gasTarget = block.gasLimit.div(ELASTICITY_MULTIPLIER);
+ const burntFees =
+ block?.baseFeePerGas && block.baseFeePerGas.mul(block.gasUsed);
+ const netFeeReward = block && block.feeReward.sub(burntFees ?? 0);
+ const totalReward = block.blockReward.add(netFeeReward ?? 0);
+
+ return (
+
+
+
+
+
+ {ethers.utils.commify(block.gasUsed.toString())}
+
+
+ {ethers.utils.commify(gasTarget.toString())}
+
+
{block.baseFeePerGas?.toString()} wei
+
+ {ethers.utils.commify(ethers.utils.formatEther(totalReward))} Ether
+
+
+ {ethers.utils.commify(
+ ethers.utils.formatUnits(
+ block.gasUsed.mul(block.baseFeePerGas!).toString(),
+ 9
+ )
+ )}{" "}
+ Gwei
+
+
+
+
+
+ );
+};
+
+export default React.memo(BlockRow);
diff --git a/src/special/london/Blocks.tsx b/src/special/london/Blocks.tsx
new file mode 100644
index 0000000..a5398f1
--- /dev/null
+++ b/src/special/london/Blocks.tsx
@@ -0,0 +1,208 @@
+import React, {
+ useState,
+ useEffect,
+ useContext,
+ useMemo,
+ useCallback,
+} from "react";
+import { ethers } from "ethers";
+import { Line } from "react-chartjs-2";
+import { ChartData, ChartOptions } from "chart.js";
+import { Transition } from "@headlessui/react";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import {
+ faBurn,
+ faCoins,
+ faCube,
+ faGasPump,
+ faHistory,
+} from "@fortawesome/free-solid-svg-icons";
+import BlockRow from "./BlockRow";
+import { ExtendedBlock, readBlock } from "../../useErigonHooks";
+import { RuntimeContext } from "../../useRuntime";
+
+const MAX_BLOCK_HISTORY = 20;
+
+const PREV_BLOCK_COUNT = 15;
+
+const options: ChartOptions = {
+ animation: false,
+ plugins: {
+ legend: {
+ display: false,
+ },
+ },
+ scales: {
+ x: {
+ ticks: {
+ callback: function (v) {
+ // @ts-ignore
+ return ethers.utils.commify(this.getLabelForValue(v));
+ },
+ },
+ },
+ y: {
+ beginAtZero: true,
+ title: {
+ display: true,
+ text: "Burnt fees",
+ },
+ ticks: {
+ callback: (v) => `${v} Gwei`,
+ },
+ },
+ },
+};
+
+type BlocksProps = {
+ latestBlock: ethers.providers.Block;
+ targetBlockNumber: number;
+};
+
+const Blocks: React.FC = ({ latestBlock, targetBlockNumber }) => {
+ const { provider } = useContext(RuntimeContext);
+ const [blocks, setBlock] = useState([]);
+ const [now, setNow] = useState(Date.now());
+
+ const addBlock = useCallback(
+ async (blockNumber: number) => {
+ if (!provider) {
+ return;
+ }
+
+ // Skip blocks before the hard fork during the transition
+ if (blockNumber < targetBlockNumber) {
+ return;
+ }
+
+ const extBlock = await readBlock(provider, blockNumber.toString());
+ setNow(Date.now());
+ setBlock((_blocks) => {
+ if (_blocks.length > 0 && blockNumber === _blocks[0].number) {
+ return _blocks;
+ }
+
+ // Leave the last block because of transition animation
+ const newBlocks = [extBlock, ..._blocks].slice(
+ 0,
+ MAX_BLOCK_HISTORY + 1
+ );
+
+ // Little hack to fix out of order block notifications
+ newBlocks.sort((a, b) => b.number - a.number);
+ return newBlocks;
+ });
+ },
+ [provider, targetBlockNumber]
+ );
+
+ useEffect(() => {
+ addBlock(latestBlock.number);
+ }, [addBlock, latestBlock]);
+
+ const data: ChartData = useMemo(() => {
+ return {
+ labels: blocks.map((b) => b.number.toString()).reverse(),
+ datasets: [
+ {
+ label: "Burnt fees (Gwei)",
+ data: blocks
+ .map((b) => b.gasUsed.mul(b.baseFeePerGas!).toNumber() / 1e9)
+ .reverse(),
+ fill: true,
+ backgroundColor: "#FDBA74",
+ borderColor: "#F97316",
+ tension: 0.2,
+ },
+ ],
+ };
+ }, [blocks]);
+
+ // On page reload, pre-populate the last N blocks
+ useEffect(
+ () => {
+ const addPreviousBlocks = async () => {
+ for (
+ let i = latestBlock.number - PREV_BLOCK_COUNT;
+ i < latestBlock.number;
+ i++
+ ) {
+ await addBlock(i);
+ }
+ };
+ addPreviousBlocks();
+ },
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ []
+ );
+
+ return (
+
+
+
+
+
+
+ London Hardfork is here. Watch the base fees burn.
+
+
+
+
+
+
+
+
+
+
+
+
+ Block
+
+
+
+
+
+ Gas used
+
+
Gas target
+
Base fee
+
+
+
+
+ Rewards
+
+
+
+
+
+ Burnt fees
+
+
+
+
+
+ Age
+
+
+ {blocks.map((b, i) => (
+
+
+
+ ))}
+
+
+ );
+};
+
+export default React.memo(Blocks);
diff --git a/src/special/london/Countdown.tsx b/src/special/london/Countdown.tsx
new file mode 100644
index 0000000..0d41dbb
--- /dev/null
+++ b/src/special/london/Countdown.tsx
@@ -0,0 +1,52 @@
+import React, { useEffect, useState } from "react";
+import { ethers } from "ethers";
+
+type CountdownProps = {
+ provider: ethers.providers.JsonRpcProvider;
+ currentBlock: ethers.providers.Block;
+ targetBlock: number;
+};
+
+const Countdown: React.FC = ({
+ provider,
+ currentBlock,
+ targetBlock,
+}) => {
+ const [targetTimestamp, setTargetTimestamp] = useState();
+
+ useEffect(() => {
+ const calcTime = async () => {
+ const diff = targetBlock - currentBlock.number;
+ const _prevBlock = await provider.getBlock(currentBlock.number - diff);
+ const _targetTimestamp =
+ currentBlock.timestamp +
+ (currentBlock.timestamp - _prevBlock.timestamp);
+ setTargetTimestamp(_targetTimestamp);
+ };
+ calcTime();
+ }, [provider, currentBlock, targetBlock]);
+
+ return (
+
+
+
London Network Upgrade
+
+ {ethers.utils.commify(targetBlock - currentBlock.number)}
+
+
Block remaining
+
+
Current block: {ethers.utils.commify(currentBlock.number)}
+ Target block: {ethers.utils.commify(targetBlock)}
+
+ {targetTimestamp && (
+
+ {new Date(targetTimestamp * 1000).toLocaleDateString()} @{" "}
+ {new Date(targetTimestamp * 1000).toLocaleTimeString()} (Estimative)
+
+ )}
+
+
+ );
+};
+
+export default React.memo(Countdown);
diff --git a/src/special/london/London.tsx b/src/special/london/London.tsx
new file mode 100644
index 0000000..0663796
--- /dev/null
+++ b/src/special/london/London.tsx
@@ -0,0 +1,31 @@
+import React, { useContext } from "react";
+import { useLatestBlock } from "../../useLatestBlock";
+import { RuntimeContext } from "../../useRuntime";
+import Countdown from "./Countdown";
+import Blocks from "./Blocks";
+import { londonBlockNumber } from "./params";
+
+const London: React.FC = () => {
+ const { provider } = useContext(RuntimeContext);
+ const block = useLatestBlock(provider);
+ if (!provider || !block) {
+ return <>>;
+ }
+
+ // Display countdown
+ const targetBlockNumber =
+ londonBlockNumber[provider.network.chainId.toString()];
+ if (block.number < targetBlockNumber) {
+ return (
+
+ );
+ }
+
+ return ;
+};
+
+export default React.memo(London);
diff --git a/src/special/london/params.ts b/src/special/london/params.ts
new file mode 100644
index 0000000..4f04caf
--- /dev/null
+++ b/src/special/london/params.ts
@@ -0,0 +1,6 @@
+export const londonBlockNumber: { [chainId: string]: number } = {
+ "1": 12965000,
+ "3": 10499401,
+ "4": 8897988,
+ "5": 5062605,
+};
diff --git a/src/useErigonHooks.ts b/src/useErigonHooks.ts
index ecbda33..d11d2f5 100644
--- a/src/useErigonHooks.ts
+++ b/src/useErigonHooks.ts
@@ -13,6 +13,49 @@ export interface ExtendedBlock extends ethers.providers.Block {
totalDifficulty: BigNumber;
}
+export const readBlock = async (
+ provider: ethers.providers.JsonRpcProvider,
+ blockNumberOrHash: string
+) => {
+ let blockPromise: Promise;
+ if (ethers.utils.isHexString(blockNumberOrHash, 32)) {
+ blockPromise = provider.send("eth_getBlockByHash", [
+ blockNumberOrHash,
+ false,
+ ]);
+ } else {
+ blockPromise = provider.send("eth_getBlockByNumber", [
+ blockNumberOrHash,
+ false,
+ ]);
+ }
+ const [_rawBlock, _rawIssuance, _rawReceipts] = await Promise.all([
+ blockPromise,
+ provider.send("erigon_issuance", [blockNumberOrHash]),
+ provider.send("eth_getBlockReceipts", [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,
+ };
+ return extBlock;
+};
+
export const useBlockData = (
provider: ethers.providers.JsonRpcProvider | undefined,
blockNumberOrHash: string
@@ -23,52 +66,11 @@ export const useBlockData = (
return;
}
- const readBlock = async () => {
- let blockPromise: Promise;
- if (ethers.utils.isHexString(blockNumberOrHash, 32)) {
- blockPromise = provider.send("eth_getBlockByHash", [
- blockNumberOrHash,
- false,
- ]);
- } else {
- blockPromise = provider.send("eth_getBlockByNumber", [
- blockNumberOrHash,
- false,
- ]);
- }
- const [_rawBlock, _rawIssuance, _rawReceipts] = await Promise.all([
- blockPromise,
- provider.send("erigon_issuance", [blockNumberOrHash]),
- provider.send("eth_getBlockReceipts", [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,
- };
+ const _readBlock = async () => {
+ const extBlock = await readBlock(provider, blockNumberOrHash);
setBlock(extBlock);
};
- readBlock();
+ _readBlock();
}, [provider, blockNumberOrHash]);
return block;