From 01fc911c34fcea43e6d24c9618df7699f8a60fa2 Mon Sep 17 00:00:00 2001 From: Linkie Link Date: Fri, 21 Apr 2023 09:01:38 +0800 Subject: [PATCH] release v1.4.4 --- src/components/common/TxModal/Action.tsx | 14 +- .../redbank/RedbankAction/RedbankAction.tsx | 791 ++++++------------ 2 files changed, 248 insertions(+), 557 deletions(-) diff --git a/src/components/common/TxModal/Action.tsx b/src/components/common/TxModal/Action.tsx index ded3373..affbf91 100644 --- a/src/components/common/TxModal/Action.tsx +++ b/src/components/common/TxModal/Action.tsx @@ -1,6 +1,5 @@ import 'chart.js/auto' -import { Coin } from '@cosmjs/stargate' import classNames from 'classnames' import { BorrowCapacity, @@ -22,7 +21,7 @@ import { maintainanceMarginWeightedDepositValue, producePercentData, } from 'libs/assetInfo' -import { formatValue, lookup, lookupSymbol, magnify } from 'libs/parse' +import { formatValue, lookup, lookupSymbol } from 'libs/parse' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { Bar } from 'react-chartjs-2' import { useTranslation } from 'react-i18next' @@ -46,7 +45,6 @@ interface Props { actionButtonSpec: ModalActionButton submitted: boolean feeError?: string - txFee?: Coin activeView: ViewType denom: string decimals: number @@ -67,7 +65,6 @@ export const Action = ({ actionButtonSpec, submitted, feeError, - txFee, activeView, denom, decimals, @@ -229,13 +226,8 @@ export const Action = ({ }, [denom, availableBalanceBaseCurrency, currentAssetPrice, marketAssetLiquidity]) const repayMax = useMemo((): number => { - let adjustedWalletBalance = walletBalance - if (denom === baseCurrency.denom) { - adjustedWalletBalance = walletBalance - Number(magnify(Number(txFee), 6)) - } - - return Math.min(assetBorrowBalance, adjustedWalletBalance) - }, [assetBorrowBalance, walletBalance, txFee, denom, baseCurrency.denom]) + return Math.min(assetBorrowBalance, walletBalance) + }, [assetBorrowBalance, walletBalance, denom, baseCurrency.denom]) const maxWithdrawableAmount = useMemo((): number => { const assetLtvRatio = findByDenom(marketInfo, denom)?.max_loan_to_value || 0 diff --git a/src/components/redbank/RedbankAction/RedbankAction.tsx b/src/components/redbank/RedbankAction/RedbankAction.tsx index affbf91..ad9e615 100644 --- a/src/components/redbank/RedbankAction/RedbankAction.tsx +++ b/src/components/redbank/RedbankAction/RedbankAction.tsx @@ -1,581 +1,280 @@ -import 'chart.js/auto' - -import classNames from 'classnames' -import { - BorrowCapacity, - Button, - Card, - ConnectButton, - DisplayCurrency, - ErrorMessage, - InputSection, -} from 'components/common' +import { TxBroadcastResult } from '@marsprotocol/wallet-connector' +import { useQueryClient } from '@tanstack/react-query' +import { Action, Notification, TxResponse } from 'components/common' import { findByDenom } from 'functions' -import { maxBorrowableAmount } from 'functions/redbank/maxBorrowableAmount' -import { produceBarChartConfig } from 'functions/redbank/produceBarChartConfig' -import { produceUpdatedAssetData } from 'functions/redbank/produceUpdatedAssetData' -import { useUserBalance } from 'hooks/queries' import { - balanceSum, - ltvWeightedDepositValue, - maintainanceMarginWeightedDepositValue, - producePercentData, -} from 'libs/assetInfo' -import { formatValue, lookup, lookupSymbol } from 'libs/parse' -import { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { Bar } from 'react-chartjs-2' + getRedbankBorrowMsgOptions, + getRedbankDepositMsgOptions, + getRedbankRepayMsgOptions, + getRedbankWithdrawMsgOptions, +} from 'functions/messages' +import { useEstimateFee } from 'hooks/queries' +import { ltvWeightedDepositValue, maintainanceMarginWeightedDepositValue } from 'libs/assetInfo' +import { lookup, lookupDecimals } from 'libs/parse' +import isEqual from 'lodash.isequal' +import { useRouter } from 'next/router' +import React, { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import useStore from 'store' -import colors from 'styles/_assets.module.scss' -import { ViewType } from 'types/enums' +import { NotificationType, ViewType } from 'types/enums' +import { QUERY_KEYS } from 'types/enums/queryKeys' -import styles from './Action.module.scss' +import styles from './RedbankAction.module.scss' interface Props { - amount: number - redBankAssets: RedBankAsset[] - depositAssets: RedBankAsset[] - borrowAssets: RedBankAsset[] - setIsMax: (isMax: boolean) => void - setCapHit: (capHit: boolean) => void - setAmountCallback: (amount: number) => void - mmScaledDepositAmount: number - ltvScaledDepositAmount: number - totalBorrowBaseCurrencyAmount: number - actionButtonSpec: ModalActionButton - submitted: boolean - feeError?: string activeView: ViewType - denom: string - decimals: number - handleClose: () => void + id: string } -export const Action = ({ - amount, - redBankAssets, - depositAssets, - borrowAssets, - setIsMax, - setCapHit, - setAmountCallback, - mmScaledDepositAmount, - ltvScaledDepositAmount, - totalBorrowBaseCurrencyAmount, - actionButtonSpec, - submitted, - feeError, - activeView, - denom, - decimals, - handleClose, -}: Props) => { - const { t } = useTranslation() - // ------------------ - // STORE STATE - // ------------------ - const baseCurrency = useStore((s) => s.baseCurrency) - const marketInfo = useStore((s) => s.marketInfo) - const marketAssetLiquidity = useStore((s) => s.marketAssetLiquidity) - const userCollateral = useStore((s) => s.userCollateral) - const userWalletAddress = useStore((s) => s.userWalletAddress) - const whitelistedAssets = useStore((s) => s.whitelistedAssets) - const convertToBaseCurrency = useStore((s) => s.convertToBaseCurrency) - const findUserDebt = useStore((s) => s.findUserDebt) - const enableAnimations = useStore((s) => s.enableAnimations) - const baseCurrencyDecimals = useStore((s) => s.baseCurrency.decimals) +export const RedbankAction = React.memo( + ({ activeView, id }: Props) => { + // ------------------ + // EXTERNAL HOOKS + // ------------------ + const { t } = useTranslation() + const router = useRouter() + const queryClient = useQueryClient() - // ------------------ - // LOCAL STATE - // ------------------ - const [currentAssetPrice, setCurrentAssetPrice] = useState(0) - const [portfolioVisible, setPortfolioVisible] = useState(false) - const [chartsDataLoaded, setChartsDataLoaded] = useState(false) + // ------------------ + // STORE STATE + // ------------------ + const client = useStore((s) => s.client) + const marketInfo = useStore((s) => s.marketInfo) + const networkConfig = useStore((s) => s.networkConfig) + const otherAssets = useStore((s) => s.otherAssets) + const redBankAssets = useStore((s) => s.redBankAssets) + const userBalances = useStore((s) => s.userBalances) + const userCollateral = useStore((s) => s.userCollateral) + const whitelistedAssets = useStore((s) => s.whitelistedAssets) + const executeMsg = useStore((s) => s.executeMsg) - const { data: userBalances } = useUserBalance() + // ------------------ + // LOCAL STATE + // ------------------ + const [amount, setAmount] = useState(0) + const [submitted, setSubmitted] = useState(false) + const [response, setResponse] = useState() + const [error, setError] = useState() + const [isMax, setIsMax] = useState(false) + const [capHit, setCapHit] = useState(false) - /// ------------------ - // VARIABLES - // ------------------ - const walletBalance = Number(findByDenom(userBalances || [], denom)?.amount.toString()) || 0 - const assetBorrowBalance = findUserDebt(denom) - const availableBalanceBaseCurrency = Math.max( - ltvScaledDepositAmount - totalBorrowBaseCurrencyAmount, - 0, - ) - const currentAsset = redBankAssets.find((asset) => asset.denom === denom) + // ------------------ + // VARIABLES + // ------------------ + const assets = [...whitelistedAssets, ...otherAssets] + const denom = assets.find((asset) => asset.id === id)?.denom || '' + const decimals = lookupDecimals(denom, whitelistedAssets || []) || 6 + const symbol = assets.find((asset) => asset.id === id)?.symbol || '' + const walletBallance = Number(findByDenom(userBalances, denom)?.amount.toString()) - // ------------------------- - // calculate - // ------------------------- - const relevantAssetData = useMemo( - () => - activeView === ViewType.Deposit || activeView === ViewType.Withdraw - ? depositAssets - : borrowAssets, - [depositAssets, borrowAssets, activeView], - ) - - const relevantBalanceKey = useMemo( - () => - activeView === ViewType.Deposit || activeView === ViewType.Withdraw - ? 'depositBalanceBaseCurrency' - : 'borrowBalanceBaseCurrency', - [activeView], - ) - - const amountAdjustedAssetData = useMemo( - () => - produceUpdatedAssetData( + // Read only states + const borrowAssetName = redBankAssets.find((asset) => asset.denom === denom) + const redBankContractAddress = networkConfig?.contracts.redBank + const totalScaledDepositbaseCurrencyBalance = useMemo(() => { + if (!userCollateral) return 0 + return ltvWeightedDepositValue( redBankAssets, - [...relevantAssetData], - denom, - amount * currentAssetPrice, // amount in display currency - activeView, - relevantBalanceKey, - baseCurrencyDecimals, - ), - [ - activeView, - amount, - relevantAssetData, - currentAssetPrice, - denom, - redBankAssets, - relevantBalanceKey, - baseCurrencyDecimals, - ], - ) + marketInfo, + userCollateral, + 'depositBalanceBaseCurrency', + ) + }, [redBankAssets, marketInfo, userCollateral]) - const percentData = producePercentData( - produceUpdatedAssetData( - redBankAssets, - [...relevantAssetData], - denom, - 0.0, - activeView, - relevantBalanceKey, - baseCurrencyDecimals, - ), - relevantBalanceKey, - ) - const updatedData = producePercentData(amountAdjustedAssetData, relevantBalanceKey) + const totalMMScaledDepositbaseCurrencyBalance = useMemo(() => { + if (!userCollateral) return 0 + return maintainanceMarginWeightedDepositValue( + redBankAssets, + marketInfo, + userCollateral, + 'depositBalanceBaseCurrency', + ) + }, [redBankAssets, marketInfo, userCollateral]) - // --------------------- - // logic - // --------------------- - const newTotalMMScaledSupplyBalance = useMemo( - () => - // For deposits and withdraws, we need to recalculate the loan limit - { - if (!userCollateral) return 0 - // On first deposit of asset, SC does not hold state of collateral.enabled - // Therefore, we need to emulate this state - const isFirstDeposit = - !relevantAssetData.find((asset) => asset.denom === denom) && - activeView === ViewType.Deposit + const totalBorrowBaseCurrencyAmount = redBankAssets.reduce( + (total, asset) => total + (Number(asset.borrowBalanceBaseCurrency) || 0), + 0, + ) - return activeView === ViewType.Deposit || activeView === ViewType.Withdraw - ? maintainanceMarginWeightedDepositValue( - amountAdjustedAssetData, - marketInfo, - userCollateral, - relevantBalanceKey, - isFirstDeposit ? denom : '', - ) - : mmScaledDepositAmount - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [activeView, amountAdjustedAssetData, mmScaledDepositAmount], - ) + // -------------------------------- + // Transaction objects + // -------------------------------- - const newTotalLTVScaledSupplyBalance = useMemo( - () => - // For deposits and withdraws, we need to recalculate the loan limit - { - if (!userCollateral) return 0 - // On first deposit of asset, SC does not hold state of collateral.enabled - // Therefore, we need to emulate this state - const isFirstDeposit = - !relevantAssetData.find((asset) => asset.denom === denom) && - activeView === ViewType.Deposit + const txMsgOptions = useMemo(() => { + if (!redBankContractAddress || amount <= 0 || !denom) return - return activeView === ViewType.Deposit || activeView === ViewType.Withdraw - ? ltvWeightedDepositValue( - amountAdjustedAssetData, - marketInfo, - userCollateral, - relevantBalanceKey, - isFirstDeposit ? denom : '', - ) - : ltvScaledDepositAmount - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [activeView, amountAdjustedAssetData, ltvScaledDepositAmount], - ) + switch (activeView) { + case ViewType.Deposit: + return getRedbankDepositMsgOptions(amount, denom) + case ViewType.Withdraw: + return getRedbankWithdrawMsgOptions(amount, denom) + case ViewType.Repay: + return getRedbankRepayMsgOptions( + amount, + denom, + Number(findByDenom(userBalances, denom)?.amount) || 0, + isMax, + ) + case ViewType.Borrow: + return getRedbankBorrowMsgOptions(amount, denom) + default: + return undefined + } + }, [activeView, amount, redBankContractAddress, denom, isMax, userBalances]) - const debtValue = - activeView === ViewType.Borrow || activeView === ViewType.Repay - ? balanceSum(amountAdjustedAssetData, relevantBalanceKey) - : totalBorrowBaseCurrencyAmount + const { data: fee, error: feeError } = useEstimateFee({ + msg: txMsgOptions?.msg, + funds: + activeView === ViewType.Deposit || activeView === ViewType.Repay + ? [{ denom, amount: amount > 0 ? amount.toFixed(0) : '1' }] + : undefined, + contract: redBankContractAddress, + }) - const calculateMaxBorrowableAmount = useMemo((): number => { - const assetLiquidity = Number(findByDenom(marketAssetLiquidity, denom)?.amount || 0) - - return maxBorrowableAmount(assetLiquidity, availableBalanceBaseCurrency, currentAssetPrice) - }, [denom, availableBalanceBaseCurrency, currentAssetPrice, marketAssetLiquidity]) - - const repayMax = useMemo((): number => { - return Math.min(assetBorrowBalance, walletBalance) - }, [assetBorrowBalance, walletBalance, denom, baseCurrency.denom]) - - const maxWithdrawableAmount = useMemo((): number => { - const assetLtvRatio = findByDenom(marketInfo, denom)?.max_loan_to_value || 0 - const assetLiquidity = Number(findByDenom(marketAssetLiquidity, denom)?.amount || 0) - const asset = depositAssets.find((asset) => asset.denom === denom) - const assetBalanceOrAvailableLiquidity = Math.min(Number(asset?.depositBalance), assetLiquidity) - - if (totalBorrowBaseCurrencyAmount === 0) { - return assetBalanceOrAvailableLiquidity + const produceActionButtonSpec = (): ModalActionButton => { + return { + disabled: amount === 0 || capHit, + fetching: (amount > 0 && typeof fee === 'undefined') || submitted, + text: t(`redbank.${activeView.toLowerCase()}`), + clickHandler: handleAction, + color: 'primary', + } } - // If we did not receive a usable asset there is nothing more to do. - if (!asset || !asset.depositBalance || !asset.denom) return 0 - - const withdrawableAmountOfAsset = - availableBalanceBaseCurrency / (currentAssetPrice * assetLtvRatio) - - return withdrawableAmountOfAsset < assetBalanceOrAvailableLiquidity - ? withdrawableAmountOfAsset - : assetBalanceOrAvailableLiquidity - }, [ - denom, - currentAssetPrice, - depositAssets, - availableBalanceBaseCurrency, - totalBorrowBaseCurrencyAmount, - marketInfo, - marketAssetLiquidity, - ]) - - const maxUsableAmount = useMemo(() => { - if (!currentAsset) return 0 - return activeView === ViewType.Deposit - ? walletBalance - : activeView === ViewType.Withdraw - ? maxWithdrawableAmount - : activeView === ViewType.Borrow - ? calculateMaxBorrowableAmount - : repayMax - }, [ - walletBalance, - maxWithdrawableAmount, - calculateMaxBorrowableAmount, - repayMax, - activeView, - currentAsset, - ]) - - useEffect(() => { - setCurrentAssetPrice(convertToBaseCurrency({ denom: denom || '', amount: '1' })) - }, [denom, convertToBaseCurrency]) - - useEffect(() => { - if (!chartsDataLoaded && percentData[0] != 0) { - setChartsDataLoaded(true) - } - }, [percentData, chartsDataLoaded]) - - const chartRefBefore = useRef(null) - const chartRefAfter = useRef(null) - - // ----------- - // callbacks - // ----------- - const handleInputAmount = useCallback( - (inputAmount: number) => { - if (inputAmount >= maxUsableAmount * 0.99) { - setIsMax(true) + const handleAction = async () => { + if (!redBankContractAddress || !client) { + alert('Uh oh, operation failed') + return } - setAmountCallback(Number(formatValue(inputAmount, 0, 0, false, false, false, false, false))) - }, - [maxUsableAmount, setIsMax, setAmountCallback], - ) + setSubmitted(true) - if (!currentAsset) return <> + if (!fee || !txMsgOptions) { + return + } - const amountUntilDepositCap = currentAsset.depositCap - Number(currentAsset.depositLiquidity) - - const onValueEntered = (microValue: number) => { - if (microValue >= maxUsableAmount) microValue = maxUsableAmount - setAmountCallback(Number(formatValue(microValue, 0, 0, false, false, false, false, false))) - setCapHit(amount > amountUntilDepositCap && activeView === ViewType.Deposit) - } - - const produceTabActionButton = () => { - return ( - <> -