Merge branch 'main' into main

This commit is contained in:
Bartosz Lipinski 2020-12-30 08:33:43 -06:00 committed by GitHub
commit 02ea379405
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 687 additions and 307 deletions

View File

@ -4,8 +4,13 @@ Any content produced by Solana, or developer resources that Solana provides, are
## TODO
- [] Finish Reserve overview page (chart, and borrows information)
- [] Deposit view calculate APY and health factor
- [] Add github link
- [] Repay from reserve (add selection for obligation/loan)
- [] Add support for token names in URL in addition to reserve address
* Add github link
* Repay from reserve (add selection for obligation/loan)
* Add support for token names in URL in addition to reserve address
* Repay select first loan when accessing from reserve overview
* convert deposit info to multiple cards
* add slider to borrow that shows risk/collateral usage
* integrate fees
* ephemeral keys
* merge margin PR and apply swap like style to borrow/repay
* add values to bar chart labels

View File

@ -231,6 +231,10 @@ em {
flex: 1;
}
.card-fill {
height: 100%;
}
.card-row {
box-sizing: border-box;
margin: 5px 0px;
@ -295,6 +299,12 @@ em {
font-weight: bold;
}
.ant-select-selection-item {
.token-balance {
display: none;
};
};
.token-input {
display: flex;
align-items: center;
@ -303,12 +313,48 @@ em {
margin: 5px 0px;
}
.token-balance {
margin-left: auto;
margin-right: 5px;
color: @text-color-secondary;
}
[class="ant-layout-header"] {
height: 16px !important;
}
.dashboard-amount-quote {
font-size: 10px;
font-style: normal;
text-align: right;
}
.small-statisitc {
.ant-statistic-title {
font-size: 12px;
}
.ant-statistic-content {
max-height: 20px;
line-height: 2px;;
}
.ant-statistic-content-value-int {
font-size: 12px;
}
.ant-statistic-content-value-decimal {
font-size: 10px;
}
}
.dashboard-amount-quote-stat {
font-size: 10px;
font-style: normal;
text-align: center;
font-weight: normal;
}
@media only screen and (max-width: 600px) {
.exchange-card {
width: 360px;

View File

@ -1,9 +1,14 @@
import { Account, Connection, PublicKey, TransactionInstruction } from '@solana/web3.js';
import { sendTransaction } from '../contexts/connection';
import { notify } from '../utils/notifications';
import { LendingReserve } from './../models/lending/reserve';
import { AccountLayout, MintInfo, MintLayout, Token } from '@solana/spl-token';
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from '../utils/ids';
import {
Account,
Connection,
PublicKey,
TransactionInstruction,
} from "@solana/web3.js";
import { sendTransaction } from "../contexts/connection";
import { notify } from "../utils/notifications";
import { LendingReserve } from "./../models/lending/reserve";
import { AccountLayout, MintInfo, MintLayout } from "@solana/spl-token";
import { LENDING_PROGRAM_ID } from "../utils/ids";
import {
createTempMemoryAccount,
createUninitializedAccount,
@ -20,8 +25,9 @@ import {
LendingMarket,
BorrowAmountType,
LendingObligation,
} from '../models';
import { toLamports } from '../utils/utils';
approve,
} from "../models";
import { toLamports } from "../utils/utils";
export const borrow = async (
connection: Connection,
@ -141,8 +147,13 @@ export const borrow = async (
);
// create approval for transfer transactions
instructions.push(
Token.createApproveInstruction(TOKEN_PROGRAM_ID, fromAccount, authority, wallet.publicKey, [], fromLamports)
approve(
instructions,
cleanupInstructions,
fromAccount,
authority,
wallet.publicKey,
fromLamports
);
const dexMarketAddress = borrowReserve.info.dexMarketOption

View File

@ -1,11 +1,24 @@
import { Account, Connection, PublicKey, TransactionInstruction } from '@solana/web3.js';
import { sendTransaction } from '../contexts/connection';
import { notify } from '../utils/notifications';
import { depositInstruction, initReserveInstruction, LendingReserve } from './../models/lending';
import { AccountLayout, Token } from '@solana/spl-token';
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from '../utils/ids';
import { createUninitializedAccount, ensureSplAccount, findOrCreateAccountByMint } from './account';
import { TokenAccount } from '../models';
import {
Account,
Connection,
PublicKey,
TransactionInstruction,
} from "@solana/web3.js";
import { sendTransaction } from "../contexts/connection";
import { notify } from "../utils/notifications";
import {
depositInstruction,
initReserveInstruction,
LendingReserve,
} from "./../models/lending";
import { AccountLayout } from "@solana/spl-token";
import { LENDING_PROGRAM_ID } from "../utils/ids";
import {
createUninitializedAccount,
ensureSplAccount,
findOrCreateAccountByMint,
} from "./account";
import { approve, TokenAccount } from "../models";
export const deposit = async (
from: TokenAccount,
@ -45,8 +58,13 @@ export const deposit = async (
);
// create approval for transfer transactions
instructions.push(
Token.createApproveInstruction(TOKEN_PROGRAM_ID, fromAccount, authority, wallet.publicKey, [], amountLamports)
approve(
instructions,
cleanupInstructions,
fromAccount,
authority,
wallet.publicKey,
amountLamports
);
let toAccount: PublicKey;

View File

@ -1,13 +1,18 @@
import { Account, Connection, PublicKey, TransactionInstruction } from '@solana/web3.js';
import { sendTransaction } from '../contexts/connection';
import { notify } from '../utils/notifications';
import { LendingReserve } from './../models/lending/reserve';
import { liquidateInstruction } from './../models/lending/liquidate';
import { AccountLayout, Token } from '@solana/spl-token';
import { createTempMemoryAccount, ensureSplAccount, findOrCreateAccountByMint } from './account';
import { LendingMarket, LendingObligation, TokenAccount } from '../models';
import { cache, ParsedAccount } from '../contexts/accounts';
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from '../utils/ids';
import {
Account,
Connection,
PublicKey,
TransactionInstruction,
} from "@solana/web3.js";
import { sendTransaction } from "../contexts/connection";
import { notify } from "../utils/notifications";
import { LendingReserve } from "./../models/lending/reserve";
import { liquidateInstruction } from "./../models/lending/liquidate";
import { AccountLayout } from "@solana/spl-token";
import { LENDING_PROGRAM_ID } from "../utils/ids";
import { createTempMemoryAccount, ensureSplAccount, findOrCreateAccountByMint } from "./account";
import { approve, LendingMarket, LendingObligation, TokenAccount } from "../models";
import { cache, ParsedAccount } from "../contexts/accounts";
export const liquidate = async (
connection: Connection,
@ -50,8 +55,13 @@ export const liquidate = async (
);
// create approval for transfer transactions
instructions.push(
Token.createApproveInstruction(TOKEN_PROGRAM_ID, fromAccount, authority, wallet.publicKey, [], amountLamports)
approve(
instructions,
cleanupInstructions,
fromAccount,
authority,
wallet.publicKey,
amountLamports
);
// get destination account

View File

@ -1,13 +1,18 @@
import { Account, Connection, PublicKey, TransactionInstruction } from '@solana/web3.js';
import { sendTransaction } from '../contexts/connection';
import { notify } from '../utils/notifications';
import { LendingReserve } from './../models/lending/reserve';
import { repayInstruction } from './../models/lending/repay';
import { AccountLayout, Token } from '@solana/spl-token';
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from '../utils/ids';
import { findOrCreateAccountByMint } from './account';
import { LendingObligation, TokenAccount } from '../models';
import { ParsedAccount } from '../contexts/accounts';
import {
Account,
Connection,
PublicKey,
TransactionInstruction,
} from "@solana/web3.js";
import { sendTransaction } from "../contexts/connection";
import { notify } from "../utils/notifications";
import { LendingReserve } from "./../models/lending/reserve";
import { repayInstruction } from "./../models/lending/repay";
import { AccountLayout } from "@solana/spl-token";
import { LENDING_PROGRAM_ID } from "../utils/ids";
import { findOrCreateAccountByMint } from "./account";
import { approve, LendingObligation, TokenAccount } from "../models";
import { ParsedAccount } from "../contexts/accounts";
export const repay = async (
from: TokenAccount, // CollateralAccount
@ -46,8 +51,13 @@ export const repay = async (
const fromAccount = from.pubkey;
// create approval for transfer transactions
instructions.push(
Token.createApproveInstruction(TOKEN_PROGRAM_ID, fromAccount, authority, wallet.publicKey, [], amountLamports)
approve(
instructions,
cleanupInstructions,
fromAccount,
authority,
wallet.publicKey,
amountLamports
);
// get destination account
@ -62,15 +72,13 @@ export const repay = async (
);
// create approval for transfer transactions
instructions.push(
Token.createApproveInstruction(
TOKEN_PROGRAM_ID,
obligationToken.pubkey,
authority,
wallet.publicKey,
[],
obligationToken.info.amount.toNumber()
)
approve(
instructions,
cleanupInstructions,
obligationToken.pubkey,
authority,
wallet.publicKey,
obligationToken.info.amount.toNumber()
);
// TODO: add obligation

View File

@ -1,11 +1,16 @@
import { Account, Connection, PublicKey, TransactionInstruction } from '@solana/web3.js';
import { sendTransaction } from '../contexts/connection';
import { notify } from '../utils/notifications';
import { LendingReserve, withdrawInstruction } from './../models/lending';
import { AccountLayout, Token } from '@solana/spl-token';
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from '../utils/ids';
import { findOrCreateAccountByMint } from './account';
import { TokenAccount } from '../models';
import {
Account,
Connection,
PublicKey,
TransactionInstruction,
} from "@solana/web3.js";
import { sendTransaction } from "../contexts/connection";
import { notify } from "../utils/notifications";
import { LendingReserve, withdrawInstruction } from "./../models/lending";
import { AccountLayout } from "@solana/spl-token";
import { LENDING_PROGRAM_ID } from "../utils/ids";
import { findOrCreateAccountByMint } from "./account";
import { approve, TokenAccount } from "../models";
export const withdraw = async (
from: TokenAccount, // CollateralAccount
@ -33,8 +38,13 @@ export const withdraw = async (
const fromAccount = from.pubkey;
// create approval for transfer transactions
instructions.push(
Token.createApproveInstruction(TOKEN_PROGRAM_ID, fromAccount, authority, wallet.publicKey, [], amountLamports)
approve(
instructions,
cleanupInstructions,
fromAccount,
authority,
wallet.publicKey,
amountLamports
);
// get destination account

View File

@ -10,7 +10,7 @@ import {
LendingReserveParser,
} from "../../models";
import { TokenIcon } from "../TokenIcon";
import { Button, Card } from "antd";
import { Card } from "antd";
import { cache, ParsedAccount } from "../../contexts/accounts";
import { NumericInput } from "../Input/numeric";
import { useConnection } from "../../contexts/connection";
@ -21,6 +21,7 @@ import "./style.less";
import { LABELS } from "../../constants";
import { ActionConfirmation } from "./../ActionConfirmation";
import { BackButton } from "./../BackButton";
import { ConnectButton } from "../ConnectButton";
export const BorrowInput = (props: {
className?: string;
@ -150,14 +151,14 @@ export const BorrowInput = (props: {
/>
<div>{name}</div>
</div>
<Button
<ConnectButton
type="primary"
onClick={onBorrow}
loading={pendingTx}
disabled={fromAccounts.length === 0}
>
{LABELS.BORROW_ACTION}
</Button>
{fromAccounts.length === 0 ? LABELS.NO_DEPOSITS : LABELS.BORROW_ACTION}
</ConnectButton>
<BackButton />
</div>
)}

View File

@ -1,14 +1,44 @@
import React from 'react';
import { useLendingReserves } from '../../hooks';
import { LendingMarket, LendingReserve } from '../../models';
import { TokenIcon } from '../TokenIcon';
import { getTokenName } from '../../utils/utils';
import { Select } from 'antd';
import { useConnectionConfig } from '../../contexts/connection';
import { cache, ParsedAccount } from '../../contexts/accounts';
import React from "react";
import { useLendingReserves, UserDeposit, useUserDeposits } from "../../hooks";
import { LendingMarket, LendingReserve } from "../../models";
import { TokenIcon } from "../TokenIcon";
import { formatAmount, getTokenName } from "../../utils/utils";
import { Select } from "antd";
import { useConnectionConfig } from "../../contexts/connection";
import { cache, ParsedAccount } from "../../contexts/accounts";
const { Option } = Select;
export const CollateralItem = (props: {
mint: string;
reserve: ParsedAccount<LendingReserve>;
userDeposit?: UserDeposit;
name: string;
}) => {
const {
mint,
name,
userDeposit,
} = props;
return (
<>
<div
style={{ display: "flex", alignItems: "center" }}
>
<TokenIcon mintAddress={mint} />
{name}
<span
className="token-balance"
>
&nbsp;{" "}
{userDeposit ? formatAmount(userDeposit.info.amount) : '--'}
</span>
</div>
</>
);
}
export const CollateralSelector = (props: {
reserve: LendingReserve;
collateralReserve?: string;
@ -17,11 +47,17 @@ export const CollateralSelector = (props: {
}) => {
const { reserveAccounts } = useLendingReserves();
const { tokenMap } = useConnectionConfig();
const { userDeposits } = useUserDeposits();
const market = cache.get(props.reserve.lendingMarket) as ParsedAccount<LendingMarket>;
const market = cache.get(props.reserve?.lendingMarket) as ParsedAccount<
LendingMarket
>;
if (!market) return null;
const onlyQuoteAllowed = !props.reserve?.liquidityMint?.equals(market?.info?.quoteMint);
const quoteMintAddress = market?.info?.quoteMint?.toBase58();
const onlyQuoteAllowed = props.reserve?.liquidityMint?.toBase58() !==
quoteMintAddress;
return (
<Select
@ -46,14 +82,14 @@ export const CollateralSelector = (props: {
const mint = reserve.info.liquidityMint.toBase58();
const address = reserve.pubkey.toBase58();
const name = getTokenName(tokenMap, mint);
return (
<Option key={address} value={address} name={name} title={address}>
<div key={address} style={{ display: 'flex', alignItems: 'center' }}>
<TokenIcon mintAddress={mint} />
{name}
</div>
</Option>
);
return <Option key={address} value={address} name={name} title={address}>
<CollateralItem
reserve={reserve}
userDeposit={userDeposits.find(dep => dep.reserve.pubkey.toBase58() === address)}
mint={mint}
name={name} />
</Option>
})}
</Select>
);

View File

@ -8,9 +8,9 @@ export const ConnectButton = (
props: ButtonProps & React.RefAttributes<HTMLElement>
) => {
const { wallet, connected } = useWallet();
const { onClick, children, ...rest } = props;
const { onClick, children, disabled, ...rest } = props;
return (
<Button {...rest} onClick={connected ? onClick : wallet.connect}>
<Button {...rest} onClick={connected ? onClick : wallet.connect} disabled={connected && disabled}>
{connected ? props.children : LABELS.CONNECT_LABEL}
</Button>
);

View File

@ -7,7 +7,7 @@ import {
} from "../../hooks";
import { LendingReserve } from "../../models/lending";
import { TokenIcon } from "../TokenIcon";
import { Button, Card, Slider } from "antd";
import { Card, Slider } from "antd";
import { NumericInput } from "../Input/numeric";
import { useConnection } from "../../contexts/connection";
import { useWallet } from "../../contexts/wallet";
@ -16,6 +16,7 @@ import { PublicKey } from "@solana/web3.js";
import "./style.less";
import { ActionConfirmation } from "./../ActionConfirmation";
import { LABELS, marks } from "../../constants";
import { ConnectButton } from "../ConnectButton";
export const DepositInput = (props: {
className?: string;
@ -126,14 +127,14 @@ export const DepositInput = (props: {
<Slider marks={marks} value={pct} onChange={setPct} />
<Button
<ConnectButton
type="primary"
onClick={onDeposit}
loading={pendingTx}
disabled={fromAccounts.length === 0}
>
{LABELS.DEPOSIT_ACTION}
</Button>
</ConnectButton>
</div>
)}
</Card>

View File

@ -9,7 +9,7 @@ import {
} from "../../hooks";
import { LendingReserve } from "../../models";
import { TokenIcon } from "../TokenIcon";
import { Button, Card, Slider } from "antd";
import { Card, Slider } from "antd";
import { ParsedAccount, useMint } from "../../contexts/accounts";
import { NumericInput } from "../Input/numeric";
import { useConnection } from "../../contexts/connection";
@ -21,6 +21,7 @@ import { LABELS, marks } from "../../constants";
import { ActionConfirmation } from "./../ActionConfirmation";
import { fromLamports, wadToLamports } from "../../utils/utils";
import { notify } from "../../utils/notifications";
import { ConnectButton } from "../ConnectButton";
export const RepayInput = (props: {
className?: string;
@ -169,14 +170,14 @@ export const RepayInput = (props: {
disabled={true}
/>
<Button
<ConnectButton
type="primary"
onClick={onRepay}
loading={pendingTx}
disabled={fromAccounts.length === 0}
>
{LABELS.REPAY_ACTION}
</Button>
</ConnectButton>
</div>
)}
</Card>

View File

@ -1,10 +1,15 @@
import React from "react";
import { LendingReserve } from "../../models/lending";
import { Card } from "antd";
import { calculateDepositAPY, LendingReserve } from "../../models/lending";
import { Card, Col, Row, Statistic } from "antd";
import { PublicKey } from "@solana/web3.js";
import "./style.less";
import { LABELS } from "../../constants";
import { GUTTER, LABELS } from "../../constants";
import { ReserveUtilizationChart } from "./../../components/ReserveUtilizationChart";
import { useMemo } from "react";
import { formatNumber, fromLamports, wadToLamports } from "../../utils/utils";
import { useMint } from "../../contexts/accounts";
import { useMidPriceInUSD } from "../../contexts/market";
import { TokenIcon } from "../TokenIcon";
export const ReserveStatus = (props: {
className?: string;
@ -17,20 +22,113 @@ export const ReserveStatus = (props: {
alignItems: "center",
};
const mintAddress = props.reserve.liquidityMint?.toBase58();
const liquidityMint = useMint(mintAddress);
const { price } = useMidPriceInUSD(mintAddress);
const availableLiquidity = fromLamports(
props.reserve.availableLiquidity.toNumber(),
liquidityMint
);
const availableLiquidityInUSD = price * availableLiquidity;
const totalBorrows = useMemo(
() =>
fromLamports(
wadToLamports(props.reserve.borrowedLiquidityWad),
liquidityMint
),
[props.reserve, liquidityMint]
);
const totalBorrowsInUSD = price * totalBorrows;
const depositAPY = useMemo(() => calculateDepositAPY(props.reserve), [
props.reserve
]);
const liquidationThreshold = props.reserve.config.liquidationThreshold;
const liquidationPenalty = props.reserve.config.liquidationBonus;
const maxLTV = props.reserve.config.loanToValueRatio;
return (
<Card
className={props.className}
title={<>{LABELS.RESERVE_STATUS_TITLE}</>}
title={<>
<TokenIcon style={{ marginRight: 0, marginTop: 0, position: 'absolute', left: 15 }}
mintAddress={mintAddress} size={30} />
{LABELS.RESERVE_STATUS_TITLE}</>}
bodyStyle={bodyStyle}
>
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "space-around",
}}
>
<ReserveUtilizationChart reserve={props.reserve} />
<div className="flexColumn">
<Row gutter={GUTTER}>
<Col span={12}>
<Statistic
title="Available Liquidity"
value={availableLiquidity}
valueRender={(node) => <div>
{node}
<div className="dashboard-amount-quote-stat">${formatNumber.format(availableLiquidityInUSD)}</div>
</div>}
precision={2} />
</Col>
<Col span={12}>
<Statistic
title="Total Borrowed"
value={totalBorrows}
valueRender={(node) => <div>
{node}
<div className="dashboard-amount-quote-stat">${formatNumber.format(totalBorrowsInUSD)}</div>
</div>}
precision={2} />
</Col>
</Row>
<Row gutter={GUTTER}>
<Col
span={24}
style={{
display: "flex",
flexDirection: "column",
justifyContent: "space-around",
}}
>
<ReserveUtilizationChart reserve={props.reserve} />
</Col>
</Row>
<Row gutter={GUTTER}>
<Col span={6}>
<Statistic
title="Maximum LTV"
className="small-statisitc"
value={maxLTV}
precision={2}
suffix="%" />
</Col>
<Col span={6}>
<Statistic
title="Liquidation threashold"
className="small-statisitc"
value={liquidationThreshold}
precision={2}
suffix="%" />
</Col>
<Col span={6}>
<Statistic
title="Liquidation penalty"
className="small-statisitc"
value={liquidationPenalty}
precision={2}
suffix="%" />
</Col>
<Col span={6}>
<Statistic
title="APY"
className="small-statisitc"
value={depositAPY * 100}
precision={2}
suffix="%" />
</Col>
</Row>
</div>
</Card>
);

View File

@ -3,9 +3,11 @@ import { LendingReserve } from "../../models/lending";
import { fromLamports, wadToLamports } from "../../utils/utils";
import { useMint } from "../../contexts/accounts";
import { WaterWave } from "./../WaterWave";
import { Statistic } from "antd";
export const ReserveUtilizationChart = (props: { reserve: LendingReserve }) => {
const liquidityMint = useMint(props.reserve.liquidityMint);
const mintAddress = props.reserve.liquidityMint?.toBase58();
const liquidityMint = useMint(mintAddress);
const availableLiquidity = fromLamports(
props.reserve.availableLiquidity.toNumber(),
liquidityMint
@ -20,10 +22,20 @@ export const ReserveUtilizationChart = (props: { reserve: LendingReserve }) => {
[props.reserve, liquidityMint]
);
const percent = (availableLiquidity * 100) / (availableLiquidity + totalBorrows);
return (
<WaterWave
style={{ height: 300 }}
percent={(availableLiquidity * 100) / (availableLiquidity + totalBorrows)}
showPercent={false}
title={
<Statistic
title="Utilization"
value={percent}
suffix="%"
precision={2} />
}
percent={percent}
/>
);
};

View File

@ -7,19 +7,22 @@ import { PublicKey } from "@solana/web3.js";
export const TokenIcon = (props: {
mintAddress?: string | PublicKey;
style?: React.CSSProperties;
size?: number;
className?: string;
}) => {
const { tokenMap } = useConnectionConfig();
const icon = getTokenIcon(tokenMap, props.mintAddress);
const size = props.size || 20;
if (icon) {
return (
<img
alt="Token icon"
className={props.className}
key={icon}
width={props.style?.width || "20"}
height={props.style?.height || "20"}
width={props.style?.width || size.toString()}
height={props.style?.height || size.toString()}
src={icon}
style={{
marginRight: "0.5rem",
@ -38,8 +41,8 @@ export const TokenIcon = (props: {
address={props.mintAddress}
style={{
marginRight: "0.5rem",
width: 20,
height: 20,
width: size,
height: size,
marginTop: 2,
...props.style,
}}

View File

@ -4,6 +4,7 @@ import {
useTokenName,
useUserBalance,
useBorrowedAmount,
useBorrowingPower,
} from "./../../hooks";
import { LendingReserve } from "../../models/lending";
import { formatNumber } from "../../utils/utils";
@ -29,9 +30,7 @@ export const UserLendingCard = (props: {
);
const { borrowed: totalBorrowed, borrowedInUSD, ltv, health } = useBorrowedAmount(address);
// TODO: calculate
const available = 0; // use all available deposits and convert using market rate
const { totalInQuote: borrowingPowerInUSD, borrowingPower } = useBorrowingPower(address);
return (
<Card
@ -82,7 +81,10 @@ export const UserLendingCard = (props: {
Available to you:
</Text>
<div className="card-cell ">
{formatNumber.format(available)} {name}
<div>
<div><em>{formatNumber.format(borrowingPower)}</em> {name}</div>
<div className="dashboard-amount-quote">${formatNumber.format(borrowingPowerInUSD)}</div>
</div>
</div>
</div>

View File

@ -4,13 +4,15 @@
display: inline-block;
position: relative;
transform-origin: left;
display: flex;
align-items: center;
justify-content: center;
.text {
position: absolute;
left: 5px;
top: calc(50% - 15px);
text-align: center;
width: 100%;
span {
.title {
color: @text-color-secondary;
font-size: 14px;
line-height: 22px;

View File

@ -5,7 +5,7 @@ export const WaterWave = (props: any) => {
const node = useRef<HTMLCanvasElement>();
const root = useRef<HTMLDivElement>();
const [radio, setRadio] = useState(1);
const { percent, title, style, color } = props;
const { percent, title, style, color, showPercent } = props;
const { height } = style;
const resize = useCallback(() => {
@ -54,8 +54,8 @@ export const WaterWave = (props: any) => {
/>
</div>
<div className="text" style={{ width: height }}>
{title && <span>{title}</span>}
<h4>{percent.toFixed(2)}%</h4>
{title}
<h4>{showPercent && `${percent.toFixed(2)}%`}</h4>
</div>
</div>
);

View File

@ -8,7 +8,7 @@ import {
} from "../../hooks";
import { LendingReserve } from "../../models/lending";
import { TokenIcon } from "../TokenIcon";
import { Button, Card, Slider } from "antd";
import { Card, Slider } from "antd";
import { NumericInput } from "../Input/numeric";
import { useConnection } from "../../contexts/connection";
import { useWallet } from "../../contexts/wallet";
@ -17,6 +17,7 @@ import { PublicKey } from "@solana/web3.js";
import "./style.less";
import { LABELS, marks } from "../../constants";
import { ActionConfirmation } from "./../ActionConfirmation";
import { ConnectButton } from "../ConnectButton";
export const WithdrawInput = (props: {
className?: string;
@ -134,14 +135,14 @@ export const WithdrawInput = (props: {
<Slider marks={marks} value={pct} onChange={setPct} />
<Button
<ConnectButton
type="primary"
onClick={onWithdraw}
loading={pendingTx}
disabled={fromAccounts.length === 0}
>
{LABELS.WITHDRAW_ACTION}
</Button>
{fromAccounts.length === 0 ? LABELS.NO_DEPOSITS : LABELS.WITHDRAW_ACTION}
</ConnectButton>
</div>
)}
</Card>

View File

@ -1,3 +1,4 @@
export * from './labels';
export * from './math';
export * from './marks';
export * from "./labels";
export * from "./math";
export * from "./marks";
export * from "./style";

View File

@ -1,57 +1,62 @@
export const LABELS = {
CONNECT_LABEL: 'Connect Wallet',
GIVE_SOL: 'Give me SOL',
FAUCET_INFO: 'This faucet will help you fund your accounts outside of Solana main network.',
ACCOUNT_FUNDED: 'Account funded.',
REPAY_QUESTION: 'How much would you like to repay?',
REPAY_ACTION: 'Repay',
RESERVE_STATUS_TITLE: 'Reserve Status & Configuration',
CONNECT_LABEL: "Connect Wallet",
BORROWING_POWER_USED: "Borrowing Power Used",
BORROWING_POWER_VALUE: "Borrowing Power",
BORROWED_VALUE: "You borrowed",
GIVE_SOL: "Give me SOL",
LIQUIDATION_INFO: "This view displays all loans that can be liquidated. A liquidation is a process where borrower collateral does not cover value of the loan. It is represented by health factor falling below 1.0. When a loan is liquidated, an liquidator can purchase collateral at a discount by repaing the portio of the loan. ",
FAUCET_INFO:
"This faucet will help you fund your accounts outside of Solana main network.",
ACCOUNT_FUNDED: "Account funded.",
REPAY_QUESTION: "How much would you like to repay?",
REPAY_ACTION: "Repay",
RESERVE_STATUS_TITLE: "Reserve Status & Configuration",
AUDIT_WARNING:
'Oyster is an unaudited software project used for internal purposes at the Solana Foundation. This app is not for public use.',
FOOTER:
'This page was produced by the Solana Foundation ("SF") for internal educational and inspiration purposes only. SF does not encourage, induce or sanction the deployment, integration or use of Oyster or any similar application (including its code) in violation of applicable laws or regulations and hereby prohibits any such deployment, integration or use. Anyone using this code or a derivation thereof must comply with applicable laws and regulations when releasing related software.',
MENU_HOME: 'Home',
MENU_DASHBOARD: 'Dashboard',
DASHBOARD_INFO: 'Connect to a wallet to view your deposits/loans.',
NO_LOANS_NO_DEPOSITS: 'No loans or deposits.',
MENU_DEPOSIT: 'Deposit',
MENU_BORROW: 'Borrow',
MENU_LIQUIDATE: 'Liquidate',
MENU_FAUCET: 'Faucet',
MARGIN_TRADING: 'Margin Trading',
APP_TITLE: 'Oyster Lending',
CONNECT_BUTTON: 'Connect',
WALLET_TOOLTIP: 'Wallet public key',
SETTINGS_TOOLTIP: 'Settings',
SELECT_COLLATERAL: 'Select collateral',
COLLATERAL: 'Collateral',
BORROW_QUESTION: 'How much would you like to borrow?',
BORROW_ACTION: 'Borrow',
LIQUIDATE_ACTION: 'Liquidate',
LIQUIDATE_NO_LOANS: 'There are no loans to liquidate.',
TABLE_TITLE_ASSET: 'Asset',
TABLE_TITLE_YOUR_LOAN_BALANCE: 'Loan balance',
TABLE_TITLE_LOAN_BALANCE: 'Loan balance',
TABLE_TITLE_COLLATERAL_BALANCE: 'Collateral',
TABLE_TITLE_DEPOSIT_BALANCE: 'Your deposits',
TABLE_TITLE_APY: 'APY',
TABLE_TITLE_LTV: 'LTV',
TABLE_TITLE_HEALTH: 'Health Factor',
TABLE_TITLE_BORROW_APY: 'Borrow APY',
TABLE_TITLE_DEPOSIT_APY: 'Deposit APY',
TABLE_TITLE_TOTAL_BORROWED: 'Total Borrowed',
TABLE_TITLE_MARKET_SIZE: 'Market Size',
TABLE_TITLE_ACTION: 'Action',
TABLE_TITLE_MAX_BORROW: 'Available for you',
DASHBOARD_TITLE_LOANS: 'Loans',
DASHBOARD_TITLE_DEPOSITS: 'Deposits',
DEPOSIT_QUESTION: 'How much would you like to deposit?',
WITHDRAW_ACTION: 'Withdraw',
WITHDRAW_QUESTION: 'How much would you like to withdraw?',
DASHBOARD_ACTION: 'Go to dashboard',
GO_BACK_ACTION: 'Go back',
DEPOSIT_ACTION: 'Deposit',
TOTAL_TITLE: 'Total',
MENU_HOME: "Home",
MENU_DASHBOARD: "Dashboard",
DASHBOARD_INFO: "Connect to a wallet to view your deposits/loans.",
NO_LOANS_NO_DEPOSITS: "No loans or deposits.",
MENU_DEPOSIT: "Deposit",
MENU_BORROW: "Borrow",
MENU_LIQUIDATE: "Liquidate",
MENU_FAUCET: "Faucet",
APP_TITLE: "Oyster Lending",
CONNECT_BUTTON: "Connect",
WALLET_TOOLTIP: "Wallet public key",
SETTINGS_TOOLTIP: "Settings",
SELECT_COLLATERAL: "Select collateral",
COLLATERAL: "Collateral",
BORROW_QUESTION: "How much would you like to borrow?",
BORROW_ACTION: "Borrow",
NO_DEPOSITS: "No collateral",
LIQUIDATE_ACTION: "Liquidate",
LIQUIDATE_NO_LOANS: "There are no loans to liquidate.",
TABLE_TITLE_ASSET: "Asset",
TABLE_TITLE_YOUR_LOAN_BALANCE: "Loan balance",
TABLE_TITLE_LOAN_BALANCE: "Loan balance",
TABLE_TITLE_COLLATERAL_BALANCE: "Collateral",
TABLE_TITLE_DEPOSIT_BALANCE: "Your deposits",
TABLE_TITLE_APY: "APY",
TABLE_TITLE_LTV: "LTV",
TABLE_TITLE_HEALTH: "Health Factor",
TABLE_TITLE_BORROW_APY: "Borrow APY",
TABLE_TITLE_DEPOSIT_APY: "Deposit APY",
TABLE_TITLE_TOTAL_BORROWED: "Total Borrowed",
TABLE_TITLE_MARKET_SIZE: "Market Size",
TABLE_TITLE_ACTION: "Action",
TABLE_TITLE_MAX_BORROW: "Available for you",
DASHBOARD_TITLE_LOANS: "Loans",
DASHBOARD_TITLE_DEPOSITS: "Deposits",
DEPOSIT_QUESTION: "How much would you like to deposit?",
WITHDRAW_ACTION: "Withdraw",
WITHDRAW_QUESTION: "How much would you like to withdraw?",
DASHBOARD_ACTION: "Go to dashboard",
GO_BACK_ACTION: "Go back",
DEPOSIT_ACTION: "Deposit",
TOTAL_TITLE: "Total",
TRADING_TABLE_TITLE_MY_COLLATERAL: 'Chosen Collateral',
TRADING_TABLE_TITLE_DESIRED_ASSET: 'Desired Asset',
TRADING_TABLE_TITLE_MULTIPLIER: 'Leverage',

5
src/constants/style.tsx Normal file
View File

@ -0,0 +1,5 @@
export const GUTTER = [16, { xs: 8, sm: 16, md: 16, lg: 16 }] as any;
export const SMALL_STATISTIC: React.CSSProperties = {
fontSize: 10,
};

View File

@ -55,7 +55,7 @@ export function useBorrowedAmount(address?: string | PublicKey) {
borrowedInUSD: 0,
colateralInUSD: 0,
ltv: 0,
health: 0,
health: 0,
};
let liquidationThreshold = 0;
@ -88,7 +88,6 @@ export function useBorrowedAmount(address?: string | PublicKey) {
result.health = Number.isFinite(result.health) ? result.health : 0;
}
setBorrowedInfo(result);
})();
}, [connection, userObligationsByReserve, setBorrowedInfo]);

View File

@ -2,29 +2,60 @@ import { PublicKey } from "@solana/web3.js";
import { useMemo } from "react";
import { useMidPriceInUSD } from "../contexts/market";
import { useLendingMarket } from "./useLendingMarket";
import { useLendingReserve } from "./useLendingReserves";
import { getLendingReserves, useLendingReserve } from "./useLendingReserves";
import { useUserDeposits } from "./useUserDeposits";
import { useUserObligations } from "./useUserObligations";
export function useBorrowingPower(reserveAddress: string | PublicKey) {
// TODO: add option to decrease buying power by overcollateralization factor
// TODO: add support for balance in the wallet
export function useBorrowingPower(reserveAddress: string | PublicKey, includeWallet = false, overcollateralize = true) {
const key = useMemo(() => typeof reserveAddress === 'string' ? reserveAddress : reserveAddress.toBase58(), [reserveAddress]);
const exclude = useMemo(() => new Set([key]), [key]);
const { totalInQuote } = useUserDeposits(exclude)
const reserve = useLendingReserve(key);
const liquidityMint = reserve?.info.liquidityMint;
const market = useLendingMarket(liquidityMint);
const price = useMidPriceInUSD(liquidityMint.toBase58()).price;
const liquidityMintAddress = liquidityMint?.toBase58();
const market = useLendingMarket(reserve?.info.lendingMarket);
const quoteMintAddess = market?.info?.quoteMint?.toBase58();
// TODO: remove once cross-collateral is supported
const onlyQuoteAllowed = liquidityMintAddress !==
quoteMintAddess;
const exclude = useMemo(() => new Set(
[key]),
[key]);
const inlcude = useMemo(() => {
const quoteReserve = getLendingReserves()
.find(r => r.info.liquidityMint.toBase58() === quoteMintAddess);
return onlyQuoteAllowed && quoteReserve ?
new Set([quoteReserve.pubkey.toBase58()]) :
undefined;
}, [onlyQuoteAllowed, quoteMintAddess]);
const { totalInQuote } = useUserDeposits(exclude, inlcude)
const price = useMidPriceInUSD(liquidityMintAddress).price;
const { totalInQuote: loansValue } = useUserObligations();
const totalDeposits = loansValue + totalInQuote;
const utilization = totalDeposits === 0 ? 0 : loansValue / totalDeposits;
// amounts already expressed as quite mint
if(liquidityMint.toBase58() === market?.info.quoteMint?.toBase58()) {
if (liquidityMintAddress === quoteMintAddess) {
return {
borrowingPower: totalInQuote,
totalInQuote,
utilization,
};
}
}
return {
borrowingPower: totalInQuote / price,
totalInQuote,
utilization
};
}

View File

@ -50,6 +50,7 @@ export function useUserCollateralBalance(
balanceInUSD,
mint: reserve?.collateralMint,
accounts,
hasBalance: accounts.length > 0 && balance > 0,
};
}
export function calculateCollateralBalance(

View File

@ -3,11 +3,11 @@ import { useEffect, useMemo, useState } from "react";
import { LendingReserve, LendingReserveParser } from "../models/lending";
import { cache, ParsedAccount } from "./../contexts/accounts";
const getLendingReserves = () => {
export const getLendingReserves = () => {
return cache
.byParser(LendingReserveParser)
.map((id) => cache.get(id))
.filter((acc) => acc !== undefined) as any[];
.filter((acc) => acc !== undefined) as ParsedAccount<LendingReserve>[];
};
export function useLendingReserves() {

View File

@ -25,7 +25,7 @@ export const useSliderInput = (
pct,
setPct: useCallback(
(val: number) => {
setType(InputType.AbsoluteValue);
setType(InputType.Percent);
setPct(val);
setValue(convert(val) as string);
},

View File

@ -5,7 +5,8 @@ import { useMarkets } from "../contexts/market";
import { fromLamports } from "../utils/utils";
import { useUserAccounts } from "./useUserAccounts";
export function useUserBalance(mint?: PublicKey, account?: PublicKey) {
export function useUserBalance(mintAddress?: PublicKey | string, account?: PublicKey) {
const mint = useMemo(() => typeof mintAddress === 'string' ? mintAddress : mintAddress?.toBase58(), [mintAddress]);
const { userAccounts } = useUserAccounts();
const [balanceInUSD, setBalanceInUSD] = useState(0);
const { marketEmitter, midPriceInUSD } = useMarkets();
@ -15,7 +16,7 @@ export function useUserBalance(mint?: PublicKey, account?: PublicKey) {
return userAccounts
.filter(
(acc) =>
mint?.equals(acc.info.mint) &&
mint === acc.info.mint.toBase58() &&
(!account || account.equals(acc.pubkey))
)
.sort((a, b) => b.info.amount.sub(a.info.amount).toNumber());
@ -32,7 +33,7 @@ export function useUserBalance(mint?: PublicKey, account?: PublicKey) {
useEffect(() => {
const updateBalance = () => {
setBalanceInUSD(balance * midPriceInUSD(mint?.toBase58() || ''));
setBalanceInUSD(balance * midPriceInUSD(mint || ''));
}
const dispose = marketEmitter.onMarket((args) => {
@ -51,5 +52,6 @@ export function useUserBalance(mint?: PublicKey, account?: PublicKey) {
balanceLamports,
balanceInUSD,
accounts,
hasBalance: accounts.length > 0 && balance > 0,
};
}

View File

@ -1,9 +1,39 @@
import { AccountInfo, PublicKey } from "@solana/web3.js";
import { AccountInfo, PublicKey, TransactionInstruction } from "@solana/web3.js";
import { AccountInfo as TokenAccountInfo } from "@solana/spl-token";
import { AccountInfo as TokenAccountInfo, Token } from "@solana/spl-token";
import { TOKEN_PROGRAM_ID } from "../constants";
export interface TokenAccount {
pubkey: PublicKey;
account: AccountInfo<Buffer>;
info: TokenAccountInfo;
}
export function approve(
instructions: TransactionInstruction[],
cleanupInstructions: TransactionInstruction[],
account: PublicKey,
delegate: PublicKey,
owner: PublicKey,
amount: number,
): void {
const tokenProgram = TOKEN_PROGRAM_ID;
instructions.push(
Token.createApproveInstruction(
tokenProgram,
account,
delegate,
owner,
[],
amount
)
);
cleanupInstructions.push(
Token.createRevokeInstruction(
tokenProgram,
account,
owner,
[]),
);
}

View File

@ -133,7 +133,7 @@ const abbreviateNumber = (number: number, precision: number) => {
return scaled.toFixed(precision) + suffix;
};
const format = (val: number, precision: number, abbr: boolean) =>
export const formatAmount = (val: number, precision: number = 6, abbr: boolean = true) =>
abbr ? abbreviateNumber(val, precision) : val.toFixed(precision);
export function formatTokenAmount(
@ -149,7 +149,11 @@ export function formatTokenAmount(
return '';
}
return `${[prefix]}${format(fromLamports(account, mint, rate), precision, abbr)}${suffix}`;
return `${[prefix]}${formatAmount(
fromLamports(account, mint, rate),
precision,
abbr
)}${suffix}`;
}
export const formatUSD = new Intl.NumberFormat('en-US', {

View File

@ -1,5 +1,5 @@
import React from "react";
import { useLendingReserve } from "../../hooks";
import { useBorrowingPower, useLendingReserve, useUserObligations } from "../../hooks";
import { useParams } from "react-router-dom";
import "./style.less";
@ -8,10 +8,16 @@ import {
SideReserveOverview,
SideReserveOverviewMode,
} from "../../components/SideReserveOverview";
import { Card, Col, Row, Statistic } from "antd";
import { BarChartStatistic } from "../../components/BarChartStatistic";
import { GUTTER, LABELS } from "../../constants";
export const BorrowReserveView = () => {
const { id } = useParams<{ id: string }>();
const lendingReserve = useLendingReserve(id);
const { userObligations, totalInQuote: loansValue } = useUserObligations();
const { totalInQuote: borrowingPower, utilization } = useBorrowingPower(id)
if (!lendingReserve) {
return null;
@ -19,17 +25,63 @@ export const BorrowReserveView = () => {
return (
<div className="borrow-reserve">
<div className="borrow-reserve-container">
<BorrowInput
className="borrow-reserve-item borrow-reserve-item-left"
reserve={lendingReserve}
/>
<SideReserveOverview
className="borrow-reserve-item borrow-reserve-item-right"
reserve={lendingReserve}
mode={SideReserveOverviewMode.Borrow}
/>
</div>
<Row gutter={GUTTER}>
<Col xs={24} xl={5}>
<Card>
<Statistic
title={LABELS.BORROWED_VALUE}
value={loansValue}
precision={2}
prefix="$"
/>
</Card>
</Col>
<Col xs={24} xl={5}>
<Card>
<Statistic
title={LABELS.BORROWING_POWER_USED}
value={utilization * 100}
precision={2}
suffix="%"
/>
</Card>
</Col>
<Col xs={24} xl={5}>
<Card>
<Statistic
title={LABELS.BORROWING_POWER_VALUE}
value={borrowingPower}
valueStyle={{ color: "#3f8600" }}
precision={2}
prefix="$"
/>
</Card>
</Col>
<Col xs={24} xl={9}>
<Card>
<BarChartStatistic
title="Your Loans"
items={userObligations}
getPct={(item) => item.obligation.info.borrowedInQuote / loansValue}
name={(item) => item.obligation.info.repayName} />
</Card>
</Col>
</Row>
<Row gutter={GUTTER} className="flexColumn">
<Col xs={24} xl={15}>
<BorrowInput
className="card-fill"
reserve={lendingReserve}
/>
</Col>
<Col xs={24} xl={9}>
<SideReserveOverview
className="card-fill"
reserve={lendingReserve}
mode={SideReserveOverviewMode.Borrow}
/>
</Col>
</Row>
</div>
);
};

View File

@ -2,10 +2,11 @@
display: flex;
flex-direction: column;
flex: 1;
overflow-x: hidden;
}
.borrow-reserve-item {
margin: 4px;
height: 100%;
}
.borrow-reserve-container {
@ -13,18 +14,3 @@
flex-wrap: wrap;
flex: 1;
}
.borrow-reserve-item-left {
flex: 60%;
}
.borrow-reserve-item-right {
flex: 30%;
}
/* Responsive layout - makes a one column layout instead of a two-column layout */
@media (max-width: 600px) {
.borrow-reserve-item-right, .borrow-reserve-item-left {
flex: 100%;
}
}

View File

@ -1,6 +1,6 @@
import { Col, Row } from "antd";
import React from "react";
import { LABELS } from "../../constants";
import { GUTTER, LABELS } from "../../constants";
import { useWallet } from "../../contexts/wallet";
import { useUserDeposits, useUserObligations } from "./../../hooks";
import { DashboardObligations } from "./obligation";
@ -28,7 +28,7 @@ export const DashboardView = () => {
{LABELS.NO_LOANS_NO_DEPOSITS}
</div>
)}
{connected && <Row gutter={[16, { xs: 8, sm: 16, md: 16, lg: 16 }]} >
{connected && <Row gutter={GUTTER} >
{userDeposits.length >0 && (
<Col md={24} xl={12} span={24}>
<DashboardDeposits />

View File

@ -1,7 +1,7 @@
import { MintInfo } from "@solana/spl-token";
import { Card, Col, Row, Statistic } from "antd";
import React, { useEffect, useState } from "react";
import { LABELS } from "../../constants";
import { GUTTER, LABELS } from "../../constants";
import { cache, ParsedAccount } from "../../contexts/accounts";
import { useConnectionConfig } from "../../contexts/connection";
import { useMarkets } from "../../contexts/market";
@ -19,7 +19,7 @@ export const HomeView = () => {
const [totals, setTotals] = useState<Totals>({
marketSize: 0,
borrowed: 0,
lentOutPct: 0,
lentOutPct: 0,
items: [],
})
@ -28,7 +28,7 @@ export const HomeView = () => {
let newTotals: Totals = {
marketSize: 0,
borrowed: 0,
lentOutPct: 0,
lentOutPct: 0,
items: [],
};
@ -44,14 +44,17 @@ export const HomeView = () => {
return;
}
const price = midPriceInUSD(liquidityMint?.pubkey.toBase58());
let leaf = {
key: item.pubkey.toBase58(),
marketSize: fromLamports(marketCapLamports, liquidityMint?.info) *
midPriceInUSD(liquidityMint?.pubkey.toBase58()),
price,
borrowed: fromLamports(
wadToLamports(item.info?.borrowedLiquidityWad).toNumber(),
liquidityMint.info
),
) *
price,
name: getTokenName(tokenMap, item.info.liquidityMint.toBase58())
}
@ -59,7 +62,7 @@ export const HomeView = () => {
newTotals.marketSize = newTotals.marketSize + leaf.marketSize;
newTotals.borrowed = newTotals.borrowed + leaf.borrowed;
});
newTotals.lentOutPct = newTotals.borrowed / newTotals.marketSize;
@ -82,8 +85,8 @@ export const HomeView = () => {
return (
<div className="flexColumn">
<Row
gutter={[16, { xs: 8, sm: 16, md: 16, lg: 16 }]}
<Row
gutter={GUTTER}
className="home-info-row" >
<Col xs={24} xl={5}>
<Card>
@ -128,16 +131,16 @@ export const HomeView = () => {
</Row>
<Card>
<div className="home-item home-header">
<div>{LABELS.TABLE_TITLE_ASSET}</div>
<div>{LABELS.TABLE_TITLE_MARKET_SIZE}</div>
<div>{LABELS.TABLE_TITLE_TOTAL_BORROWED}</div>
<div>{LABELS.TABLE_TITLE_DEPOSIT_APY}</div>
<div>{LABELS.TABLE_TITLE_BORROW_APY}</div>
</div>
{reserveAccounts.map((account) => (
<LendingReserveItem reserve={account.info} address={account.pubkey} item={totals.items.find(item => item.key === account.pubkey.toBase58())} />
))}
<div className="home-item home-header">
<div>{LABELS.TABLE_TITLE_ASSET}</div>
<div>{LABELS.TABLE_TITLE_MARKET_SIZE}</div>
<div>{LABELS.TABLE_TITLE_TOTAL_BORROWED}</div>
<div>{LABELS.TABLE_TITLE_DEPOSIT_APY}</div>
<div>{LABELS.TABLE_TITLE_BORROW_APY}</div>
</div>
{reserveAccounts.map((account) => (
<LendingReserveItem reserve={account.info} address={account.pubkey} item={totals.items.find(item => item.key === account.pubkey.toBase58())} />
))}
</Card>
</div>
);

View File

@ -1,9 +1,9 @@
import React, { useMemo } from "react";
import { LABELS } from "../../constants";
import { GUTTER, LABELS } from "../../constants";
import { LiquidateItem } from "./item";
import { useEnrichedLendingObligations } from "./../../hooks";
import "./style.less";
import { Card, Col, Row, Statistic } from "antd";
import { Card, Col, Row, Statistic, Typography } from "antd";
import { BarChartStatistic } from "../../components/BarChartStatistic";
export const LiquidateView = () => {
@ -30,9 +30,17 @@ export const LiquidateView = () => {
<div className="liquidate-info">{LABELS.LIQUIDATE_NO_LOANS}</div>
) : (
<div className="flexColumn">
<Row gutter={GUTTER}>
<Col span={24}>
<Card>
<Typography>
{LABELS.LIQUIDATION_INFO}
</Typography>
</Card>
</Col>
</Row>
<Row
gutter={[16, { xs: 8, sm: 16, md: 16, lg: 16 }]}
className="home-info-row" >
gutter={GUTTER}>
<Col xs={24} xl={5}>
<Card>
<Statistic
@ -73,23 +81,27 @@ export const LiquidateView = () => {
</Card>
</Col>
</Row>
<Card >
<div className="liquidate-item liquidate-header">
<div>{LABELS.TABLE_TITLE_ASSET}</div>
<div>{LABELS.TABLE_TITLE_LOAN_BALANCE}</div>
<div>{LABELS.TABLE_TITLE_COLLATERAL_BALANCE}</div>
<div>{LABELS.TABLE_TITLE_APY}</div>
<div>{LABELS.TABLE_TITLE_LTV}</div>
<div>{LABELS.TABLE_TITLE_HEALTH}</div>
<div>{LABELS.TABLE_TITLE_ACTION}</div>
</div>
{atRisk.map((item) => (
<LiquidateItem
key={item.account.pubkey.toBase58()}
item={item}
></LiquidateItem>
))}
</Card>
<Row gutter={GUTTER}>
<Col span={24}>
<Card className="card-fill">
<div className="liquidate-item liquidate-header">
<div>{LABELS.TABLE_TITLE_ASSET}</div>
<div>{LABELS.TABLE_TITLE_LOAN_BALANCE}</div>
<div>{LABELS.TABLE_TITLE_COLLATERAL_BALANCE}</div>
<div>{LABELS.TABLE_TITLE_APY}</div>
<div>{LABELS.TABLE_TITLE_LTV}</div>
<div>{LABELS.TABLE_TITLE_HEALTH}</div>
<div>{LABELS.TABLE_TITLE_ACTION}</div>
</div>
{atRisk.map((item) => (
<LiquidateItem
key={item.account.pubkey.toBase58()}
item={item}
></LiquidateItem>
))}
</Card>
</Col>
</Row>
</div>
)}
</div>

View File

@ -44,4 +44,5 @@
flex-direction: row;
flex-wrap: wrap;
flex: 1;
overflow-x: hidden;
}

View File

@ -5,6 +5,8 @@ import "./style.less";
import { UserLendingCard } from "./../../components/UserLendingCard";
import { ReserveStatus } from "./../../components/ReserveStatus";
import { Col, Row } from "antd";
import { GUTTER } from "../../constants";
export const ReserveView = () => {
const { id } = useParams<{ id: string }>();
@ -16,19 +18,21 @@ export const ReserveView = () => {
}
return (
<div className="reserve-overview">
<div className="reserve-overview-container">
<ReserveStatus
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 className="flexColumn">
<Row gutter={GUTTER}>
<Col sm={24} md={12} lg={14} xl={15} xxl={18}>
<ReserveStatus
reserve={reserve}
address={lendingReserve.pubkey} />
</Col>
<Col sm={24} md={12} lg={10} xl={9} xxl={6}>
<UserLendingCard
className="user-lending-card"
reserve={reserve}
address={lendingReserve.pubkey}
/>
</Col>
</Row>
</div>
);
};

View File

@ -4,27 +4,6 @@
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%;
}
.user-lending-card {
height: 100%;
}