feat: liquidations
This commit is contained in:
parent
cc9e092835
commit
390b77cde7
|
@ -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",
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue