From 8500764e730f5f8fa79b9358a17fee8b2df419cd Mon Sep 17 00:00:00 2001 From: juan Date: Tue, 26 Jan 2021 14:33:10 -0500 Subject: [PATCH 1/7] Added wallet balance to repayinput --- src/components/CollateralInput/index.tsx | 37 ++++++++++++++---------- src/components/RepayInput/index.tsx | 2 +- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/components/CollateralInput/index.tsx b/src/components/CollateralInput/index.tsx index a29ed4d..1e288c4 100644 --- a/src/components/CollateralInput/index.tsx +++ b/src/components/CollateralInput/index.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from "react"; import { cache, ParsedAccount } from "../../contexts/accounts"; import { useConnectionConfig } from "../../contexts/connection"; -import { useLendingReserves, useUserDeposits } from "../../hooks"; +import {useLendingReserves, useUserBalance, useUserDeposits} from "../../hooks"; import { LendingReserve, LendingMarket, @@ -27,9 +27,11 @@ export default function CollateralInput(props: { onLeverage?: (leverage: number) => void; onInputChange: (value: number | null) => void; hideBalance?: boolean; + useWalletBalance?: boolean; showLeverageSelector?: boolean; leverage?: number; }) { + const { balance: tokenBalance } = useUserBalance(props.reserve.liquidityMint); const { reserveAccounts } = useLendingReserves(); const { tokenMap } = useConnectionConfig(); const [collateralReserve, setCollateralReserve] = useState(); @@ -38,21 +40,26 @@ export default function CollateralInput(props: { const userDeposits = useUserDeposits(); useEffect(() => { - const id: string = - cache - .byParser(LendingReserveParser) - .find((acc) => acc === collateralReserve) || ""; - const parser = cache.get(id) as ParsedAccount; - if (parser) { - const collateralDeposit = userDeposits.userDeposits.find( - (u) => - u.reserve.info.liquidityMint.toBase58() === - parser.info.liquidityMint.toBase58() - ); - if (collateralDeposit) setBalance(collateralDeposit.info.amount); - else setBalance(0); + if (props.useWalletBalance) { + setBalance(tokenBalance) + } else { + const id: string = + cache + .byParser(LendingReserveParser) + .find((acc) => acc === collateralReserve) || ""; + const parser = cache.get(id) as ParsedAccount; + + if (parser) { + const collateralDeposit = userDeposits.userDeposits.find( + (u) => + u.reserve.info.liquidityMint.toBase58() === + parser.info.liquidityMint.toBase58() + ); + if (collateralDeposit) setBalance(collateralDeposit.info.amount); + else setBalance(0); + } } - }, [collateralReserve, userDeposits]); + }, [collateralReserve, userDeposits, tokenBalance, props.useWalletBalance]); const market = cache.get(props.reserve.lendingMarket) as ParsedAccount< LendingMarket diff --git a/src/components/RepayInput/index.tsx b/src/components/RepayInput/index.tsx index 6c759b8..7530517 100644 --- a/src/components/RepayInput/index.tsx +++ b/src/components/RepayInput/index.tsx @@ -211,7 +211,7 @@ export const RepayInput = (props: { setLastTyped("repay"); }} disabled={true} - hideBalance={true} + useWalletBalance={true} /> From 9fe4d6bda53999a50d24adbf2bf51d5e0699752d Mon Sep 17 00:00:00 2001 From: juan Date: Tue, 26 Jan 2021 14:52:37 -0500 Subject: [PATCH 2/7] BorrowInput: select first token as default collateral --- src/components/BorrowInput/index.tsx | 1 + src/components/CollateralInput/index.tsx | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/BorrowInput/index.tsx b/src/components/BorrowInput/index.tsx index 66fe316..744dbd4 100644 --- a/src/components/BorrowInput/index.tsx +++ b/src/components/BorrowInput/index.tsx @@ -195,6 +195,7 @@ export const BorrowInput = (props: { onCollateralReserve={(key) => { setCollateralReserveKey(key); }} + useFirstReserve={true} /> diff --git a/src/components/CollateralInput/index.tsx b/src/components/CollateralInput/index.tsx index 1e288c4..e7f06e0 100644 --- a/src/components/CollateralInput/index.tsx +++ b/src/components/CollateralInput/index.tsx @@ -28,6 +28,7 @@ export default function CollateralInput(props: { onInputChange: (value: number | null) => void; hideBalance?: boolean; useWalletBalance?: boolean; + useFirstReserve?: boolean; showLeverageSelector?: boolean; leverage?: number; }) { @@ -70,13 +71,19 @@ export default function CollateralInput(props: { market?.info?.quoteMint ); - const renderReserveAccounts = reserveAccounts + const filteredReserveAccounts = reserveAccounts .filter((reserve) => reserve.info !== props.reserve) .filter( (reserve) => !onlyQuoteAllowed || reserve.info.liquidityMint.equals(market.info.quoteMint) ) + + if(!collateralReserve && props.useFirstReserve && filteredReserveAccounts.length) { + const address = filteredReserveAccounts[0].pubkey.toBase58(); + setCollateralReserve(address); + } + const renderReserveAccounts = filteredReserveAccounts .map((reserve) => { const mint = reserve.info.liquidityMint.toBase58(); const address = reserve.pubkey.toBase58(); From 404770aea182ae8e3b7189f5fe6f6f2c499273c4 Mon Sep 17 00:00:00 2001 From: juan Date: Wed, 27 Jan 2021 12:44:14 -0500 Subject: [PATCH 3/7] max slider amount can't exceed current balance is user wallet --- src/components/RepayInput/index.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/RepayInput/index.tsx b/src/components/RepayInput/index.tsx index 7530517..99954f1 100644 --- a/src/components/RepayInput/index.tsx +++ b/src/components/RepayInput/index.tsx @@ -38,6 +38,9 @@ export const RepayInput = (props: { const obligation = props.obligation; const liquidityMint = useMint(repayReserve.info.liquidityMint); + const { balance: tokenBalance } = useUserBalance( + repayReserve.info.liquidityMint + ); const borrowAmountLamports = wadToLamports( obligation.info.borrowAmountWad @@ -53,14 +56,15 @@ export const RepayInput = (props: { const convert = useCallback( (val: string | number) => { + const minAmount = Math.min(tokenBalance, borrowAmount); setLastTyped("repay"); if (typeof val === "string") { - return (parseFloat(val) / borrowAmount) * 100; + return (parseFloat(val) / minAmount) * 100; } else { - return (val * borrowAmount) / 100; + return (val * minAmount) / 100; } }, - [borrowAmount] + [borrowAmount, tokenBalance] ); const { value, setValue, pct, setPct, type } = useSliderInput(convert); @@ -221,7 +225,7 @@ export const RepayInput = (props: { flexDirection: "row", justifyContent: "space-evenly", alignItems: "center", - marginBottom: 20 + marginBottom: 20, }} > Date: Wed, 27 Jan 2021 12:45:16 -0500 Subject: [PATCH 4/7] lint --- src/actions/borrow.tsx | 6 +--- src/components/BorrowInput/index.tsx | 2 +- src/components/CollateralInput/index.tsx | 43 ++++++++++++++---------- src/contexts/accounts.tsx | 2 +- src/models/account.ts | 2 +- src/views/borrow/index.tsx | 6 +++- src/views/dashboard/deposit/index.tsx | 5 ++- src/views/dashboard/index.tsx | 22 ++++++------ src/views/dashboard/obligation/index.tsx | 9 +++-- src/views/deposit/view/index.tsx | 6 +++- 10 files changed, 61 insertions(+), 42 deletions(-) diff --git a/src/actions/borrow.tsx b/src/actions/borrow.tsx index 0045551..195b04f 100644 --- a/src/actions/borrow.tsx +++ b/src/actions/borrow.tsx @@ -126,8 +126,6 @@ export const borrow = async ( ) : undefined; - - let amountLamports: number = 0; let fromLamports: number = 0; if (amountType === BorrowAmountType.LiquidityBorrowAmount) { @@ -154,7 +152,6 @@ export const borrow = async ( fromLamports = amountLamports; } - const fromAccount = ensureSplAccount( instructions, finalCleanupInstructions, @@ -197,7 +194,6 @@ export const borrow = async ( instructions = []; cleanupInstructions = [...finalCleanupInstructions]; - // create approval for transfer transactions const transferAuthority = approve( instructions, @@ -205,7 +201,7 @@ export const borrow = async ( fromAccount, wallet.publicKey, fromLamports, - false, + false ); signers.push(transferAuthority); diff --git a/src/components/BorrowInput/index.tsx b/src/components/BorrowInput/index.tsx index 744dbd4..0f411fa 100644 --- a/src/components/BorrowInput/index.tsx +++ b/src/components/BorrowInput/index.tsx @@ -205,7 +205,7 @@ export const BorrowInput = (props: { flexDirection: "row", justifyContent: "space-evenly", alignItems: "center", - marginBottom: 20 + marginBottom: 20, }} > { if (props.useWalletBalance) { - setBalance(tokenBalance) + setBalance(tokenBalance); } else { const id: string = cache @@ -77,26 +81,29 @@ export default function CollateralInput(props: { (reserve) => !onlyQuoteAllowed || reserve.info.liquidityMint.equals(market.info.quoteMint) - ) + ); - if(!collateralReserve && props.useFirstReserve && filteredReserveAccounts.length) { + if ( + !collateralReserve && + props.useFirstReserve && + filteredReserveAccounts.length + ) { const address = filteredReserveAccounts[0].pubkey.toBase58(); setCollateralReserve(address); } - const renderReserveAccounts = filteredReserveAccounts - .map((reserve) => { - const mint = reserve.info.liquidityMint.toBase58(); - const address = reserve.pubkey.toBase58(); - const name = getTokenName(tokenMap, mint); - return ( - - ); - }); + const renderReserveAccounts = filteredReserveAccounts.map((reserve) => { + const mint = reserve.info.liquidityMint.toBase58(); + const address = reserve.pubkey.toBase58(); + const name = getTokenName(tokenMap, mint); + return ( + + ); + }); return ( , parser?: AccountParser ) => { - if(obj.data.length === 0) { + if (obj.data.length === 0) { return; } diff --git a/src/models/account.ts b/src/models/account.ts index c9fa53d..022a077 100644 --- a/src/models/account.ts +++ b/src/models/account.ts @@ -39,7 +39,7 @@ export function approve( ) ); - if(autoRevoke) { + if (autoRevoke) { cleanupInstructions.push( Token.createRevokeInstruction(tokenProgram, account, owner, []) ); diff --git a/src/views/borrow/index.tsx b/src/views/borrow/index.tsx index e3f13a8..339fe7a 100644 --- a/src/views/borrow/index.tsx +++ b/src/views/borrow/index.tsx @@ -18,7 +18,11 @@ export const BorrowView = () => {
{reserveAccounts.map((account) => ( - + ))}
diff --git a/src/views/dashboard/deposit/index.tsx b/src/views/dashboard/deposit/index.tsx index cdb590d..8b49430 100644 --- a/src/views/dashboard/deposit/index.tsx +++ b/src/views/dashboard/deposit/index.tsx @@ -33,7 +33,10 @@ export const DashboardDeposits = () => {
{userDeposits.map((deposit) => ( - + ))} ); diff --git a/src/views/dashboard/index.tsx b/src/views/dashboard/index.tsx index bdac203..a2236f0 100644 --- a/src/views/dashboard/index.tsx +++ b/src/views/dashboard/index.tsx @@ -23,9 +23,7 @@ export const DashboardView = () => { /> {LABELS.DASHBOARD_INFO} - ): - userDeposits.length === 0 && userObligations.length === 0 ? - ( + ) : userDeposits.length === 0 && userObligations.length === 0 ? (
{ /> {LABELS.NO_LOANS_NO_DEPOSITS}
- ): ( + ) : ( - {userDeposits.length > 0 ? - : - {LABELS.NO_DEPOSITS} } + {userDeposits.length > 0 ? ( + + ) : ( + {LABELS.NO_DEPOSITS} + )} - {userObligations.length > 0 ? - : - {LABELS.NO_LOANS} } + {userObligations.length > 0 ? ( + + ) : ( + {LABELS.NO_LOANS} + )} )} diff --git a/src/views/dashboard/obligation/index.tsx b/src/views/dashboard/obligation/index.tsx index 3671df9..ce89ca0 100644 --- a/src/views/dashboard/obligation/index.tsx +++ b/src/views/dashboard/obligation/index.tsx @@ -35,9 +35,12 @@ export const DashboardObligations = () => {
{userObligations.map((item) => { - return ; + return ( + + ); })} ); diff --git a/src/views/deposit/view/index.tsx b/src/views/deposit/view/index.tsx index 63d746b..dfb8fc0 100644 --- a/src/views/deposit/view/index.tsx +++ b/src/views/deposit/view/index.tsx @@ -17,7 +17,11 @@ export const DepositView = () => {
{reserveAccounts.map((account) => ( - + ))} From 07c19bd183b225f10de178bad6bcfe7cca9c4be8 Mon Sep 17 00:00:00 2001 From: juan Date: Wed, 27 Jan 2021 15:22:33 -0500 Subject: [PATCH 5/7] fixed collateralInQuote value for loaninfoline --- src/components/LoanInfoLine/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/LoanInfoLine/index.tsx b/src/components/LoanInfoLine/index.tsx index f896743..14f2c20 100644 --- a/src/components/LoanInfoLine/index.tsx +++ b/src/components/LoanInfoLine/index.tsx @@ -70,7 +70,7 @@ export const LoanInfoLine = (props: { (
From 805faf8b36947e2f2dead81c4d6c88e5fdb90fcd Mon Sep 17 00:00:00 2001 From: juan Date: Wed, 27 Jan 2021 15:47:07 -0500 Subject: [PATCH 6/7] Allow users to liquidate partially --- src/components/LiquidateInput/index.tsx | 176 ++++++++++++++++++++---- src/components/RepayInput/index.tsx | 2 +- src/constants/labels.ts | 1 + src/views/liquidateReserve/index.tsx | 1 - 4 files changed, 155 insertions(+), 25 deletions(-) diff --git a/src/components/LiquidateInput/index.tsx b/src/components/LiquidateInput/index.tsx index 615017e..a7ad9ee 100644 --- a/src/components/LiquidateInput/index.tsx +++ b/src/components/LiquidateInput/index.tsx @@ -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; - withdrawReserve?: ParsedAccount; + withdrawReserve: ParsedAccount; 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", }} > -
- {LABELS.SELECT_COLLATERAL} +
{LABELS.LIQUIDATE_QUESTION}
+
+ { + setValue(val?.toString() || ""); + setLastTyped("liquidate"); + }} + disabled={true} + useWalletBalance={true} + />
- - - +
)} diff --git a/src/components/RepayInput/index.tsx b/src/components/RepayInput/index.tsx index 99954f1..190b449 100644 --- a/src/components/RepayInput/index.tsx +++ b/src/components/RepayInput/index.tsx @@ -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; diff --git a/src/constants/labels.ts b/src/constants/labels.ts index 009443f..498f5ea 100644 --- a/src/constants/labels.ts +++ b/src/constants/labels.ts @@ -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", diff --git a/src/views/liquidateReserve/index.tsx b/src/views/liquidateReserve/index.tsx index b722035..ade0c70 100644 --- a/src/views/liquidateReserve/index.tsx +++ b/src/views/liquidateReserve/index.tsx @@ -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 }>(); From 51ad245011c1551e8a0d9682184c9567109ecf92 Mon Sep 17 00:00:00 2001 From: juan Date: Wed, 27 Jan 2021 15:49:12 -0500 Subject: [PATCH 7/7] lint --- src/components/LiquidateInput/index.tsx | 28 ++++++++++++++----------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/components/LiquidateInput/index.tsx b/src/components/LiquidateInput/index.tsx index a7ad9ee..25fc8b4 100644 --- a/src/components/LiquidateInput/index.tsx +++ b/src/components/LiquidateInput/index.tsx @@ -1,21 +1,26 @@ -import {Slider} from "antd"; +import { Slider } from "antd"; import Card from "antd/lib/card"; -import React, {useCallback, useEffect} from "react"; +import React, { useCallback, useEffect } from "react"; import { useState } from "react"; -import {LABELS, marks} from "../../constants"; -import {ParsedAccount, useMint} from "../../contexts/accounts"; -import {EnrichedLendingObligation, InputType, useSliderInput, 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 { liquidate } from "../../actions"; import "./style.less"; import { useConnection } from "../../contexts/connection"; import { useWallet } from "../../contexts/wallet"; -import {fromLamports, 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"; +import { notify } from "../../utils/notifications"; +import { ConnectButton } from "../ConnectButton"; +import { useMidPriceInUSD } from "../../contexts/market"; export const LiquidateInput = (props: { className?: string; @@ -56,7 +61,6 @@ export const LiquidateInput = (props: { const { value, setValue, pct, setPct, type } = useSliderInput(convert); - const onLiquidate = useCallback(() => { if (!withdrawReserve) { return; @@ -86,7 +90,7 @@ export const LiquidateInput = (props: { setValue(""); setCollateralValue(""); setShowConfirmation(true); - } catch (error){ + } catch (error) { // TODO: notify({ message: "Unable to liquidate loan.", @@ -110,7 +114,7 @@ export const LiquidateInput = (props: { borrowAmountLamports, pct, tokenBalance, - type + type, ]); const collateralPrice = useMidPriceInUSD(