diff --git a/.env.testnet.local b/.env.testnet.local index 142c0ba2..baa2e961 100644 --- a/.env.testnet.local +++ b/.env.testnet.local @@ -7,8 +7,8 @@ NEXT_PUBLIC_BUILD_ID= NEXT_PUBLIC_RIF_ADDRESS=0x19f64674d8a5b4e652319f5e239efd3bc969a1fe NEXT_PUBLIC_STRIF_ADDRESS=0xC4b091d97AD25ceA5922f09fe80711B7ACBbb16f -NEXT_PUBLIC_GOVERNOR_ADDRESS=0x8F10473F1c2f1dA76F6E921aA6eE8c54aD43F7b7 -NEXT_PUBLIC_EA_NFT_ADDRESS=0x979deF73ec80B8AE24Ae46765b81D9aF7b1C9327 +NEXT_PUBLIC_GOVERNOR_ADDRESS=0xB1A39B8f57A55d1429324EEb1564122806eb297F +NEXT_PUBLIC_EA_NFT_ADDRESS=0x0Ee4e11f2F2B551cA31Ea7873c7bA675cb51A59d NEXT_PUBLIC_MULTICALL_ADDRESS=0xcA11bde05977b3631167028862bE2a173976CA11 NEXT_PUBLIC_GRANTS_BUCKET_ADDRESS=0xfaca664c661af7e0e630c8f92b401012cd2a30ef NEXT_PUBLIC_GRANTS_ACTIVE_BUCKET_ADDRESS=0x2217E4d3Ae0A6E30075D1B5a7b8C1520E8009f49 @@ -17,14 +17,21 @@ NEXT_PUBLIC_GENERAL_BUCKET_ADDRESS=0x72Ed7d7b7835Ad62B1f9b6280bAd62618aA71461 NEXT_PUBLIC_CHAIN_ID=31 # CR-related env variables -NEXT_PUBLIC_SIMPLIFIED_REWARD_DISTRIBUTOR_ADDRESS=0x4e84FCc953dE129C6C47c5B0AD7E57B226093Ae1 -NEXT_PUBLIC_BACKERS_MANAGER_ADDRESS=0xC2857F402096BfF24E8E05C2E047F8461d1af927 -NEXT_PUBLIC_REWARD_DISTRIBUTOR_ADDRESS=0x21534EaE65041b85cA309C3258E967f305917F8F -NEXT_PUBLIC_GOVERNANCE_MANAGER_ADDRESS=0xAEdD29bebb0dd8e29702DD1e32346365F96aA016 +# TODO: To be removed +NEXT_PUBLIC_SIMPLIFIED_REWARD_DISTRIBUTOR_ADDRESS=0xc469Cc2579De5C16210e9063B4E628bF8C46bA02 -NEXT_PUBLIC_CYCLE_DURATION_IN_DAYS=2 +NEXT_PUBLIC_BACKERS_MANAGER_ADDRESS=0xec0a29Df5180A6B04496dfAf2D827e36F4a0A52F +NEXT_PUBLIC_REWARD_DISTRIBUTOR_ADDRESS=0xD476E4804551595687C1f6F0a9C22dd1Bbfa0319 +NEXT_PUBLIC_GOVERNANCE_MANAGER_ADDRESS=0xb7C6918d6aE6df2e147FF464271a94EAfF027E5D +NEXT_PUBLIC_CYCLE_DURATION_IN_DAYS=7 NEXT_PUBLIC_FIRST_CYCLE_START_DATE_ISO="1970-01-01T00:00:00Z" -NEXT_PUBLIC_ENV_DATA_URL="https://raw.githubusercontent.com/RootstockCollective/dao-frontend/develop/data.testnet.local.json" +NEXT_PUBLIC_ENV_DATA_URL="https://raw.githubusercontent.com/RootstockCollective/dao-frontend/develop/data.testnet.qa.json" + +# OG NFT Contracts +NEXT_PUBLIC_OG_FOUNDERS=0x7E6d9969CAC008bAe5f7b144df3c955515404538 +NEXT_PUBLIC_OG_PARTNERS=0x285046a90fb322E6BaCa4F38Bb884e3C0904F7EB +NEXT_PUBLIC_OG_CONTRIBUTORS=0xDC03B8fb7E47E4651f5008bD718a804726424A75 + NEXT_PUBLIC_ENABLE_CORS_BYPASS=true NEXT_PUBLIC_ENABLE_FEATURE_V2_REWARDS=true \ No newline at end of file diff --git a/src/app/collective-rewards/types.ts b/src/app/collective-rewards/types.ts index 157d76ed..16a63599 100644 --- a/src/app/collective-rewards/types.ts +++ b/src/app/collective-rewards/types.ts @@ -2,7 +2,9 @@ import { CreateBuilderProposalEventLog } from '@/app/proposals/hooks/useFetchLat import { ProposalState } from '@/shared/types' import { Address } from 'viem' -export const builderStatusOptions = ['Whitelisted', 'In progress'] as const +export const BuilderStatusActive = 'Active' +export const BuilderStatusInProgress = 'In progress' +export const builderStatusOptions = [BuilderStatusActive, BuilderStatusInProgress] as const export type BuilderStatus = (typeof builderStatusOptions)[number] diff --git a/src/app/collective-rewards/user/hooks/useGetBuilders.ts b/src/app/collective-rewards/user/hooks/useGetBuilders.ts index ace030e8..d5e05503 100644 --- a/src/app/collective-rewards/user/hooks/useGetBuilders.ts +++ b/src/app/collective-rewards/user/hooks/useGetBuilders.ts @@ -1,8 +1,17 @@ -import { useGetIsWhitelistedBuilder, useGetWhitelistedBuilders } from '@/app/collective-rewards/user/hooks' -import { BuilderInfo } from '@/app/collective-rewards/types' +import { + BuilderInfo, + BuilderStatus, + BuilderStatusActive, + BuilderStatusInProgress, +} from '@/app/collective-rewards/types' 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, isAddressEqual } from 'viem' +import { Address, getAddress } from 'viem' +import { useReadContracts } from 'wagmi' +import { useGetGaugesArray } from './useGetGaugesArray' export type BuilderLoader = { data?: BuilderInfo @@ -14,67 +23,108 @@ export type BuildersLoader = Omit & { data: BuilderInfo[] } -export const useGetBuilderByAddress = (address: Address): BuilderLoader => { - const { - data: buildersProposalsMap, - isLoading: builderProposalsMapLoading, - error: builderProposalsMapError, - } = useFetchCreateBuilderProposals() +type BuilderStatusMap = Record +const EXCLUDED_BUILDER_STATUS = 'X' +export const useGetBuilders = (): BuildersLoader => { + /* + * get Gauges + * for each Gauge + * get Builder from Gauge + * get Builder state + * ignore the builder if paused or revoked (to be confirmed) + */ + // get the gauges + const { data: gauges, isLoading: gaugesLoading, error: gaugesError } = useGetGaugesArray() + // get the builders for each gauge + const gaugeToBuilderCalls = gauges?.map( + gauge => + ({ + address: BackersManagerAddress, + abi: BuilderRegistryAbi, + functionName: 'gaugeToBuilder', + args: [gauge], + }) as const, + ) const { - data: isWhitelistedBuilder, - isLoading: isWhitelistedBuilderLoading, - error: isWhitelistedBuilderError, - } = useGetIsWhitelistedBuilder(address) + data: buildersResult, + isLoading: buildersLoading, + error: buildersError, + } = useReadContracts({ + contracts: gaugeToBuilderCalls, + query: { + refetchInterval: AVERAGE_BLOCKTIME, + }, + }) + const builders = buildersResult?.map(builder => builder.result) as Address[] - const data = useMemo(() => { - if (buildersProposalsMap) { - const proposals = buildersProposalsMap?.[address] ?? {} + // get the builder state for each builder + const builderStatesCalls = builders?.map( + builder => + ({ + address: BackersManagerAddress, + abi: BuilderRegistryAbi, + functionName: 'builderState', + args: [builder], + }) as const, + ) + const { + data: builderStatesResult, + isLoading: builderStatesLoading, + error: builderStatesError, + } = useReadContracts({ contracts: builderStatesCalls, query: { refetchInterval: AVERAGE_BLOCKTIME } }) + const builderStates = builderStatesResult?.map( + builderState => + builderState.result as readonly [ + boolean, + boolean, + boolean, + boolean, + boolean, + `0x${string}`, + `0x${string}`, + ], + ) + const builderStatusMap = builders + ?.map((builder, index) => { + const [activated, kycApproved, whitelisted, paused, , ,] = builderStates?.[index] ?? [] return { - address, - status: isWhitelistedBuilder ? 'Whitelisted' : 'In progress', - proposals: Object.values(proposals), - } as BuilderInfo - } - }, [buildersProposalsMap, address, isWhitelistedBuilder]) - - const isLoading = builderProposalsMapLoading || isWhitelistedBuilderLoading - const error = builderProposalsMapError ?? isWhitelistedBuilderError + address: builder, + // TODO: to be refactored in a function + status: paused + ? BuilderStatusActive + : activated && kycApproved && whitelisted + ? BuilderStatusActive + : kycApproved || whitelisted + ? BuilderStatusInProgress + : EXCLUDED_BUILDER_STATUS, // used to filter out builders + } + }) + .filter(builder => builder.status !== EXCLUDED_BUILDER_STATUS) + .reduce((acc, builder) => { + acc[builder.address] = builder.status as BuilderStatus + return acc + }, {}) - return { - data, - isLoading, - error, - } -} - -export const useGetBuilders = (): BuildersLoader => { const { data: buildersProposalsMap, isLoading: builderProposalsMapLoading, error: builderProposalsMapError, } = useFetchCreateBuilderProposals() - const { - data: whitelistedBuilders, - isLoading: whitelistedBuildersLoading, - error: whitelistedBuildersError, - } = useGetWhitelistedBuilders() const data = useMemo(() => { - return Object.entries(buildersProposalsMap ?? {}).map(([builder, proposals]) => ({ - address: getAddress(builder), - status: whitelistedBuilders?.some(whitelistedBuilder => - isAddressEqual(whitelistedBuilder, getAddress(builder)), - ) - ? 'Whitelisted' - : 'In progress', - proposals: Object.values(proposals), - })) - }, [whitelistedBuilders, buildersProposalsMap]) + return Object.entries(buildersProposalsMap ?? {}) + .filter(([builder]) => builderStatusMap && builder in builderStatusMap) + .map(([builder, proposals]) => ({ + address: getAddress(builder), + status: builderStatusMap[builder as Address], + proposals: Object.values(proposals), + })) + }, [builderStatusMap, buildersProposalsMap]) - const isLoading = builderProposalsMapLoading || whitelistedBuildersLoading - const error = builderProposalsMapError ?? whitelistedBuildersError + const isLoading = builderProposalsMapLoading || builderStatesLoading || buildersLoading + const error = builderProposalsMapError ?? builderStatesError ?? buildersError return { data, diff --git a/src/app/collective-rewards/user/hooks/useGetGaugesArray.ts b/src/app/collective-rewards/user/hooks/useGetGaugesArray.ts new file mode 100644 index 00000000..59e874f5 --- /dev/null +++ b/src/app/collective-rewards/user/hooks/useGetGaugesArray.ts @@ -0,0 +1,53 @@ +import { AVERAGE_BLOCKTIME } from '@/lib/constants' +import { Address } from 'viem' +import { useReadContract, useReadContracts } from 'wagmi' +import { BuilderRegistryAbi } from '../../../../lib/abis/v2/BuilderRegistryAbi' +import { BackersManagerAddress } from '../../../../lib/contracts' + +// TODO: to be rebased since already included in another PR +export const useGetGaugesArray = () => { + const { + data: gaugesLength, + isLoading: gaugesLengthLoading, + error: gaugesLengthError, + } = useReadContract({ + address: BackersManagerAddress, + abi: BuilderRegistryAbi, + functionName: 'getGaugesLength', + query: { + refetchInterval: AVERAGE_BLOCKTIME, + }, + }) + + const length = gaugesLength ? Number(gaugesLength) : 0 + + const contractCalls = Array.from({ length }, (_, index) => { + return { + address: BackersManagerAddress, + abi: BuilderRegistryAbi, + functionName: 'getGaugeAt', + args: [index], + } as const + }) + + const { + data: gaugesAddress, + isLoading: gaugesAddressLoading, + error: gaugesAddressError, + } = useReadContracts({ + contracts: contractCalls, + query: { + refetchInterval: AVERAGE_BLOCKTIME, + }, + }) + + const gauges = gaugesAddress?.map(gauge => gauge.result as Address) + const isLoading = gaugesLengthLoading || gaugesAddressLoading + const error = gaugesLengthError ?? gaugesAddressError + + return { + data: gauges, + isLoading, + error, + } +} \ No newline at end of file diff --git a/src/app/collective-rewards/utils/getMostAdvancedProposal.ts b/src/app/collective-rewards/utils/getMostAdvancedProposal.ts index fa78f909..379d4044 100644 --- a/src/app/collective-rewards/utils/getMostAdvancedProposal.ts +++ b/src/app/collective-rewards/utils/getMostAdvancedProposal.ts @@ -1,8 +1,16 @@ import { ProposalState } from '@/shared/types' -import { BuilderInfo, ProposalsToState } from '@/app/collective-rewards/types' +import { + BuilderInfo, + BuilderStatus, + BuilderStatusActive, + BuilderStatusInProgress, + ProposalsToState, +} from '@/app/collective-rewards/types' const inactiveProposalsStates = [ProposalState.Canceled, ProposalState.Defeated, ProposalState.Expired] const isActive = (state: ProposalState) => !inactiveProposalsStates.includes(state) +const isBuilderInProgress = (builderStatus: BuilderStatus) => + [BuilderStatusActive, BuilderStatusInProgress].includes(builderStatus) export const getMostAdvancedProposal = ( { status, proposals }: BuilderInfo, @@ -12,9 +20,10 @@ export const getMostAdvancedProposal = ( .sort(({ timeStamp: a }, { timeStamp: b }) => b - a) .find(({ args: { proposalId } }) => { const state = proposalsStateMap[proposalId.toString()] - + // TODO: To be refactored + // Get the proposal only if the Builder is Active or In Progress const isExecuted = ProposalState.Executed === state - if (status === 'Whitelisted') { + if (isBuilderInProgress(status)) { return isExecuted } diff --git a/src/app/collective-rewards/whitelist/WhitelistSection.tsx b/src/app/collective-rewards/whitelist/WhitelistSection.tsx index 5f92e15d..035073aa 100644 --- a/src/app/collective-rewards/whitelist/WhitelistSection.tsx +++ b/src/app/collective-rewards/whitelist/WhitelistSection.tsx @@ -1,9 +1,10 @@ import { useAlertContext } from '@/app/providers/AlertProvider' import { LoadingSpinner } from '@/components/LoadingSpinner' import { HeaderTitle } from '@/components/Typography' -import { useEffect } from 'react' +import React, { useEffect } from 'react' import { WhitelistGrid, WhitelistSearch } from './components' import { useWhitelistContext } from './context' +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/Collapsible' export const WhitelistSection = () => { const { builders, isLoading, error: whitelistError } = useWhitelistContext() @@ -21,13 +22,20 @@ export const WhitelistSection = () => { }, [whitelistError, setErrorMessage]) return ( -
- Activated Builders - + <> + + + Activated Builders + - {/* TODO: We should show an empty table (not considered in the design yet) on error */} - {isLoading && } - {!isLoading && } -
+ + + + {/* TODO: We should show an empty table (not considered in the design yet) on error */} + {isLoading && } + {!isLoading && } + + + ) } diff --git a/src/app/collective-rewards/whitelist/hooks/useGetProposalsState.ts b/src/app/collective-rewards/whitelist/hooks/useGetProposalsState.ts index b7d1599e..59ab7840 100644 --- a/src/app/collective-rewards/whitelist/hooks/useGetProposalsState.ts +++ b/src/app/collective-rewards/whitelist/hooks/useGetProposalsState.ts @@ -6,6 +6,7 @@ import { CreateBuilderProposalEventLog } from '@/app/proposals/hooks/useFetchLat import { useMemo } from 'react' import { ProposalState } from '@/shared/types' import { ProposalsToState } from '@/app/collective-rewards/types' +import { AVERAGE_BLOCKTIME } from '@/lib/constants' export const useGetProposalsState = (proposals: CreateBuilderProposalEventLog[]) => { const contractCalls = proposals.map(({ args: { proposalId } }) => { @@ -24,7 +25,7 @@ export const useGetProposalsState = (proposals: CreateBuilderProposalEventLog[]) } = useReadContracts({ contracts: contractCalls, query: { - refetchInterval: 30_000, + refetchInterval: AVERAGE_BLOCKTIME, }, }) diff --git a/src/app/proposals/hooks/useFetchLatestProposals.ts b/src/app/proposals/hooks/useFetchLatestProposals.ts index e379e9ed..4ac80a00 100644 --- a/src/app/proposals/hooks/useFetchLatestProposals.ts +++ b/src/app/proposals/hooks/useFetchLatestProposals.ts @@ -6,6 +6,7 @@ import { Interface } from 'ethers' import { useMemo } from 'react' import { getAddress, parseEventLogs } from 'viem' import { ADDRESS_PADDING_LENGTH, RELAY_PARAMETER_PADDING_LENGTH } from '@/app/proposals/shared/utils' +import { BuilderRegistryAbi } from '@/lib/abis/v2/BuilderRegistryAbi' const useFetchLatestProposals = () => { return useQuery({ @@ -50,11 +51,11 @@ if (!RELAY_FUNCTION_SELECTOR) { } const CR_WHITELIST_FUNCTION = 'whitelistBuilder' // TODO: refactor -const CR_WHITELIST_FUNCTION_SELECTOR = new Interface(SimplifiedRewardDistributorAbi).getFunction( +const CR_WHITELIST_FUNCTION_SELECTOR = new Interface(BuilderRegistryAbi).getFunction( CR_WHITELIST_FUNCTION, )?.selector if (!CR_WHITELIST_FUNCTION_SELECTOR) { - throw new Error(`Function ${CR_WHITELIST_FUNCTION} not found in SimplifiedRewardDistributorAbi.`) + throw new Error(`Function ${CR_WHITELIST_FUNCTION} not found in BuilderRegistryAbi.`) } type ElementType = T extends (infer U)[] ? U : never