Skip to content

Commit

Permalink
Gsantini/claim all rewards (#473)
Browse files Browse the repository at this point in the history
* feat: claim all rewards

* feat: claim all rewards main structure except progress

* feat: claim all rewards do transaction

* feat: claim all reward item component

* fix: es-lint error for claim-all-rewards feature

* fix: information message for unclaimed farms

* fix: monitering for stakingInfos

* fix: component pattern for review panel

* fix: transaction rejected bug

* feat: the main structure of ClaimAllRewardPanel and cooldown feature

* feat: showing progress for each claim
  • Loading branch information
gsantini-ube authored Dec 29, 2021
1 parent 7fe5423 commit ea118b4
Show file tree
Hide file tree
Showing 9 changed files with 194 additions and 14 deletions.
4 changes: 3 additions & 1 deletion public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -226,5 +226,7 @@
"youWillRemove": "You will remove",
"unstakedLPTokens": "You have unstaked {{poolName}} LP tokens",
"stakeIntoFarmingPool": "Farm into the {{poolName}} farming pool to an additional rewards on your LP tokens",
"farmUBE": "Farm UBE"
"farmUBE": "Farm UBE",
"youHaveUnclaimedRewards": "You have {{count}} farms with unclaimed rewards",
"claimAllRewards": "Claim all rewards"
}
3 changes: 3 additions & 0 deletions src/components/Row/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,8 @@ export const RowFixed = styled(Row)<{ gap?: string; justify?: string }>`
export const RowStart = styled(AutoRow)`
align-items: start;
`
export const RowCenter = styled(AutoRow)`
align-items: center;
`

export default Row
120 changes: 120 additions & 0 deletions src/components/earn/ClaimAllRewardPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { AutoColumn, TopSection } from 'components/Column'
import Loader from 'components/Loader'
import { RowCenter, RowStart } from 'components/Row'
import { useDoTransaction } from 'components/swap/routing'
import { StakingRewards } from 'generated'
import { useStakingContracts } from 'hooks/useContract'
import { FarmSummary } from 'pages/Earn/useFarmRegistry'
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react'
import { AlertCircle } from 'react-feather'
import { Trans, useTranslation } from 'react-i18next'
import { useFilteredStakingInfo } from 'state/stake/hooks'
import styled, { ThemeContext } from 'styled-components'
import { StyledLink, TYPE } from 'theme'

import { CardSection, TopBorderCard } from './styled'

const CooldownLimit = 24 * 60 * 60 * 1000
const CoolDownKey = 'LastClaimedTime'

export const Space = styled.span`
width: 10px;
`

export interface ClaimAllRewardsProps {
stakedFarms: FarmSummary[]
}

export default function ClaimAllRewardPanel({ stakedFarms }: ClaimAllRewardsProps) {
const theme = useContext(ThemeContext)
const { t } = useTranslation()

const stakingAddresses = useMemo(() => {
return stakedFarms.map((farm) => farm.lpAddress)
}, [stakedFarms])

const doTransaction = useDoTransaction()
const stakingInfos = useFilteredStakingInfo(stakingAddresses)
const contracts = useStakingContracts(stakingInfos)

const memoizedContracts = useRef<StakingRewards[] | null>(null)
const [pending, setPending] = useState<boolean>(false)
const [pendingIndex, setPendingIndex] = useState<number>(0)
const [lastClaimedTime, setLastClaimedTime] = useState<number>(0)

useEffect(() => {
const claimedTime = localStorage.getItem(CoolDownKey)
if (claimedTime) setLastClaimedTime(Number(claimedTime))
}, [])

const startTransactions = () => {
setPending(true)
setPendingIndex(1)
claimRewards()
}

const finishTransactions = () => {
setPending(false)
const currentTime = Date.now()
setLastClaimedTime(currentTime)
localStorage.setItem(CoolDownKey, currentTime.toString())
}

const claimRewards = async () => {
if (!memoizedContracts.current) return
let currentIndex = 1
for (const contract of memoizedContracts.current) {
await doTransaction(contract, 'getReward', {
args: [],
summary: `${t('ClaimAccumulatedUbeRewards')}`,
})
.catch(console.error)
.finally(() => {
console.log(currentIndex, ' index of transaction is completed')
})
setPendingIndex(++currentIndex)
}
finishTransactions()
}

const onClaimRewards = () => {
memoizedContracts.current = contracts
startTransactions()
}

const cooldownCheck = useMemo(() => {
const deltaTime = Date.now() - lastClaimedTime
return deltaTime >= CooldownLimit
}, [lastClaimedTime])

if (!cooldownCheck || !contracts || contracts?.length == 0) return <></>

return (
<TopSection gap="md">
<TopBorderCard>
<CardSection>
<RowStart>
<div style={{ paddingRight: 16 }}>
<AlertCircle color={theme.green1} size={36} />
</div>
<AutoColumn gap="md">
<RowCenter>
<TYPE.black fontWeight={600}>
<Trans i18nKey="youHaveUnclaimedRewards" values={{ count: contracts?.length }} />
</TYPE.black>
</RowCenter>
{pending && (
<RowCenter>
<TYPE.black fontWeight={600}>{`${pendingIndex} / ${memoizedContracts?.current?.length}`}</TYPE.black>
<Space />
<Loader size="15px" />
</RowCenter>
)}
{!pending && <StyledLink onClick={onClaimRewards}>{t('claimAllRewards')}</StyledLink>}
</AutoColumn>
</RowStart>
</CardSection>
</TopBorderCard>
</TopSection>
)
}
7 changes: 7 additions & 0 deletions src/components/earn/styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,10 @@ export const Break = styled.div`
background-color: rgba(255, 255, 255, 0.2);
height: 1px;
`
export const TopBorderCard = styled(AutoColumn)<{ disabled?: boolean }>`
background-color: ${(props) => props.theme.bg1};
border-top: 3px solid ${(props) => props.theme.primary1};
width: 100%;
position: relative;
overflow: hidden;
`
28 changes: 28 additions & 0 deletions src/hooks/useContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Contract } from '@ethersproject/contracts'
import IUniswapV2PairABI from '@ubeswap/core/build/abi/IUniswapV2Pair.json'
import { ReleaseUbe } from 'generated/ReleaseUbe'
import { useMemo } from 'react'
import { StakingInfo } from 'state/stake/hooks'

import ENS_PUBLIC_RESOLVER_ABI from '../constants/abis/ens-public-resolver.json'
import ERC20_ABI, { ERC20_BYTES32_ABI } from '../constants/abis/erc20'
Expand Down Expand Up @@ -31,6 +32,23 @@ function useContract(address: string | undefined, ABI: any, withSignerIfPossible
}, [address, ABI, library, withSignerIfPossible, account])
}

function useContracts(
addresses: (string | undefined)[] | undefined,
ABI: any,
withSignerIfPossible = true
): (Contract | null)[] | null {
const { address: account } = useContractKit()
const library = useProvider()

return useMemo(() => {
if (!addresses || !ABI || !library) return null
return addresses.map((address) => {
if (!address) return null
return getContract(address, ABI, library, withSignerIfPossible && account ? account : undefined)
})
}, [addresses, ABI, library, withSignerIfPossible, account])
}

export function useTokenContract(tokenAddress?: string, withSignerIfPossible?: boolean): Erc20 | null {
return useContract(tokenAddress, ERC20_ABI, withSignerIfPossible) as Erc20 | null
}
Expand Down Expand Up @@ -63,6 +81,16 @@ export function useStakingContract(stakingAddress?: string, withSignerIfPossible
return useContract(stakingAddress, STAKING_REWARDS_ABI, withSignerIfPossible) as StakingRewards | null
}

export function useStakingContracts(
stakingInfos?: readonly StakingInfo[],
withSignerIfPossible?: boolean
): StakingRewards[] | null {
const rewardAddresses: (string | undefined)[] | undefined = stakingInfos?.map(
(stakingInfo) => stakingInfo.stakingRewardAddress
)
return useContracts(rewardAddresses, STAKING_REWARDS_ABI, withSignerIfPossible) as StakingRewards[] | null
}

export function useVotableStakingContract(
stakingAddress?: string,
withSignerIfPossible?: boolean
Expand Down
2 changes: 2 additions & 0 deletions src/pages/Earn/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ErrorBoundary } from '@sentry/react'
import { Token } from '@ubeswap/sdk'
import TokenSelect from 'components/CurrencyInputPanel/TokenSelect'
import ClaimAllRewardPanel from 'components/earn/ClaimAllRewardPanel'
import Loader from 'components/Loader'
import React, { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
Expand Down Expand Up @@ -62,6 +63,7 @@ export default function Earn() {

return (
<PageWrapper>
<ClaimAllRewardPanel stakedFarms={stakedFarms} />
<LiquidityWarning />
{stakedFarms.length === 0 && (
<TopSection gap="md">
Expand Down
16 changes: 4 additions & 12 deletions src/pages/Pool/LiquidityWarning.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,17 @@ import { useContractKit } from '@celo-tools/use-contractkit'
import React, { useContext, useMemo } from 'react'
import { AlertTriangle } from 'react-feather'
import { Trans, useTranslation } from 'react-i18next'
import styled, { ThemeContext } from 'styled-components'
import { ThemeContext } from 'styled-components'

import { AutoColumn, TopSection } from '../../components/Column'
import { CardSection } from '../../components/earn/styled'
import { CardSection, TopBorderCard } from '../../components/earn/styled'
import { RowBetween, RowStart } from '../../components/Row'
import { usePairs } from '../../data/Reserves'
import { toV2LiquidityToken, useTrackedTokenPairs } from '../../state/user/hooks'
import { useTokenBalancesWithLoadingIndicator } from '../../state/wallet/hooks'
import { StyledInternalLink, TYPE } from '../../theme'
import { useUniqueBestFarms, WarningInfo } from '../Earn/useFarmRegistry'

export const LiquidityWarningCard = styled(AutoColumn)<{ disabled?: boolean }>`
background-color: ${(props) => props.theme.bg1};
border-top: 3px solid ${(props) => props.theme.primary1};
width: 100%;
position: relative;
overflow: hidden;
`

export default function LiquidityWarning() {
const theme = useContext(ThemeContext)
const { address: account } = useContractKit()
Expand Down Expand Up @@ -67,7 +59,7 @@ export default function LiquidityWarning() {
return (
<TopSection gap="md">
{warnings.map((warning) => (
<LiquidityWarningCard key={warning.link}>
<TopBorderCard key={warning.link}>
<CardSection>
<RowStart>
<div style={{ paddingRight: 16 }}>
Expand All @@ -88,7 +80,7 @@ export default function LiquidityWarning() {
</AutoColumn>
</RowStart>
</CardSection>
</LiquidityWarningCard>
</TopBorderCard>
))}
</TopSection>
)
Expand Down
26 changes: 26 additions & 0 deletions src/state/stake/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,32 @@ export function useTotalUbeEarned(): TokenAmount | undefined {
}, [stakingInfos, ube])
}

export function useFilteredStakingInfo(stakingAddresses: string[]): readonly StakingInfo[] | undefined {
const { network } = useContractKit()
const { chainId } = network
const ube = chainId ? UBE[chainId as unknown as UbeswapChainId] : undefined
const stakingInfos = useStakingInfo()
return useMemo(() => {
if (!ube) return
return stakingInfos.filter(
(stakingInfo) => stakingInfo.stakingToken.address && stakingAddresses.includes(stakingInfo.stakingToken.address)
)
}, [stakingInfos, ube, stakingAddresses])
}

export function useFarmRewardsInfo(stakingAddresses: string[]): readonly StakingInfo[] | undefined {
const { network } = useContractKit()
const { chainId } = network
const ube = chainId ? UBE[chainId as unknown as UbeswapChainId] : undefined
const stakingInfos = useStakingInfo()
return useMemo(() => {
if (!ube) return
return stakingInfos.filter(
(stakingInfo) => stakingInfo.stakingToken.address && stakingAddresses.includes(stakingInfo.stakingToken.address)
)
}, [stakingInfos, ube, stakingAddresses])
}

// based on typed value
export function useDerivedStakeInfo(
typedValue: string,
Expand Down
2 changes: 1 addition & 1 deletion src/theme/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export const StyledInternalLink = styled(Link)`
}
`

const StyledLink = styled.a`
export const StyledLink = styled.a`
text-decoration: none;
cursor: pointer;
color: ${({ theme }) => theme.primary1};
Expand Down

0 comments on commit ea118b4

Please sign in to comment.