mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 01:47:00 +01:00
Swaps: Add conditional routing to new APIs based on a feature flag (#11470)
This commit is contained in:
parent
f51a8451b8
commit
d438439661
@ -1266,6 +1266,9 @@
|
||||
"networkNameEthereum": {
|
||||
"message": "Ethereum"
|
||||
},
|
||||
"networkNamePolygon": {
|
||||
"message": "Polygon"
|
||||
},
|
||||
"networkNameTestnet": {
|
||||
"message": "Testnet"
|
||||
},
|
||||
|
@ -19,7 +19,6 @@ import { isSwapsDefaultTokenAddress } from '../../../shared/modules/swaps.utils'
|
||||
|
||||
import {
|
||||
fetchTradesInfo as defaultFetchTradesInfo,
|
||||
fetchSwapsFeatureLiveness as defaultFetchSwapsFeatureLiveness,
|
||||
fetchSwapsQuoteRefreshTime as defaultFetchSwapsQuoteRefreshTime,
|
||||
} from '../../../ui/pages/swaps/swaps.util';
|
||||
import { MINUTE, SECOND } from '../../../shared/constants/time';
|
||||
@ -73,6 +72,7 @@ const initialState = {
|
||||
topAggId: null,
|
||||
routeState: '',
|
||||
swapsFeatureIsLive: true,
|
||||
useNewSwapsApi: false,
|
||||
swapsQuoteRefreshTime: FALLBACK_QUOTE_REFRESH_TIME,
|
||||
},
|
||||
};
|
||||
@ -85,7 +85,6 @@ export default class SwapsController {
|
||||
getProviderConfig,
|
||||
tokenRatesStore,
|
||||
fetchTradesInfo = defaultFetchTradesInfo,
|
||||
fetchSwapsFeatureLiveness = defaultFetchSwapsFeatureLiveness,
|
||||
fetchSwapsQuoteRefreshTime = defaultFetchSwapsQuoteRefreshTime,
|
||||
getCurrentChainId,
|
||||
}) {
|
||||
@ -94,7 +93,6 @@ export default class SwapsController {
|
||||
});
|
||||
|
||||
this._fetchTradesInfo = fetchTradesInfo;
|
||||
this._fetchSwapsFeatureLiveness = fetchSwapsFeatureLiveness;
|
||||
this._fetchSwapsQuoteRefreshTime = fetchSwapsQuoteRefreshTime;
|
||||
this._getCurrentChainId = getCurrentChainId;
|
||||
|
||||
@ -119,15 +117,19 @@ export default class SwapsController {
|
||||
// Sets the refresh rate for quote updates from the MetaSwap API
|
||||
async _setSwapsQuoteRefreshTime() {
|
||||
const chainId = this._getCurrentChainId();
|
||||
const { swapsState } = this.store.getState();
|
||||
|
||||
// Default to fallback time unless API returns valid response
|
||||
let swapsQuoteRefreshTime = FALLBACK_QUOTE_REFRESH_TIME;
|
||||
try {
|
||||
swapsQuoteRefreshTime = await this._fetchSwapsQuoteRefreshTime(chainId);
|
||||
swapsQuoteRefreshTime = await this._fetchSwapsQuoteRefreshTime(
|
||||
chainId,
|
||||
swapsState.useNewSwapsApi,
|
||||
);
|
||||
} catch (e) {
|
||||
console.error('Request for swaps quote refresh time failed: ', e);
|
||||
}
|
||||
|
||||
const { swapsState } = this.store.getState();
|
||||
this.store.updateState({
|
||||
swapsState: { ...swapsState, swapsQuoteRefreshTime },
|
||||
});
|
||||
@ -162,6 +164,9 @@ export default class SwapsController {
|
||||
isPolledRequest,
|
||||
) {
|
||||
const { chainId } = fetchParamsMetaData;
|
||||
const {
|
||||
swapsState: { useNewSwapsApi },
|
||||
} = this.store.getState();
|
||||
|
||||
if (!fetchParams) {
|
||||
return null;
|
||||
@ -182,7 +187,10 @@ export default class SwapsController {
|
||||
this.indexOfNewestCallInFlight = indexOfCurrentCall;
|
||||
|
||||
let [newQuotes] = await Promise.all([
|
||||
this._fetchTradesInfo(fetchParams, fetchParamsMetaData),
|
||||
this._fetchTradesInfo(fetchParams, {
|
||||
...fetchParamsMetaData,
|
||||
useNewSwapsApi,
|
||||
}),
|
||||
this._setSwapsQuoteRefreshTime(),
|
||||
]);
|
||||
|
||||
@ -449,22 +457,23 @@ export default class SwapsController {
|
||||
this.store.updateState({ swapsState: { ...swapsState, routeState } });
|
||||
}
|
||||
|
||||
setSwapsLiveness(swapsFeatureIsLive) {
|
||||
setSwapsLiveness(swapsLiveness) {
|
||||
const { swapsState } = this.store.getState();
|
||||
const { swapsFeatureIsLive, useNewSwapsApi } = swapsLiveness;
|
||||
this.store.updateState({
|
||||
swapsState: { ...swapsState, swapsFeatureIsLive },
|
||||
swapsState: { ...swapsState, swapsFeatureIsLive, useNewSwapsApi },
|
||||
});
|
||||
}
|
||||
|
||||
resetPostFetchState() {
|
||||
const { swapsState } = this.store.getState();
|
||||
|
||||
this.store.updateState({
|
||||
swapsState: {
|
||||
...initialState.swapsState,
|
||||
tokens: swapsState.tokens,
|
||||
fetchParams: swapsState.fetchParams,
|
||||
swapsFeatureIsLive: swapsState.swapsFeatureIsLive,
|
||||
useNewSwapsApi: swapsState.useNewSwapsApi,
|
||||
swapsQuoteRefreshTime: swapsState.swapsQuoteRefreshTime,
|
||||
},
|
||||
});
|
||||
@ -473,7 +482,6 @@ export default class SwapsController {
|
||||
|
||||
resetSwapsState() {
|
||||
const { swapsState } = this.store.getState();
|
||||
|
||||
this.store.updateState({
|
||||
swapsState: {
|
||||
...initialState.swapsState,
|
||||
|
@ -128,13 +128,13 @@ const EMPTY_INIT_STATE = {
|
||||
topAggId: null,
|
||||
routeState: '',
|
||||
swapsFeatureIsLive: true,
|
||||
useNewSwapsApi: false,
|
||||
swapsQuoteRefreshTime: 60000,
|
||||
},
|
||||
};
|
||||
|
||||
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);
|
||||
@ -150,7 +150,6 @@ describe('SwapsController', function () {
|
||||
getProviderConfig: MOCK_GET_PROVIDER_CONFIG,
|
||||
tokenRatesStore: MOCK_TOKEN_RATES_STORE,
|
||||
fetchTradesInfo: fetchTradesInfoStub,
|
||||
fetchSwapsFeatureLiveness: fetchSwapsFeatureLivenessStub,
|
||||
fetchSwapsQuoteRefreshTime: fetchSwapsQuoteRefreshTimeStub,
|
||||
getCurrentChainId: getCurrentChainIdStub,
|
||||
});
|
||||
@ -201,7 +200,6 @@ describe('SwapsController', function () {
|
||||
getProviderConfig: MOCK_GET_PROVIDER_CONFIG,
|
||||
tokenRatesStore: MOCK_TOKEN_RATES_STORE,
|
||||
fetchTradesInfo: fetchTradesInfoStub,
|
||||
fetchSwapsFeatureLiveness: fetchSwapsFeatureLivenessStub,
|
||||
getCurrentChainId: getCurrentChainIdStub,
|
||||
});
|
||||
const currentEthersInstance = swapsController.ethersProvider;
|
||||
@ -226,7 +224,6 @@ describe('SwapsController', function () {
|
||||
getProviderConfig: MOCK_GET_PROVIDER_CONFIG,
|
||||
tokenRatesStore: MOCK_TOKEN_RATES_STORE,
|
||||
fetchTradesInfo: fetchTradesInfoStub,
|
||||
fetchSwapsFeatureLiveness: fetchSwapsFeatureLivenessStub,
|
||||
getCurrentChainId: getCurrentChainIdStub,
|
||||
});
|
||||
const currentEthersInstance = swapsController.ethersProvider;
|
||||
@ -251,7 +248,6 @@ describe('SwapsController', function () {
|
||||
getProviderConfig: MOCK_GET_PROVIDER_CONFIG,
|
||||
tokenRatesStore: MOCK_TOKEN_RATES_STORE,
|
||||
fetchTradesInfo: fetchTradesInfoStub,
|
||||
fetchSwapsFeatureLiveness: fetchSwapsFeatureLivenessStub,
|
||||
getCurrentChainId: getCurrentChainIdStub,
|
||||
});
|
||||
const currentEthersInstance = swapsController.ethersProvider;
|
||||
@ -658,6 +654,7 @@ describe('SwapsController', function () {
|
||||
const quotes = await swapsController.fetchAndSetQuotes(undefined);
|
||||
assert.strictEqual(quotes, null);
|
||||
});
|
||||
|
||||
it('calls fetchTradesInfo with the given fetchParams and returns the correct quotes', async function () {
|
||||
fetchTradesInfoStub.resolves(getMockQuotes());
|
||||
fetchSwapsQuoteRefreshTimeStub.resolves(getMockQuoteRefreshTime());
|
||||
@ -695,15 +692,15 @@ describe('SwapsController', function () {
|
||||
metaMaskFeeInEth: '0.5050505050505050505',
|
||||
ethValueOfTokens: '50',
|
||||
});
|
||||
|
||||
assert.strictEqual(
|
||||
fetchTradesInfoStub.calledOnceWithExactly(
|
||||
MOCK_FETCH_PARAMS,
|
||||
MOCK_FETCH_METADATA,
|
||||
),
|
||||
fetchTradesInfoStub.calledOnceWithExactly(MOCK_FETCH_PARAMS, {
|
||||
...MOCK_FETCH_METADATA,
|
||||
useNewSwapsApi: false,
|
||||
}),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('performs the allowance check', async function () {
|
||||
fetchTradesInfoStub.resolves(getMockQuotes());
|
||||
fetchSwapsQuoteRefreshTimeStub.resolves(getMockQuoteRefreshTime());
|
||||
@ -878,12 +875,14 @@ describe('SwapsController', function () {
|
||||
const tokens = 'test';
|
||||
const fetchParams = 'test';
|
||||
const swapsFeatureIsLive = false;
|
||||
const useNewSwapsApi = false;
|
||||
const swapsQuoteRefreshTime = 0;
|
||||
swapsController.store.updateState({
|
||||
swapsState: {
|
||||
tokens,
|
||||
fetchParams,
|
||||
swapsFeatureIsLive,
|
||||
useNewSwapsApi,
|
||||
swapsQuoteRefreshTime,
|
||||
},
|
||||
});
|
||||
|
@ -5,10 +5,10 @@ module.exports = {
|
||||
coveragePathIgnorePatterns: ['.stories.js', '.snap'],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
branches: 45.45,
|
||||
functions: 55.29,
|
||||
lines: 60.22,
|
||||
statements: 60.43,
|
||||
branches: 45.24,
|
||||
functions: 51.94,
|
||||
lines: 58.36,
|
||||
statements: 58.6,
|
||||
},
|
||||
},
|
||||
setupFiles: ['./test/setup.js', './test/env.js'],
|
||||
|
@ -21,6 +21,7 @@ export const LOCALHOST_CHAIN_ID = '0x539';
|
||||
export const BSC_CHAIN_ID = '0x38';
|
||||
export const OPTIMISM_CHAIN_ID = '0xa';
|
||||
export const OPTIMISM_TESTNET_CHAIN_ID = '0x45';
|
||||
export const POLYGON_CHAIN_ID = '0x89';
|
||||
|
||||
/**
|
||||
* The largest possible chain ID we can handle.
|
||||
|
@ -93,3 +93,7 @@ export const SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP = {
|
||||
[BSC_CHAIN_ID]: BSC_DEFAULT_BLOCK_EXPLORER_URL,
|
||||
[MAINNET_CHAIN_ID]: MAINNET_DEFAULT_BLOCK_EXPLORER_URL,
|
||||
};
|
||||
|
||||
export const ETHEREUM = 'ethereum';
|
||||
export const POLYGON = 'polygon';
|
||||
export const BSC = 'bsc';
|
||||
|
@ -8,9 +8,21 @@
|
||||
"mockMetaMetricsResponse": true
|
||||
},
|
||||
"swaps": {
|
||||
"featureFlag": {
|
||||
"status": {
|
||||
"active": true
|
||||
"featureFlags": {
|
||||
"bsc": {
|
||||
"mobile_active": false,
|
||||
"extension_active": true,
|
||||
"fallback_to_v1": true
|
||||
},
|
||||
"ethereum": {
|
||||
"mobile_active": false,
|
||||
"extension_active": true,
|
||||
"fallback_to_v1": true
|
||||
},
|
||||
"polygon": {
|
||||
"mobile_active": false,
|
||||
"extension_active": true,
|
||||
"fallback_to_v1": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -48,9 +48,9 @@ async function setupFetchMocking(driver) {
|
||||
return { json: async () => clone(mockResponses.gasPricesBasic) };
|
||||
} else if (url.match(/chromeextensionmm/u)) {
|
||||
return { json: async () => clone(mockResponses.metametrics) };
|
||||
} else if (url.match(/^https:\/\/(api\.metaswap|.*airswap-dev)/u)) {
|
||||
if (url.match(/featureFlag$/u)) {
|
||||
return { json: async () => clone(mockResponses.swaps.featureFlag) };
|
||||
} else if (url.match(/^https:\/\/(api2\.metaswap\.codefi\.network)/u)) {
|
||||
if (url.match(/featureFlags$/u)) {
|
||||
return { json: async () => clone(mockResponses.swaps.featureFlags) };
|
||||
}
|
||||
}
|
||||
return window.origFetch(...args);
|
||||
|
@ -1 +1,2 @@
|
||||
export const METASWAP_BASE_URL = 'https://api.metaswap.codefi.network';
|
||||
export const METASWAP_API_V2_BASE_URL = 'https://api2.metaswap.codefi.network';
|
||||
|
@ -220,6 +220,7 @@ export const createSwapsMockStore = () => {
|
||||
topAggId: null,
|
||||
routeState: '',
|
||||
swapsFeatureIsLive: false,
|
||||
useNewSwapsApi: false,
|
||||
},
|
||||
},
|
||||
appState: {
|
||||
|
@ -59,3 +59,23 @@ export const TOKENS_GET_RESPONSE = [
|
||||
address: '0x0D8775F648430679A709E98d2b0Cb6250d2887EF',
|
||||
},
|
||||
];
|
||||
|
||||
export const createFeatureFlagsResponse = () => {
|
||||
return {
|
||||
bsc: {
|
||||
mobile_active: false,
|
||||
extension_active: true,
|
||||
fallback_to_v1: true,
|
||||
},
|
||||
ethereum: {
|
||||
mobile_active: false,
|
||||
extension_active: true,
|
||||
fallback_to_v1: true,
|
||||
},
|
||||
polygon: {
|
||||
mobile_active: false,
|
||||
extension_active: true,
|
||||
fallback_to_v1: false,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -34,9 +34,10 @@ import {
|
||||
SWAPS_MAINTENANCE_ROUTE,
|
||||
} from '../../helpers/constants/routes';
|
||||
import {
|
||||
fetchSwapsFeatureLiveness,
|
||||
fetchSwapsFeatureFlags,
|
||||
fetchSwapsGasPrices,
|
||||
isContractAddressValid,
|
||||
getSwapsLivenessForNetwork,
|
||||
} from '../../pages/swaps/swaps.util';
|
||||
import { calcGasTotal } from '../../pages/send/send.utils';
|
||||
import {
|
||||
@ -223,9 +224,12 @@ export function shouldShowCustomPriceTooLowWarning(state) {
|
||||
|
||||
const getSwapsState = (state) => state.metamask.swapsState;
|
||||
|
||||
export const getSwapsFeatureLiveness = (state) =>
|
||||
export const getSwapsFeatureIsLive = (state) =>
|
||||
state.metamask.swapsState.swapsFeatureIsLive;
|
||||
|
||||
export const getUseNewSwapsApi = (state) =>
|
||||
state.metamask.swapsState.useNewSwapsApi;
|
||||
|
||||
export const getSwapsQuoteRefreshTime = (state) =>
|
||||
state.metamask.swapsState.swapsQuoteRefreshTime;
|
||||
|
||||
@ -373,16 +377,21 @@ export const fetchAndSetSwapsGasPriceInfo = () => {
|
||||
|
||||
export const fetchSwapsLiveness = () => {
|
||||
return async (dispatch, getState) => {
|
||||
let swapsFeatureIsLive = false;
|
||||
let swapsLivenessForNetwork = {
|
||||
swapsFeatureIsLive: false,
|
||||
useNewSwapsApi: false,
|
||||
};
|
||||
try {
|
||||
swapsFeatureIsLive = await fetchSwapsFeatureLiveness(
|
||||
const swapsFeatureFlags = await fetchSwapsFeatureFlags();
|
||||
swapsLivenessForNetwork = getSwapsLivenessForNetwork(
|
||||
swapsFeatureFlags,
|
||||
getCurrentChainId(getState()),
|
||||
);
|
||||
} catch (error) {
|
||||
log.error('Failed to fetch Swaps liveness, defaulting to false.', error);
|
||||
}
|
||||
await dispatch(setSwapsLiveness(swapsFeatureIsLive));
|
||||
return swapsFeatureIsLive;
|
||||
await dispatch(setSwapsLiveness(swapsLivenessForNetwork));
|
||||
return swapsLivenessForNetwork;
|
||||
};
|
||||
};
|
||||
|
||||
@ -395,15 +404,22 @@ export const fetchQuotesAndSetQuoteState = (
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const chainId = getCurrentChainId(state);
|
||||
let swapsFeatureIsLive = false;
|
||||
let swapsLivenessForNetwork = {
|
||||
swapsFeatureIsLive: false,
|
||||
useNewSwapsApi: false,
|
||||
};
|
||||
try {
|
||||
swapsFeatureIsLive = await fetchSwapsFeatureLiveness(chainId);
|
||||
const swapsFeatureFlags = await fetchSwapsFeatureFlags();
|
||||
swapsLivenessForNetwork = getSwapsLivenessForNetwork(
|
||||
swapsFeatureFlags,
|
||||
chainId,
|
||||
);
|
||||
} catch (error) {
|
||||
log.error('Failed to fetch Swaps liveness, defaulting to false.', error);
|
||||
}
|
||||
await dispatch(setSwapsLiveness(swapsFeatureIsLive));
|
||||
await dispatch(setSwapsLiveness(swapsLivenessForNetwork));
|
||||
|
||||
if (!swapsFeatureIsLive) {
|
||||
if (!swapsLivenessForNetwork.swapsFeatureIsLive) {
|
||||
await history.push(SWAPS_MAINTENANCE_ROUTE);
|
||||
return;
|
||||
}
|
||||
@ -600,15 +616,22 @@ export const signAndSendTransactions = (history, metaMetricsEvent) => {
|
||||
const state = getState();
|
||||
const chainId = getCurrentChainId(state);
|
||||
const hardwareWalletUsed = isHardwareWallet(state);
|
||||
let swapsFeatureIsLive = false;
|
||||
let swapsLivenessForNetwork = {
|
||||
swapsFeatureIsLive: false,
|
||||
useNewSwapsApi: false,
|
||||
};
|
||||
try {
|
||||
swapsFeatureIsLive = await fetchSwapsFeatureLiveness(chainId);
|
||||
const swapsFeatureFlags = await fetchSwapsFeatureFlags();
|
||||
swapsLivenessForNetwork = getSwapsLivenessForNetwork(
|
||||
swapsFeatureFlags,
|
||||
chainId,
|
||||
);
|
||||
} catch (error) {
|
||||
log.error('Failed to fetch Swaps liveness, defaulting to false.', error);
|
||||
}
|
||||
await dispatch(setSwapsLiveness(swapsFeatureIsLive));
|
||||
await dispatch(setSwapsLiveness(swapsLivenessForNetwork));
|
||||
|
||||
if (!swapsFeatureIsLive) {
|
||||
if (!swapsLivenessForNetwork.swapsFeatureIsLive) {
|
||||
await history.push(SWAPS_MAINTENANCE_ROUTE);
|
||||
return;
|
||||
}
|
||||
@ -808,12 +831,13 @@ export function fetchMetaSwapsGasPriceEstimates() {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const chainId = getCurrentChainId(state);
|
||||
const useNewSwapsApi = getUseNewSwapsApi(state);
|
||||
|
||||
dispatch(swapGasPriceEstimatesFetchStarted());
|
||||
|
||||
let priceEstimates;
|
||||
try {
|
||||
priceEstimates = await fetchSwapsGasPrices(chainId);
|
||||
priceEstimates = await fetchSwapsGasPrices(chainId, useNewSwapsApi);
|
||||
} catch (e) {
|
||||
log.warn('Fetching swaps gas prices failed:', e);
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import nock from 'nock';
|
||||
|
||||
import { MOCKS } from '../../../test/jest';
|
||||
import { setSwapsLiveness } from '../../store/actions';
|
||||
import { setStorageItem } from '../../helpers/utils/storage-helpers';
|
||||
import * as swaps from './swaps';
|
||||
@ -25,7 +26,7 @@ describe('Ducks - Swaps', () => {
|
||||
describe('fetchSwapsLiveness', () => {
|
||||
const cleanFeatureFlagApiCache = () => {
|
||||
setStorageItem(
|
||||
'cachedFetch:https://api.metaswap.codefi.network/featureFlag',
|
||||
'cachedFetch:https://api2.metaswap.codefi.network/featureFlags',
|
||||
null,
|
||||
);
|
||||
};
|
||||
@ -34,12 +35,12 @@ describe('Ducks - Swaps', () => {
|
||||
cleanFeatureFlagApiCache();
|
||||
});
|
||||
|
||||
const mockFeatureFlagApiResponse = ({
|
||||
active = false,
|
||||
const mockFeatureFlagsApiResponse = ({
|
||||
featureFlagsResponse,
|
||||
replyWithError = false,
|
||||
} = {}) => {
|
||||
const apiNock = nock('https://api.metaswap.codefi.network').get(
|
||||
'/featureFlag',
|
||||
const apiNock = nock('https://api2.metaswap.codefi.network').get(
|
||||
'/featureFlags',
|
||||
);
|
||||
if (replyWithError) {
|
||||
return apiNock.replyWithError({
|
||||
@ -47,9 +48,7 @@ describe('Ducks - Swaps', () => {
|
||||
code: 'serverSideError',
|
||||
});
|
||||
}
|
||||
return apiNock.reply(200, {
|
||||
active,
|
||||
});
|
||||
return apiNock.reply(200, featureFlagsResponse);
|
||||
};
|
||||
|
||||
const createGetState = () => {
|
||||
@ -58,61 +57,111 @@ describe('Ducks - Swaps', () => {
|
||||
});
|
||||
};
|
||||
|
||||
it('returns true if the Swaps feature is enabled', async () => {
|
||||
it('checks that Swaps for ETH are enabled and can use new API', async () => {
|
||||
const mockDispatch = jest.fn();
|
||||
const featureFlagApiNock = mockFeatureFlagApiResponse({ active: true });
|
||||
const isSwapsFeatureEnabled = await swaps.fetchSwapsLiveness()(
|
||||
const expectedSwapsLiveness = {
|
||||
swapsFeatureIsLive: true,
|
||||
useNewSwapsApi: true,
|
||||
};
|
||||
const featureFlagsResponse = MOCKS.createFeatureFlagsResponse();
|
||||
const featureFlagApiNock = mockFeatureFlagsApiResponse({
|
||||
featureFlagsResponse,
|
||||
});
|
||||
const swapsLiveness = await swaps.fetchSwapsLiveness()(
|
||||
mockDispatch,
|
||||
createGetState(),
|
||||
);
|
||||
expect(featureFlagApiNock.isDone()).toBe(true);
|
||||
expect(mockDispatch).toHaveBeenCalledTimes(1);
|
||||
expect(setSwapsLiveness).toHaveBeenCalledWith(true);
|
||||
expect(isSwapsFeatureEnabled).toBe(true);
|
||||
expect(setSwapsLiveness).toHaveBeenCalledWith(expectedSwapsLiveness);
|
||||
expect(swapsLiveness).toMatchObject(expectedSwapsLiveness);
|
||||
});
|
||||
|
||||
it('returns false if the Swaps feature is disabled', async () => {
|
||||
it('checks that Swaps for ETH are disabled for API v2 and enabled for API v1', async () => {
|
||||
const mockDispatch = jest.fn();
|
||||
const featureFlagApiNock = mockFeatureFlagApiResponse({ active: false });
|
||||
const isSwapsFeatureEnabled = await swaps.fetchSwapsLiveness()(
|
||||
const expectedSwapsLiveness = {
|
||||
swapsFeatureIsLive: true,
|
||||
useNewSwapsApi: false,
|
||||
};
|
||||
const featureFlagsResponse = MOCKS.createFeatureFlagsResponse();
|
||||
featureFlagsResponse.ethereum.extension_active = false;
|
||||
const featureFlagApiNock = mockFeatureFlagsApiResponse({
|
||||
featureFlagsResponse,
|
||||
});
|
||||
const swapsLiveness = await swaps.fetchSwapsLiveness()(
|
||||
mockDispatch,
|
||||
createGetState(),
|
||||
);
|
||||
expect(featureFlagApiNock.isDone()).toBe(true);
|
||||
expect(mockDispatch).toHaveBeenCalledTimes(1);
|
||||
expect(setSwapsLiveness).toHaveBeenCalledWith(false);
|
||||
expect(isSwapsFeatureEnabled).toBe(false);
|
||||
expect(setSwapsLiveness).toHaveBeenCalledWith(expectedSwapsLiveness);
|
||||
expect(swapsLiveness).toMatchObject(expectedSwapsLiveness);
|
||||
});
|
||||
|
||||
it('returns false if the /featureFlag API call throws an error', async () => {
|
||||
it('checks that Swaps for ETH are disabled for API v1 and v2', async () => {
|
||||
const mockDispatch = jest.fn();
|
||||
const featureFlagApiNock = mockFeatureFlagApiResponse({
|
||||
const expectedSwapsLiveness = {
|
||||
swapsFeatureIsLive: false,
|
||||
useNewSwapsApi: false,
|
||||
};
|
||||
const featureFlagsResponse = MOCKS.createFeatureFlagsResponse();
|
||||
featureFlagsResponse.ethereum.extension_active = false;
|
||||
featureFlagsResponse.ethereum.fallback_to_v1 = false;
|
||||
const featureFlagApiNock = mockFeatureFlagsApiResponse({
|
||||
featureFlagsResponse,
|
||||
});
|
||||
const swapsLiveness = await swaps.fetchSwapsLiveness()(
|
||||
mockDispatch,
|
||||
createGetState(),
|
||||
);
|
||||
expect(featureFlagApiNock.isDone()).toBe(true);
|
||||
expect(mockDispatch).toHaveBeenCalledTimes(1);
|
||||
expect(setSwapsLiveness).toHaveBeenCalledWith(expectedSwapsLiveness);
|
||||
expect(swapsLiveness).toMatchObject(expectedSwapsLiveness);
|
||||
});
|
||||
|
||||
it('checks that Swaps for ETH are disabled if the /featureFlags API call throws an error', async () => {
|
||||
const mockDispatch = jest.fn();
|
||||
const expectedSwapsLiveness = {
|
||||
swapsFeatureIsLive: false,
|
||||
useNewSwapsApi: false,
|
||||
};
|
||||
const featureFlagApiNock = mockFeatureFlagsApiResponse({
|
||||
replyWithError: true,
|
||||
});
|
||||
const isSwapsFeatureEnabled = await swaps.fetchSwapsLiveness()(
|
||||
const swapsLiveness = await swaps.fetchSwapsLiveness()(
|
||||
mockDispatch,
|
||||
createGetState(),
|
||||
);
|
||||
expect(featureFlagApiNock.isDone()).toBe(true);
|
||||
expect(mockDispatch).toHaveBeenCalledTimes(1);
|
||||
expect(setSwapsLiveness).toHaveBeenCalledWith(false);
|
||||
expect(isSwapsFeatureEnabled).toBe(false);
|
||||
expect(setSwapsLiveness).toHaveBeenCalledWith(expectedSwapsLiveness);
|
||||
expect(swapsLiveness).toMatchObject(expectedSwapsLiveness);
|
||||
});
|
||||
|
||||
it('only calls the API once and returns true from cache for the second call', async () => {
|
||||
it('only calls the API once and returns response from cache for the second call', async () => {
|
||||
const mockDispatch = jest.fn();
|
||||
const featureFlagApiNock = mockFeatureFlagApiResponse({ active: true });
|
||||
const expectedSwapsLiveness = {
|
||||
swapsFeatureIsLive: true,
|
||||
useNewSwapsApi: true,
|
||||
};
|
||||
const featureFlagsResponse = MOCKS.createFeatureFlagsResponse();
|
||||
const featureFlagApiNock = mockFeatureFlagsApiResponse({
|
||||
featureFlagsResponse,
|
||||
});
|
||||
await swaps.fetchSwapsLiveness()(mockDispatch, createGetState());
|
||||
expect(featureFlagApiNock.isDone()).toBe(true);
|
||||
const featureFlagApiNock2 = mockFeatureFlagApiResponse({ active: true });
|
||||
const isSwapsFeatureEnabled = await swaps.fetchSwapsLiveness()(
|
||||
const featureFlagApiNock2 = mockFeatureFlagsApiResponse({
|
||||
featureFlagsResponse,
|
||||
});
|
||||
const swapsLiveness = await swaps.fetchSwapsLiveness()(
|
||||
mockDispatch,
|
||||
createGetState(),
|
||||
);
|
||||
expect(featureFlagApiNock2.isDone()).toBe(false); // Second API call wasn't made, cache was used instead.
|
||||
expect(mockDispatch).toHaveBeenCalledTimes(2);
|
||||
expect(setSwapsLiveness).toHaveBeenCalledWith(true);
|
||||
expect(isSwapsFeatureEnabled).toBe(true);
|
||||
expect(setSwapsLiveness).toHaveBeenCalledWith(expectedSwapsLiveness);
|
||||
expect(swapsLiveness).toMatchObject(expectedSwapsLiveness);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -31,7 +31,7 @@ import {
|
||||
} from '../../store/actions';
|
||||
import { setThreeBoxLastUpdated, hideWhatsNewPopup } from '../../ducks/app/app';
|
||||
import { getWeb3ShimUsageAlertEnabledness } from '../../ducks/metamask/metamask';
|
||||
import { getSwapsFeatureLiveness } from '../../ducks/swaps/swaps';
|
||||
import { getSwapsFeatureIsLive } from '../../ducks/swaps/swaps';
|
||||
import { getEnvironmentType } from '../../../app/scripts/lib/util';
|
||||
import {
|
||||
ENVIRONMENT_TYPE_NOTIFICATION,
|
||||
@ -60,7 +60,7 @@ const mapStateToProps = (state) => {
|
||||
const accountBalance = getCurrentEthBalance(state);
|
||||
const { forgottenPassword, threeBoxLastUpdated } = appState;
|
||||
const totalUnapprovedCount = getTotalUnapprovedCount(state);
|
||||
const swapsEnabled = getSwapsFeatureLiveness(state);
|
||||
const swapsEnabled = getSwapsFeatureIsLive(state);
|
||||
const pendingConfirmations = getUnapprovedTemplatedConfirmations(state);
|
||||
|
||||
const envType = getEnvironmentType();
|
||||
|
@ -141,7 +141,9 @@ export default function BuildQuote({
|
||||
const toTokenIsNotDefault =
|
||||
selectedToToken?.address &&
|
||||
!isSwapsDefaultTokenAddress(selectedToToken?.address, chainId);
|
||||
const occurances = Number(selectedToToken?.occurances || 0);
|
||||
const occurrences = Number(
|
||||
selectedToToken?.occurances || selectedToToken?.occurrences || 0,
|
||||
);
|
||||
const {
|
||||
address: fromTokenAddress,
|
||||
symbol: fromTokenSymbol,
|
||||
@ -354,11 +356,11 @@ export default function BuildQuote({
|
||||
|
||||
let tokenVerificationDescription = '';
|
||||
if (blockExplorerTokenLink) {
|
||||
if (occurances === 1) {
|
||||
if (occurrences === 1) {
|
||||
tokenVerificationDescription = t('verifyThisTokenOn', [
|
||||
<BlockExplorerLink key="block-explorer-link" />,
|
||||
]);
|
||||
} else if (occurances === 0) {
|
||||
} else if (occurrences === 0) {
|
||||
tokenVerificationDescription = t('verifyThisUnconfirmedTokenOn', [
|
||||
<BlockExplorerLink key="block-explorer-link" />,
|
||||
]);
|
||||
@ -470,13 +472,13 @@ export default function BuildQuote({
|
||||
/>
|
||||
</div>
|
||||
{toTokenIsNotDefault &&
|
||||
(occurances < 2 ? (
|
||||
(occurrences < 2 ? (
|
||||
<ActionableMessage
|
||||
type={occurances === 1 ? 'warning' : 'danger'}
|
||||
type={occurrences === 1 ? 'warning' : 'danger'}
|
||||
message={
|
||||
<div className="build-quote__token-verification-warning-message">
|
||||
<div className="build-quote__bold">
|
||||
{occurances === 1
|
||||
{occurrences === 1
|
||||
? t('swapTokenVerificationOnlyOneSource')
|
||||
: t('swapTokenVerificationAddedManually')}
|
||||
</div>
|
||||
@ -503,7 +505,7 @@ export default function BuildQuote({
|
||||
className="build-quote__bold"
|
||||
key="token-verification-bold-text"
|
||||
>
|
||||
{t('swapTokenVerificationSources', [occurances])}
|
||||
{t('swapTokenVerificationSources', [occurrences])}
|
||||
</span>
|
||||
{blockExplorerTokenLink && (
|
||||
<>
|
||||
@ -563,7 +565,7 @@ export default function BuildQuote({
|
||||
!selectedToToken?.address ||
|
||||
Number(maxSlippage) < 0 ||
|
||||
Number(maxSlippage) > MAX_ALLOWED_SLIPPAGE ||
|
||||
(toTokenIsNotDefault && occurances < 2 && !verificationClicked)
|
||||
(toTokenIsNotDefault && occurrences < 2 && !verificationClicked)
|
||||
}
|
||||
hideCancel
|
||||
showTermsOfService
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
MAINNET_CHAIN_ID,
|
||||
BSC_CHAIN_ID,
|
||||
LOCALHOST_CHAIN_ID,
|
||||
POLYGON_CHAIN_ID,
|
||||
} from '../../../../shared/constants/network';
|
||||
|
||||
export default function FeeCard({
|
||||
@ -38,6 +39,8 @@ export default function FeeCard({
|
||||
return t('networkNameEthereum');
|
||||
case BSC_CHAIN_ID:
|
||||
return t('networkNameBSC');
|
||||
case POLYGON_CHAIN_ID:
|
||||
return t('networkNamePolygon');
|
||||
case LOCALHOST_CHAIN_ID:
|
||||
return t('networkNameTestnet');
|
||||
default:
|
||||
|
@ -29,10 +29,11 @@ import {
|
||||
getAggregatorMetadata,
|
||||
getBackgroundSwapRouteState,
|
||||
getSwapsErrorKey,
|
||||
getSwapsFeatureLiveness,
|
||||
getSwapsFeatureIsLive,
|
||||
prepareToLeaveSwaps,
|
||||
fetchAndSetSwapsGasPriceInfo,
|
||||
fetchSwapsLiveness,
|
||||
getUseNewSwapsApi,
|
||||
} from '../../ducks/swaps/swaps';
|
||||
import {
|
||||
AWAITING_SIGNATURES_ROUTE,
|
||||
@ -103,9 +104,10 @@ export default function Swap() {
|
||||
const aggregatorMetadata = useSelector(getAggregatorMetadata);
|
||||
const fetchingQuotes = useSelector(getFetchingQuotes);
|
||||
let swapsErrorKey = useSelector(getSwapsErrorKey);
|
||||
const swapsEnabled = useSelector(getSwapsFeatureLiveness);
|
||||
const swapsEnabled = useSelector(getSwapsFeatureIsLive);
|
||||
const chainId = useSelector(getCurrentChainId);
|
||||
const isSwapsChain = useSelector(getIsSwapsChain);
|
||||
const useNewSwapsApi = useSelector(getUseNewSwapsApi);
|
||||
|
||||
const {
|
||||
balance: ethBalance,
|
||||
@ -165,27 +167,28 @@ export default function Swap() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
// eslint-disable-next-line
|
||||
useEffect(() => {
|
||||
fetchTokens(chainId)
|
||||
.then((tokens) => {
|
||||
dispatch(setSwapsTokens(tokens));
|
||||
})
|
||||
.catch((error) => console.error(error));
|
||||
|
||||
fetchTopAssets(chainId).then((topAssets) => {
|
||||
dispatch(setTopAssets(topAssets));
|
||||
});
|
||||
|
||||
fetchAggregatorMetadata(chainId).then((newAggregatorMetadata) => {
|
||||
dispatch(setAggregatorMetadata(newAggregatorMetadata));
|
||||
});
|
||||
|
||||
dispatch(fetchAndSetSwapsGasPriceInfo(chainId));
|
||||
|
||||
return () => {
|
||||
dispatch(prepareToLeaveSwaps());
|
||||
};
|
||||
}, [dispatch, chainId]);
|
||||
if (isFeatureFlagLoaded) {
|
||||
fetchTokens(chainId, useNewSwapsApi)
|
||||
.then((tokens) => {
|
||||
dispatch(setSwapsTokens(tokens));
|
||||
})
|
||||
.catch((error) => console.error(error));
|
||||
fetchTopAssets(chainId, useNewSwapsApi).then((topAssets) => {
|
||||
dispatch(setTopAssets(topAssets));
|
||||
});
|
||||
fetchAggregatorMetadata(chainId, useNewSwapsApi).then(
|
||||
(newAggregatorMetadata) => {
|
||||
dispatch(setAggregatorMetadata(newAggregatorMetadata));
|
||||
},
|
||||
);
|
||||
dispatch(fetchAndSetSwapsGasPriceInfo(chainId));
|
||||
return () => {
|
||||
dispatch(prepareToLeaveSwaps());
|
||||
};
|
||||
}
|
||||
}, [dispatch, chainId, isFeatureFlagLoaded, useNewSwapsApi]);
|
||||
|
||||
const hardwareWalletUsed = useSelector(isHardwareWallet);
|
||||
const hardwareWalletType = useSelector(getHardwareWalletType);
|
||||
|
@ -24,7 +24,7 @@ setBackgroundConnection({
|
||||
});
|
||||
|
||||
describe('Swap', () => {
|
||||
let tokensNock;
|
||||
let featureFlagsNock;
|
||||
|
||||
beforeEach(() => {
|
||||
nock(CONSTANTS.METASWAP_BASE_URL)
|
||||
@ -43,9 +43,13 @@ describe('Swap', () => {
|
||||
.get('/gasPrices')
|
||||
.reply(200, MOCKS.GAS_PRICES_GET_RESPONSE);
|
||||
|
||||
tokensNock = nock(CONSTANTS.METASWAP_BASE_URL)
|
||||
nock(CONSTANTS.METASWAP_BASE_URL)
|
||||
.get('/tokens')
|
||||
.reply(200, MOCKS.TOKENS_GET_RESPONSE);
|
||||
|
||||
featureFlagsNock = nock(CONSTANTS.METASWAP_API_V2_BASE_URL)
|
||||
.get('/featureFlags')
|
||||
.reply(200, MOCKS.createFeatureFlagsResponse());
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
@ -55,7 +59,7 @@ describe('Swap', () => {
|
||||
it('renders the component with initial props', async () => {
|
||||
const store = configureMockStore(middleware)(createSwapsMockStore());
|
||||
const { container, getByText } = renderWithProvider(<Swap />, store);
|
||||
await waitFor(() => expect(tokensNock.isDone()).toBe(true));
|
||||
await waitFor(() => expect(featureFlagsNock.isDone()).toBe(true));
|
||||
expect(getByText('Swap')).toBeInTheDocument();
|
||||
expect(getByText('Cancel')).toBeInTheDocument();
|
||||
expect(container).toMatchSnapshot();
|
||||
|
@ -9,6 +9,7 @@ import { usePrevious } from '../../../../hooks/usePrevious';
|
||||
import { isValidHexAddress } from '../../../../../shared/modules/hexstring-utils';
|
||||
import { fetchToken } from '../../swaps.util';
|
||||
import { getCurrentChainId } from '../../../../selectors/selectors';
|
||||
import { getUseNewSwapsApi } from '../../../../ducks/swaps/swaps';
|
||||
|
||||
const renderAdornment = () => (
|
||||
<InputAdornment position="start" style={{ marginRight: '12px' }}>
|
||||
@ -28,6 +29,7 @@ export default function ListItemSearch({
|
||||
const fuseRef = useRef();
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const chainId = useSelector(getCurrentChainId);
|
||||
const useNewSwapsApi = useSelector(getUseNewSwapsApi);
|
||||
|
||||
/**
|
||||
* Search a custom token for import based on a contract address.
|
||||
@ -36,7 +38,7 @@ export default function ListItemSearch({
|
||||
const handleSearchTokenForImport = async (contractAddress) => {
|
||||
setSearchQuery(contractAddress);
|
||||
try {
|
||||
const token = await fetchToken(contractAddress, chainId);
|
||||
const token = await fetchToken(contractAddress, chainId, useNewSwapsApi);
|
||||
if (token) {
|
||||
token.primaryLabel = token.symbol;
|
||||
token.secondaryLabel = token.name;
|
||||
|
@ -6,6 +6,9 @@ import {
|
||||
METASWAP_CHAINID_API_HOST_MAP,
|
||||
SWAPS_CHAINID_CONTRACT_ADDRESS_MAP,
|
||||
ETH_WETH_CONTRACT_ADDRESS,
|
||||
ETHEREUM,
|
||||
POLYGON,
|
||||
BSC,
|
||||
} from '../../../shared/constants/swaps';
|
||||
import {
|
||||
isSwapsDefaultTokenAddress,
|
||||
@ -15,6 +18,9 @@ import {
|
||||
ETH_SYMBOL,
|
||||
WETH_SYMBOL,
|
||||
MAINNET_CHAIN_ID,
|
||||
BSC_CHAIN_ID,
|
||||
POLYGON_CHAIN_ID,
|
||||
LOCALHOST_CHAIN_ID,
|
||||
} from '../../../shared/constants/network';
|
||||
import { SECOND } from '../../../shared/constants/time';
|
||||
import {
|
||||
@ -42,24 +48,50 @@ const TOKEN_TRANSFER_LOG_TOPIC_HASH =
|
||||
|
||||
const CACHE_REFRESH_FIVE_MINUTES = 300000;
|
||||
|
||||
const getBaseApi = function (type, chainId = MAINNET_CHAIN_ID) {
|
||||
const SWAPS_API_V2_BASE_URL = 'https://api2.metaswap.codefi.network';
|
||||
const GAS_API_BASE_URL = 'https://gas-api.metaswap.codefi.network';
|
||||
|
||||
/**
|
||||
* @param {string} type Type of an API call, e.g. "tokens"
|
||||
* @param {string} chainId
|
||||
* @returns string
|
||||
*/
|
||||
const getBaseUrlForNewSwapsApi = (type, chainId) => {
|
||||
const noNetworkSpecificTypes = ['refreshTime']; // These types don't need network info in the URL.
|
||||
if (noNetworkSpecificTypes.includes(type)) {
|
||||
return SWAPS_API_V2_BASE_URL;
|
||||
}
|
||||
const chainIdDecimal = chainId && parseInt(chainId, 16);
|
||||
const gasApiTypes = ['gasPrices'];
|
||||
if (gasApiTypes.includes(type)) {
|
||||
return `${GAS_API_BASE_URL}/networks/${chainIdDecimal}`; // Gas calculations are in its own repo.
|
||||
}
|
||||
return `${SWAPS_API_V2_BASE_URL}/networks/${chainIdDecimal}`;
|
||||
};
|
||||
|
||||
const getBaseApi = function (
|
||||
type,
|
||||
chainId = MAINNET_CHAIN_ID,
|
||||
useNewSwapsApi = false,
|
||||
) {
|
||||
const baseUrl = useNewSwapsApi
|
||||
? getBaseUrlForNewSwapsApi(type, chainId)
|
||||
: METASWAP_CHAINID_API_HOST_MAP[chainId];
|
||||
switch (type) {
|
||||
case 'trade':
|
||||
return `${METASWAP_CHAINID_API_HOST_MAP[chainId]}/trades?`;
|
||||
return `${baseUrl}/trades?`;
|
||||
case 'tokens':
|
||||
return `${METASWAP_CHAINID_API_HOST_MAP[chainId]}/tokens`;
|
||||
return `${baseUrl}/tokens`;
|
||||
case 'token':
|
||||
return `${METASWAP_CHAINID_API_HOST_MAP[chainId]}/token`;
|
||||
return `${baseUrl}/token`;
|
||||
case 'topAssets':
|
||||
return `${METASWAP_CHAINID_API_HOST_MAP[chainId]}/topAssets`;
|
||||
case 'featureFlag':
|
||||
return `${METASWAP_CHAINID_API_HOST_MAP[chainId]}/featureFlag`;
|
||||
return `${baseUrl}/topAssets`;
|
||||
case 'aggregatorMetadata':
|
||||
return `${METASWAP_CHAINID_API_HOST_MAP[chainId]}/aggregatorMetadata`;
|
||||
return `${baseUrl}/aggregatorMetadata`;
|
||||
case 'gasPrices':
|
||||
return `${METASWAP_CHAINID_API_HOST_MAP[chainId]}/gasPrices`;
|
||||
return `${baseUrl}/gasPrices`;
|
||||
case 'refreshTime':
|
||||
return `${METASWAP_CHAINID_API_HOST_MAP[chainId]}/quoteRefreshRate`;
|
||||
return `${baseUrl}/quoteRefreshRate`;
|
||||
default:
|
||||
throw new Error('getBaseApi requires an api call type');
|
||||
}
|
||||
@ -233,7 +265,7 @@ export async function fetchTradesInfo(
|
||||
fromAddress,
|
||||
exchangeList,
|
||||
},
|
||||
{ chainId },
|
||||
{ chainId, useNewSwapsApi },
|
||||
) {
|
||||
const urlParams = {
|
||||
destinationToken,
|
||||
@ -249,7 +281,11 @@ export async function fetchTradesInfo(
|
||||
}
|
||||
|
||||
const queryString = new URLSearchParams(urlParams).toString();
|
||||
const tradeURL = `${getBaseApi('trade', chainId)}${queryString}`;
|
||||
const tradeURL = `${getBaseApi(
|
||||
'trade',
|
||||
chainId,
|
||||
useNewSwapsApi,
|
||||
)}${queryString}`;
|
||||
const tradesResponse = await fetchWithCache(
|
||||
tradeURL,
|
||||
{ method: 'GET' },
|
||||
@ -293,8 +329,8 @@ export async function fetchTradesInfo(
|
||||
return newQuotes;
|
||||
}
|
||||
|
||||
export async function fetchToken(contractAddress, chainId) {
|
||||
const tokenUrl = getBaseApi('token', chainId);
|
||||
export async function fetchToken(contractAddress, chainId, useNewSwapsApi) {
|
||||
const tokenUrl = getBaseApi('token', chainId, useNewSwapsApi);
|
||||
const token = await fetchWithCache(
|
||||
`${tokenUrl}?address=${contractAddress}`,
|
||||
{ method: 'GET' },
|
||||
@ -303,8 +339,8 @@ export async function fetchToken(contractAddress, chainId) {
|
||||
return token;
|
||||
}
|
||||
|
||||
export async function fetchTokens(chainId) {
|
||||
const tokensUrl = getBaseApi('tokens', chainId);
|
||||
export async function fetchTokens(chainId, useNewSwapsApi) {
|
||||
const tokensUrl = getBaseApi('tokens', chainId, useNewSwapsApi);
|
||||
const tokens = await fetchWithCache(
|
||||
tokensUrl,
|
||||
{ method: 'GET' },
|
||||
@ -325,8 +361,12 @@ export async function fetchTokens(chainId) {
|
||||
return filteredTokens;
|
||||
}
|
||||
|
||||
export async function fetchAggregatorMetadata(chainId) {
|
||||
const aggregatorMetadataUrl = getBaseApi('aggregatorMetadata', chainId);
|
||||
export async function fetchAggregatorMetadata(chainId, useNewSwapsApi) {
|
||||
const aggregatorMetadataUrl = getBaseApi(
|
||||
'aggregatorMetadata',
|
||||
chainId,
|
||||
useNewSwapsApi,
|
||||
);
|
||||
const aggregators = await fetchWithCache(
|
||||
aggregatorMetadataUrl,
|
||||
{ method: 'GET' },
|
||||
@ -347,8 +387,8 @@ export async function fetchAggregatorMetadata(chainId) {
|
||||
return filteredAggregators;
|
||||
}
|
||||
|
||||
export async function fetchTopAssets(chainId) {
|
||||
const topAssetsUrl = getBaseApi('topAssets', chainId);
|
||||
export async function fetchTopAssets(chainId, useNewSwapsApi) {
|
||||
const topAssetsUrl = getBaseApi('topAssets', chainId, useNewSwapsApi);
|
||||
const response = await fetchWithCache(
|
||||
topAssetsUrl,
|
||||
{ method: 'GET' },
|
||||
@ -363,18 +403,18 @@ export async function fetchTopAssets(chainId) {
|
||||
return topAssetsMap;
|
||||
}
|
||||
|
||||
export async function fetchSwapsFeatureLiveness(chainId) {
|
||||
const status = await fetchWithCache(
|
||||
getBaseApi('featureFlag', chainId),
|
||||
export async function fetchSwapsFeatureFlags() {
|
||||
const response = await fetchWithCache(
|
||||
`${SWAPS_API_V2_BASE_URL}/featureFlags`,
|
||||
{ method: 'GET' },
|
||||
{ cacheRefreshTime: 600000 },
|
||||
);
|
||||
return status?.active;
|
||||
return response;
|
||||
}
|
||||
|
||||
export async function fetchSwapsQuoteRefreshTime(chainId) {
|
||||
export async function fetchSwapsQuoteRefreshTime(chainId, useNewSwapsApi) {
|
||||
const response = await fetchWithCache(
|
||||
getBaseApi('refreshTime', chainId),
|
||||
getBaseApi('refreshTime', chainId, useNewSwapsApi),
|
||||
{ method: 'GET' },
|
||||
{ cacheRefreshTime: 600000 },
|
||||
);
|
||||
@ -409,8 +449,8 @@ export async function fetchTokenBalance(address, userAddress) {
|
||||
return usersToken;
|
||||
}
|
||||
|
||||
export async function fetchSwapsGasPrices(chainId) {
|
||||
const gasPricesUrl = getBaseApi('gasPrices', chainId);
|
||||
export async function fetchSwapsGasPrices(chainId, useNewSwapsApi) {
|
||||
const gasPricesUrl = getBaseApi('gasPrices', chainId, useNewSwapsApi);
|
||||
const response = await fetchWithCache(
|
||||
gasPricesUrl,
|
||||
{ method: 'GET' },
|
||||
@ -730,3 +770,56 @@ export const isContractAddressValid = (
|
||||
contractAddressForChainId.toUpperCase() === contractAddress.toUpperCase()
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} chainId
|
||||
* @returns string e.g. ethereum, bsc or polygon
|
||||
*/
|
||||
export const getNetworkNameByChainId = (chainId) => {
|
||||
switch (chainId) {
|
||||
case MAINNET_CHAIN_ID:
|
||||
return ETHEREUM;
|
||||
case BSC_CHAIN_ID:
|
||||
return BSC;
|
||||
case POLYGON_CHAIN_ID:
|
||||
return POLYGON;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* It returns info about if Swaps are enabled and if we should use our new APIs for it.
|
||||
* @param {object} swapsFeatureFlags
|
||||
* @param {string} chainId
|
||||
* @returns object with 2 items: "swapsFeatureIsLive" and "useNewSwapsApi"
|
||||
*/
|
||||
export const getSwapsLivenessForNetwork = (swapsFeatureFlags = {}, chainId) => {
|
||||
const networkName = getNetworkNameByChainId(chainId);
|
||||
// Use old APIs for testnet.
|
||||
if (chainId === LOCALHOST_CHAIN_ID) {
|
||||
return {
|
||||
swapsFeatureIsLive: true,
|
||||
useNewSwapsApi: false,
|
||||
};
|
||||
}
|
||||
// If a network name is not found in the list of feature flags, disable Swaps.
|
||||
if (!swapsFeatureFlags[networkName]) {
|
||||
return {
|
||||
swapsFeatureIsLive: false,
|
||||
useNewSwapsApi: false,
|
||||
};
|
||||
}
|
||||
const isNetworkEnabledForNewApi =
|
||||
swapsFeatureFlags[networkName].extension_active;
|
||||
if (isNetworkEnabledForNewApi) {
|
||||
return {
|
||||
swapsFeatureIsLive: true,
|
||||
useNewSwapsApi: true,
|
||||
};
|
||||
}
|
||||
return {
|
||||
swapsFeatureIsLive: swapsFeatureFlags[networkName].fallback_to_v1,
|
||||
useNewSwapsApi: false,
|
||||
};
|
||||
};
|
||||
|
@ -1,14 +1,20 @@
|
||||
import nock from 'nock';
|
||||
import { MOCKS } from '../../../test/jest';
|
||||
import {
|
||||
ETH_SYMBOL,
|
||||
WETH_SYMBOL,
|
||||
MAINNET_CHAIN_ID,
|
||||
BSC_CHAIN_ID,
|
||||
POLYGON_CHAIN_ID,
|
||||
LOCALHOST_CHAIN_ID,
|
||||
RINKEBY_CHAIN_ID,
|
||||
} from '../../../shared/constants/network';
|
||||
import {
|
||||
SWAPS_CHAINID_CONTRACT_ADDRESS_MAP,
|
||||
ETH_WETH_CONTRACT_ADDRESS,
|
||||
ETHEREUM,
|
||||
POLYGON,
|
||||
BSC,
|
||||
} from '../../../shared/constants/swaps';
|
||||
import {
|
||||
TOKENS,
|
||||
@ -17,13 +23,14 @@ import {
|
||||
AGGREGATOR_METADATA,
|
||||
TOP_ASSETS,
|
||||
} from './swaps-util-test-constants';
|
||||
|
||||
import {
|
||||
fetchTradesInfo,
|
||||
fetchTokens,
|
||||
fetchAggregatorMetadata,
|
||||
fetchTopAssets,
|
||||
isContractAddressValid,
|
||||
getNetworkNameByChainId,
|
||||
getSwapsLivenessForNetwork,
|
||||
} from './swaps.util';
|
||||
|
||||
jest.mock('../../helpers/utils/storage-helpers.js', () => ({
|
||||
@ -372,4 +379,75 @@ describe('Swaps Util', () => {
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getNetworkNameByChainId', () => {
|
||||
it('returns "ethereum" for mainnet chain ID', () => {
|
||||
expect(getNetworkNameByChainId(MAINNET_CHAIN_ID)).toBe(ETHEREUM);
|
||||
});
|
||||
|
||||
it('returns "bsc" for mainnet chain ID', () => {
|
||||
expect(getNetworkNameByChainId(BSC_CHAIN_ID)).toBe(BSC);
|
||||
});
|
||||
|
||||
it('returns "polygon" for mainnet chain ID', () => {
|
||||
expect(getNetworkNameByChainId(POLYGON_CHAIN_ID)).toBe(POLYGON);
|
||||
});
|
||||
|
||||
it('returns an empty string for an unsupported network', () => {
|
||||
expect(getNetworkNameByChainId(RINKEBY_CHAIN_ID)).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSwapsLivenessForNetwork', () => {
|
||||
it('returns info that Swaps are enabled and cannot use API v2 for localhost chain ID', () => {
|
||||
const expectedSwapsLiveness = {
|
||||
swapsFeatureIsLive: true,
|
||||
useNewSwapsApi: false,
|
||||
};
|
||||
expect(
|
||||
getSwapsLivenessForNetwork(
|
||||
MOCKS.createFeatureFlagsResponse(),
|
||||
LOCALHOST_CHAIN_ID,
|
||||
),
|
||||
).toMatchObject(expectedSwapsLiveness);
|
||||
});
|
||||
|
||||
it('returns info that Swaps are disabled and cannot use API v2 if network name is not found', () => {
|
||||
const expectedSwapsLiveness = {
|
||||
swapsFeatureIsLive: false,
|
||||
useNewSwapsApi: false,
|
||||
};
|
||||
expect(
|
||||
getSwapsLivenessForNetwork(
|
||||
MOCKS.createFeatureFlagsResponse(),
|
||||
RINKEBY_CHAIN_ID,
|
||||
),
|
||||
).toMatchObject(expectedSwapsLiveness);
|
||||
});
|
||||
|
||||
it('returns info that Swaps are enabled and can use API v2 for mainnet chain ID', () => {
|
||||
const expectedSwapsLiveness = {
|
||||
swapsFeatureIsLive: true,
|
||||
useNewSwapsApi: true,
|
||||
};
|
||||
expect(
|
||||
getSwapsLivenessForNetwork(
|
||||
MOCKS.createFeatureFlagsResponse(),
|
||||
MAINNET_CHAIN_ID,
|
||||
),
|
||||
).toMatchObject(expectedSwapsLiveness);
|
||||
});
|
||||
|
||||
it('returns info that Swaps are enabled but can only use API v1 for mainnet chain ID', () => {
|
||||
const expectedSwapsLiveness = {
|
||||
swapsFeatureIsLive: true,
|
||||
useNewSwapsApi: false,
|
||||
};
|
||||
const swapsFeatureFlags = MOCKS.createFeatureFlagsResponse();
|
||||
swapsFeatureFlags[ETHEREUM].extension_active = false;
|
||||
expect(
|
||||
getSwapsLivenessForNetwork(swapsFeatureFlags, MAINNET_CHAIN_ID),
|
||||
).toMatchObject(expectedSwapsLiveness);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -2121,9 +2121,9 @@ export function setPendingTokens(pendingTokens) {
|
||||
|
||||
// Swaps
|
||||
|
||||
export function setSwapsLiveness(swapsFeatureIsLive) {
|
||||
export function setSwapsLiveness(swapsLiveness) {
|
||||
return async (dispatch) => {
|
||||
await promisifiedBackground.setSwapsLiveness(swapsFeatureIsLive);
|
||||
await promisifiedBackground.setSwapsLiveness(swapsLiveness);
|
||||
await forceUpdateMetamaskState(dispatch);
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user