Skip to content

Commit

Permalink
feat(ABI): annual backers incentives
Browse files Browse the repository at this point in the history
  • Loading branch information
antomor authored and franciscotobar committed Dec 12, 2024
1 parent 97df3b7 commit 214a4d1
Show file tree
Hide file tree
Showing 16 changed files with 233 additions and 42 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 @@ -12,6 +12,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 @@ -25,13 +26,11 @@ 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 totalAllocationsInHuman = Number(formatOnchainFraction(totalAllocations))

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

const totalAllocations = Object.values(data).reduce((acc, allocation) => acc + allocation, 0n)
const totalAllocationsInHuman = Number(formatOnchainFraction(totalAllocations))
const fiatAmount = `= ${currency} ${formatCurrency(totalAllocationsInHuman * price, 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,119 @@
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 } 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: number }>>((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)

const weightedAverageBuilderRewardsPct =
topFiveBuilders.reduce(
(acc, { allocation, current }) =>
acc + Number(((allocation * 100n) / sumTotalAllocation) * BigInt(current)),
0,
) / 100

const totalAllocationInEther = Number(formatEther(sumTotalAllocation))
const rewardsPerStRIFPerCycle =
(cyclePayout * (weightedAverageBuilderRewardsPct / totalAllocationInEther / rifPrice)) / 100

return (Math.pow(1 + rewardsPerStRIFPerCycle, 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,7 +13,7 @@ import { FC } from 'react'
import { Address, getAddress, zeroAddress } from 'viem'
import { useRouter } from 'next/navigation'
import { Link } from '@/components/Link'
import { Builder } from '../types'
import { RequiredBuilder } from '@/app/collective-rewards/types'
import { useCanManageAllocations } from '@/app/collective-rewards/allocations/hooks'

const SubText = () => {
Expand All @@ -37,11 +37,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 @@ -23,7 +23,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 @@ -13,7 +13,7 @@ import { Address } from 'viem'
import { usePricesContext } from '@/shared/context/PricesContext'
import { formatBalanceToHuman } from '@/app/user/Balances/balanceUtils'
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 @@ -53,7 +53,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,13 +10,14 @@ import {
BuilderRewardDetails,
useGetBackerRewardPercentage,
} from '@/app/collective-rewards/rewards'
import { useHandleErrors } from '@/app/collective-rewards/utils'
import { isBuilderActive, isBuilderRewarded, useHandleErrors } from '@/app/collective-rewards/utils'
import { formatBalanceToHuman } from '@/app/user/Balances/balanceUtils'
import { usePricesContext } from '@/shared/context/PricesContext'
import { FC, useEffect, useState } from 'react'
import { Address } 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 @@ -66,14 +67,20 @@ const TokenRewards: FC<TokenRewardsProps> = ({ builder, gauge, token: { id, symb

const rewardPercentageToApply = backerRewardsPct?.current ?? 0

const { getBuilderByAddress } = useBuilderContext()
const claimingBuilder = getBuilderByAddress(builder)
const isRewarded = isBuilderRewarded(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 estimatedRewardsInHuman =
Number(formatBalanceToHuman(rewardsAmount)) * (1 - rewardPercentageToApply / 100)
Expand Down
Loading

0 comments on commit 214a4d1

Please sign in to comment.