diff --git a/tinlake-ui/containers/ClaimRewards/index.tsx b/tinlake-ui/containers/ClaimRewards/index.tsx index fdb13906c0..b1d5e5ab0c 100644 --- a/tinlake-ui/containers/ClaimRewards/index.tsx +++ b/tinlake-ui/containers/ClaimRewards/index.tsx @@ -60,8 +60,7 @@ const ClaimRewards: React.FC = ({ activeLink }: Props) => { return Loading claimable rewards... } - const unclaimed = - activeLink.claimable !== null && activeLink.claimed !== null ? activeLink.claimable.sub(activeLink.claimed) : null + const unclaimed = calcUnclaimed(activeLink) return ( <> @@ -168,3 +167,14 @@ const ClaimRewards: React.FC = ({ activeLink }: Props) => { } export default ClaimRewards + +function calcUnclaimed(link: UserRewardsLink): null | BN { + if (!link.claimable || !link.claimed) { + return null + } + const unclaimed = link.claimable.sub(link.claimed) + if (unclaimed.ltn(0)) { + return new BN(0) + } + return unclaimed +} diff --git a/tinlake-ui/containers/UserRewards/index.tsx b/tinlake-ui/containers/UserRewards/index.tsx index 3fc1681765..0e56da7eea 100644 --- a/tinlake-ui/containers/UserRewards/index.tsx +++ b/tinlake-ui/containers/UserRewards/index.tsx @@ -5,7 +5,6 @@ import { useRouter } from 'next/router' import * as React from 'react' import { useDispatch, useSelector } from 'react-redux' import styled from 'styled-components' -import Alert from '../../components/Alert' import { LoadingValue } from '../../components/LoadingValue' import NumberDisplay from '../../components/NumberDisplay' import PageTitle from '../../components/PageTitle' @@ -14,7 +13,7 @@ import { AuthState, ensureAuthed } from '../../ducks/auth' import { CentChainWalletState } from '../../ducks/centChainWallet' import { PortfolioState } from '../../ducks/portfolio' import { maybeLoadRewards, RewardsState } from '../../ducks/rewards' -import { maybeLoadUserRewards, UserRewardsLink, UserRewardsState } from '../../ducks/userRewards' +import { maybeLoadUserRewards, UserRewardsData, UserRewardsLink, UserRewardsState } from '../../ducks/userRewards' import { accountIdToCentChainAddr } from '../../services/centChain/accountIdToCentChainAddr' import { addThousandsSeparators } from '../../utils/addThousandsSeparators' import { shortAddr } from '../../utils/shortAddr' @@ -150,43 +149,54 @@ const UserRewards: React.FC = ({ tinlake }: Props) => { ))} + {debug && ( + + +

Debug:

+
    +
  • Non-zero investment since: {data?.nonZeroInvestmentSince?.toString() || 'null'}
  • +
  • + Total earned rewards:{' '} + {data ? `${toPrecision(baseToDisplay(data?.totalEarnedRewards, 18), 4)} RAD` : 'null'} +
  • +
  • + Unlinked rewards:{' '} + {data ? `${toPrecision(baseToDisplay(data?.unlinkedRewards, 18), 4)} RAD` : 'null'} +
  • + {data?.links.map((c, i) => ( +
  • + Link {i + 1} {i === data.links.length - 1 && '(Active)'} +
      +
    • Centrifuge Chain Address: {accountIdToCentChainAddr(c.centAccountID)}
    • +
    • Centrifuge Chain Account ID: {c.centAccountID}
    • +
    • Earned (from Subgraph): {toPrecision(baseToDisplay(c.earned, 18), 4)} RAD
    • +
    • + Claimable (from GCP):{' '} + {c.claimable ? `${toPrecision(baseToDisplay(c.claimable, 18), 4)} RAD` : `[loading...]`} +
    • +
    • + Claimed (from Centrifuge Chain):{' '} + {c.claimed ? `${toPrecision(baseToDisplay(c.claimed, 18), 4)} RAD` : `[loading...]`} +
    • +
    +
  • + ))} +
+
+
+ )} + {ethAddr && data?.links && data.links.length > 0 && ( Claim Your RAD Rewards - {debug && ( - -

Debug:

-
    - {data.links.map((c, i) => ( -
  • - Link {i + 1} {i === data.links.length - 1 && '(Active)'} -
      -
    • Centrifuge Chain Address: {shortAddr(accountIdToCentChainAddr(c.centAccountID))}
    • -
    • Centrifuge Chain Account ID: {shortAddr(c.centAccountID)}
    • -
    • Earned (from Subgraph): {toPrecision(baseToDisplay(c.earned, 18), 4)} RAD
    • -
    • - Claimable (from GCP):{' '} - {c.claimable ? `${toPrecision(baseToDisplay(c.claimable, 18), 4)} RAD` : `[loading...]`} -
    • -
    • - Claimed (from Centrifuge Chain):{' '} - {c.claimed ? `${toPrecision(baseToDisplay(c.claimed, 18), 4)} RAD` : `[loading...]`} -
    • -
    -
  • - ))} -
-
- )} - - {!data?.claimable && comebackDate(data?.nonZeroInvestmentSince)} + {comebackDate(data?.nonZeroInvestmentSince)}
- {data?.claimable && } + {showClaimStripe(data) && }
)} @@ -228,31 +238,69 @@ const UserRewards: React.FC = ({ tinlake }: Props) => { export default UserRewards -const days = 24 * 60 * 60 +const day = 24 * 60 * 60 +const minNonZeroDays = 61 -function comebackDate(nonZero: BN | null | undefined) { +function comebackDate(nonZero: BN | null | undefined): null | string { if (!nonZero || nonZero.isZero()) { return 'You can not yet claim your rewards, please come back after investing in a Tinlake pool and waiting for 60 days.' } const start = nonZero const startDate = new Date(start.toNumber() * 1000).toLocaleDateString() - const target = start.addn(61 * days) + const target = start.addn(minNonZeroDays * day) const targetDate = new Date(target.toNumber() * 1000).toLocaleDateString() const diff = target .sub(new BN(Date.now() / 1000)) - .divn(1 * days) + .divn(1 * day) .addn(1) - .toString() + + // if not in the future + if (diff.lten(0)) { + return null + } return ( `You cannot claim your RAD rewards yet. RAD rewards can only be claimed after a minimum investment period of 60 ` + `days. Your first eligible investment was made ${startDate}. Please come back in ${ - diff === '1' ? '1 day' : `${diff} days` + diff.eqn(1) ? '1 day' : `${diff.toString()} days` } on ${targetDate} to claim your RAD rewards.` ) } +function showClaimStripe(data: UserRewardsData | null): boolean { + if (data === null) { + return false + } + + const { links } = data + + // `false` if there are no links + if (links.length === 0) { + return false + } + + const lastLink = links[links.length - 1] + + // `true` if last link has positive total rewards. That might still imply that there are no unclaimed rewards, but the + // claim stripe handles that. + if (lastLink.earned.gtn(0)) { + return true + } + + // `true` if last link has positive claimable rewards. Claimable could be positive even if earned is zero in cases where one Centrifuge Chain account receives rewards from multiple Ethereum accounts. + if (lastLink.claimable?.gtn(0)) { + return true + } + + // `true` if last link has positive claimed rewards. Claimed could be positive even if earned is zero in cases where one Centrifuge Chain account receives rewards from multiple Ethereum accounts. + if (lastLink.claimed?.gtn(0)) { + return true + } + + return false +} + const Card = ({ children, ...rest }: React.PropsWithChildren) => ( {children} diff --git a/tinlake-ui/ducks/userRewards.ts b/tinlake-ui/ducks/userRewards.ts index f1d3799440..2cee704486 100644 --- a/tinlake-ui/ducks/userRewards.ts +++ b/tinlake-ui/ducks/userRewards.ts @@ -33,7 +33,7 @@ export interface UserRewardsState { /** * Process to earn and claim rewards: * 1. User earns rewards on Ethereum for any investments on that Ethereum account `totalEarnedRewards` - * 2. After holding a non zero investements for 60 days, those rewards become `claimable` + * 2. After holding a non zero investements for 60 days, those rewards become claimable * 3. To claim rewards, user needs to link a Cent Chain account to the Ethereum account. If there is none, any rewards * are in `unlinkedRewards`. If there is a link, rewards will be fully assigned to the (last) linked Cent Chain * account. @@ -50,14 +50,10 @@ export interface UserRewardsData { * be a timestamp (in seconds). */ nonZeroInvestmentSince: BN | null - /** - * From subgraph. Determines whether investment was long enough on Ethereum yet for rewards to be claimable. - */ - claimable: boolean /** * From subgraph. Those are rewards that have not been linked to a Cent Chain account on Ethereum. They can be linked - * at any time. If claimable is true, they will be immediately assigned to a linked Cent Chain account. If claimable - * is false, they will remain unlinked until they become claimable. + * at any time. If they are claimable, they will be immediately assigned to a linked Cent Chain account. If they are + * not claimable, they will remain unlinked until they become claimable. */ unlinkedRewards: BN /** diff --git a/tinlake-ui/services/apollo/index.ts b/tinlake-ui/services/apollo/index.ts index 089d92441c..83d21a2f85 100644 --- a/tinlake-ui/services/apollo/index.ts +++ b/tinlake-ui/services/apollo/index.ts @@ -331,7 +331,6 @@ class Apollo { centAddress rewardsAccumulated } - claimable linkableRewards totalRewards nonZeroBalanceSince @@ -346,7 +345,6 @@ class Apollo { const transformed: UserRewardsData = { nonZeroInvestmentSince: null, - claimable: false, totalEarnedRewards: new BN(0), unlinkedRewards: new BN(0), links: [], @@ -356,7 +354,6 @@ class Apollo { if (rewardBalance) { transformed.nonZeroInvestmentSince = rewardBalance.nonZeroBalanceSince && new BN(rewardBalance.nonZeroBalanceSince) - transformed.claimable = rewardBalance.claimable transformed.totalEarnedRewards = new BN(new Decimal(rewardBalance.totalRewards).toFixed(0)) transformed.unlinkedRewards = new BN(new Decimal(rewardBalance.linkableRewards).toFixed(0)) transformed.links = (rewardBalance.links as any[]).map((link: any) => ({