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

feat(cr): claimable rewards #349

Merged
merged 7 commits into from
Nov 12, 2024
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
117 changes: 117 additions & 0 deletions src/app/collective-rewards/rewards/ClaimableRewards.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import {
formatMetrics,
MetricsCard,
MetricsCardTitle,
TokenMetricsCardRow,
useClaimBuilderRewards,
useClaimStateReporting,
useGetBuilderRewards,
} from '@/app/collective-rewards/rewards'
import { useHandleErrors } from '@/app/collective-rewards/utils'
import { formatBalanceToHuman } from '@/app/user/Balances/balanceUtils'
import { Button, ButtonProps } from '@/components/Button'
import { withSpinner } from '@/components/LoadingSpinner/withLoadingSpinner'
import { Popover } from '@/components/Popover'
import { usePricesContext } from '@/shared/context/PricesContext'
import { FC } from 'react'
import { Address } from 'viem'

const ClaimYourRewardsSvg = () => (
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M13.75 12.1667H13.7583M2.5 4.66667V16.3333C2.5 17.2538 3.24619 18 4.16667 18H15.8333C16.7538 18 17.5 17.2538 17.5 16.3333V8C17.5 7.07953 16.7538 6.33333 15.8333 6.33333L4.16667 6.33333C3.24619 6.33333 2.5 5.58714 2.5 4.66667ZM2.5 4.66667C2.5 3.74619 3.24619 3 4.16667 3H14.1667M14.1667 12.1667C14.1667 12.3968 13.9801 12.5833 13.75 12.5833C13.5199 12.5833 13.3333 12.3968 13.3333 12.1667C13.3333 11.9365 13.5199 11.75 13.75 11.75C13.9801 11.75 14.1667 11.9365 14.1667 12.1667Z"
stroke="white"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)

const ClaimYourRewardsButton: FC<Required<Pick<ButtonProps, 'onClick' | 'disabled'>>> = buttonProps => (
<div className="self-start justify-self-end pt-[10px]">
<Popover
content={
<div className="text-[12px] font-bold mb-1">
<p data-testid="builderAddressTooltip">Claim your rewards</p>
</div>
}
size="small"
position="top"
trigger="hover"
>
<Button {...buttonProps} variant="borderless" className="px-1 py-1">
<ClaimYourRewardsSvg />
</Button>
</Popover>
</div>
)

type Token = {
symbol: string
address: Address
}

type RewardsTokenMetricsProps = {
builder: Address
gauge: Address
token: Token
currency?: string
}

const RewardsTokenMetrics: FC<RewardsTokenMetricsProps> = ({
builder,
gauge,
token: { address, symbol },
currency = 'USD',
}) => {
const { prices } = usePricesContext()

const {
data: rewards,
isLoading: isLoadingRewards,
error: rewardsError,
} = useGetBuilderRewards(address, gauge)
useHandleErrors({ error: rewardsError, title: 'Error loading rewards' })

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

const { amount, fiatAmount } = formatMetrics(
Number(formatBalanceToHuman(rewards ?? 0n)),
tokenPrice,
symbol,
currency,
)

const { isClaimFunctionReady, claimRewards, ...claimTx } = useClaimBuilderRewards(builder)

useClaimStateReporting({ ...claimTx, error: rewardsError ?? claimTx.error })

return withSpinner(TokenMetricsCardRow)({
amount,
fiatAmount,
isLoading: isLoadingRewards,
children: (
<ClaimYourRewardsButton onClick={() => claimRewards(address)} disabled={!isClaimFunctionReady} />
),
})
}

type ClaimableRewardsProps = {
builder: Address
gauge: Address
currency?: string
data: {
[token: string]: Token
}
}

export const ClaimableRewards: FC<ClaimableRewardsProps> = ({ data, ...rest }) => {
return (
<MetricsCard borderless>
<MetricsCardTitle title="Claimable rewards" data-testid="ClaimableRewards" />
<RewardsTokenMetrics {...rest} token={data.rif} />
<RewardsTokenMetrics {...rest} token={data.rbtc} />
</MetricsCard>
)
}
9 changes: 5 additions & 4 deletions src/app/collective-rewards/rewards/MyRewards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { Address, getAddress } from 'viem'
import {
RewardsSection,
RewardsSectionHeader,
useClaimAllRewards,
useClaimBuilderRewards,
useClaimStateReporting,
LastCycleRewards,
ClaimableRewards,
} from '@/app/collective-rewards/rewards'
import { CycleContextProvider } from '@/app/collective-rewards/metrics'
import { PricesContextProvider } from '@/shared/context/PricesContext'
Expand All @@ -16,7 +17,7 @@ import { Button } from '@/components/Button'
import { Paragraph } from '@/components/Typography'

