Display live statistics from on-chain program

This commit is contained in:
Maximilian Schneider 2021-03-01 23:29:11 +01:00
parent ed3aaf0240
commit 63c807a35b
10 changed files with 1321 additions and 62 deletions

View File

@ -0,0 +1,24 @@
import React from 'react';
import styled from 'styled-components';
const Wrapper = styled.div`
margin: 5px;
padding: 20px;
background-color: #141026;
border: 1px solid #584f81;
border-radius: 9px;
`;
export default function FloatingElement({ children, style = undefined, stretchVertical = false }) {
return (
<Wrapper
style={{
height: stretchVertical ? 'calc(100% - 10px)' : undefined,
...style,
}}
>
{children}
</Wrapper>
);
}

121
components/StatsPanel.tsx Normal file
View File

@ -0,0 +1,121 @@
import { Col, Row, Divider } from 'antd';
import { useEffect, useState } from 'react';
import styled from 'styled-components';
import { IDS, MangoClient } from '@blockworks-foundation/mango-client';
import { PublicKey, Connection } from '@solana/web3.js';
import FloatingElement from './FloatingElement';
const CLUSTER = 'mainnet-beta';
const DEFAULT_MANGO_GROUP = 'BTC_ETH_USDT';
const icons = {
BTC: '/tokens/btc.svg',
ETH: '/tokens/eth.svg',
USDT:'/tokens/usdt.svg'};
const stubStats = {
depositInterest: 0,
borrowInterest: 0,
totalDeposits: 0,
totalBorrows: 0,
utilization: '0',
};
const useMangoStats = () => {
const [stats, setStats] = useState(Object.keys(icons).
map(s => ({symbol: s, ...stubStats})));
useEffect(() => {
const getStats = 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);
console.log('assets', symbol, index, totalDeposits, totalBorrows);
return {
time: new Date(),
symbol,
totalDeposits,
totalBorrows,
depositInterest: mangoGroup.getDepositRate(index),
borrowInterest: mangoGroup.getBorrowRate(index),
utilization: totalDeposits > 0.0 ? totalBorrows / totalDeposits : 0.0,
};
});
setStats(latestStats);
};
getStats();
}, []);
return { stats };
};
const Wrapper = styled.div`
height: 100%;
display: flex;
flex-direction: column;
padding: 16px 16px;
.borderNone .ant-select-selector {
border: none !important;
}
`;
const SizeTitle = styled(Row)`
color: #9490A6;
`;
export default function StatsPanel() {
const { stats } = useMangoStats();
return (
<Wrapper style={{ paddingTop: 10 }}>
<Row justify="center">
<Col lg={24} xl={18} xxl={12}>
<FloatingElement>
<React.Fragment>
<Divider style={{color: "#EEEEEE" }}>Mango Stats</Divider>
<SizeTitle>
<Col span={4}>Asset</Col>
<Col span={4}>Total Deposits</Col>
<Col span={4}>Total Borrows</Col>
<Col span={4}>Deposit Interest</Col>
<Col span={4}>Borrow Interest</Col>
<Col span={4}>Utilization</Col>
</SizeTitle>
{stats.map((stat, i) => (
<div key={stat.symbol}>
<Divider />
<Row>
<Col span={1}>
<img src={icons[stat.symbol]} alt={stat.symbol} width="14px" />
</Col>
<Col span={3}>{stat.symbol}</Col>
<Col span={4}>{stat.totalDeposits.toFixed(2-i)}</Col>
<Col span={4}>{stat.totalBorrows.toFixed(2-i)}</Col>
<Col span={4}>{stat.depositInterest.toFixed(2)}%</Col>
<Col span={4}>{stat.borrowInterest.toFixed(2)}%</Col>
<Col span={4}>{(parseFloat(stat.utilization) * 100).toFixed(2)}%</Col>
</Row>
</div>
))}
</React.Fragment>
</FloatingElement>
</Col>
</Row>
</Wrapper>
);
}

View File

