Allow setting of minBlock on sending (#3921)
* minBlock value formatting * Allow Contract execute to specify minBock * Transfer allows minBlock * Cleanups * Check errors, verify via testing * Display Submitted/Submission block in MethodDecoding
This commit is contained in:
parent
74efb22230
commit
fc620d0d3e
@ -137,6 +137,10 @@ export function inOptions (options) {
|
||||
options[key] = inNumber16((new BigNumber(options[key])).round());
|
||||
break;
|
||||
|
||||
case 'minBlock':
|
||||
options[key] = options[key] ? inNumber16(options[key]) : null;
|
||||
break;
|
||||
|
||||
case 'value':
|
||||
case 'nonce':
|
||||
options[key] = inNumber16(options[key]);
|
||||
|
@ -204,7 +204,7 @@ describe('api/format/input', () => {
|
||||
});
|
||||
});
|
||||
|
||||
['gas', 'gasPrice', 'value', 'nonce'].forEach((input) => {
|
||||
['gas', 'gasPrice', 'value', 'minBlock', 'nonce'].forEach((input) => {
|
||||
it(`formats ${input} number as hexnumber`, () => {
|
||||
const block = {};
|
||||
block[input] = 0x123;
|
||||
@ -214,6 +214,10 @@ describe('api/format/input', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('passes minBlock as null when specified as such', () => {
|
||||
expect(inOptions({ minBlock: null })).to.deep.equal({ minBlock: null });
|
||||
});
|
||||
|
||||
it('ignores and passes through unknown keys', () => {
|
||||
expect(inOptions({ someRandom: 'someRandom' })).to.deep.equal({ someRandom: 'someRandom' });
|
||||
});
|
||||
|
@ -205,6 +205,10 @@ export function outTransaction (tx) {
|
||||
tx[key] = outNumber(tx[key]);
|
||||
break;
|
||||
|
||||
case 'minBlock':
|
||||
tx[key] = tx[key] ? outNumber(tx[key]) : null;
|
||||
break;
|
||||
|
||||
case 'creates':
|
||||
case 'from':
|
||||
case 'to':
|
||||
|
@ -283,7 +283,7 @@ describe('api/format/output', () => {
|
||||
});
|
||||
});
|
||||
|
||||
['blockNumber', 'gasPrice', 'gas', 'nonce', 'transactionIndex', 'value'].forEach((input) => {
|
||||
['blockNumber', 'gasPrice', 'gas', 'minBlock', 'nonce', 'transactionIndex', 'value'].forEach((input) => {
|
||||
it(`formats ${input} number as hexnumber`, () => {
|
||||
const block = {};
|
||||
block[input] = 0x123;
|
||||
@ -294,6 +294,10 @@ describe('api/format/output', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('passes minBlock as null when null', () => {
|
||||
expect(outTransaction({ minBlock: null })).to.deep.equal({ minBlock: null });
|
||||
});
|
||||
|
||||
it('ignores and passes through unknown keys', () => {
|
||||
expect(outTransaction({ someRandom: 'someRandom' })).to.deep.equal({ someRandom: 'someRandom' });
|
||||
});
|
||||
|
57
js/src/modals/ExecuteContract/AdvancedStep/advancedStep.js
Normal file
57
js/src/modals/ExecuteContract/AdvancedStep/advancedStep.js
Normal file
@ -0,0 +1,57 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Input, GasPriceEditor } from '~/ui';
|
||||
|
||||
import styles from '../executeContract.css';
|
||||
|
||||
export default class AdvancedStep extends Component {
|
||||
static propTypes = {
|
||||
gasStore: PropTypes.object.isRequired,
|
||||
minBlock: PropTypes.string,
|
||||
minBlockError: PropTypes.string,
|
||||
onMinBlockChange: PropTypes.func
|
||||
};
|
||||
|
||||
render () {
|
||||
const { gasStore, minBlock, minBlockError, onMinBlockChange } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Input
|
||||
error={ minBlockError }
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='executeContract.advanced.minBlock.hint'
|
||||
defaultMessage='Only post the transaction after this block' />
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='executeContract.advanced.minBlock.label'
|
||||
defaultMessage='BlockNumber to send from' />
|
||||
}
|
||||
value={ minBlock }
|
||||
onSubmit={ onMinBlockChange } />
|
||||
<div className={ styles.gaseditor }>
|
||||
<GasPriceEditor store={ gasStore } />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
17
js/src/modals/ExecuteContract/AdvancedStep/index.js
Normal file
17
js/src/modals/ExecuteContract/AdvancedStep/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
export default from './advancedStep';
|
@ -14,8 +14,9 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { Checkbox, MenuItem } from 'material-ui';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { AddressSelect, Form, Input, Select, TypedInput } from '~/ui';
|
||||
|
||||
@ -29,29 +30,28 @@ const CHECK_STYLE = {
|
||||
|
||||
export default class DetailsStep extends Component {
|
||||
static propTypes = {
|
||||
advancedOptions: PropTypes.bool,
|
||||
accounts: PropTypes.object.isRequired,
|
||||
contract: PropTypes.object.isRequired,
|
||||
onAmountChange: PropTypes.func.isRequired,
|
||||
onFromAddressChange: PropTypes.func.isRequired,
|
||||
onValueChange: PropTypes.func.isRequired,
|
||||
values: PropTypes.array.isRequired,
|
||||
valuesError: PropTypes.array.isRequired,
|
||||
|
||||
amount: PropTypes.string,
|
||||
amountError: PropTypes.string,
|
||||
balances: PropTypes.object,
|
||||
contract: PropTypes.object.isRequired,
|
||||
fromAddress: PropTypes.string,
|
||||
fromAddressError: PropTypes.string,
|
||||
func: PropTypes.object,
|
||||
funcError: PropTypes.string,
|
||||
gasEdit: PropTypes.bool,
|
||||
onAdvancedClick: PropTypes.func,
|
||||
onAmountChange: PropTypes.func.isRequired,
|
||||
onFromAddressChange: PropTypes.func.isRequired,
|
||||
onFuncChange: PropTypes.func,
|
||||
onGasEditClick: PropTypes.func,
|
||||
onValueChange: PropTypes.func.isRequired,
|
||||
values: PropTypes.array.isRequired,
|
||||
valuesError: PropTypes.array.isRequired,
|
||||
warning: PropTypes.string
|
||||
}
|
||||
|
||||
render () {
|
||||
const { accounts, amount, amountError, balances, fromAddress, fromAddressError, gasEdit, onGasEditClick, onFromAddressChange, onAmountChange } = this.props;
|
||||
const { accounts, advancedOptions, amount, amountError, balances, fromAddress, fromAddressError, onAdvancedClick, onAmountChange, onFromAddressChange } = this.props;
|
||||
|
||||
return (
|
||||
<Form>
|
||||
@ -60,8 +60,16 @@ export default class DetailsStep extends Component {
|
||||
accounts={ accounts }
|
||||
balances={ balances }
|
||||
error={ fromAddressError }
|
||||
hint='the account to transact with'
|
||||
label='from account'
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='executeContract.details.address.label'
|
||||
defaultMessage='the account to transact with' />
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='executeContract.details.address.hint'
|
||||
defaultMessage='from account' />
|
||||
}
|
||||
onChange={ onFromAddressChange }
|
||||
value={ fromAddress } />
|
||||
{ this.renderFunctionSelect() }
|
||||
@ -70,16 +78,28 @@ export default class DetailsStep extends Component {
|
||||
<div>
|
||||
<Input
|
||||
error={ amountError }
|
||||
hint='the amount to send to with the transaction'
|
||||
label='transaction value (in ETH)'
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='executeContract.details.amount.hint'
|
||||
defaultMessage='the amount to send to with the transaction' />
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='executeContract.details.amount.label'
|
||||
defaultMessage='transaction value (in ETH)' />
|
||||
}
|
||||
onSubmit={ onAmountChange }
|
||||
value={ amount } />
|
||||
</div>
|
||||
<div>
|
||||
<Checkbox
|
||||
checked={ gasEdit }
|
||||
label='edit gas price or value'
|
||||
onCheck={ onGasEditClick }
|
||||
checked={ advancedOptions }
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='executeContract.details.advancedCheck.label'
|
||||
defaultMessage='advanced sending options' />
|
||||
}
|
||||
onCheck={ onAdvancedClick }
|
||||
style={ CHECK_STYLE } />
|
||||
</div>
|
||||
</div>
|
||||
@ -129,9 +149,17 @@ export default class DetailsStep extends Component {
|
||||
|
||||
return (
|
||||
<Select
|
||||
label='function to execute'
|
||||
hint='the function to call on the contract'
|
||||
error={ funcError }
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='executeContract.details.function.hint'
|
||||
defaultMessage='the function to call on the contract' />
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='executeContract.details.function.label'
|
||||
defaultMessage='function to execute' />
|
||||
}
|
||||
onChange={ this.onFuncChange }
|
||||
value={ func.signature }>
|
||||
{ functions }
|
||||
|
@ -14,6 +14,19 @@
|
||||
/* You should have received a copy of the GNU General Public License
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.funcparams {
|
||||
padding-left: 3em;
|
||||
}
|
||||
|
||||
.gaseditor {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.paramname {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.modalbody,
|
||||
.modalcenter {
|
||||
}
|
||||
@ -22,14 +35,6 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.funcparams {
|
||||
padding-left: 3em;
|
||||
}
|
||||
|
||||
.paramname {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.txhash {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
@ -14,40 +14,54 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { pick } from 'lodash';
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { observer } from 'mobx-react';
|
||||
import { pick } from 'lodash';
|
||||
|
||||
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
|
||||
import ContentClear from 'material-ui/svg-icons/content/clear';
|
||||
import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back';
|
||||
import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward';
|
||||
|
||||
import { toWei } from '~/api/util/wei';
|
||||
import { BusyStep, Button, CompletedStep, GasPriceEditor, IdentityIcon, Modal, TxHash } from '~/ui';
|
||||
import { CancelIcon, DoneIcon, NextIcon, PrevIcon } from '~/ui/Icons';
|
||||
import { MAX_GAS_ESTIMATION } from '~/util/constants';
|
||||
import { validateAddress, validateUint } from '~/util/validation';
|
||||
import { parseAbiType } from '~/util/abi';
|
||||
|
||||
import AdvancedStep from './AdvancedStep';
|
||||
import DetailsStep from './DetailsStep';
|
||||
|
||||
import { ERROR_CODES } from '~/api/transport/error';
|
||||
|
||||
const STEP_DETAILS = 0;
|
||||
const STEP_BUSY_OR_GAS = 1;
|
||||
const STEP_BUSY_OR_ADVANCED = 1;
|
||||
const STEP_BUSY = 2;
|
||||
|
||||
const TITLES = {
|
||||
transfer: 'function details',
|
||||
sending: 'sending',
|
||||
complete: 'complete',
|
||||
gas: 'gas selection',
|
||||
rejected: 'rejected'
|
||||
transfer:
|
||||
<FormattedMessage
|
||||
id='executeContract.steps.transfer'
|
||||
defaultMessage='function details' />,
|
||||
sending:
|
||||
<FormattedMessage
|
||||
id='executeContract.steps.sending'
|
||||
defaultMessage='sending' />,
|
||||
complete:
|
||||
<FormattedMessage
|
||||
id='executeContract.steps.complete'
|
||||
defaultMessage='complete' />,
|
||||
advanced:
|
||||
<FormattedMessage
|
||||
id='executeContract.steps.advanced'
|
||||
defaultMessage='advanced options' />,
|
||||
rejected:
|
||||
<FormattedMessage
|
||||
id='executeContract.steps.rejected'
|
||||
defaultMessage='rejected' />
|
||||
};
|
||||
const STAGES_BASIC = [TITLES.transfer, TITLES.sending, TITLES.complete];
|
||||
const STAGES_GAS = [TITLES.transfer, TITLES.gas, TITLES.sending, TITLES.complete];
|
||||
const STAGES_ADVANCED = [TITLES.transfer, TITLES.advanced, TITLES.sending, TITLES.complete];
|
||||
|
||||
@observer
|
||||
class ExecuteContract extends Component {
|
||||
@ -70,13 +84,15 @@ class ExecuteContract extends Component {
|
||||
gasStore = new GasPriceEditor.Store(this.context.api, { gasLimit: this.props.gasLimit });
|
||||
|
||||
state = {
|
||||
advancedOptions: false,
|
||||
amount: '0',
|
||||
amountError: null,
|
||||
busyState: null,
|
||||
fromAddressError: null,
|
||||
func: null,
|
||||
funcError: null,
|
||||
gasEdit: false,
|
||||
minBlock: '0',
|
||||
minBlockError: null,
|
||||
rejected: false,
|
||||
sending: false,
|
||||
step: STEP_DETAILS,
|
||||
@ -101,8 +117,8 @@ class ExecuteContract extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { sending, step, gasEdit, rejected } = this.state;
|
||||
const steps = gasEdit ? STAGES_GAS : STAGES_BASIC;
|
||||
const { advancedOptions, rejected, sending, step } = this.state;
|
||||
const steps = advancedOptions ? STAGES_ADVANCED : STAGES_BASIC;
|
||||
|
||||
if (rejected) {
|
||||
steps[steps.length - 1] = TITLES.rejected;
|
||||
@ -115,7 +131,7 @@ class ExecuteContract extends Component {
|
||||
current={ step }
|
||||
steps={ steps }
|
||||
visible
|
||||
waiting={ gasEdit ? [STEP_BUSY] : [STEP_BUSY_OR_GAS] }>
|
||||
waiting={ advancedOptions ? [STEP_BUSY] : [STEP_BUSY_OR_ADVANCED] }>
|
||||
{ this.renderStep() }
|
||||
</Modal>
|
||||
);
|
||||
@ -123,20 +139,28 @@ class ExecuteContract extends Component {
|
||||
|
||||
renderDialogActions () {
|
||||
const { onClose, fromAddress } = this.props;
|
||||
const { gasEdit, sending, step, fromAddressError, valuesError } = this.state;
|
||||
const hasError = fromAddressError || valuesError.find((error) => error);
|
||||
const { advancedOptions, sending, step, fromAddressError, minBlockError, valuesError } = this.state;
|
||||
const hasError = fromAddressError || minBlockError || valuesError.find((error) => error);
|
||||
|
||||
const cancelBtn = (
|
||||
<Button
|
||||
key='cancel'
|
||||
label='Cancel'
|
||||
icon={ <ContentClear /> }
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='executeContract.button.cancel'
|
||||
defaultMessage='cancel' />
|
||||
}
|
||||
icon={ <CancelIcon /> }
|
||||
onClick={ onClose } />
|
||||
);
|
||||
const postBtn = (
|
||||
<Button
|
||||
key='postTransaction'
|
||||
label='post transaction'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='executeContract.button.post'
|
||||
defaultMessage='post transaction' />
|
||||
}
|
||||
disabled={ !!(sending || hasError) }
|
||||
icon={ <IdentityIcon address={ fromAddress } button /> }
|
||||
onClick={ this.postTransaction } />
|
||||
@ -144,28 +168,36 @@ class ExecuteContract extends Component {
|
||||
const nextBtn = (
|
||||
<Button
|
||||
key='nextStep'
|
||||
label='next'
|
||||
icon={ <NavigationArrowForward /> }
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='executeContract.button.next'
|
||||
defaultMessage='next' />
|
||||
}
|
||||
icon={ <NextIcon /> }
|
||||
onClick={ this.onNextClick } />
|
||||
);
|
||||
const prevBtn = (
|
||||
<Button
|
||||
key='prevStep'
|
||||
label='prev'
|
||||
icon={ <NavigationArrowBack /> }
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='executeContract.button.prev'
|
||||
defaultMessage='prev' />
|
||||
}
|
||||
icon={ <PrevIcon /> }
|
||||
onClick={ this.onPrevClick } />
|
||||
);
|
||||
|
||||
if (step === STEP_DETAILS) {
|
||||
return [
|
||||
cancelBtn,
|
||||
gasEdit ? nextBtn : postBtn
|
||||
advancedOptions ? nextBtn : postBtn
|
||||
];
|
||||
} else if (step === (gasEdit ? STEP_BUSY : STEP_BUSY_OR_GAS)) {
|
||||
} else if (step === (advancedOptions ? STEP_BUSY : STEP_BUSY_OR_ADVANCED)) {
|
||||
return [
|
||||
cancelBtn
|
||||
];
|
||||
} else if (gasEdit && (step === STEP_BUSY_OR_GAS)) {
|
||||
} else if (advancedOptions && (step === STEP_BUSY_OR_ADVANCED)) {
|
||||
return [
|
||||
cancelBtn,
|
||||
prevBtn,
|
||||
@ -176,23 +208,34 @@ class ExecuteContract extends Component {
|
||||
return [
|
||||
<Button
|
||||
key='close'
|
||||
label='Done'
|
||||
icon={ <ActionDoneAll /> }
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='executeContract.button.done'
|
||||
defaultMessage='done' />
|
||||
}
|
||||
icon={ <DoneIcon /> }
|
||||
onClick={ onClose } />
|
||||
];
|
||||
}
|
||||
|
||||
renderStep () {
|
||||
const { onFromAddressChange } = this.props;
|
||||
const { gasEdit, step, busyState, txhash, rejected } = this.state;
|
||||
const { advancedOptions, step, busyState, minBlock, minBlockError, txhash, rejected } = this.state;
|
||||
const { errorEstimated } = this.gasStore;
|
||||
|
||||
if (rejected) {
|
||||
return (
|
||||
<BusyStep
|
||||
title='The execution has been rejected'
|
||||
state='You can safely close this window, the function execution will not occur.'
|
||||
/>
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='executeContract.rejected.title'
|
||||
defaultMessage='The execution has been rejected' />
|
||||
}
|
||||
state={
|
||||
<FormattedMessage
|
||||
id='executeContract.rejected.state'
|
||||
defaultMessage='You can safely close this window, the function execution will not occur.' />
|
||||
} />
|
||||
);
|
||||
}
|
||||
|
||||
@ -205,19 +248,26 @@ class ExecuteContract extends Component {
|
||||
onAmountChange={ this.onAmountChange }
|
||||
onFromAddressChange={ onFromAddressChange }
|
||||
onFuncChange={ this.onFuncChange }
|
||||
onGasEditClick={ this.onGasEditClick }
|
||||
onAdvancedClick={ this.onAdvancedClick }
|
||||
onValueChange={ this.onValueChange } />
|
||||
);
|
||||
} else if (step === (gasEdit ? STEP_BUSY : STEP_BUSY_OR_GAS)) {
|
||||
} else if (step === (advancedOptions ? STEP_BUSY : STEP_BUSY_OR_ADVANCED)) {
|
||||
return (
|
||||
<BusyStep
|
||||
title='The function execution is in progress'
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='executeContract.busy.title'
|
||||
defaultMessage='The function execution is in progress' />
|
||||
}
|
||||
state={ busyState } />
|
||||
);
|
||||
} else if (gasEdit && (step === STEP_BUSY_OR_GAS)) {
|
||||
} else if (advancedOptions && (step === STEP_BUSY_OR_ADVANCED)) {
|
||||
return (
|
||||
<GasPriceEditor
|
||||
store={ this.gasStore } />
|
||||
<AdvancedStep
|
||||
gasStore={ this.gasStore }
|
||||
minBlock={ minBlock }
|
||||
minBlockError={ minBlockError }
|
||||
onMinBlockChange={ this.onMinBlockChange } />
|
||||
);
|
||||
}
|
||||
|
||||
@ -245,6 +295,15 @@ class ExecuteContract extends Component {
|
||||
}, this.estimateGas);
|
||||
}
|
||||
|
||||
onMinBlockChange = (minBlock) => {
|
||||
const minBlockError = validateUint(minBlock).valueError;
|
||||
|
||||
this.setState({
|
||||
minBlock,
|
||||
minBlockError
|
||||
});
|
||||
}
|
||||
|
||||
onValueChange = (event, index, _value) => {
|
||||
const { func, values, valuesError } = this.state;
|
||||
const input = func.inputs.find((input, _index) => index === _index);
|
||||
@ -305,22 +364,28 @@ class ExecuteContract extends Component {
|
||||
postTransaction = () => {
|
||||
const { api, store } = this.context;
|
||||
const { fromAddress } = this.props;
|
||||
const { amount, func, gasEdit, values } = this.state;
|
||||
const steps = gasEdit ? STAGES_GAS : STAGES_BASIC;
|
||||
const { advancedOptions, amount, func, minBlock, values } = this.state;
|
||||
const steps = advancedOptions ? STAGES_ADVANCED : STAGES_BASIC;
|
||||
const finalstep = steps.length - 1;
|
||||
const options = {
|
||||
gas: this.gasStore.gas,
|
||||
gasPrice: this.gasStore.price,
|
||||
from: fromAddress,
|
||||
minBlock: new BigNumber(minBlock || 0).gt(0) ? minBlock : null,
|
||||
value: api.util.toWei(amount || 0)
|
||||
};
|
||||
|
||||
this.setState({ sending: true, step: gasEdit ? STEP_BUSY : STEP_BUSY_OR_GAS });
|
||||
this.setState({ sending: true, step: advancedOptions ? STEP_BUSY : STEP_BUSY_OR_ADVANCED });
|
||||
|
||||
func
|
||||
.postTransaction(options, values)
|
||||
.then((requestId) => {
|
||||
this.setState({ busyState: 'Waiting for authorization in the Parity Signer' });
|
||||
this.setState({
|
||||
busyState:
|
||||
<FormattedMessage
|
||||
id='executeContract.busy.waitAuth'
|
||||
defaultMessage='Waiting for authorization in the Parity Signer' />
|
||||
});
|
||||
|
||||
return api
|
||||
.pollMethod('parity_checkRequest', requestId)
|
||||
@ -334,7 +399,15 @@ class ExecuteContract extends Component {
|
||||
});
|
||||
})
|
||||
.then((txhash) => {
|
||||
this.setState({ sending: false, step: finalstep, txhash, busyState: 'Your transaction has been posted to the network' });
|
||||
this.setState({
|
||||
sending: false,
|
||||
step: finalstep,
|
||||
txhash,
|
||||
busyState:
|
||||
<FormattedMessage
|
||||
id='executeContract.busy.posted'
|
||||
defaultMessage='Your transaction has been posted to the network' />
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('postTransaction', error);
|
||||
@ -342,9 +415,9 @@ class ExecuteContract extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
onGasEditClick = () => {
|
||||
onAdvancedClick = () => {
|
||||
this.setState({
|
||||
gasEdit: !this.state.gasEdit
|
||||
advancedOptions: !this.state.advancedOptions
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -15,29 +15,50 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { GasPriceEditor, Form, Input } from '~/ui';
|
||||
|
||||
import styles from '../transfer.css';
|
||||
|
||||
export default class Extras extends Component {
|
||||
static propTypes = {
|
||||
isEth: PropTypes.bool,
|
||||
data: PropTypes.string,
|
||||
dataError: PropTypes.string,
|
||||
total: PropTypes.string,
|
||||
totalError: PropTypes.string,
|
||||
gasStore: PropTypes.object.isRequired,
|
||||
isEth: PropTypes.bool,
|
||||
minBlock: PropTypes.string,
|
||||
minBlockError: PropTypes.string,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
gasStore: PropTypes.object.isRequired
|
||||
total: PropTypes.string,
|
||||
totalError: PropTypes.string
|
||||
}
|
||||
|
||||
render () {
|
||||
const { gasStore, onChange } = this.props;
|
||||
const { gasStore, minBlock, minBlockError, onChange } = this.props;
|
||||
|
||||
return (
|
||||
<Form>
|
||||
{ this.renderData() }
|
||||
<GasPriceEditor
|
||||
store={ gasStore }
|
||||
onChange={ onChange } />
|
||||
<Input
|
||||
error={ minBlockError }
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='executeContract.advanced.minBlock.hint'
|
||||
defaultMessage='Only post the transaction after this block' />
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='executeContract.advanced.minBlock.label'
|
||||
defaultMessage='BlockNumber to send from' />
|
||||
}
|
||||
value={ minBlock }
|
||||
onChange={ this.onEditMinBlock } />
|
||||
<div className={ styles.gaseditor }>
|
||||
<GasPriceEditor
|
||||
store={ gasStore }
|
||||
onChange={ onChange } />
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
@ -50,18 +71,28 @@ export default class Extras extends Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Input
|
||||
hint='the data to pass through with the transaction'
|
||||
label='transaction data'
|
||||
value={ data }
|
||||
error={ dataError }
|
||||
onChange={ this.onEditData } />
|
||||
</div>
|
||||
<Input
|
||||
error={ dataError }
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='transfer.advanced.data.hint'
|
||||
defaultMessage='the data to pass through with the transaction' />
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='transfer.advanced.data.label'
|
||||
defaultMessage='transaction data' />
|
||||
}
|
||||
onChange={ this.onEditData }
|
||||
value={ data } />
|
||||
);
|
||||
}
|
||||
|
||||
onEditData = (event) => {
|
||||
this.props.onChange('data', event.target.value);
|
||||
}
|
||||
|
||||
onEditMinBlock = (event) => {
|
||||
this.props.onChange('minBlock', event.target.value);
|
||||
}
|
||||
}
|
||||
|
@ -49,6 +49,9 @@ export default class TransferStore {
|
||||
@observable data = '';
|
||||
@observable dataError = null;
|
||||
|
||||
@observable minBlock = '0';
|
||||
@observable minBlockError = null;
|
||||
|
||||
@observable recipient = '';
|
||||
@observable recipientError = ERRORS.requireRecipient;
|
||||
|
||||
@ -84,7 +87,7 @@ export default class TransferStore {
|
||||
|
||||
@computed get isValid () {
|
||||
const detailsValid = !this.recipientError && !this.valueError && !this.totalError && !this.senderError;
|
||||
const extrasValid = !this.gasStore.errorGas && !this.gasStore.errorPrice && !this.totalError;
|
||||
const extrasValid = !this.gasStore.errorGas && !this.gasStore.errorPrice && !this.minBlockError && !this.totalError;
|
||||
const verifyValid = !this.passwordError;
|
||||
|
||||
switch (this.stage) {
|
||||
@ -92,7 +95,9 @@ export default class TransferStore {
|
||||
return detailsValid;
|
||||
|
||||
case 1:
|
||||
return this.extras ? extrasValid : verifyValid;
|
||||
return this.extras
|
||||
? extrasValid
|
||||
: verifyValid;
|
||||
|
||||
case 2:
|
||||
return verifyValid;
|
||||
@ -155,6 +160,9 @@ export default class TransferStore {
|
||||
case 'gasPrice':
|
||||
return this._onUpdateGasPrice(value);
|
||||
|
||||
case 'minBlock':
|
||||
return this._onUpdateMinBlock(value);
|
||||
|
||||
case 'recipient':
|
||||
return this._onUpdateRecipient(value);
|
||||
|
||||
@ -254,6 +262,14 @@ export default class TransferStore {
|
||||
this.recalculate();
|
||||
}
|
||||
|
||||
@action _onUpdateMinBlock = (minBlock) => {
|
||||
console.log('minBlock', minBlock);
|
||||
transaction(() => {
|
||||
this.minBlock = minBlock;
|
||||
this.minBlockError = this._validatePositiveNumber(minBlock);
|
||||
});
|
||||
}
|
||||
|
||||
@action _onUpdateGasPrice = (gasPrice) => {
|
||||
this.recalculate();
|
||||
}
|
||||
@ -412,6 +428,9 @@ export default class TransferStore {
|
||||
|
||||
send () {
|
||||
const { options, values } = this._getTransferParams();
|
||||
|
||||
options.minBlock = new BigNumber(this.minBlock || 0).gt(0) ? this.minBlock : null;
|
||||
|
||||
return this._getTransferMethod().postTransaction(options, values);
|
||||
}
|
||||
|
||||
|
@ -15,65 +15,68 @@
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.columns {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-wrap: wrap;
|
||||
|
||||
&>div {
|
||||
flex: 0 1 50%;
|
||||
position: relative;
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.gaseditor {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.info {
|
||||
line-height: 1.618em;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.columns {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.columns>div {
|
||||
flex: 0 1 50%;
|
||||
width: 50%;
|
||||
flex-wrap: wrap;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.floatbutton {
|
||||
text-align: right;
|
||||
float: right;
|
||||
margin-left: -100%;
|
||||
margin-top: 28px;
|
||||
}
|
||||
text-align: right;
|
||||
|
||||
.floatbutton>div {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.tokenSelect {
|
||||
&>div {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.token {
|
||||
height: 32px;
|
||||
padding: 4px 0;
|
||||
|
||||
img {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
margin: 0 16px 0 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
div {
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
|
||||
.tokenSelect .token {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.token img {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
margin: 0 16px 0 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.token div {
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
.tokenSelect {
|
||||
.token {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.tokenbalance {
|
||||
|
@ -20,13 +20,9 @@ import { bindActionCreators } from 'redux';
|
||||
import { observer } from 'mobx-react';
|
||||
import { pick } from 'lodash';
|
||||
|
||||
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
|
||||
import ContentClear from 'material-ui/svg-icons/content/clear';
|
||||
import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back';
|
||||
import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward';
|
||||
|
||||
import { newError } from '~/ui/Errors/actions';
|
||||
import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash, Input } from '~/ui';
|
||||
import { newError } from '~/ui/Errors/actions';
|
||||
import { CancelIcon, DoneIcon, NextIcon, PrevIcon } from '~/ui/Icons';
|
||||
import { nullableProptype } from '~/util/proptypes';
|
||||
|
||||
import Details from './Details';
|
||||
@ -188,17 +184,19 @@ class Transfer extends Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { isEth, data, dataError, total, totalError } = this.store;
|
||||
const { isEth, data, dataError, minBlock, minBlockError, total, totalError } = this.store;
|
||||
|
||||
return (
|
||||
<Extras
|
||||
isEth={ isEth }
|
||||
data={ data }
|
||||
dataError={ dataError }
|
||||
total={ total }
|
||||
totalError={ totalError }
|
||||
gasStore={ this.store.gasStore }
|
||||
onChange={ this.store.onUpdateDetails } />
|
||||
isEth={ isEth }
|
||||
minBlock={ minBlock }
|
||||
minBlockError={ minBlockError }
|
||||
onChange={ this.store.onUpdateDetails }
|
||||
total={ total }
|
||||
totalError={ totalError } />
|
||||
);
|
||||
}
|
||||
|
||||
@ -208,20 +206,20 @@ class Transfer extends Component {
|
||||
|
||||
const cancelBtn = (
|
||||
<Button
|
||||
icon={ <ContentClear /> }
|
||||
icon={ <CancelIcon /> }
|
||||
label='Cancel'
|
||||
onClick={ this.handleClose } />
|
||||
);
|
||||
const nextBtn = (
|
||||
<Button
|
||||
disabled={ !this.store.isValid }
|
||||
icon={ <NavigationArrowForward /> }
|
||||
icon={ <NextIcon /> }
|
||||
label='Next'
|
||||
onClick={ this.store.onNext } />
|
||||
);
|
||||
const prevBtn = (
|
||||
<Button
|
||||
icon={ <NavigationArrowBack /> }
|
||||
icon={ <PrevIcon /> }
|
||||
label='Back'
|
||||
onClick={ this.store.onPrev } />
|
||||
);
|
||||
@ -234,7 +232,7 @@ class Transfer extends Component {
|
||||
);
|
||||
const doneBtn = (
|
||||
<Button
|
||||
icon={ <ActionDoneAll /> }
|
||||
icon={ <DoneIcon /> }
|
||||
label='Close'
|
||||
onClick={ this.handleClose } />
|
||||
);
|
||||
|
@ -14,16 +14,18 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { MenuItem } from 'material-ui';
|
||||
import { isEqual, pick } from 'lodash';
|
||||
import { MenuItem } from 'material-ui';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { fromWei } from '~/api/util/wei';
|
||||
import { nodeOrStringProptype } from '~/util/proptypes';
|
||||
|
||||
import AutoComplete from '../AutoComplete';
|
||||
import IdentityIcon from '../../IdentityIcon';
|
||||
import IdentityName from '../../IdentityName';
|
||||
|
||||
import { fromWei } from '~/api/util/wei';
|
||||
|
||||
import styles from './addressSelect.css';
|
||||
|
||||
export default class AddressSelect extends Component {
|
||||
@ -40,9 +42,9 @@ export default class AddressSelect extends Component {
|
||||
contacts: PropTypes.object,
|
||||
contracts: PropTypes.object,
|
||||
disabled: PropTypes.bool,
|
||||
error: PropTypes.string,
|
||||
hint: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
error: nodeOrStringProptype(),
|
||||
hint: nodeOrStringProptype(),
|
||||
label: nodeOrStringProptype(),
|
||||
tokens: PropTypes.object,
|
||||
value: PropTypes.string,
|
||||
wallets: PropTypes.object
|
||||
@ -116,18 +118,27 @@ export default class AddressSelect extends Component {
|
||||
<AutoComplete
|
||||
className={ !icon ? '' : styles.paddedInput }
|
||||
disabled={ disabled }
|
||||
label={ label }
|
||||
hint={ hint ? `search for ${hint}` : 'search for an address' }
|
||||
error={ error }
|
||||
onChange={ this.onChange }
|
||||
onBlur={ this.onBlur }
|
||||
onUpdateInput={ allowInput && this.onUpdateInput }
|
||||
value={ searchText }
|
||||
filter={ this.handleFilter }
|
||||
entries={ autocompleteEntries }
|
||||
entry={ this.getEntry() || {} }
|
||||
error={ error }
|
||||
filter={ this.handleFilter }
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='ui.addressSelect.search.hint'
|
||||
defaultMessage='search for {hint}'
|
||||
values={ {
|
||||
hint: hint ||
|
||||
<FormattedMessage
|
||||
id='ui.addressSelect.search.address'
|
||||
defaultMessage='address' />
|
||||
} } />
|
||||
}
|
||||
label={ label }
|
||||
onBlur={ this.onBlur }
|
||||
onChange={ this.onChange }
|
||||
onUpdateInput={ allowInput && this.onUpdateInput }
|
||||
renderItem={ this.renderItem }
|
||||
/>
|
||||
value={ searchText } />
|
||||
{ icon }
|
||||
</div>
|
||||
);
|
||||
@ -148,9 +159,10 @@ export default class AddressSelect extends Component {
|
||||
|
||||
return (
|
||||
<IdentityIcon
|
||||
address={ value }
|
||||
center
|
||||
className={ classes.join(' ') }
|
||||
inline center
|
||||
address={ value } />
|
||||
inline />
|
||||
);
|
||||
}
|
||||
|
||||
@ -162,9 +174,10 @@ export default class AddressSelect extends Component {
|
||||
|
||||
if (!this.items[address] || this.items[address].balance !== balance) {
|
||||
this.items[address] = {
|
||||
address,
|
||||
balance,
|
||||
text: name && name.toUpperCase() || address,
|
||||
value: this.renderMenuItem(address),
|
||||
address, balance
|
||||
value: this.renderMenuItem(address)
|
||||
};
|
||||
}
|
||||
|
||||
@ -189,7 +202,7 @@ export default class AddressSelect extends Component {
|
||||
}
|
||||
|
||||
renderBalance (address) {
|
||||
const balance = this.getBalance(address);
|
||||
const balance = this.getBalance(address) || 0;
|
||||
const value = fromWei(balance);
|
||||
|
||||
return (
|
||||
@ -207,12 +220,13 @@ export default class AddressSelect extends Component {
|
||||
const item = (
|
||||
<div className={ styles.account }>
|
||||
<IdentityIcon
|
||||
address={ address }
|
||||
center
|
||||
className={ styles.image }
|
||||
inline center
|
||||
address={ address } />
|
||||
inline />
|
||||
<IdentityName
|
||||
className={ styles.name }
|
||||
address={ address } />
|
||||
address={ address }
|
||||
className={ styles.name } />
|
||||
{ balance }
|
||||
</div>
|
||||
);
|
||||
@ -221,8 +235,8 @@ export default class AddressSelect extends Component {
|
||||
<MenuItem
|
||||
className={ styles.menuItem }
|
||||
key={ address }
|
||||
value={ address }
|
||||
label={ item }>
|
||||
label={ item }
|
||||
value={ address }>
|
||||
{ item }
|
||||
</MenuItem>
|
||||
);
|
||||
|
@ -14,12 +14,13 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import keycode from 'keycode';
|
||||
import { isEqual } from 'lodash';
|
||||
import { MenuItem, AutoComplete as MUIAutoComplete, Divider as MUIDivider } from 'material-ui';
|
||||
import { PopoverAnimationVertical } from 'material-ui/Popover';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
import { isEqual } from 'lodash';
|
||||
import { nodeOrStringProptype } from '~/util/proptypes';
|
||||
|
||||
import styles from './autocomplete.css';
|
||||
|
||||
@ -41,21 +42,21 @@ class Divider extends Component {
|
||||
|
||||
export default class AutoComplete extends Component {
|
||||
static propTypes = {
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onUpdateInput: PropTypes.func,
|
||||
disabled: PropTypes.bool,
|
||||
label: PropTypes.string,
|
||||
hint: PropTypes.string,
|
||||
error: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
filter: PropTypes.func,
|
||||
renderItem: PropTypes.func,
|
||||
disabled: PropTypes.bool,
|
||||
entry: PropTypes.object,
|
||||
entries: PropTypes.oneOfType([
|
||||
PropTypes.array,
|
||||
PropTypes.object
|
||||
])
|
||||
]),
|
||||
error: nodeOrStringProptype(),
|
||||
filter: PropTypes.func,
|
||||
hint: nodeOrStringProptype(),
|
||||
label: nodeOrStringProptype(),
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onUpdateInput: PropTypes.func,
|
||||
renderItem: PropTypes.func,
|
||||
value: PropTypes.string
|
||||
};
|
||||
|
||||
state = {
|
||||
|
@ -122,10 +122,24 @@ class MethodDecoding extends Component {
|
||||
</span>
|
||||
<span> for a total transaction value of </span>
|
||||
<span className={ styles.highlight }>{ this.renderEtherValue(gasValue) }</span>
|
||||
{ this.renderMinBlock() }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderMinBlock () {
|
||||
const { historic, transaction } = this.props;
|
||||
const { minBlock } = transaction;
|
||||
|
||||
if (!minBlock || minBlock.eq(0)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<span>, { historic ? 'Submitted' : 'Submission' } at block <span className={ styles.highlight }>#{ minBlock.toFormat(0) }</span></span>
|
||||
);
|
||||
}
|
||||
|
||||
renderAction () {
|
||||
const { token } = this.props;
|
||||
const { methodName, methodInputs, methodSignature, isDeploy, isReceived, isContract } = this.state;
|
||||
|
Loading…
Reference in New Issue
Block a user