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-412: builder leaderboard status flag #389

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const tableHeaders: TableHeader[] = [
{ label: 'Est. Backers Rewards', className: 'w-[22%]', sortKey: RewardsColumnKeyEnum.estimatedRewards },
{
label: 'Total Allocations',
className: 'w-[18%]',
className: 'w-[16%]',
// eslint-disable-next-line quotes
tooltip: "The Builder's share of the total allocations",
sortKey: RewardsColumnKeyEnum.totalAllocationPercentage,
Expand Down Expand Up @@ -145,13 +145,19 @@ const BuildersLeaderBoardTable: FC<BuildersLeaderBoardTableProps> = ({ tokens, c
({
address,
builderName,
stateDetails,
lastCycleReward,
estimatedReward,
totalAllocationPercentage,
rewardPercentage,
}) => (
<TableRow key={address} className="text-[14px] border-hidden">
<BuilderNameCell tableHeader={tableHeaders[0]} builderName={builderName} address={address} />
<BuilderNameCell
tableHeader={tableHeaders[0]}
builderName={builderName}
address={address}
stateDetails={stateDetails}
/>
<BackerRewardsPercentage tableHeader={tableHeaders[1]} percentage={rewardPercentage} />
<LazyRewardCell
tableHeader={tableHeaders[2]}
Expand Down
2 changes: 1 addition & 1 deletion src/app/collective-rewards/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Metrics } from '@/app/collective-rewards/metrics'
import { WhitelistContextProviderWithBuilders, WhitelistSection } from '@/app/collective-rewards/whitelist'
import { MainContainer } from '@/components/MainContainer/MainContainer'

export default function BuildersIncentiveMarket() {
export default function CollectiveRewards() {
return (
<MainContainer>
<div className="grid grid-rows-1 gap-[32px]">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,20 @@ const RewardsTable: FC<BackerRewardsTable> = ({ builder, gauges, tokens }) => {
({
address,
builderName,
stateDetails,
rewardPercentage,
estimatedRewards,
totalAllocationPercentage,
claimableRewards,
allTimeRewards,
}) => (
<TableRow key={address} className="text-[14px] border-hidden">
<BuilderNameCell tableHeader={tableHeaders[0]} builderName={builderName} address={address} />
<BuilderNameCell
tableHeader={tableHeaders[0]}
builderName={builderName}
address={address}
stateDetails={stateDetails}
/>
<BackerRewardsPercentage tableHeader={tableHeaders[1]} percentage={rewardPercentage} />
<LazyRewardCell
tableHeader={tableHeaders[2]}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { GaugeAbi } from '@/lib/abis/v2/GaugeAbi'
import { AVERAGE_BLOCKTIME } from '@/lib/constants'
import { Address } from 'viem'
import { useReadContracts } from 'wagmi'

export const useGetAllAllocationOf = (backer: Address, gauges: Address[]) => {
const { data, isLoading, error } = useReadContracts({
contracts: gauges.map(gauge => ({
abi: GaugeAbi,
address: gauge,
functionName: 'allocationOf',
args: [backer],
})),
query: {
refetchInterval: AVERAGE_BLOCKTIME,
},
})

return {
data: data?.map(({ result }) => result as bigint),
isLoading,
error,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export const useGetBackerRewards = (
const rifPrice = prices[rif.symbol]?.price ?? 0
const rbtcPrice = prices[rbtc.symbol]?.price ?? 0

const data = builders.map(({ address, builderName, gauge }) => {
const data = builders.map(({ address, builderName, gauge, stateDetails }) => {
const builderTotalAllocation = totalAllocation[gauge]
const backerAllocationOf = allocationOf[gauge]
const totalAllocationPercentage = builderTotalAllocation
Expand All @@ -82,6 +82,7 @@ export const useGetBackerRewards = (
return {
address,
builderName,
stateDetails,
rewardPercentage,
estimatedRewards: {
rif: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const useGetBuildersRewards = ({ rif, rbtc }: { [token: string]: Token },
data: activeBuilders,
isLoading: activeBuildersLoading,
error: activeBuildersError,
} = useGetFilteredBuilders({ builderName: '', status: 'Active' })
} = useGetFilteredBuilders({ builderName: '', status: 'Active', stateFlags: { activated: true } })

const {
data: totalPotentialRewards,
Expand All @@ -35,6 +35,12 @@ export const useGetBuildersRewards = ({ rif, rbtc }: { [token: string]: Token },
error: buildersRewardsPctError,
} = useGetBuildersRewardPercentage(buildersAddress)

// TODO: validate what do to here
// Leaderboard only shows active builders or inactive builders that the backer allocated to
/* const activeOrAllocatedBuilders = activeBuilders?.filter(
(builder, i) => builder.status === 'Active' || (allBackerAllocations && allBackerAllocations?.[i] > 0n),
) */

const gauges = activeBuilders?.map(({ gauge }) => gauge)
const {
data: totalAllocation,
Expand Down Expand Up @@ -108,7 +114,7 @@ export const useGetBuildersRewards = ({ rif, rbtc }: { [token: string]: Token },
const rbtcPrice = prices[rbtc.symbol]?.price ?? 0

return {
data: activeBuilders.map(({ address, builderName, gauge }) => {
data: activeBuilders.map(({ address, builderName, gauge, stateDetails }) => {
const builderRewardShares = rewardShares[gauge] ?? 0n
const rewardPercentage = buildersRewardsPct[address] ?? null
const currentRewardPercentage = rewardPercentage?.current ?? 0
Expand Down Expand Up @@ -138,6 +144,7 @@ export const useGetBuildersRewards = ({ rif, rbtc }: { [token: string]: Token },
return {
address,
builderName,
stateDetails,
totalAllocationPercentage,
rewardPercentage,
lastCycleReward: {
Expand Down
32 changes: 30 additions & 2 deletions src/app/collective-rewards/shared/components/Table/TableCells.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import { TableCell } from '@/components/Table'
import { Label, Typography } from '@/components/Typography'
import { cn, formatCurrency, shortAddress, toFixed } from '@/lib/utils'
import { FC, memo, useMemo } from 'react'
import { FaArrowDown, FaArrowUp } from 'react-icons/fa6'
import { FaArrowDown, FaArrowUp, FaCircle } from 'react-icons/fa6'
import { Address, isAddress } from 'viem'
import { BuilderRewardPercentage } from '@/app/collective-rewards/rewards'
import { TableHeader } from '@/app/collective-rewards/shared'
import { ProgressBar } from '@/components/ProgressBar'
import { Button } from '@/components/Button'
import { BuilderStateDetails } from '@/app/collective-rewards/types'

type Currency = {
value: number
Expand Down Expand Up @@ -60,21 +61,48 @@ export const LazyRewardCell = memo(RewardCell, ({ rewards: prevReward }, { rewar
prevReward.every((reward, key) => reward.fiat.value === nextReward[key].fiat.value),
)

type BuilderStatusFlagProps = {
stateDetails?: BuilderStateDetails
}

const BuilderStatusFlag: FC<BuilderStatusFlagProps> = ({ stateDetails }) => {
//TODO: check what to do here with the states for the MVP
const isDeactivated = stateDetails && (!stateDetails.kycApproved || !stateDetails.communityApproved)
const isPaused = stateDetails && stateDetails.paused

const color = isDeactivated ? '#932309' : isPaused ? '#F9E1FF' : 'transparent'
const content = isDeactivated ? 'Status: Deactivated' : isPaused ? 'Status: Paused' : ''

return (
<Popover
disabled={!isDeactivated && !isPaused}
content={content}
className="font-normal text-sm flex items-center"
size="small"
trigger="hover"
>
<FaCircle color={color} />
</Popover>
)
}

type BuilderCellProps = {
tableHeader: TableHeader
builderName: string
address: Address
}
} & BuilderStatusFlagProps

export const BuilderNameCell: FC<BuilderCellProps> = ({
tableHeader: { className },
builderName,
address,
stateDetails,
}) => {
const shortenAddress = shortAddress(address)
return (
<TableCell className={cn(className, 'border-solid')}>
<div className="flex flex-row gap-x-1">
<BuilderStatusFlag stateDetails={stateDetails} />
<Jdenticon className="rounded-md bg-white" value={builderName} size="24" />
<Popover
content={
Expand Down
9 changes: 9 additions & 0 deletions src/app/collective-rewards/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,18 @@ export const builderStatusOptions = [
export type BuilderStatus = (typeof builderStatusOptions)[number]
export type BuilderStatusShown = Exclude<BuilderInfo['status'], 'In progress - mvp'>

export type BuilderStateDetails = {
activated: boolean
kycApproved: boolean
communityApproved: boolean
paused: boolean
revoked: boolean
}

export type BuilderInfo = {
address: Address
status: BuilderStatus
stateDetails: BuilderStateDetails
proposals: CreateBuilderProposalEventLog[]
gauge: Address
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BecomeABuilderButton } from './BecomeABuilderButton'
import { useGetBuilders } from '@/app/collective-rewards/user/hooks/useGetBuilders'
import { BuilderInfo } from '@/app/collective-rewards/types'
import { BuilderInfo, BuilderStateDetails } from '@/app/collective-rewards/types'
import { useGetProposalsState } from '@/app/collective-rewards/whitelist/hooks/useGetProposalsState'
import { CreateBuilderProposalEventLog } from '@/app/proposals/hooks/useFetchLatestProposals'
import { AlertProvider, useAlertContext } from '@/app/providers/AlertProvider'
Expand Down Expand Up @@ -45,6 +45,7 @@ describe('BecomeABuilderButton', () => {
},
] as CreateBuilderProposalEventLog[],
gauge: '0x01',
stateDetails: {} as BuilderStateDetails,
}
const buildersData = [builderData]
const proposalsToStates = {
Expand Down
5 changes: 4 additions & 1 deletion src/app/collective-rewards/user/context/BuilderContext.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createContext, FC, ReactNode, useContext, useMemo } from 'react'
import { Address } from 'viem'
import {
BuilderStateDetails,
BuilderStatus,
BuilderStatusProposalCreatedMVP,
BuilderStatusShown,
Expand All @@ -15,6 +16,7 @@ import { withPricesContextProvider } from '@/shared/context/PricesContext'
export type BuilderProposal = {
builderName: string
status: BuilderStatusShown
stateDetails: BuilderStateDetails
address: Address
proposalId: bigint
proposalName: string
Expand Down Expand Up @@ -59,7 +61,7 @@ export const BuilderContextProvider: FC<BuilderProviderProps> = ({ children }) =

const filteredBuilders = useMemo(() => {
return builders.reduce<ProposalByBuilder>((acc, builder) => {
const { status, address, gauge } = builder
const { status, address, gauge, stateDetails } = builder
const proposal = getMostAdvancedProposal(builder, proposalsStateMap)

if (proposal) {
Expand All @@ -73,6 +75,7 @@ export const BuilderContextProvider: FC<BuilderProviderProps> = ({ children }) =
acc[address] = {
builderName,
status: getBuilderStatus(status),
stateDetails,
address,
proposalId,
proposalName,
Expand Down
25 changes: 22 additions & 3 deletions src/app/collective-rewards/user/hooks/useGetBuilders.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import {
BuilderInfo,
BuilderStateDetails,
BuilderStatus,
BuilderStatusActive,
BuilderStatusInProgress,
BuilderStatusProposalCreatedMVP,
} from '@/app/collective-rewards/types'
import { useGetGaugesArray } from '@/app/collective-rewards/user/hooks/useGetGaugesArray'
import { BuilderStateStruct } from '@/app/collective-rewards/utils/getBuilderGauge'
import { useFetchCreateBuilderProposals } from '@/app/proposals/hooks/useFetchLatestProposals'
import { BuilderRegistryAbi } from '@/lib/abis/v2/BuilderRegistryAbi'
import { AVERAGE_BLOCKTIME } from '@/lib/constants'
import { BackersManagerAddress } from '@/lib/contracts'
import { useMemo } from 'react'
import { Address, getAddress } from 'viem'
import { useReadContracts } from 'wagmi'
import { useGetGaugesArray } from '@/app/collective-rewards/user/hooks/useGetGaugesArray'
import { BuilderStateStruct } from '@/app/collective-rewards/utils'

export type BuilderLoader = {
data?: BuilderInfo
Expand All @@ -26,6 +27,7 @@ export type BuildersLoader = Omit<BuilderLoader, 'data'> & {
}

type BuilderStatusMap = Record<Address, BuilderStatus>
type BuilderStateDetailsMap = Record<Address, BuilderStateDetails>

const EXCLUDED_BUILDER_STATUS = 'X'
type BuilderStatusWithExcluded = BuilderStatus | 'X'
Expand Down Expand Up @@ -109,6 +111,22 @@ export const useGetBuilders = (): BuildersLoader => {
} = useReadContracts({ contracts: builderStatesCalls, query: { refetchInterval: AVERAGE_BLOCKTIME } })
const builderStates = builderStatesResult?.map(({ result }) => result as BuilderStateStruct)

// TODO: how to validate builders from mvp
const builderStateDetails = builders?.reduce<BuilderStateDetailsMap>((acc, builder, index) => {
const builderState = (builderStates?.[index] ?? [
false,
false,
false,
false,
false,
'',
'',
]) as BuilderStateStruct
const [activated, kycApproved, communityApproved, paused, revoked] = builderState
acc[builder] = { activated, kycApproved, communityApproved, paused, revoked }
return acc
}, {})

const builderStatusMap = builders?.reduce<BuilderStatusMap>((acc, builder, index) => {
const builderState = (builderStates?.[index] ?? []) as BuilderStateStruct
const status = getCombinedBuilderStatus(builderState)
Expand All @@ -132,10 +150,11 @@ export const useGetBuilders = (): BuildersLoader => {
builderStatusMap && builder in builderStatusMap
? builderStatusMap[builder as Address] // V2
: BuilderStatusProposalCreatedMVP, // MVP
stateDetails: builderStateDetails?.[builder as Address],
proposals: Object.values(proposals),
gauge: builderToGauge?.[builder as Address],
}))
}, [builderStatusMap, buildersProposalsMap, builderToGauge])
}, [builderStatusMap, buildersProposalsMap, builderToGauge, builderStateDetails])

const isLoading = builderProposalsMapLoading || builderStatesLoading || buildersLoading || gaugesLoading
const error = builderProposalsMapError ?? builderStatesError ?? buildersError ?? gaugesError
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { describe, test, expect } from 'vitest'
import { ProposalState } from '@/shared/types'
import { getMostAdvancedProposal } from '@/app/collective-rewards/utils'
import { CreateBuilderProposalEventLog } from '@/app/proposals/hooks/useFetchLatestProposals'
import { BuilderStateDetails } from '@/app/collective-rewards/types'

describe('getValidProposal', () => {
describe('whitelisted builder', () => {
Expand All @@ -25,6 +26,7 @@ describe('getValidProposal', () => {
},
] as CreateBuilderProposalEventLog[],
gauge: '0x01',
stateDetails: {} as BuilderStateDetails,
},
{
1: ProposalState.Executed,
Expand All @@ -50,6 +52,7 @@ describe('getValidProposal', () => {
},
] as CreateBuilderProposalEventLog[],
gauge: '0x01',
stateDetails: {} as BuilderStateDetails,
},
{
1: ProposalState.Active,
Expand Down Expand Up @@ -81,6 +84,7 @@ describe('getValidProposal', () => {
},
] as CreateBuilderProposalEventLog[],
gauge: '0x01',
stateDetails: {} as BuilderStateDetails,
},
{
1: ProposalState.Active,
Expand Down Expand Up @@ -112,6 +116,7 @@ describe('getValidProposal', () => {
},
] as CreateBuilderProposalEventLog[],
gauge: '0x01',
stateDetails: {} as BuilderStateDetails,
},
{
1: ProposalState.Canceled,
Expand Down
Loading
Loading