From 8e35750716b20e803fc6ddaa49b78f0d40ae1417 Mon Sep 17 00:00:00 2001 From: tylerapfledderer Date: Sun, 20 Apr 2025 20:15:01 -0400 Subject: [PATCH 1/9] feat(pages/resources): get blobscan api data --- .../resources/_components/resources.tsx | 18 ++++-- app/[locale]/resources/page.tsx | 34 ++++++++++- src/components/Resources/useResources.tsx | 23 +++++++- src/lib/api/fetchBlobscanStats.ts | 57 +++++++++++++++++++ 4 files changed, 123 insertions(+), 9 deletions(-) create mode 100644 src/lib/api/fetchBlobscanStats.ts diff --git a/app/[locale]/resources/_components/resources.tsx b/app/[locale]/resources/_components/resources.tsx index d8d2e50476d..6d2623359a4 100644 --- a/app/[locale]/resources/_components/resources.tsx +++ b/app/[locale]/resources/_components/resources.tsx @@ -26,13 +26,23 @@ import { useTranslation } from "@/hooks/useTranslation" import heroImg from "@/public/images/heroes/guides-hub-hero.jpg" interface ResourcesPageProps { txCostsMedianUsd: MetricReturnData + txFeesSaved: string + totalBlobs: string } const EVENT_CATEGORY = "dashboard" -const ResourcesPage = ({ txCostsMedianUsd }: ResourcesPageProps) => { +const ResourcesPage = ({ + txCostsMedianUsd, + txFeesSaved, + totalBlobs, +}: ResourcesPageProps) => { const { t } = useTranslation("page-resources") - const resourceSections = useResources({ txCostsMedianUsd }) + const resourceSections = useResources({ + txCostsMedianUsd, + txFeesSaved, + totalBlobs, + }) const activeSection = useActiveHash( resourceSections.map(({ key }) => key), "0% 0% -70% 0%" @@ -122,14 +132,14 @@ const ResourcesPage = ({ txCostsMedianUsd }: ResourcesPageProps) => {
{title}
-
+ {metric && metric} {items.map((item) => ( ))} -
+ ))} diff --git a/app/[locale]/resources/page.tsx b/app/[locale]/resources/page.tsx index b8db41a4b41..c8452688555 100644 --- a/app/[locale]/resources/page.tsx +++ b/app/[locale]/resources/page.tsx @@ -17,13 +17,17 @@ import { BASE_TIME_UNIT } from "@/lib/constants" import ResourcesPage from "./_components/resources" +import { fetchBlobscanStats } from "@/lib/api/fetchBlobscanStats" import { fetchGrowThePie } from "@/lib/api/fetchGrowThePie" // In seconds const REVALIDATE_TIME = BASE_TIME_UNIT * 1 const loadData = dataLoader( - [["growThePieData", fetchGrowThePie]], + [ + ["growThePieData", fetchGrowThePie], + ["blobscanOverallStats", fetchBlobscanStats], + ], REVALIDATE_TIME * 1000 ) @@ -38,12 +42,36 @@ const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => { const messages = pick(allMessages, requiredNamespaces) // Load data - const [growThePieData] = await loadData() + const [growThePieData, blobscanOverallStats] = await loadData() + const { txCostsMedianUsd } = growThePieData + const { totalBlobAsCalldataFee, totalBlobFee, totalBlobs } = + blobscanOverallStats + + const txFeesSaved = Number( + // Formatting trick to reduce big int to only two whole numbers + // @ts-expect-error Can not use exponentiation with BigInt in ES6 + (BigInt(totalBlobAsCalldataFee) - BigInt(totalBlobFee)) / 10n ** 9n + ) + + const formattedTxFeesSaved = new Intl.NumberFormat(undefined, { + notation: "compact", + maximumFractionDigits: 2, + }).format(txFeesSaved) + + const formattedTotalBlobs = new Intl.NumberFormat(undefined, { + notation: "compact", + maximumFractionDigits: 1, + }).format(totalBlobs) + return ( - + ) } diff --git a/src/components/Resources/useResources.tsx b/src/components/Resources/useResources.tsx index 3eb2e791d07..dcdf8dc29b2 100644 --- a/src/components/Resources/useResources.tsx +++ b/src/components/Resources/useResources.tsx @@ -58,7 +58,11 @@ const formatSmallUSD = (value: number, locale: string): string => maximumSignificantDigits: 2, }).format(value) -export const useResources = ({ txCostsMedianUsd }): DashboardSection[] => { +export const useResources = ({ + txCostsMedianUsd, + txFeesSaved, + totalBlobs, +}): DashboardSection[] => { const { t } = useTranslation("page-resources") const locale = useLocale() const localeForNumberFormat = getLocaleForNumberFormat(locale! as Lang) @@ -355,7 +359,22 @@ export const useResources = ({ txCostsMedianUsd }): DashboardSection[] => { }, { title: t("page-resources-blobs-title"), - // TODO: Add metric + metric: ( +
+
+
+ {totalBlobs} +
+
Total blobs
+
+
+
+ {txFeesSaved} +
+
Total Tx Fees Saved
+
+
+ ), items: [ { title: "Blob Scan", diff --git a/src/lib/api/fetchBlobscanStats.ts b/src/lib/api/fetchBlobscanStats.ts new file mode 100644 index 00000000000..851ad925f02 --- /dev/null +++ b/src/lib/api/fetchBlobscanStats.ts @@ -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 & { + json: () => BlobscanOverallStats | PromiseLike + }) + | (Omit & { + json: () => BlobscanOverallStatsErr | PromiseLike + }) + +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 +} From 25597af3ff4ec5c05ef70bc8da2a7678310625cb Mon Sep 17 00:00:00 2001 From: tylerapfledderer Date: Tue, 22 Apr 2025 10:32:46 -0400 Subject: [PATCH 2/9] feat(pages/resources): add pectra schedule --- app/[locale]/resources/_components/resources.tsx | 4 ++-- src/components/Resources/useResources.tsx | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/[locale]/resources/_components/resources.tsx b/app/[locale]/resources/_components/resources.tsx index 6d2623359a4..c7a4c9b032d 100644 --- a/app/[locale]/resources/_components/resources.tsx +++ b/app/[locale]/resources/_components/resources.tsx @@ -124,7 +124,7 @@ const ResourcesPage = ({ {boxes.map(({ title, metric, items, className }) => (
{title}
- + {metric && metric} {items.map((item) => ( diff --git a/src/components/Resources/useResources.tsx b/src/components/Resources/useResources.tsx index dcdf8dc29b2..86fe0c7b949 100644 --- a/src/components/Resources/useResources.tsx +++ b/src/components/Resources/useResources.tsx @@ -347,7 +347,14 @@ export const useResources = ({ const scalingBoxes: DashboardBox[] = [ { title: t("page-resources-roadmap-title"), - // TODO: Add metric + metric: ( +
+
Next upgrade
+
Pectra
+ {/* TODO: Convert date to a countdown */} +
07 May 2025
+
+ ), items: [ { title: "Ethereum Roadmap", From b9158d71142fea3e8251a3fcb8c2fe6ee25d98fb Mon Sep 17 00:00:00 2001 From: tylerapfledderer Date: Sun, 27 Apr 2025 23:37:54 -0400 Subject: [PATCH 3/9] refactor(page/resources): show average blob fee in usd --- app/[locale]/resources/_components/resources.tsx | 7 ++++--- app/[locale]/resources/page.tsx | 16 ++-------------- src/components/Resources/useResources.tsx | 14 +++++++++++--- 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/app/[locale]/resources/_components/resources.tsx b/app/[locale]/resources/_components/resources.tsx index c7a4c9b032d..cc4bf63424c 100644 --- a/app/[locale]/resources/_components/resources.tsx +++ b/app/[locale]/resources/_components/resources.tsx @@ -26,22 +26,23 @@ import { useTranslation } from "@/hooks/useTranslation" import heroImg from "@/public/images/heroes/guides-hub-hero.jpg" interface ResourcesPageProps { txCostsMedianUsd: MetricReturnData - txFeesSaved: string totalBlobs: string + avgBlobFee: number } const EVENT_CATEGORY = "dashboard" const ResourcesPage = ({ txCostsMedianUsd, - txFeesSaved, totalBlobs, + avgBlobFee, }: ResourcesPageProps) => { const { t } = useTranslation("page-resources") + const resourceSections = useResources({ txCostsMedianUsd, - txFeesSaved, totalBlobs, + avgBlobFee, }) const activeSection = useActiveHash( resourceSections.map(({ key }) => key), diff --git a/app/[locale]/resources/page.tsx b/app/[locale]/resources/page.tsx index c8452688555..e397ffee7f8 100644 --- a/app/[locale]/resources/page.tsx +++ b/app/[locale]/resources/page.tsx @@ -46,19 +46,7 @@ const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => { const { txCostsMedianUsd } = growThePieData - const { totalBlobAsCalldataFee, totalBlobFee, totalBlobs } = - blobscanOverallStats - - const txFeesSaved = Number( - // Formatting trick to reduce big int to only two whole numbers - // @ts-expect-error Can not use exponentiation with BigInt in ES6 - (BigInt(totalBlobAsCalldataFee) - BigInt(totalBlobFee)) / 10n ** 9n - ) - - const formattedTxFeesSaved = new Intl.NumberFormat(undefined, { - notation: "compact", - maximumFractionDigits: 2, - }).format(txFeesSaved) + const { totalBlobs, avgBlobFee } = blobscanOverallStats const formattedTotalBlobs = new Intl.NumberFormat(undefined, { notation: "compact", @@ -69,8 +57,8 @@ const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => { ) diff --git a/src/components/Resources/useResources.tsx b/src/components/Resources/useResources.tsx index 86fe0c7b949..5c476e13f90 100644 --- a/src/components/Resources/useResources.tsx +++ b/src/components/Resources/useResources.tsx @@ -16,6 +16,7 @@ import RadialChart from "../RadialChart" 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" @@ -60,13 +61,20 @@ const formatSmallUSD = (value: number, locale: string): string => export const useResources = ({ txCostsMedianUsd, - txFeesSaved, 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 gwei to USD + avgBlobFee * 1e-9 * ethPrice, + localeForNumberFormat + ).replace(/[A-Za-z]$/, "") + const medianTxCost = "error" in txCostsMedianUsd ? { error: txCostsMedianUsd.error } @@ -376,9 +384,9 @@ export const useResources = ({
- {txFeesSaved} + {avgBlobFeeUsd}
-
Total Tx Fees Saved
+
Average Blob Fee
), From a504bc196778caed4293c0273fa716b62321e4aa Mon Sep 17 00:00:00 2001 From: tylerapfledderer Date: Mon, 28 Apr 2025 10:19:26 -0400 Subject: [PATCH 4/9] fix(useResources): to avg blob fee conversion from wei --- src/components/Resources/useResources.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/Resources/useResources.tsx b/src/components/Resources/useResources.tsx index 5c476e13f90..8efe8f39c01 100644 --- a/src/components/Resources/useResources.tsx +++ b/src/components/Resources/useResources.tsx @@ -70,10 +70,10 @@ export const useResources = ({ const ethPrice = useEthPrice() const avgBlobFeeUsd = formatSmallUSD( - // Converting value from gwei to USD - avgBlobFee * 1e-9 * ethPrice, + // Converting value from wei to USD + avgBlobFee * 1e-18 * ethPrice, localeForNumberFormat - ).replace(/[A-Za-z]$/, "") + ) const medianTxCost = "error" in txCostsMedianUsd From d31aa0e20f4c3453bcc80494b5171d62ac9f0833 Mon Sep 17 00:00:00 2001 From: Pablo Date: Thu, 8 May 2025 10:09:22 +0200 Subject: [PATCH 5/9] add mock data for blobscanOverallStats --- src/data/mocks/blobscanOverallStats.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/data/mocks/blobscanOverallStats.json diff --git a/src/data/mocks/blobscanOverallStats.json b/src/data/mocks/blobscanOverallStats.json new file mode 100644 index 00000000000..9eb9561c4c0 --- /dev/null +++ b/src/data/mocks/blobscanOverallStats.json @@ -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" +} From 040d7bb483d741df4be597efd88c760d62b2feb7 Mon Sep 17 00:00:00 2001 From: tylerapfledderer Date: Thu, 8 May 2025 19:23:53 -0400 Subject: [PATCH 6/9] refactor(useResources): simplify formatSmallUSD --- src/components/Resources/useResources.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/Resources/useResources.tsx b/src/components/Resources/useResources.tsx index 8efe8f39c01..9fb132bc605 100644 --- a/src/components/Resources/useResources.tsx +++ b/src/components/Resources/useResources.tsx @@ -54,9 +54,6 @@ 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 = ({ From eba75bf31b7e31e84f1051c747fbb33582151760 Mon Sep 17 00:00:00 2001 From: tylerapfledderer Date: Thu, 8 May 2025 20:19:44 -0400 Subject: [PATCH 7/9] refactor(useResources): add countdown for scaling upgrade --- src/components/Resources/useResources.tsx | 53 ++++++++++++++++++++++- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/src/components/Resources/useResources.tsx b/src/components/Resources/useResources.tsx index 9fb132bc605..c95cfbd4e8e 100644 --- a/src/components/Resources/useResources.tsx +++ b/src/components/Resources/useResources.tsx @@ -82,6 +82,48 @@ export const useResources = ({ const [timeToNextBlock, setTimeToNextBlock] = useState(12) + const [scalingUpgradeCountdown, setPectraCountdown] = useState( + "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 = () => { @@ -356,8 +398,15 @@ export const useResources = ({
Next upgrade
Pectra
- {/* TODO: Convert date to a countdown */} -
07 May 2025
+
+ {scalingUpgradeCountdown ? ( + scalingUpgradeCountdown + ) : ( +
+ Live Since April 2025 +
+ )} +
), items: [ From 9771acf3c028e285971efbff35e528f311e39e0d Mon Sep 17 00:00:00 2001 From: tylerapfledderer Date: Thu, 8 May 2025 20:25:15 -0400 Subject: [PATCH 8/9] refactor(useResources): set link for Pectra page --- src/components/Resources/useResources.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/Resources/useResources.tsx b/src/components/Resources/useResources.tsx index c95cfbd4e8e..bd7cfaac63f 100644 --- a/src/components/Resources/useResources.tsx +++ b/src/components/Resources/useResources.tsx @@ -13,6 +13,7 @@ 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" @@ -397,7 +398,12 @@ export const useResources = ({ metric: (
Next upgrade
-
Pectra
+ + Pectra +
{scalingUpgradeCountdown ? ( scalingUpgradeCountdown From bf9423a598a5c031b09a4575f92d492b4c2eac04 Mon Sep 17 00:00:00 2001 From: tylerapfledderer Date: Fri, 23 May 2025 09:13:08 -0400 Subject: [PATCH 9/9] refactor(useResources): update label for latest scaling upgrade --- src/components/Resources/useResources.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Resources/useResources.tsx b/src/components/Resources/useResources.tsx index 73f4e4fad42..8f11b5a77d8 100644 --- a/src/components/Resources/useResources.tsx +++ b/src/components/Resources/useResources.tsx @@ -404,7 +404,7 @@ export const useResources = ({ title: t("page-resources-roadmap-title"), metric: (
-
Next upgrade
+
Latest upgrade