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:
parent
0a8f94af81
commit
9fa15dda6f
@ -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"
|
||||
},
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -289,4 +289,6 @@ export {
|
||||
toNegative,
|
||||
subtractCurrencies,
|
||||
decGWEIToHexWEI,
|
||||
toBigNumber,
|
||||
toNormalizedDenomination,
|
||||
};
|
||||
|
@ -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}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
|
@ -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'),
|
||||
|
@ -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);
|
||||
|
@ -20,7 +20,7 @@ describe('AdvancedGasInputs', () => {
|
||||
minimumGasLimit: 21000,
|
||||
};
|
||||
|
||||
const store = configureStore({});
|
||||
const store = configureStore({ metamask: { provider: {} } });
|
||||
|
||||
beforeEach(() => {
|
||||
clock = sinon.useFakeTimers();
|
||||
|
@ -109,6 +109,10 @@
|
||||
i {
|
||||
font-size: $font-size-h8;
|
||||
}
|
||||
|
||||
&--hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__input-arrows--error {
|
||||
|
1
ui/components/app/multilayer-fee-message/index.js
Normal file
1
ui/components/app/multilayer-fee-message/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './multi-layer-fee-message';
|
@ -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,
|
||||
};
|
@ -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({
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
33
ui/helpers/utils/optimism/buildUnserializedTransaction.js
Normal file
33
ui/helpers/utils/optimism/buildUnserializedTransaction.js
Normal 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 });
|
||||
}
|
@ -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]),
|
||||
});
|
||||
});
|
||||
});
|
24
ui/helpers/utils/optimism/fetchEstimatedL1Fee.js
Normal file
24
ui/helpers/utils/optimism/fetchEstimatedL1Fee.js
Normal 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);
|
||||
}
|
@ -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}
|
||||
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user