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,
+ }
+}