SimpleGas.tsx
  1  import React from 'react';
  2  import { connect } from 'react-redux';
  3  import Slider, { createSliderWithTooltip } from 'rc-slider';
  4  
  5  import { gasPriceDefaults } from 'config';
  6  import translate from 'translations';
  7  import { Wei, fromWei } from 'libs/units';
  8  import { AppState } from 'features/reducers';
  9  import { getIsWeb3Node } from 'features/config';
 10  import { transactionFieldsActions, transactionNetworkSelectors } from 'features/transaction';
 11  import { gasActions, gasSelectors } from 'features/gas';
 12  import { scheduleSelectors } from 'features/schedule';
 13  import { InlineSpinner } from 'components/ui/InlineSpinner';
 14  import FeeSummary from './FeeSummary';
 15  import './SimpleGas.scss';
 16  
 17  const SliderWithTooltip = createSliderWithTooltip(Slider);
 18  
 19  interface OwnProps {
 20    gasPrice: AppState['transaction']['fields']['gasPrice'];
 21    setGasPrice: transactionFieldsActions.TInputGasPrice;
 22  
 23    inputGasPrice(rawGas: string): void;
 24  }
 25  
 26  interface StateProps {
 27    gasEstimates: AppState['gas']['estimates'];
 28    isGasEstimating: AppState['gas']['isEstimating'];
 29    noncePending: boolean;
 30    gasLimitPending: boolean;
 31    isWeb3Node: boolean;
 32    gasLimitEstimationTimedOut: boolean;
 33    scheduleGasPrice: AppState['schedule']['scheduleGasPrice'];
 34  }
 35  
 36  interface ActionProps {
 37    fetchGasEstimates: gasActions.TFetchGasEstimates;
 38  }
 39  
 40  type Props = OwnProps & StateProps & ActionProps;
 41  
 42  interface State {
 43    hasSetRecommendedGasPrice: boolean;
 44  }
 45  
 46  class SimpleGas extends React.Component<Props> {
 47    public state: State = {
 48      hasSetRecommendedGasPrice: false
 49    };
 50  
 51    public componentDidMount() {
 52      this.props.fetchGasEstimates();
 53    }
 54  
 55    public UNSAFE_componentWillReceiveProps(nextProps: Props) {
 56      if (!this.state.hasSetRecommendedGasPrice && nextProps.gasEstimates) {
 57        this.setState({ hasSetRecommendedGasPrice: true });
 58        this.props.setGasPrice(nextProps.gasEstimates.fast.toString());
 59      }
 60    }
 61  
 62    public render() {
 63      const {
 64        isGasEstimating,
 65        gasEstimates,
 66        gasPrice,
 67        gasLimitEstimationTimedOut,
 68        isWeb3Node,
 69        noncePending,
 70        gasLimitPending,
 71        scheduleGasPrice
 72      } = this.props;
 73  
 74      const bounds = {
 75        max: gasEstimates ? gasEstimates.fastest : gasPriceDefaults.max,
 76        min: gasEstimates ? gasEstimates.safeLow : gasPriceDefaults.min
 77      };
 78  
 79      /**
 80       * @desc On retrieval of gas estimates,
 81       *  the current gas price may be lower than the lowest recommended price.
 82       *  `rc-slider` will force the onChange if the value is too low, so we
 83       *  ensure it at least passes the lower boundary.
 84       *  When this occurs, the logic in `UNSAFE_componentWillReceiveProps` fires,
 85       *  and it cannot happen again from that point forward.
 86       */
 87      const actualGasPrice = Math.max(this.getGasPriceGwei(gasPrice.value), bounds.min);
 88  
 89      return (
 90        <div className="SimpleGas row form-group">
 91          <div className="SimpleGas-title">
 92            <div className="flex-wrapper">
 93              <label>{translate('CONFIRM_TX_FEE')} </label>
 94              <div className="flex-spacer" />
 95              <InlineSpinner active={noncePending || gasLimitPending} text="Calculating" />
 96            </div>
 97          </div>
 98  
 99          {gasLimitEstimationTimedOut && (
100            <div className="prompt-toggle-gas-limit">
101              <p className="small">
102                {isWeb3Node
103                  ? "Couldn't calculate gas limit, if you know what you're doing, try setting manually in Advanced settings"
104                  : "Couldn't calculate gas limit, try switching nodes"}
105              </p>
106            </div>
107          )}
108  
109          <div className="SimpleGas-input-group">
110            <div className="SimpleGas-slider">
111              <SliderWithTooltip
112                onChange={this.handleSlider}
113                min={bounds.min}
114                max={bounds.max}
115                step={bounds.min < 1 ? 0.1 : 1}
116                value={actualGasPrice}
117                tipFormatter={this.formatTooltip}
118                disabled={isGasEstimating}
119              />
120              <div className="SimpleGas-slider-labels">
121                <span>{translate('TX_FEE_SCALE_LEFT')}</span>
122                <span>{translate('TX_FEE_SCALE_RIGHT')}</span>
123              </div>
124            </div>
125            <FeeSummary
126              gasPrice={gasPrice}
127              scheduleGasPrice={scheduleGasPrice}
128              render={({ fee, usd }) => (
129                <span>
130                  {fee} {usd && <span>/ ${usd}</span>}
131                </span>
132              )}
133            />
134          </div>
135        </div>
136      );
137    }
138  
139    private handleSlider = (gasGwei: number) => {
140      this.props.inputGasPrice(gasGwei.toString());
141    };
142  
143    private getGasPriceGwei(gasPriceValue: Wei) {
144      return parseFloat(fromWei(gasPriceValue, 'gwei'));
145    }
146  
147    private formatTooltip = (gas: number) => {
148      const { gasEstimates } = this.props;
149      let recommended = '';
150      if (gasEstimates && !gasEstimates.isDefault && gas === gasEstimates.fast) {
151        recommended = '(Recommended)';
152      }
153  
154      return `${gas} Gwei ${recommended}`;
155    };
156  }
157  
158  export default connect(
159    (state: AppState): StateProps => ({
160      gasEstimates: gasSelectors.getEstimates(state),
161      isGasEstimating: gasSelectors.getIsEstimating(state),
162      noncePending: transactionNetworkSelectors.nonceRequestPending(state),
163      gasLimitPending: transactionNetworkSelectors.getGasEstimationPending(state),
164      gasLimitEstimationTimedOut: transactionNetworkSelectors.getGasLimitEstimationTimedOut(state),
165      isWeb3Node: getIsWeb3Node(state),
166      scheduleGasPrice: scheduleSelectors.getScheduleGasPrice(state)
167    }),
168    {
169      fetchGasEstimates: gasActions.fetchGasEstimates
170    }
171  )(SimpleGas);