diff --git a/package.json b/package.json index c60ef74552..a6c7f63804 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "@walletconnect/jsonrpc-provider": "1.0.3" }, "devDependencies": { + "@apollo/client": "^3.6.8", "@babel/preset-react": "^7.16.7", "@binance-chain/bsc-connector": "^1.0.0", "@builder.io/partytown": "^0.5.2", @@ -115,6 +116,7 @@ "@types/js-cookie": "^3.0.1", "@types/ms.macro": "^2.0.0", "@types/node": "^16.11.0", + "@types/numeral": "^2.0.2", "@types/qs": "^6.9.7", "@types/react": "^17.0.2", "@types/react-slider": "^1.3.1", @@ -165,6 +167,8 @@ "graphql": "^15.5.3", "graphql-request": "^3.5.0", "graphql-tag": "^2.12.5", + "highcharts": "^10.1.0", + "highcharts-react-official": "^3.1.0", "husky": "^7.0.0", "identity-obj-proxy": "^3.0.0", "ioredis": "^4.27.7", @@ -178,6 +182,7 @@ "lottie-react": "2.1.0", "madge": "^5.0.1", "millify": "^4.0.0", + "moment": "^2.29.3", "ms.macro": "^2.0.0", "next": "^12.1.5", "next-pwa": "5.4.7", @@ -185,7 +190,7 @@ "next-unused": "^0.0.6", "ngrok": "^4.3.1", "node-vibrant": "3.1.6", - "numeral": "^2.0.0", + "numeral": "^2.0.6", "polished": "^4.1.0", "postcss": "^8.4.0", "postcss-flexbugs-fixes": "^5.0.2", @@ -198,6 +203,7 @@ "react-dom": "^17.0.2", "react-dropzone": "^12.0.4", "react-feather": "^2.0.9", + "react-icons": "^4.4.0", "react-hook-form": "7.29.0", "react-infinite-scroll-component": "^6.1.0", "react-popper": "^2.2.5", diff --git a/public/images/tokens/icon-quiz.jpg b/public/images/tokens/icon-quiz.jpg new file mode 100644 index 0000000000..f957b607d6 Binary files /dev/null and b/public/images/tokens/icon-quiz.jpg differ diff --git a/src/components/Header/useMenu.tsx b/src/components/Header/useMenu.tsx index 01d9e1de63..74aa3cd603 100644 --- a/src/components/Header/useMenu.tsx +++ b/src/components/Header/useMenu.tsx @@ -275,6 +275,16 @@ const useMenu: UseMenu = () => { title: 'Pools', link: `/analytics/pools`, }, + { + key: 'kashi', + title: 'Kashi Pairs', + link: `/analytics/kashi/pairs`, + }, + { + key: 'kashi', + title: 'Kashi Tokens', + link: `/analytics/kashi/tokens`, + }, ], } diff --git a/src/features/analytics/kashi/components/KashiPairTotalCardEx.tsx b/src/features/analytics/kashi/components/KashiPairTotalCardEx.tsx new file mode 100644 index 0000000000..fa7564e74c --- /dev/null +++ b/src/features/analytics/kashi/components/KashiPairTotalCardEx.tsx @@ -0,0 +1,125 @@ +import { useLingui } from '@lingui/react' +import KashiMediumRiskLendingPair from 'app/features/kashi/KashiMediumRiskLendingPair' +import { formatNumber } from 'app/functions' +import classNames from 'classnames' + +import Progress from './Progress' + +type AttributesByBorrowType = { + progressColor: 'green' | 'pink' | 'blue' + title: string + users: string +} + +type AttributesMapByBorrowType = { + borrow: AttributesByBorrowType + asset: AttributesByBorrowType + supply: AttributesByBorrowType +} + +const KashiPairTotalCard = ({ + containerClass = '', + data, + borrow = 'borrow', + loading = false, +}: { + containerClass?: string + data: KashiMediumRiskLendingPair[] + borrow?: 'borrow' | 'asset' | 'supply' + loading?: boolean +}) => { + const { i18n } = useLingui() + const AttributesMapByBorrow = { + borrow: { + progressColor: 'pink', + title: i18n._('Total Borrow'), + }, + asset: { + progressColor: 'green', + title: i18n._('Total Available'), + }, + supply: { + progressColor: 'blue', + title: i18n._('Total Supply'), + }, + } as AttributesMapByBorrowType + + const attributes = AttributesMapByBorrow[borrow] + const isLoading = loading || !data + const amount = data.reduce( + ( + value: { totalAssetAmountUSD: number; currentAllAssetsUSD: number; currentBorrowAmountUSD: number }, + market: KashiMediumRiskLendingPair + ) => { + value.currentAllAssetsUSD += Number(market.currentAllAssetsUSD?.toFixed(0)) || 0 + value.currentBorrowAmountUSD += Number(market.currentBorrowAmountUSD?.toFixed(0)) || 0 + value.totalAssetAmountUSD += Number(market.totalAssetAmountUSD?.toFixed(0)) || 0 + return value + }, + { totalAssetAmountUSD: 0, currentAllAssetsUSD: 0, currentBorrowAmountUSD: 0 } + ) + + const sortFunc = { + borrow: (a: KashiMediumRiskLendingPair, b: KashiMediumRiskLendingPair) => + (Number(b.currentBorrowAmountUSD?.toFixed(0)) || 0) - (Number(a.currentBorrowAmountUSD?.toFixed(0)) || 0), + asset: (a: KashiMediumRiskLendingPair, b: KashiMediumRiskLendingPair) => + (Number(b.totalAssetAmountUSD?.toFixed(0)) || 0) - (Number(a.totalAssetAmountUSD?.toFixed(0)) || 0), + supply: (a: KashiMediumRiskLendingPair, b: KashiMediumRiskLendingPair) => + (Number(b.currentAllAssetsUSD?.toFixed(0)) || 0) - (Number(a.currentAllAssetsUSD?.toFixed(0)) || 0), + } + const top3 = data.length > 3 ? [...data].sort(sortFunc[borrow]).slice(0, 3) : [...data] + + return ( +
+
{attributes.title}
+
+
+ {isLoading ? ( +
+ ) : ( + formatNumber( + borrow === 'borrow' + ? amount.currentBorrowAmountUSD + : borrow === 'asset' + ? amount.totalAssetAmountUSD + : amount.currentAllAssetsUSD, + true + ) + )} +
+
Top 3 Markets
+ {isLoading ? ( + <> + + + + + ) : ( + top3.map((market) => ( + + )) + )} +
+
+ ) +} + +export default KashiPairTotalCard diff --git a/src/features/analytics/kashi/components/KashiPairTotalDayDatasChart.tsx b/src/features/analytics/kashi/components/KashiPairTotalDayDatasChart.tsx new file mode 100644 index 0000000000..8d73c10e08 --- /dev/null +++ b/src/features/analytics/kashi/components/KashiPairTotalDayDatasChart.tsx @@ -0,0 +1,110 @@ +import { useLingui } from '@lingui/react' +import TailwindConfig from 'app/features/analytics/kashi/config/tailwind' +import { BigNumber } from 'ethers' +import Highcharts from 'highcharts/highstock' +import HighchartsReact from 'highcharts-react-official' +import moment from 'moment' + +import { KashiPairDayDataMap } from '../types/KashiPairDayData' + +const KashiPairTotalDayDatasChart = ({ loading, data }: { loading: boolean; data: KashiPairDayDataMap[] }) => { + const { i18n } = useLingui() + const getSeries = () => { + let supplyData: any[] = [] + let borrowData: any[] = [] + data.forEach((item) => { + supplyData.push({ + x: moment(item.date).valueOf(), + y: BigNumber.from(item.totalAsset).add(BigNumber.from(item.totalBorrow)).toNumber() / 100.0, + }) + borrowData.push({ + x: moment(item.date).valueOf(), + y: BigNumber.from(item.totalBorrow).toNumber() / 100.0, + }) + }) + return [ + { + type: 'line', + color: TailwindConfig.theme.colors.green.DEFAULT, + name: i18n._('Supply'), + data: supplyData, + tooltip: { + pointFormat: i18n._('Supply') + '   ${point.y}', + }, + }, + { + type: 'line', + color: TailwindConfig.theme.colors.pink.DEFAULT, + name: i18n._('Borrow'), + data: borrowData, + tooltip: { + pointFormat: i18n._('Borrow') + '   ${point.y}', + }, + }, + ] + } + + const options = { + title: { + style: { + height: '50px', + padding: '24px', + fontWeight: 'bold', + fontSize: '18px', + }, + }, + scrollbar: { + enabled: false, + }, + series: getSeries(), + rangeSelector: { + buttons: [ + { + type: 'week', + count: 1, + text: '1w', + title: i18n._('View 1 week'), + }, + { + type: 'month', + count: 1, + text: '1m', + title: i18n._('View 1 month'), + }, + { + type: 'month', + count: 3, + text: '3m', + title: i18n._('View 3 months'), + }, + { + type: 'month', + count: 6, + text: '6m', + title: i18n._('View 6 months'), + }, + ], + selected: 1, + }, + } + + return ( +
+
{i18n._('Total Supply & Total Borrow')}
+ {loading || !data || data.length === 0 ? ( +
+
+
+
+
+
+
+
+ ) : ( + + )} +
+ ) +} + +export default KashiPairTotalDayDatasChart diff --git a/src/features/analytics/kashi/components/PairCard.tsx b/src/features/analytics/kashi/components/PairCard.tsx new file mode 100644 index 0000000000..b3f1fdd680 --- /dev/null +++ b/src/features/analytics/kashi/components/PairCard.tsx @@ -0,0 +1,87 @@ +import { i18n } from '@lingui/core' +import classNames from 'classnames' +import { BigNumber } from 'ethers' +import numeral from 'numeral' + +import { KashiPair } from '../types/KashiPair' + +const PairCard = ({ containerClass = '', data }: { containerClass?: string; data?: KashiPair }) => { + return ( +
+
{i18n._('Info')}
+
+
+
{i18n._('Supply')}
+ {!data ? ( +
+ ) : ( +
+ {numeral( + BigNumber.from(data?.totalAsset).add(BigNumber.from(data.totalBorrow)).toNumber() / 100.0 + ).format('($0,0.00)')} +
+ )} +
+
+
{i18n._('Utilization')}
+ {!data ? ( +
+ ) : ( +
+ {numeral( + BigNumber.from(data?.utilization).div(BigNumber.from('100000000000000')).toNumber() / 10000.0 + ).format('(0,0.00%)')} +
+ )} +
+
+
{i18n._('Available')}
+ {!data ? ( +
+ ) : ( +
{numeral(Number(data?.totalAsset) / 100.0).format('($0,0.00)')}
+ )} +
+
+
{i18n._('Borrow')} 
+ {!data ? ( +
+ ) : ( +
{numeral(Number(data?.totalBorrow) / 100.0).format('($0,0.00)')}
+ )} +
+
+
{i18n._('Supply APY')}
+ {!data ? ( +
+ ) : ( +
+ {numeral(BigNumber.from(data?.supplyAPR).div(BigNumber.from('1000000000000')).toNumber() / 100000).format( + '%0.00' + )} +
+ )} +
+
+
{i18n._('Borrow APY')}
+ {!data ? ( +
+ ) : ( +
+ {numeral(BigNumber.from(data?.borrowAPR).div(BigNumber.from('1000000000000')).toNumber() / 100000).format( + '%0.00' + )} +
+ )} +
+
+
+ ) +} + +export default PairCard diff --git a/src/features/analytics/kashi/components/PairCollateralPieChart.tsx b/src/features/analytics/kashi/components/PairCollateralPieChart.tsx new file mode 100644 index 0000000000..a360671819 --- /dev/null +++ b/src/features/analytics/kashi/components/PairCollateralPieChart.tsx @@ -0,0 +1,100 @@ +import classNames from 'classnames' +import { BigNumber } from 'ethers' +import Highcharts from 'highcharts' +import HighchartsReact from 'highcharts-react-official' +import { useRouter } from 'next/router' + +import TailwindConfig from '../config/tailwind' +import { useAppContext } from '../context/AppContext' +import { KashiPairNew } from '../types/KashiPair' + +const PairCollateralPieChart = ({ + containerClass = '', + type = 'supply', + title = 'Supply', + data, +}: { + type?: 'supply' | 'asset' | 'borrow' + containerClass?: string + title?: string + data?: KashiPairNew[] +}) => { + const router = useRouter() + const { tokenUtilService } = useAppContext() + const valueFuncs = { + supply: (kashiPair: KashiPairNew) => + BigNumber.from(kashiPair.totalAssetAmount).add(BigNumber.from(kashiPair.totalBorrowAmount)).toNumber() / 100.0, + asset: (kashiPair: KashiPairNew) => BigNumber.from(kashiPair.totalAssetAmount).toNumber() / 100.0, + borrow: (kashiPair: KashiPairNew) => BigNumber.from(kashiPair.totalBorrowAmount).toNumber() / 100.0, + } + + const getSeries = () => { + let seriesData: any[] = [] + data?.forEach((item) => { + seriesData.push({ + id: item.id, + name: tokenUtilService.pairSymbol(item.asset?.symbol, item.collateral?.symbol), + y: valueFuncs[type](item), + }) + }) + return [ + { + name: title, + data: seriesData, + innerSize: '50%', + tooltip: { + headerFormat: "{point.key}
", + pointFormat: "${point.y}", + }, + }, + ] + } + + const options = { + title: { + text: '', + }, + chart: { + type: 'pie', + backgroundColor: TailwindConfig.theme.colors['dark-900'], + }, + colors: ['#10b981', '#2085ec', '#72b4eb', '#0a417a', '#8464a0', '#cea9bc', '#a855f7', '#323232'], + series: getSeries(), + plotOptions: { + pie: { + cursor: 'pointer', + dataLabels: { + enabled: false, + }, + // showInLegend: true, + events: { + click: (event: Highcharts.SeriesClickEventObject) => { + router.push(`/pair/${(event.point as any).id}`) + }, + }, + }, + }, + } + + return ( +
+
{title}
+ {!data || data.length === 0 ? ( +
+
+
+ ) : ( + <> + + + )} +
+ ) +} + +export default PairCollateralPieChart diff --git a/src/features/analytics/kashi/components/PairInteresetPerSecondDayDataChart.tsx b/src/features/analytics/kashi/components/PairInteresetPerSecondDayDataChart.tsx new file mode 100644 index 0000000000..4a2fb25399 --- /dev/null +++ b/src/features/analytics/kashi/components/PairInteresetPerSecondDayDataChart.tsx @@ -0,0 +1,142 @@ +import { i18n } from '@lingui/core' +import classNames from 'classnames' +import { BigNumber } from 'ethers' +import Highcharts from 'highcharts/highstock' +import HighchartsReact from 'highcharts-react-official' +import moment from 'moment' + +import TailwindConfig from '../config/tailwind' +import { KashiPairDayDataMap } from '../types/KashiPairDayData' + +const PairInterestPerSecondDayDataChart = ({ + containerClass = '', + title = 'Supply & Borrow APR', + data, +}: { + containerClass?: string + title?: string + data?: KashiPairDayDataMap[] +}) => { + const getSeries = () => { + let borrowData: any[] = [] + let supplyData: any[] = [] + data?.forEach((item) => { + borrowData.push({ + x: moment(item.date).valueOf(), + y: + BigNumber.from(item.avgInterestPerSecond) + .mul(3600 * 24 * 365) + .div(1e14) + .toNumber() / 1e2, + }) + supplyData.push({ + x: moment(item.date).valueOf(), + y: + BigNumber.from(item.avgInterestPerSecond) + .mul(3600 * 24 * 365) + .mul(BigNumber.from(item.avgUtilization)) + .div(1e14) + .div(1e14) + .div(1e4) + .toNumber() / 1e2, + }) + }) + return [ + { + type: 'line', + color: TailwindConfig.theme.colors.green.DEFAULT, + data: supplyData, + tooltip: { + pointFormat: 'Supply APR  {point.y}%', + }, + }, + { + type: 'line', + color: TailwindConfig.theme.colors.pink.DEFAULT, + data: borrowData, + tooltip: { + pointFormat: 'Borrow APR  {point.y}%', + }, + }, + ] + } + + const options = { + title: { + style: { + height: '50px', + padding: '24px', + fontWeight: 'bold', + fontSize: '18px', + }, + }, + scrollbar: { + enabled: false, + }, + chart: { + backgroundColor: TailwindConfig.theme.colors['dark-900'], + }, + yAxis: [ + { + gridLineColor: TailwindConfig.theme.colors['dark-600'], + }, + ], + series: getSeries(), + rangeSelector: { + buttons: [ + { + type: 'week', + count: 1, + text: '1w', + title: i18n._('View 1 week'), + }, + { + type: 'month', + count: 1, + text: '1m', + title: i18n._('View 1 month'), + }, + { + type: 'month', + count: 3, + text: '3m', + title: i18n._('View 3 months'), + }, + { + type: 'month', + count: 6, + text: '6m', + title: i18n._('View 6 months'), + }, + ], + selected: 1, + }, + } + + return ( +
+
{title}
+ {!data || data.length === 0 ? ( +
+
+
+
+
+
+
+
+ ) : ( + <> + + + )} +
+ ) +} + +export default PairInterestPerSecondDayDataChart diff --git a/src/features/analytics/kashi/components/PairMarketTable.tsx b/src/features/analytics/kashi/components/PairMarketTable.tsx new file mode 100644 index 0000000000..bb88db1425 --- /dev/null +++ b/src/features/analytics/kashi/components/PairMarketTable.tsx @@ -0,0 +1,433 @@ +/* eslint-disable @next/next/no-img-element */ +import { i18n } from '@lingui/core' +import { useLingui } from '@lingui/react' +import { ChainId, Token } from '@sushiswap/core-sdk' +import { CurrencyLogoArray } from 'app/components/CurrencyLogo' +import { useActiveWeb3React } from 'app/services/web3' +import { BigNumber } from 'ethers' +import { useRouter } from 'next/router' +import numeral from 'numeral' +import React, { useEffect, useState } from 'react' +import { FaChevronDown, FaChevronUp } from 'react-icons/fa' + +import { useAppContext } from '../context/AppContext' +import { KashiPairNew } from '../types/KashiPair' + +type OrderBy = 'asset' | 'collateral' | 'totalSupply' | 'totalAsset' | 'supplyAPR' | 'totalBorrow' | 'borrowAPR' | '' +type OrderDirection = 'asc' | 'desc' + +const PairMarketTableHead = ({ + onSort, + orderBy, + orderDirection, +}: { + onSort: (orderBy: OrderBy) => void + orderBy: OrderBy + orderDirection: OrderDirection +}) => { + const { i18n } = useLingui() + const iconByDirection = { + asc: , + desc: , + } + + return ( + + + { + onSort('asset') + }} + className="cursor-pointer" + > + Asset{orderBy === 'asset' && iconByDirection[orderDirection]} + + / + { + onSort('collateral') + }} + className="cursor-pointer" + > + {i18n._('Collateral')} + {orderBy === 'collateral' && iconByDirection[orderDirection]} + + + { + onSort('totalSupply') + }} + > + + {i18n._('TVL')} + {orderBy === 'totalSupply' && iconByDirection[orderDirection]} + + + { + onSort('totalBorrow') + }} + > + + {i18n._('Borrowed')} + {orderBy === 'totalBorrow' && iconByDirection[orderDirection]} + + + + { + onSort('supplyAPR') + }} + > + + {i18n._('Supply APR')} + {orderBy === 'supplyAPR' && iconByDirection[orderDirection]} + + + { + onSort('totalAsset') + }} + > + + {i18n._('Available')} + {orderBy === 'totalAsset' && iconByDirection[orderDirection]} + + + { + onSort('borrowAPR') + }} + > + + {i18n._('Borrow APR')} + {orderBy === 'borrowAPR' && iconByDirection[orderDirection]} + + + + ) +} + +const PairMarketTableRowLoading = () => ( + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+ + +
+
+
+
+
+
+ + +
+ + +) + +const PairMarketTableRow = ({ data, index }: { data: KashiPairNew; index: number }) => { + const { tokenUtilService, handleLogoError } = useAppContext() + const router = useRouter() + const goto = (route: string) => { + router.push(route) + } + + const { chainId } = useActiveWeb3React() + + const asset = new Token( + chainId ?? ChainId.ETHEREUM, + data.asset?.id ?? '0xdAC17F958D2ee523a2206206994597C13D831ec7', + Number(data.asset?.decimals ?? 0), + data.asset?.symbol, + data.asset?.name + ) + + const collateral = new Token( + Number(chainId) ?? ChainId.ETHEREUM, + data.collateral?.id ?? '0xdAC17F958D2ee523a2206206994597C13D831ec7', + Number(data.collateral?.decimals ?? 0), + data.collateral?.symbol, + data.collateral?.name + ) + + return ( + <> + goto(`/analytics/kashi/pairs/${data.id}`)} + className="border-t border-l-2 border-transparent cursor-pointer border-t-gray-900 hover:bg-dark-900" + > + +
+
+ +
+
+ {tokenUtilService.pairSymbol(data.asset?.symbol, data.collateral?.symbol)} +
+
+ + +
+ {numeral( + BigNumber.from(data?.totalAssetAmount).add(BigNumber.from(data.totalBorrowAmount)).toNumber() / 100 + ).format('$0,.00')} +
+
+ {numeral( + BigNumber.from(data?.totalAsset?.elastic) + .add(BigNumber.from(data.totalBorrow?.elastic)) + .div( + BigNumber.from('10').pow( + Number(data.asset?.decimals && Number(data.asset?.decimals) >= 2 ? data.asset?.decimals : 2) - 2 + ) + ) + .toNumber() / 100 + ).format('0,.00')} +   + {data.asset?.symbol} +
+ + +
+ {numeral(BigNumber.from(data?.totalBorrowAmount).toNumber() / 100).format('$0,.00')} +
+
+ {numeral( + BigNumber.from(data?.totalBorrow?.elastic) + .div( + BigNumber.from('10').pow( + Number(data.asset?.decimals && Number(data.asset?.decimals) >= 2 ? data.asset?.decimals : 2) - 2 + ) + ) + .toNumber() / 100 + ).format('0,.00')} +   + {data.asset?.symbol} +
+ + +
+ {numeral( + BigNumber.from(data?.kpi?.supplyAPR).div(BigNumber.from('1000000000000')).toNumber() / 100000 + ).format('%0.00')} +
+
{i18n._(`annualized`)}
+ + +
+ {numeral(BigNumber.from(data?.totalAssetAmount).toNumber() / 100).format('$0,.00')} +
+
+ {numeral( + BigNumber.from(data?.totalAsset?.elastic) + .div( + BigNumber.from('10').pow( + Number(data.asset?.decimals && Number(data.asset?.decimals) >= 2 ? data.asset?.decimals : 2) - 2 + ) + ) + .toNumber() / 100 + ).format('0,.00')} +   + {data.asset?.symbol} +
+ + + +
+ {numeral( + BigNumber.from(data?.kpi?.borrowAPR).div(BigNumber.from('1000000000000')).toNumber() / 100000 + ).format('%0.00')} +
+
{i18n._(`annualized`)}
+ + + + ) +} + +const PairMarketTable = ({ loading = false, data = [] }: { loading?: boolean; data: KashiPairNew[] }) => { + const [orderBy, setOrderBy] = useState('totalSupply') + const [orderDirection, setOrderDirection] = useState('desc') + + const [fullList, setFullList] = useState([]) + const [sortedList, setSortedList] = useState([]) + const [list, setList] = useState([]) + const [isMore, setMore] = useState(false) + const [search, setSearch] = useState('') + const { tokenUtilService } = useAppContext() + + useEffect(() => { + setFullList(data) + }, [data]) + + useEffect(() => { + let newSortedList = [...fullList] + const compareFuncs = { + asset: { + asc: (a: KashiPairNew, b: KashiPairNew) => + (a.asset?.symbol.toLowerCase() || '').localeCompare(b.asset?.symbol.toLowerCase() || ''), + desc: (a: KashiPairNew, b: KashiPairNew) => + (b.asset?.symbol.toLowerCase() || '').localeCompare(a.asset?.symbol.toLowerCase() || ''), + }, + collateral: { + asc: (a: KashiPairNew, b: KashiPairNew) => + (a.collateral?.symbol.toLowerCase() || '').localeCompare(b.collateral?.symbol.toLowerCase() || ''), + desc: (a: KashiPairNew, b: KashiPairNew) => + (b.collateral?.symbol.toLowerCase() || '').localeCompare(a.collateral?.symbol.toLowerCase() || ''), + }, + totalSupply: { + asc: (a: KashiPairNew, b: KashiPairNew) => + BigNumber.from(a.totalAssetAmount) + .add(BigNumber.from(a.totalBorrowAmount)) + .lte(BigNumber.from(b.totalAssetAmount).add(BigNumber.from(b.totalBorrowAmount))) + ? -1 + : 1, + desc: (a: KashiPairNew, b: KashiPairNew) => + BigNumber.from(a.totalAssetAmount) + .add(BigNumber.from(a.totalBorrowAmount)) + .gte(BigNumber.from(b.totalAssetAmount).add(BigNumber.from(b.totalBorrowAmount))) + ? -1 + : 1, + }, + totalAsset: { + asc: (a: KashiPairNew, b: KashiPairNew) => + BigNumber.from(a.totalAssetAmount).lte(BigNumber.from(b.totalAssetAmount)) ? -1 : 1, + desc: (a: KashiPairNew, b: KashiPairNew) => + BigNumber.from(a.totalAssetAmount).gte(BigNumber.from(b.totalAssetAmount)) ? -1 : 1, + }, + totalBorrow: { + asc: (a: KashiPairNew, b: KashiPairNew) => + BigNumber.from(a.totalBorrowAmount).lte(BigNumber.from(b.totalBorrowAmount)) ? 1 : -1, + desc: (a: KashiPairNew, b: KashiPairNew) => + BigNumber.from(a.totalBorrowAmount).gte(BigNumber.from(b.totalBorrowAmount)) ? -1 : 1, + }, + supplyAPR: { + asc: (a: KashiPairNew, b: KashiPairNew) => + BigNumber.from(a.kpi?.supplyAPR).lte(BigNumber.from(b.kpi?.supplyAPR)) ? -1 : 1, + desc: (a: KashiPairNew, b: KashiPairNew) => + BigNumber.from(a.kpi?.supplyAPR).gte(BigNumber.from(b.kpi?.supplyAPR)) ? -1 : 1, + }, + borrowAPR: { + asc: (a: KashiPairNew, b: KashiPairNew) => + BigNumber.from(a.kpi?.borrowAPR).lte(BigNumber.from(b.kpi?.borrowAPR)) ? -1 : 1, + desc: (a: KashiPairNew, b: KashiPairNew) => + BigNumber.from(a.kpi?.borrowAPR).gte(BigNumber.from(b.kpi?.borrowAPR)) ? -1 : 1, + }, + } + + if (orderBy) { + newSortedList.sort(compareFuncs[orderBy][orderDirection]) + } + setSortedList(newSortedList) + }, [fullList, orderBy, orderDirection]) + + useEffect(() => { + setList([]) + }, [sortedList]) + + const handleLoadMore = () => { + if (isMore) return + setMore(true) + if (list.length < sortedList.length) { + const start = list.length + const end = Math.min(start + 20, sortedList.length) + const newList = [...list, ...sortedList.slice(start, end)] + setList(newList) + } + setMore(false) + } + + const handleSort = (orderField: OrderBy) => { + if (orderBy === orderField) { + setOrderDirection(orderDirection === 'asc' ? 'desc' : 'asc') + return + } + setOrderBy(orderField) + setOrderDirection('desc') + } + + const handleSearchChange = (event: React.SyntheticEvent) => { + const target = event.target as HTMLInputElement + setSearch(target.value) + } + + return ( +
+
+ +
+ + + + + {loading ? ( + + + + + + + ) : ( + + {sortedList + .filter((value) => { + const token = tokenUtilService.pairSymbol(value.asset?.symbol, value.collateral?.symbol) + if (token) { + return token.toLowerCase().indexOf(search.toLowerCase()) >= 0 + } + return false + }) + .map((data, index) => ( + + ))} + + )} +
+
+ ) +} +export default PairMarketTable diff --git a/src/features/analytics/kashi/components/PairSupplyAccruedInterestDayDataChart.tsx b/src/features/analytics/kashi/components/PairSupplyAccruedInterestDayDataChart.tsx new file mode 100644 index 0000000000..507e07eea3 --- /dev/null +++ b/src/features/analytics/kashi/components/PairSupplyAccruedInterestDayDataChart.tsx @@ -0,0 +1,124 @@ +import { i18n } from '@lingui/core' +import classNames from 'classnames' +import { BigNumber } from 'ethers' +import Highcharts from 'highcharts/highstock' +import HighchartsReact from 'highcharts-react-official' +import moment from 'moment' + +import TailwindConfig from '../config/tailwind' +import { KashiPairDayDataMap } from '../types/KashiPairDayData' + +const PairSupplyAccruedInterestDayDataChart = ({ + containerClass = '', + title = i18n._('Accrued Interest'), + data, +}: { + containerClass?: string + title?: string + data?: KashiPairDayDataMap[] +}) => { + const getSeries = () => { + let seriesData: any[] = [] + const valueFunc = (item: KashiPairDayDataMap) => ({ + x: moment(item.date).valueOf(), + y: + BigNumber.from(item.totalBorrow) + .mul(BigNumber.from(item.avgInterestPerSecond)) + .mul(3600 * 24) + .div(BigNumber.from(10).pow(18)) + .toNumber() / 100.0, + }) + data?.forEach((item) => { + seriesData.push(valueFunc(item)) + }) + return [ + { + type: 'line', + color: TailwindConfig.theme.colors.blue.DEFAULT, + data: seriesData, + tooltip: { + pointFormat: i18n._('Accrued Interest') + '   ${point.y}', + }, + }, + ] + } + + const options = { + title: { + style: { + height: '50px', + padding: '24px', + fontWeight: 'bold', + fontSize: '18px', + }, + }, + scrollbar: { + enabled: false, + }, + chart: { + backgroundColor: TailwindConfig.theme.colors['dark-900'], + }, + yAxis: [ + { + gridLineColor: TailwindConfig.theme.colors['dark-600'], + }, + ], + series: getSeries(), + rangeSelector: { + buttons: [ + { + type: 'week', + count: 1, + text: '1w', + title: i18n._('View 1 week'), + }, + { + type: 'month', + count: 1, + text: '1m', + title: i18n._('View 1 month'), + }, + { + type: 'month', + count: 3, + text: '3m', + title: i18n._('View 3 months'), + }, + { + type: 'month', + count: 6, + text: '6m', + title: i18n._('View 6 months'), + }, + ], + selected: 1, + }, + } + + return ( +
+
{title}
+ {!data || data.length === 0 ? ( +
+
+
+
+
+
+
+
+ ) : ( + <> + + + )} +
+ ) +} + +export default PairSupplyAccruedInterestDayDataChart diff --git a/src/features/analytics/kashi/components/PairSupplyBorrowDayDataChart.tsx b/src/features/analytics/kashi/components/PairSupplyBorrowDayDataChart.tsx new file mode 100644 index 0000000000..0bab8baa1a --- /dev/null +++ b/src/features/analytics/kashi/components/PairSupplyBorrowDayDataChart.tsx @@ -0,0 +1,139 @@ +import { i18n } from '@lingui/core' +import classNames from 'classnames' +import { BigNumber } from 'ethers' +import Highcharts from 'highcharts/highstock' +import HighchartsReact from 'highcharts-react-official' +import moment from 'moment' + +import TailwindConfig from '../config/tailwind' +import { KashiPairDayDataMap } from '../types/KashiPairDayData' + +const AttributesByType = { + supply: { + color: TailwindConfig.theme.colors.green.DEFAULT, + valueFunc: (item: KashiPairDayDataMap) => ({ + x: moment(item.date).valueOf(), + y: BigNumber.from(item.totalAsset).add(BigNumber.from(item.totalBorrow)).toNumber() / 100.0, + }), + tooltip: { + pointFormat: 'Supply   ${point.y}', + }, + }, + borrow: { + color: TailwindConfig.theme.colors.pink.DEFAULT, + valueFunc: (item: KashiPairDayDataMap) => ({ + x: moment(item.date).valueOf(), + y: BigNumber.from(item.totalBorrow).toNumber() / 100.0, + }), + tooltip: { + pointFormat: 'Borrow   ${point.y}', + }, + }, +} + +const PairSupplyBorrowDayDataChart = ({ + type = 'supply', + containerClass = '', + title = i18n._('Deposit'), + data, +}: { + type?: 'supply' | 'borrow' + containerClass?: string + title?: string + data?: KashiPairDayDataMap[] +}) => { + const getSeries = () => { + let seriesData: any[] = [] + const attribute = AttributesByType[type] + data?.forEach((item) => { + seriesData.push(attribute.valueFunc(item)) + }) + return [ + { + type: 'area', + color: attribute.color, + data: seriesData, + tooltip: attribute.tooltip, + }, + ] + } + + const options = { + title: { + style: { + height: '50px', + padding: '24px', + fontWeight: 'bold', + fontSize: '18px', + }, + }, + scrollbar: { + enabled: false, + }, + chart: { + backgroundColor: TailwindConfig.theme.colors['dark-900'], + }, + yAxis: [ + { + gridLineColor: TailwindConfig.theme.colors['dark-600'], + }, + ], + series: getSeries(), + rangeSelector: { + buttons: [ + { + type: 'week', + count: 1, + text: '1w', + title: i18n._('View 1 week'), + }, + { + type: 'month', + count: 1, + text: '1m', + title: i18n._('View 1 month'), + }, + { + type: 'month', + count: 3, + text: '3m', + title: i18n._('View 3 months'), + }, + { + type: 'month', + count: 6, + text: '6m', + title: i18n._('View 6 months'), + }, + ], + selected: 1, + }, + } + + return ( +
+
{title}
+ {!data || data.length === 0 ? ( +
+
+
+
+
+
+
+
+ ) : ( + <> + + + )} +
+ ) +} + +export default PairSupplyBorrowDayDataChart diff --git a/src/features/analytics/kashi/components/PairSupplyBorrowMonthDataChart.tsx b/src/features/analytics/kashi/components/PairSupplyBorrowMonthDataChart.tsx new file mode 100644 index 0000000000..d1ceca7b08 --- /dev/null +++ b/src/features/analytics/kashi/components/PairSupplyBorrowMonthDataChart.tsx @@ -0,0 +1,154 @@ +import { i18n } from '@lingui/core' +import classNames from 'classnames' +import { BigNumber } from 'ethers' +import Highcharts from 'highcharts/highstock' +import HighchartsReact from 'highcharts-react-official' +import moment from 'moment' + +import TailwindConfig from '../config/tailwind' +import { KashiPairDayDataMap } from '../types/KashiPairDayData' + +const AttributesByType = { + supply: { + color: TailwindConfig.theme.colors.green.DEFAULT, + valueFunc: (item: KashiPairDayDataMap) => ({ + x: moment(item.date).valueOf(), + y: BigNumber.from(item.totalAsset).add(BigNumber.from(item.totalBorrow)).toNumber() / 100.0, + }), + tooltip: { + pointFormat: i18n._('Supply') + '   ${point.y}', + }, + }, + borrow: { + color: TailwindConfig.theme.colors.pink.DEFAULT, + valueFunc: (item: KashiPairDayDataMap) => ({ + x: moment(item.date).valueOf(), + y: BigNumber.from(item.totalBorrow).toNumber() / 100.0, + }), + tooltip: { + pointFormat: i18n._('Borrow') + '   ${point.y}', + }, + }, +} + +const PairSupplyBorrowMonthDataChart = ({ + containerClass = '', + title = i18n._('Monthly Net Supply & Borrow'), + data, +}: { + // type?: "supply" | "borrow"; + containerClass?: string + title?: string + data?: KashiPairDayDataMap[] +}) => { + const getSeries = () => { + let borrowSeriesData: any[] = [] + const borrowAttribute = AttributesByType['borrow'] + + let supplySeriesData: any[] = [] + const supplyAttribute = AttributesByType['supply'] + data?.forEach((item) => { + borrowSeriesData.push(borrowAttribute.valueFunc(item)) + supplySeriesData.push(supplyAttribute.valueFunc(item)) + }) + return [ + { + type: 'area', + color: supplyAttribute.color, + data: supplySeriesData, + pointIntervalUnit: 'month', + tooltip: supplyAttribute.tooltip, + }, + { + type: 'area', + color: borrowAttribute.color, + data: borrowSeriesData, + pointIntervalUnit: 'month', + tooltip: borrowAttribute.tooltip, + }, + ] + } + + const options = { + title: { + style: { + height: '50px', + padding: '24px', + fontWeight: 'bold', + fontSize: '18px', + }, + }, + scrollbar: { + enabled: false, + }, + chart: { + backgroundColor: TailwindConfig.theme.colors['dark-900'], + }, + yAxis: [ + { + gridLineColor: TailwindConfig.theme.colors['dark-600'], + }, + ], + series: getSeries(), + rangeSelector: { + buttons: [ + { + type: 'month', + count: 5, + text: '5m', + title: i18n._('View 5 months'), + }, + { + type: 'month', + count: 6, + text: '6m', + title: i18n._('View 6 months'), + }, + { + type: 'ytd', + text: 'YTD', + title: i18n._('View year to date'), + }, + { + type: 'year', + count: 1, + text: '1y', + title: i18n._('View 1 year'), + }, + { + type: 'all', + text: 'All', + title: i18n._('View all'), + }, + ], + selected: 0, + }, + } + + return ( +
+
{title}
+ {!data || data.length === 0 ? ( +
+
+
+
+
+
+
+
+ ) : ( + <> + + + )} +
+ ) +} + +export default PairSupplyBorrowMonthDataChart diff --git a/src/features/analytics/kashi/components/PairUtilizationDayDataChart.tsx b/src/features/analytics/kashi/components/PairUtilizationDayDataChart.tsx new file mode 100644 index 0000000000..593a23a066 --- /dev/null +++ b/src/features/analytics/kashi/components/PairUtilizationDayDataChart.tsx @@ -0,0 +1,119 @@ +import { i18n } from '@lingui/core' +import classNames from 'classnames' +import { BigNumber } from 'ethers' +import Highcharts from 'highcharts/highstock' +import HighchartsReact from 'highcharts-react-official' +import moment from 'moment' + +import TailwindConfig from '../config/tailwind' +import { KashiPairDayDataMap } from '../types/KashiPairDayData' + +const PairUtilizationDayDataChart = ({ + containerClass = '', + title = 'Utilization Rate', + data, +}: { + containerClass?: string + title?: string + data?: KashiPairDayDataMap[] +}) => { + const getSeries = () => { + let utilizationData: any[] = [] + data?.forEach((item) => { + utilizationData.push({ + x: moment(item.date).valueOf(), + y: BigNumber.from(item.avgUtilization).div(BigInt(1e14)).toNumber() / 1e2, + }) + }) + return [ + { + type: 'line', + color: TailwindConfig.theme.colors.blue.DEFAULT, + name: i18n._('Utilization'), + data: utilizationData, + tooltip: { + pointFormat: i18n._('Utilization Rate') + '  {point.y}%', + }, + }, + ] + } + + const options = { + title: { + style: { + height: '50px', + padding: '24px', + fontWeight: 'bold', + fontSize: '18px', + }, + }, + scrollbar: { + enabled: false, + }, + chart: { + backgroundColor: TailwindConfig.theme.colors['dark-900'], + }, + yAxis: [ + { + gridLineColor: TailwindConfig.theme.colors['dark-600'], + }, + ], + series: getSeries(), + rangeSelector: { + buttons: [ + { + type: 'week', + count: 1, + text: '1w', + title: i18n._('View 1 week'), + }, + { + type: 'month', + count: 1, + text: '1m', + title: i18n._('View 1 month'), + }, + { + type: 'month', + count: 3, + text: '3m', + title: i18n._('View 3 months'), + }, + { + type: 'month', + count: 6, + text: '6m', + title: i18n._('View 6 months'), + }, + ], + selected: 1, + }, + } + + return ( +
+
{title}
+ {!data || data.length === 0 ? ( +
+
+
+
+
+
+
+
+ ) : ( + <> + + + )} +
+ ) +} + +export default PairUtilizationDayDataChart diff --git a/src/features/analytics/kashi/components/Progress.tsx b/src/features/analytics/kashi/components/Progress.tsx new file mode 100644 index 0000000000..a96bf9f368 --- /dev/null +++ b/src/features/analytics/kashi/components/Progress.tsx @@ -0,0 +1,88 @@ +import { formatPercent } from 'app/functions' +import classNames from 'classnames' + +const classByColor = { + blue: { + title: 'text-gray-500', + percent: 'text-blue-500', + progressContainer: 'bg-blue-300', + progressActive: 'bg-blue-500', + }, + pink: { + title: 'text-gray-500', + percent: 'text-pink-500', + progressContainer: 'bg-pink-300', + progressActive: 'bg-pink-500', + }, + green: { + title: 'text-gray-500', + percent: 'text-green-500', + progressContainer: 'bg-green-300', + progressActive: 'bg-green-500', + }, +} + +const Progress = ({ + loading = false, + color = 'green', + progress = 0, + title = '', + containerClass = '', +}: { + loading?: boolean + color?: 'blue' | 'pink' | 'green' + progress?: number + title?: string + containerClass?: string +}) => { + const byColor = classByColor[color] + return ( +
+
+
+ {loading ?
: title} +
+
+ {loading ? ( +
+ ) : ( + formatPercent(progress * 100) + )} +
+
+
+ {loading ? ( +
+ ) : ( + <> + + + + )} +
+
+ ) +} + +export default Progress diff --git a/src/features/analytics/kashi/components/TokenCard.tsx b/src/features/analytics/kashi/components/TokenCard.tsx new file mode 100644 index 0000000000..468df0a287 --- /dev/null +++ b/src/features/analytics/kashi/components/TokenCard.tsx @@ -0,0 +1,71 @@ +import { i18n } from '@lingui/core' +import classNames from 'classnames' +import { BigNumber } from 'ethers' +import numeral from 'numeral' + +import { Token } from '../types/Token' + +const TokenCard = ({ + containerClass = '', + data, + totalAsset = BigInt(0), + totalBorrow = BigInt(0), +}: { + containerClass?: string + data?: Token + totalAsset?: BigInt + totalBorrow?: BigInt +}) => { + return ( +
+
{i18n._('Info')}
+
+
+
{i18n._('Supply')}
+ {!data ? ( +
+ ) : ( +
+ {numeral(BigNumber.from(totalAsset).add(BigNumber.from(totalBorrow)).toNumber() / 100.0).format( + '($0,0.00)' + )} +
+ )} +
+
+
{i18n._('Available')}
+ {!data ? ( +
+ ) : ( +
{numeral(Number(totalAsset) / 100.0).format('($0,0.00)')}
+ )} +
+
+
{i18n._('Borrow')}
+ {!data ? ( +
+ ) : ( +
{numeral(Number(totalBorrow) / 100.0).format('($0,0.00)')}
+ )} +
+
+
{i18n._('Oracle Price')}
+ {!data ? ( +
+ ) : ( +
+ {numeral(BigNumber.from(data.price).div(BigNumber.from(1e6)).toNumber() / 100).format('($0,0.00)')} +
+ )} +
+
+
+ ) +} + +export default TokenCard diff --git a/src/features/analytics/kashi/components/TokenMarketTable.tsx b/src/features/analytics/kashi/components/TokenMarketTable.tsx new file mode 100644 index 0000000000..37f415500e --- /dev/null +++ b/src/features/analytics/kashi/components/TokenMarketTable.tsx @@ -0,0 +1,291 @@ +/* eslint-disable @next/next/no-img-element */ +import { i18n } from '@lingui/core' +import { ChainId, Token } from '@sushiswap/core-sdk' +import { CurrencyLogo } from 'app/components/CurrencyLogo' +import { useActiveWeb3React } from 'app/services/web3' +import { BigNumber } from 'ethers' +import { useRouter } from 'next/router' +import numeral from 'numeral' +import React, { useEffect, useState } from 'react' +import { FaChevronDown, FaChevronUp } from 'react-icons/fa' + +import { useAppContext } from '../context/AppContext' +import { KashiPairsByTokenNew } from '../types/KashiPair' + +type OrderBy = 'symbol' | 'totalSupply' | 'totalAsset' | 'totalBorrow' | '' +type OrderDirection = 'asc' | 'desc' + +const MarketTableHead = ({ + onSort, + orderBy, + orderDirection, +}: { + onSort: (orderBy: OrderBy) => void + orderBy: OrderBy + orderDirection: OrderDirection +}) => { + const iconByDirection = { + asc: , + desc: , + } + + return ( + + { + onSort('symbol') + }} + > + Token {orderBy === 'symbol' && iconByDirection[orderDirection]} + + { + onSort('totalSupply') + }} + > + + Total Supply + {orderBy === 'totalSupply' && iconByDirection[orderDirection]} + + + { + onSort('totalAsset') + }} + > + + Total Available + {orderBy === 'totalAsset' && iconByDirection[orderDirection]} + + + { + onSort('totalBorrow') + }} + > + + Total Borrow + {orderBy === 'totalBorrow' && iconByDirection[orderDirection]} + + + + ) +} + +const MarketTableRowLoading = () => ( + + +
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +) + +const MarketTableRow = ({ data, index }: { data: KashiPairsByTokenNew; index: number }) => { + const { tokenUtilService, handleLogoError } = useAppContext() + const router = useRouter() + const goto = (route: string) => { + router.push(route) + } + + const { chainId } = useActiveWeb3React() + + const asset = new Token( + chainId ?? ChainId.ETHEREUM, + data.token?.id ?? '0xdAC17F958D2ee523a2206206994597C13D831ec7', + Number(data.token?.decimals ?? 0), + data.token?.symbol, + data.token?.name + ) + + return ( + <> + goto(`/analytics/kashi/tokens/${data.token.id}`)} + className="border-t border-l-2 border-transparent cursor-pointer border-t-gray-900 hover:bg-dark-900" + > + +
+ + +
+
{tokenUtilService.symbol(data.token.symbol)}
+
+
+ + + {numeral(BigNumber.from(data.totalAsset).add(BigNumber.from(data.totalBorrow)).toNumber() / 100).format( + '$0,.00' + )} + + + {numeral(BigNumber.from(data.totalAsset).toNumber() / 100).format('$0,.00')} + + + {numeral(BigNumber.from(data.totalBorrow).toNumber() / 100).format('$0,.00')} + + + + ) +} + +const TokenMarketTable = ({ loading = false, data = [] }: { loading?: boolean; data: KashiPairsByTokenNew[] }) => { + const [orderBy, setOrderBy] = useState('') + const [orderDirection, setOrderDirection] = useState('desc') + + const [fullList, setFullList] = useState([]) + const [sortedList, setSortedList] = useState([]) + const [list, setList] = useState([]) + const [isMore, setMore] = useState(false) + const [search, setSearch] = useState('') + + useEffect(() => { + setFullList(data) + }, [data]) + + useEffect(() => { + let newSortedList = [...fullList] + const compareFuncs = { + symbol: { + asc: (a: KashiPairsByTokenNew, b: KashiPairsByTokenNew) => + (a.token.symbol.toLowerCase() || '').localeCompare(b.token.symbol.toLowerCase() || ''), + desc: (a: KashiPairsByTokenNew, b: KashiPairsByTokenNew) => + (b.token.symbol.toLowerCase() || '').localeCompare(a.token.symbol.toLowerCase() || ''), + }, + totalSupply: { + asc: (a: KashiPairsByTokenNew, b: KashiPairsByTokenNew) => + BigNumber.from(a.totalAsset) + .add(BigNumber.from(a.totalBorrow)) + .lte(BigNumber.from(b.totalAsset).add(BigNumber.from(b.totalBorrow))) + ? -1 + : 1, + desc: (a: KashiPairsByTokenNew, b: KashiPairsByTokenNew) => + BigNumber.from(a.totalAsset) + .add(BigNumber.from(a.totalBorrow)) + .gte(BigNumber.from(b.totalAsset).add(BigNumber.from(b.totalBorrow))) + ? -1 + : 1, + }, + totalAsset: { + asc: (a: KashiPairsByTokenNew, b: KashiPairsByTokenNew) => + BigNumber.from(a.totalAsset).lte(BigNumber.from(b.totalAsset)) ? -1 : 1, + desc: (a: KashiPairsByTokenNew, b: KashiPairsByTokenNew) => + BigNumber.from(a.totalAsset).gte(BigNumber.from(b.totalAsset)) ? -1 : 1, + }, + totalBorrow: { + asc: (a: KashiPairsByTokenNew, b: KashiPairsByTokenNew) => + BigNumber.from(a.totalBorrow).lte(BigNumber.from(b.totalBorrow)) ? -1 : 1, + desc: (a: KashiPairsByTokenNew, b: KashiPairsByTokenNew) => + BigNumber.from(a.totalBorrow).gte(BigNumber.from(b.totalBorrow)) ? -1 : 1, + }, + } + + if (orderBy) { + newSortedList.sort(compareFuncs[orderBy][orderDirection]) + } + setSortedList(newSortedList) + }, [fullList, orderBy, orderDirection]) + + useEffect(() => { + setList([]) + }, [sortedList]) + + const handleLoadMore = () => { + if (isMore) return + setMore(true) + if (list.length < sortedList.length) { + const start = list.length + const end = Math.min(start + 20, sortedList.length) + const newList = [...list, ...sortedList.slice(start, end)] + setList(newList) + } + setMore(false) + } + + const handleSort = (orderField: OrderBy) => { + if (orderBy === orderField) { + setOrderDirection(orderDirection === 'asc' ? 'desc' : 'asc') + return + } + setOrderBy(orderField) + setOrderDirection('desc') + } + + const handleSearchChange = (event: React.SyntheticEvent) => { + const target = event.target as HTMLInputElement + setSearch(target.value) + } + + return ( + <> +
+
+ +
+ + + + + {loading ? ( + + + + + + + ) : ( + + {sortedList + .filter((value) => value.token.symbol.toLowerCase().indexOf(search.toLowerCase()) >= 0) + .map((data, index) => ( + + ))} + + )} +
+
+ + ) +} +export default TokenMarketTable diff --git a/src/features/analytics/kashi/components/TotalCard.tsx b/src/features/analytics/kashi/components/TotalCard.tsx new file mode 100644 index 0000000000..27ac0e9665 --- /dev/null +++ b/src/features/analytics/kashi/components/TotalCard.tsx @@ -0,0 +1,114 @@ +import { useLingui } from '@lingui/react' +import classNames from 'classnames' +import { BigNumber } from 'ethers' +import numeral from 'numeral' + +import { useAppContext } from '../context/AppContext' +import { KashiPairNew } from '../types/KashiPair' +import Progress from './Progress' + +export type TotalData = { + amount: BigInt + volumeIn24H: BigInt + topMarkets: KashiPairNew[] +} + +type AttributesByBorrowType = { + progressColor: 'pink' | 'blue' | 'green' + title: string + users: string +} + +type AttributesMapByBorrowType = { + borrow: AttributesByBorrowType + asset: AttributesByBorrowType + supply: AttributesByBorrowType +} + +const TotalCard = ({ + containerClass = '', + data, + borrow = 'borrow', + loading = false, +}: { + containerClass?: string + data: TotalData + borrow?: 'borrow' | 'asset' | 'supply' + loading?: boolean +}) => { + const { i18n } = useLingui() + const AttributesMapByBorrow = { + borrow: { + progressColor: 'pink', + title: i18n._('Total Borrow'), + users: i18n._('Borrowers'), + }, + asset: { + progressColor: 'green', + title: i18n._('Total Available'), + users: i18n._('Suppliers'), + }, + supply: { + progressColor: 'blue', + title: i18n._('Total Supply'), + users: i18n._('Suppliers'), + }, + } as AttributesMapByBorrowType + const attributes = AttributesMapByBorrow[borrow] + const isLoading = data.amount === BigInt(0) || loading + const { tokenUtilService } = useAppContext() + + return ( +
+
{attributes.title}
+
+
+ {isLoading ? ( +
+ ) : ( + numeral(Number(data.amount) / 100.0).format('($0,0.00)') + )} +
+
{i18n._('Top 3 Markets')}
+ {isLoading ? ( + <> + + + + + ) : ( + data.topMarkets.map((marketData) => ( + + )) + )} +
+
+ ) +} + +export default TotalCard diff --git a/src/features/analytics/kashi/components/TotalCompareChart.tsx b/src/features/analytics/kashi/components/TotalCompareChart.tsx new file mode 100644 index 0000000000..63009f56c7 --- /dev/null +++ b/src/features/analytics/kashi/components/TotalCompareChart.tsx @@ -0,0 +1,113 @@ +import { i18n } from '@lingui/core' +import { BigNumber } from 'ethers' +import Highcharts from 'highcharts/highstock' +import HighchartsReact from 'highcharts-react-official' +import moment from 'moment' + +import TailwindConfig from '../config/tailwind' +import { KashiPairDayDataMap } from '../types/KashiPairDayData' + +const TotalCompareChart = ({ loading, data }: { loading: boolean; data: KashiPairDayDataMap[] }) => { + const getSeries = () => { + let seriesData: any[] = [] + data.forEach((item) => { + if (BigNumber.from(item.totalAsset).add(BigNumber.from(item.totalBorrow)).gt(0)) { + const percent = + BigNumber.from(item.totalBorrow) + .mul(BigNumber.from('10000')) + .div(BigNumber.from(item.totalAsset).add(BigNumber.from(item.totalBorrow))) + .toNumber() / 100 + seriesData.push({ + x: moment(item.date).valueOf(), + y: percent, + }) + } + }) + + return [ + { + type: 'column', + color: TailwindConfig.theme.colors.pink.DEFAULT, + name: 'Ratio', + data: seriesData, + tooltip: { + pointFormat: 'Ratio {point.y}%', + }, + }, + ] + } + + const options = { + title: { + style: { + height: '50px', + padding: '24px', + fontWeight: 'bold', + fontSize: '18px', + }, + }, + scrollbar: { + enabled: false, + }, + chart: { + backgroundColor: TailwindConfig.theme.colors['dark-900'], + }, + yAxis: [ + { + gridLineColor: TailwindConfig.theme.colors['dark-600'], + }, + ], + rangeSelector: { + buttons: [ + { + type: 'week', + count: 1, + text: '1w', + title: i18n._('View 1 week'), + }, + { + type: 'month', + count: 1, + text: '1m', + title: i18n._('View 1 month'), + }, + { + type: 'month', + count: 3, + text: '3m', + title: i18n._('View 3 months'), + }, + { + type: 'month', + count: 6, + text: '6m', + title: i18n._('View 6 months'), + }, + ], + selected: 1, + }, + series: getSeries(), + } + + return ( +
+
{i18n._('Borrow vs Supply Ratio')}
+ {loading || !data || data.length === 0 ? ( +
+
+
+
+
+
+
+
+ ) : ( + <> + + + )} +
+ ) +} + +export default TotalCompareChart diff --git a/src/features/analytics/kashi/components/TotalDayDataChart.tsx b/src/features/analytics/kashi/components/TotalDayDataChart.tsx new file mode 100644 index 0000000000..de7bda16c7 --- /dev/null +++ b/src/features/analytics/kashi/components/TotalDayDataChart.tsx @@ -0,0 +1,119 @@ +import { i18n } from '@lingui/core' +import { BigNumber } from 'ethers' +import Highcharts from 'highcharts/highstock' +import HighchartsReact from 'highcharts-react-official' +import moment from 'moment' + +import TailwindConfig from '../config/tailwind' +import { KashiPairDayDataMap } from '../types/KashiPairDayData' + +function TotalDayDataChart({ loading, data }: { loading: boolean; data: KashiPairDayDataMap[] }) { + const getSeries = () => { + let supplyData: any[] = [] + let borrowData: any[] = [] + data.forEach((item) => { + supplyData.push({ + x: moment(item.date).valueOf(), + y: BigNumber.from(item.totalAsset).add(BigNumber.from(item.totalBorrow)).toNumber() / 100.0, + }) + borrowData.push({ + x: moment(item.date).valueOf(), + y: BigNumber.from(item.totalBorrow).toNumber() / 100.0, + }) + }) + return [ + { + type: 'line', + color: TailwindConfig.theme.colors.green.DEFAULT, + name: i18n._('Supply'), + data: supplyData, + tooltip: { + pointFormat: 'Supply   ${point.y}', + }, + }, + { + type: 'line', + color: TailwindConfig.theme.colors.pink.DEFAULT, + name: i18n._('Borrow'), + data: borrowData, + tooltip: { + pointFormat: 'Borrow   ${point.y}', + }, + }, + ] + } + + const options = { + title: { + style: { + height: '50px', + padding: '24px', + fontWeight: 'bold', + fontSize: '18px', + }, + }, + scrollbar: { + enabled: false, + }, + chart: { + backgroundColor: TailwindConfig.theme.colors['dark-900'], + }, + yAxis: [ + { + gridLineColor: TailwindConfig.theme.colors['dark-600'], + }, + ], + series: getSeries(), + rangeSelector: { + buttons: [ + { + type: 'week', + count: 1, + text: '1w', + title: i18n._('View 1 week'), + }, + { + type: 'month', + count: 1, + text: '1m', + title: i18n._('View 1 month'), + }, + { + type: 'month', + count: 3, + text: '3m', + title: i18n._('View 3 months'), + }, + { + type: 'month', + count: 6, + text: '6m', + title: i18n._('View 6 months'), + }, + ], + selected: 1, + }, + } + + return ( +
+
{i18n._('Total Supply & Total Borrow')}
+ {loading || !data || data.length === 0 ? ( +
+
+
+
+
+
+
+
+ ) : ( + <> + + + )} +
+ ) +} + +export default TotalDayDataChart diff --git a/src/features/analytics/kashi/components/TotalTokenCard.tsx b/src/features/analytics/kashi/components/TotalTokenCard.tsx new file mode 100644 index 0000000000..71477901c7 --- /dev/null +++ b/src/features/analytics/kashi/components/TotalTokenCard.tsx @@ -0,0 +1,113 @@ +import classNames from 'classnames' +import { BigNumber } from 'ethers' +import numeral from 'numeral' + +import { useAppContext } from '../context/AppContext' +import { KashiPairsByTokenNew } from '../types/KashiPair' +import Progress from './Progress' + +export type TotalData = { + amount: BigInt + volumeIn24H: BigInt + totalUsers: BigInt + topMarkets: KashiPairsByTokenNew[] +} + +type AttributesByBorrowType = { + progressColor: 'blue' | 'green' | 'pink' + title: string + users: string +} + +type AttributesMapByBorrowType = { + borrow: AttributesByBorrowType + asset: AttributesByBorrowType + supply: AttributesByBorrowType +} + +const AttributesMapByBorrow = { + borrow: { + progressColor: 'pink', + title: 'Total Borrow', + users: 'Borrowers', + }, + asset: { + progressColor: 'green', + title: 'Total Available', + users: 'Suppliers', + }, + supply: { + progressColor: 'blue', + title: 'Total Supply', + users: 'Suppliers', + }, +} as AttributesMapByBorrowType + +const TotakTokenCard = ({ + containerClass = '', + data, + borrow = 'borrow', + loading = false, +}: { + containerClass?: string + data: TotalData + borrow?: 'borrow' | 'asset' | 'supply' + loading?: boolean +}) => { + const attributes = AttributesMapByBorrow[borrow] + const isLoading = data.amount === BigInt(0) || loading + + const { tokenUtilService } = useAppContext() + + return ( +
+
{attributes.title}
+
+
+ {isLoading ? ( +
+ ) : ( + numeral(Number(data.amount) / 100.0).format('($0,0.00)') + )} +
+
Top 3 Markets
+ {isLoading ? ( + <> + + + + + ) : ( + data.topMarkets.map((marketData) => ( + + )) + )} +
+
+ ) +} + +export default TotakTokenCard diff --git a/src/features/analytics/kashi/config/tailwind.ts b/src/features/analytics/kashi/config/tailwind.ts new file mode 100644 index 0000000000..a0b201e96a --- /dev/null +++ b/src/features/analytics/kashi/config/tailwind.ts @@ -0,0 +1,9 @@ +// @ts-ignore +import resolveConfig from 'tailwindcss/resolveConfig' + +import config from '../../../../../tailwind.config.js' + +// @ts-ignore +const TailwindConfig = resolveConfig(config) + +export default TailwindConfig diff --git a/src/features/analytics/kashi/context/AppContext.tsx b/src/features/analytics/kashi/context/AppContext.tsx new file mode 100644 index 0000000000..e7a9a4df34 --- /dev/null +++ b/src/features/analytics/kashi/context/AppContext.tsx @@ -0,0 +1,46 @@ +import React, { createContext, ReactNode, useContext } from 'react' + +import CalculateService from '../services/utils/CalculateService' +import TokenUtilService from '../services/utils/TokenUtilService' + +export const handleLogoError = (event: React.SyntheticEvent) => { + const imgElement = event.target as HTMLImageElement + imgElement.src = '/images/tokens/icon-quiz.jpg' +} +interface AppContextProps { + handleLogoError: (event: React.SyntheticEvent) => void + calculateService: CalculateService + tokenUtilService: TokenUtilService +} +const AppContext = createContext({} as AppContextProps) + +export const AnalyticsKashiAppContextProvider = ({ children }: { children: ReactNode }) => { + const calculateService = CalculateService.getInstance() + const tokenUtilService = TokenUtilService.getInstance() + + return ( + + {children} + + ) +} + +export const useAppContext = () => { + return useContext(AppContext) +} + +export const useCalculateService = () => { + const { calculateService } = useAppContext() + return calculateService +} + +export const useTokenUtilService = () => { + const { tokenUtilService } = useAppContext() + return tokenUtilService +} diff --git a/src/features/analytics/kashi/services/utils/CalculateService.ts b/src/features/analytics/kashi/services/utils/CalculateService.ts new file mode 100644 index 0000000000..33f5859b6e --- /dev/null +++ b/src/features/analytics/kashi/services/utils/CalculateService.ts @@ -0,0 +1,496 @@ +import { BigNumber } from 'ethers' +import moment from 'moment' + +import { KashiPair, KashiPairNew, KashiPairsByToken, KashiPairsByTokenNew } from '../../types/KashiPair' +import { KashiPairDayData, KashiPairDayDataMap, KashiPairDayDataMapsCollateral } from '../../types/KashiPairDayData' +import { Token, TokenNew } from '../../types/Token' + +class CalculateService { + protected static instance: CalculateService + constructor() {} + + protected extractKashiPairTokenSymbols(kashiPairs: KashiPair[], tokenField: 'asset' | 'collateral' = 'asset') { + const symbols = [] as string[] + + kashiPairs.forEach((kashiPair) => { + const symbol = kashiPair[tokenField]?.symbol || '' + + const index = symbols.indexOf(symbol) + if (index === -1) { + symbols.push(symbol) + } + }) + return symbols + } + + extractKashiPairSymbols(kashiPairs: KashiPair[]) { + const symbols = [] as string[] + + kashiPairs.forEach((kashiPair) => { + const symbolAsset = kashiPair.asset?.symbol || '' + const symbolCollateral = kashiPair.collateral?.symbol || '' + + const indexAsset = symbols.indexOf(symbolAsset) + if (indexAsset === -1) { + symbols.push(symbolAsset) + } + + const indexCollateral = symbols.indexOf(symbolCollateral) + if (indexCollateral === -1) { + symbols.push(symbolCollateral) + } + }) + return symbols + } + + extractKashiPairCollateralSymbols(kashiPairs: KashiPair[]) { + return this.extractKashiPairTokenSymbols(kashiPairs, 'collateral') + } + + extractKashiPairAssetSymbols(kashiPairs: KashiPair[]) { + return this.extractKashiPairTokenSymbols(kashiPairs, 'asset') + } + + extractTokenSymbols(tokens: Token[]) { + const symbols = [] as string[] + + tokens.forEach((token) => { + const symbol = token.symbol || '' + const index = symbols.indexOf(symbol) + if (index === -1) { + symbols.push(symbol) + } + }) + return symbols + } + + calculateKashiPairPrices(kashiPairs: KashiPair[], pricesMap: { [key: string]: BigInt }) { + let sumTotalAsset = BigNumber.from('0'), + sumTotalBorrow = BigNumber.from('0') + + const newKashiPairs = kashiPairs.map((kashiPair) => { + let totalAsset = BigNumber.from('0'), + totalBorrow = BigNumber.from('0') + + if (kashiPair.asset) { + totalAsset = BigNumber.from(pricesMap?.[kashiPair.asset.symbol] ?? 0) + .mul(BigNumber.from(kashiPair.totalAssetElastic)) + .div(BigNumber.from('10').pow(Number(kashiPair.asset.decimals) + 6)) + totalBorrow = BigNumber.from(pricesMap?.[kashiPair.asset.symbol] ?? 0) + .mul(BigNumber.from(kashiPair.totalBorrowElastic)) + .div(BigNumber.from('10').pow(Number(kashiPair.asset.decimals) + 6)) + } + sumTotalAsset = sumTotalAsset.add(totalAsset) + sumTotalBorrow = sumTotalBorrow.add(totalBorrow) + const newKashiPair = { + ...kashiPair, + totalAsset: totalAsset.toBigInt(), + totalBorrow: totalBorrow.toBigInt(), + } + return newKashiPair + }) + return { + totalAsset: sumTotalAsset, + totalBorrow: sumTotalBorrow, + kashiPairs: newKashiPairs, + } + } + + calculateKashiPairPricesNew(kashiPairs: KashiPairNew[], pricesMap: { [key: string]: BigInt }) { + let sumTotalAsset = BigNumber.from('0'), + sumTotalBorrow = BigNumber.from('0') + + const newKashiPairs = kashiPairs.map((kashiPair) => { + let totalAsset = BigNumber.from('0'), + totalBorrow = BigNumber.from('0') + + if (kashiPair.asset) { + totalAsset = BigNumber.from(pricesMap?.[kashiPair.asset.symbol] ?? 0) + .mul(BigNumber.from(kashiPair.totalAsset?.elastic)) + .div(BigNumber.from('10').pow(Number(kashiPair.asset.decimals) + 6)) + totalBorrow = BigNumber.from(pricesMap?.[kashiPair.asset.symbol] ?? 0) + .mul(BigNumber.from(kashiPair.totalBorrow?.elastic)) + .div(BigNumber.from('10').pow(Number(kashiPair.asset.decimals) + 6)) + } + sumTotalAsset = sumTotalAsset.add(totalAsset) + sumTotalBorrow = sumTotalBorrow.add(totalBorrow) + const newKashiPair = { + ...kashiPair, + totalAssetAmount: totalAsset.toBigInt(), + totalBorrowAmount: totalBorrow.toBigInt(), + } + return newKashiPair + }) + return { + totalAsset: sumTotalAsset, + totalBorrow: sumTotalBorrow, + kashiPairs: newKashiPairs, + } + } + + calculateKashiPairPricesGroupByAsset(kashiPairs: KashiPair[], pricesMap: { [key: string]: BigInt }) { + const { totalAsset, totalBorrow, kashiPairs: newKashiPairs } = this.calculateKashiPairPrices(kashiPairs, pricesMap) + + const kashiPairsByTokenMap: { [key: string]: KashiPairsByToken } = {} + newKashiPairs.forEach((kashiPair) => { + const { asset } = kashiPair + if (asset) { + if (kashiPairsByTokenMap[asset.id]) { + const kashiPairsByToken = kashiPairsByTokenMap[asset.id] + kashiPairsByToken.kashiPairs.push(kashiPair) + kashiPairsByToken.totalAsset = BigNumber.from(kashiPairsByToken.totalAsset) + .add(BigNumber.from(kashiPair.totalAsset)) + .toBigInt() + + kashiPairsByToken.totalBorrow = BigNumber.from(kashiPairsByToken.totalBorrow) + .add(BigNumber.from(kashiPair.totalBorrow)) + .toBigInt() + } else { + kashiPairsByTokenMap[asset.id] = { + token: asset, + totalAsset: kashiPair.totalAsset, + totalBorrow: kashiPair.totalBorrow, + kashiPairs: [kashiPair], + } + } + } + }) + + const kashiPairsByTokens = Object.values(kashiPairsByTokenMap) + return { + totalAsset, + totalBorrow, + kashiPairsByTokens, + } + } + + calculateKashiPairPricesGroupByAssetNew(kashiPairs: KashiPairNew[], pricesMap: { [key: string]: BigInt }) { + const { + totalAsset, + totalBorrow, + kashiPairs: newKashiPairs, + } = this.calculateKashiPairPricesNew(kashiPairs, pricesMap) + + const kashiPairsByTokenMap: { [key: string]: KashiPairsByTokenNew } = {} + newKashiPairs.forEach((kashiPair) => { + const { asset } = kashiPair + if (asset) { + if (kashiPairsByTokenMap[asset.id]) { + const kashiPairsByToken = kashiPairsByTokenMap[asset.id] + kashiPairsByToken.kashiPairs.push(kashiPair) + kashiPairsByToken.totalAsset = BigNumber.from(kashiPairsByToken.totalAsset) + .add(BigNumber.from(kashiPair.totalAssetAmount)) + .toBigInt() + + kashiPairsByToken.totalBorrow = BigNumber.from(kashiPairsByToken.totalBorrow) + .add(BigNumber.from(kashiPair.totalBorrowAmount)) + .toBigInt() + } else { + kashiPairsByTokenMap[asset.id] = { + token: asset, + totalAsset: kashiPair.totalAssetAmount, + totalBorrow: kashiPair.totalBorrowAmount, + kashiPairs: [kashiPair], + } + } + } + }) + + const kashiPairsByTokens = Object.values(kashiPairsByTokenMap) + return { + totalAsset, + totalBorrow, + kashiPairsByTokens, + } + } + + calculateTokenPrices(tokens: Token[], pricesMap: { [key: string]: BigInt }) { + let sumTotalSupply = BigNumber.from('0') + + const newTokens = tokens.map((token) => { + let totalSupply = BigNumber.from('0') + totalSupply = BigNumber.from(pricesMap[token.symbol] ?? 0) + .mul(BigNumber.from(token.totalSupplyElastic)) + .div(BigNumber.from('10').pow(Number(token.decimals) + 6)) + sumTotalSupply = sumTotalSupply.add(totalSupply) + const newToken = { + ...token, + price: (pricesMap[token.symbol] ?? 0) || 0, + totalSupply: totalSupply.toBigInt(), + } + return newToken + }) + return { + totalSupply: sumTotalSupply, + tokens: newTokens, + } + } + + calculateTokenPricesNew(tokens: TokenNew[], pricesMap: { [key: string]: BigInt }) { + let sumTotalSupply = BigNumber.from('0') + + const newTokens = tokens.map((token) => { + let totalSupply = BigNumber.from('0') + totalSupply = BigNumber.from(pricesMap[token.symbol] ?? 0) + .mul(BigNumber.from(token.rebase?.elastic)) + .div(BigNumber.from('10').pow(Number(token.decimals) + 6)) + sumTotalSupply = sumTotalSupply.add(totalSupply) + const newToken = { + ...token, + price: (pricesMap[token.symbol] ?? 0) || 0, + totalSupply: totalSupply.toBigInt(), + } + return newToken + }) + return { + totalSupply: sumTotalSupply, + tokens: newTokens, + } + } + + calculateKashiPairDayDataPrices(kashiPairs: KashiPairDayData[], pricesMap: { [key: string]: BigInt }) { + const kashiPairsMaps: KashiPairDayDataMap[] = [] + + let sumTotalAsset = BigNumber.from('0'), + sumTotalBorrow = BigNumber.from('0'), + sumAvgExchangeRate = BigNumber.from('0'), + sumAvgUtilization = BigNumber.from('0'), + sumAvgInterestPerSecond = BigNumber.from('0') + + const newKashiPairs = kashiPairs.map((kashiPair) => { + let totalAsset = BigNumber.from('0'), + totalBorrow = BigNumber.from('0') + + if (kashiPair.pair.asset) { + totalAsset = BigNumber.from(pricesMap[kashiPair.pair.asset.symbol] ?? 0) + .mul(BigNumber.from(kashiPair.totalAssetElastic)) + .div(BigNumber.from('10').pow(Number(kashiPair.pair.asset.decimals) + 6)) + totalBorrow = BigNumber.from(pricesMap[kashiPair.pair.asset.symbol] ?? 0) + .mul(BigNumber.from(kashiPair.totalBorrowElastic)) + .div(BigNumber.from('10').pow(Number(kashiPair.pair.asset.decimals) + 6)) + } + + sumTotalAsset = sumTotalAsset.add(totalAsset) + sumTotalBorrow = sumTotalBorrow.add(totalBorrow) + sumAvgExchangeRate = sumAvgExchangeRate.add(BigNumber.from(kashiPair.avgExchangeRate)) + sumAvgUtilization = sumAvgUtilization.add(BigNumber.from(kashiPair.avgUtilization)) + sumAvgInterestPerSecond = sumAvgInterestPerSecond.add(BigNumber.from(kashiPair.avgInterestPerSecond)) + + const newKashiPair = { + ...kashiPair, + totalAsset: totalAsset.toBigInt(), + totalBorrow: totalBorrow.toBigInt(), + } + + const kashiPairDate = moment.unix(kashiPair.date).format('YYYY-MM-DD') + const itemKashiPairMap = kashiPairsMaps.find((kashiPairMap) => kashiPairMap.date === kashiPairDate) + + if (itemKashiPairMap) { + itemKashiPairMap.totalAsset = BigNumber.from(itemKashiPairMap.totalAsset).add(totalAsset).toBigInt() + itemKashiPairMap.totalBorrow = BigNumber.from(itemKashiPairMap.totalBorrow).add(totalBorrow).toBigInt() + itemKashiPairMap.avgExchangeRate = BigNumber.from(itemKashiPairMap.avgExchangeRate) + .add(BigNumber.from(kashiPair.avgExchangeRate)) + .toBigInt() + itemKashiPairMap.avgUtilization = BigNumber.from(itemKashiPairMap.avgUtilization) + .add(BigNumber.from(kashiPair.avgUtilization)) + .toBigInt() + itemKashiPairMap.avgInterestPerSecond = BigNumber.from(itemKashiPairMap.avgInterestPerSecond) + .add(BigNumber.from(kashiPair.avgInterestPerSecond)) + .toBigInt() + itemKashiPairMap.kashiPairs.push(newKashiPair) + } else { + kashiPairsMaps.push({ + totalAsset: totalAsset.toBigInt(), + totalBorrow: totalBorrow.toBigInt(), + avgExchangeRate: kashiPair.avgExchangeRate || BigInt(0), + avgUtilization: kashiPair.avgUtilization || BigInt(0), + avgInterestPerSecond: kashiPair.avgInterestPerSecond || BigInt(0), + date: kashiPairDate, + kashiPairs: [newKashiPair], + }) + } + + let prevKashiPairsMap: KashiPairDayDataMap = { + kashiPairs: [], + totalAsset: BigInt(0), + totalBorrow: BigInt(0), + avgExchangeRate: BigInt(0), + avgUtilization: BigInt(0), + avgInterestPerSecond: BigInt(0), + date: '0000-00-00', + } + kashiPairsMaps.forEach((kashiPairMap) => { + const { kashiPairs: prevKashiPairs } = prevKashiPairsMap + const { kashiPairs } = kashiPairMap + prevKashiPairs.forEach((prevKashiPair) => { + const index = kashiPairs.findIndex((kashiPair) => prevKashiPair.pair.id === kashiPair.pair.id) + if (index === -1) { + kashiPairMap.totalAsset = BigNumber.from(kashiPairMap.totalAsset) + .add(BigNumber.from(prevKashiPair.totalAsset)) + .toBigInt() + kashiPairMap.totalBorrow = BigNumber.from(kashiPairMap.totalBorrow) + .add(BigNumber.from(prevKashiPair.totalBorrow)) + .toBigInt() + kashiPairMap.avgExchangeRate = BigNumber.from(kashiPairMap.avgExchangeRate) + .add(BigNumber.from(prevKashiPair.avgExchangeRate)) + .toBigInt() + kashiPairMap.avgUtilization = BigNumber.from(kashiPairMap.avgUtilization) + .add(BigNumber.from(prevKashiPair.avgUtilization)) + .toBigInt() + kashiPairMap.avgInterestPerSecond = BigNumber.from(kashiPairMap.avgInterestPerSecond) + .add(BigNumber.from(prevKashiPair.avgInterestPerSecond)) + .toBigInt() + kashiPairs.push(prevKashiPair) + } + }) + + kashiPairMap.avgExchangeRate = BigNumber.from(kashiPairMap.avgExchangeRate) + .div(BigNumber.from(kashiPairMap.kashiPairs.length)) + .toBigInt() + kashiPairMap.avgUtilization = BigNumber.from(kashiPairMap.avgUtilization) + .div(BigNumber.from(kashiPairMap.kashiPairs.length)) + .toBigInt() + kashiPairMap.avgInterestPerSecond = BigNumber.from(kashiPairMap.avgInterestPerSecond) + .div(BigNumber.from(kashiPairMap.kashiPairs.length)) + .toBigInt() + prevKashiPairsMap = kashiPairMap + }) + kashiPairsMaps.sort((a, b) => a.date.localeCompare(b.date)) + return newKashiPair + }) + + return { + totalAsset: sumTotalAsset.toBigInt(), + totalBorrow: sumTotalBorrow.toBigInt(), + kashiPairs: newKashiPairs, + kashiPairsMaps, + } + } + + calculateKashiPairDayDataPricesMonthly(kashiPairs: KashiPairDayData[], pricesMap: { [key: string]: BigInt }) { + const kashiPairsMaps: KashiPairDayDataMap[] = [] + + let sumTotalAsset = BigNumber.from('0'), + sumTotalBorrow = BigNumber.from('0'), + sumAvgExchangeRate = BigNumber.from('0'), + sumAvgUtilization = BigNumber.from('0'), + sumAvgInterestPerSecond = BigNumber.from('0') + + const newKashiPairs = kashiPairs.map((kashiPair) => { + let totalAsset = BigNumber.from('0'), + totalBorrow = BigNumber.from('0') + + if (kashiPair.pair.asset) { + totalAsset = BigNumber.from(pricesMap[kashiPair.pair.asset.symbol] ?? 0) + .mul(BigNumber.from(kashiPair.totalAssetElastic)) + .div(BigNumber.from('10').pow(Number(kashiPair.pair.asset.decimals) + 6)) + totalBorrow = BigNumber.from(pricesMap[kashiPair.pair.asset.symbol] ?? 0) + .mul(BigNumber.from(kashiPair.totalBorrowElastic)) + .div(BigNumber.from('10').pow(Number(kashiPair.pair.asset.decimals) + 6)) + } + + sumTotalAsset = sumTotalAsset.add(totalAsset) + sumTotalBorrow = sumTotalBorrow.add(totalBorrow) + sumAvgExchangeRate = sumAvgExchangeRate.add(BigNumber.from(kashiPair.avgExchangeRate)) + sumAvgUtilization = sumAvgUtilization.add(BigNumber.from(kashiPair.avgUtilization)) + sumAvgInterestPerSecond = sumAvgInterestPerSecond.add(BigNumber.from(kashiPair.avgInterestPerSecond)) + + const newKashiPair = { + ...kashiPair, + totalAsset: totalAsset.toBigInt(), + totalBorrow: totalBorrow.toBigInt(), + } + + const kashiPairDate = moment.unix(kashiPair.date).startOf('month').format('YYYY-MM-DD') + const itemKashiPairMap = kashiPairsMaps.find((kashiPairMap) => kashiPairMap.date === kashiPairDate) + + if (itemKashiPairMap) { + itemKashiPairMap.totalAsset = BigNumber.from(itemKashiPairMap.totalAsset).add(totalAsset).toBigInt() + itemKashiPairMap.totalBorrow = BigNumber.from(itemKashiPairMap.totalBorrow).add(totalBorrow).toBigInt() + itemKashiPairMap.avgExchangeRate = BigNumber.from(itemKashiPairMap.avgExchangeRate) + .add(BigNumber.from(kashiPair.avgExchangeRate)) + .toBigInt() + itemKashiPairMap.avgUtilization = BigNumber.from(itemKashiPairMap.avgUtilization) + .add(BigNumber.from(kashiPair.avgUtilization)) + .toBigInt() + itemKashiPairMap.avgInterestPerSecond = BigNumber.from(itemKashiPairMap.avgInterestPerSecond) + .add(BigNumber.from(kashiPair.avgInterestPerSecond)) + .toBigInt() + itemKashiPairMap.kashiPairs.push(newKashiPair) + } else { + kashiPairsMaps.push({ + totalAsset: totalAsset.toBigInt(), + totalBorrow: totalBorrow.toBigInt(), + avgExchangeRate: kashiPair.avgExchangeRate || BigInt(0), + avgUtilization: kashiPair.avgUtilization || BigInt(0), + avgInterestPerSecond: kashiPair.avgInterestPerSecond || BigInt(0), + date: kashiPairDate, + kashiPairs: [newKashiPair], + }) + } + kashiPairsMaps.forEach((value) => { + value.avgExchangeRate = BigNumber.from(value.avgExchangeRate) + .div(BigNumber.from(value.kashiPairs.length)) + .toBigInt() + value.avgUtilization = BigNumber.from(value.avgUtilization) + .div(BigNumber.from(value.kashiPairs.length)) + .toBigInt() + value.avgInterestPerSecond = BigNumber.from(value.avgInterestPerSecond) + .div(BigNumber.from(value.kashiPairs.length)) + .toBigInt() + }) + kashiPairsMaps.sort((a, b) => a.date.localeCompare(b.date)) + return newKashiPair + }) + + return { + totalAsset: sumTotalAsset.toBigInt(), + totalBorrow: sumTotalBorrow.toBigInt(), + kashiPairs: newKashiPairs, + kashiPairsMaps, + } + } + + calculateKashiPairDayDataPricesByCollateral( + kashiPairs: KashiPairDayData[], + pricesMap: { [key: string]: BigInt } + ): KashiPairDayDataMapsCollateral[] { + const kashiPairsMapGroupTemp: { + [key: string]: { kashiPairs: KashiPairDayData[]; collateral: Token } + } = {} + kashiPairs.forEach((kashiPair) => { + const { collateral } = kashiPair.pair + if (collateral && collateral.id) { + if (kashiPairsMapGroupTemp[collateral.id]) { + kashiPairsMapGroupTemp[collateral.id].kashiPairs.push(kashiPair) + } else { + kashiPairsMapGroupTemp[collateral.id] = { + collateral, + kashiPairs: [kashiPair], + } + } + } + }) + const kashiPairsMapGroup = Object.values(kashiPairsMapGroupTemp) + + const kashiPairsMapCollateralGroup = kashiPairsMapGroup.map((value) => { + const { kashiPairsMaps } = this.calculateKashiPairDayDataPrices(value.kashiPairs, pricesMap) + return { + collateral: value.collateral, + kashiPairsMaps, + } + }) + + return kashiPairsMapCollateralGroup.sort((a, b) => a.collateral.symbol.localeCompare(b.collateral.symbol)) + } + + static getInstance() { + if (CalculateService.instance) { + return CalculateService.instance + } + CalculateService.instance = new CalculateService() + return CalculateService.instance + } +} + +export default CalculateService diff --git a/src/features/analytics/kashi/services/utils/TokenUtilService.ts b/src/features/analytics/kashi/services/utils/TokenUtilService.ts new file mode 100644 index 0000000000..c6f4a849ff --- /dev/null +++ b/src/features/analytics/kashi/services/utils/TokenUtilService.ts @@ -0,0 +1,32 @@ +class TokenUtilService { + protected static instance: TokenUtilService + constructor() {} + + static getInstance() { + if (TokenUtilService.instance) { + return TokenUtilService.instance + } + TokenUtilService.instance = new TokenUtilService() + return TokenUtilService.instance + } + + symbol(symbol: string | undefined): string | undefined { + if (symbol && symbol.toLocaleLowerCase() === 'ftx token') { + return 'FTT' + } + return symbol + } + + pairSymbol(symbol1: string | undefined, symbol2: string | undefined): string { + return `${this.symbol(symbol1)}/${this.symbol(symbol2)}` + } + + name(name: string | undefined): string | undefined { + if (name && name.toLocaleLowerCase() === 'ftt') { + return 'FTX Token' + } + return name + } +} + +export default TokenUtilService diff --git a/src/features/analytics/kashi/types/Enums.ts b/src/features/analytics/kashi/types/Enums.ts new file mode 100644 index 0000000000..629d947e65 --- /dev/null +++ b/src/features/analytics/kashi/types/Enums.ts @@ -0,0 +1,9 @@ +export enum ChainIdEnum { + Ethereum = '0x1', + Gnosis = '0x64', +} + +export enum NativeCoinEnum { + USD = 'USD', + ETH = 'ETH', +} diff --git a/src/features/analytics/kashi/types/KashiPair.ts b/src/features/analytics/kashi/types/KashiPair.ts new file mode 100644 index 0000000000..50cb62dd2a --- /dev/null +++ b/src/features/analytics/kashi/types/KashiPair.ts @@ -0,0 +1,83 @@ +import { Rebase } from '@sushiswap/core-sdk' + +import { Token } from './Token' + +export type KashiPair = { + id: string + type?: string + owner?: string + feeTo?: string + name: string + symbol: string + oracle?: string + asset?: Token + collateral?: Token + exchangeRate?: BigInt + totalAssetElastic?: BigInt + totalAssetBase?: BigInt + totalAsset?: BigInt + totalCollateralShare?: BigInt + totalBorrowElastic?: BigInt + totalBorrowBase?: BigInt + totalBorrow?: BigInt + interestPerSecond?: BigInt + utilization?: BigInt + feesEarnedFraction?: BigInt + totalFeesEarnedFraction?: BigInt + lastAccrued?: BigInt + supplyAPR?: BigInt + borrowAPR?: BigInt + block?: BigInt + timestamp?: BigInt +} + +export type KashiPairKpi = { + id: string + supplyAPR?: BigInt + borrowAPR?: BigInt + utilization?: BigInt + totalFeesEarnedFraction?: BigInt +} + +export type KashiPairAccrueInfo = { + id: string + interestPerSecond?: BigInt + lastAccrued?: BigInt + feesEarnedFraction?: BigInt +} + +export type KashiPairNew = { + id: string + type?: string + owner?: string + feeTo?: string + name: string + symbol: string + oracle?: string + asset?: Token + collateral?: Token + exchangeRate?: BigInt + totalAsset?: Rebase + totalCollateralShare?: BigInt + totalBorrow?: Rebase + accrueInfo?: KashiPairAccrueInfo + kpi?: KashiPairKpi + totalAssetAmount?: BigInt + totalBorrowAmount?: BigInt + block?: BigInt + timestamp?: BigInt +} + +export type KashiPairsByToken = { + token: Token + totalAsset: BigInt + totalBorrow: BigInt + kashiPairs: KashiPair[] +} + +export type KashiPairsByTokenNew = { + token: Token + totalAsset: BigInt + totalBorrow: BigInt + kashiPairs: KashiPairNew[] +} diff --git a/src/features/analytics/kashi/types/KashiPairDayData.ts b/src/features/analytics/kashi/types/KashiPairDayData.ts new file mode 100644 index 0000000000..d0c15221df --- /dev/null +++ b/src/features/analytics/kashi/types/KashiPairDayData.ts @@ -0,0 +1,33 @@ +import { KashiPair } from './KashiPair' +import { Token } from './Token' + +export type KashiPairDayData = { + id: string + date: number + pair: KashiPair + totalAsset?: BigInt + totalAssetElastic?: BigInt + totalAssetBase?: BigInt + totalCollateralShare?: BigInt + totalBorrow?: BigInt + totalBorrowElastic?: BigInt + totalBorrowBase?: BigInt + avgExchangeRate?: BigInt + avgUtilization?: BigInt + avgInterestPerSecond?: BigInt +} + +export type KashiPairDayDataMap = { + totalAsset: BigInt + totalBorrow: BigInt + avgExchangeRate: BigInt + avgUtilization: BigInt + avgInterestPerSecond: BigInt + date: string + kashiPairs: KashiPairDayData[] +} + +export type KashiPairDayDataMapsCollateral = { + collateral: Token + kashiPairsMaps: KashiPairDayDataMap[] +} diff --git a/src/features/analytics/kashi/types/Token.ts b/src/features/analytics/kashi/types/Token.ts new file mode 100644 index 0000000000..457c3b47d4 --- /dev/null +++ b/src/features/analytics/kashi/types/Token.ts @@ -0,0 +1,26 @@ +import { Rebase } from '@sushiswap/core-sdk' + +export type Token = { + id: string + name: string + symbol: string + decimals: BigInt + totalSupply?: BigInt + totalSupplyElastic?: BigInt + totalSupplyBase?: BigInt + price?: BigInt + block?: BigInt + timestamp?: BigInt +} + +export type TokenNew = { + id: string + name: string + symbol: string + decimals: BigInt + totalSupply?: BigInt + rebase?: Rebase + price?: BigInt + block?: BigInt + timestamp?: BigInt +} diff --git a/src/features/analytics/kashi/types/User.ts b/src/features/analytics/kashi/types/User.ts new file mode 100644 index 0000000000..957a1c1c72 --- /dev/null +++ b/src/features/analytics/kashi/types/User.ts @@ -0,0 +1,5 @@ +export type User = { + id: string + block?: BigInt + timestamp?: BigInt +} diff --git a/src/features/analytics/kashi/views/KashiPairView.tsx b/src/features/analytics/kashi/views/KashiPairView.tsx new file mode 100644 index 0000000000..37b3e7aeb7 --- /dev/null +++ b/src/features/analytics/kashi/views/KashiPairView.tsx @@ -0,0 +1,137 @@ +/* eslint-disable @next/next/no-img-element */ +import { i18n } from '@lingui/core' +import { ChainId, Token } from '@sushiswap/core-sdk' +import { CurrencyLogoArray } from 'app/components/CurrencyLogo' +import { useKashiTokens } from 'app/features/kashi/hooks' +import { useKashiPricesSubgraphWithLoadingIndicator } from 'app/hooks/usePricesSubgraph' +import { useDataKashiPairWithLoadingIndicator } from 'app/services/graph/hooks/kashipairs' +import { useActiveWeb3React } from 'app/services/web3' +import { useRouter } from 'next/router' +import { useEffect, useState } from 'react' + +import PairCard from '../components/PairCard' +import PairInterestPerSecondDayDataChart from '../components/PairInteresetPerSecondDayDataChart' +import PairSupplyAccruedInterestDayDataChart from '../components/PairSupplyAccruedInterestDayDataChart' +import PairSupplyBorrowDayDataChart from '../components/PairSupplyBorrowDayDataChart' +import PairSupplyBorrowMonthDataChart from '../components/PairSupplyBorrowMonthDataChart' +import PairUtilizationDayDataChart from '../components/PairUtilizationDayDataChart' +import { useAppContext } from '../context/AppContext' +import { KashiPair } from '../types/KashiPair' +import { KashiPairDayData, KashiPairDayDataMap } from '../types/KashiPairDayData' +const KashiPairView = () => { + const { tokenUtilService, calculateService, handleLogoError } = useAppContext() + const [kashiPair, setKashiPair] = useState() + const [kashiPairDayData, setKashiPairDayData] = useState([]) + + const [kashiPairDayDataMonthly, setKashiPairDayDataMonthly] = useState([]) + + const router = useRouter() + const { id } = router.query + + const { chainId } = useActiveWeb3React() + const { loading: loadingKashiPairs, data: dataKashiPairs } = useDataKashiPairWithLoadingIndicator({ + chainId, + variables: { id }, + }) + + const tokens = useKashiTokens() + const { loading: loadingPrice, data: pricesMap } = useKashiPricesSubgraphWithLoadingIndicator(Object.values(tokens)) + + useEffect(() => { + if (dataKashiPairs) { + setKashiPairData() + } + }, [dataKashiPairs, loadingPrice]) + + const setKashiPairData = async () => { + if (!loadingPrice) { + const { kashiPairs, kashiPairDayDatas }: { kashiPairs: KashiPair[]; kashiPairDayDatas: KashiPairDayData[] } = + dataKashiPairs + + const { kashiPairs: newKashiPairs } = calculateService.calculateKashiPairPrices(kashiPairs, pricesMap) + + const kashiPair = newKashiPairs[0] + setKashiPair(kashiPair) + + const { kashiPairsMaps } = calculateService.calculateKashiPairDayDataPrices(kashiPairDayDatas, pricesMap) + setKashiPairDayData(kashiPairsMaps) + + const { kashiPairsMaps: kashiPairsMapMonthly } = calculateService.calculateKashiPairDayDataPricesMonthly( + kashiPairDayDatas, + pricesMap + ) + setKashiPairDayDataMonthly(kashiPairsMapMonthly) + } + } + + // let asset = undefined + // let collateral = undefined + const kashiAsset = kashiPair?.asset + const kashiCollateral = kashiPair?.collateral + // if (kashiAsset !== undefined && kashiCollateral !== undefined && chainId !== undefined) { + const asset = new Token( + chainId ?? ChainId.ETHEREUM, + kashiAsset?.id ?? '0xdAC17F958D2ee523a2206206994597C13D831ec7', + Number(kashiAsset?.decimals ?? 0), + kashiAsset?.symbol, + kashiAsset?.name + ) + const collateral = new Token( + chainId ?? ChainId.ETHEREUM, + kashiCollateral?.id ?? '0xdAC17F958D2ee523a2206206994597C13D831ec7', + Number(kashiCollateral?.decimals ?? 0), + kashiCollateral?.symbol, + kashiCollateral?.name + ) + // } + return ( + <> +
+
+ {!kashiPair && asset !== undefined && collateral !== undefined ? ( +
+
+
+
+
+
+
+
+
+
+
+ ) : ( +
+
+ +
+
+

+ {tokenUtilService.symbol(kashiPair?.asset?.symbol)}/ + {tokenUtilService.symbol(kashiPair?.collateral?.symbol)} +

+
+
+ )} +
+
+
+ +
+ + + + + + +
+
+ + ) +} + +export default KashiPairView diff --git a/src/features/analytics/kashi/views/KashiPairsView.tsx b/src/features/analytics/kashi/views/KashiPairsView.tsx new file mode 100644 index 0000000000..e2db5196b2 --- /dev/null +++ b/src/features/analytics/kashi/views/KashiPairsView.tsx @@ -0,0 +1,177 @@ +import { useKashiTokens } from 'app/features/kashi/hooks' +import { useKashiPricesSubgraphWithLoadingIndicator } from 'app/hooks/usePricesSubgraph' +import { + useDataKashiPairsDayDataWithLoadingIndicator, + useDataKashiPairsWithLoadingIndicator, +} from 'app/services/graph/hooks/kashipairs' +import { useActiveWeb3React } from 'app/services/web3' +import { BigNumber } from 'ethers' +import { useEffect, useState } from 'react' + +import PairMarketTable from '../components/PairMarketTable' +import TotalCard from '../components/TotalCard' +import TotalCompareChart from '../components/TotalCompareChart' +import TotalDayDataChart from '../components/TotalDayDataChart' +import { useAppContext } from '../context/AppContext' +import { KashiPairNew } from '../types/KashiPair' +import { KashiPairDayData, KashiPairDayDataMap } from '../types/KashiPairDayData' + +const KashiPairsView = () => { + const { chainId } = useActiveWeb3React() + const { loading: loadingKashiPairs, data: dataKashiPairs } = useDataKashiPairsWithLoadingIndicator({ chainId }) + + const { loading: loadingKashiPairsDayData0, data: dataKashiPairsDayData0 } = + useDataKashiPairsDayDataWithLoadingIndicator({ + chainId, + variables: { skip: 0 }, + }) + + const { loading: loadingKashiPairsDayData1, data: dataKashiPairsDayData1 } = + useDataKashiPairsDayDataWithLoadingIndicator({ + chainId, + variables: { skip: 1000 }, + }) + + const [kashiPairsDayData, setKashiPairsDayData] = useState([]) + + const [calculating, setCalculating] = useState(true) + //const [pricesMap, setPricesMap] = useState<{ [key: string]: BigInt }>({}) + const [kashiPairs, setKashiPairs] = useState([] as KashiPairNew[]) + const { calculateService } = useAppContext() + + const [totalAssetsAmount, setTotalAssetsAmount] = useState(BigInt(0)) + const [totalBorrowsAmount, setTotalBorrowsAmount] = useState(BigInt(0)) + + const [top3MarketsBySupply, setTop3MarketsBySupply] = useState([]) + const [top3MarketsByAsset, setTop3MarketsByAsset] = useState([]) + const [top3MarketsByBorrow, setTop3MarketsByBorrow] = useState([]) + + const loading = calculating || loadingKashiPairs + const loadingDayData = loading || loadingKashiPairsDayData0 || loadingKashiPairsDayData1 + + const tokens = useKashiTokens() + const { loading: loadingPrice, data: pricesMap } = useKashiPricesSubgraphWithLoadingIndicator(Object.values(tokens)) + + useEffect(() => { + if (dataKashiPairs) { + if (dataKashiPairs.kashiPairs) { + setKashiPairsData(dataKashiPairs.kashiPairs) + } + } + }, [dataKashiPairs, loadingPrice]) + + const setKashiPairsData = async (kashiPairsData: KashiPairNew[]) => { + if (!loadingPrice) { + const { + totalAsset: totalAssetsValue, + totalBorrow: totalBorrowsValue, + kashiPairs: newKashiPairs, + } = calculateService.calculateKashiPairPricesNew(kashiPairsData, pricesMap) + + const sortedKashiPairsBySupply = [...newKashiPairs].sort((a: KashiPairNew, b: KashiPairNew) => + BigNumber.from(a.totalAssetAmount) + .add(BigNumber.from(a.totalBorrowAmount)) + .lte(BigNumber.from(b.totalAssetAmount).add(BigNumber.from(b.totalBorrowAmount))) + ? 1 + : -1 + ) + + const sortedKashiPairsByAsset = [...newKashiPairs].sort((a: KashiPairNew, b: KashiPairNew) => + BigNumber.from(a.totalAssetAmount).lte(BigNumber.from(b.totalAssetAmount)) ? 1 : -1 + ) + + const sortedKashiPairsByBorrow = [...newKashiPairs].sort((a: KashiPairNew, b: KashiPairNew) => + BigNumber.from(a.totalBorrowAmount).lte(BigNumber.from(b.totalBorrowAmount)) ? 1 : -1 + ) + + setCalculating(false) + + setTotalAssetsAmount(totalAssetsValue.toBigInt()) + setTotalBorrowsAmount(totalBorrowsValue.toBigInt()) + + setTop3MarketsBySupply( + sortedKashiPairsBySupply.slice(0, sortedKashiPairsBySupply.length < 3 ? sortedKashiPairsBySupply.length : 3) + ) + setTop3MarketsByAsset( + sortedKashiPairsByAsset.slice(0, sortedKashiPairsByAsset.length < 3 ? sortedKashiPairsByAsset.length : 3) + ) + setTop3MarketsByBorrow( + sortedKashiPairsByBorrow.slice(0, sortedKashiPairsByBorrow.length < 3 ? sortedKashiPairsByBorrow.length : 3) + ) + + setKashiPairs(newKashiPairs) + } + } + + useEffect(() => {}, [kashiPairs]) + + useEffect(() => { + if (!loadingKashiPairs && !calculating && !loadingKashiPairsDayData0 && !loadingKashiPairsDayData1) { + const dataKashiPairsDayDataMap = [dataKashiPairsDayData0, dataKashiPairsDayData1] + + const dataKashiPairsDayData = dataKashiPairsDayDataMap.reduce( + (prev: KashiPairDayData[], current?: { kashiPairDayDatas?: KashiPairDayData[] }) => [ + ...prev, + ...(current && current.kashiPairDayDatas ? current.kashiPairDayDatas : []), + ], + [] + ) + const { kashiPairsMaps: kashiPairsMap } = calculateService.calculateKashiPairDayDataPrices( + dataKashiPairsDayData, + pricesMap + ) + setKashiPairsDayData(kashiPairsMap) + } + }, [loadingKashiPairs, calculating, loadingKashiPairsDayData0, loadingKashiPairsDayData1]) + return ( + <> + {/* */} +
+ + + +
+
+
+
+ +
+
+ +
+
+
+
+ +
+ + ) +} +export default KashiPairsView diff --git a/src/features/analytics/kashi/views/KashiTokenView.tsx b/src/features/analytics/kashi/views/KashiTokenView.tsx new file mode 100644 index 0000000000..5c92337464 --- /dev/null +++ b/src/features/analytics/kashi/views/KashiTokenView.tsx @@ -0,0 +1,181 @@ +/* eslint-disable @next/next/no-img-element */ +import { ChainId, Token as TokenSDK } from '@sushiswap/core-sdk' +import { CurrencyLogo, CurrencyLogoArray } from 'app/components/CurrencyLogo' +import { useKashiTokens } from 'app/features/kashi/hooks' +import { useKashiPricesSubgraphWithLoadingIndicator } from 'app/hooks/usePricesSubgraph' +import { + useDataKashiTokensDayDataWithLoadingIndicator, + useDataKashiTokenWithLoadingIndicator, +} from 'app/services/graph/hooks/kashipairs' +import { useActiveWeb3React } from 'app/services/web3' +import { useRouter } from 'next/router' +import { useEffect, useState } from 'react' + +import PairCollateralPieChart from '../components/PairCollateralPieChart' +import PairSupplyBorrowDayDataChart from '../components/PairSupplyBorrowDayDataChart' +import TokenCard from '../components/TokenCard' +import { useAppContext } from '../context/AppContext' +import { KashiPairNew } from '../types/KashiPair' +import { KashiPairDayDataMap, KashiPairDayDataMapsCollateral } from '../types/KashiPairDayData' +import { Token, TokenNew } from '../types/Token' + +const KashiTokenView = () => { + const [token, setToken] = useState() + const [totalAsset, setTotalAsset] = useState(BigInt(0)) + const [totalBorrow, setTotalBorrow] = useState(BigInt(0)) + + const [kashiPairs, setKashiPairs] = useState([]) + const [kashiPairDayDataMaps, setKashiPairDayDataMaps] = useState([]) + + const [kashiPairDayDataMapsCollaterals, setKashiPairDayDataMapsCollaterals] = useState< + KashiPairDayDataMapsCollateral[] + >([]) + + const { calculateService, tokenUtilService } = useAppContext() + + const router = useRouter() + const { id } = router.query + const { chainId } = useActiveWeb3React() + const { loading: loadingDataToken, data: dataToken } = useDataKashiTokenWithLoadingIndicator({ + chainId, + variables: { id }, + }) + + const pairIds = kashiPairs.map((kashiPair) => kashiPair.id) + + const { loading: loadingKashiPairDayData, data: dataKashiPairDayData } = + useDataKashiTokensDayDataWithLoadingIndicator({ + chainId, + variables: { pairIds }, + }) + + const tokens = useKashiTokens() + const { loading: loadingPrice, data: pricesMap } = useKashiPricesSubgraphWithLoadingIndicator(Object.values(tokens)) + + useEffect(() => { + if (dataToken) { + setTokenData() + } + }, [dataToken, loadingPrice]) + + useEffect(() => { + if (!loadingPrice) { + const pricesMapKeys = Object.keys(pricesMap) + if (pricesMapKeys.length > 0 && dataKashiPairDayData && dataKashiPairDayData.kashiPairDayDatas.length > 0) { + const kashiPairDayDataMapsCollaterals = calculateService.calculateKashiPairDayDataPricesByCollateral( + dataKashiPairDayData.kashiPairDayDatas, + pricesMap + ) + + const { kashiPairsMaps } = calculateService.calculateKashiPairDayDataPrices( + dataKashiPairDayData.kashiPairDayDatas, + pricesMap + ) + setKashiPairDayDataMapsCollaterals(kashiPairDayDataMapsCollaterals) + setKashiPairDayDataMaps(kashiPairsMaps) + } + } + }, [dataKashiPairDayData]) + + const setTokenData = async () => { + const { tokens, kashiPairs }: { tokens: TokenNew[]; kashiPairs: KashiPairNew[] } = dataToken + + const { tokens: newTokens } = calculateService.calculateTokenPricesNew(tokens, pricesMap) + + const token = newTokens[0] + setToken(token) + + const { + kashiPairs: newKashiPairs, + totalAsset, + totalBorrow, + } = calculateService.calculateKashiPairPricesNew(kashiPairs, pricesMap) + + setTotalAsset(totalAsset.toBigInt()) + setTotalBorrow(totalBorrow.toBigInt()) + setKashiPairs(newKashiPairs) + } + + const token0 = new TokenSDK( + chainId ?? ChainId.ETHEREUM, + token?.id ?? '0xdAC17F958D2ee523a2206206994597C13D831ec7', + Number(token?.decimals ?? 0), + token?.symbol, + token?.name + ) + return ( + <> +
+
+ {!token ? ( +
+
+
+
+
+
+
+
+
+
+ ) : ( +
+
+ +
+
+

{tokenUtilService.symbol(token?.symbol)}

+
+
+ )} +
+
+
+ +
+ + + +
+
+ + +
+
+ {kashiPairDayDataMapsCollaterals + ?.filter((value) => value.kashiPairsMaps.length > 0) + .map((value) => { + const { collateral, kashiPairsMaps } = value + const token1 = new TokenSDK( + ChainId.ETHEREUM, + collateral?.id ?? '', + Number(collateral?.decimals ?? 0), + collateral?.symbol, + collateral?.name + ) + return ( +
+
+
+ +
+
+

+ {tokenUtilService.symbol(token?.symbol)}/{tokenUtilService.symbol(collateral?.symbol)} +

+
+
+
+ + +
+
+ ) + })} +
+
+ + ) +} + +export default KashiTokenView diff --git a/src/features/analytics/kashi/views/KashiTokensView.tsx b/src/features/analytics/kashi/views/KashiTokensView.tsx new file mode 100644 index 0000000000..da5b7eba6c --- /dev/null +++ b/src/features/analytics/kashi/views/KashiTokensView.tsx @@ -0,0 +1,128 @@ +import { useKashiTokens } from 'app/features/kashi/hooks' +import { useKashiPricesSubgraphWithLoadingIndicator } from 'app/hooks/usePricesSubgraph' +import { useDataKashiTokensWithLoadingIndicator } from 'app/services/graph/hooks/kashipairs' +import { useActiveWeb3React } from 'app/services/web3' +import { BigNumber } from 'ethers' +import { useEffect, useState } from 'react' + +import TokenMarketTable from '../components/TokenMarketTable' +import TotakTokenCard from '../components/TotalTokenCard' +import { useAppContext } from '../context/AppContext' +import { KashiPairsByTokenNew } from '../types/KashiPair' + +const KashiTokensView = () => { + const { chainId } = useActiveWeb3React() + const { loading: loadingToken, data: dataKashiPairs } = useDataKashiTokensWithLoadingIndicator({ chainId }) + + const [calculating, setCalculating] = useState(true) + const [totalAsset, setTotalAsset] = useState(BigInt(0)) + const [totalBorrow, setTotalBorrow] = useState(BigInt(0)) + const [top3MarketsBySupply, setTop3MarketsBySupply] = useState([]) + const [top3MarketsByAsset, setTop3MarketsByAsset] = useState([]) + const [top3MarketsByBorrow, setTop3MarketsByBorrow] = useState([]) + + const [kashiPairsByTokens, setKashiPairsByTokens] = useState([]) + const { calculateService } = useAppContext() + const loading = loadingToken || calculating + + const tokens = useKashiTokens() + const { loading: loadingPrice, data: pricesMap } = useKashiPricesSubgraphWithLoadingIndicator(Object.values(tokens)) + + useEffect(() => { + if (dataKashiPairs) { + if (dataKashiPairs.kashiPairs) { + setDataKashiPairs() + } + } + }, [dataKashiPairs, loadingPrice]) + + const setDataKashiPairs = async () => { + if (!loadingPrice) { + const { kashiPairs } = dataKashiPairs + + const { kashiPairsByTokens, totalAsset, totalBorrow } = calculateService.calculateKashiPairPricesGroupByAssetNew( + kashiPairs, + pricesMap + ) + setCalculating(false) + kashiPairsByTokens.sort((a, b) => + BigNumber.from(a.totalAsset) + .add(BigNumber.from(a.totalBorrow)) + .gte(BigNumber.from(b.totalAsset).add(BigNumber.from(b.totalBorrow))) + ? -1 + : 1 + ) + + const kashiPairsByTokensSortedByAsset = [...kashiPairsByTokens].sort((a, b) => + a.totalAsset > b.totalAsset ? -1 : 1 + ) + + const kashiPairsByTokensSortedByBorrow = [...kashiPairsByTokens].sort((a, b) => + a.totalBorrow > b.totalBorrow ? -1 : 1 + ) + + setTop3MarketsBySupply(kashiPairsByTokens.slice(0, kashiPairsByTokens.length < 3 ? kashiPairsByTokens.length : 3)) + setTop3MarketsByAsset( + kashiPairsByTokensSortedByAsset.slice( + 0, + kashiPairsByTokensSortedByAsset.length < 3 ? kashiPairsByTokensSortedByAsset.length : 3 + ) + ) + setTop3MarketsByBorrow( + kashiPairsByTokensSortedByBorrow.slice( + 0, + kashiPairsByTokensSortedByBorrow.length < 3 ? kashiPairsByTokensSortedByBorrow.length : 3 + ) + ) + + setKashiPairsByTokens(kashiPairsByTokens) + setTotalAsset(totalAsset.toBigInt()) + setTotalBorrow(totalBorrow.toBigInt()) + } + } + + return ( + <> + {/* */} +
+ + + +
+
+ +
+ + ) +} +export default KashiTokensView diff --git a/src/features/analytics/tokens/TokenList.tsx b/src/features/analytics/tokens/TokenList.tsx deleted file mode 100644 index c25565a7a1..0000000000 --- a/src/features/analytics/tokens/TokenList.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import { getAddress } from '@ethersproject/address' -import { Token as CoreToken } from '@sushiswap/core-sdk' -import { CurrencyLogo } from 'app/components/CurrencyLogo' -import LineGraph from 'app/components/LineGraph' -import Table, { Column } from 'app/components/Table' -import { formatNumber, formatPercent } from 'app/functions' -import { useRouter } from 'next/router' -import React from 'react' - -import ColoredNumber from '../ColoredNumber' - -type TokenListColumnType = 'name' | 'price' | 'liquidity' | 'priceChange' | 'volumeChange' | 'lastWeekGraph' - -interface Token { - token: { - id: string - symbol: string - } - liquidity: number - volume1d: number - volume1w: number - price: number - change1d: number - change1w: number -} - -interface TokenListProps { - tokens: Token[] - enabledColumns?: TokenListColumnType[] -} - -interface TokenListNameProps { - token: { - id: string - symbol: string - decimals: number - name: string - } -} - -function TokenListName({ token }: TokenListNameProps): JSX.Element { - const router = useRouter() - const chainId = Number(router.query.chainId) - const currency = new CoreToken(chainId, getAddress(token?.id), token?.decimals || 18, token?.symbol, token?.name) - return ( - <> -
- {/*@ts-ignore TYPE NEEDS FIXING*/} - -
{token.symbol}
-
- - ) -} - -export default function TokenList({ - tokens, - enabledColumns = Object.keys(TokenListColumns) as TokenListColumnType[], -}: TokenListProps): JSX.Element { - const router = useRouter() - const chainId = Number(router.query.chainId) - const columns = React.useMemo(() => enabledColumns.map((col) => TokenListColumns[col]), [enabledColumns]) - return ( - <> - {tokens && ( - - columns={columns} - data={tokens} - defaultSortBy={{ id: 'liquidity', desc: true }} - link={{ href: `/analytics/${chainId}/tokens/`, id: 'token.id' }} - /> - )} - - ) -} - -const TokenListColumns: Record = { - name: { - Header: 'Name', - accessor: 'token', - Cell: (props) => , - disableSortBy: true, - align: 'left', - }, - price: { - Header: 'Price', - accessor: 'price', - Cell: (props) => formatNumber(props.value, true, undefined, 2), - align: 'right', - }, - liquidity: { - Header: 'Liquidity', - accessor: 'liquidity', - Cell: (props) => formatNumber(props.value, true, false), - align: 'right', - }, - priceChange: { - Header: 'Daily / Weekly % Change', - accessor: (row) => ( -
- -
- {row.change1w > 0 && '+'} - {formatPercent(row.change1w)} -
-
- ), - align: 'right', - sortType: (a, b) => a.original.change1d - b.original.change1d, - }, - volumeChange: { - Header: 'Daily / Weekly Volume', - accessor: (row) => ( -
-
{formatNumber(row.volume1d, true, false, 2)}
-
{formatNumber(row.volume1w, true, false, 2)}
-
- ), - align: 'right', - }, - lastWeekGraph: { - Header: 'Last Week', - accessor: 'graph', - Cell: (props) => ( -
-
- = 0 ? '#00ff4f' : '#ff3838' }} /> -
-
- ), - disableSortBy: true, - align: 'right', - }, -} diff --git a/src/hooks/usePricesSubgraph.ts b/src/hooks/usePricesSubgraph.ts new file mode 100644 index 0000000000..73707f16fc --- /dev/null +++ b/src/hooks/usePricesSubgraph.ts @@ -0,0 +1,88 @@ +import { Currency, CurrencyAmount, USD } from '@sushiswap/core-sdk' +import { useNativePrice, useTokens, useTridentTokens } from 'app/services/graph' +import { useActiveWeb3React } from 'app/services/web3' +import { useMemo } from 'react' + +export function useKashiPricesSubgraph( + currencies?: Currency[], + useTrident = false +): { [symbol: string]: BigInt } | undefined { + const { chainId } = useActiveWeb3React() + + const stablecoin = chainId ? CurrencyAmount.fromRawAmount(USD[chainId], 0).currency : undefined + + const { data: ethPrice } = useNativePrice({ chainId }) + const tokensLegacy = useTokens({ + chainId, + variables: { where: { id_in: currencies?.map((currency) => currency.wrapped.address.toLowerCase()) } }, + shouldFetch: currencies && currencies?.length > 0, + }) as any[] | undefined + const { data: tokensTrident } = useTridentTokens({ + chainId, + variables: { + where: { id_in: currencies?.map((currency) => currency.wrapped.address.toLowerCase()) }, + }, + shouldFetch: currencies && currencies?.length > 0, + }) + + return useMemo(() => { + if (!currencies || currencies.length === 0 || !stablecoin || !(tokensLegacy || tokensTrident)) { + return undefined + } + + const prices: { [symbol: string]: BigInt } = {} + + currencies.map((currency) => { + let price: number | undefined = undefined + + const tokenLegacy = tokensLegacy?.find((token) => token.id === currency.wrapped.address.toLowerCase()) + const tokenTrident = tokensTrident?.find((token) => token.id === currency.wrapped.address.toLowerCase()) + + // handle usdc + if (currency?.wrapped.equals(stablecoin)) { + if (currency.wrapped.symbol) { + prices[currency.wrapped.symbol] = BigInt('100000000') + } + return + } + + if (tokenLegacy && tokenTrident) { + if (tokenLegacy.liquidity > tokenTrident.kpi.liquidity) { + price = ethPrice * tokenLegacy.derivedETH + } else { + price = tokenTrident.price.derivedUSD + } + } else if (ethPrice && tokenLegacy) { + price = ethPrice * tokenLegacy.derivedETH + } else if (tokenTrident) { + price = tokenTrident.price.derivedUSD + } + + if (price !== undefined && price !== 0) { + const base = 10 ** (String(price).split('.')?.[1]?.length ?? 0) + const quote = Math.floor(price * 10 ** 8) + try { + if (currency.wrapped.symbol) { + prices[currency.wrapped.symbol] = BigInt(quote) + //prices[currency.wrapped.symbol] = (quote * (base * 10 ** currency.decimals)) / 10 ** stablecoin.decimals + } + } catch {} + } + }) + + return prices + }, [currencies, stablecoin, tokensLegacy, tokensTrident, ethPrice]) +} + +export function useKashiPricesSubgraphWithLoadingIndicator(currencies?: Currency[], useTrident = false) { + // Bandaid solution for now, might become permanent + const data = useKashiPricesSubgraph(currencies, useTrident) + return useMemo(() => { + if (!data) return { data: {}, loading: true } + try { + return { data: data, loading: false } + } catch (error) { + return { data: {}, loading: true } + } + }, [data]) +} diff --git a/src/pages/analytics/kashi/pairs/[id].tsx b/src/pages/analytics/kashi/pairs/[id].tsx new file mode 100644 index 0000000000..f83345217f --- /dev/null +++ b/src/pages/analytics/kashi/pairs/[id].tsx @@ -0,0 +1,32 @@ +import { t } from '@lingui/macro' +import { useLingui } from '@lingui/react' +import Typography from 'app/components/Typography' +import { AnalyticsKashiAppContextProvider } from 'app/features/analytics/kashi/context/AppContext' +import KashiPairView from 'app/features/analytics/kashi/views/KashiPairView' +import { TridentBody, TridentHeader } from 'app/layouts/Trident' +import React, { FC } from 'react' + +const AnalyticsKashiPairPage: FC = () => { + const { i18n } = useLingui() + return ( + <> + +
+ + {i18n._(t`Kashi Analytics.`)} + + + {i18n._(t`Lend and borrow assets in Kashi isolated risk markets`)} + +
+
+ + + + + + + ) +} + +export default AnalyticsKashiPairPage diff --git a/src/pages/analytics/kashi/pairs/index.tsx b/src/pages/analytics/kashi/pairs/index.tsx new file mode 100644 index 0000000000..daad8694c8 --- /dev/null +++ b/src/pages/analytics/kashi/pairs/index.tsx @@ -0,0 +1,32 @@ +import { t } from '@lingui/macro' +import { useLingui } from '@lingui/react' +import Typography from 'app/components/Typography' +import { AnalyticsKashiAppContextProvider } from 'app/features/analytics/kashi/context/AppContext' +import KashiPairsView from 'app/features/analytics/kashi/views/KashiPairsView' +import { TridentBody, TridentHeader } from 'app/layouts/Trident' +import React, { FC } from 'react' + +const AnalyticsKashiPairsPage: FC = () => { + const { i18n } = useLingui() + return ( + <> + +
+ + {i18n._(t`Kashi Analytics.`)} + + + {i18n._(t`Lend and borrow assets in Kashi isolated risk markets`)} + +
+
+ + + + + + + ) +} + +export default AnalyticsKashiPairsPage diff --git a/src/pages/analytics/kashi/tokens/[id].tsx b/src/pages/analytics/kashi/tokens/[id].tsx new file mode 100644 index 0000000000..e89ec7ce05 --- /dev/null +++ b/src/pages/analytics/kashi/tokens/[id].tsx @@ -0,0 +1,32 @@ +import { t } from '@lingui/macro' +import { useLingui } from '@lingui/react' +import Typography from 'app/components/Typography' +import { AnalyticsKashiAppContextProvider } from 'app/features/analytics/kashi/context/AppContext' +import KashiTokenView from 'app/features/analytics/kashi/views/KashiTokenView' +import { TridentBody, TridentHeader } from 'app/layouts/Trident' +import React, { FC } from 'react' + +const AnalyticsKashiPairPage: FC = () => { + const { i18n } = useLingui() + return ( + <> + +
+ + {i18n._(t`Kashi Analytics.`)} + + + {i18n._(t`Lend and borrow assets in Kashi isolated risk markets`)} + +
+
+ + + + + + + ) +} + +export default AnalyticsKashiPairPage diff --git a/src/pages/analytics/kashi/tokens/index.tsx b/src/pages/analytics/kashi/tokens/index.tsx new file mode 100644 index 0000000000..3c41fa3677 --- /dev/null +++ b/src/pages/analytics/kashi/tokens/index.tsx @@ -0,0 +1,32 @@ +import { t } from '@lingui/macro' +import { useLingui } from '@lingui/react' +import Typography from 'app/components/Typography' +import { AnalyticsKashiAppContextProvider } from 'app/features/analytics/kashi/context/AppContext' +import KashiTokensView from 'app/features/analytics/kashi/views/KashiTokensView' +import { TridentBody, TridentHeader } from 'app/layouts/Trident' +import React, { FC } from 'react' + +const AnalyticsKashiTokensPage: FC = () => { + const { i18n } = useLingui() + return ( + <> + +
+ + {i18n._(t`Kashi Token Analytics.`)} + + + {i18n._(t`Lend and borrow assets in Kashi isolated risk markets`)} + +
+
+ + + + + + + ) +} + +export default AnalyticsKashiTokensPage diff --git a/src/services/graph/fetchers/index.ts b/src/services/graph/fetchers/index.ts index 478f29ec9b..c3632a470c 100644 --- a/src/services/graph/fetchers/index.ts +++ b/src/services/graph/fetchers/index.ts @@ -3,6 +3,7 @@ export * from './bentobox' export * from './blocks' export * from './exchange' export * from './kashi' +export * from './kashipairs' export * from './masterchef' export * from './pager' export * from './pools' diff --git a/src/services/graph/fetchers/kashipairs.ts b/src/services/graph/fetchers/kashipairs.ts new file mode 100644 index 0000000000..84563d8b4d --- /dev/null +++ b/src/services/graph/fetchers/kashipairs.ts @@ -0,0 +1,70 @@ +import { ChainId } from '@sushiswap/core-sdk' +import { GRAPH_HOST } from 'app/services/graph/constants' + +import { + dataKashiPairsQuery, + kashiPairQuery, + kashiPairsDayDataQuery, + kashiTokenDayDataQuery, + kashiTokenQuery, + kashiTokensQuery, +} from '../queries' +import { pager, pagerRequestOnce } from './pager' + +const KASHI = { + [ChainId.ETHEREUM]: 'sushiswap/bentobox', + [ChainId.ARBITRUM]: 'sushiswap/arbitrum-bentobox', + [ChainId.FANTOM]: 'sushiswap/fantom-bentobox', + [ChainId.BSC]: 'sushiswap/bsc-bentobox', + [ChainId.MATIC]: 'sushiswap/matic-bentobox', +} + +const KASHINew = { + [ChainId.ARBITRUM]: 'sushiswap/kashi-arbitrum', + [ChainId.MATIC]: 'sushiswap/kashi-polygon', +} + +// @ts-ignore TYPE NEEDS FIXING +const fetcher = async (chainId = ChainId.ETHEREUM, query, variables = undefined) => + // @ts-ignore TYPE NEEDS FIXING + pager(`${GRAPH_HOST[chainId]}/subgraphs/name/${KASHI[chainId]}`, query, variables) + +// @ts-ignore TYPE NEEDS FIXING +const fetcherNew = async (chainId = ChainId.ETHEREUM, query, variables = undefined) => + // @ts-ignore TYPE NEEDS FIXING + pager(`${GRAPH_HOST[chainId]}/subgraphs/name/${KASHINew[chainId]}`, query, variables) + +// @ts-ignore TYPE NEEDS FIXING +const fetcherWithLimit = async (chainId = ChainId.ETHEREUM, query, variables = undefined) => + // @ts-ignore TYPE NEEDS FIXING + pagerRequestOnce(`${GRAPH_HOST[chainId]}/subgraphs/name/${KASHI[chainId]}`, query, variables) + +export const getDataKashiPair = async (chainId = ChainId.ETHEREUM, variables = undefined) => { + const data = await fetcher(chainId, kashiPairQuery, variables) + return data +} + +export const getDataKashiPairs = async (chainId = ChainId.ETHEREUM, variables = undefined) => { + const data = await fetcherNew(chainId, dataKashiPairsQuery) + return data +} + +export const getDataKashiPairsDayData = async (chainId = ChainId.ETHEREUM, variables = undefined) => { + const data = await fetcherWithLimit(chainId, kashiPairsDayDataQuery, variables) + return data +} + +export const getDataKashiToken = async (chainId = ChainId.ETHEREUM, variables = undefined) => { + const data = await fetcherNew(chainId, kashiTokenQuery, variables) + return data +} + +export const getDataKashiTokens = async (chainId = ChainId.ETHEREUM, variables = undefined) => { + const data = await fetcherNew(chainId, kashiTokensQuery) + return data +} + +export const getDataTokenPairsDayData = async (chainId = ChainId.ETHEREUM, variables = undefined) => { + const data = await fetcherWithLimit(chainId, kashiTokenDayDataQuery, variables) + return data +} diff --git a/src/services/graph/fetchers/pager.ts b/src/services/graph/fetchers/pager.ts index 4560482ec0..610386ad43 100644 --- a/src/services/graph/fetchers/pager.ts +++ b/src/services/graph/fetchers/pager.ts @@ -28,3 +28,16 @@ export async function pager(endpoint, query, variables = {}) { } return data } + +// @ts-ignore TYPE NEEDS FIXING +export async function pagerRequestOnce(endpoint, query, variables = {}) { + if (endpoint.includes('undefined')) return {} + + let data: any = {} + const req = await request(endpoint, query, variables) + + Object.keys(req).forEach((key) => { + data[key] = data[key] ? [...data[key], ...req[key]] : req[key] + }) + return data +} diff --git a/src/services/graph/hooks/kashipairs.ts b/src/services/graph/hooks/kashipairs.ts new file mode 100644 index 0000000000..e3c72c7dfb --- /dev/null +++ b/src/services/graph/hooks/kashipairs.ts @@ -0,0 +1,195 @@ +import { ChainId } from '@sushiswap/core-sdk' +import stringify from 'fast-json-stable-stringify' +import { useMemo } from 'react' +import useSWR from 'swr' + +import { + getDataKashiPair, + getDataKashiPairs, + getDataKashiPairsDayData, + getDataKashiToken, + getDataKashiTokens, + getDataTokenPairsDayData, +} from '../fetchers' +import { GraphProps } from '../interfaces' + +export function useDataKashiPair({ + chainId = ChainId.ETHEREUM, + variables, + shouldFetch = true, + swrConfig = undefined, +}: GraphProps) { + const data = useSWR( + shouldFetch ? () => ['dataKashiPair', chainId, stringify(variables)] : null, + // @ts-ignore + (_, chainId) => getDataKashiPair(chainId, variables), + swrConfig + ) + + return data +} + +export function useDataKashiPairWithLoadingIndicator({ + chainId = ChainId.ETHEREUM, + variables = undefined, +}: GraphProps) { + const { data } = useDataKashiPair({ chainId, variables }) + return useMemo(() => { + if (!data) return { data: undefined, loading: true } + try { + return { data: data, loading: false } + } catch (error) { + return { data: undefined, loading: true } + } + }, [data]) +} + +export function useDataKashiPairs({ + chainId = ChainId.ETHEREUM, + variables, + shouldFetch = true, + swrConfig = undefined, +}: GraphProps) { + const data = useSWR( + shouldFetch ? () => ['dataKashiPairs', chainId, stringify(variables)] : null, + // @ts-ignore + (_, chainId) => getDataKashiPairs(chainId, variables), + swrConfig + ) + + return data +} + +export function useDataKashiPairsWithLoadingIndicator({ chainId = ChainId.ETHEREUM }) { + const { data } = useDataKashiPairs({ chainId }) + return useMemo(() => { + if (!data) return { data: undefined, loading: true } + try { + return { data: data, loading: false } + } catch (error) { + return { data: undefined, loading: true } + } + }, [data]) +} + +export function useDataKashiPairsDayData({ + chainId = ChainId.ETHEREUM, + variables, + shouldFetch = true, + swrConfig = undefined, +}: GraphProps) { + const data = useSWR( + shouldFetch ? () => ['dataKashiPairsDayData', chainId, stringify(variables)] : null, + // @ts-ignore + (_, chainId) => getDataKashiPairsDayData(chainId, variables), + swrConfig + ) + + return data +} + +export function useDataKashiPairsDayDataWithLoadingIndicator({ + chainId = ChainId.ETHEREUM, + variables = undefined, +}: GraphProps) { + // Bandaid solution for now, might become permanent + const { data } = useDataKashiPairsDayData({ chainId, variables }) + return useMemo(() => { + if (!data) return { data: undefined, loading: true } + try { + return { data: data, loading: false } + } catch (error) { + return { data: undefined, loading: true } + } + }, [data]) +} + +export function useDataKashiToken({ + chainId = ChainId.ETHEREUM, + variables, + shouldFetch = true, + swrConfig = undefined, +}: GraphProps) { + const data = useSWR( + shouldFetch ? () => ['dataKashiPairs', chainId, stringify(variables)] : null, + // @ts-ignore + (_, chainId) => getDataKashiToken(chainId, variables), + swrConfig + ) + + return data +} + +export function useDataKashiTokenWithLoadingIndicator({ + chainId = ChainId.ETHEREUM, + variables = undefined, +}: GraphProps) { + const { data } = useDataKashiToken({ chainId, variables }) + return useMemo(() => { + if (!data) return { data: undefined, loading: true } + try { + return { data: data, loading: false } + } catch (error) { + return { data: undefined, loading: true } + } + }, [data]) +} + +export function useDataKashiTokens({ + chainId = ChainId.ETHEREUM, + variables, + shouldFetch = true, + swrConfig = undefined, +}: GraphProps) { + const data = useSWR( + shouldFetch ? () => ['dataKashiPairs', chainId, stringify(variables)] : null, + // @ts-ignore + (_, chainId) => getDataKashiTokens(chainId, variables), + swrConfig + ) + + return data +} + +export function useDataKashiTokensWithLoadingIndicator({ chainId = ChainId.ETHEREUM }) { + const { data } = useDataKashiTokens({ chainId }) + return useMemo(() => { + if (!data) return { data: undefined, loading: true } + try { + return { data: data, loading: false } + } catch (error) { + return { data: undefined, loading: true } + } + }, [data]) +} + +export function useDataKashiTokenDayData({ + chainId = ChainId.ETHEREUM, + variables, + shouldFetch = true, + swrConfig = undefined, +}: GraphProps) { + const data = useSWR( + shouldFetch ? () => ['dataKashiTokenDayData', chainId, stringify(variables)] : null, + // @ts-ignore + (_, chainId) => getDataTokenPairsDayData(chainId, variables), + swrConfig + ) + + return data +} + +export function useDataKashiTokensDayDataWithLoadingIndicator({ + chainId = ChainId.ETHEREUM, + variables = undefined, +}: GraphProps) { + const { data } = useDataKashiTokenDayData({ chainId, variables }) + return useMemo(() => { + if (!data) return { data: undefined, loading: true } + try { + return { data: data, loading: false } + } catch (error) { + return { data: undefined, loading: true } + } + }, [data]) +} diff --git a/src/services/graph/queries/index.ts b/src/services/graph/queries/index.ts index 26aae1c25b..a87b2ea58c 100644 --- a/src/services/graph/queries/index.ts +++ b/src/services/graph/queries/index.ts @@ -2,6 +2,7 @@ export * from './bentobox' export * from './blocks' export * from './exchange' export * from './kashi' +export * from './kashipairs' export * from './masterchef' export * from './masterchef-v2' export * from './minichef' diff --git a/src/services/graph/queries/kashipairs.ts b/src/services/graph/queries/kashipairs.ts new file mode 100644 index 0000000000..c07831a314 --- /dev/null +++ b/src/services/graph/queries/kashipairs.ts @@ -0,0 +1,266 @@ +import gql from 'graphql-tag' + +export const kashiPairQuery = gql` + query GetPair($id: String) { + kashiPairs(first: 1, where: { id: $id }) { + id + name + symbol + asset { + id + name + symbol + decimals + } + collateral { + id + name + symbol + decimals + } + exchangeRate + totalAssetElastic + totalAssetBase + supplyAPR + totalBorrowElastic + totalBorrowBase + borrowAPR + utilization + } + kashiPairDayDatas(first: 1000, where: { pair: $id }, orderBy: date, orderDirection: desc) { + id + date + pair { + id + name + symbol + asset { + id + name + symbol + decimals + } + collateral { + id + name + symbol + decimals + } + } + totalAssetElastic + totalAssetBase + totalCollateralShare + totalBorrowElastic + totalBorrowBase + avgExchangeRate + avgUtilization + avgInterestPerSecond + } + } +` + +export const dataKashiPairsQuery = gql` + query GetPairs { + kashiPairs(first: 1000) { + id + name + symbol + asset { + id + name + symbol + decimals + } + collateral { + id + name + symbol + decimals + } + totalAsset { + base + elastic + } + totalBorrow { + base + elastic + } + kpi { + supplyAPR + borrowAPR + utilization + totalFeesEarnedFraction + } + exchangeRate + accrueInfo { + interestPerSecond + } + } + } +` + +export const kashiPairsDayDataQuery = gql` + query GetDataKashiPairsDayData($skip: Int) { + kashiPairDayDatas(first: 1000, skip: $skip, orderBy: date, orderDirection: desc) { + id + date + pair { + id + name + symbol + asset { + id + name + symbol + decimals + } + collateral { + id + name + symbol + decimals + } + } + totalAssetElastic + totalAssetBase + totalCollateralShare + totalBorrowElastic + totalBorrowBase + avgExchangeRate + avgUtilization + avgInterestPerSecond + } + } +` + +export const kashiTokensQuery = gql` + query GetPairs { + kashiPairs(first: 1000) { + id + name + symbol + asset { + id + name + symbol + decimals + } + collateral { + id + name + symbol + decimals + } + totalAsset { + base + elastic + } + totalBorrow { + base + elastic + } + kpi { + supplyAPR + borrowAPR + utilization + } + accrueInfo { + interestPerSecond + } + exchangeRate + } + } +` + +export const kashiTokenQuery = gql` + query GetToken($id: String) { + tokens(first: 1, where: { id: $id }) { + id + name + symbol + decimals + rebase { + base + elastic + } + # block + # timestamp + } + kashiPairs(first: 1000, where: { asset: $id }) { + id + name + symbol + asset { + id + name + symbol + decimals + rebase { + base + elastic + } + # block + # timestamp + } + collateral { + id + name + symbol + decimals + rebase { + base + elastic + } + # block + # timestamp + } + totalAsset { + base + elastic + } + totalBorrow { + base + elastic + } + totalCollateralShare + kpi { + supplyAPR + borrowAPR + } + } + } +` + +export const kashiTokenDayDataQuery = gql` + query GetDataKashiPairsDayData($pairIds: [String]) { + kashiPairDayDatas(first: 1000, where: { pair_in: $pairIds }, orderBy: date, orderDirection: desc) { + id + date + pair { + id + name + symbol + asset { + id + name + symbol + decimals + } + collateral { + id + name + symbol + decimals + } + } + totalAssetElastic + totalAssetBase + totalCollateralShare + totalBorrowElastic + totalBorrowBase + avgExchangeRate + avgUtilization + avgInterestPerSecond + } + } +` diff --git a/src/styles/index.css b/src/styles/index.css index 23620e5785..7913870ae9 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -103,3 +103,7 @@ input[type=datetime-local]::-webkit-calendar-picker-indicator { input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; } + +.highcharts-credits { + display: none; +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 8ab6b8e30e..13c5baa7dc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19,6 +19,24 @@ jsonpointer "^5.0.0" leven "^3.1.0" +"@apollo/client@^3.6.8": + version "3.6.8" + resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.6.8.tgz#97e876fdbc4c287652a61895fd2b6f0b157a7667" + integrity sha512-p/J6KRHZZPGX0bZtMLvRFAIcReYsRYGg+Jz9MkgabWPy0L8rwgyolq9fvKsNqkH888Tj9Yvwrxz9V84KfcORJA== + dependencies: + "@graphql-typed-document-node/core" "^3.1.1" + "@wry/context" "^0.6.0" + "@wry/equality" "^0.5.0" + "@wry/trie" "^0.3.0" + graphql-tag "^2.12.6" + hoist-non-react-statics "^3.3.2" + optimism "^0.16.1" + prop-types "^15.7.2" + symbol-observable "^4.0.0" + ts-invariant "^0.10.3" + tslib "^2.3.0" + zen-observable-ts "^1.2.5" + "@babel/code-frame@7.12.11": version "7.12.11" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" @@ -1773,6 +1791,11 @@ dependencies: cross-fetch "^3.1.5" +"@graphql-typed-document-node/core@^3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.1.1.tgz#076d78ce99822258cf813ecc1e7fa460fa74d052" + integrity sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg== + "@hapi/hoek@^9.0.0": version "9.3.0" resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" @@ -3117,6 +3140,11 @@ resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== +"@types/numeral@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@types/numeral/-/numeral-2.0.2.tgz#8ea2c4f4e64c0cc948ad7da375f6f827778a7912" + integrity sha512-A8F30k2gYJ/6e07spSCPpkuZu79LCnkPTvqmIWQzNGcrzwFKpVOydG41lNt5wZXjSI149qjyzC2L1+F2PD/NUA== + "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" @@ -3834,6 +3862,27 @@ dependencies: lodash "^4" +"@wry/context@^0.6.0": + version "0.6.1" + resolved "https://registry.yarnpkg.com/@wry/context/-/context-0.6.1.tgz#c3c29c0ad622adb00f6a53303c4f965ee06ebeb2" + integrity sha512-LOmVnY1iTU2D8tv4Xf6MVMZZ+juIJ87Kt/plMijjN20NMAXGmH4u8bS1t0uT74cZ5gwpocYueV58YwyI8y+GKw== + dependencies: + tslib "^2.3.0" + +"@wry/equality@^0.5.0": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@wry/equality/-/equality-0.5.2.tgz#72c8a7a7d884dff30b612f4f8464eba26c080e73" + integrity sha512-oVMxbUXL48EV/C0/M7gLVsoK6qRHPS85x8zECofEZOVvxGmIPLA9o5Z27cc2PoAyZz1S2VoM2A7FLAnpfGlneA== + dependencies: + tslib "^2.3.0" + +"@wry/trie@^0.3.0": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@wry/trie/-/trie-0.3.1.tgz#2279b790f15032f8bcea7fc944d27988e5b3b139" + integrity sha512-WwB53ikYudh9pIorgxrkHKrQZcCqNM/Q/bDzZBffEaGUKGuHrRb3zZUT9Sh2qw9yogC7SsdRmQ1ER0pqvd3bfw== + dependencies: + tslib "^2.3.0" + "@zeit/schemas@2.6.0": version "2.6.0" resolved "https://registry.yarnpkg.com/@zeit/schemas/-/schemas-2.6.0.tgz#004e8e553b4cd53d538bd38eac7bcbf58a867fe3" @@ -7402,7 +7451,7 @@ graphql-request@^3.5.0: extract-files "^9.0.0" form-data "^3.0.0" -graphql-tag@^2.12.5: +graphql-tag@^2.12.5, 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== @@ -7514,6 +7563,16 @@ hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.7: inherits "^2.0.3" minimalistic-assert "^1.0.1" +highcharts-react-official@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/highcharts-react-official/-/highcharts-react-official-3.1.0.tgz#921e5614f9e8580bc99a1e3b531d51a70f396500" + integrity sha512-CkWJHrVMOc6CT8KFu1dR+a0w5OxCVKKgZUNWtEi5TmR0xqBDIDe+RyM652MAN/jBYppxMo6TCUVlRObCyWAn0Q== + +highcharts@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/highcharts/-/highcharts-10.1.0.tgz#561872ae8ea819131d78949136d05bcaaa99108b" + integrity sha512-gW/pzy/Qy/hiYeCflYHpwgsP2nqQavwuRUswRf+papmbutZ3I7K7AIZJ/yZ9NL5yzpF7X7r2dX7/nzZGN4A1rg== + hmac-drbg@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" @@ -9599,6 +9658,11 @@ module-lookup-amd@^7.0.1: requirejs "^2.3.5" requirejs-config-file "^4.0.0" +moment@^2.29.3: + version "2.29.3" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.3.tgz#edd47411c322413999f7a5940d526de183c031f3" + integrity sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw== + mrmime@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.1.tgz#5f90c825fad4bdd41dc914eff5d1a8cfdaf24f27" @@ -9895,7 +9959,7 @@ number-to-bn@1.7.0: bn.js "4.11.6" strip-hex-prefix "1.0.0" -numeral@^2.0.0: +numeral@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/numeral/-/numeral-2.0.6.tgz#4ad080936d443c2561aed9f2197efffe25f4e506" integrity sha512-qaKRmtYPZ5qdw4jWJD6bxEf1FJEqllJrwxCLIm0sQU/A7v2/czigzOb+C2uSiFsa9lBUzeH7M1oK+Q+OLxL3kA== @@ -10009,6 +10073,14 @@ opener@^1.5.2: resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== +optimism@^0.16.1: + version "0.16.1" + resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.16.1.tgz#7c8efc1f3179f18307b887e18c15c5b7133f6e7d" + integrity sha512-64i+Uw3otrndfq5kaoGNoY7pvOhSsjFEN4bdEFh80MWVk/dbgJfMv7VFDeCT8LxNAlEVhQmdVEbfE7X2nWNIIg== + dependencies: + "@wry/context" "^0.6.0" + "@wry/trie" "^0.3.0" + optionator@^0.8.1: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" @@ -11086,6 +11158,11 @@ react-hook-form@7.29.0: resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.29.0.tgz#5e7e41a483b70731720966ed8be52163ea1fecf1" integrity sha512-NcJqWRF6el5HMW30fqZRt27s+lorvlCCDbTpAyHoodQeYWXgQCvZJJQLC1kRMKdrJknVH0NIg3At6TUzlZJFOQ== +react-icons@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.4.0.tgz#a13a8a20c254854e1ec9aecef28a95cdf24ef703" + integrity sha512-fSbvHeVYo/B5/L4VhB7sBA1i2tS8MkT0Hb9t2H1AVPkwGfVHLJCqyr2Py9dKMxsyM63Eng1GkdZfbWj+Fmv8Rg== + react-infinite-scroll-component@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/react-infinite-scroll-component/-/react-infinite-scroll-component-6.1.0.tgz#7e511e7aa0f728ac3e51f64a38a6079ac522407f" @@ -12356,6 +12433,11 @@ swr@^1.2.0: resolved "https://registry.yarnpkg.com/swr/-/swr-1.3.0.tgz#c6531866a35b4db37b38b72c45a63171faf9f4e8" integrity sha512-dkghQrOl2ORX9HYrMDtPa7LTVHJjCTeZoB1dqTbnnEDlSvN8JEKpYIYurDfvbQFUUS8Cg8PceFVZNkW0KNNYPw== +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== + symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" @@ -12651,6 +12733,13 @@ trim-newlines@^3.0.0: resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== +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-loader@^7.0.0: version "7.0.5" resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-7.0.5.tgz#789338fb01cb5dc0a33c54e50558b34a73c9c4c5" @@ -12713,6 +12802,11 @@ tslib@^2, tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== +tslib@^2.3.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" + integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" @@ -13725,6 +13819,18 @@ yup@^0.32.11: property-expr "^2.0.4" toposort "^2.0.2" +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== + zustand@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/zustand/-/zustand-3.7.2.tgz#7b44c4f4a5bfd7a8296a3957b13e1c346f42514d"