Merge pull request #3770 from ethcore/jg-execute-gas
GasPrice selection for contract execution
This commit is contained in:
commit
598fd42856
@ -15,13 +15,19 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { MenuItem } from 'material-ui';
|
import { Checkbox, MenuItem } from 'material-ui';
|
||||||
|
|
||||||
import { AddressSelect, Form, Input, Select, TypedInput } from '~/ui';
|
import { AddressSelect, Form, Input, Select, TypedInput } from '~/ui';
|
||||||
import { parseAbiType } from '~/util/abi';
|
import { parseAbiType } from '~/util/abi';
|
||||||
|
|
||||||
import styles from '../executeContract.css';
|
import styles from '../executeContract.css';
|
||||||
|
|
||||||
|
const CHECK_STYLE = {
|
||||||
|
position: 'absolute',
|
||||||
|
top: '38px',
|
||||||
|
left: '1em'
|
||||||
|
};
|
||||||
|
|
||||||
export default class DetailsStep extends Component {
|
export default class DetailsStep extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
accounts: PropTypes.object.isRequired,
|
accounts: PropTypes.object.isRequired,
|
||||||
@ -31,10 +37,12 @@ export default class DetailsStep extends Component {
|
|||||||
onAmountChange: PropTypes.func.isRequired,
|
onAmountChange: PropTypes.func.isRequired,
|
||||||
fromAddress: PropTypes.string,
|
fromAddress: PropTypes.string,
|
||||||
fromAddressError: PropTypes.string,
|
fromAddressError: PropTypes.string,
|
||||||
|
gasEdit: PropTypes.bool,
|
||||||
onFromAddressChange: PropTypes.func.isRequired,
|
onFromAddressChange: PropTypes.func.isRequired,
|
||||||
func: PropTypes.object,
|
func: PropTypes.object,
|
||||||
funcError: PropTypes.string,
|
funcError: PropTypes.string,
|
||||||
onFuncChange: PropTypes.func,
|
onFuncChange: PropTypes.func,
|
||||||
|
onGasEditClick: PropTypes.func,
|
||||||
values: PropTypes.array.isRequired,
|
values: PropTypes.array.isRequired,
|
||||||
valuesError: PropTypes.array.isRequired,
|
valuesError: PropTypes.array.isRequired,
|
||||||
warning: PropTypes.string,
|
warning: PropTypes.string,
|
||||||
@ -42,7 +50,7 @@ export default class DetailsStep extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { accounts, amount, amountError, fromAddress, fromAddressError, onFromAddressChange, onAmountChange } = this.props;
|
const { accounts, amount, amountError, fromAddress, fromAddressError, gasEdit, onGasEditClick, onFromAddressChange, onAmountChange } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
@ -56,12 +64,23 @@ export default class DetailsStep extends Component {
|
|||||||
onChange={ onFromAddressChange } />
|
onChange={ onFromAddressChange } />
|
||||||
{ this.renderFunctionSelect() }
|
{ this.renderFunctionSelect() }
|
||||||
{ this.renderParameters() }
|
{ this.renderParameters() }
|
||||||
|
<div className={ styles.columns }>
|
||||||
|
<div>
|
||||||
<Input
|
<Input
|
||||||
label='transaction value (in ETH)'
|
label='transaction value (in ETH)'
|
||||||
hint='the amount to send to with the transaction'
|
hint='the amount to send to with the transaction'
|
||||||
value={ amount }
|
value={ amount }
|
||||||
error={ amountError }
|
error={ amountError }
|
||||||
onSubmit={ onAmountChange } />
|
onSubmit={ onAmountChange } />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Checkbox
|
||||||
|
checked={ gasEdit }
|
||||||
|
label='edit gas price or value'
|
||||||
|
onCheck={ onGasEditClick }
|
||||||
|
style={ CHECK_STYLE } />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -42,3 +42,15 @@
|
|||||||
padding: 0.75em;
|
padding: 0.75em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.columns {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&>div {
|
||||||
|
flex: 0 1 50%;
|
||||||
|
width: 50%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -17,19 +17,36 @@
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
|
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
|
||||||
import ContentClear from 'material-ui/svg-icons/content/clear';
|
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 { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '~/ui';
|
import { BusyStep, Button, CompletedStep, GasPriceEditor, IdentityIcon, Modal, TxHash } from '~/ui';
|
||||||
import { MAX_GAS_ESTIMATION } from '~/util/constants';
|
import { MAX_GAS_ESTIMATION } from '~/util/constants';
|
||||||
import { validateAddress, validateUint } from '~/util/validation';
|
import { validateAddress, validateUint } from '~/util/validation';
|
||||||
import { parseAbiType } from '~/util/abi';
|
import { parseAbiType } from '~/util/abi';
|
||||||
|
|
||||||
import DetailsStep from './DetailsStep';
|
import DetailsStep from './DetailsStep';
|
||||||
|
|
||||||
import ERRORS from '../Transfer/errors';
|
|
||||||
import { ERROR_CODES } from '~/api/transport/error';
|
import { ERROR_CODES } from '~/api/transport/error';
|
||||||
|
|
||||||
|
const STEP_DETAILS = 0;
|
||||||
|
const STEP_BUSY_OR_GAS = 1;
|
||||||
|
const STEP_BUSY = 2;
|
||||||
|
|
||||||
|
const TITLES = {
|
||||||
|
transfer: 'function details',
|
||||||
|
sending: 'sending',
|
||||||
|
complete: 'complete',
|
||||||
|
gas: 'gas selection',
|
||||||
|
rejected: 'rejected'
|
||||||
|
};
|
||||||
|
const STAGES_BASIC = [TITLES.transfer, TITLES.sending, TITLES.complete];
|
||||||
|
const STAGES_GAS = [TITLES.transfer, TITLES.gas, TITLES.sending, TITLES.complete];
|
||||||
|
|
||||||
|
@observer
|
||||||
class ExecuteContract extends Component {
|
class ExecuteContract extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
api: PropTypes.object.isRequired,
|
api: PropTypes.object.isRequired,
|
||||||
@ -46,21 +63,22 @@ class ExecuteContract extends Component {
|
|||||||
onFromAddressChange: PropTypes.func.isRequired
|
onFromAddressChange: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gasStore = new GasPriceEditor.Store(this.context.api, this.props.gasLimit);
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
amount: '0',
|
amount: '0',
|
||||||
amountError: null,
|
amountError: null,
|
||||||
|
busyState: null,
|
||||||
fromAddressError: null,
|
fromAddressError: null,
|
||||||
func: null,
|
func: null,
|
||||||
funcError: null,
|
funcError: null,
|
||||||
gas: null,
|
gasEdit: false,
|
||||||
gasLimitError: null,
|
rejected: false,
|
||||||
|
step: STEP_DETAILS,
|
||||||
|
sending: false,
|
||||||
values: [],
|
values: [],
|
||||||
valuesError: [],
|
valuesError: [],
|
||||||
step: 0,
|
txhash: null
|
||||||
sending: false,
|
|
||||||
busyState: null,
|
|
||||||
txhash: null,
|
|
||||||
rejected: false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
@ -79,15 +97,21 @@ class ExecuteContract extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { sending } = this.state;
|
const { sending, step, gasEdit, rejected } = this.state;
|
||||||
|
const steps = gasEdit ? STAGES_GAS : STAGES_BASIC;
|
||||||
|
|
||||||
|
if (rejected) {
|
||||||
|
steps[steps.length - 1] = TITLES.rejected;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
actions={ this.renderDialogActions() }
|
actions={ this.renderDialogActions() }
|
||||||
title='execute function'
|
|
||||||
busy={ sending }
|
busy={ sending }
|
||||||
waiting={ [1] }
|
current={ step }
|
||||||
visible>
|
steps={ steps }
|
||||||
|
visible
|
||||||
|
waiting={ gasEdit ? [STEP_BUSY] : [STEP_BUSY_OR_GAS] }>
|
||||||
{ this.renderStep() }
|
{ this.renderStep() }
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
@ -95,7 +119,7 @@ class ExecuteContract extends Component {
|
|||||||
|
|
||||||
renderDialogActions () {
|
renderDialogActions () {
|
||||||
const { onClose, fromAddress } = this.props;
|
const { onClose, fromAddress } = this.props;
|
||||||
const { sending, step, fromAddressError, valuesError } = this.state;
|
const { gasEdit, sending, step, fromAddressError, valuesError } = this.state;
|
||||||
const hasError = fromAddressError || valuesError.find((error) => error);
|
const hasError = fromAddressError || valuesError.find((error) => error);
|
||||||
|
|
||||||
const cancelBtn = (
|
const cancelBtn = (
|
||||||
@ -105,21 +129,44 @@ class ExecuteContract extends Component {
|
|||||||
icon={ <ContentClear /> }
|
icon={ <ContentClear /> }
|
||||||
onClick={ onClose } />
|
onClick={ onClose } />
|
||||||
);
|
);
|
||||||
|
const postBtn = (
|
||||||
if (step === 0) {
|
|
||||||
return [
|
|
||||||
cancelBtn,
|
|
||||||
<Button
|
<Button
|
||||||
key='postTransaction'
|
key='postTransaction'
|
||||||
label='post transaction'
|
label='post transaction'
|
||||||
disabled={ !!(sending || hasError) }
|
disabled={ !!(sending || hasError) }
|
||||||
icon={ <IdentityIcon address={ fromAddress } button /> }
|
icon={ <IdentityIcon address={ fromAddress } button /> }
|
||||||
onClick={ this.postTransaction } />
|
onClick={ this.postTransaction } />
|
||||||
|
);
|
||||||
|
const nextBtn = (
|
||||||
|
<Button
|
||||||
|
key='nextStep'
|
||||||
|
label='next'
|
||||||
|
icon={ <NavigationArrowForward /> }
|
||||||
|
onClick={ this.onNextClick } />
|
||||||
|
);
|
||||||
|
const prevBtn = (
|
||||||
|
<Button
|
||||||
|
key='prevStep'
|
||||||
|
label='prev'
|
||||||
|
icon={ <NavigationArrowBack /> }
|
||||||
|
onClick={ this.onPrevClick } />
|
||||||
|
);
|
||||||
|
|
||||||
|
if (step === STEP_DETAILS) {
|
||||||
|
return [
|
||||||
|
cancelBtn,
|
||||||
|
gasEdit ? nextBtn : postBtn
|
||||||
];
|
];
|
||||||
} else if (step === 1) {
|
} else if (step === (gasEdit ? STEP_BUSY : STEP_BUSY_OR_GAS)) {
|
||||||
return [
|
return [
|
||||||
cancelBtn
|
cancelBtn
|
||||||
];
|
];
|
||||||
|
} else if (gasEdit && (step === STEP_BUSY_OR_GAS)) {
|
||||||
|
return [
|
||||||
|
cancelBtn,
|
||||||
|
prevBtn,
|
||||||
|
postBtn
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@ -133,7 +180,8 @@ class ExecuteContract extends Component {
|
|||||||
|
|
||||||
renderStep () {
|
renderStep () {
|
||||||
const { onFromAddressChange } = this.props;
|
const { onFromAddressChange } = this.props;
|
||||||
const { step, busyState, gasLimitError, txhash, rejected } = this.state;
|
const { gasEdit, step, busyState, txhash, rejected } = this.state;
|
||||||
|
const { errorEstimated } = this.gasStore;
|
||||||
|
|
||||||
if (rejected) {
|
if (rejected) {
|
||||||
return (
|
return (
|
||||||
@ -144,23 +192,29 @@ class ExecuteContract extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (step === 0) {
|
if (step === STEP_DETAILS) {
|
||||||
return (
|
return (
|
||||||
<DetailsStep
|
<DetailsStep
|
||||||
{ ...this.props }
|
{ ...this.props }
|
||||||
{ ...this.state }
|
{ ...this.state }
|
||||||
warning={ gasLimitError }
|
warning={ errorEstimated }
|
||||||
onAmountChange={ this.onAmountChange }
|
onAmountChange={ this.onAmountChange }
|
||||||
onFromAddressChange={ onFromAddressChange }
|
onFromAddressChange={ onFromAddressChange }
|
||||||
onFuncChange={ this.onFuncChange }
|
onFuncChange={ this.onFuncChange }
|
||||||
|
onGasEditClick={ this.onGasEditClick }
|
||||||
onValueChange={ this.onValueChange } />
|
onValueChange={ this.onValueChange } />
|
||||||
);
|
);
|
||||||
} else if (step === 1) {
|
} else if (step === (gasEdit ? STEP_BUSY : STEP_BUSY_OR_GAS)) {
|
||||||
return (
|
return (
|
||||||
<BusyStep
|
<BusyStep
|
||||||
title='The function execution is in progress'
|
title='The function execution is in progress'
|
||||||
state={ busyState } />
|
state={ busyState } />
|
||||||
);
|
);
|
||||||
|
} else if (gasEdit && (step === STEP_BUSY_OR_GAS)) {
|
||||||
|
return (
|
||||||
|
<GasPriceEditor
|
||||||
|
store={ this.gasStore } />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -171,6 +225,7 @@ class ExecuteContract extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onAmountChange = (amount) => {
|
onAmountChange = (amount) => {
|
||||||
|
this.gasStore.setEthValue(amount);
|
||||||
this.setState({ amount }, this.estimateGas);
|
this.setState({ amount }, this.estimateGas);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,7 +276,7 @@ class ExecuteContract extends Component {
|
|||||||
|
|
||||||
estimateGas = (_fromAddress) => {
|
estimateGas = (_fromAddress) => {
|
||||||
const { api } = this.context;
|
const { api } = this.context;
|
||||||
const { fromAddress, gasLimit } = this.props;
|
const { fromAddress } = this.props;
|
||||||
const { amount, func, values } = this.state;
|
const { amount, func, values } = this.state;
|
||||||
const options = {
|
const options = {
|
||||||
gas: MAX_GAS_ESTIMATION,
|
gas: MAX_GAS_ESTIMATION,
|
||||||
@ -237,18 +292,11 @@ class ExecuteContract extends Component {
|
|||||||
.estimateGas(options, values)
|
.estimateGas(options, values)
|
||||||
.then((gasEst) => {
|
.then((gasEst) => {
|
||||||
const gas = gasEst.mul(1.2);
|
const gas = gasEst.mul(1.2);
|
||||||
let gasLimitError = null;
|
|
||||||
|
|
||||||
if (gas.gte(MAX_GAS_ESTIMATION)) {
|
console.log(`estimateGas: received ${gasEst.toFormat(0)}, adjusted to ${gas.toFormat(0)}`);
|
||||||
gasLimitError = ERRORS.gasException;
|
|
||||||
} else if (gas.gt(gasLimit)) {
|
|
||||||
gasLimitError = ERRORS.gasBlockLimit;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
this.gasStore.setEstimated(gasEst.toFixed(0));
|
||||||
gas,
|
this.gasStore.setGas(gas.toFixed(0));
|
||||||
gasLimitError
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.warn('estimateGas', error);
|
console.warn('estimateGas', error);
|
||||||
@ -258,22 +306,20 @@ class ExecuteContract extends Component {
|
|||||||
postTransaction = () => {
|
postTransaction = () => {
|
||||||
const { api, store } = this.context;
|
const { api, store } = this.context;
|
||||||
const { fromAddress } = this.props;
|
const { fromAddress } = this.props;
|
||||||
const { amount, func, values } = this.state;
|
const { amount, func, gasEdit, values } = this.state;
|
||||||
|
const steps = gasEdit ? STAGES_GAS : STAGES_BASIC;
|
||||||
|
const finalstep = steps.length - 1;
|
||||||
const options = {
|
const options = {
|
||||||
gas: MAX_GAS_ESTIMATION,
|
gas: this.gasStore.gas,
|
||||||
|
gasPrice: this.gasStore.price,
|
||||||
from: fromAddress,
|
from: fromAddress,
|
||||||
value: api.util.toWei(amount || 0)
|
value: api.util.toWei(amount || 0)
|
||||||
};
|
};
|
||||||
|
|
||||||
this.setState({ sending: true, step: 1 });
|
this.setState({ sending: true, step: gasEdit ? STEP_BUSY : STEP_BUSY_OR_GAS });
|
||||||
|
|
||||||
func
|
func
|
||||||
.estimateGas(options, values)
|
.postTransaction(options, values)
|
||||||
.then((gas) => {
|
|
||||||
options.gas = gas.mul(1.2).toFixed(0);
|
|
||||||
console.log(`estimateGas: received ${gas.toFormat(0)}, adjusted to ${gas.mul(1.2).toFormat(0)}`);
|
|
||||||
return func.postTransaction(options, values);
|
|
||||||
})
|
|
||||||
.then((requestId) => {
|
.then((requestId) => {
|
||||||
this.setState({ busyState: 'Waiting for authorization in the Parity Signer' });
|
this.setState({ busyState: 'Waiting for authorization in the Parity Signer' });
|
||||||
|
|
||||||
@ -281,7 +327,7 @@ class ExecuteContract extends Component {
|
|||||||
.pollMethod('parity_checkRequest', requestId)
|
.pollMethod('parity_checkRequest', requestId)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
if (error.code === ERROR_CODES.REQUEST_REJECTED) {
|
if (error.code === ERROR_CODES.REQUEST_REJECTED) {
|
||||||
this.setState({ rejected: true });
|
this.setState({ rejected: true, step: finalstep });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,13 +335,31 @@ class ExecuteContract extends Component {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then((txhash) => {
|
.then((txhash) => {
|
||||||
this.setState({ sending: false, step: 2, txhash, busyState: 'Your transaction has been posted to the network' });
|
this.setState({ sending: false, step: finalstep, txhash, busyState: 'Your transaction has been posted to the network' });
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('postTransaction', error);
|
console.error('postTransaction', error);
|
||||||
store.dispatch({ type: 'newError', error });
|
store.dispatch({ type: 'newError', error });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onGasEditClick = () => {
|
||||||
|
this.setState({
|
||||||
|
gasEdit: !this.state.gasEdit
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onNextClick = () => {
|
||||||
|
this.setState({
|
||||||
|
step: this.state.step + 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onPrevClick = () => {
|
||||||
|
this.setState({
|
||||||
|
step: this.state.step - 1
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
|
@ -30,21 +30,14 @@ export default class Extras extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { gasStore, onChange, total, totalError } = this.props;
|
const { gasStore, onChange } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
{ this.renderData() }
|
{ this.renderData() }
|
||||||
<GasPriceEditor
|
<GasPriceEditor
|
||||||
store={ gasStore }
|
store={ gasStore }
|
||||||
onChange={ onChange }>
|
onChange={ onChange } />
|
||||||
<Input
|
|
||||||
disabled
|
|
||||||
label='total transaction amount'
|
|
||||||
hint='the total amount of the transaction'
|
|
||||||
error={ totalError }
|
|
||||||
value={ `${total} ETH` } />
|
|
||||||
</GasPriceEditor>
|
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -408,6 +408,8 @@ export default class TransferStore {
|
|||||||
this.totalError = totalError;
|
this.totalError = totalError;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
this.valueError = valueError;
|
this.valueError = valueError;
|
||||||
|
this.gasStore.setErrorTotal(totalError);
|
||||||
|
this.gasStore.setEthValue(totalEth);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,8 +26,11 @@ import styles from './gasPriceEditor.css';
|
|||||||
|
|
||||||
@observer
|
@observer
|
||||||
export default class GasPriceEditor extends Component {
|
export default class GasPriceEditor extends Component {
|
||||||
|
static contextTypes = {
|
||||||
|
api: PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
children: PropTypes.node,
|
|
||||||
store: PropTypes.object.isRequired,
|
store: PropTypes.object.isRequired,
|
||||||
onChange: PropTypes.func
|
onChange: PropTypes.func
|
||||||
}
|
}
|
||||||
@ -35,9 +38,11 @@ export default class GasPriceEditor extends Component {
|
|||||||
static Store = Store;
|
static Store = Store;
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { children, store } = this.props;
|
const { api } = this.context;
|
||||||
const { estimated, priceDefault, price, gas, histogram, errorGas, errorPrice } = store;
|
const { store } = this.props;
|
||||||
|
const { estimated, priceDefault, price, gas, histogram, errorGas, errorPrice, errorTotal, totalValue } = store;
|
||||||
|
|
||||||
|
const eth = api.util.fromWei(totalValue).toFormat();
|
||||||
const gasLabel = `gas (estimated: ${new BigNumber(estimated).toFormat()})`;
|
const gasLabel = `gas (estimated: ${new BigNumber(estimated).toFormat()})`;
|
||||||
const priceLabel = `price (current: ${new BigNumber(priceDefault).toFormat()})`;
|
const priceLabel = `price (current: ${new BigNumber(priceDefault).toFormat()})`;
|
||||||
|
|
||||||
@ -75,7 +80,12 @@ export default class GasPriceEditor extends Component {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={ styles.row }>
|
<div className={ styles.row }>
|
||||||
{ children }
|
<Input
|
||||||
|
disabled
|
||||||
|
label='total transaction amount'
|
||||||
|
hint='the total amount of the transaction'
|
||||||
|
error={ errorTotal }
|
||||||
|
value={ `${eth} ETH` } />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import { action, observable, transaction } from 'mobx';
|
import { action, computed, observable, transaction } from 'mobx';
|
||||||
|
|
||||||
import { ERRORS, validatePositiveNumber } from '~/util/validation';
|
import { ERRORS, validatePositiveNumber } from '~/util/validation';
|
||||||
import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '~/util/constants';
|
import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '~/util/constants';
|
||||||
@ -24,12 +24,14 @@ export default class GasPriceEditor {
|
|||||||
@observable errorEstimated = null;
|
@observable errorEstimated = null;
|
||||||
@observable errorGas = null;
|
@observable errorGas = null;
|
||||||
@observable errorPrice = null;
|
@observable errorPrice = null;
|
||||||
|
@observable errorTotal = null;
|
||||||
@observable estimated = DEFAULT_GAS;
|
@observable estimated = DEFAULT_GAS;
|
||||||
@observable histogram = null;
|
@observable histogram = null;
|
||||||
@observable price = DEFAULT_GASPRICE;
|
@observable price = DEFAULT_GASPRICE;
|
||||||
@observable priceDefault = DEFAULT_GASPRICE;
|
@observable priceDefault = DEFAULT_GASPRICE;
|
||||||
@observable gas = DEFAULT_GAS;
|
@observable gas = DEFAULT_GAS;
|
||||||
@observable gasLimit = 0;
|
@observable gasLimit = 0;
|
||||||
|
@observable weiValue = '0';
|
||||||
|
|
||||||
constructor (api, gasLimit, loadDefaults = true) {
|
constructor (api, gasLimit, loadDefaults = true) {
|
||||||
this._api = api;
|
this._api = api;
|
||||||
@ -40,6 +42,18 @@ export default class GasPriceEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@computed get totalValue () {
|
||||||
|
try {
|
||||||
|
return new BigNumber(this.gas).mul(this.price).add(this.weiValue);
|
||||||
|
} catch (error) {
|
||||||
|
return new BigNumber(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setErrorTotal = (errorTotal) => {
|
||||||
|
this.errorTotal = errorTotal;
|
||||||
|
}
|
||||||
|
|
||||||
@action setEstimated = (estimated) => {
|
@action setEstimated = (estimated) => {
|
||||||
transaction(() => {
|
transaction(() => {
|
||||||
const bn = new BigNumber(estimated);
|
const bn = new BigNumber(estimated);
|
||||||
@ -56,6 +70,10 @@ export default class GasPriceEditor {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action setEthValue = (weiValue) => {
|
||||||
|
this.weiValue = weiValue;
|
||||||
|
}
|
||||||
|
|
||||||
@action setHistogram = (gasHistogram) => {
|
@action setHistogram = (gasHistogram) => {
|
||||||
this.histogram = gasHistogram;
|
this.histogram = gasHistogram;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user