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);