From 444cce80e9a1eea6bda538394d10fd8d578fe120 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Wed, 3 Jul 2024 17:54:47 -0700 Subject: [PATCH] chore: set bridge destTopAssets from swaps-api response --- .../bridge/bridge-controller.test.ts | 13 +++++++- .../controllers/bridge/bridge-controller.ts | 13 ++++++++ app/scripts/controllers/bridge/constants.ts | 1 + app/scripts/controllers/bridge/types.ts | 1 + app/scripts/lib/setupSentry.js | 1 + test/e2e/default-fixture.js | 1 + ...rs-after-init-opt-in-background-state.json | 3 +- .../errors-after-init-opt-in-ui-state.json | 3 +- ...s-before-init-opt-in-background-state.json | 3 +- .../errors-before-init-opt-in-ui-state.json | 3 +- ui/ducks/bridge/selectors.test.ts | 33 +++++++++++++++++++ ui/ducks/bridge/selectors.ts | 3 ++ ui/pages/swaps/swaps.util.test.js | 20 +++++++++++ ui/pages/swaps/swaps.util.ts | 21 ++++++++---- 14 files changed, 107 insertions(+), 12 deletions(-) diff --git a/app/scripts/controllers/bridge/bridge-controller.test.ts b/app/scripts/controllers/bridge/bridge-controller.test.ts index 45dc2c15ab42..ff81aaeac14c 100644 --- a/app/scripts/controllers/bridge/bridge-controller.test.ts +++ b/app/scripts/controllers/bridge/bridge-controller.test.ts @@ -46,6 +46,14 @@ describe('BridgeController', function () { decimals: 16, }, ]); + nock(SWAPS_API_V2_BASE_URL) + .get('/networks/10/topAssets') + .reply(200, [ + { + address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', + symbol: 'ABC', + }, + ]); }); it('constructor should setup correctly', function () { @@ -66,7 +74,7 @@ describe('BridgeController', function () { ); }); - it('selectDestNetwork should set the bridge dest tokens', async function () { + it('selectDestNetwork should set the bridge dest tokens and top assets', async function () { await bridgeController.selectDestNetwork('0xa'); expect(bridgeController.state.bridgeState.destTokens).toStrictEqual({ '0x0000000000000000000000000000000000000000': { @@ -82,5 +90,8 @@ describe('BridgeController', function () { decimals: 16, }, }); + expect(bridgeController.state.bridgeState.destTopAssets).toStrictEqual([ + { address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', symbol: 'ABC' }, + ]); }); }); diff --git a/app/scripts/controllers/bridge/bridge-controller.ts b/app/scripts/controllers/bridge/bridge-controller.ts index 0c6b9b9ba735..abbba4715aa4 100644 --- a/app/scripts/controllers/bridge/bridge-controller.ts +++ b/app/scripts/controllers/bridge/bridge-controller.ts @@ -4,6 +4,7 @@ import { fetchBridgeFeatureFlags, fetchBridgeTokens, } from '../../../../ui/pages/bridge/bridge.util'; +import { fetchTopAssetsList } from '../../../../ui/pages/swaps/swaps.util'; import { BRIDGE_CONTROLLER_NAME, DEFAULT_BRIDGE_CONTROLLER_STATE, @@ -57,9 +58,21 @@ export default class BridgeController extends BaseController< }; selectDestNetwork = async (chainId: Hex) => { + await this.#setTopAssets(chainId, 'destTopAssets'); await this.#setTokens(chainId, 'destTokens'); }; + #setTopAssets = async ( + chainId: Hex, + stateKey: 'srcTopAssets' | 'destTopAssets', + ) => { + const { bridgeState } = this.state; + const topAssets = await fetchTopAssetsList(chainId); + this.update((_state) => { + _state.bridgeState = { ...bridgeState, [stateKey]: topAssets }; + }); + }; + #setTokens = async (chainId: Hex, stateKey: 'srcTokens' | 'destTokens') => { const { bridgeState } = this.state; const tokens = await fetchBridgeTokens(chainId); diff --git a/app/scripts/controllers/bridge/constants.ts b/app/scripts/controllers/bridge/constants.ts index 792337b2453e..e21071d71c4d 100644 --- a/app/scripts/controllers/bridge/constants.ts +++ b/app/scripts/controllers/bridge/constants.ts @@ -9,4 +9,5 @@ export const DEFAULT_BRIDGE_CONTROLLER_STATE: BridgeControllerState = { [BridgeFeatureFlagsKey.NETWORK_DEST_ALLOWLIST]: [], }, destTokens: {}, + destTopAssets: [], }; diff --git a/app/scripts/controllers/bridge/types.ts b/app/scripts/controllers/bridge/types.ts index d005b4fea807..ddc4668b3e53 100644 --- a/app/scripts/controllers/bridge/types.ts +++ b/app/scripts/controllers/bridge/types.ts @@ -22,6 +22,7 @@ export type BridgeFeatureFlags = { export type BridgeControllerState = { bridgeFeatureFlags: BridgeFeatureFlags; destTokens: Record; + destTopAssets: { address: string }[]; }; export enum BridgeUserAction { diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index e5656400ad09..71fce07f5d0f 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -135,6 +135,7 @@ export const SENTRY_BACKGROUND_STATE = { srcNetworkAllowlist: [], }, destTokens: {}, + destTopAssets: [], }, }, CronjobController: { diff --git a/test/e2e/default-fixture.js b/test/e2e/default-fixture.js index 95b2461ac598..13f9cfdf6dcb 100644 --- a/test/e2e/default-fixture.js +++ b/test/e2e/default-fixture.js @@ -124,6 +124,7 @@ function defaultFixture(inputChainId = CHAIN_IDS.LOCALHOST) { destNetworkAllowlist: ['0x1', '0xa', '0xe708'], }, destTokens: {}, + destTopAssets: [], }, }, CurrencyController: { diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json index 3608904c0e6c..632cfc865367 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json @@ -74,7 +74,8 @@ "2": "string" } }, - "destTokens": {} + "destTokens": {}, + "destTopAssets": {} } }, "CronjobController": { "jobs": "object" }, diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json index 108147eb6e6d..b2808892a77b 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -265,7 +265,8 @@ "2": "string" } }, - "destTokens": {} + "destTokens": {}, + "destTopAssets": {} }, "ensEntries": "object", "ensResolutionsByAddress": "object", diff --git a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json index a43245ba60bb..665861ac99b2 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json @@ -157,7 +157,8 @@ "2": "string" } }, - "destTokens": {} + "destTokens": {}, + "destTopAssets": {} } }, "SubjectMetadataController": { "subjectMetadata": "object" }, diff --git a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json index 6eb11fbd4867..863904ba183d 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json @@ -166,7 +166,8 @@ "2": "string" } }, - "destTokens": {} + "destTokens": {}, + "destTopAssets": {} } }, "TransactionController": { "transactions": "object" }, diff --git a/ui/ducks/bridge/selectors.test.ts b/ui/ducks/bridge/selectors.test.ts index c5fbe45cadb7..906b7ad275ac 100644 --- a/ui/ducks/bridge/selectors.test.ts +++ b/ui/ducks/bridge/selectors.test.ts @@ -15,6 +15,7 @@ import { getToChains, getToToken, getToTokens, + getToTopAssets, } from './selectors'; describe('Bridge selectors', () => { @@ -409,4 +410,36 @@ describe('Bridge selectors', () => { expect(result).toStrictEqual({}); }); }); + + describe('getToTopAssets', () => { + it('returns dest top assets from controller state when toChain is defined', () => { + const state = createBridgeMockStore( + {}, + { toChain: { chainId: '0x1' } }, + {}, + { + destTokens: { '0x00': { address: '0x00', symbol: 'TEST' } }, + destTopAssets: [{ address: '0x00', symbol: 'TEST' }], + }, + ); + const result = getToTopAssets(state as never); + + expect(result).toStrictEqual([{ address: '0x00', symbol: 'TEST' }]); + }); + + it('returns empty dest top assets from controller state when toChain is undefined', () => { + const state = createBridgeMockStore( + {}, + {}, + {}, + { + destTokens: { '0x00': { address: '0x00', symbol: 'TEST' } }, + destTopAssets: [{ address: '0x00', symbol: 'TEST' }], + }, + ); + const result = getToTopAssets(state as never); + + expect(result).toStrictEqual([]); + }); + }); }); diff --git a/ui/ducks/bridge/selectors.ts b/ui/ducks/bridge/selectors.ts index 1c566247fca8..e73dc4843ea4 100644 --- a/ui/ducks/bridge/selectors.ts +++ b/ui/ducks/bridge/selectors.ts @@ -84,6 +84,9 @@ export const getToToken = (state: BridgeAppState) => { export const getToTokens = (state: BridgeAppState) => { return state.bridge.toChain ? state.metamask.bridgeState.destTokens : {}; }; +export const getToTopAssets = (state: BridgeAppState) => { + return state.bridge.toChain ? state.metamask.bridgeState.destTopAssets : []; +}; export const getFromAmount = (state: BridgeAppState) => swapsSlice.getFromTokenInputValue(state); diff --git a/ui/pages/swaps/swaps.util.test.js b/ui/pages/swaps/swaps.util.test.js index d7ecace642ae..4b277ab56345 100644 --- a/ui/pages/swaps/swaps.util.test.js +++ b/ui/pages/swaps/swaps.util.test.js @@ -35,6 +35,7 @@ import { showRemainingTimeInMinAndSec, getFeeForSmartTransaction, formatSwapsValueForDisplay, + fetchTopAssetsList, } from './swaps.util'; jest.mock('../../../shared/lib/storage-helpers', () => ({ @@ -85,6 +86,25 @@ describe('Swaps Util', () => { }); }); + describe('fetchTopAssetsList', () => { + beforeEach(() => { + nock('https://swap.api.cx.metamask.io') + .persist() + .get('/networks/1/topAssets') + .reply(200, TOP_ASSETS); + }); + + it('should fetch top assets', async () => { + const result = await fetchTopAssetsList(CHAIN_IDS.MAINNET); + expect(result).toStrictEqual(TOP_ASSETS); + }); + + it('should fetch top assets on prod', async () => { + const result = await fetchTopAssetsList(CHAIN_IDS.MAINNET); + expect(result).toStrictEqual(TOP_ASSETS); + }); + }); + describe('fetchTopAssets', () => { beforeEach(() => { nock('https://swap.api.cx.metamask.io') diff --git a/ui/pages/swaps/swaps.util.ts b/ui/pages/swaps/swaps.util.ts index 33201dedf93d..e070944d44a0 100644 --- a/ui/pages/swaps/swaps.util.ts +++ b/ui/pages/swaps/swaps.util.ts @@ -199,9 +199,9 @@ export async function fetchAggregatorMetadata(chainId: any): Promise { return filteredAggregators; } -// TODO: Replace `any` with type -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export async function fetchTopAssets(chainId: any): Promise { +export async function fetchTopAssetsList( + chainId: string, +): Promise<{ address: string }[]> { const topAssetsUrl = getBaseApi('topAssets', chainId); const response = (await fetchWithCache({ @@ -210,14 +210,21 @@ export async function fetchTopAssets(chainId: any): Promise { fetchOptions: { method: 'GET', headers: clientIdHeader }, cacheOptions: { cacheRefreshTime: CACHE_REFRESH_FIVE_MINUTES }, })) || []; + const topAssetsList = response.filter((asset: { address: string }) => + validateData(TOP_ASSET_VALIDATORS, asset, topAssetsUrl), + ); + return topAssetsList; +} + +// TODO: Replace `any` with type +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export async function fetchTopAssets(chainId: any): Promise { + const response = await fetchTopAssetsList(chainId); const topAssetsMap = response.reduce( // TODO: Replace `any` with type // eslint-disable-next-line @typescript-eslint/no-explicit-any (_topAssetsMap: any, asset: { address: string }, index: number) => { - if (validateData(TOP_ASSET_VALIDATORS, asset, topAssetsUrl)) { - return { ..._topAssetsMap, [asset.address]: { index: String(index) } }; - } - return _topAssetsMap; + return { ..._topAssetsMap, [asset.address]: { index: String(index) } }; }, {}, );