diff --git a/src/app/collective-rewards/allocations/components/AllocationAmount.tsx b/src/app/collective-rewards/allocations/components/AllocationAmount.tsx index a6d84450..473f2d46 100644 --- a/src/app/collective-rewards/allocations/components/AllocationAmount.tsx +++ b/src/app/collective-rewards/allocations/components/AllocationAmount.tsx @@ -1,10 +1,12 @@ 'use client' -import { formatBalanceToHuman } from '@/app/user/Balances/balanceUtils' import { Button, ButtonProps } from '@/components/Button' import { Input } from '@/components/Input' import { cn } from '@/lib/utils' -import { useState } from 'react' +import { useContext, useState } from 'react' +import { AllocationsContext } from '@/app/collective-rewards/allocations/context' +import { formatEther, parseEther } from 'viem' +import { StakeHint } from './StakeHint' const PercentageButton = ({ children, variant, ...rest }: ButtonProps) => ( ) -type AllocationAmountProps = { - balance: bigint - errorMessage?: string - onPercentageSet?: (percentage: number) => void -} +const ALLOCATION_EXCEED_AMOUNT_ERROR = 'Builder allocations exceeds amount to allocate' -export const AllocationAmount = ({ balance, errorMessage, onPercentageSet }: AllocationAmountProps) => { - // TODO: hint is shown only when the allocated amount exceeds the balance - // const hint = - const hint = undefined +export const AllocationAmount = () => { + const { + state: { + backer: { balance, totalAllocation, allocationCount }, + cumulativeAllocation, + }, + actions: { updateAllocations, updateTotalAllocation }, + } = useContext(AllocationsContext) const [activeButton, setActiveButton] = useState(null) - const [allocatedAmount, setAllocatedAmount] = useState('0') - const onChange = (value: string) => { - setAllocatedAmount(value) - } - const onPercentageButtonClicked = (percentage: number, index: number) => { - const percentageAmount = (BigInt(balance ?? 0n) * BigInt(percentage)) / BigInt(100) - setAllocatedAmount(formatBalanceToHuman(percentageAmount)) - onPercentageSet?.(percentage) + const newTotalAllocation = (BigInt(balance ?? 0n) * BigInt(percentage)) / BigInt(100) + updateTotalAllocation(newTotalAllocation) setActiveButton(index) + const allocationValue = newTotalAllocation / BigInt(allocationCount) + + updateAllocations(Array(allocationCount).fill(allocationValue)) } + + const handleOnChange = (value: string) => { + updateTotalAllocation(parseEther(value)) + } + return (
@@ -49,10 +53,14 @@ export const AllocationAmount = ({ balance, errorMessage, onPercentageSet }: All labelProps={{ className: 'text-base leading-4 font-normal' }} name="allocated-amount" fullWidth - onChange={onChange} - value={allocatedAmount.toString()} - errorMessage={errorMessage} - hint={hint} + onChange={handleOnChange} + value={formatEther(totalAllocation)} + errorMessage={ + cumulativeAllocation > totalAllocation && cumulativeAllocation < balance + ? ALLOCATION_EXCEED_AMOUNT_ERROR + : '' + } + hint={Number(totalAllocation - cumulativeAllocation) < 0 ? : undefined} />
diff --git a/src/app/collective-rewards/allocations/components/AllocationMetrics.tsx b/src/app/collective-rewards/allocations/components/AllocationMetrics.tsx index 79aafdad..04b6529b 100644 --- a/src/app/collective-rewards/allocations/components/AllocationMetrics.tsx +++ b/src/app/collective-rewards/allocations/components/AllocationMetrics.tsx @@ -1,13 +1,9 @@ 'use client' -import { formatBalanceToHuman, getTokenBalance } from '@/app/user/Balances/balanceUtils' -import { useGetAddressTokens } from '@/app/user/Balances/hooks/useGetAddressTokens' -import { withSpinner } from '@/components/LoadingSpinner/withLoadingSpinner' import { Paragraph } from '@/components/Typography' -import { ethers } from 'ethers' -import { useAccount } from 'wagmi' -import { useHandleErrors } from '../../utils' -import { useBackerTotalAllocation } from '../hooks/useBackerTotalAllocation' +import { useContext } from 'react' +import { formatEther } from 'viem' +import { AllocationsContext } from '@/app/collective-rewards/allocations/context' type ValueProps = { value: string @@ -33,60 +29,39 @@ const Column = ({ children }: { children: React.ReactNode }) => { const Balance = ({ value }: ValueProps) => { return } -const BalanceWithSpinner = withSpinner(Balance) const AllocatedAmount = ({ value }: ValueProps) => { return } -const AllocatedAmountWithSpinner = withSpinner(AllocatedAmount) const UnallocatedAmount = ({ value }: ValueProps) => { return } -const UnallocatedAmountWithSpinner = withSpinner(UnallocatedAmount) export const AllocationMetrics = () => { - // TODO: we can move this logic to a custom hook or to a context - const { address, chainId } = useAccount() - let { - data, - isLoading: balanceLoading, - error: balanceError, - } = useGetAddressTokens(address!, chainId as number) - const stRIFBalance = getTokenBalance('stRIF', data) - const balanceValue = `${stRIFBalance.balance} ${stRIFBalance.symbol}` - balanceLoading = false + const { + initialState: { + backer: { totalAllocation, balance }, + }, + } = useContext(AllocationsContext) - let { - data: allocatedAmount, - isLoading: allocatedAmountLoading, - error: allocatedAmountError, - } = useBackerTotalAllocation(address!) - const allocatedAmountValue = `${allocatedAmount} ${stRIFBalance.symbol}` - allocatedAmountLoading = false - allocatedAmount = 0n + const balanceValue = `${formatEther(balance)} stRIF` - useHandleErrors({ - error: balanceError ?? allocatedAmountError, - title: 'Failed to fetch balance and allocated amount', - }) + const allocatedAmountValue = `${formatEther(totalAllocation)} stRIF` - const unformattedUnit = stRIFBalance.balance - const formattedUnit = ethers.parseEther(unformattedUnit) - const unallocatedAmount = formatBalanceToHuman(formattedUnit - (allocatedAmount || 0n)) + const unallocatedAmount = formatEther(balance - totalAllocation) - const unallocatedAmountValue = `${unallocatedAmount} ${stRIFBalance.symbol}` - const unallocatedAmountLoading = balanceLoading || allocatedAmountLoading + const unallocatedAmountValue = `${unallocatedAmount} stRIF` return (
- + - + - +
) diff --git a/src/app/collective-rewards/allocations/components/BuilderAllocation.tsx b/src/app/collective-rewards/allocations/components/BuilderAllocation.tsx index 86127b5b..55503479 100644 --- a/src/app/collective-rewards/allocations/components/BuilderAllocation.tsx +++ b/src/app/collective-rewards/allocations/components/BuilderAllocation.tsx @@ -1,42 +1,43 @@ -import { formatBalanceToHuman } from '@/app/user/Balances/balanceUtils' import { Input } from '@/components/Input' import { Slider } from '@/components/Slider' import { Label } from '@/components/Typography' -import { useState } from 'react' +import { useContext } from 'react' +import { formatEther, parseEther } from 'viem' +import { AllocationsContext } from '@/app/collective-rewards/allocations/context' import { BuilderAllocationHeader, BuilderAllocationHeaderProps } from './BuilderAllocationHeader' -import { Address } from 'viem' -import { BuilderStatusShown } from '../../types' - -export type BuilderStatus = BuilderStatusShown | 'Paused' | 'Deactivated' export type BuilderAllocationProps = BuilderAllocationHeaderProps & { - allocationLeft: BigInt - // TODO: what's the value we expect here? (e.g. 8% or 8.123456%) - backerRewards: number - currentAllocation: number + index: number + kickback: number + currentAllocation: bigint } export const BuilderAllocation = (builder: BuilderAllocationProps) => { - const [sliderValue, setSliderValue] = useState(builder.currentAllocation) - /* TODO: when the cumulative amount exceeds the balance - * - hint is changed - * - Slider is hidden - */ - const hint = `Allocation left ${formatBalanceToHuman(builder.allocationLeft.toString())} stRIF` - // const hint = - const onInputChange = () => { - /* TODO: - * - reset all the sliders to 0 when the user changes the input - * - update the cumulative amount - * - if the cumulative amount exceeds the total allocation, show an error message in the current input - */ + const { + state: { + backer: { totalAllocation }, + cumulativeAllocation, + }, + actions: { updateAllocation }, + } = useContext(AllocationsContext) + const allocationLeft = totalAllocation - cumulativeAllocation + const { currentAllocation, kickback, address } = builder + const onInputChange = (value: string) => { + updateAllocation(builder.index, parseEther(value)) } + return (
- - - + + 0 ? formatEther(allocationLeft) : '0'} stRIF`} + onChange={onInputChange} + value={formatEther(currentAllocation)} + /> +
) } diff --git a/src/app/collective-rewards/allocations/components/BuilderAllocationHeader.tsx b/src/app/collective-rewards/allocations/components/BuilderAllocationHeader.tsx index 929b7a0a..8770333e 100644 --- a/src/app/collective-rewards/allocations/components/BuilderAllocationHeader.tsx +++ b/src/app/collective-rewards/allocations/components/BuilderAllocationHeader.tsx @@ -2,15 +2,15 @@ import { AddressOrAlias } from '@/components/Address' import { Badge } from '@/components/Badge' import { Jdenticon } from '@/components/Header/Jdenticon' import { Paragraph, Typography } from '@/components/Typography' -import { BuilderStatusActive, BuilderStatusShown } from '../../types' -import { crStatusColorClasses } from '../../user' +import { BuilderStatus, BuilderStatusActive } from '@/app/collective-rewards/types' +import { crStatusColorClasses } from '@/app/collective-rewards/user' import { Address } from 'viem' import { FC } from 'react' export type BuilderAllocationHeaderProps = { address: Address builderName: string - status: BuilderStatusShown + status: BuilderStatus joiningDate: string } diff --git a/src/app/collective-rewards/allocations/components/index.ts b/src/app/collective-rewards/allocations/components/index.ts new file mode 100644 index 00000000..e471eb16 --- /dev/null +++ b/src/app/collective-rewards/allocations/components/index.ts @@ -0,0 +1,5 @@ +export * from './AllocationAmount' +export * from './AllocationMetrics' +export * from './BuilderAllocation' +export * from './Header' +export * from './StakeHint' diff --git a/src/app/collective-rewards/allocations/context/Action.d.ts b/src/app/collective-rewards/allocations/context/Action.d.ts deleted file mode 100644 index dd0e1e1b..00000000 --- a/src/app/collective-rewards/allocations/context/Action.d.ts +++ /dev/null @@ -1,43 +0,0 @@ -type Action = - | { - type: 'TOGGLE_SELECTED_BUILDER' - payload: { - builderIndex: number - } - } - | { - type: 'SET_ALLOCATIONS' - payload: { - allocations: BuilderAllocations - } - } - | { - type: 'UPDATE_ALLOCATION' - payload: { - builderIndex: number - value: number - } - } - | { - type: 'UPDATE_CONTEXT_LOADING' - payload: { - isLoading: boolean - } - } - | { - type: 'UPDATE_CONTEXT_ERROR' - payload: { - error: Error | null - } - } - -export type AllocationActionType = Action['type'] - -export type AllocationAction = Extract - -export type AllocationActionPayload = AllocationAction['payload'] - -export type AllocationActionHandler = ( - state: AllocationState, - payload: AllocationActionPayload, -) => AllocationState diff --git a/src/app/collective-rewards/allocations/context/AllocationsContext.tsx b/src/app/collective-rewards/allocations/context/AllocationsContext.tsx index 22be9644..6c33619a 100644 --- a/src/app/collective-rewards/allocations/context/AllocationsContext.tsx +++ b/src/app/collective-rewards/allocations/context/AllocationsContext.tsx @@ -1,45 +1,107 @@ -import { useGetAllAllocationOf } from '@/app/collective-rewards/allocations/hooks' -import { useGetActiveBuilders } from '@/app/collective-rewards/user' -import { createContext, Dispatch, FC, ReactNode, Reducer, useEffect, useReducer } from 'react' +import { + useBackerTotalAllocation, + useGetAllAllocationOf, + useGetVotingPower, +} from '@/app/collective-rewards/allocations/hooks' +import { BuilderInfo } from '@/app/collective-rewards/types' +import { useGetBuilders } from '@/app/collective-rewards/user' +import { createContext, FC, ReactNode, useEffect, useMemo, useState } from 'react' import { zeroAddress } from 'viem' import { useAccount } from 'wagmi' -import { reducer } from './reducer' -import { AllocationState, BuilderAllocations } from './Context' -import { AllocationAction, AllocationActionType } from './Action' +import { createActions } from './allocationsActions' -export const AllocationsStateContext = createContext(null) +export type Allocations = Record -export const AllocationsActionsContext = createContext -> | null>(null) +export interface Backer { + totalAllocation: bigint + balance: bigint + allocationCount: number +} -export const AllocationsContextProvider: FC<{ children: ReactNode }> = ({ children }) => { - const { address: backerAddress } = useAccount() +type AllocationsContextValue = { + selections: number[] + allocations: Allocations + backer: Backer + isContextLoading: boolean + cumulativeAllocation: bigint + contextError: Error | null + getBuilder: (index: number) => BuilderInfo | null +} - const [state, dispatch] = useReducer>>( - reducer, - { - initialState: null, - selections: [], - allocations: {}, - user: { - address: zeroAddress, - totalAllocation: 0n, - balance: 0n, - }, - meta: { - isLoading: true, - error: null, - }, +export type AllocationsActions = { + toggleSelectedBuilder: (builderIndex: number) => void + updateAllocation: (builderIndex: number, value: bigint) => void + updateAllocations: (values: bigint[]) => void + updateTotalAllocation: (value: bigint) => void + resetAllocations: () => void +} + +export type InitialState = Pick + +type AllocationsContext = { + initialState: InitialState + state: AllocationsContextValue + actions: AllocationsActions +} +export const AllocationsContext = createContext({ + initialState: { + allocations: {}, + backer: { + balance: BigInt(0), + totalAllocation: BigInt(0), + allocationCount: 0, }, - ) - const { allocations, selections, initialState, user, meta } = state + }, + state: { + selections: [], + allocations: {}, + cumulativeAllocation: BigInt(0), + backer: { + balance: BigInt(0), + totalAllocation: BigInt(0), + allocationCount: 0, + }, + isContextLoading: true, + contextError: null, + getBuilder: () => ({}) as BuilderInfo, + }, + actions: { + toggleSelectedBuilder: () => {}, + updateAllocation: () => {}, + updateAllocations: () => {}, + updateTotalAllocation: () => {}, + resetAllocations: () => {}, + }, +}) - const { - data: activeBuilders, - isLoading: isLoadingActiveBuilders, - error: activeBuildersError, - } = useGetActiveBuilders() +export const AllocationsContextProvider: FC<{ children: ReactNode }> = ({ children }) => { + const { address: backerAddress } = useAccount() + + /** + * Selections are the indexes of the builders that the backer has selected + */ + const [selections, setSelections] = useState([]) + + /** + * Allocations are the amounts that the backer has allocated to each builder + * The key is the index of the builder + * The value is the amount allocated + */ + const [allocations, setAllocations] = useState>({}) + + /** + * Cumulative allocation is the total amount that the backer has allocated to all builders in the session + */ + const [cumulativeAllocation, setCumulativeAllocation] = useState(BigInt(0)) + const [isContextLoading, setIsContextLoading] = useState(true) + const [contextError, setContextError] = useState(null) + const [backer, setBacker] = useState({ + balance: BigInt(0), + totalAllocation: BigInt(0), + allocationCount: 0, + }) + + const { data: builders, isLoading: isLoadingBuilders, error: buildersError } = useGetBuilders() const { data: allAllocations, @@ -47,107 +109,123 @@ export const AllocationsContextProvider: FC<{ children: ReactNode }> = ({ childr error: allAllocationsError, } = useGetAllAllocationOf( backerAddress ?? zeroAddress, - activeBuilders.map(builder => builder.gauge), + builders.map(builder => builder.gauge), ) + const { + data: totalAllocation, + isLoading: isTotalAllocationLoading, + error: totalAllocationError, + } = useBackerTotalAllocation(backerAddress ?? zeroAddress) + + const { data: votingPower, isLoading: isVotingPowerLoading, error: votingPowerError } = useGetVotingPower() + useEffect(() => { - dispatch({ - type: 'UPDATE_CONTEXT_LOADING', - payload: { isLoading: true }, - }) + if (isContextLoading) { + return + } if (!backerAddress || !allAllocations) { return } + let newCumulativeAllocation = BigInt(0) const allocations = allAllocations.reduce((acc, allocation, index) => { if (allocation || selections.includes(index)) { acc[index] = allocation } + newCumulativeAllocation += allocation return acc - }, {} as BuilderAllocations) - dispatch({ - type: 'SET_ALLOCATIONS', - payload: { allocations }, - }) - dispatch({ - type: 'UPDATE_CONTEXT_LOADING', - payload: { isLoading: false }, - }) - }, [allAllocations, selections, backerAddress]) + }, {} as Allocations) + setCumulativeAllocation(newCumulativeAllocation) + setAllocations(allocations) + setBacker(prevBacker => ({ + ...prevBacker, + allocationCount: builders.length, + })) + }, [allAllocations, backerAddress, selections, isContextLoading]) useEffect(() => { - if (activeBuildersError || allAllocationsError) { - dispatch({ - type: 'UPDATE_CONTEXT_ERROR', - payload: { error: activeBuildersError || allAllocationsError }, - }) + if (totalAllocation) { + setBacker(prevBacker => ({ + ...prevBacker, + totalAllocation, + })) } - }, [allAllocationsError, activeBuildersError]) + }, [totalAllocation]) useEffect(() => { - if (isLoadingActiveBuilders || isAllAllocationsLoading) { - dispatch({ - type: 'UPDATE_CONTEXT_LOADING', - payload: { isLoading: true }, - }) + if (votingPower) { + setBacker(prevBacker => ({ + ...prevBacker, + balance: votingPower, + })) + } + }, [votingPower]) + + useEffect(() => { + setContextError(buildersError ?? allAllocationsError ?? totalAllocationError ?? votingPowerError) + }, [allAllocationsError, buildersError, totalAllocationError, votingPowerError]) + + useEffect(() => { + setIsContextLoading( + isLoadingBuilders || isAllAllocationsLoading || isTotalAllocationLoading || isVotingPowerLoading, + ) + }, [isLoadingBuilders, isAllAllocationsLoading, isTotalAllocationLoading, isVotingPowerLoading]) + + const initialState: InitialState = useMemo(() => { + if (isContextLoading) { + return { + backer: { + balance: BigInt(0), + totalAllocation: BigInt(0), + allocationCount: 0, + }, + allocations: {}, + } } - }, [isLoadingActiveBuilders, isAllAllocationsLoading]) - - // const actions: AllocationsActions = { - // toggleSelectedBuilder: (builderIndex: number) => { - // const selectionIndex = selections.findIndex(selection => selection === builderIndex) - // if (selectionIndex >= 0) { - // return setSelections([ - // ...selections.slice(0, selectionIndex), - // ...selections.slice(selectionIndex + 1), - // ]) - // } - - // setSelections([...selections, builderIndex]) - // }, - // updateAllocation: (builderIndex: number, value: bigint) => { - // if (!activeBuilders[builderIndex]) { - // setContextError(new Error('Builder not found')) - // } - - // setAllocations({ - // ...allocations, - // [builderIndex]: value, - // }) - // }, - // } - - // const displayBuilders = useMemo(() => { - // Object.entries(allocations).map((index, value) => { - // const builder = activeBuilders[index] - // const { status } = builder - // return { - // allocationLeft: 0n, - // backerRewards: 0, - // currentAllocation: 0, - // address: builder.gauge, - // builderName: builder.name, - // status, - // joiningDate, - // } - // }) - // }, [activeBuilders, selections, allocations]) - - // const data: AllocationsContextValue = { - // selections, - // allocations, - // isContextLoading, - // contextError, - // displayBuilders, - // } + + const initialAllocations = allAllocations.reduce((acc, allocation, index) => { + if (allocation || selections.includes(index)) { + acc[index] = allocation + } + return acc + }, {} as Allocations) + + return { + backer: { + balance: votingPower ?? BigInt(0), + totalAllocation: totalAllocation ?? BigInt(0), + allocationCount: builders.length, + }, + allocations: initialAllocations, + } + }, [allAllocations, builders, totalAllocation, votingPower, isContextLoading]) + + const data: AllocationsContextValue = { + selections, + allocations, + cumulativeAllocation, + backer, + isContextLoading, + contextError, + getBuilder: (index: number) => (index >= 0 && index < builders.length ? builders[index] : null), + } + + const actions: AllocationsActions = useMemo( + () => createActions(setSelections, setAllocations, setCumulativeAllocation, setBacker, initialState), + [initialState], + ) return ( - - - {children} - {} - - + + {children} + ) } diff --git a/src/app/collective-rewards/allocations/context/Context.d.ts b/src/app/collective-rewards/allocations/context/Context.d.ts deleted file mode 100644 index 4732e539..00000000 --- a/src/app/collective-rewards/allocations/context/Context.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Address } from 'viem' -import { BuilderStatusShown } from '@/app/collective-rewards/types' -import { AllocationAction } from './Action' - -export type BuilderAllocations = Record - -export interface Backer { - address: Address - totalAllocation: bigint - balance: bigint -} - -export interface AllocationState { - initialState: AllocationState | null - selections: number[] - allocations: BuilderAllocations - user: Backer - meta: ContextState -} - -export interface ContextState { - isLoading: boolean - error: Error | null -} diff --git a/src/app/collective-rewards/allocations/context/allocationsActions.ts b/src/app/collective-rewards/allocations/context/allocationsActions.ts new file mode 100644 index 00000000..aa738438 --- /dev/null +++ b/src/app/collective-rewards/allocations/context/allocationsActions.ts @@ -0,0 +1,54 @@ +import { Dispatch, SetStateAction } from 'react' +import { Allocations, AllocationsActions, Backer, InitialState } from './AllocationsContext' + +export const createActions = ( + setSelections: Dispatch>, + setAllocations: Dispatch>>, + setCumulativeAllocation: Dispatch>, + setBacker: Dispatch>, + initialState: InitialState, +): AllocationsActions => ({ + toggleSelectedBuilder: (builderIndex: number) => { + setSelections(prevSelections => + prevSelections.includes(builderIndex) + ? prevSelections.filter(index => index !== builderIndex) + : [...prevSelections, builderIndex], + ) + }, + updateAllocation: (builderIndex: number, value: bigint) => { + setAllocations(prevAllocations => { + const newAllocations = { ...prevAllocations, [builderIndex]: value } + + setCumulativeAllocation( + prevCumulativeAllocation => + prevCumulativeAllocation + value - (prevAllocations[builderIndex] ?? BigInt(0)), + ) + + return newAllocations + }) + }, + updateAllocations: (values: bigint[]) => { + const [newAllocations, newCumulativeAllocation] = values.reduce( + (acc, value, index) => { + acc[0][index] = value + acc[1] += value + + return acc + }, + [{} as Allocations, BigInt(0)], + ) + setAllocations(newAllocations) + setCumulativeAllocation(newCumulativeAllocation) + }, + updateTotalAllocation: (value: bigint) => { + setBacker(prevBacker => ({ + ...prevBacker, + totalAllocation: value, + })) + }, + resetAllocations: () => { + setAllocations(initialState.allocations) + setBacker(initialState.backer) + setCumulativeAllocation(BigInt(0)) + }, +}) diff --git a/src/app/collective-rewards/allocations/context/reducer.ts b/src/app/collective-rewards/allocations/context/reducer.ts deleted file mode 100644 index 6000435f..00000000 --- a/src/app/collective-rewards/allocations/context/reducer.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { AllocationAction, AllocationActionHandler, AllocationActionType } from './Action' -import { AllocationState } from './Context' - -export const actionHandlers: { - [T in AllocationActionType]: AllocationActionHandler -} = { - TOGGLE_SELECTED_BUILDER: (state, { builderIndex }) => { - return { - ...state, - selections: state.selections.includes(builderIndex) - ? state.selections.filter((selection: number) => selection !== builderIndex) - : [...state.selections, builderIndex], - } - }, - SET_ALLOCATIONS: (state, { allocations }) => { - return { - ...state, - allocations, - } - }, - UPDATE_ALLOCATION: (state, { builderIndex, value }) => { - return { - ...state, - allocations: { - ...state.allocations, - [builderIndex]: value, - }, - } - }, - UPDATE_CONTEXT_LOADING: (state, { isLoading }) => { - return { - ...state, - isContextLoading: isLoading, - } - }, - UPDATE_CONTEXT_ERROR: (state, { error }) => { - return { - ...state, - contextError: error, - } - }, -} - -export function reducer( - state: AllocationState, - { type, payload }: AllocationAction, -): AllocationState { - const handler = actionHandlers[type] as AllocationActionHandler // FIXME: fix type - - return handler(state, payload) -} diff --git a/src/app/collective-rewards/allocations/hooks/index.ts b/src/app/collective-rewards/allocations/hooks/index.ts index e8d9fbda..3dffbdee 100644 --- a/src/app/collective-rewards/allocations/hooks/index.ts +++ b/src/app/collective-rewards/allocations/hooks/index.ts @@ -1 +1,2 @@ export * from './useBackerTotalAllocation' +export * from './useGetVotingPower' diff --git a/src/app/collective-rewards/allocations/hooks/useBackerTotalAllocation.ts b/src/app/collective-rewards/allocations/hooks/useBackerTotalAllocation.ts index f686e97d..844787e5 100644 --- a/src/app/collective-rewards/allocations/hooks/useBackerTotalAllocation.ts +++ b/src/app/collective-rewards/allocations/hooks/useBackerTotalAllocation.ts @@ -2,7 +2,7 @@ import { BackersManagerAbi } from '@/lib/abis/v2/BackersManagerAbi' import { GaugeAbi } from '@/lib/abis/v2/GaugeAbi' import { AVERAGE_BLOCKTIME } from '@/lib/constants' import { BackersManagerAddress } from '@/lib/contracts' -import { Address } from 'viem' +import { Address, parseEther } from 'viem' import { useReadContract, useReadContracts } from 'wagmi' export const useBackerTotalAllocation = (backer: Address) => { @@ -18,28 +18,49 @@ export const useBackerTotalAllocation = (backer: Address) => { }) return { - data, + data: parseEther('200'), isLoading, error, } } -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, - }, - }) +// TODO: UNCOMMENT +// 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, +// } +// } + +// TODO: DELETE +const data = [ + BigInt('100000000000000000000'), // 100 ETH equivalent for builder 1 + BigInt('200000000000000000000'), // 200 ETH equivalent for builder 2 + BigInt('15000000000000000000'), // 15 ETH equivalent for builder 3 + BigInt('30000000000000000000'), // 30 ETH equivalent for builder 4 + BigInt('250000000000000000000'), // 250 ETH equivalent for builder 5 + BigInt('180000000000000000000'), // 180 ETH equivalent for builder 6 + BigInt('22000000000000000000'), // 22 ETH equivalent for builder 7 + BigInt('27000000000000000000'), // 27 ETH equivalent for builder 8 +] + +export const useGetAllAllocationOf = (backer: Address, gauges: Address[]) => { return { - data: data?.map(({ result }) => result as bigint), - isLoading, - error, + data, + isLoading: false, + error: null, } } diff --git a/src/app/collective-rewards/allocations/hooks/useGetVotingPower.ts b/src/app/collective-rewards/allocations/hooks/useGetVotingPower.ts new file mode 100644 index 00000000..e46d2b88 --- /dev/null +++ b/src/app/collective-rewards/allocations/hooks/useGetVotingPower.ts @@ -0,0 +1,24 @@ +import { StRIFTokenAbi } from '@/lib/abis/StRIFTokenAbi' +import { AVERAGE_BLOCKTIME } from '@/lib/constants' +import { tokenContracts } from '@/lib/contracts' +import { useAccount, useReadContract, useReadContracts } from 'wagmi' + +export const useGetVotingPower = () => { + const { address } = useAccount() + + const { data, isLoading, error } = useReadContract({ + abi: StRIFTokenAbi, + address: tokenContracts.stRIF, + functionName: 'balanceOf', + args: [address!], + query: { + refetchInterval: AVERAGE_BLOCKTIME, + }, + }) + + return { + data, + isLoading, + error, + } +} diff --git a/src/app/collective-rewards/allocations/page.tsx b/src/app/collective-rewards/allocations/page.tsx index df224b77..c2ee23a7 100644 --- a/src/app/collective-rewards/allocations/page.tsx +++ b/src/app/collective-rewards/allocations/page.tsx @@ -1,22 +1,34 @@ 'use client' +import { Button } from '@/components/Button' import { MainContainer } from '@/components/MainContainer/MainContainer' import { Typography } from '@/components/Typography' -import { AllocationAmount } from './components/AllocationAmount' -import { AllocationMetrics } from './components/AllocationMetrics' -import { BuilderAllocation } from './components/BuilderAllocation' -import { Header } from './components/Header' -import { useState } from 'react' -import { useAllocationsContext } from './context' - -const ALLOCATION_EXCEED_AMOUNT_ERROR = 'Builder allocations exceeds amount to allocate' +import { useRouter } from 'next/navigation' +import { useContext } from 'react' +import { + AllocationAmount, + AllocationMetrics, + BuilderAllocation, + BuilderAllocationProps, + Header, +} from './components' +import { AllocationsContext } from './context' export default function Allocations() { - const { selections } = useAllocationsContext() - /* TODO: Error message is set when - * - the cumulative amount exceeds the allocation amount (ALLOCATION_EXCEED_AMOUNT_ERROR) - */ - const [errorMessage, setErrorMessage] = useState('') + const router = useRouter() + const { + state: { allocations, getBuilder }, + actions: { resetAllocations }, + } = useContext(AllocationsContext) + + const saveAllocations = () => { + // TODO: save current allocations + } + + const cancel = () => { + resetAllocations() + router.back() + } return ( @@ -26,20 +38,40 @@ export default function Allocations() {
- +
Selected Builders
- {builders.map((builder, index) => ( - - ))} + {Object.entries(allocations).map(([key, currentAllocation]) => { + const index = Number(key) + const builderInfo = getBuilder(index) + const builder: BuilderAllocationProps = { + ...builderInfo, + index, + currentAllocation, + } + return + })} +
+
+
+ + +
+ +
diff --git a/src/app/collective-rewards/page.tsx b/src/app/collective-rewards/page.tsx index eccdbd8f..f0c94420 100644 --- a/src/app/collective-rewards/page.tsx +++ b/src/app/collective-rewards/page.tsx @@ -4,6 +4,7 @@ import { BuildersLeaderBoard } from '@/app/collective-rewards/leaderboard' import { Metrics } from '@/app/collective-rewards/metrics' import { WhitelistContextProviderWithBuilders, WhitelistSection } from '@/app/collective-rewards/whitelist' import { MainContainer } from '@/components/MainContainer/MainContainer' +import { AllocationsContextProvider } from './allocations' export default function BuildersIncentiveMarket() { return ( diff --git a/src/app/collective-rewards/types.ts b/src/app/collective-rewards/types.ts index 24d879cd..57e97ffa 100644 --- a/src/app/collective-rewards/types.ts +++ b/src/app/collective-rewards/types.ts @@ -11,6 +11,7 @@ export const builderStatusOptions = [ BuilderStatusProposalCreatedMVP, ] as const +// TODO: type BuilderStatus should include 'Paused' & 'Deactivated' export type BuilderStatus = (typeof builderStatusOptions)[number] export type BuilderStatusShown = Exclude @@ -19,6 +20,9 @@ export type BuilderInfo = { status: BuilderStatus proposals: CreateBuilderProposalEventLog[] gauge: Address + kickback: number + builderName: string + joiningDate: string } export type ProposalsToState = Record diff --git a/src/app/collective-rewards/user/hooks/useGetBuilders.ts b/src/app/collective-rewards/user/hooks/useGetBuilders.ts index ae1353c1..f29b4cb5 100644 --- a/src/app/collective-rewards/user/hooks/useGetBuilders.ts +++ b/src/app/collective-rewards/user/hooks/useGetBuilders.ts @@ -51,162 +51,181 @@ const getCombinedBuilderStatus = (builderState: BuilderStateStruct): BuilderStat return EXCLUDED_BUILDER_STATUS } -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('active') - - // get the builders for each gauge - const gaugeToBuilderCalls = gauges?.map( - gauge => - ({ - address: BackersManagerAddress, - abi: BuilderRegistryAbi, - functionName: 'gaugeToBuilder', - args: [gauge], - }) as const, - ) - const { - data: buildersResult, - isLoading: buildersLoading, - error: buildersError, - } = useReadContracts({ - contracts: gaugeToBuilderCalls, - query: { - refetchInterval: AVERAGE_BLOCKTIME, - }, - }) - const builders = buildersResult?.map(builder => builder.result) as Address[] - - const builderToGauge = builders?.reduce( - (acc, builder, index) => { - acc[builder] = gauges![index] - return acc - }, - {} as Record, - ) - - // 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(({ result }) => result as BuilderStateStruct) - - const builderStatusMap = builders?.reduce((acc, builder, index) => { - const builderState = (builderStates?.[index] ?? []) as BuilderStateStruct - const status = getCombinedBuilderStatus(builderState) - if (status !== EXCLUDED_BUILDER_STATUS) { - acc[builder] = status as BuilderStatus - } - - return acc - }, {}) - - const { - data: buildersProposalsMap, - isLoading: builderProposalsMapLoading, - error: builderProposalsMapError, - } = useFetchCreateBuilderProposals() - - const data = useMemo(() => { - return Object.entries(buildersProposalsMap ?? {}).map(([builder, proposals]) => ({ - address: getAddress(builder), - status: - builderStatusMap && builder in builderStatusMap - ? builderStatusMap[builder as Address] // V2 - : BuilderStatusProposalCreatedMVP, // MVP - proposals: Object.values(proposals), - gauge: builderToGauge?.[builder as Address], - })) - }, [builderStatusMap, buildersProposalsMap, builderToGauge]) - - const isLoading = builderProposalsMapLoading || builderStatesLoading || buildersLoading || gaugesLoading - const error = builderProposalsMapError ?? builderStatesError ?? buildersError ?? gaugesError - - return { - data, - isLoading, - error, - } -} - -// export const useGetActiveBuilders = (): BuildersLoader => { -// const { data, isLoading, error } = useGetBuilders() +// TODO: UNCOMEENT +// 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('active') + +// // get the builders for each gauge +// const gaugeToBuilderCalls = gauges?.map( +// gauge => +// ({ +// address: BackersManagerAddress, +// abi: BuilderRegistryAbi, +// functionName: 'gaugeToBuilder', +// args: [gauge], +// }) as const, +// ) +// const { +// data: buildersResult, +// isLoading: buildersLoading, +// error: buildersError, +// } = useReadContracts({ +// contracts: gaugeToBuilderCalls, +// query: { +// refetchInterval: AVERAGE_BLOCKTIME, +// }, +// }) +// const builders = buildersResult?.map(builder => builder.result) as Address[] + +// const builderToGauge = builders?.reduce( +// (acc, builder, index) => { +// acc[builder] = gauges![index] +// return acc +// }, +// {} as Record, +// ) + +// // 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(({ result }) => result as BuilderStateStruct) + +// const builderStatusMap = builders?.reduce((acc, builder, index) => { +// const builderState = (builderStates?.[index] ?? []) as BuilderStateStruct +// const status = getCombinedBuilderStatus(builderState) +// if (status !== EXCLUDED_BUILDER_STATUS) { +// acc[builder] = status as BuilderStatus +// } + +// return acc +// }, {}) + +// const { +// data: buildersProposalsMap, +// isLoading: builderProposalsMapLoading, +// error: builderProposalsMapError, +// } = useFetchCreateBuilderProposals() + +// const data = useMemo(() => { +// // we need to add the kickback and the joining date +// TODO: return Object.entries(buildersProposalsMap ?? {}).map(([builder, proposals]) => ({ +// address: getAddress(builder), +// status: +// builderStatusMap && builder in builderStatusMap +// ? builderStatusMap[builder as Address] // V2 +// : BuilderStatusProposalCreatedMVP, // MVP +// proposals: Object.values(proposals), +// gauge: builderToGauge?.[builder as Address], +// })) +// }, [builderStatusMap, buildersProposalsMap, builderToGauge]) + +// const isLoading = builderProposalsMapLoading || builderStatesLoading || buildersLoading || gaugesLoading +// const error = builderProposalsMapError ?? builderStatesError ?? buildersError ?? gaugesError // return { -// data: data.filter(builder => builder.status === BuilderStatusActive), +// data, // isLoading, // error, // } // } -export const useGetActiveBuilders = (): BuildersLoader => { - const builders: BuilderInfo[] = [ - { - address: '0x1234567890abcdef1234567890abcdef12345678', - status: 'Active', - proposals: [], - gauge: '0x1234567890abcdef1234567890abcdef12345678', - }, - { - address: '0x1234567890abcdef1234567890abcdef12345679', - status: 'Active', - proposals: [], - gauge: '0x1234567890abcdef1234567890abcdef12345679', - }, - { - address: '0x1234567890abcdef1234567890abcdef1234567a', - status: 'Active', - proposals: [], - gauge: '0x1234567890abcdef1234567890abcdef1234567a', - }, - { - address: '0x1234567890abcdef1234567890abcdef1234567b', - status: 'Active', - proposals: [], - gauge: '0x1234567890abcdef1234567890abcdef1234567b', - }, - { - address: '0x1234567890abcdef1234567890abcdef1234567c', - status: 'Active', - proposals: [], - gauge: '0x1234567890abcdef1234567890abcdef1234567c', - }, - { - address: '0x1234567890abcdef1234567890abcdef1234567d', - status: 'Active', - proposals: [], - gauge: '0x1234567890abcdef1234567890abcdef1234567d', - }, - { - address: '0x1234567890abcdef1234567890abcdef1234567e', - status: 'Active', - proposals: [], - gauge: '0x1234567890abcdef1234567890abcdef1234567e', - }, - { - address: '0x1234567890abcdef1234567890abcdef1234567f', - status: 'Active', - proposals: [], - gauge: '0x1234567890abcdef1234567890abcdef1234567', - }, - ] + +// TODO: REMOVE +const builders: BuilderInfo[] = [ + { + address: '0x1234567890abcdef1234567890abcdef12345678', + status: 'Active', + proposals: [], + gauge: '0x1234567890abcdef1234567890abcdef12345678', + kickback: 0.1, + builderName: 'Builder One', + joiningDate: '2023-01-01', + }, + { + address: '0x1234567890abcdef1234567890abcdef12345679', + status: 'Active', + proposals: [], + gauge: '0x1234567890abcdef1234567890abcdef12345679', + kickback: 0.2, + builderName: 'Builder Two', + joiningDate: '2023-02-01', + }, + { + address: '0x1234567890abcdef1234567890abcdef1234567a', + status: 'Active', + proposals: [], + gauge: '0x1234567890abcdef1234567890abcdef1234567a', + kickback: 0.3, + builderName: 'Builder Three', + joiningDate: '2023-03-01', + }, + { + address: '0x1234567890abcdef1234567890abcdef1234567b', + status: 'Active', + proposals: [], + gauge: '0x1234567890abcdef1234567890abcdef1234567b', + kickback: 0.4, + builderName: 'Builder Four', + joiningDate: '2023-04-01', + }, + { + address: '0x1234567890abcdef1234567890abcdef1234567c', + status: 'Active', + proposals: [], + gauge: '0x1234567890abcdef1234567890abcdef1234567c', + kickback: 0.5, + builderName: 'Builder Five', + joiningDate: '2023-05-01', + }, + { + address: '0x1234567890abcdef1234567890abcdef1234567d', + status: 'Active', + proposals: [], + gauge: '0x1234567890abcdef1234567890abcdef1234567d', + kickback: 0.6, + builderName: 'Builder Six', + joiningDate: '2023-06-01', + }, + { + address: '0x1234567890abcdef1234567890abcdef1234567e', + status: 'Active', + proposals: [], + gauge: '0x1234567890abcdef1234567890abcdef1234567e', + kickback: 0.7, + builderName: 'Builder Seven', + joiningDate: '2023-07-01', + }, + { + address: '0x1234567890abcdef1234567890abcdef1234567f', + status: 'Active', + proposals: [], + gauge: '0x1234567890abcdef1234567890abcdef1234567f', + kickback: 0.8, + builderName: 'Builder Eight', + joiningDate: '2023-08-01', + }, +] + +export const useGetBuilders = (): BuildersLoader => { return { data: builders, isLoading: false, diff --git a/src/app/providers/ContextProviders.tsx b/src/app/providers/ContextProviders.tsx index b0868c88..64a51876 100644 --- a/src/app/providers/ContextProviders.tsx +++ b/src/app/providers/ContextProviders.tsx @@ -18,9 +18,7 @@ export const ContextProviders = ({ children }: Props) => { - - {children} {} - + {children}