feat: deposit cleanup

This commit is contained in:
bartosz-lipinski 2020-11-19 17:33:02 -06:00
parent b4a2e4c564
commit 1a7f557650
23 changed files with 348 additions and 53 deletions

View File

@ -202,6 +202,16 @@ body {
} }
} }
.ant-layout-content {
display: flex;
}
.flexColumn {
display: flex;
flex-direction: column;
flex: 1;
}
.card-row { .card-row {
box-sizing: border-box; box-sizing: border-box;
margin: 5px 0px; margin: 5px 0px;

View File

@ -57,10 +57,10 @@ export const DepositAdd = (props: { className?: string, reserve: LendingReserve,
const bodyStyle: React.CSSProperties = { const bodyStyle: React.CSSProperties = {
display: 'flex', display: 'flex',
flex: 1,
justifyContent: 'center', justifyContent: 'center',
width: '100%', alignItems: 'center',
height: '100%', height: '100%',
alignItems: 'center'
}; };
return <Card className={props.className} bodyStyle={bodyStyle}> return <Card className={props.className} bodyStyle={bodyStyle}>

View File

@ -1,5 +1,5 @@
import React, { useCallback, useEffect, useMemo, useState } from "react"; import React, { useCallback, useEffect, useMemo, useState } from "react";
import { useLendingReserve, useTokenName, useUserAccounts, useUserBalance } from './../../hooks'; import { useLendingReserve, useTokenName, useUserAccounts, useUserBalance, useCollateralBalance } from './../../hooks';
import { LendingReserve, LendingReserveParser } from "../../models/lending"; import { LendingReserve, LendingReserveParser } from "../../models/lending";
import { TokenIcon } from "../../components/TokenIcon"; import { TokenIcon } from "../../components/TokenIcon";
import { formatNumber } from "../../utils/utils"; import { formatNumber } from "../../utils/utils";
@ -19,7 +19,7 @@ export const DepositInfoLine = (props: {
address: PublicKey }) => { address: PublicKey }) => {
const name = useTokenName(props.reserve.liquidityMint); const name = useTokenName(props.reserve.liquidityMint);
const { balance: tokenBalance } = useUserBalance(props.reserve.liquidityMint); const { balance: tokenBalance } = useUserBalance(props.reserve.liquidityMint);
const { balance: collateralBalance } = useUserBalance(props.reserve.collateralMint); const { balance: collateralBalance } = useCollateralBalance(props.reserve);
return <Card className={props.className} bodyStyle={{ display: 'flex', justifyContent: 'space-around', }} > return <Card className={props.className} bodyStyle={{ display: 'flex', justifyContent: 'space-around', }} >
<div className="deposit-info-line-item "> <div className="deposit-info-line-item ">

View File

@ -7,21 +7,25 @@ import {
GithubOutlined, GithubOutlined,
BankOutlined, BankOutlined,
LogoutOutlined, LogoutOutlined,
HomeOutlined HomeOutlined,
RocketOutlined
} from '@ant-design/icons'; } from '@ant-design/icons';
import BasicLayout, { DefaultFooter, PageContainer } from '@ant-design/pro-layout'; import BasicLayout, { DefaultFooter, PageContainer } from '@ant-design/pro-layout';
import { AppBar } from "./../AppBar"; import { AppBar } from "./../AppBar";
import { Link, useLocation } from "react-router-dom"; import { Link, useLocation } from "react-router-dom";
import { useConnectionConfig } from "../../contexts/connection";
export const AppLayout = (props: any) => { export const AppLayout = (props: any) => {
const { env } = useConnectionConfig();
const location = useLocation(); const location = useLocation();
console.log(location.pathname) console.log(location.pathname)
const paths: { [key: string]: string } = { const paths: { [key: string]: string } = {
'/dashboard': '2', '/dashboard': '2',
'/deposit': '3', '/deposit': '3',
'/borrow': '4' '/borrow': '4',
'/faucet': '4',
}; };
@ -84,6 +88,15 @@ export const AppLayout = (props: any) => {
Borrow Borrow
</Link> </Link>
</Menu.Item> </Menu.Item>
{env !== "mainnet-beta" && <Menu.Item key="5" icon={<RocketOutlined />}>
<Link
to={{
pathname: "/faucet",
}}
>
Faucet
</Link>
</Menu.Item>}
</Menu> </Menu>
}} }}
> >

View File

@ -0,0 +1,45 @@
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { useLendingReserve, useTokenName, useUserAccounts, useUserBalance } from './../../hooks';
import { LendingReserve, LendingReserveParser } from "../../models/lending";
import { TokenIcon } from "../../components/TokenIcon";
import { formatNumber } from "../../utils/utils";
import { Button, Card } from "antd";
import { useParams } from "react-router-dom";
import { cache, useAccount } from "../../contexts/accounts";
import { NumericInput } from "../../components/Input/numeric";
import { useConnection } from "../../contexts/connection";
import { useWallet } from "../../contexts/wallet";
import { deposit } from './../../actions/deposit';
import { PublicKey } from "@solana/web3.js";
import './style.less';
export const ReserveStatus = (props: { className?: string, reserve: LendingReserve, address: PublicKey }) => {
const connection = useConnection();
const { wallet } = useWallet();
const { id } = useParams<{ id: string }>();
const [value, setValue] = useState('');
const reserve = props.reserve;
const address = props.address;
const name = useTokenName(reserve?.liquidityMint);
const { balance: tokenBalance, accounts: fromAccounts } = useUserBalance(reserve?.liquidityMint);
const bodyStyle: React.CSSProperties = {
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
};
return <Card className={props.className}
title={
<>Reserve Status &amp; Configuration</>
}
bodyStyle={bodyStyle}>
<div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'space-around' }}>
TODO: Reserve Status - add chart
</div>
</Card >;
}

