1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Switch gas price estimation in swaps to metaswap-api /gasPrices (#9599)

Adds swaps-gas-customization-modal and utilize in swaps

Remove swaps specific code from gas-modal-page-container/

Remove slow estimate data from swaps-gas-customization-modal.container

Use average as lower safe price limit in swaps-gas-customization-modal

Lint fix

Fix up unit tests

Update ui/app/ducks/swaps/swaps.js

Co-authored-by: Mark Stacey <markjstacey@gmail.com>

Remove stale properties from gas-modal-page-container.component.js

Replace use of isCustomPrice safe with isCustomSwapsGasPriceSafe, in swaps-gas-customization-modal

Remove use of averageIsSafe in isCustomPriceSafe function

Stop calling resetCustomGasState in swaps

Refactor 'setter' type actions and creators to 'event based', for swaps slice custom gas logic

Replace use of advanced-tab-content.component with advanceGasInputs in swaps gas customization component

Add validation for the gasPrices endpoint

swaps custom gas price should be considered safe if >= to average

Update renderDataSummary unit test

Lint fix

Remove customOnHideOpts for swapsGasCustomizationModal in modal.js

Better handling for swaps gas price loading and failure states

Improve semantics: isCustomSwapsGasPriceSafe renamed to isCustomSwapsGasPriceUnSafe

Mutate state directly in swaps gas slice reducer

Remove unused params

More reliable tracking of speed setting for Gas Fees Changed metrics event

Lint fix

Throw error when fetchSwapsGasPrices response is invalid

add disableSave and customTotalSupplement to swaps-gas-customization container return

Update ui/app/ducks/swaps/swaps.js

Co-authored-by: Mark Stacey <markjstacey@gmail.com>

Improve error handling in fetchMetaSwapsGasPriceEstimates

Remove metricsEvent from swaps-gas-customization-modal context

Base check of gas speed type in swaps-gas-customization-modal on gasEstimateType

Improve naming of variable and functions use to set customPriceIsSafe prop of AdvancedGasInputs in swaps-gas-customization-modal

Simplify sinon spy/stub code in gas-price-button-group-component.test.js

Remove unnecessary getSwapsFallbackGasPrice call in swaps-gas-customization-modal

Remove use of getSwapsTradeTxParams and clean up related gas price logic in swaps

Improve validator of SWAP_GAS_PRICE_VALIDATOR

Ensure default tradeValue
This commit is contained in:
Dan J Miller 2020-11-04 12:44:08 -03:30 committed by GitHub
parent ad838df3e6
commit a0d7c71011
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 788 additions and 292 deletions

View File

@ -25,7 +25,7 @@ export default class AdvancedTabContent extends Component {
isSpeedUp: PropTypes.bool,
isEthereumNetwork: PropTypes.bool,
customGasLimitMessage: PropTypes.string,
minimumGasLimit: PropTypes.number.isRequired,
minimumGasLimit: PropTypes.number,
}
renderDataSummary(transactionFee, timeRemaining) {

View File

@ -103,7 +103,7 @@ describe('AdvancedTabContent Component', function () {
const renderDataSummaryArgs = AdvancedTabContent.prototype.renderDataSummary.getCall(
0,
).args
assert.deepEqual(renderDataSummaryArgs, ['$0.25', 21500])
assert.deepEqual(renderDataSummaryArgs, ['$0.25', '21500'])
})
})

View File

@ -2,8 +2,6 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types'
import PageContainer from '../../../ui/page-container'
import { Tabs, Tab } from '../../../ui/tabs'
import { calcGasTotal } from '../../../../pages/send/send.utils'
import { sumHexWEIsToRenderableFiat } from '../../../../helpers/utils/conversions.util'
import AdvancedTabContent from './advanced-tab-content'
import BasicTabContent from './basic-tab-content'
@ -32,10 +30,6 @@ export default class GasModalPageContainer extends Component {
newTotalEth: PropTypes.string,
sendAmount: PropTypes.string,
transactionFee: PropTypes.string,
extraInfoRow: PropTypes.shape({
label: PropTypes.string,
value: PropTypes.string,
}),
}),
onSubmit: PropTypes.func,
customModalGasPriceInHex: PropTypes.string,
@ -47,16 +41,6 @@ export default class GasModalPageContainer extends Component {
isRetry: PropTypes.bool,
disableSave: PropTypes.bool,
isEthereumNetwork: PropTypes.bool,
customGasLimitMessage: PropTypes.string,
customTotalSupplement: PropTypes.string,
isSwap: PropTypes.bool,
value: PropTypes.string,
conversionRate: PropTypes.number,
minimumGasLimit: PropTypes.number.isRequired,
}
state = {
selectedTab: 'Basic',
}
componentDidMount() {
@ -92,8 +76,6 @@ export default class GasModalPageContainer extends Component {
isRetry,
infoRowProps: { transactionFee },
isEthereumNetwork,
customGasLimitMessage,
minimumGasLimit,
} = this.props
return (
@ -102,7 +84,6 @@ export default class GasModalPageContainer extends Component {
updateCustomGasLimit={updateCustomGasLimit}
customModalGasPriceInHex={customModalGasPriceInHex}
customModalGasLimitInHex={customModalGasLimitInHex}
customGasLimitMessage={customGasLimitMessage}
timeRemaining={currentTimeEstimate}
transactionFee={transactionFee}
gasChartProps={gasChartProps}
@ -112,18 +93,11 @@ export default class GasModalPageContainer extends Component {
isSpeedUp={isSpeedUp}
isRetry={isRetry}
isEthereumNetwork={isEthereumNetwork}
minimumGasLimit={minimumGasLimit}
/>
)
}
renderInfoRows(
newTotalFiat,
newTotalEth,
sendAmount,
transactionFee,
extraInfoRow,
) {
renderInfoRows(newTotalFiat, newTotalEth, sendAmount, transactionFee) {
return (
<div className="gas-modal-content__info-row-wrapper">
<div className="gas-modal-content__info-row">
@ -143,16 +117,6 @@ export default class GasModalPageContainer extends Component {
{transactionFee}
</span>
</div>
{extraInfoRow && (
<div className="gas-modal-content__info-row__transaction-info">
<span className="gas-modal-content__info-row__transaction-info__label">
{extraInfoRow.label}
</span>
<span className="gas-modal-content__info-row__transaction-info__value">
{extraInfoRow.value}
</span>
</div>
)}
<div className="gas-modal-content__info-row__total-info">
<span className="gas-modal-content__info-row__total-info__label">
{this.context.t('newTotal')}
@ -175,13 +139,7 @@ export default class GasModalPageContainer extends Component {
const {
gasPriceButtonGroupProps,
hideBasic,
infoRowProps: {
newTotalFiat,
newTotalEth,
sendAmount,
transactionFee,
extraInfoRow,
},
infoRowProps: { newTotalFiat, newTotalEth, sendAmount, transactionFee },
} = this.props
let tabsToRender = [
@ -200,7 +158,7 @@ export default class GasModalPageContainer extends Component {
}
return (
<Tabs onTabClick={(tabName) => this.setState({ selectedTab: tabName })}>
<Tabs>
{tabsToRender.map(({ name, content }, i) => (
<Tab name={name} key={`gas-modal-tab-${i}`}>
<div className="gas-modal-content">
@ -210,7 +168,6 @@ export default class GasModalPageContainer extends Component {
newTotalEth,
sendAmount,
transactionFee,
extraInfoRow,
)}
</div>
</Tab>
@ -248,45 +205,7 @@ export default class GasModalPageContainer extends Component {
},
})
}
if (this.props.isSwap) {
const newSwapGasTotal = calcGasTotal(
customModalGasLimitInHex,
customModalGasPriceInHex,
)
let speedSet = ''
if (this.state.selectedTab === 'Basic') {
const { gasButtonInfo } = this.props.gasPriceButtonGroupProps
const selectedGasButtonInfo = gasButtonInfo.find(
({ priceInHexWei }) =>
priceInHexWei === customModalGasPriceInHex,
)
speedSet = selectedGasButtonInfo?.gasEstimateType || ''
}
this.context.trackEvent({
event: 'Gas Fees Changed',
category: 'swaps',
properties: {
speed_set: speedSet,
gas_mode: this.state.selectedTab,
gas_fees: sumHexWEIsToRenderableFiat(
[
this.props.value,
newSwapGasTotal,
this.props.customTotalSupplement,
],
'usd',
this.props.conversionRate,
)?.slice(1),
},
})
}
onSubmit(
customModalGasLimitInHex,
customModalGasPriceInHex,
this.state.selectedTab,
this.context.mixPanelTrack,
)
onSubmit(customModalGasLimitInHex, customModalGasPriceInHex)
}}
submitText={this.context.t('save')}
headerCloseText={this.context.t('close')}

View File

@ -11,7 +11,6 @@ import {
updateSendAmount,
setGasTotal,
updateTransaction,
setSwapsTxGasParams,
} from '../../../../store/actions'
import {
setCustomGasPrice,
@ -67,19 +66,9 @@ import GasModalPageContainer from './gas-modal-page-container.component'
const mapStateToProps = (state, ownProps) => {
const { currentNetworkTxList, send } = state.metamask
const { modalState: { props: modalProps } = {} } = state.appState.modal || {}
const {
txData = {},
isSwap = false,
customGasLimitMessage = '',
customTotalSupplement = '',
extraInfoRow = null,
useFastestButtons = false,
minimumGasLimit = Number(MIN_GAS_LIMIT_DEC),
} = modalProps || {}
const { txData = {} } = modalProps || {}
const { transaction = {} } = ownProps
const selectedTransaction = isSwap
? txData
: currentNetworkTxList.find(
const selectedTransaction = currentNetworkTxList.find(
({ id }) => id === (transaction.id || txData.id),
)
const buttonDataLoading = getBasicGasEstimateLoadingStatus(state)
@ -107,13 +96,12 @@ const mapStateToProps = (state, ownProps) => {
const gasButtonInfo = getRenderableBasicEstimateData(
state,
customModalGasLimitInHex,
useFastestButtons,
)
const currentCurrency = getCurrentCurrency(state)
const conversionRate = getConversionRate(state)
const newTotalFiat = sumHexWEIsToRenderableFiat(
[value, customGasTotal, customTotalSupplement],
[value, customGasTotal],
currentCurrency,
conversionRate,
)
@ -137,11 +125,7 @@ const mapStateToProps = (state, ownProps) => {
const newTotalEth =
maxModeOn && !isSendTokenSet
? sumHexWEIsToRenderableEth([balance, '0x0'])
: sumHexWEIsToRenderableEth([
value,
customGasTotal,
customTotalSupplement,
])
: sumHexWEIsToRenderableEth([value, customGasTotal])
const sendAmount =
maxModeOn && !isSendTokenSet
@ -171,7 +155,6 @@ const mapStateToProps = (state, ownProps) => {
return {
hideBasic,
isConfirm: isConfirm(state),
isSwap,
customModalGasPriceInHex,
customModalGasLimitInHex,
customGasPrice,
@ -180,7 +163,7 @@ const mapStateToProps = (state, ownProps) => {
newTotalFiat,
currentTimeEstimate,
blockTime: getBasicGasEstimateBlockTime(state),
customPriceIsSafe: isCustomPriceSafe(state, isSwap),
customPriceIsSafe: isCustomPriceSafe(state),
maxModeOn,
gasPriceButtonGroupProps: {
buttonDataLoading,
@ -199,20 +182,15 @@ const mapStateToProps = (state, ownProps) => {
},
infoRowProps: {
originalTotalFiat: sumHexWEIsToRenderableFiat(
[value, customGasTotal, customTotalSupplement],
[value, customGasTotal],
currentCurrency,
conversionRate,
),
originalTotalEth: sumHexWEIsToRenderableEth([
value,
customGasTotal,
customTotalSupplement,
]),
originalTotalEth: sumHexWEIsToRenderableEth([value, customGasTotal]),
newTotalFiat: showFiat ? newTotalFiat : '',
newTotalEth,
transactionFee: sumHexWEIsToRenderableEth(['0x0', customGasTotal]),
sendAmount,
extraInfoRow,
},
transaction: txData || transaction,
isSpeedUp: transaction.status === 'submitted',
@ -225,11 +203,8 @@ const mapStateToProps = (state, ownProps) => {
sendToken,
balance,
tokenBalance: getTokenBalance(state),
customGasLimitMessage,
conversionRate,
value,
customTotalSupplement,
minimumGasLimit,
}
}
@ -271,9 +246,6 @@ const mapDispatchToProps = (dispatch) => {
dispatch(updateSendErrors({ amount: null }))
dispatch(updateSendAmount(calcMaxAmount(maxAmountDataObject)))
},
updateSwapTxGas: (gasLimit, gasPrice) => {
dispatch(setSwapsTxGasParams(gasLimit, gasPrice))
},
}
}
@ -282,7 +254,6 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
gasPriceButtonGroupProps,
// eslint-disable-next-line no-shadow
isConfirm,
isSwap,
txId,
isSpeedUp,
isRetry,
@ -295,7 +266,6 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
tokenBalance,
customGasLimit,
transaction,
minimumGasLimit,
} = stateProps
const {
hideGasButtonGroup: dispatchHideGasButtonGroup,
@ -307,7 +277,6 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
cancelAndClose: dispatchCancelAndClose,
hideModal: dispatchHideModal,
setAmountToMax: dispatchSetAmountToMax,
updateSwapTxGas: dispatchUpdateSwapTxGas,
...otherDispatchProps
} = dispatchProps
@ -316,10 +285,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
...otherDispatchProps,
...ownProps,
onSubmit: (gasLimit, gasPrice) => {
if (isSwap) {
dispatchUpdateSwapTxGas(gasLimit, gasPrice)
dispatchHideModal()
} else if (isConfirm) {
if (isConfirm) {
const updatedTx = {
...transaction,
txParams: {
@ -365,7 +331,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
disableSave:
insufficientBalance ||
(isSpeedUp && customGasPrice === 0) ||
customGasLimit < minimumGasLimit,
customGasLimit < Number(MIN_GAS_LIMIT_DEC),
}
}

View File

@ -194,14 +194,12 @@ describe('GasModalPageContainer Component', function () {
'mockNewTotalEth',
'mockSendAmount',
'mockTransactionFee',
{ label: 'mockLabel', value: 'mockValue' },
])
assert.deepEqual(GP.renderInfoRows.getCall(1).args, [
'mockNewTotalFiat',
'mockNewTotalEth',
'mockSendAmount',
'mockTransactionFee',
{ label: 'mockLabel', value: 'mockValue' },
])
})

View File

@ -63,8 +63,6 @@ describe('gas-modal-page-container container', function () {
txData: {
id: 34,
},
extraInfoRow: { label: 'mockLabel', value: 'mockValue' },
minimumGasLimit: 21000,
},
},
},
@ -131,8 +129,6 @@ describe('gas-modal-page-container container', function () {
newTotalFiat: '637.41',
blockTime: 12,
conversionRate: 50,
customGasLimitMessage: '',
customTotalSupplement: '',
customModalGasLimitInHex: 'aaaaaaaa',
customModalGasPriceInHex: 'ffffffff',
customGasTotal: 'aaaaaaa955555556',
@ -152,7 +148,6 @@ describe('gas-modal-page-container container', function () {
gasEstimatesLoading: false,
hideBasic: true,
infoRowProps: {
extraInfoRow: { label: 'mockLabel', value: 'mockValue' },
originalTotalFiat: '637.41',
originalTotalEth: '12.748189 ETH',
newTotalFiat: '637.41',
@ -163,7 +158,6 @@ describe('gas-modal-page-container container', function () {
insufficientBalance: true,
isSpeedUp: false,
isRetry: false,
isSwap: false,
txId: 34,
isEthereumNetwork: true,
isMainnet: true,
@ -174,7 +168,6 @@ describe('gas-modal-page-container container', function () {
id: 34,
},
value: '0x640000000000000',
minimumGasLimit: 21000,
}
const baseMockOwnProps = { transaction: { id: 34 } }
const tests = [

View File

@ -93,7 +93,12 @@ export default class GasPriceButtonGroup extends Component {
) {
return (
<Button
onClick={() => handleGasPriceSelection(priceInHexWei)}
onClick={() =>
handleGasPriceSelection(
priceInHexWei,
renderableGasInfo.gasEstimateType,
)
}
key={`gas-price-button-${index}`}
>
{this.renderButtonContent(

View File

@ -3,6 +3,7 @@ import React from 'react'
import sinon from 'sinon'
import shallow from '../../../../../../lib/shallow-with-context'
import GasPriceButtonGroup from '../gas-price-button-group.component'
import { GAS_ESTIMATE_TYPES } from '../../../../../helpers/constants/common'
import ButtonGroup from '../../../../ui/button-group'
@ -17,18 +18,21 @@ describe('GasPriceButtonGroup Component', function () {
className: 'gas-price-button-group',
gasButtonInfo: [
{
gasEstimateType: GAS_ESTIMATE_TYPES.SLOW,
feeInPrimaryCurrency: '$0.52',
feeInSecondaryCurrency: '0.0048 ETH',
timeEstimate: '~ 1 min 0 sec',
priceInHexWei: '0xa1b2c3f',
},
{
gasEstimateType: GAS_ESTIMATE_TYPES.AVERAGE,
feeInPrimaryCurrency: '$0.39',
feeInSecondaryCurrency: '0.004 ETH',
timeEstimate: '~ 1 min 30 sec',
priceInHexWei: '0xa1b2c39',
},
{
gasEstimateType: GAS_ESTIMATE_TYPES.FAST,
feeInPrimaryCurrency: '$0.30',
feeInSecondaryCurrency: '0.00354 ETH',
timeEstimate: '~ 2 min 1 sec',
@ -105,7 +109,9 @@ describe('GasPriceButtonGroup Component', function () {
beforeEach(function () {
GasPriceButtonGroup.prototype.renderButtonContent.resetHistory()
const renderButtonResult = GasPriceButtonGroup.prototype.renderButton(
const renderButtonResult = wrapper
.instance()
.renderButton(
{ ...mockGasPriceButtonGroupProps.gasButtonInfo[0] },
mockButtonPropsAndFlags,
)
@ -128,7 +134,10 @@ describe('GasPriceButtonGroup Component', function () {
)
assert.deepEqual(
mockGasPriceButtonGroupProps.handleGasPriceSelection.getCall(0).args,
[mockGasPriceButtonGroupProps.gasButtonInfo[0].priceInHexWei],
[
mockGasPriceButtonGroupProps.gasButtonInfo[0].priceInHexWei,
mockGasPriceButtonGroupProps.gasButtonInfo[0].gasEstimateType,
],
)
})
@ -141,12 +150,14 @@ describe('GasPriceButtonGroup Component', function () {
feeInPrimaryCurrency,
feeInSecondaryCurrency,
timeEstimate,
gasEstimateType,
} = mockGasPriceButtonGroupProps.gasButtonInfo[0]
const { showCheck, className } = mockGasPriceButtonGroupProps
assert.deepEqual(
GasPriceButtonGroup.prototype.renderButtonContent.getCall(0).args,
[
{
gasEstimateType,
feeInPrimaryCurrency,
feeInSecondaryCurrency,
timeEstimate,

View File

@ -10,6 +10,7 @@ import { ENVIRONMENT_TYPE_POPUP } from '../../../../../app/scripts/lib/enums'
// Modal Components
import ConfirmCustomizeGasModal from '../gas-customization/gas-modal-page-container'
import SwapsGasCustomizationModal from '../../../pages/swaps/swaps-gas-customization-modal'
import DepositEtherModal from './deposit-ether-modal'
import AccountDetailsModal from './account-details-modal'
import ExportPrivateKeyModal from './export-private-key-modal'
@ -272,6 +273,31 @@ const MODALS = {
},
},
CUSTOMIZE_METASWAP_GAS: {
contents: <SwapsGasCustomizationModal />,
mobileModalStyle: {
width: '100vw',
height: '100vh',
top: '0',
transform: 'none',
left: '0',
right: '0',
margin: '0 auto',
},
laptopModalStyle: {
width: 'auto',
height: '0px',
top: '80px',
left: '0px',
transform: 'none',
margin: '0 auto',
position: 'relative',
},
contentStyle: {
borderRadius: '8px',
},
},
EDIT_APPROVAL_PERMISSION: {
contents: <EditApprovalPermission />,
mobileModalStyle: {

View File

@ -2,6 +2,10 @@ import { createSlice } from '@reduxjs/toolkit'
import BigNumber from 'bignumber.js'
import log from 'loglevel'
import {
loadLocalStorageData,
saveLocalStorageData,
} from '../../../lib/local-storage-helpers'
import {
addToken,
addUnapprovedTransaction,
@ -27,7 +31,10 @@ import {
SWAPS_ERROR_ROUTE,
SWAPS_MAINTENANCE_ROUTE,
} from '../../helpers/constants/routes'
import { fetchSwapsFeatureLiveness } from '../../pages/swaps/swaps.util'
import {
fetchSwapsFeatureLiveness,
fetchSwapsGasPrices,
} from '../../pages/swaps/swaps.util'
import { calcGasTotal } from '../../pages/send/send.utils'
import {
decimalToHex,
@ -37,9 +44,9 @@ import {
hexToDecimal,
hexWEIToDecGWEI,
} from '../../helpers/utils/conversions.util'
import { conversionLessThan } from '../../helpers/utils/conversion-util'
import { calcTokenAmount } from '../../helpers/utils/token-util'
import {
getFastPriceEstimateInHexWEI,
getSelectedAccount,
getTokenExchangeRates,
conversionRateSelector as getConversionRate,
@ -51,14 +58,16 @@ import {
SWAP_FAILED_ERROR,
SWAPS_FETCH_ORDER_CONFLICT,
} from '../../helpers/constants/swaps'
import {
fetchBasicGasAndTimeEstimates,
fetchGasEstimates,
resetCustomGasState,
} from '../gas/gas.duck'
import { formatCurrency } from '../../helpers/utils/confirm-tx.util'
import { TRANSACTION_CATEGORIES } from '../../../../shared/constants/transaction'
const GAS_PRICES_LOADING_STATES = {
INITIAL: 'INITIAL',
LOADING: 'LOADING',
FAILED: 'FAILED',
COMPLETED: 'COMPLETED',
}
const initialState = {
aggregatorMetadata: null,
approveTxId: null,
@ -68,6 +77,14 @@ const initialState = {
quotesFetchStartTime: null,
topAssets: {},
toToken: null,
customGas: {
price: null,
limit: null,
loading: GAS_PRICES_LOADING_STATES.INITIAL,
priceEstimates: {},
priceEstimatesLastRetrieved: 0,
fallBackPrice: null,
},
}
const slice = createSlice({
@ -106,6 +123,27 @@ const slice = createSlice({
setToToken: (state, action) => {
state.toToken = action.payload
},
swapCustomGasModalPriceEdited: (state, action) => {
state.customGas.price = action.payload
},
swapCustomGasModalLimitEdited: (state, action) => {
state.customGas.limit = action.payload
},
swapGasPriceEstimatesFetchStarted: (state) => {
state.customGas.loading = GAS_PRICES_LOADING_STATES.LOADING
},
swapGasPriceEstimatesFetchFailed: (state) => {
state.customGas.loading = GAS_PRICES_LOADING_STATES.FAILED
},
swapGasPriceEstimatesFetchCompleted: (state, action) => {
state.customGas.priceEstimates = action.payload.priceEstimates
state.customGas.loading = GAS_PRICES_LOADING_STATES.COMPLETED
state.customGas.priceEstimatesLastRetrieved =
action.payload.priceEstimatesLastRetrieved
},
retrievedFallbackSwapsGasPrice: (state, action) => {
state.customGas.fallBackPrice = action.payload
},
},
})
@ -130,6 +168,49 @@ export const getFetchingQuotes = (state) => state.swaps.fetchingQuotes
export const getQuotesFetchStartTime = (state) =>
state.swaps.quotesFetchStartTime
export const getSwapsCustomizationModalPrice = (state) =>
state.swaps.customGas.price
export const getSwapsCustomizationModalLimit = (state) =>
state.swaps.customGas.limit
export const swapGasPriceEstimateIsLoading = (state) =>
state.swaps.customGas.loading === GAS_PRICES_LOADING_STATES.LOADING
export const swapGasEstimateLoadingHasFailed = (state) =>
state.swaps.customGas.loading === GAS_PRICES_LOADING_STATES.INITIAL
export const getSwapGasPriceEstimateData = (state) =>
state.swaps.customGas.priceEstimates
export const getSwapsPriceEstimatesLastRetrieved = (state) =>
state.swaps.customGas.priceEstimatesLastRetrieved
export const getSwapsFallbackGasPrice = (state) =>
state.swaps.customGas.fallBackPrice
export function shouldShowCustomPriceTooLowWarning(state) {
const { average } = getSwapGasPriceEstimateData(state)
const customGasPrice = getSwapsCustomizationModalPrice(state)
if (!customGasPrice || average === undefined) {
return false
}
const customPriceRisksSwapFailure = conversionLessThan(
{
value: customGasPrice,
fromNumericBase: 'hex',
fromDenomination: 'WEI',
toDenomination: 'GWEI',
},
{ value: average, fromNumericBase: 'dec' },
)
return customPriceRisksSwapFailure
}
// Background selectors
const getSwapsState = (state) => state.metamask.swapsState
@ -185,17 +266,8 @@ export const getUsedQuote = (state) =>
export const getDestinationTokenInfo = (state) =>
getFetchParams(state)?.metaData?.destinationTokenInfo
export const getSwapsTradeTxParams = (state) => {
const { selectedAggId, topAggId, quotes } = getSwapsState(state)
const usedQuote = selectedAggId ? quotes[selectedAggId] : quotes[topAggId]
if (!usedQuote) {
return null
}
const { trade } = usedQuote
const gas = getCustomSwapsGas(state) || trade.gas
const gasPrice = getCustomSwapsGasPrice(state) || trade.gasPrice
return { ...trade, gas, gasPrice }
}
export const getUsedSwapsGasPrice = (state) =>
getCustomSwapsGasPrice(state) || getSwapsFallbackGasPrice(state)
export const getApproveTxParams = (state) => {
const { approvalNeeded } = getSelectedQuote(state) || getTopQuote(state) || {}
@ -215,6 +287,9 @@ const {
clearSwapsState,
navigatedBackToBuildQuote,
retriedGetQuotes,
swapGasPriceEstimatesFetchCompleted,
swapGasPriceEstimatesFetchStarted,
swapGasPriceEstimatesFetchFailed,
setAggregatorMetadata,
setBalanceError,
setFetchingQuotes,
@ -222,6 +297,9 @@ const {
setQuotesFetchStartTime,
setTopAssets,
setToToken,
swapCustomGasModalPriceEdited,
swapCustomGasModalLimitEdited,
retrievedFallbackSwapsGasPrice,
} = actions
export {
@ -233,6 +311,8 @@ export {
setQuotesFetchStartTime as setSwapQuotesFetchStartTime,
setTopAssets,
setToToken as setSwapToToken,
swapCustomGasModalPriceEdited,
swapCustomGasModalLimitEdited,
}
export const navigateBackToBuildQuote = (history) => {
@ -255,7 +335,6 @@ export const prepareForRetryGetQuotes = () => {
export const prepareToLeaveSwaps = () => {
return async (dispatch) => {
dispatch(resetCustomGasState())
dispatch(clearSwapsState())
await dispatch(resetBackgroundSwapsState())
}
@ -263,9 +342,11 @@ export const prepareToLeaveSwaps = () => {
export const fetchAndSetSwapsGasPriceInfo = () => {
return async (dispatch) => {
const basicEstimates = await dispatch(fetchBasicGasAndTimeEstimates())
dispatch(setSwapsTxGasPrice(decGWEIToHexWEI(basicEstimates.fastest)))
await dispatch(fetchGasEstimates(basicEstimates.blockTime))
const basicEstimates = await dispatch(fetchMetaSwapsGasPriceEstimates())
if (basicEstimates?.fast) {
dispatch(setSwapsTxGasPrice(decGWEIToHexWEI(basicEstimates.fast)))
}
}
}
@ -476,6 +557,7 @@ export const fetchQuotesAndSetQuoteState = (
}
// TODO: Check for any errors we should expect to occur in production, and report others to Sentry
log.error(`Error fetching quotes: `, e)
dispatch(setSwapsErrorKey(ERROR_FETCHING_QUOTES))
}
@ -507,6 +589,8 @@ export const signAndSendTransactions = (history, metaMetricsEvent) => {
await dispatch(stopPollingForQuotes())
history.push(AWAITING_SWAP_ROUTE)
const { fast: fastGasEstimate } = getSwapGasPriceEstimateData(state)
const usedQuote = getUsedQuote(state)
const usedTradeTxParams = usedQuote.trade
@ -524,13 +608,9 @@ export const signAndSendTransactions = (history, metaMetricsEvent) => {
`0x${decimalToHex(usedQuote?.maxGas || 0)}`,
estimatedGasLimitWithMultiplier,
)
usedTradeTxParams.gas = maxGasLimit
const customConvertGasPrice = getCustomSwapsGasPrice(state)
const tradeTxParams = getSwapsTradeTxParams(state)
const fastGasEstimate = getFastPriceEstimateInHexWEI(state)
const usedGasPrice =
customConvertGasPrice || tradeTxParams?.gasPrice || fastGasEstimate
const usedGasPrice = getUsedSwapsGasPrice(state)
usedTradeTxParams.gas = maxGasLimit
usedTradeTxParams.gasPrice = usedGasPrice
const conversionRate = getConversionRate(state)
@ -568,8 +648,8 @@ export const signAndSendTransactions = (history, metaMetricsEvent) => {
: usedQuote.aggregator,
gas_fees: formatCurrency(gasEstimateTotalInEth, 'usd')?.slice(1),
estimated_gas: estimatedGasLimit.toString(10),
suggested_gas_price: hexWEIToDecGWEI(usedGasPrice),
used_gas_price: hexWEIToDecGWEI(fastGasEstimate),
suggested_gas_price: fastGasEstimate,
used_gas_price: hexWEIToDecGWEI(usedGasPrice),
average_savings: usedQuote.savings?.performance,
}
@ -645,3 +725,68 @@ export const signAndSendTransactions = (history, metaMetricsEvent) => {
await forceUpdateMetamaskState(dispatch)
}
}
export function fetchMetaSwapsGasPriceEstimates() {
return async (dispatch, getState) => {
const state = getState()
const priceEstimatesLastRetrieved = getSwapsPriceEstimatesLastRetrieved(
state,
)
const timeLastRetrieved =
priceEstimatesLastRetrieved ||
loadLocalStorageData('METASWAP_GAS_PRICE_ESTIMATES_LAST_RETRIEVED') ||
0
dispatch(swapGasPriceEstimatesFetchStarted())
let priceEstimates
try {
if (Date.now() - timeLastRetrieved > 30000) {
priceEstimates = await fetchSwapsGasPrices()
} else {
const cachedPriceEstimates = loadLocalStorageData(
'METASWAP_GAS_PRICE_ESTIMATES',
)
priceEstimates = cachedPriceEstimates || (await fetchSwapsGasPrices())
}
} catch (e) {
log.warn('Fetching swaps gas prices failed:', e)
if (!e.message?.match(/NetworkError|Fetch failed with status:/u)) {
throw e
}
dispatch(swapGasPriceEstimatesFetchFailed())
try {
const gasPrice = await global.ethQuery.gasPrice()
const gasPriceInDecGWEI = hexWEIToDecGWEI(gasPrice.toString(10))
dispatch(retrievedFallbackSwapsGasPrice(gasPriceInDecGWEI))
return null
} catch (networkGasPriceError) {
console.error(
`Failed to retrieve fallback gas price: `,
networkGasPriceError,
)
return null
}
}
const timeRetrieved = Date.now()
saveLocalStorageData(priceEstimates, 'METASWAP_GAS_PRICE_ESTIMATES')
saveLocalStorageData(
timeRetrieved,
'METASWAP_GAS_PRICE_ESTIMATES_LAST_RETRIEVED',
)
dispatch(
swapGasPriceEstimatesFetchCompleted({
priceEstimates,
priceEstimatesLastRetrieved: timeRetrieved,
}),
)
return priceEstimates
}
}

View File

@ -12,7 +12,7 @@ import {
getUsedQuote,
getFetchParams,
getApproveTxParams,
getSwapsTradeTxParams,
getUsedSwapsGasPrice,
fetchQuotesAndSetQuoteState,
navigateBackToBuildQuote,
prepareForRetryGetQuotes,
@ -67,7 +67,7 @@ export default function AwaitingSwap({
const { destinationTokenInfo, sourceTokenInfo } = fetchParams?.metaData || {}
const usedQuote = useSelector(getUsedQuote)
const approveTxParams = useSelector(getApproveTxParams)
const tradeTxParams = useSelector(getSwapsTradeTxParams)
const swapsGasPrice = useSelector(getUsedSwapsGasPrice)
const currentCurrency = useSelector(getCurrentCurrency)
const conversionRate = useSelector(conversionRateSelector)
@ -77,14 +77,15 @@ export default function AwaitingSwap({
)
let feeinFiat
if (usedQuote && tradeTxParams) {
if (usedQuote && swapsGasPrice) {
const renderableNetworkFees = getRenderableNetworkFeesForQuote(
usedQuote.gasEstimateWithRefund || usedQuote.averageGas,
approveTxParams?.gas || '0x0',
tradeTxParams.gasPrice,
swapsGasPrice,
currentCurrency,
conversionRate,
tradeTxParams.value,
usedQuote?.trade?.value,
sourceTokenInfo?.symbol,
usedQuote.sourceAmount,
)

View File

@ -21,9 +21,8 @@ import {
getApproveTxId,
getFetchingQuotes,
setBalanceError,
getCustomSwapsGasPrice,
setTopAssets,
getSwapsTradeTxParams,
getUsedSwapsGasPrice,
getFetchParams,
setAggregatorMetadata,
getAggregatorMetadata,
@ -33,7 +32,6 @@ import {
prepareToLeaveSwaps,
fetchAndSetSwapsGasPriceInfo,
} from '../../ducks/swaps/swaps'
import { resetCustomGasState } from '../../ducks/gas/gas.duck'
import {
AWAITING_SWAP_ROUTE,
BUILD_QUOTE_ROUTE,
@ -59,7 +57,6 @@ import {
setSwapsErrorKey,
} from '../../store/actions'
import {
getFastPriceEstimateInHexWEI,
currentNetworkTxListSelector,
getRpcPrefsForCurrentProvider,
} from '../../selectors'
@ -96,11 +93,9 @@ export default function Swap() {
const [maxSlippage, setMaxSlippage] = useState(fetchParams?.slippage || 2)
const routeState = useSelector(getBackgroundSwapRouteState)
const tradeTxParams = useSelector(getSwapsTradeTxParams)
const usedGasPrice = useSelector(getUsedSwapsGasPrice)
const selectedAccount = useSelector(getSelectedAccount)
const quotes = useSelector(getQuotes)
const fastGasEstimate = useSelector(getFastPriceEstimateInHexWEI)
const customConvertGasPrice = useSelector(getCustomSwapsGasPrice)
const txList = useSelector(currentNetworkTxListSelector)
const tradeTxId = useSelector(getTradeTxId)
const approveTxId = useSelector(getApproveTxId)
@ -131,8 +126,6 @@ export default function Swap() {
useSelector(getFromToken) || fetchParamsFromToken || {}
const { destinationTokenAddedForSwap } = fetchParams || {}
const usedGasPrice =
customConvertGasPrice || tradeTxParams?.gasPrice || fastGasEstimate
const approveTxData =
approveTxId && txList.find(({ id }) => approveTxId === id)
const tradeTxData = tradeTxId && txList.find(({ id }) => tradeTxId === id)
@ -197,7 +190,6 @@ export default function Swap() {
dispatch(setAggregatorMetadata(newAggregatorMetadata))
})
dispatch(resetCustomGasState())
dispatch(fetchAndSetSwapsGasPriceInfo())
return () => {

View File

@ -0,0 +1 @@
export { default } from './swaps-gas-customization-modal.container'

View File

@ -0,0 +1,269 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import PageContainer from '../../../components/ui/page-container'
import { Tabs, Tab } from '../../../components/ui/tabs'
import { calcGasTotal } from '../../send/send.utils'
import { sumHexWEIsToRenderableFiat } from '../../../helpers/utils/conversions.util'
import AdvancedGasInputs from '../../../components/app/gas-customization/advanced-gas-inputs'
import BasicTabContent from '../../../components/app/gas-customization/gas-modal-page-container/basic-tab-content'
import { GAS_ESTIMATE_TYPES } from '../../../helpers/constants/common'
export default class GasModalPageContainer extends Component {
static contextTypes = {
t: PropTypes.func,
trackEvent: PropTypes.func,
}
static propTypes = {
insufficientBalance: PropTypes.bool,
gasPriceButtonGroupProps: PropTypes.object,
infoRowProps: PropTypes.shape({
originalTotalFiat: PropTypes.string,
originalTotalEth: PropTypes.string,
newTotalFiat: PropTypes.string,
newTotalEth: PropTypes.string,
sendAmount: PropTypes.string,
transactionFee: PropTypes.string,
extraInfoRow: PropTypes.shape({
label: PropTypes.string,
value: PropTypes.string,
}),
}),
onSubmit: PropTypes.func,
cancelAndClose: PropTypes.func,
showCustomPriceTooLowWarning: PropTypes.bool,
disableSave: PropTypes.bool,
customGasLimitMessage: PropTypes.string,
customTotalSupplement: PropTypes.string,
value: PropTypes.string,
conversionRate: PropTypes.string,
customGasPrice: PropTypes.string,
customGasLimit: PropTypes.string,
setSwapsCustomizationModalPrice: PropTypes.func,
setSwapsCustomizationModalLimit: PropTypes.func,
gasEstimateLoadingHasFailed: PropTypes.bool,
}
state = {
gasSpeedType: '',
}
setGasSpeedType(gasEstimateType) {
if (gasEstimateType === GAS_ESTIMATE_TYPES.AVERAGE) {
this.setState({ gasSpeedType: 'average' })
} else {
this.setState({ gasSpeedType: 'fast' })
}
}
renderBasicTabContent(gasPriceButtonGroupProps) {
return (
<BasicTabContent
gasPriceButtonGroupProps={{
...gasPriceButtonGroupProps,
handleGasPriceSelection: (gasPriceInHexWei, gasEstimateType) => {
this.setGasSpeedType(gasEstimateType)
this.props.setSwapsCustomizationModalPrice(gasPriceInHexWei)
},
}}
/>
)
}
renderAdvancedTabContent() {
const {
insufficientBalance,
showCustomPriceTooLowWarning,
infoRowProps: { transactionFee },
customGasLimitMessage,
setSwapsCustomizationModalPrice,
setSwapsCustomizationModalLimit,
customGasPrice,
customGasLimit,
} = this.props
return (
<div className="advanced-tab">
<div className="advanced-tab__transaction-data-summary">
<div className="advanced-tab__transaction-data-summary__titles">
<span>{this.context.t('newTransactionFee')}</span>
</div>
<div className="advanced-tab__transaction-data-summary__container">
<div className="advanced-tab__transaction-data-summary__fee">
{transactionFee}
</div>
</div>
</div>
<div className="advanced-tab__fee-chart">
<div className="advanced-tab__gas-inputs">
<AdvancedGasInputs
updateCustomGasPrice={(updatedPrice) => {
this.setState({ gasSpeedType: 'custom' })
setSwapsCustomizationModalPrice(updatedPrice)
}}
updateCustomGasLimit={(updatedLimit) => {
this.setState({ gasSpeedType: 'custom' })
setSwapsCustomizationModalLimit(updatedLimit)
}}
customGasPrice={customGasPrice}
customGasLimit={customGasLimit}
insufficientBalance={insufficientBalance}
customPriceIsSafe={!showCustomPriceTooLowWarning}
customGasLimitMessage={customGasLimitMessage}
/>
</div>
</div>
</div>
)
}
renderInfoRows(
newTotalFiat,
newTotalEth,
sendAmount,
transactionFee,
extraInfoRow,
) {
return (
<div className="gas-modal-content__info-row-wrapper">
<div className="gas-modal-content__info-row">
<div className="gas-modal-content__info-row__send-info">
<span className="gas-modal-content__info-row__send-info__label">
{this.context.t('sendAmount')}
</span>
<span className="gas-modal-content__info-row__send-info__value">
{sendAmount}
</span>
</div>
<div className="gas-modal-content__info-row__transaction-info">
<span className="gas-modal-content__info-row__transaction-info__label">
{this.context.t('transactionFee')}
</span>
<span className="gas-modal-content__info-row__transaction-info__value">
{transactionFee}
</span>
</div>
{extraInfoRow && (
<div className="gas-modal-content__info-row__transaction-info">
<span className="gas-modal-content__info-row__transaction-info__label">
{extraInfoRow.label}
</span>
<span className="gas-modal-content__info-row__transaction-info__value">
{extraInfoRow.value}
</span>
</div>
)}
<div className="gas-modal-content__info-row__total-info">
<span className="gas-modal-content__info-row__total-info__label">
{this.context.t('newTotal')}
</span>
<span className="gas-modal-content__info-row__total-info__value">
{newTotalEth}
</span>
</div>
<div className="gas-modal-content__info-row__fiat-total-info">
<span className="gas-modal-content__info-row__fiat-total-info__value">
{newTotalFiat}
</span>
</div>
</div>
</div>
)
}
renderTabs() {
const {
gasPriceButtonGroupProps,
infoRowProps: {
newTotalFiat,
newTotalEth,
sendAmount,
transactionFee,
extraInfoRow,
},
gasEstimateLoadingHasFailed,
} = this.props
const basicTabInfo = {
name: this.context.t('basic'),
content: this.renderBasicTabContent({
...gasPriceButtonGroupProps,
handleGasPriceSelection: this.props.setSwapsCustomizationModalPrice,
}),
}
const advancedTabInfo = {
name: this.context.t('advanced'),
content: this.renderAdvancedTabContent(),
}
const tabsToRender = gasEstimateLoadingHasFailed
? [advancedTabInfo]
: [basicTabInfo, advancedTabInfo]
return (
<Tabs>
{tabsToRender.map(({ name, content }, i) => (
<Tab name={name} key={`gas-modal-tab-${i}`}>
<div className="gas-modal-content">
{content}
{this.renderInfoRows(
newTotalFiat,
newTotalEth,
sendAmount,
transactionFee,
extraInfoRow,
)}
</div>
</Tab>
))}
</Tabs>
)
}
render() {
const {
cancelAndClose,
onSubmit,
disableSave,
customGasPrice,
customGasLimit,
} = this.props
return (
<div className="gas-modal-page-container">
<PageContainer
title={this.context.t('customGas')}
subtitle={this.context.t('customGasSubTitle')}
tabsComponent={this.renderTabs()}
disabled={disableSave}
onCancel={() => cancelAndClose()}
onClose={() => cancelAndClose()}
onSubmit={() => {
const newSwapGasTotal = calcGasTotal(customGasLimit, customGasPrice)
this.context.trackEvent({
event: 'Gas Fees Changed',
category: 'swaps',
properties: {
speed_set: this.state.gasSpeedType,
gas_fees: sumHexWEIsToRenderableFiat(
[
this.props.value,
newSwapGasTotal,
this.props.customTotalSupplement,
],
'usd',
this.props.conversionRate,
)?.slice(1),
},
})
onSubmit(customGasLimit, customGasPrice)
}}
submitText={this.context.t('save')}
headerCloseText={this.context.t('close')}
hideCancel
/>
</div>
)
}
}

View File

@ -0,0 +1,159 @@
import { connect } from 'react-redux'
import { hideModal, customSwapsGasParamsUpdated } from '../../../store/actions'
import {
conversionRateSelector as getConversionRate,
getCurrentCurrency,
getCurrentEthBalance,
getDefaultActiveButtonIndex,
getRenderableGasButtonData,
} from '../../../selectors'
import {
getSwapsCustomizationModalPrice,
getSwapsCustomizationModalLimit,
swapGasEstimateLoadingHasFailed,
swapGasPriceEstimateIsLoading,
getSwapGasPriceEstimateData,
swapCustomGasModalPriceEdited,
swapCustomGasModalLimitEdited,
shouldShowCustomPriceTooLowWarning,
} from '../../../ducks/swaps/swaps'
import {
addHexes,
getValueFromWeiHex,
sumHexWEIsToRenderableFiat,
} from '../../../helpers/utils/conversions.util'
import { formatETHFee } from '../../../helpers/utils/formatters'
import { calcGasTotal, isBalanceSufficient } from '../../send/send.utils'
import SwapsGasCustomizationModalComponent from './swaps-gas-customization-modal.component'
const mapStateToProps = (state) => {
const currentCurrency = getCurrentCurrency(state)
const conversionRate = getConversionRate(state)
const { modalState: { props: modalProps } = {} } = state.appState.modal || {}
const {
value,
customGasLimitMessage = '',
customTotalSupplement = '',
extraInfoRow = null,
initialGasPrice,
initialGasLimit,
} = modalProps || {}
const buttonDataLoading = swapGasPriceEstimateIsLoading(state)
const swapsCustomizationModalPrice = getSwapsCustomizationModalPrice(state)
const swapsCustomizationModalLimit = getSwapsCustomizationModalLimit(state)
const customGasPrice = swapsCustomizationModalPrice || initialGasPrice
const customGasLimit = swapsCustomizationModalLimit || initialGasLimit
const customGasTotal = calcGasTotal(customGasLimit, customGasPrice)
const swapsGasPriceEstimates = getSwapGasPriceEstimateData(state)
const { averageEstimateData, fastEstimateData } = getRenderableGasButtonData(
swapsGasPriceEstimates,
customGasLimit,
true,
conversionRate,
currentCurrency,
)
const gasButtonInfo = [averageEstimateData, fastEstimateData]
const newTotalFiat = sumHexWEIsToRenderableFiat(
[value, customGasTotal, customTotalSupplement],
currentCurrency,
conversionRate,
)
const balance = getCurrentEthBalance(state)
const newTotalEth = sumHexWEIsToRenderableEth([
value,
customGasTotal,
customTotalSupplement,
])
const sendAmount = sumHexWEIsToRenderableEth([value, '0x0'])
const insufficientBalance = !isBalanceSufficient({
amount: value,
gasTotal: customGasTotal,
balance,
conversionRate,
})
return {
customGasPrice,
customGasLimit,
showCustomPriceTooLowWarning: shouldShowCustomPriceTooLowWarning(state),
gasPriceButtonGroupProps: {
buttonDataLoading,
defaultActiveButtonIndex: getDefaultActiveButtonIndex(
gasButtonInfo,
customGasPrice,
),
gasButtonInfo,
},
infoRowProps: {
originalTotalFiat: sumHexWEIsToRenderableFiat(
[value, customGasTotal, customTotalSupplement],
currentCurrency,
conversionRate,
),
originalTotalEth: sumHexWEIsToRenderableEth([
value,
customGasTotal,
customTotalSupplement,
]),
newTotalFiat,
newTotalEth,
transactionFee: sumHexWEIsToRenderableEth(['0x0', customGasTotal]),
sendAmount,
extraInfoRow,
},
gasEstimateLoadingHasFailed: swapGasEstimateLoadingHasFailed(state),
insufficientBalance,
customGasLimitMessage,
customTotalSupplement,
conversionRate,
value,
disableSave: insufficientBalance,
}
}
const mapDispatchToProps = (dispatch) => {
return {
cancelAndClose: () => {
dispatch(hideModal())
},
onSubmit: (gasLimit, gasPrice) => {
dispatch(customSwapsGasParamsUpdated(gasLimit, gasPrice))
dispatch(hideModal())
},
setSwapsCustomizationModalPrice: (newPrice) => {
dispatch(swapCustomGasModalPriceEdited(newPrice))
},
setSwapsCustomizationModalLimit: (newLimit) => {
dispatch(swapCustomGasModalLimitEdited(newLimit))
},
}
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(SwapsGasCustomizationModalComponent)
function sumHexWEIsToRenderableEth(hexWEIs) {
const hexWEIsSum = hexWEIs.filter((n) => n).reduce(addHexes)
return formatETHFee(
getValueFromWeiHex({
value: hexWEIsSum,
toCurrency: 'ETH',
numberOfDecimals: 6,
}),
)
}

View File

@ -36,6 +36,8 @@ const getBaseApi = function (type) {
return `https://api.metaswap.codefi.network/featureFlag`
case 'aggregatorMetadata':
return `https://api.metaswap.codefi.network/aggregatorMetadata`
case 'gasPrices':
return `https://api.metaswap.codefi.network/gasPrices`
default:
throw new Error('getBaseApi requires an api call type')
}
@ -150,6 +152,27 @@ const AGGREGATOR_METADATA_VALIDATORS = [
},
]
const isValidDecimalNumber = (string) =>
!isNaN(string) && string.match(/^[.0-9]+$/u) && !isNaN(parseFloat(string))
const SWAP_GAS_PRICE_VALIDATOR = [
{
property: 'SafeGasPrice',
type: 'string',
validator: isValidDecimalNumber,
},
{
property: 'ProposeGasPrice',
type: 'string',
validator: isValidDecimalNumber,
},
{
property: 'FastGasPrice',
type: 'string',
validator: isValidDecimalNumber,
},
]
function validateData(validators, object, urlUsed) {
return validators.every(({ property, type, validator }) => {
const types = type.split('|')
@ -320,6 +343,36 @@ export async function fetchTokenBalance(address, userAddress) {
return usersToken
}
export async function fetchSwapsGasPrices() {
const gasPricesUrl = getBaseApi('gasPrices')
const response = await fetchWithCache(
gasPricesUrl,
{ method: 'GET' },
{ cacheRefreshTime: 15000 },
)
const responseIsValid = validateData(
SWAP_GAS_PRICE_VALIDATOR,
response,
gasPricesUrl,
)
if (!responseIsValid) {
throw new Error(`${gasPricesUrl} response is invalid`)
}
const {
SafeGasPrice: safeLow,
ProposeGasPrice: average,
FastGasPrice: fast,
} = response
return {
safeLow,
average,
fast,
}
}
export function getRenderableNetworkFeesForQuote(
tradeGas,
approveGas,

View File

@ -22,7 +22,7 @@ import {
getBalanceError,
getCustomSwapsGas,
getDestinationTokenInfo,
getSwapsTradeTxParams,
getUsedSwapsGasPrice,
getTopQuote,
navigateBackToBuildQuote,
signAndSendTransactions,
@ -101,8 +101,7 @@ export default function ViewQuote() {
const quotesLastFetched = useSelector(getQuotesLastFetched)
// Select necessary data
const tradeTxParams = useSelector(getSwapsTradeTxParams)
const { gasPrice, value: tradeValue } = tradeTxParams || {}
const gasPrice = useSelector(getUsedSwapsGasPrice)
const customMaxGas = useSelector(getCustomSwapsGas)
const tokenConversionRates = useSelector(getTokenExchangeRates)
const memoizedTokenConversionRates = useEqualityCheck(tokenConversionRates)
@ -116,6 +115,7 @@ export default function ViewQuote() {
const selectedQuote = useSelector(getSelectedQuote)
const topQuote = useSelector(getTopQuote)
const usedQuote = selectedQuote || topQuote
const { value: tradeValue = '0x0' } = usedQuote?.trade?.value || {}
const fetchParamsSourceToken = fetchParams?.sourceToken
@ -454,9 +454,8 @@ export default function ViewQuote() {
const onFeeCardMaxRowClick = () =>
dispatch(
showModal({
name: 'CUSTOMIZE_GAS',
txData: { txParams: { ...tradeTxParams, gas: maxGasLimit } },
isSwap: true,
name: 'CUSTOMIZE_METASWAP_GAS',
value: usedQuote.value,
customGasLimitMessage: approveGas
? t('extraApprovalGas', [hexToDecimal(approveGas)])
: '',
@ -467,8 +466,8 @@ export default function ViewQuote() {
value: t('amountInEth', [extraNetworkFeeTotalInEth]),
}
: null,
useFastestButtons: true,
minimumGasLimit: Number(hexToDecimal(nonCustomMaxGasLimit)),
initialGasPrice: gasPrice,
initialGasLimit: maxGasLimit,
}),
)
@ -605,7 +604,7 @@ export default function ViewQuote() {
}}
submitText={t('swap')}
onCancel={async () => await dispatch(navigateBackToBuildQuote(history))}
disabled={balanceError}
disabled={balanceError || gasPrice === null || gasPrice === undefined}
showTermsOfService
showTopBorder
/>

View File

@ -80,20 +80,8 @@ export function getSafeLowEstimate(state) {
return safeLow
}
export function getAverageEstimate(state) {
const {
gas: {
basicEstimates: { average },
},
} = state
return average
}
export function isCustomPriceSafe(state, averageIsSafe) {
export function isCustomPriceSafe(state) {
const safeLow = getSafeLowEstimate(state)
const average = getAverageEstimate(state)
const safeMinimumPrice = averageIsSafe ? average : safeLow
const customGasPrice = getCustomGasPrice(state)
@ -101,7 +89,7 @@ export function isCustomPriceSafe(state, averageIsSafe) {
return true
}
if (safeMinimumPrice === null) {
if (safeLow === null) {
return false
}
@ -112,7 +100,7 @@ export function isCustomPriceSafe(state, averageIsSafe) {
fromDenomination: 'WEI',
toDenomination: 'GWEI',
},
{ value: safeMinimumPrice, fromNumericBase: 'dec' },
{ value: safeLow, fromNumericBase: 'dec' },
)
return customPriceSafe
@ -219,23 +207,14 @@ export function getGasPriceInHexWei(price) {
return addHexPrefix(priceEstimateToWei(value))
}
export function getRenderableBasicEstimateData(
state,
export function getRenderableGasButtonData(
estimates,
gasLimit,
useFastestButtons,
showFiat,
conversionRate,
currentCurrency,
) {
if (getBasicGasEstimateLoadingStatus(state)) {
return []
}
const { showFiatInTestnets } = getPreferences(state)
const isMainnet = getIsMainnet(state)
const showFiat = isMainnet || Boolean(showFiatInTestnets)
const { conversionRate } = state.metamask
const currentCurrency = getCurrentCurrency(state)
const {
gas: {
basicEstimates: {
safeLow,
average,
fast,
@ -244,9 +223,7 @@ export function getRenderableBasicEstimateData(
fastWait,
fastest,
fastestWait,
},
},
} = state
} = estimates
const slowEstimateData = {
gasEstimateType: GAS_ESTIMATE_TYPES.SLOW,
@ -305,9 +282,38 @@ export function getRenderableBasicEstimateData(
priceInHexWei: getGasPriceInHexWei(fastest),
}
return useFastestButtons
? [fastEstimateData, fastestEstimateData]
: [slowEstimateData, averageEstimateData, fastEstimateData]
return {
slowEstimateData,
averageEstimateData,
fastEstimateData,
fastestEstimateData,
}
}
export function getRenderableBasicEstimateData(state, gasLimit) {
if (getBasicGasEstimateLoadingStatus(state)) {
return []
}
const { showFiatInTestnets } = getPreferences(state)
const isMainnet = getIsMainnet(state)
const showFiat = isMainnet || Boolean(showFiatInTestnets)
const { conversionRate } = state.metamask
const currentCurrency = getCurrentCurrency(state)
const {
slowEstimateData,
averageEstimateData,
fastEstimateData,
} = getRenderableGasButtonData(
state.gas.basicEstimates,
gasLimit,
showFiat,
conversionRate,
currentCurrency,
)
return [slowEstimateData, averageEstimateData, fastEstimateData]
}
export function getRenderableEstimateDataForSmallButtonsFromGWEI(state) {

View File

@ -348,53 +348,6 @@ describe('custom-gas selectors', function () {
},
},
},
{
expectedResult: [
{
gasEstimateType: 'FAST',
feeInSecondaryCurrency: '$0.54',
feeInPrimaryCurrency: '0.00021 ETH',
timeEstimate: '~6 min 36 sec',
priceInHexWei: '0x2540be400',
},
{
feeInPrimaryCurrency: '0.00042 ETH',
feeInSecondaryCurrency: '$1.07',
gasEstimateType: 'FASTEST',
priceInHexWei: '0x4a817c800',
timeEstimate: '~1 min',
},
],
mockState: {
metamask: {
conversionRate: 2557.1,
currentCurrency: 'usd',
send: {
gasLimit: '0x5208',
},
preferences: {
showFiatInTestnets: true,
},
provider: {
type: 'mainnet',
},
},
gas: {
basicEstimates: {
blockTime: 14.16326530612245,
safeLow: 5,
safeLowWait: 13.2,
average: 7,
avgWait: 10.1,
fast: 10,
fastWait: 6.6,
fastest: 20,
fastestWait: 1.0,
},
},
},
useFastestButtons: true,
},
]
it('should return renderable data about basic estimates', function () {
tests.forEach((test) => {

View File

@ -2288,7 +2288,7 @@ export function setSwapsTxGasLimit(gasLimit) {
}
}
export function setSwapsTxGasParams(gasLimit, gasPrice) {
export function customSwapsGasParamsUpdated(gasLimit, gasPrice) {
return async (dispatch) => {
await promisifiedBackground.setSwapsTxGasPrice(gasPrice)
await promisifiedBackground.setSwapsTxGasLimit(gasLimit, true)