Skip to content

Commit

Permalink
feat: Add view for SAP redeem
Browse files Browse the repository at this point in the history
  • Loading branch information
usame-algan committed Oct 25, 2024
1 parent 8d175c8 commit 4c9bc09
Show file tree
Hide file tree
Showing 10 changed files with 216 additions and 4 deletions.
9 changes: 9 additions & 0 deletions pages/claim-sap.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { NextPage } from 'next'

import ClaimSAPOverview from '@/components/ClaimSAP'

const ClaimSAPPage: NextPage = () => {
return <ClaimSAPOverview />
}

export default ClaimSAPPage
5 changes: 3 additions & 2 deletions src/components/Claim/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
TextField,
CircularProgress,
InputAdornment,
Link,
} from '@mui/material'
import { useState, type ReactElement, ChangeEvent } from 'react'

Expand Down Expand Up @@ -191,9 +192,9 @@ const ClaimOverview = (): ReactElement => {
<SvgIcon component={StarIcon} inheritViewBox fontSize="inherit" />
<Box>
<Typography variant="subtitle1" fontWeight={700}>
Claim your tokens as rewards!
Token claiming for the Safe Activity Rewards Program live!
</Typography>
<Typography variant="body2">You get more tokens if you are active in activity program.</Typography>
<Link href={AppRoutes.claimSap}>Claim now</Link>
</Box>
</Stack>
</Paper>
Expand Down
108 changes: 108 additions & 0 deletions src/components/ClaimSAP/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { Grid, Typography, Button, Paper, Box, Stack } from '@mui/material'
import { type ReactElement } from 'react'

import PaperContainer from '../PaperContainer'

import { useSafeTokenAllocation } from '@/hooks/useSafeTokenAllocation'
import { useTaggedAllocations } from '@/hooks/useTaggedAllocations'
import { getVestingTypes } from '@/utils/vesting'
import { formatEther } from 'ethers/lib/utils'
import { createSAPClaimTxs } from '@/utils/claim'
import { useSafeAppsSDK } from '@safe-global/safe-apps-react-sdk'

import css from './styles.module.css'
import { Odometer } from '@/components/Odometer'
import { formatDate } from '@/utils/date'

const getDecimalLength = (amount: string) => {
const length = Number(amount).toFixed(2).length

if (length > 10) {
return 0
}

if (length > 9) {
return 1
}

return 2
}

const ClaimSAPOverview = (): ReactElement => {
const { sdk } = useSafeAppsSDK()

// Allocation, vesting and voting power
const { data: allocation } = useSafeTokenAllocation()
const { sapBoosted, sapUnboosted, totalSAP } = useTaggedAllocations()
const { sapBoostedVesting } = getVestingTypes(allocation?.vestingData ?? [])

const startDate = sapBoostedVesting && new Date(sapBoostedVesting.startDate * 1000)
const startDateFormatted = startDate && formatDate(startDate, true)

const decimals = getDecimalLength(totalSAP.inVesting)

const onRedeem = async () => {
const txs = createSAPClaimTxs({
vestingData: allocation?.vestingData ?? [],
sapBoostedClaimable: sapBoosted.inVesting,
sapUnboostedClaimable: sapUnboosted.inVesting,
})

try {
await sdk.txs.send({ txs })
} catch (error) {
console.error(error)
}
}

return (
<Grid container spacing={3} direction="row">
<Grid item xs={12} mt={4} mb={1} className={css.pageTitle}>
<Typography variant="h2">Claim SAFE tokens from the Safe Activity Rewards program</Typography>
</Grid>

<Grid item xs={12} lg={8}>
<PaperContainer>
<Grid container spacing={3} direction="row" justifyContent="space-evenly">
<Grid item container xs={12}>
<Paper sx={{ p: 3, backgroundColor: 'background.default', position: 'relative' }}>
<Typography fontWeight="700">Your total allocation</Typography>
<Grid item display="flex" alignItems="center">
<Typography
variant="h3"
variantMapping={{
h3: 'span',
}}
className={css.amountDisplay}
>
<Odometer value={Number(formatEther(totalSAP.allocation))} decimals={decimals} /> SAFE
</Typography>
</Grid>
</Paper>
</Grid>
<Grid item pt={2} xs={12}>
<Stack spacing={3}>
<Box>
<Typography variant="h6" mt={2} fontWeight={700}>
Here are the next steps:
</Typography>
<Typography color="text.secondary" variant="body1">
Your allocation will be available to claim at {startDateFormatted}
</Typography>
<Typography color="text.secondary" variant="body1" mb={2}>
You will need to redeem the airdrop before then.
</Typography>
<Button variant="contained" onClick={onRedeem}>
Redeem
</Button>
</Box>
</Stack>
</Grid>
</Grid>
</PaperContainer>
</Grid>
</Grid>
)
}

export default ClaimSAPOverview
14 changes: 14 additions & 0 deletions src/components/ClaimSAP/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.maxButton {
border: 0;
background: none;
color: var(--mui-palette-primary-main);
padding: 0;
font-size: 16px;
font-weight: bold;
cursor: pointer;
}

.input :global .MuiInputBase-input {
padding: 12px 14px 12px 0;
font-weight: 700;
}
8 changes: 7 additions & 1 deletion src/components/PageLayout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@ import { AppRoutes } from '@/config/routes'
import { useRouter } from 'next/router'
import { NAVIGATION_EVENTS } from '@/analytics/navigation'

const RoutesWithNavigation = [AppRoutes.index, AppRoutes.points, AppRoutes.activity, AppRoutes.governance]
const RoutesWithNavigation = [
AppRoutes.index,
AppRoutes.points,
AppRoutes.activity,
AppRoutes.governance,
AppRoutes.claimSap,
]

export const PageLayout = ({
children,
Expand Down
2 changes: 2 additions & 0 deletions src/config/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ export const AIRDROP_TAGS = {
SEP5: 'user_v2',
ECOSYSTEM: 'ecosystem',
INVESTOR: 'investor',
SAP_BOOSTED: 'sap_boosted',
SAP_UNBOOSTED: 'sap_unboosted',
} as const

// Delegation
Expand Down
1 change: 1 addition & 0 deletions src/config/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const AppRoutes = {
governance: '/governance',
delegate: '/delegate',
claim: '/claim',
claimSap: '/claim-sap',
activity: '/activity',
activityProgram: '/activity-program',
}
35 changes: 34 additions & 1 deletion src/hooks/useTaggedAllocations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,38 @@ export const useTaggedAllocations = (): {
claimable: string
inVesting: string
}
sapBoosted: {
claimable: string
inVesting: string
}
sapUnboosted: {
claimable: string
inVesting: string
}
total: {
claimable: string
inVesting: string
allocation: string
}
totalSAP: {
claimable: string
inVesting: string
allocation: string
}
} => {
const { data: allocation } = useSafeTokenAllocation()

// Get vesting types
const { userVesting, sep5Vesting, ecosystemVesting, investorVesting } = getVestingTypes(allocation?.vestingData || [])
const { userVesting, sep5Vesting, ecosystemVesting, investorVesting, sapBoostedVesting, sapUnboostedVesting } =
getVestingTypes(allocation?.vestingData || [])

// Calculate claimable vs. vested amounts for each vesting type
const [sep5Claimable, sep5InVesting] = useAmounts(sep5Vesting)
const [userClaimable, userInVesting] = useAmounts(userVesting)
const [ecosystemClaimable, ecosystemInVesting] = useAmounts(ecosystemVesting)
const [investorClaimable, investorInVesting] = useAmounts(investorVesting)
const [sapBoostedClaimable, sapBoostedInVesting] = useAmounts(sapBoostedVesting)
const [sapUnboostedClaimable, sapUnboostedInVesting] = useAmounts(sapUnboostedVesting)

// Calculate total of claimable vs. vested amounts
const totalAmountClaimable = getTotal(sep5Claimable, userClaimable, ecosystemClaimable, investorClaimable)
Expand All @@ -59,6 +75,10 @@ export const useTaggedAllocations = (): {
investorVesting?.amount || '0',
)

const totalSAPClaimable = getTotal(sapBoostedClaimable, sapUnboostedClaimable)
const totalSAPInVesting = getTotal(sapBoostedInVesting, sapUnboostedInVesting)
const totalSAP = getTotal(sapBoostedVesting?.amount || '0', sapUnboostedVesting?.amount || '0')

return {
sep5: {
claimable: sep5Claimable,
Expand All @@ -76,10 +96,23 @@ export const useTaggedAllocations = (): {
claimable: investorClaimable,
inVesting: investorInVesting,
},
sapBoosted: {
claimable: sapBoostedClaimable,
inVesting: sapBoostedInVesting,
},
sapUnboosted: {
claimable: sapUnboostedClaimable,
inVesting: sapUnboostedInVesting,
},
total: {
claimable: totalAmountClaimable,
inVesting: totalAmountInVesting,
allocation: totalAllocation,
},
totalSAP: {
claimable: totalSAPClaimable,
inVesting: totalSAPInVesting,
allocation: totalSAP,
},
}
}
34 changes: 34 additions & 0 deletions src/utils/claim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,37 @@ export const createClaimTxs = ({

return txs
}

export const createSAPClaimTxs = ({
vestingData,
sapBoostedClaimable,
sapUnboostedClaimable,
}: {
vestingData: Vesting[]
sapBoostedClaimable: string
sapUnboostedClaimable: string
}): BaseTransaction[] => {
const txs: BaseTransaction[] = []

const { sapBoostedVesting, sapUnboostedVesting } = getVestingTypes(vestingData)

if (sapBoostedVesting && BigNumber.from(sapBoostedClaimable).gt(0)) {
const redeemTx = createRedeemTx({
vestingClaim: sapBoostedVesting,
airdropAddress: sapBoostedVesting.contract,
})

txs.push(redeemTx)
}

if (sapUnboostedVesting && BigNumber.from(sapUnboostedClaimable).gt(0)) {
const redeemTx = createRedeemTx({
vestingClaim: sapUnboostedVesting,
airdropAddress: sapUnboostedVesting.contract,
})

txs.push(redeemTx)
}

return txs
}
4 changes: 4 additions & 0 deletions src/utils/vesting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,15 @@ export const getVestingTypes = (vestingData: Vesting[]) => {
const sep5Vesting = vestingData?.find((vesting) => vesting.tag === AIRDROP_TAGS.SEP5) ?? null
const ecosystemVesting = vestingData?.find((vesting) => vesting.tag === AIRDROP_TAGS.ECOSYSTEM) ?? null
const investorVesting = vestingData?.find((vesting) => vesting.tag === AIRDROP_TAGS.INVESTOR) ?? null
const sapBoostedVesting = vestingData?.find((vesting) => vesting.tag === AIRDROP_TAGS.SAP_BOOSTED) ?? null
const sapUnboostedVesting = vestingData?.find((vesting) => vesting.tag === AIRDROP_TAGS.SAP_UNBOOSTED) ?? null

return {
userVesting,
sep5Vesting,
ecosystemVesting,
investorVesting,
sapBoostedVesting,
sapUnboostedVesting,
}
}

0 comments on commit 4c9bc09

Please sign in to comment.