diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5d2c8f51d..b7c68bcad 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
## Current Master
+- [#379](https://github.com/poanetwork/nifty-wallet/pull/379) - (Feature) Ability to set custom nonce of tx
- [#377](https://github.com/poanetwork/nifty-wallet/pull/377) - (Fix) Sign message screen: do not decode message if it is not hex encoded
- [#364](https://github.com/poanetwork/nifty-wallet/pull/364) - (Fix) notifications order in batch requests
diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js
index 91178b21d..19e7d60c7 100644
--- a/app/scripts/controllers/transactions/index.js
+++ b/app/scripts/controllers/transactions/index.js
@@ -290,7 +290,8 @@ class TransactionController extends EventEmitter {
*/
async updateAndApproveTransaction (txMeta) {
this.txStateManager.updateTx(txMeta, 'confTx: user approved transaction')
- await this.approveTransaction(txMeta.id)
+ const customNonce = txMeta.txParams.nonce
+ await this.approveTransaction(txMeta.id, customNonce)
}
/**
@@ -301,7 +302,7 @@ class TransactionController extends EventEmitter {
if any of these steps fails the tx status will be set to failed
@param txId {number} - the tx's Id
*/
- async approveTransaction (txId) {
+ async approveTransaction (txId, customNonce) {
let nonceLock
try {
// approve
@@ -315,7 +316,7 @@ class TransactionController extends EventEmitter {
// 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))
+ txMeta.txParams.nonce = customNonce || ethUtil.addHexPrefix(nonce.toString(16))
// add nonce debugging information to txMeta
txMeta.nonceDetails = nonceLock.nonceDetails
this.txStateManager.updateTx(txMeta, 'transactions#approveTransaction')
diff --git a/old-ui/app/components/send/send.js b/old-ui/app/components/send/send.js
index edea83a32..93b531d8f 100644
--- a/old-ui/app/components/send/send.js
+++ b/old-ui/app/components/send/send.js
@@ -1,23 +1,220 @@
-const inherits = require('util').inherits
-const PersistentForm = require('../../../lib/persistent-form')
-const h = require('react-hyperscript')
-const connect = require('react-redux').connect
-const actions = require('../../../../ui/app/actions')
-const {
+import React from 'react'
+import PersistentForm from '../../../lib/persistent-form'
+import { connect } from 'react-redux'
+import actions from '../../../../ui/app/actions'
+import {
numericBalance,
isHex,
normalizeEthStringToWei,
isInvalidChecksumAddress,
isValidAddress,
-} = require('../../util')
-const EnsInput = require('../ens-input')
-const ethUtil = require('ethereumjs-util')
+} from '../../util'
+import EnsInput from '../ens-input'
+import ethUtil from 'ethereumjs-util'
import SendProfile from './send-profile'
import SendHeader from './send-header'
import ErrorComponent from '../error'
import { getMetaMaskAccounts } from '../../../../ui/app/selectors'
import ToastComponent from '../toast'
-module.exports = connect(mapStateToProps)(SendTransactionScreen)
+
+const optionalDataLabelStyle = {
+ background: '#ffffff',
+ color: '#333333',
+ marginTop: '16px',
+ marginBottom: '16px',
+}
+const optionalDataValueStyle = {
+ width: '100%',
+ resize: 'none',
+}
+
+class SendTransactionScreen extends PersistentForm {
+ render () {
+ this.persistentFormParentId = 'send-tx-form'
+
+ const props = this.props
+ const {
+ network,
+ identities,
+ addressBook,
+ error,
+ } = props
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Transaction Data (optional)
+
+
+
+
+
+ Custom nonce (optional)
+
+
+
+
+ )
+ }
+
+ componentWillUnmount () {
+ this.props.dispatch(actions.displayWarning(''))
+ }
+
+ navigateToAccounts (event) {
+ event.stopPropagation()
+ this.props.dispatch(actions.showAccountsPage())
+ }
+
+ recipientDidChange (recipient, nickname) {
+ this.setState({
+ recipient: recipient,
+ nickname: nickname,
+ })
+ }
+
+ onSubmit () {
+ const state = this.state || {}
+ let recipient = state.recipient || document.querySelector('input[name="address"]').value.replace(/^[.\s]+|[.\s]+$/g, '')
+ let nickname = state.nickname || ' '
+ if (typeof recipient === 'object') {
+ if (recipient.toAddress) {
+ recipient = recipient.toAddress
+ }
+ if (recipient.nickname) {
+ nickname = recipient.nickname
+ }
+ }
+ const input = document.querySelector('input[name="amount"]').value
+ const parts = input.split('.')
+
+ let message
+
+ if (isNaN(input) || input === '') {
+ message = 'Invalid ether value.'
+ return this.props.dispatch(actions.displayWarning(message))
+ }
+
+ if (parts[1]) {
+ const decimal = parts[1]
+ if (decimal.length > 18) {
+ message = 'Ether amount is too precise.'
+ return this.props.dispatch(actions.displayWarning(message))
+ }
+ }
+
+ const value = normalizeEthStringToWei(input)
+ const txData = document.querySelector('input[name="txData"]').value
+ const txCustomNonce = document.querySelector('input[name="txCustomNonce"]').value
+ const balance = this.props.balance
+
+ if (value.gt(balance)) {
+ message = 'Insufficient funds.'
+ return this.props.dispatch(actions.displayWarning(message))
+ }
+
+ if (input < 0) {
+ message = 'Can not send negative amounts of ETH.'
+ return this.props.dispatch(actions.displayWarning(message))
+ }
+
+ if ((isInvalidChecksumAddress(recipient, this.props.network))) {
+ message = 'Recipient address checksum is invalid.'
+ return this.props.dispatch(actions.displayWarning(message))
+ }
+
+ if ((!isValidAddress(recipient, this.props.network) && !txData) || (!recipient && !txData)) {
+ message = 'Recipient address is invalid.'
+ return this.props.dispatch(actions.displayWarning(message))
+ }
+
+ if (!isHex(ethUtil.stripHexPrefix(txData)) && txData) {
+ message = 'Transaction data must be hex string.'
+ return this.props.dispatch(actions.displayWarning(message))
+ }
+
+ this.props.dispatch(actions.hideWarning())
+
+ this.props.dispatch(actions.addToAddressBook(recipient, nickname))
+
+ const txParams = {
+ from: this.props.address,
+ value: '0x' + value.toString(16),
+ }
+
+ if (recipient) txParams.to = ethUtil.addHexPrefix(recipient)
+ if (txData) txParams.data = txData
+ if (txCustomNonce) txParams.nonce = '0x' + parseInt(txCustomNonce, 10).toString(16)
+
+ this.props.dispatch(actions.signTx(txParams))
+ }
+}
function mapStateToProps (state) {
const accounts = getMetaMaskAccounts(state)
@@ -37,200 +234,4 @@ function mapStateToProps (state) {
return result
}
-inherits(SendTransactionScreen, PersistentForm)
-function SendTransactionScreen () {
- PersistentForm.call(this)
-}
-
-SendTransactionScreen.prototype.render = function () {
- this.persistentFormParentId = 'send-tx-form'
-
- const props = this.props
- const {
- network,
- identities,
- addressBook,
- error,
- } = props
-
- return (
-
- h('.send-screen.flex-column.flex-grow', [
-
- h(ToastComponent, {
- isSuccess: false,
- }),
-
- //
- // Sender Profile
- //
-
- h(SendProfile),
-
- //
- // Send Header
- //
-
- h(SendHeader, {
- title: 'Send Transaction',
- }),
-
- // error message
- h(ErrorComponent, {
- error,
- }),
-
- // 'to' field
- h('section.flex-row.flex-center', [
- h(EnsInput, {
- name: 'address',
- placeholder: 'Recipient Address',
- onChange: this.recipientDidChange.bind(this),
- network,
- identities,
- addressBook,
- }),
- ]),
-
- // 'amount' and send button
- h('section.flex-row.flex-center', [
-
- h('input.large-input', {
- name: 'amount',
- placeholder: 'Amount',
- type: 'number',
- style: {
- marginRight: '6px',
- },
- dataset: {
- persistentFormid: 'tx-amount',
- },
- }),
-
- h('button', {
- onClick: this.onSubmit.bind(this),
- }, 'Next'),
-
- ]),
-
- //
- // Optional Fields
- //
- h('h3.flex-center', {
- style: {
- background: '#ffffff',
- color: '#333333',
- marginTop: '16px',
- marginBottom: '16px',
- },
- }, [
- 'Transaction Data (optional)',
- ]),
-
- // 'data' field
- h('section.flex-column.flex-center', [
- h('input.large-input', {
- name: 'txData',
- placeholder: '0x01234',
- style: {
- width: '100%',
- resize: 'none',
- },
- dataset: {
- persistentFormid: 'tx-data',
- },
- }),
- ]),
- ])
- )
-}
-
-SendTransactionScreen.prototype.componentWillUnmount = function () {
- this.props.dispatch(actions.displayWarning(''))
-}
-
-SendTransactionScreen.prototype.navigateToAccounts = function (event) {
- event.stopPropagation()
- this.props.dispatch(actions.showAccountsPage())
-}
-
-SendTransactionScreen.prototype.recipientDidChange = function (recipient, nickname) {
- this.setState({
- recipient: recipient,
- nickname: nickname,
- })
-}
-
-SendTransactionScreen.prototype.onSubmit = function () {
- const state = this.state || {}
- let recipient = state.recipient || document.querySelector('input[name="address"]').value.replace(/^[.\s]+|[.\s]+$/g, '')
- let nickname = state.nickname || ' '
- if (typeof recipient === 'object') {
- if (recipient.toAddress) {
- recipient = recipient.toAddress
- }
- if (recipient.nickname) {
- nickname = recipient.nickname
- }
- }
- const input = document.querySelector('input[name="amount"]').value
- const parts = input.split('.')
-
- let message
-
- if (isNaN(input) || input === '') {
- message = 'Invalid ether value.'
- return this.props.dispatch(actions.displayWarning(message))
- }
-
- if (parts[1]) {
- const decimal = parts[1]
- if (decimal.length > 18) {
- message = 'Ether amount is too precise.'
- return this.props.dispatch(actions.displayWarning(message))
- }
- }
-
- const value = normalizeEthStringToWei(input)
- const txData = document.querySelector('input[name="txData"]').value
- const balance = this.props.balance
-
- if (value.gt(balance)) {
- message = 'Insufficient funds.'
- return this.props.dispatch(actions.displayWarning(message))
- }
-
- if (input < 0) {
- message = 'Can not send negative amounts of ETH.'
- return this.props.dispatch(actions.displayWarning(message))
- }
-
- if ((isInvalidChecksumAddress(recipient, this.props.network))) {
- message = 'Recipient address checksum is invalid.'
- return this.props.dispatch(actions.displayWarning(message))
- }
-
- if ((!isValidAddress(recipient, this.props.network) && !txData) || (!recipient && !txData)) {
- message = 'Recipient address is invalid.'
- return this.props.dispatch(actions.displayWarning(message))
- }
-
- if (!isHex(ethUtil.stripHexPrefix(txData)) && txData) {
- message = 'Transaction data must be hex string.'
- return this.props.dispatch(actions.displayWarning(message))
- }
-
- this.props.dispatch(actions.hideWarning())
-
- this.props.dispatch(actions.addToAddressBook(recipient, nickname))
-
- const txParams = {
- from: this.props.address,
- value: '0x' + value.toString(16),
- }
-
- if (recipient) txParams.to = ethUtil.addHexPrefix(recipient)
- if (txData) txParams.data = txData
-
- this.props.dispatch(actions.signTx(txParams))
-}
+module.exports = connect(mapStateToProps)(SendTransactionScreen)
diff --git a/test/unit/app/controllers/transactions/tx-controller-test.js b/test/unit/app/controllers/transactions/tx-controller-test.js
index 4aa9d5f4a..dfcc64acc 100644
--- a/test/unit/app/controllers/transactions/tx-controller-test.js
+++ b/test/unit/app/controllers/transactions/tx-controller-test.js
@@ -345,7 +345,6 @@ describe('Transaction Controller', function () {
to: '0x1678a085c290ebd122dc42cba69373b5953b831d',
gasPrice: '0x77359400',
gas: '0x7b0d',
- nonce: '0x4b',
},
metamaskNetworkId: currentNetworkId,
}
diff --git a/ui/app/actions.js b/ui/app/actions.js
index 3c6ea9846..f89aebf4d 100644
--- a/ui/app/actions.js
+++ b/ui/app/actions.js
@@ -184,7 +184,6 @@ const actions = {
cancelPersonalMsg,
signTypedMsg,
cancelTypedMsg,
- sendTx: sendTx,
signTx: signTx,
signTokenTx: signTokenTx,
updateTransaction,
@@ -1198,22 +1197,6 @@ function clearSend () {
}
-function sendTx (txData) {
- log.info(`actions - sendTx: ${JSON.stringify(txData.txParams)}`)
- return (dispatch) => {
- log.debug(`actions calling background.approveTransaction`)
- background.approveTransaction(txData.id, (err) => {
- if (err) {
- err = err.message || err.error || err
- dispatch(actions.txError(err))
- return log.error(err)
- }
- dispatch(actions.completedTx(txData.id))
- dispatch(actions.closeCurrentNotificationWindow())
- })
- }
-}
-
function signTokenTx (tokenAddress, toAddress, amount, txData, confTxScreenParams) {
return dispatch => {
dispatch(actions.showLoadingIndication())