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

fix apy bugs, use strategies api for fast loading #161

Merged
merged 1 commit into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/app/api/stats/[address]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ export async function GET(_req: Request, context: any) {
return {
id: strategy.id,
usdValue: balanceInfo.usdValue,
tokenInfo: {
name: balanceInfo.tokenInfo.name,
symbol: balanceInfo.tokenInfo.name,
logo: balanceInfo.tokenInfo.logo,
decimals: balanceInfo.tokenInfo.decimals,
displayDecimals: balanceInfo.tokenInfo.displayDecimals,
},
amount: balanceInfo.amount.toEtherStr(),
};
});
Expand Down
37 changes: 26 additions & 11 deletions src/app/api/strategies/route.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,50 @@
import { NextResponse } from 'next/server';
import { atom } from 'jotai';
import ZkLendAtoms from '@/store/zklend.store';
import { PoolInfo } from '@/store/pools';
import NostraLendingAtoms from '@/store/nostralending.store';
import ZkLendAtoms, { zkLend } from '@/store/zklend.store';
import { PoolInfo, PoolType } from '@/store/pools';
import NostraLendingAtoms, { nostraLending } from '@/store/nostralending.store';
import { RpcProvider } from 'starknet';
import { getLiveStatusNumber, getStrategies } from '@/store/strategies.atoms';
import { MY_STORE } from '@/store';
import MyNumber from '@/utils/MyNumber';
import { IStrategy, NFTInfo, TokenInfo } from '@/strategies/IStrategy';
import { STRKFarmStrategyAPIResult } from '@/store/strkfarm.atoms';

export const revalidate = 3600; // 1 hr

const allPoolsAtom = atom<PoolInfo[]>((get) => {
const pools: PoolInfo[] = [];
const poolAtoms = [ZkLendAtoms, NostraLendingAtoms];
return poolAtoms.reduce((_pools, p) => _pools.concat(get(p.pools)), pools);
return [];
});

