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
|
||||
|
||||
- [] 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
|
||||
|
|
46
src/App.less
46
src/App.less
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
@ -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"
|
||||
>
|
||||
{" "}
|
||||
{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>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
}}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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,
|
||||
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]);
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ export function useUserCollateralBalance(
|
|||
balanceInUSD,
|
||||
mint: reserve?.collateralMint,
|
||||
accounts,
|
||||
hasBalance: accounts.length > 0 && balance > 0,
|
||||
};
|
||||
}
|
||||
export function calculateCollateralBalance(
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
[]),
|
||||
);
|
||||
}
|
|
@ -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', {
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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%;
|
||||
}
|
||||
}
|
|
@ -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 />
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -44,4 +44,5 @@
|
|||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
flex: 1;
|
||||
overflow-x: hidden;
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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%;
|
||||
}
|
Loading…
Reference in New Issue