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:
Linkie Link 2023-08-03 11:45:32 +02:00 committed by GitHub
parent 69dece2600
commit 1a55d8bd39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 466 additions and 128 deletions

View File

@ -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'

View File

@ -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={

View 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>
)
}

View File

@ -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]} />

View File

@ -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>
)
}

View File

@ -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' },

View File

@ -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',
},
{

View 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
}

View 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>
)
}

View File

@ -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)}
/>

View File

@ -1,4 +1,5 @@
import {
AccountDeleteController,
AddVaultBorrowAssetsModal,
AlertDialogController,
BorrowModal,
@ -14,6 +15,7 @@ import {
export default function ModalsContainer() {
return (
<>
<AccountDeleteController />
<AddVaultBorrowAssetsModal />
<BorrowModal />
<FundAndWithdrawModal />

View File

@ -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'

View File

@ -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>
)

View File

@ -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',
]}

View File

@ -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 />

View File

@ -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 }
}
},
}
}

View File

@ -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,

View File

@ -1,4 +1,5 @@
interface ButtonProps {
autoFocus?: boolean
children?: string | ReactNode
className?: string
color?: 'primary' | 'secondary' | 'tertiary' | 'quaternary'

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -166,7 +166,7 @@ module.exports = {
maxWidth: {
content: '1024px',
modal: '895px',
'modal-sm': '517px',
'modal-sm': '526px',
'modal-xs': '442px',
},
minWidth: {