GasEditor component (#3750)

* Initial split of component (WIP)

* GasPriceEditor externalised

* Fix lint
This commit is contained in:
Jaco Greeff
2016-12-09 13:44:35 +01:00
committed by GitHub
parent befcc9cc1a
commit 2582514b58
13 changed files with 440 additions and 277 deletions

View File

@@ -16,96 +16,35 @@
import React, { Component, PropTypes } from 'react';
import Form, { Input } from '~/ui/Form';
import GasPriceSelector from '../GasPriceSelector';
import styles from '../transfer.css';
import { GasPriceEditor, Form, Input } from '~/ui';
export default class Extras extends Component {
static propTypes = {
isEth: PropTypes.bool,
data: PropTypes.string,
dataError: PropTypes.string,
gas: PropTypes.string,
gasEst: PropTypes.string,
gasError: PropTypes.string,
gasPrice: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object
]),
gasPriceDefault: PropTypes.string,
gasPriceError: PropTypes.string,
gasPriceHistogram: PropTypes.object,
total: PropTypes.string,
totalError: PropTypes.string,
onChange: PropTypes.func.isRequired
onChange: PropTypes.func.isRequired,
gasStore: PropTypes.object.isRequired
}
render () {
const { gas, gasPrice, gasError, gasEst, gasPriceDefault, gasPriceError, gasPriceHistogram, total, totalError } = this.props;
const gasLabel = `gas amount (estimated: ${gasEst})`;
const priceLabel = `gas price (current: ${gasPriceDefault})`;
const { gasStore, onChange, total, totalError } = this.props;
return (
<Form>
{ this.renderData() }
<div className={ styles.columns }>
<div style={ { flex: 65 } }>
<GasPriceSelector
gasPriceHistogram={ gasPriceHistogram }
gasPrice={ gasPrice }
onChange={ this.onEditGasPrice }
/>
</div>
<div
className={ styles.row }
style={ {
flex: 35, paddingLeft: '1rem',
justifyContent: 'space-around',
paddingBottom: 12
} }
>
<div className={ styles.row }>
<Input
label={ gasLabel }
hint='the amount of gas to use for the transaction'
error={ gasError }
value={ gas }
onChange={ this.onEditGas } />
<Input
label={ priceLabel }
hint='the price of gas to use for the transaction'
error={ gasPriceError }
value={ (gasPrice || '').toString() }
onChange={ this.onEditGasPrice } />
</div>
<div className={ styles.row }>
<Input
disabled
label='total transaction amount'
hint='the total amount of the transaction'
error={ totalError }
value={ `${total} ETH` } />
</div>
</div>
</div>
<div>
<p className={ styles.gasPriceDesc }>
You can choose the gas price based on the
distribution of recent included transactions' gas prices.
The lower the gas price is, the cheaper the transaction will
be. The higher the gas price is, the faster it should
get mined by the network.
</p>
</div>
<GasPriceEditor
store={ gasStore }
onChange={ onChange }>
<Input
disabled
label='total transaction amount'
hint='the total amount of the transaction'
error={ totalError }
value={ `${total} ETH` } />
</GasPriceEditor>
</Form>
);
}
@@ -129,14 +68,6 @@ export default class Extras extends Component {
);
}
onEditGas = (event) => {
this.props.onChange('gas', event.target.value);
}
onEditGasPrice = (event, value) => {
this.props.onChange('gasPrice', value);
}
onEditData = (event) => {
this.props.onChange('data', event.target.value);
}

View File

@@ -1,17 +0,0 @@
/* Copyright 2015, 2016 Ethcore (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/>.
*/

View File

