diff --git a/src/custom/components/swap/EthWethWrap/helpers.ts b/src/custom/components/swap/EthWethWrap/helpers.ts index 83ec7f5b5..4c290a730 100644 --- a/src/custom/components/swap/EthWethWrap/helpers.ts +++ b/src/custom/components/swap/EthWethWrap/helpers.ts @@ -1,7 +1,9 @@ import { parseUnits } from '@ethersproject/units' import { CurrencyAmount, Currency } from '@uniswap/sdk-core' +import { BigNumber } from '@ethersproject/bignumber' // eslint-disable-next-line no-restricted-imports import { t } from '@lingui/macro' +import { useGasPrices } from 'state/gas/hooks' export const MINIMUM_TXS = '10' export const AVG_APPROVE_COST_GWEI = '50000' @@ -18,11 +20,12 @@ export function _isLowBalanceCheck({ nativeInput, balance, }: { - threshold: CurrencyAmount - txCost: CurrencyAmount + threshold?: CurrencyAmount + txCost?: CurrencyAmount nativeInput?: CurrencyAmount balance?: CurrencyAmount }) { + if (!threshold || !txCost) return false if (!nativeInput || !balance || nativeInput.add(txCost).greaterThan(balance)) return true // OK if: users_balance - (amt_input + 1_tx_cost) > low_balance_threshold return balance.subtract(nativeInput.add(txCost)).lessThan(threshold) @@ -35,11 +38,29 @@ export const _getAvailableTransactions = ({ }: { nativeBalance?: CurrencyAmount nativeInput?: CurrencyAmount - singleTxCost: CurrencyAmount + singleTxCost?: CurrencyAmount }) => { - if (!nativeBalance || !nativeInput || nativeBalance.lessThan(nativeInput.add(singleTxCost))) return null + if (!nativeBalance || !nativeInput || !singleTxCost || nativeBalance.lessThan(nativeInput.add(singleTxCost))) { + return null + } // USER_BALANCE - (USER_WRAP_AMT + 1_TX_CST) / 1_TX_COST = AVAILABLE_TXS const txsAvailable = nativeBalance.subtract(nativeInput.add(singleTxCost)).divide(singleTxCost) return txsAvailable.lessThan('1') ? null : txsAvailable.toSignificant(1) } + +export function _estimateTxCost(gasPrice: ReturnType, native: Currency | undefined) { + if (!native) { + return {} + } + // TODO: should use DEFAULT_GAS_FEE from backup source + // when/if implemented + const gas = gasPrice?.standard || DEFAULT_GAS_FEE + + const amount = BigNumber.from(gas).mul(MINIMUM_TXS).mul(AVG_APPROVE_COST_GWEI) + + return { + multiTxCost: CurrencyAmount.fromRawAmount(native, amount.toString()), + singleTxCost: CurrencyAmount.fromFractionalAmount(native, amount.toString(), MINIMUM_TXS), + } +} diff --git a/src/custom/components/swap/EthWethWrap/index.tsx b/src/custom/components/swap/EthWethWrap/index.tsx index 02dacdf4b..de251cf45 100644 --- a/src/custom/components/swap/EthWethWrap/index.tsx +++ b/src/custom/components/swap/EthWethWrap/index.tsx @@ -14,15 +14,7 @@ import { useIsTransactionPending } from 'state/enhancedTransactions/hooks' import Modal from 'components/Modal' import { useGasPrices } from 'state/gas/hooks' import { useActiveWeb3React } from 'hooks/web3' -import { BigNumber } from '@ethersproject/bignumber' -import { - DEFAULT_GAS_FEE, - MINIMUM_TXS, - AVG_APPROVE_COST_GWEI, - _isLowBalanceCheck, - _setNativeLowBalanceError, - _getAvailableTransactions, -} from './helpers' +import { _isLowBalanceCheck, _setNativeLowBalanceError, _getAvailableTransactions, _estimateTxCost } from './helpers' import { Trans } from '@lingui/macro' const Wrapper = styled.div` @@ -152,18 +144,7 @@ export default function EthWethWrap({ account, native, nativeInput, wrapped, wra const gasPrice = useGasPrices(chainId) // returns the cost of 1 tx and multi txs - const { multiTxCost, singleTxCost } = useMemo(() => { - // TODO: should use DEFAULT_GAS_FEE from backup source - // when/if implemented - const gas = gasPrice?.standard || DEFAULT_GAS_FEE - - const amount = BigNumber.from(gas).mul(MINIMUM_TXS).mul(AVG_APPROVE_COST_GWEI) - - return { - multiTxCost: CurrencyAmount.fromRawAmount(native, amount.toString()), - singleTxCost: CurrencyAmount.fromFractionalAmount(native, amount.toString(), MINIMUM_TXS), - } - }, [gasPrice, native]) + const { multiTxCost, singleTxCost } = useMemo(() => _estimateTxCost(gasPrice, native), [gasPrice, native]) const isWrapPending = useIsTransactionPending(pendingHash) const [nativeBalance, wrappedBalance] = useCurrencyBalances(account, [native, wrapped]) diff --git a/src/custom/constants/index.ts b/src/custom/constants/index.ts index cee437e3f..7cebfc266 100644 --- a/src/custom/constants/index.ts +++ b/src/custom/constants/index.ts @@ -84,8 +84,6 @@ export const WETH_LOGO_URI = export const XDAI_LOGO_URI = 'https://raw.githubusercontent.com/1Hive/default-token-list/master/src/assets/xdai/0xe91d153e0b41518a2ce8dd3d7944fa863463a97d/logo.png' -// 0.1 balance threshold -export const LOW_NATIVE_BALANCE_THRESHOLD = new Fraction('1', '10') export const DOCS_LINK = 'https://docs.cow.fi' export const CONTRACTS_CODE_LINK = 'https://github.com/gnosis/gp-v2-contracts' export const CODE_LINK = 'https://github.com/gnosis/gp-swap-ui' diff --git a/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx b/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx index eb120c664..958054cc4 100644 --- a/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx +++ b/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx @@ -20,12 +20,17 @@ import { useErrorModal } from 'hooks/useErrorMessageAndModal' import { tryParseAmount } from 'state/swap/hooks' import { calculateInvestmentAmounts, calculatePercentage } from 'state/claim/hooks/utils' import { AMOUNT_PRECISION, PERCENTAGE_PRECISION } from 'constants/index' +import { useGasPrices } from 'state/gas/hooks' +import { _estimateTxCost } from 'components/swap/EthWethWrap/helpers' +import { useWalletInfo } from 'hooks/useWalletInfo' const ErrorMsgs = { InsufficientBalance: (symbol = '') => `Insufficient ${symbol} balance to cover investment amount`, OverMaxInvestment: `Your investment amount can't be above the maximum investment allowed`, InvestmentIsZero: `Your investment amount can't be zero`, NotApproved: (symbol = '') => `Please approve ${symbol} token`, + InsufficientNativeBalance: (symbol = '', action = "won't") => + `You ${action} have enough ${symbol} to pay the network transaction fee`, } export default function InvestOption({ approveData, claim, optionIndex }: InvestOptionProps) { @@ -35,10 +40,12 @@ export default function InvestOption({ approveData, claim, optionIndex }: Invest const { handleSetError, handleCloseError, ErrorModal } = useErrorModal() - const { account } = useActiveWeb3React() + const { account, chainId } = useActiveWeb3React() + const { isSmartContractWallet } = useWalletInfo() const [percentage, setPercentage] = useState('0') const [typedValue, setTypedValue] = useState('0') + const [inputWarning, setInputWarning] = useState('') const investedAmount = investFlowData[optionIndex].investedAmount const inputError = investFlowData[optionIndex].error @@ -60,6 +67,12 @@ export default function InvestOption({ approveData, claim, optionIndex }: Invest const token = currencyAmount?.currency const balance = useCurrencyBalance(account || undefined, token) + const gasPrice = useGasPrices(chainId) + const { singleTxCost } = useMemo( + () => _estimateTxCost(gasPrice, token?.isNative ? token : undefined), + [gasPrice, token] + ) + const isSelfClaiming = account === activeClaimAccount const noBalance = !balance || balance.equalTo('0') @@ -132,6 +145,7 @@ export default function InvestOption({ approveData, claim, optionIndex }: Invest // handle input value change useEffect(() => { let error = null + let warning const parsedAmount = tryParseAmount(typedValue, token) @@ -150,7 +164,14 @@ export default function InvestOption({ approveData, claim, optionIndex }: Invest error = ErrorMsgs.OverMaxInvestment } else if (parsedAmount.greaterThan(balance)) { error = ErrorMsgs.InsufficientBalance(token?.symbol) + } else if (isNative && parsedAmount && singleTxCost?.add(parsedAmount).greaterThan(balance)) { + if (isSmartContractWallet) { + warning = ErrorMsgs.InsufficientNativeBalance(token?.symbol, 'might not') + } else { + error = ErrorMsgs.InsufficientNativeBalance(token?.symbol) + } } + setInputWarning(warning || '') if (error) { // if there is error set it in redux @@ -162,7 +183,7 @@ export default function InvestOption({ approveData, claim, optionIndex }: Invest } // basically the magic happens in this block - // update redux state to remove errro for this field + // update redux state to remove error for this field resetInputError() // update redux state with new investAmount value @@ -182,6 +203,8 @@ export default function InvestOption({ approveData, claim, optionIndex }: Invest setInputError, resetInputError, setInvestedAmount, + isSmartContractWallet, + singleTxCost, ]) return ( @@ -284,7 +307,8 @@ export default function InvestOption({ approveData, claim, optionIndex }: Invest Receive: {formatSmartLocaleAware(vCowAmount, AMOUNT_PRECISION) || 0} vCOW {/* Insufficient balance validation error */} - {inputError ? {inputError} : ''} + {inputError && {inputError}} + {inputWarning && {inputWarning}} diff --git a/src/custom/pages/Claim/styled.ts b/src/custom/pages/Claim/styled.ts index c25385492..6461137cc 100644 --- a/src/custom/pages/Claim/styled.ts +++ b/src/custom/pages/Claim/styled.ts @@ -935,6 +935,10 @@ export const InvestInput = styled.span` color: red; margin: 12px 0; font-size: 15px; + + &.warn { + color: orange; + } } > div > i {