feat: liquidations

This commit is contained in:
bartosz-lipinski 2020-12-15 22:22:25 -06:00
parent cc9e092835
commit 390b77cde7
7 changed files with 64 additions and 45 deletions

View File

@ -6,7 +6,7 @@ import {
GithubOutlined, GithubOutlined,
BankOutlined, BankOutlined,
LogoutOutlined, LogoutOutlined,
LoginOutlined, ShoppingOutlined,
HomeOutlined, HomeOutlined,
RocketOutlined, RocketOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
@ -94,7 +94,7 @@ export const AppLayout = (props: any) => {
{LABELS.MENU_BORROW} {LABELS.MENU_BORROW}
</Link> </Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="5" icon={<LoginOutlined />}> <Menu.Item key="5" icon={<ShoppingOutlined />}>
<Link <Link
to={{ to={{
pathname: "/liquidate", pathname: "/liquidate",

View File

@ -12,12 +12,11 @@ import "./style.less";
export const LiquidateInput = (props: { export const LiquidateInput = (props: {
className?: string; className?: string;
reserve: ParsedAccount<LendingReserve>; reserve: ParsedAccount<LendingReserve>;
collateralReserve?: ParsedAccount<LendingReserve>;
obligation: ParsedAccount<LendingObligation>; obligation: ParsedAccount<LendingObligation>;
}) => { }) => {
const { reserve } = props; const { reserve, collateralReserve } = props;
const [collateralReserveMint, setCollateralReserveMint] = useState<string>();
const [pendingTx, setPendingTx] = useState(false); const [pendingTx, setPendingTx] = useState(false);
const onLiquidate = useCallback(() => { const onLiquidate = useCallback(() => {
@ -43,8 +42,8 @@ export const LiquidateInput = (props: {
<div className="liquidate-input-title">{LABELS.SELECT_COLLATERAL}</div> <div className="liquidate-input-title">{LABELS.SELECT_COLLATERAL}</div>
<CollateralSelector <CollateralSelector
reserve={reserve.info} reserve={reserve.info}
mint={collateralReserveMint} collateralReserve={collateralReserve?.pubkey.toBase58()}
onMintChange={setCollateralReserveMint} disabled={true}
/> />
<Button <Button
type="primary" type="primary"

View File

@ -2,14 +2,14 @@ import React, { useCallback, useContext, useEffect, useState } from "react";
import { MINT_TO_MARKET } from "./../models/marketOverrides"; import { MINT_TO_MARKET } from "./../models/marketOverrides";
import { STABLE_COINS } from "./../utils/utils"; import { STABLE_COINS } from "./../utils/utils";
import { useConnectionConfig } from "./connection"; import { useConnectionConfig } from "./connection";
import { cache, getMultipleAccounts } from "./accounts"; import { cache, getMultipleAccounts, ParsedAccount } from "./accounts";
import { Market, MARKETS, Orderbook, TOKEN_MINTS } from "@project-serum/serum"; import { Market, MARKETS, Orderbook, TOKEN_MINTS } from "@project-serum/serum";
import { AccountInfo, Connection, PublicKey } from "@solana/web3.js"; import { AccountInfo, Connection, PublicKey } from "@solana/web3.js";
import { useMemo } from "react"; import { useMemo } from "react";
import { EventEmitter } from "./../utils/eventEmitter"; import { EventEmitter } from "./../utils/eventEmitter";
import { DexMarketParser } from "./../models/dex"; import { DexMarketParser } from "./../models/dex";
import { LendingReserve } from "../models"; import { LendingMarket, LendingReserve } from "../models";
export interface MarketsContextState { export interface MarketsContextState {
midPriceInUSD: (mint: string) => number; midPriceInUSD: (mint: string) => number;
@ -90,7 +90,7 @@ export function MarketProvider({ children = null as any }) {
allMarkets.filter((a) => cache.get(a) === undefined), allMarkets.filter((a) => cache.get(a) === undefined),
"single" "single"
).then(({ keys, array }) => { ).then(({ keys, array }) => {
allMarkets.forEach(() => {}); allMarkets.forEach(() => { });
return array.map((item, index) => { return array.map((item, index) => {
const marketAddress = keys[index]; const marketAddress = keys[index];
@ -159,7 +159,7 @@ export function MarketProvider({ children = null as any }) {
const info = marketByMint.get(mintAddress); const info = marketByMint.get(mintAddress);
const market = cache.get(info?.marketInfo.address.toBase58() || ""); const market = cache.get(info?.marketInfo.address.toBase58() || "");
if (!market) { if (!market) {
return () => {}; return () => { };
} }
// TODO: get recent volume // TODO: get recent volume
@ -261,7 +261,11 @@ export const simulateMarketOrderFill = (amount: number, reserve: LendingReserve)
const quoteMintDecimals = const quoteMintDecimals =
cache.get(decodedMarket.quoteMint)?.info.decimals || 0; cache.get(decodedMarket.quoteMint)?.info.decimals || 0;
const market = new Market( const lendingMarket = cache.get(reserve.lendingMarket) as ParsedAccount<
LendingMarket
>;
const dexMarket = new Market(
decodedMarket, decodedMarket,
baseMintDecimals, baseMintDecimals,
quoteMintDecimals, quoteMintDecimals,
@ -269,33 +273,35 @@ export const simulateMarketOrderFill = (amount: number, reserve: LendingReserve)
decodedMarket.programId decodedMarket.programId
); );
const bids = cache.get(decodedMarket.bids)?.info; const bookAccount = lendingMarket.info.quoteMint.equals(
const asks = cache.get(decodedMarket.asks)?.info; reserve.liquidityMint
)
? decodedMarket?.bids
: decodedMarket?.asks;
if (bids && asks) { const bookInfo = cache.get(bookAccount)?.info;
const bidsBook = new Orderbook(market, bids.accountFlags, bids.slab); if (!bookInfo) {
const asksBook = new Orderbook(market, asks.accountFlags, asks.slab); return 0;
}
// TODO: pick side const book = new Orderbook(dexMarket, bookInfo.accountFlags, bookInfo.slab);
let book = bidsBook;
let cost = 0; let cost = 0;
let remaining = amount; let remaining = amount;
if (book) {
for (const level of book) { for (const level of book) {
let size = remaining > level.size ? level.size : level.size - remaining; let size = remaining > level.size ? level.size : level.size - remaining;
cost = cost + level.price * size; cost = cost + level.price * size;
remaining = remaining - size; remaining = remaining - size;
if(remaining <= 0) { if (remaining <= 0) {
break; break;
} }
} }
return cost;
} }
return 0; return cost;
} }
const getMidPrice = (marketAddress?: string, mintAddress?: string) => { const getMidPrice = (marketAddress?: string, mintAddress?: string) => {

View File

@ -1,8 +1,10 @@
import { useMemo } from "react"; import { useMemo } from "react";
import { useLendingObligations } from "./useLendingObligations"; import { useLendingObligations } from "./useLendingObligations";
import { LendingReserve } from "../models/lending"; import { collateralToLiquidity, LendingReserve } from "../models/lending";
import { useLendingReserves } from "./useLendingReserves"; import { useLendingReserves } from "./useLendingReserves";
import { ParsedAccount } from "../contexts/accounts"; import { ParsedAccount } from "../contexts/accounts";
import { wadToLamports } from "../utils/utils";
import { simulateMarketOrderFill } from "../contexts/market";
export const useLiquidableObligations = () => { export const useLiquidableObligations = () => {
const { obligations } = useLendingObligations(); const { obligations } = useLendingObligations();
@ -27,8 +29,16 @@ export const useLiquidableObligations = () => {
reserve: availableReserves.get(obligation.info.borrowReserve.toBase58()) as ParsedAccount<LendingReserve> reserve: availableReserves.get(obligation.info.borrowReserve.toBase58()) as ParsedAccount<LendingReserve>
} }
)) ))
// use obligations with reserves available
.filter(item => item.reserve) .filter(item => item.reserve)
// use reserves with borrow amount greater than zero
.filter(item => wadToLamports(item.obligation.info.borrowAmountWad).toNumber() > 0)
.map(item => { .map(item => {
const obligation = item.obligation;
const reserve = item.reserve.info;
const collateralLamports = collateralToLiquidity(obligation.info.depositedCollateral, reserve);
const cost = simulateMarketOrderFill(collateralLamports, reserve);
// TODO: calculate LTV // TODO: calculate LTV
const ltv = 81; const ltv = 81;
const liquidationThreshold = item.reserve.info.config.liquidationThreshold; const liquidationThreshold = item.reserve.info.config.liquidationThreshold;

View File

@ -51,22 +51,6 @@ export const ObligationItem = (props: {
const borrowName = useTokenName(borrowReserve?.info.liquidityMint); const borrowName = useTokenName(borrowReserve?.info.liquidityMint);
const collateralName = useTokenName(collateralReserve?.info.liquidityMint); const collateralName = useTokenName(collateralReserve?.info.liquidityMint);
const cost = simulateMarketOrderFill(collateralLamports, borrowReserve.info);
console.log(cost);
// 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 ( return (
<Card> <Card>
<div className="dashboard-item"> <div className="dashboard-item">

View File

@ -20,7 +20,14 @@ export const LiquidateItem = (props: {
const { obligation, ltv } = props; const { obligation, ltv } = props;
const borrowReserve = cache.get(obligation.info.borrowReserve) as ParsedAccount<LendingReserve>; const borrowReserve = cache.get(
obligation.info.borrowReserve
) as ParsedAccount<LendingReserve>;
const collateralReserve = cache.get(
obligation.info.collateralReserve
) as ParsedAccount<LendingReserve>;
const tokenName = useTokenName(borrowReserve?.info.liquidityMint); const tokenName = useTokenName(borrowReserve?.info.liquidityMint);
const liquidityMint = useMint(borrowReserve.info.liquidityMint); const liquidityMint = useMint(borrowReserve.info.liquidityMint);
@ -33,13 +40,24 @@ export const LiquidateItem = (props: {
borrowReserve, borrowReserve,
]); ]);
const borrowName = useTokenName(borrowReserve?.info.liquidityMint);
const collateralName = useTokenName(collateralReserve?.info.liquidityMint);
return ( return (
<Link to={`/liquidate/${obligation.pubkey.toBase58()}`}> <Link to={`/liquidate/${obligation.pubkey.toBase58()}`}>
<Card> <Card>
<div className="liquidate-item"> <div className="liquidate-item">
<span style={{ display: "flex" }}> <span style={{ display: "flex" }}>
<TokenIcon mintAddress={borrowReserve.info.liquidityMint} /> <div style={{ display: "flex" }}>
{tokenName} <TokenIcon
mintAddress={collateralReserve?.info.liquidityMint}
style={{ marginRight: "-0.5rem" }}
/>
<TokenIcon mintAddress={borrowReserve?.info.liquidityMint} />
</div>
{collateralName}
/
{borrowName}
</span> </span>
<div> <div>
{formatNumber.format(borrowAmount)} {tokenName} {formatNumber.format(borrowAmount)} {tokenName}

View File

@ -15,6 +15,7 @@ export const LiquidateReserveView = () => {
const obligation = useLendingObligation(id); const obligation = useLendingObligation(id);
const reserve = useLendingReserve(obligation?.info.borrowReserve); const reserve = useLendingReserve(obligation?.info.borrowReserve);
const collateralReserve = useLendingReserve(obligation?.info.collateralReserve);
if (!obligation || !reserve) { if (!obligation || !reserve) {
return null; return null;
@ -26,6 +27,7 @@ export const LiquidateReserveView = () => {
<LiquidateInput <LiquidateInput
className="liquidate-reserve-item liquidate-reserve-item-left" className="liquidate-reserve-item liquidate-reserve-item-left"
obligation={obligation} obligation={obligation}
collateralReserve={collateralReserve}
reserve={reserve} reserve={reserve}
/> />
<SideReserveOverview <SideReserveOverview