MP-1638: Repay (#35)
* added repay action percentage buffer * moved repay amount calculation to RepayFunds component * refetch interval added to credit account positions query * feat: revamp borrow and repay components to modals * remove references to deleted components * cosmo signing client added to wallet store * amount decimal normalized for borrow action * amount decimals conversion lifted for borrow and repay * refactor: moved hooks to mutations folder * update button disable condition and min input value * refactor: reset modal states when reopened * react-number-format dependency and borrow/repay modals revamp * renamed borrow modal state variable * style: borrow table ui revamp
This commit is contained in:
parent
55d0910d10
commit
18c7547c00
@ -19,7 +19,7 @@ type AssetRowProps = {
|
|||||||
marketLiquidity: number
|
marketLiquidity: number
|
||||||
}
|
}
|
||||||
onBorrowClick: () => void
|
onBorrowClick: () => void
|
||||||
onRepayClick: (value: number) => void
|
onRepayClick: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const AssetRow = ({ data, onBorrowClick, onRepayClick }: AssetRowProps) => {
|
const AssetRow = ({ data, onBorrowClick, onRepayClick }: AssetRowProps) => {
|
||||||
@ -27,7 +27,7 @@ const AssetRow = ({ data, onBorrowClick, onRepayClick }: AssetRowProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="cursor-pointer rounded-md bg-[#D8DAEA] px-4 py-2 text-[#585A74] hover:bg-[#D8DAEA]/90"
|
className="cursor-pointer rounded-md px-4 py-2 hover:bg-black/30"
|
||||||
onClick={() => setIsExpanded((current) => !current)}
|
onClick={() => setIsExpanded((current) => !current)}
|
||||||
>
|
>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
@ -71,10 +71,8 @@ const AssetRow = ({ data, onBorrowClick, onRepayClick }: AssetRowProps) => {
|
|||||||
<Button
|
<Button
|
||||||
disabled={!data.borrowed}
|
disabled={!data.borrowed}
|
||||||
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
if (!data.borrowed) return
|
|
||||||
|
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
onRepayClick(data.borrowed.amount)
|
onRepayClick()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Repay
|
Repay
|
||||||
|
@ -1,171 +0,0 @@
|
|||||||
import React, { useMemo, useState } from 'react'
|
|
||||||
import { XMarkIcon } from '@heroicons/react/24/solid'
|
|
||||||
import { toast } from 'react-toastify'
|
|
||||||
|
|
||||||
import Button from 'components/Button'
|
|
||||||
import Container from 'components/Container'
|
|
||||||
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
|
|
||||||
import useBorrowFunds from 'hooks/useBorrowFunds'
|
|
||||||
import useTokenPrices from 'hooks/useTokenPrices'
|
|
||||||
import { formatCurrency } from 'utils/formatters'
|
|
||||||
import { Switch } from '@headlessui/react'
|
|
||||||
import BigNumber from 'bignumber.js'
|
|
||||||
import useAllBalances from 'hooks/useAllBalances'
|
|
||||||
import useMarkets from 'hooks/useMarkets'
|
|
||||||
import Tooltip from 'components/Tooltip'
|
|
||||||
import ContainerSecondary from 'components/ContainerSecondary'
|
|
||||||
import Spinner from 'components/Spinner'
|
|
||||||
import useCalculateMaxBorrowAmount from 'hooks/useCalculateMaxBorrowAmount'
|
|
||||||
import Slider from 'components/Slider'
|
|
||||||
|
|
||||||
const BorrowFunds = ({ tokenDenom, onClose }: any) => {
|
|
||||||
const [amount, setAmount] = useState(0)
|
|
||||||
const [borrowToCreditAccount, setBorrowToCreditAccount] = useState(false)
|
|
||||||
|
|
||||||
const tokenSymbol = getTokenSymbol(tokenDenom)
|
|
||||||
|
|
||||||
const { mutate, isLoading } = useBorrowFunds(amount, tokenDenom, !borrowToCreditAccount, {
|
|
||||||
onSuccess: () => {
|
|
||||||
onClose()
|
|
||||||
toast.success(`${amount} ${tokenSymbol} successfully Borrowed`)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const { data: tokenPrices } = useTokenPrices()
|
|
||||||
const { data: balancesData } = useAllBalances()
|
|
||||||
const { data: marketsData } = useMarkets()
|
|
||||||
|
|
||||||
const handleSubmit = () => {
|
|
||||||
mutate()
|
|
||||||
}
|
|
||||||
|
|
||||||
const walletAmount = useMemo(() => {
|
|
||||||
return BigNumber(balancesData?.find((balance) => balance.denom === tokenDenom)?.amount ?? 0)
|
|
||||||
.div(10 ** getTokenDecimals(tokenDenom))
|
|
||||||
.toNumber()
|
|
||||||
}, [balancesData, tokenDenom])
|
|
||||||
|
|
||||||
const tokenPrice = tokenPrices?.[tokenDenom] ?? 0
|
|
||||||
const borrowRate = Number(marketsData?.[tokenDenom].borrow_rate)
|
|
||||||
|
|
||||||
const maxValue = useCalculateMaxBorrowAmount(tokenDenom, borrowToCreditAccount)
|
|
||||||
|
|
||||||
const percentageValue = useMemo(() => {
|
|
||||||
if (isNaN(amount) || maxValue === 0) return 0
|
|
||||||
|
|
||||||
return (amount * 100) / maxValue
|
|
||||||
}, [amount, maxValue])
|
|
||||||
|
|
||||||
const isSubmitDisabled = !amount || amount < 0
|
|
||||||
|
|
||||||
const handleValueChange = (value: number) => {
|
|
||||||
if (value > maxValue) {
|
|
||||||
setAmount(maxValue)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setAmount(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleBorrowTargetChange = () => {
|
|
||||||
setBorrowToCreditAccount((c) => !c)
|
|
||||||
// reset amount due to max value calculations changing depending on borrow target
|
|
||||||
setAmount(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Container className="flex w-[350px] flex-col justify-between text-sm">
|
|
||||||
{isLoading && (
|
|
||||||
<div className="fixed inset-0 z-40 grid place-items-center bg-black/50">
|
|
||||||
<Spinner />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="mb-3 flex justify-between text-base font-bold">
|
|
||||||
<h3>Borrow {tokenSymbol}</h3>
|
|
||||||
<XMarkIcon className="w-5 cursor-pointer" onClick={onClose} />
|
|
||||||
</div>
|
|
||||||
<div className="mb-4 flex flex-col gap-2">
|
|
||||||
<ContainerSecondary>
|
|
||||||
<p className="mb-2">
|
|
||||||
In wallet:{' '}
|
|
||||||
<span className="text-[#585A74]/50">
|
|
||||||
{walletAmount.toLocaleString()} {tokenSymbol}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
<p className="mb-5">
|
|
||||||
Borrow Rate: <span className="text-[#585A74]/50">{(borrowRate * 100).toFixed(2)}%</span>
|
|
||||||
</p>
|
|
||||||
<div className="mb-2 flex justify-between">
|
|
||||||
<div>Amount</div>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
className="border border-black/50 bg-transparent px-2"
|
|
||||||
value={amount}
|
|
||||||
onChange={(e) => handleValueChange(e.target.valueAsNumber)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<div>
|
|
||||||
1 {tokenSymbol} ={' '}
|
|
||||||
<span className="text-[#585A74]/50">{formatCurrency(tokenPrice)}</span>
|
|
||||||
</div>
|
|
||||||
<div className="text-[#585A74]/50">{formatCurrency(tokenPrice * amount)}</div>
|
|
||||||
</div>
|
|
||||||
</ContainerSecondary>
|
|
||||||
<ContainerSecondary>
|
|
||||||
<Slider
|
|
||||||
className="mb-6"
|
|
||||||
value={percentageValue}
|
|
||||||
onChange={(value) => {
|
|
||||||
const decimal = value[0] / 100
|
|
||||||
const tokenDecimals = getTokenDecimals(tokenDenom)
|
|
||||||
// limit decimal precision based on token contract decimals
|
|
||||||
const newAmount = Number((decimal * maxValue).toFixed(tokenDecimals))
|
|
||||||
|
|
||||||
setAmount(newAmount)
|
|
||||||
}}
|
|
||||||
onMaxClick={() => setAmount(maxValue)}
|
|
||||||
/>
|
|
||||||
</ContainerSecondary>
|
|
||||||
<ContainerSecondary className="flex items-center justify-between">
|
|
||||||
<div className="flex">
|
|
||||||
Borrow to Credit Account{' '}
|
|
||||||
<Tooltip
|
|
||||||
className="ml-2"
|
|
||||||
content={
|
|
||||||
<>
|
|
||||||
<p className="mb-2">
|
|
||||||
OFF = Borrow directly into your wallet by using your account Assets as
|
|
||||||
collateral. The borrowed asset will become a liability in your account.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
ON = Borrow into your Account. The borrowed asset will be available in the
|
|
||||||
account as an Asset and appear also as a liability in your account.
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Switch
|
|
||||||
checked={borrowToCreditAccount}
|
|
||||||
onChange={handleBorrowTargetChange}
|
|
||||||
className={`${
|
|
||||||
borrowToCreditAccount ? 'bg-blue-600' : 'bg-gray-400'
|
|
||||||
} relative inline-flex h-6 w-11 items-center rounded-full`}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={`${
|
|
||||||
borrowToCreditAccount ? 'translate-x-6' : 'translate-x-1'
|
|
||||||
} inline-block h-4 w-4 transform rounded-full bg-white transition`}
|
|
||||||
/>
|
|
||||||
</Switch>
|
|
||||||
</ContainerSecondary>
|
|
||||||
</div>
|
|
||||||
<Button className="w-full" onClick={handleSubmit} disabled={isSubmitDisabled}>
|
|
||||||
Borrow
|
|
||||||
</Button>
|
|
||||||
</Container>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default BorrowFunds
|
|
@ -65,7 +65,7 @@ interface Market {
|
|||||||
type Props = {
|
type Props = {
|
||||||
data: Market[]
|
data: Market[]
|
||||||
onBorrowClick: (denom: string) => void
|
onBorrowClick: (denom: string) => void
|
||||||
onRepayClick: (denom: string, repayAmount: number) => void
|
onRepayClick: (denom: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const BorrowTable = ({ data, onBorrowClick, onRepayClick }: Props) => {
|
const BorrowTable = ({ data, onBorrowClick, onRepayClick }: Props) => {
|
||||||
@ -152,10 +152,7 @@ const BorrowTable = ({ data, onBorrowClick, onRepayClick }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<div className="w-full table-fixed border-spacing-10 text-sm">
|
<div className="w-full table-fixed border-spacing-10 text-sm">
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
<div
|
<div key={headerGroup.id} className="mb-2 flex rounded-md px-4 py-2 text-xs">
|
||||||
key={headerGroup.id}
|
|
||||||
className="mb-2 flex rounded-md bg-[#D8DAEA] px-4 py-2 text-xs text-[#585A74]/50"
|
|
||||||
>
|
|
||||||
{headerGroup.headers.map((header) => {
|
{headerGroup.headers.map((header) => {
|
||||||
return (
|
return (
|
||||||
<div key={header.id} className={`${header.index === 4 ? 'w-[50px]' : 'flex-1'}`}>
|
<div key={header.id} className={`${header.index === 4 ? 'w-[50px]' : 'flex-1'}`}>
|
||||||
@ -179,16 +176,20 @@ const BorrowTable = ({ data, onBorrowClick, onRepayClick }: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
{table.getRowModel().rows.map((row) => {
|
{table.getRowModel().rows.length === 0 ? (
|
||||||
return (
|
<div>No Data</div>
|
||||||
<AssetRow
|
) : (
|
||||||
key={row.index}
|
table.getRowModel().rows.map((row) => {
|
||||||
data={row.original}
|
return (
|
||||||
onBorrowClick={() => onBorrowClick(row.original.denom)}
|
<AssetRow
|
||||||
onRepayClick={(repayAmount: number) => onRepayClick(row.original.denom, repayAmount)}
|
key={row.index}
|
||||||
/>
|
data={row.original}
|
||||||
)
|
onBorrowClick={() => onBorrowClick(row.original.denom)}
|
||||||
})}
|
onRepayClick={() => onRepayClick(row.original.denom)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -1,116 +0,0 @@
|
|||||||
import React, { useMemo, useState } from 'react'
|
|
||||||
import { XMarkIcon } from '@heroicons/react/24/solid'
|
|
||||||
import { toast } from 'react-toastify'
|
|
||||||
|
|
||||||
import Button from 'components/Button'
|
|
||||||
import Container from 'components/Container'
|
|
||||||
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
|
|
||||||
import useRepayFunds from 'hooks/useRepayFunds'
|
|
||||||
import useTokenPrices from 'hooks/useTokenPrices'
|
|
||||||
import { formatCurrency } from 'utils/formatters'
|
|
||||||
import BigNumber from 'bignumber.js'
|
|
||||||
import useAllBalances from 'hooks/useAllBalances'
|
|
||||||
import ContainerSecondary from 'components/ContainerSecondary'
|
|
||||||
import Spinner from 'components/Spinner'
|
|
||||||
import Slider from 'components/Slider'
|
|
||||||
|
|
||||||
const RepayFunds = ({ tokenDenom, amount: repayAmount, onClose }: any) => {
|
|
||||||
const [amount, setAmount] = useState(0)
|
|
||||||
|
|
||||||
const tokenSymbol = getTokenSymbol(tokenDenom)
|
|
||||||
|
|
||||||
const { mutate, isLoading } = useRepayFunds(amount, tokenDenom, {
|
|
||||||
onSuccess: () => {
|
|
||||||
onClose()
|
|
||||||
toast.success(`${amount} ${tokenSymbol} successfully repaid`)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const { data: tokenPrices } = useTokenPrices()
|
|
||||||
const { data: balancesData } = useAllBalances()
|
|
||||||
|
|
||||||
const handleSubmit = () => {
|
|
||||||
mutate()
|
|
||||||
}
|
|
||||||
|
|
||||||
const walletAmount = useMemo(() => {
|
|
||||||
return BigNumber(balancesData?.find((balance) => balance.denom === tokenDenom)?.amount ?? 0)
|
|
||||||
.div(10 ** getTokenDecimals(tokenDenom))
|
|
||||||
.toNumber()
|
|
||||||
}, [balancesData, tokenDenom])
|
|
||||||
|
|
||||||
const tokenPrice = tokenPrices?.[tokenDenom] ?? 0
|
|
||||||
|
|
||||||
const maxValue = walletAmount > repayAmount ? repayAmount : walletAmount
|
|
||||||
const percentageValue = isNaN(amount) ? 0 : (amount * 100) / maxValue
|
|
||||||
const isSubmitDisabled = !amount || amount < 0
|
|
||||||
|
|
||||||
const handleValueChange = (value: number) => {
|
|
||||||
if (value > maxValue) {
|
|
||||||
setAmount(maxValue)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setAmount(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Container className="flex w-[350px] flex-col justify-between text-sm">
|
|
||||||
{isLoading && (
|
|
||||||
<div className="fixed inset-0 z-40 grid place-items-center bg-black/50">
|
|
||||||
<Spinner />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="mb-3 flex justify-between text-base font-bold">
|
|
||||||
<h3>Repay {tokenSymbol}</h3>
|
|
||||||
<XMarkIcon className="w-5 cursor-pointer" onClick={onClose} />
|
|
||||||
</div>
|
|
||||||
<div className="mb-4 flex flex-col gap-2">
|
|
||||||
<ContainerSecondary>
|
|
||||||
<p className="mb-2">
|
|
||||||
In wallet:{' '}
|
|
||||||
<span className="text-[#585A74]/50">
|
|
||||||
{walletAmount.toLocaleString()} {tokenSymbol}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
<div className="mb-2 flex justify-between">
|
|
||||||
<div>Amount</div>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
className="border border-black/50 bg-transparent px-2"
|
|
||||||
value={amount}
|
|
||||||
onChange={(e) => handleValueChange(e.target.valueAsNumber)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<div>
|
|
||||||
1 {tokenSymbol} ={' '}
|
|
||||||
<span className="text-[#585A74]/50">{formatCurrency(tokenPrice)}</span>
|
|
||||||
</div>
|
|
||||||
<div className="text-[#585A74]/50">{formatCurrency(tokenPrice * amount)}</div>
|
|
||||||
</div>
|
|
||||||
</ContainerSecondary>
|
|
||||||
<ContainerSecondary>
|
|
||||||
<Slider
|
|
||||||
className="mb-6"
|
|
||||||
value={percentageValue}
|
|
||||||
onChange={(value) => {
|
|
||||||
const decimal = value[0] / 100
|
|
||||||
const tokenDecimals = getTokenDecimals(tokenDenom)
|
|
||||||
// limit decimal precision based on token contract decimals
|
|
||||||
const newAmount = Number((decimal * maxValue).toFixed(tokenDecimals))
|
|
||||||
|
|
||||||
setAmount(newAmount)
|
|
||||||
}}
|
|
||||||
onMaxClick={() => setAmount(maxValue)}
|
|
||||||
/>
|
|
||||||
</ContainerSecondary>
|
|
||||||
</div>
|
|
||||||
<Button className="w-full" onClick={handleSubmit} disabled={isSubmitDisabled}>
|
|
||||||
Repay
|
|
||||||
</Button>
|
|
||||||
</Container>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default RepayFunds
|
|
@ -1,3 +1,2 @@
|
|||||||
export { default as AssetRow } from './AssetRow'
|
export { default as AssetRow } from './AssetRow'
|
||||||
export { default as BorrowFunds } from './BorrowFunds'
|
export { default as BorrowTable } from './BorrowTable'
|
||||||
export { default as RepayFunds } from './RepayFunds'
|
|
||||||
|
230
components/BorrowModal.tsx
Normal file
230
components/BorrowModal.tsx
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
import React, { useMemo, useState } from 'react'
|
||||||
|
import Image from 'next/image'
|
||||||
|
import { Transition, Dialog, Switch } from '@headlessui/react'
|
||||||
|
import BigNumber from 'bignumber.js'
|
||||||
|
import { toast } from 'react-toastify'
|
||||||
|
import { NumericFormat } from 'react-number-format'
|
||||||
|
|
||||||
|
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
|
||||||
|
import ContainerSecondary from './ContainerSecondary'
|
||||||
|
import Button from './Button'
|
||||||
|
import Spinner from './Spinner'
|
||||||
|
import useAllBalances from 'hooks/useAllBalances'
|
||||||
|
import Slider from 'components/Slider'
|
||||||
|
import useBorrowFunds from 'hooks/mutations/useBorrowFunds'
|
||||||
|
import useTokenPrices from 'hooks/useTokenPrices'
|
||||||
|
import useMarkets from 'hooks/useMarkets'
|
||||||
|
import useCalculateMaxBorrowAmount from 'hooks/useCalculateMaxBorrowAmount'
|
||||||
|
import Tooltip from './Tooltip'
|
||||||
|
import { formatCurrency } from 'utils/formatters'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
show: boolean
|
||||||
|
onClose: () => void
|
||||||
|
tokenDenom: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const BorrowModal = ({ show, onClose, tokenDenom }: Props) => {
|
||||||
|
const [amount, setAmount] = useState(0)
|
||||||
|
const [isBorrowToCreditAccount, setIsBorrowToCreditAccount] = useState(false)
|
||||||
|
|
||||||
|
const tokenSymbol = getTokenSymbol(tokenDenom)
|
||||||
|
|
||||||
|
const { mutate, isLoading } = useBorrowFunds(
|
||||||
|
BigNumber(amount)
|
||||||
|
.times(10 ** getTokenDecimals(tokenDenom))
|
||||||
|
.toNumber(),
|
||||||
|
tokenDenom,
|
||||||
|
!isBorrowToCreditAccount,
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
onClose()
|
||||||
|
toast.success(`${amount} ${tokenSymbol} successfully Borrowed`)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const { data: tokenPrices } = useTokenPrices()
|
||||||
|
const { data: balancesData } = useAllBalances()
|
||||||
|
const { data: marketsData } = useMarkets()
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
mutate()
|
||||||
|
}
|
||||||
|
|
||||||
|
const walletAmount = useMemo(() => {
|
||||||
|
return BigNumber(balancesData?.find((balance) => balance.denom === tokenDenom)?.amount ?? 0)
|
||||||
|
.div(10 ** getTokenDecimals(tokenDenom))
|
||||||
|
.toNumber()
|
||||||
|
}, [balancesData, tokenDenom])
|
||||||
|
|
||||||
|
const tokenPrice = tokenPrices?.[tokenDenom] ?? 0
|
||||||
|
const borrowRate = Number(marketsData?.[tokenDenom]?.borrow_rate)
|
||||||
|
|
||||||
|
const maxValue = useCalculateMaxBorrowAmount(tokenDenom, isBorrowToCreditAccount)
|
||||||
|
|
||||||
|
const percentageValue = useMemo(() => {
|
||||||
|
if (isNaN(amount) || maxValue === 0) return 0
|
||||||
|
|
||||||
|
return (amount * 100) / maxValue
|
||||||
|
}, [amount, maxValue])
|
||||||
|
|
||||||
|
const handleValueChange = (value: number) => {
|
||||||
|
if (value > maxValue) {
|
||||||
|
setAmount(maxValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setAmount(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSliderValueChange = (value: number[]) => {
|
||||||
|
const decimal = value[0] / 100
|
||||||
|
const tokenDecimals = getTokenDecimals(tokenDenom)
|
||||||
|
// limit decimal precision based on token contract decimals
|
||||||
|
const newAmount = Number((decimal * maxValue).toFixed(tokenDecimals))
|
||||||
|
|
||||||
|
setAmount(newAmount)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBorrowTargetChange = () => {
|
||||||
|
setIsBorrowToCreditAccount((c) => !c)
|
||||||
|
// reset amount due to max value calculations changing depending on borrow target
|
||||||
|
setAmount(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 min-h-[520px] 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 flex-1 flex-col items-start justify-between bg-[#4A4C60] p-6">
|
||||||
|
<div>
|
||||||
|
<p className="text-bold mb-3 text-xs uppercase text-white/50">About</p>
|
||||||
|
<h4 className="mb-4 text-xl leading-8">
|
||||||
|
Bringing the next generation of video creation to the Metaverse.
|
||||||
|
<br />
|
||||||
|
Powered by deep-learning.
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<Image src="/logo.svg" alt="mars" width={150} height={50} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-1 flex-col p-4">
|
||||||
|
<Dialog.Title as="h3" className="mb-4 text-center font-medium">
|
||||||
|
Borrow {tokenSymbol}
|
||||||
|
</Dialog.Title>
|
||||||
|
<div className="mb-4 flex flex-col gap-2 text-sm">
|
||||||
|
<ContainerSecondary>
|
||||||
|
<p className="mb-1">
|
||||||
|
In wallet: {walletAmount.toLocaleString()} {tokenSymbol}
|
||||||
|
</p>
|
||||||
|
<p className="mb-5">Borrow Rate: {(borrowRate * 100).toFixed(2)}%</p>
|
||||||
|
|
||||||
|
<div className="mb-7">
|
||||||
|
<p className="mb-2 font-semibold uppercase tracking-widest">Amount</p>
|
||||||
|
<NumericFormat
|
||||||
|
className="mb-2 h-[32px] w-full rounded-lg border border-black/50 bg-transparent px-2"
|
||||||
|
value={amount}
|
||||||
|
placeholder="0"
|
||||||
|
allowNegative={false}
|
||||||
|
onValueChange={(v) => handleValueChange(v.floatValue || 0)}
|
||||||
|
suffix={` ${tokenSymbol}`}
|
||||||
|
decimalScale={getTokenDecimals(tokenDenom)}
|
||||||
|
/>
|
||||||
|
<div className="flex justify-between text-xs tracking-widest">
|
||||||
|
<div>
|
||||||
|
1 {tokenSymbol} = {formatCurrency(tokenPrice)}
|
||||||
|
</div>
|
||||||
|
<div>{formatCurrency(tokenPrice * amount)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Slider
|
||||||
|
className="mb-6"
|
||||||
|
value={percentageValue}
|
||||||
|
onChange={handleSliderValueChange}
|
||||||
|
onMaxClick={() => setAmount(maxValue)}
|
||||||
|
/>
|
||||||
|
</ContainerSecondary>
|
||||||
|
<ContainerSecondary className="flex items-center justify-between">
|
||||||
|
<div className="flex">
|
||||||
|
Borrow to Credit Account{' '}
|
||||||
|
<Tooltip
|
||||||
|
className="ml-2"
|
||||||
|
content={
|
||||||
|
<>
|
||||||
|
<p className="mb-2">
|
||||||
|
OFF = Borrow directly into your wallet by using your account Assets
|
||||||
|
as collateral. The borrowed asset will become a liability in your
|
||||||
|
account.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
ON = Borrow into your Account. The borrowed asset will be available
|
||||||
|
in the account as an Asset and appear also as a liability in your
|
||||||
|
account.
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={isBorrowToCreditAccount}
|
||||||
|
onChange={handleBorrowTargetChange}
|
||||||
|
className={`${
|
||||||
|
isBorrowToCreditAccount ? 'bg-blue-600' : 'bg-gray-400'
|
||||||
|
} relative inline-flex h-6 w-11 items-center rounded-full`}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={`${
|
||||||
|
isBorrowToCreditAccount ? '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 rounded-3xl"
|
||||||
|
onClick={handleSubmit}
|
||||||
|
disabled={amount === 0 || !amount}
|
||||||
|
>
|
||||||
|
Borrow
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Dialog.Panel>
|
||||||
|
</Transition.Child>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
</Transition>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BorrowModal
|
@ -12,7 +12,7 @@ import Button from './Button'
|
|||||||
import Spinner from './Spinner'
|
import Spinner from './Spinner'
|
||||||
import useAllBalances from 'hooks/useAllBalances'
|
import useAllBalances from 'hooks/useAllBalances'
|
||||||
import useAllowedCoins from 'hooks/useAllowedCoins'
|
import useAllowedCoins from 'hooks/useAllowedCoins'
|
||||||
import useDepositCreditAccount from 'hooks/useDepositCreditAccount'
|
import useDepositCreditAccount from 'hooks/mutations/useDepositCreditAccount'
|
||||||
import Slider from 'components/Slider'
|
import Slider from 'components/Slider'
|
||||||
|
|
||||||
const FundAccountModal = ({ show, onClose }: any) => {
|
const FundAccountModal = ({ show, onClose }: any) => {
|
||||||
@ -128,7 +128,7 @@ const FundAccountModal = ({ show, onClose }: any) => {
|
|||||||
<p>Loading...</p>
|
<p>Loading...</p>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="mb-2 rounded-md border border-[#585A74] text-sm">
|
<div className="mb-2 rounded-md border border-[#585A74] text-sm">
|
||||||
<div className="mb-1 flex justify-between border-b border-[#585A74] p-2">
|
<div className="mb-1 flex justify-between border-b border-[#585A74] p-2">
|
||||||
<div className="font-bold">Asset:</div>
|
<div className="font-bold">Asset:</div>
|
||||||
<select
|
<select
|
||||||
|
@ -10,8 +10,8 @@ import Spinner from 'components/Spinner'
|
|||||||
import Wallet from 'components/Wallet'
|
import Wallet from 'components/Wallet'
|
||||||
import { formatCurrency } from 'utils/formatters'
|
import { formatCurrency } from 'utils/formatters'
|
||||||
import useCreditAccounts from 'hooks/useCreditAccounts'
|
import useCreditAccounts from 'hooks/useCreditAccounts'
|
||||||
import useCreateCreditAccount from 'hooks/useCreateCreditAccount'
|
import useCreateCreditAccount from 'hooks/mutations/useCreateCreditAccount'
|
||||||
import useDeleteCreditAccount from 'hooks/useDeleteCreditAccount'
|
import useDeleteCreditAccount from 'hooks/mutations/useDeleteCreditAccount'
|
||||||
import useCreditManagerStore from 'stores/useCreditManagerStore'
|
import useCreditManagerStore from 'stores/useCreditManagerStore'
|
||||||
import useAccountStats from 'hooks/useAccountStats'
|
import useAccountStats from 'hooks/useAccountStats'
|
||||||
import SemiCircleProgress from './SemiCircleProgress'
|
import SemiCircleProgress from './SemiCircleProgress'
|
||||||
|
189
components/RepayModal.tsx
Normal file
189
components/RepayModal.tsx
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
import React, { useMemo, useState } from 'react'
|
||||||
|
import Image from 'next/image'
|
||||||
|
import { Transition, Dialog } from '@headlessui/react'
|
||||||
|
import BigNumber from 'bignumber.js'
|
||||||
|
import { toast } from 'react-toastify'
|
||||||
|
import { NumericFormat } from 'react-number-format'
|
||||||
|
|
||||||
|
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
|
||||||
|
import ContainerSecondary from './ContainerSecondary'
|
||||||
|
import useCreditManagerStore from 'stores/useCreditManagerStore'
|
||||||
|
import Button from './Button'
|
||||||
|
import Spinner from './Spinner'
|
||||||
|
import useAllBalances from 'hooks/useAllBalances'
|
||||||
|
import Slider from 'components/Slider'
|
||||||
|
import useCreditAccountPositions from 'hooks/useCreditAccountPositions'
|
||||||
|
import useRepayFunds from 'hooks/mutations/useRepayFunds'
|
||||||
|
import useTokenPrices from 'hooks/useTokenPrices'
|
||||||
|
import { formatCurrency } from 'utils/formatters'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
show: boolean
|
||||||
|
onClose: () => void
|
||||||
|
tokenDenom: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const RepayModal = ({ show, onClose, tokenDenom }: Props) => {
|
||||||
|
const [amount, setAmount] = useState(0)
|
||||||
|
|
||||||
|
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
|
||||||
|
const { data: positionsData } = useCreditAccountPositions(selectedAccount ?? '')
|
||||||
|
|
||||||
|
const tokenSymbol = getTokenSymbol(tokenDenom)
|
||||||
|
|
||||||
|
const maxRepayAmount = useMemo(() => {
|
||||||
|
const tokenDebtAmount =
|
||||||
|
positionsData?.debts.find((coin) => coin.denom === tokenDenom)?.amount ?? 0
|
||||||
|
|
||||||
|
return BigNumber(tokenDebtAmount)
|
||||||
|
.div(10 ** getTokenDecimals(tokenDenom))
|
||||||
|
.toNumber()
|
||||||
|
}, [positionsData, tokenDenom])
|
||||||
|
|
||||||
|
const { mutate, isLoading } = useRepayFunds(
|
||||||
|
BigNumber(amount)
|
||||||
|
.times(10 ** getTokenDecimals(tokenDenom))
|
||||||
|
.toNumber(),
|
||||||
|
tokenDenom,
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
onClose()
|
||||||
|
toast.success(`${amount} ${tokenSymbol} successfully repaid`)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const { data: tokenPrices } = useTokenPrices()
|
||||||
|
const { data: balancesData } = useAllBalances()
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
mutate()
|
||||||
|
}
|
||||||
|
|
||||||
|
const walletAmount = useMemo(() => {
|
||||||
|
return BigNumber(balancesData?.find((balance) => balance.denom === tokenDenom)?.amount ?? 0)
|
||||||
|
.div(10 ** getTokenDecimals(tokenDenom))
|
||||||
|
.toNumber()
|
||||||
|
}, [balancesData, tokenDenom])
|
||||||
|
|
||||||
|
const tokenPrice = tokenPrices?.[tokenDenom] ?? 0
|
||||||
|
|
||||||
|
const maxValue = walletAmount > maxRepayAmount ? maxRepayAmount : walletAmount
|
||||||
|
const percentageValue = isNaN(amount) ? 0 : (amount * 100) / maxValue
|
||||||
|
|
||||||
|
const handleValueChange = (value: number) => {
|
||||||
|
if (value > maxValue) {
|
||||||
|
setAmount(maxValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setAmount(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 min-h-[520px] 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 flex-1 flex-col items-start justify-between bg-[#4A4C60] p-6">
|
||||||
|
<div>
|
||||||
|
<p className="text-bold mb-3 text-xs uppercase text-white/50">About</p>
|
||||||
|
<h4 className="mb-4 text-xl leading-8">
|
||||||
|
Bringing the next generation of video creation to the Metaverse.
|
||||||
|
<br />
|
||||||
|
Powered by deep-learning.
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<Image src="/logo.svg" alt="mars" width={150} height={50} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-1 flex-col p-4">
|
||||||
|
<Dialog.Title as="h3" className="mb-4 text-center font-medium">
|
||||||
|
Repay {tokenSymbol}
|
||||||
|
</Dialog.Title>
|
||||||
|
<div className="mb-4 flex flex-col gap-2 text-sm">
|
||||||
|
<ContainerSecondary>
|
||||||
|
<p className="mb-7">
|
||||||
|
In wallet: {walletAmount.toLocaleString()} {tokenSymbol}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="mb-7">
|
||||||
|
<p className="mb-2 font-semibold uppercase tracking-widest">Amount</p>
|
||||||
|
<NumericFormat
|
||||||
|
className="mb-2 h-[32px] w-full rounded-lg border border-black/50 bg-transparent px-2"
|
||||||
|
value={amount}
|
||||||
|
placeholder="0"
|
||||||
|
allowNegative={false}
|
||||||
|
onValueChange={(v) => handleValueChange(v.floatValue || 0)}
|
||||||
|
suffix={` ${tokenSymbol}`}
|
||||||
|
decimalScale={getTokenDecimals(tokenDenom)}
|
||||||
|
/>
|
||||||
|
<div className="flex justify-between text-xs tracking-widest">
|
||||||
|
<div>
|
||||||
|
1 {tokenSymbol} = {formatCurrency(tokenPrice)}
|
||||||
|
</div>
|
||||||
|
<div>{formatCurrency(tokenPrice * amount)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Slider
|
||||||
|
className="mb-6"
|
||||||
|
value={percentageValue}
|
||||||
|
onChange={(value) => {
|
||||||
|
const decimal = value[0] / 100
|
||||||
|
const tokenDecimals = getTokenDecimals(tokenDenom)
|
||||||
|
// limit decimal precision based on token contract decimals
|
||||||
|
const newAmount = Number((decimal * maxValue).toFixed(tokenDecimals))
|
||||||
|
|
||||||
|
setAmount(newAmount)
|
||||||
|
}}
|
||||||
|
onMaxClick={() => setAmount(maxValue)}
|
||||||
|
/>
|
||||||
|
</ContainerSecondary>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
className="mt-auto w-full"
|
||||||
|
onClick={handleSubmit}
|
||||||
|
disabled={amount === 0 || !amount}
|
||||||
|
>
|
||||||
|
Repay
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Dialog.Panel>
|
||||||
|
</Transition.Child>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
</Transition>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RepayModal
|
@ -1,44 +1,26 @@
|
|||||||
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
|
||||||
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
|
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
|
||||||
import { useEffect, useMemo, useState } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import BigNumber from 'bignumber.js'
|
|
||||||
|
|
||||||
import useWalletStore from 'stores/useWalletStore'
|
import useWalletStore from 'stores/useWalletStore'
|
||||||
import { chain } from 'utils/chains'
|
|
||||||
import { contractAddresses } from 'config/contracts'
|
import { contractAddresses } from 'config/contracts'
|
||||||
import { hardcodedFee } from 'utils/contants'
|
import { hardcodedFee } from 'utils/contants'
|
||||||
import useCreditManagerStore from 'stores/useCreditManagerStore'
|
import useCreditManagerStore from 'stores/useCreditManagerStore'
|
||||||
import { queryKeys } from 'types/query-keys-factory'
|
import { queryKeys } from 'types/query-keys-factory'
|
||||||
import { getTokenDecimals } from 'utils/tokens'
|
|
||||||
|
|
||||||
const useBorrowFunds = (
|
const useBorrowFunds = (
|
||||||
amount: string | number,
|
amount: number,
|
||||||
denom: string,
|
denom: string,
|
||||||
withdraw = false,
|
withdraw = false,
|
||||||
options: Omit<UseMutationOptions, 'onError'>
|
options: Omit<UseMutationOptions, 'onError'>
|
||||||
) => {
|
) => {
|
||||||
const [signingClient, setSigningClient] = useState<SigningCosmWasmClient>()
|
const signingClient = useWalletStore((s) => s.signingClient)
|
||||||
|
|
||||||
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
|
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
|
||||||
const address = useWalletStore((s) => s.address)
|
const address = useWalletStore((s) => s.address)
|
||||||
|
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
;(async () => {
|
|
||||||
if (!window.keplr) return
|
|
||||||
|
|
||||||
const offlineSigner = window.keplr.getOfflineSigner(chain.chainId)
|
|
||||||
const clientInstance = await SigningCosmWasmClient.connectWithSigner(chain.rpc, offlineSigner)
|
|
||||||
|
|
||||||
setSigningClient(clientInstance)
|
|
||||||
})()
|
|
||||||
}, [address])
|
|
||||||
|
|
||||||
const executeMsg = useMemo(() => {
|
const executeMsg = useMemo(() => {
|
||||||
const tokenDecimals = getTokenDecimals(denom)
|
|
||||||
|
|
||||||
if (!withdraw) {
|
if (!withdraw) {
|
||||||
return {
|
return {
|
||||||
update_credit_account: {
|
update_credit_account: {
|
||||||
@ -47,9 +29,7 @@ const useBorrowFunds = (
|
|||||||
{
|
{
|
||||||
borrow: {
|
borrow: {
|
||||||
denom: denom,
|
denom: denom,
|
||||||
amount: BigNumber(amount)
|
amount: String(amount),
|
||||||
.times(10 ** tokenDecimals)
|
|
||||||
.toString(),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -64,17 +44,13 @@ const useBorrowFunds = (
|
|||||||
{
|
{
|
||||||
borrow: {
|
borrow: {
|
||||||
denom: denom,
|
denom: denom,
|
||||||
amount: BigNumber(amount)
|
amount: String(amount),
|
||||||
.times(10 ** tokenDecimals)
|
|
||||||
.toString(),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
withdraw: {
|
withdraw: {
|
||||||
denom: denom,
|
denom: denom,
|
||||||
amount: BigNumber(amount)
|
amount: String(amount),
|
||||||
.times(10 ** tokenDecimals)
|
|
||||||
.toString(),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
@ -1,10 +1,7 @@
|
|||||||
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
|
||||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
|
|
||||||
import useWalletStore from 'stores/useWalletStore'
|
import useWalletStore from 'stores/useWalletStore'
|
||||||
import { chain } from 'utils/chains'
|
|
||||||
import { contractAddresses } from 'config/contracts'
|
import { contractAddresses } from 'config/contracts'
|
||||||
import { hardcodedFee } from 'utils/contants'
|
import { hardcodedFee } from 'utils/contants'
|
||||||
import useCreditManagerStore from 'stores/useCreditManagerStore'
|
import useCreditManagerStore from 'stores/useCreditManagerStore'
|
||||||
@ -16,23 +13,12 @@ const executeMsg = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const useCreateCreditAccount = () => {
|
const useCreateCreditAccount = () => {
|
||||||
const [signingClient, setSigningClient] = useState<SigningCosmWasmClient>()
|
const signingClient = useWalletStore((s) => s.signingClient)
|
||||||
const setSelectedAccount = useCreditManagerStore((s) => s.actions.setSelectedAccount)
|
const setSelectedAccount = useCreditManagerStore((s) => s.actions.setSelectedAccount)
|
||||||
const address = useWalletStore((s) => s.address)
|
const address = useWalletStore((s) => s.address)
|
||||||
|
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
;(async () => {
|
|
||||||
if (!window.keplr) return
|
|
||||||
|
|
||||||
const offlineSigner = window.keplr.getOfflineSigner(chain.chainId)
|
|
||||||
const clientInstance = await SigningCosmWasmClient.connectWithSigner(chain.rpc, offlineSigner)
|
|
||||||
|
|
||||||
setSigningClient(clientInstance)
|
|
||||||
})()
|
|
||||||
}, [address])
|
|
||||||
|
|
||||||
return useMutation(
|
return useMutation(
|
||||||
async () =>
|
async () =>
|
||||||
await signingClient?.execute(
|
await signingClient?.execute(
|
@ -1,31 +1,17 @@
|
|||||||
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
|
||||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
|
|
||||||
import useWalletStore from 'stores/useWalletStore'
|
import useWalletStore from 'stores/useWalletStore'
|
||||||
import { chain } from 'utils/chains'
|
|
||||||
import { contractAddresses } from 'config/contracts'
|
import { contractAddresses } from 'config/contracts'
|
||||||
import { hardcodedFee } from 'utils/contants'
|
import { hardcodedFee } from 'utils/contants'
|
||||||
import { queryKeys } from 'types/query-keys-factory'
|
import { queryKeys } from 'types/query-keys-factory'
|
||||||
|
|
||||||
const useCreateCreditAccount = (accountId: string) => {
|
const useCreateCreditAccount = (accountId: string) => {
|
||||||
const [signingClient, setSigningClient] = useState<SigningCosmWasmClient>()
|
const signingClient = useWalletStore((s) => s.signingClient)
|
||||||
const address = useWalletStore((s) => s.address)
|
const address = useWalletStore((s) => s.address)
|
||||||
|
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
;(async () => {
|
|
||||||
if (!window.keplr) return
|
|
||||||
|
|
||||||
const offlineSigner = window.keplr.getOfflineSigner(chain.chainId)
|
|
||||||
const clientInstance = await SigningCosmWasmClient.connectWithSigner(chain.rpc, offlineSigner)
|
|
||||||
|
|
||||||
setSigningClient(clientInstance)
|
|
||||||
})()
|
|
||||||
}, [address])
|
|
||||||
|
|
||||||
return useMutation(
|
return useMutation(
|
||||||
async () =>
|
async () =>
|
||||||
await signingClient?.execute(
|
await signingClient?.execute(
|
@ -1,10 +1,7 @@
|
|||||||
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||||
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
|
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
|
|
||||||
import useWalletStore from 'stores/useWalletStore'
|
import useWalletStore from 'stores/useWalletStore'
|
||||||
import { chain } from 'utils/chains'
|
|
||||||
import { contractAddresses } from 'config/contracts'
|
import { contractAddresses } from 'config/contracts'
|
||||||
import { hardcodedFee } from 'utils/contants'
|
import { hardcodedFee } from 'utils/contants'
|
||||||
import { queryKeys } from 'types/query-keys-factory'
|
import { queryKeys } from 'types/query-keys-factory'
|
||||||
@ -17,22 +14,11 @@ const useDepositCreditAccount = (
|
|||||||
onSuccess?: () => void
|
onSuccess?: () => void
|
||||||
}
|
}
|
||||||
) => {
|
) => {
|
||||||
const [signingClient, setSigningClient] = useState<SigningCosmWasmClient>()
|
const signingClient = useWalletStore((s) => s.signingClient)
|
||||||
const address = useWalletStore((s) => s.address)
|
const address = useWalletStore((s) => s.address)
|
||||||
|
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
;(async () => {
|
|
||||||
if (!window.keplr) return
|
|
||||||
|
|
||||||
const offlineSigner = window.keplr.getOfflineSigner(chain.chainId)
|
|
||||||
const clientInstance = await SigningCosmWasmClient.connectWithSigner(chain.rpc, offlineSigner)
|
|
||||||
|
|
||||||
setSigningClient(clientInstance)
|
|
||||||
})()
|
|
||||||
}, [address])
|
|
||||||
|
|
||||||
return useMutation(
|
return useMutation(
|
||||||
async () =>
|
async () =>
|
||||||
await signingClient?.execute(
|
await signingClient?.execute(
|
@ -1,41 +1,30 @@
|
|||||||
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
|
||||||
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
|
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
|
||||||
import { useEffect, useMemo, useState } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import BigNumber from 'bignumber.js'
|
import BigNumber from 'bignumber.js'
|
||||||
|
|
||||||
import useWalletStore from 'stores/useWalletStore'
|
import useWalletStore from 'stores/useWalletStore'
|
||||||
import { chain } from 'utils/chains'
|
|
||||||
import { contractAddresses } from 'config/contracts'
|
import { contractAddresses } from 'config/contracts'
|
||||||
import { hardcodedFee } from 'utils/contants'
|
import { hardcodedFee } from 'utils/contants'
|
||||||
import useCreditManagerStore from 'stores/useCreditManagerStore'
|
import useCreditManagerStore from 'stores/useCreditManagerStore'
|
||||||
import { queryKeys } from 'types/query-keys-factory'
|
import { queryKeys } from 'types/query-keys-factory'
|
||||||
import { getTokenDecimals } from 'utils/tokens'
|
import { getTokenDecimals } from 'utils/tokens'
|
||||||
|
|
||||||
|
// 0.001% buffer / slippage to avoid repay action from not fully repaying the debt amount
|
||||||
|
const REPAY_BUFFER = 1.00001
|
||||||
|
|
||||||
const useRepayFunds = (
|
const useRepayFunds = (
|
||||||
amount: string | number,
|
amount: number,
|
||||||
denom: string,
|
denom: string,
|
||||||
options: Omit<UseMutationOptions, 'onError'>
|
options: Omit<UseMutationOptions, 'onError'>
|
||||||
) => {
|
) => {
|
||||||
const [signingClient, setSigningClient] = useState<SigningCosmWasmClient>()
|
const signingClient = useWalletStore((s) => s.signingClient)
|
||||||
|
|
||||||
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
|
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
|
||||||
const address = useWalletStore((s) => s.address)
|
const address = useWalletStore((s) => s.address)
|
||||||
|
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
useEffect(() => {
|
const adjustedAmount = BigNumber(amount).times(REPAY_BUFFER).decimalPlaces(0).toString()
|
||||||
;(async () => {
|
|
||||||
if (!window.keplr) return
|
|
||||||
|
|
||||||
const offlineSigner = window.keplr.getOfflineSigner(chain.chainId)
|
|
||||||
const clientInstance = await SigningCosmWasmClient.connectWithSigner(chain.rpc, offlineSigner)
|
|
||||||
|
|
||||||
setSigningClient(clientInstance)
|
|
||||||
})()
|
|
||||||
}, [address])
|
|
||||||
|
|
||||||
const tokenDecimals = getTokenDecimals(denom)
|
|
||||||
|
|
||||||
const executeMsg = useMemo(() => {
|
const executeMsg = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
@ -45,23 +34,19 @@ const useRepayFunds = (
|
|||||||
{
|
{
|
||||||
deposit: {
|
deposit: {
|
||||||
denom: denom,
|
denom: denom,
|
||||||
amount: BigNumber(amount)
|
amount: adjustedAmount,
|
||||||
.times(10 ** tokenDecimals)
|
|
||||||
.toString(),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
repay: {
|
repay: {
|
||||||
denom: denom,
|
denom: denom,
|
||||||
amount: BigNumber(amount)
|
amount: adjustedAmount,
|
||||||
.times(10 ** tokenDecimals)
|
|
||||||
.toString(),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}, [amount, denom, selectedAccount, tokenDecimals])
|
}, [adjustedAmount, denom, selectedAccount])
|
||||||
|
|
||||||
return useMutation(
|
return useMutation(
|
||||||
async () =>
|
async () =>
|
||||||
@ -74,9 +59,7 @@ const useRepayFunds = (
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
denom,
|
denom,
|
||||||
amount: BigNumber(amount)
|
amount: adjustedAmount,
|
||||||
.times(10 ** tokenDecimals)
|
|
||||||
.toString(),
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
),
|
),
|
@ -38,6 +38,7 @@ const useCreditAccountPositions = (accountId: string) => {
|
|||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
enabled: !!address && !!client,
|
enabled: !!address && !!client,
|
||||||
|
refetchInterval: 30000,
|
||||||
staleTime: Infinity,
|
staleTime: Infinity,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
"next": "12.3.1",
|
"next": "12.3.1",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
|
"react-number-format": "^5.1.0",
|
||||||
"react-toastify": "^9.0.8",
|
"react-toastify": "^9.0.8",
|
||||||
"use-local-storage-state": "^18.1.1",
|
"use-local-storage-state": "^18.1.1",
|
||||||
"zustand": "^4.1.1"
|
"zustand": "^4.1.1"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useMemo, useState } from 'react'
|
import React, { useMemo, useRef, useState } from 'react'
|
||||||
import BigNumber from 'bignumber.js'
|
import BigNumber from 'bignumber.js'
|
||||||
|
|
||||||
import Container from 'components/Container'
|
import Container from 'components/Container'
|
||||||
@ -8,27 +8,23 @@ import useCreditAccountPositions from 'hooks/useCreditAccountPositions'
|
|||||||
import useCreditManagerStore from 'stores/useCreditManagerStore'
|
import useCreditManagerStore from 'stores/useCreditManagerStore'
|
||||||
import useMarkets from 'hooks/useMarkets'
|
import useMarkets from 'hooks/useMarkets'
|
||||||
import useTokenPrices from 'hooks/useTokenPrices'
|
import useTokenPrices from 'hooks/useTokenPrices'
|
||||||
import { BorrowFunds, RepayFunds } from 'components/Borrow'
|
import BorrowModal from 'components/BorrowModal'
|
||||||
|
import RepayModal from 'components/RepayModal'
|
||||||
import BorrowTable from 'components/Borrow/BorrowTable'
|
import BorrowTable from 'components/Borrow/BorrowTable'
|
||||||
import useRedbankBalances from 'hooks/useRedbankBalances'
|
import useRedbankBalances from 'hooks/useRedbankBalances'
|
||||||
|
|
||||||
type ModuleState =
|
type ModalState = {
|
||||||
| {
|
show: 'borrow' | 'repay' | false
|
||||||
show: 'borrow'
|
data: {
|
||||||
data: {
|
tokenDenom: string
|
||||||
tokenDenom: string
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
| {
|
|
||||||
show: 'repay'
|
|
||||||
data: {
|
|
||||||
tokenDenom: string
|
|
||||||
amount: number
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const Borrow = () => {
|
const Borrow = () => {
|
||||||
const [moduleState, setModuleState] = useState<ModuleState | null>(null)
|
const [modalState, setModalState] = useState<ModalState>({
|
||||||
|
show: false,
|
||||||
|
data: { tokenDenom: '' },
|
||||||
|
})
|
||||||
|
|
||||||
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
|
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
|
||||||
|
|
||||||
@ -38,6 +34,9 @@ const Borrow = () => {
|
|||||||
const { data: tokenPrices } = useTokenPrices()
|
const { data: tokenPrices } = useTokenPrices()
|
||||||
const { data: redbankBalances } = useRedbankBalances()
|
const { data: redbankBalances } = useRedbankBalances()
|
||||||
|
|
||||||
|
// recreate modals and reset state whenever ref changes
|
||||||
|
const modalId = useRef(0)
|
||||||
|
|
||||||
const borrowedAssetsMap = useMemo(() => {
|
const borrowedAssetsMap = useMemo(() => {
|
||||||
let borrowedAssetsMap: Map<string, string> = new Map()
|
let borrowedAssetsMap: Map<string, string> = new Map()
|
||||||
|
|
||||||
@ -106,27 +105,31 @@ const Borrow = () => {
|
|||||||
}, [allowedCoinsData, borrowedAssetsMap, marketsData, redbankBalances, tokenPrices])
|
}, [allowedCoinsData, borrowedAssetsMap, marketsData, redbankBalances, tokenPrices])
|
||||||
|
|
||||||
const handleBorrowClick = (denom: string) => {
|
const handleBorrowClick = (denom: string) => {
|
||||||
setModuleState({ show: 'borrow', data: { tokenDenom: denom } })
|
setModalState({ show: 'borrow', data: { tokenDenom: denom } })
|
||||||
|
modalId.current += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleRepayClick = (denom: string, repayAmount: number) => {
|
const handleRepayClick = (denom: string) => {
|
||||||
setModuleState({ show: 'repay', data: { tokenDenom: denom, amount: repayAmount } })
|
setModalState({ show: 'repay', data: { tokenDenom: denom } })
|
||||||
|
modalId.current += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<Container>
|
<Container className="mb-4">
|
||||||
<div className="mb-5">
|
<div>
|
||||||
<h3 className="mb-1 text-center font-medium uppercase">Borrowed</h3>
|
<h3 className="mb-7 text-center text-xl font-medium uppercase">Borrowings</h3>
|
||||||
<BorrowTable
|
<BorrowTable
|
||||||
data={borrowedAssets}
|
data={borrowedAssets}
|
||||||
onBorrowClick={handleBorrowClick}
|
onBorrowClick={handleBorrowClick}
|
||||||
onRepayClick={handleRepayClick}
|
onRepayClick={handleRepayClick}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</Container>
|
||||||
|
<Container>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="mb-1 text-center font-medium uppercase">Not Borrowed Yet</h3>
|
<h3 className="mb-7 text-center text-xl font-medium uppercase">Available to Borrow</h3>
|
||||||
<BorrowTable
|
<BorrowTable
|
||||||
data={notBorrowedAssets}
|
data={notBorrowedAssets}
|
||||||
onBorrowClick={handleBorrowClick}
|
onBorrowClick={handleBorrowClick}
|
||||||
@ -135,20 +138,18 @@ const Borrow = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
{moduleState?.show === 'borrow' && (
|
<BorrowModal
|
||||||
<BorrowFunds
|
key={`borrowModal_${modalId.current}`}
|
||||||
key={`borrow_${selectedAccount}_${moduleState.data.tokenDenom}`}
|
tokenDenom={modalState.data.tokenDenom}
|
||||||
{...moduleState.data}
|
show={modalState.show === 'borrow'}
|
||||||
onClose={() => setModuleState(null)}
|
onClose={() => setModalState({ ...modalState, show: false })}
|
||||||
/>
|
/>
|
||||||
)}
|
<RepayModal
|
||||||
{moduleState?.show === 'repay' && (
|
key={`repayModal${modalId.current}`}
|
||||||
<RepayFunds
|
tokenDenom={modalState.data.tokenDenom}
|
||||||
key={`repay_${selectedAccount}_${moduleState.data.tokenDenom}`}
|
show={modalState.show === 'repay'}
|
||||||
{...moduleState.data}
|
onClose={() => setModalState({ ...modalState, show: false })}
|
||||||
onClose={() => setModuleState(null)}
|
/>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import create from 'zustand'
|
|||||||
import { persist } from 'zustand/middleware'
|
import { persist } from 'zustand/middleware'
|
||||||
|
|
||||||
import { Wallet } from 'types'
|
import { Wallet } from 'types'
|
||||||
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
import { CosmWasmClient, SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
||||||
import { chain } from 'utils/chains'
|
import { chain } from 'utils/chains'
|
||||||
|
|
||||||
interface WalletStore {
|
interface WalletStore {
|
||||||
@ -10,6 +10,7 @@ interface WalletStore {
|
|||||||
metamaskInstalled: boolean
|
metamaskInstalled: boolean
|
||||||
wallet: Wallet | null
|
wallet: Wallet | null
|
||||||
client?: CosmWasmClient
|
client?: CosmWasmClient
|
||||||
|
signingClient?: SigningCosmWasmClient
|
||||||
actions: {
|
actions: {
|
||||||
disconnect: () => void
|
disconnect: () => void
|
||||||
initialize: () => void
|
initialize: () => void
|
||||||
@ -26,20 +27,42 @@ const useWalletStore = create<WalletStore>()(
|
|||||||
wallet: null,
|
wallet: null,
|
||||||
actions: {
|
actions: {
|
||||||
disconnect: () => {
|
disconnect: () => {
|
||||||
set(() => ({ address: '', wallet: null }))
|
set(() => ({ address: '', wallet: null, signingClient: undefined }))
|
||||||
},
|
},
|
||||||
initialize: async () => {
|
initialize: async () => {
|
||||||
const clientInstance = await CosmWasmClient.connect(chain.rpc)
|
const clientInstance = await CosmWasmClient.connect(chain.rpc)
|
||||||
let address = ''
|
|
||||||
|
|
||||||
if (get().wallet === Wallet.Keplr && window.keplr) {
|
if (get().wallet === Wallet.Keplr && window.keplr) {
|
||||||
const key = await window.keplr.getKey(chain.chainId)
|
const key = await window.keplr.getKey(chain.chainId)
|
||||||
address = key.bech32Address
|
const offlineSigner = window.keplr.getOfflineSigner(chain.chainId)
|
||||||
|
|
||||||
|
const address = key.bech32Address
|
||||||
|
const signingClientInstance = await SigningCosmWasmClient.connectWithSigner(
|
||||||
|
chain.rpc,
|
||||||
|
offlineSigner
|
||||||
|
)
|
||||||
|
|
||||||
|
set(() => ({
|
||||||
|
client: clientInstance,
|
||||||
|
signingClient: signingClientInstance,
|
||||||
|
address,
|
||||||
|
}))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
set(() => ({ client: clientInstance, address }))
|
set(() => ({ client: clientInstance }))
|
||||||
|
},
|
||||||
|
connect: async (address: string, wallet: Wallet) => {
|
||||||
|
if (!window.keplr) return
|
||||||
|
|
||||||
|
const offlineSigner = window.keplr.getOfflineSigner(chain.chainId)
|
||||||
|
const clientInstance = await SigningCosmWasmClient.connectWithSigner(
|
||||||
|
chain.rpc,
|
||||||
|
offlineSigner
|
||||||
|
)
|
||||||
|
|
||||||
|
set(() => ({ address, wallet, signingClient: clientInstance }))
|
||||||
},
|
},
|
||||||
connect: (address: string, wallet: Wallet) => set(() => ({ address, wallet })),
|
|
||||||
setMetamaskInstalledStatus: (value: boolean) => set(() => ({ metamaskInstalled: value })),
|
setMetamaskInstalledStatus: (value: boolean) => set(() => ({ metamaskInstalled: value })),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
@ -5320,7 +5320,7 @@ progress@^2.0.3:
|
|||||||
resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz"
|
resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz"
|
||||||
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
|
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
|
||||||
|
|
||||||
prop-types@^15.8.1:
|
prop-types@^15.7.2, prop-types@^15.8.1:
|
||||||
version "15.8.1"
|
version "15.8.1"
|
||||||
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz"
|
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz"
|
||||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||||
@ -5407,6 +5407,13 @@ react-is@^16.13.1, react-is@^16.7.0:
|
|||||||
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
|
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
|
||||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||||
|
|
||||||
|
react-number-format@^5.1.0:
|
||||||
|
version "5.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-number-format/-/react-number-format-5.1.0.tgz#64f3d760395df4d34b09fb6bf2d483e1a2ea50d3"
|
||||||
|
integrity sha512-zAJokH+4czPhvXbvWWQvWoa0/HNMvGGGT09xBgfoiXYnsJYWqxvHfAl0eDhIZ+BJQ5ssiHZK8OW54fj2Dc8HMQ==
|
||||||
|
dependencies:
|
||||||
|
prop-types "^15.7.2"
|
||||||
|
|
||||||
react-toastify@^9.0.8:
|
react-toastify@^9.0.8:
|
||||||
version "9.0.8"
|
version "9.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-9.0.8.tgz#3876c89fc6211a29027b3075010b5ec39ebe4f7e"
|
resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-9.0.8.tgz#3876c89fc6211a29027b3075010b5ec39ebe4f7e"
|
||||||
|
Loading…
Reference in New Issue
Block a user