From 82ca11d1a87a97c8a13b33d77e27cf9f40169551 Mon Sep 17 00:00:00 2001 From: Iwueseiter <156322726+Iwueseiter@users.noreply.github.com> Date: Fri, 6 Sep 2024 11:33:14 +0000 Subject: [PATCH 01/11] feat: harvest time ui --- src/components/HarvestTime.tsx | 150 +++++++++++++++++++++++++++++++++ src/components/Strategies.tsx | 5 ++ 2 files changed, 155 insertions(+) create mode 100644 src/components/HarvestTime.tsx diff --git a/src/components/HarvestTime.tsx b/src/components/HarvestTime.tsx new file mode 100644 index 00000000..fd622f64 --- /dev/null +++ b/src/components/HarvestTime.tsx @@ -0,0 +1,150 @@ +import { Box, Text } from '@chakra-ui/react'; +import React from 'react'; + +export default function HarvestTime() { + return ( + + + + + APY + + + + 23.94% + + + + 🔥 1.96x boosted + + + + + + + + Next Harvest in: + + + + + Days + + + 18 + + + + + + Hrs + + + 15 + + + + + + Mins + + + 05 + + + + + + Sec + + + 01 + + + + + + + + Total rewards harvested: + + $500 | Total number of times harvested: 48 + + + + + How does it work? + + + Deposit your USDC to automatically loop your funds between zkLend and + Nostra to create a delta neutral position. This strategy is designed to + maximize your yield on USDC. Your position is automatically adjusted + periodically to maintain a healthy health factor. You receive a NFT as + representation for your stake on STRKFarm. You can withdraw anytime by + redeeming your NFT for USDC. + + + ); +} diff --git a/src/components/Strategies.tsx b/src/components/Strategies.tsx index b215f901..c8df4807 100755 --- a/src/components/Strategies.tsx +++ b/src/components/Strategies.tsx @@ -40,6 +40,7 @@ import { userStatsAtom } from '@/store/utils.atoms'; import { IStrategyProps, StrategyLiveStatus } from '@/strategies/IStrategy'; import { allPoolsAtomUnSorted } from '@/store/protocols'; import { FaWallet } from 'react-icons/fa'; +import HarvestTime from './HarvestTime'; export default function Strategies() { const allPools = useAtomValue(allPoolsAtomUnSorted); @@ -389,6 +390,9 @@ export default function Strategies() { return ( + + + + From f02c748459a52fb945b7b7a5121949c803e88c2f Mon Sep 17 00:00:00 2001 From: Iwueseiter <156322726+Iwueseiter@users.noreply.github.com> Date: Mon, 16 Sep 2024 05:06:25 +0000 Subject: [PATCH 02/11] feat: display API data --- .../[strategyId]/_components/Strategy.tsx | 112 +------ src/components/HarvestTime.tsx | 308 +++++++++++++----- src/store/harvest.atom.ts | 25 +- src/utils.ts | 11 + 4 files changed, 261 insertions(+), 195 deletions(-) diff --git a/src/app/strategy/[strategyId]/_components/Strategy.tsx b/src/app/strategy/[strategyId]/_components/Strategy.tsx index 61f5609e..2a51cf68 100755 --- a/src/app/strategy/[strategyId]/_components/Strategy.tsx +++ b/src/app/strategy/[strategyId]/_components/Strategy.tsx @@ -31,7 +31,6 @@ import { useAccount } from '@starknet-react/core'; import { atom, useAtomValue, useSetAtom } from 'jotai'; import mixpanel from 'mixpanel-browser'; import { useEffect, useMemo, useState } from 'react'; -import { isMobile } from 'react-device-detect'; import Deposit from '@/components/Deposit'; import CONSTANTS from '@/constants'; @@ -109,112 +108,20 @@ const Strategy = ({ params }: StrategyParams) => { {strategy ? strategy.name : 'Strategy Not found'} + + {strategy && ( + + - - - - - How does it work? - - - {strategy.description} - - - {getUniqueById( - strategy.actions.map((p) => ({ - id: p.pool.protocol.name, - logo: p.pool.protocol.logo, - })), - ).map((p) => ( - -
- - {p.id} -
-
- ))} -
-
- - - - APY - - - {(strategy.netYield * 100).toFixed(2)}% - - - {strategy.leverage.toFixed(2)}x boosted - - - -
- - {!balData.isLoading && - !balData.isError && - !balData.isPending && - balData.data && - balData.data.tokenInfo && ( - - Your Holdings: - {address - ? `${balData.data.amount.toEtherToFixedDecimals(4)} ${balData.data.tokenInfo?.name}` - : isMobile - ? CONSTANTS.MOBILE_MSG - : 'Connect wallet'} - - )} - {(balData.isLoading || - balData.isPending || - !balData.data?.tokenInfo) && ( - - Your Holdings: - {address ? ( - - ) : isMobile ? ( - CONSTANTS.MOBILE_MSG - ) : ( - 'Connect wallet' - )} - - )} - {balData.isError && ( - - Your Holdings: Error - - )} - -
+ +
+ + + @@ -276,7 +183,6 @@ const Strategy = ({ params }: StrategyParams) => {
- diff --git a/src/components/HarvestTime.tsx b/src/components/HarvestTime.tsx index 0c149e9c..02d3f761 100644 --- a/src/components/HarvestTime.tsx +++ b/src/components/HarvestTime.tsx @@ -1,18 +1,34 @@ import React, { useMemo } from 'react'; -import { Box, Text } from '@chakra-ui/react'; +import { + Avatar, + Box, + Card, + Center, + Spinner, + Stat, + StatHelpText, + StatLabel, + StatNumber, + Text, + Wrap, + WrapItem, +} from '@chakra-ui/react'; import { useAccount } from '@starknet-react/core'; -import { StrategyInfo } from '@/store/strategies.atoms'; // Adjust import path if necessary +import { StrategyInfo } from '@/store/strategies.atoms'; import { HarvestTimeAtom } from '@/store/harvest.atoms'; import { useAtomValue } from 'jotai'; +import { formatTimestamp, getUniqueById } from '@/utils'; +import CONSTANTS from '@/constants'; +import { isMobile } from 'react-device-detect'; interface HarvestTimeProps { strategy: StrategyInfo; + balData: any; } -const HarvestTime: React.FC = ({ strategy }) => { +const HarvestTime: React.FC = ({ strategy, balData }) => { const { address } = useAccount(); - - const contractAddress = strategy.holdingTokens[0].address ?? ""; + const contractAddress = strategy.holdingTokens[0].address ?? ''; const harvestTimeAtom = useMemo( () => HarvestTimeAtom(contractAddress), @@ -21,94 +37,224 @@ const HarvestTime: React.FC = ({ strategy }) => { const harvestTime = useAtomValue(harvestTimeAtom); + const data = harvestTime.data?.findManyHarvests[0]; + + const harvestTimestamp = useMemo(() => { + if (!data?.timestamp) return null; + return formatTimestamp(data.timestamp); + }, [data?.timestamp]); + return ( - - - - APY - - - - 23.94% - - - - 🔥 1.96x boosted - - + + + + + APY + + + {(strategy.netYield * 100).toFixed(2)}% + + + {strategy.leverage.toFixed(2)}x boosted + + + - - - - Next Harvest in: - - - {/* Repeated Box components for days, hours, mins, secs */} + + + Next Harvest in: + - - Days - - - 18 - + + + Mon + + + {harvestTimestamp?.month} + + + + + + Days + + + {harvestTimestamp?.day} + + + + + + Hour + + + {harvestTimestamp?.hour} + + + + + + Mins + + + {harvestTimestamp?.minute} + + + + {/* + + Secs + + + {harvestTimestamp?.second} + + */} - {/* Other time boxes omitted for brevity */} - - - - Total rewards harvested: - - $500 | Total number of times harvested: 48 + + + + Total rewards harvested: + + {(BigInt(data?.amount.toString() ?? '') / BigInt(10 ** 18)).toString()} | + Total number of times harvested:{' '} + {harvestTime?.data?.totalHarvests} + - - - - How does it work? - - - Deposit your USDC to automatically loop your funds between zkLend and - Nostra to create a delta neutral position. This strategy is designed to - maximize your yield on USDC. Your position is automatically adjusted - periodically to maintain a healthy health factor. You receive a NFT as - representation for your stake on STRKFarm. You can withdraw anytime by - redeeming your NFT for USDC. - + + + + + + How does it work? + + + {strategy.description} + + + {getUniqueById( + strategy.actions.map((p) => ({ + id: p.pool.protocol.name, + logo: p.pool.protocol.logo, + })), + ).map((p) => ( + +
+ + {p.id} +
+
+ ))} +
+
+
+ + + {!balData.isLoading && + !balData.isError && + !balData.isPending && + balData.data && + balData.data.tokenInfo && ( + + Your Holdings: + {address + ? `${balData.data.amount.toEtherToFixedDecimals(4)} ${balData.data.tokenInfo?.name}` + : isMobile + ? CONSTANTS.MOBILE_MSG + : 'Connect wallet'} + + )} + {(balData.isLoading || + balData.isPending || + !balData.data?.tokenInfo) && ( + + Your Holdings: + {address ? ( + + ) : isMobile ? ( + CONSTANTS.MOBILE_MSG + ) : ( + 'Connect wallet' + )} + + )} + {balData.isError && ( + + Your Holdings: Error + + )} + +
); }; diff --git a/src/store/harvest.atom.ts b/src/store/harvest.atom.ts index 8f41ea65..ad2d20c7 100644 --- a/src/store/harvest.atom.ts +++ b/src/store/harvest.atom.ts @@ -19,26 +19,24 @@ const GET_HARVESTS_QUERY = gql` query Query( $where: HarvestsWhereInput, $take: Int, - $orderBy: [HarvestsOrderByWithRelationInput!], - $contract: String!, - $totalStrkHarvestedContract2: String! + $orderBy: [HarvestsOrderByWithRelationInput!] ) { findManyHarvests(where: $where, take: $take, orderBy: $orderBy) { contract amount timestamp } - totalHarvests(contract: $contract) - totalStrkHarvested(contract: $totalStrkHarvestedContract2) + totalHarvests + } `; + // Function to execute the query async function getHarvestData( contract: string, - totalStrkHarvestedContract2?: string, - take: number = 10, - orderBy: any = [{ timestamp: 'desc' }] // adjust the sort order as needed + take: number = 1, + orderBy: any = [{ timestamp: 'desc' }] ): Promise { try { const { data } = await apolloClient.query({ @@ -51,8 +49,6 @@ async function getHarvestData( }, take, orderBy, - contract, - totalStrkHarvestedContract2, }, }); @@ -69,7 +65,14 @@ export const HarvestTimeAtom = (contract: string) => atomWithQuery((get) => ({ queryKey: ['harvest_data', contract], queryFn: async () => { - const result = await getHarvestData(contract); + + //Mock Harvest time + const testContractAddress = "0x20d5fc4c9df4f943ebb36078e703369c04176ed00accf290e8295b659d2cea6" + const result = await getHarvestData(testContractAddress); return result; + + // const testContractAddress = "0x20d5fc4c9df4f943ebb36078e703369c04176ed00accf290e8295b659d2cea6" + // const result = await getHarvestData(contract); + // return result; }, })); diff --git a/src/utils.ts b/src/utils.ts index dfc2c871..6f121851 100755 --- a/src/utils.ts +++ b/src/utils.ts @@ -101,3 +101,14 @@ export function getDisplayCurrencyAmount( ) { return Number(Number(amount).toFixed(decimals)).toLocaleString(); } + +export function formatTimestamp(timestamp: string) { + const date = new Date(parseInt(timestamp, 10) * 1000); + return { + day: date.getUTCDate(), + month: date.getUTCMonth() + 1, + hour: date.getUTCHours(), + minute: date.getUTCMinutes(), + second: date.getUTCSeconds(), + }; +} From 56a7b75a4092493d16c8050bfd710a3bef35084f Mon Sep 17 00:00:00 2001 From: Iwueseiter <156322726+Iwueseiter@users.noreply.github.com> Date: Mon, 16 Sep 2024 11:24:41 +0000 Subject: [PATCH 03/11] fix: imports --- src/components/HarvestTime.tsx | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/components/HarvestTime.tsx b/src/components/HarvestTime.tsx index 02d3f761..530a9c77 100644 --- a/src/components/HarvestTime.tsx +++ b/src/components/HarvestTime.tsx @@ -14,8 +14,8 @@ import { WrapItem, } from '@chakra-ui/react'; import { useAccount } from '@starknet-react/core'; -import { StrategyInfo } from '@/store/strategies.atoms'; -import { HarvestTimeAtom } from '@/store/harvest.atoms'; +import { StrategyInfo } from '@/store/strategies.atoms'; +import { HarvestTimeAtom } from '@/store/harvest.atom'; import { useAtomValue } from 'jotai'; import { formatTimestamp, getUniqueById } from '@/utils'; import CONSTANTS from '@/constants'; @@ -51,12 +51,17 @@ const HarvestTime: React.FC = ({ strategy, balData }) => { APY - + {(strategy.netYield * 100).toFixed(2)}% - {strategy.leverage.toFixed(2)}x boosted + {strategy.leverage.toFixed(2)}x boosted @@ -175,8 +180,12 @@ const HarvestTime: React.FC = ({ strategy, balData }) => { Total rewards harvested: - {(BigInt(data?.amount.toString() ?? '') / BigInt(10 ** 18)).toString()} | - Total number of times harvested:{' '} + + {( + BigInt(data?.amount.toString() ?? '') / BigInt(10 ** 18) + ).toString()} + {' '} + | Total number of times harvested:{' '} {harvestTime?.data?.totalHarvests} From 808a4f50a5768900f0a24787f27ff3c82dd625b5 Mon Sep 17 00:00:00 2001 From: Iwueseiter <156322726+Iwueseiter@users.noreply.github.com> Date: Mon, 16 Sep 2024 18:19:06 +0000 Subject: [PATCH 04/11] fix: lint --- .../[strategyId]/_components/Strategy.tsx | 20 ++----------------- src/components/Strategies.tsx | 1 - src/store/harvest.atom.ts | 17 +++++++--------- src/utils/apolloClient.ts | 2 +- 4 files changed, 10 insertions(+), 30 deletions(-) diff --git a/src/app/strategy/[strategyId]/_components/Strategy.tsx b/src/app/strategy/[strategyId]/_components/Strategy.tsx index 2a51cf68..f40c2ed1 100755 --- a/src/app/strategy/[strategyId]/_components/Strategy.tsx +++ b/src/app/strategy/[strategyId]/_components/Strategy.tsx @@ -4,18 +4,12 @@ import { Avatar, Box, Card, - Center, Flex, Grid, GridItem, Link, ListItem, OrderedList, - Spinner, - Stat, - StatHelpText, - StatLabel, - StatNumber, Tab, TabIndicator, TabList, @@ -24,8 +18,6 @@ import { Tabs, Text, VStack, - Wrap, - WrapItem, } from '@chakra-ui/react'; import { useAccount } from '@starknet-react/core'; import { atom, useAtomValue, useSetAtom } from 'jotai'; @@ -33,14 +25,13 @@ import mixpanel from 'mixpanel-browser'; import { useEffect, useMemo, useState } from 'react'; 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 { StrategyTxPropsToMessageWithStrategies, transactionsAtom, } from '@/store/transactions.atom'; -import { getUniqueById, shortAddress } from '@/utils'; +import { shortAddress } from '@/utils'; import { StrategyParams } from '../page'; import HarvestTime from '@/components/HarvestTime'; @@ -109,19 +100,13 @@ const Strategy = ({ params }: StrategyParams) => { - {strategy && ( - - - - + - - @@ -182,7 +167,6 @@ const Strategy = ({ params }: StrategyParams) => { - diff --git a/src/components/Strategies.tsx b/src/components/Strategies.tsx index a089c014..235a91ac 100755 --- a/src/components/Strategies.tsx +++ b/src/components/Strategies.tsx @@ -40,7 +40,6 @@ export default function Strategies() { return ( - What are strategies? diff --git a/src/store/harvest.atom.ts b/src/store/harvest.atom.ts index ad2d20c7..1932d2ec 100644 --- a/src/store/harvest.atom.ts +++ b/src/store/harvest.atom.ts @@ -17,8 +17,8 @@ interface QueryResponse { // GraphQL query const GET_HARVESTS_QUERY = gql` query Query( - $where: HarvestsWhereInput, - $take: Int, + $where: HarvestsWhereInput + $take: Int $orderBy: [HarvestsOrderByWithRelationInput!] ) { findManyHarvests(where: $where, take: $take, orderBy: $orderBy) { @@ -27,16 +27,14 @@ const GET_HARVESTS_QUERY = gql` timestamp } totalHarvests - } `; - // Function to execute the query async function getHarvestData( contract: string, take: number = 1, - orderBy: any = [{ timestamp: 'desc' }] + orderBy: any = [{ timestamp: 'desc' }], ): Promise { try { const { data } = await apolloClient.query({ @@ -60,19 +58,18 @@ async function getHarvestData( } } - export const HarvestTimeAtom = (contract: string) => atomWithQuery((get) => ({ queryKey: ['harvest_data', contract], queryFn: async () => { - //Mock Harvest time - const testContractAddress = "0x20d5fc4c9df4f943ebb36078e703369c04176ed00accf290e8295b659d2cea6" + const testContractAddress = + '0x20d5fc4c9df4f943ebb36078e703369c04176ed00accf290e8295b659d2cea6'; const result = await getHarvestData(testContractAddress); - return result; + return result; // const testContractAddress = "0x20d5fc4c9df4f943ebb36078e703369c04176ed00accf290e8295b659d2cea6" // const result = await getHarvestData(contract); - // return result; + // return result; }, })); diff --git a/src/utils/apolloClient.ts b/src/utils/apolloClient.ts index 419a28bf..0c05c718 100644 --- a/src/utils/apolloClient.ts +++ b/src/utils/apolloClient.ts @@ -5,4 +5,4 @@ const apolloClient = new ApolloClient({ cache: new InMemoryCache(), }); -export default apolloClient; \ No newline at end of file +export default apolloClient; From b607e865350bc3894a029467da9238e0799f7567 Mon Sep 17 00:00:00 2001 From: Iwueseiter <156322726+Iwueseiter@users.noreply.github.com> Date: Tue, 17 Sep 2024 05:58:49 +0000 Subject: [PATCH 05/11] fix: remove mock contract --- src/components/HarvestTime.tsx | 29 ++++++++++------------------- src/store/harvest.atom.ts | 20 ++++++++------------ 2 files changed, 18 insertions(+), 31 deletions(-) diff --git a/src/components/HarvestTime.tsx b/src/components/HarvestTime.tsx index 530a9c77..b98df005 100644 --- a/src/components/HarvestTime.tsx +++ b/src/components/HarvestTime.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react'; +import React, { useEffect, useMemo } from 'react'; import { Avatar, Box, @@ -14,7 +14,7 @@ import { WrapItem, } from '@chakra-ui/react'; import { useAccount } from '@starknet-react/core'; -import { StrategyInfo } from '@/store/strategies.atoms'; +import { StrategyInfo } from '@/store/strategies.atoms'; import { HarvestTimeAtom } from '@/store/harvest.atom'; import { useAtomValue } from 'jotai'; import { formatTimestamp, getUniqueById } from '@/utils'; @@ -51,17 +51,12 @@ const HarvestTime: React.FC = ({ strategy, balData }) => { APY - + {(strategy.netYield * 100).toFixed(2)}% - {strategy.leverage.toFixed(2)}x boosted + {strategy.leverage.toFixed(2)}x boosted @@ -93,7 +88,7 @@ const HarvestTime: React.FC = ({ strategy, balData }) => { Mon - {harvestTimestamp?.month} + {harvestTimestamp?.month ?? 0} @@ -112,7 +107,7 @@ const HarvestTime: React.FC = ({ strategy, balData }) => { Days - {harvestTimestamp?.day} + {harvestTimestamp?.day ?? 0} @@ -131,7 +126,7 @@ const HarvestTime: React.FC = ({ strategy, balData }) => { Hour - {harvestTimestamp?.hour} + {harvestTimestamp?.hour ?? 0} @@ -150,7 +145,7 @@ const HarvestTime: React.FC = ({ strategy, balData }) => { Mins - {harvestTimestamp?.minute} + {harvestTimestamp?.minute ?? 0} @@ -180,12 +175,8 @@ const HarvestTime: React.FC = ({ strategy, balData }) => { Total rewards harvested: - - {( - BigInt(data?.amount.toString() ?? '') / BigInt(10 ** 18) - ).toString()} - {' '} - | Total number of times harvested:{' '} + {(BigInt(data?.amount.toString() ?? '') / BigInt(10 ** 18)).toString()} | + Total number of times harvested:{' '} {harvestTime?.data?.totalHarvests} diff --git a/src/store/harvest.atom.ts b/src/store/harvest.atom.ts index 1932d2ec..654c565a 100644 --- a/src/store/harvest.atom.ts +++ b/src/store/harvest.atom.ts @@ -17,8 +17,8 @@ interface QueryResponse { // GraphQL query const GET_HARVESTS_QUERY = gql` query Query( - $where: HarvestsWhereInput - $take: Int + $where: HarvestsWhereInput, + $take: Int, $orderBy: [HarvestsOrderByWithRelationInput!] ) { findManyHarvests(where: $where, take: $take, orderBy: $orderBy) { @@ -27,14 +27,16 @@ const GET_HARVESTS_QUERY = gql` timestamp } totalHarvests + } `; + // Function to execute the query async function getHarvestData( contract: string, take: number = 1, - orderBy: any = [{ timestamp: 'desc' }], + orderBy: any = [{ timestamp: 'desc' }] ): Promise { try { const { data } = await apolloClient.query({ @@ -58,18 +60,12 @@ async function getHarvestData( } } + export const HarvestTimeAtom = (contract: string) => atomWithQuery((get) => ({ queryKey: ['harvest_data', contract], queryFn: async () => { - //Mock Harvest time - const testContractAddress = - '0x20d5fc4c9df4f943ebb36078e703369c04176ed00accf290e8295b659d2cea6'; - const result = await getHarvestData(testContractAddress); - return result; - - // const testContractAddress = "0x20d5fc4c9df4f943ebb36078e703369c04176ed00accf290e8295b659d2cea6" - // const result = await getHarvestData(contract); - // return result; + const result = await getHarvestData(contract); + return result; }, })); From 9c1bb355c2a54fb59ea79268ca5b42641f88d9ce Mon Sep 17 00:00:00 2001 From: Iwueseiter <156322726+Iwueseiter@users.noreply.github.com> Date: Tue, 17 Sep 2024 05:59:28 +0000 Subject: [PATCH 06/11] fix: remove mock contract --- src/components/HarvestTime.tsx | 21 +++++++++++++++------ src/store/harvest.atom.ts | 11 ++++------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/components/HarvestTime.tsx b/src/components/HarvestTime.tsx index b98df005..d17d23db 100644 --- a/src/components/HarvestTime.tsx +++ b/src/components/HarvestTime.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo } from 'react'; +import React, { useMemo } from 'react'; import { Avatar, Box, @@ -14,7 +14,7 @@ import { WrapItem, } from '@chakra-ui/react'; import { useAccount } from '@starknet-react/core'; -import { StrategyInfo } from '@/store/strategies.atoms'; +import { StrategyInfo } from '@/store/strategies.atoms'; import { HarvestTimeAtom } from '@/store/harvest.atom'; import { useAtomValue } from 'jotai'; import { formatTimestamp, getUniqueById } from '@/utils'; @@ -51,12 +51,17 @@ const HarvestTime: React.FC = ({ strategy, balData }) => { APY - + {(strategy.netYield * 100).toFixed(2)}% - {strategy.leverage.toFixed(2)}x boosted + {strategy.leverage.toFixed(2)}x boosted @@ -175,8 +180,12 @@ const HarvestTime: React.FC = ({ strategy, balData }) => { Total rewards harvested: - {(BigInt(data?.amount.toString() ?? '') / BigInt(10 ** 18)).toString()} | - Total number of times harvested:{' '} + + {( + BigInt(data?.amount.toString() ?? '') / BigInt(10 ** 18) + ).toString()} + {' '} + | Total number of times harvested:{' '} {harvestTime?.data?.totalHarvests} diff --git a/src/store/harvest.atom.ts b/src/store/harvest.atom.ts index 654c565a..5f351766 100644 --- a/src/store/harvest.atom.ts +++ b/src/store/harvest.atom.ts @@ -17,8 +17,8 @@ interface QueryResponse { // GraphQL query const GET_HARVESTS_QUERY = gql` query Query( - $where: HarvestsWhereInput, - $take: Int, + $where: HarvestsWhereInput + $take: Int $orderBy: [HarvestsOrderByWithRelationInput!] ) { findManyHarvests(where: $where, take: $take, orderBy: $orderBy) { @@ -27,16 +27,14 @@ const GET_HARVESTS_QUERY = gql` timestamp } totalHarvests - } `; - // Function to execute the query async function getHarvestData( contract: string, take: number = 1, - orderBy: any = [{ timestamp: 'desc' }] + orderBy: any = [{ timestamp: 'desc' }], ): Promise { try { const { data } = await apolloClient.query({ @@ -60,12 +58,11 @@ async function getHarvestData( } } - export const HarvestTimeAtom = (contract: string) => atomWithQuery((get) => ({ queryKey: ['harvest_data', contract], queryFn: async () => { const result = await getHarvestData(contract); - return result; + return result; }, })); From c41bba76f3339f0ca880a65e0861e79ff2220c4e Mon Sep 17 00:00:00 2001 From: Iwueseiter <156322726+Iwueseiter@users.noreply.github.com> Date: Tue, 24 Sep 2024 13:49:09 +0000 Subject: [PATCH 07/11] fix: change query --- .../[strategyId]/_components/Strategy.tsx | 311 ++++++++++++------ src/components/HarvestTime.tsx | 31 +- src/store/harvest.atom.ts | 17 +- src/store/transactions.atom.ts | 161 ++++++++- src/utils.ts | 69 ++++ yarn.lock | 125 ++++++- 6 files changed, 577 insertions(+), 137 deletions(-) diff --git a/src/app/strategy/[strategyId]/_components/Strategy.tsx b/src/app/strategy/[strategyId]/_components/Strategy.tsx index f40c2ed1..2d162211 100755 --- a/src/app/strategy/[strategyId]/_components/Strategy.tsx +++ b/src/app/strategy/[strategyId]/_components/Strategy.tsx @@ -22,18 +22,22 @@ import { import { useAccount } from '@starknet-react/core'; import { atom, useAtomValue, useSetAtom } from 'jotai'; import mixpanel from 'mixpanel-browser'; -import { useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import Deposit from '@/components/Deposit'; import { DUMMY_BAL_ATOM } from '@/store/balance.atoms'; import { StrategyInfo, strategiesAtom } from '@/store/strategies.atoms'; -import { - StrategyTxPropsToMessageWithStrategies, - transactionsAtom, -} from '@/store/transactions.atom'; -import { shortAddress } from '@/utils'; +import { transactionsAtom, TxHistoryAtom } from '@/store/transactions.atom'; import { StrategyParams } from '../page'; import HarvestTime from '@/components/HarvestTime'; +import { + capitalize, + getTokenInfoFromAddr, + shortAddress, + timeAgo, +} from '@/utils'; +import MyNumber from '@/utils/MyNumber'; +import { ExternalLinkIcon } from '@chakra-ui/icons'; const Strategy = ({ params }: StrategyParams) => { const { address } = useAccount(); @@ -41,10 +45,6 @@ const Strategy = ({ params }: StrategyParams) => { const transactions = useAtomValue(transactionsAtom); const [isMounted, setIsMounted] = useState(false); - useEffect(() => { - console.log('txs', transactions); - }, [transactions]); - const strategy: StrategyInfo | undefined = useMemo(() => { const id = params.strategyId; @@ -53,25 +53,92 @@ const Strategy = ({ params }: StrategyParams) => { return strategies.find((s) => s.id === id); }, [params.strategyId, strategies]); + console.log('strategy', strategy); + + const strategyAddress = useMemo(() => { + const holdingTokens = strategy?.holdingTokens; + if (holdingTokens && holdingTokens.length) { + const holdingTokenInfo: any = holdingTokens[0]; + return (holdingTokenInfo.address || holdingTokenInfo.token) as string; + } + return ''; + }, [strategy]); + const setBalQueryEnable = useSetAtom(strategy?.balEnabled || atom(false)); useEffect(() => { setBalQueryEnable(true); }, []); - // const balAtom = getBalanceAtom(strategy?.holdingTokens[0]); const balData = useAtomValue(strategy?.balanceAtom || DUMMY_BAL_ATOM); - // cons{ balance, underlyingTokenInfo, isLoading, isError } - useEffect(() => { + + // fetch tx history + const txHistoryAtom = useMemo( + () => + TxHistoryAtom( + strategyAddress, + address!, + strategy?.balanceAtom || DUMMY_BAL_ATOM, + ), + [address, strategyAddress, balData], + ); + const txHistoryResult = useAtomValue(txHistoryAtom); + const txHistory = useMemo(() => { + if (txHistoryResult.data) { + return { + findManyInvestment_flows: [ + ...txHistoryResult.data.findManyInvestment_flows, + ].sort((a, b) => { + return b.timestamp - a.timestamp; + }), + }; + } console.log( - 'balData', - balData.isError, - balData.isLoading, - balData.isPending, - balData.data, - balData.error, + 'TxHistoryAtom', + txHistoryResult.error, + txHistoryResult.isError, + txHistoryResult.isLoading, ); - }, [balData]); + return txHistoryResult.data || { findManyInvestment_flows: [] }; + }, [txHistoryResult.data]); + + // compute profit + // profit doesnt change quickly in real time, but total deposit amount can change + // and it can impact the profit calc as txHistory may not be updated at the same time as balData + // So, we compute profit once only + const [profit, setProfit] = useState(0); + const computeProfit = useCallback(() => { + if (!txHistory.findManyInvestment_flows.length) return 0; + const tokenInfo = getTokenInfoFromAddr( + txHistory.findManyInvestment_flows[0].asset, + ); + if (!tokenInfo) return 0; + const netDeposits = txHistory.findManyInvestment_flows.reduce((acc, tx) => { + const sign = tx.type === 'deposit' ? 1 : -1; + return ( + acc + + sign * + Number( + new MyNumber(tx.amount, tokenInfo.decimals).toEtherToFixedDecimals( + 4, + ), + ) + ); + }, 0); + const currentValue = Number( + balData.data?.amount.toEtherToFixedDecimals(4) || '0', + ); + if (currentValue === 0) return 0; + + if (netDeposits === 0) return 0; + setProfit(currentValue - netDeposits); + }, [txHistory, balData]); + + useEffect(() => { + if (profit == 0) { + computeProfit(); + } + }, [txHistory, balData]); useEffect(() => { mixpanel.track('Strategy page open', { name: params.strategyId }); @@ -258,94 +325,136 @@ const Strategy = ({ params }: StrategyParams) => { ))} - - {/* Risks card */} - - - Risks - - - {strategy.risks.map((r) => ( - - {r} - - ))} - - - - {/* Transaction history card */} - - - Transaction history - - - {/* If more than 1 filtered tx */} - {transactions.filter((tx) => tx.info.strategyId == strategy.id) - .length > 0 && ( - <> + + + {/* Risks card */} + - Note: This feature saves and shows transactions made on this - device since it was added. Clearing your browser cache will - remove this data. + Risks + + + {strategy.risks.map((r) => ( + + {r} + + ))} + + + + + {/* Transaction history card */} + + + Transaction history + + + There may be delays fetching data. If your transaction{' '} + {`isn't`} found, try again later. - {transactions - .filter((tx) => tx.info.strategyId == strategy.id) - .map((tx, index) => { - return ( - - - {/* The default msg contains strategy name, since this for a specific strategy, replace it */} - {index + 1}){' '} - {StrategyTxPropsToMessageWithStrategies( - tx.info, - strategies, - ).replace(` in ${strategy.name}`, '')} - - - {/* The default msg contains strategy name, since this for a specific strategy, replace it */} - Transacted on {tx.createdAt.toLocaleDateString()} [ - + {txHistory.findManyInvestment_flows.map((tx, index) => { + const token = getTokenInfoFromAddr(tx.asset); + const decimals = token?.decimals; + + return ( + + - {shortAddress(tx.txHash)} - - ] - - - ); - })} - - )} + {index + 1}. + + + {Number( + new MyNumber( + tx.amount, + decimals!, + ).toEtherToFixedDecimals( + token.displayDecimals, + ), + ).toLocaleString()}{' '} + {token?.name} + + + {capitalize(tx.type)} + + - {/* If no filtered tx */} - {transactions.filter((tx) => tx.info.strategyId == strategy.id) - .length == 0 && ( - - No transactions recorded since this feature was added. We use - your {"browser's"} storage to save your transaction history. - Make a deposit or withdrawal to see your transactions here. - Clearning browser cache will remove this data. - - )} - + + + + {shortAddress(tx.txHash)} + + + + {/* The default msg contains strategy name, since this for a specific strategy, replace it */} + {timeAgo(new Date(tx.timestamp * 1000))} + + + + + ); + })} + + )} + + {/* If no filtered tx */} + {txHistory.findManyInvestment_flows.length === 0 && ( + + No transactions found + + )} + + +
)} diff --git a/src/components/HarvestTime.tsx b/src/components/HarvestTime.tsx index d17d23db..e7358600 100644 --- a/src/components/HarvestTime.tsx +++ b/src/components/HarvestTime.tsx @@ -153,40 +153,17 @@ const HarvestTime: React.FC = ({ strategy, balData }) => { {harvestTimestamp?.minute ?? 0} - - {/* - - Secs - - - {harvestTimestamp?.second} - - */} - Total rewards harvested: + Total rewards harvested:{' '} + {harvestTime?.data?.totalStrkHarvestedByContract.STRKAmount} - - {( - BigInt(data?.amount.toString() ?? '') / BigInt(10 ** 18) - ).toString()} - {' '} - | Total number of times harvested:{' '} - {harvestTime?.data?.totalHarvests} + {} | Total number of times harvested:{' '} + {harvestTime?.data?.totalHarvestsByContract} diff --git a/src/store/harvest.atom.ts b/src/store/harvest.atom.ts index 5f351766..60b89c7d 100644 --- a/src/store/harvest.atom.ts +++ b/src/store/harvest.atom.ts @@ -10,8 +10,12 @@ interface HarvestTime { interface QueryResponse { findManyHarvests: HarvestTime[]; - totalHarvests: number; - totalStrkHarvested: number; + totalHarvestsByContract: number; + totalStrkHarvestedByContract: { + STRKAmount: number; + USDValue: number; + rawSTRKAmount: string; + }; } // GraphQL query @@ -20,13 +24,19 @@ const GET_HARVESTS_QUERY = gql` $where: HarvestsWhereInput $take: Int $orderBy: [HarvestsOrderByWithRelationInput!] + $contract: String! ) { findManyHarvests(where: $where, take: $take, orderBy: $orderBy) { contract amount timestamp } - totalHarvests + totalHarvestsByContract(contract: $contract) + totalStrkHarvestedByContract(contract: $contract) { + STRKAmount + USDValue + rawSTRKAmount + } } `; @@ -47,6 +57,7 @@ async function getHarvestData( }, take, orderBy, + contract, }, }); diff --git a/src/store/transactions.atom.ts b/src/store/transactions.atom.ts index b13fda53..78a11a63 100755 --- a/src/store/transactions.atom.ts +++ b/src/store/transactions.atom.ts @@ -4,11 +4,15 @@ import { TOKENS } from '@/constants'; import { capitalize, standariseAddress } from '@/utils'; import MyNumber from '@/utils/MyNumber'; -import { Getter, Setter, atom } from 'jotai'; +import { Atom, Getter, Setter, atom } from 'jotai'; import toast from 'react-hot-toast'; import { RpcProvider, TransactionExecutionStatus } from 'starknet'; import { StrategyInfo, strategiesAtom } from './strategies.atoms'; import { createAtomWithStorage } from './utils.atoms'; +import { atomWithQuery, AtomWithQueryResult } from 'jotai-tanstack-query'; +import { gql } from '@apollo/client'; +import apolloClient from '@/utils/apolloClient'; +import { BalanceResult } from './balance.atoms'; export interface StrategyTxProps { strategyId: string; @@ -25,6 +29,105 @@ export interface TransactionInfo { createdAt: Date; } +export interface TxHistory { + findManyInvestment_flows: { + amount: string; + timestamp: number; + type: string; + txHash: string; + asset: string; + __typename: 'Investment_flows'; + }[]; +} + +async function getTxHistory( + contract: string, + owner: string, +): Promise { + try { + const contractAddrFormatted = standariseAddress(contract); + const ownerAddrFormatted = standariseAddress(owner); + const { data } = await apolloClient.query({ + query: gql` + query Query($where: Investment_flowsWhereInput) { + findManyInvestment_flows(where: $where) { + amount + timestamp + type + txHash + asset + } + } + `, + variables: { + where: { + contract: { + equals: contractAddrFormatted, + }, + owner: { + equals: ownerAddrFormatted, + }, + }, + }, + // fetchPolicy: 'network-only' + }); + + return data; + } catch (error) { + console.error('GraphQL Error:', error); + throw error; + } +} + +export const newTxsAtom = atom([]); + +export const TxHistoryAtom = ( + contract: string, + owner: string, + balData: Atom>, +) => + atomWithQuery((get) => ({ + // balData just to trigger a refetch + queryKey: ['tx_history', { contract, owner }, get(balData)], + queryFn: async ({ queryKey }: any): Promise => { + const [, { contract, owner }] = queryKey; + const res = await getTxHistory(contract, owner); + + console.log('TxHistoryAtom res', res); + // add new txs from local cache + const newTxs = get(newTxsAtom); + console.log('TxHistoryAtom newTxs', newTxs); + const allTxs = res.findManyInvestment_flows.concat( + newTxs.map((tx) => { + return { + amount: tx.info.amount.toString(), + timestamp: Math.round(tx.createdAt.getTime() / 1000), + type: tx.info.actionType, + txHash: tx.txHash, + asset: tx.info.tokenAddr, + __typename: 'Investment_flows', + }; + }), + ); + + console.log('TxHistoryAtom', allTxs); + // remove any duplicate txs by txHash + const txMap: any = {}; // txHash: boolean + const txHashes = allTxs.filter((txInfo) => { + if (txMap[txInfo.txHash]) { + return false; + } + txMap[txInfo.txHash] = true; + return true; + }); + + console.log('TxHistoryAtom txHashes', txHashes); + return { + findManyInvestment_flows: txHashes, + }; + }, + })); + // in local storage, objects like Date, MyNumber are stored as strings // this function deserialises them back to their original types // declare let localStorage: any; @@ -76,14 +179,64 @@ async function waitForTransaction( nodeUrl: process.env.NEXT_PUBLIC_RPC_URL, }); console.log('waitForTransaction', tx); - await provider.waitForTransaction(tx.txHash, { - successStates: [TransactionExecutionStatus.SUCCEEDED], - }); + await isTxAccepted(tx.txHash); console.log('waitForTransaction done', tx); const txs = await get(transactionsAtom); tx.status = 'success'; txs.push(tx); set(transactionsAtom, txs); + + let newTxs = get(newTxsAtom); + const txExists = newTxs.find( + (t) => t.txHash.toLowerCase() === tx.txHash.toLowerCase(), + ); + if (!txExists) { + newTxs = [...newTxs, tx]; + set(newTxsAtom, newTxs); + } +} + +// Somehow waitForTransaction is giving delayed confirmation +// even with 5s retry interval. So, using this function instead +async function isTxAccepted(txHash: string) { + const provider = new RpcProvider({ + nodeUrl: process.env.NEXT_PUBLIC_RPC_URL, + }); + let keepChecking = true; + const maxRetries = 30; + let retry = 0; + while (keepChecking) { + let txInfo: any; + try { + txInfo = await provider.getTransactionStatus(txHash); + } catch (error) { + console.error('isTxAccepted error', error); + retry++; + if (retry > maxRetries) { + throw new Error('Transaction status unknown'); + } + await new Promise((resolve) => setTimeout(resolve, 2000)); + continue; + } + + console.debug('isTxAccepted', txInfo); + if (!txInfo.finality_status || txInfo.finality_status == 'RECEIVED') { + // do nothing + await new Promise((resolve) => setTimeout(resolve, 2000)); + continue; + } + if (txInfo.finality_status == 'ACCEPTED_ON_L2') { + if (txInfo.execution_status === TransactionExecutionStatus.SUCCEEDED) { + keepChecking = false; + return true; + } + throw new Error('Transaction reverted'); + } else if (txInfo.finality_status == 'REJECTED') { + throw new Error('Transaction rejected'); + } else { + throw new Error('Transaction status unknown'); + } + } } async function initToast(tx: TransactionInfo, get: Getter, set: Setter) { diff --git a/src/utils.ts b/src/utils.ts index 6f121851..85c71029 100755 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,9 @@ import { MenuItemProps, MenuListProps } from '@chakra-ui/react'; import { num } from 'starknet'; import { TOKENS } from './constants'; +import toast from 'react-hot-toast'; +import { TokenInfo } from './strategies/IStrategy'; +import axios from 'axios'; export function getUniqueStrings(arr: Array) { const _arr: string[] = []; @@ -92,6 +95,9 @@ export function generateReferralCode() { } export function getReferralUrl(referralCode: string) { + if (window.location.origin.includes('app.strkfarm.xyz')) { + return `https://strkfarm.xyz/r/${referralCode}`; + } return `${window.location.origin}/r/${referralCode}`; } @@ -112,3 +118,66 @@ export function formatTimestamp(timestamp: string) { second: date.getUTCSeconds(), }; } + +export function copyReferralLink(refCode: string) { + navigator.clipboard.writeText(getReferralUrl(refCode)); + + toast.success('Referral link copied to clipboard', { + position: 'bottom-right', + }); +} + +export async function getPrice(tokenInfo: TokenInfo) { + try { + return await getPriceFromMyAPI(tokenInfo); + } catch (e) { + console.error('getPriceFromMyAPI error', e); + } + console.log('getPrice coinbase', tokenInfo.name); + const priceInfo = await axios.get( + `https://api.coinbase.com/v2/prices/${tokenInfo.name}-USDT/spot`, + ); + const price = Number(priceInfo.data.data.amount); + return price; +} + +export async function getPriceFromMyAPI(tokenInfo: TokenInfo) { + console.log('getPrice from redis', tokenInfo.name); + + const endpoint = + typeof window === 'undefined' + ? process.env.HOSTNAME + : window.location.origin; + const priceInfo = await axios.get(`${endpoint}/api/price/${tokenInfo.name}`); + const now = new Date(); + const priceTime = new Date(priceInfo.data.timestamp); + if (now.getTime() - priceTime.getTime() > 900000) { + // 15 mins + throw new Error('Price is stale'); + } + const price = Number(priceInfo.data.price); + return price; +} + +export function timeAgo(date: Date): string { + const now = new Date(); + const seconds = Math.floor((now.getTime() - date.getTime()) / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); + const weeks = Math.floor(days / 7); + const months = Math.floor(days / 30); + + if (hours < 1) return `${minutes}min ago`; + if (hours < 24) return `${hours}h ago`; + if (days < 7) return `${days}d ago`; + if (weeks < 4) return `${weeks}w ago`; + if (months < 3) return `${months}mon ago`; + + // If more than 3 months, return in DD MMM, YY format + return date.toLocaleDateString('en-US', { + day: '2-digit', + month: 'short', + year: '2-digit', + }); +} diff --git a/yarn.lock b/yarn.lock index 9470188d..472108b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15,6 +15,26 @@ framer-motion "^6.3.11" lodash.union "^4.6.0" +"@apollo/client@^3.11.8": + version "3.11.8" + resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.11.8.tgz#f6bacdc7e1b243807c1387113e1d445a53471a9c" + integrity sha512-CgG1wbtMjsV2pRGe/eYITmV5B8lXUCYljB2gB/6jWTFQcrvirUVvKg7qtFdjYkQSFbIffU1IDyxgeaN81eTjbA== + dependencies: + "@graphql-typed-document-node/core" "^3.1.1" + "@wry/caches" "^1.0.0" + "@wry/equality" "^0.5.6" + "@wry/trie" "^0.5.0" + graphql-tag "^2.12.6" + hoist-non-react-statics "^3.3.2" + optimism "^0.18.0" + prop-types "^15.7.2" + rehackt "^0.1.0" + response-iterator "^0.2.6" + symbol-observable "^4.0.0" + ts-invariant "^0.10.3" + tslib "^2.3.0" + zen-observable-ts "^1.2.5" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.21.4", "@babel/code-frame@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465" @@ -1128,6 +1148,11 @@ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.7.tgz#d0ece53ce99ab5a8e37ebdfe5e32452a2bfc073e" integrity sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA== +"@graphql-typed-document-node/core@^3.1.1": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861" + integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ== + "@humanwhocodes/config-array@^0.11.14": version "0.11.14" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" @@ -2263,6 +2288,41 @@ "@walletconnect/window-getters" "^1.0.1" tslib "1.14.1" +"@wry/caches@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@wry/caches/-/caches-1.0.1.tgz#8641fd3b6e09230b86ce8b93558d44cf1ece7e52" + integrity sha512-bXuaUNLVVkD20wcGBWRyo7j9N3TxePEWFZj2Y+r9OoUzfqmavM84+mFykRicNsBqatba5JLay1t48wxaXaWnlA== + dependencies: + tslib "^2.3.0" + +"@wry/context@^0.7.0": + version "0.7.4" + resolved "https://registry.yarnpkg.com/@wry/context/-/context-0.7.4.tgz#e32d750fa075955c4ab2cfb8c48095e1d42d5990" + integrity sha512-jmT7Sb4ZQWI5iyu3lobQxICu2nC/vbUhP0vIdd6tHC9PTfenmRmuIFqktc6GH9cgi+ZHnsLWPvfSvc4DrYmKiQ== + dependencies: + tslib "^2.3.0" + +"@wry/equality@^0.5.6": + version "0.5.7" + resolved "https://registry.yarnpkg.com/@wry/equality/-/equality-0.5.7.tgz#72ec1a73760943d439d56b7b1e9985aec5d497bb" + integrity sha512-BRFORjsTuQv5gxcXsuDXx6oGRhuVsEGwZy6LOzRRfgu+eSfxbhUQ9L9YtSEIuIjY/o7g3iWFjrc5eSY1GXP2Dw== + dependencies: + tslib "^2.3.0" + +"@wry/trie@^0.4.3": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@wry/trie/-/trie-0.4.3.tgz#077d52c22365871bf3ffcbab8e95cb8bc5689af4" + integrity sha512-I6bHwH0fSf6RqQcnnXLJKhkSXG45MFral3GxPaY4uAl0LYDZM+YDVDAiU9bYwjTuysy1S0IeecWtmq1SZA3M1w== + dependencies: + tslib "^2.3.0" + +"@wry/trie@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@wry/trie/-/trie-0.5.0.tgz#11e783f3a53f6e4cd1d42d2d1323f5bc3fa99c94" + integrity sha512-FNoYzHawTMk/6KMQoEG5O4PuioX19UbwdQKF44yw0nLfOypfQdjtfZzo/UIJWAJ23sNIFbD1Ug9lbaDGMwbqQA== + dependencies: + tslib "^2.3.0" + "@zag-js/dom-query@0.16.0": version "0.16.0" resolved "https://registry.yarnpkg.com/@zag-js/dom-query/-/dom-query-0.16.0.tgz#bca46bcd78f78c900064478646d95f9781ed098e" @@ -4089,6 +4149,18 @@ graphemer@^1.4.0: resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== +graphql-tag@^2.12.6: + version "2.12.6" + resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.12.6.tgz#d441a569c1d2537ef10ca3d1633b48725329b5f1" + integrity sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg== + dependencies: + tslib "^2.1.0" + +graphql@^16.9.0: + version "16.9.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.9.0.tgz#1c310e63f16a49ce1fbb230bd0a000e99f6f115f" + integrity sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw== + h3@^1.10.2, h3@^1.11.1: version "1.12.0" resolved "https://registry.yarnpkg.com/h3/-/h3-1.12.0.tgz#9d7f05f08a997d263e484b02436cb027df3026d8" @@ -4185,7 +4257,7 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^3.3.1: +hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -5152,6 +5224,16 @@ onetime@^6.0.0: dependencies: mimic-fn "^4.0.0" +optimism@^0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.18.0.tgz#e7bb38b24715f3fdad8a9a7fc18e999144bbfa63" + integrity sha512-tGn8+REwLRNFnb9WmcY5IfpOqeX2kpaYJ1s6Ae3mn12AeydLkR3j+jSCmVQFoXqU8D41PAJ1RG1rCRNWmNZVmQ== + dependencies: + "@wry/caches" "^1.0.0" + "@wry/context" "^0.7.0" + "@wry/trie" "^0.4.3" + tslib "^2.3.0" + optionator@^0.9.3: version "0.9.4" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" @@ -5436,7 +5518,7 @@ process-warning@^1.0.0: resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-1.0.0.tgz#980a0b25dc38cd6034181be4b7726d89066b4616" integrity sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q== -prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.8.1: +prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -5732,6 +5814,11 @@ regexp.prototype.flags@^1.5.1, regexp.prototype.flags@^1.5.2: es-errors "^1.3.0" set-function-name "^2.0.1" +rehackt@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/rehackt/-/rehackt-0.1.0.tgz#a7c5e289c87345f70da8728a7eb878e5d03c696b" + integrity sha512-7kRDOuLHB87D/JESKxQoRwv4DzbIdwkAGQ7p6QKGdVlY1IZheUnVhlk/4UZlNUVxdAXpyxikE3URsG067ybVzw== + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -5770,6 +5857,11 @@ resolve@^2.0.0-next.5: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +response-iterator@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/response-iterator/-/response-iterator-0.2.6.tgz#249005fb14d2e4eeb478a3f735a28fd8b4c9f3da" + integrity sha512-pVzEEzrsg23Sh053rmDUvLSkGXluZio0qu8VT6ukrYuvtjVfCbDZH9d6PGXb8HZfzdNZt8feXv/jvUzlhRgLnw== + reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -6252,6 +6344,11 @@ swr@2.2.5: client-only "^0.0.1" use-sync-external-store "^1.2.0" +symbol-observable@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-4.0.0.tgz#5b425f192279e87f2f9b937ac8540d1984b39205" + integrity sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ== + system-architecture@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/system-architecture/-/system-architecture-0.1.0.tgz#71012b3ac141427d97c67c56bc7921af6bff122d" @@ -6385,6 +6482,13 @@ ts-interface-checker@^0.1.9: resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== +ts-invariant@^0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.10.3.tgz#3e048ff96e91459ffca01304dbc7f61c1f642f6c" + integrity sha512-uivwYcQaxAucv1CzRp2n/QdYPo4ILf9VXgH19zEIjFx2EJufV16P0JtJVpYHy89DItG6Kwj2oIUjrcK5au+4tQ== + dependencies: + tslib "^2.1.0" + ts-mixer@^6.0.3: version "6.0.4" resolved "https://registry.yarnpkg.com/ts-mixer/-/ts-mixer-6.0.4.tgz#1da39ceabc09d947a82140d9f09db0f84919ca28" @@ -6415,6 +6519,11 @@ tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.1, tslib@^2.4.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== +tslib@^2.3.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" + integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -6822,6 +6931,18 @@ yocto-queue@^1.0.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.1.1.tgz#fef65ce3ac9f8a32ceac5a634f74e17e5b232110" integrity sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g== +zen-observable-ts@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-1.2.5.tgz#6c6d9ea3d3a842812c6e9519209365a122ba8b58" + integrity sha512-QZWQekv6iB72Naeake9hS1KxHlotfRpe+WGNbNx5/ta+R3DNjVO2bswf63gXlWDcs+EMd7XY8HfVQyP1X6T4Zg== + dependencies: + zen-observable "0.8.15" + +zen-observable@0.8.15: + version "0.8.15" + resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15" + integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ== + zod@^3.22.2: version "3.23.8" resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" From c0fd32898b22a06a23dc31ae910199a8c0203b1b Mon Sep 17 00:00:00 2001 From: Akira <156126180+akiraonstarknet@users.noreply.github.com> Date: Tue, 24 Sep 2024 11:31:01 +0530 Subject: [PATCH 08/11] Add tx history (#78) * Add GraphQL tx history (#77) * feat: use STRKFarm Graphql API to show tx history * add console.log statements for debugging * modify graphql query * fix: fix apollo client * refac: modify graphgl query * feat: implement function to get tx history using graphql * feat: show tx history in Strategy page * fix: fix linting issue * feat: implement function to get strategy address --------- Co-authored-by: Mystic <149405096+Gift-Naomi@users.noreply.github.com> Co-authored-by: Gift-Naomi * fix tx toast update delay, add earnings info, cleaned tx component --------- Co-authored-by: Mystic <149405096+Gift-Naomi@users.noreply.github.com> Co-authored-by: Gift-Naomi --- .../[strategyId]/_components/Strategy.tsx | 1 + yarn.lock | 17 +++++++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/app/strategy/[strategyId]/_components/Strategy.tsx b/src/app/strategy/[strategyId]/_components/Strategy.tsx index 7de0ce10..b58b7b18 100755 --- a/src/app/strategy/[strategyId]/_components/Strategy.tsx +++ b/src/app/strategy/[strategyId]/_components/Strategy.tsx @@ -17,6 +17,7 @@ import { TabPanels, Tabs, Text, + Tooltip, VStack, } from '@chakra-ui/react'; import { useAccount } from '@starknet-react/core'; diff --git a/yarn.lock b/yarn.lock index e9085882..8d96df68 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15,11 +15,7 @@ framer-motion "^6.3.11" lodash.union "^4.6.0" -<<<<<<< HEAD -"@apollo/client@^3.11.8": -======= "@apollo/client@3.11.8": ->>>>>>> e9f234a0c9a1a6d378aa3ac9ba7310f3486707ef version "3.11.8" resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.11.8.tgz#f6bacdc7e1b243807c1387113e1d445a53471a9c" integrity sha512-CgG1wbtMjsV2pRGe/eYITmV5B8lXUCYljB2gB/6jWTFQcrvirUVvKg7qtFdjYkQSFbIffU1IDyxgeaN81eTjbA== @@ -4656,11 +4652,7 @@ graphql-tag@^2.12.6: dependencies: tslib "^2.1.0" -<<<<<<< HEAD -graphql@^16.9.0: -======= graphql@16.9.0: ->>>>>>> e9f234a0c9a1a6d378aa3ac9ba7310f3486707ef version "16.9.0" resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.9.0.tgz#1c310e63f16a49ce1fbb230bd0a000e99f6f115f" integrity sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw== @@ -6503,7 +6495,10 @@ rehackt@^0.1.0: integrity sha512-7kRDOuLHB87D/JESKxQoRwv4DzbIdwkAGQ7p6QKGdVlY1IZheUnVhlk/4UZlNUVxdAXpyxikE3URsG067ybVzw== <<<<<<< HEAD +<<<<<<< HEAD +======= ======= +>>>>>>> fb93dff (Add tx history (#78)) request-promise-core@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9" @@ -7332,8 +7327,11 @@ tslib@^2.3.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== +<<<<<<< HEAD <<<<<<< HEAD ======= +======= +>>>>>>> fb93dff (Add tx history (#78)) tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" @@ -7820,7 +7818,10 @@ yoctocolors-cjs@^2.1.2: resolved "https://registry.yarnpkg.com/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz#f4b905a840a37506813a7acaa28febe97767a242" integrity sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA== +<<<<<<< HEAD >>>>>>> e9f234a0c9a1a6d378aa3ac9ba7310f3486707ef +======= +>>>>>>> fb93dff (Add tx history (#78)) zen-observable-ts@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-1.2.5.tgz#6c6d9ea3d3a842812c6e9519209365a122ba8b58" From 75355dfdf35fd4b5d8c2523632b55b8f73c755cf Mon Sep 17 00:00:00 2001 From: akiraonstarknet Date: Thu, 26 Sep 2024 13:36:28 +0530 Subject: [PATCH 09/11] add cache to /stats --- src/app/api/stats/route.ts | 10 ++++++++-- src/app/strategy/[strategyId]/_components/Strategy.tsx | 1 - 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/app/api/stats/route.ts b/src/app/api/stats/route.ts index ee639367..ee17e3da 100755 --- a/src/app/api/stats/route.ts +++ b/src/app/api/stats/route.ts @@ -1,7 +1,7 @@ import { getStrategies } from '@/store/strategies.atoms'; import { NextResponse } from 'next/server'; -export const revalidate = 60; +export const revalidate = 1800; export async function GET(_req: Request) { const strategies = getStrategies(); @@ -28,7 +28,13 @@ export async function GET(_req: Request) { const result = await Promise.all(values); - return NextResponse.json({ + const response = NextResponse.json({ tvl: result.reduce((a, b) => a + b, 0), }); + + response.headers.set( + 'Cache-Control', + 's-maxage=1800, stale-while-revalidate=120', + ); + return response; } diff --git a/src/app/strategy/[strategyId]/_components/Strategy.tsx b/src/app/strategy/[strategyId]/_components/Strategy.tsx index b58b7b18..7de0ce10 100755 --- a/src/app/strategy/[strategyId]/_components/Strategy.tsx +++ b/src/app/strategy/[strategyId]/_components/Strategy.tsx @@ -17,7 +17,6 @@ import { TabPanels, Tabs, Text, - Tooltip, VStack, } from '@chakra-ui/react'; import { useAccount } from '@starknet-react/core'; From 43ee68bea723efb792653d7fcbef57f914ebab0c Mon Sep 17 00:00:00 2001 From: Akira <156126180+akiraonstarknet@users.noreply.github.com> Date: Fri, 27 Sep 2024 22:45:24 +0530 Subject: [PATCH 10/11] Harvest timer (#147) * feat: harvest time ui * feat: display API data * fix: imports * fix: lint * fix: remove mock contract * fix: remove mock contract * fix: change query * fix harvest timer: standarise address * fix harvest timer bugs * fix harvest timer bugs [2] * fix harvest timer bugs [3] --------- Co-authored-by: Iwueseiter <156322726+Iwueseiter@users.noreply.github.com> --- .../[strategyId]/_components/Strategy.tsx | 37 +-- src/components/HarvestTime.tsx | 224 ++++++++++++++++++ src/store/harvest.atom.ts | 80 +++++++ src/utils.ts | 33 ++- 4 files changed, 344 insertions(+), 30 deletions(-) create mode 100644 src/components/HarvestTime.tsx create mode 100644 src/store/harvest.atom.ts diff --git a/src/app/strategy/[strategyId]/_components/Strategy.tsx b/src/app/strategy/[strategyId]/_components/Strategy.tsx index 521ef738..d9866875 100755 --- a/src/app/strategy/[strategyId]/_components/Strategy.tsx +++ b/src/app/strategy/[strategyId]/_components/Strategy.tsx @@ -12,10 +12,6 @@ import { ListItem, OrderedList, Spinner, - Stat, - StatHelpText, - StatLabel, - StatNumber, Tab, TabIndicator, TabList, @@ -32,13 +28,12 @@ import { useAccount } from '@starknet-react/core'; 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, @@ -49,6 +44,8 @@ import { 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(); @@ -177,13 +174,15 @@ const Strategy = ({ params }: StrategyParams) => { {strategy ? strategy.name : 'Strategy Not found'} + {strategy && ( - - + + + { ))} - - - - APY - - - {(strategy.netYield * 100).toFixed(2)}% - - - {strategy.leverage.toFixed(2)}x boosted - - - { + @@ -370,6 +350,7 @@ const Strategy = ({ params }: StrategyParams) => { + Behind the scenes diff --git a/src/components/HarvestTime.tsx b/src/components/HarvestTime.tsx new file mode 100644 index 00000000..63adf6c3 --- /dev/null +++ b/src/components/HarvestTime.tsx @@ -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 = ({ 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 ( + + + + + APY + + {(strategy.netYield * 100).toFixed(2)}% + + + + + + 🔥{strategy.leverage.toFixed(2)}x boosted + + + + + + {!isMobile && ( + + + + Next Harvest in:{' '} + {harvestTimestamp.isZero && ( + + Anytime now + + )} + + + + + Days + + + {harvestTimestamp.days ?? 0} + + + + + + Hour + + + {harvestTimestamp.hours ?? 0} + + + + + + Mins + + + {harvestTimestamp.minutes ?? 0} + + + + + + )} + + + + + Harvested{' '} + + {getDisplayCurrencyAmount( + harvestTime?.data?.totalStrkHarvestedByContract.STRKAmount || 0, + 2, + )}{' '} + STRK + {' '} + over {harvestTime?.data?.totalHarvestsByContract} claims.{' '} + {lastHarvest && ( + + Last harvested {timeAgo(lastHarvest)}. + + )} + + + + ); +}; + +export default HarvestTime; diff --git a/src/store/harvest.atom.ts b/src/store/harvest.atom.ts new file mode 100644 index 00000000..73413d07 --- /dev/null +++ b/src/store/harvest.atom.ts @@ -0,0 +1,80 @@ +import { gql } from '@apollo/client'; +import apolloClient from '@/utils/apolloClient'; +import { atomWithQuery } from 'jotai-tanstack-query'; +import { standariseAddress } from '@/utils'; + +interface HarvestTime { + contract: string; + amount: string; + timestamp: string; +} + +interface QueryResponse { + findManyHarvests: HarvestTime[]; + totalHarvestsByContract: number; + totalStrkHarvestedByContract: { + STRKAmount: number; + USDValue: number; + rawSTRKAmount: string; + }; +} + +// GraphQL query +const GET_HARVESTS_QUERY = gql` + query Query( + $where: HarvestsWhereInput + $take: Int + $orderBy: [HarvestsOrderByWithRelationInput!] + $contract: String! + ) { + findManyHarvests(where: $where, take: $take, orderBy: $orderBy) { + contract + amount + timestamp + } + totalHarvestsByContract(contract: $contract) + totalStrkHarvestedByContract(contract: $contract) { + STRKAmount + USDValue + rawSTRKAmount + } + } +`; + +// Function to execute the query +async function getHarvestData( + contract: string, + take: number = 1, + orderBy: any = [{ timestamp: 'desc' }], +): Promise { + try { + const { data } = await apolloClient.query({ + query: GET_HARVESTS_QUERY, + variables: { + where: { + contract: { + equals: standariseAddress(contract), + }, + }, + take, + orderBy, + contract: standariseAddress(contract), + }, + }); + + // Return the data + return data; + } catch (error) { + console.error('Error fetching harvest data:', error); + return null; + } +} + +export const HarvestTimeAtom = (contract: string) => + atomWithQuery((get) => ({ + queryKey: ['harvest_data', contract], + queryFn: async () => { + const result = await getHarvestData(contract); + return result; + }, + })); diff --git a/src/utils.ts b/src/utils.ts index c4211f2b..3f50a652 100755 --- a/src/utils.ts +++ b/src/utils.ts @@ -108,6 +108,35 @@ export function getDisplayCurrencyAmount( return Number(Number(amount).toFixed(decimals)).toLocaleString(); } +// returns time to endtime in days, hours, minutes +export function formatTimediff(endTime: Date) { + const now = new Date(); + if (now.getTime() >= endTime.getTime()) { + return { + days: 0, + hours: 0, + minutes: 0, + isZero: true, + }; + } + + // else return number of days, months, weeks, hours, minutrs, seconds to endtime + const diff = endTime.getTime() - now.getTime(); + // get days floor + const days = Math.floor(diff / (1000 * 60 * 60 * 24)); + // after accounting days, get remaining hours + const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); + // after accounting days and hours, get remaining minutes + const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); + + return { + days, + hours, + minutes, + isZero: false, + }; +} + export function copyReferralLink(refCode: string) { navigator.clipboard.writeText(getReferralUrl(refCode)); @@ -134,9 +163,9 @@ export async function getPriceFromMyAPI(tokenInfo: TokenInfo) { console.log('getPrice from redis', tokenInfo.name); const endpoint = - typeof window === 'undefined' + (typeof window === 'undefined' ? process.env.HOSTNAME - : window.location.origin; + : window.location.origin) || 'https://app.strkfarm.xyz'; const priceInfo = await axios.get(`${endpoint}/api/price/${tokenInfo.name}`); const now = new Date(); const priceTime = new Date(priceInfo.data.timestamp); From 3fb92053a800b0f3f4b7a8160d3dc03834aea189 Mon Sep 17 00:00:00 2001 From: Akira <156126180+akiraonstarknet@users.noreply.github.com> Date: Sat, 28 Sep 2024 18:27:09 +0530 Subject: [PATCH 11/11] bug: harvest timer lastHarvest state fix --- src/components/HarvestTime.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/HarvestTime.tsx b/src/components/HarvestTime.tsx index 63adf6c3..7fa6a5b5 100644 --- a/src/components/HarvestTime.tsx +++ b/src/components/HarvestTime.tsx @@ -68,7 +68,7 @@ const HarvestTime: React.FC = ({ strategy, balData }) => { } return formatTimediff(nextHarvest); - }, [data?.timestamp]); + }, [data?.timestamp, lastHarvest]); return (