Skip to content

Commit

Permalink
feat(cr_v2): metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
franciscotobar committed Nov 18, 2024
1 parent 45266c0 commit 67c1a4f
Show file tree
Hide file tree
Showing 23 changed files with 364 additions and 165 deletions.
1 change: 1 addition & 0 deletions src/app/collective-rewards/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useGetGaugesFunction'
65 changes: 65 additions & 0 deletions src/app/collective-rewards/hooks/useGetGaugesFunction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { GaugeAbi } from '@/lib/abis/v2/GaugeAbi'
import { Address } from 'viem'
import { useReadContracts } from 'wagmi'
import { AVERAGE_BLOCKTIME } from '@/lib/constants'
import { useMemo } from 'react'
import { AbiFunction } from 'viem'

type FunctionEntry = Extract<(typeof GaugeAbi)[number], AbiFunction>
type FunctionName = Extract<
FunctionEntry['name'],
'earned' | 'claimedBackerRewards' | 'estimatedBackerRewards' | 'totalAllocation'
>
type FunctionParams = FunctionEntry['inputs']

//TODO: Add the correct return type
export const useGaugesGetFunction = <FunctionResult>(
gauges: Address[],
functionName: FunctionName,
functionParams?: FunctionParams,
) => {
const contractCalls = gauges.map(gauge => {
return {
address: gauge,
abi: GaugeAbi,
functionName,
args: functionParams,
} as const
})

const {
data: contractResults,
isLoading,
error,
} = useReadContracts<bigint[]>({
contracts: contractCalls,
query: {
refetchInterval: AVERAGE_BLOCKTIME,
},
})

const gaugesMap = useMemo(
() =>
gauges.reduce<Record<Address, FunctionResult>>((acc, gauge, index) => {
if (!contractResults) {
return {} as Record<Address, FunctionResult>
}

if (contractResults[index].error) {
throw contractResults[index].error
}

const result = contractResults[index].result as FunctionResult
acc[gauge] = result

return acc
}, {}),
[gauges, contractResults],
)

return {
data: gaugesMap,
isLoading,
error,
}
}
73 changes: 73 additions & 0 deletions src/app/collective-rewards/metrics/AllTimeRewardsMetrics.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Address } from 'viem'
import {
MetricsCard,
MetricsCardTitle,
useGetGaugesNotifyReward,
Token,
TokenMetricsCardRow,
formatMetrics,
} from '@/app/collective-rewards/rewards'
import { FC } from 'react'
import { useHandleErrors } from '@/app/collective-rewards/utils'
import { withSpinner } from '@/components/LoadingSpinner/withLoadingSpinner'
import { formatBalanceToHuman } from '@/app/user/Balances/balanceUtils'
import { usePricesContext } from '@/shared/context/PricesContext'

type TokenRewardsMetricsProps = {
gauges: Address[]
currency?: string
token: Token
}

const TokenRewardsMetrics: FC<TokenRewardsMetricsProps> = ({
gauges,
token: { symbol, address },
currency = 'USD',
}) => {
const { data, isLoading, error } = useGetGaugesNotifyReward(gauges, address)

useHandleErrors({ error, title: 'Error loading all time rewards' })

const { prices } = usePricesContext()

const totalRewards = Object.values(data).reduce(
(acc, events) =>
acc +
events.reduce(
(acc, { args: { backersAmount_, builderAmount_ } }) => acc + backersAmount_ + builderAmount_,
0n,
),
0n,
)

const totalRewardsInHuman = Number(formatBalanceToHuman(totalRewards))
const price = prices[symbol]?.price ?? 0

const { amount, fiatAmount } = formatMetrics(totalRewardsInHuman, price, symbol, currency)

return withSpinner(
TokenMetricsCardRow,
'min-h-0 grow-0',
)({
amount,
fiatAmount,
isLoading,
})
}

type AllTimeRewardsProps = {
gauges: Address[]
tokens: {
[token: string]: Token
}
}

export const AllTimeRewardsMetrics: FC<AllTimeRewardsProps> = ({ gauges, tokens: { rif, rbtc } }) => {
return (
<MetricsCard borderless>
<MetricsCardTitle title="All time rewards" data-testid="AllTimeRewards" />
<TokenRewardsMetrics gauges={gauges} token={rif} />
<TokenRewardsMetrics gauges={gauges} token={rbtc} />
</MetricsCard>
)
}
48 changes: 48 additions & 0 deletions src/app/collective-rewards/metrics/CycleMetrics.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { MetricsCard } from '@/components/MetricsCard'
import { useCycleContext } from '@/app/collective-rewards/metrics'
import { Duration, DateTime } from 'luxon'
import { useEffect, useState } from 'react'

let timeout: NodeJS.Timeout

