Merge pull request #379 from poanetwork/vb-set-custom-nonce

Ability to set custom tx nonce
This commit is contained in:
Victor Baranov 2020-05-04 23:50:10 +03:00 committed by GitHub
commit d1611a4e04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 213 additions and 228 deletions

View File

@ -2,6 +2,7 @@
## Current Master ## 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 - [#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 - [#364](https://github.com/poanetwork/nifty-wallet/pull/364) - (Fix) notifications order in batch requests

View File

@ -290,7 +290,8 @@ class TransactionController extends EventEmitter {
*/ */
async updateAndApproveTransaction (txMeta) { async updateAndApproveTransaction (txMeta) {
this.txStateManager.updateTx(txMeta, 'confTx: user approved transaction') 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 if any of these steps fails the tx status will be set to failed
@param txId {number} - the tx's Id @param txId {number} - the tx's Id
*/ */
async approveTransaction (txId) { async approveTransaction (txId, customNonce) {
let nonceLock let nonceLock
try { try {
// approve // approve
@ -315,7 +316,7 @@ class TransactionController extends EventEmitter {
// if txMeta has lastGasPrice then it is a retry at same nonce with higher // 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 // gas price transaction and their for the nonce should not be calculated
const nonce = txMeta.lastGasPrice ? txMeta.txParams.nonce : nonceLock.nextNonce 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 // add nonce debugging information to txMeta
txMeta.nonceDetails = nonceLock.nonceDetails txMeta.nonceDetails = nonceLock.nonceDetails
this.txStateManager.updateTx(txMeta, 'transactions#approveTransaction') this.txStateManager.updateTx(txMeta, 'transactions#approveTransaction')

View File

@ -1,48 +1,35 @@
const inherits = require('util').inherits import React from 'react'
const PersistentForm = require('../../../lib/persistent-form') import PersistentForm from '../../../lib/persistent-form'
const h = require('react-hyperscript') import { connect } from 'react-redux'
const connect = require('react-redux').connect import actions from '../../../../ui/app/actions'
const actions = require('../../../../ui/app/actions') import {
const {
numericBalance, numericBalance,
isHex, isHex,
normalizeEthStringToWei, normalizeEthStringToWei,
isInvalidChecksumAddress, isInvalidChecksumAddress,
isValidAddress, isValidAddress,
} = require('../../util') } from '../../util'
const EnsInput = require('../ens-input') import EnsInput from '../ens-input'
const ethUtil = require('ethereumjs-util') import ethUtil from 'ethereumjs-util'
import SendProfile from './send-profile' import SendProfile from './send-profile'
import SendHeader from './send-header' import SendHeader from './send-header'
import ErrorComponent from '../error' import ErrorComponent from '../error'
import { getMetaMaskAccounts } from '../../../../ui/app/selectors' import { getMetaMaskAccounts } from '../../../../ui/app/selectors'
import ToastComponent from '../toast' import ToastComponent from '../toast'
module.exports = connect(mapStateToProps)(SendTransactionScreen)
function mapStateToProps (state) { const optionalDataLabelStyle = {
const accounts = getMetaMaskAccounts(state) background: '#ffffff',
const result = { color: '#333333',
address: state.metamask.selectedAddress, marginTop: '16px',
accounts, marginBottom: '16px',
identities: state.metamask.identities, }
warning: state.appState.warning, const optionalDataValueStyle = {
network: state.metamask.network, width: '100%',
addressBook: state.metamask.addressBook, resize: 'none',
} }
result.error = result.warning && result.warning.split('.')[0] class SendTransactionScreen extends PersistentForm {
result.account = result.accounts[result.address] render () {
result.balance = result.account ? numericBalance(result.account.balance) : null
return result
}
inherits(SendTransactionScreen, PersistentForm)
function SendTransactionScreen () {
PersistentForm.call(this)
}
SendTransactionScreen.prototype.render = function () {
this.persistentFormParentId = 'send-tx-form' this.persistentFormParentId = 'send-tx-form'
const props = this.props const props = this.props
@ -54,114 +41,105 @@ SendTransactionScreen.prototype.render = function () {
} = props } = props
return ( return (
<div className="send-screen flex-column flex-grow">
<ToastComponent isSuccess={false} />
<SendProfile/>
h('.send-screen.flex-column.flex-grow', [ <SendHeader
title= "Send Transaction"
/>
h(ToastComponent, { <ErrorComponent
isSuccess: false, error={error}
}), />
// <section className="flex-row flex-center">
// Sender Profile <EnsInput
// name="address"
placeholder="Recipient Address"
onChange={this.recipientDidChange.bind(this)}
network={network}
identities={identities}
addressBook={addressBook}
/>
</section>
h(SendProfile), <section className="flex-row flex-center">
// <input className="large-input"
// Send Header name= "amount"
// placeholder= "Amount"
type= "number"
h(SendHeader, { style= {{
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', marginRight: '6px',
}, }}
dataset: { dataset={{
persistentFormid: 'tx-amount', persistentFormid: 'tx-amount',
}, }}
}), />
h('button', { <button
onClick: this.onSubmit.bind(this), onClick={this.onSubmit.bind(this)}>
}, 'Next'), Next
</button>
]), </section>
// <h3 className="flex-center"
// Optional Fields style={optionalDataLabelStyle}
// >
h('h3.flex-center', { Transaction Data (optional)
style: { </h3>
background: '#ffffff',
color: '#333333',
marginTop: '16px',
marginBottom: '16px',
},
}, [
'Transaction Data (optional)',
]),
// 'data' field <section className="flex-column flex-center">
h('section.flex-column.flex-center', [ <input className="large-input"
h('input.large-input', { name= "txData"
name: 'txData', placeholder= "e.g. 0x01234"
placeholder: '0x01234', style={optionalDataValueStyle}
style: { dataset={{
width: '100%',
resize: 'none',
},
dataset: {
persistentFormid: 'tx-data', persistentFormid: 'tx-data',
}, }}
}), />
]), </section>
])
<h3 className="flex-center"
style={optionalDataLabelStyle}
>
Custom nonce (optional)
</h3>
<section className="flex-column flex-center">
<input className="large-input"
name= "txCustomNonce"
type= "number"
placeholder= "e.g. 42"
style={optionalDataValueStyle}
dataset={{
persistentFormid: 'tx-custom-nonce',
}}
/>
</section>
</div>
) )
} }
SendTransactionScreen.prototype.componentWillUnmount = function () { componentWillUnmount () {
this.props.dispatch(actions.displayWarning('')) this.props.dispatch(actions.displayWarning(''))
} }
SendTransactionScreen.prototype.navigateToAccounts = function (event) { navigateToAccounts (event) {
event.stopPropagation() event.stopPropagation()
this.props.dispatch(actions.showAccountsPage()) this.props.dispatch(actions.showAccountsPage())
} }
SendTransactionScreen.prototype.recipientDidChange = function (recipient, nickname) { recipientDidChange (recipient, nickname) {
this.setState({ this.setState({
recipient: recipient, recipient: recipient,
nickname: nickname, nickname: nickname,
}) })
} }
SendTransactionScreen.prototype.onSubmit = function () { onSubmit () {
const state = this.state || {} const state = this.state || {}
let recipient = state.recipient || document.querySelector('input[name="address"]').value.replace(/^[.\s]+|[.\s]+$/g, '') let recipient = state.recipient || document.querySelector('input[name="address"]').value.replace(/^[.\s]+|[.\s]+$/g, '')
let nickname = state.nickname || ' ' let nickname = state.nickname || ' '
@ -193,6 +171,7 @@ SendTransactionScreen.prototype.onSubmit = function () {
const value = normalizeEthStringToWei(input) const value = normalizeEthStringToWei(input)
const txData = document.querySelector('input[name="txData"]').value const txData = document.querySelector('input[name="txData"]').value
const txCustomNonce = document.querySelector('input[name="txCustomNonce"]').value
const balance = this.props.balance const balance = this.props.balance
if (value.gt(balance)) { if (value.gt(balance)) {
@ -231,6 +210,28 @@ SendTransactionScreen.prototype.onSubmit = function () {
if (recipient) txParams.to = ethUtil.addHexPrefix(recipient) if (recipient) txParams.to = ethUtil.addHexPrefix(recipient)
if (txData) txParams.data = txData if (txData) txParams.data = txData
if (txCustomNonce) txParams.nonce = '0x' + parseInt(txCustomNonce, 10).toString(16)
this.props.dispatch(actions.signTx(txParams)) this.props.dispatch(actions.signTx(txParams))
} }
}
function mapStateToProps (state) {
const accounts = getMetaMaskAccounts(state)
const result = {
address: state.metamask.selectedAddress,
accounts,
identities: state.metamask.identities,
warning: state.appState.warning,
network: state.metamask.network,
addressBook: state.metamask.addressBook,
}
result.error = result.warning && result.warning.split('.')[0]
result.account = result.accounts[result.address]
result.balance = result.account ? numericBalance(result.account.balance) : null
return result
}
module.exports = connect(mapStateToProps)(SendTransactionScreen)

View File

@ -345,7 +345,6 @@ describe('Transaction Controller', function () {
to: '0x1678a085c290ebd122dc42cba69373b5953b831d', to: '0x1678a085c290ebd122dc42cba69373b5953b831d',
gasPrice: '0x77359400', gasPrice: '0x77359400',
gas: '0x7b0d', gas: '0x7b0d',
nonce: '0x4b',
}, },
metamaskNetworkId: currentNetworkId, metamaskNetworkId: currentNetworkId,
} }

View File

@ -184,7 +184,6 @@ const actions = {
cancelPersonalMsg, cancelPersonalMsg,
signTypedMsg, signTypedMsg,
cancelTypedMsg, cancelTypedMsg,
sendTx: sendTx,
signTx: signTx, signTx: signTx,
signTokenTx: signTokenTx, signTokenTx: signTokenTx,
updateTransaction, 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) { function signTokenTx (tokenAddress, toAddress, amount, txData, confTxScreenParams) {
return dispatch => { return dispatch => {
dispatch(actions.showLoadingIndication()) dispatch(actions.showLoadingIndication())