Merge branch 'main' into main

This commit is contained in:
Bartosz Lipinski 2020-12-15 12:20:02 -06:00 committed by GitHub
commit c57082969a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 149 additions and 48 deletions

View File

@ -34,16 +34,16 @@ export const BorrowInput = (props: {
const borrowReserve = props.reserve;
const [collateralReserveMint, setCollateralReserveMint] = useState<string>();
const [collateralReserveKey, setCollateralReserveKey] = useState<string>();
const collateralReserve = useMemo(() => {
const id: string =
cache
.byParser(LendingReserveParser)
.find((acc) => acc === collateralReserveMint) || "";
.find((acc) => acc === collateralReserveKey) || "";
return cache.get(id) as ParsedAccount<LendingReserve>;
}, [collateralReserveMint]);
}, [collateralReserveKey]);
const name = useTokenName(borrowReserve?.info.liquidityMint);
const { accounts: fromAccounts } = useUserBalance(
@ -51,7 +51,8 @@ export const BorrowInput = (props: {
);
const { userObligationsByReserve } = useUserObligationByReserve(
borrowReserve.pubkey
borrowReserve?.pubkey,
collateralReserve?.pubkey,
);
const onBorrow = useCallback(() => {
@ -74,6 +75,7 @@ export const BorrowInput = (props: {
borrowReserve,
collateralReserve,
// TODO: select exsisting obligations by collateral reserve
userObligationsByReserve.length > 0
? userObligationsByReserve[0].obligation
: undefined,
@ -126,8 +128,8 @@ export const BorrowInput = (props: {
<div className="borrow-input-title">{LABELS.SELECT_COLLATERAL}</div>
<CollateralSelector
reserve={borrowReserve.info}
mint={collateralReserveMint}
onMintChange={setCollateralReserveMint}
collateralReserve={collateralReserveKey}
onCollateralReserve={setCollateralReserveKey}
/>
<div className="borrow-input-title">{LABELS.BORROW_QUESTION}</div>

View File

@ -11,8 +11,9 @@ const { Option } = Select;
export const CollateralSelector = (props: {
reserve: LendingReserve;
mint?: string;
onMintChange: (id: string) => void;
collateralReserve?: string;
disabled?: boolean;
onCollateralReserve?: (id: string) => void;
}) => {
const { reserveAccounts } = useLendingReserves();
const { tokenMap } = useConnectionConfig();
@ -20,8 +21,8 @@ export const CollateralSelector = (props: {
const market = cache.get(props.reserve.lendingMarket) as ParsedAccount<
LendingMarket
>;
const onlyQuoteAllowed = !props.reserve.liquidityMint.equals(
market.info.quoteMint
const onlyQuoteAllowed = !props.reserve?.liquidityMint?.equals(
market?.info?.quoteMint
);
return (
@ -30,10 +31,12 @@ export const CollateralSelector = (props: {
showSearch
style={{ minWidth: 300 , margin: "5px 0px" }}
placeholder="Collateral"
value={props.mint}
value={props.collateralReserve}
disabled={props.disabled}
defaultValue={props.collateralReserve}
onChange={(item) => {
if (props.onMintChange) {
props.onMintChange(item);
if (props.onCollateralReserve) {
props.onCollateralReserve(item);
}
}}
filterOption={(input, option) =>

View File

@ -21,7 +21,8 @@ import { notify } from "../../utils/notifications";
export const RepayInput = (props: {
className?: string;
reserve: ParsedAccount<LendingReserve>;
borrowReserve: ParsedAccount<LendingReserve>;
collateralReserve?: ParsedAccount<LendingReserve>;
obligation: ParsedAccount<LendingObligation>;
}) => {
const connection = useConnection();
@ -29,7 +30,7 @@ export const RepayInput = (props: {
const [pendingTx, setPendingTx] = useState(false);
const [showConfirmation, setShowConfirmation] = useState(false);
const repayReserve = props.reserve;
const repayReserve = props.borrowReserve;
const obligation = props.obligation;
const liquidityMint = useMint(repayReserve.info.liquidityMint);
@ -39,17 +40,7 @@ export const RepayInput = (props: {
borrowAmountLamports,
liquidityMint
);
const [collateralReserveMint, setCollateralReserveMint] = useState<string>();
const collateralReserve = useMemo(() => {
const id: string =
cache
.byParser(LendingReserveParser)
.find((acc) => acc === collateralReserveMint) || "";
return cache.get(id) as ParsedAccount<LendingReserve>;
}, [collateralReserveMint]);
const collateralReserve = props.collateralReserve;
const name = useTokenName(repayReserve?.info.liquidityMint);
const { accounts: fromAccounts } = useUserBalance(
@ -173,11 +164,11 @@ export const RepayInput = (props: {
value={pct}
onChange={setPct}
/>
<div className="repay-input-title">{LABELS.SELECT_COLLATERAL}</div>
<div className="repay-input-title">{LABELS.COLLATERAL}</div>
<CollateralSelector
reserve={repayReserve.info}
mint={collateralReserveMint}
onMintChange={setCollateralReserveMint}
collateralReserve={collateralReserve?.pubkey.toBase58()}
disabled={true}
/>
<Button

View File

@ -24,6 +24,7 @@ export const LABELS = {
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",
@ -31,6 +32,7 @@ export const LABELS = {
TABLE_TITLE_ASSET: "Asset",
TABLE_TITLE_YOUR_LOAN_BALANCE: "Your loan balance",
TABLE_TITLE_LOAN_BALANCE: "Loan balance",
TABLE_TITLE_COLLATERAL_BALANCE: "Collateral",
TABLE_TITLE_DEPOSIT_BALANCE: "Your deposit balance",
TABLE_TITLE_APY: "APY",
TABLE_TITLE_LTV: "LTV",

View File

@ -9,6 +9,7 @@ import { useMemo } from "react";
import { EventEmitter } from "./../utils/eventEmitter";
import { DexMarketParser } from "./../models/dex";
import { LendingReserve } from "../models";
export interface MarketsContextState {
midPriceInUSD: (mint: string) => number;
@ -248,6 +249,55 @@ export const usePrecacheMarket = () => {
return context.precacheMarkets;
};
export const simulateMarketOrderFill = (amount: number, reserve: LendingReserve) => {
const marketInfo = cache.get(reserve?.dexMarket);
if (!marketInfo) {
return 0.0;
}
const decodedMarket = marketInfo.info;
const baseMintDecimals =
cache.get(decodedMarket.baseMint)?.info.decimals || 0;
const quoteMintDecimals =
cache.get(decodedMarket.quoteMint)?.info.decimals || 0;
const market = new Market(
decodedMarket,
baseMintDecimals,
quoteMintDecimals,
undefined,
decodedMarket.programId
);
const bids = cache.get(decodedMarket.bids)?.info;
const asks = cache.get(decodedMarket.asks)?.info;
if (bids && asks) {
const bidsBook = new Orderbook(market, bids.accountFlags, bids.slab);
const asksBook = new Orderbook(market, asks.accountFlags, asks.slab);
// TODO: pick side
let book = bidsBook;
let cost = 0;
let remaining = amount;
for (const level of book) {
let size = remaining > level.size ? level.size : level.size - remaining;
cost = cost + level.price * size;
remaining = remaining - size;
if(remaining <= 0) {
break;
}
}
return cost;
}
return 0;
}
const getMidPrice = (marketAddress?: string, mintAddress?: string) => {
const SERUM_TOKEN = TOKEN_MINTS.find(
(a) => a.address.toBase58() === mintAddress

View File

@ -2,15 +2,19 @@ import { useMemo } from "react";
import { useUserObligations } from "./useUserObligations";
import { PublicKey } from "@solana/web3.js";
export function useUserObligationByReserve(reserve?: string | PublicKey) {
export function useUserObligationByReserve(
borrowReserve?: string | PublicKey,
collateralReserve?: string | PublicKey) {
const { userObligations } = useUserObligations();
const userObligationsByReserve = useMemo(() => {
const id = typeof reserve === "string" ? reserve : reserve?.toBase58();
const borrowId = typeof borrowReserve === "string" ? borrowReserve : borrowReserve?.toBase58();
const collateralId = typeof collateralReserve === "string" ? collateralReserve : collateralReserve?.toBase58();
return userObligations.filter(
(item) => item.obligation.info.borrowReserve.toBase58() === id
(item) => item.obligation.info.borrowReserve.toBase58() === borrowId &&
item.obligation.info.collateralReserve.toBase58() === collateralId
);
}, [reserve, userObligations]);
}, [borrowReserve, collateralReserve, userObligations]);
return {
userObligationsByReserve,

View File

@ -47,7 +47,7 @@ export const liquidateInstruction = (
dataLayout.encode(
{
instruction: LendingInstruction.LiquidateObligation,
collateralAmount: new BN(liquidityAmount),
liquidityAmount: new BN(liquidityAmount),
},
data
);

View File

@ -8,9 +8,9 @@ export const LendingObligationLayout: typeof BufferLayout.Structure = BufferLayo
/// Slot when obligation was updated. Used for calculating interest.
Layout.uint64("lastUpdateSlot"),
/// Amount of collateral tokens deposited for this obligation
Layout.uint64("collateralAmount"),
Layout.uint64("depositedCollateral"),
/// Reserve which collateral tokens were deposited into
Layout.publicKey("collateralSupply"),
Layout.publicKey("collateralReserve"),
/// Borrow rate used for calculating interest.
Layout.uint128("cumulativeBorrowRateWad"),
/// Amount of tokens borrowed for this obligation plus interest
@ -28,8 +28,8 @@ export const isLendingObligation = (info: AccountInfo<Buffer>) => {
export interface LendingObligation {
lastUpdateSlot: BN;
collateralAmount: BN;
collateralSupply: PublicKey;
depositedCollateral: BN;
collateralReserve: PublicKey;
cumulativeBorrowRateWad: BN; // decimals
borrowAmountWad: BN; // decimals
borrowReserve: PublicKey;

View File

@ -187,3 +187,12 @@ export const reserveMarketCap = (reserve?: LendingReserve) => {
export const collateralExchangeRate = (reserve?: LendingReserve) => {
return (reserve?.collateralMintSupply.toNumber() || 1) / reserveMarketCap(reserve);
}
export const collateralToLiquidity = (collateralAmount: BN, reserve?: LendingReserve) => {
return Math.floor(collateralAmount.toNumber() / collateralExchangeRate(reserve));
}
export const liquidityToCollateral = (liquidityAmount: BN, reserve?: LendingReserve) => {
return Math.floor(liquidityAmount.toNumber() * collateralExchangeRate(reserve));
}

View File

@ -41,6 +41,7 @@ export const DashboardView = () => {
<div className="dashboard-item dashboard-header">
<div>{LABELS.TABLE_TITLE_ASSET}</div>
<div>{LABELS.TABLE_TITLE_YOUR_LOAN_BALANCE}</div>
<div>{LABELS.TABLE_TITLE_COLLATERAL_BALANCE}</div>
<div>{LABELS.TABLE_TITLE_APY}</div>
<div>{LABELS.TABLE_TITLE_ACTION}</div>
</div>

View File

@ -3,6 +3,7 @@ import { useUserCollateralBalance, useTokenName } from "../../hooks";
import {
calculateBorrowAPY,
collateralExchangeRate,
collateralToLiquidity,
LendingObligation,
LendingReserve,
reserveMarketCap,
@ -17,6 +18,7 @@ import {
import { Button, Card } from "antd";
import { Link } from "react-router-dom";
import { cache, ParsedAccount, useMint } from "../../contexts/accounts";
import { simulateMarketOrderFill } from "../../contexts/market";
export const ObligationItem = (props: {
obligation: ParsedAccount<LendingObligation>;
@ -27,10 +29,12 @@ export const ObligationItem = (props: {
obligation.info.borrowReserve
) as ParsedAccount<LendingReserve>;
const name = useTokenName(borrowReserve?.info.liquidityMint);
const collateralReserve = cache.get(
obligation.info.collateralReserve
) as ParsedAccount<LendingReserve>;
const liquidityMint = useMint(borrowReserve.info.liquidityMint);
const collateralMint = useMint(borrowReserve.info.collateralMint);
const collateralMint = useMint(collateralReserve.info.liquidityMint);
const borrowAmount = fromLamports(
wadToLamports(obligation.info.borrowAmountWad),
@ -40,21 +44,49 @@ export const ObligationItem = (props: {
const borrowAPY = useMemo(() => calculateBorrowAPY(borrowReserve.info), [
borrowReserve,
]);
const collateralLamports = collateralToLiquidity(obligation.info.depositedCollateral, borrowReserve.info);
const collateral = fromLamports(collateralLamports, collateralMint);
const borrowName = useTokenName(borrowReserve?.info.liquidityMint);
const collateralName = useTokenName(collateralReserve?.info.liquidityMint);
const rate = collateralExchangeRate(borrowReserve.info) ;
const cost = simulateMarketOrderFill(collateralLamports, borrowReserve.info);
console.log(cost);
console.log(`collateral ${fromLamports(obligation.info.collateralAmount.toNumber() / rate, collateralMint)}`);
// TODO: health factor
// let borrow_amount_as_collateral = withdraw_reserve_rates.liquidity_to_collateral(
// simulate_market_order_fill(
// obligation.borrowed_liquidity_wads,
// memory,
// dex_market_order_book_side_info,
// dex_market_info,
// &repay_reserve,
// )?
// .round_u64(),
// );
return (
<Card>
<div className="dashboard-item">
<span style={{ display: "flex" }}>
<TokenIcon mintAddress={borrowReserve?.info.liquidityMint} />
{name}
<div style={{ display: "flex" }}>
<TokenIcon
mintAddress={collateralReserve?.info.liquidityMint}
style={{ marginRight: "-0.5rem" }}
/>
<TokenIcon mintAddress={borrowReserve?.info.liquidityMint} />
</div>
{collateralName}
/
{borrowName}
</span>
<div>
{formatNumber.format(borrowAmount)} {name}
{formatNumber.format(borrowAmount)} {borrowName}
</div>
<div>
{formatNumber.format(collateral)} {collateralName}
</div>
<div>{formatPct.format(borrowAPY)}</div>
<div style={{ display: "flex", justifyContent: "flex-end" }}>

View File

@ -10,7 +10,7 @@
}
& > :first-child {
flex: 80px
flex: 120px
}
& > :last-child {
@ -28,7 +28,7 @@
& > :first-child {
text-align: left;
flex: 80px
flex: 120px
}
& > :last-child {

View File

@ -20,6 +20,12 @@ export const RepayReserveView = () => {
const lendingReserve = useLendingReserve(
obligationId ? lendingObligation?.info.borrowReserve : reserveId
);
const repayReserve = useLendingReserve(
obligationId ? lendingObligation?.info.collateralReserve : reserveId
);
const reserve = lendingReserve?.info;
if (!reserve || !lendingReserve || !lendingObligation) {
@ -31,7 +37,8 @@ export const RepayReserveView = () => {
<div className="repay-reserve-container">
<RepayInput
className="repay-reserve-item repay-reserve-item-left"
reserve={lendingReserve}
borrowReserve={lendingReserve}
collateralReserve={repayReserve}
obligation={lendingObligation}
/>
<SideReserveOverview