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

Support for Layer 2 networks with transaction fees on both layers (#12658)

* Support for Layer 2 networks with transaction fees on both layers

* Use  variable name in transaction-breakdown

* Add comment on code source to ui/helpers/utils/optimism/fetchEstimatedL1Fee.js

* Fix unit tests

* Ensure values passed to  are defined

* Fix activity log
This commit is contained in:
Dan J Miller 2021-11-11 13:16:45 -03:30 committed by GitHub
parent 0a8f94af81
commit 9fa15dda6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1153 additions and 79 deletions

View File

@ -1265,6 +1265,9 @@
"lastConnected": {
"message": "Last Connected"
},
"layer1Fees": {
"message": "Layer 1 fees"
},
"learnMore": {
"message": "Learn more"
},
@ -2825,6 +2828,12 @@
"transactionDetailGasTotalSubtitle": {
"message": "Amount + gas fee"
},
"transactionDetailLayer2GasHeading": {
"message": "Layer 2 gas fee"
},
"transactionDetailMultiLayerTotalSubtitle": {
"message": "Amount + fees"
},
"transactionDropped": {
"message": "Transaction dropped at $2."
},
@ -2843,6 +2852,15 @@
"transactionHistoryBaseFee": {
"message": "Base Fee (GWEI)"
},
"transactionHistoryL1GasLabel": {
"message": "Total L1 gas fee"
},
"transactionHistoryL2GasLimitLabel": {
"message": "L2 gas limit`"
},
"transactionHistoryL2GasPriceLabel": {
"message": "L2 gas price`"
},
"transactionHistoryMaxFeePerGas": {
"message": "Max Fee Per Gas"
},

View File

@ -84,6 +84,12 @@
"multihashes": true
}
},
"@eth-optimism/contracts": {
"packages": {
"@ethersproject/abstract-provider": true,
"ethers": true
}
},
"@ethereumjs/common": {
"packages": {
"buffer": true,
@ -319,6 +325,7 @@
"@ethersproject/bignumber": true,
"@ethersproject/bytes": true,
"@ethersproject/keccak256": true,
"@ethersproject/logger": true,
"@ethersproject/sha2": true,
"@ethersproject/strings": true
}

View File

@ -98,6 +98,7 @@
"@babel/runtime": "^7.5.5",
"@download/blockies": "^1.0.3",
"@ensdomains/content-hash": "^2.5.6",
"@eth-optimism/contracts": "0.0.0-2021919175625",
"@ethereumjs/common": "^2.3.1",
"@ethereumjs/tx": "^3.2.1",
"@formatjs/intl-relativetimeformat": "^5.2.6",
@ -367,7 +368,9 @@
"github:assemblyscript/assemblyscript": false,
"tiny-secp256k1": false,
"@lavamoat/preinstall-always-fail": false,
"fsevents": false
"fsevents": false,
"node-hid": false,
"usb": false
}
}
}

View File

@ -289,4 +289,6 @@ export {
toNegative,
subtractCurrencies,
decGWEIToHexWEI,
toBigNumber,
toNormalizedDenomination,
};

View File

