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

Harvest timer #147

Merged
merged 15 commits into from
Sep 27, 2024
37 changes: 9 additions & 28 deletions src/app/strategy/[strategyId]/_components/Strategy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@
ListItem,
OrderedList,
Spinner,
Stat,
StatHelpText,
StatLabel,
StatNumber,
Tab,
TabIndicator,
TabList,
Expand All @@ -32,13 +28,12 @@
import { atom, useAtomValue, useSetAtom } from 'jotai';
import mixpanel from 'mixpanel-browser';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { isMobile } from 'react-device-detect';

import Deposit from '@/components/Deposit';
import CONSTANTS from '@/constants';
import { DUMMY_BAL_ATOM } from '@/store/balance.atoms';
import { StrategyInfo, strategiesAtom } from '@/store/strategies.atoms';
import { transactionsAtom, TxHistoryAtom } from '@/store/transactions.atom';
import HarvestTime from '@/components/HarvestTime';
import {
capitalize,
getTokenInfoFromAddr,
Expand All @@ -49,11 +44,13 @@
import { StrategyParams } from '../page';
import MyNumber from '@/utils/MyNumber';
import { ExternalLinkIcon } from '@chakra-ui/icons';
import { isMobile } from 'react-device-detect';
import CONSTANTS from '@/constants';

const Strategy = ({ params }: StrategyParams) => {
const { address } = useAccount();
const strategies = useAtomValue(strategiesAtom);
const transactions = useAtomValue(transactionsAtom);

Check warning on line 53 in src/app/strategy/[strategyId]/_components/Strategy.tsx

View workflow job for this annotation

GitHub Actions / Performs linting, formatting on the application

'transactions' is assigned a value but never used. Allowed unused vars must match /^_/u
const [isMounted, setIsMounted] = useState(false);

const strategy: StrategyInfo | undefined = useMemo(() => {
Expand All @@ -79,7 +76,7 @@

useEffect(() => {
setBalQueryEnable(true);
}, []);

Check warning on line 79 in src/app/strategy/[strategyId]/_components/Strategy.tsx

View workflow job for this annotation

GitHub Actions / Performs linting, formatting on the application

React Hook useEffect has a missing dependency: 'setBalQueryEnable'. Either include it or remove the dependency array

const balData = useAtomValue(strategy?.balanceAtom || DUMMY_BAL_ATOM);

Expand All @@ -91,7 +88,7 @@
address!,
strategy?.balanceAtom || DUMMY_BAL_ATOM,
),
[address, strategyAddress, balData],

Check warning on line 91 in src/app/strategy/[strategyId]/_components/Strategy.tsx

View workflow job for this annotation

GitHub Actions / Performs linting, formatting on the application

React Hook useMemo has a missing dependency: 'strategy?.balanceAtom'. Either include it or remove the dependency array
);
const txHistoryResult = useAtomValue(txHistoryAtom);
const txHistory = useMemo(() => {
Expand All @@ -111,7 +108,7 @@
txHistoryResult.isLoading,
);
return txHistoryResult.data || { findManyInvestment_flows: [] };
}, [txHistoryResult.data]);

Check warning on line 111 in src/app/strategy/[strategyId]/_components/Strategy.tsx

View workflow job for this annotation

GitHub Actions / Performs linting, formatting on the application

React Hook useMemo has missing dependencies: 'txHistoryResult.error', 'txHistoryResult.isError', and 'txHistoryResult.isLoading'. Either include them or remove the dependency array

// compute profit
// profit doesnt change quickly in real time, but total deposit amount can change
Expand Down Expand Up @@ -177,13 +174,15 @@
{strategy ? strategy.name : 'Strategy Not found'}
</Text>
</Flex>

{strategy && (
<VStack width={'100%'}>
<Grid width={'100%'} templateColumns="repeat(5, 1fr)" gap={2}>
<GridItem display="flex" colSpan={colSpan1}>
<Card width="100%" padding={'15px'} color="white" bg="highlight">
<Box display={{ base: 'block', md: 'flex' }}>
<Box width={{ base: '100%', md: '80%' }} float={'left'}>
<HarvestTime strategy={strategy} balData={balData} />
<Box display={{ base: 'block', md: 'flex' }} marginTop={'10px'}>
<Box width={{ base: '100%', md: '100%' }}>
<Text
fontSize={'20px'}
marginBottom={'0px'}
Expand Down Expand Up @@ -219,26 +218,6 @@
))}
</Wrap>
</Box>
<Box
width={{ base: '100%', md: '20%' }}
float={'left'}
marginTop={{ base: '10px' }}
>
<Stat>
<StatLabel textAlign={{ base: 'left', md: 'right' }}>
APY
</StatLabel>
<StatNumber
color="cyan"
textAlign={{ base: 'left', md: 'right' }}
>
{(strategy.netYield * 100).toFixed(2)}%
</StatNumber>
<StatHelpText textAlign={{ base: 'left', md: 'right' }}>
{strategy.leverage.toFixed(2)}x boosted
</StatHelpText>
</Stat>
</Box>
</Box>
<Box
padding={'10px'}
Expand Down Expand Up @@ -310,6 +289,7 @@
</Box>
</Card>
</GridItem>

