1
0
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:
Kristian Tapia 2019-10-31 18:51:28 -07:00 committed by Dan J Miller
parent 6bd87e1f09
commit f9cd775eae
16 changed files with 325 additions and 98 deletions

View File

@ -44,6 +44,7 @@ class PreferencesController {
// perform sensitive operations.
featureFlags: {
showIncomingTransactions: true,
transactionTime: false,
},
knownMethodData: {},
participateInMetaMetrics: null,

View File

@ -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)

View File

@ -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)
}

View File

@ -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)

View File

@ -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;

View File

@ -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>

View File

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

View File

@ -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}
/>
)
}

View File

@ -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()),
}
}

View File

@ -0,0 +1 @@
export { default } from './transaction-time-remaining.container'

View File

@ -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>
)
}
}

View File

@ -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))
}

View File

@ -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
}

View 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)
}

View File

@ -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,

View File

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