using 1559 V2 for cancel speed up flows (#13019)
parent
dbfdf3b0eb
commit
f5dcd12293
@ -0,0 +1,30 @@ |
|||||||
|
import { useSelector } from 'react-redux'; |
||||||
|
import PropTypes from 'prop-types'; |
||||||
|
import React from 'react'; |
||||||
|
|
||||||
|
import { getAppIsLoading } from '../../../selectors'; |
||||||
|
import Spinner from '../../ui/spinner'; |
||||||
|
|
||||||
|
const AppLoadingSpinner = ({ className }) => { |
||||||
|
const appIsLoading = useSelector(getAppIsLoading); |
||||||
|
|
||||||
|
if (!appIsLoading) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<div |
||||||
|
className={`${className} app-loading-spinner`} |
||||||
|
role="alert" |
||||||
|
aria-busy="true" |
||||||
|
> |
||||||
|
<Spinner color="#F7C06C" className="app-loading-spinner__inner" /> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
AppLoadingSpinner.propTypes = { |
||||||
|
className: PropTypes.string, |
||||||
|
}; |
||||||
|
|
||||||
|
export default AppLoadingSpinner; |
@ -0,0 +1,27 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { screen } from '@testing-library/react'; |
||||||
|
|
||||||
|
import { renderWithProvider } from '../../../../test/lib/render-helpers'; |
||||||
|
import configureStore from '../../../store/store'; |
||||||
|
|
||||||
|
import AppLoadingSpinner from './app-loading-spinner'; |
||||||
|
|
||||||
|
const render = (params) => { |
||||||
|
const store = configureStore({ |
||||||
|
...params, |
||||||
|
}); |
||||||
|
|
||||||
|
return renderWithProvider(<AppLoadingSpinner />, store); |
||||||
|
}; |
||||||
|
|
||||||
|
describe('AppLoadingSpinner', () => { |
||||||
|
it('should return null if app state is not loading', () => { |
||||||
|
render(); |
||||||
|
expect(screen.queryByRole('alert')).not.toBeInTheDocument(); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should show spinner if app state is loading', () => { |
||||||
|
render({ appState: { isLoading: true } }); |
||||||
|
expect(screen.queryByRole('alert')).toBeInTheDocument(); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1 @@ |
|||||||
|
export { default } from './app-loading-spinner'; |
@ -0,0 +1,13 @@ |
|||||||
|
.app-loading-spinner { |
||||||
|
background-color: rgba(255, 255, 255, 0.75); |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
position: absolute; |
||||||
|
height: 100%; |
||||||
|
width: 100%; |
||||||
|
|
||||||
|
&__inner { |
||||||
|
width: 50px; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,159 @@ |
|||||||
|
import { useSelector } from 'react-redux'; |
||||||
|
import React, { useEffect } from 'react'; |
||||||
|
|
||||||
|
import { |
||||||
|
EDIT_GAS_MODES, |
||||||
|
PRIORITY_LEVELS, |
||||||
|
} from '../../../../shared/constants/gas'; |
||||||
|
import { |
||||||
|
ALIGN_ITEMS, |
||||||
|
DISPLAY, |
||||||
|
FLEX_DIRECTION, |
||||||
|
TYPOGRAPHY, |
||||||
|
} from '../../../helpers/constants/design-system'; |
||||||
|
import { getAppIsLoading } from '../../../selectors'; |
||||||
|
import { gasEstimateGreaterThanGasUsedPlusTenPercent } from '../../../helpers/utils/gas'; |
||||||
|
import { useGasFeeContext } from '../../../contexts/gasFee'; |
||||||
|
import { useI18nContext } from '../../../hooks/useI18nContext'; |
||||||
|
import { useTransactionModalContext } from '../../../contexts/transaction-modal'; |
||||||
|
import EditGasFeeButton from '../edit-gas-fee-button'; |
||||||
|
import GasDetailsItem from '../gas-details-item'; |
||||||
|
import Box from '../../ui/box'; |
||||||
|
import Button from '../../ui/button'; |
||||||
|
import I18nValue from '../../ui/i18n-value'; |
||||||
|
import InfoTooltip from '../../ui/info-tooltip'; |
||||||
|
import Popover from '../../ui/popover'; |
||||||
|
import Typography from '../../ui/typography'; |
||||||
|
import AppLoadingSpinner from '../app-loading-spinner'; |
||||||
|
|
||||||
|
const CancelSpeedupPopover = () => { |
||||||
|
const { |
||||||
|
cancelTransaction, |
||||||
|
editGasMode, |
||||||
|
gasFeeEstimates, |
||||||
|
speedUpTransaction, |
||||||
|
transaction, |
||||||
|
updateTransaction, |
||||||
|
updateTransactionToMinimumGasFee, |
||||||
|
updateTransactionUsingEstimate, |
||||||
|
} = useGasFeeContext(); |
||||||
|
const t = useI18nContext(); |
||||||
|
const { closeModal, currentModal } = useTransactionModalContext(); |
||||||
|
const appIsLoading = useSelector(getAppIsLoading); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if ( |
||||||
|
transaction.previousGas || |
||||||
|
appIsLoading || |
||||||
|
currentModal !== 'cancelSpeedUpTransaction' |
||||||
|
) { |
||||||
|
return; |
||||||
|
} |
||||||
|
// If gas used previously + 10% is less than medium estimated gas
|
||||||
|
// estimate is set to medium, else estimate is set to minimum
|
||||||
|
const gasUsedLessThanMedium = |
||||||
|
gasFeeEstimates && |
||||||
|
gasEstimateGreaterThanGasUsedPlusTenPercent( |
||||||
|
transaction, |
||||||
|
gasFeeEstimates, |
||||||
|
PRIORITY_LEVELS.MEDIUM, |
||||||
|
); |
||||||
|
if (gasUsedLessThanMedium) { |
||||||
|
updateTransactionUsingEstimate(PRIORITY_LEVELS.MEDIUM); |
||||||
|
return; |
||||||
|
} |
||||||
|
updateTransactionToMinimumGasFee(); |
||||||
|
}, [ |
||||||
|
appIsLoading, |
||||||
|
currentModal, |
||||||
|
editGasMode, |
||||||
|
gasFeeEstimates, |
||||||
|
transaction, |
||||||
|
updateTransaction, |
||||||
|
updateTransactionToMinimumGasFee, |
||||||
|
updateTransactionUsingEstimate, |
||||||
|
]); |
||||||
|
|
||||||
|
if (currentModal !== 'cancelSpeedUpTransaction') { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
const submitTransactionChange = () => { |
||||||
|
if (editGasMode === EDIT_GAS_MODES.CANCEL) { |
||||||
|
cancelTransaction(); |
||||||
|
} else { |
||||||
|
speedUpTransaction(); |
||||||
|
} |
||||||
|
closeModal('cancelSpeedUpTransaction'); |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<Popover |
||||||
|
title={ |
||||||
|
<> |
||||||
|
{editGasMode === EDIT_GAS_MODES.CANCEL |
||||||
|
? `❌${t('cancel')}` |
||||||
|
: `🚀${t('speedUp')}`} |
||||||
|
</> |
||||||
|
} |
||||||
|
onClose={() => closeModal('cancelSpeedUpTransaction')} |
||||||
|
className="cancel-speedup-popover" |
||||||
|
> |
||||||
|
<AppLoadingSpinner className="cancel-speedup-popover__spinner" /> |
||||||
|
<div className="cancel-speedup-popover__wrapper"> |
||||||
|
<Typography |
||||||
|
boxProps={{ alignItems: ALIGN_ITEMS.CENTER, display: DISPLAY.FLEX }} |
||||||
|
variant={TYPOGRAPHY.H6} |
||||||
|
margin={[0, 0, 2, 0]} |
||||||
|
> |
||||||
|
<I18nValue |
||||||
|
messageKey="cancelSpeedUpLabel" |
||||||
|
options={[ |
||||||
|
<strong key="cancelSpeedupReplace"> |
||||||
|
<I18nValue messageKey="replace" /> |
||||||
|
</strong>, |
||||||
|
]} |
||||||
|
/> |
||||||
|
<InfoTooltip |
||||||
|
position="top" |
||||||
|
contentText={ |
||||||
|
<Box> |
||||||
|
{t('cancelSpeedUpTransactionTooltip', [ |
||||||
|
EDIT_GAS_MODES.CANCEL ? t('cancel') : t('speedUp'), |
||||||
|
])} |
||||||
|
<div> |
||||||
|
<a |
||||||
|
href="https://community.metamask.io/t/how-to-speed-up-or-cancel-transactions-on-metamask/3296" |
||||||
|
target="_blank" |
||||||
|
rel="noopener noreferrer" |
||||||
|
> |
||||||
|
{t('learnMoreUpperCase')} |
||||||
|
</a> |
||||||
|
</div> |
||||||
|
</Box> |
||||||
|
} |
||||||
|
/> |
||||||
|
</Typography> |
||||||
|
<div className="cancel-speedup-popover__separator" /> |
||||||
|
<Box |
||||||
|
display={DISPLAY.FLEX} |
||||||
|
alignItems={ALIGN_ITEMS.CENTER} |
||||||
|
flexDirection={FLEX_DIRECTION.COLUMN} |
||||||
|
marginTop={4} |
||||||
|
> |
||||||
|
<Box className="cancel-speedup-popover__edit-gas-button"> |
||||||
|
<EditGasFeeButton /> |
||||||
|
</Box> |
||||||
|
<Box className="cancel-speedup-popover__gas-details"> |
||||||
|
<GasDetailsItem /> |
||||||
|
</Box> |
||||||
|
</Box> |
||||||
|
<Button type="primary" onClick={submitTransactionChange}> |
||||||
|
<I18nValue messageKey="submit" /> |
||||||
|
</Button> |
||||||
|
</div> |
||||||
|
</Popover> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default CancelSpeedupPopover; |
@ -0,0 +1,88 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { act, screen } from '@testing-library/react'; |
||||||
|
|
||||||
|
import { |
||||||
|
EDIT_GAS_MODES, |
||||||
|
GAS_ESTIMATE_TYPES, |
||||||
|
} from '../../../../shared/constants/gas'; |
||||||
|
import { renderWithProvider } from '../../../../test/lib/render-helpers'; |
||||||
|
import mockEstimates from '../../../../test/data/mock-estimates.json'; |
||||||
|
import mockState from '../../../../test/data/mock-state.json'; |
||||||
|
import { GasFeeContextProvider } from '../../../contexts/gasFee'; |
||||||
|
import configureStore from '../../../store/store'; |
||||||
|
|
||||||
|
import CancelSpeedupPopover from './cancel-speedup-popover'; |
||||||
|
|
||||||
|
jest.mock('../../../store/actions', () => ({ |
||||||
|
disconnectGasFeeEstimatePoller: jest.fn(), |
||||||
|
getGasFeeTimeEstimate: jest.fn().mockImplementation(() => Promise.resolve()), |
||||||
|
getGasFeeEstimatesAndStartPolling: jest |
||||||
|
.fn() |
||||||
|
.mockImplementation(() => Promise.resolve()), |
||||||
|
addPollingTokenToAppState: jest.fn(), |
||||||
|
removePollingTokenFromAppState: jest.fn(), |
||||||
|
updateTransaction: () => ({ type: 'UPDATE_TRANSACTION_PARAMS' }), |
||||||
|
})); |
||||||
|
|
||||||
|
jest.mock('../../../contexts/transaction-modal', () => ({ |
||||||
|
useTransactionModalContext: () => ({ |
||||||
|
closeModal: () => undefined, |
||||||
|
currentModal: 'cancelSpeedUpTransaction', |
||||||
|
}), |
||||||
|
})); |
||||||
|
|
||||||
|
const render = (props) => { |
||||||
|
const store = configureStore({ |
||||||
|
metamask: { |
||||||
|
...mockState.metamask, |
||||||
|
accounts: { |
||||||
|
[mockState.metamask.selectedAddress]: { |
||||||
|
address: mockState.metamask.selectedAddress, |
||||||
|
balance: '0x1F4', |
||||||
|
}, |
||||||
|
}, |
||||||
|
featureFlags: { advancedInlineGas: true }, |
||||||
|
gasFeeEstimates: |
||||||
|
mockEstimates[GAS_ESTIMATE_TYPES.FEE_MARKET].gasFeeEstimates, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
return renderWithProvider( |
||||||
|
<GasFeeContextProvider |
||||||
|
transaction={{ |
||||||
|
userFeeLevel: 'minimum', |
||||||
|
txParams: { |
||||||
|
gas: '0x5208', |
||||||
|
maxFeePerGas: '0x59682f10', |
||||||
|
maxPriorityFeePerGas: '0x59682f00', |
||||||
|
}, |
||||||
|
}} |
||||||
|
editGasMode={EDIT_GAS_MODES.CANCEL} |
||||||
|
{...props} |
||||||
|
> |
||||||
|
<CancelSpeedupPopover /> |
||||||
|
</GasFeeContextProvider>, |
||||||
|
store, |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
describe('CancelSpeedupPopover', () => { |
||||||
|
it('should have ❌Cancel in header if editGasMode is cancel', async () => { |
||||||
|
await act(async () => render()); |
||||||
|
expect(screen.queryByText('❌Cancel')).toBeInTheDocument(); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should have 🚀Speed Up in header if editGasMode is speedup', async () => { |
||||||
|
await act(async () => render({ editGasMode: EDIT_GAS_MODES.SPEED_UP })); |
||||||
|
expect(screen.queryByText('🚀Speed Up')).toBeInTheDocument(); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should show correct gas values', async () => { |
||||||
|
await act(async () => |
||||||
|
render({ |
||||||
|
editGasMode: EDIT_GAS_MODES.SPEED_UP, |
||||||
|
}), |
||||||
|
); |
||||||
|
expect(screen.queryAllByTitle('0.0000315 ETH').length).toBeGreaterThan(0); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1 @@ |
|||||||
|
export { default } from './cancel-speedup-popover'; |
@ -0,0 +1,27 @@ |
|||||||
|
.cancel-speedup-popover { |
||||||
|
&__wrapper { |
||||||
|
padding: 0 16px 16px; |
||||||
|
|
||||||
|
.info-tooltip { |
||||||
|
margin-left: 4px; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
&__edit-gas-button { |
||||||
|
align-self: flex-end; |
||||||
|
} |
||||||
|
|
||||||
|
&__gas-details { |
||||||
|
padding-top: 10px; |
||||||
|
} |
||||||
|
|
||||||
|
&__spinner { |
||||||
|
margin-top: -30px; |
||||||
|
height: calc(100% + 30px); |
||||||
|
} |
||||||
|
|
||||||
|
&__separator { |
||||||
|
border-bottom: 1px solid $ui-grey; |
||||||
|
width: 100%; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,126 @@ |
|||||||
|
import { useEffect, useState } from 'react'; |
||||||
|
import { useSelector } from 'react-redux'; |
||||||
|
|
||||||
|
import { |
||||||
|
EDIT_GAS_MODES, |
||||||
|
PRIORITY_LEVELS, |
||||||
|
} from '../../../../../shared/constants/gas'; |
||||||
|
import { getMaximumGasTotalInHexWei } from '../../../../../shared/modules/gas.utils'; |
||||||
|
import { |
||||||
|
decGWEIToHexWEI, |
||||||
|
decimalToHex, |
||||||
|
hexWEIToDecGWEI, |
||||||
|
} from '../../../../helpers/utils/conversions.util'; |
||||||
|
import { |
||||||
|
addTenPercentAndRound, |
||||||
|
gasEstimateGreaterThanGasUsedPlusTenPercent, |
||||||
|
} from '../../../../helpers/utils/gas'; |
||||||
|
import { getAdvancedGasFeeValues } from '../../../../selectors'; |
||||||
|
import { useGasFeeContext } from '../../../../contexts/gasFee'; |
||||||
|
import { useCustomTimeEstimate } from './useCustomTimeEstimate'; |
||||||
|
|
||||||
|
export const useGasItemFeeDetails = (priorityLevel) => { |
||||||
|
const { |
||||||
|
editGasMode, |
||||||
|
estimateUsed, |
||||||
|
gasFeeEstimates, |
||||||
|
gasLimit, |
||||||
|
maxFeePerGas: maxFeePerGasValue, |
||||||
|
maxPriorityFeePerGas: maxPriorityFeePerGasValue, |
||||||
|
transaction, |
||||||
|
} = useGasFeeContext(); |
||||||
|
const [estimateGreaterThanGasUse, setEstimateGreaterThanGasUse] = useState( |
||||||
|
false, |
||||||
|
); |
||||||
|
const advancedGasFeeValues = useSelector(getAdvancedGasFeeValues); |
||||||
|
|
||||||
|
let maxFeePerGas; |
||||||
|
let maxPriorityFeePerGas; |
||||||
|
let minWaitTime; |
||||||
|
|
||||||
|
const { dappSuggestedGasFees } = transaction; |
||||||
|
|
||||||
|
if (gasFeeEstimates?.[priorityLevel]) { |
||||||
|
maxFeePerGas = gasFeeEstimates[priorityLevel].suggestedMaxFeePerGas; |
||||||
|
maxPriorityFeePerGas = |
||||||
|
gasFeeEstimates[priorityLevel].suggestedMaxPriorityFeePerGas; |
||||||
|
} else if ( |
||||||
|
priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED && |
||||||
|
dappSuggestedGasFees |
||||||
|
) { |
||||||
|
maxFeePerGas = hexWEIToDecGWEI( |
||||||
|
dappSuggestedGasFees.maxFeePerGas || dappSuggestedGasFees.gasPrice, |
||||||
|
); |
||||||
|
maxPriorityFeePerGas = hexWEIToDecGWEI( |
||||||
|
dappSuggestedGasFees.maxPriorityFeePerGas || maxFeePerGas, |
||||||
|
); |
||||||
|
} else if (priorityLevel === PRIORITY_LEVELS.CUSTOM) { |
||||||
|
if (estimateUsed === PRIORITY_LEVELS.CUSTOM) { |
||||||
|
maxFeePerGas = maxFeePerGasValue; |
||||||
|
maxPriorityFeePerGas = maxPriorityFeePerGasValue; |
||||||
|
} else if (advancedGasFeeValues) { |
||||||
|
maxFeePerGas = |
||||||
|
gasFeeEstimates.estimatedBaseFee * |
||||||
|
parseFloat(advancedGasFeeValues.maxBaseFee); |
||||||
|
maxPriorityFeePerGas = advancedGasFeeValues.priorityFee; |
||||||
|
} |
||||||
|
} else if ( |
||||||
|
priorityLevel === PRIORITY_LEVELS.MINIMUM && |
||||||
|
transaction.previousGas |
||||||
|
) { |
||||||
|
maxFeePerGas = hexWEIToDecGWEI( |
||||||
|
addTenPercentAndRound(transaction.previousGas?.maxFeePerGas), |
||||||
|
); |
||||||
|
maxPriorityFeePerGas = hexWEIToDecGWEI( |
||||||
|
addTenPercentAndRound(transaction.previousGas?.maxPriorityFeePerGas), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
const { waitTimeEstimate } = useCustomTimeEstimate({ |
||||||
|
gasFeeEstimates, |
||||||
|
maxFeePerGas, |
||||||
|
maxPriorityFeePerGas, |
||||||
|
}); |
||||||
|
|
||||||
|
if (gasFeeEstimates[priorityLevel]) { |
||||||
|
minWaitTime = |
||||||
|
priorityLevel === PRIORITY_LEVELS.HIGH |
||||||
|
? gasFeeEstimates?.high.minWaitTimeEstimate |
||||||
|
: gasFeeEstimates?.low.maxWaitTimeEstimate; |
||||||
|
} else { |
||||||
|
minWaitTime = waitTimeEstimate; |
||||||
|
} |
||||||
|
|
||||||
|
const hexMaximumTransactionFee = maxFeePerGas |
||||||
|
? getMaximumGasTotalInHexWei({ |
||||||
|
gasLimit: decimalToHex(gasLimit), |
||||||
|
maxFeePerGas: decGWEIToHexWEI(maxFeePerGas), |
||||||
|
}) |
||||||
|
: null; |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
// For cancel and speed-up medium / high option is disabled if
|
||||||
|
// gas used in transaction + 10% is greater tham estimate
|
||||||
|
if ( |
||||||
|
(editGasMode === EDIT_GAS_MODES.CANCEL || |
||||||
|
editGasMode === EDIT_GAS_MODES.SPEED_UP) && |
||||||
|
(priorityLevel === PRIORITY_LEVELS.MEDIUM || |
||||||
|
priorityLevel === PRIORITY_LEVELS.HIGH) |
||||||
|
) { |
||||||
|
const estimateGreater = !gasEstimateGreaterThanGasUsedPlusTenPercent( |
||||||
|
transaction, |
||||||
|
gasFeeEstimates, |
||||||
|
priorityLevel, |
||||||
|
); |
||||||
|
setEstimateGreaterThanGasUse(estimateGreater); |
||||||
|
} |
||||||
|
}, [editGasMode, gasFeeEstimates, priorityLevel, transaction]); |
||||||
|
|
||||||
|
return { |
||||||
|
estimateGreaterThanGasUse, |
||||||
|
maxFeePerGas, |
||||||
|
maxPriorityFeePerGas, |
||||||
|
minWaitTime, |
||||||
|
hexMaximumTransactionFee, |
||||||
|
}; |
||||||
|
}; |
@ -0,0 +1,52 @@ |
|||||||
|
import BigNumber from 'bignumber.js'; |
||||||
|
import { addHexPrefix } from 'ethereumjs-util'; |
||||||
|
|
||||||
|
import { multiplyCurrencies } from '../../../shared/modules/conversion.utils'; |
||||||
|
import { bnGreaterThan } from './util'; |
||||||
|
import { hexWEIToDecGWEI } from './conversions.util'; |
||||||
|
|
||||||
|
export const gasEstimateGreaterThanGasUsedPlusTenPercent = ( |
||||||
|
transaction, |
||||||
|
gasFeeEstimates, |
||||||
|
estimate, |
||||||
|
) => { |
||||||
|
let { maxFeePerGas: maxFeePerGasInTransaction } = transaction.txParams; |
||||||
|
maxFeePerGasInTransaction = new BigNumber( |
||||||
|
hexWEIToDecGWEI(addTenPercentAndRound(maxFeePerGasInTransaction)), |
||||||
|
); |
||||||
|
|
||||||
|
const maxFeePerGasFromEstimate = |
||||||
|
gasFeeEstimates[estimate]?.suggestedMaxFeePerGas; |
||||||
|
return bnGreaterThan(maxFeePerGasFromEstimate, maxFeePerGasInTransaction); |
||||||
|
}; |
||||||
|
|
||||||
|
/** |
||||||
|
* Simple helper to save on duplication to multiply the supplied wei hex string |
||||||
|
* by 1.10 to get bare minimum new gas fee. |
||||||
|
* |
||||||
|
* @param {string | undefined} hexStringValue - hex value in wei to be incremented |
||||||
|
* @returns {string | undefined} - hex value in WEI 10% higher than the param. |
||||||
|
*/ |
||||||
|
export function addTenPercent(hexStringValue, conversionOptions = {}) { |
||||||
|
if (hexStringValue === undefined) return undefined; |
||||||
|
return addHexPrefix( |
||||||
|
multiplyCurrencies(hexStringValue, 1.1, { |
||||||
|
toNumericBase: 'hex', |
||||||
|
multiplicandBase: 16, |
||||||
|
multiplierBase: 10, |
||||||
|
numberOfDecimals: 0, |
||||||
|
...conversionOptions, |
||||||
|
}), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Simple helper to save on duplication to multiply the supplied wei hex string |
||||||
|
* by 1.10 to get bare minimum new gas fee. |
||||||
|
* |
||||||
|
* @param {string | undefined} hexStringValue - hex value in wei to be incremented |
||||||
|
* @returns {string | undefined} - hex value in WEI 10% higher than the param. |
||||||
|
*/ |
||||||
|
export function addTenPercentAndRound(hexStringValue) { |
||||||
|
return addTenPercent(hexStringValue, { numberOfDecimals: 0 }); |
||||||
|
} |
@ -0,0 +1,52 @@ |
|||||||
|
import { PRIORITY_LEVELS } from '../../../shared/constants/gas'; |
||||||
|
|
||||||
|
import { |
||||||
|
addTenPercent, |
||||||
|
gasEstimateGreaterThanGasUsedPlusTenPercent, |
||||||
|
} from './gas'; |
||||||
|
|
||||||
|
describe('Gas utils', () => { |
||||||
|
describe('gasEstimateGreaterThanGasUsedPlusTenPercent', () => { |
||||||
|
const compareGas = (estimateValues) => { |
||||||
|
return gasEstimateGreaterThanGasUsedPlusTenPercent( |
||||||
|
{ |
||||||
|
txParams: { |
||||||
|
maxFeePerGas: '0x59682f10', |
||||||
|
maxPriorityFeePerGas: '0x59682f00', |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
medium: estimateValues, |
||||||
|
}, |
||||||
|
PRIORITY_LEVELS.MEDIUM, |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
it('should return true if gas used in transaction + 10% is greater that estimate', () => { |
||||||
|
const result = compareGas({ |
||||||
|
suggestedMaxPriorityFeePerGas: '7', |
||||||
|
suggestedMaxFeePerGas: '70', |
||||||
|
}); |
||||||
|
expect(result).toStrictEqual(true); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should return false if gas used in transaction + 10% is less that estimate', () => { |
||||||
|
const result = compareGas({ |
||||||
|
suggestedMaxPriorityFeePerGas: '.5', |
||||||
|
suggestedMaxFeePerGas: '1', |
||||||
|
}); |
||||||
|
expect(result).toStrictEqual(false); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('addTenPercent', () => { |
||||||
|
it('should add 10% to hex value passed', () => { |
||||||
|
const result = addTenPercent('0x59682f00'); |
||||||
|
expect(result).toStrictEqual('0x62590080'); |
||||||
|
}); |
||||||
|
it('should return undefined if undefined value is passed', () => { |
||||||
|
const result = addTenPercent(undefined); |
||||||
|
expect(result).toBeUndefined(); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,144 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { Provider } from 'react-redux'; |
||||||
|
import { renderHook } from '@testing-library/react-hooks'; |
||||||
|
|
||||||
|
import { |
||||||
|
CUSTOM_GAS_ESTIMATE, |
||||||
|
EDIT_GAS_MODES, |
||||||
|
GAS_RECOMMENDATIONS, |
||||||
|
} from '../../../shared/constants/gas'; |
||||||
|
import mockState from '../../../test/data/mock-state.json'; |
||||||
|
import * as Actions from '../../store/actions'; |
||||||
|
import configureStore from '../../store/store'; |
||||||
|
import { useGasFeeEstimates } from '../useGasFeeEstimates'; |
||||||
|
import { FEE_MARKET_ESTIMATE_RETURN_VALUE } from './test-utils'; |
||||||
|
import { useTransactionFunctions } from './useTransactionFunctions'; |
||||||
|
|
||||||
|
jest.mock('../useGasFeeEstimates', () => ({ |
||||||
|
useGasFeeEstimates: jest.fn(), |
||||||
|
})); |
||||||
|
useGasFeeEstimates.mockImplementation(() => FEE_MARKET_ESTIMATE_RETURN_VALUE); |
||||||
|
|
||||||
|
jest.mock('../../selectors', () => ({ |
||||||
|
checkNetworkAndAccountSupports1559: () => true, |
||||||
|
})); |
||||||
|
|
||||||
|
const wrapper = ({ children }) => ( |
||||||
|
<Provider store={configureStore(mockState)}>{children}</Provider> |
||||||
|
); |
||||||
|
|
||||||
|
const renderUseTransactionFunctions = (props) => { |
||||||
|
return renderHook( |
||||||
|
() => |
||||||
|
useTransactionFunctions({ |
||||||
|
defaultEstimateToUse: GAS_RECOMMENDATIONS.MEDIUM, |
||||||
|
editGasMode: EDIT_GAS_MODES.MODIFY_IN_PLACE, |
||||||
|
estimatedBaseFee: '0x59682f10', |
||||||
|
gasFeeEstimates: FEE_MARKET_ESTIMATE_RETURN_VALUE.gasFeeEstimates, |
||||||
|
gasLimit: '21000', |
||||||
|
maxPriorityFeePerGas: '0x59682f10', |
||||||
|
transaction: { |
||||||
|
userFeeLevel: CUSTOM_GAS_ESTIMATE, |
||||||
|
txParams: { maxFeePerGas: '0x5028', maxPriorityFeePerGas: '0x5028' }, |
||||||
|
}, |
||||||
|
...props, |
||||||
|
}), |
||||||
|
{ wrapper }, |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
describe('useMaxPriorityFeePerGasInput', () => { |
||||||
|
beforeEach(() => { |
||||||
|
jest.clearAllMocks(); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should invoke action createCancelTransaction when cancelTransaction callback is invoked', () => { |
||||||
|
const mock = jest |
||||||
|
.spyOn(Actions, 'createCancelTransaction') |
||||||
|
.mockImplementation(() => ({ type: '' })); |
||||||
|
const { result } = renderUseTransactionFunctions(); |
||||||
|
result.current.cancelTransaction(); |
||||||
|
expect(mock).toHaveBeenCalledTimes(1); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should invoke action createSpeedUpTransaction when speedUpTransaction callback is invoked', () => { |
||||||
|
const mock = jest |
||||||
|
.spyOn(Actions, 'createSpeedUpTransaction') |
||||||
|
.mockImplementation(() => ({ type: '' })); |
||||||
|
const { result } = renderUseTransactionFunctions(); |
||||||
|
result.current.speedUpTransaction(); |
||||||
|
expect(mock).toHaveBeenCalledTimes(1); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should invoke action updateTransaction with 10% increased fee when updateTransactionToMinimumGasFee callback is invoked', () => { |
||||||
|
const mock = jest |
||||||
|
.spyOn(Actions, 'updateTransaction') |
||||||
|
.mockImplementation(() => ({ type: '' })); |
||||||
|
const { result } = renderUseTransactionFunctions(); |
||||||
|
result.current.updateTransactionToMinimumGasFee(); |
||||||
|
expect(mock).toHaveBeenCalledTimes(1); |
||||||
|
expect(mock).toHaveBeenCalledWith({ |
||||||
|
txParams: { |
||||||
|
estimateSuggested: 'medium', |
||||||
|
estimateUsed: 'minimum', |
||||||
|
gas: '5208', |
||||||
|
gasLimit: '5208', |
||||||
|
maxFeePerGas: '0x582c', |
||||||
|
maxPriorityFeePerGas: '0x582c', |
||||||
|
}, |
||||||
|
userFeeLevel: 'minimum', |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should invoke action updateTransaction with estimate gas values fee when updateTransactionUsingEstimate callback is invoked', () => { |
||||||
|
const mock = jest |
||||||
|
.spyOn(Actions, 'updateTransaction') |
||||||
|
.mockImplementation(() => ({ type: '' })); |
||||||
|
const { result } = renderUseTransactionFunctions(); |
||||||
|
result.current.updateTransactionUsingEstimate(GAS_RECOMMENDATIONS.LOW); |
||||||
|
expect(mock).toHaveBeenCalledTimes(1); |
||||||
|
expect(mock).toHaveBeenCalledWith({ |
||||||
|
txParams: { |
||||||
|
estimateSuggested: 'medium', |
||||||
|
estimateUsed: 'low', |
||||||
|
gas: '5208', |
||||||
|
gasLimit: '5208', |
||||||
|
maxFeePerGas: 'c570bd200', |
||||||
|
maxPriorityFeePerGas: 'b2d05e00', |
||||||
|
}, |
||||||
|
userFeeLevel: 'low', |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should invoke action updateTransaction with dappSuggestedValues values fee when updateTransactionUsingDAPPSuggestedValues callback is invoked', () => { |
||||||
|
const mock = jest |
||||||
|
.spyOn(Actions, 'updateTransaction') |
||||||
|
.mockImplementation(() => ({ type: '' })); |
||||||
|
const { result } = renderUseTransactionFunctions({ |
||||||
|
transaction: { |
||||||
|
userFeeLevel: CUSTOM_GAS_ESTIMATE, |
||||||
|
dappSuggestedGasFees: { |
||||||
|
maxFeePerGas: '0x5028', |
||||||
|
maxPriorityFeePerGas: '0x5028', |
||||||
|
}, |
||||||
|
}, |
||||||
|
}); |
||||||
|
result.current.updateTransactionUsingDAPPSuggestedValues(); |
||||||
|
expect(mock).toHaveBeenCalledTimes(1); |
||||||
|
expect(mock).toHaveBeenCalledWith({ |
||||||
|
dappSuggestedGasFees: { |
||||||
|
maxFeePerGas: '0x5028', |
||||||
|
maxPriorityFeePerGas: '0x5028', |
||||||
|
}, |
||||||
|
txParams: { |
||||||
|
estimateSuggested: 'medium', |
||||||
|
estimateUsed: 'dappSuggested', |
||||||
|
gas: '5208', |
||||||
|
gasLimit: '5208', |
||||||
|
maxFeePerGas: '0x5028', |
||||||
|
maxPriorityFeePerGas: '0x5028', |
||||||
|
}, |
||||||
|
userFeeLevel: 'dappSuggested', |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
Loading…
Reference in new issue