Merge branch 'main' into main
This commit is contained in:
commit
02ea379405
15
README.md
15
README.md
|
@ -4,8 +4,13 @@ Any content produced by Solana, or developer resources that Solana provides, are
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
- [] Finish Reserve overview page (chart, and borrows information)
|
* Add github link
|
||||||
- [] Deposit view calculate APY and health factor
|
* Repay from reserve (add selection for obligation/loan)
|
||||||
- [] Add github link
|
* Add support for token names in URL in addition to reserve address
|
||||||
- [] Repay from reserve (add selection for obligation/loan)
|
* Repay select first loan when accessing from reserve overview
|
||||||
- [] Add support for token names in URL in addition to reserve address
|
* 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
|
||||||
|
|
46
src/App.less
46
src/App.less
|
@ -231,6 +231,10 @@ em {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-fill {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.card-row {
|
.card-row {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: 5px 0px;
|
margin: 5px 0px;
|
||||||
|
@ -295,6 +299,12 @@ em {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-select-selection-item {
|
||||||
|
.token-balance {
|
||||||
|
display: none;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
.token-input {
|
.token-input {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -303,12 +313,48 @@ em {
|
||||||
margin: 5px 0px;
|
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 {
|
.dashboard-amount-quote {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
text-align: right;
|
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) {
|
@media only screen and (max-width: 600px) {
|
||||||
.exchange-card {
|
.exchange-card {
|
||||||
width: 360px;
|
width: 360px;
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
import { Account, Connection, PublicKey, TransactionInstruction } from '@solana/web3.js';
|
import {
|
||||||
import { sendTransaction } from '../contexts/connection';
|
Account,
|
||||||
import { notify } from '../utils/notifications';
|
Connection,
|
||||||
import { LendingReserve } from './../models/lending/reserve';
|
PublicKey,
|
||||||
import { AccountLayout, MintInfo, MintLayout, Token } from '@solana/spl-token';
|
TransactionInstruction,
|
||||||
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from '../utils/ids';
|
} 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 {
|
import {
|
||||||
createTempMemoryAccount,
|
createTempMemoryAccount,
|
||||||
createUninitializedAccount,
|
createUninitializedAccount,
|
||||||
|
@ -20,8 +25,9 @@ import {
|
||||||
LendingMarket,
|
LendingMarket,
|
||||||
BorrowAmountType,
|
BorrowAmountType,
|
||||||
LendingObligation,
|
LendingObligation,
|
||||||
} from '../models';
|
approve,
|
||||||
import { toLamports } from '../utils/utils';
|
} from "../models";
|
||||||
|
import { toLamports } from "../utils/utils";
|
||||||
|
|
||||||
export const borrow = async (
|
export const borrow = async (
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
|
@ -141,8 +147,13 @@ export const borrow = async (
|
||||||
);
|
);
|
||||||
|
|
||||||
// create approval for transfer transactions
|
// create approval for transfer transactions
|
||||||
instructions.push(
|
approve(
|
||||||
Token.createApproveInstruction(TOKEN_PROGRAM_ID, fromAccount, authority, wallet.publicKey, [], fromLamports)
|
instructions,
|
||||||
|
cleanupInstructions,
|
||||||
|
fromAccount,
|
||||||
|
authority,
|
||||||
|
wallet.publicKey,
|
||||||
|
fromLamports
|
||||||
);
|
);
|
||||||
|
|
||||||
const dexMarketAddress = borrowReserve.info.dexMarketOption
|
const dexMarketAddress = borrowReserve.info.dexMarketOption
|
||||||
|
|
|
@ -1,11 +1,24 @@
|
||||||
import { Account, Connection, PublicKey, TransactionInstruction } from '@solana/web3.js';
|
import {
|
||||||
import { sendTransaction } from '../contexts/connection';
|
Account,
|
||||||
import { notify } from '../utils/notifications';
|
Connection,
|
||||||
import { depositInstruction, initReserveInstruction, LendingReserve } from './../models/lending';
|
PublicKey,
|
||||||
import { AccountLayout, Token } from '@solana/spl-token';
|
TransactionInstruction,
|
||||||
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from '../utils/ids';
|
} from "@solana/web3.js";
|
||||||
import { createUninitializedAccount, ensureSplAccount, findOrCreateAccountByMint } from './account';
|
import { sendTransaction } from "../contexts/connection";
|
||||||
import { TokenAccount } from '../models';
|
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 (
|
export const deposit = async (
|
||||||
from: TokenAccount,
|
from: TokenAccount,
|
||||||
|
@ -45,8 +58,13 @@ export const deposit = async (
|
||||||
);
|
);
|
||||||
|
|
||||||
// create approval for transfer transactions
|
// create approval for transfer transactions
|
||||||
instructions.push(
|
approve(
|
||||||
Token.createApproveInstruction(TOKEN_PROGRAM_ID, fromAccount, authority, wallet.publicKey, [], amountLamports)
|
instructions,
|
||||||
|
cleanupInstructions,
|
||||||
|
fromAccount,
|
||||||
|
authority,
|
||||||
|
wallet.publicKey,
|
||||||
|
amountLamports
|
||||||
);
|
);
|
||||||
|
|
||||||
let toAccount: PublicKey;
|
let toAccount: PublicKey;
|
||||||
|
|
|
@ -1,13 +1,18 @@
|
||||||
import { Account, Connection, PublicKey, TransactionInstruction } from '@solana/web3.js';
|
import {
|
||||||
import { sendTransaction } from '../contexts/connection';
|
Account,
|
||||||
import { notify } from '../utils/notifications';
|
Connection,
|
||||||
import { LendingReserve } from './../models/lending/reserve';
|
PublicKey,
|
||||||
import { liquidateInstruction } from './../models/lending/liquidate';
|
TransactionInstruction,
|
||||||
import { AccountLayout, Token } from '@solana/spl-token';
|
} from "@solana/web3.js";
|
||||||
import { createTempMemoryAccount, ensureSplAccount, findOrCreateAccountByMint } from './account';
|
import { sendTransaction } from "../contexts/connection";
|
||||||
import { LendingMarket, LendingObligation, TokenAccount } from '../models';
|
import { notify } from "../utils/notifications";
|
||||||
import { cache, ParsedAccount } from '../contexts/accounts';
|
import { LendingReserve } from "./../models/lending/reserve";
|
||||||
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from '../utils/ids';
|
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 (
|
export const liquidate = async (
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
|
@ -50,8 +55,13 @@ export const liquidate = async (
|
||||||
);
|
);
|
||||||
|
|
||||||
// create approval for transfer transactions
|
// create approval for transfer transactions
|
||||||
instructions.push(
|
approve(
|
||||||
Token.createApproveInstruction(TOKEN_PROGRAM_ID, fromAccount, authority, wallet.publicKey, [], amountLamports)
|
instructions,
|
||||||
|
cleanupInstructions,
|
||||||
|
fromAccount,
|
||||||
|
authority,
|
||||||
|
wallet.publicKey,
|
||||||
|
amountLamports
|
||||||
);
|
);
|
||||||
|
|
||||||
// get destination account
|
// get destination account
|
||||||
|
|
|
@ -1,13 +1,18 @@
|
||||||
import { Account, Connection, PublicKey, TransactionInstruction } from '@solana/web3.js';
|
import {
|
||||||
import { sendTransaction } from '../contexts/connection';
|
Account,
|
||||||
import { notify } from '../utils/notifications';
|
Connection,
|
||||||
import { LendingReserve } from './../models/lending/reserve';
|
PublicKey,
|
||||||
import { repayInstruction } from './../models/lending/repay';
|
TransactionInstruction,
|
||||||
import { AccountLayout, Token } from '@solana/spl-token';
|
} from "@solana/web3.js";
|
||||||
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from '../utils/ids';
|
import { sendTransaction } from "../contexts/connection";
|
||||||
import { findOrCreateAccountByMint } from './account';
|
import { notify } from "../utils/notifications";
|
||||||
import { LendingObligation, TokenAccount } from '../models';
|
import { LendingReserve } from "./../models/lending/reserve";
|
||||||
import { ParsedAccount } from '../contexts/accounts';
|
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 (
|
export const repay = async (
|
||||||
from: TokenAccount, // CollateralAccount
|
from: TokenAccount, // CollateralAccount
|
||||||
|
@ -46,8 +51,13 @@ export const repay = async (
|
||||||
const fromAccount = from.pubkey;
|
const fromAccount = from.pubkey;
|
||||||
|
|
||||||
// create approval for transfer transactions
|
// create approval for transfer transactions
|
||||||
instructions.push(
|
approve(
|
||||||
Token.createApproveInstruction(TOKEN_PROGRAM_ID, fromAccount, authority, wallet.publicKey, [], amountLamports)
|
instructions,
|
||||||
|
cleanupInstructions,
|
||||||
|
fromAccount,
|
||||||
|
authority,
|
||||||
|
wallet.publicKey,
|
||||||
|
amountLamports
|
||||||
);
|
);
|
||||||
|
|
||||||
// get destination account
|
// get destination account
|
||||||
|
@ -62,15 +72,13 @@ export const repay = async (
|
||||||
);
|
);
|
||||||
|
|
||||||
// create approval for transfer transactions
|
// create approval for transfer transactions
|
||||||
instructions.push(
|
approve(
|
||||||
Token.createApproveInstruction(
|
instructions,
|
||||||
TOKEN_PROGRAM_ID,
|
cleanupInstructions,
|
||||||
obligationToken.pubkey,
|
obligationToken.pubkey,
|
||||||
authority,
|
authority,
|
||||||
wallet.publicKey,
|
wallet.publicKey,
|
||||||
[],
|
obligationToken.info.amount.toNumber()
|
||||||
obligationToken.info.amount.toNumber()
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: add obligation
|
// TODO: add obligation
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
import { Account, Connection, PublicKey, TransactionInstruction } from '@solana/web3.js';
|
import {
|
||||||
import { sendTransaction } from '../contexts/connection';
|
Account,
|
||||||
import { notify } from '../utils/notifications';
|
Connection,
|
||||||
import { LendingReserve, withdrawInstruction } from './../models/lending';
|
PublicKey,
|
||||||
import { AccountLayout, Token } from '@solana/spl-token';
|
TransactionInstruction,
|
||||||
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from '../utils/ids';
|
} from "@solana/web3.js";
|
||||||
import { findOrCreateAccountByMint } from './account';
|
import { sendTransaction } from "../contexts/connection";
|
||||||
import { TokenAccount } from '../models';
|
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 (
|
export const withdraw = async (
|
||||||
from: TokenAccount, // CollateralAccount
|
from: TokenAccount, // CollateralAccount
|
||||||
|
@ -33,8 +38,13 @@ export const withdraw = async (
|
||||||
const fromAccount = from.pubkey;
|
const fromAccount = from.pubkey;
|
||||||
|
|
||||||
// create approval for transfer transactions
|
// create approval for transfer transactions
|
||||||
instructions.push(
|
approve(
|
||||||
Token.createApproveInstruction(TOKEN_PROGRAM_ID, fromAccount, authority, wallet.publicKey, [], amountLamports)
|
instructions,
|
||||||
|
cleanupInstructions,
|
||||||
|
fromAccount,
|
||||||
|
authority,
|
||||||
|
wallet.publicKey,
|
||||||
|
amountLamports
|
||||||
);
|
);
|
||||||
|
|
||||||
// get destination account
|
// get destination account
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {
|
||||||
LendingReserveParser,
|
LendingReserveParser,
|
||||||
} from "../../models";
|
} from "../../models";
|
||||||
import { TokenIcon } from "../TokenIcon";
|
import { TokenIcon } from "../TokenIcon";
|
||||||
import { Button, Card } from "antd";
|
import { Card } from "antd";
|
||||||
import { cache, ParsedAccount } from "../../contexts/accounts";
|
import { cache, ParsedAccount } from "../../contexts/accounts";
|
||||||
import { NumericInput } from "../Input/numeric";
|
import { NumericInput } from "../Input/numeric";
|
||||||
import { useConnection } from "../../contexts/connection";
|
import { useConnection } from "../../contexts/connection";
|
||||||
|
@ -21,6 +21,7 @@ import "./style.less";
|
||||||
import { LABELS } from "../../constants";
|
import { LABELS } from "../../constants";
|
||||||
import { ActionConfirmation } from "./../ActionConfirmation";
|
import { ActionConfirmation } from "./../ActionConfirmation";
|
||||||
import { BackButton } from "./../BackButton";
|
import { BackButton } from "./../BackButton";
|
||||||
|
import { ConnectButton } from "../ConnectButton";
|
||||||
|
|
||||||
export const BorrowInput = (props: {
|
export const BorrowInput = (props: {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
@ -150,14 +151,14 @@ export const BorrowInput = (props: {
|
||||||
/>
|
/>
|
||||||
<div>{name}</div>
|
<div>{name}</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<ConnectButton
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={onBorrow}
|
onClick={onBorrow}
|
||||||
loading={pendingTx}
|
loading={pendingTx}
|
||||||
disabled={fromAccounts.length === 0}
|
disabled={fromAccounts.length === 0}
|
||||||
>
|
>
|
||||||
{LABELS.BORROW_ACTION}
|
{fromAccounts.length === 0 ? LABELS.NO_DEPOSITS : LABELS.BORROW_ACTION}
|
||||||
</Button>
|
</ConnectButton>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,14 +1,44 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import { useLendingReserves } from '../../hooks';
|
import { useLendingReserves, UserDeposit, useUserDeposits } from "../../hooks";
|
||||||
import { LendingMarket, LendingReserve } from '../../models';
|
import { LendingMarket, LendingReserve } from "../../models";
|
||||||
import { TokenIcon } from '../TokenIcon';
|
import { TokenIcon } from "../TokenIcon";
|
||||||
import { getTokenName } from '../../utils/utils';
|
import { formatAmount, getTokenName } from "../../utils/utils";
|
||||||
import { Select } from 'antd';
|
import { Select } from "antd";
|
||||||
import { useConnectionConfig } from '../../contexts/connection';
|
import { useConnectionConfig } from "../../contexts/connection";
|
||||||
import { cache, ParsedAccount } from '../../contexts/accounts';
|
import { cache, ParsedAccount } from "../../contexts/accounts";
|
||||||
|
|
||||||
const { Option } = Select;
|
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"
|
||||||
|
>
|
||||||
|
{" "}
|
||||||
|
{userDeposit ? formatAmount(userDeposit.info.amount) : '--'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const CollateralSelector = (props: {
|
export const CollateralSelector = (props: {
|
||||||
reserve: LendingReserve;
|
reserve: LendingReserve;
|
||||||
collateralReserve?: string;
|
collateralReserve?: string;
|
||||||
|
@ -17,11 +47,17 @@ export const CollateralSelector = (props: {
|
||||||
}) => {
|
}) => {
|
||||||
const { reserveAccounts } = useLendingReserves();
|
const { reserveAccounts } = useLendingReserves();
|
||||||
const { tokenMap } = useConnectionConfig();
|
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;
|
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 (
|
return (
|
||||||
<Select
|
<Select
|
||||||
|
@ -46,14 +82,14 @@ export const CollateralSelector = (props: {
|
||||||
const mint = reserve.info.liquidityMint.toBase58();
|
const mint = reserve.info.liquidityMint.toBase58();
|
||||||
const address = reserve.pubkey.toBase58();
|
const address = reserve.pubkey.toBase58();
|
||||||
const name = getTokenName(tokenMap, mint);
|
const name = getTokenName(tokenMap, mint);
|
||||||
return (
|
|
||||||
<Option key={address} value={address} name={name} title={address}>
|
return <Option key={address} value={address} name={name} title={address}>
|
||||||
<div key={address} style={{ display: 'flex', alignItems: 'center' }}>
|
<CollateralItem
|
||||||
<TokenIcon mintAddress={mint} />
|
reserve={reserve}
|
||||||
{name}
|
userDeposit={userDeposits.find(dep => dep.reserve.pubkey.toBase58() === address)}
|
||||||
</div>
|
mint={mint}
|
||||||
</Option>
|
name={name} />
|
||||||
);
|
</Option>
|
||||||
})}
|
})}
|
||||||
</Select>
|
</Select>
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,9 +8,9 @@ export const ConnectButton = (
|
||||||
props: ButtonProps & React.RefAttributes<HTMLElement>
|
props: ButtonProps & React.RefAttributes<HTMLElement>
|
||||||
) => {
|
) => {
|
||||||
const { wallet, connected } = useWallet();
|
const { wallet, connected } = useWallet();
|
||||||
const { onClick, children, ...rest } = props;
|
const { onClick, children, disabled, ...rest } = props;
|
||||||
return (
|
return (
|
||||||
<Button {...rest} onClick={connected ? onClick : wallet.connect}>
|
<Button {...rest} onClick={connected ? onClick : wallet.connect} disabled={connected && disabled}>
|
||||||
{connected ? props.children : LABELS.CONNECT_LABEL}
|
{connected ? props.children : LABELS.CONNECT_LABEL}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
} from "../../hooks";
|
} from "../../hooks";
|
||||||
import { LendingReserve } from "../../models/lending";
|
import { LendingReserve } from "../../models/lending";
|
||||||
import { TokenIcon } from "../TokenIcon";
|
import { TokenIcon } from "../TokenIcon";
|
||||||
import { Button, Card, Slider } from "antd";
|
import { Card, Slider } from "antd";
|
||||||
import { NumericInput } from "../Input/numeric";
|
import { NumericInput } from "../Input/numeric";
|
||||||
import { useConnection } from "../../contexts/connection";
|
import { useConnection } from "../../contexts/connection";
|
||||||
import { useWallet } from "../../contexts/wallet";
|
import { useWallet } from "../../contexts/wallet";
|
||||||
|
@ -16,6 +16,7 @@ import { PublicKey } from "@solana/web3.js";
|
||||||
import "./style.less";
|
import "./style.less";
|
||||||
import { ActionConfirmation } from "./../ActionConfirmation";
|
import { ActionConfirmation } from "./../ActionConfirmation";
|
||||||
import { LABELS, marks } from "../../constants";
|
import { LABELS, marks } from "../../constants";
|
||||||
|
import { ConnectButton } from "../ConnectButton";
|
||||||
|
|
||||||
export const DepositInput = (props: {
|
export const DepositInput = (props: {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
@ -126,14 +127,14 @@ export const DepositInput = (props: {
|
||||||
|
|
||||||
<Slider marks={marks} value={pct} onChange={setPct} />
|
<Slider marks={marks} value={pct} onChange={setPct} />
|
||||||
|
|
||||||
<Button
|
<ConnectButton
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={onDeposit}
|
onClick={onDeposit}
|
||||||
loading={pendingTx}
|
loading={pendingTx}
|
||||||
disabled={fromAccounts.length === 0}
|
disabled={fromAccounts.length === 0}
|
||||||
>
|
>
|
||||||
{LABELS.DEPOSIT_ACTION}
|
{LABELS.DEPOSIT_ACTION}
|
||||||
</Button>
|
</ConnectButton>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
} from "../../hooks";
|
} from "../../hooks";
|
||||||
import { LendingReserve } from "../../models";
|
import { LendingReserve } from "../../models";
|
||||||
import { TokenIcon } from "../TokenIcon";
|
import { TokenIcon } from "../TokenIcon";
|
||||||
import { Button, Card, Slider } from "antd";
|
import { Card, Slider } from "antd";
|
||||||
import { ParsedAccount, useMint } from "../../contexts/accounts";
|
import { ParsedAccount, useMint } from "../../contexts/accounts";
|
||||||
import { NumericInput } from "../Input/numeric";
|
import { NumericInput } from "../Input/numeric";
|
||||||
import { useConnection } from "../../contexts/connection";
|
import { useConnection } from "../../contexts/connection";
|
||||||
|
@ -21,6 +21,7 @@ import { LABELS, marks } from "../../constants";
|
||||||
import { ActionConfirmation } from "./../ActionConfirmation";
|
import { ActionConfirmation } from "./../ActionConfirmation";
|
||||||
import { fromLamports, wadToLamports } from "../../utils/utils";
|
import { fromLamports, wadToLamports } from "../../utils/utils";
|
||||||
import { notify } from "../../utils/notifications";
|
import { notify } from "../../utils/notifications";
|
||||||
|
import { ConnectButton } from "../ConnectButton";
|
||||||
|
|
||||||
export const RepayInput = (props: {
|
export const RepayInput = (props: {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
@ -169,14 +170,14 @@ export const RepayInput = (props: {
|
||||||
disabled={true}
|
disabled={true}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button
|
<ConnectButton
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={onRepay}
|
onClick={onRepay}
|
||||||
loading={pendingTx}
|
loading={pendingTx}
|
||||||
disabled={fromAccounts.length === 0}
|
disabled={fromAccounts.length === 0}
|
||||||
>
|
>
|
||||||
{LABELS.REPAY_ACTION}
|
{LABELS.REPAY_ACTION}
|
||||||
</Button>
|
</ConnectButton>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { LendingReserve } from "../../models/lending";
|
import { calculateDepositAPY, LendingReserve } from "../../models/lending";
|
||||||
import { Card } from "antd";
|
import { Card, Col, Row, Statistic } from "antd";
|
||||||
import { PublicKey } from "@solana/web3.js";
|
import { PublicKey } from "@solana/web3.js";
|
||||||
import "./style.less";
|
import "./style.less";
|
||||||
import { LABELS } from "../../constants";
|
import { GUTTER, LABELS } from "../../constants";
|
||||||
import { ReserveUtilizationChart } from "./../../components/ReserveUtilizationChart";
|
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: {
|
export const ReserveStatus = (props: {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
@ -17,20 +22,113 @@ export const ReserveStatus = (props: {
|
||||||
alignItems: "center",
|
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 (
|
return (
|
||||||
<Card
|
<Card
|
||||||
className={props.className}
|
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}
|
bodyStyle={bodyStyle}
|
||||||
>
|
>
|
||||||
<div
|
<div className="flexColumn">
|
||||||
style={{
|
<Row gutter={GUTTER}>
|
||||||
display: "flex",
|
<Col span={12}>
|
||||||
flexDirection: "column",
|
<Statistic
|
||||||
justifyContent: "space-around",
|
title="Available Liquidity"
|
||||||
}}
|
value={availableLiquidity}
|
||||||
>
|
valueRender={(node) => <div>
|
||||||
<ReserveUtilizationChart reserve={props.reserve} />
|
{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>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,9 +3,11 @@ import { LendingReserve } from "../../models/lending";
|
||||||
import { fromLamports, wadToLamports } from "../../utils/utils";
|
import { fromLamports, wadToLamports } from "../../utils/utils";
|
||||||
import { useMint } from "../../contexts/accounts";
|
import { useMint } from "../../contexts/accounts";
|
||||||
import { WaterWave } from "./../WaterWave";
|
import { WaterWave } from "./../WaterWave";
|
||||||
|
import { Statistic } from "antd";
|
||||||
|
|
||||||
export const ReserveUtilizationChart = (props: { reserve: LendingReserve }) => {
|
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(
|
const availableLiquidity = fromLamports(
|
||||||
props.reserve.availableLiquidity.toNumber(),
|
props.reserve.availableLiquidity.toNumber(),
|
||||||
liquidityMint
|
liquidityMint
|
||||||
|
@ -20,10 +22,20 @@ export const ReserveUtilizationChart = (props: { reserve: LendingReserve }) => {
|
||||||
[props.reserve, liquidityMint]
|
[props.reserve, liquidityMint]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const percent = (availableLiquidity * 100) / (availableLiquidity + totalBorrows);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WaterWave
|
<WaterWave
|
||||||
style={{ height: 300 }}
|
style={{ height: 300 }}
|
||||||
percent={(availableLiquidity * 100) / (availableLiquidity + totalBorrows)}
|
showPercent={false}
|
||||||
|
title={
|
||||||
|
<Statistic
|
||||||
|
title="Utilization"
|
||||||
|
value={percent}
|
||||||
|
suffix="%"
|
||||||
|
precision={2} />
|
||||||
|
}
|
||||||
|
percent={percent}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,19 +7,22 @@ import { PublicKey } from "@solana/web3.js";
|
||||||
export const TokenIcon = (props: {
|
export const TokenIcon = (props: {
|
||||||
mintAddress?: string | PublicKey;
|
mintAddress?: string | PublicKey;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
|
size?: number;
|
||||||
className?: string;
|
className?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const { tokenMap } = useConnectionConfig();
|
const { tokenMap } = useConnectionConfig();
|
||||||
const icon = getTokenIcon(tokenMap, props.mintAddress);
|
const icon = getTokenIcon(tokenMap, props.mintAddress);
|
||||||
|
|
||||||
|
const size = props.size || 20;
|
||||||
|
|
||||||
if (icon) {
|
if (icon) {
|
||||||
return (
|
return (
|
||||||
<img
|
<img
|
||||||
alt="Token icon"
|
alt="Token icon"
|
||||||
className={props.className}
|
className={props.className}
|
||||||
key={icon}
|
key={icon}
|
||||||
width={props.style?.width || "20"}
|
width={props.style?.width || size.toString()}
|
||||||
height={props.style?.height || "20"}
|
height={props.style?.height || size.toString()}
|
||||||
src={icon}
|
src={icon}
|
||||||
style={{
|
style={{
|
||||||
marginRight: "0.5rem",
|
marginRight: "0.5rem",
|
||||||
|
@ -38,8 +41,8 @@ export const TokenIcon = (props: {
|
||||||
address={props.mintAddress}
|
address={props.mintAddress}
|
||||||
style={{
|
style={{
|
||||||
marginRight: "0.5rem",
|
marginRight: "0.5rem",
|
||||||
width: 20,
|
width: size,
|
||||||
height: 20,
|
height: size,
|
||||||
marginTop: 2,
|
marginTop: 2,
|
||||||
...props.style,
|
...props.style,
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {
|
||||||
useTokenName,
|
useTokenName,
|
||||||
useUserBalance,
|
useUserBalance,
|
||||||
useBorrowedAmount,
|
useBorrowedAmount,
|
||||||
|
useBorrowingPower,
|
||||||
} from "./../../hooks";
|
} from "./../../hooks";
|
||||||
import { LendingReserve } from "../../models/lending";
|
import { LendingReserve } from "../../models/lending";
|
||||||
import { formatNumber } from "../../utils/utils";
|
import { formatNumber } from "../../utils/utils";
|
||||||
|
@ -29,9 +30,7 @@ export const UserLendingCard = (props: {
|
||||||
);
|
);
|
||||||
|
|
||||||
const { borrowed: totalBorrowed, borrowedInUSD, ltv, health } = useBorrowedAmount(address);
|
const { borrowed: totalBorrowed, borrowedInUSD, ltv, health } = useBorrowedAmount(address);
|
||||||
|
const { totalInQuote: borrowingPowerInUSD, borrowingPower } = useBorrowingPower(address);
|
||||||
// TODO: calculate
|
|
||||||
const available = 0; // use all available deposits and convert using market rate
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
|
@ -82,7 +81,10 @@ export const UserLendingCard = (props: {
|
||||||
Available to you:
|
Available to you:
|
||||||
</Text>
|
</Text>
|
||||||
<div className="card-cell ">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -4,13 +4,15 @@
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
transform-origin: left;
|
transform-origin: left;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
.text {
|
.text {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 5px;
|
|
||||||
top: calc(50% - 15px);
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
span {
|
.title {
|
||||||
color: @text-color-secondary;
|
color: @text-color-secondary;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 22px;
|
line-height: 22px;
|
||||||
|
|
|
@ -5,7 +5,7 @@ export const WaterWave = (props: any) => {
|
||||||
const node = useRef<HTMLCanvasElement>();
|
const node = useRef<HTMLCanvasElement>();
|
||||||
const root = useRef<HTMLDivElement>();
|
const root = useRef<HTMLDivElement>();
|
||||||
const [radio, setRadio] = useState(1);
|
const [radio, setRadio] = useState(1);
|
||||||
const { percent, title, style, color } = props;
|
const { percent, title, style, color, showPercent } = props;
|
||||||
const { height } = style;
|
const { height } = style;
|
||||||
|
|
||||||
const resize = useCallback(() => {
|
const resize = useCallback(() => {
|
||||||
|
@ -54,8 +54,8 @@ export const WaterWave = (props: any) => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="text" style={{ width: height }}>
|
<div className="text" style={{ width: height }}>
|
||||||
{title && <span>{title}</span>}
|
{title}
|
||||||
<h4>{percent.toFixed(2)}%</h4>
|
<h4>{showPercent && `${percent.toFixed(2)}%`}</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
} from "../../hooks";
|
} from "../../hooks";
|
||||||
import { LendingReserve } from "../../models/lending";
|
import { LendingReserve } from "../../models/lending";
|
||||||
import { TokenIcon } from "../TokenIcon";
|
import { TokenIcon } from "../TokenIcon";
|
||||||
import { Button, Card, Slider } from "antd";
|
import { Card, Slider } from "antd";
|
||||||
import { NumericInput } from "../Input/numeric";
|
import { NumericInput } from "../Input/numeric";
|
||||||
import { useConnection } from "../../contexts/connection";
|
import { useConnection } from "../../contexts/connection";
|
||||||
import { useWallet } from "../../contexts/wallet";
|
import { useWallet } from "../../contexts/wallet";
|
||||||
|
@ -17,6 +17,7 @@ import { PublicKey } from "@solana/web3.js";
|
||||||
import "./style.less";
|
import "./style.less";
|
||||||
import { LABELS, marks } from "../../constants";
|
import { LABELS, marks } from "../../constants";
|
||||||
import { ActionConfirmation } from "./../ActionConfirmation";
|
import { ActionConfirmation } from "./../ActionConfirmation";
|
||||||
|
import { ConnectButton } from "../ConnectButton";
|
||||||
|
|
||||||
export const WithdrawInput = (props: {
|
export const WithdrawInput = (props: {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
@ -134,14 +135,14 @@ export const WithdrawInput = (props: {
|
||||||
|
|
||||||
<Slider marks={marks} value={pct} onChange={setPct} />
|
<Slider marks={marks} value={pct} onChange={setPct} />
|
||||||
|
|
||||||
<Button
|
<ConnectButton
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={onWithdraw}
|
onClick={onWithdraw}
|
||||||
loading={pendingTx}
|
loading={pendingTx}
|
||||||
disabled={fromAccounts.length === 0}
|
disabled={fromAccounts.length === 0}
|
||||||
>
|
>
|
||||||
{LABELS.WITHDRAW_ACTION}
|
{fromAccounts.length === 0 ? LABELS.NO_DEPOSITS : LABELS.WITHDRAW_ACTION}
|
||||||
</Button>
|
</ConnectButton>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
export * from './labels';
|
export * from "./labels";
|
||||||
export * from './math';
|
export * from "./math";
|
||||||
export * from './marks';
|
export * from "./marks";
|
||||||
|
export * from "./style";
|
||||||
|
|
|
@ -1,57 +1,62 @@
|
||||||
export const LABELS = {
|
export const LABELS = {
|
||||||
CONNECT_LABEL: 'Connect Wallet',
|
CONNECT_LABEL: "Connect Wallet",
|
||||||
GIVE_SOL: 'Give me SOL',
|
BORROWING_POWER_USED: "Borrowing Power Used",
|
||||||
FAUCET_INFO: 'This faucet will help you fund your accounts outside of Solana main network.',
|
BORROWING_POWER_VALUE: "Borrowing Power",
|
||||||
ACCOUNT_FUNDED: 'Account funded.',
|
BORROWED_VALUE: "You borrowed",
|
||||||
REPAY_QUESTION: 'How much would you like to repay?',
|
GIVE_SOL: "Give me SOL",
|
||||||
REPAY_ACTION: 'Repay',
|
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. ",
|
||||||
RESERVE_STATUS_TITLE: 'Reserve Status & Configuration',
|
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:
|
AUDIT_WARNING:
|
||||||
'Oyster is an unaudited software project used for internal purposes at the Solana Foundation. This app is not for public use.',
|
'Oyster is an unaudited software project used for internal purposes at the Solana Foundation. This app is not for public use.',
|
||||||
FOOTER:
|
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.',
|
'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_HOME: "Home",
|
||||||
MENU_DASHBOARD: 'Dashboard',
|
MENU_DASHBOARD: "Dashboard",
|
||||||
DASHBOARD_INFO: 'Connect to a wallet to view your deposits/loans.',
|
DASHBOARD_INFO: "Connect to a wallet to view your deposits/loans.",
|
||||||
NO_LOANS_NO_DEPOSITS: 'No loans or deposits.',
|
NO_LOANS_NO_DEPOSITS: "No loans or deposits.",
|
||||||
MENU_DEPOSIT: 'Deposit',
|
MENU_DEPOSIT: "Deposit",
|
||||||
MENU_BORROW: 'Borrow',
|
MENU_BORROW: "Borrow",
|
||||||
MENU_LIQUIDATE: 'Liquidate',
|
MENU_LIQUIDATE: "Liquidate",
|
||||||
MENU_FAUCET: 'Faucet',
|
MENU_FAUCET: "Faucet",
|
||||||
MARGIN_TRADING: 'Margin Trading',
|
APP_TITLE: "Oyster Lending",
|
||||||
APP_TITLE: 'Oyster Lending',
|
CONNECT_BUTTON: "Connect",
|
||||||
CONNECT_BUTTON: 'Connect',
|
WALLET_TOOLTIP: "Wallet public key",
|
||||||
WALLET_TOOLTIP: 'Wallet public key',
|
SETTINGS_TOOLTIP: "Settings",
|
||||||
SETTINGS_TOOLTIP: 'Settings',
|
SELECT_COLLATERAL: "Select collateral",
|
||||||
SELECT_COLLATERAL: 'Select collateral',
|
COLLATERAL: "Collateral",
|
||||||
COLLATERAL: 'Collateral',
|
BORROW_QUESTION: "How much would you like to borrow?",
|
||||||
BORROW_QUESTION: 'How much would you like to borrow?',
|
BORROW_ACTION: "Borrow",
|
||||||
BORROW_ACTION: 'Borrow',
|
NO_DEPOSITS: "No collateral",
|
||||||
LIQUIDATE_ACTION: 'Liquidate',
|
LIQUIDATE_ACTION: "Liquidate",
|
||||||
LIQUIDATE_NO_LOANS: 'There are no loans to liquidate.',
|
LIQUIDATE_NO_LOANS: "There are no loans to liquidate.",
|
||||||
TABLE_TITLE_ASSET: 'Asset',
|
TABLE_TITLE_ASSET: "Asset",
|
||||||
TABLE_TITLE_YOUR_LOAN_BALANCE: 'Loan balance',
|
TABLE_TITLE_YOUR_LOAN_BALANCE: "Loan balance",
|
||||||
TABLE_TITLE_LOAN_BALANCE: 'Loan balance',
|
TABLE_TITLE_LOAN_BALANCE: "Loan balance",
|
||||||
TABLE_TITLE_COLLATERAL_BALANCE: 'Collateral',
|
TABLE_TITLE_COLLATERAL_BALANCE: "Collateral",
|
||||||
TABLE_TITLE_DEPOSIT_BALANCE: 'Your deposits',
|
TABLE_TITLE_DEPOSIT_BALANCE: "Your deposits",
|
||||||
TABLE_TITLE_APY: 'APY',
|
TABLE_TITLE_APY: "APY",
|
||||||
TABLE_TITLE_LTV: 'LTV',
|
TABLE_TITLE_LTV: "LTV",
|
||||||
TABLE_TITLE_HEALTH: 'Health Factor',
|
TABLE_TITLE_HEALTH: "Health Factor",
|
||||||
TABLE_TITLE_BORROW_APY: 'Borrow APY',
|
TABLE_TITLE_BORROW_APY: "Borrow APY",
|
||||||
TABLE_TITLE_DEPOSIT_APY: 'Deposit APY',
|
TABLE_TITLE_DEPOSIT_APY: "Deposit APY",
|
||||||
TABLE_TITLE_TOTAL_BORROWED: 'Total Borrowed',
|
TABLE_TITLE_TOTAL_BORROWED: "Total Borrowed",
|
||||||
TABLE_TITLE_MARKET_SIZE: 'Market Size',
|
TABLE_TITLE_MARKET_SIZE: "Market Size",
|
||||||
TABLE_TITLE_ACTION: 'Action',
|
TABLE_TITLE_ACTION: "Action",
|
||||||
TABLE_TITLE_MAX_BORROW: 'Available for you',
|
TABLE_TITLE_MAX_BORROW: "Available for you",
|
||||||
DASHBOARD_TITLE_LOANS: 'Loans',
|
DASHBOARD_TITLE_LOANS: "Loans",
|
||||||
DASHBOARD_TITLE_DEPOSITS: 'Deposits',
|
DASHBOARD_TITLE_DEPOSITS: "Deposits",
|
||||||
DEPOSIT_QUESTION: 'How much would you like to deposit?',
|
DEPOSIT_QUESTION: "How much would you like to deposit?",
|
||||||
WITHDRAW_ACTION: 'Withdraw',
|
WITHDRAW_ACTION: "Withdraw",
|
||||||
WITHDRAW_QUESTION: 'How much would you like to withdraw?',
|
WITHDRAW_QUESTION: "How much would you like to withdraw?",
|
||||||
DASHBOARD_ACTION: 'Go to dashboard',
|
DASHBOARD_ACTION: "Go to dashboard",
|
||||||
GO_BACK_ACTION: 'Go back',
|
GO_BACK_ACTION: "Go back",
|
||||||
DEPOSIT_ACTION: 'Deposit',
|
DEPOSIT_ACTION: "Deposit",
|
||||||
TOTAL_TITLE: 'Total',
|
TOTAL_TITLE: "Total",
|
||||||
TRADING_TABLE_TITLE_MY_COLLATERAL: 'Chosen Collateral',
|
TRADING_TABLE_TITLE_MY_COLLATERAL: 'Chosen Collateral',
|
||||||
TRADING_TABLE_TITLE_DESIRED_ASSET: 'Desired Asset',
|
TRADING_TABLE_TITLE_DESIRED_ASSET: 'Desired Asset',
|
||||||
TRADING_TABLE_TITLE_MULTIPLIER: 'Leverage',
|
TRADING_TABLE_TITLE_MULTIPLIER: 'Leverage',
|
||||||
|
|
|
@ -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,
|
||||||
|
};
|
|
@ -55,7 +55,7 @@ export function useBorrowedAmount(address?: string | PublicKey) {
|
||||||
borrowedInUSD: 0,
|
borrowedInUSD: 0,
|
||||||
colateralInUSD: 0,
|
colateralInUSD: 0,
|
||||||
ltv: 0,
|
ltv: 0,
|
||||||
health: 0,
|
health: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let liquidationThreshold = 0;
|
let liquidationThreshold = 0;
|
||||||
|
@ -88,7 +88,6 @@ export function useBorrowedAmount(address?: string | PublicKey) {
|
||||||
result.health = Number.isFinite(result.health) ? result.health : 0;
|
result.health = Number.isFinite(result.health) ? result.health : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
setBorrowedInfo(result);
|
setBorrowedInfo(result);
|
||||||
})();
|
})();
|
||||||
}, [connection, userObligationsByReserve, setBorrowedInfo]);
|
}, [connection, userObligationsByReserve, setBorrowedInfo]);
|
||||||
|
|
|
@ -2,29 +2,60 @@ import { PublicKey } from "@solana/web3.js";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { useMidPriceInUSD } from "../contexts/market";
|
import { useMidPriceInUSD } from "../contexts/market";
|
||||||
import { useLendingMarket } from "./useLendingMarket";
|
import { useLendingMarket } from "./useLendingMarket";
|
||||||
import { useLendingReserve } from "./useLendingReserves";
|
import { getLendingReserves, useLendingReserve } from "./useLendingReserves";
|
||||||
import { useUserDeposits } from "./useUserDeposits";
|
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 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 reserve = useLendingReserve(key);
|
||||||
|
|
||||||
const liquidityMint = reserve?.info.liquidityMint;
|
const liquidityMint = reserve?.info.liquidityMint;
|
||||||
const market = useLendingMarket(liquidityMint);
|
const liquidityMintAddress = liquidityMint?.toBase58();
|
||||||
const price = useMidPriceInUSD(liquidityMint.toBase58()).price;
|
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
|
// amounts already expressed as quite mint
|
||||||
if(liquidityMint.toBase58() === market?.info.quoteMint?.toBase58()) {
|
if (liquidityMintAddress === quoteMintAddess) {
|
||||||
return {
|
return {
|
||||||
borrowingPower: totalInQuote,
|
borrowingPower: totalInQuote,
|
||||||
totalInQuote,
|
totalInQuote,
|
||||||
|
utilization,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
borrowingPower: totalInQuote / price,
|
borrowingPower: totalInQuote / price,
|
||||||
totalInQuote,
|
totalInQuote,
|
||||||
|
utilization
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,7 @@ export function useUserCollateralBalance(
|
||||||
balanceInUSD,
|
balanceInUSD,
|
||||||
mint: reserve?.collateralMint,
|
mint: reserve?.collateralMint,
|
||||||
accounts,
|
accounts,
|
||||||
|
hasBalance: accounts.length > 0 && balance > 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export function calculateCollateralBalance(
|
export function calculateCollateralBalance(
|
||||||
|
|
|
@ -3,11 +3,11 @@ import { useEffect, useMemo, useState } from "react";
|
||||||
import { LendingReserve, LendingReserveParser } from "../models/lending";
|
import { LendingReserve, LendingReserveParser } from "../models/lending";
|
||||||
import { cache, ParsedAccount } from "./../contexts/accounts";
|
import { cache, ParsedAccount } from "./../contexts/accounts";
|
||||||
|
|
||||||
const getLendingReserves = () => {
|
export const getLendingReserves = () => {
|
||||||
return cache
|
return cache
|
||||||
.byParser(LendingReserveParser)
|
.byParser(LendingReserveParser)
|
||||||
.map((id) => cache.get(id))
|
.map((id) => cache.get(id))
|
||||||
.filter((acc) => acc !== undefined) as any[];
|
.filter((acc) => acc !== undefined) as ParsedAccount<LendingReserve>[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useLendingReserves() {
|
export function useLendingReserves() {
|
||||||
|
|
|
@ -25,7 +25,7 @@ export const useSliderInput = (
|
||||||
pct,
|
pct,
|
||||||
setPct: useCallback(
|
setPct: useCallback(
|
||||||
(val: number) => {
|
(val: number) => {
|
||||||
setType(InputType.AbsoluteValue);
|
setType(InputType.Percent);
|
||||||
setPct(val);
|
setPct(val);
|
||||||
setValue(convert(val) as string);
|
setValue(convert(val) as string);
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,7 +5,8 @@ import { useMarkets } from "../contexts/market";
|
||||||
import { fromLamports } from "../utils/utils";
|
import { fromLamports } from "../utils/utils";
|
||||||
import { useUserAccounts } from "./useUserAccounts";
|
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 { userAccounts } = useUserAccounts();
|
||||||
const [balanceInUSD, setBalanceInUSD] = useState(0);
|
const [balanceInUSD, setBalanceInUSD] = useState(0);
|
||||||
const { marketEmitter, midPriceInUSD } = useMarkets();
|
const { marketEmitter, midPriceInUSD } = useMarkets();
|
||||||
|
@ -15,7 +16,7 @@ export function useUserBalance(mint?: PublicKey, account?: PublicKey) {
|
||||||
return userAccounts
|
return userAccounts
|
||||||
.filter(
|
.filter(
|
||||||
(acc) =>
|
(acc) =>
|
||||||
mint?.equals(acc.info.mint) &&
|
mint === acc.info.mint.toBase58() &&
|
||||||
(!account || account.equals(acc.pubkey))
|
(!account || account.equals(acc.pubkey))
|
||||||
)
|
)
|
||||||
.sort((a, b) => b.info.amount.sub(a.info.amount).toNumber());
|
.sort((a, b) => b.info.amount.sub(a.info.amount).toNumber());
|
||||||
|
@ -32,7 +33,7 @@ export function useUserBalance(mint?: PublicKey, account?: PublicKey) {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const updateBalance = () => {
|
const updateBalance = () => {
|
||||||
setBalanceInUSD(balance * midPriceInUSD(mint?.toBase58() || ''));
|
setBalanceInUSD(balance * midPriceInUSD(mint || ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
const dispose = marketEmitter.onMarket((args) => {
|
const dispose = marketEmitter.onMarket((args) => {
|
||||||
|
@ -51,5 +52,6 @@ export function useUserBalance(mint?: PublicKey, account?: PublicKey) {
|
||||||
balanceLamports,
|
balanceLamports,
|
||||||
balanceInUSD,
|
balanceInUSD,
|
||||||
accounts,
|
accounts,
|
||||||
|
hasBalance: accounts.length > 0 && balance > 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
export interface TokenAccount {
|
||||||
pubkey: PublicKey;
|
pubkey: PublicKey;
|
||||||
account: AccountInfo<Buffer>;
|
account: AccountInfo<Buffer>;
|
||||||
info: TokenAccountInfo;
|
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,
|
||||||
|
[]),
|
||||||
|
);
|
||||||
|
}
|
|
@ -133,7 +133,7 @@ const abbreviateNumber = (number: number, precision: number) => {
|
||||||
return scaled.toFixed(precision) + suffix;
|
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);
|
abbr ? abbreviateNumber(val, precision) : val.toFixed(precision);
|
||||||
|
|
||||||
export function formatTokenAmount(
|
export function formatTokenAmount(
|
||||||
|
@ -149,7 +149,11 @@ export function formatTokenAmount(
|
||||||
return '';
|
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', {
|
export const formatUSD = new Intl.NumberFormat('en-US', {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useLendingReserve } from "../../hooks";
|
import { useBorrowingPower, useLendingReserve, useUserObligations } from "../../hooks";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import "./style.less";
|
import "./style.less";
|
||||||
|
|
||||||
|
@ -8,10 +8,16 @@ import {
|
||||||
SideReserveOverview,
|
SideReserveOverview,
|
||||||
SideReserveOverviewMode,
|
SideReserveOverviewMode,
|
||||||
} from "../../components/SideReserveOverview";
|
} from "../../components/SideReserveOverview";
|
||||||
|
import { Card, Col, Row, Statistic } from "antd";
|
||||||
|
import { BarChartStatistic } from "../../components/BarChartStatistic";
|
||||||
|
import { GUTTER, LABELS } from "../../constants";
|
||||||
|
|
||||||
export const BorrowReserveView = () => {
|
export const BorrowReserveView = () => {
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
const lendingReserve = useLendingReserve(id);
|
const lendingReserve = useLendingReserve(id);
|
||||||
|
const { userObligations, totalInQuote: loansValue } = useUserObligations();
|
||||||
|
|
||||||
|
const { totalInQuote: borrowingPower, utilization } = useBorrowingPower(id)
|
||||||
|
|
||||||
if (!lendingReserve) {
|
if (!lendingReserve) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -19,17 +25,63 @@ export const BorrowReserveView = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="borrow-reserve">
|
<div className="borrow-reserve">
|
||||||
<div className="borrow-reserve-container">
|
<Row gutter={GUTTER}>
|
||||||
<BorrowInput
|
<Col xs={24} xl={5}>
|
||||||
className="borrow-reserve-item borrow-reserve-item-left"
|
<Card>
|
||||||
reserve={lendingReserve}
|
<Statistic
|
||||||
/>
|
title={LABELS.BORROWED_VALUE}
|
||||||
<SideReserveOverview
|
value={loansValue}
|
||||||
className="borrow-reserve-item borrow-reserve-item-right"
|
precision={2}
|
||||||
reserve={lendingReserve}
|
prefix="$"
|
||||||
mode={SideReserveOverviewMode.Borrow}
|
/>
|
||||||
/>
|
</Card>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,10 +2,11 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.borrow-reserve-item {
|
.borrow-reserve-item {
|
||||||
margin: 4px;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.borrow-reserve-container {
|
.borrow-reserve-container {
|
||||||
|
@ -13,18 +14,3 @@
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
flex: 1;
|
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%;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Col, Row } from "antd";
|
import { Col, Row } from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { LABELS } from "../../constants";
|
import { GUTTER, LABELS } from "../../constants";
|
||||||
import { useWallet } from "../../contexts/wallet";
|
import { useWallet } from "../../contexts/wallet";
|
||||||
import { useUserDeposits, useUserObligations } from "./../../hooks";
|
import { useUserDeposits, useUserObligations } from "./../../hooks";
|
||||||
import { DashboardObligations } from "./obligation";
|
import { DashboardObligations } from "./obligation";
|
||||||
|
@ -28,7 +28,7 @@ export const DashboardView = () => {
|
||||||
{LABELS.NO_LOANS_NO_DEPOSITS}
|
{LABELS.NO_LOANS_NO_DEPOSITS}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{connected && <Row gutter={[16, { xs: 8, sm: 16, md: 16, lg: 16 }]} >
|
{connected && <Row gutter={GUTTER} >
|
||||||
{userDeposits.length >0 && (
|
{userDeposits.length >0 && (
|
||||||
<Col md={24} xl={12} span={24}>
|
<Col md={24} xl={12} span={24}>
|
||||||
<DashboardDeposits />
|
<DashboardDeposits />
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { MintInfo } from "@solana/spl-token";
|
import { MintInfo } from "@solana/spl-token";
|
||||||
import { Card, Col, Row, Statistic } from "antd";
|
import { Card, Col, Row, Statistic } from "antd";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { LABELS } from "../../constants";
|
import { GUTTER, LABELS } from "../../constants";
|
||||||
import { cache, ParsedAccount } from "../../contexts/accounts";
|
import { cache, ParsedAccount } from "../../contexts/accounts";
|
||||||
import { useConnectionConfig } from "../../contexts/connection";
|
import { useConnectionConfig } from "../../contexts/connection";
|
||||||
import { useMarkets } from "../../contexts/market";
|
import { useMarkets } from "../../contexts/market";
|
||||||
|
@ -44,14 +44,17 @@ export const HomeView = () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const price = midPriceInUSD(liquidityMint?.pubkey.toBase58());
|
||||||
|
|
||||||
let leaf = {
|
let leaf = {
|
||||||
key: item.pubkey.toBase58(),
|
key: item.pubkey.toBase58(),
|
||||||
marketSize: fromLamports(marketCapLamports, liquidityMint?.info) *
|
marketSize: fromLamports(marketCapLamports, liquidityMint?.info) *
|
||||||
midPriceInUSD(liquidityMint?.pubkey.toBase58()),
|
price,
|
||||||
borrowed: fromLamports(
|
borrowed: fromLamports(
|
||||||
wadToLamports(item.info?.borrowedLiquidityWad).toNumber(),
|
wadToLamports(item.info?.borrowedLiquidityWad).toNumber(),
|
||||||
liquidityMint.info
|
liquidityMint.info
|
||||||
),
|
) *
|
||||||
|
price,
|
||||||
name: getTokenName(tokenMap, item.info.liquidityMint.toBase58())
|
name: getTokenName(tokenMap, item.info.liquidityMint.toBase58())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +86,7 @@ export const HomeView = () => {
|
||||||
return (
|
return (
|
||||||
<div className="flexColumn">
|
<div className="flexColumn">
|
||||||
<Row
|
<Row
|
||||||
gutter={[16, { xs: 8, sm: 16, md: 16, lg: 16 }]}
|
gutter={GUTTER}
|
||||||
className="home-info-row" >
|
className="home-info-row" >
|
||||||
<Col xs={24} xl={5}>
|
<Col xs={24} xl={5}>
|
||||||
<Card>
|
<Card>
|
||||||
|
@ -128,16 +131,16 @@ export const HomeView = () => {
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<div className="home-item home-header">
|
<div className="home-item home-header">
|
||||||
<div>{LABELS.TABLE_TITLE_ASSET}</div>
|
<div>{LABELS.TABLE_TITLE_ASSET}</div>
|
||||||
<div>{LABELS.TABLE_TITLE_MARKET_SIZE}</div>
|
<div>{LABELS.TABLE_TITLE_MARKET_SIZE}</div>
|
||||||
<div>{LABELS.TABLE_TITLE_TOTAL_BORROWED}</div>
|
<div>{LABELS.TABLE_TITLE_TOTAL_BORROWED}</div>
|
||||||
<div>{LABELS.TABLE_TITLE_DEPOSIT_APY}</div>
|
<div>{LABELS.TABLE_TITLE_DEPOSIT_APY}</div>
|
||||||
<div>{LABELS.TABLE_TITLE_BORROW_APY}</div>
|
<div>{LABELS.TABLE_TITLE_BORROW_APY}</div>
|
||||||
</div>
|
</div>
|
||||||
{reserveAccounts.map((account) => (
|
{reserveAccounts.map((account) => (
|
||||||
<LendingReserveItem reserve={account.info} address={account.pubkey} item={totals.items.find(item => item.key === account.pubkey.toBase58())} />
|
<LendingReserveItem reserve={account.info} address={account.pubkey} item={totals.items.find(item => item.key === account.pubkey.toBase58())} />
|
||||||
))}
|
))}
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import React, { useMemo } from "react";
|
import React, { useMemo } from "react";
|
||||||
import { LABELS } from "../../constants";
|
import { GUTTER, LABELS } from "../../constants";
|
||||||
import { LiquidateItem } from "./item";
|
import { LiquidateItem } from "./item";
|
||||||
import { useEnrichedLendingObligations } from "./../../hooks";
|
import { useEnrichedLendingObligations } from "./../../hooks";
|
||||||
import "./style.less";
|
import "./style.less";
|
||||||
import { Card, Col, Row, Statistic } from "antd";
|
import { Card, Col, Row, Statistic, Typography } from "antd";
|
||||||
import { BarChartStatistic } from "../../components/BarChartStatistic";
|
import { BarChartStatistic } from "../../components/BarChartStatistic";
|
||||||
|
|
||||||
export const LiquidateView = () => {
|
export const LiquidateView = () => {
|
||||||
|
@ -30,9 +30,17 @@ export const LiquidateView = () => {
|
||||||
<div className="liquidate-info">{LABELS.LIQUIDATE_NO_LOANS}</div>
|
<div className="liquidate-info">{LABELS.LIQUIDATE_NO_LOANS}</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flexColumn">
|
<div className="flexColumn">
|
||||||
|
<Row gutter={GUTTER}>
|
||||||
|
<Col span={24}>
|
||||||
|
<Card>
|
||||||
|
<Typography>
|
||||||
|
{LABELS.LIQUIDATION_INFO}
|
||||||
|
</Typography>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row
|
<Row
|
||||||
gutter={[16, { xs: 8, sm: 16, md: 16, lg: 16 }]}
|
gutter={GUTTER}>
|
||||||
className="home-info-row" >
|
|
||||||
<Col xs={24} xl={5}>
|
<Col xs={24} xl={5}>
|
||||||
<Card>
|
<Card>
|
||||||
<Statistic
|
<Statistic
|
||||||
|
@ -73,23 +81,27 @@ export const LiquidateView = () => {
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Card >
|
<Row gutter={GUTTER}>
|
||||||
<div className="liquidate-item liquidate-header">
|
<Col span={24}>
|
||||||
<div>{LABELS.TABLE_TITLE_ASSET}</div>
|
<Card className="card-fill">
|
||||||
<div>{LABELS.TABLE_TITLE_LOAN_BALANCE}</div>
|
<div className="liquidate-item liquidate-header">
|
||||||
<div>{LABELS.TABLE_TITLE_COLLATERAL_BALANCE}</div>
|
<div>{LABELS.TABLE_TITLE_ASSET}</div>
|
||||||
<div>{LABELS.TABLE_TITLE_APY}</div>
|
<div>{LABELS.TABLE_TITLE_LOAN_BALANCE}</div>
|
||||||
<div>{LABELS.TABLE_TITLE_LTV}</div>
|
<div>{LABELS.TABLE_TITLE_COLLATERAL_BALANCE}</div>
|
||||||
<div>{LABELS.TABLE_TITLE_HEALTH}</div>
|
<div>{LABELS.TABLE_TITLE_APY}</div>
|
||||||
<div>{LABELS.TABLE_TITLE_ACTION}</div>
|
<div>{LABELS.TABLE_TITLE_LTV}</div>
|
||||||
</div>
|
<div>{LABELS.TABLE_TITLE_HEALTH}</div>
|
||||||
{atRisk.map((item) => (
|
<div>{LABELS.TABLE_TITLE_ACTION}</div>
|
||||||
<LiquidateItem
|
</div>
|
||||||
key={item.account.pubkey.toBase58()}
|
{atRisk.map((item) => (
|
||||||
item={item}
|
<LiquidateItem
|
||||||
></LiquidateItem>
|
key={item.account.pubkey.toBase58()}
|
||||||
))}
|
item={item}
|
||||||
</Card>
|
></LiquidateItem>
|
||||||
|
))}
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -44,4 +44,5 @@
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
|
@ -5,6 +5,8 @@ import "./style.less";
|
||||||
|
|
||||||
import { UserLendingCard } from "./../../components/UserLendingCard";
|
import { UserLendingCard } from "./../../components/UserLendingCard";
|
||||||
import { ReserveStatus } from "./../../components/ReserveStatus";
|
import { ReserveStatus } from "./../../components/ReserveStatus";
|
||||||
|
import { Col, Row } from "antd";
|
||||||
|
import { GUTTER } from "../../constants";
|
||||||
|
|
||||||
export const ReserveView = () => {
|
export const ReserveView = () => {
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
|
@ -16,19 +18,21 @@ export const ReserveView = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="reserve-overview">
|
<div className="flexColumn">
|
||||||
<div className="reserve-overview-container">
|
<Row gutter={GUTTER}>
|
||||||
<ReserveStatus
|
<Col sm={24} md={12} lg={14} xl={15} xxl={18}>
|
||||||
className="reserve-overview-item reserve-overview-item-left"
|
<ReserveStatus
|
||||||
reserve={reserve}
|
reserve={reserve}
|
||||||
address={lendingReserve.pubkey}
|
address={lendingReserve.pubkey} />
|
||||||
/>
|
</Col>
|
||||||
<UserLendingCard
|
<Col sm={24} md={12} lg={10} xl={9} xxl={6}>
|
||||||
className="reserve-overview-item reserve-overview-item-right"
|
<UserLendingCard
|
||||||
reserve={reserve}
|
className="user-lending-card"
|
||||||
address={lendingReserve.pubkey}
|
reserve={reserve}
|
||||||
/>
|
address={lendingReserve.pubkey}
|
||||||
</div>
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,27 +4,6 @@
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reserve-overview-item {
|
.user-lending-card {
|
||||||
margin: 4px;
|
height: 100%;
|
||||||
}
|
|
||||||
|
|
||||||
.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%;
|
|
||||||
}
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue