diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index dfeeaeb82..1cfbb4d4c 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -44,6 +44,7 @@ class PreferencesController { // perform sensitive operations. featureFlags: { showIncomingTransactions: true, + transactionTime: false, }, knownMethodData: {}, participateInMetaMetrics: null, diff --git a/test/unit/ui/app/selectors.spec.js b/test/unit/ui/app/selectors.spec.js index a190462b0..f8d58f61c 100644 --- a/test/unit/ui/app/selectors.spec.js +++ b/test/unit/ui/app/selectors.spec.js @@ -109,12 +109,6 @@ describe('Selectors', function () { assert.equal(currentAccountwithSendEther.name, 'Test Account') }) - describe('#transactionSelector', function () { - it('returns transactions from state', function () { - selectors.transactionsSelector(mockState) - }) - }) - it('#getGasIsLoading', () => { const gasIsLoading = selectors.getGasIsLoading(mockState) assert.equal(gasIsLoading, false) diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js b/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js index bf17a049a..520946a95 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js @@ -34,8 +34,6 @@ import { preferencesSelector, } from '../../../../selectors/selectors.js' import { - formatTimeEstimate, - getFastPriceEstimateInHexWEI, getBasicGasEstimateLoadingStatus, getGasEstimatesLoadingStatus, getCustomGasLimit, @@ -47,6 +45,9 @@ import { getBasicGasEstimateBlockTime, isCustomPriceSafe, } from '../../../../selectors/custom-gas' +import { + getTxParams, +} from '../../../../selectors/transactions' import { getTokenBalance, } from '../../../../pages/send/send.selectors' @@ -59,6 +60,7 @@ import { decEthToConvertedCurrency as ethTotalToConvertedCurrency, hexWEIToDecGWEI, } from '../../../../helpers/utils/conversions.util' +import { getRenderableTimeEstimate } from '../../../../helpers/utils/gas-time-estimates.util' import { formatETHFee, } from '../../../../helpers/utils/formatters' @@ -67,7 +69,6 @@ import { isBalanceSufficient, } from '../../../../pages/send/send.utils' import { addHexPrefix } from 'ethereumjs-util' -import { getAdjacentGasPrices, extrapolateY } from '../gas-price-chart/gas-price-chart.utils' import { getMaxModeOn } from '../../../../pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.selectors' import { calcMaxAmount } from '../../../../pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.utils' @@ -301,18 +302,6 @@ function calcCustomGasLimit (customGasLimitInHex) { return parseInt(customGasLimitInHex, 16) } -function getTxParams (state, selectedTransaction = {}) { - const { metamask: { send } } = state - const { txParams } = selectedTransaction - return txParams || { - from: send.from, - gas: send.gasLimit || '0x5208', - gasPrice: send.gasPrice || getFastPriceEstimateInHexWEI(state, true), - to: send.to, - value: getSelectedToken(state) ? '0x0' : send.amount, - } -} - function addHexWEIsToRenderableEth (aHexWEI, bHexWEI) { return pipe( addHexWEIsToDec, @@ -334,31 +323,3 @@ function addHexWEIsToRenderableFiat (aHexWEI, bHexWEI, convertedCurrency, conver partialRight(formatCurrency, [convertedCurrency]), )(aHexWEI, bHexWEI) } - -function getRenderableTimeEstimate (currentGasPrice, gasPrices, estimatedTimes) { - const minGasPrice = gasPrices[0] - const maxGasPrice = gasPrices[gasPrices.length - 1] - let priceForEstimation = currentGasPrice - if (currentGasPrice < minGasPrice) { - priceForEstimation = minGasPrice - } else if (currentGasPrice > maxGasPrice) { - priceForEstimation = maxGasPrice - } - - const { - closestLowerValueIndex, - closestHigherValueIndex, - closestHigherValue, - closestLowerValue, - } = getAdjacentGasPrices({ gasPrices, priceToPosition: priceForEstimation }) - - const newTimeEstimate = extrapolateY({ - higherY: estimatedTimes[closestHigherValueIndex], - lowerY: estimatedTimes[closestLowerValueIndex], - higherX: closestHigherValue, - lowerX: closestLowerValue, - xForExtrapolation: priceForEstimation, - }) - - return formatTimeEstimate(newTimeEstimate, currentGasPrice > maxGasPrice, currentGasPrice < minGasPrice) -} diff --git a/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js b/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js index b941f1cf9..419cae0cd 100644 --- a/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js +++ b/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js @@ -1,11 +1,12 @@ import * as d3 from 'd3' import c3 from 'c3' -import BigNumber from 'bignumber.js' - -const newBigSigDig = n => (new BigNumber(n.toPrecision(15))) -const createOp = (a, b, op) => (newBigSigDig(a))[op](newBigSigDig(b)) -const bigNumMinus = (a = 0, b = 0) => createOp(a, b, 'minus') -const bigNumDiv = (a = 0, b = 1) => createOp(a, b, 'div') +import { + extrapolateY, + getAdjacentGasPrices, + newBigSigDig, + bigNumMinus, + bigNumDiv, +} from '../../../../helpers/utils/gas-time-estimates.util' export function handleMouseMove ({ xMousePos, chartXStart, chartWidth, gasPrices, estimatedTimes, chart }) { const { currentPosValue, newTimeEstimate } = getNewXandTimeEstimate({ @@ -66,25 +67,6 @@ export function handleChartUpdate ({ chart, gasPrices, newPrice, cssId }) { } } -export function getAdjacentGasPrices ({ gasPrices, priceToPosition }) { - const closestLowerValueIndex = gasPrices.findIndex((e, i, a) => e <= priceToPosition && a[i + 1] >= priceToPosition) - const closestHigherValueIndex = gasPrices.findIndex((e) => e > priceToPosition) - return { - closestLowerValueIndex, - closestHigherValueIndex, - closestHigherValue: gasPrices[closestHigherValueIndex], - closestLowerValue: gasPrices[closestLowerValueIndex], - } -} - -export function extrapolateY ({ higherY = 0, lowerY = 0, higherX = 0, lowerX = 0, xForExtrapolation = 0 }) { - const slope = bigNumMinus(higherY, lowerY).div(bigNumMinus(higherX, lowerX)) - const newTimeEstimate = slope.times(bigNumMinus(higherX, xForExtrapolation)).minus(newBigSigDig(higherY)).negated() - - return newTimeEstimate.toNumber() -} - - export function getNewXandTimeEstimate ({ xMousePos, chartXStart, chartWidth, gasPrices, estimatedTimes }) { const chartMouseXPos = bigNumMinus(xMousePos, chartXStart) const posPercentile = bigNumDiv(chartMouseXPos, chartWidth) diff --git a/ui/app/components/app/transaction-list-item/index.scss b/ui/app/components/app/transaction-list-item/index.scss index 02732768e..54a2e9db3 100644 --- a/ui/app/components/app/transaction-list-item/index.scss +++ b/ui/app/components/app/transaction-list-item/index.scss @@ -15,17 +15,17 @@ display: grid; grid-template-columns: 45px 1fr 1fr 1fr; grid-template-areas: - "identicon action status primary-amount" - "identicon nonce status secondary-amount"; + "identicon action status estimated-time primary-amount" + "identicon nonce status estimated-time secondary-amount"; grid-template-rows: 24px; @media screen and (max-width: $break-small) { padding: .5rem 1rem; grid-template-columns: 45px 5fr 3fr; grid-template-areas: - "nonce nonce nonce" - "identicon action primary-amount" - "identicon status secondary-amount"; + "nonce nonce nonce nonce" + "identicon action estimated-time primary-amount" + "identicon status estimated-time secondary-amount"; grid-template-rows: auto 24px; } @@ -65,6 +65,18 @@ } } + &__estimated-time { + grid-area: estimated-time; + grid-row: 1 / span 2; + align-self: center; + + @media screen and (max-width: $break-small) { + grid-row: 3; + grid-column: 4; + font-size: small; + } + } + &__nonce { font-size: .75rem; color: #5e6064; diff --git a/ui/app/components/app/transaction-list-item/transaction-list-item.component.js b/ui/app/components/app/transaction-list-item/transaction-list-item.component.js index bb6acae68..d1d6f061d 100644 --- a/ui/app/components/app/transaction-list-item/transaction-list-item.component.js +++ b/ui/app/components/app/transaction-list-item/transaction-list-item.component.js @@ -7,6 +7,7 @@ import TransactionAction from '../transaction-action' import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display' import TokenCurrencyDisplay from '../../ui/token-currency-display' import TransactionListItemDetails from '../transaction-list-item-details' +import TransactionTimeRemaining from '../transaction-time-remaining' import { CONFIRM_TRANSACTION_ROUTE } from '../../../helpers/constants/routes' import { UNAPPROVED_STATUS, TOKEN_METHOD_TRANSFER } from '../../../helpers/constants/transactions' import { PRIMARY, SECONDARY } from '../../../helpers/constants/common' @@ -38,6 +39,8 @@ export default class TransactionListItem extends PureComponent { data: PropTypes.string, getContractMethodData: PropTypes.func, isDeposit: PropTypes.bool, + transactionTimeFeatureActive: PropTypes.bool, + firstPendingTransactionId: PropTypes.number, } static defaultProps = { @@ -52,6 +55,13 @@ export default class TransactionListItem extends PureComponent { showTransactionDetails: false, } + componentDidMount () { + if (this.props.data) { + this.props.getContractMethodData(this.props.data) + } + + } + handleClick = () => { const { transaction, @@ -162,12 +172,6 @@ export default class TransactionListItem extends PureComponent { ) } - componentDidMount () { - if (this.props.data) { - this.props.getContractMethodData(this.props.data) - } - } - render () { const { assetImages, @@ -182,6 +186,8 @@ export default class TransactionListItem extends PureComponent { transactionGroup, rpcPrefs, isEarliestNonce, + firstPendingTransactionId, + transactionTimeFeatureActive, } = this.props const { txParams = {} } = transaction const { showTransactionDetails } = this.state @@ -221,6 +227,13 @@ export default class TransactionListItem extends PureComponent { : primaryTransaction.err && primaryTransaction.err.message )} /> + { transactionTimeFeatureActive && (transaction.id === firstPendingTransactionId) + ? + : null + } { this.renderPrimaryCurrency() } { this.renderSecondaryCurrency() } diff --git a/ui/app/components/app/transaction-list-item/transaction-list-item.container.js b/ui/app/components/app/transaction-list-item/transaction-list-item.container.js index c3cf0295b..26ccec1f7 100644 --- a/ui/app/components/app/transaction-list-item/transaction-list-item.container.js +++ b/ui/app/components/app/transaction-list-item/transaction-list-item.container.js @@ -8,12 +8,19 @@ import { getTokenData } from '../../../helpers/utils/transactions.util' import { getHexGasTotal, increaseLastGasPrice } from '../../../helpers/utils/confirm-tx.util' import { formatDate } from '../../../helpers/utils/util' import { - fetchBasicGasAndTimeEstimates, fetchGasEstimates, + fetchBasicGasAndTimeEstimates, setCustomGasPriceForRetry, setCustomGasLimit, } from '../../../ducks/gas/gas.duck' -import { getIsMainnet, preferencesSelector, getSelectedAddress, conversionRateSelector, getKnownMethodData } from '../../../selectors/selectors' +import { + getIsMainnet, + preferencesSelector, + getSelectedAddress, + conversionRateSelector, + getKnownMethodData, + getFeatureFlags, +} from '../../../selectors/selectors' import { isBalanceSufficient } from '../../../pages/send/send.utils' const mapStateToProps = (state, ownProps) => { @@ -38,6 +45,8 @@ const mapStateToProps = (state, ownProps) => { conversionRate: conversionRateSelector(state), }) + const transactionTimeFeatureActive = getFeatureFlags(state).transactionTime + return { methodData: getKnownMethodData(state, data) || {}, showFiat: (isMainnet || !!showFiatInTestnets), @@ -45,6 +54,7 @@ const mapStateToProps = (state, ownProps) => { hasEnoughCancelGas, rpcPrefs, isDeposit, + transactionTimeFeatureActive, } } diff --git a/ui/app/components/app/transaction-list/transaction-list.component.js b/ui/app/components/app/transaction-list/transaction-list.component.js index 553a0ffc4..0e0540257 100644 --- a/ui/app/components/app/transaction-list/transaction-list.component.js +++ b/ui/app/components/app/transaction-list/transaction-list.component.js @@ -22,19 +22,50 @@ export default class TransactionList extends PureComponent { selectedToken: PropTypes.object, updateNetworkNonce: PropTypes.func, assetImages: PropTypes.object, + fetchBasicGasAndTimeEstimates: PropTypes.func, + fetchGasEstimates: PropTypes.func, + transactionTimeFeatureActive: PropTypes.bool, + firstPendingTransactionId: PropTypes.number, } componentDidMount () { - this.props.updateNetworkNonce() + const { + pendingTransactions, + updateNetworkNonce, + fetchBasicGasAndTimeEstimates, + fetchGasEstimates, + transactionTimeFeatureActive, + } = this.props + + updateNetworkNonce() + + if (transactionTimeFeatureActive && pendingTransactions.length) { + fetchBasicGasAndTimeEstimates() + .then(({ blockTime }) => fetchGasEstimates(blockTime)) + } } componentDidUpdate (prevProps) { const { pendingTransactions: prevPendingTransactions = [] } = prevProps - const { pendingTransactions = [], updateNetworkNonce } = this.props + const { + pendingTransactions = [], + updateNetworkNonce, + fetchBasicGasAndTimeEstimates, + fetchGasEstimates, + transactionTimeFeatureActive, + } = this.props if (pendingTransactions.length > prevPendingTransactions.length) { updateNetworkNonce() } + + const transactionTimeFeatureWasActivated = !prevProps.transactionTimeFeatureActive && transactionTimeFeatureActive + const pendingTransactionAdded = pendingTransactions.length > 0 && prevPendingTransactions.length === 0 + + if (transactionTimeFeatureActive && pendingTransactions.length > 0 && (transactionTimeFeatureWasActivated || pendingTransactionAdded)) { + fetchBasicGasAndTimeEstimates() + .then(({ blockTime }) => fetchGasEstimates(blockTime)) + } } shouldShowSpeedUp = (transactionGroup, isEarliestNonce) => { @@ -87,7 +118,7 @@ export default class TransactionList extends PureComponent { } renderTransaction (transactionGroup, index, isPendingTx = false) { - const { selectedToken, assetImages } = this.props + const { selectedToken, assetImages, firstPendingTransactionId } = this.props const { transactions = [] } = transactionGroup return transactions[0].key === TRANSACTION_TYPE_SHAPESHIFT @@ -105,6 +136,7 @@ export default class TransactionList extends PureComponent { isEarliestNonce={isPendingTx && index === 0} token={selectedToken} assetImages={assetImages} + firstPendingTransactionId={firstPendingTransactionId} /> ) } diff --git a/ui/app/components/app/transaction-list/transaction-list.container.js b/ui/app/components/app/transaction-list/transaction-list.container.js index 67a24588b..4da044b2a 100644 --- a/ui/app/components/app/transaction-list/transaction-list.container.js +++ b/ui/app/components/app/transaction-list/transaction-list.container.js @@ -6,23 +6,30 @@ import { nonceSortedCompletedTransactionsSelector, nonceSortedPendingTransactionsSelector, } from '../../../selectors/transactions' -import { getSelectedAddress, getAssetImages } from '../../../selectors/selectors' +import { getSelectedAddress, getAssetImages, getFeatureFlags } from '../../../selectors/selectors' import { selectedTokenSelector } from '../../../selectors/tokens' import { updateNetworkNonce } from '../../../store/actions' +import { fetchBasicGasAndTimeEstimates, fetchGasEstimates } from '../../../ducks/gas/gas.duck' -const mapStateToProps = state => { +const mapStateToProps = (state) => { + const pendingTransactions = nonceSortedPendingTransactionsSelector(state) + const firstPendingTransactionId = pendingTransactions[0] && pendingTransactions[0].primaryTransaction.id return { completedTransactions: nonceSortedCompletedTransactionsSelector(state), - pendingTransactions: nonceSortedPendingTransactionsSelector(state), + pendingTransactions, + firstPendingTransactionId, selectedToken: selectedTokenSelector(state), selectedAddress: getSelectedAddress(state), assetImages: getAssetImages(state), + transactionTimeFeatureActive: getFeatureFlags(state).transactionTime, } } const mapDispatchToProps = dispatch => { return { updateNetworkNonce: address => dispatch(updateNetworkNonce(address)), + fetchGasEstimates: (blockTime) => dispatch(fetchGasEstimates(blockTime)), + fetchBasicGasAndTimeEstimates: () => dispatch(fetchBasicGasAndTimeEstimates()), } } diff --git a/ui/app/components/app/transaction-time-remaining/index.js b/ui/app/components/app/transaction-time-remaining/index.js new file mode 100644 index 000000000..87c6821d8 --- /dev/null +++ b/ui/app/components/app/transaction-time-remaining/index.js @@ -0,0 +1 @@ +export { default } from './transaction-time-remaining.container' diff --git a/ui/app/components/app/transaction-time-remaining/transaction-time-remaining.component.js b/ui/app/components/app/transaction-time-remaining/transaction-time-remaining.component.js new file mode 100644 index 000000000..c9598d69b --- /dev/null +++ b/ui/app/components/app/transaction-time-remaining/transaction-time-remaining.component.js @@ -0,0 +1,52 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import { calcTransactionTimeRemaining } from './transaction-time-remaining.util' + +export default class TransactionTimeRemaining extends PureComponent { + static propTypes = { + className: PropTypes.string, + initialTimeEstimate: PropTypes.number, + submittedTime: PropTypes.number, + } + + constructor (props) { + super(props) + const { initialTimeEstimate, submittedTime } = props + this.state = { + timeRemaining: calcTransactionTimeRemaining(initialTimeEstimate, submittedTime), + } + this.interval = setInterval( + () => this.setState({ timeRemaining: calcTransactionTimeRemaining(initialTimeEstimate, submittedTime) }), + 1000 + ) + } + + componentDidUpdate (prevProps) { + const { initialTimeEstimate, submittedTime } = this.props + if (initialTimeEstimate !== prevProps.initialTimeEstimate) { + clearInterval(this.interval) + const calcedTimeRemaining = calcTransactionTimeRemaining(initialTimeEstimate, submittedTime) + this.setState({ timeRemaining: calcedTimeRemaining }) + this.interval = setInterval( + () => this.setState({ timeRemaining: calcTransactionTimeRemaining(initialTimeEstimate, submittedTime) }), + 1000 + ) + } + } + + componentWillUnmount () { + clearInterval(this.interval) + } + + render () { + const { className } = this.props + const { timeRemaining } = this.state + + return ( +
+ { timeRemaining } +
+ + ) + } +} diff --git a/ui/app/components/app/transaction-time-remaining/transaction-time-remaining.container.js b/ui/app/components/app/transaction-time-remaining/transaction-time-remaining.container.js new file mode 100644 index 000000000..65eeaa0c3 --- /dev/null +++ b/ui/app/components/app/transaction-time-remaining/transaction-time-remaining.container.js @@ -0,0 +1,41 @@ +import { connect } from 'react-redux' +import { withRouter } from 'react-router-dom' +import { compose } from 'recompose' +import TransactionTimeRemaining from './transaction-time-remaining.component' +import { + getTxParams, +} from '../../../selectors/transactions' +import { + getEstimatedGasPrices, + getEstimatedGasTimes, +} from '../../../selectors/custom-gas' +import { getRawTimeEstimateData } from '../../../helpers/utils/gas-time-estimates.util' +import { hexWEIToDecGWEI } from '../../../helpers/utils/conversions.util' + +const mapStateToProps = (state, ownProps) => { + const { transaction } = ownProps + const { gasPrice: currentGasPrice } = getTxParams(state, transaction) + const customGasPrice = calcCustomGasPrice(currentGasPrice) + const gasPrices = getEstimatedGasPrices(state) + const estimatedTimes = getEstimatedGasTimes(state) + + const { + newTimeEstimate: initialTimeEstimate, + } = getRawTimeEstimateData(customGasPrice, gasPrices, estimatedTimes) + + const submittedTime = transaction.submittedTime + + return { + initialTimeEstimate, + submittedTime, + } +} + +export default compose( + withRouter, + connect(mapStateToProps) +)(TransactionTimeRemaining) + +function calcCustomGasPrice (customGasPriceInHex) { + return Number(hexWEIToDecGWEI(customGasPriceInHex)) +} diff --git a/ui/app/components/app/transaction-time-remaining/transaction-time-remaining.util.js b/ui/app/components/app/transaction-time-remaining/transaction-time-remaining.util.js new file mode 100644 index 000000000..0ba81edfc --- /dev/null +++ b/ui/app/components/app/transaction-time-remaining/transaction-time-remaining.util.js @@ -0,0 +1,13 @@ +import { formatTimeEstimate } from '../../../helpers/utils/gas-time-estimates.util' + +export function calcTransactionTimeRemaining (initialTimeEstimate, submittedTime) { + const currentTime = (new Date()).getTime() + const timeElapsedSinceSubmission = (currentTime - submittedTime) / 1000 + const timeRemainingOnEstimate = initialTimeEstimate - timeElapsedSinceSubmission + + const renderingTimeRemainingEstimate = timeRemainingOnEstimate < 30 + ? '< 30 s' + : formatTimeEstimate(timeRemainingOnEstimate) + + return renderingTimeRemainingEstimate +} diff --git a/ui/app/helpers/utils/gas-time-estimates.util.js b/ui/app/helpers/utils/gas-time-estimates.util.js new file mode 100644 index 000000000..7e143a028 --- /dev/null +++ b/ui/app/helpers/utils/gas-time-estimates.util.js @@ -0,0 +1,99 @@ +import BigNumber from 'bignumber.js' + +export function newBigSigDig (n) { + return new BigNumber((new BigNumber(String(n))).toPrecision(15)) +} + +const createOp = (a, b, op) => (newBigSigDig(a))[op](newBigSigDig(b)) + +export function bigNumMinus (a = 0, b = 0) { + return createOp(a, b, 'minus') +} + +export function bigNumDiv (a = 0, b = 1) { + return createOp(a, b, 'div') +} + +export function extrapolateY ({ higherY = 0, lowerY = 0, higherX = 0, lowerX = 0, xForExtrapolation = 0 }) { + const slope = bigNumMinus(higherY, lowerY).div(bigNumMinus(higherX, lowerX)) + const newTimeEstimate = slope.times(bigNumMinus(higherX, xForExtrapolation)).minus(newBigSigDig(higherY)).negated() + + return newTimeEstimate.toNumber() +} + +export function getAdjacentGasPrices ({ gasPrices, priceToPosition }) { + const closestLowerValueIndex = gasPrices.findIndex((e, i, a) => e <= priceToPosition && a[i + 1] >= priceToPosition) + const closestHigherValueIndex = gasPrices.findIndex((e) => e > priceToPosition) + return { + closestLowerValueIndex, + closestHigherValueIndex, + closestHigherValue: gasPrices[closestHigherValueIndex], + closestLowerValue: gasPrices[closestLowerValueIndex], + } +} + +export function formatTimeEstimate (totalSeconds, greaterThanMax, lessThanMin) { + const minutes = Math.floor(totalSeconds / 60) + const seconds = Math.floor(totalSeconds % 60) + + if (!minutes && !seconds) { + return '...' + } + + let symbol = '~' + if (greaterThanMax) { + symbol = '< ' + } else if (lessThanMin) { + symbol = '> ' + } + + const formattedMin = `${minutes ? minutes + ' min' : ''}` + const formattedSec = `${seconds ? seconds + ' sec' : ''}` + const formattedCombined = formattedMin && formattedSec + ? `${symbol}${formattedMin} ${formattedSec}` + : symbol + (formattedMin || formattedSec) + + return formattedCombined +} + +export function getRawTimeEstimateData (currentGasPrice, gasPrices, estimatedTimes) { + const minGasPrice = gasPrices[0] + const maxGasPrice = gasPrices[gasPrices.length - 1] + let priceForEstimation = currentGasPrice + if (currentGasPrice < minGasPrice) { + priceForEstimation = minGasPrice + } else if (currentGasPrice > maxGasPrice) { + priceForEstimation = maxGasPrice + } + + const { + closestLowerValueIndex, + closestHigherValueIndex, + closestHigherValue, + closestLowerValue, + } = getAdjacentGasPrices({ gasPrices, priceToPosition: priceForEstimation }) + + const newTimeEstimate = extrapolateY({ + higherY: estimatedTimes[closestHigherValueIndex], + lowerY: estimatedTimes[closestLowerValueIndex], + higherX: closestHigherValue, + lowerX: closestLowerValue, + xForExtrapolation: priceForEstimation, + }) + + return { + newTimeEstimate, + minGasPrice, + maxGasPrice, + } +} + +export function getRenderableTimeEstimate (currentGasPrice, gasPrices, estimatedTimes) { + const { + newTimeEstimate, + minGasPrice, + maxGasPrice, + } = getRawTimeEstimateData(currentGasPrice, gasPrices, estimatedTimes) + + return formatTimeEstimate(newTimeEstimate, currentGasPrice > maxGasPrice, currentGasPrice < minGasPrice) +} diff --git a/ui/app/selectors/selectors.js b/ui/app/selectors/selectors.js index 943ceba0c..fab5f1dae 100644 --- a/ui/app/selectors/selectors.js +++ b/ui/app/selectors/selectors.js @@ -1,11 +1,7 @@ import { NETWORK_TYPES } from '../helpers/constants/common' import { stripHexPrefix, addHexPrefix } from 'ethereumjs-util' - const abi = require('human-standard-token-abi') -import { - transactionsSelector, -} from './transactions' const { multiplyCurrencies, } = require('../helpers/utils/conversion-util') @@ -24,7 +20,6 @@ const selectors = { getAssetImages, getTokenExchangeRate, conversionRateSelector, - transactionsSelector, accountsWithSendEtherInfoSelector, getCurrentAccountWithSendEtherInfo, getGasIsLoading, diff --git a/ui/app/selectors/transactions.js b/ui/app/selectors/transactions.js index 2a6a92ddf..e25bb5be0 100644 --- a/ui/app/selectors/transactions.js +++ b/ui/app/selectors/transactions.js @@ -11,6 +11,8 @@ import { } from '../../../app/scripts/controllers/transactions/enums' import { hexToDecimal } from '../helpers/utils/conversions.util' import { selectedTokenAddressSelector } from './tokens' +import { getFastPriceEstimateInHexWEI } from './custom-gas' +import { getSelectedToken } from './selectors' import txHelper from '../../lib/tx-helper' export const shapeShiftTxListSelector = state => state.metamask.shapeShiftTxList @@ -303,3 +305,15 @@ export const submittedPendingTransactionsSelector = createSelector( transactions.filter(transaction => transaction.status === SUBMITTED_STATUS) ) ) + +export const getTxParams = (state, selectedTransaction = {}) => { + const { metamask: { send } } = state + const { txParams } = selectedTransaction + return txParams || { + from: send.from, + gas: send.gasLimit || '0x5208', + gasPrice: send.gasPrice || getFastPriceEstimateInHexWEI(state, true), + to: send.to, + value: getSelectedToken(state) ? '0x0' : send.amount, + } +}