<GridItem display="flex" colSpan={colSpan2}>
<Card width="100%" padding={'15px'} color="white" bg="highlight">
<Tabs position="relative" variant="unstyled" width={'100%'}>
Expand Down Expand Up @@ -370,6 +350,7 @@
</Card>
</GridItem>
</Grid>

<Card width={'100%'} color="white" bg="highlight" padding={'15px'}>
<Text fontSize={'20px'} marginBottom={'0px'} fontWeight={'bold'}>
Behind the scenes
Expand Down
224 changes: 224 additions & 0 deletions src/components/HarvestTime.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
import React, { useMemo } from 'react';
import {
Box,
Flex,
Stat,
StatLabel,
StatNumber,
Tag,
Text,
Tooltip,
} from '@chakra-ui/react';
import { useAccount } from '@starknet-react/core';
import { StrategyInfo } from '@/store/strategies.atoms';
import { HarvestTimeAtom } from '@/store/harvest.atom';
import { useAtomValue } from 'jotai';
import { formatTimediff, getDisplayCurrencyAmount, timeAgo } from '@/utils';
import { isMobile } from 'react-device-detect';

interface HarvestTimeProps {
strategy: StrategyInfo;
balData: any;
}

const HarvestTime: React.FC<HarvestTimeProps> = ({ strategy, balData }) => {
const { address } = useAccount();
const holdingToken: any = strategy.holdingTokens[0];
const contractAddress = holdingToken.address || holdingToken.token || '';

const harvestTimeAtom = useMemo(
() => HarvestTimeAtom(contractAddress),
[address],
);

const harvestTime = useAtomValue(harvestTimeAtom);

const data = harvestTime.data?.findManyHarvests[0];

const lastHarvest = useMemo(() => {
if (!data || !data.timestamp) return null;
return new Date(Number(data.timestamp) * 1000);
}, [data?.timestamp]);

const harvestTimestamp = useMemo(() => {
const DAYMS = 86400 * 1000;
// Base date is last harvest time + 2 days or now (for no harvest strats)
const baseDate = lastHarvest
? new Date(lastHarvest.getTime() + 2 * DAYMS)
: new Date();

// With base date, get next sunday 12am UTC
// set date to coming sunday in UTC
const nextHarvest = baseDate;
nextHarvest.setUTCDate(
nextHarvest.getUTCDate() + (7 - nextHarvest.getUTCDay()),
);
nextHarvest.setUTCHours(0);
nextHarvest.setUTCMinutes(0);
nextHarvest.setUTCSeconds(0);

// if nextHarvest is within 24hrs of last harvest,
// increase it by 7 days
// This is needed as harvest can happen anytime near deadline
if (
lastHarvest &&
nextHarvest.getTime() - lastHarvest.getTime() < 86400 * 1000
) {
nextHarvest.setUTCDate(nextHarvest.getUTCDate() + 7);
}

return formatTimediff(nextHarvest);
}, [data?.timestamp]);

return (
<Box>
<Flex justifyContent="space-between">
<Flex>
<Stat
marginRight={'5px'}
display={'flex'}
flexDirection={'column'}
justifyContent={'flex-end'}
>
<StatLabel>APY</StatLabel>
<StatNumber color="cyan" lineHeight="24px">
{(strategy.netYield * 100).toFixed(2)}%
</StatNumber>
</Stat>
<Flex flexDirection={'column'} justifyContent={'flex-end'}>
<Tooltip label="This shows how much higher your yield is compared to zKLend">
<Tag
bg="bg"
color={'white'}
fontSize={'12px'}
padding={'2px 5px'}
>
🔥{strategy.leverage.toFixed(2)}x boosted
</Tag>
</Tooltip>
</Flex>
</Flex>

{!isMobile && (
<Tooltip
label={`This is when your investment increases as STRK rewards are automatically claimed and reinvested into the strategy's tokens.`}
>
<Box>
<Text
color="#D4D4D6"
fontSize="14px"
fontWeight="500"
display={'flex'}
>
Next Harvest in:{' '}
{harvestTimestamp.isZero && (
<Text color={'cyan'} fontWeight={'bold'} marginLeft={'5px'}>
Anytime now
</Text>
)}
</Text>
<Box
display="flex"
alignItems="center"
pt="5px"
gap="10px"
justifyContent="space-between"
>
<Box
display="flex"
alignItems="center"
justifyContent="center"
flexDirection="column"
gap="4px"
bgColor="color2_50p"
width="53px"
height="53px"
borderRadius="8px"
>
<Text color="#AEAEAE" fontSize="12px" fontWeight="300">
Days
</Text>
<Text color="white" fontWeight="semi-bold">
{harvestTimestamp.days ?? 0}
</Text>
</Box>

<Box
display="flex"
alignItems="center"
justifyContent="center"
flexDirection="column"
gap="4px"
bgColor="color2_50p"
width="53px"
height="53px"
borderRadius="8px"
>
<Text color="#AEAEAE" fontSize="12px" fontWeight="300">
Hour
</Text>
<Text color="white" fontWeight="semi-bold">
{harvestTimestamp.hours ?? 0}
</Text>
</Box>

<Box
display="flex"
alignItems="center"
justifyContent="center"
flexDirection="column"
gap="4px"
bgColor="color2_50p"
width="53px"
height="53px"
borderRadius="8px"
>
<Text color="#AEAEAE" fontSize="12px" fontWeight="300">
Mins
</Text>
<Text color="white" fontWeight="semi-bold">
{harvestTimestamp.minutes ?? 0}
</Text>
</Box>
</Box>
</Box>
</Tooltip>
)}
</Flex>

<Box
display="flex"
padding="5px"
width={'100%'}
bg="bg"
marginTop={'10px'}
borderRadius={'5px'}
>
<Text
color="white"
fontSize="12px"
fontWeight="normal"
textAlign={isMobile ? 'left' : 'right'}
width={'100%'}
>
Harvested{' '}
<b>
{getDisplayCurrencyAmount(
harvestTime?.data?.totalStrkHarvestedByContract.STRKAmount || 0,
2,
)}{' '}
STRK
</b>{' '}
over <b>{harvestTime?.data?.totalHarvestsByContract} claims.</b>{' '}
{lastHarvest && (
<span>
Last harvested <b>{timeAgo(lastHarvest)}</b>.
</span>
)}
</Text>
</Box>
</Box>
);
};

export default HarvestTime;
Loading
Loading