diff --git a/next.config.mjs b/next.config.mjs index c7615d2..0c0fa0a 100755 --- a/next.config.mjs +++ b/next.config.mjs @@ -2,9 +2,9 @@ const nextConfig = { // output: 'export', compiler: { - removeConsole: { - exclude: ['error'], - }, + // removeConsole: { + // exclude: ['error'], + // }, }, async rewrites() { return [ diff --git a/src/app/globals.css b/src/app/globals.css index b293933..7903e56 100755 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -135,4 +135,5 @@ body { td, th { border-color: var(--chakra-colors-bg) !important; + vertical-align: top; } \ No newline at end of file diff --git a/src/app/page.tsx b/src/app/page.tsx index f8e00e2..f572357 100755 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -65,7 +65,7 @@ export default function Home() { } function handleTabsChange(index: number) { - if (index === 1) { + if (index === 0) { setRoute('pools'); } else { setRoute('strategies'); @@ -80,9 +80,9 @@ export default function Home() { (async () => { const tab = searchParams.get('tab'); if (tab === 'pools') { - setTabIndex(1); - } else { setTabIndex(0); + } else { + setTabIndex(1); } })(); }, [searchParams]); @@ -184,19 +184,19 @@ export default function Home() { color="light_grey" _selected={{ color: 'purple' }} onClick={() => { - mixpanel.track('Strategies opened'); + mixpanel.track('All pools clicked'); }} > - Strategies✨ + Find yields { - mixpanel.track('All pools clicked'); + mixpanel.track('Strategies opened'); }} > - Find yields + Strategies✨ - + - + diff --git a/src/components/Filters.tsx b/src/components/Filters.tsx index 48308e2..2a25ee8 100755 --- a/src/components/Filters.tsx +++ b/src/components/Filters.tsx @@ -1,217 +1,400 @@ import React from 'react'; -import Select, { StylesConfig } from 'react-select'; -import * as chroma from 'chroma.ts'; -import { useSetAtom } from 'jotai'; +import { useAtomValue, useSetAtom } from 'jotai'; import { - Accordion, - AccordionButton, - AccordionItem, - AccordionPanel, + Avatar, + AvatarBadge, + AvatarGroup, Box, - Stack, + HStack, + IconButton, + Tag, + TagLabel, Text, + Tooltip, } from '@chakra-ui/react'; -import { HamburgerIcon } from '@chakra-ui/icons'; -import { ALL_FILTER, filters, updateFiltersAtom } from '@/store/protocols'; - -export interface Option { - readonly value: string; - readonly label: string; - readonly color: string; - readonly isFixed?: boolean; - readonly isDisabled?: boolean; -} +import { CheckIcon, CloseIcon } from '@chakra-ui/icons'; +import { + ALL_FILTER, + filterAtoms, + filters, + updateFiltersAtom, +} from '@/store/protocols'; +import { getTokenInfoFromName } from '@/utils'; +import { Category, PoolType } from '@/store/pools'; -const colourStyles: StylesConfig = { - control: (styles) => ({ - ...styles, - padding: '10px 0', - backgroundColor: 'var(--chakra-colors-bg)', - borderColor: 'var(--chakra-colors-bg)', - }), - option: (styles, { data, isDisabled, isFocused, isSelected }) => { - const color = chroma.color(data.color); - return { - ...styles, - backgroundColor: 'var(--chakra-colors-bg)', - color: isDisabled - ? '#ccc' - : isSelected - ? chroma.contrast(color, 'white') > 2 - ? 'white' - : 'black' - : data.color, - cursor: isDisabled ? 'not-allowed' : 'default', - - ':active': { - ...styles[':active'], - backgroundColor: !isDisabled - ? isSelected - ? data.color - : color.alpha(0.3).css() - : undefined, - }, - }; - }, - container: (styles) => { - return { - ...styles, - width: '100%', - }; - }, - dropdownIndicator: (styles) => { - return { - ...styles, - color: 'var(--chakra-colors-purple)', - }; - }, - clearIndicator: (styles) => { - return { - ...styles, - color: 'var(--chakra-colors-purple)', - }; - }, - indicatorSeparator: (styles) => { - return { - ...styles, - backgroundColor: 'var(--chakra-colors-purple)', - }; - }, - menu: (styles) => { - return { - ...styles, - backgroundColor: 'var(--chakra-colors-highlight)', - }; - }, - multiValue: (styles, { data }) => { - const color = chroma.color(data.color); - return { - ...styles, - fontWeight: 'bold', - backgroundColor: 'none', - borderColor: color.alpha(0.7).css(), - borderWidth: '1px', - borderRadius: '5px', - }; - }, - multiValueLabel: (styles, { data }) => ({ - ...styles, - color: data.color, - }), - multiValueRemove: (styles, { data }) => ({ - ...styles, - color: data.color, - ':hover': { - backgroundColor: data.color, - color: 'white', - }, - }), -}; - -export default function Filters() { - // const colors: readonly string[] = ['#00B8D9', '#407cd5', '#7967e5', '#FF5630', - // '#FF8B00','#FFC400', '#36B37E', '#00875A', '#253858', '#666666'] - - const colors = ['rgba(132, 132, 195, 1)']; - const updateFilters = useSetAtom(updateFiltersAtom); +export function ProtocolFilters() { + const protocolsFilter = useAtomValue(filterAtoms.protocolsAtom); - const protocolOptions: readonly Option[] = filters.protocols.map( - (p, index) => { - return { value: p, label: p, color: colors[index % colors.length] }; - }, - ); + function isProtocolSelected(protocolName: string) { + return ( + protocolsFilter.includes(ALL_FILTER) || + protocolsFilter.includes(protocolName) + ); + } - const categories: readonly Option[] = filters.categories.map((p, index) => { - return { value: p, label: p, color: colors[index % colors.length] }; - }); + function atleastOneProtocolSelected() { + return protocolsFilter.length > 0; + } + const updateFilters = useSetAtom(updateFiltersAtom); - const poolTypes: readonly Option[] = filters.types.map((p, index) => { - return { value: p, label: p, color: colors[index % colors.length] }; - }); return ( - - - - - - Filters - - - - - - - Protocols: - - { - console.log(x, Array.isArray(x), b); - if (filters.protocols.length === x.length) { - updateFilters('protocols', [ALL_FILTER]); - } else { - updateFilters( - 'protocols', - x.map((p) => p.value), - ); + + {filters.protocols.map((p) => ( + { + console.log('clicked', p.name); + // generated list of protocols selected. All means array of all protocols. + const selectedProtocols = protocolsFilter.includes(ALL_FILTER) + ? filters.protocols.map((p) => p.name) + : protocolsFilter; + + let updatedProtocols = []; + if (selectedProtocols.includes(p.name)) { + updatedProtocols = selectedProtocols.filter((x) => x !== p.name); + } else { + updatedProtocols = [...selectedProtocols, p.name]; + } + if (updatedProtocols.length === filters.protocols.length) { + updatedProtocols = [ALL_FILTER]; + } + console.log('updateFilters', updatedProtocols); + updateFilters('protocols', updatedProtocols); + }} + key={p.name} + _hover={{ + boxShadow: '0px 0px 5px var(--chakra-colors-color1)', + }} + > + + + + + ))} + + {/* Clear all or select all button */} + + + : } + onClick={() => { + updateFilters( + 'protocols', + atleastOneProtocolSelected() ? [] : [ALL_FILTER], + ); + }} + _hover={{ + bg: atleastOneProtocolSelected() ? 'bg' : 'purple', + boxShadow: '0px 0px 5px var(--chakra-colors-color1)', }} /> + + + + ); +} + +export function CategoryFilters() { + const updateFilters = useSetAtom(updateFiltersAtom); + const categoriesFilter = useAtomValue(filterAtoms.categoriesAtom); + const riskLevelFilters = useAtomValue(filterAtoms.riskAtom); + const poolTypeFilters = useAtomValue(filterAtoms.typesAtom); + + function updateCategory(category: Category) { + const existingCategories = categoriesFilter.includes(ALL_FILTER) + ? [] + : categoriesFilter; + console.log('filter34', 'categories', existingCategories); + if (existingCategories.includes(category)) { + const newFilters = existingCategories.filter( + (x) => x !== category.valueOf(), + ); + updateFilters( + 'categories', + newFilters.length === 0 ? [ALL_FILTER] : newFilters, + ); + } else { + updateFilters('categories', [...existingCategories, category.valueOf()]); + } + } + + function updateRiskLevel(riskLevels: string[]) { + let existingRiskLevels = riskLevelFilters.includes(ALL_FILTER) + ? [] + : riskLevelFilters; + console.log('filter34', 'riskLevels', existingRiskLevels); + riskLevels.map((riskLevel) => { + if (existingRiskLevels.includes(riskLevel)) { + const newFilters = existingRiskLevels.filter((x) => x !== riskLevel); + existingRiskLevels = + newFilters.length === 0 ? [ALL_FILTER] : newFilters; + updateFilters('risk', existingRiskLevels); + } else { + existingRiskLevels = [...existingRiskLevels, riskLevel]; + updateFilters('risk', existingRiskLevels); + } + }); + } + + function updatePoolType(types: PoolType[]) { + let existingPoolTypes = poolTypeFilters.includes(ALL_FILTER) + ? [] + : poolTypeFilters; + console.log('filter34', 'poolType', existingPoolTypes); + types.map((type) => { + if (existingPoolTypes.includes(type.valueOf())) { + const newFilters = existingPoolTypes.filter( + (x) => x !== type.valueOf(), + ); + existingPoolTypes = newFilters.length === 0 ? [ALL_FILTER] : newFilters; + updateFilters('poolTypes', existingPoolTypes); + } else { + existingPoolTypes = [...existingPoolTypes, type.valueOf()]; + updateFilters('poolTypes', existingPoolTypes); + } + }); + } + + function isLowRisk() { + return riskLevelFilters.includes('1') || riskLevelFilters.includes('2'); + } + + return ( + + {/* Stable pools */} + { + updateCategory(Category.Stable); + }} + bg={ + categoriesFilter.includes(Category.Stable.valueOf()) + ? 'purple' + : 'color1' + } + marginBottom={'10px'} + > + + + + + + {Category.Stable.valueOf()} + + + + {/* STRK pools */} + { + updateCategory(Category.STRK); + }} + bg={ + categoriesFilter.includes(Category.STRK.valueOf()) + ? 'purple' + : 'color1' + } + marginBottom={'10px'} + > + + + + + {Category.STRK.valueOf()} + + + + {/* Low risk pools */} + { + updateRiskLevel(['1', '2']); + }} + bg={isLowRisk() ? 'purple' : 'color1'} + marginBottom={'10px'} + > + + {['USDC', 'USDT', 'STRK', 'ETH'].map((token, index) => ( + + {index == 0 && } + + ))} + + Low risk + + + {/* DEXes */} + { + updatePoolType([PoolType.DEXV2, PoolType.DEXV3]); + }} + bg={ + poolTypeFilters.includes(PoolType.DEXV2.valueOf()) || + poolTypeFilters.includes(PoolType.DEXV3.valueOf()) + ? 'purple' + : 'color1' + } + marginBottom={'10px'} + > + + DEX Pools + + + + {/* Lending */} + { + updatePoolType([PoolType.Lending]); + }} + bg={poolTypeFilters.includes(PoolType.Lending) ? 'purple' : 'color1'} + marginBottom={'10px'} + > + + Lending Pools + + + + {/* Derivatives */} + { + updatePoolType([PoolType.Derivatives]); + }} + bg={ + poolTypeFilters.includes(PoolType.Derivatives) ? 'purple' : 'color1' + } + marginBottom={'10px'} + > + + Derivative Pools + + - - - - Categories: - - { - console.log(x, Array.isArray(x), b); - updateFilters( - 'categories', - x.map((p) => p.value), - ); - }} - /> - - - - Protocol types: - - { - updateFilters( - 'poolTypes', - x.map((p) => p.value), - ); - }} - /> - - - - - + {/* Reset */} + { + updateFilters('categories', [ALL_FILTER]); + updateFilters('risk', [ALL_FILTER]); + updateFilters('poolTypes', [ALL_FILTER]); + }} + marginBottom={'10px'} + > + + + Reset + + + + ); } diff --git a/src/components/Pools.tsx b/src/components/Pools.tsx index f77375e..da02b3d 100755 --- a/src/components/Pools.tsx +++ b/src/components/Pools.tsx @@ -1,25 +1,19 @@ 'use client'; -import { PoolInfo } from '@/store/pools'; import { - Avatar, - AvatarGroup, Box, - Card, - CardBody, Container, - Flex, - HStack, - Heading, - LinkOverlay, Skeleton, - Spinner, Stack, + Table, + Tbody, Text, - Tooltip, + Th, + Thead, + Tr, } from '@chakra-ui/react'; import { useAtomValue } from 'jotai'; -import { useEffect, useMemo } from 'react'; +import { useMemo } from 'react'; import { Pagination, PaginationContainer, @@ -29,13 +23,13 @@ import { PaginationPage, PaginationPageGroup, } from '@ajna/pagination'; -import Filters from '@/components/Filters'; -import mixpanel from 'mixpanel-browser'; +import { CategoryFilters, ProtocolFilters } from '@/components/Filters'; import { allPoolsAtomUnSorted, filteredPools, sortPoolsAtom, } from '@/store/protocols'; +import YieldCard from './YieldCard'; export default function Pools() { const allPools = useAtomValue(allPoolsAtomUnSorted); @@ -53,150 +47,18 @@ export default function Pools() { const sortedPools = useAtomValue(sortPoolsAtom); - useEffect(() => { - console.log('sortedPools', sortedPools); - }, [sortedPools]); - useEffect(() => { - console.log('pages', currentPage, setCurrentPage, pagesCount, pages); - }, [currentPage, setCurrentPage, pagesCount, pages]); - - function GetRiskLevel(poolName: string) { - let color = ''; - let bgColor = ''; - let count = 0; - let tooltipLabel = ''; - const checkToken = (tokens: string[]) => { - const regex = new RegExp(tokens.join('|'), 'i'); - return regex.test(poolName.toLowerCase()); - }; - - if (checkToken(['eth', 'strk'])) { - color = 'rgba(255, 146, 0, 1)'; - bgColor = 'rgba(255, 146, 0, 0.3)'; - count = 3; - tooltipLabel = 'Medium risk'; - } else if (checkToken(['usdt', 'usdc'])) { - color = 'rgba(131, 241, 77, 1)'; - bgColor = 'rgba(131, 241, 77, 0.3)'; - count = 1; - tooltipLabel = 'Low risk'; - } else { - color = 'rgba(255, 32, 32, 1)'; - bgColor = 'rgba(255, 32, 32, 0.3)'; - count = 5; - tooltipLabel = 'High risk'; - } - - return ( - <> - - - - - - {[...Array(5)].map((_, index) => ( - - ))} - - - - - - - > - ); - } - - function getAPRWithToolTip(pool: PoolInfo) { - const tip = ( - - {pool.aprSplits.map((split) => { - if (split.apr === 0) { - return ( - - {split.title}: {split.description} - - ); - } - return ( - - - {split.title}{' '} - {split.description ? `(${split.description})` : ''} - - - {split.apr === 'Err' ? split.apr : (split.apr * 100).toFixed(2)} - % - - - ); - })} - - ); - return ( - - - {pool.isLoading && } - {!pool.isLoading && ( - <> - - {(pool.apr * 100).toFixed(2)}% - - > - )} - - - ); - } - return ( - - { - + + + + + + - } + - - - - - - Pool - - - STRK APY(%) - - - - - STRK APY(%) - - - Risk - - - TVL($) - - - - - - {pools.length > 0 && ( - - {pools.map((pool, index) => ( - - - - - - { - mixpanel.track('Pool clicked', { - pool: pool.pool.name, - protocol: pool.protocol.name, - yield: pool.apr, - }); - }} - > - - - {pool.pool.logos.map((logo) => ( - - ))} - - {pool.pool.name} - - - - - {pool.protocol.name} - - - - - - {getAPRWithToolTip(pool)} - - - - - {getAPRWithToolTip(pool)} - - {GetRiskLevel(pool.pool.name)} - - ${Math.round(pool.tvl).toLocaleString()} - - - - - - ))} - - )} + + + + Pool name + APY + Risk + TVL + + + + {allPools.length > 0 && ( + <> + {pools.map((pool, index) => { + return ( + + ); + })} + > + )} + + {allPools.length > 0 && pools.length === 0 && ( diff --git a/src/components/Strategies.tsx b/src/components/Strategies.tsx index 789dbfe..a428809 100755 --- a/src/components/Strategies.tsx +++ b/src/components/Strategies.tsx @@ -1,47 +1,30 @@ import CONSTANTS from '@/constants'; -import { StrategyInfo, strategiesAtom } from '@/store/strategies.atoms'; -import { getDisplayCurrencyAmount } from '@/utils'; -import { AddIcon } from '@chakra-ui/icons'; +import { strategiesAtom } from '@/store/strategies.atoms'; import { - Avatar, - Badge, Box, - Button, Container, - Grid, - GridItem, - HStack, - Heading, - Icon, - Image, Link, - Popover, - PopoverArrow, - PopoverBody, - PopoverCloseButton, - PopoverContent, - PopoverTrigger, Skeleton, Stack, Table, Tbody, - Td, Text, Th, Thead, - Tooltip, Tr, } from '@chakra-ui/react'; import { useAtomValue } from 'jotai'; -import React from 'react'; -import mixpanel from 'mixpanel-browser'; +import React, { useMemo } from 'react'; import TVL from './TVL'; -import shield from '@/assets/shield.svg'; import { userStatsAtom } from '@/store/utils.atoms'; -import { IStrategyProps, StrategyLiveStatus } from '@/strategies/IStrategy'; -import { allPoolsAtomUnSorted } from '@/store/protocols'; -import { FaWallet } from 'react-icons/fa'; +import { + allPoolsAtomUnSorted, + filteredPools, + sortPoolsAtom, +} from '@/store/protocols'; import { addressAtom } from '@/store/claims.atoms'; +import { usePagination } from '@ajna/pagination'; +import { YieldStrategyCard } from './YieldCard'; export default function Strategies() { const allPools = useAtomValue(allPoolsAtomUnSorted); @@ -49,361 +32,19 @@ export default function Strategies() { const { data: userData } = useAtomValue(userStatsAtom); const address = useAtomValue(addressAtom); - function getStratCardBg(strat: IStrategyProps, index: number) { - if ( - strat.liveStatus == StrategyLiveStatus.ACTIVE || - strat.liveStatus == StrategyLiveStatus.NEW - ) { - return index % 2 === 0 ? 'color1_50p' : 'color2_50p'; - } - return 'bg'; - } - - function getStratCardBgHover(strat: IStrategyProps, index: number) { - if (strat.liveStatus == StrategyLiveStatus.ACTIVE) { - return index % 2 === 0 ? 'color1_65p' : 'color2_65p'; - } - return 'bg'; - } - - function getStratCardBadgeBg(strat: IStrategyProps) { - if (strat.liveStatus === StrategyLiveStatus.NEW) { - return 'cyan'; - } else if (strat.liveStatus === StrategyLiveStatus.COMING_SOON) { - return 'yellow'; - } - return 'bg'; - } - - function DepositButton(strat: StrategyInfo) { - // const { isOpen, onOpen, onClose } = useDisclosure() - return ( - - - - {strat.description} - - - - { - mixpanel.track('Click one click deposit', { name: strat.name }); - }} - > - - - - - - - - - Thanks for showing interest in{' '} - `One Click Deposit` feature. We are developing this as - you read this message and will be available soon. The button is - to let you know that we will be supporting this soon. 😎 - - - - - Join our Telegram group to get instant updates. Link on the - top. - - - - - - - ); - } + const _filteredPools = useAtomValue(filteredPools); + const ITEMS_PER_PAGE = 15; + const { currentPage, setCurrentPage, pagesCount, pages } = usePagination({ + pagesCount: Math.floor(_filteredPools.length / ITEMS_PER_PAGE) + 1, + initialState: { currentPage: 1 }, + }); - function GetRiskLevel(riskFactor: number) { - let color = ''; - let bgColor = ''; - let count = 0; - let tooltipLabel = ''; + const pools = useMemo(() => { + const startIndex = (currentPage - 1) * ITEMS_PER_PAGE; + return _filteredPools.slice(startIndex, startIndex + ITEMS_PER_PAGE); + }, [_filteredPools, currentPage]); - if (riskFactor <= 2) { - color = 'rgba(131, 241, 77, 1)'; - bgColor = 'rgba(131, 241, 77, 0.3)'; - count = 1; - tooltipLabel = 'Low risk'; - } else if (riskFactor < 4) { - color = 'rgba(255, 146, 0, 1)'; - bgColor = 'rgba(255, 146, 0, 0.3)'; - count = 3; - tooltipLabel = 'Medium risk'; - } else { - color = 'rgba(255, 32, 32, 1)'; - bgColor = 'rgba(255, 32, 32, 0.3)'; - count = 5; - tooltipLabel = 'High risk'; - } - - return ( - - - - - - {[...Array(5)].map((_, index) => ( - - ))} - - - - - - - ); - } - - function getStrategyWiseInfo(id: string) { - const amount = userData?.strategyWise.find((item) => item.id === id); - return amount?.usdValue ? amount?.usdValue : 0; - } - - function StrategyCard(props: { strat: StrategyInfo; index: number }) { - const { strat, index } = props; - - return ( - - - - - - - - {GetRiskLevel(strat.riskFactor)} - - - - - ); - } - - function StrategyInfo(props: { strat: IStrategyProps }) { - const { strat } = props; - return ( - - - - {strat.name} - - {strat.liveStatus != StrategyLiveStatus.ACTIVE && ( - - {strat.liveStatus.valueOf()} - - )} - {strat.isLive() && ( - - - - - - - - )} - - ); - } - - function StrategyAPY(props: { strat: IStrategyProps }) { - const { strat } = props; - return ( - - - - {(strat.netYield * 100).toFixed(2)}% - - - - - - - ⚡ - - - {strat.leverage.toFixed(1)}X - - - - - - ); - } - - function StrategyTVL(props: { strat: IStrategyProps }) { - const { strat } = props; - const tvlInfo = useAtomValue(props.strat.tvlAtom); - - return ( - - {strat.isLive() && ( - - ${getDisplayCurrencyAmount(tvlInfo.data?.usdValue || 0, 0)} - - )} - {!strat.isLive() && -} - {address && strat.isLive() && ( - - <> - - - $ - {Math.round(getStrategyWiseInfo(strat.id)).toLocaleString()} - - - > - - )} - - ); - } - - function StrategyMobileCard(props: { strat: StrategyInfo; index: number }) { - const { strat, index } = props; - return ( - - - - - - - APY - - - - - - RISK - - {GetRiskLevel(strat.riskFactor)} - - - - TVL - - - - - ); - } + const sortedPools = useAtomValue(sortPoolsAtom); return ( @@ -430,12 +71,15 @@ export default function Strategies() { {allPools.length > 0 && strategies.length > 0 && ( <> - {strategies.map((strat, index) => ( - <> - - - > - ))} + {strategies.map((strat, index) => { + return ( + + ); + })} > )} diff --git a/src/components/YieldCard.tsx b/src/components/YieldCard.tsx new file mode 100644 index 0000000..f87b35c --- /dev/null +++ b/src/components/YieldCard.tsx @@ -0,0 +1,462 @@ +import CONSTANTS from '@/constants'; +import { PoolInfo } from '@/store/pools'; +import { + Avatar, + AvatarGroup, + Badge, + Box, + Flex, + Grid, + GridItem, + Heading, + HStack, + Icon, + Image, + Link, + Spinner, + Stack, + Td, + Text, + Tooltip, + Tr, +} from '@chakra-ui/react'; +import shield from '@/assets/shield.svg'; +import { IStrategyProps, StrategyLiveStatus } from '@/strategies/IStrategy'; +import { useAtomValue } from 'jotai'; +import { getDisplayCurrencyAmount } from '@/utils'; +import { addressAtom } from '@/store/claims.atoms'; +import { FaWallet } from 'react-icons/fa'; +import { UserStats, userStatsAtom } from '@/store/utils.atoms'; +import { getPoolInfoFromStrategy } from '@/store/protocols'; + +interface YieldCardProps { + pool: PoolInfo; + index: number; + showProtocolName?: boolean; +} + +function getStratCardBg(status: StrategyLiveStatus, index: number) { + if (status == StrategyLiveStatus.ACTIVE || status == StrategyLiveStatus.NEW) { + return index % 2 === 0 ? 'color1_50p' : 'color2_50p'; + } + return 'bg'; +} + +function getStratCardBadgeBg(status: StrategyLiveStatus) { + if (status === StrategyLiveStatus.NEW) { + return 'cyan'; + } else if (status === StrategyLiveStatus.COMING_SOON) { + return 'yellow'; + } + return 'bg'; +} + +function StrategyInfo(props: YieldCardProps) { + const { pool } = props; + return ( + + + + {pool.pool.logos.map((logo) => ( + + ))} + + + + + {pool.pool.name} + + {pool.additional && + pool.additional.tags + .filter((tag) => tag != StrategyLiveStatus.ACTIVE) + .map((tag) => { + return ( + + {tag} + + ); + })} + ; + {pool.additional && pool.additional.isAudited && ( + + + + + + + + )} + + {props.showProtocolName && ( + + + + {pool.protocol.name} + + + )} + + + + ); +} + +function getAPRWithToolTip(pool: PoolInfo) { + const tip = ( + + {pool.aprSplits.map((split) => { + if (split.apr === 0) { + return ( + + {split.title}: {split.description} + + ); + } + return ( + + + {split.title} {split.description ? `(${split.description})` : ''} + + + {split.apr === 'Err' ? split.apr : (split.apr * 100).toFixed(2)}% + + + ); + })} + + ); + return ( + + + {pool.isLoading && } + {!pool.isLoading && ( + <> + + {(pool.apr * 100).toFixed(2)}% + + > + )} + + + ); +} + +function StrategyAPY(props: YieldCardProps) { + const { pool } = props; + return ( + + {getAPRWithToolTip(pool)} + {pool.additional && pool.additional.leverage && ( + + + + + ⚡ + + + {pool.additional.leverage.toFixed(1)}X + + + + + )} + + ); +} + +function isLive(status: StrategyLiveStatus) { + return ( + status === StrategyLiveStatus.ACTIVE || status === StrategyLiveStatus.NEW + ); +} + +function getStrategyWiseInfo( + userData: UserStats | null | undefined, + id: string, +) { + const amount = userData?.strategyWise.find((item) => item.id === id); + return amount?.usdValue ? amount?.usdValue : 0; +} + +function StrategyTVL(props: YieldCardProps) { + const { pool } = props; + const address = useAtomValue(addressAtom); + const { data: userData } = useAtomValue(userStatsAtom); + + const isPoolLive = + pool.additional && + pool.additional.tags[0] && + isLive(pool.additional.tags[0]); + return ( + + {isPoolLive && ( + + ${getDisplayCurrencyAmount(pool.tvl || 0, 0)} + + )} + {!isPoolLive && -} + {address && isPoolLive && pool.protocol.name == 'STRKFarm' && ( + + <> + + + $ + {Math.round( + getStrategyWiseInfo(userData, pool.pool.id), + ).toLocaleString()} + + + > + + )} + + ); +} + +function GetRiskLevel(riskFactor: number) { + let color = ''; + let bgColor = ''; + let count = 0; + let tooltipLabel = ''; + + if (riskFactor <= 2) { + color = 'rgba(131, 241, 77, 1)'; + bgColor = 'rgba(131, 241, 77, 0.3)'; + count = 1; + tooltipLabel = 'Low risk'; + } else if (riskFactor < 4) { + color = 'rgba(255, 146, 0, 1)'; + bgColor = 'rgba(255, 146, 0, 0.3)'; + count = 3; + tooltipLabel = 'Medium risk'; + } else { + color = 'rgba(255, 32, 32, 1)'; + bgColor = 'rgba(255, 32, 32, 0.3)'; + count = 5; + tooltipLabel = 'High risk'; + } + + return ( + + + + + + {[...Array(5)].map((_, index) => ( + + ))} + + + + + + + ); +} + +function StrategyMobileCard(props: YieldCardProps) { + const { pool, index } = props; + return ( + + + + + + + APY + + + + + + RISK + + {pool.additional?.riskFactor + ? GetRiskLevel(pool.additional?.riskFactor) + : '-'} + + + + TVL + + + + + ); +} + +export default function YieldCard(props: YieldCardProps) { + const { pool, index } = props; + + return ( + <> + + + + + + + + + {pool.additional?.riskFactor + ? GetRiskLevel(pool.additional?.riskFactor) + : '-'} + + + + + + + > + ); +} + +export function YieldStrategyCard(props: { + strat: IStrategyProps; + index: number; +}) { + const tvlInfo = useAtomValue(props.strat.tvlAtom); + const pool = getPoolInfoFromStrategy( + props.strat, + tvlInfo.data?.usdValue || 0, + ); + + return ; +} diff --git a/src/store/IDapp.store.ts b/src/store/IDapp.store.ts index d4aff39..bd995b9 100755 --- a/src/store/IDapp.store.ts +++ b/src/store/IDapp.store.ts @@ -75,4 +75,12 @@ export class IDapp { // return !poolName.includes('DAI') && !poolName.includes('WSTETH') && !poolName.includes('BTC'); return supportedPools.includes(poolName); } + + getPoolId(protocol: string, poolName: string) { + return getPoolId(protocol, poolName); + } +} + +export function getPoolId(protocol: string, poolName: string) { + return `${protocol.toLowerCase().replaceAll(' ', '_')}__${poolName.toLowerCase().replaceAll(' ', '_')}`; } diff --git a/src/store/carmine.store.ts b/src/store/carmine.store.ts index afce7de..525fe58 100755 --- a/src/store/carmine.store.ts +++ b/src/store/carmine.store.ts @@ -4,6 +4,7 @@ import { atom } from 'jotai'; import { PoolInfo, ProtocolAtoms } from './pools'; import { Jediswap } from './jedi.store'; import { atomWithQuery } from 'jotai-tanstack-query'; +import { StrategyLiveStatus } from '@/strategies/IStrategy'; const poolConfigs = [ { name: 'STRK/USDC Call Pool (STRK)', tokenA: 'STRK', tokenB: 'USDC' }, @@ -34,6 +35,7 @@ export class Carmine extends Jediswap { if (!poolData || !poolData.data) return; let category: Category; + const riskFactor = 3; if (config.name.endsWith('(USDC)')) { category = Category.Stable; } else if (config.name.endsWith('(STRK)')) { @@ -55,6 +57,7 @@ export class Carmine extends Jediswap { const poolInfo: PoolInfo = { pool: { + id: this.getPoolId(this.name, config.name), name: config.name, logos: [logo1, logo2], }, @@ -86,6 +89,11 @@ export class Carmine extends Jediswap { borrowFactor: 0, apr: 0, }, + additional: { + tags: [StrategyLiveStatus.ACTIVE], + isAudited: false, // TODO: Update this + riskFactor, + }, }; pools.push(poolInfo); }); diff --git a/src/store/ekobu.store.ts b/src/store/ekobu.store.ts index 636fdeb..792bd2a 100755 --- a/src/store/ekobu.store.ts +++ b/src/store/ekobu.store.ts @@ -11,6 +11,7 @@ import { ProtocolAtoms, StrkDexIncentivesAtom, } from './pools'; +import { StrategyLiveStatus } from '@/strategies/IStrategy'; const _fetcher = async (...args: any[]) => { return fetch(args[0], args[1]).then((res) => res.json()); @@ -110,8 +111,10 @@ export class Ekubo extends IDapp { .forEach((poolName) => { const arr = myData[poolName]; let category = Category.Others; + let riskFactor = 3; if (poolName === 'USDC/USDT') { category = Category.Stable; + riskFactor = 0.5; } else if (poolName.includes('STRK')) { category = Category.STRK; } @@ -122,6 +125,7 @@ export class Ekubo extends IDapp { const poolInfo: PoolInfo = { pool: { + id: this.getPoolId(this.name, poolName), name: poolName, logos: [logo1, logo2], }, @@ -148,6 +152,11 @@ export class Ekubo extends IDapp { borrowFactor: 0, apr: 0, }, + additional: { + tags: [StrategyLiveStatus.ACTIVE], + riskFactor, + isAudited: false, // TODO: Update this + }, }; const { rewardAPY } = this.getBaseAPY(poolInfo, baseInfo); diff --git a/src/store/haiko.store.ts b/src/store/haiko.store.ts index 40ab86e..8483a78 100755 --- a/src/store/haiko.store.ts +++ b/src/store/haiko.store.ts @@ -11,6 +11,7 @@ import { import { atom } from 'jotai'; import { AtomWithQueryResult, atomWithQuery } from 'jotai-tanstack-query'; import { IDapp } from './IDapp.store'; +import { StrategyLiveStatus } from '@/strategies/IStrategy'; type Token = { address: string; @@ -61,8 +62,10 @@ export class Haiko extends IDapp { .forEach((poolName) => { const arr = myData[poolName]; let category = Category.Others; + let riskFactor = 3; if (poolName === 'USDC/USDT') { category = Category.Stable; + riskFactor = 0.5; } else if (poolName.includes('STRK')) { category = Category.STRK; } @@ -73,6 +76,7 @@ export class Haiko extends IDapp { const poolInfo: PoolInfo = { pool: { + id: this.getPoolId(this.name, poolName), name: poolName, logos: [logo1, logo2], }, @@ -99,6 +103,11 @@ export class Haiko extends IDapp { borrowFactor: 0, apr: 0, }, + additional: { + tags: [StrategyLiveStatus.ACTIVE], + isAudited: false, // TODO: Update this + riskFactor, + }, }; pools.push(poolInfo); }); diff --git a/src/store/hashstack.store.ts b/src/store/hashstack.store.ts index 19203a5..4d32a3d 100755 --- a/src/store/hashstack.store.ts +++ b/src/store/hashstack.store.ts @@ -10,6 +10,7 @@ import { atom } from 'jotai'; import { IDapp } from './IDapp.store'; import { LendingSpace } from './lending.base'; import { AtomWithQueryResult } from 'jotai-tanstack-query'; +import { StrategyLiveStatus } from '@/strategies/IStrategy'; export class Hashstack extends IDapp { name = 'Hashstack'; @@ -39,6 +40,7 @@ export class Hashstack extends IDapp { const poolInfo: PoolInfo = { pool: { + id: this.getPoolId(this.name, poolName), name: poolName, logos: [logo1], }, @@ -70,6 +72,11 @@ export class Hashstack extends IDapp { borrowFactor: 0, apr: 0, }, + additional: { + tags: [StrategyLiveStatus.ACTIVE], + isAudited: false, // TODO: Update this + riskFactor: 0.5, + }, }; pools.push(poolInfo); }); diff --git a/src/store/jedi.store.ts b/src/store/jedi.store.ts index 8c6c0b1..fac01af 100755 --- a/src/store/jedi.store.ts +++ b/src/store/jedi.store.ts @@ -12,6 +12,7 @@ import { atom } from 'jotai'; import { IDapp } from './IDapp.store'; import { AtomWithQueryResult, atomWithQuery } from 'jotai-tanstack-query'; import { BlockInfo, getBlock } from './utils.atoms'; +import { StrategyLiveStatus } from '@/strategies/IStrategy'; interface MyBaseAprDoc { id: string; @@ -45,12 +46,12 @@ export class Jediswap extends IDapp { const arr = myData[poolName]; let category = Category.Others; + let riskFactor = 3; if (poolName === 'USDC/USDT') { category = Category.Stable; + riskFactor = 0.5; } else if (poolName.includes('STRK')) { category = Category.STRK; - } else if (poolName.includes('DEGEN')) { - category = Category.Degen; } const tokens: TokenName[] = poolName.split('/'); @@ -58,6 +59,7 @@ export class Jediswap extends IDapp { const logo2 = CONSTANTS.LOGOS[tokens[1]]; const poolInfo: PoolInfo = { pool: { + id: this.getPoolId(this.name, poolName), name: poolName, logos: [logo1, logo2], }, @@ -84,6 +86,11 @@ export class Jediswap extends IDapp { borrowFactor: 0, apr: 0, }, + additional: { + riskFactor, + tags: [StrategyLiveStatus.ACTIVE], + isAudited: false, // TODO: Update this + }, }; pools.push(poolInfo); }); diff --git a/src/store/lending.base.ts b/src/store/lending.base.ts index 1e4921c..11179fc 100755 --- a/src/store/lending.base.ts +++ b/src/store/lending.base.ts @@ -1,7 +1,8 @@ import CONSTANTS, { TokenName } from '@/constants'; import { APRSplit, Category, PoolInfo, PoolMetadata, PoolType } from './pools'; import { AtomWithQueryResult } from 'jotai-tanstack-query'; -import { StrategyAction } from '@/strategies/IStrategy'; +import { StrategyAction, StrategyLiveStatus } from '@/strategies/IStrategy'; +import { getPoolId } from './IDapp.store'; export namespace LendingSpace { export interface MyBaseAprDoc { @@ -66,6 +67,7 @@ export namespace LendingSpace { const poolInfo: PoolInfo = { pool: { + id: getPoolId(info.name, poolName), name: poolName, logos: [logo1], }, @@ -92,6 +94,11 @@ export namespace LendingSpace { lending: { collateralFactor: 0, }, + additional: { + tags: [StrategyLiveStatus.ACTIVE], + isAudited: false, // TODO: Update this + riskFactor: 0.5, + }, }; pools.push(poolInfo); }); diff --git a/src/store/myswap.store.ts b/src/store/myswap.store.ts index 615e1fe..bcf4d8c 100755 --- a/src/store/myswap.store.ts +++ b/src/store/myswap.store.ts @@ -11,6 +11,7 @@ import { atom } from 'jotai'; import { IDapp } from './IDapp.store'; import CONSTANTS, { TokenName } from '@/constants'; import { AtomWithQueryResult, atomWithQuery } from 'jotai-tanstack-query'; +import { StrategyLiveStatus } from '@/strategies/IStrategy'; interface Token { symbol: string; @@ -76,8 +77,10 @@ export class MySwap extends IDapp { .forEach((poolName) => { const arr = myData[poolName]; let category = Category.Others; + let riskFactor = 3; if (poolName === 'USDC/USDT') { category = Category.Stable; + riskFactor = 0.5; } else if (poolName.includes('STRK')) { category = Category.STRK; } @@ -88,6 +91,7 @@ export class MySwap extends IDapp { const poolInfo: PoolInfo = { pool: { + id: this.getPoolId(this.name, poolName), name: poolName, logos: [logo1, logo2], }, @@ -114,6 +118,11 @@ export class MySwap extends IDapp { borrowFactor: 0, apr: 0, }, + additional: { + tags: [StrategyLiveStatus.ACTIVE], + isAudited: false, // TODO: Update this + riskFactor, + }, }; pools.push(poolInfo); }); diff --git a/src/store/nostradegen.store.ts b/src/store/nostradegen.store.ts index cf62bda..5a58325 100755 --- a/src/store/nostradegen.store.ts +++ b/src/store/nostradegen.store.ts @@ -3,9 +3,10 @@ import { Category, PoolType } from './pools'; import { atom } from 'jotai'; import { PoolInfo, ProtocolAtoms, StrkIncentivesAtom } from './pools'; import { Jediswap } from './jedi.store'; +import { StrategyLiveStatus } from '@/strategies/IStrategy'; export class NostraDegen extends Jediswap { - name = 'Nostra DEGEN'; + name = 'Nostra'; link = 'https://app.nostra.finance/pools'; logo = 'https://static-assets-8zct.onrender.com/integrations/nostra/logo_dark.jpg'; @@ -19,16 +20,20 @@ export class NostraDegen extends Jediswap { Object.entries(myData) .filter(([_, poolData]: any) => poolData.isDegen) .forEach(([poolName, poolData]: any) => { - const category = Category.Others; const tokens: TokenName[] = [poolData.tokenA, poolData.tokenB]; const logo1 = CONSTANTS.LOGOS[tokens[0]]; const logo2 = CONSTANTS.LOGOS[tokens[1]]; const baseApr = poolData.baseApr === '0' ? 0.0 : parseFloat(poolData.baseApr); const rewardApr = parseFloat(poolData.rewardApr); + const isStrkPool = poolData.id.includes('STRK'); + const category = isStrkPool ? Category.STRK : Category.Others; + + const _poolName = poolData.id; const poolInfo: PoolInfo = { pool: { - name: poolData.id.slice(0, -6), + id: this.getPoolId(this.name, _poolName), + name: _poolName, logos: [logo1, logo2], }, protocol: { @@ -59,6 +64,11 @@ export class NostraDegen extends Jediswap { borrowFactor: 0, apr: 0, }, + additional: { + tags: [StrategyLiveStatus.ACTIVE], + isAudited: false, // TODO: Update this + riskFactor: 3, + }, }; pools.push(poolInfo); }); diff --git a/src/store/nostradex.store.ts b/src/store/nostradex.store.ts index d2b9b78..aa17ed0 100755 --- a/src/store/nostradex.store.ts +++ b/src/store/nostradex.store.ts @@ -3,9 +3,10 @@ import { Category, PoolType } from './pools'; import { atom } from 'jotai'; import { PoolInfo, ProtocolAtoms, StrkIncentivesAtom } from './pools'; import { Jediswap } from './jedi.store'; +import { StrategyLiveStatus } from '@/strategies/IStrategy'; export class NostraDex extends Jediswap { - name = 'Nostra DEX'; + name = 'Nostra'; link = 'https://app.nostra.finance/pools'; logo = 'https://static-assets-8zct.onrender.com/integrations/nostra/logo_dark.jpg'; @@ -17,22 +18,32 @@ export class NostraDex extends Jediswap { if (!myData) return []; const pools: PoolInfo[] = []; + const supportedPools = ['ETH-USDC', 'STRK-ETH', 'STRK-USDC', 'USDC-USDT']; // Filter and map only the required pools Object.values(myData) .filter((poolData: any) => { const id = poolData.id; - return id === 'ETH-USDC' || id === 'STRK-ETH' || id === 'STRK-USDC'; + return supportedPools.includes(id); }) .forEach((poolData: any) => { - const category = Category.Others; const tokens: TokenName[] = [poolData.tokenA, poolData.tokenB]; const logo1 = CONSTANTS.LOGOS[tokens[0]]; const logo2 = CONSTANTS.LOGOS[tokens[1]]; const baseApr = poolData.baseApr === '0' ? 0.0 : parseFloat(poolData.baseApr); const rewardApr = parseFloat(poolData.rewardApr); + + let category = Category.Others; + let riskFactor = 3; + if (poolData.id === 'USDC-USDT') { + category = Category.Stable; + riskFactor = 0.5; + } else if (poolData.id.includes('STRK')) { + category = Category.STRK; + } const poolInfo: PoolInfo = { pool: { + id: this.getPoolId(this.name, poolData.id.slice(0, -6)), name: poolData.id, logos: [logo1, logo2], }, @@ -64,6 +75,11 @@ export class NostraDex extends Jediswap { borrowFactor: 0, apr: 0, }, + additional: { + riskFactor, + tags: [StrategyLiveStatus.ACTIVE], + isAudited: false, // TODO: Update this + }, }; pools.push(poolInfo); }); diff --git a/src/store/nostralending.store.ts b/src/store/nostralending.store.ts index 05f0426..1963c2d 100755 --- a/src/store/nostralending.store.ts +++ b/src/store/nostralending.store.ts @@ -64,7 +64,7 @@ const PoolAddresses: { [token: string]: NostraPoolFactor } = { }; export class NostraLending extends IDapp { - name = 'Nostra Money Markets'; + name = 'Nostra'; link = 'https://app.nostra.finance/'; logo = 'https://static-assets-8zct.onrender.com/integrations/nostra/logo_dark.jpg'; diff --git a/src/store/pools.ts b/src/store/pools.ts index 52f9e62..e6c0d60 100755 --- a/src/store/pools.ts +++ b/src/store/pools.ts @@ -3,11 +3,11 @@ import { Atom, atom } from 'jotai'; import { AtomWithQueryResult, atomWithQuery } from 'jotai-tanstack-query'; import { CustomAtomWithQueryResult } from '@/utils/customAtomWithQuery'; import { customAtomWithFetch } from '@/utils/customAtomWithFetch'; +import { StrategyLiveStatus } from '@/strategies/IStrategy'; export enum Category { Stable = 'Stable Pools', STRK = 'STRK Pools', - Degen = 'MetaStable Pools', Others = 'Others', } @@ -36,6 +36,7 @@ export interface PoolMetadata { export interface PoolInfo extends PoolMetadata { pool: { + id: string; name: string; logos: string[]; }; @@ -50,6 +51,12 @@ export interface PoolInfo extends PoolMetadata { category: Category; type: PoolType; isLoading?: boolean; + additional: { + leverage?: number; + riskFactor: number; + tags: StrategyLiveStatus[]; + isAudited: boolean; + }; } export interface ProtocolAtoms { diff --git a/src/store/protocols.ts b/src/store/protocols.ts index 41e29ca..859fc99 100644 --- a/src/store/protocols.ts +++ b/src/store/protocols.ts @@ -3,7 +3,6 @@ import HaikoAtoms, { haiko } from './haiko.store'; import HashstackAtoms, { hashstack } from './hashstack.store'; import JediAtoms, { jedi } from './jedi.store'; import MySwapAtoms, { mySwap } from './myswap.store'; -import NimboraAtoms, { nimbora } from './nimbora.store'; import NostraDexAtoms, { nostraDex } from './nostradex.store'; import NostraDegenAtoms, { nostraDegen } from './nostradegen.store'; import NostraLendingAtoms, { nostraLending } from './nostralending.store'; @@ -14,6 +13,9 @@ import ZkLendAtoms, { zkLend } from './zklend.store'; import CarmineAtoms, { carmine } from './carmine.store'; import { atom } from 'jotai'; import { Category, PoolInfo, PoolType } from './pools'; +import { strategiesAtom } from './strategies.atoms'; +import strkfarmLogo from '@public/logo.png'; +import { IStrategyProps } from '@/strategies/IStrategy'; export const PROTOCOLS = [ { @@ -81,24 +83,38 @@ export const PROTOCOLS = [ class: hashstack, atoms: HashstackAtoms, }, - { - name: nimbora.name, - class: nimbora, - atoms: NimboraAtoms, - }, + // { + // name: nimbora.name, + // class: nimbora, + // atoms: NimboraAtoms, + // } ]; export const ALL_FILTER = 'All'; + +const allProtocols = [ + { + name: 'STRKFarm', + logo: strkfarmLogo.src, + }, + ...PROTOCOLS.map((p) => ({ + name: p.name, + logo: p.class.logo, + })), +]; export const filters = { categories: [...Object.values(Category)], types: [...Object.values(PoolType)], - protocols: [...PROTOCOLS.map((p) => p.name)], + protocols: allProtocols.filter( + (p, index) => allProtocols.findIndex((_p) => _p.name == p.name) == index, + ), }; export const filterAtoms = { categoriesAtom: atom([ALL_FILTER]), typesAtom: atom([ALL_FILTER]), protocolsAtom: atom([ALL_FILTER]), + riskAtom: atom([ALL_FILTER]), }; export const updateFiltersAtom = atom( @@ -106,15 +122,18 @@ export const updateFiltersAtom = atom( ( get, set, - type: 'categories' | 'poolTypes' | 'protocols', + type: 'categories' | 'poolTypes' | 'protocols' | 'risk', newOptions: string[], ) => { + console.log(`filter33`, type, newOptions); if (type === 'categories') { set(filterAtoms.categoriesAtom, newOptions); } else if (type === 'poolTypes') { set(filterAtoms.typesAtom, newOptions); } else if (type === 'protocols') { set(filterAtoms.protocolsAtom, newOptions); + } else if (type === 'risk') { + set(filterAtoms.riskAtom, newOptions); } }, ); @@ -127,21 +146,88 @@ export const allPoolsAtomUnSorted = atom((get) => { ); }); +export function getPoolInfoFromStrategy( + strat: IStrategyProps, + tvlInfo: number, +): PoolInfo { + let category = Category.Others; + if (strat.name.includes('STRK')) { + category = Category.STRK; + } else if (strat.name.includes('USDC')) { + category = Category.Stable; + } + return { + pool: { + id: strat.id, + name: strat.name, + logos: [strat.holdingTokens[0].logo], + }, + protocol: { + name: 'STRKFarm', + link: `/strategy/${strat.id}`, + logo: strkfarmLogo.src, + }, + tvl: tvlInfo, + apr: strat.netYield, + aprSplits: [ + { + apr: strat.netYield, + title: 'Net Yield', + description: 'Includes fees & Defi spring rewards', + }, + ], + category, + type: PoolType.Derivatives, + borrow: { + apr: 0, + borrowFactor: 0, + }, + lending: { + collateralFactor: 0, + }, + additional: { + riskFactor: strat.riskFactor, + tags: [strat.liveStatus], + isAudited: true, + leverage: strat.leverage, + }, + }; +} + +export const allPoolsAtomWithStrategiesUnSorted = atom((get) => { + const pools: PoolInfo[] = get(allPoolsAtomUnSorted); + const strategies = get(strategiesAtom); + const strategyPools: PoolInfo[] = strategies.map((strategy) => { + const tvlInfo = get(strategy.tvlAtom); + return getPoolInfoFromStrategy(strategy, tvlInfo.data?.usdValue || 0); + }); + return strategyPools.concat(pools); +}); + // const allPoolsAtom = atom([]); -const SORT_OPTIONS = ['APR', 'TVL']; +const SORT_OPTIONS = ['DEFAULT', 'APR', 'TVL', 'RISK']; export const sortAtom = atom(SORT_OPTIONS[0]); export const sortPoolsAtom = atom((get) => { - const pools = get(allPoolsAtomUnSorted); + const pools = get(allPoolsAtomWithStrategiesUnSorted); console.log('pre sort', pools); const sortOption = get(sortAtom); pools.sort((a, b) => { - if (sortOption === SORT_OPTIONS[1]) { + if (sortOption === SORT_OPTIONS[2]) { return b.tvl - a.tvl; + } else if (sortOption === SORT_OPTIONS[3]) { + return b.additional.riskFactor - a.additional.riskFactor; + } else if (sortOption === SORT_OPTIONS[1]) { + return b.apr - a.apr; } - return b.apr - a.apr; + // sort by risk factor, then sort by apr + // rounding to sync with risk signals shown on UI + return ( + Math.round(a.additional.riskFactor) - + Math.round(b.additional.riskFactor) || b.apr - a.apr + ); }); return pools; }); @@ -151,6 +237,8 @@ export const filteredPools = atom((get) => { const categories = get(filterAtoms.categoriesAtom); const types = get(filterAtoms.typesAtom); const protocols = get(filterAtoms.protocolsAtom); + const riskLevels = get(filterAtoms.riskAtom); + console.log(`risk_levels`, riskLevels); return pools .filter((pool) => { // category filter @@ -169,5 +257,16 @@ export const filteredPools = atom((get) => { if (protocols.includes(ALL_FILTER)) return true; if (protocols.includes(pool.protocol.name)) return true; return false; + }) + .filter((pool) => { + // risk filter + if (riskLevels.includes(ALL_FILTER)) return true; + if ( + riskLevels.includes( + Math.round(pool.additional.riskFactor).toFixed(0).toString(), + ) + ) + return true; + return false; }); }); diff --git a/src/store/strategies.atoms.ts b/src/store/strategies.atoms.ts index 7be4a04..6cf7b1d 100755 --- a/src/store/strategies.atoms.ts +++ b/src/store/strategies.atoms.ts @@ -94,14 +94,12 @@ export const STRATEGIES_INFO = getStrategies(); export const strategiesAtom = atom((get) => { const strategies = getStrategies(); const allPools = get(allPoolsAtomUnSorted); - const filteredPools = allPools.filter( - (p) => - p.protocol.name === 'zkLend' || - p.protocol.name === 'Nostra Money Markets', + const requiredPools = allPools.filter( + (p) => p.protocol.name === 'zkLend' || p.protocol.name === 'Nostra', ); for (const s of strategies) { - s.solve(filteredPools, '1000'); + s.solve(requiredPools, '1000'); } strategies.sort((a, b) => { diff --git a/src/store/utils.atoms.ts b/src/store/utils.atoms.ts index 5a916ac..7c78f45 100755 --- a/src/store/utils.atoms.ts +++ b/src/store/utils.atoms.ts @@ -82,7 +82,7 @@ interface StrategyWise { amount: string; } -interface UserStats { +export interface UserStats { holdingsUSD: number; strategyWise: StrategyWise[]; }