diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index 16c1999990ab..cb6227af602a 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -195,6 +195,18 @@
"blockiesIdenticon": {
"message": "Use Blockies Identicon"
},
+ "nonceField": {
+ "message": "Customize transaction nonce"
+ },
+ "nonceFieldPlaceholder": {
+ "message": "Automatically calculate"
+ },
+ "nonceFieldHeading": {
+ "message": "Custom Nonce"
+ },
+ "nonceFieldDescription": {
+ "message": "Turn this on to change the nonce (transaction number) on confirmation screens. This is an advanced feature, use cautiously."
+ },
"browserNotSupported": {
"message": "Your Browser is not supported..."
},
@@ -851,6 +863,10 @@
"next": {
"message": "Next"
},
+ "nextNonceWarning": {
+ "message": "Nonce is higher than suggested nonce of $1",
+ "description": "The next nonce according to MetaMask's internal logic"
+ },
"noAddressForName": {
"message": "No address has been set for this name."
},
diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js
index b3523cc768b0..dfeeaeb82015 100644
--- a/app/scripts/controllers/preferences.js
+++ b/app/scripts/controllers/preferences.js
@@ -17,6 +17,7 @@ class PreferencesController {
* @property {object} store.accountTokens The tokens stored per account and then per network type
* @property {object} store.assetImages Contains assets objects related to assets added
* @property {boolean} store.useBlockie The users preference for blockie identicons within the UI
+ * @property {boolean} store.useNonceField The users preference for nonce field within the UI
* @property {object} store.featureFlags A key-boolean map, where keys refer to features and booleans to whether the
* user wishes to see that feature.
*
@@ -35,6 +36,7 @@ class PreferencesController {
tokens: [],
suggestedTokens: {},
useBlockie: false,
+ useNonceField: false,
// WARNING: Do not use feature flags for security-sensitive things.
// Feature flag toggling is available in the global namespace
@@ -89,6 +91,16 @@ class PreferencesController {
this.store.updateState({ useBlockie: val })
}
+ /**
+ * Setter for the `useNonceField` property
+ *
+ * @param {boolean} val Whether or not the user prefers to set nonce
+ *
+ */
+ setUseNonceField (val) {
+ this.store.updateState({ useNonceField: val })
+ }
+
/**
* Setter for the `participateInMetaMetrics` property
*
@@ -204,6 +216,16 @@ class PreferencesController {
return this.store.getState().useBlockie
}
+ /**
+ * Getter for the `getUseNonceField` property
+ *
+ * @returns {boolean} this.store.getUseNonceField
+ *
+ */
+ getUseNonceField () {
+ return this.store.getState().useNonceField
+ }
+
/**
* Setter for the `currentLocale` property
*
diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js
index 008d1c72dfa1..c69b82673d03 100644
--- a/app/scripts/controllers/transactions/index.js
+++ b/app/scripts/controllers/transactions/index.js
@@ -373,14 +373,21 @@ class TransactionController extends EventEmitter {
const txMeta = this.txStateManager.getTx(txId)
const fromAddress = txMeta.txParams.from
// wait for a nonce
+ let { customNonceValue = null } = txMeta
+ customNonceValue = Number(customNonceValue)
nonceLock = await this.nonceTracker.getNonceLock(fromAddress)
// add nonce to txParams
// if txMeta has lastGasPrice then it is a retry at same nonce with higher
// gas price transaction and their for the nonce should not be calculated
const nonce = txMeta.lastGasPrice ? txMeta.txParams.nonce : nonceLock.nextNonce
- txMeta.txParams.nonce = ethUtil.addHexPrefix(nonce.toString(16))
+ const customOrNonce = customNonceValue || nonce
+
+ txMeta.txParams.nonce = ethUtil.addHexPrefix(customOrNonce.toString(16))
// add nonce debugging information to txMeta
txMeta.nonceDetails = nonceLock.nonceDetails
+ if (customNonceValue) {
+ txMeta.nonceDetails.customNonceValue = customNonceValue
+ }
this.txStateManager.updateTx(txMeta, 'transactions#approveTransaction')
// sign transaction
const rawTx = await this.signTransaction(txId)
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index 2986add10f21..d6c19deb859a 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -440,6 +440,7 @@ module.exports = class MetamaskController extends EventEmitter {
getState: (cb) => cb(null, this.getState()),
setCurrentCurrency: this.setCurrentCurrency.bind(this),
setUseBlockie: this.setUseBlockie.bind(this),
+ setUseNonceField: this.setUseNonceField.bind(this),
setParticipateInMetaMetrics: this.setParticipateInMetaMetrics.bind(this),
setMetaMetricsSendCount: this.setMetaMetricsSendCount.bind(this),
setFirstTimeFlowType: this.setFirstTimeFlowType.bind(this),
@@ -519,6 +520,7 @@ module.exports = class MetamaskController extends EventEmitter {
getFilteredTxList: nodeify(txController.getFilteredTxList, txController),
isNonceTaken: nodeify(txController.isNonceTaken, txController),
estimateGas: nodeify(this.estimateGas, this),
+ getPendingNonce: nodeify(this.getPendingNonce, this),
// messageManager
signMessage: nodeify(this.signMessage, this),
@@ -1727,6 +1729,20 @@ module.exports = class MetamaskController extends EventEmitter {
}
}
+ /**
+ * Sets whether or not to use the nonce field.
+ * @param {boolean} val - True for nonce field, false for not nonce field.
+ * @param {Function} cb - A callback function called when complete.
+ */
+ setUseNonceField (val, cb) {
+ try {
+ this.preferencesController.setUseNonceField(val)
+ cb(null)
+ } catch (err) {
+ cb(err)
+ }
+ }
+
/**
* Sets whether or not the user will have usage data tracked with MetaMetrics
* @param {boolean} bool - True for users that wish to opt-in, false for users that wish to remain out.
diff --git a/test/e2e/threebox.spec.js b/test/e2e/threebox.spec.js
index eb0eebcb2fe3..a60f76e4134d 100644
--- a/test/e2e/threebox.spec.js
+++ b/test/e2e/threebox.spec.js
@@ -116,7 +116,7 @@ describe('MetaMask', function () {
await advancedButton.click()
const threeBoxToggle = await findElements(driver, By.css('.toggle-button'))
- const threeBoxToggleButton = await threeBoxToggle[3].findElement(By.css('div'))
+ const threeBoxToggleButton = await threeBoxToggle[4].findElement(By.css('div'))
await threeBoxToggleButton.click()
})
diff --git a/ui/app/components/app/confirm-page-container/confirm-detail-row/index.scss b/ui/app/components/app/confirm-page-container/confirm-detail-row/index.scss
index 1672ef8c60e4..80eb9cce6ae6 100644
--- a/ui/app/components/app/confirm-page-container/confirm-detail-row/index.scss
+++ b/ui/app/components/app/confirm-page-container/confirm-detail-row/index.scss
@@ -47,4 +47,11 @@
.advanced-gas-inputs__gas-edit-rows {
margin-bottom: 16px;
}
+
+ .custom-nonce-input {
+ input {
+ width: 90px;
+ font-size: 1rem;
+ }
+ }
}
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 27b9e2608c10..c3cf0295bcc7 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
@@ -21,10 +21,10 @@ const mapStateToProps = (state, ownProps) => {
const { showFiatInTestnets } = preferencesSelector(state)
const isMainnet = getIsMainnet(state)
const { transactionGroup: { primaryTransaction } = {} } = ownProps
- const { txParams: { gas: gasLimit, gasPrice, data, to } = {} } = primaryTransaction
+ const { txParams: { gas: gasLimit, gasPrice, data } = {}, transactionCategory } = primaryTransaction
const selectedAddress = getSelectedAddress(state)
const selectedAccountBalance = accounts[selectedAddress].balance
- const isDeposit = selectedAddress === to
+ const isDeposit = transactionCategory === 'incoming'
const selectRpcInfo = frequentRpcListDetail.find(rpcInfo => rpcInfo.rpcUrl === provider.rpcTarget)
const { rpcPrefs } = selectRpcInfo || {}
diff --git a/ui/app/ducks/metamask/metamask.js b/ui/app/ducks/metamask/metamask.js
index f1fa7cd030fe..74e71df21f25 100644
--- a/ui/app/ducks/metamask/metamask.js
+++ b/ui/app/ducks/metamask/metamask.js
@@ -25,6 +25,7 @@ function reduceMetamask (state, action) {
tokenExchangeRates: {},
tokens: [],
pendingTokens: {},
+ customNonceValue: '',
send: {
gasLimit: null,
gasPrice: null,
@@ -57,6 +58,7 @@ function reduceMetamask (state, action) {
knownMethodData: {},
participateInMetaMetrics: null,
metaMetricsSendCount: 0,
+ nextNonce: null,
}, state.metamask)
switch (action.type) {
@@ -188,7 +190,10 @@ function reduceMetamask (state, action) {
gasLimit: action.value,
},
})
-
+ case actions.UPDATE_CUSTOM_NONCE:
+ return extend(metamaskState, {
+ customNonceValue: action.value,
+ })
case actions.UPDATE_GAS_PRICE:
return extend(metamaskState, {
send: {
@@ -412,6 +417,13 @@ function reduceMetamask (state, action) {
})
}
+ case actions.SET_NEXT_NONCE: {
+ console.log('action.value', action.value)
+ return extend(metamaskState, {
+ nextNonce: action.value,
+ })
+ }
+
default:
return metamaskState
diff --git a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js
index 87771502dce4..8265dd20b7fb 100644
--- a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js
+++ b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js
@@ -15,6 +15,7 @@ import { CONFIRMED_STATUS, DROPPED_STATUS } from '../../helpers/constants/transa
import UserPreferencedCurrencyDisplay from '../../components/app/user-preferenced-currency-display'
import { PRIMARY, SECONDARY } from '../../helpers/constants/common'
import AdvancedGasInputs from '../../components/app/gas-customization/advanced-gas-inputs'
+import TextField from '../../components/ui/text-field'
export default class ConfirmTransactionBase extends Component {
static contextTypes = {
@@ -50,6 +51,9 @@ export default class ConfirmTransactionBase extends Component {
isTxReprice: PropTypes.bool,
methodData: PropTypes.object,
nonce: PropTypes.string,
+ useNonceField: PropTypes.bool,
+ customNonceValue: PropTypes.string,
+ updateCustomNonce: PropTypes.func,
assetImage: PropTypes.string,
sendTransaction: PropTypes.func,
showCustomizeGasModal: PropTypes.func,
@@ -96,11 +100,14 @@ export default class ConfirmTransactionBase extends Component {
insufficientBalance: PropTypes.bool,
hideFiatConversion: PropTypes.bool,
transactionCategory: PropTypes.string,
+ getNextNonce: PropTypes.func,
+ nextNonce: PropTypes.number,
}
state = {
submitting: false,
submitError: null,
+ submitWarning: '',
}
componentDidUpdate (prevProps) {
@@ -109,11 +116,21 @@ export default class ConfirmTransactionBase extends Component {
showTransactionConfirmedModal,
history,
clearConfirmTransaction,
+ nextNonce,
+ customNonceValue,
} = this.props
const { transactionStatus: prevTxStatus } = prevProps
const statusUpdated = transactionStatus !== prevTxStatus
const txDroppedOrConfirmed = transactionStatus === DROPPED_STATUS || transactionStatus === CONFIRMED_STATUS
+ if (nextNonce !== prevProps.nextNonce || customNonceValue !== prevProps.customNonceValue) {
+ if (customNonceValue > nextNonce) {
+ this.setState({ submitWarning: this.context.t('nextNonceWarning', [nextNonce]) })
+ } else {
+ this.setState({ submitWarning: '' })
+ }
+ }
+
if (statusUpdated && txDroppedOrConfirmed) {
showTransactionConfirmedModal({
onSubmit: () => {
@@ -204,11 +221,16 @@ export default class ConfirmTransactionBase extends Component {
hexTransactionFee,
hexTransactionTotal,
hideDetails,
+ useNonceField,
+ customNonceValue,
+ updateCustomNonce,
advancedInlineGasShown,
customGas,
insufficientBalance,
updateGasAndCalculate,
hideFiatConversion,
+ nextNonce,
+ getNextNonce,
} = this.props
if (hideDetails) {
@@ -240,7 +262,7 @@ export default class ConfirmTransactionBase extends Component {
: null
}
-
+
+ {useNonceField ?
+
+
+ { this.context.t('nonceFieldHeading') }
+
+
+ {
+ if (!value.length || Number(value) < 0) {
+ updateCustomNonce('')
+ } else {
+ updateCustomNonce(String(Math.floor(value)))
+ }
+ getNextNonce()
+ }}
+ fullWidth
+ margin="dense"
+ value={customNonceValue || nextNonce || ''}
+ />
+
+
+
: null}
)
)
@@ -347,7 +394,17 @@ export default class ConfirmTransactionBase extends Component {
handleCancel () {
const { metricsEvent } = this.context
- const { onCancel, txData, cancelTransaction, history, clearConfirmTransaction, actionKey, txData: { origin }, methodData = {} } = this.props
+ const {
+ onCancel,
+ txData,
+ cancelTransaction,
+ history,
+ clearConfirmTransaction,
+ actionKey,
+ txData: { origin },
+ methodData = {},
+ updateCustomNonce,
+ } = this.props
metricsEvent({
eventOpts: {
@@ -361,6 +418,7 @@ export default class ConfirmTransactionBase extends Component {
origin,
},
})
+ updateCustomNonce('')
if (onCancel) {
onCancel(txData)
} else {
@@ -374,7 +432,19 @@ export default class ConfirmTransactionBase extends Component {
handleSubmit () {
const { metricsEvent } = this.context
- const { txData: { origin }, sendTransaction, clearConfirmTransaction, txData, history, onSubmit, actionKey, metaMetricsSendCount = 0, setMetaMetricsSendCount, methodData = {} } = this.props
+ const {
+ txData: { origin },
+ sendTransaction,
+ clearConfirmTransaction,
+ txData,
+ history,
+ onSubmit,
+ actionKey,
+ metaMetricsSendCount = 0,
+ setMetaMetricsSendCount,
+ methodData = {},
+ updateCustomNonce,
+ } = this.props
const { submitting } = this.state
if (submitting) {
@@ -406,6 +476,7 @@ export default class ConfirmTransactionBase extends Component {
this.setState({
submitting: false,
})
+ updateCustomNonce('')
})
} else {
sendTransaction(txData)
@@ -415,6 +486,7 @@ export default class ConfirmTransactionBase extends Component {
submitting: false,
}, () => {
history.push(DEFAULT_ROUTE)
+ updateCustomNonce('')
})
})
.catch(error => {
@@ -493,7 +565,7 @@ export default class ConfirmTransactionBase extends Component {
}
componentDidMount () {
- const { txData: { origin, id } = {}, cancelTransaction } = this.props
+ const { txData: { origin, id } = {}, cancelTransaction, getNextNonce } = this.props
const { metricsEvent } = this.context
metricsEvent({
eventOpts: {
@@ -521,6 +593,8 @@ export default class ConfirmTransactionBase extends Component {
cancelTransaction({ id })
}
}
+
+ getNextNonce()
}
render () {
@@ -543,12 +617,13 @@ export default class ConfirmTransactionBase extends Component {
contentComponent,
onEdit,
nonce,
+ customNonceValue,
assetImage,
warning,
unapprovedTxCount,
transactionCategory,
} = this.props
- const { submitting, submitError } = this.state
+ const { submitting, submitError, submitWarning } = this.state
const { name } = methodData
const { valid, errorKey } = this.getErrorKey()
@@ -572,13 +647,13 @@ export default class ConfirmTransactionBase extends Component {
detailsComponent={this.renderDetails()}
dataComponent={this.renderData()}
contentComponent={contentComponent}
- nonce={nonce}
+ nonce={customNonceValue || nonce}
unapprovedTxCount={unapprovedTxCount}
assetImage={assetImage}
identiconAddress={identiconAddress}
errorMessage={errorMessage || submitError}
errorKey={propsErrorKey || errorKey}
- warning={warning}
+ warning={warning || submitWarning}
totalTx={totalTx}
positionOfCurrentTx={positionOfCurrentTx}
nextTxId={nextTxId}
diff --git a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js
index 700883f17630..6ffa13bd0dd8 100644
--- a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js
+++ b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js
@@ -8,7 +8,17 @@ import {
clearConfirmTransaction,
} from '../../ducks/confirm-transaction/confirm-transaction.duck'
-import { clearSend, cancelTx, cancelTxs, updateAndApproveTx, showModal, setMetaMetricsSendCount, updateTransaction } from '../../store/actions'
+import {
+ updateCustomNonce,
+ clearSend,
+ cancelTx,
+ cancelTxs,
+ updateAndApproveTx,
+ showModal,
+ setMetaMetricsSendCount,
+ updateTransaction,
+ getNextNonce,
+} from '../../store/actions'
import {
INSUFFICIENT_FUNDS_ERROR_KEY,
GAS_LIMIT_TOO_LOW_ERROR_KEY,
@@ -18,7 +28,7 @@ import { isBalanceSufficient, calcGasTotal } from '../send/send.utils'
import { conversionGreaterThan } from '../../helpers/utils/conversion-util'
import { MIN_GAS_LIMIT_DEC } from '../send/send.constants'
import { checksumAddress, addressSlicer, valuesFor } from '../../helpers/utils/util'
-import { getMetaMaskAccounts, getAdvancedInlineGasShown, preferencesSelector, getIsMainnet, getKnownMethodData } from '../../selectors/selectors'
+import { getMetaMaskAccounts, getCustomNonceValue, getUseNonceField, getAdvancedInlineGasShown, preferencesSelector, getIsMainnet, getKnownMethodData } from '../../selectors/selectors'
import { transactionFeeSelector } from '../../selectors/confirm-transaction'
const casedContractMap = Object.keys(contractMap).reduce((acc, base) => {
@@ -28,6 +38,12 @@ const casedContractMap = Object.keys(contractMap).reduce((acc, base) => {
}
}, {})
+let customNonceValue = ''
+const customNonceMerge = txData => customNonceValue ? ({
+ ...txData,
+ customNonceValue,
+}) : txData
+
const mapStateToProps = (state, ownProps) => {
const { toAddress: propsToAddress, match: { params = {} } } = ownProps
const { id: paramsTransactionId } = params
@@ -45,6 +61,7 @@ const mapStateToProps = (state, ownProps) => {
network,
unapprovedTxs,
metaMetricsSendCount,
+ nextNonce,
} = metamask
const {
tokenData,
@@ -146,16 +163,23 @@ const mapStateToProps = (state, ownProps) => {
gasPrice,
},
advancedInlineGasShown: getAdvancedInlineGasShown(state),
+ useNonceField: getUseNonceField(state),
+ customNonceValue: getCustomNonceValue(state),
insufficientBalance,
hideSubtitle: (!isMainnet && !showFiatInTestnets),
hideFiatConversion: (!isMainnet && !showFiatInTestnets),
metaMetricsSendCount,
transactionCategory,
+ nextNonce,
}
}
-const mapDispatchToProps = dispatch => {
+export const mapDispatchToProps = dispatch => {
return {
+ updateCustomNonce: value => {
+ customNonceValue = value
+ dispatch(updateCustomNonce(value))
+ },
clearConfirmTransaction: () => dispatch(clearConfirmTransaction()),
clearSend: () => dispatch(clearSend()),
showTransactionConfirmedModal: ({ onSubmit }) => {
@@ -172,8 +196,9 @@ const mapDispatchToProps = dispatch => {
},
cancelTransaction: ({ id }) => dispatch(cancelTx({ id })),
cancelAllTransactions: (txList) => dispatch(cancelTxs(txList)),
- sendTransaction: txData => dispatch(updateAndApproveTx(txData)),
+ sendTransaction: txData => dispatch(updateAndApproveTx(customNonceMerge(txData))),
setMetaMetricsSendCount: val => dispatch(setMetaMetricsSendCount(val)),
+ getNextNonce: () => dispatch(getNextNonce()),
}
}
diff --git a/ui/app/pages/confirm-transaction-base/tests/confirm-transaction-base.container.test.js b/ui/app/pages/confirm-transaction-base/tests/confirm-transaction-base.container.test.js
new file mode 100644
index 000000000000..a8045d0e086d
--- /dev/null
+++ b/ui/app/pages/confirm-transaction-base/tests/confirm-transaction-base.container.test.js
@@ -0,0 +1,20 @@
+import assert from 'assert'
+import { mapDispatchToProps } from '../confirm-transaction-base.container'
+
+describe('Confirm Transaction Base Container', () => {
+ it('should map dispatch to props correctly', () => {
+ const props = mapDispatchToProps(() => 'mockDispatch')
+
+ assert.ok(typeof props.updateCustomNonce === 'function')
+ assert.ok(typeof props.clearConfirmTransaction === 'function')
+ assert.ok(typeof props.clearSend === 'function')
+ assert.ok(typeof props.showTransactionConfirmedModal === 'function')
+ assert.ok(typeof props.showCustomizeGasModal === 'function')
+ assert.ok(typeof props.updateGasAndCalculate === 'function')
+ assert.ok(typeof props.showRejectTransactionsConfirmationModal === 'function')
+ assert.ok(typeof props.cancelTransaction === 'function')
+ assert.ok(typeof props.cancelAllTransactions === 'function')
+ assert.ok(typeof props.sendTransaction === 'function')
+ assert.ok(typeof props.setMetaMetricsSendCount === 'function')
+ })
+})
diff --git a/ui/app/pages/settings/advanced-tab/advanced-tab.component.js b/ui/app/pages/settings/advanced-tab/advanced-tab.component.js
index 55f25607f657..9e7b70a9356d 100644
--- a/ui/app/pages/settings/advanced-tab/advanced-tab.component.js
+++ b/ui/app/pages/settings/advanced-tab/advanced-tab.component.js
@@ -14,6 +14,8 @@ export default class AdvancedTab extends PureComponent {
}
static propTypes = {
+ setUseNonceField: PropTypes.func,
+ useNonceField: PropTypes.bool,
setHexDataFeatureFlag: PropTypes.func,
setRpcTarget: PropTypes.func,
displayWarning: PropTypes.func,
@@ -212,6 +214,32 @@ export default class AdvancedTab extends PureComponent {
)
}
+ renderUseNonceOptIn () {
+ const { t } = this.context
+ const { useNonceField, setUseNonceField } = this.props
+
+ return (
+
+
+
{ this.context.t('nonceField') }
+
+ { t('nonceFieldDescription') }
+
+
+
+
+ setUseNonceField(!value)}
+ offLabel={t('off')}
+ onLabel={t('on')}
+ />
+
+
+
+ )
+ }
+
renderAutoLogoutTimeLimit () {
const { t } = this.context
const {
@@ -311,6 +339,7 @@ export default class AdvancedTab extends PureComponent {
{ this.renderAdvancedGasInputInline() }
{ this.renderHexDataOptIn() }
{ this.renderShowConversionInTestnets() }
+ { this.renderUseNonceOptIn() }
{ this.renderAutoLogoutTimeLimit() }
{ this.renderThreeBoxControl() }
diff --git a/ui/app/pages/settings/advanced-tab/advanced-tab.container.js b/ui/app/pages/settings/advanced-tab/advanced-tab.container.js
index f4f3eaee9825..8b64e864fdac 100644
--- a/ui/app/pages/settings/advanced-tab/advanced-tab.container.js
+++ b/ui/app/pages/settings/advanced-tab/advanced-tab.container.js
@@ -11,6 +11,7 @@ import {
setAutoLogoutTimeLimit,
setThreeBoxSyncingPermission,
turnThreeBoxSyncingOnAndInitialize,
+ setUseNonceField,
} from '../../../store/actions'
import {preferencesSelector} from '../../../selectors/selectors'
@@ -23,6 +24,7 @@ export const mapStateToProps = state => {
} = {},
threeBoxSyncingAllowed,
threeBoxDisabled,
+ useNonceField,
} = metamask
const { showFiatInTestnets, autoLogoutTimeLimit } = preferencesSelector(state)
@@ -34,6 +36,7 @@ export const mapStateToProps = state => {
autoLogoutTimeLimit,
threeBoxSyncingAllowed,
threeBoxDisabled,
+ useNonceField,
}
}
@@ -44,6 +47,7 @@ export const mapDispatchToProps = dispatch => {
displayWarning: warning => dispatch(displayWarning(warning)),
showResetAccountConfirmationModal: () => dispatch(showModal({ name: 'CONFIRM_RESET_ACCOUNT' })),
setAdvancedInlineGasFeatureFlag: shouldShow => dispatch(setFeatureFlag('advancedInlineGas', shouldShow)),
+ setUseNonceField: value => dispatch(setUseNonceField(value)),
setShowFiatConversionOnTestnetsPreference: value => {
return dispatch(setShowFiatConversionOnTestnetsPreference(value))
},
diff --git a/ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js b/ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js
index 914330d05dd4..760f62e548a0 100644
--- a/ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js
+++ b/ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js
@@ -16,7 +16,7 @@ describe('AdvancedTab Component', () => {
}
)
- assert.equal(root.find('.settings-page__content-row').length, 8)
+ assert.equal(root.find('.settings-page__content-row').length, 9)
})
it('should update autoLogoutTimeLimit', () => {
@@ -32,7 +32,7 @@ describe('AdvancedTab Component', () => {
}
)
- const autoTimeout = root.find('.settings-page__content-row').at(6)
+ const autoTimeout = root.find('.settings-page__content-row').at(7)
const textField = autoTimeout.find(TextField)
textField.props().onChange({ target: { value: 1440 } })
diff --git a/ui/app/pages/settings/advanced-tab/tests/advanced-tab-container.test.js b/ui/app/pages/settings/advanced-tab/tests/advanced-tab-container.test.js
index dab0177ff985..7f5223315f07 100644
--- a/ui/app/pages/settings/advanced-tab/tests/advanced-tab-container.test.js
+++ b/ui/app/pages/settings/advanced-tab/tests/advanced-tab-container.test.js
@@ -17,6 +17,7 @@ const defaultState = {
},
threeBoxSyncingAllowed: false,
threeBoxDisabled: false,
+ useNonceField: false,
},
}
@@ -31,6 +32,7 @@ describe('AdvancedTab Container', () => {
autoLogoutTimeLimit: 0,
threeBoxSyncingAllowed: false,
threeBoxDisabled: false,
+ useNonceField: false,
}
assert.deepEqual(props, expected)
@@ -46,5 +48,6 @@ describe('AdvancedTab Container', () => {
assert.ok(typeof props.setAdvancedInlineGasFeatureFlag === 'function')
assert.ok(typeof props.setShowFiatConversionOnTestnetsPreference === 'function')
assert.ok(typeof props.setAutoLogoutTimeLimit === 'function')
+ assert.ok(typeof props.setUseNonceField === 'function')
})
})
diff --git a/ui/app/selectors/selectors.js b/ui/app/selectors/selectors.js
index 96c646caf0c8..943ceba0ce59 100644
--- a/ui/app/selectors/selectors.js
+++ b/ui/app/selectors/selectors.js
@@ -45,6 +45,8 @@ const selectors = {
getNetworkIdentifier,
isBalanceCached,
getAdvancedInlineGasShown,
+ getUseNonceField,
+ getCustomNonceValue,
getIsMainnet,
getCurrentNetworkId,
getSelectedAsset,
@@ -341,6 +343,14 @@ function getAdvancedInlineGasShown (state) {
return Boolean(state.metamask.featureFlags.advancedInlineGas)
}
+function getUseNonceField (state) {
+ return Boolean(state.metamask.useNonceField)
+}
+
+function getCustomNonceValue (state) {
+ return String(state.metamask.customNonceValue)
+}
+
function getMetaMetricState (state) {
return {
network: getCurrentNetworkId(state),
diff --git a/ui/app/store/actions.js b/ui/app/store/actions.js
index ee4f577cd9b7..cb4d4f41f66f 100644
--- a/ui/app/store/actions.js
+++ b/ui/app/store/actions.js
@@ -178,6 +178,9 @@ var actions = {
VIEW_PENDING_TX: 'VIEW_PENDING_TX',
updateTransactionParams,
UPDATE_TRANSACTION_PARAMS: 'UPDATE_TRANSACTION_PARAMS',
+ setNextNonce,
+ SET_NEXT_NONCE: 'SET_NEXT_NONCE',
+ getNextNonce,
// send screen
UPDATE_GAS_LIMIT: 'UPDATE_GAS_LIMIT',
UPDATE_GAS_PRICE: 'UPDATE_GAS_PRICE',
@@ -298,6 +301,10 @@ var actions = {
SET_USE_BLOCKIE: 'SET_USE_BLOCKIE',
setUseBlockie,
+ SET_USE_NONCEFIELD: 'SET_USE_NONCEFIELD',
+ setUseNonceField,
+ UPDATE_CUSTOM_NONCE: 'UPDATE_CUSTOM_NONCE',
+ updateCustomNonce,
SET_PARTICIPATE_IN_METAMETRICS: 'SET_PARTICIPATE_IN_METAMETRICS',
SET_METAMETRICS_SEND_COUNT: 'SET_METAMETRICS_SEND_COUNT',
@@ -1063,6 +1070,13 @@ function updateSendAmount (amount) {
}
}
+function updateCustomNonce (value) {
+ return {
+ type: actions.UPDATE_CUSTOM_NONCE,
+ value: value,
+ }
+}
+
function updateSendMemo (memo) {
return {
type: actions.UPDATE_SEND_MEMO,
@@ -1208,6 +1222,7 @@ function updateAndApproveTx (txData) {
dispatch(actions.clearSend())
dispatch(actions.completedTx(txData.id))
dispatch(actions.hideLoadingIndication())
+ dispatch(actions.updateCustomNonce(''))
dispatch(closeCurrentNotificationWindow())
return txData
@@ -2591,6 +2606,23 @@ function setUseBlockie (val) {
}
}
+function setUseNonceField (val) {
+ return (dispatch) => {
+ dispatch(actions.showLoadingIndication())
+ log.debug(`background.setUseNonceField`)
+ background.setUseNonceField(val, (err) => {
+ dispatch(actions.hideLoadingIndication())
+ if (err) {
+ return dispatch(actions.displayWarning(err.message))
+ }
+ })
+ dispatch({
+ type: actions.SET_USE_NONCEFIELD,
+ value: val,
+ })
+ }
+}
+
function updateCurrentLocale (key) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
@@ -2888,3 +2920,26 @@ function turnThreeBoxSyncingOnAndInitialize () {
await dispatch(initializeThreeBox(true))
}
}
+
+function setNextNonce (nextNonce) {
+ return {
+ type: actions.SET_NEXT_NONCE,
+ value: nextNonce,
+ }
+}
+
+function getNextNonce () {
+ return (dispatch, getState) => {
+ const address = getState().metamask.selectedAddress
+ return new Promise((resolve, reject) => {
+ background.getPendingNonce(address, (err, pendingNonce) => {
+ if (err) {
+ dispatch(actions.displayWarning(err.message))
+ return reject(err)
+ }
+ dispatch(setNextNonce(pendingNonce))
+ resolve(pendingNonce)
+ })
+ })
+ }
+}