mango-ui-v2/pages/stats.tsx

343 lines
11 KiB
TypeScript

import { useEffect, useState } from 'react'
import { LineChart, Line, ReferenceLine, XAxis, YAxis, Tooltip } from 'recharts'
import useDimensions from 'react-cool-dimensions'
import { IDS, MangoClient } from '@blockworks-foundation/mango-client'
import { PublicKey, Connection } from '@solana/web3.js'
import { DEFAULT_MANGO_GROUP } from '../utils/mango'
import useConnection from '../hooks/useConnection'
import TopBar from '../components/TopBar'
import { formatBalanceDisplay } from '../utils/index'
import { Table, Thead, Tbody, Tr, Th, Td } from 'react-super-responsive-table'
const DECIMALS = {
BTC: 4,
ETH: 3,
SOL: 2,
SRM: 2,
USDT: 2,
USDC: 2,
}
const icons = {
BTC: '/assets/icons/btc.svg',
ETH: '/assets/icons/eth.svg',
SOL: '/assets/icons/sol.svg',
SRM: '/assets/icons/srm.svg',
USDT: '/assets/icons/usdt.svg',
USDC: '/assets/icons/usdc.svg',
WUSDT: '/assets/icons/usdt.svg',
}
const useMangoStats = () => {
const [stats, setStats] = useState([
{
symbol: '',
depositInterest: 0,
borrowInterest: 0,
totalDeposits: 0,
totalBorrows: 0,
utilization: '0',
},
])
const [latestStats, setLatestStats] = useState<any[]>([])
const { cluster } = useConnection()
useEffect(() => {
const fetchStats = async () => {
const response = await fetch(
`https://mango-stats.herokuapp.com?mangoGroup=BTC_ETH_SOL_SRM_USDC`
)
const stats = await response.json()
setStats(stats)
}
fetchStats()
}, [])
useEffect(() => {
const getLatestStats = async () => {
const client = new MangoClient()
const connection = new Connection(
IDS.cluster_urls[cluster],
'singleGossip'
)
const assets = IDS[cluster].mango_groups?.[DEFAULT_MANGO_GROUP]?.symbols
const mangoGroupId =
IDS[cluster].mango_groups?.[DEFAULT_MANGO_GROUP]?.mango_group_pk
if (!mangoGroupId) return
const mangoGroupPk = new PublicKey(mangoGroupId)
const mangoGroup = await client.getMangoGroup(connection, mangoGroupPk)
const latestStats = Object.keys(assets).map((symbol, index) => {
const totalDeposits = mangoGroup.getUiTotalDeposit(index)
const totalBorrows = mangoGroup.getUiTotalBorrow(index)
return {
time: new Date(),
symbol,
totalDeposits,
totalBorrows,
depositInterest: mangoGroup.getDepositRate(index) * 100,
borrowInterest: mangoGroup.getBorrowRate(index) * 100,
utilization: totalDeposits > 0.0 ? totalBorrows / totalDeposits : 0.0,
}
})
setLatestStats(latestStats)
}
getLatestStats()
}, [cluster])
return { latestStats, stats }
}
const StatsChart = ({ title, xAxis, yAxis, data, labelFormat }) => {
const [mouseData, setMouseData] = useState<string | null>(null)
// @ts-ignore
const { observe, width, height } = useDimensions()
const handleMouseMove = (coords) => {
if (coords.activePayload) {
setMouseData(coords.activePayload[0].payload)
}
}
const handleMouseLeave = () => {
setMouseData(null)
}
return (
<div className="h-full w-full" ref={observe}>
<div className="absolute -top-4 left-0 h-full w-full pb-4">
<div className="text-center text-th-fgd-1 text-base font-semibold">
{title}
</div>
{mouseData ? (
<div className="text-center pt-1">
<div className="text-sm font-normal text-th-fgd-3">
{labelFormat(mouseData[yAxis])}
</div>
<div className="text-xs font-normal text-th-fgd-4">
{new Date(mouseData[xAxis]).toDateString()}
</div>
</div>
) : null}
</div>
{width > 0 ? (
<LineChart
width={width}
height={height}
margin={{ top: 50, right: 50 }}
data={data}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
>
<Tooltip
cursor={{
stroke: '#f7f7f7',
strokeWidth: 1.5,
strokeOpacity: 0.3,
strokeDasharray: '6 5',
}}
content={<></>}
/>
<Line
isAnimationActive={false}
type="linear"
dot={false}
dataKey={yAxis}
stroke="#f2c94c"
strokeWidth={2}
/>
{mouseData ? (
<ReferenceLine
y={mouseData[yAxis]}
strokeDasharray="6 5"
strokeWidth={1.5}
strokeOpacity={0.3}
/>
) : null}
<XAxis dataKey={xAxis} hide />
<YAxis dataKey={yAxis} hide />
</LineChart>
) : null}
</div>
)
}
export default function StatsPage() {
const [selectedAsset, setSelectedAsset] = useState<string>('BTC')
const { latestStats, stats } = useMangoStats()
const selectedStatsData = stats.filter(
(stat) => stat.symbol === selectedAsset
)
return (
<div className={`bg-th-bkg-1 text-th-fgd-1 transition-all `}>
<TopBar />
<div className="min-h-screen w-full xl:w-3/4 mx-auto px-4 sm:px-6 sm:py-1 md:px-8 md:py-1 lg:px-12">
<div className="text-center pt-8 pb-6 md:pt-10">
<h1 className={`text-th-fgd-1 text-2xl font-semibold`}>
Mango Stats
</h1>
</div>
<div className="md:flex md:flex-col min-w-full">
<Table className="min-w-full divide-y divide-th-bkg-2">
<Thead>
<Tr className="text-th-fgd-3">
<Th scope="col" className="px-6 py-3 text-left font-normal">
Asset
</Th>
<Th scope="col" className="px-6 py-3 text-left font-normal">
Total Deposits
</Th>
<Th scope="col" className="px-6 py-3 text-left font-normal">
Total Borrows
</Th>
<Th scope="col" className="px-6 py-3 text-left font-normal">
Deposit Interest
</Th>
<Th scope="col" className="px-6 py-3 text-left font-normal">
Borrow Interest
</Th>
<Th scope="col" className="px-6 py-3 text-left font-normal">
Utilization
</Th>
</Tr>
</Thead>
<Tbody>
{latestStats.map((stat, index) => (
<Tr
key={stat.symbol}
className={`border-b border-th-bkg-2
${index % 2 === 0 ? `bg-th-bkg-2` : `bg-th-bkg-1`}
`}
>
<Td className="px-6 py-4 whitespace-nowrap text-sm text-th-fgd-1">
<div className="flex items-center">
<img
src={icons[stat.symbol]}
alt={icons[stat.symbol]}
className="w-5 h-5 md:w-6 md:h-6"
/>
<button
onClick={() => setSelectedAsset(stat.symbol)}
className="underline cursor-pointer ml-3 hover:text-th-primary hover:no-underline"
>
{stat.symbol}
</button>
</div>
</Td>
<Td className="px-6 py-4 whitespace-nowrap text-sm text-th-fgd-1">
{formatBalanceDisplay(
stat.totalDeposits,
DECIMALS[stat.symbol]
).toLocaleString(undefined, {
maximumFractionDigits: DECIMALS[stat.symbol],
})}
</Td>
<Td className="px-6 py-4 whitespace-nowrap text-sm text-th-fgd-1">
{formatBalanceDisplay(
stat.totalBorrows,
DECIMALS[stat.symbol]
).toLocaleString(undefined, {
maximumFractionDigits: DECIMALS[stat.symbol],
})}
</Td>
<Td className="px-6 py-4 whitespace-nowrap text-sm text-th-fgd-1">
{stat.depositInterest.toFixed(2)}%
</Td>
<Td className="px-6 py-4 whitespace-nowrap text-sm text-th-fgd-1">
{stat.borrowInterest.toFixed(2)}%
</Td>
<Td className="px-6 py-4 whitespace-nowrap text-sm text-th-fgd-1">
{(parseFloat(stat.utilization) * 100).toFixed(2)}%
</Td>
</Tr>
))}
</Tbody>
</Table>
</div>
{selectedAsset ? (
<div className="py-10 md:py-14">
<div className="flex flex-col items-center pb-12">
<h2 className="text-th-fgd-1 text-center text-2xl font-semibold mb-4">
Historical Stats
</h2>
<div className="flex self-center">
{latestStats.map((stat) => (
<div
className={`px-2 py-1 mr-2 rounded-md cursor-pointer default-transition bg-th-bkg-3
${
selectedAsset === stat.symbol
? `text-th-primary`
: `text-th-fgd-1 opacity-50 hover:opacity-100`
}
`}
onClick={() => setSelectedAsset(stat.symbol)}
key={stat.symbol as string}
>
{stat.symbol}
</div>
))}
</div>
</div>
<div className="flex flex-col md:flex-row pb-14">
<div
className="relative my-2 pb-14 md:pb-0 md:w-1/2"
style={{ height: '300px' }}
>
<StatsChart
title="Total Deposits"
xAxis="time"
yAxis="totalDeposits"
data={selectedStatsData}
labelFormat={(x) => x.toFixed(DECIMALS[selectedAsset])}
/>
</div>
<div
className="relative my-2 md:w-1/2"
style={{ height: '300px' }}
>
<StatsChart
title="Total Borrows"
xAxis="time"
yAxis="totalBorrows"
data={selectedStatsData}
labelFormat={(x) => x.toFixed(DECIMALS[selectedAsset])}
/>
</div>
</div>
<div className="flex flex-col md:flex-row">
<div
className="relative my-2 pb-14 md:pb-0 md:w-1/2"
style={{ height: '300px' }}
>
<StatsChart
title="Deposit Interest"
xAxis="time"
yAxis="depositInterest"
data={selectedStatsData}
labelFormat={(x) => `${(x * 100).toFixed(5)}%`}
/>
</div>
<div
className="relative my-2 md:w-1/2"
style={{ height: '300px' }}
>
<StatsChart
title="Borrow Interest"
xAxis="time"
yAxis="borrowInterest"
data={selectedStatsData}
labelFormat={(x) => `${(x * 100).toFixed(5)}%`}
/>
</div>
</div>
</div>
) : null}
</div>
</div>
)
}