View File

View File

@ -0,0 +1,96 @@
import React, { useMemo } from "react";
import { useLendingReserve, useTokenName, useUserAccounts, useUserBalance } from './../../hooks';
import { LendingReserve, LendingReserveParser } from "../../models/lending";
import { TokenIcon } from "../../components/TokenIcon";
import { formatNumber, formatPct, fromLamports } from "../../utils/utils";
import { Button, Card, Typography } from "antd";
import { useParams } from "react-router-dom";
import { useAccount, useMint } from "../../contexts/accounts";
import { PublicKey } from "@solana/web3.js";
const { Text } = Typography;
export const UserLendingCard = (props: {
className?: string;
reserve: LendingReserve,
address: PublicKey,
}) => {
const reserve = props.reserve;
const name = useTokenName(reserve?.liquidityMint);
const liquidityMint = useMint(props.reserve.liquidityMint);
const totalLiquidity = fromLamports(props.reserve.totalLiquidity.toNumber(), liquidityMint);
// TODO: calculate
const borrowed = 0;
const healthFactor = '--';
const ltv = 0.75;
const available = 0;
return <Card className={props.className} title={
<div style={{ display: 'flex', alignItems: 'center', fontSize: '1.2rem', justifyContent: 'center' }}>
Your Information
</div>
}>
<h3>Borrows</h3>
<div className="card-row">
<Text type="secondary" className="card-cell ">
Borrowed
</Text>
<div className="card-cell ">
{formatNumber.format(borrowed)} {name}
</div>
</div>
<div className="card-row">
<Text type="secondary" className="card-cell ">
Health factor:
</Text>
<div className="card-cell ">
{healthFactor}
</div>
</div>
<div className="card-row">
<Text type="secondary" className="card-cell ">
Loan to value:
</Text>
<div className="card-cell ">
{formatNumber.format(ltv)}
</div>
</div>
<div className="card-row">
<Text type="secondary" className="card-cell ">
Available to you:
</Text>
<div className="card-cell ">
{formatNumber.format(available)} {name}
</div>
</div>
<h3>Deposits</h3>
<div className="card-row">
<Text type="secondary" className="card-cell ">
Wallet balance:
</Text>
<div className="card-cell ">
{formatNumber.format(totalLiquidity)} {name}
</div>
</div>
<div className="card-row">
<Text type="secondary" className="card-cell ">
You already deposited:
</Text>
<div className="card-cell ">
{formatNumber.format(totalLiquidity)} {name}
</div>
</div>
</Card>;
}

