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

TOK-478: metrics #371

Merged
merged 3 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
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>(
franciscotobar marked this conversation as resolved.
Show resolved Hide resolved
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
antomor marked this conversation as resolved.
Show resolved Hide resolved
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])
franciscotobar marked this conversation as resolved.
Show resolved Hide resolved

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)
antomor marked this conversation as resolved.
Show resolved Hide resolved
}, [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">
antomor marked this conversation as resolved.
Show resolved Hide resolved
<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
antomor marked this conversation as resolved.
Show resolved Hide resolved
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
Loading