mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Add Estimated time to pending tx (#6924)
* Add estimated time to pending transactions * add sytles for pending transactions component * add media queries styling for pending transactions component * fix lint errors, remove extra spaces * refactor code to call `fetchBasicGasAndTimeEstimates` method once * refactor code to call `getgetRenderableTimeEstimate` method once * fix, correct export to use `transaction-time-remaining-component` * fix indentation issues after running `yarn lint` * newBigSigDig in gas-price-chart.utils supports strings * Code cleanup * Ensure fetchBasicGasAndTimeEstimates is only called from tx-list if there are pending-txs * Move gas time estimate utilities into utility file * Move getTxParams to transaction selector file * Add feature flag for display of remaining transaction time in tx history list * Fix circular dependency by removing unused import of transactionSelector in selectors.js * Use correct feature flag property name transactionTime * Ensure that tx list component correctly responds to turning tx time feature on * Prevent precision errors in newBigSigDig * Code clean up for pending transaction times * Update transaction-time-remaining feature to count down seconds, countdown seconds and show '< 30' * Code clean up for transaction-time-remaining feature
This commit is contained in:
parent
6bd87e1f09
commit
f9cd775eae
@ -44,6 +44,7 @@ class PreferencesController {
|
||||
// perform sensitive operations.
|
||||
featureFlags: {
|
||||
showIncomingTransactions: true,
|
||||
transactionTime: false,
|
||||
},
|
||||
knownMethodData: {},
|
||||
participateInMetaMetrics: null,
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
? <TransactionTimeRemaining
|
||||
className="transaction-list-item__estimated-time"
|
||||
transaction={ primaryTransaction }
|
||||
/>
|
||||
: null
|
||||
}
|
||||
{ this.renderPrimaryCurrency() }
|
||||
{ this.renderSecondaryCurrency() }
|
||||
</div>
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -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()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1 @@
|
||||
export { default } from './transaction-time-remaining.container'
|
@ -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 (
|
||||
<div className={className}>
|
||||
{ timeRemaining }
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
@ -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
|
||||
}
|
99
ui/app/helpers/utils/gas-time-estimates.util.js
Normal file
99
ui/app/helpers/utils/gas-time-estimates.util.js
Normal file
@ -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)
|
||||
}
|
@ -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,
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user