@@ -1,558 +0,0 @@
// Copyright 2015, 2016 Ethcore (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 {
Bar, BarChart,
Rectangle,
Scatter, ScatterChart,
Tooltip,
XAxis, YAxis,
Dot,
ResponsiveContainer
} from 'recharts';
import Slider from 'material-ui/Slider';
import BigNumber from 'bignumber.js';
import componentStyles from './gasPriceSelector.css';
import mainStyles from '../transfer.css';
const styles = Object.assign({}, mainStyles, componentStyles);
const COLORS = {
default: 'rgba(255, 99, 132, 0.2)',
selected: 'rgba(255, 99, 132, 0.5)',
hover: 'rgba(255, 99, 132, 0.15)',
grid: 'rgba(255, 99, 132, 0.5)',
line: 'rgb(255, 99, 132)',
intersection: '#fff'
};
const countModifier = (count) => {
const val = count.toNumber ? count.toNumber() : count;
return Math.log10(val + 1) + 0.1;
};
class CustomCursor extends Component {
static propTypes = {
x: PropTypes.number,
y: PropTypes.number,
width: PropTypes.number,
height: PropTypes.number,
onClick: PropTypes.func,
getIndex: PropTypes.func,
counts: PropTypes.array,
yDomain: PropTypes.array
}
render () {
const { x, y, width, height, getIndex, counts, yDomain } = this.props;
const index = getIndex();
if (index === -1) {
return null;
}
const count = countModifier(counts[index]);
const barHeight = (count / yDomain[1]) * (y + height);
return (
<g>
<Rectangle
x={ x }
y={ 0 }
width={ width }
height={ height + y }
fill='transparent'
onClick={ this.onClick }
/>
<Rectangle
x={ x }
y={ y + (height - barHeight) }
width={ width }
height={ barHeight }
fill={ COLORS.hover }
onClick={ this.onClick }
/>
</g>
);
}
onClick = () => {
const { onClick, getIndex } = this.props;
const index = getIndex();
onClick({ index });
}
}
class CustomBar extends Component {
static propTypes = {
selected: PropTypes.number,
x: PropTypes.number,
y: PropTypes.number,
width: PropTypes.number,
height: PropTypes.number,
index: PropTypes.number,
onClick: PropTypes.func
}
render () {
const { x, y, selected, index, width, height, onClick } = this.props;
const fill = selected === index
? COLORS.selected
: COLORS.default;
const borderWidth = 0.5;
const borderColor = 'rgba(255, 255, 255, 0.5)';
return (
<g>
<Rectangle
x={ x - borderWidth }
y={ y }
width={ borderWidth }
height={ height }
fill={ borderColor }
/>
<Rectangle
x={ x + width }
y={ y }
width={ borderWidth }
height={ height }
fill={ borderColor }
/>
<Rectangle
x={ x - borderWidth }
y={ y - borderWidth }
width={ width + borderWidth * 2 }
height={ borderWidth }
fill={ borderColor }
/>
<Rectangle
x={ x }
y={ y }
width={ width }
height={ height }
fill={ fill }
onClick={ onClick }
/>
</g>
);
}
}
class CustomizedShape extends Component {
static propTypes = {
showValue: PropTypes.number.isRequired,
cx: PropTypes.number,
cy: PropTypes.number,
payload: PropTypes.object
}
render () {
const { cx, cy, showValue, payload } = this.props;
if (showValue !== payload.y) {
return null;
}
return (
<g>
<Dot
style={ { fill: 'white' } }
cx={ cx }
cy={ cy }
r={ 5 }
/>
<Dot
style={ { fill: 'rgb(255, 99, 132)' } }
cx={ cx }
cy={ cy }
r={ 3 }
/>
</g>
);
}
}
class CustomTooltip extends Component {
static propTypes = {
gasPriceHistogram: PropTypes.shape({
bucketBounds: PropTypes.array.isRequired,
counts: PropTypes.array.isRequired
}).isRequired,
type: PropTypes.string,
payload: PropTypes.array,
label: PropTypes.number,
active: PropTypes.bool
}
render () {
const { active, label, gasPriceHistogram } = this.props;
if (!active) {
return null;
}
const index = label;
const count = gasPriceHistogram.counts[index];
const minGasPrice = gasPriceHistogram.bucketBounds[index];
const maxGasPrice = gasPriceHistogram.bucketBounds[index + 1];
return (
<div>
<p className='label'>
{ count.toNumber() } transactions
with gas price set from
<span> { minGasPrice.toFormat(0) } </span>
to
<span> { maxGasPrice.toFormat(0) } </span>
</p>
</div>
);
}
}
export default class GasPriceSelector extends Component {
static propTypes = {
gasPriceHistogram: PropTypes.shape({
bucketBounds: PropTypes.array.isRequired,
counts: PropTypes.array.isRequired
}).isRequired,
onChange: PropTypes.func.isRequired,
gasPrice: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object
])
}
state = {
gasPrice: null,
sliderValue: 0.5,
selectedIndex: 0,
chartData: {
values: [],
xDomain: [],
yDomain: [],
N: 0
}
}
componentWillMount () {
this.computeCharts();
this.setGasPrice();
}
componentWillReceiveProps (nextProps) {
if (nextProps.gasPrice !== this.props.gasPrice) {
this.setGasPrice(nextProps);
}
}
componentWillUpdate (nextProps, nextState) {
if (Math.floor(nextState.sliderValue) !== Math.floor(this.state.sliderValue)) {
this.updateSelectedBarChart(nextState);
}
}
render () {
return (
<div>
{ this.renderChart() }
{ this.renderSlider() }
</div>
);
}
renderSlider () {
const { sliderValue } = this.state;
return (<div className={ styles.columns }>
<Slider
min={ 0 }
max={ 1 }
value={ sliderValue }
onChange={ this.onEditGasPriceSlider }
style={ {
flex: 1,
padding: '0 0.3em'
} }
sliderStyle={ {
marginBottom: 12
} }
/>
</div>);
}
renderChart () {
const { gasPriceHistogram } = this.props;
const { chartData, sliderValue, selectedIndex } = this.state;
if (chartData.values.length === 0) {
return null;
}
const height = 300;
const countIndex = Math.max(0, Math.min(selectedIndex, gasPriceHistogram.counts.length - 1));
const selectedCount = countModifier(gasPriceHistogram.counts[countIndex]);
return (<div className={ styles.columns }>
<div style={ { flex: 1, height } }>
<div className={ styles.chart }>
<ResponsiveContainer
height={ height }
>
<ScatterChart
margin={ { top: 0, right: 0, left: 0, bottom: 0 } }
>
<Scatter
data={ [
{ x: sliderValue, y: 0 },
{ x: sliderValue, y: selectedCount },
{ x: sliderValue, y: chartData.yDomain[1] }
] }
shape={ <CustomizedShape showValue={ selectedCount } /> }
line
isAnimationActive={ false }
/>
<XAxis
hide
height={ 0 }
dataKey='x'
domain={ [0, 1] }
/>
<YAxis
hide
width={ 0 }
dataKey='y'
domain={ chartData.yDomain }
/>
</ScatterChart>
</ResponsiveContainer>
</div>
<div className={ styles.chart }>
<ResponsiveContainer
height={ height }
>
<BarChart
data={ chartData.values }
margin={ { top: 0, right: 0, left: 0, bottom: 0 } }
barCategoryGap={ 1 }
ref='barChart'
>
<Bar
dataKey='value'
stroke={ COLORS.line }
onClick={ this.onClickGasPrice }
shape={ <CustomBar selected={ selectedIndex } onClick={ this.onClickGasPrice } /> }
/>
<Tooltip
wrapperStyle={ {
backgroundColor: 'rgba(0, 0, 0, 0.75)',
padding: '0 0.5em',
fontSize: '0.9em'
} }
cursor={ this.renderCustomCursor() }
content={ <CustomTooltip gasPriceHistogram={ gasPriceHistogram } /> }
/>
<XAxis
hide
dataKey='index'
type='category'
domain={ chartData.xDomain }
/>
<YAxis
hide
type='number'
domain={ chartData.yDomain }
/>
</BarChart>
</ResponsiveContainer>
</div>
</div>
</div>);
}
renderCustomCursor = () => {
const { gasPriceHistogram } = this.props;
const { chartData } = this.state;
return (
<CustomCursor
getIndex={ this.getBarHoverIndex }
onClick={ this.onClickGasPrice }
counts={ gasPriceHistogram.counts }
yDomain={ chartData.yDomain }
/>
);
}
getBarHoverIndex = () => {
const { barChart } = this.refs;
if (!barChart || !barChart.state) {
return -1;
}
return barChart.state.activeTooltipIndex;
}
computeChartsData () {
const { gasPriceChartData } = this.state;
const { gasPriceHistogram } = this.props;
const values = gasPriceChartData
.map((value, index) => ({ value, index }));
const N = values.length - 1;
const maxGasCounts = countModifier(
gasPriceHistogram
.counts
.reduce((max, count) => count.greaterThan(max) ? count : max, 0)
);
const xDomain = [0, N];
const yDomain = [0, maxGasCounts * 1.1];
const chartData = {
values, N,
xDomain, yDomain
};
this.setState({ chartData }, () => {
this.updateSelectedBarChart();
});
}
computeCharts (props = this.props) {
const { gasPriceHistogram } = props;
const gasPriceChartData = gasPriceHistogram
.counts
.map(count => countModifier(count));
this.setState(
{ gasPriceChartData },
() => this.computeChartsData()
);
}
updateSelectedBarChart (state = this.state) {
}
setGasPrice (props = this.props) {
const { gasPrice, gasPriceHistogram } = props;
// If no gas price yet...
if (!gasPrice) {
return this.setSliderValue(0.5);
}
const bnGasPrice = (typeof gasPrice === 'string')
? new BigNumber(gasPrice)
: gasPrice;
// If gas price hasn't changed
if (this.state.gasPrice && bnGasPrice.equals(this.state.gasPrice)) {
return;
}
const gasPrices = gasPriceHistogram.bucketBounds;
const startIndex = gasPrices
.findIndex(price => price.greaterThan(bnGasPrice)) - 1;
// gasPrice Lower than the max in histogram
if (startIndex === -1) {
return this.setSliderValue(0, bnGasPrice);
}
// gasPrice Greater than the max in histogram
if (startIndex === -2) {
return this.setSliderValue(1, bnGasPrice);
}
const priceA = gasPrices[startIndex];
const priceB = gasPrices[startIndex + 1];
const sliderValueDec = bnGasPrice
.minus(priceA)
.dividedBy(priceB.minus(priceA))
.toNumber();
const sliderValue = (startIndex + sliderValueDec) / (gasPrices.length - 1);
this.setSliderValue(sliderValue, bnGasPrice);
}
setSliderValue (value, gasPrice = this.state.gasPrice) {
const { gasPriceHistogram } = this.props;
const N = gasPriceHistogram.bucketBounds.length - 1;
const sliderValue = Math.max(0, Math.min(value, 1));
const selectedIndex = Math.floor(sliderValue * N);
this.setState({ sliderValue, gasPrice, selectedIndex });
}
onBarChartMouseUp = (event) => {
console.log(event);
}
onClickGasPrice = (bar) => {
const { index } = bar;
const ratio = (index + 0.5) / (this.state.chartData.N + 1);
this.onEditGasPriceSlider(null, ratio);
}
onEditGasPriceSlider = (event, sliderValue) => {
const { gasPriceHistogram } = this.props;
const gasPrices = gasPriceHistogram.bucketBounds;
const N = gasPrices.length - 1;
const gasPriceAIdx = Math.floor(sliderValue * N);
const gasPriceBIdx = gasPriceAIdx + 1;
if (gasPriceBIdx === N + 1) {
const gasPrice = gasPrices[gasPriceAIdx].round();
this.props.onChange(event, gasPrice);
return;
}
const gasPriceA = gasPrices[gasPriceAIdx];
const gasPriceB = gasPrices[gasPriceBIdx];
const mult = Math.round((sliderValue % 1) * 100) / 100;
const gasPrice = gasPriceA
.plus(gasPriceB.minus(gasPriceA).times(mult))
.round();
this.setSliderValue(sliderValue, gasPrice);
this.props.onChange(event, gasPrice);
}
}

