mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-10-22 11:22:43 +02:00
Connect the gas-button-group component to redux and a live api.
This commit is contained in:
parent
112d18e316
commit
0a7dfcd55d
@ -5,6 +5,7 @@ import classnames from 'classnames'
|
|||||||
export default class ButtonGroup extends PureComponent {
|
export default class ButtonGroup extends PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
defaultActiveButtonIndex: PropTypes.number,
|
defaultActiveButtonIndex: PropTypes.number,
|
||||||
|
noButtonActiveByDefault: PropTypes.bool,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
children: PropTypes.array,
|
children: PropTypes.array,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
@ -16,7 +17,9 @@ export default class ButtonGroup extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
activeButtonIndex: this.props.defaultActiveButtonIndex || 0,
|
activeButtonIndex: this.props.noButtonActiveByDefault
|
||||||
|
? null
|
||||||
|
: this.props.defaultActiveButtonIndex || 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
handleButtonClick (activeButtonIndex) {
|
handleButtonClick (activeButtonIndex) {
|
||||||
|
@ -15,7 +15,13 @@ export default class BasicTabContent extends Component {
|
|||||||
return (
|
return (
|
||||||
<div className="basic-tab-content">
|
<div className="basic-tab-content">
|
||||||
<div className="basic-tab-content__title">Suggest gas fee increases</div>
|
<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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,9 @@ export default class GasModalPageContainer extends Component {
|
|||||||
|
|
||||||
renderBasicTabContent () {
|
renderBasicTabContent () {
|
||||||
return (
|
return (
|
||||||
<BasicTabContent gasPriceButtonGroupProps={this.props.gasPriceButtonGroupProps} />
|
<BasicTabContent
|
||||||
|
gasPriceButtonGroupProps={this.props.gasPriceButtonGroupProps}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,45 +4,23 @@ import { hideModal } from '../../../actions'
|
|||||||
import {
|
import {
|
||||||
setCustomGasPrice,
|
setCustomGasPrice,
|
||||||
setCustomGasLimit,
|
setCustomGasLimit,
|
||||||
} from '../../../ducks/custom-gas'
|
} from '../../../ducks/gas.duck'
|
||||||
import {
|
import {
|
||||||
getCustomGasPrice,
|
getCustomGasPrice,
|
||||||
getCustomGasLimit,
|
getCustomGasLimit,
|
||||||
|
getRenderableBasicEstimateData,
|
||||||
|
getBasicGasEstimateLoadingStatus,
|
||||||
} from '../../../selectors/custom-gas'
|
} 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 mapStateToProps = state => {
|
||||||
|
const buttonDataLoading = getBasicGasEstimateLoadingStatus(state)
|
||||||
return {
|
return {
|
||||||
customGasPrice: getCustomGasPrice(state),
|
customGasPrice: getCustomGasPrice(state),
|
||||||
customGasLimit: getCustomGasLimit(state),
|
customGasLimit: getCustomGasLimit(state),
|
||||||
gasPriceButtonGroupProps: mockGasPriceButtonGroupProps,
|
gasPriceButtonGroupProps: {
|
||||||
|
buttonDataLoading,
|
||||||
|
gasButtonInfo: getRenderableBasicEstimateData(state),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,6 +29,7 @@ const mapDispatchToProps = dispatch => {
|
|||||||
hideModal: () => dispatch(hideModal()),
|
hideModal: () => dispatch(hideModal()),
|
||||||
updateCustomGasPrice: (newPrice) => dispatch(setCustomGasPrice(newPrice)),
|
updateCustomGasPrice: (newPrice) => dispatch(setCustomGasPrice(newPrice)),
|
||||||
updateCustomGasLimit: (newLimit) => dispatch(setCustomGasLimit(newLimit)),
|
updateCustomGasLimit: (newLimit) => dispatch(setCustomGasLimit(newLimit)),
|
||||||
|
handleGasPriceSelection: newPrice => console.log('NewPrice: ', newPrice),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,9 +25,11 @@ proxyquire('../gas-modal-page-container.container.js', {
|
|||||||
'../../../selectors/custom-gas': {
|
'../../../selectors/custom-gas': {
|
||||||
getCustomGasPrice: (s) => `mockGasPrice:${s}`,
|
getCustomGasPrice: (s) => `mockGasPrice:${s}`,
|
||||||
getCustomGasLimit: (s) => `mockGasLimit:${s}`,
|
getCustomGasLimit: (s) => `mockGasLimit:${s}`,
|
||||||
|
getBasicGasEstimateLoadingStatus: (s) => `mockBasicGasEstimateLoadingStatus:${s}`,
|
||||||
|
getRenderableBasicEstimateData: (s) => `mockRenderableBasicEstimateData:${s}`,
|
||||||
},
|
},
|
||||||
'../../../actions': actionSpies,
|
'../../../actions': actionSpies,
|
||||||
'../../../ducks/custom-gas': customGasActionSpies,
|
'../../../ducks/gas.duck': customGasActionSpies,
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('gas-modal-page-container container', () => {
|
describe('gas-modal-page-container container', () => {
|
||||||
@ -37,6 +39,8 @@ describe('gas-modal-page-container container', () => {
|
|||||||
it('should map the correct properties to props', () => {
|
it('should map the correct properties to props', () => {
|
||||||
assert.equal(mapStateToProps('mockState').customGasPrice, 'mockGasPrice:mockState')
|
assert.equal(mapStateToProps('mockState').customGasPrice, 'mockGasPrice:mockState')
|
||||||
assert.equal(mapStateToProps('mockState').customGasLimit, 'mockGasLimit:mockState')
|
assert.equal(mapStateToProps('mockState').customGasLimit, 'mockGasLimit:mockState')
|
||||||
|
assert.equal(mapStateToProps('mockState').gasPriceButtonGroupProps.buttonDataLoading, 'mockBasicGasEstimateLoadingStatus:mockState')
|
||||||
|
assert.equal(mapStateToProps('mockState').gasPriceButtonGroupProps.gasButtonInfo, 'mockRenderableBasicEstimateData:mockState')
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
14
ui/app/components/gas-customization/gas.selectors.js
Normal file
14
ui/app/components/gas-customization/gas.selectors.js
Normal file
@ -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
|
||||||
|
}
|
@ -162,6 +162,10 @@ export default class SendTransactionScreen extends PersistentForm {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
this.props.fetchGasEstimates()
|
||||||
|
}
|
||||||
|
|
||||||
componentWillMount () {
|
componentWillMount () {
|
||||||
const {
|
const {
|
||||||
from: { address },
|
from: { address },
|
||||||
|
@ -36,6 +36,9 @@ import {
|
|||||||
resetSendState,
|
resetSendState,
|
||||||
updateSendErrors,
|
updateSendErrors,
|
||||||
} from '../../ducks/send.duck'
|
} from '../../ducks/send.duck'
|
||||||
|
import {
|
||||||
|
fetchGasEstimates,
|
||||||
|
} from '../../ducks/gas.duck'
|
||||||
import {
|
import {
|
||||||
calcGasTotal,
|
calcGasTotal,
|
||||||
} from './send.utils.js'
|
} from './send.utils.js'
|
||||||
@ -104,5 +107,6 @@ function mapDispatchToProps (dispatch) {
|
|||||||
scanQrCode: () => dispatch(showQrScanner(SEND_ROUTE)),
|
scanQrCode: () => dispatch(showQrScanner(SEND_ROUTE)),
|
||||||
qrCodeDetected: (data) => dispatch(qrCodeDetected(data)),
|
qrCodeDetected: (data) => dispatch(qrCodeDetected(data)),
|
||||||
updateSendTo: (to, nickname) => dispatch(updateSendTo(to, nickname)),
|
updateSendTo: (to, nickname) => dispatch(updateSendTo(to, nickname)),
|
||||||
|
fetchGasEstimates: () => dispatch(fetchGasEstimates()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ const propsMethodSpies = {
|
|||||||
updateSendErrors: sinon.spy(),
|
updateSendErrors: sinon.spy(),
|
||||||
updateSendTokenBalance: sinon.spy(),
|
updateSendTokenBalance: sinon.spy(),
|
||||||
resetSendState: sinon.spy(),
|
resetSendState: sinon.spy(),
|
||||||
|
fetchGasEstimates: sinon.spy(),
|
||||||
}
|
}
|
||||||
const utilsMethodStubs = {
|
const utilsMethodStubs = {
|
||||||
getAmountErrorObject: sinon.stub().returns({ amount: 'mockAmountError' }),
|
getAmountErrorObject: sinon.stub().returns({ amount: 'mockAmountError' }),
|
||||||
@ -37,6 +38,7 @@ describe('Send Component', function () {
|
|||||||
blockGasLimit={'mockBlockGasLimit'}
|
blockGasLimit={'mockBlockGasLimit'}
|
||||||
conversionRate={10}
|
conversionRate={10}
|
||||||
editingTransactionId={'mockEditingTransactionId'}
|
editingTransactionId={'mockEditingTransactionId'}
|
||||||
|
fetchGasEstimates={propsMethodSpies.fetchGasEstimates}
|
||||||
from={ { address: 'mockAddress', balance: 'mockBalance' } }
|
from={ { address: 'mockAddress', balance: 'mockBalance' } }
|
||||||
gasLimit={'mockGasLimit'}
|
gasLimit={'mockGasLimit'}
|
||||||
gasPrice={'mockGasPrice'}
|
gasPrice={'mockGasPrice'}
|
||||||
@ -63,6 +65,7 @@ describe('Send Component', function () {
|
|||||||
utilsMethodStubs.doesAmountErrorRequireUpdate.resetHistory()
|
utilsMethodStubs.doesAmountErrorRequireUpdate.resetHistory()
|
||||||
utilsMethodStubs.getAmountErrorObject.resetHistory()
|
utilsMethodStubs.getAmountErrorObject.resetHistory()
|
||||||
utilsMethodStubs.getGasFeeErrorObject.resetHistory()
|
utilsMethodStubs.getGasFeeErrorObject.resetHistory()
|
||||||
|
propsMethodSpies.fetchGasEstimates.resetHistory()
|
||||||
propsMethodSpies.updateAndSetGasTotal.resetHistory()
|
propsMethodSpies.updateAndSetGasTotal.resetHistory()
|
||||||
propsMethodSpies.updateSendErrors.resetHistory()
|
propsMethodSpies.updateSendErrors.resetHistory()
|
||||||
propsMethodSpies.updateSendTokenBalance.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', () => {
|
describe('componentWillUnmount', () => {
|
||||||
it('should call this.props.resetSendState', () => {
|
it('should call this.props.resetSendState', () => {
|
||||||
propsMethodSpies.resetSendState.resetHistory()
|
propsMethodSpies.resetSendState.resetHistory()
|
||||||
|
@ -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 }
|
|
||||||
}
|
|
189
ui/app/ducks/gas.duck.js
Normal file
189
ui/app/ducks/gas.duck.js
Normal file
@ -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 }
|
||||||
|
}
|
@ -95,7 +95,7 @@ export function formatCurrency (value, currencyCode) {
|
|||||||
const upperCaseCurrencyCode = currencyCode.toUpperCase()
|
const upperCaseCurrencyCode = currencyCode.toUpperCase()
|
||||||
|
|
||||||
return currencies.find(currency => currency.code === upperCaseCurrencyCode)
|
return currencies.find(currency => currency.code === upperCaseCurrencyCode)
|
||||||
? currencyFormatter.format(Number(value), { code: upperCaseCurrencyCode })
|
? currencyFormatter.format(Number(value), { code: upperCaseCurrencyCode, style: 'currency' })
|
||||||
: value
|
: value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ const reduceApp = require('./reducers/app')
|
|||||||
const reduceLocale = require('./reducers/locale')
|
const reduceLocale = require('./reducers/locale')
|
||||||
const reduceSend = require('./ducks/send.duck').default
|
const reduceSend = require('./ducks/send.duck').default
|
||||||
import reduceConfirmTransaction from './ducks/confirm-transaction.duck'
|
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
|
window.METAMASK_CACHED_LOG_STATE = null
|
||||||
|
|
||||||
@ -50,7 +50,7 @@ function rootReducer (state, action) {
|
|||||||
|
|
||||||
state.confirmTransaction = reduceConfirmTransaction(state, action)
|
state.confirmTransaction = reduceConfirmTransaction(state, action)
|
||||||
|
|
||||||
state.customGas = reduceCustomGas(state, action)
|
state.gas = reduceGas(state, action)
|
||||||
|
|
||||||
window.METAMASK_CACHED_LOG_STATE = state
|
window.METAMASK_CACHED_LOG_STATE = state
|
||||||
return state
|
return 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 = {
|
const selectors = {
|
||||||
getCustomGasPrice,
|
|
||||||
getCustomGasLimit,
|
|
||||||
getCustomGasErrors,
|
getCustomGasErrors,
|
||||||
|
getCustomGasLimit,
|
||||||
|
getCustomGasPrice,
|
||||||
|
getCustomGasTotal,
|
||||||
|
getRenderableBasicEstimateData,
|
||||||
|
getBasicGasEstimateLoadingStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = selectors
|
module.exports = selectors
|
||||||
|
|
||||||
function getCustomGasPrice (state) {
|
function getCustomGasErrors (state) {
|
||||||
return state.customGas.price
|
return state.gas.errors
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCustomGasLimit (state) {
|
function getCustomGasLimit (state) {
|
||||||
return state.customGas.limit
|
return state.gas.customData.limit
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCustomGasErrors (state) {
|
function getCustomGasPrice (state) {
|
||||||
return state.customGas.errors
|
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),
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
140
ui/app/selectors/tests/custom-gas.test.js
Normal file
140
ui/app/selectors/tests/custom-gas.test.js
Normal file
@ -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
Block a user