import { connect } from 'react-redux'; import { addHexPrefix } from '../../../../../app/scripts/lib/util'; import { hideModal, createRetryTransaction, createSpeedUpTransaction, } from '../../../../store/actions'; import { setCustomGasPrice, setCustomGasLimit, resetCustomData, } from '../../../../ducks/gas/gas.duck'; import { getSendMaxModeState, getGasLimit, getGasPrice, getSendAmount, updateGasLimit, updateGasPrice, useCustomGas, getSendAsset, } from '../../../../ducks/send'; import { conversionRateSelector as getConversionRate, getCurrentCurrency, getCurrentEthBalance, getIsMainnet, getIsTestnet, getBasicGasEstimateLoadingStatus, getCustomGasLimit, getCustomGasPrice, getDefaultActiveButtonIndex, getRenderableBasicEstimateData, isCustomPriceSafe, isCustomPriceSafeForCustomNetwork, getAveragePriceEstimateInHexWEI, isCustomPriceExcessive, getIsGasEstimatesFetched, getShouldShowFiat, getIsCustomNetworkGasPriceFetched, } from '../../../../selectors'; import { addHexes, subtractHexWEIsToDec, getValueFromWeiHex, sumHexWEIsToRenderableFiat, } from '../../../../helpers/utils/conversions.util'; import { formatETHFee } from '../../../../helpers/utils/formatters'; import { isBalanceSufficient } from '../../../../pages/send/send.utils'; import { MIN_GAS_LIMIT_DEC } from '../../../../pages/send/send.constants'; import { ASSET_TYPES, TRANSACTION_STATUSES, } from '../../../../../shared/constants/transaction'; import { GAS_LIMITS } from '../../../../../shared/constants/gas'; import { updateGasFees } from '../../../../ducks/metamask/metamask'; import { calcGasTotal, hexWEIToDecGWEI, } from '../../../../../shared/lib/transactions-controller-utils'; import GasModalPageContainer from './gas-modal-page-container.component'; const mapStateToProps = (state, ownProps) => { const gasLimit = getGasLimit(state); const gasPrice = getGasPrice(state); const amount = getSendAmount(state); const { currentNetworkTxList } = state.metamask; const { modalState: { props: modalProps } = {} } = state.appState.modal || {}; const { txData = {} } = modalProps || {}; const { transaction = {}, onSubmit } = ownProps; const selectedTransaction = currentNetworkTxList.find( ({ id }) => id === (transaction.id || txData.id), ); const buttonDataLoading = getBasicGasEstimateLoadingStatus(state); const asset = getSendAsset(state); // a "default" txParams is used during the send flow, since the transaction doesn't exist yet in that case const txParams = selectedTransaction?.txParams ? selectedTransaction.txParams : { gas: gasLimit || GAS_LIMITS.SIMPLE, gasPrice: gasPrice || getAveragePriceEstimateInHexWEI(state, true), value: asset.type === ASSET_TYPES.TOKEN ? '0x0' : amount, }; const { gasPrice: currentGasPrice, gas: currentGasLimit } = txParams; const value = ownProps.transaction?.txParams?.value || txParams.value; const customModalGasPriceInHex = getCustomGasPrice(state) || currentGasPrice; const customModalGasLimitInHex = getCustomGasLimit(state) || currentGasLimit || GAS_LIMITS.SIMPLE; const customGasTotal = calcGasTotal( customModalGasLimitInHex, customModalGasPriceInHex, ); const gasButtonInfo = getRenderableBasicEstimateData( state, customModalGasLimitInHex, ); const currentCurrency = getCurrentCurrency(state); const conversionRate = getConversionRate(state); const newTotalFiat = sumHexWEIsToRenderableFiat( [value, customGasTotal], currentCurrency, conversionRate, ); const { hideBasic } = state.appState.modal.modalState.props; const customGasPrice = calcCustomGasPrice(customModalGasPriceInHex); const maxModeOn = getSendMaxModeState(state); const balance = getCurrentEthBalance(state); const isMainnet = getIsMainnet(state); const isTestnet = getIsTestnet(state); const showFiat = getShouldShowFiat(state); const newTotalEth = maxModeOn && asset.type === ASSET_TYPES.NATIVE ? sumHexWEIsToRenderableEth([balance, '0x0']) : sumHexWEIsToRenderableEth([value, customGasTotal]); const sendAmount = maxModeOn && asset.type === ASSET_TYPES.NATIVE ? subtractHexWEIsFromRenderableEth(balance, customGasTotal) : sumHexWEIsToRenderableEth([value, '0x0']); const insufficientBalance = maxModeOn ? false : !isBalanceSufficient({ amount: value, gasTotal: customGasTotal, balance, conversionRate, }); const isGasEstimate = getIsGasEstimatesFetched(state); const customNetworkEstimateWasFetched = getIsCustomNetworkGasPriceFetched(state); let customPriceIsSafe = true; if ((isMainnet || process.env.IN_TEST) && isGasEstimate) { customPriceIsSafe = isCustomPriceSafe(state); } else if ( !(isMainnet || process.env.IN_TEST || isTestnet) && customNetworkEstimateWasFetched ) { customPriceIsSafe = isCustomPriceSafeForCustomNetwork(state); } return { hideBasic, isConfirm: isConfirm(state), customModalGasPriceInHex, customModalGasLimitInHex, customGasPrice, customGasLimit: calcCustomGasLimit(customModalGasLimitInHex), customGasTotal, newTotalFiat, customPriceIsSafe, customPriceIsExcessive: isCustomPriceExcessive(state), maxModeOn, gasPriceButtonGroupProps: { buttonDataLoading, defaultActiveButtonIndex: getDefaultActiveButtonIndex( gasButtonInfo, customModalGasPriceInHex, ), gasButtonInfo, }, infoRowProps: { originalTotalFiat: sumHexWEIsToRenderableFiat( [value, customGasTotal], currentCurrency, conversionRate, ), originalTotalEth: sumHexWEIsToRenderableEth([value, customGasTotal]), newTotalFiat: showFiat ? newTotalFiat : '', newTotalEth, transactionFee: sumHexWEIsToRenderableEth(['0x0', customGasTotal]), sendAmount, }, transaction: txData || transaction, isSpeedUp: transaction.status === TRANSACTION_STATUSES.SUBMITTED, isRetry: transaction.status === TRANSACTION_STATUSES.FAILED, txId: transaction.id, insufficientBalance, isMainnet, balance, conversionRate, value, onSubmit, }; }; const mapDispatchToProps = (dispatch) => { const updateCustomGasPrice = (newPrice) => dispatch(setCustomGasPrice(addHexPrefix(newPrice))); return { cancelAndClose: () => { dispatch(resetCustomData()); dispatch(hideModal()); }, hideModal: () => dispatch(hideModal()), useCustomGas: () => dispatch(useCustomGas()), updateTransactionGasFees: (gasFees) => { dispatch(updateGasFees({ ...gasFees, expectHexWei: true })); }, updateCustomGasPrice, updateCustomGasLimit: (newLimit) => dispatch(setCustomGasLimit(addHexPrefix(newLimit))), setGasData: (newLimit, newPrice) => { dispatch(updateGasLimit(newLimit)); dispatch(updateGasPrice(newPrice)); }, createRetryTransaction: (txId, customGasSettings) => { return dispatch(createRetryTransaction(txId, customGasSettings)); }, createSpeedUpTransaction: (txId, customGasSettings) => { return dispatch(createSpeedUpTransaction(txId, customGasSettings)); }, }; }; const mergeProps = (stateProps, dispatchProps, ownProps) => { const { gasPriceButtonGroupProps, // eslint-disable-next-line no-shadow isConfirm, txId, isSpeedUp, isRetry, insufficientBalance, customGasPrice, customGasLimit, transaction, } = stateProps; const { useCustomGas: dispatchUseCustomGas, setGasData: dispatchSetGasData, createSpeedUpTransaction: dispatchCreateSpeedUpTransaction, createRetryTransaction: dispatchCreateRetryTransaction, updateTransactionGasFees: dispatchUpdateTransactionGasFees, cancelAndClose: dispatchCancelAndClose, hideModal: dispatchHideModal, ...otherDispatchProps } = dispatchProps; return { ...stateProps, ...otherDispatchProps, ...ownProps, onSubmit: (gasLimit, gasPrice) => { if (ownProps.onSubmit) { dispatchCancelAndClose(); ownProps.onSubmit({ gasLimit, gasPrice }); return; } if (isConfirm) { dispatchUpdateTransactionGasFees({ gasLimit, gasPrice, transaction, isModal: true, }); dispatchHideModal(); dispatchCancelAndClose(); } else if (isSpeedUp) { dispatchCreateSpeedUpTransaction(txId, { gasPrice, gasLimit }); dispatchCancelAndClose(); } else if (isRetry) { dispatchCreateRetryTransaction(txId, { gasPrice, gasLimit }); dispatchCancelAndClose(); } else { dispatchSetGasData(gasLimit, gasPrice); dispatchUseCustomGas(); dispatchCancelAndClose(); } }, gasPriceButtonGroupProps: { ...gasPriceButtonGroupProps, handleGasPriceSelection: ({ gasPrice }) => otherDispatchProps.updateCustomGasPrice(gasPrice), }, cancelAndClose: () => { dispatchCancelAndClose(); }, disableSave: insufficientBalance || (isSpeedUp && customGasPrice === 0) || customGasLimit < Number(MIN_GAS_LIMIT_DEC), }; }; export default connect( mapStateToProps, mapDispatchToProps, mergeProps, )(GasModalPageContainer); function isConfirm(state) { return Boolean(Object.keys(state.confirmTransaction.txData).length); } function calcCustomGasPrice(customGasPriceInHex) { return Number(hexWEIToDecGWEI(customGasPriceInHex)); } function calcCustomGasLimit(customGasLimitInHex) { return parseInt(customGasLimitInHex, 16); } function sumHexWEIsToRenderableEth(hexWEIs) { const hexWEIsSum = hexWEIs.filter(Boolean).reduce(addHexes); return formatETHFee( getValueFromWeiHex({ value: hexWEIsSum, toCurrency: 'ETH', numberOfDecimals: 6, }), ); } function subtractHexWEIsFromRenderableEth(aHexWEI, bHexWEI) { return formatETHFee(subtractHexWEIsToDec(aHexWEI, bHexWEI)); }