export const CycleMetrics = () => {
const [timeRemaining, setTimeRemaining] = useState<Duration>(Duration.fromObject({ minutes: 0 }))
let {
data: { cycleDuration, cycleNext },
} = useCycleContext()

const duration =
cycleDuration.as('days') < 1 ? cycleDuration.shiftTo('hours') : cycleDuration.shiftTo('days')

useEffect(() => {
if (timeRemaining.as('minutes') > 0) {
timeout = setTimeout(() => {
setTimeRemaining(state => state.minus({ minutes: 1 }))
}, 60000) // every minute
} else {
clearTimeout(timeout)
}

return () => clearTimeout(timeout)
}, [timeRemaining.minutes])

useEffect(() => {
const now = DateTime.now()
let diff = cycleNext.diff(now, ['days']).mapUnits(unit => Math.floor(unit))

if (diff.as('days') < 1) {
diff = cycleNext.diff(now, ['hours', 'minutes']).mapUnits(unit => Math.floor(unit))
}

setTimeRemaining(diff)
}, [cycleNext])

return (
<MetricsCard
title="Current cycle"
amount={`${timeRemaining?.toHuman()}`}
fiatAmount={`out of ${duration.toHuman()}. Ends ${cycleNext.toFormat('EEE, dd MMM')}`}
borderless
/>
)
}
52 changes: 52 additions & 0 deletions src/app/collective-rewards/metrics/Metrics.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { useGetGaugesArray, withBuilderButton } from '@/app/collective-rewards/user'
import { HeaderTitle } from '@/components/Typography'
import {
TotalAllocationsMetrics,
CycleMetrics,
CycleContextProvider,
TotalActiveBuildersMetrics,
AllTimeRewardsMetrics,
} from '@/app/collective-rewards/metrics'
import { getAddress } from 'viem'
import { tokenContracts } from '@/lib/contracts'
import { getCoinbaseAddress } from '@/app/collective-rewards/utils'
import { PricesContextProvider } from '@/shared/context/PricesContext'

const HeaderWithBuilderButton = withBuilderButton(HeaderTitle)

export const Metrics = () => {
const { data: activeGauges } = useGetGaugesArray('active')
const gauges = activeGauges ?? []

const tokens = {
rif: {
address: getAddress(tokenContracts.RIF),
symbol: 'RIF',
},
rbtc: {
address: getCoinbaseAddress(),
symbol: 'RBTC',
},
}

return (
<div>
<HeaderWithBuilderButton>Metrics</HeaderWithBuilderButton>
<PricesContextProvider>
<CycleContextProvider>
<div className="flex gap-4 w-full">
<div className="flex gap-4 h-min w-3/4">
<CycleMetrics />
<TotalActiveBuildersMetrics />

<TotalAllocationsMetrics gauges={gauges} token={tokens.rif} />
</div>
<div className="w-1/4">
<AllTimeRewardsMetrics gauges={gauges} tokens={tokens} />
</div>
</div>
</CycleContextProvider>
</PricesContextProvider>
</div>
)
}
17 changes: 0 additions & 17 deletions src/app/collective-rewards/metrics/MetricsSection.tsx

This file was deleted.

17 changes: 17 additions & 0 deletions src/app/collective-rewards/metrics/TotalActiveBuildersMetrics.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { MetricsCardWithSpinner } from '@/components/MetricsCard/MetricsCard'
import { useGetGaugesLength } from '../user'
import { useHandleErrors } from '@/app/collective-rewards/utils'

export const TotalActiveBuildersMetrics = () => {
const { data, isLoading, error } = useGetGaugesLength('active')
useHandleErrors({ error, title: 'Error loading active builders' })

return (
<MetricsCardWithSpinner
title="Total active builders"
amount={Number(data || 0n).toFixed()}
isLoading={isLoading}
borderless
/>
)
}
41 changes: 41 additions & 0 deletions src/app/collective-rewards/metrics/TotalAllocationsMetrics.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useGaugesGetFunction } from '@/app/collective-rewards/hooks'
import { Address } from 'viem'
import { useHandleErrors } from '@/app/collective-rewards/utils'
import { MetricsCardWithSpinner } from '@/components/MetricsCard/MetricsCard'
import { FC } from 'react'
import { usePricesContext } from '@/shared/context/PricesContext'
import { Token } from '@/app/collective-rewards/rewards'
import { formatCurrency } from '@/lib/utils'
import { formatBalanceToHuman } from '@/app/user/Balances/balanceUtils'

type TotalAllocationsProps = {
gauges: Address[]
currency?: string
token: Token
}

export const TotalAllocationsMetrics: FC<TotalAllocationsProps> = ({
gauges,
token: { symbol },
currency = 'USD',
}) => {
const { prices } = usePricesContext()
const { data, isLoading, error } = useGaugesGetFunction<bigint>(gauges, 'totalAllocation', [])
useHandleErrors({ error, title: 'Error loading total allocations' })

const price = prices[symbol]?.price ?? 0

const totalAllocations = Object.values(data).reduce((acc, allocation) => acc + allocation, 0n)
const totalAllocationsInHuman = Number(formatBalanceToHuman(totalAllocations))
const fiatAmount = `= ${currency} ${formatCurrency(totalAllocationsInHuman * price, currency)}`

return (
<MetricsCardWithSpinner
title="Total allocations"
amount={`${totalAllocationsInHuman} STRIF`}
fiatAmount={fiatAmount}
isLoading={isLoading}
borderless
/>
)
}
23 changes: 0 additions & 23 deletions src/app/collective-rewards/metrics/components/CycleMetrics.tsx

This file was deleted.

This file was deleted.

2 changes: 0 additions & 2 deletions src/app/collective-rewards/metrics/components/index.ts

This file was deleted.

Loading

0 comments on commit 67c1a4f

Please sign in to comment.