Improved Gas Estimate UX (#830)
This commit is contained in:
parent
67b2e6491c
commit
6108d08693
|
@ -9,6 +9,13 @@ export function toggleOfflineConfig(): interfaces.ToggleOfflineAction {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TToggleAutoGasLimit = typeof toggleAutoGasLimit;
|
||||||
|
export function toggleAutoGasLimit(): interfaces.ToggleAutoGasLimitAction {
|
||||||
|
return {
|
||||||
|
type: TypeKeys.CONFIG_TOGGLE_AUTO_GAS_LIMIT
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export type TChangeLanguage = typeof changeLanguage;
|
export type TChangeLanguage = typeof changeLanguage;
|
||||||
export function changeLanguage(sign: string): interfaces.ChangeLanguageAction {
|
export function changeLanguage(sign: string): interfaces.ChangeLanguageAction {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -6,6 +6,10 @@ export interface ToggleOfflineAction {
|
||||||
type: TypeKeys.CONFIG_TOGGLE_OFFLINE;
|
type: TypeKeys.CONFIG_TOGGLE_OFFLINE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ToggleAutoGasLimitAction {
|
||||||
|
type: TypeKeys.CONFIG_TOGGLE_AUTO_GAS_LIMIT;
|
||||||
|
}
|
||||||
|
|
||||||
/*** Change Language ***/
|
/*** Change Language ***/
|
||||||
export interface ChangeLanguageAction {
|
export interface ChangeLanguageAction {
|
||||||
type: TypeKeys.CONFIG_LANGUAGE_CHANGE;
|
type: TypeKeys.CONFIG_LANGUAGE_CHANGE;
|
||||||
|
@ -74,6 +78,7 @@ export type ConfigAction =
|
||||||
| ChangeNodeAction
|
| ChangeNodeAction
|
||||||
| ChangeLanguageAction
|
| ChangeLanguageAction
|
||||||
| ToggleOfflineAction
|
| ToggleOfflineAction
|
||||||
|
| ToggleAutoGasLimitAction
|
||||||
| PollOfflineStatus
|
| PollOfflineStatus
|
||||||
| ChangeNodeIntentAction
|
| ChangeNodeIntentAction
|
||||||
| AddCustomNodeAction
|
| AddCustomNodeAction
|
||||||
|
|
|
@ -3,6 +3,7 @@ export enum TypeKeys {
|
||||||
CONFIG_NODE_CHANGE = 'CONFIG_NODE_CHANGE',
|
CONFIG_NODE_CHANGE = 'CONFIG_NODE_CHANGE',
|
||||||
CONFIG_NODE_CHANGE_INTENT = 'CONFIG_NODE_CHANGE_INTENT',
|
CONFIG_NODE_CHANGE_INTENT = 'CONFIG_NODE_CHANGE_INTENT',
|
||||||
CONFIG_TOGGLE_OFFLINE = 'CONFIG_TOGGLE_OFFLINE',
|
CONFIG_TOGGLE_OFFLINE = 'CONFIG_TOGGLE_OFFLINE',
|
||||||
|
CONFIG_TOGGLE_AUTO_GAS_LIMIT = 'CONFIG_TOGGLE_AUTO_GAS_LIMIT',
|
||||||
CONFIG_POLL_OFFLINE_STATUS = 'CONFIG_POLL_OFFLINE_STATUS',
|
CONFIG_POLL_OFFLINE_STATUS = 'CONFIG_POLL_OFFLINE_STATUS',
|
||||||
CONFIG_ADD_CUSTOM_NODE = 'CONFIG_ADD_CUSTOM_NODE',
|
CONFIG_ADD_CUSTOM_NODE = 'CONFIG_ADD_CUSTOM_NODE',
|
||||||
CONFIG_REMOVE_CUSTOM_NODE = 'CONFIG_REMOVE_CUSTOM_NODE',
|
CONFIG_REMOVE_CUSTOM_NODE = 'CONFIG_REMOVE_CUSTOM_NODE',
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import {
|
import {
|
||||||
|
TypeKeys,
|
||||||
EstimateGasFailedAction,
|
EstimateGasFailedAction,
|
||||||
EstimateGasRequestedAction,
|
EstimateGasRequestedAction,
|
||||||
TypeKeys,
|
EstimateGasTimeoutAction,
|
||||||
EstimateGasSucceededAction,
|
EstimateGasSucceededAction,
|
||||||
GetFromRequestedAction,
|
GetFromRequestedAction,
|
||||||
GetFromSucceededAction,
|
GetFromSucceededAction,
|
||||||
|
@ -29,6 +30,11 @@ const estimateGasFailed = (): EstimateGasFailedAction => ({
|
||||||
type: TypeKeys.ESTIMATE_GAS_FAILED
|
type: TypeKeys.ESTIMATE_GAS_FAILED
|
||||||
});
|
});
|
||||||
|
|
||||||
|
type TEstimateGasTimedout = typeof estimateGasTimedout;
|
||||||
|
const estimateGasTimedout = (): EstimateGasTimeoutAction => ({
|
||||||
|
type: TypeKeys.ESTIMATE_GAS_TIMEDOUT
|
||||||
|
});
|
||||||
|
|
||||||
type TGetFromRequested = typeof getFromRequested;
|
type TGetFromRequested = typeof getFromRequested;
|
||||||
const getFromRequested = (): GetFromRequestedAction => ({
|
const getFromRequested = (): GetFromRequestedAction => ({
|
||||||
type: TypeKeys.GET_FROM_REQUESTED
|
type: TypeKeys.GET_FROM_REQUESTED
|
||||||
|
@ -63,6 +69,7 @@ const getNonceFailed = (): GetNonceFailedAction => ({
|
||||||
export {
|
export {
|
||||||
estimateGasRequested,
|
estimateGasRequested,
|
||||||
estimateGasFailed,
|
estimateGasFailed,
|
||||||
|
estimateGasTimedout,
|
||||||
estimateGasSucceeded,
|
estimateGasSucceeded,
|
||||||
getFromRequested,
|
getFromRequested,
|
||||||
getFromSucceeded,
|
getFromSucceeded,
|
||||||
|
@ -73,6 +80,7 @@ export {
|
||||||
TEstimateGasRequested,
|
TEstimateGasRequested,
|
||||||
TEstimateGasFailed,
|
TEstimateGasFailed,
|
||||||
TEstimateGasSucceeded,
|
TEstimateGasSucceeded,
|
||||||
|
TEstimateGasTimedout,
|
||||||
TGetFromRequested,
|
TGetFromRequested,
|
||||||
TGetFromSucceeded,
|
TGetFromSucceeded,
|
||||||
TGetNonceRequested,
|
TGetNonceRequested,
|
||||||
|
|
|
@ -11,6 +11,9 @@ interface EstimateGasSucceededAction {
|
||||||
interface EstimateGasFailedAction {
|
interface EstimateGasFailedAction {
|
||||||
type: TypeKeys.ESTIMATE_GAS_FAILED;
|
type: TypeKeys.ESTIMATE_GAS_FAILED;
|
||||||
}
|
}
|
||||||
|
interface EstimateGasTimeoutAction {
|
||||||
|
type: TypeKeys.ESTIMATE_GAS_TIMEDOUT;
|
||||||
|
}
|
||||||
interface GetFromRequestedAction {
|
interface GetFromRequestedAction {
|
||||||
type: TypeKeys.GET_FROM_REQUESTED;
|
type: TypeKeys.GET_FROM_REQUESTED;
|
||||||
}
|
}
|
||||||
|
@ -36,6 +39,7 @@ type NetworkAction =
|
||||||
| EstimateGasFailedAction
|
| EstimateGasFailedAction
|
||||||
| EstimateGasRequestedAction
|
| EstimateGasRequestedAction
|
||||||
| EstimateGasSucceededAction
|
| EstimateGasSucceededAction
|
||||||
|
| EstimateGasTimeoutAction
|
||||||
| GetFromRequestedAction
|
| GetFromRequestedAction
|
||||||
| GetFromSucceededAction
|
| GetFromSucceededAction
|
||||||
| GetFromFailedAction
|
| GetFromFailedAction
|
||||||
|
@ -47,6 +51,7 @@ export {
|
||||||
EstimateGasRequestedAction,
|
EstimateGasRequestedAction,
|
||||||
EstimateGasSucceededAction,
|
EstimateGasSucceededAction,
|
||||||
EstimateGasFailedAction,
|
EstimateGasFailedAction,
|
||||||
|
EstimateGasTimeoutAction,
|
||||||
GetFromRequestedAction,
|
GetFromRequestedAction,
|
||||||
GetFromSucceededAction,
|
GetFromSucceededAction,
|
||||||
GetFromFailedAction,
|
GetFromFailedAction,
|
||||||
|
|
|
@ -2,6 +2,7 @@ export enum TypeKeys {
|
||||||
ESTIMATE_GAS_REQUESTED = 'ESTIMATE_GAS_REQUESTED',
|
ESTIMATE_GAS_REQUESTED = 'ESTIMATE_GAS_REQUESTED',
|
||||||
ESTIMATE_GAS_SUCCEEDED = 'ESTIMATE_GAS_SUCCEEDED',
|
ESTIMATE_GAS_SUCCEEDED = 'ESTIMATE_GAS_SUCCEEDED',
|
||||||
ESTIMATE_GAS_FAILED = 'ESTIMATE_GAS_FAILED',
|
ESTIMATE_GAS_FAILED = 'ESTIMATE_GAS_FAILED',
|
||||||
|
ESTIMATE_GAS_TIMEDOUT = 'ESTIMATE_GAS_TIMEDOUT',
|
||||||
|
|
||||||
GET_FROM_REQUESTED = 'GET_FROM_REQUESTED',
|
GET_FROM_REQUESTED = 'GET_FROM_REQUESTED',
|
||||||
GET_FROM_SUCCEEDED = 'GET_FROM_SUCCEEDED',
|
GET_FROM_SUCCEEDED = 'GET_FROM_SUCCEEDED',
|
||||||
|
|
|
@ -1,22 +1,13 @@
|
||||||
import { DataFieldFactory } from './DataFieldFactory';
|
import { DataFieldFactory } from './DataFieldFactory';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Expandable, ExpandHandler } from 'components/ui';
|
|
||||||
import translate from 'translations';
|
import translate from 'translations';
|
||||||
import { donationAddressMap } from 'config/data';
|
import { donationAddressMap } from 'config/data';
|
||||||
|
|
||||||
const expander = (expandHandler: ExpandHandler) => (
|
|
||||||
<a onClick={expandHandler}>
|
|
||||||
<p className="strong">{translate('TRANS_advanced')}</p>
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const DataField: React.SFC<{}> = () => (
|
export const DataField: React.SFC<{}> = () => (
|
||||||
<DataFieldFactory
|
<DataFieldFactory
|
||||||
withProps={({ data: { raw }, dataExists, onChange, readOnly }) => (
|
withProps={({ data: { raw }, dataExists, onChange, readOnly }) => (
|
||||||
<Expandable expandLabel={expander}>
|
<>
|
||||||
<div className="form-group">
|
<label>{translate('OFFLINE_Step2_Label_6')}</label>
|
||||||
<label>{translate('TRANS_data')}</label>
|
|
||||||
|
|
||||||
<input
|
<input
|
||||||
className={`form-control ${dataExists ? 'is-valid' : 'is-invalid'}`}
|
className={`form-control ${dataExists ? 'is-valid' : 'is-invalid'}`}
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -25,8 +16,7 @@ export const DataField: React.SFC<{}> = () => (
|
||||||
readOnly={!!readOnly}
|
readOnly={!!readOnly}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</>
|
||||||
</Expandable>
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,20 +1,45 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { GasLimitFieldFactory } from './GasLimitFieldFactory';
|
import { GasLimitFieldFactory } from './GasLimitFieldFactory';
|
||||||
import translate from 'translations';
|
import translate from 'translations';
|
||||||
|
import { CSSTransition } from 'react-transition-group';
|
||||||
|
import { Spinner } from 'components/ui';
|
||||||
|
|
||||||
export const GasLimitField: React.SFC<{}> = () => (
|
interface Props {
|
||||||
|
includeLabel: boolean;
|
||||||
|
onlyIncludeLoader: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GaslimitLoading: React.SFC<{ gasEstimationPending: boolean }> = ({
|
||||||
|
gasEstimationPending
|
||||||
|
}) => (
|
||||||
|
<CSSTransition in={gasEstimationPending} timeout={300} classNames="fade">
|
||||||
|
<div className={`SimpleGas-estimating small ${gasEstimationPending ? 'active' : ''}`}>
|
||||||
|
Calculating gas limit
|
||||||
|
<Spinner />
|
||||||
|
</div>
|
||||||
|
</CSSTransition>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const GasLimitField: React.SFC<Props> = ({ includeLabel, onlyIncludeLoader }) => (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<label>{translate('TRANS_gas')} </label>
|
{includeLabel ? <label>{translate('TRANS_gas')} </label> : null}
|
||||||
|
|
||||||
<GasLimitFieldFactory
|
<GasLimitFieldFactory
|
||||||
withProps={({ gasLimit: { raw, value }, onChange, readOnly }) => (
|
withProps={({ gasLimit: { raw, value }, onChange, readOnly, gasEstimationPending }) => (
|
||||||
|
<>
|
||||||
|
<GaslimitLoading gasEstimationPending={gasEstimationPending} />
|
||||||
|
{onlyIncludeLoader ? null : (
|
||||||
<input
|
<input
|
||||||
className={`form-control ${!!value ? 'is-valid' : 'is-invalid'}`}
|
className={`form-control ${!!value ? 'is-valid' : 'is-invalid'}`}
|
||||||
type="text"
|
type="number"
|
||||||
|
placeholder="e.g. 21000"
|
||||||
readOnly={!!readOnly}
|
readOnly={!!readOnly}
|
||||||
value={raw}
|
value={raw}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
|
|
|
@ -10,6 +10,7 @@ const defaultGasLimit = '21000';
|
||||||
export interface CallBackProps {
|
export interface CallBackProps {
|
||||||
readOnly: boolean;
|
readOnly: boolean;
|
||||||
gasLimit: AppState['transaction']['fields']['gasLimit'];
|
gasLimit: AppState['transaction']['fields']['gasLimit'];
|
||||||
|
gasEstimationPending: boolean;
|
||||||
onChange(value: React.FormEvent<HTMLInputElement>): void;
|
onChange(value: React.FormEvent<HTMLInputElement>): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,14 @@ import React, { Component } from 'react';
|
||||||
import { Query } from 'components/renderCbs';
|
import { Query } from 'components/renderCbs';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { AppState } from 'reducers';
|
import { AppState } from 'reducers';
|
||||||
import { getGasLimit } from 'selectors/transaction';
|
import { getGasLimit, getGasEstimationPending } from 'selectors/transaction';
|
||||||
import { CallBackProps } from 'components/GasLimitFieldFactory';
|
import { CallBackProps } from 'components/GasLimitFieldFactory';
|
||||||
|
import { getAutoGasLimitEnabled } from 'selectors/config';
|
||||||
|
|
||||||
interface StateProps {
|
interface StateProps {
|
||||||
gasLimit: AppState['transaction']['fields']['gasLimit'];
|
gasLimit: AppState['transaction']['fields']['gasLimit'];
|
||||||
|
gasEstimationPending: boolean;
|
||||||
|
autoGasLimitEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OwnProps {
|
interface OwnProps {
|
||||||
|
@ -17,18 +20,24 @@ interface OwnProps {
|
||||||
type Props = StateProps & OwnProps;
|
type Props = StateProps & OwnProps;
|
||||||
class GasLimitInputClass extends Component<Props> {
|
class GasLimitInputClass extends Component<Props> {
|
||||||
public render() {
|
public render() {
|
||||||
const { gasLimit, onChange } = this.props;
|
const { gasLimit, onChange, gasEstimationPending, autoGasLimitEnabled } = this.props;
|
||||||
return (
|
return (
|
||||||
<Query
|
<Query
|
||||||
params={['readOnly']}
|
params={['readOnly']}
|
||||||
withQuery={({ readOnly }) =>
|
withQuery={({ readOnly }) =>
|
||||||
this.props.withProps({ gasLimit, onChange, readOnly: !!readOnly })
|
this.props.withProps({
|
||||||
|
gasLimit,
|
||||||
|
onChange,
|
||||||
|
readOnly: !!(readOnly || autoGasLimitEnabled),
|
||||||
|
gasEstimationPending
|
||||||
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export const GasLimitInput = connect((state: AppState) => ({
|
||||||
export const GasLimitInput = connect((state: AppState) => ({ gasLimit: getGasLimit(state) }))(
|
gasLimit: getGasLimit(state),
|
||||||
GasLimitInputClass
|
gasEstimationPending: getGasEstimationPending(state),
|
||||||
);
|
autoGasLimitEnabled: getAutoGasLimitEnabled(state)
|
||||||
|
}))(GasLimitInputClass);
|
||||||
|
|
|
@ -1,37 +1,32 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { translateRaw } from 'translations';
|
import { translateRaw } from 'translations';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import { inputGasPrice, TInputGasPrice } from 'actions/transaction';
|
||||||
inputGasPrice,
|
|
||||||
TInputGasPrice,
|
|
||||||
inputGasLimit,
|
|
||||||
TInputGasLimit,
|
|
||||||
inputNonce,
|
|
||||||
TInputNonce
|
|
||||||
} from 'actions/transaction';
|
|
||||||
import { fetchCCRates, TFetchCCRates } from 'actions/rates';
|
import { fetchCCRates, TFetchCCRates } from 'actions/rates';
|
||||||
import { getNetworkConfig } from 'selectors/config';
|
import { getNetworkConfig, getOffline } from 'selectors/config';
|
||||||
import { AppState } from 'reducers';
|
import { AppState } from 'reducers';
|
||||||
import SimpleGas from './components/SimpleGas';
|
import SimpleGas from './components/SimpleGas';
|
||||||
import AdvancedGas from './components/AdvancedGas';
|
import AdvancedGas from './components/AdvancedGas';
|
||||||
import './GasSlider.scss';
|
import './GasSlider.scss';
|
||||||
|
import { getGasPrice } from 'selectors/transaction';
|
||||||
|
|
||||||
interface Props {
|
interface StateProps {
|
||||||
// Component configuration
|
|
||||||
disableAdvanced?: boolean;
|
|
||||||
// Data
|
|
||||||
gasPrice: AppState['transaction']['fields']['gasPrice'];
|
gasPrice: AppState['transaction']['fields']['gasPrice'];
|
||||||
gasLimit: AppState['transaction']['fields']['gasLimit'];
|
|
||||||
nonce: AppState['transaction']['fields']['nonce'];
|
|
||||||
offline: AppState['config']['offline'];
|
offline: AppState['config']['offline'];
|
||||||
network: AppState['config']['network'];
|
network: AppState['config']['network'];
|
||||||
// Actions
|
}
|
||||||
|
|
||||||
|
interface DispatchProps {
|
||||||
inputGasPrice: TInputGasPrice;
|
inputGasPrice: TInputGasPrice;
|
||||||
inputGasLimit: TInputGasLimit;
|
|
||||||
inputNonce: TInputNonce;
|
|
||||||
fetchCCRates: TFetchCCRates;
|
fetchCCRates: TFetchCCRates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface OwnProps {
|
||||||
|
disableAdvanced?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = DispatchProps & OwnProps & StateProps;
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
showAdvanced: boolean;
|
showAdvanced: boolean;
|
||||||
}
|
}
|
||||||
|
@ -54,22 +49,15 @@ class GasSlider extends React.Component<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const { gasPrice, gasLimit, nonce, offline, disableAdvanced } = this.props;
|
const { offline, disableAdvanced, gasPrice } = this.props;
|
||||||
const showAdvanced = (this.state.showAdvanced || offline) && !disableAdvanced;
|
const showAdvanced = (this.state.showAdvanced || offline) && !disableAdvanced;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="GasSlider">
|
<div className="GasSlider">
|
||||||
{showAdvanced ? (
|
{showAdvanced ? (
|
||||||
<AdvancedGas
|
<AdvancedGas gasPrice={gasPrice} inputGasPrice={this.props.inputGasPrice} />
|
||||||
gasPrice={gasPrice.raw}
|
|
||||||
gasLimit={gasLimit.raw}
|
|
||||||
nonce={nonce.raw}
|
|
||||||
changeGasPrice={this.props.inputGasPrice}
|
|
||||||
changeGasLimit={this.props.inputGasLimit}
|
|
||||||
changeNonce={this.props.inputNonce}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<SimpleGas gasPrice={gasPrice.raw} changeGasPrice={this.props.inputGasPrice} />
|
<SimpleGas gasPrice={gasPrice} inputGasPrice={this.props.inputGasPrice} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!offline &&
|
{!offline &&
|
||||||
|
@ -79,7 +67,7 @@ class GasSlider extends React.Component<Props, State> {
|
||||||
<strong>
|
<strong>
|
||||||
{showAdvanced
|
{showAdvanced
|
||||||
? `- ${translateRaw('Back to simple')}`
|
? `- ${translateRaw('Back to simple')}`
|
||||||
: `+ ${translateRaw('Advanced: Data, Gas Price, Gas Limit')}`}
|
: `+ ${translateRaw('Advanced Settings')}`}
|
||||||
</strong>
|
</strong>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -93,19 +81,15 @@ class GasSlider extends React.Component<Props, State> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps(state: AppState) {
|
function mapStateToProps(state: AppState): StateProps {
|
||||||
return {
|
return {
|
||||||
gasPrice: state.transaction.fields.gasPrice,
|
gasPrice: getGasPrice(state),
|
||||||
gasLimit: state.transaction.fields.gasLimit,
|
offline: getOffline(state),
|
||||||
nonce: state.transaction.fields.nonce,
|
|
||||||
offline: state.config.offline,
|
|
||||||
network: getNetworkConfig(state)
|
network: getNetworkConfig(state)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, {
|
export default connect(mapStateToProps, {
|
||||||
inputGasPrice,
|
inputGasPrice,
|
||||||
inputGasLimit,
|
|
||||||
inputNonce,
|
|
||||||
fetchCCRates
|
fetchCCRates
|
||||||
})(GasSlider);
|
})(GasSlider);
|
||||||
|
|
|
@ -1,4 +1,31 @@
|
||||||
.AdvancedGas {
|
.AdvancedGas {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
.checkbox {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: fit-content;
|
||||||
|
input[type='checkbox'] {
|
||||||
|
position: initial;
|
||||||
|
margin: 0;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-gasLimit {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: baseline;
|
||||||
|
.flex-spacer {
|
||||||
|
flex-grow: 2;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,71 +1,69 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import translate from 'translations';
|
import translate from 'translations';
|
||||||
import { DataFieldFactory } from 'components/DataFieldFactory';
|
|
||||||
import FeeSummary from './FeeSummary';
|
import FeeSummary from './FeeSummary';
|
||||||
import './AdvancedGas.scss';
|
import './AdvancedGas.scss';
|
||||||
|
import { TToggleAutoGasLimit, toggleAutoGasLimit } from 'actions/config';
|
||||||
|
import { AppState } from 'reducers';
|
||||||
|
import { TInputGasPrice } from 'actions/transaction';
|
||||||
|
import { NonceField, GasLimitField, DataField } from 'components';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { getAutoGasLimitEnabled } from 'selectors/config';
|
||||||
|
|
||||||
interface Props {
|
interface OwnProps {
|
||||||
gasPrice: string;
|
inputGasPrice: TInputGasPrice;
|
||||||
gasLimit: string;
|
gasPrice: AppState['transaction']['fields']['gasPrice'];
|
||||||
nonce: string;
|
|
||||||
changeGasPrice(gwei: string): void;
|
|
||||||
changeGasLimit(wei: string): void;
|
|
||||||
changeNonce(nonce: string): void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class AdvancedGas extends React.Component<Props> {
|
interface StateProps {
|
||||||
public render() {
|
autoGasLimitEnabled: AppState['config']['autoGasLimit'];
|
||||||
// Can't shadow var names for data & fee summary
|
}
|
||||||
const vals = this.props;
|
|
||||||
|
|
||||||
|
interface DispatchProps {
|
||||||
|
toggleAutoGasLimit: TToggleAutoGasLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = OwnProps & StateProps & DispatchProps;
|
||||||
|
|
||||||
|
class AdvancedGas extends React.Component<Props> {
|
||||||
|
public render() {
|
||||||
|
const { autoGasLimitEnabled, gasPrice } = this.props;
|
||||||
return (
|
return (
|
||||||
<div className="AdvancedGas row form-group">
|
<div className="AdvancedGas row form-group">
|
||||||
|
<div className="col-md-12">
|
||||||
|
<label className="checkbox">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
defaultChecked={autoGasLimitEnabled}
|
||||||
|
onChange={this.handleToggleAutoGasLimit}
|
||||||
|
/>
|
||||||
|
<span>Automatically Calculate Gas Limit</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="col-md-4 col-sm-6 col-xs-12">
|
<div className="col-md-4 col-sm-6 col-xs-12">
|
||||||
<label>{translate('OFFLINE_Step2_Label_3')} (gwei)</label>
|
<label>{translate('OFFLINE_Step2_Label_3')} (gwei)</label>
|
||||||
<input
|
<input
|
||||||
className={classnames('form-control', !vals.gasPrice && 'is-invalid')}
|
className={classnames('form-control', { 'is-invalid': !gasPrice.value })}
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="e.g. 40"
|
placeholder="e.g. 40"
|
||||||
value={vals.gasPrice}
|
value={gasPrice.raw}
|
||||||
onChange={this.handleGasPriceChange}
|
onChange={this.handleGasPriceChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-md-4 col-sm-6 col-xs-12">
|
<div className="col-md-4 col-sm-6 col-xs-12 AdvancedGas-gasLimit">
|
||||||
<label>{translate('OFFLINE_Step2_Label_4')}</label>
|
<label>{translate('OFFLINE_Step2_Label_4')}</label>
|
||||||
<input
|
<div className="SimpleGas-flex-spacer" />
|
||||||
className={classnames('form-control', !vals.gasLimit && 'is-invalid')}
|
<GasLimitField includeLabel={false} onlyIncludeLoader={false} />
|
||||||
type="number"
|
|
||||||
placeholder="e.g. 21000"
|
|
||||||
value={vals.gasLimit}
|
|
||||||
onChange={this.handleGasLimitChange}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-md-4 col-sm-12">
|
<div className="col-md-4 col-sm-12">
|
||||||
<label>{translate('OFFLINE_Step2_Label_5')}</label>
|
<NonceField alwaysDisplay={true} />
|
||||||
<input
|
|
||||||
className={classnames('form-control', !vals.nonce && 'is-invalid')}
|
|
||||||
type="number"
|
|
||||||
placeholder="e.g. 7"
|
|
||||||
value={vals.nonce}
|
|
||||||
onChange={this.handleNonceChange}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-md-12">
|
<div className="col-md-12">
|
||||||
<label>{translate('OFFLINE_Step2_Label_6')}</label>
|
<DataField />
|
||||||
<DataFieldFactory
|
|
||||||
withProps={({ data, onChange }) => (
|
|
||||||
<input
|
|
||||||
className="form-control"
|
|
||||||
value={data.raw}
|
|
||||||
onChange={onChange}
|
|
||||||
placeholder="0x7cB57B5A..."
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-sm-12">
|
<div className="col-sm-12">
|
||||||
|
@ -82,14 +80,15 @@ export default class AdvancedGas extends React.Component<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleGasPriceChange = (ev: React.FormEvent<HTMLInputElement>) => {
|
private handleGasPriceChange = (ev: React.FormEvent<HTMLInputElement>) => {
|
||||||
this.props.changeGasPrice(ev.currentTarget.value);
|
this.props.inputGasPrice(ev.currentTarget.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
private handleGasLimitChange = (ev: React.FormEvent<HTMLInputElement>) => {
|
private handleToggleAutoGasLimit = (_: React.FormEvent<HTMLInputElement>) => {
|
||||||
this.props.changeGasLimit(ev.currentTarget.value);
|
this.props.toggleAutoGasLimit();
|
||||||
};
|
|
||||||
|
|
||||||
private handleNonceChange = (ev: React.FormEvent<HTMLInputElement>) => {
|
|
||||||
this.props.changeNonce(ev.currentTarget.value);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
(state: AppState) => ({ autoGasLimitEnabled: getAutoGasLimitEnabled(state) }),
|
||||||
|
{ toggleAutoGasLimit }
|
||||||
|
)(AdvancedGas);
|
||||||
|
|
|
@ -4,8 +4,24 @@
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
|
||||||
&-label {
|
&-flex-spacer {
|
||||||
display: block;
|
flex-grow: 2;
|
||||||
|
}
|
||||||
|
&-title {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
&-estimating {
|
||||||
|
color: rgba(51, 51, 51, 0.7);
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
font-weight: 400;
|
||||||
|
opacity: 0;
|
||||||
|
&.active {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.Spinner {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-slider {
|
&-slider {
|
||||||
|
@ -34,3 +50,26 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fade {
|
||||||
|
&-enter,
|
||||||
|
&-exit {
|
||||||
|
transition: opacity 300ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-enter {
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
&-active {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-exit {
|
||||||
|
opacity: 1;
|
||||||
|
|
||||||
|
&-active {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,30 +3,55 @@ import Slider from 'rc-slider';
|
||||||
import translate from 'translations';
|
import translate from 'translations';
|
||||||
import { gasPriceDefaults } from 'config/data';
|
import { gasPriceDefaults } from 'config/data';
|
||||||
import FeeSummary from './FeeSummary';
|
import FeeSummary from './FeeSummary';
|
||||||
|
import { TInputGasPrice } from 'actions/transaction';
|
||||||
import './SimpleGas.scss';
|
import './SimpleGas.scss';
|
||||||
|
import { AppState } from 'reducers';
|
||||||
|
import { getGasLimitEstimationTimedOut } from 'selectors/transaction';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { GasLimitField } from 'components/GasLimitField';
|
||||||
|
import { getIsWeb3Node } from 'selectors/config';
|
||||||
|
|
||||||
interface Props {
|
interface OwnProps {
|
||||||
gasPrice: string;
|
gasPrice: AppState['transaction']['fields']['gasPrice'];
|
||||||
changeGasPrice(gwei: string): void;
|
inputGasPrice: TInputGasPrice;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class SimpleGas extends React.Component<Props> {
|
interface StateProps {
|
||||||
|
isWeb3Node: boolean;
|
||||||
|
gasLimitEstimationTimedOut: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = OwnProps & StateProps;
|
||||||
|
|
||||||
|
class SimpleGas extends React.Component<Props> {
|
||||||
public render() {
|
public render() {
|
||||||
const { gasPrice } = this.props;
|
const { gasPrice, gasLimitEstimationTimedOut, isWeb3Node } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="SimpleGas row form-group">
|
<div className="SimpleGas row form-group">
|
||||||
<div className="col-md-12">
|
<div className="col-md-12 SimpleGas-title">
|
||||||
<label className="SimpleGas-label">{translate('Transaction Fee')}</label>
|
<label className="SimpleGas-label">{translate('Transaction Fee')}</label>
|
||||||
|
<div className="SimpleGas-flex-spacer" />
|
||||||
|
<GasLimitField includeLabel={false} onlyIncludeLoader={true} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{gasLimitEstimationTimedOut && (
|
||||||
|
<div className="col-md-12 prompt-toggle-gas-limit">
|
||||||
|
<p className="small">
|
||||||
|
{isWeb3Node
|
||||||
|
? "Couldn't calculate gas limit, if you know what your doing, try setting manually in Advanced settings"
|
||||||
|
: "Couldn't calculate gas limit, try switching nodes"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="col-md-8 col-sm-12">
|
<div className="col-md-8 col-sm-12">
|
||||||
<div className="SimpleGas-slider">
|
<div className="SimpleGas-slider">
|
||||||
<Slider
|
<Slider
|
||||||
onChange={this.handleSlider}
|
onChange={this.handleSlider}
|
||||||
min={gasPriceDefaults.gasPriceMinGwei}
|
min={gasPriceDefaults.gasPriceMinGwei}
|
||||||
max={gasPriceDefaults.gasPriceMaxGwei}
|
max={gasPriceDefaults.gasPriceMaxGwei}
|
||||||
value={parseFloat(gasPrice)}
|
value={parseFloat(gasPrice.raw)}
|
||||||
/>
|
/>
|
||||||
<div className="SimpleGas-slider-labels">
|
<div className="SimpleGas-slider-labels">
|
||||||
<span>{translate('Cheap')}</span>
|
<span>{translate('Cheap')}</span>
|
||||||
|
@ -49,6 +74,10 @@ export default class SimpleGas extends React.Component<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleSlider = (gasGwei: number) => {
|
private handleSlider = (gasGwei: number) => {
|
||||||
this.props.changeGasPrice(gasGwei.toString());
|
this.props.inputGasPrice(gasGwei.toString());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
export default connect((state: AppState) => ({
|
||||||
|
gasLimitEstimationTimedOut: getGasLimitEstimationTimedOut(state),
|
||||||
|
isWeb3Node: getIsWeb3Node(state)
|
||||||
|
}))(SimpleGas);
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { NonceFieldFactory } from 'components/NonceFieldFactory';
|
||||||
|
import Help from 'components/ui/Help';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
alwaysDisplay: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nonceHelp = (
|
||||||
|
<Help
|
||||||
|
size={'x1'}
|
||||||
|
link={'https://myetherwallet.github.io/knowledge-base/transactions/what-is-nonce.html'}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const NonceField: React.SFC<Props> = ({ alwaysDisplay }) => (
|
||||||
|
<NonceFieldFactory
|
||||||
|
withProps={({ nonce: { raw, value }, onChange, readOnly, shouldDisplay }) => {
|
||||||
|
const content = (
|
||||||
|
<>
|
||||||
|
<label>Nonce</label>
|
||||||
|
{nonceHelp}
|
||||||
|
|
||||||
|
<input
|
||||||
|
className={`form-control ${!!value ? 'is-valid' : 'is-invalid'}`}
|
||||||
|
type="number"
|
||||||
|
placeholder="e.g. 7"
|
||||||
|
value={raw}
|
||||||
|
readOnly={readOnly}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return alwaysDisplay || shouldDisplay ? content : null;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
|
@ -1,23 +0,0 @@
|
||||||
import { NonceInput } from './NonceInput';
|
|
||||||
import { inputNonce, TInputNonce } from 'actions/transaction';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
interface DispatchProps {
|
|
||||||
inputNonce: TInputNonce;
|
|
||||||
}
|
|
||||||
|
|
||||||
class NonceFieldClass extends Component<DispatchProps> {
|
|
||||||
public render() {
|
|
||||||
return <NonceInput onChange={this.setNonce} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
private setNonce = (ev: React.FormEvent<HTMLInputElement>) => {
|
|
||||||
const { value } = ev.currentTarget;
|
|
||||||
this.props.inputNonce(value);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const NonceField = connect(null, {
|
|
||||||
inputNonce
|
|
||||||
})(NonceFieldClass);
|
|
|
@ -1,54 +0,0 @@
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { Query } from 'components/renderCbs';
|
|
||||||
import Help from 'components/ui/Help';
|
|
||||||
import { getNonce, nonceRequestFailed } from 'selectors/transaction';
|
|
||||||
import { getOffline } from 'selectors/config';
|
|
||||||
import { AppState } from 'reducers';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
const nonceHelp = (
|
|
||||||
<Help
|
|
||||||
size={'x1'}
|
|
||||||
link={'https://myetherwallet.github.io/knowledge-base/transactions/what-is-nonce.html'}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
interface OwnProps {
|
|
||||||
onChange(ev: React.FormEvent<HTMLInputElement>): void;
|
|
||||||
}
|
|
||||||
interface StateProps {
|
|
||||||
shouldDisplay: boolean;
|
|
||||||
nonce: AppState['transaction']['fields']['nonce'];
|
|
||||||
}
|
|
||||||
type Props = OwnProps & StateProps;
|
|
||||||
|
|
||||||
class NonceInputClass extends Component<Props> {
|
|
||||||
public render() {
|
|
||||||
const { nonce: { raw, value }, onChange, shouldDisplay } = this.props;
|
|
||||||
const content = (
|
|
||||||
<React.Fragment>
|
|
||||||
<label>Nonce</label>
|
|
||||||
{nonceHelp}
|
|
||||||
|
|
||||||
<Query
|
|
||||||
params={['readOnly']}
|
|
||||||
withQuery={({ readOnly }) => (
|
|
||||||
<input
|
|
||||||
className={`form-control ${!!value ? 'is-valid' : 'is-invalid'}`}
|
|
||||||
type="text"
|
|
||||||
value={raw}
|
|
||||||
readOnly={!!readOnly}
|
|
||||||
onChange={onChange}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
|
|
||||||
return shouldDisplay ? content : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const NonceInput = connect((state: AppState) => ({
|
|
||||||
shouldDisplay: getOffline(state) || nonceRequestFailed(state),
|
|
||||||
nonce: getNonce(state)
|
|
||||||
}))(NonceInputClass);
|
|
|
@ -1 +0,0 @@
|
||||||
export * from './NonceField';
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { NonceInputFactory } from './NonceInputFactory';
|
||||||
|
import { inputNonce, TInputNonce } from 'actions/transaction';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { AppState } from 'reducers';
|
||||||
|
|
||||||
|
export interface CallbackProps {
|
||||||
|
nonce: AppState['transaction']['fields']['nonce'];
|
||||||
|
readOnly: boolean;
|
||||||
|
shouldDisplay: boolean;
|
||||||
|
onChange(ev: React.FormEvent<HTMLInputElement>): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DispatchProps {
|
||||||
|
inputNonce: TInputNonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OwnProps {
|
||||||
|
withProps(props: CallbackProps): React.ReactElement<any> | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = OwnProps & DispatchProps;
|
||||||
|
|
||||||
|
class NonceFieldClass extends Component<Props> {
|
||||||
|
public render() {
|
||||||
|
return <NonceInputFactory onChange={this.setNonce} withProps={this.props.withProps} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
private setNonce = (ev: React.FormEvent<HTMLInputElement>) => {
|
||||||
|
const { value } = ev.currentTarget;
|
||||||
|
this.props.inputNonce(value);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NonceFieldFactory = connect(null, {
|
||||||
|
inputNonce
|
||||||
|
})(NonceFieldClass);
|
|
@ -0,0 +1,39 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { Query } from 'components/renderCbs';
|
||||||
|
import { getNonce, nonceRequestFailed } from 'selectors/transaction';
|
||||||
|
import { getOffline } from 'selectors/config';
|
||||||
|
import { AppState } from 'reducers';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { CallbackProps } from 'components/NonceFieldFactory';
|
||||||
|
|
||||||
|
interface OwnProps {
|
||||||
|
onChange(ev: React.FormEvent<HTMLInputElement>): void;
|
||||||
|
withProps(props: CallbackProps): React.ReactElement<any> | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StateProps {
|
||||||
|
shouldDisplay: boolean;
|
||||||
|
nonce: AppState['transaction']['fields']['nonce'];
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = OwnProps & StateProps;
|
||||||
|
|
||||||
|
class NonceInputFactoryClass extends Component<Props> {
|
||||||
|
public render() {
|
||||||
|
const { nonce, onChange, shouldDisplay, withProps } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Query
|
||||||
|
params={['readOnly']}
|
||||||
|
withQuery={({ readOnly }) =>
|
||||||
|
withProps({ nonce, onChange, readOnly: !!readOnly, shouldDisplay })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NonceInputFactory = connect((state: AppState) => ({
|
||||||
|
shouldDisplay: getOffline(state) || nonceRequestFailed(state),
|
||||||
|
nonce: getNonce(state)
|
||||||
|
}))(NonceInputFactoryClass);
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './NonceFieldFactory';
|
|
@ -64,7 +64,7 @@ class DeployClass extends Component<DispatchProps> {
|
||||||
</label>
|
</label>
|
||||||
<div className="row form-group">
|
<div className="row form-group">
|
||||||
<div className="col-xs-11">
|
<div className="col-xs-11">
|
||||||
<NonceField />
|
<NonceField alwaysDisplay={false} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="row form-group">
|
<div className="row form-group">
|
||||||
|
|
|
@ -14,7 +14,7 @@ export class Fields extends Component<OwnProps> {
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<GasLimitField />
|
<GasLimitField />
|
||||||
<AmountField />
|
<AmountField />
|
||||||
<NonceField />
|
<NonceField alwaysDisplay={false} />
|
||||||
{this.props.button}
|
{this.props.button}
|
||||||
<SigningStatus />
|
<SigningStatus />
|
||||||
<SendButton />
|
<SendButton />
|
||||||
|
|
|
@ -106,7 +106,7 @@ class RequestPayment extends React.Component<Props, {}> {
|
||||||
|
|
||||||
<div className="row form-group">
|
<div className="row form-group">
|
||||||
<div className="col-xs-11">
|
<div className="col-xs-11">
|
||||||
<GasLimitField />
|
<GasLimitField includeLabel={true} onlyIncludeLoader={false} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -46,10 +46,15 @@ export default class RpcNode implements INode {
|
||||||
}
|
}
|
||||||
|
|
||||||
public estimateGas(transaction: Partial<IHexStrTransaction>): Promise<Wei> {
|
public estimateGas(transaction: Partial<IHexStrTransaction>): Promise<Wei> {
|
||||||
|
// Timeout after 10 seconds
|
||||||
|
|
||||||
return this.client
|
return this.client
|
||||||
.call(this.requests.estimateGas(transaction))
|
.call(this.requests.estimateGas(transaction))
|
||||||
.then(isValidEstimateGas)
|
.then(isValidEstimateGas)
|
||||||
.then(({ result }) => Wei(result));
|
.then(({ result }) => Wei(result))
|
||||||
|
.catch(error => {
|
||||||
|
throw new Error(error.message);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTokenBalance(
|
public getTokenBalance(
|
||||||
|
|
|
@ -28,6 +28,7 @@ export interface State {
|
||||||
network: NetworkConfig;
|
network: NetworkConfig;
|
||||||
isChangingNode: boolean;
|
isChangingNode: boolean;
|
||||||
offline: boolean;
|
offline: boolean;
|
||||||
|
autoGasLimit: boolean;
|
||||||
customNodes: CustomNodeConfig[];
|
customNodes: CustomNodeConfig[];
|
||||||
customNetworks: CustomNetworkConfig[];
|
customNetworks: CustomNetworkConfig[];
|
||||||
latestBlock: string;
|
latestBlock: string;
|
||||||
|
@ -41,6 +42,7 @@ export const INITIAL_STATE: State = {
|
||||||
network: NETWORKS[NODES[defaultNode].network],
|
network: NETWORKS[NODES[defaultNode].network],
|
||||||
isChangingNode: false,
|
isChangingNode: false,
|
||||||
offline: false,
|
offline: false,
|
||||||
|
autoGasLimit: true,
|
||||||
customNodes: [],
|
customNodes: [],
|
||||||
customNetworks: [],
|
customNetworks: [],
|
||||||
latestBlock: '???'
|
latestBlock: '???'
|
||||||
|
@ -77,6 +79,13 @@ function toggleOffline(state: State): State {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleAutoGasLimitEstimation(state: State): State {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
autoGasLimit: !state.autoGasLimit
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function addCustomNode(state: State, action: AddCustomNodeAction): State {
|
function addCustomNode(state: State, action: AddCustomNodeAction): State {
|
||||||
const newId = makeCustomNodeId(action.payload);
|
const newId = makeCustomNodeId(action.payload);
|
||||||
return {
|
return {
|
||||||
|
@ -132,6 +141,8 @@ export function config(state: State = INITIAL_STATE, action: ConfigAction): Stat
|
||||||
return changeNodeIntent(state);
|
return changeNodeIntent(state);
|
||||||
case TypeKeys.CONFIG_TOGGLE_OFFLINE:
|
case TypeKeys.CONFIG_TOGGLE_OFFLINE:
|
||||||
return toggleOffline(state);
|
return toggleOffline(state);
|
||||||
|
case TypeKeys.CONFIG_TOGGLE_AUTO_GAS_LIMIT:
|
||||||
|
return toggleAutoGasLimitEstimation(state);
|
||||||
case TypeKeys.CONFIG_ADD_CUSTOM_NODE:
|
case TypeKeys.CONFIG_ADD_CUSTOM_NODE:
|
||||||
return addCustomNode(state, action);
|
return addCustomNode(state, action);
|
||||||
case TypeKeys.CONFIG_REMOVE_CUSTOM_NODE:
|
case TypeKeys.CONFIG_REMOVE_CUSTOM_NODE:
|
||||||
|
|
|
@ -26,6 +26,8 @@ export const network = (state: State = INITIAL_STATE, action: NetworkAction | Re
|
||||||
return nextState('gasEstimationStatus')(state, action);
|
return nextState('gasEstimationStatus')(state, action);
|
||||||
case TK.ESTIMATE_GAS_FAILED:
|
case TK.ESTIMATE_GAS_FAILED:
|
||||||
return nextState('gasEstimationStatus')(state, action);
|
return nextState('gasEstimationStatus')(state, action);
|
||||||
|
case TK.ESTIMATE_GAS_TIMEDOUT:
|
||||||
|
return nextState('gasEstimationStatus')(state, action);
|
||||||
case TK.ESTIMATE_GAS_SUCCEEDED:
|
case TK.ESTIMATE_GAS_SUCCEEDED:
|
||||||
return nextState('gasEstimationStatus')(state, action);
|
return nextState('gasEstimationStatus')(state, action);
|
||||||
case TK.GET_FROM_REQUESTED:
|
case TK.GET_FROM_REQUESTED:
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
export enum RequestStatus {
|
export enum RequestStatus {
|
||||||
REQUESTED = 'PENDING',
|
REQUESTED = 'PENDING',
|
||||||
SUCCEEDED = 'SUCCESS',
|
SUCCEEDED = 'SUCCESS',
|
||||||
FAILED = 'FAIL'
|
FAILED = 'FAIL',
|
||||||
|
TIMEDOUT = 'TIMEDOUT'
|
||||||
}
|
}
|
||||||
export interface State {
|
export interface State {
|
||||||
gasEstimationStatus: RequestStatus | null;
|
gasEstimationStatus: RequestStatus | null;
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import { SagaIterator, buffers, delay } from 'redux-saga';
|
import { SagaIterator, buffers, delay } from 'redux-saga';
|
||||||
import { apply, put, select, take, actionChannel, call, fork } from 'redux-saga/effects';
|
import { apply, put, select, take, actionChannel, call, fork, race } from 'redux-saga/effects';
|
||||||
import { INode } from 'libs/nodes/INode';
|
import { INode } from 'libs/nodes/INode';
|
||||||
import { getNodeLib, getOffline } from 'selectors/config';
|
import { getNodeLib, getOffline, getAutoGasLimitEnabled } from 'selectors/config';
|
||||||
import { getWalletInst } from 'selectors/wallet';
|
import { getWalletInst } from 'selectors/wallet';
|
||||||
import { getTransaction, IGetTransaction } from 'selectors/transaction';
|
import { getTransaction, IGetTransaction } from 'selectors/transaction';
|
||||||
import {
|
import {
|
||||||
EstimateGasRequestedAction,
|
EstimateGasRequestedAction,
|
||||||
setGasLimitField,
|
setGasLimitField,
|
||||||
estimateGasFailed,
|
estimateGasFailed,
|
||||||
|
estimateGasTimedout,
|
||||||
estimateGasSucceeded,
|
estimateGasSucceeded,
|
||||||
TypeKeys,
|
TypeKeys,
|
||||||
estimateGasRequested,
|
estimateGasRequested,
|
||||||
|
@ -17,31 +18,36 @@ import {
|
||||||
SwapTokenToTokenAction,
|
SwapTokenToTokenAction,
|
||||||
SwapTokenToEtherAction
|
SwapTokenToEtherAction
|
||||||
} from 'actions/transaction';
|
} from 'actions/transaction';
|
||||||
|
import { TypeKeys as ConfigTypeKeys, ToggleAutoGasLimitAction } from 'actions/config';
|
||||||
import { IWallet } from 'libs/wallet';
|
import { IWallet } from 'libs/wallet';
|
||||||
import { makeTransaction, getTransactionFields, IHexStrTransaction } from 'libs/transaction';
|
import { makeTransaction, getTransactionFields, IHexStrTransaction } from 'libs/transaction';
|
||||||
|
|
||||||
export function* shouldEstimateGas(): SagaIterator {
|
export function* shouldEstimateGas(): SagaIterator {
|
||||||
while (true) {
|
while (true) {
|
||||||
const isOffline = yield select(getOffline);
|
|
||||||
if (isOffline) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const action:
|
const action:
|
||||||
| SetToFieldAction
|
| SetToFieldAction
|
||||||
| SetDataFieldAction
|
| SetDataFieldAction
|
||||||
| SwapEtherToTokenAction
|
| SwapEtherToTokenAction
|
||||||
| SwapTokenToTokenAction
|
| SwapTokenToTokenAction
|
||||||
| SwapTokenToEtherAction = yield take([
|
| SwapTokenToEtherAction
|
||||||
|
| ToggleAutoGasLimitAction = yield take([
|
||||||
TypeKeys.TO_FIELD_SET,
|
TypeKeys.TO_FIELD_SET,
|
||||||
TypeKeys.DATA_FIELD_SET,
|
TypeKeys.DATA_FIELD_SET,
|
||||||
TypeKeys.ETHER_TO_TOKEN_SWAP,
|
TypeKeys.ETHER_TO_TOKEN_SWAP,
|
||||||
TypeKeys.TOKEN_TO_TOKEN_SWAP,
|
TypeKeys.TOKEN_TO_TOKEN_SWAP,
|
||||||
TypeKeys.TOKEN_TO_ETHER_SWAP
|
TypeKeys.TOKEN_TO_ETHER_SWAP,
|
||||||
|
ConfigTypeKeys.CONFIG_TOGGLE_AUTO_GAS_LIMIT
|
||||||
]);
|
]);
|
||||||
// invalid field is a field that the value is null and the input box isnt empty
|
// invalid field is a field that the value is null and the input box isnt empty
|
||||||
// reason being is an empty field is valid because it'll be null
|
// reason being is an empty field is valid because it'll be null
|
||||||
|
|
||||||
|
const isOffline: boolean = yield select(getOffline);
|
||||||
|
const autoGasLimitEnabled: boolean = yield select(getAutoGasLimitEnabled);
|
||||||
|
|
||||||
|
if (isOffline || !autoGasLimitEnabled) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const invalidField =
|
const invalidField =
|
||||||
(action.type === TypeKeys.TO_FIELD_SET || action.type === TypeKeys.DATA_FIELD_SET) &&
|
(action.type === TypeKeys.TO_FIELD_SET || action.type === TypeKeys.DATA_FIELD_SET) &&
|
||||||
!action.payload.value &&
|
!action.payload.value &&
|
||||||
|
@ -56,6 +62,7 @@ export function* shouldEstimateGas(): SagaIterator {
|
||||||
getTransactionFields,
|
getTransactionFields,
|
||||||
transaction
|
transaction
|
||||||
);
|
);
|
||||||
|
|
||||||
yield put(estimateGasRequested(rest));
|
yield put(estimateGasRequested(rest));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,8 +71,10 @@ export function* estimateGas(): SagaIterator {
|
||||||
const requestChan = yield actionChannel(TypeKeys.ESTIMATE_GAS_REQUESTED, buffers.sliding(1));
|
const requestChan = yield actionChannel(TypeKeys.ESTIMATE_GAS_REQUESTED, buffers.sliding(1));
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
const autoGasLimitEnabled: boolean = yield select(getAutoGasLimitEnabled);
|
||||||
const isOffline = yield select(getOffline);
|
const isOffline = yield select(getOffline);
|
||||||
if (isOffline) {
|
|
||||||
|
if (isOffline || !autoGasLimitEnabled) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,17 +86,28 @@ export function* estimateGas(): SagaIterator {
|
||||||
try {
|
try {
|
||||||
const from: string = yield apply(walletInst, walletInst.getAddressString);
|
const from: string = yield apply(walletInst, walletInst.getAddressString);
|
||||||
const txObj = { ...payload, from };
|
const txObj = { ...payload, from };
|
||||||
const gasLimit = yield apply(node, node.estimateGas, [txObj]);
|
const { gasLimit } = yield race({
|
||||||
|
gasLimit: apply(node, node.estimateGas, [txObj]),
|
||||||
|
timeout: call(delay, 10000)
|
||||||
|
});
|
||||||
|
if (gasLimit) {
|
||||||
yield put(setGasLimitField({ raw: gasLimit.toString(), value: gasLimit }));
|
yield put(setGasLimitField({ raw: gasLimit.toString(), value: gasLimit }));
|
||||||
yield put(estimateGasSucceeded());
|
yield put(estimateGasSucceeded());
|
||||||
|
} else {
|
||||||
|
yield put(estimateGasTimedout());
|
||||||
|
yield call(localGasEstimation, payload);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
yield put(estimateGasFailed());
|
yield put(estimateGasFailed());
|
||||||
// fallback for estimating locally
|
yield call(localGasEstimation, payload);
|
||||||
const tx = yield call(makeTransaction, payload);
|
|
||||||
const gasLimit = yield apply(tx, tx.getBaseFee);
|
|
||||||
yield put(setGasLimitField({ raw: gasLimit.toString(), value: gasLimit }));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function* localGasEstimation(payload: EstimateGasRequestedAction['payload']) {
|
||||||
|
const tx = yield call(makeTransaction, payload);
|
||||||
|
const gasLimit = yield apply(tx, tx.getBaseFee);
|
||||||
|
yield put(setGasLimitField({ raw: gasLimit.toString(), value: gasLimit }));
|
||||||
|
}
|
||||||
|
|
||||||
export const gas = [fork(shouldEstimateGas), fork(estimateGas)];
|
export const gas = [fork(shouldEstimateGas), fork(estimateGas)];
|
||||||
|
|
|
@ -16,6 +16,10 @@ export function getNode(state: AppState): string {
|
||||||
return state.config.nodeSelection;
|
return state.config.nodeSelection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getIsWeb3Node(state: AppState): boolean {
|
||||||
|
return getNode(state) === 'web3';
|
||||||
|
}
|
||||||
|
|
||||||
export function getNodeConfig(state: AppState): NodeConfig {
|
export function getNodeConfig(state: AppState): NodeConfig {
|
||||||
return state.config.node;
|
return state.config.node;
|
||||||
}
|
}
|
||||||
|
@ -86,6 +90,10 @@ export function getOffline(state: AppState): boolean {
|
||||||
return state.config.offline;
|
return state.config.offline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getAutoGasLimitEnabled(state: AppState): boolean {
|
||||||
|
return state.config.autoGasLimit;
|
||||||
|
}
|
||||||
|
|
||||||
export function isSupportedUnit(state: AppState, unit: string) {
|
export function isSupportedUnit(state: AppState, unit: string) {
|
||||||
const isToken: boolean = tokenExists(state, unit);
|
const isToken: boolean = tokenExists(state, unit);
|
||||||
const isEther: boolean = isEtherUnit(unit);
|
const isEther: boolean = isEtherUnit(unit);
|
||||||
|
|
|
@ -2,10 +2,12 @@ import { AppState } from 'reducers';
|
||||||
import { getTransactionState } from 'selectors/transaction';
|
import { getTransactionState } from 'selectors/transaction';
|
||||||
import { RequestStatus } from 'reducers/transaction/network';
|
import { RequestStatus } from 'reducers/transaction/network';
|
||||||
|
|
||||||
const getNetworkStatus = (state: AppState) => getTransactionState(state).network;
|
export const getNetworkStatus = (state: AppState) => getTransactionState(state).network;
|
||||||
const nonceRequestFailed = (state: AppState) =>
|
|
||||||
|
export const nonceRequestFailed = (state: AppState) =>
|
||||||
getNetworkStatus(state).getNonceStatus === RequestStatus.FAILED;
|
getNetworkStatus(state).getNonceStatus === RequestStatus.FAILED;
|
||||||
const isNetworkRequestPending = (state: AppState) => {
|
|
||||||
|
export const isNetworkRequestPending = (state: AppState) => {
|
||||||
const network = getNetworkStatus(state);
|
const network = getNetworkStatus(state);
|
||||||
const states: RequestStatus[] = Object.values(network);
|
const states: RequestStatus[] = Object.values(network);
|
||||||
return states.reduce(
|
return states.reduce(
|
||||||
|
@ -14,4 +16,8 @@ const isNetworkRequestPending = (state: AppState) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export { nonceRequestFailed, isNetworkRequestPending };
|
export const getGasEstimationPending = (state: AppState) =>
|
||||||
|
getNetworkStatus(state).gasEstimationStatus === RequestStatus.REQUESTED;
|
||||||
|
|
||||||
|
export const getGasLimitEstimationTimedOut = (state: AppState) =>
|
||||||
|
getNetworkStatus(state).gasEstimationStatus === RequestStatus.TIMEDOUT;
|
||||||
|
|
|
@ -133,7 +133,8 @@ const configureStore = () => {
|
||||||
nodeSelection: state.config.nodeSelection,
|
nodeSelection: state.config.nodeSelection,
|
||||||
languageSelection: state.config.languageSelection,
|
languageSelection: state.config.languageSelection,
|
||||||
customNodes: state.config.customNodes,
|
customNodes: state.config.customNodes,
|
||||||
customNetworks: state.config.customNetworks
|
customNetworks: state.config.customNetworks,
|
||||||
|
setGasLimit: state.config.setGasLimit
|
||||||
},
|
},
|
||||||
transaction: {
|
transaction: {
|
||||||
fields: {
|
fields: {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { buffers, delay } from 'redux-saga';
|
import { buffers, delay } from 'redux-saga';
|
||||||
import { apply, put, select, take, actionChannel, call } from 'redux-saga/effects';
|
import { apply, put, select, take, actionChannel, call, race } from 'redux-saga/effects';
|
||||||
import { getNodeLib, getOffline } from 'selectors/config';
|
import { getNodeLib, getOffline, getAutoGasLimitEnabled } from 'selectors/config';
|
||||||
import { getWalletInst } from 'selectors/wallet';
|
import { getWalletInst } from 'selectors/wallet';
|
||||||
import { getTransaction } from 'selectors/transaction';
|
import { getTransaction } from 'selectors/transaction';
|
||||||
import {
|
import {
|
||||||
|
@ -8,15 +8,18 @@ import {
|
||||||
estimateGasFailed,
|
estimateGasFailed,
|
||||||
estimateGasSucceeded,
|
estimateGasSucceeded,
|
||||||
TypeKeys,
|
TypeKeys,
|
||||||
estimateGasRequested
|
estimateGasRequested,
|
||||||
|
estimateGasTimedout
|
||||||
} from 'actions/transaction';
|
} from 'actions/transaction';
|
||||||
import { makeTransaction, getTransactionFields } from 'libs/transaction';
|
import { makeTransaction, getTransactionFields } from 'libs/transaction';
|
||||||
import { shouldEstimateGas, estimateGas } from 'sagas/transaction/network/gas';
|
import { shouldEstimateGas, estimateGas, localGasEstimation } from 'sagas/transaction/network/gas';
|
||||||
import { cloneableGenerator } from 'redux-saga/utils';
|
import { cloneableGenerator } from 'redux-saga/utils';
|
||||||
import { Wei } from 'libs/units';
|
import { Wei } from 'libs/units';
|
||||||
|
import { TypeKeys as ConfigTypeKeys } from 'actions/config';
|
||||||
|
|
||||||
describe('shouldEstimateGas*', () => {
|
describe('shouldEstimateGas*', () => {
|
||||||
const offline = false;
|
const offline = false;
|
||||||
|
const autoGasLimitEnabled = true;
|
||||||
const transaction: any = 'transaction';
|
const transaction: any = 'transaction';
|
||||||
const tx = { transaction };
|
const tx = { transaction };
|
||||||
const rest: any = {
|
const rest: any = {
|
||||||
|
@ -40,24 +43,29 @@ describe('shouldEstimateGas*', () => {
|
||||||
|
|
||||||
const gen = shouldEstimateGas();
|
const gen = shouldEstimateGas();
|
||||||
|
|
||||||
it('should select getOffline', () => {
|
|
||||||
expect(gen.next().value).toEqual(select(getOffline));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should take expected types', () => {
|
it('should take expected types', () => {
|
||||||
expect(gen.next(offline).value).toEqual(
|
expect(gen.next().value).toEqual(
|
||||||
take([
|
take([
|
||||||
TypeKeys.TO_FIELD_SET,
|
TypeKeys.TO_FIELD_SET,
|
||||||
TypeKeys.DATA_FIELD_SET,
|
TypeKeys.DATA_FIELD_SET,
|
||||||
TypeKeys.ETHER_TO_TOKEN_SWAP,
|
TypeKeys.ETHER_TO_TOKEN_SWAP,
|
||||||
TypeKeys.TOKEN_TO_TOKEN_SWAP,
|
TypeKeys.TOKEN_TO_TOKEN_SWAP,
|
||||||
TypeKeys.TOKEN_TO_ETHER_SWAP
|
TypeKeys.TOKEN_TO_ETHER_SWAP,
|
||||||
|
ConfigTypeKeys.CONFIG_TOGGLE_AUTO_GAS_LIMIT
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should select getOffline', () => {
|
||||||
|
expect(gen.next(action).value).toEqual(select(getOffline));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should select autoGasLimitEnabled', () => {
|
||||||
|
expect(gen.next(offline).value).toEqual(select(getAutoGasLimitEnabled));
|
||||||
|
});
|
||||||
|
|
||||||
it('should select getTransaction', () => {
|
it('should select getTransaction', () => {
|
||||||
expect(gen.next(action).value).toEqual(select(getTransaction));
|
expect(gen.next(autoGasLimitEnabled).value).toEqual(select(getTransaction));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call getTransactionFields with transaction', () => {
|
it('should call getTransactionFields with transaction', () => {
|
||||||
|
@ -71,6 +79,7 @@ describe('shouldEstimateGas*', () => {
|
||||||
|
|
||||||
describe('estimateGas*', () => {
|
describe('estimateGas*', () => {
|
||||||
const offline = false;
|
const offline = false;
|
||||||
|
const autoGasLimitEnabled = true;
|
||||||
const requestChan = 'requestChan';
|
const requestChan = 'requestChan';
|
||||||
const payload: any = {
|
const payload: any = {
|
||||||
mock1: 'mock1',
|
mock1: 'mock1',
|
||||||
|
@ -86,9 +95,16 @@ describe('estimateGas*', () => {
|
||||||
const from = '0xa';
|
const from = '0xa';
|
||||||
const txObj = { ...payload, from };
|
const txObj = { ...payload, from };
|
||||||
const gasLimit = Wei('100');
|
const gasLimit = Wei('100');
|
||||||
|
const successfulGasEstimationResult = {
|
||||||
|
gasLimit
|
||||||
|
};
|
||||||
|
|
||||||
const gens: any = {};
|
const unsuccessfulGasEstimationResult = {
|
||||||
gens.gen = cloneableGenerator(estimateGas)();
|
gasLimit: null
|
||||||
|
};
|
||||||
|
|
||||||
|
const gens: { [name: string]: any } = {};
|
||||||
|
gens.successCase = cloneableGenerator(estimateGas)();
|
||||||
|
|
||||||
let random;
|
let random;
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
|
@ -104,41 +120,53 @@ describe('estimateGas*', () => {
|
||||||
const expected = JSON.stringify(
|
const expected = JSON.stringify(
|
||||||
actionChannel(TypeKeys.ESTIMATE_GAS_REQUESTED, buffers.sliding(1))
|
actionChannel(TypeKeys.ESTIMATE_GAS_REQUESTED, buffers.sliding(1))
|
||||||
);
|
);
|
||||||
const result = JSON.stringify(gens.gen.next().value);
|
const result = JSON.stringify(gens.successCase.next().value);
|
||||||
expect(expected).toEqual(result);
|
expect(expected).toEqual(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should select autoGasLimit', () => {
|
||||||
|
expect(gens.successCase.next(requestChan).value).toEqual(select(getAutoGasLimitEnabled));
|
||||||
|
});
|
||||||
|
|
||||||
it('should select getOffline', () => {
|
it('should select getOffline', () => {
|
||||||
expect(gens.gen.next(requestChan).value).toEqual(select(getOffline));
|
expect(gens.successCase.next(autoGasLimitEnabled).value).toEqual(select(getOffline));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should take requestChan', () => {
|
it('should take requestChan', () => {
|
||||||
expect(gens.gen.next(offline).value).toEqual(take(requestChan));
|
expect(gens.successCase.next(offline).value).toEqual(take(requestChan));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call delay', () => {
|
it('should call delay', () => {
|
||||||
expect(gens.gen.next(action).value).toEqual(call(delay, 250));
|
expect(gens.successCase.next(action).value).toEqual(call(delay, 250));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should select getNodeLib', () => {
|
it('should select getNodeLib', () => {
|
||||||
expect(gens.gen.next().value).toEqual(select(getNodeLib));
|
expect(gens.successCase.next().value).toEqual(select(getNodeLib));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should select getWalletInst', () => {
|
it('should select getWalletInst', () => {
|
||||||
expect(gens.gen.next(node).value).toEqual(select(getWalletInst));
|
expect(gens.successCase.next(node).value).toEqual(select(getWalletInst));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should apply walletInst', () => {
|
it('should apply walletInst', () => {
|
||||||
expect(gens.gen.next(walletInst).value).toEqual(apply(walletInst, walletInst.getAddressString));
|
expect(gens.successCase.next(walletInst).value).toEqual(
|
||||||
|
apply(walletInst, walletInst.getAddressString)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should apply node.estimateGas', () => {
|
it('should race between node.estimate gas and a 10 second timeout', () => {
|
||||||
gens.clone = gens.gen.clone();
|
gens.failCase = gens.successCase.clone();
|
||||||
expect(gens.gen.next(from).value).toEqual(apply(node, node.estimateGas, [txObj]));
|
expect(gens.successCase.next(from).value).toEqual(
|
||||||
|
race({
|
||||||
|
gasLimit: apply(node, node.estimateGas, [txObj]),
|
||||||
|
timeout: call(delay, 10000)
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should put setGasLimitField', () => {
|
it('should put setGasLimitField', () => {
|
||||||
expect(gens.gen.next(gasLimit).value).toEqual(
|
gens.timeOutCase = gens.successCase.clone();
|
||||||
|
expect(gens.successCase.next(successfulGasEstimationResult).value).toEqual(
|
||||||
put(
|
put(
|
||||||
setGasLimitField({
|
setGasLimitField({
|
||||||
raw: gasLimit.toString(),
|
raw: gasLimit.toString(),
|
||||||
|
@ -149,28 +177,56 @@ describe('estimateGas*', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should put estimateGasSucceeded', () => {
|
it('should put estimateGasSucceeded', () => {
|
||||||
expect(gens.gen.next().value).toEqual(put(estimateGasSucceeded()));
|
expect(gens.successCase.next().value).toEqual(put(estimateGasSucceeded()));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when it times out', () => {
|
||||||
|
it('should put estimateGasTimedout ', () => {
|
||||||
|
expect(gens.timeOutCase.next(unsuccessfulGasEstimationResult).value).toEqual(
|
||||||
|
put(estimateGasTimedout())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('should call localGasEstimation', () => {
|
||||||
|
expect(gens.timeOutCase.next(estimateGasFailed()).value).toEqual(
|
||||||
|
call(localGasEstimation, payload)
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when it throws', () => {
|
describe('when it throws', () => {
|
||||||
|
it('should catch and put estimateGasFailed', () => {
|
||||||
|
expect(gens.failCase.throw().value).toEqual(put(estimateGasFailed()));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call localGasEstimation', () => {
|
||||||
|
expect(gens.failCase.next(estimateGasFailed()).value).toEqual(
|
||||||
|
call(localGasEstimation, payload)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('localGasEstimation', () => {
|
||||||
|
const payload: any = {
|
||||||
|
mock1: 'mock1',
|
||||||
|
mock2: 'mock2'
|
||||||
|
};
|
||||||
const tx = {
|
const tx = {
|
||||||
getBaseFee: jest.fn()
|
getBaseFee: jest.fn()
|
||||||
};
|
};
|
||||||
|
const gasLimit = Wei('100');
|
||||||
|
|
||||||
it('should catch and put estimateGasFailed', () => {
|
const gen = localGasEstimation(payload);
|
||||||
expect(gens.clone.throw().value).toEqual(put(estimateGasFailed()));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call makeTransaction with payload', () => {
|
it('should call makeTransaction with payload', () => {
|
||||||
expect(gens.clone.next().value).toEqual(call(makeTransaction, payload));
|
expect(gen.next().value).toEqual(call(makeTransaction, payload));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should apply tx.getBaseFee', () => {
|
it('should apply tx.getBaseFee', () => {
|
||||||
expect(gens.clone.next(tx).value).toEqual(apply(tx, tx.getBaseFee));
|
expect(gen.next(tx).value).toEqual(apply(tx, tx.getBaseFee));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should put setGasLimitField', () => {
|
it('should put setGasLimitField', () => {
|
||||||
expect(gens.clone.next(gasLimit).value).toEqual(
|
expect(gen.next(gasLimit).value).toEqual(
|
||||||
put(
|
put(
|
||||||
setGasLimitField({
|
setGasLimitField({
|
||||||
raw: gasLimit.toString(),
|
raw: gasLimit.toString(),
|
||||||
|
@ -180,4 +236,3 @@ describe('estimateGas*', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
Loading…
Reference in New Issue