Merge pull request #379 from poanetwork/vb-set-custom-nonce
Ability to set custom tx nonce
This commit is contained in:
commit
d1611a4e04
|
@ -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
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -345,7 +345,6 @@ describe('Transaction Controller', function () {
|
|||
to: '0x1678a085c290ebd122dc42cba69373b5953b831d',
|
||||
gasPrice: '0x77359400',
|
||||
gas: '0x7b0d',
|
||||
nonce: '0x4b',
|
||||
},
|
||||
metamaskNetworkId: currentNetworkId,
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
Loading…
Reference in New Issue