parent
112d18e316
commit
0a7dfcd55d
@ -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 |
||||
} |
@ -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 } |
||||
} |
@ -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 } |
||||
} |
@ -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), |
||||
}, |
||||
] |
||||
} |
||||
|
@ -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 |
||||
) |
||||
}) |
||||
}) |
||||
|
||||
}) |
||||
|
||||
}) |
Loading…
Reference in new issue