1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Enable new networks for Swaps (#16396)

This commit is contained in:
Daniel 2022-11-08 19:14:17 +01:00 committed by GitHub
parent 6c0930899d
commit 1a9ebab828
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 115 additions and 10 deletions

View File

@ -2143,6 +2143,9 @@
"networkName": {
"message": "Network name"
},
"networkNameArbitrum": {
"message": "Arbitrum"
},
"networkNameAvalanche": {
"message": "Avalanche"
},
@ -2158,6 +2161,9 @@
"networkNameGoerli": {
"message": "Goerli"
},
"networkNameOptimism": {
"message": "Optimism"
},
"networkNamePolygon": {
"message": "Polygon"
},

View File

@ -274,6 +274,7 @@ export const CURRENCY_SYMBOLS = {
USDC: 'USDC',
USDT: 'USDT',
WETH: 'WETH',
OPTIMISM: 'OP',
} as const;
/**
@ -531,6 +532,7 @@ export const NATIVE_CURRENCY_TOKEN_IMAGE_MAP = {
[CURRENCY_SYMBOLS.BNB]: BNB_TOKEN_IMAGE_URL,
[CURRENCY_SYMBOLS.MATIC]: MATIC_TOKEN_IMAGE_URL,
[CURRENCY_SYMBOLS.AVALANCHE]: AVAX_TOKEN_IMAGE_URL,
[CURRENCY_SYMBOLS.OPTIMISM]: OPTIMISM_TOKEN_IMAGE_URL,
} as const;
export const INFURA_BLOCKED_KEY = 'countryBlocked';

View File

@ -1,4 +1,5 @@
import {
ETH_TOKEN_IMAGE_URL,
TEST_ETH_TOKEN_IMAGE_URL,
BNB_TOKEN_IMAGE_URL,
MATIC_TOKEN_IMAGE_URL,
@ -23,7 +24,7 @@ export const ETH_SWAPS_TOKEN_OBJECT = {
name: 'Ether',
address: DEFAULT_TOKEN_ADDRESS,
decimals: 18,
iconUrl: './images/black-eth-logo.svg',
iconUrl: ETH_TOKEN_IMAGE_URL,
};
export const BNB_SWAPS_TOKEN_OBJECT = {
@ -66,6 +67,10 @@ export const GOERLI_SWAPS_TOKEN_OBJECT = {
iconUrl: TEST_ETH_TOKEN_IMAGE_URL,
};
export const ARBITRUM_SWAPS_TOKEN_OBJECT = { ...ETH_SWAPS_TOKEN_OBJECT };
export const OPTIMISM_SWAPS_TOKEN_OBJECT = { ...ETH_SWAPS_TOKEN_OBJECT };
// A gas value for ERC20 approve calls that should be sufficient for all ERC20 approve implementations
export const DEFAULT_ERC20_APPROVE_GAS = '0x1d4c0';
@ -77,8 +82,9 @@ const BSC_CONTRACT_ADDRESS = '0x1a1ec25dc08e98e5e93f1104b5e5cdd298707d31';
// It's the same as we use for BSC.
const POLYGON_CONTRACT_ADDRESS = '0x1a1ec25dc08e98e5e93f1104b5e5cdd298707d31';
const AVALANCHE_CONTRACT_ADDRESS = '0x1a1ec25dc08e98e5e93f1104b5e5cdd298707d31';
const OPTIMISM_CONTRACT_ADDRESS = '0x9dDA6Ef3D919c9bC8885D5560999A3640431e8e6';
const ARBITRUM_CONTRACT_ADDRESS = '0x9dDA6Ef3D919c9bC8885D5560999A3640431e8e6';
export const WETH_CONTRACT_ADDRESS =
'0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
@ -91,6 +97,11 @@ export const WMATIC_CONTRACT_ADDRESS =
export const WAVAX_CONTRACT_ADDRESS =
'0xb31f66aa3c1e785363f0875a1b74e27b85fd66c7';
export const WETH_OPTIMISM_CONTRACT_ADDRESS =
'0x4200000000000000000000000000000000000006';
export const WETH_ARBITRUM_CONTRACT_ADDRESS =
'0x82aF49447D8a07e3bd95BD0d56f35241523fBab1';
const SWAPS_TESTNET_CHAIN_ID = '0x539';
export const SWAPS_API_V2_BASE_URL = 'https://swap.metaswap.codefi.network';
@ -105,6 +116,8 @@ const MAINNET_DEFAULT_BLOCK_EXPLORER_URL = 'https://etherscan.io/';
const GOERLI_DEFAULT_BLOCK_EXPLORER_URL = 'https://goerli.etherscan.io/';
const POLYGON_DEFAULT_BLOCK_EXPLORER_URL = 'https://polygonscan.com/';
const AVALANCHE_DEFAULT_BLOCK_EXPLORER_URL = 'https://snowtrace.io/';
const OPTIMISM_DEFAULT_BLOCK_EXPLORER_URL = 'https://optimistic.etherscan.io/';
const ARBITRUM_DEFAULT_BLOCK_EXPLORER_URL = 'https://arbiscan.io/';
export const ALLOWED_PROD_SWAPS_CHAIN_IDS = [
CHAIN_IDS.MAINNET,
@ -112,6 +125,8 @@ export const ALLOWED_PROD_SWAPS_CHAIN_IDS = [
CHAIN_IDS.BSC,
CHAIN_IDS.POLYGON,
CHAIN_IDS.AVALANCHE,
CHAIN_IDS.OPTIMISM,
CHAIN_IDS.ARBITRUM,
];
export const ALLOWED_DEV_SWAPS_CHAIN_IDS = [
@ -131,6 +146,8 @@ export const SWAPS_CHAINID_CONTRACT_ADDRESS_MAP = {
[CHAIN_IDS.POLYGON]: POLYGON_CONTRACT_ADDRESS,
[CHAIN_IDS.GOERLI]: TESTNET_CONTRACT_ADDRESS,
[CHAIN_IDS.AVALANCHE]: AVALANCHE_CONTRACT_ADDRESS,
[CHAIN_IDS.OPTIMISM]: OPTIMISM_CONTRACT_ADDRESS,
[CHAIN_IDS.ARBITRUM]: ARBITRUM_CONTRACT_ADDRESS,
};
export const SWAPS_WRAPPED_TOKENS_ADDRESSES = {
@ -140,6 +157,8 @@ export const SWAPS_WRAPPED_TOKENS_ADDRESSES = {
[CHAIN_IDS.POLYGON]: WMATIC_CONTRACT_ADDRESS,
[CHAIN_IDS.GOERLI]: WETH_GOERLI_CONTRACT_ADDRESS,
[CHAIN_IDS.AVALANCHE]: WAVAX_CONTRACT_ADDRESS,
[CHAIN_IDS.OPTIMISM]: WETH_OPTIMISM_CONTRACT_ADDRESS,
[CHAIN_IDS.ARBITRUM]: WETH_ARBITRUM_CONTRACT_ADDRESS,
};
export const ALLOWED_CONTRACT_ADDRESSES = {
@ -167,6 +186,14 @@ export const ALLOWED_CONTRACT_ADDRESSES = {
SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[CHAIN_IDS.AVALANCHE],
SWAPS_WRAPPED_TOKENS_ADDRESSES[CHAIN_IDS.AVALANCHE],
],
[CHAIN_IDS.OPTIMISM]: [
SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[CHAIN_IDS.OPTIMISM],
SWAPS_WRAPPED_TOKENS_ADDRESSES[CHAIN_IDS.OPTIMISM],
],
[CHAIN_IDS.ARBITRUM]: [
SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[CHAIN_IDS.ARBITRUM],
SWAPS_WRAPPED_TOKENS_ADDRESSES[CHAIN_IDS.ARBITRUM],
],
};
export const SWAPS_CHAINID_DEFAULT_TOKEN_MAP = {
@ -176,6 +203,8 @@ export const SWAPS_CHAINID_DEFAULT_TOKEN_MAP = {
[CHAIN_IDS.POLYGON]: MATIC_SWAPS_TOKEN_OBJECT,
[CHAIN_IDS.GOERLI]: GOERLI_SWAPS_TOKEN_OBJECT,
[CHAIN_IDS.AVALANCHE]: AVAX_SWAPS_TOKEN_OBJECT,
[CHAIN_IDS.OPTIMISM]: OPTIMISM_SWAPS_TOKEN_OBJECT,
[CHAIN_IDS.ARBITRUM]: ARBITRUM_SWAPS_TOKEN_OBJECT,
};
export const SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP = {
@ -184,6 +213,8 @@ export const SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP = {
[CHAIN_IDS.POLYGON]: POLYGON_DEFAULT_BLOCK_EXPLORER_URL,
[CHAIN_IDS.GOERLI]: GOERLI_DEFAULT_BLOCK_EXPLORER_URL,
[CHAIN_IDS.AVALANCHE]: AVALANCHE_DEFAULT_BLOCK_EXPLORER_URL,
[CHAIN_IDS.OPTIMISM]: OPTIMISM_DEFAULT_BLOCK_EXPLORER_URL,
[CHAIN_IDS.ARBITRUM]: ARBITRUM_DEFAULT_BLOCK_EXPLORER_URL,
};
export const ETHEREUM = 'ethereum';
@ -191,6 +222,8 @@ export const POLYGON = 'polygon';
export const BSC = 'bsc';
export const GOERLI = 'goerli';
export const AVALANCHE = 'avalanche';
export const OPTIMISM = 'optimism';
export const ARBITRUM = 'arbitrum';
export const SWAPS_CLIENT_ID = 'extension';

View File

@ -146,7 +146,6 @@ export const getBaseApi = function (type, chainId = CHAIN_IDS.MAINNET) {
// eslint-disable-next-line no-param-reassign
chainId = TEST_CHAIN_IDS.includes(chainId) ? CHAIN_IDS.MAINNET : chainId;
const baseUrl = getBaseUrlForNewSwapsApi(type, chainId);
const chainIdDecimal = chainId && parseInt(chainId, 16);
if (!baseUrl) {
throw new Error(`Swaps API calls are disabled for chainId: ${chainId}`);
}
@ -164,8 +163,7 @@ export const getBaseApi = function (type, chainId = CHAIN_IDS.MAINNET) {
case 'gasPrices':
return `${baseUrl}/gasPrices`;
case 'network':
// Only use v2 for this endpoint.
return `${SWAPS_API_V2_BASE_URL}/networks/${chainIdDecimal}`;
return baseUrl;
default:
throw new Error('getBaseApi requires an api call type');
}

View File

@ -65,7 +65,10 @@ export function getRenderableTokenData(
(symbol === CURRENCY_SYMBOLS.ETH && chainId === CHAIN_IDS.GOERLI) ||
(symbol === CURRENCY_SYMBOLS.BNB && chainId === CHAIN_IDS.BSC) ||
(symbol === CURRENCY_SYMBOLS.MATIC && chainId === CHAIN_IDS.POLYGON) ||
(symbol === CURRENCY_SYMBOLS.AVALANCHE && chainId === CHAIN_IDS.AVALANCHE)
(symbol === CURRENCY_SYMBOLS.AVALANCHE &&
chainId === CHAIN_IDS.AVALANCHE) ||
(symbol === CURRENCY_SYMBOLS.ETH && chainId === CHAIN_IDS.OPTIMISM) ||
(symbol === CURRENCY_SYMBOLS.ETH && chainId === CHAIN_IDS.ARBITRUM)
? iconUrl
: formatIconUrlWithProxy({
chainId: chainIdForTokenIcons,

View File

@ -46,6 +46,10 @@ export default function FeeCard({
return t('networkNameGoerli');
case CHAIN_IDS.AVALANCHE:
return t('networkNameAvalanche');
case CHAIN_IDS.OPTIMISM:
return t('networkNameOptimism');
case CHAIN_IDS.ARBITRUM:
return t('networkNameArbitrum');
default:
throw new Error('This network is not supported for token swaps');
}

View File

@ -8,6 +8,8 @@ import {
BSC,
GOERLI,
AVALANCHE,
OPTIMISM,
ARBITRUM,
SWAPS_API_V2_BASE_URL,
SWAPS_DEV_API_V2_BASE_URL,
SWAPS_CLIENT_ID,
@ -38,6 +40,7 @@ import {
validateData,
} from '../../../shared/lib/swaps-utils';
import { SECOND } from '../../../shared/constants/time';
import { sumHexes } from '../../helpers/utils/transactions.util';
const CACHE_REFRESH_FIVE_MINUTES = 300000;
const USD_CURRENCY_CODE = 'usd';
@ -384,11 +387,18 @@ export function getRenderableNetworkFeesForQuote({
sourceAmount,
chainId,
nativeCurrencySymbol,
multiLayerL1FeeTotal,
}) {
const totalGasLimitForCalculation = new BigNumber(tradeGas || '0x0', 16)
.plus(approveGas || '0x0', 16)
.toString(16);
const gasTotalInWeiHex = calcGasTotal(totalGasLimitForCalculation, gasPrice);
let gasTotalInWeiHex = calcGasTotal(totalGasLimitForCalculation, gasPrice);
if (multiLayerL1FeeTotal !== null) {
gasTotalInWeiHex = sumHexes(
gasTotalInWeiHex || '0x0',
multiLayerL1FeeTotal || '0x0',
);
}
const nonGasFee = new BigNumber(tradeValue, 16)
.minus(
@ -449,6 +459,7 @@ export function quotesToRenderableData(
chainId,
smartTransactionEstimatedGas,
nativeCurrencySymbol,
multiLayerL1FeeTotal,
) {
return Object.values(quotes).map((quote) => {
const {
@ -489,6 +500,7 @@ export function quotesToRenderableData(
sourceSymbol: sourceTokenInfo.symbol,
sourceAmount,
chainId,
multiLayerL1FeeTotal,
}));
if (smartTransactionEstimatedGas) {
@ -611,6 +623,10 @@ export const getNetworkNameByChainId = (chainId) => {
return GOERLI;
case CHAIN_IDS.AVALANCHE:
return AVALANCHE;
case CHAIN_IDS.OPTIMISM:
return OPTIMISM;
case CHAIN_IDS.ARBITRUM:
return ARBITRUM;
default:
return '';
}

View File

@ -13,6 +13,8 @@ import {
BSC,
GOERLI,
AVALANCHE,
OPTIMISM,
ARBITRUM,
} from '../../../shared/constants/swaps';
import {
fetchTradesInfo,
@ -319,6 +321,14 @@ describe('Swaps Util', () => {
it('returns "avalanche" for Avalanche chain ID', () => {
expect(getNetworkNameByChainId(CHAIN_IDS.AVALANCHE)).toBe(AVALANCHE);
});
it('returns "optimism" for Optimism chain ID', () => {
expect(getNetworkNameByChainId(CHAIN_IDS.OPTIMISM)).toBe(OPTIMISM);
});
it('returns "arbitrum" for Arbitrum chain ID', () => {
expect(getNetworkNameByChainId(CHAIN_IDS.ARBITRUM)).toBe(ARBITRUM);
});
});
describe('getSwapsLivenessForNetwork', () => {

View File

@ -11,6 +11,7 @@ import { useHistory } from 'react-router-dom';
import BigNumber from 'bignumber.js';
import { isEqual } from 'lodash';
import classnames from 'classnames';
import { captureException } from '@sentry/browser';
import { I18nContext } from '../../../contexts/i18n';
import SelectQuotePopover from '../select-quote-popover';
@ -63,9 +64,9 @@ import {
getHardwareWalletType,
checkNetworkAndAccountSupports1559,
getUSDConversionRate,
getIsMultiLayerFeeNetwork,
} from '../../../selectors';
import { getNativeCurrency, getTokens } from '../../../ducks/metamask/metamask';
import {
safeRefetchQuotes,
setCustomApproveTxData,
@ -113,6 +114,8 @@ import {
toPrecisionWithoutTrailingZeros,
} from '../../../../shared/lib/transactions-controller-utils';
import { calcTokenValue } from '../../../../shared/lib/swaps-utils';
import fetchEstimatedL1Fee from '../../../helpers/utils/optimism/fetchEstimatedL1Fee';
import { sumHexes } from '../../../helpers/utils/transactions.util';
import ViewQuotePriceDifference from './view-quote-price-difference';
let intervalId;
@ -128,6 +131,7 @@ export default function ViewQuote() {
const [selectQuotePopoverShown, setSelectQuotePopoverShown] = useState(false);
const [warningHidden, setWarningHidden] = useState(false);
const [originalApproveAmount, setOriginalApproveAmount] = useState(null);
const [multiLayerL1FeeTotal, setMultiLayerL1FeeTotal] = useState(null);
// We need to have currentTimestamp in state, otherwise it would change with each rerender.
const [currentTimestamp] = useState(Date.now());
@ -161,6 +165,7 @@ export default function ViewQuote() {
const { balance: ethBalance } = useSelector(getSelectedAccount, shallowEqual);
const conversionRate = useSelector(conversionRateSelector);
const USDConversionRate = useSelector(getUSDConversionRate);
const isMultiLayerFeeNetwork = useSelector(getIsMultiLayerFeeNetwork);
const currentCurrency = useSelector(getCurrentCurrency);
const swapsTokens = useSelector(getTokens, isEqual);
const networkAndAccountSupports1559 = useSelector(
@ -261,8 +266,13 @@ export default function ViewQuote() {
maxPriorityFeePerGas,
);
}
const gasTotalInWeiHex = calcGasTotal(maxGasLimit, maxFeePerGas || gasPrice);
let gasTotalInWeiHex = calcGasTotal(maxGasLimit, maxFeePerGas || gasPrice);
if (multiLayerL1FeeTotal !== null) {
gasTotalInWeiHex = sumHexes(
gasTotalInWeiHex || '0x0',
multiLayerL1FeeTotal || '0x0',
);
}
const { tokensWithBalances } = useTokenTracker(swapsTokens, true);
const balanceToken =
@ -303,6 +313,7 @@ export default function ViewQuote() {
smartTransactionsOptInStatus &&
smartTransactionFees?.tradeTxFees,
nativeCurrencySymbol,
multiLayerL1FeeTotal,
);
}, [
quotes,
@ -318,6 +329,7 @@ export default function ViewQuote() {
nativeCurrencySymbol,
smartTransactionsEnabled,
smartTransactionsOptInStatus,
multiLayerL1FeeTotal,
]);
const renderableDataForUsedQuote = renderablePopoverData.find(
@ -351,6 +363,7 @@ export default function ViewQuote() {
sourceAmount: usedQuote.sourceAmount,
chainId,
nativeCurrencySymbol,
multiLayerL1FeeTotal,
});
additionalTrackingParams.reg_tx_fee_in_usd = Number(feeInUsd);
additionalTrackingParams.reg_tx_fee_in_eth = Number(rawEthFee);
@ -367,6 +380,7 @@ export default function ViewQuote() {
sourceAmount: usedQuote.sourceAmount,
chainId,
nativeCurrencySymbol,
multiLayerL1FeeTotal,
});
let {
feeInFiat: maxFeeInFiat,
@ -875,6 +889,25 @@ export default function ViewQuote() {
submitClicked,
]);
useEffect(() => {
if (!isMultiLayerFeeNetwork) {
return;
}
const getEstimatedL1Fee = async () => {
try {
const result = await fetchEstimatedL1Fee(global.eth, {
txParams: unsignedTransaction,
chainId,
});
setMultiLayerL1FeeTotal(result);
} catch (e) {
captureException(e);
setMultiLayerL1FeeTotal(null);
}
};
getEstimatedL1Fee();
}, [unsignedTransaction, isMultiLayerFeeNetwork, chainId]);
useEffect(() => {
if (currentSmartTransactionsEnabled && smartTransactionsOptInStatus) {
// Removes a smart transactions error when the component loads.