feat: added borrow and repay
This commit is contained in:
parent
37be3240eb
commit
21bedd7905
@ -35,18 +35,9 @@ interface Props {
|
|||||||
modal: BorrowModal
|
modal: BorrowModal
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDebtAmount(modal: BorrowModal) {
|
|
||||||
return BN((modal.marketData as BorrowMarketTableData)?.debt ?? 0).toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAssetLogo(modal: BorrowModal) {
|
|
||||||
if (!modal.asset) return null
|
|
||||||
return <AssetImage asset={modal.asset} size={24} />
|
|
||||||
}
|
|
||||||
|
|
||||||
function RepayNotAvailable(props: { asset: Asset; repayFromWallet: boolean }) {
|
function RepayNotAvailable(props: { asset: Asset; repayFromWallet: boolean }) {
|
||||||
return (
|
return (
|
||||||
<Card className='mt-6'>
|
<Card className='w-full'>
|
||||||
<div className='flex items-start p-4'>
|
<div className='flex items-start p-4'>
|
||||||
<InfoCircle className='w-6 mr-2 flex-0' />
|
<InfoCircle className='w-6 mr-2 flex-0' />
|
||||||
<div className='flex flex-col flex-1 gap-1'>
|
<div className='flex flex-col flex-1 gap-1'>
|
||||||
@ -90,7 +81,6 @@ function BorrowModal(props: Props) {
|
|||||||
const apy = modal.marketData.apy.borrow
|
const apy = modal.marketData.apy.borrow
|
||||||
const isAutoLendEnabled = autoLendEnabledAccountIds.includes(account.id)
|
const isAutoLendEnabled = autoLendEnabledAccountIds.includes(account.id)
|
||||||
const { computeMaxBorrowAmount } = useHealthComputer(account)
|
const { computeMaxBorrowAmount } = useHealthComputer(account)
|
||||||
const totalDebt = BN(getDebtAmount(modal))
|
|
||||||
const accountDebt = account.debts.find(byDenom(asset.denom))?.amount ?? BN_ZERO
|
const accountDebt = account.debts.find(byDenom(asset.denom))?.amount ?? BN_ZERO
|
||||||
const markets = useMarkets()
|
const markets = useMarkets()
|
||||||
|
|
||||||
@ -237,7 +227,7 @@ function BorrowModal(props: Props) {
|
|||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
header={
|
header={
|
||||||
<span className='flex items-center gap-4 px-4'>
|
<span className='flex items-center gap-4 px-4'>
|
||||||
{getAssetLogo(modal)}
|
<AssetImage asset={asset} size={24} />
|
||||||
<Text>
|
<Text>
|
||||||
{isRepay ? 'Repay' : 'Borrow'} {asset.symbol}
|
{isRepay ? 'Repay' : 'Borrow'} {asset.symbol}
|
||||||
</Text>
|
</Text>
|
||||||
@ -251,14 +241,14 @@ function BorrowModal(props: Props) {
|
|||||||
title={formatPercent(modal.marketData.apy.borrow)}
|
title={formatPercent(modal.marketData.apy.borrow)}
|
||||||
sub={'Borrow Rate APY'}
|
sub={'Borrow Rate APY'}
|
||||||
/>
|
/>
|
||||||
{totalDebt.isGreaterThan(0) && (
|
{accountDebt.isGreaterThan(0) && (
|
||||||
<>
|
<>
|
||||||
<div className='h-100 w-[1px] bg-white/10' />
|
<div className='h-100 w-[1px] bg-white/10' />
|
||||||
<div className='flex flex-col gap-0.5'>
|
<div className='flex flex-col gap-0.5'>
|
||||||
<div className='flex gap-2'>
|
<div className='flex gap-2'>
|
||||||
<FormattedNumber
|
<FormattedNumber
|
||||||
className='text-xs'
|
className='text-xs'
|
||||||
amount={totalDebt.toNumber()}
|
amount={accountDebt.toNumber()}
|
||||||
options={{
|
options={{
|
||||||
decimals: asset.decimals,
|
decimals: asset.decimals,
|
||||||
abbreviated: false,
|
abbreviated: false,
|
||||||
@ -267,7 +257,7 @@ function BorrowModal(props: Props) {
|
|||||||
/>
|
/>
|
||||||
<DisplayCurrency
|
<DisplayCurrency
|
||||||
className='text-xs'
|
className='text-xs'
|
||||||
coin={BNCoin.fromDenomAndBigNumber(asset.denom, totalDebt)}
|
coin={BNCoin.fromDenomAndBigNumber(asset.denom, accountDebt)}
|
||||||
parentheses
|
parentheses
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -303,59 +293,57 @@ function BorrowModal(props: Props) {
|
|||||||
<div className='flex items-start flex-1 gap-6 p-6'>
|
<div className='flex items-start flex-1 gap-6 p-6'>
|
||||||
<Card
|
<Card
|
||||||
className='flex flex-1 p-4 bg-white/5'
|
className='flex flex-1 p-4 bg-white/5'
|
||||||
contentClassName='gap-6 flex flex-col justify-between h-full min-h-[380px]'
|
contentClassName='gap-6 flex flex-col justify-between h-full'
|
||||||
>
|
>
|
||||||
<div className='flex flex-wrap w-full'>
|
<TokenInputWithSlider
|
||||||
<TokenInputWithSlider
|
asset={asset}
|
||||||
asset={asset}
|
onChange={handleChange}
|
||||||
onChange={handleChange}
|
onDebounce={onDebounce}
|
||||||
onDebounce={onDebounce}
|
amount={amount}
|
||||||
amount={amount}
|
max={max}
|
||||||
max={max}
|
disabled={max.isZero()}
|
||||||
disabled={max.isZero()}
|
className='w-full'
|
||||||
className='w-full'
|
maxText='Max'
|
||||||
maxText='Max'
|
warningMessages={[]}
|
||||||
warningMessages={[]}
|
/>
|
||||||
/>
|
{isRepay && maxRepayAmount.isZero() && (
|
||||||
{isRepay && maxRepayAmount.isZero() && (
|
<RepayNotAvailable asset={asset} repayFromWallet={repayFromWallet} />
|
||||||
<RepayNotAvailable asset={asset} repayFromWallet={repayFromWallet} />
|
)}
|
||||||
)}
|
{isRepay ? (
|
||||||
{isRepay ? (
|
<>
|
||||||
<>
|
<Divider />
|
||||||
<Divider className='my-6' />
|
<div className='flex items-center w-full'>
|
||||||
<div className='flex flex-wrap flex-1'>
|
<div className='flex flex-wrap flex-1'>
|
||||||
<Text className='w-full mb-1'>Repay from Wallet</Text>
|
<Text className='w-full mb-1'>Repay from Wallet</Text>
|
||||||
<Text size='xs' className='text-white/50'>
|
<Text size='xs' className='text-white/50'>
|
||||||
Repay your debt directly from your wallet
|
Repay your debt directly from your wallet
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-wrap items-center justify-end'>
|
<Switch
|
||||||
<Switch
|
name='borrow-to-wallet'
|
||||||
name='borrow-to-wallet'
|
checked={repayFromWallet}
|
||||||
checked={repayFromWallet}
|
onChange={setRepayFromWallet}
|
||||||
onChange={setRepayFromWallet}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</>
|
||||||
</>
|
) : (
|
||||||
) : (
|
<>
|
||||||
<>
|
<Divider />
|
||||||
<Divider className='my-6' />
|
<div className='flex items-center w-full'>
|
||||||
<div className='flex flex-wrap flex-1'>
|
<div className='flex flex-wrap flex-1'>
|
||||||
<Text className='w-full mb-1'>Receive funds to Wallet</Text>
|
<Text className='w-full mb-1'>Receive funds to Wallet</Text>
|
||||||
<Text size='xs' className='text-white/50'>
|
<Text size='xs' className='text-white/50'>
|
||||||
Your borrowed funds will directly go to your wallet
|
Your borrowed funds will directly go to your wallet
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-wrap items-center justify-end'>
|
<Switch
|
||||||
<Switch
|
name='borrow-to-wallet'
|
||||||
name='borrow-to-wallet'
|
checked={borrowToWallet}
|
||||||
checked={borrowToWallet}
|
onChange={setBorrowToWallet}
|
||||||
onChange={setBorrowToWallet}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</>
|
||||||
</>
|
)}
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<Button
|
<Button
|
||||||
onClick={onConfirmClick}
|
onClick={onConfirmClick}
|
||||||
className='w-full'
|
className='w-full'
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
LendAndReclaimModalController,
|
LendAndReclaimModalController,
|
||||||
SettingsModal,
|
SettingsModal,
|
||||||
UnlockModal,
|
UnlockModal,
|
||||||
|
V1BorrowAndRepay,
|
||||||
V1DepositAndWithdraw,
|
V1DepositAndWithdraw,
|
||||||
VaultModal,
|
VaultModal,
|
||||||
WalletAssets,
|
WalletAssets,
|
||||||
@ -34,6 +35,7 @@ export default function ModalsContainer() {
|
|||||||
<HlsModal />
|
<HlsModal />
|
||||||
<HlsManageModal />
|
<HlsManageModal />
|
||||||
<V1DepositAndWithdraw />
|
<V1DepositAndWithdraw />
|
||||||
|
<V1BorrowAndRepay />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,8 @@ export { default as HlsManageModal } from 'components/Modals/HLS/Manage'
|
|||||||
export { default as LendAndReclaimModalController } from 'components/Modals/LendAndReclaim'
|
export { default as LendAndReclaimModalController } from 'components/Modals/LendAndReclaim'
|
||||||
export { default as SettingsModal } from 'components/Modals/Settings'
|
export { default as SettingsModal } from 'components/Modals/Settings'
|
||||||
export { default as UnlockModal } from 'components/Modals/Unlock'
|
export { default as UnlockModal } from 'components/Modals/Unlock'
|
||||||
export { default as V1DepositAndWithdraw } from 'components/Modals/V1DepositAndWithdraw'
|
|
||||||
export { default as VaultModal } from 'components/Modals/Vault'
|
export { default as VaultModal } from 'components/Modals/Vault'
|
||||||
export { default as WalletAssets } from 'components/Modals/WalletAssets'
|
export { default as WalletAssets } from 'components/Modals/WalletAssets'
|
||||||
export { default as WithdrawFromVaultsModal } from 'components/Modals/WithdrawFromVaultsModal'
|
export { default as WithdrawFromVaultsModal } from 'components/Modals/WithdrawFromVaultsModal'
|
||||||
|
export { default as V1BorrowAndRepay } from 'components/Modals/v1/V1BorrowAndRepay'
|
||||||
|
export { default as V1DepositAndWithdraw } from 'components/Modals/v1/V1DepositAndWithdraw'
|
||||||
|
179
src/components/Modals/v1/Borrow.tsx
Normal file
179
src/components/Modals/v1/Borrow.tsx
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
import BigNumber from 'bignumber.js'
|
||||||
|
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
|
|
||||||
|
import Modal from 'components/Modals/Modal'
|
||||||
|
import AccountSummaryInModal from 'components/account/AccountSummary/AccountSummaryInModal'
|
||||||
|
import Button from 'components/common/Button'
|
||||||
|
import Card from 'components/common/Card'
|
||||||
|
import DisplayCurrency from 'components/common/DisplayCurrency'
|
||||||
|
import Divider from 'components/common/Divider'
|
||||||
|
import { FormattedNumber } from 'components/common/FormattedNumber'
|
||||||
|
import { ArrowRight } from 'components/common/Icons'
|
||||||
|
import Text from 'components/common/Text'
|
||||||
|
import TitleAndSubCell from 'components/common/TitleAndSubCell'
|
||||||
|
import TokenInputWithSlider from 'components/common/TokenInput/TokenInputWithSlider'
|
||||||
|
import AssetImage from 'components/common/assets/AssetImage'
|
||||||
|
import { BN_ZERO } from 'constants/math'
|
||||||
|
import useBaseAsset from 'hooks/assets/useBasetAsset'
|
||||||
|
import useHealthComputer from 'hooks/useHealthComputer'
|
||||||
|
import { useUpdatedAccount } from 'hooks/useUpdatedAccount'
|
||||||
|
import useStore from 'store'
|
||||||
|
import { BNCoin } from 'types/classes/BNCoin'
|
||||||
|
import { formatPercent } from 'utils/formatters'
|
||||||
|
import { getDebtAmountWithInterest } from 'utils/tokens'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
account: Account
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Borrow(props: Props) {
|
||||||
|
const { account } = props
|
||||||
|
const modal = useStore((s) => s.v1BorrowAndRepayModal)
|
||||||
|
const baseAsset = useBaseAsset()
|
||||||
|
const [amount, setAmount] = useState(BN_ZERO)
|
||||||
|
const v1Action = useStore((s) => s.v1Action)
|
||||||
|
const asset = modal?.data.asset ?? baseAsset
|
||||||
|
const [max, setMax] = useState(BN_ZERO)
|
||||||
|
const { simulateBorrow } = useUpdatedAccount(account)
|
||||||
|
const apy = modal?.data.apy.borrow ?? 0
|
||||||
|
const { computeMaxBorrowAmount } = useHealthComputer(account)
|
||||||
|
const accountDebt = modal?.data.accountDebtAmount ?? BN_ZERO
|
||||||
|
|
||||||
|
const accountDebtWithInterest = useMemo(
|
||||||
|
() => getDebtAmountWithInterest(accountDebt, apy),
|
||||||
|
[accountDebt, apy],
|
||||||
|
)
|
||||||
|
|
||||||
|
const close = useCallback(() => {
|
||||||
|
setAmount(BN_ZERO)
|
||||||
|
useStore.setState({ v1BorrowAndRepayModal: null })
|
||||||
|
}, [setAmount])
|
||||||
|
|
||||||
|
const onConfirmClick = useCallback(() => {
|
||||||
|
v1Action('borrow', BNCoin.fromDenomAndBigNumber(asset.denom, amount))
|
||||||
|
close()
|
||||||
|
}, [v1Action, asset, amount, close])
|
||||||
|
|
||||||
|
const handleChange = useCallback(
|
||||||
|
(newAmount: BigNumber) => {
|
||||||
|
if (!amount.isEqualTo(newAmount)) setAmount(newAmount)
|
||||||
|
},
|
||||||
|
[amount, setAmount],
|
||||||
|
)
|
||||||
|
|
||||||
|
const onDebounce = useCallback(() => {
|
||||||
|
const borrowCoin = BNCoin.fromDenomAndBigNumber(
|
||||||
|
asset.denom,
|
||||||
|
amount.isGreaterThan(max) ? max : amount,
|
||||||
|
)
|
||||||
|
simulateBorrow('wallet', borrowCoin)
|
||||||
|
}, [amount, max, asset, simulateBorrow])
|
||||||
|
|
||||||
|
const maxBorrow = useMemo(() => {
|
||||||
|
const maxBorrowAmount = computeMaxBorrowAmount(asset.denom, 'wallet')
|
||||||
|
|
||||||
|
return BigNumber.min(maxBorrowAmount, modal?.data.liquidity || 0)
|
||||||
|
}, [asset.denom, computeMaxBorrowAmount, modal?.data.liquidity])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (maxBorrow.isEqualTo(max)) return
|
||||||
|
setMax(maxBorrow)
|
||||||
|
}, [account, maxBorrow, max])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (amount.isLessThanOrEqualTo(max)) return
|
||||||
|
handleChange(max)
|
||||||
|
setAmount(max)
|
||||||
|
}, [amount, max, handleChange])
|
||||||
|
|
||||||
|
if (!modal) return null
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
onClose={close}
|
||||||
|
header={
|
||||||
|
<span className='flex items-center gap-4 px-4'>
|
||||||
|
<AssetImage asset={modal.data.asset} size={24} />
|
||||||
|
<Text>{`Borrow ${asset.symbol} from the Red Bank`}</Text>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
headerClassName='gradient-header pl-2 pr-2.5 py-2.5 border-b-white/5 border-b'
|
||||||
|
contentClassName='flex flex-col'
|
||||||
|
>
|
||||||
|
<div className='flex gap-3 px-6 py-4 border-b border-white/5 gradient-header'>
|
||||||
|
<TitleAndSubCell title={formatPercent(apy)} sub={'Borrow Rate APY'} />
|
||||||
|
{accountDebt.isGreaterThan(0) && (
|
||||||
|
<>
|
||||||
|
<div className='h-100 w-[1px] bg-white/10' />
|
||||||
|
<div className='flex flex-col gap-0.5'>
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<FormattedNumber
|
||||||
|
className='text-xs'
|
||||||
|
amount={accountDebt.toNumber()}
|
||||||
|
options={{
|
||||||
|
decimals: asset.decimals,
|
||||||
|
abbreviated: false,
|
||||||
|
suffix: ` ${asset.symbol}`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<DisplayCurrency
|
||||||
|
className='text-xs'
|
||||||
|
coin={BNCoin.fromDenomAndBigNumber(asset.denom, accountDebt)}
|
||||||
|
parentheses
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Text size='xs' className='text-white/50' tag='span'>
|
||||||
|
Total Borrowed
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<div className='h-100 w-[1px] bg-white/10' />
|
||||||
|
<div className='flex flex-col gap-0.5'>
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<FormattedNumber
|
||||||
|
className='text-xs'
|
||||||
|
amount={modal.data.liquidity.toNumber() ?? 0}
|
||||||
|
options={{ decimals: asset.decimals, abbreviated: true, suffix: ` ${asset.symbol}` }}
|
||||||
|
animate
|
||||||
|
/>
|
||||||
|
<DisplayCurrency
|
||||||
|
className='text-xs'
|
||||||
|
coin={BNCoin.fromDenomAndBigNumber(asset.denom, modal.data.liquidity ?? BN_ZERO)}
|
||||||
|
parentheses
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Text size='xs' className='text-white/50' tag='span'>
|
||||||
|
Liquidity available
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex items-start flex-1 gap-6 p-6'>
|
||||||
|
<Card
|
||||||
|
className='flex flex-1 p-4 bg-white/5'
|
||||||
|
contentClassName='gap-6 flex flex-col justify-between h-full'
|
||||||
|
>
|
||||||
|
<TokenInputWithSlider
|
||||||
|
asset={asset}
|
||||||
|
onChange={handleChange}
|
||||||
|
onDebounce={onDebounce}
|
||||||
|
amount={amount}
|
||||||
|
max={max}
|
||||||
|
disabled={max.isZero()}
|
||||||
|
className='w-full'
|
||||||
|
maxText='Max'
|
||||||
|
warningMessages={[]}
|
||||||
|
/>
|
||||||
|
<Divider />
|
||||||
|
<Button
|
||||||
|
onClick={onConfirmClick}
|
||||||
|
className='w-full'
|
||||||
|
disabled={amount.isZero()}
|
||||||
|
text={`Borrow ${asset.symbol}`}
|
||||||
|
rightIcon={<ArrowRight />}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
<AccountSummaryInModal account={account} />
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
@ -11,9 +11,8 @@ import { BNCoin } from 'types/classes/BNCoin'
|
|||||||
import { byDenom } from 'utils/array'
|
import { byDenom } from 'utils/array'
|
||||||
import { defaultFee } from 'utils/constants'
|
import { defaultFee } from 'utils/constants'
|
||||||
import { BN } from 'utils/helpers'
|
import { BN } from 'utils/helpers'
|
||||||
|
import AssetAmountSelectActionModal from 'components/Modals/AssetAmountSelectActionModal'
|
||||||
import AssetAmountSelectActionModal from '../AssetAmountSelectActionModal'
|
import DetailsHeader from 'components/Modals/LendAndReclaim/DetailsHeader'
|
||||||
import DetailsHeader from '../LendAndReclaim/DetailsHeader'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
account: Account
|
account: Account
|
208
src/components/Modals/v1/Repay.tsx
Normal file
208
src/components/Modals/v1/Repay.tsx
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
import BigNumber from 'bignumber.js'
|
||||||
|
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
|
|
||||||
|
import Modal from 'components/Modals/Modal'
|
||||||
|
import AccountSummaryInModal from 'components/account/AccountSummary/AccountSummaryInModal'
|
||||||
|
import Button from 'components/common/Button'
|
||||||
|
import Card from 'components/common/Card'
|
||||||
|
import DisplayCurrency from 'components/common/DisplayCurrency'
|
||||||
|
import Divider from 'components/common/Divider'
|
||||||
|
import { FormattedNumber } from 'components/common/FormattedNumber'
|
||||||
|
import { ArrowRight, InfoCircle } from 'components/common/Icons'
|
||||||
|
import Text from 'components/common/Text'
|
||||||
|
import TitleAndSubCell from 'components/common/TitleAndSubCell'
|
||||||
|
import TokenInputWithSlider from 'components/common/TokenInput/TokenInputWithSlider'
|
||||||
|
import AssetImage from 'components/common/assets/AssetImage'
|
||||||
|
import { BN_ZERO } from 'constants/math'
|
||||||
|
import useBaseAsset from 'hooks/assets/useBasetAsset'
|
||||||
|
import useMarkets from 'hooks/markets/useMarkets'
|
||||||
|
import useCurrentWalletBalance from 'hooks/useCurrentWalletBalance'
|
||||||
|
import { useUpdatedAccount } from 'hooks/useUpdatedAccount'
|
||||||
|
import useStore from 'store'
|
||||||
|
import { BNCoin } from 'types/classes/BNCoin'
|
||||||
|
import { formatPercent } from 'utils/formatters'
|
||||||
|
import { BN } from 'utils/helpers'
|
||||||
|
import { getDebtAmountWithInterest } from 'utils/tokens'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
account: Account
|
||||||
|
}
|
||||||
|
|
||||||
|
function RepayNotAvailable(props: { asset: Asset }) {
|
||||||
|
return (
|
||||||
|
<Card className='w-full'>
|
||||||
|
<div className='flex items-start p-4'>
|
||||||
|
<InfoCircle className='w-6 mr-2 flex-0' />
|
||||||
|
<div className='flex flex-col flex-1 gap-1'>
|
||||||
|
<Text size='sm'>No funds for repay</Text>
|
||||||
|
<Text
|
||||||
|
size='xs'
|
||||||
|
className='text-white/40'
|
||||||
|
>{`Unfortunately you don't have any ${props.asset.symbol} in your Wallet to repay the debt.`}</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Repay(props: Props) {
|
||||||
|
const { account } = props
|
||||||
|
const modal = useStore((s) => s.v1BorrowAndRepayModal)
|
||||||
|
const baseAsset = useBaseAsset()
|
||||||
|
const asset = modal?.data.asset ?? baseAsset
|
||||||
|
const [amount, setAmount] = useState(BN_ZERO)
|
||||||
|
const balance = useCurrentWalletBalance(asset.denom)
|
||||||
|
const v1Action = useStore((s) => s.v1Action)
|
||||||
|
const [max, setMax] = useState(BN_ZERO)
|
||||||
|
const { simulateRepay } = useUpdatedAccount(account)
|
||||||
|
const apy = modal?.data.apy.borrow ?? 0
|
||||||
|
const accountDebt = modal?.data.accountDebtAmount ?? BN_ZERO
|
||||||
|
const markets = useMarkets()
|
||||||
|
|
||||||
|
const accountDebtWithInterest = useMemo(
|
||||||
|
() => getDebtAmountWithInterest(accountDebt, apy),
|
||||||
|
[accountDebt, apy],
|
||||||
|
)
|
||||||
|
|
||||||
|
const overpayExeedsCap = useMemo(() => {
|
||||||
|
const marketAsset = markets.find((market) => market.asset.denom === asset.denom)
|
||||||
|
if (!marketAsset) return
|
||||||
|
const overpayAmount = accountDebtWithInterest.minus(accountDebt)
|
||||||
|
const marketCapAfterOverpay = marketAsset.cap.used.plus(overpayAmount)
|
||||||
|
|
||||||
|
return marketAsset.cap.max.isLessThanOrEqualTo(marketCapAfterOverpay)
|
||||||
|
}, [markets, asset.denom, accountDebt, accountDebtWithInterest])
|
||||||
|
|
||||||
|
const maxRepayAmount = useMemo(() => {
|
||||||
|
const maxBalance = BN(balance?.amount ?? 0)
|
||||||
|
return BigNumber.min(maxBalance, overpayExeedsCap ? accountDebt : accountDebtWithInterest)
|
||||||
|
}, [accountDebtWithInterest, overpayExeedsCap, accountDebt, balance?.amount])
|
||||||
|
|
||||||
|
const close = useCallback(() => {
|
||||||
|
setAmount(BN_ZERO)
|
||||||
|
useStore.setState({ v1BorrowAndRepayModal: null })
|
||||||
|
}, [setAmount])
|
||||||
|
|
||||||
|
const onConfirmClick = useCallback(() => {
|
||||||
|
v1Action('repay', BNCoin.fromDenomAndBigNumber(asset.denom, amount))
|
||||||
|
close()
|
||||||
|
}, [v1Action, asset, amount, close])
|
||||||
|
|
||||||
|
const handleChange = useCallback(
|
||||||
|
(newAmount: BigNumber) => {
|
||||||
|
if (!amount.isEqualTo(newAmount)) setAmount(newAmount)
|
||||||
|
},
|
||||||
|
[amount, setAmount],
|
||||||
|
)
|
||||||
|
|
||||||
|
const onDebounce = useCallback(() => {
|
||||||
|
const repayCoin = BNCoin.fromDenomAndBigNumber(
|
||||||
|
asset.denom,
|
||||||
|
amount.isGreaterThan(accountDebt) ? accountDebt : amount,
|
||||||
|
)
|
||||||
|
simulateRepay(repayCoin, true)
|
||||||
|
}, [amount, accountDebt, asset, simulateRepay])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (maxRepayAmount.isEqualTo(max)) return
|
||||||
|
setMax(maxRepayAmount)
|
||||||
|
}, [max, maxRepayAmount])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (amount.isLessThanOrEqualTo(max)) return
|
||||||
|
handleChange(max)
|
||||||
|
setAmount(max)
|
||||||
|
}, [amount, max, handleChange])
|
||||||
|
|
||||||
|
if (!modal) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
onClose={close}
|
||||||
|
header={
|
||||||
|
<span className='flex items-center gap-4 px-4'>
|
||||||
|
<AssetImage asset={modal.data.asset} size={24} />
|
||||||
|
<Text>
|
||||||
|
{'Repay'} {asset.symbol}
|
||||||
|
</Text>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
headerClassName='gradient-header pl-2 pr-2.5 py-2.5 border-b-white/5 border-b'
|
||||||
|
contentClassName='flex flex-col'
|
||||||
|
>
|
||||||
|
<div className='flex gap-3 px-6 py-4 border-b border-white/5 gradient-header'>
|
||||||
|
<TitleAndSubCell title={formatPercent(apy)} sub={'Borrow Rate APY'} />
|
||||||
|
|
||||||
|
<div className='h-100 w-[1px] bg-white/10' />
|
||||||
|
<div className='flex flex-col gap-0.5'>
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<FormattedNumber
|
||||||
|
className='text-xs'
|
||||||
|
amount={accountDebt.toNumber()}
|
||||||
|
options={{
|
||||||
|
decimals: asset.decimals,
|
||||||
|
abbreviated: false,
|
||||||
|
suffix: ` ${asset.symbol}`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<DisplayCurrency
|
||||||
|
className='text-xs'
|
||||||
|
coin={BNCoin.fromDenomAndBigNumber(asset.denom, accountDebt)}
|
||||||
|
parentheses
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Text size='xs' className='text-white/50' tag='span'>
|
||||||
|
Total Borrowed
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<div className='h-100 w-[1px] bg-white/10' />
|
||||||
|
<div className='flex flex-col gap-0.5'>
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<FormattedNumber
|
||||||
|
className='text-xs'
|
||||||
|
amount={modal.data?.liquidity.toNumber() ?? 0}
|
||||||
|
options={{ decimals: asset.decimals, abbreviated: true, suffix: ` ${asset.symbol}` }}
|
||||||
|
animate
|
||||||
|
/>
|
||||||
|
<DisplayCurrency
|
||||||
|
className='text-xs'
|
||||||
|
coin={BNCoin.fromDenomAndBigNumber(asset.denom, modal.data?.liquidity ?? BN_ZERO)}
|
||||||
|
parentheses
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Text size='xs' className='text-white/50' tag='span'>
|
||||||
|
Liquidity available
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex items-start flex-1 gap-6 p-6'>
|
||||||
|
<Card
|
||||||
|
className='flex flex-1 p-4 bg-white/5'
|
||||||
|
contentClassName='gap-6 flex flex-col justify-between h-full'
|
||||||
|
>
|
||||||
|
<TokenInputWithSlider
|
||||||
|
asset={asset}
|
||||||
|
onChange={handleChange}
|
||||||
|
onDebounce={onDebounce}
|
||||||
|
amount={amount}
|
||||||
|
max={max}
|
||||||
|
disabled={max.isZero()}
|
||||||
|
className='w-full'
|
||||||
|
maxText='Max'
|
||||||
|
warningMessages={[]}
|
||||||
|
/>
|
||||||
|
<Divider />
|
||||||
|
{maxRepayAmount.isZero() && <RepayNotAvailable asset={asset} />}
|
||||||
|
<Button
|
||||||
|
onClick={onConfirmClick}
|
||||||
|
className='w-full'
|
||||||
|
disabled={amount.isZero()}
|
||||||
|
text={`Repay ${asset.symbol}`}
|
||||||
|
rightIcon={<ArrowRight />}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
<AccountSummaryInModal account={account} />
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
15
src/components/Modals/v1/V1BorrowAndRepay.tsx
Normal file
15
src/components/Modals/v1/V1BorrowAndRepay.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import useAccount from 'hooks/accounts/useAccount'
|
||||||
|
import useStore from 'store'
|
||||||
|
import Borrow from 'components/Modals/v1/Borrow'
|
||||||
|
import Repay from 'components/Modals/v1/Repay'
|
||||||
|
|
||||||
|
export default function V1BorrowAndRepayModal() {
|
||||||
|
const address = useStore((s) => s.address)
|
||||||
|
const { data: account } = useAccount(address)
|
||||||
|
const modal = useStore<V1BorrowAndRepayModal | null>((s) => s.v1BorrowAndRepayModal)
|
||||||
|
const isBorrow = modal?.type === 'borrow'
|
||||||
|
|
||||||
|
if (!modal || !account) return null
|
||||||
|
if (isBorrow) return <Borrow account={account} />
|
||||||
|
return <Repay account={account} />
|
||||||
|
}
|
@ -1,8 +1,7 @@
|
|||||||
import useAccount from 'hooks/accounts/useAccount'
|
import useAccount from 'hooks/accounts/useAccount'
|
||||||
import useStore from 'store'
|
import useStore from 'store'
|
||||||
|
import Deposit from 'components/Modals/v1/Deposit'
|
||||||
import Deposit from './Deposit'
|
import Withdraw from 'components/Modals/v1/Withdraw'
|
||||||
import Withdraw from './Withdraw'
|
|
||||||
|
|
||||||
export default function V1DepositAndWithdraw() {
|
export default function V1DepositAndWithdraw() {
|
||||||
const address = useStore((s) => s.address)
|
const address = useStore((s) => s.address)
|
@ -1,5 +1,7 @@
|
|||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
|
|
||||||
|
import AssetAmountSelectActionModal from 'components/Modals/AssetAmountSelectActionModal'
|
||||||
|
import DetailsHeader from 'components/Modals/LendAndReclaim/DetailsHeader'
|
||||||
import { BN_ZERO } from 'constants/math'
|
import { BN_ZERO } from 'constants/math'
|
||||||
import useBaseAsset from 'hooks/assets/useBasetAsset'
|
import useBaseAsset from 'hooks/assets/useBasetAsset'
|
||||||
import useHealthComputer from 'hooks/useHealthComputer'
|
import useHealthComputer from 'hooks/useHealthComputer'
|
||||||
@ -7,9 +9,6 @@ import { useUpdatedAccount } from 'hooks/useUpdatedAccount'
|
|||||||
import useStore from 'store'
|
import useStore from 'store'
|
||||||
import { BNCoin } from 'types/classes/BNCoin'
|
import { BNCoin } from 'types/classes/BNCoin'
|
||||||
|
|
||||||
import AssetAmountSelectActionModal from '../AssetAmountSelectActionModal'
|
|
||||||
import DetailsHeader from '../LendAndReclaim/DetailsHeader'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
account: Account
|
account: Account
|
||||||
}
|
}
|
@ -16,7 +16,6 @@ export const BORROW_BUTTON_META = {
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data: LendingMarketTableData
|
data: LendingMarketTableData
|
||||||
v1?: boolean
|
|
||||||
}
|
}
|
||||||
export default function BorrowButton(props: Props) {
|
export default function BorrowButton(props: Props) {
|
||||||
const account = useCurrentAccount()
|
const account = useCurrentAccount()
|
||||||
|
@ -12,7 +12,6 @@ export const MANAGE_META = {
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data: BorrowMarketTableData
|
data: BorrowMarketTableData
|
||||||
v1?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Manage(props: Props) {
|
export default function Manage(props: Props) {
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
import moment from 'moment/moment'
|
import moment from 'moment/moment'
|
||||||
import React, { useCallback, useMemo, useState } from 'react'
|
import { useCallback, useMemo, useState } from 'react'
|
||||||
|
|
||||||
|
import DropDownButton from 'components/common/Button/DropDownButton'
|
||||||
import { AccountArrowDown, LockLocked, LockUnlocked, Plus } from 'components/common/Icons'
|
import { AccountArrowDown, LockLocked, LockUnlocked, Plus } from 'components/common/Icons'
|
||||||
import Loading from 'components/common/Loading'
|
import Loading from 'components/common/Loading'
|
||||||
|
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||||
|
import { LocalStorageKeys } from 'constants/localStorageKeys'
|
||||||
|
import useLocalStorage from 'hooks/localStorage/useLocalStorage'
|
||||||
|
import useAccountId from 'hooks/useAccountId'
|
||||||
|
import useStore from 'store'
|
||||||
import { VaultStatus } from 'types/enums/vault'
|
import { VaultStatus } from 'types/enums/vault'
|
||||||
|
|
||||||
import { DEFAULT_SETTINGS } from '../../../../../constants/defaultSettings'
|
|
||||||
import { LocalStorageKeys } from '../../../../../constants/localStorageKeys'
|
|
||||||
import useLocalStorage from '../../../../../hooks/localStorage/useLocalStorage'
|
|
||||||
import useAccountId from '../../../../../hooks/useAccountId'
|
|
||||||
import useStore from '../../../../../store'
|
|
||||||
import DropDownButton from '../../../../common/Button/DropDownButton'
|
|
||||||
|
|
||||||
export const MANAGE_META = { accessorKey: 'details', enableSorting: false, header: '' }
|
export const MANAGE_META = { accessorKey: 'details', enableSorting: false, header: '' }
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -104,7 +103,7 @@ export default function Manage(props: Props) {
|
|||||||
if (!address) return null
|
if (!address) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex justify-end z-10'>
|
<div className='z-10 flex justify-end'>
|
||||||
<DropDownButton
|
<DropDownButton
|
||||||
items={ITEMS}
|
items={ITEMS}
|
||||||
text='Manage'
|
text='Manage'
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import BorrowButton from 'components/borrow/Table/Columns/BorrowButton'
|
import BorrowButton from 'components/v1/Table/borrowings/Columns/BorrowButton'
|
||||||
import Manage from 'components/borrow/Table/Columns/Manage'
|
import Manage from 'components/v1/Table/borrowings/Columns/Manage'
|
||||||
|
|
||||||
export const MANAGE_META = {
|
export const MANAGE_META = {
|
||||||
accessorKey: 'manage',
|
accessorKey: 'manage',
|
||||||
@ -14,7 +14,7 @@ interface Props {
|
|||||||
export default function Action(props: Props) {
|
export default function Action(props: Props) {
|
||||||
const hasDebt = !props.data.accountDebtAmount?.isZero() ?? false
|
const hasDebt = !props.data.accountDebtAmount?.isZero() ?? false
|
||||||
|
|
||||||
if (hasDebt) return <Manage data={props.data} v1 />
|
if (hasDebt) return <Manage data={props.data} />
|
||||||
|
|
||||||
return <BorrowButton data={props.data} v1 />
|
return <BorrowButton data={props.data} />
|
||||||
}
|
}
|
||||||
|
50
src/components/v1/Table/borrowings/Columns/BorrowButton.tsx
Normal file
50
src/components/v1/Table/borrowings/Columns/BorrowButton.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import ActionButton from 'components/common/Button/ActionButton'
|
||||||
|
import { Plus } from 'components/common/Icons'
|
||||||
|
import Text from 'components/common/Text'
|
||||||
|
import { Tooltip } from 'components/common/Tooltip'
|
||||||
|
import ConditionalWrapper from 'hocs/ConditionalWrapper'
|
||||||
|
import useAccount from 'hooks/accounts/useAccount'
|
||||||
|
import useStore from 'store'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data: BorrowMarketTableData
|
||||||
|
}
|
||||||
|
export default function BorrowButton(props: Props) {
|
||||||
|
const address = useStore((s) => s.address)
|
||||||
|
const { data: account } = useAccount(address)
|
||||||
|
|
||||||
|
const hasCollateral = account?.lends?.length ?? 0 > 0
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex justify-end'>
|
||||||
|
<ConditionalWrapper
|
||||||
|
condition={!hasCollateral}
|
||||||
|
wrapper={(children) => (
|
||||||
|
<Tooltip
|
||||||
|
type='warning'
|
||||||
|
content={
|
||||||
|
<Text size='sm'>{`You don’t have assets deposited in the Red Bank. Please deposit assets before you borrow.`}</Text>
|
||||||
|
}
|
||||||
|
contentClassName='max-w-[200px]'
|
||||||
|
className='ml-auto'
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ActionButton
|
||||||
|
leftIcon={<Plus />}
|
||||||
|
disabled={!hasCollateral}
|
||||||
|
color='tertiary'
|
||||||
|
onClick={(e) => {
|
||||||
|
useStore.setState({
|
||||||
|
v1BorrowAndRepayModal: { type: 'borrow', data: props.data },
|
||||||
|
})
|
||||||
|
e.stopPropagation()
|
||||||
|
}}
|
||||||
|
text='Borrow'
|
||||||
|
/>
|
||||||
|
</ConditionalWrapper>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
47
src/components/v1/Table/borrowings/Columns/Manage.tsx
Normal file
47
src/components/v1/Table/borrowings/Columns/Manage.tsx
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { useMemo } from 'react'
|
||||||
|
|
||||||
|
import DropDownButton from 'components/common/Button/DropDownButton'
|
||||||
|
import { HandCoins, Plus } from 'components/common/Icons'
|
||||||
|
import useWalletBalances from 'hooks/useWalletBalances'
|
||||||
|
import useStore from 'store'
|
||||||
|
import { byDenom } from 'utils/array'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data: BorrowMarketTableData
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Manage(props: Props) {
|
||||||
|
const address = useStore((s) => s.address)
|
||||||
|
const { data: balances } = useWalletBalances(address)
|
||||||
|
const hasBalance = !!balances.find(byDenom(props.data.asset.denom))
|
||||||
|
|
||||||
|
const ITEMS: DropDownItem[] = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
icon: <Plus />,
|
||||||
|
text: 'Borrow more',
|
||||||
|
onClick: () =>
|
||||||
|
useStore.setState({
|
||||||
|
v1BorrowAndRepayModal: { type: 'borrow', data: props.data },
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <HandCoins />,
|
||||||
|
text: 'Repay',
|
||||||
|
onClick: () =>
|
||||||
|
useStore.setState({
|
||||||
|
v1BorrowAndRepayModal: { type: 'repay', data: props.data },
|
||||||
|
}),
|
||||||
|
disabled: !hasBalance,
|
||||||
|
disabledTooltip: `You don’t have any ${props.data.asset.symbol} in your Wallet.`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[hasBalance, props.data],
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='z-10 flex justify-end'>
|
||||||
|
<DropDownButton items={ITEMS} text='Manage' color='tertiary' />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -12,7 +12,6 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Manage(props: Props) {
|
export default function Manage(props: Props) {
|
||||||
const { openLend, openReclaim } = useLendAndReclaimModal()
|
|
||||||
const address = useStore((s) => s.address)
|
const address = useStore((s) => s.address)
|
||||||
const { data: balances } = useWalletBalances(address)
|
const { data: balances } = useWalletBalances(address)
|
||||||
const hasBalance = !!balances.find(byDenom(props.data.asset.denom))
|
const hasBalance = !!balances.find(byDenom(props.data.asset.denom))
|
||||||
|
@ -20,5 +20,6 @@ export default function createModalSlice(set: SetState<ModalSlice>, get: GetStat
|
|||||||
walletAssetsModal: null,
|
walletAssetsModal: null,
|
||||||
withdrawFromVaultsModal: null,
|
withdrawFromVaultsModal: null,
|
||||||
v1DepositAndWithdrawModal: null,
|
v1DepositAndWithdrawModal: null,
|
||||||
|
v1BorrowAndRepayModal: null,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
8
src/types/interfaces/store/modals.d.ts
vendored
8
src/types/interfaces/store/modals.d.ts
vendored
@ -7,7 +7,6 @@ interface ModalSlice {
|
|||||||
hlsManageModal: HlsManageModal | null
|
hlsManageModal: HlsManageModal | null
|
||||||
borrowModal: BorrowModal | null
|
borrowModal: BorrowModal | null
|
||||||
fundAndWithdrawModal: 'fund' | 'withdraw' | null
|
fundAndWithdrawModal: 'fund' | 'withdraw' | null
|
||||||
v1DepositAndWithdrawModal: V1DepositAndWithdrawModal | null
|
|
||||||
getStartedModal: boolean
|
getStartedModal: boolean
|
||||||
hlsInformationModal: boolean | null
|
hlsInformationModal: boolean | null
|
||||||
lendAndReclaimModal: LendAndReclaimModalConfig | null
|
lendAndReclaimModal: LendAndReclaimModalConfig | null
|
||||||
@ -16,6 +15,8 @@ interface ModalSlice {
|
|||||||
vaultModal: VaultModal | null
|
vaultModal: VaultModal | null
|
||||||
walletAssetsModal: WalletAssetModal | null
|
walletAssetsModal: WalletAssetModal | null
|
||||||
withdrawFromVaultsModal: DepositedVault[] | null
|
withdrawFromVaultsModal: DepositedVault[] | null
|
||||||
|
v1DepositAndWithdrawModal: V1DepositAndWithdrawModal | null
|
||||||
|
v1BorrowAndRepayModal: V1BorrowAndRepayModal | null
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AlertDialogButton {
|
interface AlertDialogButton {
|
||||||
@ -90,3 +91,8 @@ interface V1DepositAndWithdrawModal {
|
|||||||
type: 'deposit' | 'withdraw'
|
type: 'deposit' | 'withdraw'
|
||||||
data: LendingMarketTableData
|
data: LendingMarketTableData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface V1BorrowAndRepayModal {
|
||||||
|
type: 'borrow' | 'repay'
|
||||||
|
data: BorrowMarketTableData
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user