diff --git a/common/api/gas.ts b/common/api/gas.ts index d61df701..1f0400c1 100644 --- a/common/api/gas.ts +++ b/common/api/gas.ts @@ -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 { .then((res: RawGasEstimates) => ({ ...res, time: Date.now(), + chainId: 1, isDefault: false })); } diff --git a/common/components/Header/components/GasPriceDropdown.tsx b/common/components/Header/components/GasPriceDropdown.tsx deleted file mode 100644 index dc355042..00000000 --- a/common/components/Header/components/GasPriceDropdown.tsx +++ /dev/null @@ -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 { - constructor(props: Props) { - super(props); - this.updateGasPrice = throttle(this.updateGasPrice, 50); - } - - public render() { - return ( - - ); - } - - private renderLabel = () => { - return ( - - Gas Price: {this.props.gasPrice.raw} Gwei - - ); - }; - - private renderOptions = () => { - return ( -
-
- Gas Price: {this.props.gasPrice.raw} Gwei - -

Not So Fast

-

Fast

-

Fast AF

-

- Gas Price is the amount you pay per unit of gas.{' '} - TX fee = gas price * gas limit & is paid to miners for including your TX in - a block. Higher the gas price = faster transaction, but more expensive. Default is{' '} - 21 GWEI. -

-

- Read more -

-
-
- ); - }; - - private updateGasPrice = (value: string) => { - this.props.onChange({ raw: value, value: gasPricetoBase(parseInt(value, 10)) }); - }; - - private handleGasPriceChange = (e: React.FormEvent) => { - this.updateGasPrice(e.currentTarget.value); - }; -} - -const mapStateToProps = (state: AppState): StateProps => ({ gasPrice: getGasPrice(state) }); - -export default connect(mapStateToProps)(GasPriceDropdown); diff --git a/common/components/TXMetaDataPanel/TXMetaDataPanel.tsx b/common/components/TXMetaDataPanel/TXMetaDataPanel.tsx index 22d56dfa..05432376 100644 --- a/common/components/TXMetaDataPanel/TXMetaDataPanel.tsx +++ b/common/components/TXMetaDataPanel/TXMetaDataPanel.tsx @@ -125,8 +125,10 @@ class TXMetaDataPanel extends React.Component { }; 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 } }); diff --git a/common/components/TXMetaDataPanel/components/SimpleGas.tsx b/common/components/TXMetaDataPanel/components/SimpleGas.tsx index 6ae51845..8b6fa1e8 100644 --- a/common/components/TXMetaDataPanel/components/SimpleGas.tsx +++ b/common/components/TXMetaDataPanel/components/SimpleGas.tsx @@ -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 { public componentDidMount() { - this.fixGasPrice(this.props.gasPrice); + this.fixGasPrice(); this.props.fetchGasEstimates(); } @@ -63,8 +63,8 @@ class SimpleGas extends React.Component { } = 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 { 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 { 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()); } } diff --git a/common/config/constants.ts b/common/config/constants.ts index b1a932bb..4ee74c8e 100644 --- a/common/config/constants.ts +++ b/common/config/constants.ts @@ -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; diff --git a/common/config/data.tsx b/common/config/data.tsx index bf388b63..1c0ed729 100644 --- a/common/config/data.tsx +++ b/common/config/data.tsx @@ -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; diff --git a/common/reducers/config/networks/staticNetworks.ts b/common/reducers/config/networks/staticNetworks.ts index c6c749c0..c29cad75 100644 --- a/common/reducers/config/networks/staticNetworks.ts +++ b/common/reducers/config/networks/staticNetworks.ts @@ -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 } } }; diff --git a/common/reducers/transaction/fields/fields.ts b/common/reducers/transaction/fields/fields.ts index 4bba6fc3..a4e065a2 100644 --- a/common/reducers/transaction/fields/fields.ts +++ b/common/reducers/transaction/fields/fields.ts @@ -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, action: FieldAction) => ({ diff --git a/common/sagas/gas.ts b/common/sagas/gas.ts index 35e7dfe6..8abd3d43 100644 --- a/common/sagas/gas.ts +++ b/common/sagas/gas.ts @@ -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); } } diff --git a/shared/types/network.d.ts b/shared/types/network.d.ts index 756253a6..cba0ced9 100644 --- a/shared/types/network.d.ts +++ b/shared/types/network.d.ts @@ -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 { diff --git a/spec/reducers/config/networks/staticNetworks.spec.ts b/spec/reducers/config/networks/staticNetworks.spec.ts index 08158e56..e804d413 100644 --- a/spec/reducers/config/networks/staticNetworks.spec.ts +++ b/spec/reducers/config/networks/staticNetworks.spec.ts @@ -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 }; diff --git a/spec/reducers/gas.spec.ts b/spec/reducers/gas.spec.ts index bb814627..600f47ee 100644 --- a/spec/reducers/gas.spec.ts +++ b/spec/reducers/gas.spec.ts @@ -18,6 +18,7 @@ describe('gas reducer', () => { fast: 4, fastest: 20, time: Date.now(), + chainId: 1, isDefault: false }; const state = gas(undefined, setGasEstimates(estimates)); diff --git a/spec/sagas/gas.spec.ts b/spec/sagas/gas.spec.ts index 4042ce7c..421fecb1 100644 --- a/spec/sagas/gas.spec.ts +++ b/spec/sagas/gas.spec.ts @@ -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 })