Handle Gas / Estimates on a Per Network Basis (#1160)
* Give each network the ability to specify default estimates, and whether or not they should fetch estimates from API. Convert gas slider to always use estimates. * Fix gas cache invalidation, invalid too high / low logic. * Fix up tests. * tscheck
This commit is contained in:
parent
afaf045edd
commit
c76d0b3fa5
|
@ -17,6 +17,7 @@ export interface GasEstimates {
|
|||
fast: number;
|
||||
fastest: number;
|
||||
time: number;
|
||||
chainId: number;
|
||||
isDefault: boolean;
|
||||
}
|
||||
|
||||
|
@ -66,6 +67,7 @@ export function fetchGasEstimates(): Promise<GasEstimates> {
|
|||
.then((res: RawGasEstimates) => ({
|
||||
...res,
|
||||
time: Date.now(),
|
||||
chainId: 1,
|
||||
isDefault: false
|
||||
}));
|
||||
}
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
import { gasPriceDefaults, HELP_ARTICLE } from 'config';
|
||||
import throttle from 'lodash/throttle';
|
||||
import React, { Component } from 'react';
|
||||
import { DropdownShell, HelpLink } from 'components/ui';
|
||||
import './GasPriceDropdown.scss';
|
||||
import { SetGasLimitFieldAction } from 'actions/transaction';
|
||||
import { gasPricetoBase } from 'libs/units';
|
||||
import { AppState } from 'reducers';
|
||||
import { getGasPrice } from 'selectors/transaction';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
interface OwnProps {
|
||||
onChange(payload: SetGasLimitFieldAction['payload']): void;
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
gasPrice: AppState['transaction']['fields']['gasPrice'];
|
||||
}
|
||||
|
||||
type Props = OwnProps & StateProps;
|
||||
|
||||
class GasPriceDropdown extends Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.updateGasPrice = throttle(this.updateGasPrice, 50);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<DropdownShell
|
||||
color="white"
|
||||
size="smr"
|
||||
ariaLabel={`adjust gas price. current price is ${this.props.gasPrice.raw} gwei`}
|
||||
renderLabel={this.renderLabel}
|
||||
renderOptions={this.renderOptions}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderLabel = () => {
|
||||
return (
|
||||
<span>
|
||||
Gas Price<span className="hidden-xs">: {this.props.gasPrice.raw} Gwei</span>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
private renderOptions = () => {
|
||||
return (
|
||||
<div className="GasPrice-dropdown-menu dropdown-menu dropdown-menu-right">
|
||||
<div className="GasPrice-header">
|
||||
<span>Gas Price</span>: {this.props.gasPrice.raw} Gwei
|
||||
<input
|
||||
type="range"
|
||||
value={this.props.gasPrice.raw}
|
||||
min={gasPriceDefaults.minGwei}
|
||||
max={gasPriceDefaults.maxGwei}
|
||||
onChange={this.handleGasPriceChange}
|
||||
/>
|
||||
<p className="small col-xs-4 text-left GasPrice-padding-reset">Not So Fast</p>
|
||||
<p className="small col-xs-4 text-center GasPrice-padding-reset">Fast</p>
|
||||
<p className="small col-xs-4 text-right GasPrice-padding-reset">Fast AF</p>
|
||||
<p className="small GasPrice-description">
|
||||
Gas Price is the amount you pay per unit of gas.{' '}
|
||||
<code>TX fee = gas price * gas limit</code> & is paid to miners for including your TX in
|
||||
a block. Higher the gas price = faster transaction, but more expensive. Default is{' '}
|
||||
<code>21 GWEI</code>.
|
||||
</p>
|
||||
<p>
|
||||
<HelpLink article={HELP_ARTICLE.WHAT_IS_GAS}>Read more</HelpLink>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
private updateGasPrice = (value: string) => {
|
||||
this.props.onChange({ raw: value, value: gasPricetoBase(parseInt(value, 10)) });
|
||||
};
|
||||
|
||||
private handleGasPriceChange = (e: React.FormEvent<HTMLInputElement>) => {
|
||||
this.updateGasPrice(e.currentTarget.value);
|
||||
};
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: AppState): StateProps => ({ gasPrice: getGasPrice(state) });
|
||||
|
||||
export default connect(mapStateToProps)(GasPriceDropdown);
|
|
@ -125,8 +125,10 @@ class TXMetaDataPanel extends React.Component<Props, State> {
|
|||
};
|
||||
|
||||
private handleGasPriceInput = (raw: string) => {
|
||||
const gasBn = new BN(raw);
|
||||
const value = gasBn.mul(new BN(Units.gwei));
|
||||
// Realistically, we're not going to end up with a > 32 bit int, so it's
|
||||
// safe to cast to float, multiply by gwei units, then big number, since
|
||||
// some of the inputs may be sub-one float values.
|
||||
const value = new BN(parseFloat(raw) * parseFloat(Units.gwei));
|
||||
this.setState({
|
||||
gasPrice: { raw, value }
|
||||
});
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
import Slider from 'rc-slider';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import { gasPriceDefaults } from 'config';
|
||||
import FeeSummary from './FeeSummary';
|
||||
import './SimpleGas.scss';
|
||||
import { AppState } from 'reducers';
|
||||
|
@ -15,6 +14,7 @@ import { fetchGasEstimates, TFetchGasEstimates } from 'actions/gas';
|
|||
import { getIsWeb3Node } from 'selectors/config';
|
||||
import { getEstimates, getIsEstimating } from 'selectors/gas';
|
||||
import { Wei, fromWei } from 'libs/units';
|
||||
import { gasPriceDefaults } from 'config';
|
||||
import { InlineSpinner } from 'components/ui/InlineSpinner';
|
||||
const SliderWithTooltip = Slider.createSliderWithTooltip(Slider);
|
||||
|
||||
|
@ -41,7 +41,7 @@ type Props = OwnProps & StateProps & ActionProps;
|
|||
|
||||
class SimpleGas extends React.Component<Props> {
|
||||
public componentDidMount() {
|
||||
this.fixGasPrice(this.props.gasPrice);
|
||||
this.fixGasPrice();
|
||||
this.props.fetchGasEstimates();
|
||||
}
|
||||
|
||||
|
@ -63,8 +63,8 @@ class SimpleGas extends React.Component<Props> {
|
|||
} = this.props;
|
||||
|
||||
const bounds = {
|
||||
max: gasEstimates ? gasEstimates.fastest : gasPriceDefaults.minGwei,
|
||||
min: gasEstimates ? gasEstimates.safeLow : gasPriceDefaults.maxGwei
|
||||
max: gasEstimates ? gasEstimates.fastest : gasPriceDefaults.max,
|
||||
min: gasEstimates ? gasEstimates.safeLow : gasPriceDefaults.min
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -93,6 +93,7 @@ class SimpleGas extends React.Component<Props> {
|
|||
onChange={this.handleSlider}
|
||||
min={bounds.min}
|
||||
max={bounds.max}
|
||||
step={bounds.min < 1 ? 0.1 : 1}
|
||||
value={this.getGasPriceGwei(gasPrice.value)}
|
||||
tipFormatter={this.formatTooltip}
|
||||
disabled={isGasEstimating}
|
||||
|
@ -119,13 +120,18 @@ class SimpleGas extends React.Component<Props> {
|
|||
this.props.inputGasPrice(gasGwei.toString());
|
||||
};
|
||||
|
||||
private fixGasPrice(gasPrice: AppState['transaction']['fields']['gasPrice']) {
|
||||
private fixGasPrice() {
|
||||
const { gasPrice, gasEstimates } = this.props;
|
||||
if (!gasEstimates) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the gas price is above or below our minimum, bring it in line
|
||||
const gasPriceGwei = this.getGasPriceGwei(gasPrice.value);
|
||||
if (gasPriceGwei > gasPriceDefaults.maxGwei) {
|
||||
this.props.setGasPrice(gasPriceDefaults.maxGwei.toString());
|
||||
} else if (gasPriceGwei < gasPriceDefaults.minGwei) {
|
||||
this.props.setGasPrice(gasPriceDefaults.minGwei.toString());
|
||||
if (gasPriceGwei < gasEstimates.safeLow) {
|
||||
this.props.setGasPrice(gasEstimates.safeLow.toString());
|
||||
} else if (gasPriceGwei > gasEstimates.fastest) {
|
||||
this.props.setGasPrice(gasEstimates.fastest.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,6 @@ export const GAS_LIMIT_LOWER_BOUND = 21000;
|
|||
export const GAS_LIMIT_UPPER_BOUND = 8000000;
|
||||
|
||||
// Lower/upper ranges for gas price in gwei
|
||||
export const GAS_PRICE_GWEI_LOWER_BOUND = 1;
|
||||
export const GAS_PRICE_GWEI_UPPER_BOUND = 10000;
|
||||
export const GAS_PRICE_GWEI_DEFAULT = 40;
|
||||
export const GAS_PRICE_GWEI_LOWER_BOUND = 0.01;
|
||||
export const GAS_PRICE_GWEI_UPPER_BOUND = 3000;
|
||||
export const GAS_PRICE_GWEI_DEFAULT = 20;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react'; // For ANNOUNCEMENT_MESSAGE jsx
|
||||
import { getValues } from '../utils/helpers';
|
||||
import packageJson from '../../package.json';
|
||||
import { GasPriceSetting } from 'types/network';
|
||||
|
||||
export const languages = require('./languages.json');
|
||||
|
||||
|
@ -42,12 +43,12 @@ export const donationAddressMap = {
|
|||
REP: '0x4bbeEB066eD09B7AEd07bF39EEe0460DFa261520'
|
||||
};
|
||||
|
||||
export const gasPriceDefaults = {
|
||||
minGwei: 1,
|
||||
maxGwei: 60,
|
||||
default: 21
|
||||
};
|
||||
export const gasEstimateCacheTime = 60000;
|
||||
export const gasPriceDefaults: GasPriceSetting = {
|
||||
min: 1,
|
||||
max: 60,
|
||||
initial: 20
|
||||
};
|
||||
|
||||
export const MINIMUM_PASSWORD_LENGTH = 12;
|
||||
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
import { ethPlorer, ETHTokenExplorer, SecureWalletName, InsecureWalletName } from 'config/data';
|
||||
import {
|
||||
ethPlorer,
|
||||
ETHTokenExplorer,
|
||||
SecureWalletName,
|
||||
InsecureWalletName,
|
||||
gasPriceDefaults
|
||||
} from 'config/data';
|
||||
import {
|
||||
ETH_DEFAULT,
|
||||
ETH_TREZOR,
|
||||
|
@ -27,7 +33,13 @@ export function makeExplorer(name: string, origin: string): BlockExplorerConfig
|
|||
};
|
||||
}
|
||||
|
||||
const INITIAL_STATE: State = {
|
||||
const testnetDefaultGasPrice = {
|
||||
min: 0.1,
|
||||
max: 40,
|
||||
initial: 4
|
||||
};
|
||||
|
||||
export const INITIAL_STATE: State = {
|
||||
ETH: {
|
||||
name: 'ETH',
|
||||
unit: 'ETH',
|
||||
|
@ -45,7 +57,9 @@ const INITIAL_STATE: State = {
|
|||
[SecureWalletName.TREZOR]: ETH_TREZOR,
|
||||
[SecureWalletName.LEDGER_NANO_S]: ETH_LEDGER,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: ETH_DEFAULT
|
||||
}
|
||||
},
|
||||
gasPriceSettings: gasPriceDefaults,
|
||||
shouldEstimateGasPrice: true
|
||||
},
|
||||
Ropsten: {
|
||||
name: 'Ropsten',
|
||||
|
@ -61,7 +75,8 @@ const INITIAL_STATE: State = {
|
|||
[SecureWalletName.TREZOR]: ETH_TESTNET,
|
||||
[SecureWalletName.LEDGER_NANO_S]: ETH_TESTNET,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: ETH_TESTNET
|
||||
}
|
||||
},
|
||||
gasPriceSettings: testnetDefaultGasPrice
|
||||
},
|
||||
Kovan: {
|
||||
name: 'Kovan',
|
||||
|
@ -77,7 +92,8 @@ const INITIAL_STATE: State = {
|
|||
[SecureWalletName.TREZOR]: ETH_TESTNET,
|
||||
[SecureWalletName.LEDGER_NANO_S]: ETH_TESTNET,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: ETH_TESTNET
|
||||
}
|
||||
},
|
||||
gasPriceSettings: testnetDefaultGasPrice
|
||||
},
|
||||
Rinkeby: {
|
||||
name: 'Rinkeby',
|
||||
|
@ -93,7 +109,8 @@ const INITIAL_STATE: State = {
|
|||
[SecureWalletName.TREZOR]: ETH_TESTNET,
|
||||
[SecureWalletName.LEDGER_NANO_S]: ETH_TESTNET,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: ETH_TESTNET
|
||||
}
|
||||
},
|
||||
gasPriceSettings: testnetDefaultGasPrice
|
||||
},
|
||||
ETC: {
|
||||
name: 'ETC',
|
||||
|
@ -108,6 +125,11 @@ const INITIAL_STATE: State = {
|
|||
[SecureWalletName.TREZOR]: ETC_TREZOR,
|
||||
[SecureWalletName.LEDGER_NANO_S]: ETC_LEDGER,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: ETC_TREZOR
|
||||
},
|
||||
gasPriceSettings: {
|
||||
min: 0.1,
|
||||
max: 10,
|
||||
initial: 1
|
||||
}
|
||||
},
|
||||
UBQ: {
|
||||
|
@ -123,6 +145,11 @@ const INITIAL_STATE: State = {
|
|||
[SecureWalletName.TREZOR]: UBQ_DEFAULT,
|
||||
[SecureWalletName.LEDGER_NANO_S]: UBQ_DEFAULT,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: UBQ_DEFAULT
|
||||
},
|
||||
gasPriceSettings: {
|
||||
min: 1,
|
||||
max: 60,
|
||||
initial: 20
|
||||
}
|
||||
},
|
||||
EXP: {
|
||||
|
@ -138,6 +165,11 @@ const INITIAL_STATE: State = {
|
|||
[SecureWalletName.TREZOR]: EXP_DEFAULT,
|
||||
[SecureWalletName.LEDGER_NANO_S]: EXP_DEFAULT,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: EXP_DEFAULT
|
||||
},
|
||||
gasPriceSettings: {
|
||||
min: 0.1,
|
||||
max: 20,
|
||||
initial: 2
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -11,7 +11,6 @@ import {
|
|||
import { Reducer } from 'redux';
|
||||
import { State } from './typings';
|
||||
import { gasPricetoBase } from 'libs/units';
|
||||
import { gasPriceDefaults } from 'config';
|
||||
|
||||
const INITIAL_STATE: State = {
|
||||
to: { raw: '', value: null },
|
||||
|
@ -19,10 +18,7 @@ const INITIAL_STATE: State = {
|
|||
nonce: { raw: '', value: null },
|
||||
value: { raw: '', value: null },
|
||||
gasLimit: { raw: '21000', value: new BN(21000) },
|
||||
gasPrice: {
|
||||
raw: gasPriceDefaults.default.toString(),
|
||||
value: gasPricetoBase(gasPriceDefaults.default)
|
||||
}
|
||||
gasPrice: { raw: '20', value: gasPricetoBase(20) }
|
||||
};
|
||||
|
||||
const updateField = (key: keyof State): Reducer<State> => (state: State, action: FieldAction) => ({
|
||||
|
|
|
@ -5,35 +5,49 @@ import { AppState } from 'reducers';
|
|||
import { fetchGasEstimates, GasEstimates } from 'api/gas';
|
||||
import { gasPriceDefaults, gasEstimateCacheTime } from 'config';
|
||||
import { getEstimates } from 'selectors/gas';
|
||||
import { getOffline } from 'selectors/config';
|
||||
import { getOffline, getNetworkConfig } from 'selectors/config';
|
||||
import { NetworkConfig } from 'types/network';
|
||||
|
||||
export function* setDefaultEstimates(): SagaIterator {
|
||||
export function* setDefaultEstimates(network: NetworkConfig): SagaIterator {
|
||||
// Must yield time for testability
|
||||
const time = yield call(Date.now);
|
||||
const gasSettings = network.isCustom ? gasPriceDefaults : network.gasPriceSettings;
|
||||
|
||||
yield put(
|
||||
setGasEstimates({
|
||||
safeLow: gasPriceDefaults.minGwei,
|
||||
standard: gasPriceDefaults.default,
|
||||
fast: gasPriceDefaults.default,
|
||||
fastest: gasPriceDefaults.maxGwei,
|
||||
safeLow: gasSettings.min,
|
||||
standard: gasSettings.initial,
|
||||
fast: gasSettings.initial,
|
||||
fastest: gasSettings.max,
|
||||
isDefault: true,
|
||||
chainId: network.chainId,
|
||||
time
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function* fetchEstimates(): SagaIterator {
|
||||
// Don't even try offline
|
||||
// Don't try on non-estimating network
|
||||
const network: NetworkConfig = yield select(getNetworkConfig);
|
||||
if (network.isCustom || !network.shouldEstimateGasPrice) {
|
||||
yield call(setDefaultEstimates, network);
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't try while offline
|
||||
const isOffline: boolean = yield select(getOffline);
|
||||
if (isOffline) {
|
||||
yield call(setDefaultEstimates);
|
||||
yield call(setDefaultEstimates, network);
|
||||
return;
|
||||
}
|
||||
|
||||
// Cache estimates for a bit
|
||||
const oldEstimates: AppState['gas']['estimates'] = yield select(getEstimates);
|
||||
if (oldEstimates && oldEstimates.time + gasEstimateCacheTime > Date.now()) {
|
||||
if (
|
||||
oldEstimates &&
|
||||
oldEstimates.chainId === network.chainId &&
|
||||
oldEstimates.time + gasEstimateCacheTime > Date.now()
|
||||
) {
|
||||
yield put(setGasEstimates(oldEstimates));
|
||||
return;
|
||||
}
|
||||
|
@ -44,7 +58,7 @@ export function* fetchEstimates(): SagaIterator {
|
|||
yield put(setGasEstimates(estimates));
|
||||
} catch (err) {
|
||||
console.warn('Failed to fetch gas estimates:', err);
|
||||
yield call(setDefaultEstimates);
|
||||
yield call(setDefaultEstimates, network);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,12 @@ interface DPathFormats {
|
|||
mnemonicPhrase: DPath;
|
||||
}
|
||||
|
||||
export interface GasPriceSetting {
|
||||
min: number;
|
||||
max: number;
|
||||
initial: number;
|
||||
}
|
||||
|
||||
interface StaticNetworkConfig {
|
||||
isCustom: false; // used for type guards
|
||||
name: StaticNetworkIds;
|
||||
|
@ -44,6 +50,8 @@ interface StaticNetworkConfig {
|
|||
contracts: NetworkContract[] | null;
|
||||
dPathFormats: DPathFormats;
|
||||
isTestnet?: boolean;
|
||||
gasPriceSettings: GasPriceSetting;
|
||||
shouldEstimateGasPrice?: boolean;
|
||||
}
|
||||
|
||||
interface CustomNetworkConfig {
|
||||
|
|
|
@ -1,138 +1,8 @@
|
|||
import { staticNetworks, makeExplorer } from 'reducers/config/networks/staticNetworks';
|
||||
import { ethPlorer, ETHTokenExplorer, SecureWalletName, InsecureWalletName } from 'config/data';
|
||||
import {
|
||||
ETH_DEFAULT,
|
||||
ETH_TREZOR,
|
||||
ETH_LEDGER,
|
||||
ETC_LEDGER,
|
||||
ETC_TREZOR,
|
||||
ETH_TESTNET,
|
||||
EXP_DEFAULT,
|
||||
UBQ_DEFAULT
|
||||
} from 'config/dpaths';
|
||||
|
||||
const expectedInitialState = {
|
||||
ETH: {
|
||||
name: 'ETH',
|
||||
unit: 'ETH',
|
||||
chainId: 1,
|
||||
isCustom: false,
|
||||
color: '#0e97c0',
|
||||
blockExplorer: makeExplorer('Etherscan', 'https://etherscan.io'),
|
||||
tokenExplorer: {
|
||||
name: ethPlorer,
|
||||
address: ETHTokenExplorer
|
||||
},
|
||||
tokens: require('config/tokens/eth.json'),
|
||||
contracts: require('config/contracts/eth.json'),
|
||||
dPathFormats: {
|
||||
[SecureWalletName.TREZOR]: ETH_TREZOR,
|
||||
[SecureWalletName.LEDGER_NANO_S]: ETH_LEDGER,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: ETH_DEFAULT
|
||||
}
|
||||
},
|
||||
Ropsten: {
|
||||
name: 'Ropsten',
|
||||
unit: 'ETH',
|
||||
chainId: 3,
|
||||
isCustom: false,
|
||||
color: '#adc101',
|
||||
blockExplorer: makeExplorer('Etherscan', 'https://ropsten.etherscan.io'),
|
||||
tokens: require('config/tokens/ropsten.json'),
|
||||
contracts: require('config/contracts/ropsten.json'),
|
||||
isTestnet: true,
|
||||
dPathFormats: {
|
||||
[SecureWalletName.TREZOR]: ETH_TESTNET,
|
||||
[SecureWalletName.LEDGER_NANO_S]: ETH_TESTNET,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: ETH_TESTNET
|
||||
}
|
||||
},
|
||||
Kovan: {
|
||||
name: 'Kovan',
|
||||
unit: 'ETH',
|
||||
chainId: 42,
|
||||
isCustom: false,
|
||||
color: '#adc101',
|
||||
blockExplorer: makeExplorer('Etherscan', 'https://kovan.etherscan.io'),
|
||||
tokens: require('config/tokens/ropsten.json'),
|
||||
contracts: require('config/contracts/ropsten.json'),
|
||||
isTestnet: true,
|
||||
dPathFormats: {
|
||||
[SecureWalletName.TREZOR]: ETH_TESTNET,
|
||||
[SecureWalletName.LEDGER_NANO_S]: ETH_TESTNET,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: ETH_TESTNET
|
||||
}
|
||||
},
|
||||
Rinkeby: {
|
||||
name: 'Rinkeby',
|
||||
unit: 'ETH',
|
||||
chainId: 4,
|
||||
isCustom: false,
|
||||
color: '#adc101',
|
||||
blockExplorer: makeExplorer('Etherscan', 'https://rinkeby.etherscan.io'),
|
||||
tokens: require('config/tokens/rinkeby.json'),
|
||||
contracts: require('config/contracts/rinkeby.json'),
|
||||
isTestnet: true,
|
||||
dPathFormats: {
|
||||
[SecureWalletName.TREZOR]: ETH_TESTNET,
|
||||
[SecureWalletName.LEDGER_NANO_S]: ETH_TESTNET,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: ETH_TESTNET
|
||||
}
|
||||
},
|
||||
ETC: {
|
||||
name: 'ETC',
|
||||
unit: 'ETC',
|
||||
chainId: 61,
|
||||
isCustom: false,
|
||||
color: '#669073',
|
||||
blockExplorer: makeExplorer('GasTracker', 'https://gastracker.io'),
|
||||
tokens: require('config/tokens/etc.json'),
|
||||
contracts: require('config/contracts/etc.json'),
|
||||
dPathFormats: {
|
||||
[SecureWalletName.TREZOR]: ETC_TREZOR,
|
||||
[SecureWalletName.LEDGER_NANO_S]: ETC_LEDGER,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: ETC_TREZOR
|
||||
}
|
||||
},
|
||||
UBQ: {
|
||||
name: 'UBQ',
|
||||
unit: 'UBQ',
|
||||
chainId: 8,
|
||||
isCustom: false,
|
||||
color: '#b37aff',
|
||||
blockExplorer: makeExplorer('Ubiqscan', 'https://ubiqscan.io/en'),
|
||||
tokens: require('config/tokens/ubq.json'),
|
||||
contracts: require('config/contracts/ubq.json'),
|
||||
dPathFormats: {
|
||||
[SecureWalletName.TREZOR]: UBQ_DEFAULT,
|
||||
[SecureWalletName.LEDGER_NANO_S]: UBQ_DEFAULT,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: UBQ_DEFAULT
|
||||
}
|
||||
},
|
||||
EXP: {
|
||||
name: 'EXP',
|
||||
unit: 'EXP',
|
||||
chainId: 2,
|
||||
isCustom: false,
|
||||
color: '#673ab7',
|
||||
blockExplorer: makeExplorer('Gander', 'https://www.gander.tech'),
|
||||
tokens: require('config/tokens/exp.json'),
|
||||
contracts: require('config/contracts/exp.json'),
|
||||
dPathFormats: {
|
||||
[SecureWalletName.TREZOR]: EXP_DEFAULT,
|
||||
[SecureWalletName.LEDGER_NANO_S]: EXP_DEFAULT,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: EXP_DEFAULT
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const expectedState = {
|
||||
initialState: expectedInitialState
|
||||
};
|
||||
import { INITIAL_STATE, staticNetworks } from 'reducers/config/networks/staticNetworks';
|
||||
|
||||
describe('Testing contained data', () => {
|
||||
it(`contain unique chainIds`, () => {
|
||||
const networkValues = Object.values(expectedInitialState);
|
||||
const networkValues = Object.values(INITIAL_STATE);
|
||||
const chainIds = networkValues.map(a => a.chainId);
|
||||
const chainIdsSet = new Set(chainIds);
|
||||
expect(Array.from(chainIdsSet).length).toEqual(chainIds.length);
|
||||
|
@ -142,8 +12,6 @@ describe('Testing contained data', () => {
|
|||
describe('static networks reducer', () => {
|
||||
it('should return the initial state', () =>
|
||||
expect(JSON.stringify(staticNetworks(undefined, {} as any))).toEqual(
|
||||
JSON.stringify(expectedState.initialState)
|
||||
JSON.stringify(INITIAL_STATE)
|
||||
));
|
||||
});
|
||||
|
||||
export { expectedState as staticNetworksExpectedState };
|
||||
|
|
|
@ -18,6 +18,7 @@ describe('gas reducer', () => {
|
|||
fast: 4,
|
||||
fastest: 20,
|
||||
time: Date.now(),
|
||||
chainId: 1,
|
||||
isDefault: false
|
||||
};
|
||||
const state = gas(undefined, setGasEstimates(estimates));
|
||||
|
|
|
@ -4,8 +4,13 @@ import { cloneableGenerator } from 'redux-saga/utils';
|
|||
import { fetchGasEstimates, GasEstimates } from 'api/gas';
|
||||
import { setGasEstimates } from 'actions/gas';
|
||||
import { getEstimates } from 'selectors/gas';
|
||||
import { getOffline } from 'selectors/config';
|
||||
import { getOffline, getNetworkConfig } from 'selectors/config';
|
||||
import { gasPriceDefaults, gasEstimateCacheTime } from 'config';
|
||||
import { staticNetworks } from 'reducers/config/networks/staticNetworks';
|
||||
|
||||
const networkState = staticNetworks(undefined, {} as any);
|
||||
const network = networkState.ETH;
|
||||
const nonEstimateNetwork = networkState.ETC;
|
||||
|
||||
describe('fetchEstimates*', () => {
|
||||
const gen = cloneableGenerator(fetchEstimates)();
|
||||
|
@ -16,24 +21,42 @@ describe('fetchEstimates*', () => {
|
|||
fast: 4,
|
||||
fastest: 20,
|
||||
time: Date.now() - gasEstimateCacheTime - 1000,
|
||||
chainId: network.chainId,
|
||||
isDefault: false
|
||||
};
|
||||
const newEstimates: GasEstimates = {
|
||||
const newTimeEstimates: GasEstimates = {
|
||||
safeLow: 2,
|
||||
standard: 2,
|
||||
fast: 8,
|
||||
fastest: 80,
|
||||
time: Date.now(),
|
||||
chainId: network.chainId,
|
||||
isDefault: false
|
||||
};
|
||||
const newChainIdEstimates: GasEstimates = {
|
||||
...oldEstimates,
|
||||
chainId: network.chainId + 1
|
||||
};
|
||||
|
||||
it('Should select getOffline', () => {
|
||||
expect(gen.next().value).toEqual(select(getOffline));
|
||||
it('Should select getNetworkConfig', () => {
|
||||
expect(gen.next().value).toEqual(select(getNetworkConfig));
|
||||
});
|
||||
|
||||
it('Should use default estimates if offline', () => {
|
||||
it('Should use network default gas price settings if network shouldn’t estimate', () => {
|
||||
const noEstimateGen = gen.clone();
|
||||
expect(noEstimateGen.next(nonEstimateNetwork).value).toEqual(
|
||||
call(setDefaultEstimates, nonEstimateNetwork)
|
||||
);
|
||||
expect(noEstimateGen.next().done).toBeTruthy();
|
||||
});
|
||||
|
||||
it('Should select getOffline', () => {
|
||||
expect(gen.next(network).value).toEqual(select(getOffline));
|
||||
});
|
||||
|
||||
it('Should use network default gas price settings if offline', () => {
|
||||
const offlineGen = gen.clone();
|
||||
expect(offlineGen.next(true).value).toEqual(call(setDefaultEstimates));
|
||||
expect(offlineGen.next(true).value).toEqual(call(setDefaultEstimates, network));
|
||||
expect(offlineGen.next().done).toBeTruthy();
|
||||
});
|
||||
|
||||
|
@ -59,32 +82,67 @@ describe('fetchEstimates*', () => {
|
|||
const failedReqGen = gen.clone();
|
||||
// Not sure why, but typescript seems to think throw might be missing.
|
||||
if (failedReqGen.throw) {
|
||||
expect(failedReqGen.throw('test').value).toEqual(call(setDefaultEstimates));
|
||||
expect(failedReqGen.throw('test').value).toEqual(call(setDefaultEstimates, network));
|
||||
expect(failedReqGen.next().done).toBeTruthy();
|
||||
} else {
|
||||
throw new Error('SagaIterator didn’t have throw');
|
||||
}
|
||||
});
|
||||
|
||||
it('Should use new estimates if chainId changed, even if time is similar', () => {
|
||||
const newChainGen = gen.clone();
|
||||
expect(newChainGen.next(newChainIdEstimates).value).toEqual(
|
||||
put(setGasEstimates(newChainIdEstimates))
|
||||
);
|
||||
expect(newChainGen.next().done).toBeTruthy();
|
||||
});
|
||||
|
||||
it('Should use fetched estimates', () => {
|
||||
expect(gen.next(newEstimates).value).toEqual(put(setGasEstimates(newEstimates)));
|
||||
expect(gen.next(newTimeEstimates).value).toEqual(put(setGasEstimates(newTimeEstimates)));
|
||||
expect(gen.next().done).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('setDefaultEstimates*', () => {
|
||||
const gen = cloneableGenerator(setDefaultEstimates)();
|
||||
const time = Date.now();
|
||||
|
||||
it('Should put setGasEstimates with config defaults', () => {
|
||||
const time = Date.now();
|
||||
const gen = setDefaultEstimates(network);
|
||||
gen.next();
|
||||
expect(gen.next(time).value).toEqual(
|
||||
put(
|
||||
setGasEstimates({
|
||||
safeLow: gasPriceDefaults.minGwei,
|
||||
standard: gasPriceDefaults.default,
|
||||
fast: gasPriceDefaults.default,
|
||||
fastest: gasPriceDefaults.maxGwei,
|
||||
safeLow: network.gasPriceSettings.min,
|
||||
standard: network.gasPriceSettings.initial,
|
||||
fast: network.gasPriceSettings.initial,
|
||||
fastest: network.gasPriceSettings.max,
|
||||
chainId: network.chainId,
|
||||
isDefault: true,
|
||||
time
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('Should use config defaults if network has no defaults', () => {
|
||||
const customNetwork = {
|
||||
isCustom: true as true,
|
||||
name: 'Custon',
|
||||
unit: 'CST',
|
||||
chainId: 123,
|
||||
dPathFormats: null
|
||||
};
|
||||
const gen = setDefaultEstimates(customNetwork);
|
||||
|
||||
gen.next();
|
||||
expect(gen.next(time).value).toEqual(
|
||||
put(
|
||||
setGasEstimates({
|
||||
safeLow: gasPriceDefaults.min,
|
||||
standard: gasPriceDefaults.initial,
|
||||
fast: gasPriceDefaults.initial,
|
||||
fastest: gasPriceDefaults.max,
|
||||
chainId: customNetwork.chainId,
|
||||
isDefault: true,
|
||||
time
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue