Withdraw assets from account (#43)
* withdraw modal and respective hooks initial commit * max withdraw amount hook logic improvements * withdraw flow code cleanup * reset modal state when reopened * memoize withdraw amount. percentage value fix * unused store selector * credit manager and accountnft clients added to store * script to pull generated types from rover repo
This commit is contained in:
parent
c15743e2f4
commit
9bc09c68af
@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react'
|
||||
import React, { useRef, useState } from 'react'
|
||||
import BigNumber from 'bignumber.js'
|
||||
|
||||
import Button from '../Button'
|
||||
@ -11,10 +11,15 @@ import useTokenPrices from 'hooks/useTokenPrices'
|
||||
import useAccountStats from 'hooks/useAccountStats'
|
||||
import useMarkets from 'hooks/useMarkets'
|
||||
import ContainerSecondary from 'components/ContainerSecondary'
|
||||
import WithdrawModal from 'components/WithdrawModal'
|
||||
import FundAccountModal from 'components/FundAccountModal'
|
||||
|
||||
const CreditManager = () => {
|
||||
const [showFundWalletModal, setShowFundWalletModal] = useState(false)
|
||||
const [showWithdrawModal, setShowWithdrawModal] = useState(false)
|
||||
|
||||
// recreate modals and reset state whenever ref changes
|
||||
const modalId = useRef(0)
|
||||
|
||||
const address = useWalletStore((s) => s.address)
|
||||
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
|
||||
@ -49,12 +54,21 @@ const CreditManager = () => {
|
||||
return (
|
||||
<div className="absolute inset-0 left-auto w-[400px] border-l border-white/20 bg-background-2 p-2">
|
||||
<ContainerSecondary className="mb-2 flex gap-3">
|
||||
<Button className="flex-1 rounded-md" onClick={() => setShowFundWalletModal(true)}>
|
||||
<Button
|
||||
className="flex-1 rounded-md"
|
||||
onClick={() => {
|
||||
setShowFundWalletModal(true)
|
||||
modalId.current += 1
|
||||
}}
|
||||
>
|
||||
Fund
|
||||
</Button>
|
||||
<Button
|
||||
className="flex-1 rounded-md"
|
||||
onClick={() => alert('TODO')}
|
||||
onClick={() => {
|
||||
setShowWithdrawModal(true)
|
||||
modalId.current += 1
|
||||
}}
|
||||
disabled={!positionsData || positionsData.coins.length === 0}
|
||||
>
|
||||
Withdraw
|
||||
@ -123,10 +137,15 @@ const CreditManager = () => {
|
||||
)}
|
||||
</ContainerSecondary>
|
||||
<FundAccountModal
|
||||
key={`fundModal_${selectedAccount}`}
|
||||
key={`fundModal_${modalId.current}`}
|
||||
show={showFundWalletModal}
|
||||
onClose={() => setShowFundWalletModal(false)}
|
||||
/>
|
||||
<WithdrawModal
|
||||
key={`withdrawModal_${modalId.current}`}
|
||||
show={showWithdrawModal}
|
||||
onClose={() => setShowWithdrawModal(false)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
329
components/WithdrawModal.tsx
Normal file
329
components/WithdrawModal.tsx
Normal file
@ -0,0 +1,329 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import { Transition, Dialog, Switch } from '@headlessui/react'
|
||||
import * as RSlider from '@radix-ui/react-slider'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
import useCreditAccountPositions from 'hooks/useCreditAccountPositions'
|
||||
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
|
||||
import ContainerSecondary from './ContainerSecondary'
|
||||
import useCreditManagerStore from 'stores/useCreditManagerStore'
|
||||
import Button from './Button'
|
||||
import useMarkets from 'hooks/useMarkets'
|
||||
import useTokenPrices from 'hooks/useTokenPrices'
|
||||
import { formatCurrency } from 'utils/formatters'
|
||||
import ProgressBar from './ProgressBar'
|
||||
import SemiCircleProgress from './SemiCircleProgress'
|
||||
import useAccountStats from 'hooks/useAccountStats'
|
||||
import useWithdrawFunds from 'hooks/mutations/useWithdrawFunds'
|
||||
import Spinner from './Spinner'
|
||||
import useCalculateMaxWithdrawAmount from 'hooks/useCalculateMaxWithdrawAmount'
|
||||
import useAllBalances from 'hooks/useAllBalances'
|
||||
import Slider from 'components/Slider'
|
||||
|
||||
const WithdrawModal = ({ show, onClose }: any) => {
|
||||
const [amount, setAmount] = useState(0)
|
||||
const [selectedToken, setSelectedToken] = useState('')
|
||||
const [isBorrowEnabled, setIsBorrowEnabled] = useState(false)
|
||||
|
||||
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
|
||||
const { data: positionsData, isLoading: isLoadingPositions } = useCreditAccountPositions(
|
||||
selectedAccount ?? ''
|
||||
)
|
||||
|
||||
const { data: balancesData } = useAllBalances()
|
||||
const { data: tokenPrices } = useTokenPrices()
|
||||
const { data: marketsData } = useMarkets()
|
||||
const accountStats = useAccountStats()
|
||||
|
||||
const selectedTokenSymbol = getTokenSymbol(selectedToken)
|
||||
const selectedTokenDecimals = getTokenDecimals(selectedToken)
|
||||
|
||||
const tokenAmountInCreditAccount = useMemo(() => {
|
||||
return BigNumber(positionsData?.coins.find((coin) => coin.denom === selectedToken)?.amount ?? 0)
|
||||
.div(10 ** selectedTokenDecimals)
|
||||
.toNumber()
|
||||
}, [positionsData, selectedTokenDecimals, selectedToken])
|
||||
|
||||
const { borrowAmount, withdrawAmount } = useMemo(() => {
|
||||
const borrowAmount =
|
||||
amount > tokenAmountInCreditAccount
|
||||
? BigNumber(amount)
|
||||
.minus(tokenAmountInCreditAccount)
|
||||
.times(10 ** selectedTokenDecimals)
|
||||
.toNumber()
|
||||
: 0
|
||||
|
||||
const withdrawAmount = BigNumber(amount)
|
||||
.times(10 ** selectedTokenDecimals)
|
||||
.toNumber()
|
||||
|
||||
return {
|
||||
borrowAmount,
|
||||
withdrawAmount,
|
||||
}
|
||||
}, [amount, selectedTokenDecimals, tokenAmountInCreditAccount])
|
||||
|
||||
const { mutate, isLoading } = useWithdrawFunds(withdrawAmount, borrowAmount, selectedToken, {
|
||||
onSuccess: () => {
|
||||
onClose()
|
||||
toast.success(`${amount} ${selectedTokenSymbol} successfully withdrawn`)
|
||||
},
|
||||
})
|
||||
|
||||
const maxWithdrawAmount = useCalculateMaxWithdrawAmount(selectedToken, isBorrowEnabled)
|
||||
|
||||
const walletAmount = useMemo(() => {
|
||||
if (!selectedToken) return 0
|
||||
|
||||
return BigNumber(balancesData?.find((balance) => balance.denom === selectedToken)?.amount ?? 0)
|
||||
.div(10 ** selectedTokenDecimals)
|
||||
.toNumber()
|
||||
}, [balancesData, selectedToken, selectedTokenDecimals])
|
||||
|
||||
useEffect(() => {
|
||||
if (positionsData && positionsData.coins.length > 0) {
|
||||
// initialize selected token when allowedCoins fetch data is available
|
||||
setSelectedToken(positionsData.coins[0].denom)
|
||||
}
|
||||
}, [positionsData])
|
||||
|
||||
const handleTokenChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
setSelectedToken(e.target.value)
|
||||
|
||||
if (e.target.value !== selectedToken) setAmount(0)
|
||||
}
|
||||
|
||||
const handleValueChange = (value: number) => {
|
||||
if (value > maxWithdrawAmount) {
|
||||
setAmount(maxWithdrawAmount)
|
||||
return
|
||||
}
|
||||
|
||||
setAmount(value)
|
||||
}
|
||||
|
||||
const handleBorrowChange = () => {
|
||||
setIsBorrowEnabled((c) => !c)
|
||||
// reset amount due to max value calculations changing depending on wheter the user is borrowing or not
|
||||
setAmount(0)
|
||||
}
|
||||
|
||||
const getTokenTotalUSDValue = (amount: string, denom: string) => {
|
||||
// early return if prices are not fetched yet
|
||||
if (!tokenPrices) return 0
|
||||
|
||||
return (
|
||||
BigNumber(amount)
|
||||
.div(10 ** getTokenDecimals(denom))
|
||||
.toNumber() * tokenPrices[denom]
|
||||
)
|
||||
}
|
||||
|
||||
const percentageValue = useMemo(() => {
|
||||
if (isNaN(amount) || maxWithdrawAmount === 0) return 0
|
||||
|
||||
return (amount * 100) / maxWithdrawAmount
|
||||
}, [amount, maxWithdrawAmount])
|
||||
|
||||
return (
|
||||
<Transition appear show={show} as={React.Fragment}>
|
||||
<Dialog as="div" className="relative z-10" onClose={onClose}>
|
||||
<Transition.Child
|
||||
as={React.Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-80" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4">
|
||||
<Transition.Child
|
||||
as={React.Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="flex w-full max-w-3xl transform overflow-hidden rounded-2xl bg-[#585A74] align-middle shadow-xl transition-all">
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 z-40 grid place-items-center bg-black/50">
|
||||
<Spinner />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex w-1/2 flex-col p-4">
|
||||
<Dialog.Title as="h3" className="mb-4 text-center text-lg font-medium">
|
||||
Withdraw from Account {selectedAccount}
|
||||
</Dialog.Title>
|
||||
<div>
|
||||
<ContainerSecondary className="mb-3 p-3">
|
||||
<div className="mb-4 text-sm">
|
||||
<div className="mb-1 flex justify-between">
|
||||
<div className="font-bold">Asset:</div>
|
||||
<select className="bg-transparent" onChange={handleTokenChange}>
|
||||
{positionsData?.coins?.map((coin) => (
|
||||
<option key={coin.denom} value={coin.denom}>
|
||||
{getTokenSymbol(coin.denom)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<div className="font-bold">Amount:</div>
|
||||
<input
|
||||
type="number"
|
||||
className="border border-black/50 bg-transparent px-2"
|
||||
value={amount}
|
||||
min="0"
|
||||
onChange={(e) => handleValueChange(e.target.valueAsNumber)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mb-2 text-sm">In wallet: {walletAmount.toLocaleString()}</p>
|
||||
<Slider
|
||||
className="mb-6"
|
||||
value={percentageValue}
|
||||
onChange={(value) => {
|
||||
const decimal = value[0] / 100
|
||||
// limit decimal precision based on token contract decimals
|
||||
const newAmount = Number(
|
||||
(decimal * maxWithdrawAmount).toFixed(selectedTokenDecimals)
|
||||
)
|
||||
|
||||
setAmount(newAmount)
|
||||
}}
|
||||
onMaxClick={() => setAmount(maxWithdrawAmount)}
|
||||
/>
|
||||
</ContainerSecondary>
|
||||
<ContainerSecondary className="mb-10 flex items-center justify-between">
|
||||
<div className="text-left">
|
||||
<h3 className="font-bold">Withdraw with borrowing</h3>
|
||||
<div className="text-sm text-[#585A74]/50">Explanation....</div>
|
||||
</div>
|
||||
|
||||
<Switch
|
||||
checked={isBorrowEnabled}
|
||||
onChange={handleBorrowChange}
|
||||
className={`${
|
||||
isBorrowEnabled ? 'bg-blue-600' : 'bg-gray-400'
|
||||
} relative inline-flex h-6 w-11 items-center rounded-full`}
|
||||
>
|
||||
<span
|
||||
className={`${
|
||||
isBorrowEnabled ? 'translate-x-6' : 'translate-x-1'
|
||||
} inline-block h-4 w-4 transform rounded-full bg-white transition`}
|
||||
/>
|
||||
</Switch>
|
||||
</ContainerSecondary>
|
||||
</div>
|
||||
<Button className="mt-auto w-full" onClick={() => mutate()}>
|
||||
Withdraw
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex w-1/2 flex-col justify-center bg-[#4A4C60] p-4">
|
||||
<p className="text-bold mb-3 text-xs uppercase text-white/50">About</p>
|
||||
<h4 className="mb-4 text-xl">Subaccount {selectedAccount}</h4>
|
||||
<div className="mb-2 rounded-md border border-white/20 p-3">
|
||||
{accountStats && (
|
||||
<div className="flex items-center gap-x-3">
|
||||
<p>{formatCurrency(accountStats.netWorth)}</p>
|
||||
{/* TOOLTIP */}
|
||||
<div title={`${String(accountStats.currentLeverage.toFixed(1))}x`}>
|
||||
<SemiCircleProgress
|
||||
value={accountStats.currentLeverage / accountStats.maxLeverage}
|
||||
label="Lvg"
|
||||
/>
|
||||
</div>
|
||||
<SemiCircleProgress value={accountStats.risk} label="Risk" />
|
||||
<ProgressBar value={accountStats.health} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mb-2 rounded-md border border-white/20 p-3 text-sm">
|
||||
<div className="mb-1 flex justify-between">
|
||||
<div>Total Position:</div>
|
||||
<div className="font-semibold">
|
||||
{formatCurrency(accountStats?.totalPosition ?? 0)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<div>Total Liabilities:</div>
|
||||
<div className="font-semibold">
|
||||
{formatCurrency(accountStats?.totalDebt ?? 0)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-md border border-white/20 p-3">
|
||||
<h4 className="mb-2 font-bold">Balances</h4>
|
||||
{isLoadingPositions ? (
|
||||
<div>Loading...</div>
|
||||
) : (
|
||||
<table className="w-full border-separate border-spacing-1">
|
||||
<thead className="text-left text-xs font-semibold">
|
||||
<tr>
|
||||
<th>Asset</th>
|
||||
<th>Value</th>
|
||||
<th>Size</th>
|
||||
<th className="text-right">APY</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{positionsData?.coins.map((coin) => (
|
||||
<tr key={coin.denom} className="text-xs text-white/50">
|
||||
<td>{getTokenSymbol(coin.denom)}</td>
|
||||
<td>
|
||||
{formatCurrency(getTokenTotalUSDValue(coin.amount, coin.denom))}
|
||||
</td>
|
||||
<td>
|
||||
{BigNumber(coin.amount)
|
||||
.div(10 ** getTokenDecimals(coin.denom))
|
||||
.toNumber()
|
||||
.toLocaleString(undefined, {
|
||||
maximumFractionDigits: getTokenDecimals(coin.denom),
|
||||
})}
|
||||
</td>
|
||||
<td className="text-right">-</td>
|
||||
</tr>
|
||||
))}
|
||||
{positionsData?.debts.map((coin) => (
|
||||
<tr key={coin.denom} className="text-xs text-red-500">
|
||||
<td className="text-white/50">{getTokenSymbol(coin.denom)}</td>
|
||||
<td>
|
||||
-{formatCurrency(getTokenTotalUSDValue(coin.amount, coin.denom))}
|
||||
</td>
|
||||
<td>
|
||||
-
|
||||
{BigNumber(coin.amount)
|
||||
.div(10 ** getTokenDecimals(coin.denom))
|
||||
.toNumber()
|
||||
.toLocaleString(undefined, {
|
||||
maximumFractionDigits: 6,
|
||||
})}
|
||||
</td>
|
||||
<td className="text-right">
|
||||
-{(Number(marketsData?.[coin.denom].borrow_rate) * 100).toFixed(1)}%
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
)
|
||||
}
|
||||
|
||||
export default WithdrawModal
|
19
generate_types.sh
Executable file
19
generate_types.sh
Executable file
@ -0,0 +1,19 @@
|
||||
# generates smart contracts type definitions and copies respective files to types directory
|
||||
# Usage: ./generate-types.sh
|
||||
|
||||
R='\033[0;31m' #'0;31' is Red's ANSI color code
|
||||
G='\033[0;32m' #'0;32' is Green's ANSI color code
|
||||
|
||||
dir=$(pwd)
|
||||
echo $dir
|
||||
|
||||
if [ -d "../rover" ]; then
|
||||
echo "Fetching latest changes from rover repo"
|
||||
cd ../rover && git fetch && git checkout master && git pull
|
||||
cd $dir
|
||||
echo "Generating types for rover..."
|
||||
cp -r ../rover/scripts/types/generated ./types
|
||||
echo "${G}Success"
|
||||
else
|
||||
echo "${R}Directory rover not found..."
|
||||
fi
|
76
hooks/mutations/useWithdrawFunds.tsx
Normal file
76
hooks/mutations/useWithdrawFunds.tsx
Normal file
@ -0,0 +1,76 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { useMemo } from 'react'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
import useWalletStore from 'stores/useWalletStore'
|
||||
import { hardcodedFee } from 'utils/contants'
|
||||
import useCreditManagerStore from 'stores/useCreditManagerStore'
|
||||
import { queryKeys } from 'types/query-keys-factory'
|
||||
|
||||
const useWithdrawFunds = (
|
||||
amount: number,
|
||||
borrowAmount: number,
|
||||
denom: string,
|
||||
options?: {
|
||||
onSuccess?: () => void
|
||||
}
|
||||
) => {
|
||||
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount ?? '')
|
||||
const address = useWalletStore((s) => s.address)
|
||||
const creditManagerClient = useWalletStore((s) => s.clients.creditManager)
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const actions = useMemo(() => {
|
||||
if (borrowAmount > 0) {
|
||||
return [
|
||||
{
|
||||
borrow: {
|
||||
denom,
|
||||
amount: String(borrowAmount),
|
||||
},
|
||||
},
|
||||
{
|
||||
withdraw: {
|
||||
denom,
|
||||
amount: String(amount),
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
withdraw: {
|
||||
denom,
|
||||
amount: String(amount),
|
||||
},
|
||||
},
|
||||
]
|
||||
}, [amount, borrowAmount, denom])
|
||||
|
||||
const { onSuccess } = { ...options }
|
||||
|
||||
return useMutation(
|
||||
async () =>
|
||||
creditManagerClient?.updateCreditAccount(
|
||||
{ accountId: selectedAccount, actions },
|
||||
hardcodedFee
|
||||
),
|
||||
{
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(queryKeys.creditAccountsPositions(selectedAccount))
|
||||
queryClient.invalidateQueries(queryKeys.tokenBalance(address, denom))
|
||||
queryClient.invalidateQueries(queryKeys.allBalances(address))
|
||||
queryClient.invalidateQueries(queryKeys.redbankBalances())
|
||||
|
||||
onSuccess && onSuccess()
|
||||
},
|
||||
onError: (err: Error) => {
|
||||
toast.error(err.message)
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export default useWithdrawFunds
|
108
hooks/useCalculateMaxWithdrawAmount.tsx
Normal file
108
hooks/useCalculateMaxWithdrawAmount.tsx
Normal file
@ -0,0 +1,108 @@
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import BigNumber from 'bignumber.js'
|
||||
|
||||
import { getTokenDecimals } from 'utils/tokens'
|
||||
import useCreditAccountPositions from './useCreditAccountPositions'
|
||||
import useCreditManagerStore from 'stores/useCreditManagerStore'
|
||||
import useTokenPrices from './useTokenPrices'
|
||||
import useMarkets from './useMarkets'
|
||||
|
||||
const useCalculateMaxWithdrawAmount = (denom: string, borrow: boolean) => {
|
||||
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
|
||||
|
||||
const { data: positionsData } = useCreditAccountPositions(selectedAccount ?? '')
|
||||
const { data: marketsData } = useMarkets()
|
||||
const { data: tokenPrices } = useTokenPrices()
|
||||
|
||||
const tokenDecimals = getTokenDecimals(denom)
|
||||
|
||||
const getTokenValue = useCallback(
|
||||
(amount: string, denom: string) => {
|
||||
if (!tokenPrices) return 0
|
||||
|
||||
return BigNumber(amount).times(tokenPrices[denom]).toNumber()
|
||||
},
|
||||
[tokenPrices]
|
||||
)
|
||||
|
||||
const tokenAmountInCreditAccount = useMemo(() => {
|
||||
return positionsData?.coins.find((coin) => coin.denom === denom)?.amount ?? 0
|
||||
}, [denom, positionsData])
|
||||
|
||||
const maxAmount = useMemo(() => {
|
||||
if (!marketsData || !tokenPrices || !positionsData || !denom) return 0
|
||||
|
||||
const hasDebt = positionsData.debts.length > 0
|
||||
|
||||
const borrowTokenPrice = tokenPrices[denom]
|
||||
const borrowTokenMaxLTV = Number(marketsData[denom].max_loan_to_value)
|
||||
|
||||
const totalLiabilitiesValue = positionsData?.debts.reduce((acc, coin) => {
|
||||
const tokenUSDValue = BigNumber(getTokenValue(coin.amount, coin.denom))
|
||||
|
||||
return tokenUSDValue.plus(acc).toNumber()
|
||||
}, 0)
|
||||
|
||||
const positionsWeightedAverageWithoutAsset = positionsData?.coins.reduce((acc, coin) => {
|
||||
if (coin.denom === denom) return acc
|
||||
|
||||
const tokenWeightedValue = BigNumber(getTokenValue(coin.amount, coin.denom)).times(
|
||||
Number(marketsData[coin.denom].max_loan_to_value)
|
||||
)
|
||||
|
||||
return tokenWeightedValue.plus(acc).toNumber()
|
||||
}, 0)
|
||||
|
||||
const isHealthyAfterFullWithdraw = !hasDebt
|
||||
? true
|
||||
: positionsWeightedAverageWithoutAsset / totalLiabilitiesValue > 1
|
||||
|
||||
let maxAmountCapacity = 0
|
||||
|
||||
if (isHealthyAfterFullWithdraw) {
|
||||
const maxBorrow = BigNumber(positionsWeightedAverageWithoutAsset)
|
||||
.minus(totalLiabilitiesValue)
|
||||
.div(borrowTokenPrice)
|
||||
|
||||
maxAmountCapacity = maxBorrow
|
||||
.plus(tokenAmountInCreditAccount)
|
||||
.decimalPlaces(tokenDecimals)
|
||||
.toNumber()
|
||||
} else {
|
||||
const requiredCollateral = BigNumber(totalLiabilitiesValue)
|
||||
.minus(positionsWeightedAverageWithoutAsset)
|
||||
.dividedBy(borrowTokenPrice * borrowTokenMaxLTV)
|
||||
|
||||
maxAmountCapacity = BigNumber(tokenAmountInCreditAccount)
|
||||
.minus(requiredCollateral)
|
||||
.decimalPlaces(tokenDecimals)
|
||||
.toNumber()
|
||||
}
|
||||
|
||||
const isCapacityHigherThanBalance = BigNumber(maxAmountCapacity).gt(tokenAmountInCreditAccount)
|
||||
|
||||
if (!borrow && isCapacityHigherThanBalance) {
|
||||
return BigNumber(tokenAmountInCreditAccount)
|
||||
.div(10 ** tokenDecimals)
|
||||
.toNumber()
|
||||
}
|
||||
|
||||
return BigNumber(maxAmountCapacity)
|
||||
.div(10 ** tokenDecimals)
|
||||
.decimalPlaces(tokenDecimals)
|
||||
.toNumber()
|
||||
}, [
|
||||
borrow,
|
||||
denom,
|
||||
getTokenValue,
|
||||
marketsData,
|
||||
positionsData,
|
||||
tokenAmountInCreditAccount,
|
||||
tokenDecimals,
|
||||
tokenPrices,
|
||||
])
|
||||
|
||||
return maxAmount
|
||||
}
|
||||
|
||||
export default useCalculateMaxWithdrawAmount
|
@ -20,7 +20,6 @@ async function isMetamaskInstalled(): Promise<boolean> {
|
||||
const queryClient = new QueryClient()
|
||||
|
||||
function MyApp({ Component, pageProps }: AppProps) {
|
||||
const address = useWalletStore((s) => s.address)
|
||||
const actions = useWalletStore((s) => s.actions)
|
||||
|
||||
// init store
|
||||
|
@ -145,7 +145,7 @@ const Borrow = () => {
|
||||
onClose={() => setModalState({ ...modalState, show: false })}
|
||||
/>
|
||||
<RepayModal
|
||||
key={`repayModal${modalId.current}`}
|
||||
key={`repayModal_${modalId.current}`}
|
||||
tokenDenom={modalState.data.tokenDenom}
|
||||
show={modalState.show === 'repay'}
|
||||
onClose={() => setModalState({ ...modalState, show: false })}
|
||||
|
@ -4,6 +4,9 @@ import { persist } from 'zustand/middleware'
|
||||
import { Wallet } from 'types'
|
||||
import { CosmWasmClient, SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
||||
import { chain } from 'utils/chains'
|
||||
import { contractAddresses } from 'config/contracts'
|
||||
import { AccountNftClient } from 'types/generated/account-nft/AccountNft.client'
|
||||
import { CreditManagerClient } from 'types/generated/credit-manager/CreditManager.client'
|
||||
|
||||
interface WalletStore {
|
||||
address: string
|
||||
@ -11,8 +14,13 @@ interface WalletStore {
|
||||
wallet: Wallet | null
|
||||
client?: CosmWasmClient
|
||||
signingClient?: SigningCosmWasmClient
|
||||
clients: {
|
||||
accountNft: AccountNftClient | null
|
||||
creditManager: CreditManagerClient | null
|
||||
}
|
||||
actions: {
|
||||
disconnect: () => void
|
||||
initClients: (address: string, signingClient: SigningCosmWasmClient) => void
|
||||
initialize: () => void
|
||||
connect: (address: string, wallet: Wallet) => void
|
||||
setMetamaskInstalledStatus: (value: boolean) => void
|
||||
@ -25,10 +33,33 @@ const useWalletStore = create<WalletStore>()(
|
||||
address: '',
|
||||
metamaskInstalled: false,
|
||||
wallet: null,
|
||||
clients: {
|
||||
accountNft: null,
|
||||
creditManager: null,
|
||||
},
|
||||
actions: {
|
||||
disconnect: () => {
|
||||
set(() => ({ address: '', wallet: null, signingClient: undefined }))
|
||||
},
|
||||
initClients: (address, signingClient) => {
|
||||
const accountNft = new AccountNftClient(
|
||||
signingClient,
|
||||
address,
|
||||
contractAddresses.accountNft
|
||||
)
|
||||
const creditManager = new CreditManagerClient(
|
||||
signingClient,
|
||||
address,
|
||||
contractAddresses.creditManager
|
||||
)
|
||||
|
||||
set(() => ({
|
||||
clients: {
|
||||
accountNft,
|
||||
creditManager,
|
||||
},
|
||||
}))
|
||||
},
|
||||
initialize: async () => {
|
||||
const clientInstance = await CosmWasmClient.connect(chain.rpc)
|
||||
|
||||
@ -42,6 +73,8 @@ const useWalletStore = create<WalletStore>()(
|
||||
offlineSigner
|
||||
)
|
||||
|
||||
get().actions.initClients(address, signingClientInstance)
|
||||
|
||||
set(() => ({
|
||||
client: clientInstance,
|
||||
signingClient: signingClientInstance,
|
||||
@ -56,12 +89,14 @@ const useWalletStore = create<WalletStore>()(
|
||||
if (!window.keplr) return
|
||||
|
||||
const offlineSigner = window.keplr.getOfflineSigner(chain.chainId)
|
||||
const clientInstance = await SigningCosmWasmClient.connectWithSigner(
|
||||
const signingClientInstance = await SigningCosmWasmClient.connectWithSigner(
|
||||
chain.rpc,
|
||||
offlineSigner
|
||||
)
|
||||
|
||||
set(() => ({ address, wallet, signingClient: clientInstance }))
|
||||
get().actions.initClients(address, signingClientInstance)
|
||||
|
||||
set(() => ({ address, wallet, signingClient: signingClientInstance }))
|
||||
},
|
||||
setMetamaskInstalledStatus: (value: boolean) => set(() => ({ metamaskInstalled: value })),
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user