aboutsummaryrefslogtreecommitdiffstats
path: root/ui/app
diff options
context:
space:
mode:
authorDan Miller <danjm.com@gmail.com>2018-08-16 20:28:27 +0800
committerDan Miller <danjm.com@gmail.com>2018-12-04 11:36:04 +0800
commit0a7dfcd55d02a7204d8f0773ff9d91f325aabea8 (patch)
treea4c7d3e219ca926f17f26d020fddc78854a23b53 /ui/app
parent112d18e316df312a648b8c8ac17c201314fc9ed6 (diff)
downloadtangerine-wallet-browser-0a7dfcd55d02a7204d8f0773ff9d91f325aabea8.tar.gz
tangerine-wallet-browser-0a7dfcd55d02a7204d8f0773ff9d91f325aabea8.tar.zst
tangerine-wallet-browser-0a7dfcd55d02a7204d8f0773ff9d91f325aabea8.zip
Connect the gas-button-group component to redux and a live api.
Diffstat (limited to 'ui/app')
-rw-r--r--ui/app/components/button-group/button-group.component.js5
-rw-r--r--ui/app/components/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js8
-rw-r--r--ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js4
-rw-r--r--ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js39
-rw-r--r--ui/app/components/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js6
-rw-r--r--ui/app/components/gas-customization/gas.selectors.js14
-rw-r--r--ui/app/components/send/send.component.js4
-rw-r--r--ui/app/components/send/send.container.js4
-rw-r--r--ui/app/components/send/tests/send-component.test.js12
-rw-r--r--ui/app/ducks/custom-gas.js67
-rw-r--r--ui/app/ducks/gas.duck.js189
-rw-r--r--ui/app/helpers/confirm-transaction/util.js2
-rw-r--r--ui/app/reducers.js4
-rw-r--r--ui/app/selectors/custom-gas.js186
-rw-r--r--ui/app/selectors/tests/custom-gas.test.js140
15 files changed, 573 insertions, 111 deletions
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 (
<div className="basic-tab-content">
<div className="basic-tab-content__title">Suggest gas fee increases</div>
- <GasPriceButtonGroup {...this.props.gasPriceButtonGroupProps} />
+ <GasPriceButtonGroup
+ {...this.props.gasPriceButtonGroupProps}
+ className="gas-price-button-group"
+ noButtonActiveByDefault={true}
+ showCheck={true}
+ handleGasPriceSelection={(newPrice) => console.log('New Price:', newPrice)}
+ />
</div>
)
}
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 (
- <BasicTabContent gasPriceButtonGroupProps={this.props.gasPriceButtonGroupProps} />
+ <BasicTabContent
+ gasPriceButtonGroupProps={this.props.gasPriceButtonGroupProps}
+ />
)
}
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
@@ -37,6 +37,9 @@ import {
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
+ )
+ })
+ })
+
+ })
+
+})