View File

@ -1,8 +1,8 @@
import React, { useCallback, useContext, useEffect, useState } from "react"; import React, { useCallback, useContext, useEffect, useState } from "react";
import { useConnection } from "./connection"; import { useConnection } from "./connection";
import { LENDING_PROGRAM_ID } from "./../constants/ids"; import { LENDING_PROGRAM_ID } from "./../constants/ids";
import { LendingReserveLayout, LendingMarketLayout, LendingMarket, LendingMarketParser, isLendingReserve, isLendingMarket, LendingReserveParser } from "./../models/lending"; import { LendingReserveLayout, LendingMarketLayout, LendingMarket, LendingMarketParser, isLendingReserve, isLendingMarket, LendingReserveParser, LendingReserve } from "./../models/lending";
import { cache, getMultipleAccounts } from "./accounts"; import { cache, getMultipleAccounts, ParsedAccount } from "./accounts";
import { AccountInfo, PublicKey } from "@solana/web3.js"; import { AccountInfo, PublicKey } from "@solana/web3.js";
import { isForInStatement } from "typescript"; import { isForInStatement } from "typescript";
@ -52,14 +52,10 @@ export const useLending = () => {
console.log(accounts); console.log(accounts);
const toQuery = accounts const toQuery = [
.map( ...accounts.filter(acc => (acc?.info as LendingReserve).lendingMarket !== undefined)
(p) => .map(acc => (acc?.info as LendingReserve).collateralMint.toBase58())
[ ].flat().filter((p) => p) as string[];
// TODO: add dependent accounts ....
].filter((p) => p) as string[]
)
.flat();
// This will pre-cache all accounts used by pools // This will pre-cache all accounts used by pools
// All those accounts are updated whenever there is a change // All those accounts are updated whenever there is a change

View File

@ -2,4 +2,5 @@ export * from './useUserAccounts';
export * from './useAccountByMint'; export * from './useAccountByMint';
export * from './useLendingReserves'; export * from './useLendingReserves';
export * from './useTokenName'; export * from './useTokenName';
export * from './useUserBalance'; export * from './useUserBalance';
export * from './useCollateralBalance';

View File

@ -0,0 +1,16 @@
import { PublicKey } from "@solana/web3.js";
import { useMemo } from "react";
import { useAccount, useMint } from "../contexts/accounts";
import { LendingReserve } from "../models/lending";
import { fromLamports } from "../utils/utils";
import { useUserAccounts } from "./useUserAccounts";
import { useUserBalance } from "./useUserBalance";
export function useCollateralBalance(reserve?: LendingReserve) {
const mint = useMint(reserve?.collateralMint);
const { balance: nativeBalance, accounts } = useUserBalance(reserve?.collateralMint, true);
const balance = fromLamports((reserve?.totalLiquidity.toNumber() || 0) * (nativeBalance / (reserve?.collateralMintSupply.toNumber() || 1)), mint);
return { balance, accounts };
}

View File

@ -4,7 +4,7 @@ import { useMint } from "../contexts/accounts";
import { fromLamports } from "../utils/utils"; import { fromLamports } from "../utils/utils";
import { useUserAccounts } from "./useUserAccounts"; import { useUserAccounts } from "./useUserAccounts";
export function useUserBalance(mint?: PublicKey) { export function useUserBalance(mint?: PublicKey, inLamports = false) {
const { userAccounts } = useUserAccounts(); const { userAccounts } = useUserAccounts();
const mintInfo = useMint(mint); const mintInfo = useMint(mint);
const accounts = useMemo(() => { const accounts = useMemo(() => {
@ -13,11 +13,11 @@ export function useUserBalance(mint?: PublicKey) {
.sort((a, b) => b.info.amount.sub(a.info.amount).toNumber()); .sort((a, b) => b.info.amount.sub(a.info.amount).toNumber());
}, [userAccounts]); }, [userAccounts]);
const balance = useMemo(() => const balance = useMemo(() => {
fromLamports(accounts const result = accounts
.reduce((res, item) => res += item.info.amount.toNumber(), 0) .reduce((res, item) => res += item.info.amount.toNumber(), 0);
, mintInfo), return inLamports ? result : fromLamports(result , mintInfo);
[accounts, mintInfo]); },[accounts, mintInfo]);
return { balance, accounts }; return { balance, accounts };
} }

View File

@ -52,7 +52,7 @@ export function getTokenName(
mintAddress?: string, mintAddress?: string,
shorten = true shorten = true
): string { ): string {
if(!mintAddress) { if (!mintAddress) {
return 'N/A'; return 'N/A';
} }
@ -69,7 +69,7 @@ export function getTokenIcon(
mintAddress?: string | PublicKey, mintAddress?: string | PublicKey,
): string | undefined { ): string | undefined {
const address = typeof mintAddress === 'string' ? mintAddress : mintAddress?.toBase58(); const address = typeof mintAddress === 'string' ? mintAddress : mintAddress?.toBase58();
if(!address) { if (!address) {
return; return;
} }
@ -162,12 +162,22 @@ export const formatUSD = new Intl.NumberFormat("en-US", {
currency: "USD", currency: "USD",
}); });
export const formatNumber = new Intl.NumberFormat("en-US", { const numberFormater = new Intl.NumberFormat("en-US", {
style: "decimal", style: "decimal",
minimumFractionDigits: 2, minimumFractionDigits: 2,
maximumFractionDigits: 2, maximumFractionDigits: 2,
}); });
export const formatNumber = {
format: (val?: number) => {
if (!val) {
return '--';
}
return numberFormater.format(val);
}
}
export const formatPct = new Intl.NumberFormat("en-US", { export const formatPct = new Intl.NumberFormat("en-US", {
style: "percent", style: "percent",
minimumFractionDigits: 2, minimumFractionDigits: 2,

View File

@ -7,7 +7,7 @@ import { Button } from "antd";
export const BorrowView = () => { export const BorrowView = () => {
return <div style={{ display: 'flex', justifyContent: 'space-around' }}> return <div className="flexColumn">
Borrow Borrow
</div>; </div>;
} }

View File

@ -8,7 +8,7 @@ import { Button } from "antd";
export const DashboardView = () => { export const DashboardView = () => {
const { reserveAccounts } = useLendingReserves(); const { reserveAccounts } = useLendingReserves();
return <div> return <div className="flexColumn">
DASHBOARD: DASHBOARD:
TODO: TODO:
1. Add deposits 1. Add deposits

View File

@ -1,8 +1,7 @@
.deposit-add { .deposit-add {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%; flex: 1;
width: 100%;
} }
.deposit-add-item { .deposit-add-item {
@ -12,8 +11,7 @@
.deposit-add-container { .deposit-add-container {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
height: 100%; flex: 1;
width: 100%;
} }
.deposit-add-item-left { .deposit-add-item-left {

View File

@ -6,12 +6,13 @@ import { getTokenName } from "../../../utils/utils";
import { useConnectionConfig } from "../../../contexts/connection"; import { useConnectionConfig } from "../../../contexts/connection";
import { TokenIcon } from "../../../components/TokenIcon"; import { TokenIcon } from "../../../components/TokenIcon";
import { ReserveItem } from './item'; import { ReserveItem } from './item';
import './itemStyle.less';
export const DepositView = () => { export const DepositView = () => {
const { reserveAccounts } = useLendingReserves(); const { reserveAccounts } = useLendingReserves();
return ( return (
<div> <div className="flexColumn">
<div style={{ display: 'flex', justifyContent: 'space-around' }}> <div className="deposit-item deposit-header">
<div>Asset</div> <div>Asset</div>
<div>Your wallet balance</div> <div>Your wallet balance</div>
<div>Your balance in Oyster</div> <div>Your balance in Oyster</div>

View File

@ -1,5 +1,5 @@
import React, { useMemo } from "react"; import React, { useMemo } from "react";
import { useTokenName, useUserAccounts, useUserBalance } from '../../../hooks'; import { useCollateralBalance, useTokenName, useUserAccounts, useUserBalance } from '../../../hooks';
import { LendingReserve } from "../../../models/lending"; import { LendingReserve } from "../../../models/lending";
import { TokenIcon } from "../../../components/TokenIcon"; import { TokenIcon } from "../../../components/TokenIcon";
import { formatNumber } from "../../../utils/utils"; import { formatNumber } from "../../../utils/utils";
@ -10,18 +10,20 @@ import { PublicKey } from "@solana/web3.js";
export const ReserveItem = (props: { reserve: LendingReserve, address: PublicKey }) => { export const ReserveItem = (props: { reserve: LendingReserve, address: PublicKey }) => {
const name = useTokenName(props.reserve.liquidityMint); const name = useTokenName(props.reserve.liquidityMint);
const { balance: tokenBalance } = useUserBalance(props.reserve.liquidityMint); const { balance: tokenBalance } = useUserBalance(props.reserve.liquidityMint);
const { balance: collateralBalance } = useUserBalance(props.reserve.collateralMint); const { balance: collateralBalance } = useCollateralBalance(props.reserve);
return <Link to={`/deposit/${props.address.toBase58()}`}> return <Link to={`/deposit/${props.address.toBase58()}`}>
<Card> <Card>
<div style={{ display: 'flex', justifyContent: 'space-around', alignItems: 'center' }}> <div className="deposit-item">
<span style={{ display: 'flex' }}><TokenIcon mintAddress={props.reserve.liquidityMint} />{name}</span> <span style={{ display: 'flex' }}><TokenIcon mintAddress={props.reserve.liquidityMint} />{name}</span>
<div>{formatNumber.format(tokenBalance)} {name}</div> <div>{formatNumber.format(tokenBalance)} {name}</div>
<div>{formatNumber.format(collateralBalance)} {name}</div> <div>{formatNumber.format(collateralBalance)} {name}</div>
<div>--</div> <div>--</div>
<Button> <div>
<span>Deposit</span> <Button>
</Button> <span>Deposit</span>
</Button>
</div>
</div> </div>
</Card> </Card>
</Link>; </Link>;

View File

@ -0,0 +1,29 @@
.deposit-item {
display: flex;
justify-content: space-between;
align-items: center;
& > div, span {
flex: 20%;
height: 22px;
text-align: right;
}
& > :first-child {
flex: 80px
}
}
.deposit-header {
margin: 0px 30px;
& > div {
flex: 20%;
text-align: right;
}
& > :first-child {
text-align: left;
flex: 80px
}
}

View File

@ -5,14 +5,15 @@ import { TokenIcon } from "../../components/TokenIcon";
import { formatNumber } from "../../utils/utils"; import { formatNumber } from "../../utils/utils";
import { Button } from "antd"; import { Button } from "antd";
import { LendingReserveItem } from "./item"; import { LendingReserveItem } from "./item";
import './itemStyle.less';
export const HomeView = () => { export const HomeView = () => {
const { reserveAccounts } = useLendingReserves(); const { reserveAccounts } = useLendingReserves();
// TODO: add total Liquidity amount ... // TODO: add total Liquidity amount ...
return <div> return <div className="flexColumn">
<div style={{ display: 'flex', justifyContent: 'space-around' }}> <div className="home-item home-header">
<div>Asset</div> <div>Asset</div>
<div>Market Size</div> <div>Market Size</div>
<div>Total Borrowed</div> <div>Total Borrowed</div>

View File

@ -24,11 +24,12 @@ export const LendingReserveItem = (props: { reserve: LendingReserve, address: Pu
return <Link to={`/reserve/${props.address.toBase58()}`}> return <Link to={`/reserve/${props.address.toBase58()}`}>
<Card> <Card>
<div style={{ display: 'flex', justifyContent: 'space-around', alignItems: 'center' }}> <div className="home-item">
<span style={{ display: 'flex' }}><TokenIcon mintAddress={props.reserve.liquidityMint} />{name}</span> <span style={{ display: 'flex' }}><TokenIcon mintAddress={props.reserve.liquidityMint} />{name}</span>
<div>{formatNumber.format(totalLiquidity)} {name}</div> <div>{formatNumber.format(totalLiquidity)} {name}</div>
<div>{totalBorrows} {name}</div> <div>{totalBorrows} {name}</div>
<div>--</div> <div>--</div>
<div>--</div>
</div> </div>
</Card> </Card>

View File

@ -0,0 +1,28 @@
.home-item {
display: flex;
justify-content: space-between;
align-items: center;
& > div, span {
flex: 20%;
text-align: right;
}
& > :first-child {
flex: 80px
}
}
.home-header {
margin: 0px 30px;
& > div {
flex: 20%;
text-align: right;
}
& > :first-child {
text-align: left;
flex: 80px
}
}

View File

@ -1,24 +1,42 @@
import React, { useMemo } from "react"; import React, { useCallback, useEffect, useMemo, useState } from "react";
import { useLendingReserve, useTokenName, useUserAccounts, useUserBalance } from './../../hooks'; import { useLendingReserve, useTokenName, useUserAccounts, useUserBalance } from './../../hooks';
import { LendingReserve, LendingReserveParser } from "../../models/lending"; import { LendingReserve, LendingReserveParser } from "../../models/lending";
import { TokenIcon } from "../../components/TokenIcon"; import { TokenIcon } from "../../components/TokenIcon";
import { formatNumber } from "../../utils/utils"; import { formatNumber } from "../../utils/utils";
import { Button, Card } from "antd"; import { Button, Card } from "antd";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { useAccount } from "../../contexts/accounts"; import { cache, useAccount } from "../../contexts/accounts";
import { NumericInput } from "../../components/Input/numeric";
import { useConnection } from "../../contexts/connection";
import { useWallet } from "../../contexts/wallet";
import { deposit } from './../../actions/deposit';
import './style.less';
import { DepositAdd } from './../../components/DepositAdd';
import { UserLendingCard } from './../../components/UserLendingCard';
import { ReserveStatus } from './../../components/ReserveStatus';
export const ReserveView = () => { export const ReserveView = () => {
const connection = useConnection();
const { wallet } = useWallet();
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
const lendingReserve = useLendingReserve(id); const lendingReserve = useLendingReserve(id);
const reserve = lendingReserve?.info; const reserve = lendingReserve?.info;
const name = useTokenName(reserve?.liquidityMint); if (!reserve || !lendingReserve) {
const { balance: tokenBalance } = useUserBalance(reserve?.liquidityMint); return null;
const { balance: collateralBalance } = useUserBalance(reserve?.collateralMint); }
return <Card> return <div className="reserve-overview">
<div className="reserve-overview-container">
<ReserveStatus
</Card>; className="reserve-overview-item reserve-overview-item-left"
reserve={reserve}
address={lendingReserve.pubkey} />
<UserLendingCard
className="reserve-overview-item reserve-overview-item-right"
reserve={reserve}
address={lendingReserve.pubkey} />
</div>
</div>;
} }

View File

@ -0,0 +1,30 @@
.reserve-overview {
display: flex;
flex-direction: column;
flex: 1;
}
.reserve-overview-item {
margin: 4px;
}
.reserve-overview-container {
display: flex;
flex-wrap: wrap;
flex: 1;
}
.reserve-overview-item-left {
flex: 60%;
}
.reserve-overview-item-right {
flex: 30%;
}
/* Responsive layout - makes a one column layout instead of a two-column layout */
@media (max-width: 600px) {
.reserve-overview-item-right, .reserve-overview-item-left {
flex: 100%;
}
}