export const Rewards: FC<{ builder: Address; gauge: Address }> = ({ builder, gauge }) => {
const { isClaimFunctionReady, claimAllRewards, ...claimTx } = useClaimAllRewards(builder)
const { isClaimFunctionReady, claimRewards, ...claimTx } = useClaimBuilderRewards(builder)

useClaimStateReporting({ ...claimTx })

Expand Down Expand Up @@ -44,7 +45,7 @@ export const Rewards: FC<{ builder: Address; gauge: Address }> = ({ builder, gau
<div className="grid grid-cols-5 gap-[16px]">
<CycleContextProvider>
<PricesContextProvider>
<div>Claimable Rewards</div>
<ClaimableRewards builder={builder} gauge={gauge} data={data} />
<LastCycleRewards gauge={gauge} data={data} />
<div>Estimated Rewards</div>
<div>All time rewards</div>
Expand All @@ -62,7 +63,7 @@ export const Rewards: FC<{ builder: Address; gauge: Address }> = ({ builder, gau
position="bottom"
className="z-[100]"
>
<Button onClick={claimAllRewards} disabled={!isClaimFunctionReady} variant="primary">
<Button onClick={() => claimRewards()} disabled={!isClaimFunctionReady} variant="primary">
Claim all
</Button>
</Popover>
Expand Down
3 changes: 2 additions & 1 deletion src/app/collective-rewards/rewards/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './useClaimAllRewards'
export * from './useClaimBuilderRewards'
export * from './useGetBuilderRewards'
export * from './useGetRewardDistributedLogs'
export * from './useGetTokenProjectedReward'
export * from './useGetNotifyRewardLogs'
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useEffect, useMemo } from 'react'
import { useAlertContext } from '@/app/providers'
import { useGetBuilderToGauge } from '@/app/collective-rewards/user'

export const useClaimAllRewards = (builder: Address) => {
export const useClaimBuilderRewards = (builder: Address) => {
const { writeContractAsync, error: executionError, data: hash, isPending } = useWriteContract()
const {
data: gauge,
Expand Down Expand Up @@ -52,18 +52,18 @@ export const useClaimAllRewards = (builder: Address) => {
}
}, [gauge, gaugeError, builder, executionError, receiptError, isFetched])

const claimAllRewards = () => {
const claimBuilderReward = (rewardToken?: Address) => {
return writeContractAsync({
abi: GaugeAbi,
address: gauge as Address,
functionName: 'claimBuilderReward',
args: [],
args: rewardToken ? [rewardToken] : [],
})
}

return {
isClaimFunctionReady,
claimAllRewards: () => isClaimFunctionReady && claimAllRewards(),
claimRewards: (rewardToken?: Address) => isClaimFunctionReady && claimBuilderReward(rewardToken),
error,
isPendingTx: isPending,
isLoadingReceipt: isLoading,
Expand All @@ -78,7 +78,7 @@ export const useClaimStateReporting = ({
isLoadingReceipt,
isSuccess,
receipt,
}: Omit<ReturnType<typeof useClaimAllRewards>, 'isClaimFunctionReady' | 'claimAllRewards'>) => {
}: Omit<ReturnType<typeof useClaimBuilderRewards>, 'isClaimFunctionReady' | 'claimRewards'>) => {
const { setMessage } = useAlertContext()

useEffect(() => {
Expand Down
22 changes: 22 additions & 0 deletions src/app/collective-rewards/rewards/hooks/useGetBuilderRewards.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { GaugeAbi } from '@/lib/abis/v2/GaugeAbi'
import { Address } from 'viem'
import { useReadContract } from 'wagmi'
import { AVERAGE_BLOCKTIME } from '@/lib/constants'

export const useGetBuilderRewards = (rewardToken: Address, gauge: Address) => {
const { data, isLoading, error } = useReadContract({
address: gauge,
abi: GaugeAbi,
functionName: 'builderRewards',
args: [rewardToken],
query: {
refetchInterval: AVERAGE_BLOCKTIME,
},
})

return {
data,
isLoading,
error,
}
}
1 change: 1 addition & 0 deletions src/app/collective-rewards/rewards/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './hooks'
export * from './utils'
export * from './MyRewards'
export * from './LastCycleRewards'
export * from './ClaimableRewards'
1 change: 1 addition & 0 deletions src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ interface Props {
startIconClasses?: string
'data-testid'?: string
}
export type ButtonProps = Props

const DEFAULT_DATA_TESTID = 'Button'

Expand Down
1 change: 0 additions & 1 deletion src/components/Popover/Popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'conte
size?: 'small' | 'medium'
hasCaret?: boolean
}

export const Popover = ({
children,
content,
Expand Down
Loading