diff --git a/src/components/Modals/AssetAmountSelectActionModal.tsx b/src/components/Modals/AssetAmountSelectActionModal.tsx index e0b2f05a..6dbdd83c 100644 --- a/src/components/Modals/AssetAmountSelectActionModal.tsx +++ b/src/components/Modals/AssetAmountSelectActionModal.tsx @@ -10,12 +10,12 @@ import Text from 'components/common/Text' import TokenInputWithSlider from 'components/common/TokenInput/TokenInputWithSlider' import AssetImage from 'components/common/assets/AssetImage' import { BN_ZERO } from 'constants/math' -import useCurrentAccount from 'hooks/accounts/useCurrentAccount' import { BNCoin } from 'types/classes/BNCoin' import { byDenom } from 'utils/array' import { BN } from 'utils/helpers' interface Props { + account: Account asset: Asset title: string coinBalances: BNCoin[] @@ -29,6 +29,7 @@ interface Props { export default function AssetAmountSelectActionModal(props: Props) { const { + account, asset, title, coinBalances, @@ -41,7 +42,6 @@ export default function AssetAmountSelectActionModal(props: Props) { } = props const [amount, setAmount] = useState(BN_ZERO) const maxAmount = BN(coinBalances.find(byDenom(asset.denom))?.amount ?? 0) - const account = useCurrentAccount() const handleAmountChange = useCallback( (value: BigNumber) => { setAmount(value) @@ -54,7 +54,6 @@ export default function AssetAmountSelectActionModal(props: Props) { onAction(amount, amount.isEqualTo(maxAmount)) }, [amount, maxAmount, onAction]) - if (!account) return return ( s.lend) const reclaim = useStore((s) => s.reclaim) const { close } = useLendAndReclaimModal() @@ -65,8 +66,11 @@ function LendAndReclaimModal({ currentAccount, config }: Props) { }, [asset.denom, close, currentAccount.id, isLendAction, lend, reclaim], ) + if (!account) return null + return ( } coinBalances={coinBalances} diff --git a/src/components/Modals/ModalsContainer.tsx b/src/components/Modals/ModalsContainer.tsx index 01471b9d..ae749fa7 100644 --- a/src/components/Modals/ModalsContainer.tsx +++ b/src/components/Modals/ModalsContainer.tsx @@ -10,6 +10,7 @@ import { LendAndReclaimModalController, SettingsModal, UnlockModal, + V1DepositAndWithdraw, VaultModal, WalletAssets, WithdrawFromVaultsModal, @@ -32,6 +33,7 @@ export default function ModalsContainer() { + ) } diff --git a/src/components/Modals/V1DepositAndWithdraw/Deposit.tsx b/src/components/Modals/V1DepositAndWithdraw/Deposit.tsx new file mode 100644 index 00000000..b2849729 --- /dev/null +++ b/src/components/Modals/V1DepositAndWithdraw/Deposit.tsx @@ -0,0 +1,83 @@ +import { useCallback, useEffect, useMemo, useState } from 'react' + +import WalletBridges from 'components/Wallet/WalletBridges' +import { BN_ZERO } from 'constants/math' +import useBaseAsset from 'hooks/assets/useBasetAsset' +import useCurrentWalletBalance from 'hooks/useCurrentWalletBalance' +import { useUpdatedAccount } from 'hooks/useUpdatedAccount' +import useWalletBalances from 'hooks/useWalletBalances' +import useStore from 'store' +import { BNCoin } from 'types/classes/BNCoin' +import { byDenom } from 'utils/array' +import { defaultFee } from 'utils/constants' +import { BN } from 'utils/helpers' + +import AssetAmountSelectActionModal from '../AssetAmountSelectActionModal' +import DetailsHeader from '../LendAndReclaim/DetailsHeader' + +interface Props { + account: Account +} + +export default function Deposit(props: Props) { + const { account } = props + const baseAsset = useBaseAsset() + const modal = useStore((s) => s.v1DepositAndWithdrawModal) + const address = useStore((s) => s.address) + const asset = modal?.data.asset ?? baseAsset + const [fundingAsset, setFundingAsset] = useState( + BNCoin.fromDenomAndBigNumber(modal?.data.asset.denom ?? baseAsset.denom, BN_ZERO), + ) + const { data: walletBalances } = useWalletBalances(address) + const { simulateDeposits } = useUpdatedAccount(account) + const balance = useCurrentWalletBalance(asset.denom) + const v1Action = useStore((s) => s.v1Action) + + const baseBalance = useMemo( + () => walletBalances.find(byDenom(baseAsset.denom))?.amount ?? '0', + [walletBalances, baseAsset], + ) + + const close = useCallback(() => { + useStore.setState({ v1DepositAndWithdrawModal: null }) + }, []) + + const handleClick = useCallback(async () => { + v1Action('deposit', fundingAsset) + close() + }, [v1Action, fundingAsset, close]) + + useEffect(() => { + if (BN(baseBalance).isLessThan(defaultFee.amount[0].amount)) { + useStore.setState({ focusComponent: { component: } }) + } + }, [baseBalance]) + + const onDebounce = useCallback(() => { + simulateDeposits('lend', [fundingAsset]) + }, [fundingAsset, simulateDeposits]) + + const handleAmountChange = useCallback( + (value: BigNumber) => { + setFundingAsset(BNCoin.fromDenomAndBigNumber(asset.denom, value)) + }, + [asset.denom], + ) + + if (!modal) return + + return ( + } + coinBalances={balance ? [BNCoin.fromCoin(balance)] : []} + actionButtonText={`Deposit ${asset.symbol}`} + title={`Deposit ${asset.symbol} into the Red Bank`} + onClose={close} + onAction={handleClick} + onChange={handleAmountChange} + onDebounce={onDebounce} + /> + ) +} diff --git a/src/components/Modals/V1DepositAndWithdraw/Withdraw.tsx b/src/components/Modals/V1DepositAndWithdraw/Withdraw.tsx new file mode 100644 index 00000000..7cc27f62 --- /dev/null +++ b/src/components/Modals/V1DepositAndWithdraw/Withdraw.tsx @@ -0,0 +1,67 @@ +import { useCallback, useState } from 'react' + +import { BN_ZERO } from 'constants/math' +import useBaseAsset from 'hooks/assets/useBasetAsset' +import useHealthComputer from 'hooks/useHealthComputer' +import { useUpdatedAccount } from 'hooks/useUpdatedAccount' +import useStore from 'store' +import { BNCoin } from 'types/classes/BNCoin' + +import AssetAmountSelectActionModal from '../AssetAmountSelectActionModal' +import DetailsHeader from '../LendAndReclaim/DetailsHeader' + +interface Props { + account: Account +} + +export default function Withdraw(props: Props) { + const { account } = props + const baseAsset = useBaseAsset() + const modal = useStore((s) => s.v1DepositAndWithdrawModal) + const asset = modal?.data.asset ?? baseAsset + const [withdrawAsset, setWithdrawAsset] = useState( + BNCoin.fromDenomAndBigNumber(modal?.data.asset.denom ?? baseAsset.denom, BN_ZERO), + ) + const { computeMaxWithdrawAmount } = useHealthComputer(account) + const maxWithdrawAmount = computeMaxWithdrawAmount(asset.denom) + const { simulateWithdraw } = useUpdatedAccount(account) + const balance = BNCoin.fromDenomAndBigNumber(asset.denom, maxWithdrawAmount) + const v1Action = useStore((s) => s.v1Action) + + const close = useCallback(() => { + useStore.setState({ v1DepositAndWithdrawModal: null }) + }, []) + + const handleClick = useCallback(async () => { + v1Action('withdraw', withdrawAsset) + close() + }, [v1Action, withdrawAsset, close]) + + const onDebounce = useCallback(() => { + simulateWithdraw(false, withdrawAsset) + }, [withdrawAsset, simulateWithdraw]) + + const handleAmountChange = useCallback( + (value: BigNumber) => { + setWithdrawAsset(BNCoin.fromDenomAndBigNumber(asset.denom, value)) + }, + [asset.denom], + ) + + if (!modal) return + + return ( + } + coinBalances={[balance]} + actionButtonText={`Withdraw ${asset.symbol}`} + title={`Withdraw ${asset.symbol} from the Red Bank`} + onClose={close} + onAction={handleClick} + onChange={handleAmountChange} + onDebounce={onDebounce} + /> + ) +} diff --git a/src/components/Modals/V1DepositAndWithdraw/index.tsx b/src/components/Modals/V1DepositAndWithdraw/index.tsx new file mode 100644 index 00000000..698db31d --- /dev/null +++ b/src/components/Modals/V1DepositAndWithdraw/index.tsx @@ -0,0 +1,16 @@ +import useAccount from 'hooks/accounts/useAccount' +import useStore from 'store' + +import Deposit from './Deposit' +import Withdraw from './Withdraw' + +export default function V1DepositAndWithdraw() { + const address = useStore((s) => s.address) + const { data: account } = useAccount(address) + const modal = useStore((s) => s.v1DepositAndWithdrawModal) + const isDeposit = modal?.type === 'deposit' + + if (!modal || !account) return null + if (isDeposit) return + return +} diff --git a/src/components/Modals/index.tsx b/src/components/Modals/index.tsx index e71ffa31..c8b5b890 100644 --- a/src/components/Modals/index.tsx +++ b/src/components/Modals/index.tsx @@ -4,11 +4,12 @@ export { default as AlertDialogController } from 'components/Modals/AlertDialog' export { default as BorrowModal } from 'components/Modals/BorrowModal' export { default as FundAndWithdrawModal } from 'components/Modals/FundWithdraw' export { default as GetStartedModal } from 'components/Modals/GetStartedModal' +export { default as HlsModal } from 'components/Modals/HLS' +export { default as HlsManageModal } from 'components/Modals/HLS/Manage' export { default as LendAndReclaimModalController } from 'components/Modals/LendAndReclaim' export { default as SettingsModal } from 'components/Modals/Settings' export { default as UnlockModal } from 'components/Modals/Unlock' +export { default as V1DepositAndWithdraw } from 'components/Modals/V1DepositAndWithdraw' export { default as VaultModal } from 'components/Modals/Vault' export { default as WalletAssets } from 'components/Modals/WalletAssets' export { default as WithdrawFromVaultsModal } from 'components/Modals/WithdrawFromVaultsModal' -export { default as HlsModal } from 'components/Modals/HLS' -export { default as HlsManageModal } from 'components/Modals/HLS/Manage' diff --git a/src/components/Wallet/RecentTransactions.tsx b/src/components/Wallet/RecentTransactions.tsx index 567da7d9..962e1a88 100644 --- a/src/components/Wallet/RecentTransactions.tsx +++ b/src/components/Wallet/RecentTransactions.tsx @@ -7,8 +7,8 @@ import Text from 'components/common/Text' import { TextLink } from 'components/common/TextLink' import { generateToastContent } from 'components/common/Toaster' import useTransactions from 'hooks/localStorage/useTransactions' -import useStore from 'store' import useChainConfig from 'hooks/useChainConfig' +import useStore from 'store' export default function RecentTransactions() { const address = useStore((s) => s.address) @@ -47,7 +47,9 @@ export default function RecentTransactions() { key={hash} >
- Credit Account {accountId} + + {accountId === address ? 'Red Bank' : `Credit Account ${accountId}`} + {moment.unix(timestamp).format('lll')} diff --git a/src/components/common/Toaster/index.tsx b/src/components/common/Toaster/index.tsx index f83e80b7..6baddeaf 100644 --- a/src/components/common/Toaster/index.tsx +++ b/src/components/common/Toaster/index.tsx @@ -11,11 +11,11 @@ import { TextLink } from 'components/common/TextLink' import { DEFAULT_SETTINGS } from 'constants/defaultSettings' import { LocalStorageKeys } from 'constants/localStorageKeys' import useLocalStorage from 'hooks/localStorage/useLocalStorage' +import useChainConfig from 'hooks/useChainConfig' import useTransactionStore from 'hooks/useTransactionStore' import useStore from 'store' import { formatAmountWithSymbol } from 'utils/formatters' import { BN } from 'utils/helpers' -import useChainConfig from 'hooks/useChainConfig' const toastBodyClasses = classNames( 'flex flex-wrap w-full group/transaction', @@ -99,6 +99,13 @@ export default function Toaster() { if (!isError && toast.accountId) addTransaction(toast) const generalMessage = isError ? 'Transaction failed!' : 'Transaction completed successfully!' const showDetailElement = !!(!details && toast.hash) + const address = useStore.getState().address + + let target: string + if (!isError) { + target = toast.accountId === address ? 'Red Bank' : `Credit Account ${toast.accountId}` + } + const Msg = () => (
@@ -141,7 +148,7 @@ export default function Toaster() { )} > {!isError && toast.accountId && ( - {`Credit Account ${toast.accountId}`} + {target} )} {showDetailElement && toast.message && ( diff --git a/src/components/v1/Table/deposits/Columns/DepositButton.tsx b/src/components/v1/Table/deposits/Columns/DepositButton.tsx index 26c9e8d9..4fa1af9a 100644 --- a/src/components/v1/Table/deposits/Columns/DepositButton.tsx +++ b/src/components/v1/Table/deposits/Columns/DepositButton.tsx @@ -37,7 +37,9 @@ export default function DepositButton(props: Props) { disabled={!hasBalance} color='tertiary' onClick={(e) => { - useStore.setState({ fundAndWithdrawModal: 'fund' }) + useStore.setState({ + v1DepositAndWithdrawModal: { type: 'deposit', data: props.data }, + }) e.stopPropagation() }} text='Deposit' diff --git a/src/components/v1/Table/deposits/Columns/Manage.tsx b/src/components/v1/Table/deposits/Columns/Manage.tsx index 51c447a7..f36616ba 100644 --- a/src/components/v1/Table/deposits/Columns/Manage.tsx +++ b/src/components/v1/Table/deposits/Columns/Manage.tsx @@ -22,17 +22,23 @@ export default function Manage(props: Props) { { icon: , text: 'Deposit more', - onClick: () => openLend(props.data), + onClick: () => + useStore.setState({ + v1DepositAndWithdrawModal: { type: 'deposit', data: props.data }, + }), disabled: !hasBalance, disabledTooltip: `You don’t have any ${props.data.asset.symbol} in your Wallet.`, }, { icon: , text: 'Withdraw', - onClick: () => openReclaim(props.data), + onClick: () => + useStore.setState({ + v1DepositAndWithdrawModal: { type: 'withdraw', data: props.data }, + }), }, ], - [hasBalance, openLend, openReclaim, props.data], + [hasBalance, props.data], ) return ( diff --git a/src/hooks/accounts/useAccount.tsx b/src/hooks/accounts/useAccount.tsx index 2973d98e..2a5f6a06 100644 --- a/src/hooks/accounts/useAccount.tsx +++ b/src/hooks/accounts/useAccount.tsx @@ -3,10 +3,12 @@ import useSWR from 'swr' import getAccount from 'api/accounts/getAccount' import getV1Positions from 'api/v1/getV1Positions' import useChainConfig from 'hooks/useChainConfig' +import useStore from 'store' export default function useAccount(accountId?: string, suspense?: boolean) { const chainConfig = useChainConfig() - const isV1 = isNaN(parseInt(accountId || '')) + const address = useStore((s) => s.address) + const isV1 = accountId === address const cacheKey = isV1 ? `chains/${chainConfig.id}/v1/user/${accountId}` diff --git a/src/store/slices/broadcast.ts b/src/store/slices/broadcast.ts index fa9ff5bb..a759afb7 100644 --- a/src/store/slices/broadcast.ts +++ b/src/store/slices/broadcast.ts @@ -16,6 +16,7 @@ import { ExecuteMsg as CreditManagerExecuteMsg, ExecuteMsg, } from 'types/generated/mars-credit-manager/MarsCreditManager.types' +import { ExecuteMsg as RedBankExecuteMsg } from 'types/generated/mars-red-bank/MarsRedBank.types' import { AccountKind } from 'types/generated/mars-rover-health-types/MarsRoverHealthTypes.types' import { byDenom, bySymbol } from 'utils/array' import { generateErrorMessage, getSingleValueFromBroadcastResult } from 'utils/broadcast' @@ -30,7 +31,7 @@ import { getVaultDepositCoinsFromActions } from 'utils/vaults' function generateExecutionMessage( sender: string | undefined = '', contract: string, - msg: CreditManagerExecuteMsg | AccountNftExecuteMsg | PythUpdateExecuteMsg, + msg: CreditManagerExecuteMsg | AccountNftExecuteMsg | RedBankExecuteMsg | PythUpdateExecuteMsg, funds: Coin[], ) { return new MsgExecuteContract({ @@ -1065,5 +1066,69 @@ export default function createBroadcastSlice( { denom: get().chainConfig.assets[0].denom, amount: String(pythAssets.length) }, ]) }, + v1Action: async (type: V1ActionType, coin: BNCoin) => { + let msg: RedBankExecuteMsg + let toastOptions: ToastObjectOptions = { + action: type, + accountId: get().address, + changes: {}, + } + let funds: Coin[] = [] + + switch (type) { + case 'withdraw': + msg = { + withdraw: { + amount: coin.amount.toString(), + denom: coin.denom, + }, + } + toastOptions = { + ...toastOptions, + changes: { deposits: [coin] }, + target: 'wallet', + } + break + case 'repay': + msg = { + repay: {}, + } + toastOptions.changes = { deposits: [coin] } + funds = [coin.toCoin()] + break + case 'borrow': + msg = { + borrow: { + amount: coin.amount.toString(), + denom: coin.denom, + }, + } + toastOptions = { + ...toastOptions, + changes: { debts: [coin] }, + target: 'wallet', + } + break + default: + msg = { + deposit: {}, + } + toastOptions.changes = { deposits: [coin] } + funds = [coin.toCoin()] + } + + const redBankContract = get().chainConfig.contracts.redBank + + const response = get().executeMsg({ + messages: [generateExecutionMessage(get().address, redBankContract, msg, funds)], + }) + + get().setToast({ + response, + options: toastOptions, + }) + + return response.then((response) => !!response.result) + }, } } diff --git a/src/store/slices/modal.ts b/src/store/slices/modal.ts index fe373b2a..f0984fd2 100644 --- a/src/store/slices/modal.ts +++ b/src/store/slices/modal.ts @@ -19,5 +19,6 @@ export default function createModalSlice(set: SetState, get: GetStat vaultModal: null, walletAssetsModal: null, withdrawFromVaultsModal: null, + v1DepositAndWithdrawModal: null, } } diff --git a/src/types/interfaces/store/broadcast.d.ts b/src/types/interfaces/store/broadcast.d.ts index 719c1c69..1ec3edd1 100644 --- a/src/types/interfaces/store/broadcast.d.ts +++ b/src/types/interfaces/store/broadcast.d.ts @@ -167,4 +167,7 @@ interface BroadcastSlice { borrow: BNCoin[] reclaims: ActionCoin[] }) => Promise + v1Action: (type: V1ActionType, funds: BNCoin) => Promise } + +type V1ActionType = 'withdraw' | 'deposit' | 'borrow' | 'repay' diff --git a/src/types/interfaces/store/modals.d.ts b/src/types/interfaces/store/modals.d.ts index ceef9fa6..d2dd91e3 100644 --- a/src/types/interfaces/store/modals.d.ts +++ b/src/types/interfaces/store/modals.d.ts @@ -7,6 +7,7 @@ interface ModalSlice { hlsManageModal: HlsManageModal | null borrowModal: BorrowModal | null fundAndWithdrawModal: 'fund' | 'withdraw' | null + v1DepositAndWithdrawModal: V1DepositAndWithdrawModal | null getStartedModal: boolean hlsInformationModal: boolean | null lendAndReclaimModal: LendAndReclaimModalConfig | null @@ -84,3 +85,8 @@ interface HlsManageModal { } type HlsStakingManageAction = 'deposit' | 'withdraw' | 'repay' | 'leverage' + +interface V1DepositAndWithdrawModal { + type: 'deposit' | 'withdraw' + data: LendingMarketTableData +}