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
- [#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

View File

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

View File

@ -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 (
<div className="send-screen flex-column flex-grow">
<ToastComponent isSuccess={false} />
<SendProfile/>
<SendHeader
title= "Send Transaction"
/>
<ErrorComponent
error={error}
/>
<section className="flex-row flex-center">
<EnsInput
name="address"
placeholder="Recipient Address"
onChange={this.recipientDidChange.bind(this)}
network={network}
identities={identities}
addressBook={addressBook}
/>
</section>
<section className="flex-row flex-center">
<input className="large-input"
name= "amount"
placeholder= "Amount"
type= "number"
style= {{
marginRight: '6px',
}}
dataset={{
persistentFormid: 'tx-amount',
}}
/>
<button
onClick={this.onSubmit.bind(this)}>
Next
</button>
</section>
<h3 className="flex-center"
style={optionalDataLabelStyle}
>
Transaction Data (optional)
</h3>
<section className="flex-column flex-center">
<input className="large-input"
name= "txData"
placeholder= "e.g. 0x01234"
style={optionalDataValueStyle}
dataset={{
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>
)
}
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)

View File

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

View File

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