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

Swaps support for local testnet (#10658)

* Swaps support for local testnet

* Create util method for comparison of token addresses/symbols to default swaps token

* Get chainId from txMeta in _trackSwapsMetrics of transaction controller

* Add comment to document purpose of getTransactionGroupRecipientAddressFilter

* Use isSwapsDefaultTokenSymbol in place of repeated defaultTokenSymbol comparisons in build-quote.js
This commit is contained in:
Dan J Miller 2021-03-18 07:50:06 -02:30 committed by GitHub
parent 1c573ef852
commit 480512d14f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 466 additions and 227 deletions

View File

@ -8,12 +8,13 @@ import { calcTokenAmount } from '../../../ui/app/helpers/utils/token-util';
import { calcGasTotal } from '../../../ui/app/pages/send/send.utils'; import { calcGasTotal } from '../../../ui/app/pages/send/send.utils';
import { conversionUtil } from '../../../ui/app/helpers/utils/conversion-util'; import { conversionUtil } from '../../../ui/app/helpers/utils/conversion-util';
import { import {
ETH_SWAPS_TOKEN_OBJECT,
DEFAULT_ERC20_APPROVE_GAS, DEFAULT_ERC20_APPROVE_GAS,
QUOTES_EXPIRED_ERROR, QUOTES_EXPIRED_ERROR,
QUOTES_NOT_AVAILABLE_ERROR, QUOTES_NOT_AVAILABLE_ERROR,
SWAPS_FETCH_ORDER_CONFLICT, SWAPS_FETCH_ORDER_CONFLICT,
} from '../../../shared/constants/swaps'; } from '../../../shared/constants/swaps';
import { isSwapsDefaultTokenAddress } from '../../../shared/modules/swaps.utils';
import { import {
fetchTradesInfo as defaultFetchTradesInfo, fetchTradesInfo as defaultFetchTradesInfo,
fetchSwapsFeatureLiveness as defaultFetchSwapsFeatureLiveness, fetchSwapsFeatureLiveness as defaultFetchSwapsFeatureLiveness,
@ -85,6 +86,7 @@ export default class SwapsController {
fetchTradesInfo = defaultFetchTradesInfo, fetchTradesInfo = defaultFetchTradesInfo,
fetchSwapsFeatureLiveness = defaultFetchSwapsFeatureLiveness, fetchSwapsFeatureLiveness = defaultFetchSwapsFeatureLiveness,
fetchSwapsQuoteRefreshTime = defaultFetchSwapsQuoteRefreshTime, fetchSwapsQuoteRefreshTime = defaultFetchSwapsQuoteRefreshTime,
getCurrentChainId,
}) { }) {
this.store = new ObservableStore({ this.store = new ObservableStore({
swapsState: { ...initialState.swapsState }, swapsState: { ...initialState.swapsState },
@ -93,6 +95,7 @@ export default class SwapsController {
this._fetchTradesInfo = fetchTradesInfo; this._fetchTradesInfo = fetchTradesInfo;
this._fetchSwapsFeatureLiveness = fetchSwapsFeatureLiveness; this._fetchSwapsFeatureLiveness = fetchSwapsFeatureLiveness;
this._fetchSwapsQuoteRefreshTime = fetchSwapsQuoteRefreshTime; this._fetchSwapsQuoteRefreshTime = fetchSwapsQuoteRefreshTime;
this._getCurrentChainId = getCurrentChainId;
this.getBufferedGasLimit = getBufferedGasLimit; this.getBufferedGasLimit = getBufferedGasLimit;
this.tokenRatesStore = tokenRatesStore; this.tokenRatesStore = tokenRatesStore;
@ -116,10 +119,11 @@ export default class SwapsController {
// Sets the refresh rate for quote updates from the MetaSwap API // Sets the refresh rate for quote updates from the MetaSwap API
async _setSwapsQuoteRefreshTime() { async _setSwapsQuoteRefreshTime() {
const chainId = this._getCurrentChainId();
// Default to fallback time unless API returns valid response // Default to fallback time unless API returns valid response
let swapsQuoteRefreshTime = FALLBACK_QUOTE_REFRESH_TIME; let swapsQuoteRefreshTime = FALLBACK_QUOTE_REFRESH_TIME;
try { try {
swapsQuoteRefreshTime = await this._fetchSwapsQuoteRefreshTime(); swapsQuoteRefreshTime = await this._fetchSwapsQuoteRefreshTime(chainId);
} catch (e) { } catch (e) {
console.error('Request for swaps quote refresh time failed: ', e); console.error('Request for swaps quote refresh time failed: ', e);
} }
@ -158,6 +162,8 @@ export default class SwapsController {
fetchParamsMetaData = {}, fetchParamsMetaData = {},
isPolledRequest, isPolledRequest,
) { ) {
const { chainId } = fetchParamsMetaData;
if (!fetchParams) { if (!fetchParams) {
return null; return null;
} }
@ -177,7 +183,7 @@ export default class SwapsController {
this.indexOfNewestCallInFlight = indexOfCurrentCall; this.indexOfNewestCallInFlight = indexOfCurrentCall;
let [newQuotes] = await Promise.all([ let [newQuotes] = await Promise.all([
this._fetchTradesInfo(fetchParams), this._fetchTradesInfo(fetchParams, fetchParamsMetaData),
this._setSwapsQuoteRefreshTime(), this._setSwapsQuoteRefreshTime(),
]); ]);
@ -191,7 +197,7 @@ export default class SwapsController {
let approvalRequired = false; let approvalRequired = false;
if ( if (
fetchParams.sourceToken !== ETH_SWAPS_TOKEN_OBJECT.address && !isSwapsDefaultTokenAddress(fetchParams.sourceToken, chainId) &&
Object.values(newQuotes).length Object.values(newQuotes).length
) { ) {
const allowance = await this._getERC20Allowance( const allowance = await this._getERC20Allowance(
@ -490,6 +496,7 @@ export default class SwapsController {
const { const {
swapsState: { customGasPrice }, swapsState: { customGasPrice },
} = this.store.getState(); } = this.store.getState();
const chainId = this._getCurrentChainId();
const numQuotes = Object.keys(quotes).length; const numQuotes = Object.keys(quotes).length;
if (!numQuotes) { if (!numQuotes) {
@ -533,8 +540,8 @@ export default class SwapsController {
// trade.value is a sum of different values depending on the transaction. // trade.value is a sum of different values depending on the transaction.
// It always includes any external fees charged by the quote source. In // It always includes any external fees charged by the quote source. In
// addition, if the source asset is ETH, trade.value includes the amount // addition, if the source asset is the selected chain's default token, trade.value
// of swapped ETH. // includes the amount of that token.
const totalWeiCost = new BigNumber(gasTotalInWeiHex, 16).plus( const totalWeiCost = new BigNumber(gasTotalInWeiHex, 16).plus(
trade.value, trade.value,
16, 16,
@ -549,21 +556,21 @@ export default class SwapsController {
}); });
// The total fee is aggregator/exchange fees plus gas fees. // The total fee is aggregator/exchange fees plus gas fees.
// If the swap is from ETH, subtract the sourceAmount from the total cost. // If the swap is from the selected chain's default token, subtract
// Otherwise, the total fee is simply trade.value plus gas fees. // the sourceAmount from the total cost. Otherwise, the total fee
const ethFee = // is simply trade.value plus gas fees.
sourceToken === ETH_SWAPS_TOKEN_OBJECT.address const ethFee = isSwapsDefaultTokenAddress(sourceToken, chainId)
? conversionUtil( ? conversionUtil(
totalWeiCost.minus(sourceAmount, 10), // sourceAmount is in wei totalWeiCost.minus(sourceAmount, 10), // sourceAmount is in wei
{ {
fromCurrency: 'ETH', fromCurrency: 'ETH',
fromDenomination: 'WEI', fromDenomination: 'WEI',
toDenomination: 'ETH', toDenomination: 'ETH',
fromNumericBase: 'BN', fromNumericBase: 'BN',
numberOfDecimals: 6, numberOfDecimals: 6,
}, },
) )
: totalEthCost; : totalEthCost;
const decimalAdjustedDestinationAmount = calcTokenAmount( const decimalAdjustedDestinationAmount = calcTokenAmount(
destinationAmount, destinationAmount,
@ -588,10 +595,12 @@ export default class SwapsController {
10, 10,
); );
const conversionRateForCalculations = const conversionRateForCalculations = isSwapsDefaultTokenAddress(
destinationToken === ETH_SWAPS_TOKEN_OBJECT.address destinationToken,
? 1 chainId,
: tokenConversionRate; )
? 1
: tokenConversionRate;
const overallValueOfQuoteForSorting = const overallValueOfQuoteForSorting =
conversionRateForCalculations === undefined conversionRateForCalculations === undefined
@ -618,8 +627,10 @@ export default class SwapsController {
}); });
const isBest = const isBest =
newQuotes[topAggId].destinationToken === ETH_SWAPS_TOKEN_OBJECT.address || isSwapsDefaultTokenAddress(
Boolean(tokenConversionRates[newQuotes[topAggId]?.destinationToken]); newQuotes[topAggId].destinationToken,
chainId,
) || Boolean(tokenConversionRates[newQuotes[topAggId]?.destinationToken]);
let savings = null; let savings = null;
@ -726,13 +737,17 @@ export default class SwapsController {
async _fetchAndSetSwapsLiveness() { async _fetchAndSetSwapsLiveness() {
const { swapsState } = this.store.getState(); const { swapsState } = this.store.getState();
const { swapsFeatureIsLive: oldSwapsFeatureIsLive } = swapsState; const { swapsFeatureIsLive: oldSwapsFeatureIsLive } = swapsState;
const chainId = this._getCurrentChainId();
let swapsFeatureIsLive = false; let swapsFeatureIsLive = false;
let successfullyFetched = false; let successfullyFetched = false;
let numAttempts = 0; let numAttempts = 0;
const fetchAndIncrementNumAttempts = async () => { const fetchAndIncrementNumAttempts = async () => {
try { try {
swapsFeatureIsLive = Boolean(await this._fetchSwapsFeatureLiveness()); swapsFeatureIsLive = Boolean(
await this._fetchSwapsFeatureLiveness(chainId),
);
successfullyFetched = true; successfullyFetched = true;
} catch (err) { } catch (err) {
log.error(err); log.error(err);

View File

@ -8,6 +8,7 @@ import { ObservableStore } from '@metamask/obs-store';
import { import {
ROPSTEN_NETWORK_ID, ROPSTEN_NETWORK_ID,
MAINNET_NETWORK_ID, MAINNET_NETWORK_ID,
MAINNET_CHAIN_ID,
} from '../../../shared/constants/network'; } from '../../../shared/constants/network';
import { ETH_SWAPS_TOKEN_OBJECT } from '../../../shared/constants/swaps'; import { ETH_SWAPS_TOKEN_OBJECT } from '../../../shared/constants/swaps';
import { createTestProviderTools } from '../../../test/stub/provider'; import { createTestProviderTools } from '../../../test/stub/provider';
@ -75,6 +76,7 @@ const MOCK_FETCH_METADATA = {
symbol: 'FOO', symbol: 'FOO',
decimals: 18, decimals: 18,
}, },
chainId: MAINNET_CHAIN_ID,
}; };
const MOCK_TOKEN_RATES_STORE = new ObservableStore({ const MOCK_TOKEN_RATES_STORE = new ObservableStore({
@ -131,6 +133,8 @@ const sandbox = sinon.createSandbox();
const fetchTradesInfoStub = sandbox.stub(); const fetchTradesInfoStub = sandbox.stub();
const fetchSwapsFeatureLivenessStub = sandbox.stub(); const fetchSwapsFeatureLivenessStub = sandbox.stub();
const fetchSwapsQuoteRefreshTimeStub = sandbox.stub(); const fetchSwapsQuoteRefreshTimeStub = sandbox.stub();
const getCurrentChainIdStub = sandbox.stub();
getCurrentChainIdStub.returns(MAINNET_CHAIN_ID);
describe('SwapsController', function () { describe('SwapsController', function () {
let provider; let provider;
@ -145,6 +149,7 @@ describe('SwapsController', function () {
fetchTradesInfo: fetchTradesInfoStub, fetchTradesInfo: fetchTradesInfoStub,
fetchSwapsFeatureLiveness: fetchSwapsFeatureLivenessStub, fetchSwapsFeatureLiveness: fetchSwapsFeatureLivenessStub,
fetchSwapsQuoteRefreshTime: fetchSwapsQuoteRefreshTimeStub, fetchSwapsQuoteRefreshTime: fetchSwapsQuoteRefreshTimeStub,
getCurrentChainId: getCurrentChainIdStub,
}); });
}; };
@ -194,6 +199,7 @@ describe('SwapsController', function () {
tokenRatesStore: MOCK_TOKEN_RATES_STORE, tokenRatesStore: MOCK_TOKEN_RATES_STORE,
fetchTradesInfo: fetchTradesInfoStub, fetchTradesInfo: fetchTradesInfoStub,
fetchSwapsFeatureLiveness: fetchSwapsFeatureLivenessStub, fetchSwapsFeatureLiveness: fetchSwapsFeatureLivenessStub,
getCurrentChainId: getCurrentChainIdStub,
}); });
const currentEthersInstance = swapsController.ethersProvider; const currentEthersInstance = swapsController.ethersProvider;
const onNetworkDidChange = networkController.on.getCall(0).args[1]; const onNetworkDidChange = networkController.on.getCall(0).args[1];
@ -218,6 +224,7 @@ describe('SwapsController', function () {
tokenRatesStore: MOCK_TOKEN_RATES_STORE, tokenRatesStore: MOCK_TOKEN_RATES_STORE,
fetchTradesInfo: fetchTradesInfoStub, fetchTradesInfo: fetchTradesInfoStub,
fetchSwapsFeatureLiveness: fetchSwapsFeatureLivenessStub, fetchSwapsFeatureLiveness: fetchSwapsFeatureLivenessStub,
getCurrentChainId: getCurrentChainIdStub,
}); });
const currentEthersInstance = swapsController.ethersProvider; const currentEthersInstance = swapsController.ethersProvider;
const onNetworkDidChange = networkController.on.getCall(0).args[1]; const onNetworkDidChange = networkController.on.getCall(0).args[1];
@ -242,6 +249,7 @@ describe('SwapsController', function () {
tokenRatesStore: MOCK_TOKEN_RATES_STORE, tokenRatesStore: MOCK_TOKEN_RATES_STORE,
fetchTradesInfo: fetchTradesInfoStub, fetchTradesInfo: fetchTradesInfoStub,
fetchSwapsFeatureLiveness: fetchSwapsFeatureLivenessStub, fetchSwapsFeatureLiveness: fetchSwapsFeatureLivenessStub,
getCurrentChainId: getCurrentChainIdStub,
}); });
const currentEthersInstance = swapsController.ethersProvider; const currentEthersInstance = swapsController.ethersProvider;
const onNetworkDidChange = networkController.on.getCall(0).args[1]; const onNetworkDidChange = networkController.on.getCall(0).args[1];
@ -686,7 +694,10 @@ describe('SwapsController', function () {
}); });
assert.strictEqual( assert.strictEqual(
fetchTradesInfoStub.calledOnceWithExactly(MOCK_FETCH_PARAMS), fetchTradesInfoStub.calledOnceWithExactly(
MOCK_FETCH_PARAMS,
MOCK_FETCH_METADATA,
),
true, true,
); );
}); });

View File

@ -958,6 +958,7 @@ export default class TransactionController extends EventEmitter {
txMeta.txParams.from, txMeta.txParams.from,
txMeta.destinationTokenDecimals, txMeta.destinationTokenDecimals,
approvalTxMeta, approvalTxMeta,
txMeta.chainId,
); );
const quoteVsExecutionRatio = `${new BigNumber(tokensReceived, 10) const quoteVsExecutionRatio = `${new BigNumber(tokensReceived, 10)

View File

@ -383,6 +383,9 @@ export default class MetamaskController extends EventEmitter {
this.networkController, this.networkController,
), ),
tokenRatesStore: this.tokenRatesController.store, tokenRatesStore: this.tokenRatesController.store,
getCurrentChainId: this.networkController.getCurrentChainId.bind(
this.networkController,
),
}); });
// ensure accountTracker updates balances after network change // ensure accountTracker updates balances after network change

View File

@ -1,3 +1,12 @@
import { MAINNET_CHAIN_ID } from './network';
export const QUOTES_EXPIRED_ERROR = 'quotes-expired';
export const SWAP_FAILED_ERROR = 'swap-failed-error';
export const ERROR_FETCHING_QUOTES = 'error-fetching-quotes';
export const QUOTES_NOT_AVAILABLE_ERROR = 'quotes-not-avilable';
export const OFFLINE_FOR_MAINTENANCE = 'offline-for-maintenance';
export const SWAPS_FETCH_ORDER_CONFLICT = 'swaps-fetch-order-conflict';
// An address that the metaswap-api recognizes as ETH, in place of the token address that ERC-20 tokens have // An address that the metaswap-api recognizes as ETH, in place of the token address that ERC-20 tokens have
const ETH_SWAPS_TOKEN_ADDRESS = '0x0000000000000000000000000000000000000000'; const ETH_SWAPS_TOKEN_ADDRESS = '0x0000000000000000000000000000000000000000';
@ -9,17 +18,42 @@ export const ETH_SWAPS_TOKEN_OBJECT = {
iconUrl: 'images/black-eth-logo.svg', iconUrl: 'images/black-eth-logo.svg',
}; };
export const QUOTES_EXPIRED_ERROR = 'quotes-expired'; const TEST_ETH_SWAPS_TOKEN_OBJECT = {
export const SWAP_FAILED_ERROR = 'swap-failed-error'; symbol: 'TESTETH',
export const ERROR_FETCHING_QUOTES = 'error-fetching-quotes'; name: 'Test Ether',
export const QUOTES_NOT_AVAILABLE_ERROR = 'quotes-not-avilable'; address: ETH_SWAPS_TOKEN_ADDRESS,
export const OFFLINE_FOR_MAINTENANCE = 'offline-for-maintenance'; decimals: 18,
export const SWAPS_FETCH_ORDER_CONFLICT = 'swaps-fetch-order-conflict'; iconUrl: 'images/black-eth-logo.svg',
};
// A gas value for ERC20 approve calls that should be sufficient for all ERC20 approve implementations // A gas value for ERC20 approve calls that should be sufficient for all ERC20 approve implementations
export const DEFAULT_ERC20_APPROVE_GAS = '0x1d4c0'; export const DEFAULT_ERC20_APPROVE_GAS = '0x1d4c0';
export const SWAPS_CONTRACT_ADDRESS = const MAINNET_CONTRACT_ADDRESS = '0x881d40237659c251811cec9c364ef91dc08d300c';
'0x881d40237659c251811cec9c364ef91dc08d300c';
export const METASWAP_API_HOST = 'https://api.metaswap.codefi.network'; const TESTNET_CONTRACT_ADDRESS = '0x881d40237659c251811cec9c364ef91dc08d300c';
const METASWAP_ETH_API_HOST = 'https://api.metaswap.codefi.network';
const SWAPS_TESTNET_CHAIN_ID = '0x539';
const SWAPS_TESTNET_HOST = 'https://metaswap-api.airswap-dev.codefi.network';
export const ALLOWED_SWAPS_CHAIN_IDS = {
[MAINNET_CHAIN_ID]: true,
[SWAPS_TESTNET_CHAIN_ID]: true,
};
export const METASWAP_CHAINID_API_HOST_MAP = {
[MAINNET_CHAIN_ID]: METASWAP_ETH_API_HOST,
[SWAPS_TESTNET_CHAIN_ID]: SWAPS_TESTNET_HOST,
};
export const SWAPS_CHAINID_CONTRACT_ADDRESS_MAP = {
[MAINNET_CHAIN_ID]: MAINNET_CONTRACT_ADDRESS,
[SWAPS_TESTNET_CHAIN_ID]: TESTNET_CONTRACT_ADDRESS,
};
export const SWAPS_CHAINID_DEFAULT_TOKEN_MAP = {
[MAINNET_CHAIN_ID]: ETH_SWAPS_TOKEN_OBJECT,
[SWAPS_TESTNET_CHAIN_ID]: TEST_ETH_SWAPS_TOKEN_OBJECT,
};

View File

@ -0,0 +1,33 @@
import { SWAPS_CHAINID_DEFAULT_TOKEN_MAP } from '../constants/swaps';
/**
* Checks whether the provided address is strictly equal to the address for
* the default swaps token of the provided chain.
*
* @param {string} address - The string to compare to the default token address
* @param {string} chainId - The hex encoded chain ID of the default swaps token to check
* @returns {boolean} Whether the address is the provided chain's default token address
*/
export function isSwapsDefaultTokenAddress(address, chainId) {
if (!address || !chainId) {
return false;
}
return address === SWAPS_CHAINID_DEFAULT_TOKEN_MAP[chainId]?.address;
}
/**
* Checks whether the provided symbol is strictly equal to the symbol for
* the default swaps token of the provided chain.
*
* @param {string} symbol - The string to compare to the default token symbol
* @param {string} chainId - The hex encoded chain ID of the default swaps token to check
* @returns {boolean} Whether the symbl is the provided chain's default token symbol
*/
export function isSwapsDefaultTokenSymbol(symbol, chainId) {
if (!symbol || !chainId) {
return false;
}
return symbol === SWAPS_CHAINID_DEFAULT_TOKEN_MAP[chainId]?.symbol;
}

View File

@ -5,20 +5,31 @@ import {
nonceSortedCompletedTransactionsSelector, nonceSortedCompletedTransactionsSelector,
nonceSortedPendingTransactionsSelector, nonceSortedPendingTransactionsSelector,
} from '../../../selectors/transactions'; } from '../../../selectors/transactions';
import { getCurrentChainId } from '../../../selectors';
import { useI18nContext } from '../../../hooks/useI18nContext'; import { useI18nContext } from '../../../hooks/useI18nContext';
import TransactionListItem from '../transaction-list-item'; import TransactionListItem from '../transaction-list-item';
import Button from '../../ui/button'; import Button from '../../ui/button';
import { TOKEN_CATEGORY_HASH } from '../../../helpers/constants/transactions'; import { TOKEN_CATEGORY_HASH } from '../../../helpers/constants/transactions';
import { SWAPS_CONTRACT_ADDRESS } from '../../../../../shared/constants/swaps'; import { SWAPS_CHAINID_CONTRACT_ADDRESS_MAP } from '../../../../../shared/constants/swaps';
import { TRANSACTION_TYPES } from '../../../../../shared/constants/transaction'; import { TRANSACTION_TYPES } from '../../../../../shared/constants/transaction';
const PAGE_INCREMENT = 10; const PAGE_INCREMENT = 10;
const getTransactionGroupRecipientAddressFilter = (recipientAddress) => { // When we are on a token page, we only want to show transactions that involve that token.
// In the case of token transfers or approvals, these will be transactions sent to the
// token contract. In the case of swaps, these will be transactions sent to the swaps contract
// and which have the token address in the transaction data.
//
// getTransactionGroupRecipientAddressFilter is used to determine whether a transaction matches
// either of those criteria
const getTransactionGroupRecipientAddressFilter = (
recipientAddress,
chainId,
) => {
return ({ initialTransaction: { txParams } }) => { return ({ initialTransaction: { txParams } }) => {
return ( return (
txParams?.to === recipientAddress || txParams?.to === recipientAddress ||
(txParams?.to === SWAPS_CONTRACT_ADDRESS && (txParams?.to === SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[chainId] &&
txParams.data.match(recipientAddress.slice(2))) txParams.data.match(recipientAddress.slice(2)))
); );
}; };
@ -39,12 +50,13 @@ const getFilteredTransactionGroups = (
transactionGroups, transactionGroups,
hideTokenTransactions, hideTokenTransactions,
tokenAddress, tokenAddress,
chainId,
) => { ) => {
if (hideTokenTransactions) { if (hideTokenTransactions) {
return transactionGroups.filter(tokenTransactionFilter); return transactionGroups.filter(tokenTransactionFilter);
} else if (tokenAddress) { } else if (tokenAddress) {
return transactionGroups.filter( return transactionGroups.filter(
getTransactionGroupRecipientAddressFilter(tokenAddress), getTransactionGroupRecipientAddressFilter(tokenAddress, chainId),
); );
} }
return transactionGroups; return transactionGroups;
@ -63,6 +75,7 @@ export default function TransactionList({
const unfilteredCompletedTransactions = useSelector( const unfilteredCompletedTransactions = useSelector(
nonceSortedCompletedTransactionsSelector, nonceSortedCompletedTransactionsSelector,
); );
const chainId = useSelector(getCurrentChainId);
const pendingTransactions = useMemo( const pendingTransactions = useMemo(
() => () =>
@ -70,8 +83,14 @@ export default function TransactionList({
unfilteredPendingTransactions, unfilteredPendingTransactions,
hideTokenTransactions, hideTokenTransactions,
tokenAddress, tokenAddress,
chainId,
), ),
[hideTokenTransactions, tokenAddress, unfilteredPendingTransactions], [
hideTokenTransactions,
tokenAddress,
unfilteredPendingTransactions,
chainId,
],
); );
const completedTransactions = useMemo( const completedTransactions = useMemo(
() => () =>
@ -79,8 +98,14 @@ export default function TransactionList({
unfilteredCompletedTransactions, unfilteredCompletedTransactions,
hideTokenTransactions, hideTokenTransactions,
tokenAddress, tokenAddress,
chainId,
), ),
[hideTokenTransactions, tokenAddress, unfilteredCompletedTransactions], [
hideTokenTransactions,
tokenAddress,
unfilteredCompletedTransactions,
chainId,
],
); );
const viewMore = useCallback( const viewMore = useCallback(

View File

@ -25,7 +25,8 @@ import {
getIsMainnet, getIsMainnet,
getIsTestnet, getIsTestnet,
getCurrentKeyring, getCurrentKeyring,
getSwapsEthToken, getSwapsDefaultToken,
getIsSwapsChain,
} from '../../../selectors/selectors'; } from '../../../selectors/selectors';
import SwapIcon from '../../ui/icon/swap-icon.component'; import SwapIcon from '../../ui/icon/swap-icon.component';
import BuyIcon from '../../ui/icon/overview-buy-icon.component'; import BuyIcon from '../../ui/icon/overview-buy-icon.component';
@ -63,13 +64,14 @@ const EthOverview = ({ className }) => {
const { balance } = selectedAccount; const { balance } = selectedAccount;
const isMainnetChain = useSelector(getIsMainnet); const isMainnetChain = useSelector(getIsMainnet);
const isTestnetChain = useSelector(getIsTestnet); const isTestnetChain = useSelector(getIsTestnet);
const isSwapsChain = useSelector(getIsSwapsChain);
const enteredSwapsEvent = useNewMetricEvent({ const enteredSwapsEvent = useNewMetricEvent({
event: 'Swaps Opened', event: 'Swaps Opened',
properties: { source: 'Main View', active_currency: 'ETH' }, properties: { source: 'Main View', active_currency: 'ETH' },
category: 'swaps', category: 'swaps',
}); });
const swapsEnabled = useSelector(getSwapsFeatureLiveness); const swapsEnabled = useSelector(getSwapsFeatureLiveness);
const swapsEthToken = useSelector(getSwapsEthToken); const defaultSwapsToken = useSelector(getSwapsDefaultToken);
return ( return (
<WalletOverview <WalletOverview
@ -136,12 +138,12 @@ const EthOverview = ({ className }) => {
{swapsEnabled ? ( {swapsEnabled ? (
<IconButton <IconButton
className="eth-overview__button" className="eth-overview__button"
disabled={!isMainnetChain} disabled={!isSwapsChain}
Icon={SwapIcon} Icon={SwapIcon}
onClick={() => { onClick={() => {
if (isMainnetChain) { if (isSwapsChain) {
enteredSwapsEvent(); enteredSwapsEvent();
dispatch(setSwapsFromToken(swapsEthToken)); dispatch(setSwapsFromToken(defaultSwapsToken));
if (usingHardwareWallet) { if (usingHardwareWallet) {
global.platform.openExtensionInBrowser(BUILD_QUOTE_ROUTE); global.platform.openExtensionInBrowser(BUILD_QUOTE_ROUTE);
} else { } else {
@ -154,7 +156,7 @@ const EthOverview = ({ className }) => {
<Tooltip <Tooltip
title={t('onlyAvailableOnMainnet')} title={t('onlyAvailableOnMainnet')}
position="bottom" position="bottom"
disabled={isMainnetChain} disabled={isSwapsChain}
> >
{contents} {contents}
</Tooltip> </Tooltip>

View File

@ -25,9 +25,8 @@ import {
import { import {
getAssetImages, getAssetImages,
getCurrentKeyring, getCurrentKeyring,
getCurrentChainId, getIsSwapsChain,
} from '../../../selectors/selectors'; } from '../../../selectors/selectors';
import { MAINNET_CHAIN_ID } from '../../../../../shared/constants/network';
import SwapIcon from '../../ui/icon/swap-icon.component'; import SwapIcon from '../../ui/icon/swap-icon.component';
import SendIcon from '../../ui/icon/overview-send-icon.component'; import SendIcon from '../../ui/icon/overview-send-icon.component';
@ -58,7 +57,7 @@ const TokenOverview = ({ className, token }) => {
balanceToRender, balanceToRender,
token.symbol, token.symbol,
); );
const chainId = useSelector(getCurrentChainId); const isSwapsChain = useSelector(getIsSwapsChain);
const enteredSwapsEvent = useNewMetricEvent({ const enteredSwapsEvent = useNewMetricEvent({
event: 'Swaps Opened', event: 'Swaps Opened',
properties: { source: 'Token View', active_currency: token.symbol }, properties: { source: 'Token View', active_currency: token.symbol },
@ -100,10 +99,10 @@ const TokenOverview = ({ className, token }) => {
{swapsEnabled ? ( {swapsEnabled ? (
<IconButton <IconButton
className="token-overview__button" className="token-overview__button"
disabled={chainId !== MAINNET_CHAIN_ID} disabled={!isSwapsChain}
Icon={SwapIcon} Icon={SwapIcon}
onClick={() => { onClick={() => {
if (chainId === MAINNET_CHAIN_ID) { if (isSwapsChain) {
enteredSwapsEvent(); enteredSwapsEvent();
dispatch( dispatch(
setSwapsFromToken({ setSwapsFromToken({
@ -125,7 +124,7 @@ const TokenOverview = ({ className, token }) => {
<Tooltip <Tooltip
title={t('onlyAvailableOnMainnet')} title={t('onlyAvailableOnMainnet')}
position="bottom" position="bottom"
disabled={chainId === MAINNET_CHAIN_ID} disabled={isSwapsChain}
> >
{contents} {contents}
</Tooltip> </Tooltip>

View File

@ -49,7 +49,8 @@ import {
getSelectedAccount, getSelectedAccount,
getTokenExchangeRates, getTokenExchangeRates,
getUSDConversionRate, getUSDConversionRate,
getSwapsEthToken, getSwapsDefaultToken,
getCurrentChainId,
} from '../../selectors'; } from '../../selectors';
import { import {
ERROR_FETCHING_QUOTES, ERROR_FETCHING_QUOTES,
@ -376,9 +377,11 @@ export const fetchQuotesAndSetQuoteState = (
metaMetricsEvent, metaMetricsEvent,
) => { ) => {
return async (dispatch, getState) => { return async (dispatch, getState) => {
const state = getState();
const chainId = getCurrentChainId(state);
let swapsFeatureIsLive = false; let swapsFeatureIsLive = false;
try { try {
swapsFeatureIsLive = await fetchSwapsFeatureLiveness(); swapsFeatureIsLive = await fetchSwapsFeatureLiveness(chainId);
} catch (error) { } catch (error) {
log.error('Failed to fetch Swaps liveness, defaulting to false.', error); log.error('Failed to fetch Swaps liveness, defaulting to false.', error);
} }
@ -389,13 +392,14 @@ export const fetchQuotesAndSetQuoteState = (
return; return;
} }
const state = getState();
const fetchParams = getFetchParams(state); const fetchParams = getFetchParams(state);
const selectedAccount = getSelectedAccount(state); const selectedAccount = getSelectedAccount(state);
const balanceError = getBalanceError(state); const balanceError = getBalanceError(state);
const swapsDefaultToken = getSwapsDefaultToken(state);
const fetchParamsFromToken = const fetchParamsFromToken =
fetchParams?.metaData?.sourceTokenInfo?.symbol === 'ETH' fetchParams?.metaData?.sourceTokenInfo?.symbol ===
? getSwapsEthToken(state) swapsDefaultToken.symbol
? swapsDefaultToken
: fetchParams?.metaData?.sourceTokenInfo; : fetchParams?.metaData?.sourceTokenInfo;
const selectedFromToken = getFromToken(state) || fetchParamsFromToken || {}; const selectedFromToken = getFromToken(state) || fetchParamsFromToken || {};
const selectedToToken = const selectedToToken =
@ -420,7 +424,10 @@ export const fetchQuotesAndSetQuoteState = (
const contractExchangeRates = getTokenExchangeRates(state); const contractExchangeRates = getTokenExchangeRates(state);
let destinationTokenAddedForSwap = false; let destinationTokenAddedForSwap = false;
if (toTokenSymbol !== 'ETH' && !contractExchangeRates[toTokenAddress]) { if (
toTokenSymbol !== swapsDefaultToken.symbol &&
!contractExchangeRates[toTokenAddress]
) {
destinationTokenAddedForSwap = true; destinationTokenAddedForSwap = true;
await dispatch( await dispatch(
addToken( addToken(
@ -433,7 +440,7 @@ export const fetchQuotesAndSetQuoteState = (
); );
} }
if ( if (
fromTokenSymbol !== 'ETH' && fromTokenSymbol !== swapsDefaultToken.symbol &&
!contractExchangeRates[fromTokenAddress] && !contractExchangeRates[fromTokenAddress] &&
fromTokenBalance && fromTokenBalance &&
new BigNumber(fromTokenBalance, 16).gt(0) new BigNumber(fromTokenBalance, 16).gt(0)
@ -494,6 +501,7 @@ export const fetchQuotesAndSetQuoteState = (
sourceTokenInfo, sourceTokenInfo,
destinationTokenInfo, destinationTokenInfo,
accountBalance: selectedAccount.balance, accountBalance: selectedAccount.balance,
chainId,
}, },
), ),
); );
@ -563,9 +571,12 @@ export const fetchQuotesAndSetQuoteState = (
export const signAndSendTransactions = (history, metaMetricsEvent) => { export const signAndSendTransactions = (history, metaMetricsEvent) => {
return async (dispatch, getState) => { return async (dispatch, getState) => {
const state = getState();
const chainId = getCurrentChainId(state);
let swapsFeatureIsLive = false; let swapsFeatureIsLive = false;
try { try {
swapsFeatureIsLive = await fetchSwapsFeatureLiveness(); swapsFeatureIsLive = await fetchSwapsFeatureLiveness(chainId);
} catch (error) { } catch (error) {
log.error('Failed to fetch Swaps liveness, defaulting to false.', error); log.error('Failed to fetch Swaps liveness, defaulting to false.', error);
} }
@ -576,7 +587,6 @@ export const signAndSendTransactions = (history, metaMetricsEvent) => {
return; return;
} }
const state = getState();
const customSwapsGas = getCustomSwapsGas(state); const customSwapsGas = getCustomSwapsGas(state);
const fetchParams = getFetchParams(state); const fetchParams = getFetchParams(state);
const { metaData, value: swapTokenValue, slippage } = fetchParams; const { metaData, value: swapTokenValue, slippage } = fetchParams;

View File

@ -1,13 +1,18 @@
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { useRouteMatch } from 'react-router-dom'; import { useRouteMatch } from 'react-router-dom';
import { getTokens } from '../ducks/metamask/metamask'; import { getTokens } from '../ducks/metamask/metamask';
import { getCurrentChainId } from '../selectors';
import { ASSET_ROUTE } from '../helpers/constants/routes'; import { ASSET_ROUTE } from '../helpers/constants/routes';
import { ETH_SWAPS_TOKEN_OBJECT } from '../../../shared/constants/swaps'; import {
SWAPS_CHAINID_DEFAULT_TOKEN_MAP,
ETH_SWAPS_TOKEN_OBJECT,
} from '../../../shared/constants/swaps';
/** /**
* Returns a token object for the asset that is currently being viewed. * Returns a token object for the asset that is currently being viewed.
* Will return the ETH_SWAPS_TOKEN_OBJECT when the user is viewing either * Will return the default token object for the current chain when the
* the primary, unfiltered, activity list or the ETH asset page. * user is viewing either the primary, unfiltered, activity list or the
* default token asset page.
* @returns {import('./useTokenDisplayValue').Token} * @returns {import('./useTokenDisplayValue').Token}
*/ */
export function useCurrentAsset() { export function useCurrentAsset() {
@ -22,6 +27,10 @@ export function useCurrentAsset() {
const knownTokens = useSelector(getTokens); const knownTokens = useSelector(getTokens);
const token = const token =
tokenAddress && knownTokens.find(({ address }) => address === tokenAddress); tokenAddress && knownTokens.find(({ address }) => address === tokenAddress);
const chainId = useSelector(getCurrentChainId);
return token ?? ETH_SWAPS_TOKEN_OBJECT; return (
token ??
(SWAPS_CHAINID_DEFAULT_TOKEN_MAP[chainId] || ETH_SWAPS_TOKEN_OBJECT)
);
} }

View File

@ -1,6 +1,11 @@
import { useSelector } from 'react-redux';
import { TRANSACTION_TYPES } from '../../../shared/constants/transaction'; import { TRANSACTION_TYPES } from '../../../shared/constants/transaction';
import { ETH_SWAPS_TOKEN_OBJECT } from '../../../shared/constants/swaps'; import {
isSwapsDefaultTokenAddress,
isSwapsDefaultTokenSymbol,
} from '../../../shared/modules/swaps.utils';
import { getSwapsTokensReceivedFromTxMeta } from '../pages/swaps/swaps.util'; import { getSwapsTokensReceivedFromTxMeta } from '../pages/swaps/swaps.util';
import { getCurrentChainId } from '../selectors';
import { useTokenFiatAmount } from './useTokenFiatAmount'; import { useTokenFiatAmount } from './useTokenFiatAmount';
/** /**
@ -14,10 +19,11 @@ import { useTokenFiatAmount } from './useTokenFiatAmount';
/** /**
* A Swap transaction group's primaryTransaction contains details of the swap, * A Swap transaction group's primaryTransaction contains details of the swap,
* including the source (from) and destination (to) token type (ETH, DAI, etc..) * including the source (from) and destination (to) token type (ETH, DAI, etc..)
* When viewing a non ETH asset page, we need to determine if that asset is the * When viewing an asset page that is not for the current chain's default token, we
* token that was received (destination) from the swap. In that circumstance we * need to determine if that asset is the token that was received (destination) from
* would want to show the primaryCurrency in the activity list that is most relevant * the swap. In that circumstance we would want to show the primaryCurrency in the
* for that token (- 1000 DAI, for example, when swapping DAI for ETH). * activity list that is most relevant for that token (- 1000 DAI, for example, when
* swapping DAI for ETH).
* @param {import('../selectors').transactionGroup} transactionGroup - Group of transactions by nonce * @param {import('../selectors').transactionGroup} transactionGroup - Group of transactions by nonce
* @param {import('./useTokenDisplayValue').Token} currentAsset - The current asset the user is looking at * @param {import('./useTokenDisplayValue').Token} currentAsset - The current asset the user is looking at
* @returns {SwappedTokenValue} * @returns {SwappedTokenValue}
@ -27,11 +33,15 @@ export function useSwappedTokenValue(transactionGroup, currentAsset) {
const { primaryTransaction, initialTransaction } = transactionGroup; const { primaryTransaction, initialTransaction } = transactionGroup;
const { type } = initialTransaction; const { type } = initialTransaction;
const { from: senderAddress } = initialTransaction.txParams || {}; const { from: senderAddress } = initialTransaction.txParams || {};
const chainId = useSelector(getCurrentChainId);
const isViewingReceivedTokenFromSwap = const isViewingReceivedTokenFromSwap =
currentAsset?.symbol === primaryTransaction.destinationTokenSymbol || currentAsset?.symbol === primaryTransaction.destinationTokenSymbol ||
(currentAsset.address === ETH_SWAPS_TOKEN_OBJECT.address && (isSwapsDefaultTokenAddress(currentAsset.address, chainId) &&
primaryTransaction.destinationTokenSymbol === 'ETH'); isSwapsDefaultTokenSymbol(
primaryTransaction.destinationTokenSymbol,
chainId,
));
const swapTokenValue = const swapTokenValue =
type === TRANSACTION_TYPES.SWAP && isViewingReceivedTokenFromSwap type === TRANSACTION_TYPES.SWAP && isViewingReceivedTokenFromSwap
@ -41,6 +51,8 @@ export function useSwappedTokenValue(transactionGroup, currentAsset) {
address, address,
senderAddress, senderAddress,
decimals, decimals,
null,
chainId,
) )
: type === TRANSACTION_TYPES.SWAP && primaryTransaction.swapTokenValue; : type === TRANSACTION_TYPES.SWAP && primaryTransaction.swapTokenValue;

View File

@ -9,9 +9,11 @@ import {
getTokenExchangeRates, getTokenExchangeRates,
getConversionRate, getConversionRate,
getCurrentCurrency, getCurrentCurrency,
getSwapsEthToken, getSwapsDefaultToken,
getCurrentChainId,
} from '../selectors'; } from '../selectors';
import { getSwapsTokens } from '../ducks/swaps/swaps'; import { getSwapsTokens } from '../ducks/swaps/swaps';
import { isSwapsDefaultTokenSymbol } from '../../../shared/modules/swaps.utils';
import { useEqualityCheck } from './useEqualityCheck'; import { useEqualityCheck } from './useEqualityCheck';
const tokenList = shuffle( const tokenList = shuffle(
@ -28,12 +30,15 @@ export function getRenderableTokenData(
contractExchangeRates, contractExchangeRates,
conversionRate, conversionRate,
currentCurrency, currentCurrency,
chainId,
) { ) {
const { symbol, name, address, iconUrl, string, balance, decimals } = token; const { symbol, name, address, iconUrl, string, balance, decimals } = token;
const formattedFiat = const formattedFiat =
getTokenFiatAmount( getTokenFiatAmount(
symbol === 'ETH' ? 1 : contractExchangeRates[address], isSwapsDefaultTokenSymbol(symbol, chainId)
? 1
: contractExchangeRates[address],
conversionRate, conversionRate,
currentCurrency, currentCurrency,
string, string,
@ -42,7 +47,9 @@ export function getRenderableTokenData(
) || ''; ) || '';
const rawFiat = const rawFiat =
getTokenFiatAmount( getTokenFiatAmount(
symbol === 'ETH' ? 1 : contractExchangeRates[address], isSwapsDefaultTokenSymbol(symbol, chainId)
? 1
: contractExchangeRates[address],
conversionRate, conversionRate,
currentCurrency, currentCurrency,
string, string,
@ -70,30 +77,32 @@ export function getRenderableTokenData(
} }
export function useTokensToSearch({ usersTokens = [], topTokens = {} }) { export function useTokensToSearch({ usersTokens = [], topTokens = {} }) {
const chainId = useSelector(getCurrentChainId);
const tokenConversionRates = useSelector(getTokenExchangeRates, isEqual); const tokenConversionRates = useSelector(getTokenExchangeRates, isEqual);
const conversionRate = useSelector(getConversionRate); const conversionRate = useSelector(getConversionRate);
const currentCurrency = useSelector(getCurrentCurrency); const currentCurrency = useSelector(getCurrentCurrency);
const swapsEthToken = useSelector(getSwapsEthToken); const defaultSwapsToken = useSelector(getSwapsDefaultToken);
const memoizedTopTokens = useEqualityCheck(topTokens); const memoizedTopTokens = useEqualityCheck(topTokens);
const memoizedUsersToken = useEqualityCheck(usersTokens); const memoizedUsersToken = useEqualityCheck(usersTokens);
const ethToken = getRenderableTokenData( const defaultToken = getRenderableTokenData(
swapsEthToken, defaultSwapsToken,
tokenConversionRates, tokenConversionRates,
conversionRate, conversionRate,
currentCurrency, currentCurrency,
chainId,
); );
const memoizedEthToken = useEqualityCheck(ethToken); const memoizedDefaultToken = useEqualityCheck(defaultToken);
const swapsTokens = useSelector(getSwapsTokens) || []; const swapsTokens = useSelector(getSwapsTokens) || [];
const tokensToSearch = swapsTokens.length const tokensToSearch = swapsTokens.length
? swapsTokens ? swapsTokens
: [ : [
memoizedEthToken, memoizedDefaultToken,
...tokenList.filter( ...tokenList.filter(
(token) => token.symbol !== memoizedEthToken.symbol, (token) => token.symbol !== memoizedDefaultToken.symbol,
), ),
]; ];
@ -116,9 +125,10 @@ export function useTokensToSearch({ usersTokens = [], topTokens = {} }) {
tokenConversionRates, tokenConversionRates,
conversionRate, conversionRate,
currentCurrency, currentCurrency,
chainId,
); );
if ( if (
renderableDataToken.symbol === 'ETH' || isSwapsDefaultTokenSymbol(renderableDataToken.symbol, chainId) ||
(usersTokensAddressMap[token.address] && (usersTokensAddressMap[token.address] &&
Number(renderableDataToken.balance ?? 0) !== 0) Number(renderableDataToken.balance ?? 0) !== 0)
) { ) {
@ -150,5 +160,6 @@ export function useTokensToSearch({ usersTokens = [], topTokens = {} }) {
conversionRate, conversionRate,
currentCurrency, currentCurrency,
memoizedTopTokens, memoizedTopTokens,
chainId,
]); ]);
} }

View File

@ -10,11 +10,13 @@ import {
getShouldShowFiat, getShouldShowFiat,
getNativeCurrency, getNativeCurrency,
getCurrentCurrency, getCurrentCurrency,
getCurrentChainId,
} from '../selectors'; } from '../selectors';
import { getTokens } from '../ducks/metamask/metamask'; import { getTokens } from '../ducks/metamask/metamask';
import { getMessage } from '../helpers/utils/i18n-helper'; import { getMessage } from '../helpers/utils/i18n-helper';
import messages from '../../../app/_locales/en/messages.json'; import messages from '../../../app/_locales/en/messages.json';
import { ASSET_ROUTE, DEFAULT_ROUTE } from '../helpers/constants/routes'; import { ASSET_ROUTE, DEFAULT_ROUTE } from '../helpers/constants/routes';
import { MAINNET_CHAIN_ID } from '../../../shared/constants/network';
import { import {
TRANSACTION_TYPES, TRANSACTION_TYPES,
TRANSACTION_GROUP_CATEGORIES, TRANSACTION_GROUP_CATEGORIES,
@ -164,6 +166,8 @@ describe('useTransactionDisplayData', function () {
return 'ETH'; return 'ETH';
} else if (selector === getCurrentCurrency) { } else if (selector === getCurrentCurrency) {
return 'ETH'; return 'ETH';
} else if (selector === getCurrentChainId) {
return MAINNET_CHAIN_ID;
} }
return null; return null;
}); });

View File

@ -6,12 +6,14 @@ import { useHistory } from 'react-router-dom';
import { I18nContext } from '../../../contexts/i18n'; import { I18nContext } from '../../../contexts/i18n';
import { useNewMetricEvent } from '../../../hooks/useMetricEvent'; import { useNewMetricEvent } from '../../../hooks/useMetricEvent';
import { MetaMetricsContext } from '../../../contexts/metametrics.new'; import { MetaMetricsContext } from '../../../contexts/metametrics.new';
import { import {
getCurrentChainId, getCurrentChainId,
getCurrentCurrency, getCurrentCurrency,
getRpcPrefsForCurrentProvider, getRpcPrefsForCurrentProvider,
getUSDConversionRate, getUSDConversionRate,
} from '../../../selectors'; } from '../../../selectors';
import { import {
getUsedQuote, getUsedQuote,
getFetchParams, getFetchParams,
@ -23,7 +25,6 @@ import {
prepareToLeaveSwaps, prepareToLeaveSwaps,
} from '../../../ducks/swaps/swaps'; } from '../../../ducks/swaps/swaps';
import Mascot from '../../../components/ui/mascot'; import Mascot from '../../../components/ui/mascot';
import PulseLoader from '../../../components/ui/pulse-loader';
import { import {
QUOTES_EXPIRED_ERROR, QUOTES_EXPIRED_ERROR,
SWAP_FAILED_ERROR, SWAP_FAILED_ERROR,
@ -31,6 +32,9 @@ import {
QUOTES_NOT_AVAILABLE_ERROR, QUOTES_NOT_AVAILABLE_ERROR,
OFFLINE_FOR_MAINTENANCE, OFFLINE_FOR_MAINTENANCE,
} from '../../../../../shared/constants/swaps'; } from '../../../../../shared/constants/swaps';
import { isSwapsDefaultTokenSymbol } from '../../../../../shared/modules/swaps.utils';
import PulseLoader from '../../../components/ui/pulse-loader';
import { ASSET_ROUTE, DEFAULT_ROUTE } from '../../../helpers/constants/routes'; import { ASSET_ROUTE, DEFAULT_ROUTE } from '../../../helpers/constants/routes';
import { getRenderableNetworkFeesForQuote } from '../swaps.util'; import { getRenderableNetworkFeesForQuote } from '../swaps.util';
@ -73,16 +77,17 @@ export default function AwaitingSwap({
let feeinUnformattedFiat; let feeinUnformattedFiat;
if (usedQuote && swapsGasPrice) { if (usedQuote && swapsGasPrice) {
const renderableNetworkFees = getRenderableNetworkFeesForQuote( const renderableNetworkFees = getRenderableNetworkFeesForQuote({
usedQuote.gasEstimateWithRefund || usedQuote.averageGas, tradeGas: usedQuote.gasEstimateWithRefund || usedQuote.averageGas,
approveTxParams?.gas || '0x0', approveGas: approveTxParams?.gas || '0x0',
swapsGasPrice, gasPrice: swapsGasPrice,
currentCurrency, currentCurrency,
usdConversionRate, conversionRate: usdConversionRate,
usedQuote?.trade?.value, tradeValue: usedQuote?.trade?.value,
sourceTokenInfo?.symbol, sourceSymbol: sourceTokenInfo?.symbol,
usedQuote.sourceAmount, sourceAmount: usedQuote.sourceAmount,
); chainId,
});
feeinUnformattedFiat = renderableNetworkFees.rawNetworkFees; feeinUnformattedFiat = renderableNetworkFees.rawNetworkFees;
} }
@ -228,7 +233,9 @@ export default function AwaitingSwap({
); );
} else if (errorKey) { } else if (errorKey) {
await dispatch(navigateBackToBuildQuote(history)); await dispatch(navigateBackToBuildQuote(history));
} else if (destinationTokenInfo?.symbol === 'ETH') { } else if (
isSwapsDefaultTokenSymbol(destinationTokenInfo?.symbol, chainId)
) {
history.push(DEFAULT_ROUTE); history.push(DEFAULT_ROUTE);
} else { } else {
history.push(`${ASSET_ROUTE}/${destinationTokenInfo?.address}`); history.push(`${ASSET_ROUTE}/${destinationTokenInfo?.address}`);

View File

@ -29,10 +29,11 @@ import {
getFetchParams, getFetchParams,
} from '../../../ducks/swaps/swaps'; } from '../../../ducks/swaps/swaps';
import { import {
getSwapsEthToken, getSwapsDefaultToken,
getTokenExchangeRates, getTokenExchangeRates,
getConversionRate, getConversionRate,
getCurrentCurrency, getCurrentCurrency,
getCurrentChainId,
} from '../../../selectors'; } from '../../../selectors';
import { import {
getValueFromWeiHex, getValueFromWeiHex,
@ -44,7 +45,10 @@ import { useTokenTracker } from '../../../hooks/useTokenTracker';
import { useTokenFiatAmount } from '../../../hooks/useTokenFiatAmount'; import { useTokenFiatAmount } from '../../../hooks/useTokenFiatAmount';
import { useEthFiatAmount } from '../../../hooks/useEthFiatAmount'; import { useEthFiatAmount } from '../../../hooks/useEthFiatAmount';
import { ETH_SWAPS_TOKEN_OBJECT } from '../../../../../shared/constants/swaps'; import {
isSwapsDefaultTokenAddress,
isSwapsDefaultTokenSymbol,
} from '../../../../../shared/modules/swaps.utils';
import { resetSwapsPostFetchState, removeToken } from '../../../store/actions'; import { resetSwapsPostFetchState, removeToken } from '../../../store/actions';
import { fetchTokenPrice, fetchTokenBalance } from '../swaps.util'; import { fetchTokenPrice, fetchTokenBalance } from '../swaps.util';
@ -84,21 +88,29 @@ export default function BuildQuote({
const topAssets = useSelector(getTopAssets); const topAssets = useSelector(getTopAssets);
const fromToken = useSelector(getFromToken); const fromToken = useSelector(getFromToken);
const toToken = useSelector(getToToken) || destinationTokenInfo; const toToken = useSelector(getToToken) || destinationTokenInfo;
const swapsEthToken = useSelector(getSwapsEthToken); const defaultSwapsToken = useSelector(getSwapsDefaultToken);
const fetchParamsFromToken = const chainId = useSelector(getCurrentChainId);
sourceTokenInfo?.symbol === 'ETH' ? swapsEthToken : sourceTokenInfo;
const tokenConversionRates = useSelector(getTokenExchangeRates, isEqual); const tokenConversionRates = useSelector(getTokenExchangeRates, isEqual);
const conversionRate = useSelector(getConversionRate); const conversionRate = useSelector(getConversionRate);
const currentCurrency = useSelector(getCurrentCurrency); const currentCurrency = useSelector(getCurrentCurrency);
const fetchParamsFromToken = isSwapsDefaultTokenSymbol(
sourceTokenInfo?.symbol,
chainId,
)
? defaultSwapsToken
: sourceTokenInfo;
const { loading, tokensWithBalances } = useTokenTracker(tokens); const { loading, tokensWithBalances } = useTokenTracker(tokens);
// If the fromToken was set in a call to `onFromSelect` (see below), and that from token has a balance // If the fromToken was set in a call to `onFromSelect` (see below), and that from token has a balance
// but is not in tokensWithBalances or tokens, then we want to add it to the usersTokens array so that // but is not in tokensWithBalances or tokens, then we want to add it to the usersTokens array so that
// the balance of the token can appear in the from token selection dropdown // the balance of the token can appear in the from token selection dropdown
const fromTokenArray = const fromTokenArray =
fromToken?.symbol !== 'ETH' && fromToken?.balance ? [fromToken] : []; !isSwapsDefaultTokenSymbol(fromToken?.symbol, chainId) && fromToken?.balance
? [fromToken]
: [];
const usersTokens = uniqBy( const usersTokens = uniqBy(
[...tokensWithBalances, ...tokens, ...fromTokenArray], [...tokensWithBalances, ...tokens, ...fromTokenArray],
'address', 'address',
@ -110,6 +122,7 @@ export default function BuildQuote({
tokenConversionRates, tokenConversionRates,
conversionRate, conversionRate,
currentCurrency, currentCurrency,
chainId,
); );
const tokensToSearch = useTokensToSearch({ const tokensToSearch = useTokensToSearch({
@ -119,9 +132,9 @@ export default function BuildQuote({
const selectedToToken = const selectedToToken =
tokensToSearch.find(({ address }) => address === toToken?.address) || tokensToSearch.find(({ address }) => address === toToken?.address) ||
toToken; toToken;
const toTokenIsNotEth = const toTokenIsNotDefault =
selectedToToken?.address && selectedToToken?.address &&
selectedToToken?.address !== ETH_SWAPS_TOKEN_OBJECT.address; !isSwapsDefaultTokenAddress(selectedToToken?.address, chainId);
const occurances = Number(selectedToToken?.occurances || 0); const occurances = Number(selectedToToken?.occurances || 0);
const { const {
address: fromTokenAddress, address: fromTokenAddress,
@ -151,8 +164,9 @@ export default function BuildQuote({
{ showFiat: true }, { showFiat: true },
true, true,
); );
const swapFromFiatValue = const swapFromFiatValue = isSwapsDefaultTokenSymbol(fromTokenSymbol, chainId)
fromTokenSymbol === 'ETH' ? swapFromEthFiatValue : swapFromTokenFiatValue; ? swapFromEthFiatValue
: swapFromTokenFiatValue;
const onFromSelect = (token) => { const onFromSelect = (token) => {
if ( if (
@ -227,15 +241,17 @@ export default function BuildQuote({
); );
useEffect(() => { useEffect(() => {
const notEth = const notDefault = !isSwapsDefaultTokenAddress(
tokensWithBalancesFromToken?.address !== ETH_SWAPS_TOKEN_OBJECT.address; tokensWithBalancesFromToken?.address,
chainId,
);
const addressesAreTheSame = const addressesAreTheSame =
tokensWithBalancesFromToken?.address === tokensWithBalancesFromToken?.address ===
previousTokensWithBalancesFromToken?.address; previousTokensWithBalancesFromToken?.address;
const balanceHasChanged = const balanceHasChanged =
tokensWithBalancesFromToken?.balance !== tokensWithBalancesFromToken?.balance !==
previousTokensWithBalancesFromToken?.balance; previousTokensWithBalancesFromToken?.balance;
if (notEth && addressesAreTheSame && balanceHasChanged) { if (notDefault && addressesAreTheSame && balanceHasChanged) {
dispatch( dispatch(
setSwapsFromToken({ setSwapsFromToken({
...fromToken, ...fromToken,
@ -249,12 +265,13 @@ export default function BuildQuote({
tokensWithBalancesFromToken, tokensWithBalancesFromToken,
previousTokensWithBalancesFromToken, previousTokensWithBalancesFromToken,
fromToken, fromToken,
chainId,
]); ]);
// If the eth balance changes while on build quote, we update the selected from token // If the eth balance changes while on build quote, we update the selected from token
useEffect(() => { useEffect(() => {
if ( if (
fromToken?.address === ETH_SWAPS_TOKEN_OBJECT.address && isSwapsDefaultTokenAddress(fromToken?.address, chainId) &&
fromToken?.balance !== hexToDecimal(ethBalance) fromToken?.balance !== hexToDecimal(ethBalance)
) { ) {
dispatch( dispatch(
@ -269,7 +286,7 @@ export default function BuildQuote({
}), }),
); );
} }
}, [dispatch, fromToken, ethBalance]); }, [dispatch, fromToken, ethBalance, chainId]);
useEffect(() => { useEffect(() => {
if (prevFromTokenBalance !== fromTokenBalance) { if (prevFromTokenBalance !== fromTokenBalance) {
@ -286,7 +303,7 @@ export default function BuildQuote({
<div className="build-quote__content"> <div className="build-quote__content">
<div className="build-quote__dropdown-input-pair-header"> <div className="build-quote__dropdown-input-pair-header">
<div className="build-quote__input-label">{t('swapSwapFrom')}</div> <div className="build-quote__input-label">{t('swapSwapFrom')}</div>
{fromTokenSymbol !== 'ETH' && ( {!isSwapsDefaultTokenSymbol(fromTokenSymbol, chainId) && (
<div <div
className="build-quote__max-button" className="build-quote__max-button"
onClick={() => onClick={() =>
@ -384,7 +401,7 @@ export default function BuildQuote({
defaultToAll defaultToAll
/> />
</div> </div>
{toTokenIsNotEth && {toTokenIsNotDefault &&
(occurances < 2 ? ( (occurances < 2 ? (
<ActionableMessage <ActionableMessage
message={ message={
@ -474,7 +491,7 @@ export default function BuildQuote({
!selectedToToken?.address || !selectedToToken?.address ||
Number(maxSlippage) === 0 || Number(maxSlippage) === 0 ||
Number(maxSlippage) > MAX_ALLOWED_SLIPPAGE || Number(maxSlippage) > MAX_ALLOWED_SLIPPAGE ||
(toTokenIsNotEth && occurances < 2 && !verificationClicked) (toTokenIsNotDefault && occurances < 2 && !verificationClicked)
} }
hideCancel hideCancel
showTermsOfService showTermsOfService

View File

@ -12,6 +12,7 @@ import { I18nContext } from '../../contexts/i18n';
import { import {
getSelectedAccount, getSelectedAccount,
getCurrentChainId, getCurrentChainId,
getIsSwapsChain,
} from '../../selectors/selectors'; } from '../../selectors/selectors';
import { import {
getQuotes, getQuotes,
@ -45,7 +46,6 @@ import {
SWAP_FAILED_ERROR, SWAP_FAILED_ERROR,
OFFLINE_FOR_MAINTENANCE, OFFLINE_FOR_MAINTENANCE,
} from '../../../../shared/constants/swaps'; } from '../../../../shared/constants/swaps';
import { MAINNET_CHAIN_ID } from '../../../../shared/constants/network';
import { import {
resetBackgroundSwapsState, resetBackgroundSwapsState,
@ -96,6 +96,8 @@ export default function Swap() {
const fetchingQuotes = useSelector(getFetchingQuotes); const fetchingQuotes = useSelector(getFetchingQuotes);
let swapsErrorKey = useSelector(getSwapsErrorKey); let swapsErrorKey = useSelector(getSwapsErrorKey);
const swapsEnabled = useSelector(getSwapsFeatureLiveness); const swapsEnabled = useSelector(getSwapsFeatureLiveness);
const chainId = useSelector(getCurrentChainId);
const isSwapsChain = useSelector(getIsSwapsChain);
const { const {
balance: ethBalance, balance: ethBalance,
@ -116,6 +118,7 @@ export default function Swap() {
selectedAccountAddress, selectedAccountAddress,
destinationTokenInfo?.decimals, destinationTokenInfo?.decimals,
approveTxData, approveTxData,
chainId,
); );
const tradeConfirmed = tradeTxData?.status === TRANSACTION_STATUSES.CONFIRMED; const tradeConfirmed = tradeTxData?.status === TRANSACTION_STATUSES.CONFIRMED;
const approveError = const approveError =
@ -155,26 +158,26 @@ export default function Swap() {
}, []); }, []);
useEffect(() => { useEffect(() => {
fetchTokens() fetchTokens(chainId)
.then((tokens) => { .then((tokens) => {
dispatch(setSwapsTokens(tokens)); dispatch(setSwapsTokens(tokens));
}) })
.catch((error) => console.error(error)); .catch((error) => console.error(error));
fetchTopAssets().then((topAssets) => { fetchTopAssets(chainId).then((topAssets) => {
dispatch(setTopAssets(topAssets)); dispatch(setTopAssets(topAssets));
}); });
fetchAggregatorMetadata().then((newAggregatorMetadata) => { fetchAggregatorMetadata(chainId).then((newAggregatorMetadata) => {
dispatch(setAggregatorMetadata(newAggregatorMetadata)); dispatch(setAggregatorMetadata(newAggregatorMetadata));
}); });
dispatch(fetchAndSetSwapsGasPriceInfo()); dispatch(fetchAndSetSwapsGasPriceInfo(chainId));
return () => { return () => {
dispatch(prepareToLeaveSwaps()); dispatch(prepareToLeaveSwaps());
}; };
}, [dispatch]); }, [dispatch, chainId]);
const exitedSwapsEvent = useNewMetricEvent({ const exitedSwapsEvent = useNewMetricEvent({
event: 'Exited Swaps', event: 'Exited Swaps',
@ -224,8 +227,7 @@ export default function Swap() {
return () => window.removeEventListener('beforeunload', fn); return () => window.removeEventListener('beforeunload', fn);
}, [dispatch, isLoadingQuotesRoute]); }, [dispatch, isLoadingQuotesRoute]);
const chainId = useSelector(getCurrentChainId); if (!isSwapsChain) {
if (chainId !== MAINNET_CHAIN_ID) {
return <Redirect to={{ pathname: DEFAULT_ROUTE }} />; return <Redirect to={{ pathname: DEFAULT_ROUTE }} />;
} }

View File

@ -6,7 +6,7 @@ import { setSwapsFromToken } from '../../../ducks/swaps/swaps';
import { I18nContext } from '../../../contexts/i18n'; import { I18nContext } from '../../../contexts/i18n';
import { BUILD_QUOTE_ROUTE } from '../../../helpers/constants/routes'; import { BUILD_QUOTE_ROUTE } from '../../../helpers/constants/routes';
import { useNewMetricEvent } from '../../../hooks/useMetricEvent'; import { useNewMetricEvent } from '../../../hooks/useMetricEvent';
import { getSwapsEthToken } from '../../../selectors'; import { getSwapsDefaultToken } from '../../../selectors';
import Button from '../../../components/ui/button'; import Button from '../../../components/ui/button';
import Popover from '../../../components/ui/popover'; import Popover from '../../../components/ui/popover';
@ -14,9 +14,14 @@ export default function IntroPopup({ onClose }) {
const dispatch = useDispatch(useDispatch); const dispatch = useDispatch(useDispatch);
const history = useHistory(); const history = useHistory();
const t = useContext(I18nContext); const t = useContext(I18nContext);
const swapsDefaultToken = useSelector(getSwapsDefaultToken);
const enteredSwapsEvent = useNewMetricEvent({ const enteredSwapsEvent = useNewMetricEvent({
event: 'Swaps Opened', event: 'Swaps Opened',
properties: { source: 'Intro popup', active_currency: 'ETH' }, properties: {
source: 'Intro popup',
active_currency: swapsDefaultToken.symbol,
},
category: 'swaps', category: 'swaps',
}); });
const blogPostVisitedEvent = useNewMetricEvent({ const blogPostVisitedEvent = useNewMetricEvent({
@ -31,7 +36,6 @@ export default function IntroPopup({ onClose }) {
event: 'Product Overview Dismissed', event: 'Product Overview Dismissed',
category: 'swaps', category: 'swaps',
}); });
const swapsEthToken = useSelector(getSwapsEthToken);
return ( return (
<div className="intro-popup"> <div className="intro-popup">
@ -51,7 +55,7 @@ export default function IntroPopup({ onClose }) {
onClick={() => { onClick={() => {
onClose(); onClose();
enteredSwapsEvent(); enteredSwapsEvent();
dispatch(setSwapsFromToken(swapsEthToken)); dispatch(setSwapsFromToken(swapsDefaultToken));
history.push(BUILD_QUOTE_ROUTE); history.push(BUILD_QUOTE_ROUTE);
}} }}
> >

View File

@ -3,9 +3,15 @@ import BigNumber from 'bignumber.js';
import abi from 'human-standard-token-abi'; import abi from 'human-standard-token-abi';
import { isValidAddress } from 'ethereumjs-util'; import { isValidAddress } from 'ethereumjs-util';
import { import {
ETH_SWAPS_TOKEN_OBJECT, SWAPS_CHAINID_DEFAULT_TOKEN_MAP,
METASWAP_API_HOST, METASWAP_CHAINID_API_HOST_MAP,
} from '../../../../shared/constants/swaps'; } from '../../../../shared/constants/swaps';
import {
isSwapsDefaultTokenAddress,
isSwapsDefaultTokenSymbol,
} from '../../../../shared/modules/swaps.utils';
import { MAINNET_CHAIN_ID } from '../../../../shared/constants/network';
import { import {
calcTokenValue, calcTokenValue,
calcTokenAmount, calcTokenAmount,
@ -30,22 +36,22 @@ const TOKEN_TRANSFER_LOG_TOPIC_HASH =
const CACHE_REFRESH_ONE_HOUR = 3600000; const CACHE_REFRESH_ONE_HOUR = 3600000;
const getBaseApi = function (type) { const getBaseApi = function (type, chainId = MAINNET_CHAIN_ID) {
switch (type) { switch (type) {
case 'trade': case 'trade':
return `${METASWAP_API_HOST}/trades?`; return `${METASWAP_CHAINID_API_HOST_MAP[chainId]}/trades?`;
case 'tokens': case 'tokens':
return `${METASWAP_API_HOST}/tokens`; return `${METASWAP_CHAINID_API_HOST_MAP[chainId]}/tokens`;
case 'topAssets': case 'topAssets':
return `${METASWAP_API_HOST}/topAssets`; return `${METASWAP_CHAINID_API_HOST_MAP[chainId]}/topAssets`;
case 'featureFlag': case 'featureFlag':
return `${METASWAP_API_HOST}/featureFlag`; return `${METASWAP_CHAINID_API_HOST_MAP[chainId]}/featureFlag`;
case 'aggregatorMetadata': case 'aggregatorMetadata':
return `${METASWAP_API_HOST}/aggregatorMetadata`; return `${METASWAP_CHAINID_API_HOST_MAP[chainId]}/aggregatorMetadata`;
case 'gasPrices': case 'gasPrices':
return `${METASWAP_API_HOST}/gasPrices`; return `${METASWAP_CHAINID_API_HOST_MAP[chainId]}/gasPrices`;
case 'refreshTime': case 'refreshTime':
return `${METASWAP_API_HOST}/quoteRefreshRate`; return `${METASWAP_CHAINID_API_HOST_MAP[chainId]}/quoteRefreshRate`;
default: default:
throw new Error('getBaseApi requires an api call type'); throw new Error('getBaseApi requires an api call type');
} }
@ -205,15 +211,18 @@ function validateData(validators, object, urlUsed) {
}); });
} }
export async function fetchTradesInfo({ export async function fetchTradesInfo(
slippage, {
sourceToken, slippage,
sourceDecimals, sourceToken,
destinationToken, sourceDecimals,
value, destinationToken,
fromAddress, value,
exchangeList, fromAddress,
}) { exchangeList,
},
{ chainId },
) {
const urlParams = { const urlParams = {
destinationToken, destinationToken,
sourceToken, sourceToken,
@ -228,7 +237,7 @@ export async function fetchTradesInfo({
} }
const queryString = new URLSearchParams(urlParams).toString(); const queryString = new URLSearchParams(urlParams).toString();
const tradeURL = `${getBaseApi('trade')}${queryString}`; const tradeURL = `${getBaseApi('trade', chainId)}${queryString}`;
const tradesResponse = await fetchWithCache( const tradesResponse = await fetchWithCache(
tradeURL, tradeURL,
{ method: 'GET' }, { method: 'GET' },
@ -272,21 +281,21 @@ export async function fetchTradesInfo({
return newQuotes; return newQuotes;
} }
export async function fetchTokens() { export async function fetchTokens(chainId) {
const tokenUrl = getBaseApi('tokens'); const tokenUrl = getBaseApi('tokens', chainId);
const tokens = await fetchWithCache( const tokens = await fetchWithCache(
tokenUrl, tokenUrl,
{ method: 'GET' }, { method: 'GET' },
{ cacheRefreshTime: CACHE_REFRESH_ONE_HOUR }, { cacheRefreshTime: CACHE_REFRESH_ONE_HOUR },
); );
const filteredTokens = [ const filteredTokens = [
ETH_SWAPS_TOKEN_OBJECT, SWAPS_CHAINID_DEFAULT_TOKEN_MAP[chainId],
...tokens.filter((token) => { ...tokens.filter((token) => {
return ( return (
validateData(TOKEN_VALIDATORS, token, tokenUrl) && validateData(TOKEN_VALIDATORS, token, tokenUrl) &&
!( !(
token.symbol === ETH_SWAPS_TOKEN_OBJECT.symbol || isSwapsDefaultTokenSymbol(token.symbol, chainId) ||
token.address === ETH_SWAPS_TOKEN_OBJECT.address isSwapsDefaultTokenAddress(token.address, chainId)
) )
); );
}), }),
@ -294,8 +303,8 @@ export async function fetchTokens() {
return filteredTokens; return filteredTokens;
} }
export async function fetchAggregatorMetadata() { export async function fetchAggregatorMetadata(chainId) {
const aggregatorMetadataUrl = getBaseApi('aggregatorMetadata'); const aggregatorMetadataUrl = getBaseApi('aggregatorMetadata', chainId);
const aggregators = await fetchWithCache( const aggregators = await fetchWithCache(
aggregatorMetadataUrl, aggregatorMetadataUrl,
{ method: 'GET' }, { method: 'GET' },
@ -316,8 +325,8 @@ export async function fetchAggregatorMetadata() {
return filteredAggregators; return filteredAggregators;
} }
export async function fetchTopAssets() { export async function fetchTopAssets(chainId) {
const topAssetsUrl = getBaseApi('topAssets'); const topAssetsUrl = getBaseApi('topAssets', chainId);
const response = await fetchWithCache( const response = await fetchWithCache(
topAssetsUrl, topAssetsUrl,
{ method: 'GET' }, { method: 'GET' },
@ -332,18 +341,18 @@ export async function fetchTopAssets() {
return topAssetsMap; return topAssetsMap;
} }
export async function fetchSwapsFeatureLiveness() { export async function fetchSwapsFeatureLiveness(chainId) {
const status = await fetchWithCache( const status = await fetchWithCache(
getBaseApi('featureFlag'), getBaseApi('featureFlag', chainId),
{ method: 'GET' }, { method: 'GET' },
{ cacheRefreshTime: 600000 }, { cacheRefreshTime: 600000 },
); );
return status?.active; return status?.active;
} }
export async function fetchSwapsQuoteRefreshTime() { export async function fetchSwapsQuoteRefreshTime(chainId) {
const response = await fetchWithCache( const response = await fetchWithCache(
getBaseApi('refreshTime'), getBaseApi('refreshTime', chainId),
{ method: 'GET' }, { method: 'GET' },
{ cacheRefreshTime: 600000 }, { cacheRefreshTime: 600000 },
); );
@ -378,8 +387,8 @@ export async function fetchTokenBalance(address, userAddress) {
return usersToken; return usersToken;
} }
export async function fetchSwapsGasPrices() { export async function fetchSwapsGasPrices(chainId) {
const gasPricesUrl = getBaseApi('gasPrices'); const gasPricesUrl = getBaseApi('gasPrices', chainId);
const response = await fetchWithCache( const response = await fetchWithCache(
gasPricesUrl, gasPricesUrl,
{ method: 'GET' }, { method: 'GET' },
@ -408,7 +417,7 @@ export async function fetchSwapsGasPrices() {
}; };
} }
export function getRenderableNetworkFeesForQuote( export function getRenderableNetworkFeesForQuote({
tradeGas, tradeGas,
approveGas, approveGas,
gasPrice, gasPrice,
@ -417,14 +426,18 @@ export function getRenderableNetworkFeesForQuote(
tradeValue, tradeValue,
sourceSymbol, sourceSymbol,
sourceAmount, sourceAmount,
) { chainId,
}) {
const totalGasLimitForCalculation = new BigNumber(tradeGas || '0x0', 16) const totalGasLimitForCalculation = new BigNumber(tradeGas || '0x0', 16)
.plus(approveGas || '0x0', 16) .plus(approveGas || '0x0', 16)
.toString(16); .toString(16);
const gasTotalInWeiHex = calcGasTotal(totalGasLimitForCalculation, gasPrice); const gasTotalInWeiHex = calcGasTotal(totalGasLimitForCalculation, gasPrice);
const nonGasFee = new BigNumber(tradeValue, 16) const nonGasFee = new BigNumber(tradeValue, 16)
.minus(sourceSymbol === 'ETH' ? sourceAmount : 0, 10) .minus(
isSwapsDefaultTokenSymbol(sourceSymbol, chainId) ? sourceAmount : 0,
10,
)
.toString(16); .toString(16);
const totalWeiCost = new BigNumber(gasTotalInWeiHex, 16) const totalWeiCost = new BigNumber(gasTotalInWeiHex, 16)
@ -447,7 +460,7 @@ export function getRenderableNetworkFeesForQuote(
rawNetworkFees, rawNetworkFees,
rawEthFee: ethFee, rawEthFee: ethFee,
feeInFiat: formattedNetworkFee, feeInFiat: formattedNetworkFee,
feeInEth: `${ethFee} ETH`, feeInEth: `${ethFee} ${SWAPS_CHAINID_DEFAULT_TOKEN_MAP[chainId].symbol}`,
nonGasFee, nonGasFee,
}; };
} }
@ -459,6 +472,7 @@ export function quotesToRenderableData(
currentCurrency, currentCurrency,
approveGas, approveGas,
tokenConversionRates, tokenConversionRates,
chainId,
) { ) {
return Object.values(quotes).map((quote) => { return Object.values(quotes).map((quote) => {
const { const {
@ -488,16 +502,17 @@ export function quotesToRenderableData(
rawNetworkFees, rawNetworkFees,
rawEthFee, rawEthFee,
feeInEth, feeInEth,
} = getRenderableNetworkFeesForQuote( } = getRenderableNetworkFeesForQuote({
gasEstimateWithRefund || decimalToHex(averageGas || 800000), tradeGas: gasEstimateWithRefund || decimalToHex(averageGas || 800000),
approveGas, approveGas,
gasPrice, gasPrice,
currentCurrency, currentCurrency,
conversionRate, conversionRate,
trade.value, tradeValue: trade.value,
sourceTokenInfo.symbol, sourceSymbol: sourceTokenInfo.symbol,
sourceAmount, sourceAmount,
); chainId,
});
const slippageMultiplier = new BigNumber(100 - slippage).div(100); const slippageMultiplier = new BigNumber(100 - slippage).div(100);
const minimumAmountReceived = new BigNumber(destinationValue) const minimumAmountReceived = new BigNumber(destinationValue)
@ -506,18 +521,20 @@ export function quotesToRenderableData(
const tokenConversionRate = const tokenConversionRate =
tokenConversionRates[destinationTokenInfo.address]; tokenConversionRates[destinationTokenInfo.address];
const ethValueOfTrade = const ethValueOfTrade = isSwapsDefaultTokenSymbol(
destinationTokenInfo.symbol === 'ETH' destinationTokenInfo.symbol,
? calcTokenAmount( chainId,
destinationAmount, )
destinationTokenInfo.decimals, ? calcTokenAmount(destinationAmount, destinationTokenInfo.decimals).minus(
).minus(rawEthFee, 10) rawEthFee,
: new BigNumber(tokenConversionRate || 0, 10) 10,
.times( )
calcTokenAmount(destinationAmount, destinationTokenInfo.decimals), : new BigNumber(tokenConversionRate || 0, 10)
10, .times(
) calcTokenAmount(destinationAmount, destinationTokenInfo.decimals),
.minus(rawEthFee, 10); 10,
)
.minus(rawEthFee, 10);
let liquiditySourceKey; let liquiditySourceKey;
let renderedSlippage = slippage; let renderedSlippage = slippage;
@ -566,9 +583,10 @@ export function getSwapsTokensReceivedFromTxMeta(
accountAddress, accountAddress,
tokenDecimals, tokenDecimals,
approvalTxMeta, approvalTxMeta,
chainId,
) { ) {
const txReceipt = txMeta?.txReceipt; const txReceipt = txMeta?.txReceipt;
if (tokenSymbol === 'ETH') { if (isSwapsDefaultTokenSymbol(tokenSymbol, chainId)) {
if ( if (
!txReceipt || !txReceipt ||
!txMeta || !txMeta ||

View File

@ -1,5 +1,6 @@
import { strict as assert } from 'assert'; import { strict as assert } from 'assert';
import proxyquire from 'proxyquire'; import proxyquire from 'proxyquire';
import { MAINNET_CHAIN_ID } from '../../../../shared/constants/network';
import { import {
TRADES_BASE_PROD_URL, TRADES_BASE_PROD_URL,
TOKENS_BASE_PROD_URL, TOKENS_BASE_PROD_URL,
@ -89,42 +90,45 @@ describe('Swaps Util', function () {
}, },
}; };
it('should fetch trade info on prod', async function () { it('should fetch trade info on prod', async function () {
const result = await fetchTradesInfo({ const result = await fetchTradesInfo(
TOKENS, {
slippage: '3', TOKENS,
sourceToken: TOKENS[0].address, slippage: '3',
destinationToken: TOKENS[1].address, sourceToken: TOKENS[0].address,
value: '2000000000000000000', destinationToken: TOKENS[1].address,
fromAddress: '0xmockAddress', value: '2000000000000000000',
sourceSymbol: TOKENS[0].symbol, fromAddress: '0xmockAddress',
sourceDecimals: TOKENS[0].decimals, sourceSymbol: TOKENS[0].symbol,
sourceTokenInfo: { ...TOKENS[0] }, sourceDecimals: TOKENS[0].decimals,
destinationTokenInfo: { ...TOKENS[1] }, sourceTokenInfo: { ...TOKENS[0] },
}); destinationTokenInfo: { ...TOKENS[1] },
},
{ chainId: MAINNET_CHAIN_ID },
);
assert.deepStrictEqual(result, expectedResult2); assert.deepStrictEqual(result, expectedResult2);
}); });
}); });
describe('fetchTokens', function () { describe('fetchTokens', function () {
it('should fetch tokens', async function () { it('should fetch tokens', async function () {
const result = await fetchTokens(true); const result = await fetchTokens(MAINNET_CHAIN_ID);
assert.deepStrictEqual(result, EXPECTED_TOKENS_RESULT); assert.deepStrictEqual(result, EXPECTED_TOKENS_RESULT);
}); });
it('should fetch tokens on prod', async function () { it('should fetch tokens on prod', async function () {
const result = await fetchTokens(false); const result = await fetchTokens(MAINNET_CHAIN_ID);
assert.deepStrictEqual(result, EXPECTED_TOKENS_RESULT); assert.deepStrictEqual(result, EXPECTED_TOKENS_RESULT);
}); });
}); });
describe('fetchAggregatorMetadata', function () { describe('fetchAggregatorMetadata', function () {
it('should fetch aggregator metadata', async function () { it('should fetch aggregator metadata', async function () {
const result = await fetchAggregatorMetadata(true); const result = await fetchAggregatorMetadata(MAINNET_CHAIN_ID);
assert.deepStrictEqual(result, AGGREGATOR_METADATA); assert.deepStrictEqual(result, AGGREGATOR_METADATA);
}); });
it('should fetch aggregator metadata on prod', async function () { it('should fetch aggregator metadata on prod', async function () {
const result = await fetchAggregatorMetadata(false); const result = await fetchAggregatorMetadata(MAINNET_CHAIN_ID);
assert.deepStrictEqual(result, AGGREGATOR_METADATA); assert.deepStrictEqual(result, AGGREGATOR_METADATA);
}); });
}); });
@ -148,12 +152,12 @@ describe('Swaps Util', function () {
}, },
}; };
it('should fetch top assets', async function () { it('should fetch top assets', async function () {
const result = await fetchTopAssets(true); const result = await fetchTopAssets(MAINNET_CHAIN_ID);
assert.deepStrictEqual(result, expectedResult); assert.deepStrictEqual(result, expectedResult);
}); });
it('should fetch top assets on prod', async function () { it('should fetch top assets on prod', async function () {
const result = await fetchTopAssets(false); const result = await fetchTopAssets(MAINNET_CHAIN_ID);
assert.deepStrictEqual(result, expectedResult); assert.deepStrictEqual(result, expectedResult);
}); });
}); });

View File

@ -36,7 +36,8 @@ import {
getSelectedAccount, getSelectedAccount,
getCurrentCurrency, getCurrentCurrency,
getTokenExchangeRates, getTokenExchangeRates,
getSwapsEthToken, getSwapsDefaultToken,
getCurrentChainId,
} from '../../../selectors'; } from '../../../selectors';
import { toPrecisionWithoutTrailingZeros } from '../../../helpers/utils/util'; import { toPrecisionWithoutTrailingZeros } from '../../../helpers/utils/util';
import { getTokens } from '../../../ducks/metamask/metamask'; import { getTokens } from '../../../ducks/metamask/metamask';
@ -125,7 +126,8 @@ export default function ViewQuote() {
const usedQuote = selectedQuote || topQuote; const usedQuote = selectedQuote || topQuote;
const tradeValue = usedQuote?.trade?.value ?? '0x0'; const tradeValue = usedQuote?.trade?.value ?? '0x0';
const swapsQuoteRefreshTime = useSelector(getSwapsQuoteRefreshTime); const swapsQuoteRefreshTime = useSelector(getSwapsQuoteRefreshTime);
const swapsEthToken = useSelector(getSwapsEthToken); const defaultSwapsToken = useSelector(getSwapsDefaultToken);
const chainId = useSelector(getCurrentChainId);
const { isBestQuote } = usedQuote; const { isBestQuote } = usedQuote;
@ -151,8 +153,8 @@ export default function ViewQuote() {
const { tokensWithBalances } = useTokenTracker(swapsTokens, true); const { tokensWithBalances } = useTokenTracker(swapsTokens, true);
const balanceToken = const balanceToken =
fetchParamsSourceToken === swapsEthToken.address fetchParamsSourceToken === defaultSwapsToken.address
? swapsEthToken ? defaultSwapsToken
: tokensWithBalances.find( : tokensWithBalances.find(
({ address }) => address === fetchParamsSourceToken, ({ address }) => address === fetchParamsSourceToken,
); );
@ -183,6 +185,7 @@ export default function ViewQuote() {
currentCurrency, currentCurrency,
approveGas, approveGas,
memoizedTokenConversionRates, memoizedTokenConversionRates,
chainId,
); );
}, [ }, [
quotes, quotes,
@ -191,6 +194,7 @@ export default function ViewQuote() {
currentCurrency, currentCurrency,
approveGas, approveGas,
memoizedTokenConversionRates, memoizedTokenConversionRates,
chainId,
]); ]);
const renderableDataForUsedQuote = renderablePopoverData.find( const renderableDataForUsedQuote = renderablePopoverData.find(
@ -209,31 +213,33 @@ export default function ViewQuote() {
sourceTokenIconUrl, sourceTokenIconUrl,
} = renderableDataForUsedQuote; } = renderableDataForUsedQuote;
const { feeInFiat, feeInEth } = getRenderableNetworkFeesForQuote( const { feeInFiat, feeInEth } = getRenderableNetworkFeesForQuote({
usedGasLimit, tradeGas: usedGasLimit,
approveGas, approveGas,
gasPrice, gasPrice,
currentCurrency, currentCurrency,
conversionRate, conversionRate,
tradeValue, tradeValue,
sourceTokenSymbol, sourceSymbol: sourceTokenSymbol,
usedQuote.sourceAmount, sourceAmount: usedQuote.sourceAmount,
); chainId,
});
const { const {
feeInFiat: maxFeeInFiat, feeInFiat: maxFeeInFiat,
feeInEth: maxFeeInEth, feeInEth: maxFeeInEth,
nonGasFee, nonGasFee,
} = getRenderableNetworkFeesForQuote( } = getRenderableNetworkFeesForQuote({
maxGasLimit, tradeGas: maxGasLimit,
approveGas, approveGas,
gasPrice, gasPrice,
currentCurrency, currentCurrency,
conversionRate, conversionRate,
tradeValue, tradeValue,
sourceTokenSymbol, sourceSymbol: sourceTokenSymbol,
usedQuote.sourceAmount, sourceAmount: usedQuote.sourceAmount,
); chainId,
});
const tokenCost = new BigNumber(usedQuote.sourceAmount); const tokenCost = new BigNumber(usedQuote.sourceAmount);
const ethCost = new BigNumber(usedQuote.trade.value || 0, 10).plus( const ethCost = new BigNumber(usedQuote.trade.value || 0, 10).plus(
@ -481,9 +487,9 @@ export default function ViewQuote() {
<span key="swapApproveNeedMoreTokens-1" className="view-quote__bold"> <span key="swapApproveNeedMoreTokens-1" className="view-quote__bold">
{tokenBalanceNeeded || ethBalanceNeeded} {tokenBalanceNeeded || ethBalanceNeeded}
</span>, </span>,
tokenBalanceNeeded && !(sourceTokenSymbol === 'ETH') tokenBalanceNeeded && !(sourceTokenSymbol === defaultSwapsToken.symbol)
? sourceTokenSymbol ? sourceTokenSymbol
: 'ETH', : defaultSwapsToken.symbol,
]); ]);
// Price difference warning // Price difference warning
@ -643,7 +649,7 @@ export default function ViewQuote() {
setSelectQuotePopoverShown(true); setSelectQuotePopoverShown(true);
}} }}
tokenConversionRate={ tokenConversionRate={
destinationTokenSymbol === 'ETH' destinationTokenSymbol === defaultSwapsToken.symbol
? 1 ? 1
: memoizedTokenConversionRates[destinationToken.address] : memoizedTokenConversionRates[destinationToken.address]
} }
@ -655,7 +661,7 @@ export default function ViewQuote() {
setSubmitClicked(true); setSubmitClicked(true);
if (!balanceError) { if (!balanceError) {
dispatch(signAndSendTransactions(history, metaMetricsEvent)); dispatch(signAndSendTransactions(history, metaMetricsEvent));
} else if (destinationToken.symbol === 'ETH') { } else if (destinationToken.symbol === defaultSwapsToken.symbol) {
history.push(DEFAULT_ROUTE); history.push(DEFAULT_ROUTE);
} else { } else {
history.push(`${ASSET_ROUTE}/${destinationToken.address}`); history.push(`${ASSET_ROUTE}/${destinationToken.address}`);

View File

@ -15,7 +15,10 @@ import {
getValueFromWeiHex, getValueFromWeiHex,
hexToDecimal, hexToDecimal,
} from '../helpers/utils/conversions.util'; } from '../helpers/utils/conversions.util';
import { ETH_SWAPS_TOKEN_OBJECT } from '../../../shared/constants/swaps'; import {
SWAPS_CHAINID_DEFAULT_TOKEN_MAP,
ALLOWED_SWAPS_CHAIN_IDS,
} from '../../../shared/constants/swaps';
/** /**
* One of the only remaining valid uses of selecting the network subkey of the * One of the only remaining valid uses of selecting the network subkey of the
@ -431,22 +434,26 @@ export function getWeb3ShimUsageStateForOrigin(state, origin) {
* minimal token units (according to its decimals). * minimal token units (according to its decimals).
* `string` is the token balance in a readable format, ready for rendering. * `string` is the token balance in a readable format, ready for rendering.
* *
* Swaps treats ETH as a token, and we use the ETH_SWAPS_TOKEN_OBJECT constant * Swaps treats the selected chain's currency as a token, and we use the token constants
* to set the standard properties for the token. The getSwapsEthToken selector * in the SWAPS_CHAINID_DEFAULT_TOKEN_MAP to set the standard properties for
* extends that object with `balance` and `balance` values of the same type as * the token. The getSwapsDefaultToken selector extends that object with
* in regular ERC-20 token objects, per the above description. * `balance` and `string` values of the same type as in regular ERC-20 token
* objects, per the above description.
* *
* @param {object} state - the redux state object * @param {object} state - the redux state object
* @returns {SwapsEthToken} The token object representation of the currently * @returns {SwapsEthToken} The token object representation of the currently
* selected account's ETH balance, as expected by the Swaps API. * selected account's ETH balance, as expected by the Swaps API.
*/ */
export function getSwapsEthToken(state) { export function getSwapsDefaultToken(state) {
const selectedAccount = getSelectedAccount(state); const selectedAccount = getSelectedAccount(state);
const { balance } = selectedAccount; const { balance } = selectedAccount;
const chainId = getCurrentChainId(state);
const defaultTokenObject = SWAPS_CHAINID_DEFAULT_TOKEN_MAP[chainId];
return { return {
...ETH_SWAPS_TOKEN_OBJECT, ...defaultTokenObject,
balance: hexToDecimal(balance), balance: hexToDecimal(balance),
string: getValueFromWeiHex({ string: getValueFromWeiHex({
value: balance, value: balance,
@ -455,3 +462,8 @@ export function getSwapsEthToken(state) {
}), }),
}; };
} }
export function getIsSwapsChain(state) {
const chainId = getCurrentChainId(state);
return ALLOWED_SWAPS_CHAIN_IDS[chainId];
}