diff --git a/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js b/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js
index b4488729a..3ffb929a9 100644
--- a/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js
+++ b/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js
@@ -15,13 +15,19 @@
// along with Parity. If not, see .
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 { parseAbiType } from '~/util/abi';
import styles from '../executeContract.css';
+const CHECK_STYLE = {
+ position: 'absolute',
+ top: '38px',
+ left: '1em'
+};
+
export default class DetailsStep extends Component {
static propTypes = {
accounts: PropTypes.object.isRequired,
@@ -31,10 +37,12 @@ export default class DetailsStep extends Component {
onAmountChange: PropTypes.func.isRequired,
fromAddress: PropTypes.string,
fromAddressError: PropTypes.string,
+ gasEdit: PropTypes.bool,
onFromAddressChange: PropTypes.func.isRequired,
func: PropTypes.object,
funcError: PropTypes.string,
onFuncChange: PropTypes.func,
+ onGasEditClick: PropTypes.func,
values: PropTypes.array.isRequired,
valuesError: PropTypes.array.isRequired,
warning: PropTypes.string,
@@ -42,7 +50,7 @@ export default class DetailsStep extends Component {
}
render () {
- const { accounts, amount, amountError, fromAddress, fromAddressError, onFromAddressChange, onAmountChange } = this.props;
+ const { accounts, amount, amountError, fromAddress, fromAddressError, gasEdit, onGasEditClick, onFromAddressChange, onAmountChange } = this.props;
return (
);
}
diff --git a/js/src/modals/ExecuteContract/executeContract.css b/js/src/modals/ExecuteContract/executeContract.css
index a83b373ee..6b7132912 100644
--- a/js/src/modals/ExecuteContract/executeContract.css
+++ b/js/src/modals/ExecuteContract/executeContract.css
@@ -42,3 +42,15 @@
padding: 0.75em;
text-align: center;
}
+
+.columns {
+ display: flex;
+ flex-wrap: wrap;
+ position: relative;
+
+ &>div {
+ flex: 0 1 50%;
+ width: 50%;
+ position: relative;
+ }
+}
diff --git a/js/src/modals/ExecuteContract/executeContract.js b/js/src/modals/ExecuteContract/executeContract.js
index 2db5e2b04..7b4e8ccd2 100644
--- a/js/src/modals/ExecuteContract/executeContract.js
+++ b/js/src/modals/ExecuteContract/executeContract.js
@@ -17,19 +17,36 @@
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
+import { observer } from 'mobx-react';
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 { 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 { validateAddress, validateUint } from '~/util/validation';
import { parseAbiType } from '~/util/abi';
import DetailsStep from './DetailsStep';
-import ERRORS from '../Transfer/errors';
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 {
static contextTypes = {
api: PropTypes.object.isRequired,
@@ -46,21 +63,22 @@ class ExecuteContract extends Component {
onFromAddressChange: PropTypes.func.isRequired
}
+ gasStore = new GasPriceEditor.Store(this.context.api, this.props.gasLimit);
+
state = {
amount: '0',
amountError: null,
+ busyState: null,
fromAddressError: null,
func: null,
funcError: null,
- gas: null,
- gasLimitError: null,
+ gasEdit: false,
+ rejected: false,
+ step: STEP_DETAILS,
+ sending: false,
values: [],
valuesError: [],
- step: 0,
- sending: false,
- busyState: null,
- txhash: null,
- rejected: false
+ txhash: null
}
componentDidMount () {
@@ -79,15 +97,21 @@ class ExecuteContract extends Component {
}
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 (
+ current={ step }
+ steps={ steps }
+ visible
+ waiting={ gasEdit ? [STEP_BUSY] : [STEP_BUSY_OR_GAS] }>
{ this.renderStep() }
);
@@ -95,7 +119,7 @@ class ExecuteContract extends Component {
renderDialogActions () {
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 cancelBtn = (
@@ -105,21 +129,44 @@ class ExecuteContract extends Component {
icon={ }
onClick={ onClose } />
);
+ const postBtn = (
+ }
+ onClick={ this.postTransaction } />
+ );
+ const nextBtn = (
+ }
+ onClick={ this.onNextClick } />
+ );
+ const prevBtn = (
+ }
+ onClick={ this.onPrevClick } />
+ );
- if (step === 0) {
+ if (step === STEP_DETAILS) {
return [
cancelBtn,
- }
- onClick={ this.postTransaction } />
+ gasEdit ? nextBtn : postBtn
];
- } else if (step === 1) {
+ } else if (step === (gasEdit ? STEP_BUSY : STEP_BUSY_OR_GAS)) {
return [
cancelBtn
];
+ } else if (gasEdit && (step === STEP_BUSY_OR_GAS)) {
+ return [
+ cancelBtn,
+ prevBtn,
+ postBtn
+ ];
}
return [
@@ -133,7 +180,8 @@ class ExecuteContract extends Component {
renderStep () {
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) {
return (
@@ -144,23 +192,29 @@ class ExecuteContract extends Component {
);
}
- if (step === 0) {
+ if (step === STEP_DETAILS) {
return (
);
- } else if (step === 1) {
+ } else if (step === (gasEdit ? STEP_BUSY : STEP_BUSY_OR_GAS)) {
return (
);
+ } else if (gasEdit && (step === STEP_BUSY_OR_GAS)) {
+ return (
+
+ );
}
return (
@@ -171,6 +225,7 @@ class ExecuteContract extends Component {
}
onAmountChange = (amount) => {
+ this.gasStore.setEthValue(amount);
this.setState({ amount }, this.estimateGas);
}
@@ -221,7 +276,7 @@ class ExecuteContract extends Component {
estimateGas = (_fromAddress) => {
const { api } = this.context;
- const { fromAddress, gasLimit } = this.props;
+ const { fromAddress } = this.props;
const { amount, func, values } = this.state;
const options = {
gas: MAX_GAS_ESTIMATION,
@@ -237,18 +292,11 @@ class ExecuteContract extends Component {
.estimateGas(options, values)
.then((gasEst) => {
const gas = gasEst.mul(1.2);
- let gasLimitError = null;
- if (gas.gte(MAX_GAS_ESTIMATION)) {
- gasLimitError = ERRORS.gasException;
- } else if (gas.gt(gasLimit)) {
- gasLimitError = ERRORS.gasBlockLimit;
- }
+ console.log(`estimateGas: received ${gasEst.toFormat(0)}, adjusted to ${gas.toFormat(0)}`);
- this.setState({
- gas,
- gasLimitError
- });
+ this.gasStore.setEstimated(gasEst.toFixed(0));
+ this.gasStore.setGas(gas.toFixed(0));
})
.catch((error) => {
console.warn('estimateGas', error);
@@ -258,22 +306,20 @@ class ExecuteContract extends Component {
postTransaction = () => {
const { api, store } = this.context;
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 = {
- gas: MAX_GAS_ESTIMATION,
+ gas: this.gasStore.gas,
+ gasPrice: this.gasStore.price,
from: fromAddress,
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
- .estimateGas(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);
- })
+ .postTransaction(options, values)
.then((requestId) => {
this.setState({ busyState: 'Waiting for authorization in the Parity Signer' });
@@ -281,7 +327,7 @@ class ExecuteContract extends Component {
.pollMethod('parity_checkRequest', requestId)
.catch((error) => {
if (error.code === ERROR_CODES.REQUEST_REJECTED) {
- this.setState({ rejected: true });
+ this.setState({ rejected: true, step: finalstep });
return false;
}
@@ -289,13 +335,31 @@ class ExecuteContract extends Component {
});
})
.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) => {
console.error('postTransaction', 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) {
diff --git a/js/src/modals/Transfer/Extras/extras.js b/js/src/modals/Transfer/Extras/extras.js
index 6d2bfc821..def5a22c6 100644
--- a/js/src/modals/Transfer/Extras/extras.js
+++ b/js/src/modals/Transfer/Extras/extras.js
@@ -30,21 +30,14 @@ export default class Extras extends Component {
}
render () {
- const { gasStore, onChange, total, totalError } = this.props;
+ const { gasStore, onChange } = this.props;
return (
);
}
diff --git a/js/src/modals/Transfer/store.js b/js/src/modals/Transfer/store.js
index a43057c86..cbb10f17f 100644
--- a/js/src/modals/Transfer/store.js
+++ b/js/src/modals/Transfer/store.js
@@ -408,6 +408,8 @@ export default class TransferStore {
this.totalError = totalError;
this.value = value;
this.valueError = valueError;
+ this.gasStore.setErrorTotal(totalError);
+ this.gasStore.setEthValue(totalEth);
});
}
diff --git a/js/src/ui/GasPriceEditor/gasPriceEditor.js b/js/src/ui/GasPriceEditor/gasPriceEditor.js
index c6759ddf1..8c94dfca7 100644
--- a/js/src/ui/GasPriceEditor/gasPriceEditor.js
+++ b/js/src/ui/GasPriceEditor/gasPriceEditor.js
@@ -26,8 +26,11 @@ import styles from './gasPriceEditor.css';
@observer
export default class GasPriceEditor extends Component {
+ static contextTypes = {
+ api: PropTypes.object.isRequired
+ };
+
static propTypes = {
- children: PropTypes.node,
store: PropTypes.object.isRequired,
onChange: PropTypes.func
}
@@ -35,9 +38,11 @@ export default class GasPriceEditor extends Component {
static Store = Store;
render () {
- const { children, store } = this.props;
- const { estimated, priceDefault, price, gas, histogram, errorGas, errorPrice } = store;
+ const { api } = this.context;
+ 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 priceLabel = `price (current: ${new BigNumber(priceDefault).toFormat()})`;
@@ -75,7 +80,12 @@ export default class GasPriceEditor extends Component {
- { children }
+
diff --git a/js/src/ui/GasPriceEditor/store.js b/js/src/ui/GasPriceEditor/store.js
index 3f3e50430..afa5e15b2 100644
--- a/js/src/ui/GasPriceEditor/store.js
+++ b/js/src/ui/GasPriceEditor/store.js
@@ -15,7 +15,7 @@
// along with Parity. If not, see .
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 { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '~/util/constants';
@@ -24,12 +24,14 @@ export default class GasPriceEditor {
@observable errorEstimated = null;
@observable errorGas = null;
@observable errorPrice = null;
+ @observable errorTotal = null;
@observable estimated = DEFAULT_GAS;
@observable histogram = null;
@observable price = DEFAULT_GASPRICE;
@observable priceDefault = DEFAULT_GASPRICE;
@observable gas = DEFAULT_GAS;
@observable gasLimit = 0;
+ @observable weiValue = '0';
constructor (api, gasLimit, loadDefaults = true) {
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) => {
transaction(() => {
const bn = new BigNumber(estimated);
@@ -56,6 +70,10 @@ export default class GasPriceEditor {
});
}
+ @action setEthValue = (weiValue) => {
+ this.weiValue = weiValue;
+ }
+
@action setHistogram = (gasHistogram) => {
this.histogram = gasHistogram;
}