async function getPools(store: any, retry = 0) {
const allPools = store.get(allPoolsAtom);
if (!allPools.length && retry < 10) {
const allPools: PoolInfo[] | undefined = store.get(allPoolsAtom);

const minProtocolsRequired = [zkLend.name, nostraLending.name];
const hasRequiredPools = minProtocolsRequired.every((p) => {
if (!allPools) return false;
return allPools.some(
(pool) => pool.protocol.name === p && pool.type == PoolType.Lending,

Check warning on line 28 in src/app/api/strategies/route.ts

View workflow job for this annotation

GitHub Actions / Performs linting, formatting on the application

Expected '===' and instead saw '=='
);
});
const MAX_RETRIES = 120;
if (retry >= MAX_RETRIES) {
throw new Error('Failed to fetch pools');
} else if (!allPools || !hasRequiredPools) {
await new Promise((resolve) => setTimeout(resolve, 1000));
return getPools(store, retry + 1);
}
if (retry >= 10) {
throw new Error('Failed to fetch pools');
}
return allPools;
}

const provider = new RpcProvider({
nodeUrl: process.env.RPC_URL || 'https://starknet-mainnet.public.blastapi.io',
});

async function getStrategyInfo(strategy: IStrategy) {
async function getStrategyInfo(
strategy: IStrategy,
): Promise<STRKFarmStrategyAPIResult> {
const tvl = await strategy.getTVL();

return {
Expand Down Expand Up @@ -64,11 +74,16 @@
};
}

export async function GET(req: Request) {

Check warning on line 77 in src/app/api/strategies/route.ts

View workflow job for this annotation

GitHub Actions / Performs linting, formatting on the application

'req' is defined but never used. Allowed unused args must match /^_/u
const allPools = await getPools(MY_STORE);
const strategies = getStrategies();

strategies.forEach((strategy) => {
strategy.solve(allPools, '1000');
try {
strategy.solve(allPools, '1000');
} catch (err) {
console.error('Error solving strategy', strategy.name, err);
}
});

const stratsDataProms: any[] = [];
Expand Down
39 changes: 15 additions & 24 deletions src/components/Strategies.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import CONSTANTS from '@/constants';
import { strategiesAtom } from '@/store/strategies.atoms';
import {
Box,
Container,
Link,
Skeleton,
Expand All @@ -15,16 +13,20 @@ import {
} from '@chakra-ui/react';
import { useAtomValue } from 'jotai';
import React, { useMemo } from 'react';
import { userStatsAtom } from '@/store/utils.atoms';
import { allPoolsAtomUnSorted, filteredPools } from '@/store/protocols';
import { addressAtom } from '@/store/claims.atoms';
import { filteredPools } from '@/store/protocols';
import { usePagination } from '@ajna/pagination';
import { YieldStrategyCard } from './YieldCard';
import {
STRKFarmBaseAPYsAtom,
STRKFarmStrategyAPIResult,
} from '@/store/strkfarm.atoms';
export default function Strategies() {
const allPools = useAtomValue(allPoolsAtomUnSorted);
const strategies = useAtomValue(strategiesAtom);
const { data: userData } = useAtomValue(userStatsAtom);
const address = useAtomValue(addressAtom);
const strkFarmPoolsRes = useAtomValue(STRKFarmBaseAPYsAtom);
const strkFarmPools = useMemo(() => {
if (!strkFarmPoolsRes || !strkFarmPoolsRes.data)
return [] as STRKFarmStrategyAPIResult[];
return strkFarmPoolsRes.data.strategies.sort((a, b) => b.apy - a.apy);
}, [strkFarmPoolsRes]);

const _filteredPools = useAtomValue(filteredPools);
const ITEMS_PER_PAGE = 15;
Expand Down Expand Up @@ -57,29 +59,18 @@ export default function Strategies() {
</Tr>
</Thead>
<Tbody>
{allPools.length > 0 && strategies.length > 0 && (
{strkFarmPools.length > 0 && (
<>
{strategies.map((strat, index) => {
{strkFarmPools.map((pool, index) => {
return (
<YieldStrategyCard
key={strat.id}
strat={strat}
index={index}
/>
<YieldStrategyCard key={pool.id} strat={pool} index={index} />
);
})}
</>
)}
</Tbody>
</Table>
{allPools.length > 0 && strategies.length === 0 && (
<Box padding="10px 0" width={'100%'} float={'left'}>
<Text color="light_grey" textAlign={'center'}>
No strategies. Check back soon.
</Text>
</Box>
)}
{allPools.length === 0 && (
{strkFarmPools.length === 0 && (
<Stack>
<Skeleton height="70px" />
<Skeleton height="70px" />
Expand Down
11 changes: 8 additions & 3 deletions src/components/TncModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import axios from 'axios';
import { atomWithQuery } from 'jotai-tanstack-query';
import React, { useEffect, useMemo, useState } from 'react';
import { UserTncInfo } from '@/app/api/interfaces';
import { useAtomValue, useSetAtom } from 'jotai';
import { useAtom, useAtomValue } from 'jotai';
import { referralCodeAtom } from '@/store/referral.store';
import { useSearchParams } from 'next/navigation';
import { generateReferralCode } from '@/utils';
Expand All @@ -44,10 +44,13 @@ export const UserTnCAtom = atomWithQuery((get) => {

const TncModal: React.FC<TncModalProps> = (props) => {
const { address, account } = useAccount();
const setReferralCode = useSetAtom(referralCodeAtom);
const [refCode, setReferralCode] = useAtom(referralCodeAtom);
const searchParams = useSearchParams();
const userTncInfoRes = useAtomValue(UserTnCAtom);
const userTncInfo = useMemo(() => userTncInfoRes.data, [userTncInfoRes]);
const userTncInfo = useMemo(
() => userTncInfoRes.data,
[userTncInfoRes, refCode],
);
const { isOpen, onOpen, onClose } = useDisclosure();
const [isSigningPending, setIsSigningPending] = useState(false);
const { disconnectAsync } = useDisconnect();
Expand All @@ -64,6 +67,8 @@ const TncModal: React.FC<TncModalProps> = (props) => {
!userTncInfo.user.isTncSigned
) {
onOpen();
} else {
onClose();
}
return;
}
Expand Down
84 changes: 63 additions & 21 deletions src/components/YieldCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
VStack,
} from '@chakra-ui/react';
import shield from '@/assets/shield.svg';
import { IStrategyProps, StrategyLiveStatus } from '@/strategies/IStrategy';
import { StrategyLiveStatus } from '@/strategies/IStrategy';
import { useAtomValue } from 'jotai';
import { getDisplayCurrencyAmount } from '@/utils';
import { addressAtom } from '@/store/claims.atoms';
Expand All @@ -32,6 +32,7 @@ import { getPoolInfoFromStrategy } from '@/store/protocols';
import { TriangleDownIcon, TriangleUpIcon } from '@chakra-ui/icons';
import { useState } from 'react';
import mixpanel from 'mixpanel-browser';
import { STRKFarmStrategyAPIResult } from '@/store/strkfarm.atoms';

interface YieldCardProps {
pool: PoolInfo;
Expand Down Expand Up @@ -206,19 +207,38 @@ function isLive(status: StrategyLiveStatus) {
);
}

function getStrategyWiseInfo(
function getStrategyWiseHoldingsInfo(
userData: UserStats | null | undefined,
id: string,
) {
const amount = userData?.strategyWise.find((item) => item.id === id);
return amount?.usdValue ? amount?.usdValue : 0;
if (!amount) {
return {
usdValue: 0,
amount: 0,
tokenInfo: {
symbol: '',
decimals: 0,
displayDecimals: 0,
logo: '',
name: '',
},
};
}
return {
usdValue: amount.usdValue,
amount: Number(amount.amount),
tokenInfo: amount.tokenInfo,
};
}

function StrategyTVL(props: YieldCardProps) {
const { pool } = props;
const address = useAtomValue(addressAtom);
const { data: userData } = useAtomValue(userStatsAtom);

const holdingsInfo = getStrategyWiseHoldingsInfo(userData, pool.pool.id);

const isPoolLive =
pool.additional &&
pool.additional.tags[0] &&
Expand Down Expand Up @@ -247,17 +267,44 @@ function StrategyTVL(props: YieldCardProps) {
borderRadius={'20px'}
color="grey_text"
fontSize={'12px'}
width={'100%'}
mt="5px"
>
<>
<Tooltip label="Your deposits in this STRKFarm strategy">
<Text width={'100%'} textAlign={'right'} fontWeight={600}>
<Icon as={FaWallet} marginRight={'5px'} marginTop={'-2px'} />$
{Math.round(
getStrategyWiseInfo(userData, pool.pool.id),
).toLocaleString()}
</Text>
</Tooltip>
</>
<Tooltip label="Your deposits in this STRKFarm strategy">
<Box width={'100%'} fontWeight={600}>
<Flex>
<Text width={'100%'} justifyContent={'flex-end'}>
${getDisplayCurrencyAmount(holdingsInfo.usdValue, 0)}
</Text>
<Box>
<Icon as={FaWallet} marginLeft={'3px'} mt={'-3px'} />
</Box>
</Flex>
{holdingsInfo.amount != 0 && (
<Flex
justifyContent={'flex-end'}
marginTop={'-5px'}
width={'100%'}
opacity={0.5}
>
{/* <Avatar size={'2xs'} src={holdingsInfo.tokenInfo.logo} mr={'2px'}/> */}
<Text textAlign={'right'} fontSize={'11px'}>
{getDisplayCurrencyAmount(
holdingsInfo.amount,
holdingsInfo.tokenInfo.displayDecimals,
).toLocaleString()}
</Text>
<Image
width={'10px'}
src={holdingsInfo.tokenInfo.logo}
ml={'4px'}
mr={'1px'}
filter={'grayscale(1)'}
/>
</Flex>
)}
</Box>
</Tooltip>
</Box>
)}
</Box>
Expand Down Expand Up @@ -470,16 +517,11 @@ export default function YieldCard(props: YieldCardProps) {
}

export function YieldStrategyCard(props: {
strat: IStrategyProps;
strat: STRKFarmStrategyAPIResult;
index: number;
}) {
const tvlInfo = useAtomValue(props.strat.tvlAtom);
const pool = getPoolInfoFromStrategy(
props.strat,
tvlInfo.data?.usdValue || 0,
);

return <YieldCard pool={pool} index={props.index} showProtocolName={false} />;
const strat = getPoolInfoFromStrategy(props.strat);
return <YieldCard pool={strat} index={props.index} showProtocolName={true} />;
}

export function HeaderSorter(props: {
Expand Down
20 changes: 11 additions & 9 deletions src/store/protocols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@ import CarmineAtoms, { carmine } from './carmine.store';
import { atom } from 'jotai';
import { Category, PoolInfo, PoolType } from './pools';
import strkfarmLogo from '@public/logo.png';
import { IStrategyProps } from '@/strategies/IStrategy';
import STRKFarmAtoms, { strkfarm } from './strkfarm.atoms';
import STRKFarmAtoms, {
strkfarm,
STRKFarmStrategyAPIResult,
} from './strkfarm.atoms';
import { getLiveStatusEnum } from './strategies.atoms';

export const PROTOCOLS = [
{
Expand Down Expand Up @@ -151,8 +154,7 @@ export const allPoolsAtomUnSorted = atom((get) => {
});

export function getPoolInfoFromStrategy(
strat: IStrategyProps,
tvlInfo: number,
strat: STRKFarmStrategyAPIResult,
): PoolInfo {
let category = Category.Others;
if (strat.name.includes('STRK')) {
Expand All @@ -164,18 +166,18 @@ export function getPoolInfoFromStrategy(
pool: {
id: strat.id,
name: strat.name,
logos: [strat.holdingTokens[0].logo],
logos: [strat.logo],
},
protocol: {
name: 'STRKFarm',
link: `/strategy/${strat.id}`,
logo: strkfarmLogo.src,
},
tvl: tvlInfo,
apr: strat.netYield,
tvl: strat.tvlUsd,
apr: strat.apy,
aprSplits: [
{
apr: strat.netYield,
apr: strat.apy,
title: 'Net Yield',
description: 'Includes fees & Defi spring rewards',
},
Expand All @@ -191,7 +193,7 @@ export function getPoolInfoFromStrategy(
},
additional: {
riskFactor: strat.riskFactor,
tags: [strat.liveStatus],
tags: [getLiveStatusEnum(strat.status.number)],
isAudited: true,
leverage: strat.leverage,
},
Expand Down
Loading
Loading