@ -8,6 +8,7 @@
"type-check": "tsc"
},
"dependencies": {
"@blockworks-foundation/mango-client": "^0.1.2",
"@zeit/next-less": "^1.0.1",
"antd": "^4.12.3",
"babel-plugin-import": "^1.13.3",
@ -16,7 +17,8 @@
"next": "latest",
"null-loader": "^4.0.1",
"react": "^16.12.0",
"react-dom": "^16.12.0"
"react-dom": "^16.12.0",
"styled-components": "^5.2.1"
},
"devDependencies": {
"@types/node": "^12.12.21",

View File

@ -2,6 +2,7 @@ import Head from 'next/head'
import { Divider, Button, Layout, Space, Row, Col, Image } from 'antd';
import Logo from '../components/Logo';
import Navigation from '../components/Navigation';
import StatsPanel from '../components/StatsPanel';
const { Header, Footer, Sider, Content } = Layout;
@ -32,8 +33,7 @@ const IndexPage = () => (
</Col>
<Col xs={24} lg={14}>
<div style={{ padding: "2em" }}>
<img src="/previews/stats.png" />
<StatsPanel />
</div>
</Col>
</Row>
@ -54,7 +54,7 @@ const IndexPage = () => (
<p style={{ fontSize: 36 }}>
Lend
</p>
<p style={{ width: 400 }}>
<p>
Earn maximal interest on deposits, protect against inflation, and utilize idle investments. Always maintain custody of your funds.
</p>
</div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

6
public/tokens/btc.svg Normal file
View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32">
<g fill="none" fill-rule="evenodd">
<circle cx="16" cy="16" r="16" fill="#F7931A"/>
<path fill="#FFF" fill-rule="nonzero" d="M23.189 14.02c.314-2.096-1.283-3.223-3.465-3.975l.708-2.84-1.728-.43-.69 2.765c-.454-.114-.92-.22-1.385-.326l.695-2.783L15.596 6l-.708 2.839c-.376-.086-.746-.17-1.104-.26l.002-.009-2.384-.595-.46 1.846s1.283.294 1.256.312c.7.175.826.638.805 1.006l-.806 3.235c.048.012.11.03.18.057l-.183-.045-1.13 4.532c-.086.212-.303.531-.793.41.018.025-1.256-.313-1.256-.313l-.858 1.978 2.25.561c.418.105.828.215 1.231.318l-.715 2.872 1.727.43.708-2.84c.472.127.93.245 1.378.357l-.706 2.828 1.728.43.715-2.866c2.948.558 5.164.333 6.097-2.333.752-2.146-.037-3.385-1.588-4.192 1.13-.26 1.98-1.003 2.207-2.538zm-3.95 5.538c-.533 2.147-4.148.986-5.32.695l.95-3.805c1.172.293 4.929.872 4.37 3.11zm.535-5.569c-.487 1.953-3.495.96-4.47.717l.86-3.45c.975.243 4.118.696 3.61 2.733z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 973 B

13
public/tokens/eth.svg Normal file
View File

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32">
<g fill="none" fill-rule="evenodd">
<circle cx="16" cy="16" r="16" fill="#627EEA"/>
<g fill="#FFF" fill-rule="nonzero">
<path fill-opacity=".602" d="M16.498 4v8.87l7.497 3.35z"/>
<path d="M16.498 4L9 16.22l7.498-3.35z"/>
<path fill-opacity=".602" d="M16.498 21.968v6.027L24 17.616z"/>
<path d="M16.498 27.995v-6.028L9 17.616z"/>
<path fill-opacity=".2" d="M16.498 20.573l7.497-4.353-7.497-3.348z"/>
<path fill-opacity=".602" d="M9 16.22l7.498 4.353v-7.701z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 592 B

1
public/tokens/usdt.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"><g fill="none" fill-rule="evenodd"><circle cx="16" cy="16" r="16" fill="#26A17B"/><path fill="#FFF" d="M17.922 17.383v-.002c-.11.008-.677.042-1.942.042-1.01 0-1.721-.03-1.971-.042v.003c-3.888-.171-6.79-.848-6.79-1.658 0-.809 2.902-1.486 6.79-1.66v2.644c.254.018.982.061 1.988.061 1.207 0 1.812-.05 1.925-.06v-2.643c3.88.173 6.775.85 6.775 1.658 0 .81-2.895 1.485-6.775 1.657m0-3.59v-2.366h5.414V7.819H8.595v3.608h5.414v2.365c-4.4.202-7.709 1.074-7.709 2.118 0 1.044 3.309 1.915 7.709 2.118v7.582h3.913v-7.584c4.393-.202 7.694-1.073 7.694-2.116 0-1.043-3.301-1.914-7.694-2.117"/></g></svg>

After

Width:  |  Height:  |  Size: 651 B

View File

@ -6,8 +6,8 @@
@error-color: #E54033;
@text-color: #EEEEEE;
@text-color-secondary: #9490A6;
@border-color-base: #F0EDFF;
@divider-color: #F0EDFF;
@border-color-base: #584f81;
@divider-color: #584f81;
@btn-primary-color: #141026;
@body-background: #141026;
@component-background: #141026;

1204
yarn.lock

File diff suppressed because it is too large Load Diff