mirror of https://github.com/certusone/oyster.git
Allow users to liquidate partially
This commit is contained in:
parent
07c19bd183
commit
805faf8b36
|
@ -1,35 +1,61 @@
|
|||
import { Button } from "antd";
|
||||
import {Slider} from "antd";
|
||||
import Card from "antd/lib/card";
|
||||
import React, { useCallback } from "react";
|
||||
import React, {useCallback, useEffect} from "react";
|
||||
import { useState } from "react";
|
||||
import { LABELS } from "../../constants";
|
||||
import { ParsedAccount } from "../../contexts/accounts";
|
||||
import { EnrichedLendingObligation, useUserBalance } from "../../hooks";
|
||||
import {LABELS, marks} from "../../constants";
|
||||
import {ParsedAccount, useMint} from "../../contexts/accounts";
|
||||
import {EnrichedLendingObligation, InputType, useSliderInput, useUserBalance} from "../../hooks";
|
||||
import { LendingReserve } from "../../models";
|
||||
import { ActionConfirmation } from "../ActionConfirmation";
|
||||
import { BackButton } from "../BackButton";
|
||||
import { CollateralSelector } from "../CollateralSelector";
|
||||
import { liquidate } from "../../actions";
|
||||
import "./style.less";
|
||||
import { useConnection } from "../../contexts/connection";
|
||||
import { useWallet } from "../../contexts/wallet";
|
||||
import { wadToLamports } from "../../utils/utils";
|
||||
import {fromLamports, wadToLamports} from "../../utils/utils";
|
||||
import CollateralInput from "../CollateralInput";
|
||||
import {notify} from "../../utils/notifications";
|
||||
import {ConnectButton} from "../ConnectButton";
|
||||
import {useMidPriceInUSD} from "../../contexts/market";
|
||||
|
||||
export const LiquidateInput = (props: {
|
||||
className?: string;
|
||||
repayReserve: ParsedAccount<LendingReserve>;
|
||||
withdrawReserve?: ParsedAccount<LendingReserve>;
|
||||
withdrawReserve: ParsedAccount<LendingReserve>;
|
||||
obligation: EnrichedLendingObligation;
|
||||
}) => {
|
||||
const connection = useConnection();
|
||||
const { wallet } = useWallet();
|
||||
const { repayReserve, withdrawReserve, obligation } = props;
|
||||
const [lastTyped, setLastTyped] = useState("liquidate");
|
||||
const [pendingTx, setPendingTx] = useState(false);
|
||||
const [showConfirmation, setShowConfirmation] = useState(false);
|
||||
const [collateralValue, setCollateralValue] = useState("");
|
||||
|
||||
const { accounts: fromAccounts } = useUserBalance(
|
||||
const liquidityMint = useMint(repayReserve.info.liquidityMint);
|
||||
const { accounts: fromAccounts, balance: tokenBalance } = useUserBalance(
|
||||
repayReserve?.info.liquidityMint
|
||||
);
|
||||
const borrowAmountLamports = wadToLamports(
|
||||
obligation.info.borrowAmountWad
|
||||
).toNumber();
|
||||
|
||||
const borrowAmount = fromLamports(borrowAmountLamports, liquidityMint);
|
||||
|
||||
const convert = useCallback(
|
||||
(val: string | number) => {
|
||||
const minAmount = Math.min(tokenBalance || Infinity, borrowAmount);
|
||||
setLastTyped("liquidate");
|
||||
if (typeof val === "string") {
|
||||
return (parseFloat(val) / minAmount) * 100;
|
||||
} else {
|
||||
return (val * minAmount) / 100;
|
||||
}
|
||||
},
|
||||
[borrowAmount, tokenBalance]
|
||||
);
|
||||
|
||||
const { value, setValue, pct, setPct, type } = useSliderInput(convert);
|
||||
|
||||
|
||||
const onLiquidate = useCallback(() => {
|
||||
if (!withdrawReserve) {
|
||||
|
@ -40,20 +66,33 @@ export const LiquidateInput = (props: {
|
|||
|
||||
(async () => {
|
||||
try {
|
||||
const toLiquidateLamports =
|
||||
type === InputType.Percent && tokenBalance >= borrowAmount
|
||||
? (pct * borrowAmountLamports) / 100
|
||||
: Math.ceil(
|
||||
borrowAmountLamports * (parseFloat(value) / borrowAmount)
|
||||
);
|
||||
await liquidate(
|
||||
connection,
|
||||
wallet,
|
||||
fromAccounts[0],
|
||||
// TODO: ensure user has available amount
|
||||
wadToLamports(obligation.info.borrowAmountWad).toNumber(),
|
||||
toLiquidateLamports,
|
||||
obligation.account,
|
||||
repayReserve,
|
||||
withdrawReserve
|
||||
);
|
||||
|
||||
setValue("");
|
||||
setCollateralValue("");
|
||||
setShowConfirmation(true);
|
||||
} catch {
|
||||
} catch (error){
|
||||
// TODO:
|
||||
notify({
|
||||
message: "Unable to liquidate loan.",
|
||||
type: "error",
|
||||
description: error.message,
|
||||
});
|
||||
} finally {
|
||||
setPendingTx(false);
|
||||
}
|
||||
|
@ -65,8 +104,64 @@ export const LiquidateInput = (props: {
|
|||
repayReserve,
|
||||
wallet,
|
||||
connection,
|
||||
value,
|
||||
setValue,
|
||||
borrowAmount,
|
||||
borrowAmountLamports,
|
||||
pct,
|
||||
tokenBalance,
|
||||
type
|
||||
]);
|
||||
|
||||
const collateralPrice = useMidPriceInUSD(
|
||||
withdrawReserve?.info.liquidityMint.toBase58()
|
||||
)?.price;
|
||||
|
||||
useEffect(() => {
|
||||
if (withdrawReserve && lastTyped === "liquidate") {
|
||||
const collateralInQuote = obligation.info.collateralInQuote;
|
||||
const collateral = collateralInQuote / collateralPrice;
|
||||
if (value) {
|
||||
const borrowRatio = (parseFloat(value) / borrowAmount) * 100;
|
||||
const collateralAmount = (borrowRatio * collateral) / 100;
|
||||
setCollateralValue(collateralAmount.toString());
|
||||
} else {
|
||||
setCollateralValue("");
|
||||
}
|
||||
}
|
||||
}, [
|
||||
borrowAmount,
|
||||
collateralPrice,
|
||||
withdrawReserve,
|
||||
lastTyped,
|
||||
obligation.info.collateralInQuote,
|
||||
value,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (withdrawReserve && lastTyped === "collateral") {
|
||||
const collateralInQuote = obligation.info.collateralInQuote;
|
||||
const collateral = collateralInQuote / collateralPrice;
|
||||
if (collateralValue) {
|
||||
const collateralRatio =
|
||||
(parseFloat(collateralValue) / collateral) * 100;
|
||||
const borrowValue = (collateralRatio * borrowAmount) / 100;
|
||||
setValue(borrowValue.toString());
|
||||
} else {
|
||||
setValue("");
|
||||
}
|
||||
}
|
||||
}, [
|
||||
borrowAmount,
|
||||
collateralPrice,
|
||||
withdrawReserve,
|
||||
collateralValue,
|
||||
lastTyped,
|
||||
obligation.info.collateralInQuote,
|
||||
setValue,
|
||||
]);
|
||||
|
||||
if (!withdrawReserve) return null;
|
||||
const bodyStyle: React.CSSProperties = {
|
||||
display: "flex",
|
||||
flex: 1,
|
||||
|
@ -87,23 +182,58 @@ export const LiquidateInput = (props: {
|
|||
justifyContent: "space-around",
|
||||
}}
|
||||
>
|
||||
<div className="liquidate-input-title">
|
||||
{LABELS.SELECT_COLLATERAL}
|
||||
</div>
|
||||
<CollateralSelector
|
||||
<div className="repay-input-title">{LABELS.LIQUIDATE_QUESTION}</div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-evenly",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<CollateralInput
|
||||
title="Liquidate Amount"
|
||||
reserve={repayReserve.info}
|
||||
collateralReserve={withdrawReserve?.pubkey.toBase58()}
|
||||
amount={parseFloat(value) || 0}
|
||||
onInputChange={(val: number | null) => {
|
||||
setValue(val?.toString() || "");
|
||||
setLastTyped("liquidate");
|
||||
}}
|
||||
disabled={true}
|
||||
useWalletBalance={true}
|
||||
/>
|
||||
<Button
|
||||
</div>
|
||||
<Slider marks={marks} value={pct} onChange={setPct} />
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-evenly",
|
||||
alignItems: "center",
|
||||
marginBottom: 20,
|
||||
}}
|
||||
>
|
||||
<CollateralInput
|
||||
title="Collateral Amount (estimated)"
|
||||
reserve={withdrawReserve?.info}
|
||||
amount={parseFloat(collateralValue) || 0}
|
||||
onInputChange={(val: number | null) => {
|
||||
setCollateralValue(val?.toString() || "");
|
||||
setLastTyped("collateral");
|
||||
}}
|
||||
disabled={true}
|
||||
hideBalance={true}
|
||||
/>
|
||||
</div>
|
||||
<ConnectButton
|
||||
type="primary"
|
||||
size="large"
|
||||
onClick={onLiquidate}
|
||||
disabled={fromAccounts.length === 0}
|
||||
loading={pendingTx}
|
||||
disabled={fromAccounts.length === 0}
|
||||
>
|
||||
{LABELS.LIQUIDATE_ACTION}
|
||||
</Button>
|
||||
<BackButton />
|
||||
</ConnectButton>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
|
|
|
@ -56,7 +56,7 @@ export const RepayInput = (props: {
|
|||
|
||||
const convert = useCallback(
|
||||
(val: string | number) => {
|
||||
const minAmount = Math.min(tokenBalance, borrowAmount);
|
||||
const minAmount = Math.min(tokenBalance || Infinity, borrowAmount);
|
||||
setLastTyped("repay");
|
||||
if (typeof val === "string") {
|
||||
return (parseFloat(val) / minAmount) * 100;
|
||||
|
|
|
@ -40,6 +40,7 @@ export const LABELS = {
|
|||
NO_COLLATERAL: "No collateral",
|
||||
NO_DEPOSITS: "No deposits",
|
||||
NO_LOANS: "No loans",
|
||||
LIQUIDATE_QUESTION: "How much would you like to liquidate?",
|
||||
LIQUIDATE_ACTION: "Liquidate",
|
||||
LIQUIDATE_NO_LOANS: "There are no loans to liquidate.",
|
||||
TABLE_TITLE_ASSET: "Asset",
|
||||
|
|
|
@ -13,7 +13,6 @@ import { LiquidateInput } from "../../components/LiquidateInput";
|
|||
import "./style.less";
|
||||
import { Col, Row } from "antd";
|
||||
import { GUTTER } from "../../constants";
|
||||
import { BorrowInput } from "../../components/BorrowInput";
|
||||
|
||||
export const LiquidateReserveView = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
|
|
Loading…
Reference in New Issue