mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-22 09:23:21 +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:
parent
1c573ef852
commit
480512d14f
@ -8,12 +8,13 @@ import { calcTokenAmount } from '../../../ui/app/helpers/utils/token-util';
|
||||
import { calcGasTotal } from '../../../ui/app/pages/send/send.utils';
|
||||
import { conversionUtil } from '../../../ui/app/helpers/utils/conversion-util';
|
||||
import {
|
||||
ETH_SWAPS_TOKEN_OBJECT,
|
||||
DEFAULT_ERC20_APPROVE_GAS,
|
||||
QUOTES_EXPIRED_ERROR,
|
||||
QUOTES_NOT_AVAILABLE_ERROR,
|
||||
SWAPS_FETCH_ORDER_CONFLICT,
|
||||
} from '../../../shared/constants/swaps';
|
||||
import { isSwapsDefaultTokenAddress } from '../../../shared/modules/swaps.utils';
|
||||
|
||||
import {
|
||||
fetchTradesInfo as defaultFetchTradesInfo,
|
||||
fetchSwapsFeatureLiveness as defaultFetchSwapsFeatureLiveness,
|
||||
@ -85,6 +86,7 @@ export default class SwapsController {
|
||||
fetchTradesInfo = defaultFetchTradesInfo,
|
||||
fetchSwapsFeatureLiveness = defaultFetchSwapsFeatureLiveness,
|
||||
fetchSwapsQuoteRefreshTime = defaultFetchSwapsQuoteRefreshTime,
|
||||
getCurrentChainId,
|
||||
}) {
|
||||
this.store = new ObservableStore({
|
||||
swapsState: { ...initialState.swapsState },
|
||||
@ -93,6 +95,7 @@ export default class SwapsController {
|
||||
this._fetchTradesInfo = fetchTradesInfo;
|
||||
this._fetchSwapsFeatureLiveness = fetchSwapsFeatureLiveness;
|
||||
this._fetchSwapsQuoteRefreshTime = fetchSwapsQuoteRefreshTime;
|
||||
this._getCurrentChainId = getCurrentChainId;
|
||||
|
||||
this.getBufferedGasLimit = getBufferedGasLimit;
|
||||
this.tokenRatesStore = tokenRatesStore;
|
||||
@ -116,10 +119,11 @@ export default class SwapsController {
|
||||
|
||||
// Sets the refresh rate for quote updates from the MetaSwap API
|
||||
async _setSwapsQuoteRefreshTime() {
|
||||
const chainId = this._getCurrentChainId();
|
||||
// Default to fallback time unless API returns valid response
|
||||
let swapsQuoteRefreshTime = FALLBACK_QUOTE_REFRESH_TIME;
|
||||
try {
|
||||
swapsQuoteRefreshTime = await this._fetchSwapsQuoteRefreshTime();
|
||||
swapsQuoteRefreshTime = await this._fetchSwapsQuoteRefreshTime(chainId);
|
||||
} catch (e) {
|
||||
console.error('Request for swaps quote refresh time failed: ', e);
|
||||
}
|
||||
@ -158,6 +162,8 @@ export default class SwapsController {
|
||||
fetchParamsMetaData = {},
|
||||
isPolledRequest,
|
||||
) {
|
||||
const { chainId } = fetchParamsMetaData;
|
||||
|
||||
if (!fetchParams) {
|
||||
return null;
|
||||
}
|
||||
@ -177,7 +183,7 @@ export default class SwapsController {
|
||||
this.indexOfNewestCallInFlight = indexOfCurrentCall;
|
||||
|
||||
let [newQuotes] = await Promise.all([
|
||||
this._fetchTradesInfo(fetchParams),
|
||||
this._fetchTradesInfo(fetchParams, fetchParamsMetaData),
|
||||
this._setSwapsQuoteRefreshTime(),
|
||||
]);
|
||||
|
||||
@ -191,7 +197,7 @@ export default class SwapsController {
|
||||
|
||||
let approvalRequired = false;
|
||||
if (
|
||||
fetchParams.sourceToken !== ETH_SWAPS_TOKEN_OBJECT.address &&
|
||||
!isSwapsDefaultTokenAddress(fetchParams.sourceToken, chainId) &&
|
||||
Object.values(newQuotes).length
|
||||
) {
|
||||
const allowance = await this._getERC20Allowance(
|
||||
@ -490,6 +496,7 @@ export default class SwapsController {
|
||||
const {
|
||||
swapsState: { customGasPrice },
|
||||
} = this.store.getState();
|
||||
const chainId = this._getCurrentChainId();
|
||||
|
||||
const numQuotes = Object.keys(quotes).length;
|
||||
if (!numQuotes) {
|
||||
@ -533,8 +540,8 @@ export default class SwapsController {
|
||||
|
||||
// trade.value is a sum of different values depending on the transaction.
|
||||
// It always includes any external fees charged by the quote source. In
|
||||
// addition, if the source asset is ETH, trade.value includes the amount
|
||||
// of swapped ETH.
|
||||
// addition, if the source asset is the selected chain's default token, trade.value
|
||||
// includes the amount of that token.
|
||||
const totalWeiCost = new BigNumber(gasTotalInWeiHex, 16).plus(
|
||||
trade.value,
|
||||
16,
|
||||
@ -549,21 +556,21 @@ export default class SwapsController {
|
||||
});
|
||||
|
||||
// The total fee is aggregator/exchange fees plus gas fees.
|
||||
// If the swap is from ETH, subtract the sourceAmount from the total cost.
|
||||
// Otherwise, the total fee is simply trade.value plus gas fees.
|
||||
const ethFee =
|
||||
sourceToken === ETH_SWAPS_TOKEN_OBJECT.address
|
||||
? conversionUtil(
|
||||
totalWeiCost.minus(sourceAmount, 10), // sourceAmount is in wei
|
||||
{
|
||||
fromCurrency: 'ETH',
|
||||
fromDenomination: 'WEI',
|
||||
toDenomination: 'ETH',
|
||||
fromNumericBase: 'BN',
|
||||
numberOfDecimals: 6,
|
||||
},
|
||||
)
|
||||
: totalEthCost;
|
||||
// If the swap is from the selected chain's default token, subtract
|
||||
// the sourceAmount from the total cost. Otherwise, the total fee
|
||||
// is simply trade.value plus gas fees.
|
||||
const ethFee = isSwapsDefaultTokenAddress(sourceToken, chainId)
|
||||
? conversionUtil(
|
||||
totalWeiCost.minus(sourceAmount, 10), // sourceAmount is in wei
|
||||
{
|
||||
fromCurrency: 'ETH',
|
||||
fromDenomination: 'WEI',
|
||||
toDenomination: 'ETH',
|
||||
fromNumericBase: 'BN',
|
||||
numberOfDecimals: 6,
|
||||
},
|
||||
)
|
||||
: totalEthCost;
|
||||
|
||||
const decimalAdjustedDestinationAmount = calcTokenAmount(
|
||||
destinationAmount,
|
||||
@ -588,10 +595,12 @@ export default class SwapsController {
|
||||
10,
|
||||
);
|
||||
|
||||
const conversionRateForCalculations =
|
||||
destinationToken === ETH_SWAPS_TOKEN_OBJECT.address
|
||||
? 1
|
||||
: tokenConversionRate;
|
||||
const conversionRateForCalculations = isSwapsDefaultTokenAddress(
|
||||
destinationToken,
|
||||
chainId,
|
||||
)
|
||||
? 1
|
||||
: tokenConversionRate;
|
||||
|
||||
const overallValueOfQuoteForSorting =
|
||||
conversionRateForCalculations === undefined
|
||||
@ -618,8 +627,10 @@ export default class SwapsController {
|
||||
});
|
||||
|
||||
const isBest =
|
||||
newQuotes[topAggId].destinationToken === ETH_SWAPS_TOKEN_OBJECT.address ||
|
||||
Boolean(tokenConversionRates[newQuotes[topAggId]?.destinationToken]);
|
||||
isSwapsDefaultTokenAddress(
|
||||
newQuotes[topAggId].destinationToken,
|
||||
chainId,
|
||||
) || Boolean(tokenConversionRates[newQuotes[topAggId]?.destinationToken]);
|
||||
|
||||
let savings = null;
|
||||
|
||||
@ -726,13 +737,17 @@ export default class SwapsController {
|
||||
async _fetchAndSetSwapsLiveness() {
|
||||
const { swapsState } = this.store.getState();
|
||||
const { swapsFeatureIsLive: oldSwapsFeatureIsLive } = swapsState;
|
||||
const chainId = this._getCurrentChainId();
|
||||
|
||||
let swapsFeatureIsLive = false;
|
||||
let successfullyFetched = false;
|
||||
let numAttempts = 0;
|
||||
|
||||
const fetchAndIncrementNumAttempts = async () => {
|
||||
try {
|
||||
swapsFeatureIsLive = Boolean(await this._fetchSwapsFeatureLiveness());
|
||||
swapsFeatureIsLive = Boolean(
|
||||
await this._fetchSwapsFeatureLiveness(chainId),
|
||||
);
|
||||
successfullyFetched = true;
|
||||
} catch (err) {
|
||||
log.error(err);
|
||||
|
@ -8,6 +8,7 @@ import { ObservableStore } from '@metamask/obs-store';
|
||||
import {
|
||||
ROPSTEN_NETWORK_ID,
|
||||
MAINNET_NETWORK_ID,
|
||||
MAINNET_CHAIN_ID,
|
||||
} from '../../../shared/constants/network';
|
||||
import { ETH_SWAPS_TOKEN_OBJECT } from '../../../shared/constants/swaps';
|
||||
import { createTestProviderTools } from '../../../test/stub/provider';
|
||||
@ -75,6 +76,7 @@ const MOCK_FETCH_METADATA = {
|
||||
symbol: 'FOO',
|
||||
decimals: 18,
|
||||
},
|
||||
chainId: MAINNET_CHAIN_ID,
|
||||
};
|
||||
|
||||
const MOCK_TOKEN_RATES_STORE = new ObservableStore({
|
||||
@ -131,6 +133,8 @@ const sandbox = sinon.createSandbox();
|
||||
const fetchTradesInfoStub = sandbox.stub();
|
||||
const fetchSwapsFeatureLivenessStub = sandbox.stub();
|
||||
const fetchSwapsQuoteRefreshTimeStub = sandbox.stub();
|
||||
const getCurrentChainIdStub = sandbox.stub();
|
||||
getCurrentChainIdStub.returns(MAINNET_CHAIN_ID);
|
||||
|
||||
describe('SwapsController', function () {
|
||||
let provider;
|
||||
@ -145,6 +149,7 @@ describe('SwapsController', function () {
|
||||
fetchTradesInfo: fetchTradesInfoStub,
|
||||
fetchSwapsFeatureLiveness: fetchSwapsFeatureLivenessStub,
|
||||
fetchSwapsQuoteRefreshTime: fetchSwapsQuoteRefreshTimeStub,
|
||||
getCurrentChainId: getCurrentChainIdStub,
|
||||
});
|
||||
};
|
||||
|
||||
@ -194,6 +199,7 @@ describe('SwapsController', function () {
|
||||
tokenRatesStore: MOCK_TOKEN_RATES_STORE,
|
||||
fetchTradesInfo: fetchTradesInfoStub,
|
||||
fetchSwapsFeatureLiveness: fetchSwapsFeatureLivenessStub,
|
||||
getCurrentChainId: getCurrentChainIdStub,
|
||||
});
|
||||
const currentEthersInstance = swapsController.ethersProvider;
|
||||
const onNetworkDidChange = networkController.on.getCall(0).args[1];
|
||||
@ -218,6 +224,7 @@ describe('SwapsController', function () {
|
||||
tokenRatesStore: MOCK_TOKEN_RATES_STORE,
|
||||
fetchTradesInfo: fetchTradesInfoStub,
|
||||
fetchSwapsFeatureLiveness: fetchSwapsFeatureLivenessStub,
|
||||
getCurrentChainId: getCurrentChainIdStub,
|
||||
});
|
||||
const currentEthersInstance = swapsController.ethersProvider;
|
||||
const onNetworkDidChange = networkController.on.getCall(0).args[1];
|
||||
@ -242,6 +249,7 @@ describe('SwapsController', function () {
|
||||
tokenRatesStore: MOCK_TOKEN_RATES_STORE,
|
||||
fetchTradesInfo: fetchTradesInfoStub,
|
||||
fetchSwapsFeatureLiveness: fetchSwapsFeatureLivenessStub,
|
||||
getCurrentChainId: getCurrentChainIdStub,
|
||||
});
|
||||
const currentEthersInstance = swapsController.ethersProvider;
|
||||
const onNetworkDidChange = networkController.on.getCall(0).args[1];
|
||||
@ -686,7 +694,10 @@ describe('SwapsController', function () {
|
||||
});
|
||||
|
||||
assert.strictEqual(
|
||||
fetchTradesInfoStub.calledOnceWithExactly(MOCK_FETCH_PARAMS),
|
||||
fetchTradesInfoStub.calledOnceWithExactly(
|
||||
MOCK_FETCH_PARAMS,
|
||||
MOCK_FETCH_METADATA,
|
||||
),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
@ -958,6 +958,7 @@ export default class TransactionController extends EventEmitter {
|
||||
txMeta.txParams.from,
|
||||
txMeta.destinationTokenDecimals,
|
||||
approvalTxMeta,
|
||||
txMeta.chainId,
|
||||
);
|
||||
|
||||
const quoteVsExecutionRatio = `${new BigNumber(tokensReceived, 10)
|
||||
|
@ -383,6 +383,9 @@ export default class MetamaskController extends EventEmitter {
|
||||
this.networkController,
|
||||
),
|
||||
tokenRatesStore: this.tokenRatesController.store,
|
||||
getCurrentChainId: this.networkController.getCurrentChainId.bind(
|
||||
this.networkController,
|
||||
),
|
||||
});
|
||||
|
||||
// ensure accountTracker updates balances after network change
|
||||
|
@ -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
|
||||
const ETH_SWAPS_TOKEN_ADDRESS = '0x0000000000000000000000000000000000000000';
|
||||
|
||||
@ -9,17 +18,42 @@ export const ETH_SWAPS_TOKEN_OBJECT = {
|
||||
iconUrl: 'images/black-eth-logo.svg',
|
||||
};
|
||||
|
||||
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';
|
||||
const TEST_ETH_SWAPS_TOKEN_OBJECT = {
|
||||
symbol: 'TESTETH',
|
||||
name: 'Test Ether',
|
||||
address: ETH_SWAPS_TOKEN_ADDRESS,
|
||||
decimals: 18,
|
||||
iconUrl: 'images/black-eth-logo.svg',
|
||||
};
|
||||
|
||||
// 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 SWAPS_CONTRACT_ADDRESS =
|
||||
'0x881d40237659c251811cec9c364ef91dc08d300c';
|
||||
const MAINNET_CONTRACT_ADDRESS = '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,
|
||||
};
|
||||
|
33
shared/modules/swaps.utils.js
Normal file
33
shared/modules/swaps.utils.js
Normal 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;
|
||||
}
|
@ -5,20 +5,31 @@ import {
|
||||
nonceSortedCompletedTransactionsSelector,
|
||||
nonceSortedPendingTransactionsSelector,
|
||||
} from '../../../selectors/transactions';
|
||||
import { getCurrentChainId } from '../../../selectors';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
import TransactionListItem from '../transaction-list-item';
|
||||
import Button from '../../ui/button';
|
||||
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';
|
||||
|
||||
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 (
|
||||
txParams?.to === recipientAddress ||
|
||||
(txParams?.to === SWAPS_CONTRACT_ADDRESS &&
|
||||
(txParams?.to === SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[chainId] &&
|
||||
txParams.data.match(recipientAddress.slice(2)))
|
||||
);
|
||||
};
|
||||
@ -39,12 +50,13 @@ const getFilteredTransactionGroups = (
|
||||
transactionGroups,
|
||||
hideTokenTransactions,
|
||||
tokenAddress,
|
||||
chainId,
|
||||
) => {
|
||||
if (hideTokenTransactions) {
|
||||
return transactionGroups.filter(tokenTransactionFilter);
|
||||
} else if (tokenAddress) {
|
||||
return transactionGroups.filter(
|
||||
getTransactionGroupRecipientAddressFilter(tokenAddress),
|
||||
getTransactionGroupRecipientAddressFilter(tokenAddress, chainId),
|
||||
);
|
||||
}
|
||||
return transactionGroups;
|
||||
@ -63,6 +75,7 @@ export default function TransactionList({
|
||||
const unfilteredCompletedTransactions = useSelector(
|
||||
nonceSortedCompletedTransactionsSelector,
|
||||
);
|
||||
const chainId = useSelector(getCurrentChainId);
|
||||
|
||||
const pendingTransactions = useMemo(
|
||||
() =>
|
||||
@ -70,8 +83,14 @@ export default function TransactionList({
|
||||
unfilteredPendingTransactions,
|
||||
hideTokenTransactions,
|
||||
tokenAddress,
|
||||
chainId,
|
||||
),
|
||||
[hideTokenTransactions, tokenAddress, unfilteredPendingTransactions],
|
||||
[
|
||||
hideTokenTransactions,
|
||||
tokenAddress,
|
||||
unfilteredPendingTransactions,
|
||||
chainId,
|
||||
],
|
||||
);
|
||||
const completedTransactions = useMemo(
|
||||
() =>
|
||||
@ -79,8 +98,14 @@ export default function TransactionList({
|
||||
unfilteredCompletedTransactions,
|
||||
hideTokenTransactions,
|
||||
tokenAddress,
|
||||
chainId,
|
||||
),
|
||||
[hideTokenTransactions, tokenAddress, unfilteredCompletedTransactions],
|
||||
[
|
||||
hideTokenTransactions,
|
||||
tokenAddress,
|
||||
unfilteredCompletedTransactions,
|
||||
chainId,
|
||||
],
|
||||
);
|
||||
|
||||
const viewMore = useCallback(
|
||||
|
@ -25,7 +25,8 @@ import {
|
||||
getIsMainnet,
|
||||
getIsTestnet,
|
||||
getCurrentKeyring,
|
||||
getSwapsEthToken,
|
||||
getSwapsDefaultToken,
|
||||
getIsSwapsChain,
|
||||
} from '../../../selectors/selectors';
|
||||
import SwapIcon from '../../ui/icon/swap-icon.component';
|
||||
import BuyIcon from '../../ui/icon/overview-buy-icon.component';
|
||||
@ -63,13 +64,14 @@ const EthOverview = ({ className }) => {
|
||||
const { balance } = selectedAccount;
|
||||
const isMainnetChain = useSelector(getIsMainnet);
|
||||
const isTestnetChain = useSelector(getIsTestnet);
|
||||
const isSwapsChain = useSelector(getIsSwapsChain);
|
||||
const enteredSwapsEvent = useNewMetricEvent({
|
||||
event: 'Swaps Opened',
|
||||
properties: { source: 'Main View', active_currency: 'ETH' },
|
||||
category: 'swaps',
|
||||
});
|
||||
const swapsEnabled = useSelector(getSwapsFeatureLiveness);
|
||||
const swapsEthToken = useSelector(getSwapsEthToken);
|
||||
const defaultSwapsToken = useSelector(getSwapsDefaultToken);
|
||||
|
||||
return (
|
||||
<WalletOverview
|
||||
@ -136,12 +138,12 @@ const EthOverview = ({ className }) => {
|
||||
{swapsEnabled ? (
|
||||
<IconButton
|
||||
className="eth-overview__button"
|
||||
disabled={!isMainnetChain}
|
||||
disabled={!isSwapsChain}
|
||||
Icon={SwapIcon}
|
||||
onClick={() => {
|
||||
if (isMainnetChain) {
|
||||
if (isSwapsChain) {
|
||||
enteredSwapsEvent();
|
||||
dispatch(setSwapsFromToken(swapsEthToken));
|
||||
dispatch(setSwapsFromToken(defaultSwapsToken));
|
||||
if (usingHardwareWallet) {
|
||||
global.platform.openExtensionInBrowser(BUILD_QUOTE_ROUTE);
|
||||
} else {
|
||||
@ -154,7 +156,7 @@ const EthOverview = ({ className }) => {
|
||||
<Tooltip
|
||||
title={t('onlyAvailableOnMainnet')}
|
||||
position="bottom"
|
||||
disabled={isMainnetChain}
|
||||
disabled={isSwapsChain}
|
||||
>
|
||||
{contents}
|
||||
</Tooltip>
|
||||
|
@ -25,9 +25,8 @@ import {
|
||||
import {
|
||||
getAssetImages,
|
||||
getCurrentKeyring,
|
||||
getCurrentChainId,
|
||||
getIsSwapsChain,
|
||||
} from '../../../selectors/selectors';
|
||||
import { MAINNET_CHAIN_ID } from '../../../../../shared/constants/network';
|
||||
|
||||
import SwapIcon from '../../ui/icon/swap-icon.component';
|
||||
import SendIcon from '../../ui/icon/overview-send-icon.component';
|
||||
@ -58,7 +57,7 @@ const TokenOverview = ({ className, token }) => {
|
||||
balanceToRender,
|
||||
token.symbol,
|
||||
);
|
||||
const chainId = useSelector(getCurrentChainId);
|
||||
const isSwapsChain = useSelector(getIsSwapsChain);
|
||||
const enteredSwapsEvent = useNewMetricEvent({
|
||||
event: 'Swaps Opened',
|
||||
properties: { source: 'Token View', active_currency: token.symbol },
|
||||
@ -100,10 +99,10 @@ const TokenOverview = ({ className, token }) => {
|
||||
{swapsEnabled ? (
|
||||
<IconButton
|
||||
className="token-overview__button"
|
||||
disabled={chainId !== MAINNET_CHAIN_ID}
|
||||
disabled={!isSwapsChain}
|
||||
Icon={SwapIcon}
|
||||
onClick={() => {
|
||||
if (chainId === MAINNET_CHAIN_ID) {
|
||||
if (isSwapsChain) {
|
||||
enteredSwapsEvent();
|
||||
dispatch(
|
||||
setSwapsFromToken({
|
||||
@ -125,7 +124,7 @@ const TokenOverview = ({ className, token }) => {
|
||||
<Tooltip
|
||||
title={t('onlyAvailableOnMainnet')}
|
||||
position="bottom"
|
||||
disabled={chainId === MAINNET_CHAIN_ID}
|
||||
disabled={isSwapsChain}
|
||||
>
|
||||
{contents}
|
||||
</Tooltip>
|
||||
|
@ -49,7 +49,8 @@ import {
|
||||
getSelectedAccount,
|
||||
getTokenExchangeRates,
|
||||
getUSDConversionRate,
|
||||
getSwapsEthToken,
|
||||
getSwapsDefaultToken,
|
||||
getCurrentChainId,
|
||||
} from '../../selectors';
|
||||
import {
|
||||
ERROR_FETCHING_QUOTES,
|
||||
@ -376,9 +377,11 @@ export const fetchQuotesAndSetQuoteState = (
|
||||
metaMetricsEvent,
|
||||
) => {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const chainId = getCurrentChainId(state);
|
||||
let swapsFeatureIsLive = false;
|
||||
try {
|
||||
swapsFeatureIsLive = await fetchSwapsFeatureLiveness();
|
||||
swapsFeatureIsLive = await fetchSwapsFeatureLiveness(chainId);
|
||||
} catch (error) {
|
||||
log.error('Failed to fetch Swaps liveness, defaulting to false.', error);
|
||||
}
|
||||
@ -389,13 +392,14 @@ export const fetchQuotesAndSetQuoteState = (
|
||||
return;
|
||||
}
|
||||
|
||||
const state = getState();
|
||||
const fetchParams = getFetchParams(state);
|
||||
const selectedAccount = getSelectedAccount(state);
|
||||
const balanceError = getBalanceError(state);
|
||||
const swapsDefaultToken = getSwapsDefaultToken(state);
|
||||
const fetchParamsFromToken =
|
||||
fetchParams?.metaData?.sourceTokenInfo?.symbol === 'ETH'
|
||||
? getSwapsEthToken(state)
|
||||
fetchParams?.metaData?.sourceTokenInfo?.symbol ===
|
||||
swapsDefaultToken.symbol
|
||||
? swapsDefaultToken
|
||||
: fetchParams?.metaData?.sourceTokenInfo;
|
||||
const selectedFromToken = getFromToken(state) || fetchParamsFromToken || {};
|
||||
const selectedToToken =
|
||||
@ -420,7 +424,10 @@ export const fetchQuotesAndSetQuoteState = (
|
||||
const contractExchangeRates = getTokenExchangeRates(state);
|
||||
|
||||
let destinationTokenAddedForSwap = false;
|
||||
if (toTokenSymbol !== 'ETH' && !contractExchangeRates[toTokenAddress]) {
|
||||
if (
|
||||
toTokenSymbol !== swapsDefaultToken.symbol &&
|
||||
!contractExchangeRates[toTokenAddress]
|
||||
) {
|
||||
destinationTokenAddedForSwap = true;
|
||||
await dispatch(
|
||||
addToken(
|
||||
@ -433,7 +440,7 @@ export const fetchQuotesAndSetQuoteState = (
|
||||
);
|
||||
}
|
||||
if (
|
||||
fromTokenSymbol !== 'ETH' &&
|
||||
fromTokenSymbol !== swapsDefaultToken.symbol &&
|
||||
!contractExchangeRates[fromTokenAddress] &&
|
||||
fromTokenBalance &&
|
||||
new BigNumber(fromTokenBalance, 16).gt(0)
|
||||
@ -494,6 +501,7 @@ export const fetchQuotesAndSetQuoteState = (
|
||||
sourceTokenInfo,
|
||||
destinationTokenInfo,
|
||||
accountBalance: selectedAccount.balance,
|
||||
chainId,
|
||||
},
|
||||
),
|
||||
);
|
||||
@ -563,9 +571,12 @@ export const fetchQuotesAndSetQuoteState = (
|
||||
|
||||
export const signAndSendTransactions = (history, metaMetricsEvent) => {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const chainId = getCurrentChainId(state);
|
||||
|
||||
let swapsFeatureIsLive = false;
|
||||
try {
|
||||
swapsFeatureIsLive = await fetchSwapsFeatureLiveness();
|
||||
swapsFeatureIsLive = await fetchSwapsFeatureLiveness(chainId);
|
||||
} catch (error) {
|
||||
log.error('Failed to fetch Swaps liveness, defaulting to false.', error);
|
||||
}
|
||||
@ -576,7 +587,6 @@ export const signAndSendTransactions = (history, metaMetricsEvent) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const state = getState();
|
||||
const customSwapsGas = getCustomSwapsGas(state);
|
||||
const fetchParams = getFetchParams(state);
|
||||
const { metaData, value: swapTokenValue, slippage } = fetchParams;
|
||||
|
@ -1,13 +1,18 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useRouteMatch } from 'react-router-dom';
|
||||
import { getTokens } from '../ducks/metamask/metamask';
|
||||
import { getCurrentChainId } from '../selectors';
|
||||
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.
|
||||
* Will return the ETH_SWAPS_TOKEN_OBJECT when the user is viewing either
|
||||
* the primary, unfiltered, activity list or the ETH asset page.
|
||||
* Will return the default token object for the current chain when the
|
||||
* user is viewing either the primary, unfiltered, activity list or the
|
||||
* default token asset page.
|
||||
* @returns {import('./useTokenDisplayValue').Token}
|
||||
*/
|
||||
export function useCurrentAsset() {
|
||||
@ -22,6 +27,10 @@ export function useCurrentAsset() {
|
||||
const knownTokens = useSelector(getTokens);
|
||||
const token =
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,11 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
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 { getCurrentChainId } from '../selectors';
|
||||
import { useTokenFiatAmount } from './useTokenFiatAmount';
|
||||
|
||||
/**
|
||||
@ -14,10 +19,11 @@ import { useTokenFiatAmount } from './useTokenFiatAmount';
|
||||
/**
|
||||
* A Swap transaction group's primaryTransaction contains details of the swap,
|
||||
* 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
|
||||
* token that was received (destination) from the swap. In that circumstance we
|
||||
* would want to show the primaryCurrency in the activity list that is most relevant
|
||||
* for that token (- 1000 DAI, for example, when swapping DAI for ETH).
|
||||
* When viewing an asset page that is not for the current chain's default token, we
|
||||
* need to determine if that asset is the token that was received (destination) from
|
||||
* the swap. In that circumstance we would want to show the primaryCurrency in the
|
||||
* 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('./useTokenDisplayValue').Token} currentAsset - The current asset the user is looking at
|
||||
* @returns {SwappedTokenValue}
|
||||
@ -27,11 +33,15 @@ export function useSwappedTokenValue(transactionGroup, currentAsset) {
|
||||
const { primaryTransaction, initialTransaction } = transactionGroup;
|
||||
const { type } = initialTransaction;
|
||||
const { from: senderAddress } = initialTransaction.txParams || {};
|
||||
const chainId = useSelector(getCurrentChainId);
|
||||
|
||||
const isViewingReceivedTokenFromSwap =
|
||||
currentAsset?.symbol === primaryTransaction.destinationTokenSymbol ||
|
||||
(currentAsset.address === ETH_SWAPS_TOKEN_OBJECT.address &&
|
||||
primaryTransaction.destinationTokenSymbol === 'ETH');
|
||||
(isSwapsDefaultTokenAddress(currentAsset.address, chainId) &&
|
||||
isSwapsDefaultTokenSymbol(
|
||||
primaryTransaction.destinationTokenSymbol,
|
||||
chainId,
|
||||
));
|
||||
|
||||
const swapTokenValue =
|
||||
type === TRANSACTION_TYPES.SWAP && isViewingReceivedTokenFromSwap
|
||||
@ -41,6 +51,8 @@ export function useSwappedTokenValue(transactionGroup, currentAsset) {
|
||||
address,
|
||||
senderAddress,
|
||||
decimals,
|
||||
null,
|
||||
chainId,
|
||||
)
|
||||
: type === TRANSACTION_TYPES.SWAP && primaryTransaction.swapTokenValue;
|
||||
|
||||
|
@ -9,9 +9,11 @@ import {
|
||||
getTokenExchangeRates,
|
||||
getConversionRate,
|
||||
getCurrentCurrency,
|
||||
getSwapsEthToken,
|
||||
getSwapsDefaultToken,
|
||||
getCurrentChainId,
|
||||
} from '../selectors';
|
||||
import { getSwapsTokens } from '../ducks/swaps/swaps';
|
||||
import { isSwapsDefaultTokenSymbol } from '../../../shared/modules/swaps.utils';
|
||||
import { useEqualityCheck } from './useEqualityCheck';
|
||||
|
||||
const tokenList = shuffle(
|
||||
@ -28,12 +30,15 @@ export function getRenderableTokenData(
|
||||
contractExchangeRates,
|
||||
conversionRate,
|
||||
currentCurrency,
|
||||
chainId,
|
||||
) {
|
||||
const { symbol, name, address, iconUrl, string, balance, decimals } = token;
|
||||
|
||||
const formattedFiat =
|
||||
getTokenFiatAmount(
|
||||
symbol === 'ETH' ? 1 : contractExchangeRates[address],
|
||||
isSwapsDefaultTokenSymbol(symbol, chainId)
|
||||
? 1
|
||||
: contractExchangeRates[address],
|
||||
conversionRate,
|
||||
currentCurrency,
|
||||
string,
|
||||
@ -42,7 +47,9 @@ export function getRenderableTokenData(
|
||||
) || '';
|
||||
const rawFiat =
|
||||
getTokenFiatAmount(
|
||||
symbol === 'ETH' ? 1 : contractExchangeRates[address],
|
||||
isSwapsDefaultTokenSymbol(symbol, chainId)
|
||||
? 1
|
||||
: contractExchangeRates[address],
|
||||
conversionRate,
|
||||
currentCurrency,
|
||||
string,
|
||||
@ -70,30 +77,32 @@ export function getRenderableTokenData(
|
||||
}
|
||||
|
||||
export function useTokensToSearch({ usersTokens = [], topTokens = {} }) {
|
||||
const chainId = useSelector(getCurrentChainId);
|
||||
const tokenConversionRates = useSelector(getTokenExchangeRates, isEqual);
|
||||
const conversionRate = useSelector(getConversionRate);
|
||||
const currentCurrency = useSelector(getCurrentCurrency);
|
||||
const swapsEthToken = useSelector(getSwapsEthToken);
|
||||
const defaultSwapsToken = useSelector(getSwapsDefaultToken);
|
||||
|
||||
const memoizedTopTokens = useEqualityCheck(topTokens);
|
||||
const memoizedUsersToken = useEqualityCheck(usersTokens);
|
||||
|
||||
const ethToken = getRenderableTokenData(
|
||||
swapsEthToken,
|
||||
const defaultToken = getRenderableTokenData(
|
||||
defaultSwapsToken,
|
||||
tokenConversionRates,
|
||||
conversionRate,
|
||||
currentCurrency,
|
||||
chainId,
|
||||
);
|
||||
const memoizedEthToken = useEqualityCheck(ethToken);
|
||||
const memoizedDefaultToken = useEqualityCheck(defaultToken);
|
||||
|
||||
const swapsTokens = useSelector(getSwapsTokens) || [];
|
||||
|
||||
const tokensToSearch = swapsTokens.length
|
||||
? swapsTokens
|
||||
: [
|
||||
memoizedEthToken,
|
||||
memoizedDefaultToken,
|
||||
...tokenList.filter(
|
||||
(token) => token.symbol !== memoizedEthToken.symbol,
|
||||
(token) => token.symbol !== memoizedDefaultToken.symbol,
|
||||
),
|
||||
];
|
||||
|
||||
@ -116,9 +125,10 @@ export function useTokensToSearch({ usersTokens = [], topTokens = {} }) {
|
||||
tokenConversionRates,
|
||||
conversionRate,
|
||||
currentCurrency,
|
||||
chainId,
|
||||
);
|
||||
if (
|
||||
renderableDataToken.symbol === 'ETH' ||
|
||||
isSwapsDefaultTokenSymbol(renderableDataToken.symbol, chainId) ||
|
||||
(usersTokensAddressMap[token.address] &&
|
||||
Number(renderableDataToken.balance ?? 0) !== 0)
|
||||
) {
|
||||
@ -150,5 +160,6 @@ export function useTokensToSearch({ usersTokens = [], topTokens = {} }) {
|
||||
conversionRate,
|
||||
currentCurrency,
|
||||
memoizedTopTokens,
|
||||
chainId,
|
||||
]);
|
||||
}
|
||||
|
@ -10,11 +10,13 @@ import {
|
||||
getShouldShowFiat,
|
||||
getNativeCurrency,
|
||||
getCurrentCurrency,
|
||||
getCurrentChainId,
|
||||
} from '../selectors';
|
||||
import { getTokens } from '../ducks/metamask/metamask';
|
||||
import { getMessage } from '../helpers/utils/i18n-helper';
|
||||
import messages from '../../../app/_locales/en/messages.json';
|
||||
import { ASSET_ROUTE, DEFAULT_ROUTE } from '../helpers/constants/routes';
|
||||
import { MAINNET_CHAIN_ID } from '../../../shared/constants/network';
|
||||
import {
|
||||
TRANSACTION_TYPES,
|
||||
TRANSACTION_GROUP_CATEGORIES,
|
||||
@ -164,6 +166,8 @@ describe('useTransactionDisplayData', function () {
|
||||
return 'ETH';
|
||||
} else if (selector === getCurrentCurrency) {
|
||||
return 'ETH';
|
||||
} else if (selector === getCurrentChainId) {
|
||||
return MAINNET_CHAIN_ID;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
@ -6,12 +6,14 @@ import { useHistory } from 'react-router-dom';
|
||||
import { I18nContext } from '../../../contexts/i18n';
|
||||
import { useNewMetricEvent } from '../../../hooks/useMetricEvent';
|
||||
import { MetaMetricsContext } from '../../../contexts/metametrics.new';
|
||||
|
||||
import {
|
||||
getCurrentChainId,
|
||||
getCurrentCurrency,
|
||||
getRpcPrefsForCurrentProvider,
|
||||
getUSDConversionRate,
|
||||
} from '../../../selectors';
|
||||
|
||||
import {
|
||||
getUsedQuote,
|
||||
getFetchParams,
|
||||
@ -23,7 +25,6 @@ import {
|
||||
prepareToLeaveSwaps,
|
||||
} from '../../../ducks/swaps/swaps';
|
||||
import Mascot from '../../../components/ui/mascot';
|
||||
import PulseLoader from '../../../components/ui/pulse-loader';
|
||||
import {
|
||||
QUOTES_EXPIRED_ERROR,
|
||||
SWAP_FAILED_ERROR,
|
||||
@ -31,6 +32,9 @@ import {
|
||||
QUOTES_NOT_AVAILABLE_ERROR,
|
||||
OFFLINE_FOR_MAINTENANCE,
|
||||
} 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 { getRenderableNetworkFeesForQuote } from '../swaps.util';
|
||||
@ -73,16 +77,17 @@ export default function AwaitingSwap({
|
||||
let feeinUnformattedFiat;
|
||||
|
||||
if (usedQuote && swapsGasPrice) {
|
||||
const renderableNetworkFees = getRenderableNetworkFeesForQuote(
|
||||
usedQuote.gasEstimateWithRefund || usedQuote.averageGas,
|
||||
approveTxParams?.gas || '0x0',
|
||||
swapsGasPrice,
|
||||
const renderableNetworkFees = getRenderableNetworkFeesForQuote({
|
||||
tradeGas: usedQuote.gasEstimateWithRefund || usedQuote.averageGas,
|
||||
approveGas: approveTxParams?.gas || '0x0',
|
||||
gasPrice: swapsGasPrice,
|
||||
currentCurrency,
|
||||
usdConversionRate,
|
||||
usedQuote?.trade?.value,
|
||||
sourceTokenInfo?.symbol,
|
||||
usedQuote.sourceAmount,
|
||||
);
|
||||
conversionRate: usdConversionRate,
|
||||
tradeValue: usedQuote?.trade?.value,
|
||||
sourceSymbol: sourceTokenInfo?.symbol,
|
||||
sourceAmount: usedQuote.sourceAmount,
|
||||
chainId,
|
||||
});
|
||||
feeinUnformattedFiat = renderableNetworkFees.rawNetworkFees;
|
||||
}
|
||||
|
||||
@ -228,7 +233,9 @@ export default function AwaitingSwap({
|
||||
);
|
||||
} else if (errorKey) {
|
||||
await dispatch(navigateBackToBuildQuote(history));
|
||||
} else if (destinationTokenInfo?.symbol === 'ETH') {
|
||||
} else if (
|
||||
isSwapsDefaultTokenSymbol(destinationTokenInfo?.symbol, chainId)
|
||||
) {
|
||||
history.push(DEFAULT_ROUTE);
|
||||
} else {
|
||||
history.push(`${ASSET_ROUTE}/${destinationTokenInfo?.address}`);
|
||||
|
@ -29,10 +29,11 @@ import {
|
||||
getFetchParams,
|
||||
} from '../../../ducks/swaps/swaps';
|
||||
import {
|
||||
getSwapsEthToken,
|
||||
getSwapsDefaultToken,
|
||||
getTokenExchangeRates,
|
||||
getConversionRate,
|
||||
getCurrentCurrency,
|
||||
getCurrentChainId,
|
||||
} from '../../../selectors';
|
||||
import {
|
||||
getValueFromWeiHex,
|
||||
@ -44,7 +45,10 @@ import { useTokenTracker } from '../../../hooks/useTokenTracker';
|
||||
import { useTokenFiatAmount } from '../../../hooks/useTokenFiatAmount';
|
||||
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 { fetchTokenPrice, fetchTokenBalance } from '../swaps.util';
|
||||
@ -84,21 +88,29 @@ export default function BuildQuote({
|
||||
const topAssets = useSelector(getTopAssets);
|
||||
const fromToken = useSelector(getFromToken);
|
||||
const toToken = useSelector(getToToken) || destinationTokenInfo;
|
||||
const swapsEthToken = useSelector(getSwapsEthToken);
|
||||
const fetchParamsFromToken =
|
||||
sourceTokenInfo?.symbol === 'ETH' ? swapsEthToken : sourceTokenInfo;
|
||||
const defaultSwapsToken = useSelector(getSwapsDefaultToken);
|
||||
const chainId = useSelector(getCurrentChainId);
|
||||
|
||||
const tokenConversionRates = useSelector(getTokenExchangeRates, isEqual);
|
||||
const conversionRate = useSelector(getConversionRate);
|
||||
const currentCurrency = useSelector(getCurrentCurrency);
|
||||
|
||||
const fetchParamsFromToken = isSwapsDefaultTokenSymbol(
|
||||
sourceTokenInfo?.symbol,
|
||||
chainId,
|
||||
)
|
||||
? defaultSwapsToken
|
||||
: sourceTokenInfo;
|
||||
|
||||
const { loading, tokensWithBalances } = useTokenTracker(tokens);
|
||||
|
||||
// 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
|
||||
// the balance of the token can appear in the from token selection dropdown
|
||||
const fromTokenArray =
|
||||
fromToken?.symbol !== 'ETH' && fromToken?.balance ? [fromToken] : [];
|
||||
!isSwapsDefaultTokenSymbol(fromToken?.symbol, chainId) && fromToken?.balance
|
||||
? [fromToken]
|
||||
: [];
|
||||
const usersTokens = uniqBy(
|
||||
[...tokensWithBalances, ...tokens, ...fromTokenArray],
|
||||
'address',
|
||||
@ -110,6 +122,7 @@ export default function BuildQuote({
|
||||
tokenConversionRates,
|
||||
conversionRate,
|
||||
currentCurrency,
|
||||
chainId,
|
||||
);
|
||||
|
||||
const tokensToSearch = useTokensToSearch({
|
||||
@ -119,9 +132,9 @@ export default function BuildQuote({
|
||||
const selectedToToken =
|
||||
tokensToSearch.find(({ address }) => address === toToken?.address) ||
|
||||
toToken;
|
||||
const toTokenIsNotEth =
|
||||
const toTokenIsNotDefault =
|
||||
selectedToToken?.address &&
|
||||
selectedToToken?.address !== ETH_SWAPS_TOKEN_OBJECT.address;
|
||||
!isSwapsDefaultTokenAddress(selectedToToken?.address, chainId);
|
||||
const occurances = Number(selectedToToken?.occurances || 0);
|
||||
const {
|
||||
address: fromTokenAddress,
|
||||
@ -151,8 +164,9 @@ export default function BuildQuote({
|
||||
{ showFiat: true },
|
||||
true,
|
||||
);
|
||||
const swapFromFiatValue =
|
||||
fromTokenSymbol === 'ETH' ? swapFromEthFiatValue : swapFromTokenFiatValue;
|
||||
const swapFromFiatValue = isSwapsDefaultTokenSymbol(fromTokenSymbol, chainId)
|
||||
? swapFromEthFiatValue
|
||||
: swapFromTokenFiatValue;
|
||||
|
||||
const onFromSelect = (token) => {
|
||||
if (
|
||||
@ -227,15 +241,17 @@ export default function BuildQuote({
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const notEth =
|
||||
tokensWithBalancesFromToken?.address !== ETH_SWAPS_TOKEN_OBJECT.address;
|
||||
const notDefault = !isSwapsDefaultTokenAddress(
|
||||
tokensWithBalancesFromToken?.address,
|
||||
chainId,
|
||||
);
|
||||
const addressesAreTheSame =
|
||||
tokensWithBalancesFromToken?.address ===
|
||||
previousTokensWithBalancesFromToken?.address;
|
||||
const balanceHasChanged =
|
||||
tokensWithBalancesFromToken?.balance !==
|
||||
previousTokensWithBalancesFromToken?.balance;
|
||||
if (notEth && addressesAreTheSame && balanceHasChanged) {
|
||||
if (notDefault && addressesAreTheSame && balanceHasChanged) {
|
||||
dispatch(
|
||||
setSwapsFromToken({
|
||||
...fromToken,
|
||||
@ -249,12 +265,13 @@ export default function BuildQuote({
|
||||
tokensWithBalancesFromToken,
|
||||
previousTokensWithBalancesFromToken,
|
||||
fromToken,
|
||||
chainId,
|
||||
]);
|
||||
|
||||
// If the eth balance changes while on build quote, we update the selected from token
|
||||
useEffect(() => {
|
||||
if (
|
||||
fromToken?.address === ETH_SWAPS_TOKEN_OBJECT.address &&
|
||||
isSwapsDefaultTokenAddress(fromToken?.address, chainId) &&
|
||||
fromToken?.balance !== hexToDecimal(ethBalance)
|
||||
) {
|
||||
dispatch(
|
||||
@ -269,7 +286,7 @@ export default function BuildQuote({
|
||||
}),
|
||||
);
|
||||
}
|
||||
}, [dispatch, fromToken, ethBalance]);
|
||||
}, [dispatch, fromToken, ethBalance, chainId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (prevFromTokenBalance !== fromTokenBalance) {
|
||||
@ -286,7 +303,7 @@ export default function BuildQuote({
|
||||
<div className="build-quote__content">
|
||||
<div className="build-quote__dropdown-input-pair-header">
|
||||
<div className="build-quote__input-label">{t('swapSwapFrom')}</div>
|
||||
{fromTokenSymbol !== 'ETH' && (
|
||||
{!isSwapsDefaultTokenSymbol(fromTokenSymbol, chainId) && (
|
||||
<div
|
||||
className="build-quote__max-button"
|
||||
onClick={() =>
|
||||
@ -384,7 +401,7 @@ export default function BuildQuote({
|
||||
defaultToAll
|
||||
/>
|
||||
</div>
|
||||
{toTokenIsNotEth &&
|
||||
{toTokenIsNotDefault &&
|
||||
(occurances < 2 ? (
|
||||
<ActionableMessage
|
||||
message={
|
||||
@ -474,7 +491,7 @@ export default function BuildQuote({
|
||||
!selectedToToken?.address ||
|
||||
Number(maxSlippage) === 0 ||
|
||||
Number(maxSlippage) > MAX_ALLOWED_SLIPPAGE ||
|
||||
(toTokenIsNotEth && occurances < 2 && !verificationClicked)
|
||||
(toTokenIsNotDefault && occurances < 2 && !verificationClicked)
|
||||
}
|
||||
hideCancel
|
||||
showTermsOfService
|
||||
|
@ -12,6 +12,7 @@ import { I18nContext } from '../../contexts/i18n';
|
||||
import {
|
||||
getSelectedAccount,
|
||||
getCurrentChainId,
|
||||
getIsSwapsChain,
|
||||
} from '../../selectors/selectors';
|
||||
import {
|
||||
getQuotes,
|
||||
@ -45,7 +46,6 @@ import {
|
||||
SWAP_FAILED_ERROR,
|
||||
OFFLINE_FOR_MAINTENANCE,
|
||||
} from '../../../../shared/constants/swaps';
|
||||
import { MAINNET_CHAIN_ID } from '../../../../shared/constants/network';
|
||||
|
||||
import {
|
||||
resetBackgroundSwapsState,
|
||||
@ -96,6 +96,8 @@ export default function Swap() {
|
||||
const fetchingQuotes = useSelector(getFetchingQuotes);
|
||||
let swapsErrorKey = useSelector(getSwapsErrorKey);
|
||||
const swapsEnabled = useSelector(getSwapsFeatureLiveness);
|
||||
const chainId = useSelector(getCurrentChainId);
|
||||
const isSwapsChain = useSelector(getIsSwapsChain);
|
||||
|
||||
const {
|
||||
balance: ethBalance,
|
||||
@ -116,6 +118,7 @@ export default function Swap() {
|
||||
selectedAccountAddress,
|
||||
destinationTokenInfo?.decimals,
|
||||
approveTxData,
|
||||
chainId,
|
||||
);
|
||||
const tradeConfirmed = tradeTxData?.status === TRANSACTION_STATUSES.CONFIRMED;
|
||||
const approveError =
|
||||
@ -155,26 +158,26 @@ export default function Swap() {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchTokens()
|
||||
fetchTokens(chainId)
|
||||
.then((tokens) => {
|
||||
dispatch(setSwapsTokens(tokens));
|
||||
})
|
||||
.catch((error) => console.error(error));
|
||||
|
||||
fetchTopAssets().then((topAssets) => {
|
||||
fetchTopAssets(chainId).then((topAssets) => {
|
||||
dispatch(setTopAssets(topAssets));
|
||||
});
|
||||
|
||||
fetchAggregatorMetadata().then((newAggregatorMetadata) => {
|
||||
fetchAggregatorMetadata(chainId).then((newAggregatorMetadata) => {
|
||||
dispatch(setAggregatorMetadata(newAggregatorMetadata));
|
||||
});
|
||||
|
||||
dispatch(fetchAndSetSwapsGasPriceInfo());
|
||||
dispatch(fetchAndSetSwapsGasPriceInfo(chainId));
|
||||
|
||||
return () => {
|
||||
dispatch(prepareToLeaveSwaps());
|
||||
};
|
||||
}, [dispatch]);
|
||||
}, [dispatch, chainId]);
|
||||
|
||||
const exitedSwapsEvent = useNewMetricEvent({
|
||||
event: 'Exited Swaps',
|
||||
@ -224,8 +227,7 @@ export default function Swap() {
|
||||
return () => window.removeEventListener('beforeunload', fn);
|
||||
}, [dispatch, isLoadingQuotesRoute]);
|
||||
|
||||
const chainId = useSelector(getCurrentChainId);
|
||||
if (chainId !== MAINNET_CHAIN_ID) {
|
||||
if (!isSwapsChain) {
|
||||
return <Redirect to={{ pathname: DEFAULT_ROUTE }} />;
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ import { setSwapsFromToken } from '../../../ducks/swaps/swaps';
|
||||
import { I18nContext } from '../../../contexts/i18n';
|
||||
import { BUILD_QUOTE_ROUTE } from '../../../helpers/constants/routes';
|
||||
import { useNewMetricEvent } from '../../../hooks/useMetricEvent';
|
||||
import { getSwapsEthToken } from '../../../selectors';
|
||||
import { getSwapsDefaultToken } from '../../../selectors';
|
||||
import Button from '../../../components/ui/button';
|
||||
import Popover from '../../../components/ui/popover';
|
||||
|
||||
@ -14,9 +14,14 @@ export default function IntroPopup({ onClose }) {
|
||||
const dispatch = useDispatch(useDispatch);
|
||||
const history = useHistory();
|
||||
const t = useContext(I18nContext);
|
||||
|
||||
const swapsDefaultToken = useSelector(getSwapsDefaultToken);
|
||||
const enteredSwapsEvent = useNewMetricEvent({
|
||||
event: 'Swaps Opened',
|
||||
properties: { source: 'Intro popup', active_currency: 'ETH' },
|
||||
properties: {
|
||||
source: 'Intro popup',
|
||||
active_currency: swapsDefaultToken.symbol,
|
||||
},
|
||||
category: 'swaps',
|
||||
});
|
||||
const blogPostVisitedEvent = useNewMetricEvent({
|
||||
@ -31,7 +36,6 @@ export default function IntroPopup({ onClose }) {
|
||||
event: 'Product Overview Dismissed',
|
||||
category: 'swaps',
|
||||
});
|
||||
const swapsEthToken = useSelector(getSwapsEthToken);
|
||||
|
||||
return (
|
||||
<div className="intro-popup">
|
||||
@ -51,7 +55,7 @@ export default function IntroPopup({ onClose }) {
|
||||
onClick={() => {
|
||||
onClose();
|
||||
enteredSwapsEvent();
|
||||
dispatch(setSwapsFromToken(swapsEthToken));
|
||||
dispatch(setSwapsFromToken(swapsDefaultToken));
|
||||
history.push(BUILD_QUOTE_ROUTE);
|
||||
}}
|
||||
>
|
||||
|
@ -3,9 +3,15 @@ import BigNumber from 'bignumber.js';
|
||||
import abi from 'human-standard-token-abi';
|
||||
import { isValidAddress } from 'ethereumjs-util';
|
||||
import {
|
||||
ETH_SWAPS_TOKEN_OBJECT,
|
||||
METASWAP_API_HOST,
|
||||
SWAPS_CHAINID_DEFAULT_TOKEN_MAP,
|
||||
METASWAP_CHAINID_API_HOST_MAP,
|
||||
} from '../../../../shared/constants/swaps';
|
||||
import {
|
||||
isSwapsDefaultTokenAddress,
|
||||
isSwapsDefaultTokenSymbol,
|
||||
} from '../../../../shared/modules/swaps.utils';
|
||||
|
||||
import { MAINNET_CHAIN_ID } from '../../../../shared/constants/network';
|
||||
import {
|
||||
calcTokenValue,
|
||||
calcTokenAmount,
|
||||
@ -30,22 +36,22 @@ const TOKEN_TRANSFER_LOG_TOPIC_HASH =
|
||||
|
||||
const CACHE_REFRESH_ONE_HOUR = 3600000;
|
||||
|
||||
const getBaseApi = function (type) {
|
||||
const getBaseApi = function (type, chainId = MAINNET_CHAIN_ID) {
|
||||
switch (type) {
|
||||
case 'trade':
|
||||
return `${METASWAP_API_HOST}/trades?`;
|
||||
return `${METASWAP_CHAINID_API_HOST_MAP[chainId]}/trades?`;
|
||||
case 'tokens':
|
||||
return `${METASWAP_API_HOST}/tokens`;
|
||||
return `${METASWAP_CHAINID_API_HOST_MAP[chainId]}/tokens`;
|
||||
case 'topAssets':
|
||||
return `${METASWAP_API_HOST}/topAssets`;
|
||||
return `${METASWAP_CHAINID_API_HOST_MAP[chainId]}/topAssets`;
|
||||
case 'featureFlag':
|
||||
return `${METASWAP_API_HOST}/featureFlag`;
|
||||
return `${METASWAP_CHAINID_API_HOST_MAP[chainId]}/featureFlag`;
|
||||
case 'aggregatorMetadata':
|
||||
return `${METASWAP_API_HOST}/aggregatorMetadata`;
|
||||
return `${METASWAP_CHAINID_API_HOST_MAP[chainId]}/aggregatorMetadata`;
|
||||
case 'gasPrices':
|
||||
return `${METASWAP_API_HOST}/gasPrices`;
|
||||
return `${METASWAP_CHAINID_API_HOST_MAP[chainId]}/gasPrices`;
|
||||
case 'refreshTime':
|
||||
return `${METASWAP_API_HOST}/quoteRefreshRate`;
|
||||
return `${METASWAP_CHAINID_API_HOST_MAP[chainId]}/quoteRefreshRate`;
|
||||
default:
|
||||
throw new Error('getBaseApi requires an api call type');
|
||||
}
|
||||
@ -205,15 +211,18 @@ function validateData(validators, object, urlUsed) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchTradesInfo({
|
||||
slippage,
|
||||
sourceToken,
|
||||
sourceDecimals,
|
||||
destinationToken,
|
||||
value,
|
||||
fromAddress,
|
||||
exchangeList,
|
||||
}) {
|
||||
export async function fetchTradesInfo(
|
||||
{
|
||||
slippage,
|
||||
sourceToken,
|
||||
sourceDecimals,
|
||||
destinationToken,
|
||||
value,
|
||||
fromAddress,
|
||||
exchangeList,
|
||||
},
|
||||
{ chainId },
|
||||
) {
|
||||
const urlParams = {
|
||||
destinationToken,
|
||||
sourceToken,
|
||||
@ -228,7 +237,7 @@ export async function fetchTradesInfo({
|
||||
}
|
||||
|
||||
const queryString = new URLSearchParams(urlParams).toString();
|
||||
const tradeURL = `${getBaseApi('trade')}${queryString}`;
|
||||
const tradeURL = `${getBaseApi('trade', chainId)}${queryString}`;
|
||||
const tradesResponse = await fetchWithCache(
|
||||
tradeURL,
|
||||
{ method: 'GET' },
|
||||
@ -272,21 +281,21 @@ export async function fetchTradesInfo({
|
||||
return newQuotes;
|
||||
}
|
||||
|
||||
export async function fetchTokens() {
|
||||
const tokenUrl = getBaseApi('tokens');
|
||||
export async function fetchTokens(chainId) {
|
||||
const tokenUrl = getBaseApi('tokens', chainId);
|
||||
const tokens = await fetchWithCache(
|
||||
tokenUrl,
|
||||
{ method: 'GET' },
|
||||
{ cacheRefreshTime: CACHE_REFRESH_ONE_HOUR },
|
||||
);
|
||||
const filteredTokens = [
|
||||
ETH_SWAPS_TOKEN_OBJECT,
|
||||
SWAPS_CHAINID_DEFAULT_TOKEN_MAP[chainId],
|
||||
...tokens.filter((token) => {
|
||||
return (
|
||||
validateData(TOKEN_VALIDATORS, token, tokenUrl) &&
|
||||
!(
|
||||
token.symbol === ETH_SWAPS_TOKEN_OBJECT.symbol ||
|
||||
token.address === ETH_SWAPS_TOKEN_OBJECT.address
|
||||
isSwapsDefaultTokenSymbol(token.symbol, chainId) ||
|
||||
isSwapsDefaultTokenAddress(token.address, chainId)
|
||||
)
|
||||
);
|
||||
}),
|
||||
@ -294,8 +303,8 @@ export async function fetchTokens() {
|
||||
return filteredTokens;
|
||||
}
|
||||
|
||||
export async function fetchAggregatorMetadata() {
|
||||
const aggregatorMetadataUrl = getBaseApi('aggregatorMetadata');
|
||||
export async function fetchAggregatorMetadata(chainId) {
|
||||
const aggregatorMetadataUrl = getBaseApi('aggregatorMetadata', chainId);
|
||||
const aggregators = await fetchWithCache(
|
||||
aggregatorMetadataUrl,
|
||||
{ method: 'GET' },
|
||||
@ -316,8 +325,8 @@ export async function fetchAggregatorMetadata() {
|
||||
return filteredAggregators;
|
||||
}
|
||||
|
||||
export async function fetchTopAssets() {
|
||||
const topAssetsUrl = getBaseApi('topAssets');
|
||||
export async function fetchTopAssets(chainId) {
|
||||
const topAssetsUrl = getBaseApi('topAssets', chainId);
|
||||
const response = await fetchWithCache(
|
||||
topAssetsUrl,
|
||||
{ method: 'GET' },
|
||||
@ -332,18 +341,18 @@ export async function fetchTopAssets() {
|
||||
return topAssetsMap;
|
||||
}
|
||||
|
||||
export async function fetchSwapsFeatureLiveness() {
|
||||
export async function fetchSwapsFeatureLiveness(chainId) {
|
||||
const status = await fetchWithCache(
|
||||
getBaseApi('featureFlag'),
|
||||
getBaseApi('featureFlag', chainId),
|
||||
{ method: 'GET' },
|
||||
{ cacheRefreshTime: 600000 },
|
||||
);
|
||||
return status?.active;
|
||||
}
|
||||
|
||||
export async function fetchSwapsQuoteRefreshTime() {
|
||||
export async function fetchSwapsQuoteRefreshTime(chainId) {
|
||||
const response = await fetchWithCache(
|
||||
getBaseApi('refreshTime'),
|
||||
getBaseApi('refreshTime', chainId),
|
||||
{ method: 'GET' },
|
||||
{ cacheRefreshTime: 600000 },
|
||||
);
|
||||
@ -378,8 +387,8 @@ export async function fetchTokenBalance(address, userAddress) {
|
||||
return usersToken;
|
||||
}
|
||||
|
||||
export async function fetchSwapsGasPrices() {
|
||||
const gasPricesUrl = getBaseApi('gasPrices');
|
||||
export async function fetchSwapsGasPrices(chainId) {
|
||||
const gasPricesUrl = getBaseApi('gasPrices', chainId);
|
||||
const response = await fetchWithCache(
|
||||
gasPricesUrl,
|
||||
{ method: 'GET' },
|
||||
@ -408,7 +417,7 @@ export async function fetchSwapsGasPrices() {
|
||||
};
|
||||
}
|
||||
|
||||
export function getRenderableNetworkFeesForQuote(
|
||||
export function getRenderableNetworkFeesForQuote({
|
||||
tradeGas,
|
||||
approveGas,
|
||||
gasPrice,
|
||||
@ -417,14 +426,18 @@ export function getRenderableNetworkFeesForQuote(
|
||||
tradeValue,
|
||||
sourceSymbol,
|
||||
sourceAmount,
|
||||
) {
|
||||
chainId,
|
||||
}) {
|
||||
const totalGasLimitForCalculation = new BigNumber(tradeGas || '0x0', 16)
|
||||
.plus(approveGas || '0x0', 16)
|
||||
.toString(16);
|
||||
const gasTotalInWeiHex = calcGasTotal(totalGasLimitForCalculation, gasPrice);
|
||||
|
||||
const nonGasFee = new BigNumber(tradeValue, 16)
|
||||
.minus(sourceSymbol === 'ETH' ? sourceAmount : 0, 10)
|
||||
.minus(
|
||||
isSwapsDefaultTokenSymbol(sourceSymbol, chainId) ? sourceAmount : 0,
|
||||
10,
|
||||
)
|
||||
.toString(16);
|
||||
|
||||
const totalWeiCost = new BigNumber(gasTotalInWeiHex, 16)
|
||||
@ -447,7 +460,7 @@ export function getRenderableNetworkFeesForQuote(
|
||||
rawNetworkFees,
|
||||
rawEthFee: ethFee,
|
||||
feeInFiat: formattedNetworkFee,
|
||||
feeInEth: `${ethFee} ETH`,
|
||||
feeInEth: `${ethFee} ${SWAPS_CHAINID_DEFAULT_TOKEN_MAP[chainId].symbol}`,
|
||||
nonGasFee,
|
||||
};
|
||||
}
|
||||
@ -459,6 +472,7 @@ export function quotesToRenderableData(
|
||||
currentCurrency,
|
||||
approveGas,
|
||||
tokenConversionRates,
|
||||
chainId,
|
||||
) {
|
||||
return Object.values(quotes).map((quote) => {
|
||||
const {
|
||||
@ -488,16 +502,17 @@ export function quotesToRenderableData(
|
||||
rawNetworkFees,
|
||||
rawEthFee,
|
||||
feeInEth,
|
||||
} = getRenderableNetworkFeesForQuote(
|
||||
gasEstimateWithRefund || decimalToHex(averageGas || 800000),
|
||||
} = getRenderableNetworkFeesForQuote({
|
||||
tradeGas: gasEstimateWithRefund || decimalToHex(averageGas || 800000),
|
||||
approveGas,
|
||||
gasPrice,
|
||||
currentCurrency,
|
||||
conversionRate,
|
||||
trade.value,
|
||||
sourceTokenInfo.symbol,
|
||||
tradeValue: trade.value,
|
||||
sourceSymbol: sourceTokenInfo.symbol,
|
||||
sourceAmount,
|
||||
);
|
||||
chainId,
|
||||
});
|
||||
|
||||
const slippageMultiplier = new BigNumber(100 - slippage).div(100);
|
||||
const minimumAmountReceived = new BigNumber(destinationValue)
|
||||
@ -506,18 +521,20 @@ export function quotesToRenderableData(
|
||||
|
||||
const tokenConversionRate =
|
||||
tokenConversionRates[destinationTokenInfo.address];
|
||||
const ethValueOfTrade =
|
||||
destinationTokenInfo.symbol === 'ETH'
|
||||
? calcTokenAmount(
|
||||
destinationAmount,
|
||||
destinationTokenInfo.decimals,
|
||||
).minus(rawEthFee, 10)
|
||||
: new BigNumber(tokenConversionRate || 0, 10)
|
||||
.times(
|
||||
calcTokenAmount(destinationAmount, destinationTokenInfo.decimals),
|
||||
10,
|
||||
)
|
||||
.minus(rawEthFee, 10);
|
||||
const ethValueOfTrade = isSwapsDefaultTokenSymbol(
|
||||
destinationTokenInfo.symbol,
|
||||
chainId,
|
||||
)
|
||||
? calcTokenAmount(destinationAmount, destinationTokenInfo.decimals).minus(
|
||||
rawEthFee,
|
||||
10,
|
||||
)
|
||||
: new BigNumber(tokenConversionRate || 0, 10)
|
||||
.times(
|
||||
calcTokenAmount(destinationAmount, destinationTokenInfo.decimals),
|
||||
10,
|
||||
)
|
||||
.minus(rawEthFee, 10);
|
||||
|
||||
let liquiditySourceKey;
|
||||
let renderedSlippage = slippage;
|
||||
@ -566,9 +583,10 @@ export function getSwapsTokensReceivedFromTxMeta(
|
||||
accountAddress,
|
||||
tokenDecimals,
|
||||
approvalTxMeta,
|
||||
chainId,
|
||||
) {
|
||||
const txReceipt = txMeta?.txReceipt;
|
||||
if (tokenSymbol === 'ETH') {
|
||||
if (isSwapsDefaultTokenSymbol(tokenSymbol, chainId)) {
|
||||
if (
|
||||
!txReceipt ||
|
||||
!txMeta ||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import proxyquire from 'proxyquire';
|
||||
import { MAINNET_CHAIN_ID } from '../../../../shared/constants/network';
|
||||
import {
|
||||
TRADES_BASE_PROD_URL,
|
||||
TOKENS_BASE_PROD_URL,
|
||||
@ -89,42 +90,45 @@ describe('Swaps Util', function () {
|
||||
},
|
||||
};
|
||||
it('should fetch trade info on prod', async function () {
|
||||
const result = await fetchTradesInfo({
|
||||
TOKENS,
|
||||
slippage: '3',
|
||||
sourceToken: TOKENS[0].address,
|
||||
destinationToken: TOKENS[1].address,
|
||||
value: '2000000000000000000',
|
||||
fromAddress: '0xmockAddress',
|
||||
sourceSymbol: TOKENS[0].symbol,
|
||||
sourceDecimals: TOKENS[0].decimals,
|
||||
sourceTokenInfo: { ...TOKENS[0] },
|
||||
destinationTokenInfo: { ...TOKENS[1] },
|
||||
});
|
||||
const result = await fetchTradesInfo(
|
||||
{
|
||||
TOKENS,
|
||||
slippage: '3',
|
||||
sourceToken: TOKENS[0].address,
|
||||
destinationToken: TOKENS[1].address,
|
||||
value: '2000000000000000000',
|
||||
fromAddress: '0xmockAddress',
|
||||
sourceSymbol: TOKENS[0].symbol,
|
||||
sourceDecimals: TOKENS[0].decimals,
|
||||
sourceTokenInfo: { ...TOKENS[0] },
|
||||
destinationTokenInfo: { ...TOKENS[1] },
|
||||
},
|
||||
{ chainId: MAINNET_CHAIN_ID },
|
||||
);
|
||||
assert.deepStrictEqual(result, expectedResult2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchTokens', 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);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchAggregatorMetadata', 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);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
@ -148,12 +152,12 @@ describe('Swaps Util', function () {
|
||||
},
|
||||
};
|
||||
it('should fetch top assets', async function () {
|
||||
const result = await fetchTopAssets(true);
|
||||
const result = await fetchTopAssets(MAINNET_CHAIN_ID);
|
||||
assert.deepStrictEqual(result, expectedResult);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
@ -36,7 +36,8 @@ import {
|
||||
getSelectedAccount,
|
||||
getCurrentCurrency,
|
||||
getTokenExchangeRates,
|
||||
getSwapsEthToken,
|
||||
getSwapsDefaultToken,
|
||||
getCurrentChainId,
|
||||
} from '../../../selectors';
|
||||
import { toPrecisionWithoutTrailingZeros } from '../../../helpers/utils/util';
|
||||
import { getTokens } from '../../../ducks/metamask/metamask';
|
||||
@ -125,7 +126,8 @@ export default function ViewQuote() {
|
||||
const usedQuote = selectedQuote || topQuote;
|
||||
const tradeValue = usedQuote?.trade?.value ?? '0x0';
|
||||
const swapsQuoteRefreshTime = useSelector(getSwapsQuoteRefreshTime);
|
||||
const swapsEthToken = useSelector(getSwapsEthToken);
|
||||
const defaultSwapsToken = useSelector(getSwapsDefaultToken);
|
||||
const chainId = useSelector(getCurrentChainId);
|
||||
|
||||
const { isBestQuote } = usedQuote;
|
||||
|
||||
@ -151,8 +153,8 @@ export default function ViewQuote() {
|
||||
|
||||
const { tokensWithBalances } = useTokenTracker(swapsTokens, true);
|
||||
const balanceToken =
|
||||
fetchParamsSourceToken === swapsEthToken.address
|
||||
? swapsEthToken
|
||||
fetchParamsSourceToken === defaultSwapsToken.address
|
||||
? defaultSwapsToken
|
||||
: tokensWithBalances.find(
|
||||
({ address }) => address === fetchParamsSourceToken,
|
||||
);
|
||||
@ -183,6 +185,7 @@ export default function ViewQuote() {
|
||||
currentCurrency,
|
||||
approveGas,
|
||||
memoizedTokenConversionRates,
|
||||
chainId,
|
||||
);
|
||||
}, [
|
||||
quotes,
|
||||
@ -191,6 +194,7 @@ export default function ViewQuote() {
|
||||
currentCurrency,
|
||||
approveGas,
|
||||
memoizedTokenConversionRates,
|
||||
chainId,
|
||||
]);
|
||||
|
||||
const renderableDataForUsedQuote = renderablePopoverData.find(
|
||||
@ -209,31 +213,33 @@ export default function ViewQuote() {
|
||||
sourceTokenIconUrl,
|
||||
} = renderableDataForUsedQuote;
|
||||
|
||||
const { feeInFiat, feeInEth } = getRenderableNetworkFeesForQuote(
|
||||
usedGasLimit,
|
||||
const { feeInFiat, feeInEth } = getRenderableNetworkFeesForQuote({
|
||||
tradeGas: usedGasLimit,
|
||||
approveGas,
|
||||
gasPrice,
|
||||
currentCurrency,
|
||||
conversionRate,
|
||||
tradeValue,
|
||||
sourceTokenSymbol,
|
||||
usedQuote.sourceAmount,
|
||||
);
|
||||
sourceSymbol: sourceTokenSymbol,
|
||||
sourceAmount: usedQuote.sourceAmount,
|
||||
chainId,
|
||||
});
|
||||
|
||||
const {
|
||||
feeInFiat: maxFeeInFiat,
|
||||
feeInEth: maxFeeInEth,
|
||||
nonGasFee,
|
||||
} = getRenderableNetworkFeesForQuote(
|
||||
maxGasLimit,
|
||||
} = getRenderableNetworkFeesForQuote({
|
||||
tradeGas: maxGasLimit,
|
||||
approveGas,
|
||||
gasPrice,
|
||||
currentCurrency,
|
||||
conversionRate,
|
||||
tradeValue,
|
||||
sourceTokenSymbol,
|
||||
usedQuote.sourceAmount,
|
||||
);
|
||||
sourceSymbol: sourceTokenSymbol,
|
||||
sourceAmount: usedQuote.sourceAmount,
|
||||
chainId,
|
||||
});
|
||||
|
||||
const tokenCost = new BigNumber(usedQuote.sourceAmount);
|
||||
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">
|
||||
{tokenBalanceNeeded || ethBalanceNeeded}
|
||||
</span>,
|
||||
tokenBalanceNeeded && !(sourceTokenSymbol === 'ETH')
|
||||
tokenBalanceNeeded && !(sourceTokenSymbol === defaultSwapsToken.symbol)
|
||||
? sourceTokenSymbol
|
||||
: 'ETH',
|
||||
: defaultSwapsToken.symbol,
|
||||
]);
|
||||
|
||||
// Price difference warning
|
||||
@ -643,7 +649,7 @@ export default function ViewQuote() {
|
||||
setSelectQuotePopoverShown(true);
|
||||
}}
|
||||
tokenConversionRate={
|
||||
destinationTokenSymbol === 'ETH'
|
||||
destinationTokenSymbol === defaultSwapsToken.symbol
|
||||
? 1
|
||||
: memoizedTokenConversionRates[destinationToken.address]
|
||||
}
|
||||
@ -655,7 +661,7 @@ export default function ViewQuote() {
|
||||
setSubmitClicked(true);
|
||||
if (!balanceError) {
|
||||
dispatch(signAndSendTransactions(history, metaMetricsEvent));
|
||||
} else if (destinationToken.symbol === 'ETH') {
|
||||
} else if (destinationToken.symbol === defaultSwapsToken.symbol) {
|
||||
history.push(DEFAULT_ROUTE);
|
||||
} else {
|
||||
history.push(`${ASSET_ROUTE}/${destinationToken.address}`);
|
||||
|
@ -15,7 +15,10 @@ import {
|
||||
getValueFromWeiHex,
|
||||
hexToDecimal,
|
||||
} 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
|
||||
@ -431,22 +434,26 @@ export function getWeb3ShimUsageStateForOrigin(state, origin) {
|
||||
* minimal token units (according to its decimals).
|
||||
* `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
|
||||
* to set the standard properties for the token. The getSwapsEthToken selector
|
||||
* extends that object with `balance` and `balance` values of the same type as
|
||||
* in regular ERC-20 token objects, per the above description.
|
||||
* Swaps treats the selected chain's currency as a token, and we use the token constants
|
||||
* in the SWAPS_CHAINID_DEFAULT_TOKEN_MAP to set the standard properties for
|
||||
* the token. The getSwapsDefaultToken selector extends that object with
|
||||
* `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
|
||||
* @returns {SwapsEthToken} The token object representation of the currently
|
||||
* selected account's ETH balance, as expected by the Swaps API.
|
||||
*/
|
||||
|
||||
export function getSwapsEthToken(state) {
|
||||
export function getSwapsDefaultToken(state) {
|
||||
const selectedAccount = getSelectedAccount(state);
|
||||
const { balance } = selectedAccount;
|
||||
const chainId = getCurrentChainId(state);
|
||||
|
||||
const defaultTokenObject = SWAPS_CHAINID_DEFAULT_TOKEN_MAP[chainId];
|
||||
|
||||
return {
|
||||
...ETH_SWAPS_TOKEN_OBJECT,
|
||||
...defaultTokenObject,
|
||||
balance: hexToDecimal(balance),
|
||||
string: getValueFromWeiHex({
|
||||
value: balance,
|
||||
@ -455,3 +462,8 @@ export function getSwapsEthToken(state) {
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export function getIsSwapsChain(state) {
|
||||
const chainId = getCurrentChainId(state);
|
||||
return ALLOWED_SWAPS_CHAIN_IDS[chainId];
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user