Skip to content
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

feat(wallet): Dune stats #496

Merged
merged 27 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
44bd931
styles: allow paragraph margins in the LegalDisclaimer
DiogoSoaress Aug 27, 2024
7f48eaa
Merge remote-tracking branch 'origin/main' into wallet-landing-page
DiogoSoaress Oct 10, 2024
0f1fd22
feat(Wallet): Hero section (#446)
DiogoSoaress Oct 10, 2024
bf9f6fe
feat: chain logos marquee (#450)
DiogoSoaress Oct 10, 2024
22db08c
chore: update project logos
DiogoSoaress Oct 23, 2024
a934a20
Merge remote-tracking branch 'origin/main' into wallet-landing-page
DiogoSoaress Oct 28, 2024
a1b6ecb
feat(Wallet): Big cards grid (#448)
DiogoSoaress Oct 29, 2024
84abe05
feat(Wallet): Vertical Slide (#449)
DiogoSoaress Oct 30, 2024
e8ad6bf
feat: gradient stats (#483)
DiogoSoaress Oct 30, 2024
58bb207
Merge remote-tracking branch 'origin/main' into wallet-landing-page
DiogoSoaress Oct 30, 2024
ba095b6
feat(wallet): FeatureCards component (#484)
DiogoSoaress Oct 31, 2024
f140ac5
feat(wallet): Wallet faq (#486)
DiogoSoaress Nov 5, 2024
b643592
chore: delete old content sections
DiogoSoaress Nov 5, 2024
405ede0
feat(wallet): vertical stack (#488)
DiogoSoaress Nov 6, 2024
693a0d3
feat(wallet): centered text blocks (#489)
DiogoSoaress Nov 6, 2024
f41112e
style: fix spacing
DiogoSoaress Nov 7, 2024
aa03804
style: fix Hero spacings
DiogoSoaress Nov 7, 2024
36fa296
fix: copy typos
DiogoSoaress Nov 7, 2024
6e79423
Merge remote-tracking branch 'origin/main' into wallet-landing-page
DiogoSoaress Nov 7, 2024
80361d8
Merge remote-tracking branch 'origin/main' into wallet-landing-page
DiogoSoaress Nov 18, 2024
6e482f6
feat: scroll based animation (#495)
DiogoSoaress Nov 20, 2024
475dbb2
styles(wallet): adjust page styles (#494)
DiogoSoaress Nov 20, 2024
e83b66e
feat: fetch Safe numbers from Dune
DiogoSoaress Nov 20, 2024
d216211
Merge remote-tracking branch 'origin/main' into wallet-dune-links
DiogoSoaress Dec 5, 2024
38b865d
fix: handle !res.ok in the fetch response
DiogoSoaress Dec 5, 2024
d9e4992
fix: return object from hook instead of array
DiogoSoaress Dec 5, 2024
94f9f29
refactor: move the Dune data fetching functions to their own files
DiogoSoaress Dec 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/components/Home/SafeAtScale/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import useScrollReveal from '@/hooks/useScrollReveal'
import TickerElement from './TickerElement'

const SafeAtScale = ({ caption, title, items }: BaseBlock) => {
const safeStats = useSafeStats()
const { formattedTotalTransactions, formattedTotalBalanceUsd, formattedTotalSafesDeployed } = useSafeStats()
const safeStats = [formattedTotalTransactions, formattedTotalBalanceUsd, formattedTotalSafesDeployed]

const containerRef = useRef<HTMLDivElement>(null)
const visibleInViewport = useScrollReveal(containerRef, 50, true)
Expand Down
49 changes: 31 additions & 18 deletions src/components/Wallet/GradientNumbers/index.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,41 @@
import { Container, Grid, Typography } from '@mui/material'
import { type ReactElement } from 'react'
import type { BaseBlock } from '@/components/Home/types'
import { useSafeDataRoomStats } from '@/hooks/useSafeDataRoomStats'
import { useSafeStats } from '@/hooks/useSafeStats'
import { formatValue } from '@/lib/formatValue'
import layoutCss from '@/components/common/styles.module.css'
import css from './styles.module.css'

const Stats = ({ title, items }: BaseBlock): ReactElement => (
<Container className={layoutCss.container}>
<Typography variant="h2" mb={3} textAlign="center">
{title}
</Typography>
const Stats = ({ title, items }: BaseBlock): ReactElement => {
const { formattedTotalBalanceUsd, formattedMonthlyActiveUsers } = useSafeStats()
const { annualisedOutgoingTVP } = useSafeDataRoomStats()

<Grid container spacing={{ xs: '30px', xl: '50px' }} justifyContent="space-between">
{items?.map((item, index) => (
<Grid item xs={12} md={4} key={index}>
<a href={item.link?.href} target="_blank" rel="noreferrer" className={css.metric}>
{/* TODO: Fetch these values from a Dune query */}
<p className={css.value}>{item.title}</p>
const formattedAnnualisedOutgoingTVP = annualisedOutgoingTVP ? '$' + formatValue(annualisedOutgoingTVP) : null

<Typography variant="caption">{item.text}</Typography>
</a>
</Grid>
))}
</Grid>
</Container>
)
const formattedSafeStats = [formattedTotalBalanceUsd, formattedMonthlyActiveUsers, formattedAnnualisedOutgoingTVP]

return (
<Container className={layoutCss.container}>
<Typography variant="h2" mb={3} textAlign="center">
{title}
</Typography>

<Grid container spacing={{ xs: '30px', xl: '50px' }} justifyContent="space-between">
{items?.map((item, index) => (
<Grid item xs={12} md={4} key={index}>
<a href={item.link?.href} target="_blank" rel="noreferrer" className={css.metric}>
<p className={css.value}>{formattedSafeStats[index] ?? item.title}</p>

<Typography variant="caption" textAlign="center">
{item.text}
</Typography>
</a>
</Grid>
))}
</Grid>
</Container>
)
}

export default Stats
18 changes: 14 additions & 4 deletions src/components/Wallet/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,19 @@ import PageContent from '../common/PageContent'
import type { getStaticProps } from '@/pages/wallet'
import walletContent from '@/content/wallet.json'
import FaqContentContext from '@/contexts/FaqContentContext'
import SafeDataRoomContext from '@/contexts/SafeDataRoomContext'
import SafeStatsContext from '@/contexts/SafeStatsContext'

export const Wallet = ({ pageData }: InferGetStaticPropsType<typeof getStaticProps>) => (
<FaqContentContext.Provider value={pageData}>
<PageContent content={walletContent} path="wallet.json" />
</FaqContentContext.Provider>
export const Wallet = ({
pageData,
safeDataRoomStats,
safeStatsData,
}: InferGetStaticPropsType<typeof getStaticProps>) => (
<SafeStatsContext.Provider value={safeStatsData}>
<SafeDataRoomContext.Provider value={safeDataRoomStats}>
<FaqContentContext.Provider value={pageData}>
<PageContent content={walletContent} path="wallet.json" />
</FaqContentContext.Provider>
</SafeDataRoomContext.Provider>
</SafeStatsContext.Provider>
)
4 changes: 2 additions & 2 deletions src/content/home.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"title": "<i>Safe</i> Smart Accounts are battle-tested<br/>and used more than any other",
"items": [
{
"title": "46M",
"title": "143M",
"text": "Total transactions",
"link": {
"href": "https://dune.com/queries/2093960/3449499"
Expand All @@ -44,7 +44,7 @@
}
},
{
"title": "8.2M",
"title": "30M",
"text": "Total accounts deployed",
"link": {
"href": "https://dune.com/queries/2459401/4044167"
Expand Down
2 changes: 1 addition & 1 deletion src/contexts/SafeDataRoomContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type SafeDataRoomStats = {
annualisedOutgoingTVP: number | null
}

const SafeDataRoomContext = createContext<SafeDataRoomStats>({
const SafeDataRoomContext = createContext<Partial<SafeDataRoomStats>>({
tvpToGDPPercentage: null,
usdcPercentageStored: null,
cryptoPunksStoredPercentage: null,
Expand Down
8 changes: 5 additions & 3 deletions src/contexts/SafeStatsContext.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { createContext } from 'react'

type SafeStats = {
totalAssets: number | null
totalBalanceUsd: number | null
totalTransactions: number | null
totalSafesDeployed: number | null
monthlyActiveUsers: number | null
}

const SafeStatsContext = createContext<SafeStats>({
totalAssets: null,
const SafeStatsContext = createContext<Partial<SafeStats>>({
totalBalanceUsd: null,
totalTransactions: null,
totalSafesDeployed: null,
monthlyActiveUsers: null,
})

export default SafeStatsContext
46 changes: 15 additions & 31 deletions src/hooks/useSafeStats.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,26 @@
import { useContext } from 'react'
import { formatValue } from '@/lib/formatValue'
import SafeStatsContext from '@/contexts/SafeStatsContext'
import { DUNE_API_KEY } from '@/config/constants'
import { duneQueryUrlBuilder } from '@/lib/duneQueryUrlBuilder'

const QUERY_ID_TOTAL_TRANSACTIONS = 2093960
// const QUERY_ID_TOTAL_ASSETS = 2893829
const QUERY_ID_TOTAL_SAFES_DEPLOYED = 2459401

export const fetchTotalTransactions = async (): Promise<number | null> => {
return fetch(duneQueryUrlBuilder(QUERY_ID_TOTAL_TRANSACTIONS, DUNE_API_KEY))
.then((res) => res.json())
.then((data) => data.result.rows[0].num_txs)
.catch(() => null)
}

export const fetchTotalAssets = async (): Promise<number | null> => {
return null
// Dune query has a but that needs to be fixed so defaulting to a static value
// return fetch(duneQueryUrlBuilder(QUERY_ID_TOTAL_ASSETS, DUNE_API_KEY))
// .then((res) => res.json())
// .then((data) => data.result.rows[0].usd_value)
// .catch(() => null)
}

export const fetchTotalSafesDeployed = async (): Promise<number | null> => {
return fetch(duneQueryUrlBuilder(QUERY_ID_TOTAL_SAFES_DEPLOYED, DUNE_API_KEY))
.then((res) => res.json())
.then((data) => data.result.rows[0].num_safes)
.catch(() => null)
type SafeStats = {
formattedTotalTransactions: string | null
formattedTotalBalanceUsd: string | null
formattedTotalSafesDeployed: string | null
formattedMonthlyActiveUsers: string | null
}

export const useSafeStats = (): Array<string | null> => {
const { totalTransactions, totalAssets, totalSafesDeployed } = useContext(SafeStatsContext)
export const useSafeStats = (): SafeStats => {
const { totalTransactions, totalBalanceUsd, totalSafesDeployed, monthlyActiveUsers } = useContext(SafeStatsContext)

const formattedTotalTransactions = totalTransactions ? formatValue(totalTransactions) : null
const formattedTotalAssets = totalAssets ? '$' + formatValue(totalAssets) : null
const formattedTotalBalanceUsd = totalBalanceUsd ? '$' + formatValue(totalBalanceUsd) : null
const formattedTotalSafesDeployed = totalSafesDeployed ? formatValue(totalSafesDeployed) : null
const formattedMonthlyActiveUsers = monthlyActiveUsers ? formatValue(monthlyActiveUsers) : null

return [formattedTotalTransactions, formattedTotalAssets, formattedTotalSafesDeployed]
return {
formattedTotalTransactions,
formattedTotalBalanceUsd,
formattedTotalSafesDeployed,
formattedMonthlyActiveUsers,
}
}
17 changes: 17 additions & 0 deletions src/lib/fetchMonthlyActiveUsers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { DUNE_API_KEY } from '@/config/constants'
import { duneQueryUrlBuilder } from '@/lib/duneQueryUrlBuilder'

const QUERY_ID_MONTHLY_ACTIVE_USERS = 4151164

export const fetchMonthlyActiveUsers = async (): Promise<number | null> => {
return fetch(duneQueryUrlBuilder(QUERY_ID_MONTHLY_ACTIVE_USERS, DUNE_API_KEY))
.then((res) => {
if (!res.ok) {
throw new Error(res.statusText)
}

return res.json()
})
.then((data) => data.result.rows[data.result.rows.length - 1].active_users)
.catch((err) => console.error(`Error fetching monthly active users: ${err.message}`))
}
12 changes: 12 additions & 0 deletions src/lib/fetchTotalBalanceUsd.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const QUERY_ID_TOTAL_ASSETS_BY_CHAIN = 3609251

export const fetchTotalBalanceUsd = async (): Promise<number | null> => {
// TODO: Uncomment this code after agreeing with the narrative team on the data source
return null
// return fetch(duneQueryUrlBuilder(QUERY_ID_TOTAL_ASSETS_BY_CHAIN, DUNE_API_KEY))
// .then((res) => res.json())
// .then((data) => {
// return data.result.rows[0].total_balance_usd
// })
// .catch(() => null)
}
17 changes: 17 additions & 0 deletions src/lib/fetchTotalSafesDeployed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { DUNE_API_KEY } from '@/config/constants'
import { duneQueryUrlBuilder } from '@/lib/duneQueryUrlBuilder'

const QUERY_ID_TOTAL_SAFES_DEPLOYED = 2459401

export const fetchTotalSafesDeployed = async (): Promise<number | null> => {
return fetch(duneQueryUrlBuilder(QUERY_ID_TOTAL_SAFES_DEPLOYED, DUNE_API_KEY))
.then((res) => {
if (!res.ok) {
throw new Error(res.statusText)
}

return res.json()
})
.then((data) => data.result.rows[0].num_safes)
.catch((err) => console.error(`Error fetching total safes deployed: ${err.message}`))
}
17 changes: 17 additions & 0 deletions src/lib/fetchTotalTransactions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { DUNE_API_KEY } from '@/config/constants'
import { duneQueryUrlBuilder } from '@/lib/duneQueryUrlBuilder'

const QUERY_ID_TOTAL_TRANSACTIONS = 2093960

export const fetchTotalTransactions = async (): Promise<number | null> => {
return fetch(duneQueryUrlBuilder(QUERY_ID_TOTAL_TRANSACTIONS, DUNE_API_KEY))
.then((res) => {
if (!res.ok) {
throw new Error(res.statusText)
}

return res.json()
})
.then((data) => data.result.rows[0].num_txs)
.catch((err) => console.error(`Error fetching total number of transactions: ${err.message}`))
}
10 changes: 6 additions & 4 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import type { InferGetStaticPropsType, NextPage } from 'next'
import { loadChainsData } from '@/lib/loadChainsData'
import { Home } from '@/components/Home'
import { fetchTotalAssets, fetchTotalSafesDeployed, fetchTotalTransactions } from '@/hooks/useSafeStats'
import { fetchTotalTransactions } from '@/lib/fetchTotalTransactions'
import { fetchTotalBalanceUsd } from '@/lib/fetchTotalBalanceUsd'
import { fetchTotalSafesDeployed } from '@/lib/fetchTotalSafesDeployed'

const IndexPage: NextPage<InferGetStaticPropsType<typeof getStaticProps>> = (props) => {
return <Home {...props} />
}

export async function getStaticProps() {
const [chainsData, totalTransactions, totalAssets, totalSafesDeployed] = await Promise.all([
const [chainsData, totalTransactions, totalBalanceUsd, totalSafesDeployed] = await Promise.all([
loadChainsData(),
fetchTotalTransactions(),
fetchTotalAssets(),
fetchTotalBalanceUsd(),
fetchTotalSafesDeployed(),
])

Expand All @@ -20,7 +22,7 @@ export async function getStaticProps() {
chainsData,
safeStatsData: {
totalTransactions,
totalAssets,
totalBalanceUsd,
totalSafesDeployed,
},
},
Expand Down
4 changes: 2 additions & 2 deletions src/pages/press.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import client from '@/lib/contentful'
import PressRoom, { type PressRoomProps } from '@/components/Pressroom'
import type { TypePressRoomSkeleton, TypePostSkeleton } from '@/contentful/types'
import { fetchTotalAssets } from '@/hooks/useSafeStats'
import { fetchTotalBalanceUsd } from '@/lib/fetchTotalBalanceUsd'
import type { GetStaticProps, InferGetStaticPropsType, NextPage } from 'next'

const PressroomPage: NextPage<InferGetStaticPropsType<typeof getStaticProps>> = (props) => {
Expand All @@ -21,7 +21,7 @@ export const getStaticProps: GetStaticProps<PressRoomProps> = async () => {
include: 3,
})

const totalAssets = await fetchTotalAssets()
const totalAssets = await fetchTotalBalanceUsd()

const pressRoom = pressRoomEntries.items[0]

Expand Down
18 changes: 16 additions & 2 deletions src/pages/wallet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,29 @@ import type { InferGetStaticPropsType, NextPage } from 'next'
import client from '@/lib/contentful'
import { Wallet } from '@/components/Wallet'
import type { TypeBaseBlockSkeleton } from '@/contentful/types'
import { fetchDataRoomStats } from '@/hooks/useSafeDataRoomStats'
import { fetchTotalBalanceUsd } from '@/lib/fetchTotalBalanceUsd'
import { fetchMonthlyActiveUsers } from '@/lib/fetchMonthlyActiveUsers'

const FAQ_CONTENT_TYPE_ID = '1jCIVFDUzFO1okK8b6TTxS'

const WalletPage: NextPage<InferGetStaticPropsType<typeof getStaticProps>> = (props) => <Wallet {...props} />

export async function getStaticProps() {
const faqContent = await client.getEntry<TypeBaseBlockSkeleton>(FAQ_CONTENT_TYPE_ID)
const [faqContent, dataRoomStats, totalBalanceUsd, monthlyActiveUsers] = await Promise.all([
client.getEntry<TypeBaseBlockSkeleton>(FAQ_CONTENT_TYPE_ID),
fetchDataRoomStats(),
fetchTotalBalanceUsd(),
fetchMonthlyActiveUsers(),
])

return { props: { pageData: { faqContent } } }
return {
props: {
pageData: { faqContent },
safeDataRoomStats: { annualisedOutgoingTVP: dataRoomStats?.annualised_outgoing_tvp },
safeStatsData: { totalBalanceUsd, monthlyActiveUsers },
},
}
}

export default WalletPage
Loading