View File

@@ -1,17 +0,0 @@
// Copyright 2015, 2016 Ethcore (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 './gasPriceSelector';

View File

@@ -23,7 +23,8 @@ import { bytesToHex } from '~/api/util/format';
import Contract from '~/api/contract';
import ERRORS from './errors';
import { ERROR_CODES } from '~/api/transport/error';
import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '~/util/constants';
import { DEFAULT_GAS, MAX_GAS_ESTIMATION } from '~/util/constants';
import GasPriceStore from '~/ui/GasPriceEditor/store';
const TITLES = {
transfer: 'transfer details',
@@ -48,14 +49,6 @@ export default class TransferStore {
@observable data = '';
@observable dataError = null;
@observable gas = DEFAULT_GAS;
@observable gasError = null;
@observable gasEst = '0';
@observable gasLimitError = null;
@observable gasPrice = DEFAULT_GASPRICE;
@observable gasPriceError = null;
@observable recipient = '';
@observable recipientError = ERRORS.requireRecipient;
@@ -68,11 +61,8 @@ export default class TransferStore {
@observable value = '0.0';
@observable valueError = null;
gasPriceHistogram = {};
account = null;
balance = null;
gasLimit = null;
onClose = null;
senders = null;
@@ -81,6 +71,8 @@ export default class TransferStore {
isWallet = false;
wallet = null;
gasStore = null;
@computed get steps () {
const steps = [].concat(this.extras ? STAGES_EXTRA : STAGES_BASIC);
@@ -93,7 +85,7 @@ export default class TransferStore {
@computed get isValid () {
const detailsValid = !this.recipientError && !this.valueError && !this.totalError && !this.senderError;
const extrasValid = !this.gasError && !this.gasPriceError && !this.totalError;
const extrasValid = !this.gasStore.errorGas && !this.gasStore.errorPrice && !this.totalError;
const verifyValid = !this.passwordError;
switch (this.stage) {
@@ -118,11 +110,12 @@ export default class TransferStore {
const { account, balance, gasLimit, senders, onClose, newError, sendersBalances } = props;
this.account = account;
this.balance = balance;
this.gasLimit = gasLimit;
this.onClose = onClose;
this.isWallet = account && account.wallet;
this.newError = newError;
this.gasStore = new GasPriceStore(api, gasLimit);
if (this.isWallet) {
this.wallet = props.wallet;
this.walletContract = new Contract(this.api, walletAbi);
@@ -179,26 +172,6 @@ export default class TransferStore {
}
}
@action getDefaults = () => {
Promise
.all([
this.api.parity.gasPriceHistogram(),
this.api.eth.gasPrice()
])
.then(([gasPriceHistogram, gasPrice]) => {
transaction(() => {
this.gasPrice = gasPrice.toString();
this.gasPriceDefault = gasPrice.toFormat();
this.gasPriceHistogram = gasPriceHistogram;
this.recalculate();
});
})
.catch((error) => {
console.warn('getDefaults', error);
});
}
@action onSend = () => {
this.onNext();
this.sending = true;
@@ -281,25 +254,11 @@ export default class TransferStore {
}
@action _onUpdateGas = (gas) => {
const gasError = this._validatePositiveNumber(gas);
transaction(() => {
this.gas = gas;
this.gasError = gasError;
this.recalculate();
});
this.recalculate();
}
@action _onUpdateGasPrice = (gasPrice) => {
const gasPriceError = this._validatePositiveNumber(gasPrice);
transaction(() => {
this.gasPrice = gasPrice;
this.gasPriceError = gasPriceError;
this.recalculate();
});
this.recalculate();
}
@action _onUpdateRecipient = (recipient) => {
@@ -362,7 +321,7 @@ export default class TransferStore {
@action recalculateGas = () => {
if (!this.isValid) {
this.gas = 0;
this.gasStore.setGas('0');
return this.recalculate();
}
@@ -370,28 +329,20 @@ export default class TransferStore {
.estimateGas()
.then((gasEst) => {
let gas = gasEst;
let gasLimitError = null;
if (gas.gt(DEFAULT_GAS)) {
gas = gas.mul(1.2);
}
if (gas.gte(MAX_GAS_ESTIMATION)) {
gasLimitError = ERRORS.gasException;
} else if (gas.gt(this.gasLimit)) {
gasLimitError = ERRORS.gasBlockLimit;
}
transaction(() => {
this.gas = gas.toFixed(0);
this.gasEst = gasEst.toFormat();
this.gasLimitError = gasLimitError;
this.gasStore.setEstimated(gasEst.toFixed(0));
this.gasStore.setGas(gas.toFixed(0));
this.recalculate();
});
})
.catch((error) => {
console.error('etimateGas', error);
console.warn('etimateGas', error);
this.recalculate();
});
}
@@ -411,9 +362,9 @@ export default class TransferStore {
return;
}
const { gas, gasPrice, tag, valueAll, isEth, isWallet } = this;
const { tag, valueAll, isEth, isWallet } = this;
const gasTotal = new BigNumber(gasPrice || 0).mul(new BigNumber(gas || 0));
const gasTotal = new BigNumber(this.gasStore.price || 0).mul(new BigNumber(this.gasStore.gas || 0));
const availableEth = new BigNumber(balance.tokens[0].value);
@@ -453,7 +404,7 @@ export default class TransferStore {
}
transaction(() => {
this.total = this.api.util.fromWei(totalEth).toString();
this.total = this.api.util.fromWei(totalEth).toFixed();
this.totalError = totalError;
this.value = value;
this.valueError = valueError;
@@ -522,8 +473,8 @@ export default class TransferStore {
};
if (!gas) {
options.gas = this.gas;
options.gasPrice = this.gasPrice;
options.gas = this.gasStore.gas;
options.gasPrice = this.gasStore.price;
} else {
options.gas = MAX_GAS_ESTIMATION;
}

View File

@@ -144,15 +144,6 @@
font-size: 1.2rem;
}
.chart {
position: absolute;
width: 100%;
}
.gasPriceDesc {
font-size: 0.9em;
}
.warning {
border-radius: 0.5em;
background: #f80;

View File

@@ -56,10 +56,6 @@ class Transfer extends Component {
store = new TransferStore(this.context.api, this.props);
componentDidMount () {
this.store.getDefaults();
}
render () {
const { stage, extras, steps } = this.store;
@@ -186,27 +182,20 @@ class Transfer extends Component {
}
renderExtrasPage () {
if (!this.store.gasPriceHistogram) {
if (!this.store.gasStore.histogram) {
return null;
}
const { isEth, data, dataError, gas, gasEst, gasError, gasPrice } = this.store;
const { gasPriceDefault, gasPriceError, gasPriceHistogram, total, totalError } = this.store;
const { isEth, data, dataError, total, totalError } = this.store;
return (
<Extras
isEth={ isEth }
data={ data }
dataError={ dataError }
gas={ gas }
gasEst={ gasEst }
gasError={ gasError }
gasPrice={ gasPrice }
gasPriceDefault={ gasPriceDefault }
gasPriceError={ gasPriceError }
gasPriceHistogram={ gasPriceHistogram }
total={ total }
totalError={ totalError }
gasStore={ this.store.gasStore }
onChange={ this.store.onUpdateDetails } />
);
}
@@ -263,15 +252,15 @@ class Transfer extends Component {
}
renderWarning () {
const { gasLimitError } = this.store;
const { errorEstimated } = this.store.gasStore;
if (!gasLimitError) {
if (!errorEstimated) {
return null;
}
return (
<div className={ styles.warning }>
{ gasLimitError }
{ errorEstimated }
</div>
);
}