1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 09:57:02 +01:00

Eip1559 v2 12593 (#12719)

This commit is contained in:
Jyoti Puri 2021-11-23 23:48:44 +05:30 committed by GitHub
parent 582882b3ed
commit f65063ac6c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 269 additions and 105 deletions

View File

@ -404,6 +404,9 @@
"cancelPopoverTitle": {
"message": "Cancel transaction"
},
"cancelSpeedUp": {
"message": "cancel or speed up a tranaction."
},
"cancellationGasFee": {
"message": "Cancellation Gas Fee"
},
@ -1378,6 +1381,9 @@
"learmMoreAboutGas": {
"message": "Want to $1 about gas?"
},
"learnCancelSpeeedup": {
"message": "Learn how to $1"
},
"learnMore": {
"message": "learn more"
},
@ -1963,6 +1969,12 @@
"pending": {
"message": "Pending"
},
"pendingTransaction": {
"message": "You have ($1) pending transaction(s)."
},
"pendingTransactionInfo": {
"message": "This transaction will not process until that one is complete."
},
"permissionCheckedIconDescription": {
"message": "You have approved this permission"
},

View File

@ -7,6 +7,9 @@ import ActionableMessage from '../../../ui/actionable-message/actionable-message
import { PageContainerFooter } from '../../../ui/page-container';
import { ConfirmPageContainerSummary, ConfirmPageContainerWarning } from '.';
// eslint-disable-next-line prefer-destructuring
const EIP_1559_V2 = process.env.EIP_1559_V2;
export default class ConfirmPageContainerContent extends Component {
static contextTypes = {
t: PropTypes.func.isRequired,
@ -138,7 +141,7 @@ export default class ConfirmPageContainerContent extends Component {
hideTitle={hideTitle}
/>
{this.renderContent()}
{(errorKey || errorMessage) && !hasSimulationError && (
{!EIP_1559_V2 && !hasSimulationError && (errorKey || errorMessage) && (
<div className="confirm-page-container-content__error-container">
<ErrorMessage errorMessage={errorMessage} errorKey={errorKey} />
</div>

View File

@ -4,6 +4,9 @@ import PropTypes from 'prop-types';
import classnames from 'classnames';
import Identicon from '../../../../ui/identicon';
// eslint-disable-next-line prefer-destructuring
const EIP_1559_V2 = process.env.EIP_1559_V2;
const ConfirmPageContainerSummary = (props) => {
const {
action,
@ -45,7 +48,7 @@ const ConfirmPageContainerSummary = (props) => {
</div>
) : null}
</div>
{hideSubtitle || (
{!hideSubtitle && !EIP_1559_V2 && (
<div className="confirm-page-container-summary__subtitle">
{subtitleComponent}
</div>

View File

@ -3,17 +3,20 @@ import React from 'react';
import { PRIORITY_LEVELS } from '../../../../shared/constants/gas';
import { useI18nContext } from '../../../hooks/useI18nContext';
import { useTransactionModalContext } from '../../../contexts/transaction-modal';
import ErrorMessage from '../../ui/error-message';
import I18nValue from '../../ui/i18n-value';
import LoadingHeartBeat from '../../ui/loading-heartbeat';
import Popover from '../../ui/popover';
import Typography from '../../ui/typography/typography';
import { COLORS } from '../../../helpers/constants/design-system';
import { INSUFFICIENT_FUNDS_ERROR_KEY } from '../../../helpers/constants/error-keys';
import { useGasFeeContext } from '../../../contexts/gasFee';
import EditGasItem from './edit-gas-item';
import NetworkStatus from './network-status';
const EditGasFeePopover = () => {
const { balanceError } = useGasFeeContext();
const t = useI18nContext();
const { closeModal, currentModal } = useTransactionModalContext();
@ -29,6 +32,9 @@ const EditGasFeePopover = () => {
{process.env.IN_TEST === 'true' ? null : <LoadingHeartBeat />}
<div className="edit-gas-fee-popover__wrapper">
<div className="edit-gas-fee-popover__content">
{balanceError && (
<ErrorMessage errorKey={INSUFFICIENT_FUNDS_ERROR_KEY} />
)}
<div className="edit-gas-fee-popover__content__header">
<span className="edit-gas-fee-popover__content__header-option">
<I18nValue messageKey="gasOption" />

View File

@ -45,7 +45,7 @@ const MOCK_FEE_ESTIMATE = {
estimatedBaseFee: '50',
};
const renderComponent = () => {
const render = (txProps) => {
const store = configureStore({
metamask: {
nativeCurrency: ETH,
@ -54,7 +54,7 @@ const renderComponent = () => {
accounts: {
'0xAddress': {
address: '0xAddress',
balance: '0x176e5b6f173ebe66',
balance: '0x1F4',
},
},
selectedAddress: '0xAddress',
@ -64,7 +64,9 @@ const renderComponent = () => {
});
return renderWithProvider(
<GasFeeContextProvider transaction={{ txParams: { gas: '0x5208' } }}>
<GasFeeContextProvider
transaction={{ txParams: { gas: '0x5208' }, ...txProps }}
>
<EditGasFeePopover />
</GasFeeContextProvider>,
store,
@ -73,7 +75,7 @@ const renderComponent = () => {
describe('EditGasFeePopover', () => {
it('should renders low / medium / high options', () => {
renderComponent();
render();
expect(screen.queryByText('🐢')).toBeInTheDocument();
expect(screen.queryByText('🦊')).toBeInTheDocument();
@ -88,15 +90,25 @@ describe('EditGasFeePopover', () => {
});
it('should show time estimates', () => {
renderComponent();
render();
expect(screen.queryAllByText('5 min')).toHaveLength(2);
expect(screen.queryByText('15 sec')).toBeInTheDocument();
});
it('should show gas fee estimates', () => {
renderComponent();
render();
expect(screen.queryByTitle('0.001113 ETH')).toBeInTheDocument();
expect(screen.queryByTitle('0.00147 ETH')).toBeInTheDocument();
expect(screen.queryByTitle('0.0021 ETH')).toBeInTheDocument();
});
it('should not show insufficient balance message if transaction value is less than balance', () => {
render({ userFeeLevel: 'high', txParams: { value: '0x64' } });
expect(screen.queryByText('Insufficient funds.')).not.toBeInTheDocument();
});
it('should show insufficient balance message if transaction value is more than balance', () => {
render({ userFeeLevel: 'high', txParams: { value: '0x5208' } });
expect(screen.queryByText('Insufficient funds.')).toBeInTheDocument();
});
});

View File

@ -41,7 +41,7 @@ const EditGasItem = ({ priorityLevel }) => {
let maxPriorityFeePerGas;
let minWaitTime;
if (gasFeeEstimates[priorityLevel]) {
if (gasFeeEstimates?.[priorityLevel]) {
maxFeePerGas = gasFeeEstimates[priorityLevel].suggestedMaxFeePerGas;
} else if (
priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED &&
@ -95,15 +95,20 @@ const EditGasItem = ({ priorityLevel }) => {
};
return (
<div
<button
className={classNames('edit-gas-item', {
'edit-gas-item-selected': priorityLevel === estimateUsed,
'edit-gas-item-disabled':
priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED &&
!dappSuggestedGasFees,
})}
role="button"
disabled={
priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED &&
!dappSuggestedGasFees
}
onClick={onOptionSelect}
aria-label={priorityLevel}
autoFocus={priorityLevel === estimateUsed}
>
<span className="edit-gas-item__name">
<span
@ -142,7 +147,7 @@ const EditGasItem = ({ priorityLevel }) => {
<span className="edit-gas-item__tooltip">
<InfoTooltip position="top" />
</span>
</div>
</button>
);
};

View File

@ -82,6 +82,7 @@ const renderComponent = (props, transactionProps, gasFeeContextProps) => {
describe('EditGasItem', () => {
it('should renders low gas estimate option for priorityLevel low', () => {
renderComponent({ priorityLevel: 'low' });
expect(screen.queryByRole('button', { name: 'low' })).toBeInTheDocument();
expect(screen.queryByText('🐢')).toBeInTheDocument();
expect(screen.queryByText('Low')).toBeInTheDocument();
expect(screen.queryByText('5 min')).toBeInTheDocument();
@ -90,6 +91,9 @@ describe('EditGasItem', () => {
it('should renders market gas estimate option for priorityLevel medium', () => {
renderComponent({ priorityLevel: 'medium' });
expect(
screen.queryByRole('button', { name: 'medium' }),
).toBeInTheDocument();
expect(screen.queryByText('🦊')).toBeInTheDocument();
expect(screen.queryByText('Market')).toBeInTheDocument();
expect(screen.queryByText('5 min')).toBeInTheDocument();
@ -98,6 +102,7 @@ describe('EditGasItem', () => {
it('should renders aggressive gas estimate option for priorityLevel high', () => {
renderComponent({ priorityLevel: 'high' });
expect(screen.queryByRole('button', { name: 'high' })).toBeInTheDocument();
expect(screen.queryByText('🦍')).toBeInTheDocument();
expect(screen.queryByText('Aggressive')).toBeInTheDocument();
expect(screen.queryByText('15 sec')).toBeInTheDocument();
@ -116,6 +121,9 @@ describe('EditGasItem', () => {
{ priorityLevel: 'dappSuggested' },
{ dappSuggestedGasFees: DAPP_SUGGESTED_ESTIMATE },
);
expect(
screen.queryByRole('button', { name: 'dappSuggested' }),
).toBeInTheDocument();
expect(screen.queryByText('🌐')).toBeInTheDocument();
expect(screen.queryByText('Site')).toBeInTheDocument();
expect(screen.queryByTitle('0.0000315 ETH')).toBeInTheDocument();
@ -130,6 +138,9 @@ describe('EditGasItem', () => {
it('should renders advance gas estimate option for priorityLevel custom', () => {
renderComponent({ priorityLevel: 'custom' });
expect(
screen.queryByRole('button', { name: 'custom' }),
).toBeInTheDocument();
expect(screen.queryByText('⚙')).toBeInTheDocument();
expect(screen.queryByText('Advanced')).toBeInTheDocument();
// below value of custom gas fee estimate is default obtained from state.metamask.advancedGasFee

View File

@ -1,5 +1,6 @@
.edit-gas-item {
border-radius: 24px;
background: white;
color: $ui-4;
cursor: pointer;
font-size: 12px;
@ -8,6 +9,7 @@
margin: 12px 0;
padding: 4px 12px;
height: 32px;
width: 100%;
&--selected {
background-color: $ui-1;

View File

@ -1,5 +1,5 @@
.edit-gas-fee-popover {
height: 500px;
height: 540px;
&__wrapper {
border-top: 1px solid $ui-grey;
@ -8,6 +8,11 @@
&__content {
padding: 16px 12px;
& .error-message {
margin-top: 0;
margin-bottom: 12px;
}
&__header {
color: $ui-4;
font-size: 10px;

View File

@ -3,7 +3,7 @@
flex-direction: column;
align-items: center;
justify-content: center;
width: 65%;
width: 55%;
&__line {
background-image: linear-gradient(to right, #037dd6, #d73a49);

View File

@ -54,7 +54,7 @@ import Typography from '../../components/ui/typography/typography';
import { MIN_GAS_LIMIT_DEC } from '../send/send.constants';
import GasDetailsItem from './gas-details-item';
import LowPriorityMessage from './low-priority-message';
import TransactionAlerts from './transaction-alerts';
// eslint-disable-next-line prefer-destructuring
const EIP_1559_V2 = process.env.EIP_1559_V2;
@ -569,7 +569,7 @@ export default class ConfirmTransactionBase extends Component {
return (
<div className="confirm-page-container-content__details">
{EIP_1559_V2 && <LowPriorityMessage />}
{EIP_1559_V2 && <TransactionAlerts />}
<TransactionDetail
disabled={isDisabled()}
onEdit={

View File

@ -1 +0,0 @@
export { default } from './low-priority-message';

View File

@ -1,24 +0,0 @@
import React from 'react';
import ActionableMessage from '../../../components/ui/actionable-message/actionable-message';
import { useGasFeeContext } from '../../../contexts/gasFee';
import { useI18nContext } from '../../../hooks/useI18nContext';
const LowPriorityMessage = () => {
const { estimateUsed } = useGasFeeContext();
const t = useI18nContext();
if (estimateUsed !== 'low') return null;
return (
<div className="low-priority-message">
<ActionableMessage
className="actionable-message--warning"
message={t('lowPriorityMessage')}
useIcon
iconFillColor="#f8c000"
/>
</div>
);
};
export default LowPriorityMessage;

View File

@ -1,3 +0,0 @@
.low-priority-message {
margin-top: 20px;
}

View File

@ -1,59 +0,0 @@
import React from 'react';
import { renderWithProvider } from '../../../../test/lib/render-helpers';
import { ETH } from '../../../helpers/constants/common';
import { GasFeeContextProvider } from '../../../contexts/gasFee';
import configureStore from '../../../store/store';
import LowPriorityMessage from './low-priority-message';
jest.mock('../../../store/actions', () => ({
disconnectGasFeeEstimatePoller: jest.fn(),
getGasFeeEstimatesAndStartPolling: jest
.fn()
.mockImplementation(() => Promise.resolve()),
addPollingTokenToAppState: jest.fn(),
}));
const render = (props) => {
const store = configureStore({
metamask: {
nativeCurrency: ETH,
preferences: {
useNativeCurrencyAsPrimaryCurrency: true,
},
provider: {},
cachedBalances: {},
accounts: {
'0xAddress': {
address: '0xAddress',
balance: '0x176e5b6f173ebe66',
},
},
selectedAddress: '0xAddress',
},
});
return renderWithProvider(
<GasFeeContextProvider {...props}>
<LowPriorityMessage />
</GasFeeContextProvider>,
store,
);
};
describe('LowPriorityMessage', () => {
it('should returning warning message for low gas estimate', () => {
render({ transaction: { userFeeLevel: 'low' } });
expect(
document.getElementsByClassName('actionable-message--warning'),
).toHaveLength(1);
});
it('should return null for gas estimate other than low', () => {
render({ transaction: { userFeeLevel: 'high' } });
expect(
document.getElementsByClassName('actionable-message--warning'),
).toHaveLength(0);
});
});

View File

@ -0,0 +1 @@
export { default } from './transaction-alerts';

View File

@ -0,0 +1,73 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { INSUFFICIENT_FUNDS_ERROR_KEY } from '../../../helpers/constants/error-keys';
import { submittedPendingTransactionsSelector } from '../../../selectors/transactions';
import { useGasFeeContext } from '../../../contexts/gasFee';
import ActionableMessage from '../../../components/ui/actionable-message/actionable-message';
import ErrorMessage from '../../../components/ui/error-message';
import I18nValue from '../../../components/ui/i18n-value';
const TransactionAlerts = () => {
const { balanceError, estimateUsed } = useGasFeeContext();
const pendingTransactions = useSelector(submittedPendingTransactionsSelector);
return (
<div className="transaction-alerts">
{pendingTransactions?.length > 0 && (
<ActionableMessage
message={
<div className="transaction-alerts__pending-transactions">
<strong>
<I18nValue
messageKey="pendingTransaction"
options={[pendingTransactions?.length]}
/>
</strong>{' '}
<I18nValue messageKey="pendingTransactionInfo" />{' '}
<I18nValue
messageKey="learnCancelSpeeedup"
options={[
<a
key="cancelSpeedUpInfo"
href="https://metamask.zendesk.com/hc/en-us/articles/360015489251-How-to-speed-up-or-cancel-a-pending-transaction"
rel="noopener noreferrer"
target="_blank"
>
<I18nValue messageKey="cancelSpeedUp" />
</a>,
]}
/>
</div>
}
useIcon
iconFillColor="#f8c000"
type="warning"
/>
)}
{balanceError && (
<>
{pendingTransactions?.length > 0 && (
<div className="transaction-alerts--separator" />
)}
<ErrorMessage errorKey={INSUFFICIENT_FUNDS_ERROR_KEY} />
</>
)}
{estimateUsed === 'low' && (
<>
{balanceError && (
<div className="transaction-alerts-message--separator" />
)}
<ActionableMessage
message={<I18nValue messageKey="lowPriorityMessage" />}
useIcon
iconFillColor="#f8c000"
type="warning"
/>
</>
)}
</div>
);
};
export default TransactionAlerts;

View File

@ -0,0 +1,17 @@
.transaction-alerts {
margin-top: 20px;
&--separator {
margin-top: 12px;
}
& strong {
font-weight: bold;
}
&__pending-transactions {
& a {
color: $primary-1;
}
}
}

View File

@ -0,0 +1,101 @@
import React from 'react';
import { screen } from '@testing-library/react';
import { ETH } from '../../../helpers/constants/common';
import { TRANSACTION_STATUSES } from '../../../../shared/constants/transaction';
import { renderWithProvider } from '../../../../test/lib/render-helpers';
import { GasFeeContextProvider } from '../../../contexts/gasFee';
import configureStore from '../../../store/store';
import TransactionAlerts from './transaction-alerts';
jest.mock('../../../store/actions', () => ({
disconnectGasFeeEstimatePoller: jest.fn(),
getGasFeeEstimatesAndStartPolling: jest
.fn()
.mockImplementation(() => Promise.resolve()),
addPollingTokenToAppState: jest.fn(),
}));
const render = ({ props, state }) => {
const store = configureStore({
metamask: {
nativeCurrency: ETH,
preferences: {
useNativeCurrencyAsPrimaryCurrency: true,
},
provider: {},
cachedBalances: {},
accounts: {
'0xAddress': {
address: '0xAddress',
balance: '0x1F4',
},
},
selectedAddress: '0xAddress',
...state,
},
});
return renderWithProvider(
<GasFeeContextProvider {...props}>
<TransactionAlerts />
</GasFeeContextProvider>,
store,
);
};
describe('TransactionAlerts', () => {
it('should returning warning message for low gas estimate', () => {
render({ props: { transaction: { userFeeLevel: 'low' } } });
expect(
document.getElementsByClassName('actionable-message--warning'),
).toHaveLength(1);
});
it('should return null for gas estimate other than low', () => {
render({ props: { transaction: { userFeeLevel: 'high' } } });
expect(
document.getElementsByClassName('actionable-message--warning'),
).toHaveLength(0);
});
it('should not show insufficient balance message if transaction value is less than balance', () => {
render({
props: {
transaction: { userFeeLevel: 'high', txParams: { value: '0x64' } },
},
});
expect(screen.queryByText('Insufficient funds.')).not.toBeInTheDocument();
});
it('should show insufficient balance message if transaction value is more than balance', () => {
render({
props: {
transaction: { userFeeLevel: 'high', txParams: { value: '0x5208' } },
},
});
expect(screen.queryByText('Insufficient funds.')).toBeInTheDocument();
});
it('should show pending transaction message if there are >= 1 pending transactions', () => {
render({
state: {
currentNetworkTxList: [
{
id: 0,
time: 0,
txParams: {
from: '0xAddress',
to: '0xRecipient',
},
status: TRANSACTION_STATUSES.SUBMITTED,
},
],
},
});
expect(
screen.queryByText('You have (1) pending transaction(s).'),
).toBeInTheDocument();
});
});

View File

@ -6,7 +6,7 @@
@import 'confirm-decrypt-message/confirm-decrypt-message';
@import 'confirm-encryption-public-key/confirm-encryption-public-key';
@import 'confirm-transaction-base/gas-details-item/gas-details-item';
@import 'confirm-transaction-base/low-priority-message/low-priority-message';
@import 'confirm-transaction-base/transaction-alerts/transaction-alerts';
@import 'confirmation/confirmation';
@import 'connected-sites/index';
@import 'connected-accounts/index';