From 0a7dfcd55d02a7204d8f0773ff9d91f325aabea8 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Thu, 16 Aug 2018 09:58:27 -0230 Subject: Connect the gas-button-group component to redux and a live api. --- .../button-group/button-group.component.js | 5 +- .../basic-tab-content.component.js | 8 +- .../gas-modal-page-container.component.js | 4 +- .../gas-modal-page-container.container.js | 39 +---- .../gas-modal-page-container-container.test.js | 6 +- .../components/gas-customization/gas.selectors.js | 14 ++ ui/app/components/send/send.component.js | 4 + ui/app/components/send/send.container.js | 4 + .../components/send/tests/send-component.test.js | 12 ++ ui/app/ducks/custom-gas.js | 67 -------- ui/app/ducks/gas.duck.js | 189 +++++++++++++++++++++ ui/app/helpers/confirm-transaction/util.js | 2 +- ui/app/reducers.js | 4 +- ui/app/selectors/custom-gas.js | 186 +++++++++++++++++++- ui/app/selectors/tests/custom-gas.test.js | 140 +++++++++++++++ 15 files changed, 573 insertions(+), 111 deletions(-) create mode 100644 ui/app/components/gas-customization/gas.selectors.js delete mode 100644 ui/app/ducks/custom-gas.js create mode 100644 ui/app/ducks/gas.duck.js create mode 100644 ui/app/selectors/tests/custom-gas.test.js diff --git a/ui/app/components/button-group/button-group.component.js b/ui/app/components/button-group/button-group.component.js index f99f710ce..905bbe9d2 100644 --- a/ui/app/components/button-group/button-group.component.js +++ b/ui/app/components/button-group/button-group.component.js @@ -5,6 +5,7 @@ import classnames from 'classnames' export default class ButtonGroup extends PureComponent { static propTypes = { defaultActiveButtonIndex: PropTypes.number, + noButtonActiveByDefault: PropTypes.bool, disabled: PropTypes.bool, children: PropTypes.array, className: PropTypes.string, @@ -16,7 +17,9 @@ export default class ButtonGroup extends PureComponent { } state = { - activeButtonIndex: this.props.defaultActiveButtonIndex || 0, + activeButtonIndex: this.props.noButtonActiveByDefault + ? null + : this.props.defaultActiveButtonIndex || 0, } handleButtonClick (activeButtonIndex) { diff --git a/ui/app/components/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js b/ui/app/components/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js index 751042871..99ef28b5e 100644 --- a/ui/app/components/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js +++ b/ui/app/components/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js @@ -15,7 +15,13 @@ export default class BasicTabContent extends Component { return (
Suggest gas fee increases
- + console.log('New Price:', newPrice)} + />
) } diff --git a/ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js b/ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js index 9a0070b2a..399520baf 100644 --- a/ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js +++ b/ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js @@ -23,7 +23,9 @@ export default class GasModalPageContainer extends Component { renderBasicTabContent () { return ( - + ) } diff --git a/ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js b/ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js index 2354d578c..ebdd035ea 100644 --- a/ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js +++ b/ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js @@ -4,45 +4,23 @@ import { hideModal } from '../../../actions' import { setCustomGasPrice, setCustomGasLimit, -} from '../../../ducks/custom-gas' +} from '../../../ducks/gas.duck' import { getCustomGasPrice, getCustomGasLimit, + getRenderableBasicEstimateData, + getBasicGasEstimateLoadingStatus, } from '../../../selectors/custom-gas' -const mockGasPriceButtonGroupProps = { - buttonDataLoading: false, - className: 'gas-price-button-group', - gasButtonInfo: [ - { - feeInPrimaryCurrency: '$0.52', - feeInSecondaryCurrency: '0.0048 ETH', - timeEstimate: '~ 1 min 0 sec', - priceInHexWei: '0xa1b2c3f', - }, - { - feeInPrimaryCurrency: '$0.39', - feeInSecondaryCurrency: '0.004 ETH', - timeEstimate: '~ 1 min 30 sec', - priceInHexWei: '0xa1b2c39', - }, - { - feeInPrimaryCurrency: '$0.30', - feeInSecondaryCurrency: '0.00354 ETH', - timeEstimate: '~ 2 min 1 sec', - priceInHexWei: '0xa1b2c30', - }, - ], - handleGasPriceSelection: newPrice => console.log('NewPrice: ', newPrice), - noButtonActiveByDefault: true, - showCheck: true, -} - const mapStateToProps = state => { + const buttonDataLoading = getBasicGasEstimateLoadingStatus(state) return { customGasPrice: getCustomGasPrice(state), customGasLimit: getCustomGasLimit(state), - gasPriceButtonGroupProps: mockGasPriceButtonGroupProps, + gasPriceButtonGroupProps: { + buttonDataLoading, + gasButtonInfo: getRenderableBasicEstimateData(state), + }, } } @@ -51,6 +29,7 @@ const mapDispatchToProps = dispatch => { hideModal: () => dispatch(hideModal()), updateCustomGasPrice: (newPrice) => dispatch(setCustomGasPrice(newPrice)), updateCustomGasLimit: (newLimit) => dispatch(setCustomGasLimit(newLimit)), + handleGasPriceSelection: newPrice => console.log('NewPrice: ', newPrice), } } diff --git a/ui/app/components/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js b/ui/app/components/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js index 119ae640b..4067265b5 100644 --- a/ui/app/components/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js +++ b/ui/app/components/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js @@ -25,9 +25,11 @@ proxyquire('../gas-modal-page-container.container.js', { '../../../selectors/custom-gas': { getCustomGasPrice: (s) => `mockGasPrice:${s}`, getCustomGasLimit: (s) => `mockGasLimit:${s}`, + getBasicGasEstimateLoadingStatus: (s) => `mockBasicGasEstimateLoadingStatus:${s}`, + getRenderableBasicEstimateData: (s) => `mockRenderableBasicEstimateData:${s}`, }, '../../../actions': actionSpies, - '../../../ducks/custom-gas': customGasActionSpies, + '../../../ducks/gas.duck': customGasActionSpies, }) describe('gas-modal-page-container container', () => { @@ -37,6 +39,8 @@ describe('gas-modal-page-container container', () => { it('should map the correct properties to props', () => { assert.equal(mapStateToProps('mockState').customGasPrice, 'mockGasPrice:mockState') assert.equal(mapStateToProps('mockState').customGasLimit, 'mockGasLimit:mockState') + assert.equal(mapStateToProps('mockState').gasPriceButtonGroupProps.buttonDataLoading, 'mockBasicGasEstimateLoadingStatus:mockState') + assert.equal(mapStateToProps('mockState').gasPriceButtonGroupProps.gasButtonInfo, 'mockRenderableBasicEstimateData:mockState') }) }) diff --git a/ui/app/components/gas-customization/gas.selectors.js b/ui/app/components/gas-customization/gas.selectors.js new file mode 100644 index 000000000..89374b5f1 --- /dev/null +++ b/ui/app/components/gas-customization/gas.selectors.js @@ -0,0 +1,14 @@ +const selectors = { + getCurrentBlockTime, + getBasicGasEstimateLoadingStatus, +} + +module.exports = selectors + +function getCurrentBlockTime (state) { + return state.gas.currentBlockTime +} + +function getBasicGasEstimateLoadingStatus (state) { + return state.gas.basicEstimateIsLoading +} diff --git a/ui/app/components/send/send.component.js b/ui/app/components/send/send.component.js index a27401f30..a639c24b0 100644 --- a/ui/app/components/send/send.component.js +++ b/ui/app/components/send/send.component.js @@ -162,6 +162,10 @@ export default class SendTransactionScreen extends PersistentForm { } } + componentDidMount () { + this.props.fetchGasEstimates() + } + componentWillMount () { const { from: { address }, diff --git a/ui/app/components/send/send.container.js b/ui/app/components/send/send.container.js index 87056499f..a00fb3545 100644 --- a/ui/app/components/send/send.container.js +++ b/ui/app/components/send/send.container.js @@ -36,6 +36,9 @@ import { resetSendState, updateSendErrors, } from '../../ducks/send.duck' +import { + fetchGasEstimates, +} from '../../ducks/gas.duck' import { calcGasTotal, } from './send.utils.js' @@ -104,5 +107,6 @@ function mapDispatchToProps (dispatch) { scanQrCode: () => dispatch(showQrScanner(SEND_ROUTE)), qrCodeDetected: (data) => dispatch(qrCodeDetected(data)), updateSendTo: (to, nickname) => dispatch(updateSendTo(to, nickname)), + fetchGasEstimates: () => dispatch(fetchGasEstimates()), } } diff --git a/ui/app/components/send/tests/send-component.test.js b/ui/app/components/send/tests/send-component.test.js index bd136a0b8..0f3902c07 100644 --- a/ui/app/components/send/tests/send-component.test.js +++ b/ui/app/components/send/tests/send-component.test.js @@ -13,6 +13,7 @@ const propsMethodSpies = { updateSendErrors: sinon.spy(), updateSendTokenBalance: sinon.spy(), resetSendState: sinon.spy(), + fetchGasEstimates: sinon.spy(), } const utilsMethodStubs = { getAmountErrorObject: sinon.stub().returns({ amount: 'mockAmountError' }), @@ -37,6 +38,7 @@ describe('Send Component', function () { blockGasLimit={'mockBlockGasLimit'} conversionRate={10} editingTransactionId={'mockEditingTransactionId'} + fetchGasEstimates={propsMethodSpies.fetchGasEstimates} from={ { address: 'mockAddress', balance: 'mockBalance' } } gasLimit={'mockGasLimit'} gasPrice={'mockGasPrice'} @@ -63,6 +65,7 @@ describe('Send Component', function () { utilsMethodStubs.doesAmountErrorRequireUpdate.resetHistory() utilsMethodStubs.getAmountErrorObject.resetHistory() utilsMethodStubs.getGasFeeErrorObject.resetHistory() + propsMethodSpies.fetchGasEstimates.resetHistory() propsMethodSpies.updateAndSetGasTotal.resetHistory() propsMethodSpies.updateSendErrors.resetHistory() propsMethodSpies.updateSendTokenBalance.resetHistory() @@ -82,6 +85,15 @@ describe('Send Component', function () { }) }) + describe('componentDidMount', () => { + it('should call props.fetchGasEstimates', () => { + propsMethodSpies.fetchGasEstimates.resetHistory() + assert.equal(propsMethodSpies.fetchGasEstimates.callCount, 0) + wrapper.instance().componentDidMount() + assert.equal(propsMethodSpies.fetchGasEstimates.callCount, 1) + }) + }) + describe('componentWillUnmount', () => { it('should call this.props.resetSendState', () => { propsMethodSpies.resetSendState.resetHistory() diff --git a/ui/app/ducks/custom-gas.js b/ui/app/ducks/custom-gas.js deleted file mode 100644 index f1f483e93..000000000 --- a/ui/app/ducks/custom-gas.js +++ /dev/null @@ -1,67 +0,0 @@ -import extend from 'xtend' - -// Actions -const SET_CUSTOM_GAS_PRICE = 'metamask/custom-gas/SET_CUSTOM_GAS_PRICE' -const SET_CUSTOM_GAS_LIMIT = 'metamask/custom-gas/SET_CUSTOM_GAS_LIMIT' -const SET_CUSTOM_GAS_ERRORS = 'metamask/custom-gas/SET_CUSTOM_GAS_ERRORS' -const RESET_CUSTOM_GAS_STATE = 'metamask/custom-gas/RESET_CUSTOM_GAS_STATE' - -// TODO: determine if this approach to initState is consistent with conventional ducks pattern -const initState = { - price: 0, - limit: 21000, - errors: {}, -} - -// Reducer -export default function reducer ({ customGas: customGasState = initState }, action = {}) { - const newState = extend({}, customGasState) - - switch (action.type) { - case SET_CUSTOM_GAS_PRICE: - return extend(newState, { - price: action.value, - }) - case SET_CUSTOM_GAS_LIMIT: - return extend(newState, { - limit: action.value, - }) - case SET_CUSTOM_GAS_ERRORS: - return extend(newState, { - errors: { - ...newState.errors, - ...action.value, - }, - }) - case RESET_CUSTOM_GAS_STATE: - return extend({}, initState) - default: - return newState - } -} - -// Action Creators -export function setCustomGasPrice (newPrice) { - return { - type: SET_CUSTOM_GAS_PRICE, - value: newPrice, - } -} - -export function setCustomGasLimit (newLimit) { - return { - type: SET_CUSTOM_GAS_LIMIT, - value: newLimit, - } -} - -export function setCustomGasErrors (newErrors) { - return { - type: SET_CUSTOM_GAS_ERRORS, - value: newErrors, - } -} - -export function resetCustomGasState () { - return { type: RESET_CUSTOM_GAS_STATE } -} diff --git a/ui/app/ducks/gas.duck.js b/ui/app/ducks/gas.duck.js new file mode 100644 index 000000000..8b2fbcfdb --- /dev/null +++ b/ui/app/ducks/gas.duck.js @@ -0,0 +1,189 @@ +import { clone } from 'ramda' + +// Actions +const BASIC_GAS_ESTIMATE_LOADING_FINISHED = 'metamask/gas/BASIC_GAS_ESTIMATE_LOADING_FINISHED' +const BASIC_GAS_ESTIMATE_LOADING_STARTED = 'metamask/gas/BASIC_GAS_ESTIMATE_LOADING_STARTED' +const RESET_CUSTOM_GAS_STATE = 'metamask/gas/RESET_CUSTOM_GAS_STATE' +const SET_BASIC_GAS_ESTIMATE_DATA = 'metamask/gas/SET_BASIC_GAS_ESTIMATE_DATA' +const SET_CUSTOM_GAS_ERRORS = 'metamask/gas/SET_CUSTOM_GAS_ERRORS' +const SET_CUSTOM_GAS_LIMIT = 'metamask/gas/SET_CUSTOM_GAS_LIMIT' +const SET_CUSTOM_GAS_PRICE = 'metamask/gas/SET_CUSTOM_GAS_PRICE' +const SET_CUSTOM_GAS_TOTAL = 'metamask/gas/SET_CUSTOM_GAS_TOTAL' + +// TODO: determine if this approach to initState is consistent with conventional ducks pattern +const initState = { + customData: { + price: 0, + limit: 21000, + }, + basicEstimates: { + average: null, + fastestWait: null, + fastWait: null, + fast: null, + safeLowWait: null, + blockNum: null, + avgWait: null, + blockTime: null, + speed: null, + fastest: null, + safeLow: null, + }, + basicEstimateIsLoading: true, + errors: {}, +} + +// Reducer +export default function reducer ({ gas: gasState = initState }, action = {}) { + const newState = clone(gasState) + + switch (action.type) { + case BASIC_GAS_ESTIMATE_LOADING_STARTED: + return { + ...newState, + basicEstimateIsLoading: true, + } + case BASIC_GAS_ESTIMATE_LOADING_FINISHED: + return { + ...newState, + basicEstimateIsLoading: false, + } + case SET_BASIC_GAS_ESTIMATE_DATA: + return { + ...newState, + basicEstimates: action.value, + } + case SET_CUSTOM_GAS_PRICE: + return { + ...newState, + customData: { + ...newState.customData, + price: action.value, + }, + } + case SET_CUSTOM_GAS_LIMIT: + return { + ...newState, + customData: { + ...newState.customData, + limit: action.value, + }, + } + case SET_CUSTOM_GAS_TOTAL: + return { + ...newState, + customData: { + ...newState.customData, + total: action.value, + }, + } + case SET_CUSTOM_GAS_ERRORS: + return { + ...newState, + errors: { + ...newState.errors, + ...action.value, + }, + } + case RESET_CUSTOM_GAS_STATE: + return clone(initState) + default: + return newState + } +} + +// Action Creators +export function basicGasEstimatesLoadingStarted () { + return { + type: BASIC_GAS_ESTIMATE_LOADING_STARTED, + } +} + +export function basicGasEstimatesLoadingFinished () { + return { + type: BASIC_GAS_ESTIMATE_LOADING_FINISHED, + } +} + +export function fetchGasEstimates () { + return (dispatch) => { + dispatch(basicGasEstimatesLoadingStarted()) + + return fetch('https://ethgasstation.info/json/ethgasAPI.json', { + 'headers': {}, + 'referrer': 'http://ethgasstation.info/json/', + 'referrerPolicy': 'no-referrer-when-downgrade', + 'body': null, + 'method': 'GET', + 'mode': 'cors'} + ) + .then(r => r.json()) + .then(({ + average, + avgWait, + block_time: blockTime, + blockNum, + fast, + fastest, + fastestWait, + fastWait, + safeLow, + safeLowWait, + speed, + }) => { + dispatch(setBasicGasEstimateData({ + average, + avgWait, + blockTime, + blockNum, + fast, + fastest, + fastestWait, + fastWait, + safeLow, + safeLowWait, + speed, + })) + dispatch(basicGasEstimatesLoadingFinished()) + }) + } +} + +export function setBasicGasEstimateData (basicGasEstimateData) { + return { + type: SET_BASIC_GAS_ESTIMATE_DATA, + value: basicGasEstimateData, + } +} + +export function setCustomGasPrice (newPrice) { + return { + type: SET_CUSTOM_GAS_PRICE, + value: newPrice, + } +} + +export function setCustomGasLimit (newLimit) { + return { + type: SET_CUSTOM_GAS_LIMIT, + value: newLimit, + } +} + +export function setCustomGasTotal (newTotal) { + return { + type: SET_CUSTOM_GAS_TOTAL, + value: newTotal, + } +} + +export function setCustomGasErrors (newErrors) { + return { + type: SET_CUSTOM_GAS_ERRORS, + value: newErrors, + } +} + +export function resetCustomGasState () { + return { type: RESET_CUSTOM_GAS_STATE } +} diff --git a/ui/app/helpers/confirm-transaction/util.js b/ui/app/helpers/confirm-transaction/util.js index eb334a4b8..0451824e8 100644 --- a/ui/app/helpers/confirm-transaction/util.js +++ b/ui/app/helpers/confirm-transaction/util.js @@ -95,7 +95,7 @@ export function formatCurrency (value, currencyCode) { const upperCaseCurrencyCode = currencyCode.toUpperCase() return currencies.find(currency => currency.code === upperCaseCurrencyCode) - ? currencyFormatter.format(Number(value), { code: upperCaseCurrencyCode }) + ? currencyFormatter.format(Number(value), { code: upperCaseCurrencyCode, style: 'currency' }) : value } diff --git a/ui/app/reducers.js b/ui/app/reducers.js index 7234c3c02..786af853d 100644 --- a/ui/app/reducers.js +++ b/ui/app/reducers.js @@ -10,7 +10,7 @@ const reduceApp = require('./reducers/app') const reduceLocale = require('./reducers/locale') const reduceSend = require('./ducks/send.duck').default import reduceConfirmTransaction from './ducks/confirm-transaction.duck' -import reduceCustomGas from './ducks/custom-gas' +import reduceGas from './ducks/gas.duck' window.METAMASK_CACHED_LOG_STATE = null @@ -50,7 +50,7 @@ function rootReducer (state, action) { state.confirmTransaction = reduceConfirmTransaction(state, action) - state.customGas = reduceCustomGas(state, action) + state.gas = reduceGas(state, action) window.METAMASK_CACHED_LOG_STATE = state return state diff --git a/ui/app/selectors/custom-gas.js b/ui/app/selectors/custom-gas.js index 97280190f..ff2fd0e05 100644 --- a/ui/app/selectors/custom-gas.js +++ b/ui/app/selectors/custom-gas.js @@ -1,19 +1,191 @@ +import { pipe, partialRight } from 'ramda' +import { + getConversionRate, + getGasLimit, +} from '../components/send/send.selectors' +import { + conversionUtil, + multiplyCurrencies, +} from '../conversion-util' +import { + getCurrentCurrency, +} from '../selectors' +import { + formatCurrency, +} from '../helpers/confirm-transaction/util' +import { + calcGasTotal, +} from '../components/send/send.utils' +import { addHexPrefix } from 'ethereumjs-util' + const selectors = { - getCustomGasPrice, - getCustomGasLimit, getCustomGasErrors, + getCustomGasLimit, + getCustomGasPrice, + getCustomGasTotal, + getRenderableBasicEstimateData, + getBasicGasEstimateLoadingStatus, } module.exports = selectors -function getCustomGasPrice (state) { - return state.customGas.price +function getCustomGasErrors (state) { + return state.gas.errors } function getCustomGasLimit (state) { - return state.customGas.limit + return state.gas.customData.limit } -function getCustomGasErrors (state) { - return state.customGas.errors +function getCustomGasPrice (state) { + return state.gas.customData.price +} + +function getCustomGasTotal (state) { + return state.gas.customData.total +} + +function getBasicGasEstimateLoadingStatus (state) { + return state.gas.basicEstimateIsLoading +} + + +function apiEstimateModifiedToGWEI (estimate) { + return multiplyCurrencies(estimate, 0.10, { + toNumericBase: 'hex', + multiplicandBase: 10, + multiplierBase: 10, + numberOfDecimals: 9, + }) +} + +function basicPriceEstimateToETHTotal (estimate, gasLimit) { + return conversionUtil(calcGasTotal(gasLimit, estimate), { + fromNumericBase: 'hex', + toNumericBase: 'dec', + fromDenomination: 'GWEI', + numberOfDecimals: 9, + }) +} + +function ethTotalToConvertedCurrency (ethTotal, convertedCurrency, conversionRate) { + return conversionUtil(ethTotal, { + fromNumericBase: 'dec', + toNumericBase: 'dec', + fromCurrency: 'ETH', + toCurrency: convertedCurrency, + numberOfDecimals: 2, + conversionRate, + }) +} + +function formatETHFee (ethFee) { + return ethFee + ' ETH' +} + +function getRenderableEthFee (estimate, gasLimit) { + return pipe( + apiEstimateModifiedToGWEI, + partialRight(basicPriceEstimateToETHTotal, [gasLimit]), + formatETHFee + )(estimate, gasLimit) +} + +function getRenderableConvertedCurrencyFee (estimate, gasLimit, convertedCurrency, conversionRate) { + return pipe( + apiEstimateModifiedToGWEI, + partialRight(basicPriceEstimateToETHTotal, [gasLimit]), + partialRight(ethTotalToConvertedCurrency, [convertedCurrency, conversionRate]), + partialRight(formatCurrency, [convertedCurrency]) + )(estimate, gasLimit, convertedCurrency, conversionRate) +} + +function getTimeEstimateInSeconds (blockWaitEstimate, currentBlockTime) { + return multiplyCurrencies(blockWaitEstimate, currentBlockTime, { + toNumericBase: 'dec', + multiplicandBase: 10, + multiplierBase: 10, + numberOfDecimals: 1, + }) +} + +function formatTimeEstimate (totalSeconds) { + const minutes = Math.floor(totalSeconds / 60) + const seconds = Math.floor(totalSeconds % 60) + const formattedMin = `${minutes ? minutes + ' min' : ''}` + const formattedSec = `${seconds ? seconds + ' sec' : ''}` + const formattedCombined = formattedMin && formattedSec + ? `~${formattedMin} ${formattedSec}` + : '~' + [formattedMin, formattedSec].find(t => t) + + return formattedCombined +} + +function getRenderableTimeEstimate (blockWaitEstimate, currentBlockTime) { + return pipe( + getTimeEstimateInSeconds, + formatTimeEstimate + )(blockWaitEstimate, currentBlockTime) +} + +function priceEstimateToWei (priceEstimate) { + return conversionUtil(priceEstimate, { + fromNumericBase: 'hex', + toNumericBase: 'hex', + fromDenomination: 'GWEI', + toDenomination: 'WEI', + numberOfDecimals: 9, + }) +} + +function getGasPriceInHexWei (price) { + return pipe( + apiEstimateModifiedToGWEI, + priceEstimateToWei, + addHexPrefix + )(price) +} + +function getRenderableBasicEstimateData (state) { + if (getBasicGasEstimateLoadingStatus(state)) { + return [] + } + + const gasLimit = getGasLimit(state) + const conversionRate = getConversionRate(state) + const currentCurrency = getCurrentCurrency(state) + const { + gas: { + basicEstimates: { + safeLow, + average, + fast, + blockTime, + safeLowWait, + avgWait, + fastWait, + }, + }, + } = state + + return [ + { + feeInPrimaryCurrency: getRenderableConvertedCurrencyFee(fast, gasLimit, currentCurrency, conversionRate), + feeInSecondaryCurrency: getRenderableEthFee(fast, gasLimit), + timeEstimate: getRenderableTimeEstimate(fastWait, blockTime), + priceInHexWei: getGasPriceInHexWei(fast), + }, + { + feeInPrimaryCurrency: getRenderableConvertedCurrencyFee(average, gasLimit, currentCurrency, conversionRate), + feeInSecondaryCurrency: getRenderableEthFee(average, gasLimit), + timeEstimate: getRenderableTimeEstimate(avgWait, blockTime), + priceInHexWei: getGasPriceInHexWei(average), + }, + { + feeInPrimaryCurrency: getRenderableConvertedCurrencyFee(safeLow, gasLimit, currentCurrency, conversionRate), + feeInSecondaryCurrency: getRenderableEthFee(safeLow, gasLimit), + timeEstimate: getRenderableTimeEstimate(safeLowWait, blockTime), + priceInHexWei: getGasPriceInHexWei(safeLow), + }, + ] } diff --git a/ui/app/selectors/tests/custom-gas.test.js b/ui/app/selectors/tests/custom-gas.test.js new file mode 100644 index 000000000..d53c1ec98 --- /dev/null +++ b/ui/app/selectors/tests/custom-gas.test.js @@ -0,0 +1,140 @@ +import assert from 'assert' +import proxyquire from 'proxyquire' + +const { + getCustomGasErrors, + getCustomGasLimit, + getCustomGasPrice, + getCustomGasTotal, + getRenderableBasicEstimateData, +} = proxyquire('../custom-gas', {}) + +describe('custom-gas selectors', () => { + + describe('getCustomGasPrice()', () => { + it('should return gas.customData.price', () => { + const mockState = { gas: { customData: { price: 'mockPrice' } } } + assert.equal(getCustomGasPrice(mockState), 'mockPrice') + }) + }) + + describe('getCustomGasLimit()', () => { + it('should return gas.customData.limit', () => { + const mockState = { gas: { customData: { limit: 'mockLimit' } } } + assert.equal(getCustomGasLimit(mockState), 'mockLimit') + }) + }) + + describe('getCustomGasTotal()', () => { + it('should return gas.customData.total', () => { + const mockState = { gas: { customData: { total: 'mockTotal' } } } + assert.equal(getCustomGasTotal(mockState), 'mockTotal') + }) + }) + + describe('getCustomGasErrors()', () => { + it('should return gas.errors', () => { + const mockState = { gas: { errors: 'mockErrors' } } + assert.equal(getCustomGasErrors(mockState), 'mockErrors') + }) + }) + + describe('getRenderableBasicEstimateData()', () => { + const tests = [ + { + expectedResult: [ + { + feeInPrimaryCurrency: '$0.05', + feeInSecondaryCurrency: '0.00021 ETH', + timeEstimate: '~7 sec', + priceInHexWei: '0x2540be400', + }, + { + feeInPrimaryCurrency: '$0.03', + feeInSecondaryCurrency: '0.000105 ETH', + timeEstimate: '~46 sec', + priceInHexWei: '0x12a05f200', + }, + { + feeInPrimaryCurrency: '$0.01', + feeInSecondaryCurrency: '0.0000525 ETH', + timeEstimate: '~1 min 33 sec', + priceInHexWei: '0x9502f900', + }, + ], + mockState: { + metamask: { + conversionRate: 255.71, + currentCurrency: 'usd', + send: { + gasLimit: '0x5208', + }, + }, + gas: { + basicEstimates: { + blockTime: 14.16326530612245, + safeLow: 25, + safeLowWait: 6.6, + average: 50, + avgWait: 3.3, + fast: 100, + fastWait: 0.5, + }, + }, + }, + }, + { + expectedResult: [ + { + feeInPrimaryCurrency: '$1.07', + feeInSecondaryCurrency: '0.00042 ETH', + timeEstimate: '~14 sec', + priceInHexWei: '0x4a817c800', + }, + { + feeInPrimaryCurrency: '$0.54', + feeInSecondaryCurrency: '0.00021 ETH', + timeEstimate: '~1 min 33 sec', + priceInHexWei: '0x2540be400', + }, + { + feeInPrimaryCurrency: '$0.27', + feeInSecondaryCurrency: '0.000105 ETH', + timeEstimate: '~3 min 7 sec', + priceInHexWei: '0x12a05f200', + }, + ], + mockState: { + metamask: { + conversionRate: 2557.1, + currentCurrency: 'usd', + send: { + gasLimit: '0x5208', + }, + }, + gas: { + basicEstimates: { + blockTime: 14.16326530612245, + safeLow: 50, + safeLowWait: 13.2, + average: 100, + avgWait: 6.6, + fast: 200, + fastWait: 1.0, + }, + }, + }, + }, + ] + it('should return renderable data about basic estimates', () => { + tests.forEach(test => { + assert.deepEqual( + getRenderableBasicEstimateData(test.mockState), + test.expectedResult + ) + }) + }) + + }) + +}) -- cgit v1.2.3