1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-22 17:33:23 +01:00

New reusable gas-display component (#17976)

This commit is contained in:
Niranjana Binoy 2023-04-17 10:34:26 -04:00 committed by GitHub
parent ca10a1cdb6
commit fedd6d4970
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 823 additions and 289 deletions

View File

@ -17,7 +17,7 @@ import {
TextVariant,
} from '../../../helpers/constants/design-system';
import { I18nContext } from '../../../contexts/i18n';
import GasDetailsItem from '../gas-details-item/gas-details-item';
import { ConfirmGasDisplay } from '../confirm-gas-display';
import MultiLayerFeeMessage from '../multilayer-fee-message/multi-layer-fee-message';
import { formatCurrency } from '../../../helpers/utils/confirm-tx.util';
@ -111,7 +111,7 @@ export default function ApproveContentCard({
(!isMultiLayerFeeNetwork &&
supportsEIP1559 &&
!renderSimulationFailureWarning ? (
<GasDetailsItem
<ConfirmGasDisplay
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
/>
) : (

View File

@ -0,0 +1,157 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ConfirmGasDisplay should match snapshot 1`] = `
<div>
<div
class="transaction-detail-item"
>
<div
class="transaction-detail-item__row"
>
<h6
class="box box--margin-top-1 box--margin-bottom-1 box--display-flex box--flex-direction-row box--flex-wrap-nowrap box--align-items-center typography typography--h6 typography--weight-bold typography--style-normal typography--color-text-default"
>
<div
class="box box--display-flex box--flex-direction-row"
>
<div
class="box box--margin-right-1 box--flex-direction-row"
>
Gas
</div>
<span
class="gas-details-item-title__estimate"
>
(
estimated
)
</span>
<div
class="info-tooltip"
>
<div>
<div
aria-describedby="tippy-tooltip-1"
class="info-tooltip__tooltip-container"
data-original-title="null"
data-tooltipped=""
style="display: inline;"
tabindex="0"
>
<svg
viewBox="0 0 10 10"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M5 0C2.2 0 0 2.2 0 5s2.2 5 5 5 5-2.2 5-5-2.2-5-5-5zm0 2c.4 0 .7.3.7.7s-.3.7-.7.7-.7-.2-.7-.6.3-.8.7-.8zm.7 6H4.3V4.3h1.5V8z"
fill="var(--color-icon-alternative)"
/>
</svg>
</div>
</div>
</div>
</div>
</h6>
<div
class="transaction-detail-item__detail-values"
>
<h6
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography typography--h6 typography--weight-normal typography--style-normal typography--color-text-alternative"
>
<div
class="gas-details-item__currency-container"
>
<div
class="currency-display-component"
title="0"
>
<span
class="currency-display-component__prefix"
/>
<span
class="currency-display-component__text"
>
0
</span>
</div>
</div>
</h6>
<h6
class="box box--margin-top-1 box--margin-bottom-1 box--margin-left-1 box--flex-direction-row box--text-align-right typography typography--h6 typography--weight-bold typography--style-normal typography--color-text-default"
>
<div
class="gas-details-item__currency-container"
>
<div
class="currency-display-component"
title="0 ETH"
>
<span
class="currency-display-component__prefix"
/>
<span
class="currency-display-component__text"
>
0
</span>
<span
class="currency-display-component__suffix"
>
ETH
</span>
</div>
</div>
</h6>
</div>
</div>
<div
class="transaction-detail-item__row"
>
<div>
<h6
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography gas-timing gas-timing--positive typography--h7 typography--weight-normal typography--style-normal typography--color-text-default"
>
Maybe in 1 seconds
</h6>
</div>
<h6
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography transaction-detail-item__row-subText typography--h7 typography--weight-normal typography--style-normal typography--align-end typography--color-text-alternative"
>
<div
class="box gas-details-item__gasfee-label box--display-inline-flex box--flex-direction-row"
>
<div
class="box box--margin-right-1 box--flex-direction-row"
>
<strong>
Max fee:
</strong>
</div>
<div
class="gas-details-item__currency-container"
>
<div
class="currency-display-component"
title="0 ETH"
>
<span
class="currency-display-component__prefix"
/>
<span
class="currency-display-component__text"
>
0
</span>
<span
class="currency-display-component__suffix"
>
ETH
</span>
</div>
</div>
</div>
</h6>
</div>
</div>
</div>
`;

View File

@ -0,0 +1,43 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import {
checkNetworkAndAccountSupports1559,
txDataSelector,
} from '../../../selectors';
import { isLegacyTransaction } from '../../../helpers/utils/transactions.util';
import GasDetailsItem from '../gas-details-item';
import { getCurrentDraftTransaction } from '../../../ducks/send';
import { TransactionEnvelopeType } from '../../../../shared/constants/transaction';
import { ConfirmLegacyGasDisplay } from './confirm-legacy-gas-display';
const ConfirmGasDisplay = ({ userAcknowledgedGasMissing = false }) => {
const { txParams } = useSelector((state) => txDataSelector(state));
const draftTransaction = useSelector(getCurrentDraftTransaction);
const transactionType = draftTransaction?.transactionType;
let isLegacyTxn;
if (transactionType) {
isLegacyTxn = transactionType === TransactionEnvelopeType.legacy;
} else {
isLegacyTxn = isLegacyTransaction(txParams);
}
const networkAndAccountSupports1559 = useSelector(
checkNetworkAndAccountSupports1559,
);
const supportsEIP1559 = networkAndAccountSupports1559 && !isLegacyTxn;
return supportsEIP1559 ? (
<GasDetailsItem userAcknowledgedGasMissing={userAcknowledgedGasMissing} />
) : (
<ConfirmLegacyGasDisplay />
);
};
ConfirmGasDisplay.propTypes = {
userAcknowledgedGasMissing: PropTypes.bool,
};
export default ConfirmGasDisplay;

View File

@ -0,0 +1,101 @@
import React from 'react';
import { screen } from '@testing-library/react';
import { GasEstimateTypes } from '../../../../shared/constants/gas';
import mockEstimates from '../../../../test/data/mock-estimates.json';
import mockState from '../../../../test/data/mock-state.json';
import { renderWithProvider } from '../../../../test/jest';
import configureStore from '../../../store/store';
import { GasFeeContextProvider } from '../../../contexts/gasFee';
import ConfirmGasDisplay from './confirm-gas-display';
jest.mock('../../../store/actions', () => ({
disconnectGasFeeEstimatePoller: jest.fn(),
getGasFeeEstimatesAndStartPolling: jest
.fn()
.mockImplementation(() => Promise.resolve()),
addPollingTokenToAppState: jest.fn(),
getGasFeeTimeEstimate: jest.fn().mockImplementation(() => Promise.resolve()),
}));
const render = ({ transactionProp = {}, contextProps = {} } = {}) => {
const store = configureStore({
...mockState,
...contextProps,
metamask: {
...mockState.metamask,
accounts: {
[mockState.metamask.selectedAddress]: {
address: mockState.metamask.selectedAddress,
balance: '0x1F4',
},
},
preferences: {
useNativeCurrencyAsPrimaryCurrency: true,
},
gasFeeEstimates: mockEstimates[GasEstimateTypes.feeMarket],
},
});
return renderWithProvider(
<GasFeeContextProvider transaction={transactionProp}>
<ConfirmGasDisplay />
</GasFeeContextProvider>,
store,
);
};
describe('ConfirmGasDisplay', () => {
it('should match snapshot', async () => {
const { container } = render();
expect(container).toMatchSnapshot();
});
it('should render gas display labels for EIP1559 transcations', () => {
render({
transactionProp: {
txParams: {
gas: '0x5208',
maxFeePerGas: '0x59682f10',
maxPriorityFeePerGas: '0x59682f00',
},
userFeeLevel: 'medium',
},
});
expect(screen.queryByText('Gas')).toBeInTheDocument();
expect(screen.queryByText('(estimated)')).toBeInTheDocument();
expect(screen.queryByText('Max fee:')).toBeInTheDocument();
expect(screen.queryAllByText('ETH').length).toBeGreaterThan(0);
});
it('should render gas display labels for legacy transcations', () => {
render({
contextProps: {
metamask: {
networkDetails: {
EIPS: {
1559: false,
},
},
},
confirmTransaction: {
txData: {
id: 8393540981007587,
status: 'unapproved',
chainId: '0x5',
txParams: {
from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
to: '0xc42edfcc21ed14dda456aa0756c153f7985d8813',
value: '0x0',
gas: '0x5208',
gasPrice: '0x3b9aca00',
type: '0x0',
},
},
},
},
});
expect(screen.queryByText('Estimated gas fee')).toBeInTheDocument();
expect(screen.queryByText('Max fee:')).toBeInTheDocument();
expect(screen.queryAllByText('ETH').length).toBeGreaterThan(0);
});
});

View File

@ -0,0 +1,9 @@
import { Story, Canvas, ArgsTable } from '@storybook/addon-docs';
import { ConfirmLegacyGasDisplay } from '.';
# Confirm Legacy Gas Display
Confirm Legacy Gas Display is used on confirmation screen and send screen to display gas details for legacy transaction.
<Canvas>
<Story id="components-app-ConfirmLegacyGasDisplay--default-story" />
</Canvas>

View File

@ -0,0 +1,124 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ConfirmLegacyGasDisplay should match snapshot 1`] = `
<div>
<div
class="transaction-detail-item"
>
<div
class="transaction-detail-item__row"
>
<h6
class="box box--margin-top-1 box--margin-bottom-1 box--display-flex box--flex-direction-row box--flex-wrap-nowrap box--align-items-center typography typography--h6 typography--weight-bold typography--style-normal typography--color-text-default"
>
Estimated gas fee
<div
class="info-tooltip"
>
<div>
<div
aria-describedby="tippy-tooltip-1"
class="info-tooltip__tooltip-container"
data-original-title="null"
data-tooltipped=""
style="display: inline;"
tabindex="0"
>
<svg
viewBox="0 0 10 10"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M5 0C2.2 0 0 2.2 0 5s2.2 5 5 5 5-2.2 5-5-2.2-5-5-5zm0 2c.4 0 .7.3.7.7s-.3.7-.7.7-.7-.2-.7-.6.3-.8.7-.8zm.7 6H4.3V4.3h1.5V8z"
fill="var(--color-icon-alternative)"
/>
</svg>
</div>
</div>
</div>
</h6>
<div
class="transaction-detail-item__detail-values"
>
<h6
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography typography--h6 typography--weight-normal typography--style-normal typography--color-text-alternative"
>
<div>
<div
class="currency-display-component"
title="0.000021"
>
<span
class="currency-display-component__prefix"
/>
<span
class="currency-display-component__text"
>
0.000021
</span>
</div>
</div>
</h6>
<h6
class="box box--margin-top-1 box--margin-bottom-1 box--margin-left-1 box--flex-direction-row box--text-align-right typography typography--h6 typography--weight-bold typography--style-normal typography--color-text-default"
>
<div>
<div
class="currency-display-component"
title="0.000021 ETH"
>
<span
class="currency-display-component__prefix"
/>
<span
class="currency-display-component__text"
>
0.000021
</span>
<span
class="currency-display-component__suffix"
>
ETH
</span>
</div>
</div>
</h6>
</div>
</div>
<div
class="transaction-detail-item__row"
>
<div />
<h6
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography transaction-detail-item__row-subText typography--h7 typography--weight-normal typography--style-normal typography--align-end typography--color-text-alternative"
>
<strong>
Max fee:
</strong>
<div>
<div
class="currency-display-component"
title="0.000021 ETH"
>
<span
class="currency-display-component__prefix"
/>
<span
class="currency-display-component__text"
>
0.000021
</span>
<span
class="currency-display-component__suffix"
>
ETH
</span>
</div>
</div>
</h6>
</div>
</div>
</div>
`;
exports[`ConfirmLegacyGasDisplay should match snapshot 2`] = `<div />`;

View File

@ -0,0 +1,149 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { useI18nContext } from '../../../../hooks/useI18nContext';
import {
getIsMainnet,
getPreferences,
getUnapprovedTransactions,
getUseCurrencyRateCheck,
transactionFeeSelector,
txDataSelector,
} from '../../../../selectors';
import { PRIMARY, SECONDARY } from '../../../../helpers/constants/common';
import TransactionDetailItem from '../../transaction-detail-item';
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display';
import InfoTooltip from '../../../ui/info-tooltip';
import LoadingHeartBeat from '../../../ui/loading-heartbeat';
import { Text } from '../../../component-library/text';
import {
FONT_STYLE,
TextVariant,
TextColor,
} from '../../../../helpers/constants/design-system';
import { useDraftTransactionGasValues } from '../../../../hooks/useDraftTransactionGasValues';
const renderHeartBeatIfNotInTest = () =>
process.env.IN_TEST ? null : <LoadingHeartBeat />;
const ConfirmLegacyGasDisplay = () => {
const t = useI18nContext();
// state selectors
const isMainnet = useSelector(getIsMainnet);
const useCurrencyRateCheck = useSelector(getUseCurrencyRateCheck);
const { useNativeCurrencyAsPrimaryCurrency } = useSelector(getPreferences);
const unapprovedTxs = useSelector(getUnapprovedTransactions);
const { transactionData } = useDraftTransactionGasValues();
const txData = useSelector((state) => txDataSelector(state));
const { id: transactionId, dappSuggestedGasFees } = txData;
const transaction = Object.keys(transactionData).length
? transactionData
: unapprovedTxs[transactionId] || {};
const { hexMinimumTransactionFee, hexMaximumTransactionFee } = useSelector(
(state) => transactionFeeSelector(state, transaction),
);
return (
<TransactionDetailItem
key="legacy-gas-details"
detailTitle={
dappSuggestedGasFees ? (
<>
{t('transactionDetailGasHeading')}
<InfoTooltip
contentText={t('transactionDetailDappGasTooltip')}
position="top"
>
<i className="fa fa-info-circle" />
</InfoTooltip>
</>
) : (
<>
{t('transactionDetailGasHeading')}
<InfoTooltip
contentText={
<>
<p>
{t('transactionDetailGasTooltipIntro', [
isMainnet ? t('networkNameEthereum') : '',
])}
</p>
<p>{t('transactionDetailGasTooltipExplanation')}</p>
<p>
<a
href="https://community.metamask.io/t/what-is-gas-why-do-transactions-take-so-long/3172"
target="_blank"
rel="noopener noreferrer"
>
{t('transactionDetailGasTooltipConversion')}
</a>
</p>
</>
}
position="top"
>
<i className="fa fa-info-circle" />
</InfoTooltip>
</>
)
}
detailText={
useCurrencyRateCheck && (
<div>
{renderHeartBeatIfNotInTest()}
<UserPreferencedCurrencyDisplay
type={SECONDARY}
value={hexMinimumTransactionFee}
hideLabel={Boolean(useNativeCurrencyAsPrimaryCurrency)}
/>
</div>
)
}
detailTotal={
<div>
{renderHeartBeatIfNotInTest()}
<UserPreferencedCurrencyDisplay
type={PRIMARY}
value={hexMinimumTransactionFee}
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
numberOfDecimals={6}
/>
</div>
}
subText={
<>
<strong key="editGasSubTextFeeLabel">
{t('editGasSubTextFeeLabel')}
</strong>
<div key="editGasSubTextFeeValue">
{renderHeartBeatIfNotInTest()}
<UserPreferencedCurrencyDisplay
key="editGasSubTextFeeAmount"
type={PRIMARY}
value={hexMaximumTransactionFee}
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
/>
</div>
</>
}
subTitle={
<>
{dappSuggestedGasFees && (
<Text
variant={TextVariant.bodySm}
fontStyle={FONT_STYLE.ITALIC}
color={TextColor.textAlternative}
as="h6"
>
{t('transactionDetailDappGasMoreInfo')}
</Text>
)}
</>
}
/>
);
};
export default ConfirmLegacyGasDisplay;

View File

@ -0,0 +1,28 @@
import React from 'react';
import { Provider } from 'react-redux';
import mockState from '../../../../../test/data/mock-state.json';
import configureStore from '../../../../store/store';
import README from './README.mdx';
import ConfirmLegacyGasDisplay from './confirm-legacy-gas-display';
const store = configureStore(mockState);
export default {
title: 'Components/App/ConfirmLegacyGasDisplay',
component: ConfirmLegacyGasDisplay,
decorators: [(story) => <Provider store={store}>{story()}</Provider>],
parameters: {
docs: {
page: README,
},
},
};
export const DefaultStory = () => {
return <ConfirmLegacyGasDisplay />;
};
DefaultStory.storyName = 'Default';

View File

@ -0,0 +1,110 @@
import React from 'react';
import { screen, waitFor } from '@testing-library/react';
import mockState from '../../../../../test/data/mock-state.json';
import { renderWithProvider } from '../../../../../test/jest';
import configureStore from '../../../../store/store';
import ConfirmLegacyGasDisplay from './confirm-legacy-gas-display';
const render = ({ contextProps } = {}) => {
const store = configureStore({
...mockState,
...contextProps,
metamask: {
...mockState.metamask,
accounts: {
[mockState.metamask.selectedAddress]: {
address: mockState.metamask.selectedAddress,
balance: '0x1F4',
},
},
unapprovedTxs: {
8393540981007587: {
...mockState.metamask.unapprovedTxs[8393540981007587],
txParams: {
from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
to: '0xc42edfcc21ed14dda456aa0756c153f7985d8813',
value: '0x0',
gas: '0x5208',
gasPrice: '0x3b9aca00',
type: '0x0',
},
},
},
preferences: {
useNativeCurrencyAsPrimaryCurrency: true,
},
},
confirmTransaction: {
txData: {
id: 8393540981007587,
status: 'unapproved',
chainId: '0x5',
txParams: {
from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
to: '0xc42edfcc21ed14dda456aa0756c153f7985d8813',
value: '0x0',
gas: '0x5208',
gasPrice: '0x3b9aca00',
type: '0x0',
},
},
},
});
return renderWithProvider(<ConfirmLegacyGasDisplay />, store);
};
describe('ConfirmLegacyGasDisplay', () => {
it('should match snapshot', async () => {
const { container } = render();
await waitFor(() => {
expect(container).toMatchSnapshot();
});
});
it('should render label', async () => {
render();
await waitFor(() => {
expect(screen.queryByText('Estimated gas fee')).toBeInTheDocument();
expect(screen.queryByText('Max fee:')).toBeInTheDocument();
expect(screen.queryAllByText('ETH').length).toBeGreaterThan(0);
});
});
it('should render gas fee details', async () => {
render();
await waitFor(() => {
expect(screen.queryAllByTitle('0.000021 ETH').length).toBeGreaterThan(0);
expect(screen.queryAllByText('ETH').length).toBeGreaterThan(0);
});
});
it('should render label and gas details with draftTransaction', async () => {
render({
send: {
currentTransactionUUID: '1d40b578-6184-4607-8513-762c24d0a19b',
draftTransactions: {
'1d40b578-6184-4607-8513-762c24d0a19b': {
gas: {
error: null,
gasLimit: '0x5208',
gasPrice: '0x3b9aca00',
gasTotal: '0x157c9fbb9a000',
maxFeePerGas: '0x0',
maxPriorityFeePerGas: '0x0',
wasManuallyEdited: false,
},
},
},
},
});
await waitFor(() => {
expect(screen.queryByText('Estimated gas fee')).toBeInTheDocument();
expect(screen.queryByText('Max fee:')).toBeInTheDocument();
expect(screen.queryAllByText('ETH').length).toBeGreaterThan(0);
expect(screen.queryAllByTitle('0.000021 ETH').length).toBeGreaterThan(0);
});
});
});

View File

@ -0,0 +1 @@
export { default as ConfirmLegacyGasDisplay } from './confirm-legacy-gas-display';

View File

@ -0,0 +1 @@
export { default as ConfirmGasDisplay } from './confirm-gas-display';

View File

@ -5,7 +5,12 @@ import { useSelector } from 'react-redux';
import { TextColor } from '../../../helpers/constants/design-system';
import { PRIMARY, SECONDARY } from '../../../helpers/constants/common';
import { getPreferences, getUseCurrencyRateCheck } from '../../../selectors';
import {
getPreferences,
getUseCurrencyRateCheck,
transactionFeeSelector,
} from '../../../selectors';
import { getCurrentDraftTransaction } from '../../../ducks/send';
import { useGasFeeContext } from '../../../contexts/gasFee';
import { useI18nContext } from '../../../hooks/useI18nContext';
@ -14,10 +19,20 @@ import LoadingHeartBeat from '../../ui/loading-heartbeat';
import GasTiming from '../gas-timing/gas-timing.component';
import TransactionDetailItem from '../transaction-detail-item/transaction-detail-item.component';
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display';
import { hexWEIToDecGWEI } from '../../../../shared/modules/conversion.utils';
import { useDraftTransactionGasValues } from '../../../hooks/useDraftTransactionGasValues';
import GasDetailsItemTitle from './gas-details-item-title';
const GasDetailsItem = ({ userAcknowledgedGasMissing = false }) => {
const t = useI18nContext();
const draftTransaction = useSelector(getCurrentDraftTransaction);
const { transactionData } = useDraftTransactionGasValues();
const {
hexMinimumTransactionFee: draftHexMinimumTransactionFee,
hexMaximumTransactionFee: draftHexMaximumTransactionFee,
} = useSelector((state) => transactionFeeSelector(state, transactionData));
const {
estimateUsed,
hasSimulationError,
@ -41,7 +56,8 @@ const GasDetailsItem = ({ userAcknowledgedGasMissing = false }) => {
detailTitle={<GasDetailsItemTitle />}
detailTitleColor={TextColor.textDefault}
detailText={
useCurrencyRateCheck && (
useCurrencyRateCheck &&
Object.keys(draftTransaction).length === 0 && (
<div className="gas-details-item__currency-container">
<LoadingHeartBeat estimateUsed={estimateUsed} />
<UserPreferencedCurrencyDisplay
@ -57,7 +73,7 @@ const GasDetailsItem = ({ userAcknowledgedGasMissing = false }) => {
<LoadingHeartBeat estimateUsed={estimateUsed} />
<UserPreferencedCurrencyDisplay
type={PRIMARY}
value={hexMinimumTransactionFee}
value={hexMinimumTransactionFee || draftHexMinimumTransactionFee}
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
/>
</div>
@ -86,7 +102,9 @@ const GasDetailsItem = ({ userAcknowledgedGasMissing = false }) => {
<UserPreferencedCurrencyDisplay
key="editGasSubTextFeeAmount"
type={PRIMARY}
value={hexMaximumTransactionFee}
value={
hexMaximumTransactionFee || draftHexMaximumTransactionFee
}
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
/>
</div>
@ -95,8 +113,14 @@ const GasDetailsItem = ({ userAcknowledgedGasMissing = false }) => {
}
subTitle={
<GasTiming
maxPriorityFeePerGas={maxPriorityFeePerGas.toString()}
maxFeePerGas={maxFeePerGas.toString()}
maxPriorityFeePerGas={(
maxPriorityFeePerGas ||
hexWEIToDecGWEI(transactionData.txParams.maxPriorityFeePerGas)
).toString()}
maxFeePerGas={(
maxFeePerGas ||
hexWEIToDecGWEI(transactionData.txParams.maxFeePerGas)
).toString()}
/>
}
/>

View File

@ -0,0 +1,40 @@
import { useSelector } from 'react-redux';
import { getCurrentDraftTransaction } from '../ducks/send';
import { getUnapprovedTransactions } from '../selectors';
/**
* Returns an object that resembles the txData.txParams from the Transactions state.
* While processing gas details for send transaction and edit transaction,
* the gas data from draftTransaction and unapprovedTx has to be reorganized
* to mimic the txdata.txParam from a confirmTransaction
*
* @returns {Object txData.txParams}
*/
export const useDraftTransactionGasValues = () => {
const draftTransaction = useSelector(getCurrentDraftTransaction);
const unapprovedTxs = useSelector(getUnapprovedTransactions);
let transactionData = {};
if (Object.keys(draftTransaction).length !== 0) {
const editingTransaction = unapprovedTxs[draftTransaction.id];
transactionData = {
txParams: {
gasPrice: draftTransaction.gas?.gasPrice,
gas: editingTransaction?.userEditedGasLimit
? editingTransaction?.txParams?.gas
: draftTransaction.gas?.gasLimit,
maxFeePerGas: editingTransaction?.txParams?.maxFeePerGas
? editingTransaction?.txParams?.maxFeePerGas
: draftTransaction.gas?.maxFeePerGas,
maxPriorityFeePerGas: editingTransaction?.txParams?.maxPriorityFeePerGas
? editingTransaction?.txParams?.maxPriorityFeePerGas
: draftTransaction.gas?.maxPriorityFeePerGas,
value: draftTransaction.amount?.value,
type: draftTransaction.transactionType,
},
userFeeLevel: editingTransaction?.userFeeLevel,
};
}
return { transactionData };
};

View File

@ -23,7 +23,6 @@ import {
AlignItems,
} from '../../../helpers/constants/design-system';
import { ConfirmPageContainerWarning } from '../../../components/app/confirm-page-container/confirm-page-container-content';
import GasDetailsItem from '../../../components/app/gas-details-item';
import LedgerInstructionField from '../../../components/app/ledger-instruction-field';
import { TokenStandard } from '../../../../shared/constants/transaction';
import { CHAIN_IDS, TEST_CHAINS } from '../../../../shared/constants/network';
@ -34,6 +33,7 @@ import {
} from '../../../components/component-library/icon/deprecated';
import { ButtonIcon } from '../../../components/component-library/button-icon/deprecated';
import { Text } from '../../../components/component-library';
import { ConfirmGasDisplay } from '../../../components/app/confirm-gas-display';
export default class ConfirmApproveContent extends Component {
static contextTypes = {
@ -170,7 +170,7 @@ export default class ConfirmApproveContent extends Component {
!renderSimulationFailureWarning
) {
return (
<GasDetailsItem
<ConfirmGasDisplay
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
/>
);

View File

@ -374,9 +374,7 @@ exports[`Confirm Transaction Base should match snapshot 1`] = `
<h6
class="box box--margin-top-1 box--margin-bottom-1 box--margin-left-1 box--flex-direction-row box--text-align-right typography typography--h6 typography--weight-bold typography--style-normal typography--color-text-default"
>
<div
class="confirm-page-container-content__currency-container"
>
<div>
<div
class="currency-display-component"
title="0.000021"
@ -397,18 +395,14 @@ exports[`Confirm Transaction Base should match snapshot 1`] = `
<div
class="transaction-detail-item__row"
>
<div>
</div>
<div />
<h6
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography transaction-detail-item__row-subText typography--h7 typography--weight-normal typography--style-normal typography--align-end typography--color-text-alternative"
>
<strong>
Max fee:
</strong>
<div
class="confirm-page-container-content__currency-container"
>
<div>
<div
class="currency-display-component"
title="0.000021"

View File

@ -28,18 +28,9 @@ import {
import { TransactionModalContextProvider } from '../../contexts/transaction-modal';
import TransactionDetail from '../../components/app/transaction-detail/transaction-detail.component';
import TransactionDetailItem from '../../components/app/transaction-detail-item/transaction-detail-item.component';
import InfoTooltip from '../../components/ui/info-tooltip/info-tooltip';
import LoadingHeartBeat from '../../components/ui/loading-heartbeat';
import GasDetailsItem from '../../components/app/gas-details-item';
import GasTiming from '../../components/app/gas-timing/gas-timing.component';
import LedgerInstructionField from '../../components/app/ledger-instruction-field';
import MultiLayerFeeMessage from '../../components/app/multilayer-fee-message';
import Typography from '../../components/ui/typography/typography';
import {
TextColor,
FONT_STYLE,
TypographyVariant,
} from '../../helpers/constants/design-system';
import {
disconnectGasFeeEstimatePoller,
getGasFeeEstimatesAndStartPolling,
@ -53,16 +44,13 @@ import { NETWORK_TO_NAME_MAP } from '../../../shared/constants/network';
import {
addHexes,
hexToDecimal,
hexWEIToDecGWEI,
} from '../../../shared/modules/conversion.utils';
import TransactionAlerts from '../../components/app/transaction-alerts';
import { ConfirmHexData } from '../../components/app/confirm-hexdata';
import { ConfirmData } from '../../components/app/confirm-data';
import { ConfirmTitle } from '../../components/app/confirm-title';
import { ConfirmSubTitle } from '../../components/app/confirm-subtitle';
const renderHeartBeatIfNotInTest = () =>
process.env.IN_TEST ? null : <LoadingHeartBeat />;
import { ConfirmGasDisplay } from '../../components/app/confirm-gas-display';
export default class ConfirmTransactionBase extends Component {
static contextTypes = {
@ -136,7 +124,6 @@ export default class ConfirmTransactionBase extends Component {
maxFeePerGas: PropTypes.string,
maxPriorityFeePerGas: PropTypes.string,
baseFeePerGas: PropTypes.string,
isMainnet: PropTypes.bool,
gasFeeIsCustom: PropTypes.bool,
showLedgerSteps: PropTypes.bool.isRequired,
nativeCurrency: PropTypes.string,
@ -319,11 +306,7 @@ export default class ConfirmTransactionBase extends Component {
txData,
useNativeCurrencyAsPrimaryCurrency,
primaryTotalTextOverrideMaxAmount,
maxFeePerGas,
maxPriorityFeePerGas,
isMainnet,
showLedgerSteps,
supportsEIP1559,
isMultiLayerFeeNetwork,
nativeCurrency,
isBuyableChain,
@ -439,128 +422,6 @@ export default class ConfirmTransactionBase extends Component {
</div>
) : null;
const renderGasDetailsItem = () => {
return this.supportsEIP1559 ? (
<GasDetailsItem
key="gas_details"
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
/>
) : (
<TransactionDetailItem
key="gas-item"
detailTitle={
txData.dappSuggestedGasFees ? (
<>
{t('transactionDetailGasHeading')}
<InfoTooltip
contentText={t('transactionDetailDappGasTooltip')}
position="top"
>
<i className="fa fa-info-circle" />
</InfoTooltip>
</>
) : (
<>
{t('transactionDetailGasHeading')}
<InfoTooltip
contentText={
<>
<p>
{t('transactionDetailGasTooltipIntro', [
isMainnet ? t('networkNameEthereum') : '',
])}
</p>
<p>{t('transactionDetailGasTooltipExplanation')}</p>
<p>
<a
href="https://community.metamask.io/t/what-is-gas-why-do-transactions-take-so-long/3172"
target="_blank"
rel="noopener noreferrer"
>
{t('transactionDetailGasTooltipConversion')}
</a>
</p>
</>
}
position="top"
>
<i className="fa fa-info-circle" />
</InfoTooltip>
</>
)
}
detailText={
useCurrencyRateCheck && (
<div className="confirm-page-container-content__currency-container test">
{renderHeartBeatIfNotInTest()}
<UserPreferencedCurrencyDisplay
type={SECONDARY}
value={hexMinimumTransactionFee}
hideLabel={Boolean(useNativeCurrencyAsPrimaryCurrency)}
/>
</div>
)
}
detailTotal={
<div className="confirm-page-container-content__currency-container">
{renderHeartBeatIfNotInTest()}
<UserPreferencedCurrencyDisplay
type={PRIMARY}
value={hexMinimumTransactionFee}
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
numberOfDecimals={6}
/>
</div>
}
subText={
<>
<strong key="editGasSubTextFeeLabel">
{t('editGasSubTextFeeLabel')}
</strong>
<div
key="editGasSubTextFeeValue"
className="confirm-page-container-content__currency-container"
>
{renderHeartBeatIfNotInTest()}
<UserPreferencedCurrencyDisplay
key="editGasSubTextFeeAmount"
type={PRIMARY}
value={hexMaximumTransactionFee}
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
/>
</div>
</>
}
subTitle={
<>
{txData.dappSuggestedGasFees ? (
<Typography
variant={TypographyVariant.H7}
fontStyle={FONT_STYLE.ITALIC}
color={TextColor.textAlternative}
>
{t('transactionDetailDappGasMoreInfo')}
</Typography>
) : (
''
)}
{supportsEIP1559 && (
<GasTiming
maxPriorityFeePerGas={hexWEIToDecGWEI(
maxPriorityFeePerGas ||
txData.txParams.maxPriorityFeePerGas,
).toString()}
maxFeePerGas={hexWEIToDecGWEI(
maxFeePerGas || txData.txParams.maxFeePerGas,
).toString()}
/>
)}
</>
}
/>
);
};
const simulationFailureWarning = () => (
<div className="confirm-page-container-content__error-container">
<SimulationErrorMessage
@ -594,9 +455,11 @@ export default class ConfirmTransactionBase extends Component {
}
rows={[
renderSimulationFailureWarning && simulationFailureWarning(),
!renderSimulationFailureWarning &&
!isMultiLayerFeeNetwork &&
renderGasDetailsItem(),
!renderSimulationFailureWarning && !isMultiLayerFeeNetwork && (
<ConfirmGasDisplay
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
/>
),
!renderSimulationFailureWarning && isMultiLayerFeeNetwork && (
<MultiLayerFeeMessage
transaction={txData}

View File

@ -23,7 +23,11 @@ setBackgroundConnection({
});
const baseStore = {
send: INITIAL_SEND_STATE_FOR_EXISTING_DRAFT,
send: {
...INITIAL_SEND_STATE_FOR_EXISTING_DRAFT,
currentTransactionUUID: null,
draftTransactions: {},
},
DNS: domainInitialState,
gas: {
customData: { limit: null, price: null },

View File

@ -1,13 +1,10 @@
import React, { useContext } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { I18nContext } from '../../../contexts/i18n';
import { useGasFeeContext } from '../../../contexts/gasFee';
import { PRIMARY, SECONDARY } from '../../../helpers/constants/common';
import UserPreferencedCurrencyDisplay from '../../../components/app/user-preferenced-currency-display';
import GasTiming from '../../../components/app/gas-timing';
import InfoTooltip from '../../../components/ui/info-tooltip';
import Typography from '../../../components/ui/typography';
import Button from '../../../components/ui/button';
import Box from '../../../components/ui/box';
@ -16,13 +13,11 @@ import {
DISPLAY,
FLEX_DIRECTION,
BLOCK_SIZES,
Color,
FONT_STYLE,
FONT_WEIGHT,
} from '../../../helpers/constants/design-system';
import { TokenStandard } from '../../../../shared/constants/transaction';
import LoadingHeartBeat from '../../../components/ui/loading-heartbeat';
import TransactionDetailItem from '../../../components/app/transaction-detail-item';
import { ConfirmGasDisplay } from '../../../components/app/confirm-gas-display';
import { NETWORK_TO_NAME_MAP } from '../../../../shared/constants/network';
import TransactionDetail from '../../../components/app/transaction-detail';
import ActionableMessage from '../../../components/ui/actionable-message';
@ -31,7 +26,6 @@ import {
getPreferences,
getIsBuyableChain,
transactionFeeSelector,
getIsMainnet,
getIsTestnet,
getUseCurrencyRateCheck,
} from '../../../selectors';
@ -43,7 +37,6 @@ import { showModal } from '../../../store/actions';
import {
addHexes,
hexWEIToDecETH,
hexWEIToDecGWEI,
} from '../../../../shared/modules/conversion.utils';
import {
MetaMetricsEventCategory,
@ -61,7 +54,6 @@ export default function GasDisplay({ gasError }) {
const { openBuyCryptoInPdapp } = useRamps();
const currentProvider = useSelector(getProvider);
const isMainnet = useSelector(getIsMainnet);
const isTestnet = useSelector(getIsTestnet);
const isBuyableChain = useSelector(getIsBuyableChain);
const draftTransaction = useSelector(getCurrentDraftTransaction);
@ -95,11 +87,9 @@ export default function GasDisplay({ gasError }) {
userFeeLevel: editingTransaction?.userFeeLevel,
};
const {
hexMinimumTransactionFee,
hexMaximumTransactionFee,
hexTransactionTotal,
} = useSelector((state) => transactionFeeSelector(state, transactionData));
const { hexMaximumTransactionFee, hexTransactionTotal } = useSelector(
(state) => transactionFeeSelector(state, transactionData),
);
let title;
if (
@ -158,119 +148,13 @@ export default function GasDisplay({ gasError }) {
detailTotal = primaryTotalTextOverrideMaxAmount;
maxAmount = primaryTotalTextOverrideMaxAmount;
}
return (
<>
<Box className="gas-display">
<TransactionDetail
userAcknowledgedGasMissing={false}
rows={[
<TransactionDetailItem
key="gas-item"
detailTitle={
<Box display={DISPLAY.FLEX}>
<Box marginRight={1}>{t('gas')}</Box>
<Typography
as="span"
marginTop={0}
color={Color.textMuted}
fontStyle={FONT_STYLE.ITALIC}
fontWeight={FONT_WEIGHT.NORMAL}
className="gas-display__title__estimate"
>
({t('transactionDetailGasInfoV2')})
</Typography>
<InfoTooltip
contentText={
<>
<Typography variant={TypographyVariant.H7}>
{t('transactionDetailGasTooltipIntro', [
isMainnet ? t('networkNameEthereum') : '',
])}
</Typography>
<Typography variant={TypographyVariant.H7}>
{t('transactionDetailGasTooltipExplanation')}
</Typography>
<Typography variant={TypographyVariant.H7}>
<a
href="https://community.metamask.io/t/what-is-gas-why-do-transactions-take-so-long/3172"
target="_blank"
rel="noopener noreferrer"
>
{t('transactionDetailGasTooltipConversion')}
</a>
</Typography>
</>
}
position="right"
/>
</Box>
}
detailTitleColor={Color.textDefault}
detailText={
showCurrencyRateCheck && (
<Box className="gas-display__currency-container">
<LoadingHeartBeat estimateUsed={estimateUsed} />
<UserPreferencedCurrencyDisplay
type={SECONDARY}
value={hexMinimumTransactionFee}
hideLabel={Boolean(useNativeCurrencyAsPrimaryCurrency)}
/>
</Box>
)
}
detailTotal={
<Box className="gas-display__currency-container">
<LoadingHeartBeat estimateUsed={estimateUsed} />
<UserPreferencedCurrencyDisplay
type={PRIMARY}
value={hexMinimumTransactionFee}
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
/>
</Box>
}
subText={
<>
<Box
key="editGasSubTextFeeLabel"
display={DISPLAY.INLINE_FLEX}
className={classNames('gas-display__gas-fee-label', {
'gas-display__gas-fee-warning': estimateUsed === 'high',
})}
>
<LoadingHeartBeat estimateUsed={estimateUsed} />
<Box marginRight={1}>
<strong>
{estimateUsed === 'high' && '⚠ '}
{t('editGasSubTextFeeLabel')}
</strong>
</Box>
<Box
key="editGasSubTextFeeValue"
className="gas-display__currency-container"
>
<LoadingHeartBeat estimateUsed={estimateUsed} />
<UserPreferencedCurrencyDisplay
key="editGasSubTextFeeAmount"
type={PRIMARY}
value={hexMaximumTransactionFee}
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
/>
</Box>
</Box>
</>
}
subTitle={
<GasTiming
maxPriorityFeePerGas={hexWEIToDecGWEI(
draftTransaction.gas.maxPriorityFeePerGas,
)}
maxFeePerGas={hexWEIToDecGWEI(
draftTransaction.gas.maxFeePerGas,
)}
/>
}
/>,
<ConfirmGasDisplay key="gas-display" />,
(gasError || isInsufficientTokenError) && (
<TransactionDetailItem
key="total-item"

View File

@ -35,7 +35,6 @@
height: 120px;
}
&__currency-container,
&__total-amount,
&__total-value {
position: relative;

View File

@ -197,7 +197,7 @@ exports[`SendContent Component render should match snapshot 1`] = `
Gas
</div>
<span
class="box box--margin-bottom-1 box--flex-direction-row typography gas-display__title__estimate typography--p typography--weight-normal typography--style-italic typography--color-text-muted"
class="gas-details-item-title__estimate"
>
(
estimated
@ -236,7 +236,7 @@ exports[`SendContent Component render should match snapshot 1`] = `
class="box box--margin-top-1 box--margin-bottom-1 box--margin-left-1 box--flex-direction-row box--text-align-right typography typography--h6 typography--weight-bold typography--style-normal typography--color-text-default"
>
<div
class="box gas-display__currency-container box--flex-direction-row"
class="gas-details-item__currency-container"
>
<div
class="currency-display-component"
@ -274,7 +274,7 @@ exports[`SendContent Component render should match snapshot 1`] = `
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography transaction-detail-item__row-subText typography--h7 typography--weight-normal typography--style-normal typography--align-end typography--color-text-alternative"
>
<div
class="box gas-display__gas-fee-label box--display-inline-flex box--flex-direction-row"
class="box gas-details-item__gasfee-label box--display-inline-flex box--flex-direction-row"
>
<div
class="box box--margin-right-1 box--flex-direction-row"
@ -284,7 +284,7 @@ exports[`SendContent Component render should match snapshot 1`] = `
</strong>
</div>
<div
class="box gas-display__currency-container box--flex-direction-row"
class="gas-details-item__currency-container"
>
<div
class="currency-display-component"

View File

@ -79,6 +79,9 @@ const state = {
confirmTransaction: {
txData: {},
},
send: {
draftTransactions: {},
},
};
jest.mock('../../store/actions', () => ({