Skip to content

Commit

Permalink
TOK-541: add ABI (#472)
Browse files Browse the repository at this point in the history
* feat(ABI): annual backers incentives

* refactor: pr comments

---------

Co-authored-by: Francisco Tobar <[email protected]>
  • Loading branch information
antomor and franciscotobar authored Dec 16, 2024
1 parent 6d83922 commit 3d6855f
Show file tree
Hide file tree
Showing 15 changed files with 230 additions and 38 deletions.
14 changes: 9 additions & 5 deletions src/app/collective-rewards/metrics/Metrics.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useGetGaugesArrayByType, withBuilderButton } from '@/app/collective-rewards/user'
import { useGetGaugesArray, withBuilderButton } from '@/app/collective-rewards/user'
import { HeaderTitle } from '@/components/Typography'
import {
TotalAllocationsMetrics,
Expand All @@ -11,12 +11,13 @@ import { getAddress } from 'viem'
import { tokenContracts } from '@/lib/contracts'
import { getCoinbaseAddress } from '@/app/collective-rewards/utils'
import { PricesContextProvider } from '@/shared/context/PricesContext'
import { ABIMetrics } from './components/ABIMetrics'

const HeaderWithBuilderButton = withBuilderButton(HeaderTitle)

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

const tokens = {
rif: {
Expand All @@ -35,14 +36,17 @@ export const Metrics = () => {
<PricesContextProvider>
<CycleContextProvider>
<div className="flex gap-4 w-full">
<div className="flex gap-4 h-min w-3/4">
<div className="flex gap-4 h-min w-3/5">
<CycleMetrics />
<TotalActiveBuildersMetrics />
<TotalAllocationsMetrics gauges={gauges} token={tokens.rif} />
</div>
<div className="w-1/4">
<div className="w-1/5">
<AllTimeRewardsMetrics gauges={gauges} tokens={tokens} />
</div>
<div className="w-1/5">
<ABIMetrics />
</div>
</div>
</CycleContextProvider>
</PricesContextProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ import { useGetBuildersByState } from '@/app/collective-rewards/user'
import { MetricsCard, MetricsCardTitle, TokenMetricsCardRow } from '@/app/collective-rewards/rewards'
import { withSpinner } from '@/components/LoadingSpinner/withLoadingSpinner'
import { useHandleErrors } from '@/app/collective-rewards/utils'
import { Builder } from '../types'

export const TotalActiveBuildersMetrics = () => {
const {
data: activatedBuilders,
isLoading,
error,
} = useGetBuildersByState<Required<Builder>>({
} = useGetBuildersByState({
activated: true,
communityApproved: true,
kycApproved: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from '@/app/collective-rewards/rewards'
import { withSpinner } from '@/components/LoadingSpinner/withLoadingSpinner'
import { useHandleErrors } from '@/app/collective-rewards/utils'
import { useGetTotalAllocation } from './hooks/useGetTotalAllocation'

type TotalAllocationsProps = {
gauges: Address[]
Expand All @@ -24,12 +25,10 @@ export const TotalAllocationsMetrics: FC<TotalAllocationsProps> = ({
currency = 'USD',
}) => {
const { prices } = usePricesContext()
const { data, isLoading, error } = useGaugesGetFunction(gauges, 'totalAllocation')
const { data: totalAllocations, isLoading, error } = useGetTotalAllocation(gauges)
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 { amount, fiatAmount } = formatMetrics(totalAllocations, price, symbol, currency)

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { MetricsCard, MetricsCardTitle, TokenMetricsCardRow } from '@/app/collective-rewards/rewards'
import { withSpinner } from '@/components/LoadingSpinner/withLoadingSpinner'
import { useGetABI } from './hooks/useGetABI'

export const ABIMetrics = () => {
const { data: abiPct, isLoading } = useGetABI()
return (
<>
<MetricsCard borderless>
<MetricsCardTitle
title="Annual Backers Incentives %"
data-testid="abiPct"
tooltip={{
text: (
<span className="font-rootstock-sans text-sm font-normal">
The Annual Backers Incentives (%) represents an estimate of the annualized percentage of
rewards that backers could receive based on their backing allocations.
<br />
<br />
The calculation follows the formula: (1 + Rewards per stRIF per Cycle / RIF price)^26 - 1.
<br />
<br />
This estimation is dynamic and may vary based on total rewards and user activity. This data is
for informational purposes only.{' '}
</span>
),
popoverProps: {
size: 'medium',
position: 'left-bottom',
},
}}
/>
{withSpinner(
TokenMetricsCardRow,
'min-h-0 grow-0',
)({
amount: `${abiPct.toFixed(0)}%`,
isLoading,
})}
</MetricsCard>
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import {
useGetBackersRewardPercentage,
useGetRewardsCoinbase,
useGetRewardsERC20,
} from '@/app/collective-rewards/rewards'
import { usePricesContext } from '@/shared/context/PricesContext'
import { useGetBuildersByState } from '@/app/collective-rewards/user'
import { RequiredBuilder } from '@/app/collective-rewards/types'
import { useGaugesGetFunction } from '@/app/collective-rewards/shared'
import { useCycleContext } from '@/app/collective-rewards/metrics'
import { formatEther, parseUnits } from 'viem'
import { useMemo } from 'react'

export const useGetABI = () => {
const {
data: builders,
isLoading: buildersLoading,
error: buildersError,
} = useGetBuildersByState<RequiredBuilder>({
activated: true,
communityApproved: true,
kycApproved: true,
revoked: false,
})

const { data: rifRewards, isLoading: rifRewardsLoading, error: rifRewardsError } = useGetRewardsERC20()
const {
data: rbtcRewards,
isLoading: rbtcRewardsLoading,
error: rbtcRewardsError,
} = useGetRewardsCoinbase()

const {
data: { cycleNext },
isLoading: cycleLoading,
error: cycleError,
} = useCycleContext()

const gauges = builders.map(({ gauge }) => gauge)
const {
data: totalAllocation,
isLoading: totalAllocationLoading,
error: totalAllocationError,
} = useGaugesGetFunction(gauges, 'totalAllocation')

const buildersAddress = builders.map(({ address }) => address)
const {
data: backersRewardsPct,
isLoading: backersRewardsPctLoading,
error: backersRewardsPctError,
} = useGetBackersRewardPercentage(buildersAddress, cycleNext.toSeconds())

const { prices } = usePricesContext()

const abi = useMemo(() => {
const sumTotalAllocation = Object.values(totalAllocation).reduce((acc, value) => acc + (value ?? 0n), 0n)

if (!sumTotalAllocation) {
return 0
}

const rifPrice = prices.RIF?.price ?? 0
const rbtcPrice = prices.RBTC?.price ?? 0
const rifAmount = Number(formatEther(rifRewards ?? 0n))
const rbtcAmount = Number(formatEther(rbtcRewards ?? 0n))
const cyclePayout = rifAmount * rifPrice + rbtcAmount * rbtcPrice

if (!rifPrice) {
return 0
}

const topFiveBuilders = builders
.reduce<Array<{ allocation: bigint; current: bigint }>>((acc, builder) => {
const allocation = totalAllocation[builder.gauge]
const rewardPct = backersRewardsPct[builder.address]
if (allocation && rewardPct) {
acc.push({ allocation, current: rewardPct.current })
}
return acc
}, [])
.sort((a, b) => (a.allocation > b.allocation ? -1 : 1))
.slice(0, 5)

// We use the multiplication with the current backer rewards % to avoid losing precision
// Thats why we don't need to multiply by 100
const weightedAverageBuilderRewardsPct = topFiveBuilders.reduce(
(acc, { allocation, current }) => acc + (allocation * current) / sumTotalAllocation,
0n,
)
const weightedAverageBuilderRewardsPctInEther = Number(formatEther(weightedAverageBuilderRewardsPct))

const totalAllocationInEther = Number(formatEther(sumTotalAllocation))
const rewardsPerStRIFPerCycle =
cyclePayout * (weightedAverageBuilderRewardsPctInEther / totalAllocationInEther)

return (Math.pow(1 + rewardsPerStRIFPerCycle / rifPrice, 26) - 1) * 100
}, [backersRewardsPct, builders, prices, rbtcRewards, rifRewards, totalAllocation])

const isLoading =
buildersLoading ||
rifRewardsLoading ||
rbtcRewardsLoading ||
cycleLoading ||
totalAllocationLoading ||
backersRewardsPctLoading

const error =
buildersError ??
rifRewardsError ??
rbtcRewardsError ??
totalAllocationError ??
cycleError ??
backersRewardsPctError

return {
data: abi,
isLoading,
error,
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './ABIMetrics'
12 changes: 12 additions & 0 deletions src/app/collective-rewards/metrics/hooks/useGetTotalAllocation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { useGaugesGetFunction } from '../../shared'

export const useGetTotalAllocation = (gauges: any[]) => {
const { data, isLoading, error } = useGaugesGetFunction(gauges, 'totalAllocation')

const totalAllocations = Object.values(data).reduce((acc, allocation) => acc + allocation, 0n)
return {
data: totalAllocations,
isLoading,
error,
}
}
10 changes: 4 additions & 6 deletions src/app/collective-rewards/rewards/MyRewards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import { tokenContracts } from '@/lib/contracts'
import { FC } from 'react'
import { Address, getAddress, zeroAddress } from 'viem'
import { useRouter } from 'next/navigation'
import { Builder } from '../types'
import { useCanManageAllocations } from '@/app/collective-rewards/allocations/hooks'
import { CRWhitepaperLink } from '@/app/collective-rewards/shared'
import { RequiredBuilder } from '@/app/collective-rewards/types'

const SubText = () => {
return (
Expand All @@ -28,11 +28,9 @@ const SubText = () => {

export const Rewards: FC<{ builder: Address }> = ({ builder }) => {
const router = useRouter()
const { data: activatedBuilders, error: activatedBuildersError } = useGetBuildersByState<Required<Builder>>(
{
activated: true,
},
)
const { data: activatedBuilders, error: activatedBuildersError } = useGetBuildersByState<RequiredBuilder>({
activated: true,
})
const activatedGauges = activatedBuilders?.map(({ gauge }) => gauge) ?? []
const { data: gauge, error: gaugeError } = useGetBuilderToGauge(builder)
const canManageAllocations = useCanManageAllocations()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ type TokenRewardsMetricsProps = {
}

const TokenRewardsMetrics: FC<TokenRewardsMetricsProps> = ({
gauges,
token: { address, symbol },
currency = 'USD',
}) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { useGaugesGetFunction } from '@/app/collective-rewards/shared'
import { Address } from 'viem'
import { usePricesContext } from '@/shared/context/PricesContext'
import { useGetBuildersByState } from '@/app/collective-rewards//user'
import { Builder, BuilderStateFlags } from '@/app/collective-rewards/types'
import { BuilderStateFlags, RequiredBuilder } from '@/app/collective-rewards/types'
import { useMemo } from 'react'

export type BackerRewards = {
Expand Down Expand Up @@ -48,7 +48,7 @@ export const useGetBackerRewards = (
data: builders,
isLoading: buildersLoading,
error: buildersError,
} = useGetBuildersByState<Required<Builder>>()
} = useGetBuildersByState<RequiredBuilder>()
const buildersAddress = builders.map(({ address }) => address)
const {
data: backersRewardsPct,
Expand Down
11 changes: 9 additions & 2 deletions src/app/collective-rewards/rewards/builders/EstimatedRewards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import {
BuilderRewardDetails,
useGetBackerRewardPercentage,
} from '@/app/collective-rewards/rewards'
import { useHandleErrors } from '@/app/collective-rewards/utils'
import { isBuilderRewardable, useHandleErrors } from '@/app/collective-rewards/utils'
import { usePricesContext } from '@/shared/context/PricesContext'
import { FC, useEffect, useState } from 'react'
import { Address, parseUnits } from 'viem'
import { withSpinner } from '@/components/LoadingSpinner/withLoadingSpinner'
import { useCycleContext } from '@/app/collective-rewards/metrics/context/CycleContext'
import { useBuilderContext } from '@/app/collective-rewards/user'

type TokenRewardsProps = {
builder: Address
Expand Down Expand Up @@ -65,14 +66,20 @@ const TokenRewards: FC<TokenRewardsProps> = ({ builder, gauge, token: { id, symb

const rewardPercentageToApply = backerRewardsPct.current

const { getBuilderByAddress } = useBuilderContext()
const claimingBuilder = getBuilderByAddress(builder)
const isRewarded = isBuilderRewardable(claimingBuilder?.stateFlags)

const error =
rewardsError ?? totalPotentialRewardsError ?? rewardSharesError ?? backerRewardsPctError ?? cycleError
useHandleErrors({ error, title: 'Error loading estimated rewards' })

const { prices } = usePricesContext()

const rewardsAmount =
rewardShares && totalPotentialRewards ? (rewards * rewardShares) / totalPotentialRewards : 0n
isRewarded && rewardShares && totalPotentialRewards
? (rewards * rewardShares) / totalPotentialRewards
: 0n
// The complement of the reward percentage is applied to the estimated rewards since are from the builder's perspective
const weiPerEther = parseUnits('1', 18)
const estimatedRewards = (rewardsAmount * (weiPerEther - rewardPercentageToApply)) / weiPerEther
Expand Down
Loading

0 comments on commit 3d6855f

Please sign in to comment.