-
Notifications
You must be signed in to change notification settings - Fork 5.3k
feat(pages/resources): add pectra countdown and blobscan api #15324
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
Changes from 10 commits
8e35750
25597af
b9158d7
a504bc1
55a27de
d31aa0e
040d7bb
eba75bf
9771acf
472b762
15408bb
bf9423a
c35f66d
2aaa16f
69de9e1
195836c
1dd199b
bbfe6eb
edd4626
2a924eb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,9 +13,11 @@ import { getLocaleForNumberFormat } from "@/lib/utils/translations" | |
|
|
||
| import BigNumber from "../BigNumber" | ||
| import RadialChart from "../RadialChart" | ||
| import { BaseLink } from "../ui/Link" | ||
|
|
||
| import type { DashboardBox, DashboardSection } from "./types" | ||
|
|
||
| import { useEthPrice } from "@/hooks/useEthPrice" | ||
| import { useTranslation } from "@/hooks/useTranslation" | ||
| import IconBeaconchain from "@/public/images/resources/beaconcha-in.png" | ||
| import IconBlobsGuru from "@/public/images/resources/blobsguru.png" | ||
|
|
@@ -54,16 +56,24 @@ const formatSmallUSD = (value: number, locale: string): string => | |
| new Intl.NumberFormat(locale, { | ||
| style: "currency", | ||
| currency: "USD", | ||
| notation: "compact", | ||
| minimumSignificantDigits: 2, | ||
| maximumSignificantDigits: 2, | ||
| }).format(value) | ||
|
|
||
| export const useResources = ({ txCostsMedianUsd }): DashboardSection[] => { | ||
| export const useResources = ({ | ||
| txCostsMedianUsd, | ||
| totalBlobs, | ||
| avgBlobFee, | ||
| }): DashboardSection[] => { | ||
| const { t } = useTranslation("page-resources") | ||
| const locale = useLocale() | ||
| const localeForNumberFormat = getLocaleForNumberFormat(locale! as Lang) | ||
|
|
||
| const ethPrice = useEthPrice() | ||
| const avgBlobFeeUsd = formatSmallUSD( | ||
| // Converting value from wei to USD | ||
| avgBlobFee * 1e-18 * ethPrice, | ||
| localeForNumberFormat | ||
| ) | ||
|
|
||
| const medianTxCost = | ||
| "error" in txCostsMedianUsd | ||
| ? { error: txCostsMedianUsd.error } | ||
|
|
@@ -74,6 +84,48 @@ export const useResources = ({ txCostsMedianUsd }): DashboardSection[] => { | |
|
|
||
| const [timeToNextBlock, setTimeToNextBlock] = useState(12) | ||
|
|
||
| const [scalingUpgradeCountdown, setPectraCountdown] = useState<string | null>( | ||
| "Loading..." | ||
| ) | ||
|
|
||
| useEffect(() => { | ||
| // Countdown time for Scaling Upgrade to the final date of May 7 2025 | ||
| const scalingUpgradeDate = new Date("2025-05-07T00:00:00Z") | ||
| const scalingUpgradeDateTime = scalingUpgradeDate.getTime() | ||
| const SECONDS = 1000 | ||
| const MINUTES = SECONDS * 60 | ||
| const HOURS = MINUTES * 60 | ||
| const DAYS = HOURS * 24 | ||
|
|
||
| const countdown = () => { | ||
| const now = Date.now() | ||
| const timeLeft = scalingUpgradeDateTime - now | ||
|
|
||
| // If the date has past, set the countdown to null | ||
| if (timeLeft < 0) return setPectraCountdown(null) | ||
|
|
||
| const daysLeft = Math.floor(timeLeft / DAYS) | ||
| const hoursLeft = Math.floor((timeLeft % DAYS) / HOURS) | ||
| const minutesLeft = Math.floor((timeLeft % HOURS) / MINUTES) | ||
| const secondsLeft = Math.floor((timeLeft % MINUTES) / SECONDS) | ||
|
|
||
| setPectraCountdown( | ||
| `${daysLeft}days :: ${hoursLeft}h ${minutesLeft}m ${secondsLeft}s` | ||
| ) | ||
| } | ||
| countdown() | ||
|
|
||
| let interval: NodeJS.Timeout | undefined | ||
|
|
||
| if (scalingUpgradeCountdown !== null) { | ||
| // Only run the interval if the date has not passed | ||
| interval = setInterval(countdown, SECONDS) | ||
| } | ||
|
|
||
| return () => clearInterval(interval) | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, []) | ||
|
|
||
| useEffect(() => { | ||
| const genesisTime = new Date("2020-12-01T12:00:23Z").getTime() | ||
| const updateTime = () => { | ||
|
|
@@ -350,7 +402,26 @@ export const useResources = ({ txCostsMedianUsd }): DashboardSection[] => { | |
| const scalingBoxes: DashboardBox[] = [ | ||
| { | ||
| title: t("page-resources-roadmap-title"), | ||
| // TODO: Add metric | ||
| metric: ( | ||
| <div className="grid place-items-center py-5"> | ||
| <div className="text-sm">Next upgrade</div> | ||
|
||
| <BaseLink | ||
| href="/roadmap/pectra/" | ||
| className="text-5xl font-bold text-body no-underline hover:text-primary" | ||
| > | ||
| Pectra | ||
| </BaseLink> | ||
| <div className="text-xl font-bold text-body-medium"> | ||
| {scalingUpgradeCountdown ? ( | ||
| scalingUpgradeCountdown | ||
| ) : ( | ||
| <div className="rounded-full bg-success px-2 py-1 text-xs font-normal uppercase text-success-light"> | ||
| Live Since April 2025 | ||
| </div> | ||
| )} | ||
| </div> | ||
| </div> | ||
| ), | ||
| items: [ | ||
| { | ||
| title: "Ethereum Roadmap", | ||
|
|
@@ -362,7 +433,22 @@ export const useResources = ({ txCostsMedianUsd }): DashboardSection[] => { | |
| }, | ||
| { | ||
| title: t("page-resources-blobs-title"), | ||
| // TODO: Add metric | ||
| metric: ( | ||
| <div className="flex [&>*]:grid [&>*]:flex-1 [&>*]:place-items-center"> | ||
| <div> | ||
| <div className="text-[42px] font-bold leading-2xs"> | ||
| {totalBlobs} | ||
| </div> | ||
| <div className="text-sm text-body-medium">Total blobs</div> | ||
| </div> | ||
| <div> | ||
| <div className="text-[42px] font-bold leading-2xs"> | ||
| {avgBlobFeeUsd} | ||
| </div> | ||
| <div className="text-sm text-body-medium">Average Blob Fee</div> | ||
| </div> | ||
| </div> | ||
| ), | ||
| items: [ | ||
| { | ||
| title: "Blob Scan", | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added this file. If you run with |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| { | ||
| "avgBlobAsCalldataFee": 18402670294113620, | ||
| "avgBlobFee": 1337454615991715, | ||
| "avgBlobGasPrice": 4657716809.805255, | ||
| "avgMaxBlobGasFee": 19666167416.48503, | ||
| "totalBlobGasUsed": "875492278272", | ||
| "totalBlobAsCalldataGasUsed": "12165759474144", | ||
| "totalBlobFee": "4174952855822794358784", | ||
| "totalBlobAsCalldataFee": "57445149899315095107588", | ||
| "totalBlobs": 6679476, | ||
| "totalBlobSize": "875492278272", | ||
| "totalBlocks": 1664933, | ||
| "totalTransactions": 3121566, | ||
| "totalUniqueBlobs": 6575105, | ||
| "totalUniqueReceivers": 5361, | ||
| "totalUniqueSenders": 5941, | ||
| "updatedAt": "2025-03-25T11:45:00.590Z" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| type BlobscanOverallStats = { | ||
| avgBlobAsCalldataFee: number | ||
| avgBlobFee: number | ||
| avgBlobGasPrice: number | ||
| avgMaxBlobGasFee: number | ||
| totalBlobGasUsed: string | ||
| totalBlobAsCalldataGasUsed: string | ||
| totalBlobFee: string | ||
| totalBlobAsCalldataFee: string | ||
| totalBlobs: number | ||
| totalBlobSize: string | ||
| totalBlocks: number | ||
| totalTransactions: number | ||
| totalUniqueBlobs: number | ||
| totalUniqueReceivers: number | ||
| totalUniqueSenders: number | ||
| updatedAt: string | ||
| } | ||
|
|
||
| type BlobscanOverallStatsErr = { | ||
| message: string | ||
| code: string | ||
| issues: [message: string] | ||
| } | ||
|
|
||
| /** | ||
| * Fetch the overall stats from Blobscan | ||
| * | ||
| * @see https://api.blobscan.com/#/stats/stats-getOverallStats | ||
| * | ||
| */ | ||
| export const fetchBlobscanStats = async () => { | ||
| const data = await fetch("https://api.blobscan.com/stats/overall").then( | ||
| (res) => responseHandler(res) | ||
| ) | ||
|
|
||
| return data | ||
| } | ||
|
|
||
| type BlobscanResponse = | ||
| | (Omit<Response, "json"> & { | ||
| json: () => BlobscanOverallStats | PromiseLike<BlobscanOverallStats> | ||
| }) | ||
| | (Omit<Response, "json"> & { | ||
| json: () => BlobscanOverallStatsErr | PromiseLike<BlobscanOverallStatsErr> | ||
| }) | ||
|
|
||
| const responseHandler = async (response: Response) => { | ||
| const res = await (response as BlobscanResponse).json() | ||
|
|
||
| if ("message" in res) { | ||
| throw Error(`Code ${res.code}: Failed to fetch Blobscan Overall Stats`, { | ||
| cause: res.message, | ||
| }) | ||
| } | ||
| return res | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Simplify the price formatting for
formatSmallUSD(), so that two decimal places are always shown, no matter the number of significant figures. (i.e.$xx.xxversus$x.xx)