From cb660fb10e7eecb070e1d950e11553ca789ea121 Mon Sep 17 00:00:00 2001 From: Victor Creed <69458664+creed-victor@users.noreply.github.com> Date: Tue, 5 Dec 2023 12:03:34 +0200 Subject: [PATCH] SOV-3390: reimplement increasing debt without adding collateral (#703) * feat: allow borrowing more money * fix: review comments * chore: add changeset --------- Co-authored-by: soulBit --- .changeset/gold-turkeys-behave.md | 5 ++ .../5_pages/BorrowPage/BorrowPage.utils.tsx | 8 ++ .../AdjustLoanForm.constants.tsx | 10 +-- .../AdjustLoanForm/AdjustLoanForm.tsx | 73 +++++++++++-------- .../AdjustLoanForm/AdjustLoanForm.types.ts | 2 +- .../AdjustLoanForm/AdjustLoanForm.utils.tsx | 65 +++++++++++++++++ .../AdjustLoanForm/hooks/useDrawdown.ts | 51 +++++++++++++ .../OpenLoansTable/OpenLoansTable.types.ts | 2 + .../OpenLoansTable/hooks/useGetOpenLoans.ts | 4 + 9 files changed, 182 insertions(+), 38 deletions(-) create mode 100644 .changeset/gold-turkeys-behave.md create mode 100644 apps/frontend/src/app/5_pages/BorrowPage/components/AdjustLoanForm/hooks/useDrawdown.ts diff --git a/.changeset/gold-turkeys-behave.md b/.changeset/gold-turkeys-behave.md new file mode 100644 index 000000000..cbb700c03 --- /dev/null +++ b/.changeset/gold-turkeys-behave.md @@ -0,0 +1,5 @@ +--- +"frontend": patch +--- + +SOV-3390: Fixed Rate Borrow - add ability to increase debt without adding collateral diff --git a/apps/frontend/src/app/5_pages/BorrowPage/BorrowPage.utils.tsx b/apps/frontend/src/app/5_pages/BorrowPage/BorrowPage.utils.tsx index 0d496d8dd..646e9ec26 100644 --- a/apps/frontend/src/app/5_pages/BorrowPage/BorrowPage.utils.tsx +++ b/apps/frontend/src/app/5_pages/BorrowPage/BorrowPage.utils.tsx @@ -108,3 +108,11 @@ export const normalizeToken = (token: string): SupportedTokens => { return SupportedTokens[token] || token; }; + +export const normalizeTokenWrapped = (token: string): SupportedTokens => { + if (isBtcBasedAsset(token)) { + return SupportedTokens.wrbtc; + } + + return normalizeToken(token); +}; diff --git a/apps/frontend/src/app/5_pages/BorrowPage/components/AdjustLoanForm/AdjustLoanForm.constants.tsx b/apps/frontend/src/app/5_pages/BorrowPage/components/AdjustLoanForm/AdjustLoanForm.constants.tsx index 849be4006..d99aaf8b6 100644 --- a/apps/frontend/src/app/5_pages/BorrowPage/components/AdjustLoanForm/AdjustLoanForm.constants.tsx +++ b/apps/frontend/src/app/5_pages/BorrowPage/components/AdjustLoanForm/AdjustLoanForm.constants.tsx @@ -8,11 +8,11 @@ export const INTEREST_DURATION = 3600; // according to the PRD export const ACTIVE_CLASSNAME = 'bg-gray-70 text-primary-20'; export const DEBT_TABS = [ - // { - // tabAction: DebtTabAction.Borrow, - // label: t(translations.fixedInterestPage.adjustLoanDialog.actions.borrow), - // activeClassName: ACTIVE_CLASSNAME, - // }, + { + tabAction: DebtTabAction.Borrow, + label: t(translations.fixedInterestPage.adjustLoanDialog.actions.borrow), + activeClassName: ACTIVE_CLASSNAME, + }, { tabAction: DebtTabAction.Repay, label: t(translations.fixedInterestPage.adjustLoanDialog.actions.repay), diff --git a/apps/frontend/src/app/5_pages/BorrowPage/components/AdjustLoanForm/AdjustLoanForm.tsx b/apps/frontend/src/app/5_pages/BorrowPage/components/AdjustLoanForm/AdjustLoanForm.tsx index 6f6acce0a..f6b6da68a 100644 --- a/apps/frontend/src/app/5_pages/BorrowPage/components/AdjustLoanForm/AdjustLoanForm.tsx +++ b/apps/frontend/src/app/5_pages/BorrowPage/components/AdjustLoanForm/AdjustLoanForm.tsx @@ -45,6 +45,7 @@ import { useGetBorrowingAPR } from '../../hooks/useGetBorrowingAPR'; import { useGetMaintenanceStates } from '../../hooks/useGetMaintenanceStates'; import { useGetOriginationFee } from '../../hooks/useGetOriginationFee'; import { CurrentLoanData } from '../CurrentLoanData/CurrentLoanData'; +import { useBorrow } from '../NewLoanForm/hooks/useBorrow'; import { useGetMaximumCollateralAmount } from '../NewLoanForm/hooks/useGetMaximumCollateralAmount'; import { COLLATERAL_TABS, @@ -59,6 +60,7 @@ import { import { Label } from './components/Label'; import { useCloseWithDepositIsTinyPosition } from './hooks/useCloseWithDepositIsTinyPosition'; import { useDepositCollateral } from './hooks/useDepositCollateral'; +import { useDrawdown } from './hooks/useDrawdown'; import { useGetInterestRefund } from './hooks/useGetInterestRefund'; import { useGetMaxCollateralWithdrawal } from './hooks/useGetMaxCollateralWithdrawal'; import { useGetMaxRepayAmount } from './hooks/useGetMaxRepayAmount'; @@ -77,9 +79,9 @@ export const AdjustLoanForm: FC = ({ loan }) => { const [collateralAmount, setCollateralAmount, collateralSize] = useDecimalAmountInput(''); - const [debtTab, setDebtTab] = useState(DebtTabAction.Repay); + const [debtTab, setDebtTab] = useState(DebtTabAction.Borrow); const [collateralTab, setCollateralTab] = useState( - CollateralTabAction.WithdrawCollateral, + CollateralTabAction.AddCollateral, ); const isCloseTab = useMemo(() => debtTab === DebtTabAction.Close, [debtTab]); @@ -392,7 +394,7 @@ export const AdjustLoanForm: FC = ({ loan }) => { }, [setCollateralAmount, setDebtAmount]); const handleRepay = useRepayLoan(); - // const handleBorrow = useBorrow(); + const handleBorrow = useBorrow(); const handleWithdrawCollateral = useWithdrawCollateral(); const handleDepositCollateral = useDepositCollateral(); @@ -416,53 +418,60 @@ export const AdjustLoanForm: FC = ({ loan }) => { return; } + if (isBorrowTab) { + if (debtSize.isZero()) { + handleDepositCollateral(collateralAmount, collateralToken, loan.id); + } else { + handleBorrow( + debtToken, + debtAmount, + loan.rolloverDate, + collateralSize.toString(), + collateralToken, + loan.id, + ); + } + return; + } + if (isAddCollateralTab) { handleDepositCollateral(collateralAmount, collateralToken, loan.id); return; } - // if (isBorrowTab) { - // if (debtSize.isZero()) { - // handleDepositCollateral(collateralAmount, collateralToken, loan.id); - // } else { - // handleBorrow( - // debtToken, - // debtAmount, - // loan.rolloverDate, - // collateralSize.toString(), - // collateralToken, - // loan.id, - // ); - // } - // } - if (isCollateralWithdrawMode) { handleWithdrawCollateral(collateralAmount, loan.id); } }, [ collateralAmount, + collateralSize, collateralToken, + debtAmount, debtSize, + debtToken, + handleBorrow, handleDepositCollateral, handleRepay, handleWithdrawCollateral, isAddCollateralTab, + isBorrowTab, isCloseTab, isCollateralWithdrawMode, isRepayTab, loan.debt, loan.debtAsset, loan.id, + loan.rolloverDate, ]); const onDebtTabChange = useCallback( (value: DebtTabAction) => { switch (value) { - // case DebtTabAction.Borrow: - // setCollateralTab(CollateralTabAction.AddCollateral); - // setDebtTab(value); - // resetCloseDebtTabValues(); - // return; + case DebtTabAction.Borrow: + setCollateralTab(CollateralTabAction.AddCollateral); + setDebtTab(value); + resetCloseDebtTabValues(); + return; case DebtTabAction.Repay: if (isCloseTab) { setIsTinyPosition(false); @@ -487,11 +496,10 @@ export const AdjustLoanForm: FC = ({ loan }) => { (value: CollateralTabAction) => { switch (value) { case CollateralTabAction.AddCollateral: - // if (!isBorrowTab) { - // setDebtTab(DebtTabAction.Borrow); - // } + if (!isBorrowTab) { + setDebtTab(DebtTabAction.Borrow); + } - setDebtTab(DebtTabAction.None); setDebtAmount(loan.debt.toString()); setCollateralTab(value); @@ -674,6 +682,8 @@ export const AdjustLoanForm: FC = ({ loan }) => { [isValidDebtAmount], ); + const { maxBorrow } = useDrawdown(loan, collateralSize, isAddCollateralTab); + return ( <> = ({ loan }) => { invalid={!isValidDebtAmount} placeholder="0" disabled={ - isCloseTab || - isCollateralWithdrawMode || - isDebtAmountDisabled || - isAddCollateralTab + isCloseTab || isCollateralWithdrawMode || isDebtAmountDisabled } /> = ({ loan }) => { className="w-full" onClick={handleFormSubmit} dataAttribute="adjust-loan-confirm-button" - disabled={submitButtonDisabled} + disabled={ + submitButtonDisabled || (isBorrowTab && debtSize.gt(maxBorrow)) + } /> diff --git a/apps/frontend/src/app/5_pages/BorrowPage/components/AdjustLoanForm/AdjustLoanForm.types.ts b/apps/frontend/src/app/5_pages/BorrowPage/components/AdjustLoanForm/AdjustLoanForm.types.ts index 206cbf31a..3ab5e9eb0 100644 --- a/apps/frontend/src/app/5_pages/BorrowPage/components/AdjustLoanForm/AdjustLoanForm.types.ts +++ b/apps/frontend/src/app/5_pages/BorrowPage/components/AdjustLoanForm/AdjustLoanForm.types.ts @@ -1,8 +1,8 @@ export enum DebtTabAction { None = -1, + Borrow, Repay, Close, - Borrow, } export enum CollateralTabAction { diff --git a/apps/frontend/src/app/5_pages/BorrowPage/components/AdjustLoanForm/AdjustLoanForm.utils.tsx b/apps/frontend/src/app/5_pages/BorrowPage/components/AdjustLoanForm/AdjustLoanForm.utils.tsx index 9e1e34bf0..de9cf9429 100644 --- a/apps/frontend/src/app/5_pages/BorrowPage/components/AdjustLoanForm/AdjustLoanForm.utils.tsx +++ b/apps/frontend/src/app/5_pages/BorrowPage/components/AdjustLoanForm/AdjustLoanForm.utils.tsx @@ -1,5 +1,16 @@ +import { + SupportedTokens, + getLoanTokenContract, + getProtocolContract, + getTokenDetails, +} from '@sovryn/contracts'; +import { getProvider } from '@sovryn/ethers-provider'; import { Decimal } from '@sovryn/utils'; +import { defaultChainId } from '../../../../../config/chains'; + +import { normalizeTokenWrapped } from '../../BorrowPage.utils'; + export const calculateDebtRepaidPercentage = ( totalDebt: string, debtRepaid: Decimal, @@ -13,3 +24,57 @@ export const calculateRepayCollateralWithdrawn = ( Decimal.from(totalCollateral).mul( calculateDebtRepaidPercentage(totalDebt, debtRepaid), ); + +export const getMaxDrawdown = async ( + loanToken: SupportedTokens, + collateralToken: SupportedTokens, + loanAmount: Decimal, + collateralAmount: Decimal, + margin: Decimal, +): Promise => { + const [contract, loanAddress, collateralAddress] = await Promise.all([ + getProtocolContract('priceFeed', defaultChainId).then(({ contract }) => + contract(getProvider(defaultChainId)), + ), + getTokenDetails(normalizeTokenWrapped(loanToken), defaultChainId).then( + ({ address }) => address, + ), + getTokenDetails( + normalizeTokenWrapped(collateralToken), + defaultChainId, + ).then(({ address }) => address), + ]); + + const amount = await contract.getMaxDrawdown( + loanAddress, + collateralAddress, + loanAmount.toHexString(), + collateralAmount.toHexString(), + margin.toHexString(), + ); + return Decimal.fromBigNumberString(amount); +}; + +export const getBorrowAmount = async ( + loanToken: SupportedTokens, + collateralAmount: Decimal, + collateralToken: SupportedTokens, + durationInSeconds: number, +): Promise => { + const [contract, collateralTokenAddress] = await Promise.all([ + getLoanTokenContract(loanToken, defaultChainId).then(({ contract }) => + contract(getProvider(defaultChainId)), + ), + getTokenDetails(collateralToken, defaultChainId).then( + ({ address }) => address, + ), + ]); + + const amount = await contract.getBorrowAmountForDeposit( + collateralAmount.toHexString(), + durationInSeconds, + collateralTokenAddress, + ); + + return Decimal.fromBigNumberString(amount); +}; diff --git a/apps/frontend/src/app/5_pages/BorrowPage/components/AdjustLoanForm/hooks/useDrawdown.ts b/apps/frontend/src/app/5_pages/BorrowPage/components/AdjustLoanForm/hooks/useDrawdown.ts new file mode 100644 index 000000000..78d5ae1da --- /dev/null +++ b/apps/frontend/src/app/5_pages/BorrowPage/components/AdjustLoanForm/hooks/useDrawdown.ts @@ -0,0 +1,51 @@ +import { useEffect, useState } from 'react'; + +import { Decimal } from '@sovryn/utils'; + +import { decimalic } from '../../../../../../utils/math'; +import { LoanItem } from '../../OpenLoansTable/OpenLoansTable.types'; +import { getBorrowAmount, getMaxDrawdown } from '../AdjustLoanForm.utils'; + +export const useDrawdown = ( + loan: LoanItem, + collateralSize: Decimal, + isAddCollateralTab: boolean, +) => { + const [drawDown, setDrawDown] = useState(Decimal.ZERO); + const [maxBorrow, setMaxBorrow] = useState(Decimal.ZERO); + + useEffect(() => { + getMaxDrawdown( + loan.debtAsset, + loan.collateralAsset, + decimalic(loan.debt), + decimalic(loan.collateral), + loan.startMargin, + ).then(setDrawDown); + }, [ + loan.collateral, + loan.collateralAsset, + loan.debt, + loan.debtAsset, + loan.startMargin, + ]); + + useEffect(() => { + getBorrowAmount( + loan.debtAsset, + drawDown.add(collateralSize.mul(isAddCollateralTab ? 1 : -1)), + loan.collateralAsset, + Math.max(Math.floor(loan.rolloverDate - Date.now() / 1000), 0), + // 28 * 86400, + ).then(setMaxBorrow); + }, [ + collateralSize, + drawDown, + isAddCollateralTab, + loan.collateralAsset, + loan.debtAsset, + loan.rolloverDate, + ]); + + return { drawDown, maxBorrow }; +}; diff --git a/apps/frontend/src/app/5_pages/BorrowPage/components/OpenLoansTable/OpenLoansTable.types.ts b/apps/frontend/src/app/5_pages/BorrowPage/components/OpenLoansTable/OpenLoansTable.types.ts index e1e0b76d6..0ef8a9457 100644 --- a/apps/frontend/src/app/5_pages/BorrowPage/components/OpenLoansTable/OpenLoansTable.types.ts +++ b/apps/frontend/src/app/5_pages/BorrowPage/components/OpenLoansTable/OpenLoansTable.types.ts @@ -13,4 +13,6 @@ export type LoanItem = { rolloverDate: number; interestOwedPerDay: number; startMargin: Decimal; + currentMargin: Decimal; + maintenanceMargin: Decimal; }; diff --git a/apps/frontend/src/app/5_pages/BorrowPage/components/OpenLoansTable/hooks/useGetOpenLoans.ts b/apps/frontend/src/app/5_pages/BorrowPage/components/OpenLoansTable/hooks/useGetOpenLoans.ts index 557616b42..490df2c57 100644 --- a/apps/frontend/src/app/5_pages/BorrowPage/components/OpenLoansTable/hooks/useGetOpenLoans.ts +++ b/apps/frontend/src/app/5_pages/BorrowPage/components/OpenLoansTable/hooks/useGetOpenLoans.ts @@ -114,6 +114,10 @@ export const useGetOpenLoans = () => { item.interestOwedPerDay, ), startMargin: Decimal.fromBigNumberString(item.startMargin), + currentMargin: Decimal.fromBigNumberString(item.currentMargin), + maintenanceMargin: Decimal.fromBigNumberString( + item.maintenanceMargin, + ), }; }) .filter(Boolean)