@ -7,6 +7,7 @@ import FormField from '../../ui/form-field';
import { GAS_ESTIMATE_TYPES } from '../../../../shared/constants/gas';
import { getGasFormErrorText } from '../../../helpers/constants/gas';
import { getIsGasEstimatesLoading } from '../../../ducks/metamask/metamask';
import { getNetworkSupportsSettingGasPrice } from '../../../selectors/selectors';
export default function AdvancedGasControls({
gasEstimateType,
@ -34,6 +35,10 @@ export default function AdvancedGasControls({
gasEstimateType === GAS_ESTIMATE_TYPES.ETH_GASPRICE ||
isGasEstimatesLoading);
const networkSupportsSettingGasPrice = useSelector(
getNetworkSupportsSettingGasPrice,
);
return (
<div className="advanced-gas-controls">
<FormField
@ -106,6 +111,7 @@ export default function AdvancedGasControls({
? getGasFormErrorText(gasErrors.gasPrice, t)
: null
}
disabled={!networkSupportsSettingGasPrice}
/>
</>
)}

View File

@ -7,7 +7,9 @@ import { renderWithProvider } from '../../../../test/jest/rendering';
import AdvancedGasControls from './advanced-gas-controls.component';
const renderComponent = (props) => {
const store = configureMockStore([])({ metamask: { identities: [] } });
const store = configureMockStore([])({
metamask: { identities: [], provider: {} },
});
return renderWithProvider(<AdvancedGasControls {...props} />, store);
};

View File

@ -20,10 +20,12 @@ export default class AdvancedGasInputs extends Component {
customGasLimitMessage: PropTypes.string,
minimumGasLimit: PropTypes.number,
customPriceIsExcessive: PropTypes.bool,
networkSupportsSettingGasPrice: PropTypes.bool,
};
static defaultProps = {
customPriceIsExcessive: false,
networkSupportsSettingGasPrice: true,
};
constructor(props) {
@ -131,6 +133,7 @@ export default class AdvancedGasInputs extends Component {
testId,
customMessageComponent,
tooltipTitle,
disabled,
}) {
return (
<div className="advanced-gas-inputs__gas-edit-row">
@ -152,6 +155,7 @@ export default class AdvancedGasInputs extends Component {
min="0"
value={value}
onChange={onChange}
disabled={disabled}
data-testid={testId}
/>
<div
@ -162,18 +166,22 @@ export default class AdvancedGasInputs extends Component {
errorType === 'error',
'advanced-gas-inputs__gas-edit-row__input--warning':
errorType === 'warning',
'advanced-gas-inputs__gas-edit-row__input-arrows--hidden': disabled,
},
)}
>
<div
className="advanced-gas-inputs__gas-edit-row__input-arrows__i-wrap"
onClick={() => onChange({ target: { value: value + 1 } })}
onClick={() =>
!disabled && onChange({ target: { value: value + 1 } })
}
>
<i className="fa fa-sm fa-angle-up" />
</div>
<div
className="advanced-gas-inputs__gas-edit-row__input-arrows__i-wrap"
onClick={() =>
!disabled &&
onChange({ target: { value: Math.max(value - 1, 0) } })
}
>
@ -194,6 +202,7 @@ export default class AdvancedGasInputs extends Component {
customGasLimitMessage,
minimumGasLimit,
customPriceIsExcessive,
networkSupportsSettingGasPrice,
} = this.props;
const { gasPrice, gasLimit } = this.state;
@ -243,6 +252,7 @@ export default class AdvancedGasInputs extends Component {
onChange: this.onChangeGasPrice,
errorComponent: gasPriceErrorComponent,
errorType: gasPriceErrorType,
disabled: !networkSupportsSettingGasPrice,
})}
{this.renderGasInput({
label: this.context.t('gasLimit'),

View File

@ -4,6 +4,7 @@ import {
decimalToHex,
hexWEIToDecGWEI,
} from '../../../../helpers/utils/conversions.util';
import { getNetworkSupportsSettingGasPrice } from '../../../../selectors/selectors';
import { MIN_GAS_LIMIT_DEC } from '../../../../pages/send/send.constants';
import AdvancedGasInputs from './advanced-gas-inputs.component';
@ -19,7 +20,13 @@ function convertMinimumGasLimitForInputs(minimumGasLimit = MIN_GAS_LIMIT_DEC) {
return parseInt(minimumGasLimit, 10);
}
const mergeProps = (stateProps, dispatchProps, ownProps) => {
function mapStateToProps(state) {
return {
networkSupportsSettingGasPrice: getNetworkSupportsSettingGasPrice(state),
};
}
function mergeProps(stateProps, dispatchProps, ownProps) {
const {
customGasPrice,
customGasLimit,
@ -38,6 +45,6 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
updateCustomGasPrice(decGWEIToHexWEI(price)),
updateCustomGasLimit: (limit) => updateCustomGasLimit(decimalToHex(limit)),
};
};
}
export default connect(null, null, mergeProps)(AdvancedGasInputs);
export default connect(mapStateToProps, null, mergeProps)(AdvancedGasInputs);

View File

@ -20,7 +20,7 @@ describe('AdvancedGasInputs', () => {
minimumGasLimit: 21000,
};
const store = configureStore({});
const store = configureStore({ metamask: { provider: {} } });
beforeEach(() => {
clock = sinon.useFakeTimers();

View File

@ -109,6 +109,10 @@
i {
font-size: $font-size-h8;
}
&--hidden {
display: none;
}
}
&__input-arrows--error {

View File

@ -0,0 +1 @@
export { default } from './multi-layer-fee-message';

View File

@ -0,0 +1,70 @@
import React, { useContext, useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { captureException } from '@sentry/browser';
import TransactionDetailItem from '../transaction-detail-item/transaction-detail-item.component';
import fetchEstimatedL1Fee from '../../../helpers/utils/optimism/fetchEstimatedL1Fee';
import { I18nContext } from '../../../contexts/i18n';
import { sumHexes } from '../../../helpers/utils/transactions.util';
import {
toBigNumber,
toNormalizedDenomination,
} from '../../../../shared/modules/conversion.utils';
export default function MultilayerFeeMessage({ transaction, layer2fee }) {
const t = useContext(I18nContext);
const [fetchedLayer1Total, setLayer1Total] = useState(null);
let layer1Total = 'unknown';
if (fetchedLayer1Total !== null) {
const layer1TotalBN = toBigNumber.hex(fetchedLayer1Total);
layer1Total = `${toNormalizedDenomination
.WEI(layer1TotalBN)
.toString(10)} ETH`;
}
const totalInWeiHex = sumHexes(
layer2fee || '0x0',
fetchedLayer1Total || '0x0',
transaction.txParams.value || '0x0',
);
const totalBN = toBigNumber.hex(totalInWeiHex);
const totalInEth = `${toNormalizedDenomination
.WEI(totalBN)
.toString(10)} ETH`;
useEffect(() => {
const getEstimatedL1Fee = async () => {
try {
const result = await fetchEstimatedL1Fee(global.eth, transaction);
setLayer1Total(result);
} catch (e) {
captureException(e);
setLayer1Total(null);
}
};
getEstimatedL1Fee();
}, [transaction]);
return (
<>
<TransactionDetailItem
key="total-item"
detailTitle={t('layer1Fees')}
detailTotal={layer1Total}
/>
<TransactionDetailItem
key="total-item"
detailTitle={t('total')}
detailTotal={totalInEth}
subTitle={t('transactionDetailMultiLayerTotalSubtitle')}
/>
</>
);
}
MultilayerFeeMessage.propTypes = {
transaction: PropTypes.object,
layer2fee: PropTypes.string,
};

View File

@ -89,7 +89,7 @@ export function getActivities(transaction, isFirstTransaction = false) {
// need to cache these values because the status update history events don't provide us with
// the latest gas limit and gas price.
cachedGasLimit = gas;
cachedGasPrice = eip1559Price || gasPrice || '0x0';
cachedGasPrice = eip1559Price || gasPrice || paramsGasPrice || '0x0';
if (isFirstTransaction) {
return acc.concat({

View File

@ -33,6 +33,8 @@ export default class TransactionBreakdown extends PureComponent {
priorityFee: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
hexGasTotal: PropTypes.string,
isEIP1559Transaction: PropTypes.bool,
isMultiLayerFeeNetwork: PropTypes.bool,
l1HexGasTotal: PropTypes.string,
};
static defaultProps = {
@ -57,6 +59,8 @@ export default class TransactionBreakdown extends PureComponent {
priorityFee,
hexGasTotal,
isEIP1559Transaction,
isMultiLayerFeeNetwork,
l1HexGasTotal,
} = this.props;
return (
<div className={classnames('transaction-breakdown', className)}>
@ -77,7 +81,11 @@ export default class TransactionBreakdown extends PureComponent {
</span>
</TransactionBreakdownRow>
<TransactionBreakdownRow
title={`${t('gasLimit')} (${t('units')})`}
title={
isMultiLayerFeeNetwork
? t('transactionHistoryL2GasLimitLabel')
: `${t('gasLimit')} (${t('units')})`
}
className="transaction-breakdown__row-title"
>
{typeof gas === 'undefined' ? (
@ -127,7 +135,13 @@ export default class TransactionBreakdown extends PureComponent {
</TransactionBreakdownRow>
) : null}
{!isEIP1559Transaction && (
<TransactionBreakdownRow title={t('advancedGasPriceTitle')}>
<TransactionBreakdownRow
title={
isMultiLayerFeeNetwork
? t('transactionHistoryL2GasPriceLabel')
: t('advancedGasPriceTitle')
}
>
{typeof gasPrice === 'undefined' ? (
'?'
) : (
@ -182,11 +196,30 @@ export default class TransactionBreakdown extends PureComponent {
)}
</TransactionBreakdownRow>
)}
{isMultiLayerFeeNetwork && (
<TransactionBreakdownRow title={t('transactionHistoryL1GasLabel')}>
<UserPreferencedCurrencyDisplay
className="transaction-breakdown__value"
data-testid="transaction-breakdown__l1-gas-total"
numberOfDecimals={18}
value={l1HexGasTotal}
type={PRIMARY}
/>
{showFiat && (
<UserPreferencedCurrencyDisplay
className="transaction-breakdown__value"
type={SECONDARY}
value={l1HexGasTotal}
/>
)}
</TransactionBreakdownRow>
)}
<TransactionBreakdownRow title={t('total')}>
<UserPreferencedCurrencyDisplay
className="transaction-breakdown__value transaction-breakdown__value--eth-total"
type={PRIMARY}
value={totalInHex}
numberOfDecimals={isMultiLayerFeeNetwork ? 18 : null}
/>
{showFiat && (
<UserPreferencedCurrencyDisplay

View File

@ -1,17 +1,21 @@
import { connect } from 'react-redux';
import { getShouldShowFiat } from '../../../selectors';
import {
getShouldShowFiat,
getIsMultiLayerFeeNetwork,
} from '../../../selectors';
import { getNativeCurrency } from '../../../ducks/metamask/metamask';
import { getHexGasTotal } from '../../../helpers/utils/confirm-tx.util';
import { subtractHexes } from '../../../helpers/utils/conversions.util';
import { sumHexes } from '../../../helpers/utils/transactions.util';
import { isEIP1559Transaction } from '../../../../shared/modules/transaction.utils';
import TransactionBreakdown from './transaction-breakdown.component';
const mapStateToProps = (state, ownProps) => {
const { transaction, isTokenApprove } = ownProps;
const {
txParams: { gas, gasPrice, maxFeePerGas, value } = {},
txReceipt: { gasUsed, effectiveGasPrice } = {},
txReceipt: { gasUsed, effectiveGasPrice, l1Fee: l1HexGasTotal } = {},
baseFeePerGas,
} = transaction;
@ -33,7 +37,15 @@ const mapStateToProps = (state, ownProps) => {
usedGasPrice &&
getHexGasTotal({ gasLimit, gasPrice: usedGasPrice })) ||
'0x0';
const totalInHex = sumHexes(hexGasTotal, value);
let totalInHex = sumHexes(hexGasTotal, value);
const isMultiLayerFeeNetwork =
getIsMultiLayerFeeNetwork(state) && l1HexGasTotal !== undefined;
if (isMultiLayerFeeNetwork) {
totalInHex = sumHexes(totalInHex, l1HexGasTotal);
}
return {
nativeCurrency: getNativeCurrency(state),
@ -48,6 +60,8 @@ const mapStateToProps = (state, ownProps) => {
priorityFee,
baseFee: baseFeePerGas,
isEIP1559Transaction: isEIP1559Transaction(transaction),
isMultiLayerFeeNetwork,
l1HexGasTotal,
};
};

View File

@ -0,0 +1,33 @@
import { omit } from 'lodash';
import { BN, stripHexPrefix } from 'ethereumjs-util';
import Common, { Chain, Hardfork } from '@ethereumjs/common';
import { TransactionFactory } from '@ethereumjs/tx';
function buildTxParams(txMeta) {
return {
...omit(txMeta.txParams, 'gas'),
gasLimit: txMeta.txParams.gas,
};
}
function buildTransactionCommon(txMeta) {
// This produces a transaction whose information does not completely match an
// Optimism transaction — for instance, DEFAULT_CHAIN is still 'mainnet' and
// genesis points to the mainnet genesis, not the Optimism genesis — but
// considering that all we want to do is serialize a transaction, this works
// fine for our use case.
return Common.forCustomChain(Chain.Mainnet, {
chainId: new BN(stripHexPrefix(txMeta.chainId), 16),
networkId: new BN(txMeta.metamaskNetworkId, 10),
// Optimism only supports type-0 transactions; it does not support any of
// the newer EIPs since EIP-155. Source:
// <https://github.com/ethereum-optimism/optimism/blob/develop/specs/l2geth/transaction-types.md>
defaultHardfork: Hardfork.SpuriousDragon,
});
}
export default function buildUnserializedTransaction(txMeta) {
const txParams = buildTxParams(txMeta);
const common = buildTransactionCommon(txMeta);
return TransactionFactory.fromTxData(txParams, { common });
}

View File

@ -0,0 +1,28 @@
import { BN } from 'ethereumjs-util';
import { times } from 'lodash';
import buildUnserializedTransaction from './buildUnserializedTransaction';
describe('buildUnserializedTransaction', () => {
it('returns a transaction that can be serialized and fed to an Optimism smart contract', () => {
const unserializedTransaction = buildUnserializedTransaction({
txParams: {
nonce: '0x0',
gasPrice: `0x${new BN('100').toString(16)}`,
gas: `0x${new BN('21000').toString(16)}`,
to: '0x0000000000000000000000000000000000000000',
value: `0x${new BN('10000000000000').toString(16)}`,
data: '0x0',
},
});
expect(unserializedTransaction).toMatchObject({
nonce: new BN('00', 16),
gasPrice: new BN('64', 16),
gasLimit: new BN('5208', 16),
to: expect.objectContaining({
buf: Buffer.from(times(20, 0)),
}),
value: new BN('09184e72a000', 16),
data: Buffer.from([0]),
});
});
});

View File

@ -0,0 +1,24 @@
import * as ethers from 'ethers';
import * as optimismContracts from '@eth-optimism/contracts';
import buildUnserializedTransaction from './buildUnserializedTransaction';
// The code in this file is largely drawn from https://community.optimism.io/docs/developers/l2/new-fees.html#for-frontend-and-wallet-developers
function buildOVMGasPriceOracleContract(eth) {
const OVMGasPriceOracle = optimismContracts
.getContractFactory('OVM_GasPriceOracle')
.attach(optimismContracts.predeploys.OVM_GasPriceOracle);
const abi = JSON.parse(
OVMGasPriceOracle.interface.format(ethers.utils.FormatTypes.json),
);
return eth.contract(abi).at(OVMGasPriceOracle.address);
}
export default async function fetchEstimatedL1Fee(eth, txMeta) {
const contract = buildOVMGasPriceOracleContract(eth);
const serializedTransaction = buildUnserializedTransaction(
txMeta,
).serialize();
const result = await contract.getL1Fee(serializedTransaction);
return result?.[0]?.toString(16);
}

View File

@ -36,6 +36,7 @@ import InfoTooltip from '../../components/ui/info-tooltip/info-tooltip';
import LoadingHeartBeat from '../../components/ui/loading-heartbeat';
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 {
COLORS,
@ -134,6 +135,7 @@ export default class ConfirmTransactionBase extends Component {
nativeCurrency: PropTypes.string,
supportsEIP1559: PropTypes.bool,
hardwareWalletRequiresConnection: PropTypes.bool,
isMultiLayerFeeNetwork: PropTypes.bool,
};
state = {
@ -315,6 +317,7 @@ export default class ConfirmTransactionBase extends Component {
isMainnet,
showLedgerSteps,
supportsEIP1559,
isMultiLayerFeeNetwork,
} = this.props;
const { t } = this.context;
@ -433,7 +436,9 @@ export default class ConfirmTransactionBase extends Component {
detailTitle={
txData.dappSuggestedGasFees ? (
<>
{t('transactionDetailGasHeading')}
{isMultiLayerFeeNetwork
? t('transactionDetailLayer2GasHeading')
: t('transactionDetailGasHeading')}
<InfoTooltip
contentText={t('transactionDetailDappGasTooltip')}
position="top"
@ -443,7 +448,9 @@ export default class ConfirmTransactionBase extends Component {
</>
) : (
<>
{t('transactionDetailGasHeading')}
{isMultiLayerFeeNetwork
? t('transactionDetailLayer2GasHeading')
: t('transactionDetailGasHeading')}
<InfoTooltip
contentText={
<>
@ -473,14 +480,16 @@ export default class ConfirmTransactionBase extends Component {
}
detailTitleColor={COLORS.BLACK}
detailText={
<div className="confirm-page-container-content__currency-container">
{renderHeartBeatIfNotInTest()}
<UserPreferencedCurrencyDisplay
type={SECONDARY}
value={hexMinimumTransactionFee}
hideLabel={Boolean(useNativeCurrencyAsPrimaryCurrency)}
/>
</div>
!isMultiLayerFeeNetwork && (
<div className="confirm-page-container-content__currency-container">
{renderHeartBeatIfNotInTest()}
<UserPreferencedCurrencyDisplay
type={SECONDARY}
value={hexMinimumTransactionFee}
hideLabel={Boolean(useNativeCurrencyAsPrimaryCurrency)}
/>
</div>
)
}
detailTotal={
<div className="confirm-page-container-content__currency-container">
@ -489,26 +498,30 @@ export default class ConfirmTransactionBase extends Component {
type={PRIMARY}
value={hexMinimumTransactionFee}
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
numberOfDecimals={isMultiLayerFeeNetwork ? 18 : 6}
/>
</div>
}
subText={t('editGasSubTextFee', [
<b key="editGasSubTextFeeLabel">
{t('editGasSubTextFeeLabel')}
</b>,
<div
key="editGasSubTextFeeValue"
className="confirm-page-container-content__currency-container"
>
{renderHeartBeatIfNotInTest()}
<UserPreferencedCurrencyDisplay
key="editGasSubTextFeeAmount"
type={PRIMARY}
value={hexMaximumTransactionFee}
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
/>
</div>,
])}
subText={
!isMultiLayerFeeNetwork &&
t('editGasSubTextFee', [
<b key="editGasSubTextFeeLabel">
{t('editGasSubTextFeeLabel')}
</b>,
<div
key="editGasSubTextFeeValue"
className="confirm-page-container-content__currency-container"
>
{renderHeartBeatIfNotInTest()}
<UserPreferencedCurrencyDisplay
key="editGasSubTextFeeAmount"
type={PRIMARY}
value={hexMaximumTransactionFee}
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
/>
</div>,
])
}
subTitle={
<>
{txData.dappSuggestedGasFees ? (
@ -537,19 +550,27 @@ export default class ConfirmTransactionBase extends Component {
}
/>
),
<TransactionDetailItem
key="total-item"
detailTitle={t('total')}
detailText={renderTotalDetailText()}
detailTotal={renderTotalDetailTotal()}
subTitle={t('transactionDetailGasTotalSubtitle')}
subText={t('editGasSubTextAmount', [
<b key="editGasSubTextAmountLabel">
{t('editGasSubTextAmountLabel')}
</b>,
renderTotalMaxAmount(),
])}
/>,
isMultiLayerFeeNetwork && (
<MultiLayerFeeMessage
transaction={txData}
layer2fee={hexMinimumTransactionFee}
/>
),
!isMultiLayerFeeNetwork && (
<TransactionDetailItem
key="total-item"
detailTitle={t('total')}
detailText={renderTotalDetailText()}
detailTotal={renderTotalDetailTotal()}
subTitle={t('transactionDetailGasTotalSubtitle')}
subText={t('editGasSubTextAmount', [
<b key="editGasSubTextAmountLabel">
{t('editGasSubTextAmountLabel')}
</b>,
renderTotalMaxAmount(),
])}
/>
),
]}
/>
{nonceField}

View File

@ -31,6 +31,7 @@ import {
doesAddressRequireLedgerHidConnection,
getUseTokenDetection,
getTokenList,
getIsMultiLayerFeeNetwork,
} from '../../selectors';
import { getMostRecentOverviewPage } from '../../ducks/history/history';
import {
@ -179,6 +180,8 @@ const mapStateToProps = (state, ownProps) => {
fromAddress,
);
const isMultiLayerFeeNetwork = getIsMultiLayerFeeNetwork(state);
return {
balance,
fromAddress,
@ -227,6 +230,7 @@ const mapStateToProps = (state, ownProps) => {
showLedgerSteps: fromAddressIsLedger,
nativeCurrency,
hardwareWalletRequiresConnection,
isMultiLayerFeeNetwork,
};
};

View File

@ -5,6 +5,8 @@ import {
TEST_CHAINS,
NETWORK_TYPE_RPC,
NATIVE_CURRENCY_TOKEN_IMAGE_MAP,
OPTIMISM_CHAIN_ID,
OPTIMISM_TESTNET_CHAIN_ID,
} from '../../shared/constants/network';
import {
KEYRING_TYPES,
@ -701,3 +703,18 @@ export function getProvider(state) {
export function getFrequentRpcListDetail(state) {
return state.metamask.frequentRpcListDetail;
}
export function getIsOptimism(state) {
return (
getCurrentChainId(state) === OPTIMISM_CHAIN_ID ||
getCurrentChainId(state) === OPTIMISM_TESTNET_CHAIN_ID
);
}
export function getNetworkSupportsSettingGasPrice(state) {
return !getIsOptimism(state);
}
export function getIsMultiLayerFeeNetwork(state) {
return getIsOptimism(state);
}

822
yarn.lock

File diff suppressed because it is too large Load Diff