Mp 3104 delete account (#336)
* MP-3104: added deleteAccount and alerts for vaults and debts * MP-3104: added Borrow and Farm links to the AlertDialog * MP-3104: finished delete flow * fix: adjusted according to feedback * refactor: tidy
This commit is contained in:
parent
69dece2600
commit
1a55d8bd39
@ -56,7 +56,7 @@ export default function AccountComposition(props: Props) {
|
||||
className='pb-3'
|
||||
/>
|
||||
<Item
|
||||
title='Total Liabilities'
|
||||
title='Total Debt'
|
||||
current={debtBalance}
|
||||
change={debtBalance.plus(debtBalanceChange)}
|
||||
className='pb-3'
|
||||
|
@ -1,5 +1,5 @@
|
||||
import classNames from 'classnames'
|
||||
import { useEffect } from 'react'
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import { useLocation, useNavigate, useParams } from 'react-router-dom'
|
||||
|
||||
import AccountFundFirst from 'components/Account/AccountFund'
|
||||
@ -11,10 +11,10 @@ import Radio from 'components/Radio'
|
||||
import SwitchWithLabel from 'components/SwitchWithLabel'
|
||||
import Text from 'components/Text'
|
||||
import useAutoLendEnabledAccountIds from 'hooks/useAutoLendEnabledAccountIds'
|
||||
import useCurrentAccount from 'hooks/useCurrentAccount'
|
||||
import usePrices from 'hooks/usePrices'
|
||||
import useStore from 'store'
|
||||
import { calculateAccountDepositsValue } from 'utils/accounts'
|
||||
import { hardcodedFee } from 'utils/constants'
|
||||
import { getPage, getRoute } from 'utils/route'
|
||||
|
||||
interface Props {
|
||||
@ -30,21 +30,19 @@ const accountCardHeaderClasses = classNames(
|
||||
export default function AccountList(props: Props) {
|
||||
const navigate = useNavigate()
|
||||
const { pathname } = useLocation()
|
||||
const { accountId, address } = useParams()
|
||||
const { address } = useParams()
|
||||
const { data: prices } = usePrices()
|
||||
const { autoLendEnabledAccountIds, toggleAutoLend } = useAutoLendEnabledAccountIds()
|
||||
const deleteAccount = useStore((s) => s.deleteAccount)
|
||||
const accountSelected = !!accountId && !isNaN(Number(accountId))
|
||||
const account = useCurrentAccount()
|
||||
const accountId = account?.id
|
||||
|
||||
async function deleteAccountHandler() {
|
||||
if (!accountSelected) return
|
||||
const isSuccess = await deleteAccount({ fee: hardcodedFee, accountId: accountId })
|
||||
if (isSuccess) {
|
||||
navigate(getRoute(getPage(pathname), address))
|
||||
}
|
||||
}
|
||||
const deleteAccountHandler = useCallback(() => {
|
||||
if (!account) return
|
||||
useStore.setState({ accountDeleteModal: account })
|
||||
}, [account])
|
||||
|
||||
useEffect(() => {
|
||||
if (!accountId) return
|
||||
const element = document.getElementById(`account-${accountId}`)
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth' })
|
||||
@ -69,6 +67,7 @@ export default function AccountList(props: Props) {
|
||||
contentClassName='bg-white/10 group-hover/account:bg-white/20'
|
||||
onClick={() => {
|
||||
if (isActive) return
|
||||
useStore.setState({ accountDeleteModal: null })
|
||||
navigate(getRoute(getPage(pathname), address, account.id))
|
||||
}}
|
||||
title={
|
||||
|
34
src/components/AssetBalanceRow.tsx
Normal file
34
src/components/AssetBalanceRow.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import AssetImage from 'components/AssetImage'
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import Text from 'components/Text'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { demagnify } from 'utils/formatters'
|
||||
|
||||
interface Props {
|
||||
asset: Asset
|
||||
coin: BNCoin
|
||||
}
|
||||
|
||||
export default function AssetBalanceRow(props: Props) {
|
||||
return (
|
||||
<div className='flex w-full items-center gap-4'>
|
||||
<AssetImage asset={props.asset} size={32} />
|
||||
<div className='flex flex-1 flex-wrap'>
|
||||
<Text className='w-full'>{props.asset.symbol}</Text>
|
||||
<Text size='sm' className='w-full text-white/50'>
|
||||
{props.asset.name}
|
||||
</Text>
|
||||
</div>
|
||||
<div className='flex flex-wrap'>
|
||||
<DisplayCurrency coin={props.coin} className='w-full text-right' />
|
||||
<FormattedNumber
|
||||
amount={demagnify(props.coin.amount, props.asset)}
|
||||
className='w-full text-right text-sm text-white/50'
|
||||
options={{ suffix: ` ${props.asset.symbol}` }}
|
||||
animate
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import classNames from 'classnames'
|
||||
import React, { LegacyRef, ReactElement, ReactNode, useMemo } from 'react'
|
||||
import React, { LegacyRef, useMemo } from 'react'
|
||||
|
||||
import {
|
||||
buttonBorderClasses,
|
||||
@ -22,6 +22,7 @@ import useLocalStorage from 'hooks/useLocalStorage'
|
||||
|
||||
const Button = React.forwardRef(function Button(
|
||||
{
|
||||
autoFocus,
|
||||
children,
|
||||
className = '',
|
||||
color = 'primary',
|
||||
@ -93,6 +94,7 @@ const Button = React.forwardRef(function Button(
|
||||
ref={ref as LegacyRef<HTMLButtonElement>}
|
||||
onClick={isDisabled ? () => {} : onClick}
|
||||
tabIndex={tabIndex}
|
||||
autoFocus={autoFocus}
|
||||
>
|
||||
{showProgressIndicator ? (
|
||||
<CircularProgress size={circularProgressSize[size]} />
|
||||
|
@ -14,29 +14,25 @@ export default function Tab(props: Props) {
|
||||
const { address, accountId } = useParams()
|
||||
|
||||
return (
|
||||
<div className='mb-4 w-full'>
|
||||
<div className='flex gap-2'>
|
||||
<div className='relative'>
|
||||
<NavLink
|
||||
to={getRoute('farm', address, accountId)}
|
||||
className={classNames(
|
||||
!props.isFarm ? 'text-white/20' : underlineClasses,
|
||||
'relative mr-8 text-xl',
|
||||
)}
|
||||
>
|
||||
Farm
|
||||
</NavLink>
|
||||
</div>
|
||||
<NavLink
|
||||
to={getRoute('lend', address, accountId)}
|
||||
className={classNames(
|
||||
props.isFarm ? 'text-white/20' : underlineClasses,
|
||||
'relative text-xl',
|
||||
)}
|
||||
>
|
||||
Lend
|
||||
</NavLink>
|
||||
</div>
|
||||
<div className='relative mb-4 w-full'>
|
||||
<NavLink
|
||||
to={getRoute('lend', address, accountId)}
|
||||
className={classNames(
|
||||
props.isFarm ? 'text-white/20' : underlineClasses,
|
||||
'relative mr-8 text-xl ',
|
||||
)}
|
||||
>
|
||||
Lend
|
||||
</NavLink>
|
||||
<NavLink
|
||||
to={getRoute('farm', address, accountId)}
|
||||
className={classNames(
|
||||
!props.isFarm ? 'text-white/20' : underlineClasses,
|
||||
'relative text-xl',
|
||||
)}
|
||||
>
|
||||
Farm
|
||||
</NavLink>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import useStore from 'store'
|
||||
|
||||
export const menuTree: { page: Page; label: string }[] = [
|
||||
{ page: 'trade', label: 'Trade' },
|
||||
{ page: 'farm', label: 'Earn' },
|
||||
{ page: 'lend', label: 'Earn' },
|
||||
{ page: 'borrow', label: 'Borrow' },
|
||||
{ page: 'portfolio', label: 'Portfolio' },
|
||||
{ page: 'council', label: 'Council' },
|
||||
|
@ -33,11 +33,16 @@ export default function MarketDetails({ data, type }: Props) {
|
||||
const totalBorrowed = marketDepositAmount.minus(marketLiquidityAmount)
|
||||
|
||||
const details: Detail[] = useMemo(() => {
|
||||
const isDollar = displayCurrencySymbol === '$'
|
||||
function getLendingMarketDetails() {
|
||||
return [
|
||||
{
|
||||
amount: convertAmount(asset, marketDepositAmount).toNumber(),
|
||||
options: { abbreviated: true, suffix: ` ${displayCurrencySymbol}` },
|
||||
options: {
|
||||
abbreviated: true,
|
||||
suffix: isDollar ? undefined : ` ${displayCurrencySymbol}`,
|
||||
prefix: isDollar ? '$' : undefined,
|
||||
},
|
||||
title: 'Total Supplied',
|
||||
},
|
||||
{
|
||||
@ -52,7 +57,12 @@ export default function MarketDetails({ data, type }: Props) {
|
||||
},
|
||||
{
|
||||
amount: getConversionRate(asset.denom).toNumber(),
|
||||
options: { minDecimals: 2, maxDecimals: 2, suffix: ` ${displayCurrencySymbol}` },
|
||||
options: {
|
||||
minDecimals: 2,
|
||||
maxDecimals: 2,
|
||||
suffix: isDollar ? undefined : ` ${displayCurrencySymbol}`,
|
||||
prefix: isDollar ? '$' : undefined,
|
||||
},
|
||||
title: 'Oracle Price',
|
||||
},
|
||||
{
|
||||
@ -67,12 +77,21 @@ export default function MarketDetails({ data, type }: Props) {
|
||||
return [
|
||||
{
|
||||
amount: convertAmount(asset, totalBorrowed).toNumber(),
|
||||
options: { abbreviated: true, suffix: ` ${displayCurrencySymbol}` },
|
||||
options: {
|
||||
abbreviated: true,
|
||||
suffix: isDollar ? undefined : ` ${displayCurrencySymbol}`,
|
||||
prefix: isDollar ? '$' : undefined,
|
||||
},
|
||||
title: 'Total Borrowed',
|
||||
},
|
||||
{
|
||||
amount: getConversionRate(asset.denom).toNumber(),
|
||||
options: { minDecimals: 2, maxDecimals: 2, suffix: ` ${displayCurrencySymbol}` },
|
||||
options: {
|
||||
minDecimals: 2,
|
||||
maxDecimals: 2,
|
||||
suffix: isDollar ? undefined : ` ${displayCurrencySymbol}`,
|
||||
prefix: isDollar ? '$' : undefined,
|
||||
},
|
||||
title: 'Oracle Price',
|
||||
},
|
||||
{
|
||||
|
36
src/components/Modals/Account/AccountDeleteAlertDialog.tsx
Normal file
36
src/components/Modals/Account/AccountDeleteAlertDialog.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import { useEffect } from 'react'
|
||||
|
||||
import { Enter, InfoCircle } from 'components/Icons'
|
||||
import useAlertDialog from 'hooks/useAlertDialog'
|
||||
|
||||
interface Props {
|
||||
title: string
|
||||
description: string
|
||||
closeHandler: () => void
|
||||
positiveButton: AlertDialogButton
|
||||
}
|
||||
|
||||
export default function AccoundDeleteAlertDialog(props: Props) {
|
||||
const { open: showAlertDialog } = useAlertDialog()
|
||||
const { title, description, closeHandler, positiveButton } = props
|
||||
|
||||
useEffect(() => {
|
||||
showAlertDialog({
|
||||
icon: (
|
||||
<div className='flex h-full w-full p-3'>
|
||||
<InfoCircle />
|
||||
</div>
|
||||
),
|
||||
title,
|
||||
description,
|
||||
negativeButton: {
|
||||
text: 'Close',
|
||||
icon: <Enter />,
|
||||
onClick: closeHandler,
|
||||
},
|
||||
positiveButton,
|
||||
})
|
||||
}, [showAlertDialog, title, description, closeHandler, positiveButton])
|
||||
|
||||
return null
|
||||
}
|
144
src/components/Modals/Account/AccountDeleteModal.tsx
Normal file
144
src/components/Modals/Account/AccountDeleteModal.tsx
Normal file
@ -0,0 +1,144 @@
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { useLocation, useNavigate, useParams } from 'react-router-dom'
|
||||
|
||||
import AssetBalanceRow from 'components/AssetBalanceRow'
|
||||
import Button from 'components/Button'
|
||||
import { ArrowRight } from 'components/Icons'
|
||||
import Modal from 'components/Modal'
|
||||
import AccoundDeleteAlertDialog from 'components/Modals/Account/AccountDeleteAlertDialog'
|
||||
import Text from 'components/Text'
|
||||
import useStore from 'store'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { getAssetByDenom } from 'utils/assets'
|
||||
import { hardcodedFee } from 'utils/constants'
|
||||
import { combineBNCoins } from 'utils/parsers'
|
||||
import { getPage, getRoute } from 'utils/route'
|
||||
|
||||
interface Props {
|
||||
modal: Account
|
||||
}
|
||||
|
||||
export default function AccountDeleteController() {
|
||||
const modal = useStore((s) => s.accountDeleteModal)
|
||||
|
||||
if (!modal) return null
|
||||
|
||||
return <AccountDeleteModal modal={modal} />
|
||||
}
|
||||
|
||||
function AccountDeleteModal(props: Props) {
|
||||
const modal = props.modal
|
||||
const deleteAccount = useStore((s) => s.deleteAccount)
|
||||
const navigate = useNavigate()
|
||||
const { pathname } = useLocation()
|
||||
const { address } = useParams()
|
||||
const { debts, vaults, id: accountId } = modal || {}
|
||||
const [isConfirming, setIsConfirming] = useState(false)
|
||||
|
||||
const closeDeleteAccountModal = useCallback(() => {
|
||||
useStore.setState({ accountDeleteModal: null })
|
||||
}, [])
|
||||
|
||||
const deleteAccountHandler = useCallback(async () => {
|
||||
setIsConfirming(true)
|
||||
const options = { fee: hardcodedFee, accountId: modal.id, lends: modal.lends }
|
||||
const isSuccess = await deleteAccount(options)
|
||||
setIsConfirming(false)
|
||||
if (isSuccess) {
|
||||
navigate(getRoute(getPage(pathname), address))
|
||||
closeDeleteAccountModal()
|
||||
}
|
||||
}, [modal, deleteAccount, navigate, pathname, address, closeDeleteAccountModal])
|
||||
|
||||
const depositsAndLends = useMemo(
|
||||
() => combineBNCoins([...modal.deposits, ...modal.lends]),
|
||||
[modal],
|
||||
)
|
||||
|
||||
if (debts.length > 0)
|
||||
return (
|
||||
<AccoundDeleteAlertDialog
|
||||
title='Repay your Debts to delete your account'
|
||||
description='You must repay all borrowings before deleting your account.'
|
||||
closeHandler={closeDeleteAccountModal}
|
||||
positiveButton={{
|
||||
text: 'Repay Debts',
|
||||
icon: <ArrowRight />,
|
||||
onClick: () => {
|
||||
navigate(getRoute('borrow', address, accountId))
|
||||
closeDeleteAccountModal()
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
if (vaults.length > 0)
|
||||
return (
|
||||
<AccoundDeleteAlertDialog
|
||||
title='Close your positions to delete your account'
|
||||
description='You must first close your farming positions before deleting your account.'
|
||||
closeHandler={closeDeleteAccountModal}
|
||||
positiveButton={{
|
||||
text: 'Close Positions',
|
||||
icon: <ArrowRight />,
|
||||
onClick: () => {
|
||||
navigate(getRoute('farm', address, accountId))
|
||||
closeDeleteAccountModal()
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
if (depositsAndLends.length === 0)
|
||||
return (
|
||||
<AccoundDeleteAlertDialog
|
||||
title={`Delete Credit Account ${accountId}`}
|
||||
description='Deleting your credit account is irreversible.'
|
||||
closeHandler={closeDeleteAccountModal}
|
||||
positiveButton={{
|
||||
text: 'Close Positions',
|
||||
icon: <ArrowRight />,
|
||||
isAsync: true,
|
||||
onClick: deleteAccountHandler,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
return (
|
||||
<Modal
|
||||
onClose={closeDeleteAccountModal}
|
||||
header={
|
||||
<span className='flex items-center'>
|
||||
<Text>{`Delete Account ${modal.id}`}</Text>
|
||||
</span>
|
||||
}
|
||||
modalClassName='max-w-modal-sm'
|
||||
headerClassName='gradient-header p-4 border-b-white/5 border-b'
|
||||
contentClassName='w-full'
|
||||
>
|
||||
<div className='w-full border-b border-white/5 p-4 gradient-header'>
|
||||
<Text className='text-white/50' size='sm'>
|
||||
The following assets within your credit account will be sent to your wallet.
|
||||
</Text>
|
||||
</div>
|
||||
<div className='max-h-100 flex w-full flex-col gap-4 overflow-y-scroll p-4 scrollbar-hide'>
|
||||
{depositsAndLends.map((position, index) => {
|
||||
const coin = BNCoin.fromDenomAndBigNumber(position.denom, position.amount)
|
||||
const asset = getAssetByDenom(position.denom)
|
||||
|
||||
if (!asset) return null
|
||||
return <AssetBalanceRow key={index} asset={asset} coin={coin} />
|
||||
})}
|
||||
</div>
|
||||
<div className='w-full px-4 pb-4'>
|
||||
<Button
|
||||
className='w-full'
|
||||
onClick={deleteAccountHandler}
|
||||
text='Delete Account'
|
||||
rightIcon={<ArrowRight />}
|
||||
showProgressIndicator={isConfirming}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
@ -1,3 +1,6 @@
|
||||
import { useState } from 'react'
|
||||
import classNames from 'classnames'
|
||||
|
||||
import Button from 'components/Button'
|
||||
import { ExclamationMarkCircled } from 'components/Icons'
|
||||
import Modal from 'components/Modal'
|
||||
@ -21,11 +24,21 @@ interface Props {
|
||||
function AlertDialog(props: Props) {
|
||||
const { title, icon, description, negativeButton, positiveButton } = props.config
|
||||
|
||||
const [isConfirming, setIsConfirming] = useState(false)
|
||||
|
||||
const handleButtonClick = (button?: AlertDialogButton) => {
|
||||
button?.onClick && button.onClick()
|
||||
props.close()
|
||||
}
|
||||
|
||||
async function handleAsyncButtonClick(button?: AlertDialogButton) {
|
||||
if (!button?.onClick) return
|
||||
setIsConfirming(true)
|
||||
await button.onClick()
|
||||
setIsConfirming(false)
|
||||
props.close()
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
onClose={props.close}
|
||||
@ -41,19 +54,29 @@ function AlertDialog(props: Props) {
|
||||
>
|
||||
<Text size='xl'>{title}</Text>
|
||||
<Text className='mt-2 text-white/60'>{description}</Text>
|
||||
<div className='mt-10 flex flex-row-reverse justify-between'>
|
||||
<Button
|
||||
text={positiveButton.text ?? 'Yes'}
|
||||
color='tertiary'
|
||||
className='px-6'
|
||||
rightIcon={positiveButton.icon ?? <YesIcon />}
|
||||
onClick={() => handleButtonClick(positiveButton)}
|
||||
/>
|
||||
<div
|
||||
className={classNames('mt-10 flex justify-between', positiveButton && 'flex-row-reverse')}
|
||||
>
|
||||
{positiveButton && (
|
||||
<Button
|
||||
text={positiveButton.text ?? 'Yes'}
|
||||
color='tertiary'
|
||||
className='px-6'
|
||||
rightIcon={positiveButton.icon ?? <YesIcon />}
|
||||
showProgressIndicator={isConfirming}
|
||||
onClick={() =>
|
||||
positiveButton.isAsync
|
||||
? handleAsyncButtonClick(positiveButton)
|
||||
: handleButtonClick(positiveButton)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
text={negativeButton?.text ?? 'No'}
|
||||
color='secondary'
|
||||
className='px-6'
|
||||
rightIcon={negativeButton?.icon ?? <NoIcon />}
|
||||
disabled={isConfirming}
|
||||
tabIndex={1}
|
||||
onClick={() => handleButtonClick(negativeButton)}
|
||||
/>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import {
|
||||
AccountDeleteController,
|
||||
AddVaultBorrowAssetsModal,
|
||||
AlertDialogController,
|
||||
BorrowModal,
|
||||
@ -14,6 +15,7 @@ import {
|
||||
export default function ModalsContainer() {
|
||||
return (
|
||||
<>
|
||||
<AccountDeleteController />
|
||||
<AddVaultBorrowAssetsModal />
|
||||
<BorrowModal />
|
||||
<FundAndWithdrawModal />
|
||||
|
@ -1,3 +1,4 @@
|
||||
export { default as AccountDeleteController } from 'components/Modals/Account/AccountDeleteModal'
|
||||
export { default as AddVaultBorrowAssetsModal } from 'components/Modals/AddVaultAssets'
|
||||
export { default as AlertDialogController } from 'components/Modals/AlertDialog'
|
||||
export { default as BorrowModal } from 'components/Modals/BorrowModal'
|
||||
|
@ -1,13 +1,12 @@
|
||||
import classNames from 'classnames'
|
||||
import { ChangeEvent, LegacyRef } from 'react'
|
||||
import React from 'react'
|
||||
import React, { ChangeEvent, LegacyRef } from 'react'
|
||||
|
||||
import { Search } from 'components/Icons'
|
||||
|
||||
interface Props {
|
||||
value: string
|
||||
placeholder: string
|
||||
autofocus?: boolean
|
||||
autoFocus?: boolean
|
||||
onChange: (value: string) => void
|
||||
}
|
||||
|
||||
@ -31,7 +30,7 @@ const SearchBar = (props: Props, ref: LegacyRef<HTMLDivElement>) => {
|
||||
className='h-full w-full bg-transparent text-xs placeholder-white/50 outline-none'
|
||||
placeholder={props.placeholder}
|
||||
onChange={(event) => onChange(event)}
|
||||
autoFocus={props.autofocus}
|
||||
autoFocus={props.autoFocus}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
@ -58,7 +58,7 @@ export default function TermsOfService() {
|
||||
>
|
||||
<Benefits
|
||||
benefits={[
|
||||
'Swap tokens with margin acress any whitelisted pair',
|
||||
'Swap tokens with margin across any whitelisted pair',
|
||||
'Amplify your LP rewards with leveraged yield farming',
|
||||
'Earn interest on deposited tokens',
|
||||
]}
|
||||
|
@ -63,7 +63,7 @@ export default function AssetOverlay(props: Props) {
|
||||
value={searchString}
|
||||
onChange={onChangeSearch}
|
||||
placeholder='Search for e.g. "ETH" or "Ethereum"'
|
||||
autofocus
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
<Divider />
|
||||
|
@ -15,6 +15,22 @@ import { getSingleValueFromBroadcastResult } from 'utils/broadcast'
|
||||
import { formatAmountWithSymbol } from 'utils/formatters'
|
||||
import getTokenOutFromSwapResponse from 'utils/getTokenOutFromSwapResponse'
|
||||
|
||||
function generateExecutionMessage(
|
||||
sender: string | undefined,
|
||||
contract: string,
|
||||
msg: CreditManagerExecuteMsg | AccountNftExecuteMsg,
|
||||
funds: Coin[],
|
||||
) {
|
||||
if (!sender) return
|
||||
|
||||
return new MsgExecuteContract({
|
||||
sender,
|
||||
contract,
|
||||
msg,
|
||||
funds,
|
||||
})
|
||||
}
|
||||
|
||||
export default function createBroadcastSlice(
|
||||
set: SetState<Store>,
|
||||
get: GetState<Store>,
|
||||
@ -60,7 +76,10 @@ export default function createBroadcastSlice(
|
||||
},
|
||||
}
|
||||
|
||||
const response = await get().executeMsg({ msg, fee: options.fee })
|
||||
const response = await get().executeMsg({
|
||||
messages: [generateExecutionMessage(get().address, ENV.ADDRESS_CREDIT_MANAGER, msg, [])],
|
||||
fee: options.fee,
|
||||
})
|
||||
|
||||
handleResponseMessages(
|
||||
response,
|
||||
@ -74,8 +93,11 @@ export default function createBroadcastSlice(
|
||||
const msg: CreditManagerExecuteMsg = {
|
||||
create_credit_account: 'default',
|
||||
}
|
||||
set({ createAccountModal: true })
|
||||
const response = await get().executeMsg({ msg, fee: options.fee })
|
||||
|
||||
const response = await get().executeMsg({
|
||||
messages: [generateExecutionMessage(get().address, ENV.ADDRESS_CREDIT_MANAGER, msg, [])],
|
||||
fee: options.fee,
|
||||
})
|
||||
|
||||
if (response.result) {
|
||||
set({ createAccountModal: false })
|
||||
@ -93,16 +115,33 @@ export default function createBroadcastSlice(
|
||||
return null
|
||||
}
|
||||
},
|
||||
deleteAccount: async (options: { fee: StdFee; accountId: string }) => {
|
||||
const msg: AccountNftExecuteMsg = {
|
||||
deleteAccount: async (options: { fee: StdFee; accountId: string; lends: BNCoin[] }) => {
|
||||
const reclaimMsg = options.lends.map((coin) => {
|
||||
return {
|
||||
reclaim: coin.toActionCoin(true),
|
||||
}
|
||||
})
|
||||
|
||||
const refundMessage: CreditManagerExecuteMsg = {
|
||||
update_credit_account: {
|
||||
account_id: options.accountId,
|
||||
actions: [...reclaimMsg, { refund_all_coin_balances: {} }],
|
||||
},
|
||||
}
|
||||
|
||||
const burnMessage: AccountNftExecuteMsg = {
|
||||
burn: {
|
||||
token_id: options.accountId,
|
||||
},
|
||||
}
|
||||
set({ deleteAccountModal: true })
|
||||
const response = await get().executeMsg({ msg, fee: options.fee })
|
||||
|
||||
set({ deleteAccountModal: false })
|
||||
const response = await get().executeMsg({
|
||||
messages: [
|
||||
generateExecutionMessage(get().address, ENV.ADDRESS_CREDIT_MANAGER, refundMessage, []),
|
||||
generateExecutionMessage(get().address, ENV.ADDRESS_ACCOUNT_NFT, burnMessage, []),
|
||||
],
|
||||
fee: options.fee,
|
||||
})
|
||||
|
||||
handleResponseMessages(response, `Account ${options.accountId} deleted`)
|
||||
|
||||
@ -118,7 +157,12 @@ export default function createBroadcastSlice(
|
||||
},
|
||||
}
|
||||
|
||||
const response = await get().executeMsg({ msg, fee: options.fee, funds: options.coins })
|
||||
const response = await get().executeMsg({
|
||||
messages: [
|
||||
generateExecutionMessage(get().address, ENV.ADDRESS_CREDIT_MANAGER, msg, options.coins),
|
||||
],
|
||||
fee: options.fee,
|
||||
})
|
||||
|
||||
const depositString = options.coins.map((coin) => formatAmountWithSymbol(coin)).join('and ')
|
||||
handleResponseMessages(response, `Deposited ${depositString} to Account ${options.accountId}`)
|
||||
@ -145,9 +189,8 @@ export default function createBroadcastSlice(
|
||||
}
|
||||
|
||||
const response = await get().executeMsg({
|
||||
msg,
|
||||
messages: [generateExecutionMessage(get().address, ENV.ADDRESS_CREDIT_MANAGER, msg, [])],
|
||||
fee: options.fee,
|
||||
funds: [],
|
||||
})
|
||||
|
||||
handleResponseMessages(response, `Requested unlock for ${options.vault.name}`)
|
||||
@ -176,7 +219,11 @@ export default function createBroadcastSlice(
|
||||
},
|
||||
}
|
||||
|
||||
const response = await get().executeMsg({ msg, fee: options.fee })
|
||||
const response = await get().executeMsg({
|
||||
messages: [generateExecutionMessage(get().address, ENV.ADDRESS_CREDIT_MANAGER, msg, [])],
|
||||
fee: options.fee,
|
||||
})
|
||||
|
||||
const vaultsString = options.vaults.length === 1 ? 'vault' : 'vaults'
|
||||
handleResponseMessages(
|
||||
response,
|
||||
@ -191,7 +238,11 @@ export default function createBroadcastSlice(
|
||||
actions: options.actions,
|
||||
},
|
||||
}
|
||||
const response = await get().executeMsg({ msg, fee: options.fee })
|
||||
|
||||
const response = await get().executeMsg({
|
||||
messages: [generateExecutionMessage(get().address, ENV.ADDRESS_CREDIT_MANAGER, msg, [])],
|
||||
fee: options.fee,
|
||||
})
|
||||
|
||||
handleResponseMessages(response, `Deposited into vault`)
|
||||
return !!response.result
|
||||
@ -206,7 +257,11 @@ export default function createBroadcastSlice(
|
||||
},
|
||||
}
|
||||
|
||||
const response = await get().executeMsg({ msg, fee: options.fee })
|
||||
const response = await get().executeMsg({
|
||||
messages: [generateExecutionMessage(get().address, ENV.ADDRESS_CREDIT_MANAGER, msg, [])],
|
||||
fee: options.fee,
|
||||
})
|
||||
|
||||
const withdrawString = options.coins.map((coin) => formatAmountWithSymbol(coin)).join('and ')
|
||||
handleResponseMessages(
|
||||
response,
|
||||
@ -214,44 +269,6 @@ export default function createBroadcastSlice(
|
||||
)
|
||||
return !!response.result
|
||||
},
|
||||
executeMsg: async (options: {
|
||||
fee: StdFee
|
||||
msg: Record<string, unknown>
|
||||
funds?: Coin[]
|
||||
}): Promise<BroadcastResult> => {
|
||||
const funds = options.funds ?? []
|
||||
|
||||
try {
|
||||
const client = get().client
|
||||
if (!client) return { error: 'no client detected' }
|
||||
|
||||
const broadcastOptions = {
|
||||
messages: [
|
||||
new MsgExecuteContract({
|
||||
sender: client.connectedWallet.account.address,
|
||||
contract: ENV.ADDRESS_CREDIT_MANAGER,
|
||||
msg: options.msg,
|
||||
funds,
|
||||
}),
|
||||
],
|
||||
feeAmount: options.fee.amount[0].amount,
|
||||
gasLimit: options.fee.gas,
|
||||
memo: undefined,
|
||||
wallet: client.connectedWallet,
|
||||
mobile: isMobile,
|
||||
}
|
||||
|
||||
const result = await client.broadcast(broadcastOptions)
|
||||
|
||||
if (result.hash) {
|
||||
return { result }
|
||||
}
|
||||
return { result: undefined, error: 'Transaction failed' }
|
||||
} catch (e: unknown) {
|
||||
const error = typeof e === 'string' ? e : 'Transaction failed'
|
||||
return { result: undefined, error }
|
||||
}
|
||||
},
|
||||
repay: async (options: {
|
||||
fee: StdFee
|
||||
accountId: string
|
||||
@ -271,7 +288,10 @@ export default function createBroadcastSlice(
|
||||
},
|
||||
}
|
||||
|
||||
const response = await get().executeMsg({ msg, fee: options.fee, funds: [] })
|
||||
const response = await get().executeMsg({
|
||||
messages: [generateExecutionMessage(get().address, ENV.ADDRESS_CREDIT_MANAGER, msg, [])],
|
||||
fee: options.fee,
|
||||
})
|
||||
|
||||
handleResponseMessages(
|
||||
response,
|
||||
@ -291,7 +311,10 @@ export default function createBroadcastSlice(
|
||||
},
|
||||
}
|
||||
|
||||
const response = await get().executeMsg({ msg, fee: options.fee })
|
||||
const response = await get().executeMsg({
|
||||
messages: [generateExecutionMessage(get().address, ENV.ADDRESS_CREDIT_MANAGER, msg, [])],
|
||||
fee: options.fee,
|
||||
})
|
||||
|
||||
handleResponseMessages(
|
||||
response,
|
||||
@ -311,7 +334,10 @@ export default function createBroadcastSlice(
|
||||
},
|
||||
}
|
||||
|
||||
const response = await get().executeMsg({ msg, fee: options.fee })
|
||||
const response = await get().executeMsg({
|
||||
messages: [generateExecutionMessage(get().address, ENV.ADDRESS_CREDIT_MANAGER, msg, [])],
|
||||
fee: options.fee,
|
||||
})
|
||||
|
||||
handleResponseMessages(
|
||||
response,
|
||||
@ -341,7 +367,11 @@ export default function createBroadcastSlice(
|
||||
},
|
||||
}
|
||||
|
||||
const response = await get().executeMsg({ msg, fee: options.fee })
|
||||
const response = await get().executeMsg({
|
||||
messages: [generateExecutionMessage(get().address, ENV.ADDRESS_CREDIT_MANAGER, msg, [])],
|
||||
fee: options.fee,
|
||||
})
|
||||
|
||||
const coinOut = getTokenOutFromSwapResponse(response, options.denomOut)
|
||||
const successMessage = `Swapped ${formatAmountWithSymbol(
|
||||
options.coinIn.toCoin(),
|
||||
@ -350,5 +380,37 @@ export default function createBroadcastSlice(
|
||||
handleResponseMessages(response, successMessage)
|
||||
return !!response.result
|
||||
},
|
||||
executeMsg: async (options: {
|
||||
messages: MsgExecuteContract[]
|
||||
fee: StdFee
|
||||
}): Promise<BroadcastResult> => {
|
||||
try {
|
||||
const client = get().client
|
||||
if (!client) return { error: 'no client detected' }
|
||||
|
||||
const broadcastOptions = {
|
||||
messages: options.messages,
|
||||
feeAmount: options.fee.amount[0].amount,
|
||||
gasLimit: options.fee.gas,
|
||||
memo: undefined,
|
||||
wallet: client.connectedWallet,
|
||||
mobile: isMobile,
|
||||
}
|
||||
|
||||
const result = await client.broadcast(broadcastOptions)
|
||||
|
||||
if (result.hash) {
|
||||
return { result }
|
||||
}
|
||||
|
||||
return {
|
||||
result: undefined,
|
||||
error: 'Transaction failed',
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
const error = typeof e === 'string' ? e : 'Transaction failed'
|
||||
return { result: undefined, error }
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -3,10 +3,10 @@ import { GetState, SetState } from 'zustand'
|
||||
export default function createModalSlice(set: SetState<ModalSlice>, get: GetState<ModalSlice>) {
|
||||
return {
|
||||
addVaultBorrowingsModal: null,
|
||||
accountDeleteModal: null,
|
||||
alertDialog: null,
|
||||
borrowModal: null,
|
||||
createAccountModal: false,
|
||||
deleteAccountModal: false,
|
||||
fundAccountModal: false,
|
||||
fundAndWithdrawModal: null,
|
||||
lendAndReclaimModal: null,
|
||||
|
1
src/types/interfaces/components/Button.d.ts
vendored
1
src/types/interfaces/components/Button.d.ts
vendored
@ -1,4 +1,5 @@
|
||||
interface ButtonProps {
|
||||
autoFocus?: boolean
|
||||
children?: string | ReactNode
|
||||
className?: string
|
||||
color?: 'primary' | 'secondary' | 'tertiary' | 'quaternary'
|
||||
|
8
src/types/interfaces/store/broadcast.d.ts
vendored
8
src/types/interfaces/store/broadcast.d.ts
vendored
@ -7,11 +7,7 @@ interface BroadcastResult {
|
||||
|
||||
interface BroadcastSlice {
|
||||
toast: { message: string; isError?: boolean; title?: string } | null
|
||||
executeMsg: (options: {
|
||||
msg: Record<string, unknown>
|
||||
fee: StdFee
|
||||
funds?: Coin[]
|
||||
}) => Promise<BroadcastResult>
|
||||
executeMsg: (options: { messages: MsgExecuteContract[]; fee: StdFee }) => Promise<BroadcastResult>
|
||||
borrow: (options: {
|
||||
fee: StdFee
|
||||
accountId: string
|
||||
@ -19,7 +15,7 @@ interface BroadcastSlice {
|
||||
borrowToWallet: boolean
|
||||
}) => Promise<boolean>
|
||||
createAccount: (options: { fee: StdFee }) => Promise<string | null>
|
||||
deleteAccount: (options: { fee: StdFee; accountId: string }) => Promise<boolean>
|
||||
deleteAccount: (options: { fee: StdFee; accountId: string; lends: BNCoin[] }) => Promise<boolean>
|
||||
deposit: (options: { fee: StdFee; accountId: string; coins: Coin[] }) => Promise<boolean>
|
||||
unlock: (options: {
|
||||
fee: StdFee
|
||||
|
16
src/types/interfaces/store/modals.d.ts
vendored
16
src/types/interfaces/store/modals.d.ts
vendored
@ -1,9 +1,9 @@
|
||||
interface ModalSlice {
|
||||
accountDeleteModal: Account | null
|
||||
addVaultBorrowingsModal: AddVaultBorrowingsModal | null
|
||||
alertDialog: AlertDialogConfig | null
|
||||
borrowModal: BorrowModal | null
|
||||
createAccountModal: boolean
|
||||
deleteAccountModal: boolean
|
||||
fundAccountModal: boolean
|
||||
fundAndWithdrawModal: 'fund' | 'withdraw' | null
|
||||
lendAndReclaimModal: LendAndReclaimModalConfig | null
|
||||
@ -17,6 +17,7 @@ interface ModalSlice {
|
||||
interface AlertDialogButton {
|
||||
text?: string
|
||||
icon?: JSX.Element
|
||||
isAsync?: boolean
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
@ -25,7 +26,12 @@ interface AlertDialogConfig {
|
||||
title: JSX.Element | string
|
||||
description: JSX.Element | string
|
||||
negativeButton?: AlertDialogButton
|
||||
positiveButton: AlertDialogButton
|
||||
positiveButton?: AlertDialogButton
|
||||
}
|
||||
interface BorrowModal {
|
||||
asset: Asset
|
||||
marketData: BorrowMarketTableData
|
||||
isRepay?: boolean
|
||||
}
|
||||
|
||||
type LendAndReclaimModalAction = 'lend' | 'reclaim'
|
||||
@ -34,12 +40,6 @@ interface LendAndReclaimModalConfig {
|
||||
action: LendAndReclaimModalAction
|
||||
}
|
||||
|
||||
interface BorrowModal {
|
||||
asset: Asset
|
||||
marketData: BorrowMarketTableData
|
||||
isRepay?: boolean
|
||||
}
|
||||
|
||||
interface VaultModal {
|
||||
vault: Vault | DepositedVault
|
||||
isDeposited?: boolean
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { BN } from 'utils/helpers'
|
||||
export function isNumber(value: unknown) {
|
||||
if (typeof value === 'string' && value !== '') {
|
||||
return !isNaN(Number(value))
|
||||
@ -13,3 +15,25 @@ export function isNumber(value: unknown) {
|
||||
export const convertAprToApy = (apr: number, numberOfCompoundingPeriods: number): number => {
|
||||
return ((1 + apr / 100 / numberOfCompoundingPeriods) ** numberOfCompoundingPeriods - 1) * 100
|
||||
}
|
||||
|
||||
export const combineBNCoins = (coins: BNCoin[]): BNCoin[] => {
|
||||
const combinedMap: { [key: string]: number } = {}
|
||||
|
||||
coins.forEach((coin) => {
|
||||
if (combinedMap.hasOwnProperty(coin.denom)) {
|
||||
combinedMap[coin.denom] += coin.amount.toNumber()
|
||||
} else {
|
||||
combinedMap[coin.denom] = coin.amount.toNumber()
|
||||
}
|
||||
})
|
||||
|
||||
const combinedArray: BNCoin[] = Object.keys(combinedMap).map(
|
||||
(denom) =>
|
||||
new BNCoin({
|
||||
denom,
|
||||
amount: BN(combinedMap[denom]).toString(),
|
||||
}),
|
||||
)
|
||||
|
||||
return combinedArray
|
||||
}
|
||||
|
@ -166,7 +166,7 @@ module.exports = {
|
||||
maxWidth: {
|
||||
content: '1024px',
|
||||
modal: '895px',
|
||||
'modal-sm': '517px',
|
||||
'modal-sm': '526px',
|
||||
'modal-xs': '442px',
|
||||
},
|
||||
minWidth: {
|
||||
|
Loading…
Reference in New Issue
Block a user