From 0065bd4763d1ce26cfb0d0da4e8a8270f7da8c08 Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Wed, 16 Aug 2023 16:03:43 +0200 Subject: [PATCH] feat: fetch Dune Safe numbers --- src/components/Home/Stats/index.tsx | 7 +++-- src/config/constants.ts | 1 + src/content/home.json | 18 ++++++++----- src/hooks/useSafeNumbers.ts | 40 +++++++++++++++++++++++++++++ src/lib/formatValue.ts | 34 ++++++++++++++++++++++++ 5 files changed, 92 insertions(+), 8 deletions(-) create mode 100644 src/hooks/useSafeNumbers.ts create mode 100644 src/lib/formatValue.ts diff --git a/src/components/Home/Stats/index.tsx b/src/components/Home/Stats/index.tsx index 33036ebd7..1824ce918 100644 --- a/src/components/Home/Stats/index.tsx +++ b/src/components/Home/Stats/index.tsx @@ -1,11 +1,14 @@ import { Container, Grid, Typography } from '@mui/material' -import type { ReactElement } from 'react' +import { type ReactElement } from 'react' import css from './styles.module.css' import layoutCss from '@/components/common/styles.module.css' import type { BaseBlock } from '@/components/Home/types' +import { useSafeNumbers } from '@/hooks/useSafeNumbers' const Stats = ({ caption, title, text, items }: BaseBlock): ReactElement => { + const stats = useSafeNumbers() + return ( @@ -28,7 +31,7 @@ const Stats = ({ caption, title, text, items }: BaseBlock): ReactElement => { items.map((item, index) => { const textBlock = ( <> -

{item.title}

+

{stats[index]}

{item.text} ) diff --git a/src/config/constants.ts b/src/config/constants.ts index 12968dd6f..e95b1b3d5 100644 --- a/src/config/constants.ts +++ b/src/config/constants.ts @@ -3,6 +3,7 @@ export const LS_NAMESPACE = 'SAFE__' export const IS_PRODUCTION = process.env.NEXT_PUBLIC_IS_PRODUCTION export const GOOGLE_ANALYTICS_TRACKING_ID = process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_TRACKING_ID || '' export const GOOGLE_ANALYTICS_DOMAIN = process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_DOMAIN || 'safe.global' +export const DUNE_API_KEY = process.env.NEXT_PUBLIC_DUNE_API_KEY || '' // Links export const WALLET_LINK = 'https://app.safe.global' diff --git a/src/content/home.json b/src/content/home.json index 6b28c54bc..c168b5657 100644 --- a/src/content/home.json +++ b/src/content/home.json @@ -14,16 +14,22 @@ "text": "Previously called Gnosis Safe, Safe spun out with a mission to build a better standard for ownership with smart contract accounts. Since 2018, Safe has grown to support several EVM chains", "items": [ { - "title": "16M", - "text": "Total transactions" + "text": "Total transactions", + "link": { + "href": "https://dune.com/queries/2093960/3449499" + } }, { - "title": "$56B", - "text": "Total assets stored" + "text": "Total assets stored", + "link": { + "href": "https://dune.com/queries/2771785/4614463" + } }, { - "title": "4.3M", - "text": "Safe accounts deployed" + "text": "Safe accounts deployed", + "link": { + "href": "https://dune.com/queries/2459401/4044167" + } } ], "component": "Home/Stats" diff --git a/src/hooks/useSafeNumbers.ts b/src/hooks/useSafeNumbers.ts new file mode 100644 index 000000000..75f5c5017 --- /dev/null +++ b/src/hooks/useSafeNumbers.ts @@ -0,0 +1,40 @@ +import useSWR from 'swr' +import { formatValue } from '@/lib/formatValue' +import { DUNE_API_KEY } from '@/config/constants' + +const QUERY_ID_TOTAL_TRANSACTIONS = 2093960 +const QUERY_ID_TOTAL_ASSETS = 2771785 +const QUERY_ID_TOTAL_SAFES_DEPOLOYED = 2459401 + +const fallbackStats = ['16M', '$56B', '4.3M'] + +const fetcher = (url: string) => + fetch(url) + .then((res) => res.json()) + .then((data) => data.result.rows[0]) + +function totalAssetsEndpoint(queryId: number): string { + return `https://api.dune.com/api/v1/query/${queryId}/results?api_key=${DUNE_API_KEY}` +} + +export const useSafeNumbers = (): Array => { + const { data: totalTransactions, isLoading: isLoadingTransactions } = useSWR( + totalAssetsEndpoint(QUERY_ID_TOTAL_TRANSACTIONS), + fetcher, + ) + const { data: totalAssets, isLoading: isLoadingAssets } = useSWR(totalAssetsEndpoint(QUERY_ID_TOTAL_ASSETS), fetcher) + const { data: totalSafesDeployed, isLoading: isLoadingSafesDeployed } = useSWR( + totalAssetsEndpoint(QUERY_ID_TOTAL_SAFES_DEPOLOYED), + fetcher, + ) + + if (isLoadingTransactions || isLoadingAssets || isLoadingSafesDeployed) { + return fallbackStats + } + + const formattedTotalAssets = '$' + formatValue(totalAssets.usd_value) + const formattedTotalSafesDeployed = formatValue(totalSafesDeployed.num_safes) + const formattedTotalTransactions = formatValue(totalTransactions.num_txs) + + return [formattedTotalTransactions, formattedTotalAssets, formattedTotalSafesDeployed] +} diff --git a/src/lib/formatValue.ts b/src/lib/formatValue.ts new file mode 100644 index 000000000..968c12e07 --- /dev/null +++ b/src/lib/formatValue.ts @@ -0,0 +1,34 @@ +const hasSingleIntegerDigit = (value: number): boolean => value < 10 + +/** + * Formats a numeric value into its thousands order of magnitude. + * If it rounds to a single integer digit, keep one decimal place. + * + * @param {number} value - The numeric value to format. + * @returns {string} The formatted value with the order of magnitude suffix. + */ +export function formatValue(value: number): string { + if (value >= 1e12) { + const valueInTrillions = value / 1e12 + const isSingleDigitInteger = hasSingleIntegerDigit(valueInTrillions) + const formattedValue = isSingleDigitInteger ? valueInTrillions.toFixed(1) : Math.floor(valueInTrillions).toString() + return formattedValue + 'T' + } else if (value >= 1e9) { + const valueInBillions = value / 1e9 + const isSingleDigitInteger = hasSingleIntegerDigit(valueInBillions) + const formattedValue = isSingleDigitInteger ? valueInBillions.toFixed(1) : Math.floor(valueInBillions).toString() + return formattedValue + 'B' + } else if (value >= 1e6) { + const valueInMillions = value / 1e6 + const isSingleDigitInteger = hasSingleIntegerDigit(valueInMillions) + const formattedValue = isSingleDigitInteger ? valueInMillions.toFixed(1) : Math.floor(valueInMillions).toString() + return formattedValue + 'M' + } else if (value >= 1e3) { + const valueInThousands = value / 1e3 + const isSingleDigitInteger = hasSingleIntegerDigit(valueInThousands) + const formattedValue = isSingleDigitInteger ? valueInThousands.toFixed(1) : Math.floor(valueInThousands).toString() + return formattedValue + 'K' + } else { + return `${value}` + } +}