From 7a46fdfe28a0d0694c6695e5a1462db1fb8ac591 Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Tue, 21 Jan 2025 19:42:02 -0700 Subject: [PATCH 001/124] feat: add-token-search-discovery-controller --- .../constants.ts | 4 ++ .../TokenSearchDiscoveryController/index.ts | 1 + .../TokenSearchDiscoveryController/types.ts | 7 ++++ .../utils.test.ts | 0 .../TokenSearchDiscoveryController/utils.ts | 37 +++++++++++++++++++ 5 files changed, 49 insertions(+) create mode 100644 app/core/Engine/controllers/TokenSearchDiscoveryController/constants.ts create mode 100644 app/core/Engine/controllers/TokenSearchDiscoveryController/index.ts create mode 100644 app/core/Engine/controllers/TokenSearchDiscoveryController/types.ts create mode 100644 app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts create mode 100644 app/core/Engine/controllers/TokenSearchDiscoveryController/utils.ts diff --git a/app/core/Engine/controllers/TokenSearchDiscoveryController/constants.ts b/app/core/Engine/controllers/TokenSearchDiscoveryController/constants.ts new file mode 100644 index 000000000000..cbd0d569bc3e --- /dev/null +++ b/app/core/Engine/controllers/TokenSearchDiscoveryController/constants.ts @@ -0,0 +1,4 @@ +export const PORTFOLIO_API_URL = { + dev: 'https://portfolio.dev-api.cx.metamask.io/', + prod: 'https://portfolio.api.cx.metamask.io/', +}; diff --git a/app/core/Engine/controllers/TokenSearchDiscoveryController/index.ts b/app/core/Engine/controllers/TokenSearchDiscoveryController/index.ts new file mode 100644 index 000000000000..6099020bebe1 --- /dev/null +++ b/app/core/Engine/controllers/TokenSearchDiscoveryController/index.ts @@ -0,0 +1 @@ +export { createTokenSearchDiscoveryController } from './utils'; diff --git a/app/core/Engine/controllers/TokenSearchDiscoveryController/types.ts b/app/core/Engine/controllers/TokenSearchDiscoveryController/types.ts new file mode 100644 index 000000000000..28d12f4a1805 --- /dev/null +++ b/app/core/Engine/controllers/TokenSearchDiscoveryController/types.ts @@ -0,0 +1,7 @@ +import { TokenSearchDiscoveryControllerMessenger } from '@metamask/token-search-discovery-controller/dist/token-search-discovery-controller.cjs'; +import { TokenSearchDiscoveryControllerState } from '@metamask/token-search-discovery-controller'; + +export interface TokenSearchDiscoveryControllerParams { + state?: Partial; + messenger: TokenSearchDiscoveryControllerMessenger; +} diff --git a/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts b/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.ts b/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.ts new file mode 100644 index 000000000000..af80f9cc0dfb --- /dev/null +++ b/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.ts @@ -0,0 +1,37 @@ +import Logger from '../../../../util/Logger'; +import { + TokenSearchApiService, + TokenSearchDiscoveryController, +} from '@metamask/token-search-discovery-controller'; +import { TokenSearchDiscoveryControllerParams } from './types'; +import { PORTFOLIO_API_URL } from './constants'; + +const getPortfolioApiBaseUrl = () => { + const env = process.env.METAMASK_ENVIRONMENT; + switch (env) { + case 'local': + return PORTFOLIO_API_URL.dev; + case 'pre-release': + case 'production': + return PORTFOLIO_API_URL.prod; + default: + return PORTFOLIO_API_URL.dev; + } +}; + +export const createTokenSearchDiscoveryController = ({ + state, + messenger, +}: TokenSearchDiscoveryControllerParams) => { + try { + const controller = new TokenSearchDiscoveryController({ + state, + messenger, + tokenSearchService: new TokenSearchApiService(getPortfolioApiBaseUrl()), + }); + return controller; + } catch (error) { + Logger.error(error as Error); + throw error; + } +}; From 5e57f42515505cca91dfd02d8aa806abd6ffb67a Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Tue, 21 Jan 2025 19:42:30 -0700 Subject: [PATCH 002/124] chore: update package and yarn lock --- package.json | 4 +++- yarn.lock | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 5230866d3d8b..1444744772cf 100644 --- a/package.json +++ b/package.json @@ -208,6 +208,7 @@ "@metamask/stake-sdk": "^0.3.0", "@metamask/swappable-obj-proxy": "^2.1.0", "@metamask/swaps-controller": "^12.0.0", + "@metamask/token-search-discovery-controller": "^1.0.0", "@metamask/transaction-controller": "^42.0.0", "@metamask/utils": "^11.0.1", "@ngraveio/bc-ur": "^1.1.6", @@ -601,7 +602,8 @@ "@react-native-firebase/app>firebase>@firebase/firestore>@grpc/proto-loader>protobufjs": false, "appium": false, "appium>@appium/support>sharp": false, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service>eip55>keccak": false + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service>eip55>keccak": false, + "@storybook/builder-webpack5>@storybook/core-common>esbuild": false } }, "packageManager": "yarn@1.22.22" diff --git a/yarn.lock b/yarn.lock index 61868ccf2e66..e7628352e3de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5514,6 +5514,14 @@ resolved "https://registry.yarnpkg.com/@metamask/test-dapp/-/test-dapp-8.9.0.tgz#bac680e8f0007b3a11440f7e311674d6457d37ed" integrity sha512-N/WfmdrzJm+xbpuqJsfMrlrAhiNDsllIpwt9gDDeEKDlQAfJnMtT9xvOvBJbXY7zgMdtGZuD+KY64jNKabbuVQ== +"@metamask/token-search-discovery-controller@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@metamask/token-search-discovery-controller/-/token-search-discovery-controller-1.0.0.tgz#f36a9db5d3003ad75374fa515c193acfba122512" + integrity sha512-Cbu8i1mEZViFsy/OyeOfOvDGOUUgIRxecU8PCO/IXW04gBj7vwQ9EZPLuW+/z8zqTeiwupd7JMx1rgZMMdma7Q== + dependencies: + "@metamask/base-controller" "^7.1.1" + "@metamask/utils" "^11.0.1" + "@metamask/transaction-controller@^42.0.0": version "42.0.0" resolved "https://registry.yarnpkg.com/@metamask/transaction-controller/-/transaction-controller-42.0.0.tgz#f5c035d018b7f72e4b21757bd075c6863a6301ca" From db3774a45c1c98c32fbb5c24001fc3dadd8b0770 Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Tue, 21 Jan 2025 19:44:44 -0700 Subject: [PATCH 003/124] feat: introduce to engine context --- app/core/Engine/Engine.ts | 13 +++++++++++++ app/core/Engine/types.ts | 6 ++++++ app/core/EngineService/EngineService.test.ts | 1 + app/core/EngineService/EngineService.ts | 4 ++++ 4 files changed, 24 insertions(+) diff --git a/app/core/Engine/Engine.ts b/app/core/Engine/Engine.ts index 740c50e7b456..8f0f7821f8fe 100644 --- a/app/core/Engine/Engine.ts +++ b/app/core/Engine/Engine.ts @@ -215,6 +215,7 @@ import { getGlobalNetworkClientId, } from '../../util/networks/global-network'; import { logEngineCreation } from './utils/logger'; +import { createTokenSearchDiscoveryController } from './controllers/TokenSearchDiscoveryController'; const NON_EMPTY = 'NON_EMPTY'; @@ -516,6 +517,17 @@ export class Engine { disabled: !isBasicFunctionalityToggleEnabled(), }); + const tokenSearchDiscoveryController = createTokenSearchDiscoveryController( + { + state: initialState.TokenSearchDiscoveryController, + messenger: this.controllerMessenger.getRestricted({ + name: 'TokenSearchDiscoveryController', + allowedActions: [], + allowedEvents: [], + }), + }, + ); + const phishingController = new PhishingController({ messenger: this.controllerMessenger.getRestricted({ name: 'PhishingController', @@ -1446,6 +1458,7 @@ export class Engine { isDecodeSignatureRequestEnabled: () => preferencesController.state.useTransactionSimulations, }), + TokenSearchDiscoveryController: tokenSearchDiscoveryController, LoggingController: loggingController, ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) SnapController: this.snapController, diff --git a/app/core/Engine/types.ts b/app/core/Engine/types.ts index 7153b4a4ff07..d0346272ae95 100644 --- a/app/core/Engine/types.ts +++ b/app/core/Engine/types.ts @@ -170,6 +170,10 @@ import { RemoteFeatureFlagControllerActions, RemoteFeatureFlagControllerEvents, } from '@metamask/remote-feature-flag-controller/dist/remote-feature-flag-controller.cjs'; +import { + TokenSearchDiscoveryController, + TokenSearchDiscoveryControllerState, +} from '@metamask/token-search-discovery-controller'; /** * Controllers that area always instantiated @@ -323,6 +327,7 @@ export type Controllers = { TokenListController: TokenListController; TokenDetectionController: TokenDetectionController; TokenRatesController: TokenRatesController; + TokenSearchDiscoveryController: TokenSearchDiscoveryController; TokensController: TokensController; TransactionController: TransactionController; SmartTransactionsController: SmartTransactionsController; @@ -363,6 +368,7 @@ export type EngineState = { PhishingController: PhishingControllerState; TokenBalancesController: TokenBalancesControllerState; TokenRatesController: TokenRatesControllerState; + TokenSearchDiscoveryController: TokenSearchDiscoveryControllerState; TransactionController: TransactionControllerState; SmartTransactionsController: SmartTransactionsControllerState; SwapsController: SwapsControllerState; diff --git a/app/core/EngineService/EngineService.test.ts b/app/core/EngineService/EngineService.test.ts index b35cbe45aba1..d8ef03c18381 100644 --- a/app/core/EngineService/EngineService.test.ts +++ b/app/core/EngineService/EngineService.test.ts @@ -77,6 +77,7 @@ jest.mock('../Engine', () => { NotificationServicesController: { subscribe: jest.fn() }, SelectedNetworkController: { subscribe: jest.fn() }, SignatureController: { subscribe: jest.fn() }, + TokenSearchDiscoveryController: { subscribe: jest.fn() }, }, }; return instance; diff --git a/app/core/EngineService/EngineService.ts b/app/core/EngineService/EngineService.ts index dd49c7013615..cd5d54bd14ae 100644 --- a/app/core/EngineService/EngineService.ts +++ b/app/core/EngineService/EngineService.ts @@ -201,6 +201,10 @@ export class EngineService { name: 'SignatureController', key: `${engine.context.SignatureController.name}:stateChange`, }, + { + name: 'TokenSearchDiscoveryController', + key: `${engine.context.TokenSearchDiscoveryController.name}:stateChange`, + }, ]; engine.controllerMessenger.subscribeOnceIf( From 02b04eddbca050456041d5bfdfdaa194424ecaf2 Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Tue, 21 Jan 2025 19:45:12 -0700 Subject: [PATCH 004/124] feat: hooks and selectors for FE --- .../useTokenSearchDiscovery.test.ts | 67 +++++++++++++++++++ .../useTokenSearchDiscovery.ts | 21 ++++++ .../tokenSearchDiscoveryController.ts | 10 +++ app/selectors/types.ts | 3 + 4 files changed, 101 insertions(+) create mode 100644 app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts create mode 100644 app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts create mode 100644 app/selectors/tokenSearchDiscoveryController.ts diff --git a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts new file mode 100644 index 000000000000..6a02fb5a51ec --- /dev/null +++ b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts @@ -0,0 +1,67 @@ +import { renderHook } from '@testing-library/react-hooks'; +import { useSelector } from 'react-redux'; +import Engine from '../../../core/Engine'; +import useTokenSearchDiscovery from './useTokenSearchDiscovery'; + +jest.mock('react-redux', () => ({ + ...jest.requireActual('react-redux'), + useSelector: jest.fn(), +})); + +jest.mock('../../../core/Engine', () => ({ + context: { + TokenSearchDiscoveryController: { + searchTokens: jest.fn(), + }, + }, +})); + +describe('useTokenSearchDiscovery', () => { + const mockRecentSearches = ['0x123', '0x456']; + + beforeEach(() => { + jest.clearAllMocks(); + (useSelector as jest.Mock).mockReturnValue(mockRecentSearches); + }); + + it('should return searchTokens function and recent searches', () => { + const { result } = renderHook(() => useTokenSearchDiscovery()); + + expect(result.current.searchTokens).toBeDefined(); + expect(result.current.recentSearches).toEqual(mockRecentSearches); + }); + + it('should call TokenSearchDiscoveryController.searchTokens with correct params', async () => { + const mockSearchParams = { + chainId: '0x1', + query: 'DAI', + limit: '10', + }; + const mockSearchResult = { tokens: [] }; + + ( + Engine.context.TokenSearchDiscoveryController.searchTokens as jest.Mock + ).mockResolvedValueOnce(mockSearchResult); + + const { result } = renderHook(() => useTokenSearchDiscovery()); + const response = await result.current.searchTokens(mockSearchParams); + + expect( + Engine.context.TokenSearchDiscoveryController.searchTokens, + ).toHaveBeenCalledWith(mockSearchParams); + expect(response).toEqual(mockSearchResult); + }); + + it('should handle search errors gracefully', async () => { + const mockError = new Error('Search failed'); + ( + Engine.context.TokenSearchDiscoveryController.searchTokens as jest.Mock + ).mockRejectedValueOnce(mockError); + + const { result } = renderHook(() => useTokenSearchDiscovery()); + + await expect(result.current.searchTokens({})).rejects.toThrow( + 'Search failed', + ); + }); +}); diff --git a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts new file mode 100644 index 000000000000..80f0625ab6da --- /dev/null +++ b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts @@ -0,0 +1,21 @@ +import { useCallback } from 'react'; +import { useSelector } from 'react-redux'; +import Engine from '../../../core/Engine'; +import { selectRecentTokenSearches } from '../../../selectors/tokenSearchDiscoveryController'; +import { TokenSearchParams } from '@metamask/token-search-discovery-controller/dist/types.cjs'; + +export const useTokenSearchDiscovery = () => { + const recentSearches = useSelector(selectRecentTokenSearches); + + const searchTokens = useCallback(async (params: TokenSearchParams) => { + const { TokenSearchDiscoveryController } = Engine.context; + return await TokenSearchDiscoveryController.searchTokens(params); + }, []); + + return { + searchTokens, + recentSearches, + }; +}; + +export default useTokenSearchDiscovery; diff --git a/app/selectors/tokenSearchDiscoveryController.ts b/app/selectors/tokenSearchDiscoveryController.ts new file mode 100644 index 000000000000..760b705e47a9 --- /dev/null +++ b/app/selectors/tokenSearchDiscoveryController.ts @@ -0,0 +1,10 @@ +import { createSelector } from 'reselect'; +import { RootState } from '../reducers'; + +const selectTokenSearchDiscoveryControllerState = (state: RootState) => + state.engine.backgroundState.TokenSearchDiscoveryController; + +export const selectRecentTokenSearches = createSelector( + selectTokenSearchDiscoveryControllerState, + (state) => state.recentSearches, +); diff --git a/app/selectors/types.ts b/app/selectors/types.ts index ecfedfb3ac43..af524cb1667a 100644 --- a/app/selectors/types.ts +++ b/app/selectors/types.ts @@ -18,9 +18,11 @@ import { GasFeeController } from '@metamask/gas-fee-controller'; import { PPOMState } from '@metamask/ppom-validator'; import { ApprovalControllerState } from '@metamask/approval-controller'; import { AccountsControllerState } from '@metamask/accounts-controller'; +import { TokenSearchDiscoveryControllerState } from '@metamask/token-search-discovery-controller'; ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) import { SnapController } from '@metamask/snaps-controllers'; ///: END:ONLY_INCLUDE_IF + export interface EngineState { engine: { backgroundState: { @@ -45,6 +47,7 @@ export interface EngineState { TokensController: TokensControllerState; ApprovalController: ApprovalControllerState; AccountsController: AccountsControllerState; + TokenSearchDiscoveryController: TokenSearchDiscoveryControllerState; }; }; } From a0a125fad351355c349332196f949d6faaae0e2f Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Tue, 21 Jan 2025 19:54:29 -0700 Subject: [PATCH 005/124] chore: remove empty tests --- .../controllers/TokenSearchDiscoveryController/utils.test.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts diff --git a/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts b/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts deleted file mode 100644 index e69de29bb2d1..000000000000 From d85c0f5128ecc5dddb6a539b8748c979e5e1a544 Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Tue, 21 Jan 2025 20:07:53 -0700 Subject: [PATCH 006/124] chore: update test json --- app/util/test/initial-background-state.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/util/test/initial-background-state.json b/app/util/test/initial-background-state.json index 953fb97a48b4..25b1e4054c89 100644 --- a/app/util/test/initial-background-state.json +++ b/app/util/test/initial-background-state.json @@ -175,6 +175,10 @@ "TokenRatesController": { "marketData": {} }, + "TokenSearchDiscoveryController": { + "lastSearchTimestamp": null, + "recentSearches": [] + }, "TransactionController": { "lastFetchedBlockNumbers": {}, "methodData": {}, From ab3aa5b64d22e2eccdcbec0157909bad10c24ba9 Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Wed, 22 Jan 2025 08:52:49 -0700 Subject: [PATCH 007/124] chore: update failing snapshot --- app/util/logs/__snapshots__/index.test.ts.snap | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/util/logs/__snapshots__/index.test.ts.snap b/app/util/logs/__snapshots__/index.test.ts.snap index 27c9ea11dcad..7be739089eb4 100644 --- a/app/util/logs/__snapshots__/index.test.ts.snap +++ b/app/util/logs/__snapshots__/index.test.ts.snap @@ -310,6 +310,10 @@ exports[`logs :: generateStateLogs generates a valid json export 1`] = ` "TokenRatesController": { "marketData": {}, }, + "TokenSearchDiscoveryController": { + "lastSearchTimestamp": null, + "recentSearches": [], + }, "TransactionController": { "lastFetchedBlockNumbers": {}, "methodData": {}, From 6dde5641182ea69712063d1731f979bde5e44344 Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Tue, 28 Jan 2025 13:48:36 -0700 Subject: [PATCH 008/124] chore: add controller factory tests --- .../utils.test.ts | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts diff --git a/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts b/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts new file mode 100644 index 000000000000..8296f10a8a95 --- /dev/null +++ b/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts @@ -0,0 +1,83 @@ +import { createTokenSearchDiscoveryController } from './utils'; +import { ControllerMessenger } from '@metamask/base-controller'; +import { + TokenSearchDiscoveryControllerMessenger, + TokenSearchDiscoveryControllerState, +} from '@metamask/token-search-discovery-controller/dist/token-search-discovery-controller.cjs'; + +describe('TokenSearchDiscoveryController utils', () => { + let messenger: TokenSearchDiscoveryControllerMessenger; + + beforeEach(() => { + messenger = + new ControllerMessenger() as unknown as TokenSearchDiscoveryControllerMessenger; + }); + + describe('createTokenSearchDiscoveryController', () => { + it('creates controller with initial undefined state', () => { + const controller = createTokenSearchDiscoveryController({ + state: undefined, + messenger: messenger, + }); + + expect(controller).toBeDefined(); + expect(controller.state).toStrictEqual({ + lastSearchTimestamp: null, + recentSearches: [], + }); + }); + + it('internal state matches initial state', () => { + const initialState: TokenSearchDiscoveryControllerState = { + lastSearchTimestamp: 123456789, + recentSearches: [ + { + tokenAddress: '0x123', + chainId: '0x1', + name: 'Test Token 1', + symbol: 'TEST1', + usdPrice: 1.0, + usdPricePercentChange: { + oneDay: 0.0, + }, + }, + { + tokenAddress: '0x456', + chainId: '0x1', + name: 'Test Token 2', + symbol: 'TEST2', + usdPrice: 2.0, + usdPricePercentChange: { + oneDay: 0.0, + }, + }, + ], + }; + + const controller = createTokenSearchDiscoveryController({ + state: initialState, + messenger: messenger, + }); + + expect(controller.state).toStrictEqual(initialState); + }); + + it('controller keeps initial extra data in its state', () => { + const initialState = { + extraData: true, + }; + + const controller = createTokenSearchDiscoveryController({ + // @ts-expect-error giving a wrong initial state + state: initialState, + messenger: messenger, + }); + + expect(controller.state).toStrictEqual({ + lastSearchTimestamp: null, + recentSearches: [], + extraData: true, + }); + }); + }); +}); From d8019b707fbd0de3aa6480745f4e3f55ff082c47 Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Tue, 28 Jan 2025 13:58:22 -0700 Subject: [PATCH 009/124] chore: linter fix for obj property shorthand --- .../TokenSearchDiscoveryController/utils.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts b/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts index 8296f10a8a95..76583a1b8a0c 100644 --- a/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts +++ b/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts @@ -17,7 +17,7 @@ describe('TokenSearchDiscoveryController utils', () => { it('creates controller with initial undefined state', () => { const controller = createTokenSearchDiscoveryController({ state: undefined, - messenger: messenger, + messenger, }); expect(controller).toBeDefined(); @@ -56,7 +56,7 @@ describe('TokenSearchDiscoveryController utils', () => { const controller = createTokenSearchDiscoveryController({ state: initialState, - messenger: messenger, + messenger, }); expect(controller.state).toStrictEqual(initialState); @@ -70,7 +70,7 @@ describe('TokenSearchDiscoveryController utils', () => { const controller = createTokenSearchDiscoveryController({ // @ts-expect-error giving a wrong initial state state: initialState, - messenger: messenger, + messenger, }); expect(controller.state).toStrictEqual({ From a6214b50285bdd9b08177c36180a316f5d4db73f Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Tue, 28 Jan 2025 14:51:35 -0700 Subject: [PATCH 010/124] chore: code coverage and resilient selector --- .../utils.test.ts | 107 ++++++++++++++++++ .../tokenSearchDiscoveryController.test.ts | 46 ++++++++ .../tokenSearchDiscoveryController.ts | 2 +- 3 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 app/selectors/tokenSearchDiscoveryController.test.ts diff --git a/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts b/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts index 76583a1b8a0c..9971ce781bc1 100644 --- a/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts +++ b/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts @@ -4,6 +4,32 @@ import { TokenSearchDiscoveryControllerMessenger, TokenSearchDiscoveryControllerState, } from '@metamask/token-search-discovery-controller/dist/token-search-discovery-controller.cjs'; +import Logger from '../../../../util/Logger'; +import { TokenSearchApiService } from '@metamask/token-search-discovery-controller'; + +const mockError = new Error('Controller creation failed'); + +// Top-level mocks +jest.mock('../../../../util/Logger', () => ({ + error: jest.fn(), +})); + +jest.mock('@metamask/token-search-discovery-controller', () => ({ + TokenSearchApiService: jest.fn(), + TokenSearchDiscoveryController: jest + .fn() + .mockImplementation(function ( + this: { state: any }, + params: { state?: any }, + ) { + this.state = { + lastSearchTimestamp: null, + recentSearches: [], + ...params.state, + }; + return this; + }), +})); describe('TokenSearchDiscoveryController utils', () => { let messenger: TokenSearchDiscoveryControllerMessenger; @@ -14,6 +40,11 @@ describe('TokenSearchDiscoveryController utils', () => { }); describe('createTokenSearchDiscoveryController', () => { + afterEach(() => { + jest.clearAllMocks(); + jest.resetModules(); + }); + it('creates controller with initial undefined state', () => { const controller = createTokenSearchDiscoveryController({ state: undefined, @@ -79,5 +110,81 @@ describe('TokenSearchDiscoveryController utils', () => { extraData: true, }); }); + + it('logs and rethrows error when controller creation fails', () => { + (TokenSearchApiService as jest.Mock).mockImplementation(() => { + throw mockError; + }); + + expect(() => + createTokenSearchDiscoveryController({ + state: undefined, + messenger, + }), + ).toThrow(mockError); + + expect(Logger.error).toHaveBeenCalledWith(mockError); + }); + }); + + describe('getPortfolioApiBaseUrl', () => { + const originalEnv = process.env; + + beforeEach(() => { + jest.resetModules(); + process.env = { ...originalEnv }; + }); + + afterEach(() => { + process.env = originalEnv; + }); + + it('returns dev URL when environment is local', () => { + process.env.METAMASK_ENVIRONMENT = 'local'; + const { + createTokenSearchDiscoveryController: freshCreate, + } = require('./utils'); + const controller = freshCreate({ + state: undefined, + messenger, + }); + expect(controller.state).toBeDefined(); + }); + + it('returns prod URL when environment is pre-release', () => { + process.env.METAMASK_ENVIRONMENT = 'pre-release'; + const { + createTokenSearchDiscoveryController: freshCreate, + } = require('./utils'); + const controller = freshCreate({ + state: undefined, + messenger, + }); + expect(controller.state).toBeDefined(); + }); + + it('returns prod URL when environment is production', () => { + process.env.METAMASK_ENVIRONMENT = 'production'; + const { + createTokenSearchDiscoveryController: freshCreate, + } = require('./utils'); + const controller = freshCreate({ + state: undefined, + messenger, + }); + expect(controller.state).toBeDefined(); + }); + + it('returns dev URL when environment is not recognized', () => { + process.env.METAMASK_ENVIRONMENT = 'unknown'; + const { + createTokenSearchDiscoveryController: freshCreate, + } = require('./utils'); + const controller = freshCreate({ + state: undefined, + messenger, + }); + expect(controller.state).toBeDefined(); + }); }); }); diff --git a/app/selectors/tokenSearchDiscoveryController.test.ts b/app/selectors/tokenSearchDiscoveryController.test.ts new file mode 100644 index 000000000000..a9b946526ca7 --- /dev/null +++ b/app/selectors/tokenSearchDiscoveryController.test.ts @@ -0,0 +1,46 @@ +import { RootState } from '../reducers'; +import { selectRecentTokenSearches } from './tokenSearchDiscoveryController'; + +describe('Token Search Discovery Controller Selectors', () => { + const mockRecentSearches = ['ETH', 'USDC', 'DAI']; + + const mockState = { + engine: { + backgroundState: { + TokenSearchDiscoveryController: { + recentSearches: mockRecentSearches, + }, + }, + }, + } as unknown as RootState; + + describe('selectRecentTokenSearches', () => { + it('returns recent token searches from state', () => { + expect(selectRecentTokenSearches(mockState)).toEqual(mockRecentSearches); + }); + + it('returns empty array when no recent searches exist', () => { + const stateWithoutSearches = { + engine: { + backgroundState: { + TokenSearchDiscoveryController: { + recentSearches: [], + }, + }, + }, + } as unknown as RootState; + + expect(selectRecentTokenSearches(stateWithoutSearches)).toEqual([]); + }); + + it('returns empty array when TokenSearchDiscoveryController is not initialized', () => { + const stateWithoutController = { + engine: { + backgroundState: {}, + }, + } as unknown as RootState; + + expect(selectRecentTokenSearches(stateWithoutController)).toEqual([]); + }); + }); +}); diff --git a/app/selectors/tokenSearchDiscoveryController.ts b/app/selectors/tokenSearchDiscoveryController.ts index 760b705e47a9..4aff774c7b58 100644 --- a/app/selectors/tokenSearchDiscoveryController.ts +++ b/app/selectors/tokenSearchDiscoveryController.ts @@ -6,5 +6,5 @@ const selectTokenSearchDiscoveryControllerState = (state: RootState) => export const selectRecentTokenSearches = createSelector( selectTokenSearchDiscoveryControllerState, - (state) => state.recentSearches, + (state) => state?.recentSearches ?? [], ); From b767d504aed8560efd37091f24019c0f3593ad5d Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Tue, 28 Jan 2025 15:12:32 -0700 Subject: [PATCH 011/124] chore: use requireActual method per convention and linter --- .../utils.test.ts | 64 ++++++++++--------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts b/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts index 9971ce781bc1..1698121c6a83 100644 --- a/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts +++ b/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts @@ -19,8 +19,8 @@ jest.mock('@metamask/token-search-discovery-controller', () => ({ TokenSearchDiscoveryController: jest .fn() .mockImplementation(function ( - this: { state: any }, - params: { state?: any }, + this: { state: TokenSearchDiscoveryControllerState }, + params: { state?: TokenSearchDiscoveryControllerState }, ) { this.state = { lastSearchTimestamp: null, @@ -141,50 +141,54 @@ describe('TokenSearchDiscoveryController utils', () => { it('returns dev URL when environment is local', () => { process.env.METAMASK_ENVIRONMENT = 'local'; - const { - createTokenSearchDiscoveryController: freshCreate, - } = require('./utils'); - const controller = freshCreate({ - state: undefined, - messenger, + jest.isolateModules(() => { + const { createTokenSearchDiscoveryController: freshCreate } = + jest.requireActual('./utils'); + const controller = freshCreate({ + state: undefined, + messenger, + }); + expect(controller.state).toBeDefined(); }); - expect(controller.state).toBeDefined(); }); it('returns prod URL when environment is pre-release', () => { process.env.METAMASK_ENVIRONMENT = 'pre-release'; - const { - createTokenSearchDiscoveryController: freshCreate, - } = require('./utils'); - const controller = freshCreate({ - state: undefined, - messenger, + jest.isolateModules(() => { + const { createTokenSearchDiscoveryController: freshCreate } = + jest.requireActual('./utils'); + const controller = freshCreate({ + state: undefined, + messenger, + }); + expect(controller.state).toBeDefined(); }); - expect(controller.state).toBeDefined(); }); it('returns prod URL when environment is production', () => { process.env.METAMASK_ENVIRONMENT = 'production'; - const { - createTokenSearchDiscoveryController: freshCreate, - } = require('./utils'); - const controller = freshCreate({ - state: undefined, - messenger, + jest.isolateModules(() => { + const { createTokenSearchDiscoveryController: freshCreate } = + jest.requireActual('./utils'); + const controller = freshCreate({ + state: undefined, + messenger, + }); + expect(controller.state).toBeDefined(); }); - expect(controller.state).toBeDefined(); }); it('returns dev URL when environment is not recognized', () => { process.env.METAMASK_ENVIRONMENT = 'unknown'; - const { - createTokenSearchDiscoveryController: freshCreate, - } = require('./utils'); - const controller = freshCreate({ - state: undefined, - messenger, + jest.isolateModules(() => { + const { createTokenSearchDiscoveryController: freshCreate } = + jest.requireActual('./utils'); + const controller = freshCreate({ + state: undefined, + messenger, + }); + expect(controller.state).toBeDefined(); }); - expect(controller.state).toBeDefined(); }); }); }); From f89c331faa04eebd5848308b2879a5202ede9033 Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Thu, 30 Jan 2025 10:39:47 -0700 Subject: [PATCH 012/124] feat: introduce generic debounce hook to searchTokens --- .../useTokenSearchDiscovery/useTokenSearchDiscovery.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts index 80f0625ab6da..fd635a0db882 100644 --- a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts +++ b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts @@ -1,15 +1,19 @@ import { useCallback } from 'react'; import { useSelector } from 'react-redux'; +import { useDebouncedValue } from '../useDebouncedValue'; import Engine from '../../../core/Engine'; import { selectRecentTokenSearches } from '../../../selectors/tokenSearchDiscoveryController'; -import { TokenSearchParams } from '@metamask/token-search-discovery-controller/dist/types.cjs'; +import type { TokenSearchParams } from '@metamask/token-search-discovery-controller/dist/types.d.cts'; + +const SEARCH_DEBOUNCE_DELAY = 300; export const useTokenSearchDiscovery = () => { const recentSearches = useSelector(selectRecentTokenSearches); const searchTokens = useCallback(async (params: TokenSearchParams) => { + const debouncedParams = useDebouncedValue(params, SEARCH_DEBOUNCE_DELAY); const { TokenSearchDiscoveryController } = Engine.context; - return await TokenSearchDiscoveryController.searchTokens(params); + return await TokenSearchDiscoveryController.searchTokens(debouncedParams); }, []); return { From 2e2dea82d13e77c8024baf47309b31fca4057771 Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Thu, 30 Jan 2025 11:05:22 -0700 Subject: [PATCH 013/124] chore: use lodash debounce directly, update test --- .../useTokenSearchDiscovery.test.ts | 7 ++++--- .../useTokenSearchDiscovery.ts | 14 ++++++++------ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts index 6a02fb5a51ec..db9288e8a313 100644 --- a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts +++ b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts @@ -2,6 +2,7 @@ import { renderHook } from '@testing-library/react-hooks'; import { useSelector } from 'react-redux'; import Engine from '../../../core/Engine'; import useTokenSearchDiscovery from './useTokenSearchDiscovery'; +import { TokenSearchParams } from '@metamask/token-search-discovery-controller/dist/types.cjs'; jest.mock('react-redux', () => ({ ...jest.requireActual('react-redux'), @@ -32,9 +33,9 @@ describe('useTokenSearchDiscovery', () => { }); it('should call TokenSearchDiscoveryController.searchTokens with correct params', async () => { - const mockSearchParams = { - chainId: '0x1', - query: 'DAI', + const mockSearchParams: TokenSearchParams = { + chains: ['0x1'], + name: 'DAI', limit: '10', }; const mockSearchResult = { tokens: [] }; diff --git a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts index fd635a0db882..276d9e80da2d 100644 --- a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts +++ b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts @@ -1,6 +1,6 @@ import { useCallback } from 'react'; import { useSelector } from 'react-redux'; -import { useDebouncedValue } from '../useDebouncedValue'; +import { debounce } from 'lodash'; import Engine from '../../../core/Engine'; import { selectRecentTokenSearches } from '../../../selectors/tokenSearchDiscoveryController'; import type { TokenSearchParams } from '@metamask/token-search-discovery-controller/dist/types.d.cts'; @@ -10,11 +10,13 @@ const SEARCH_DEBOUNCE_DELAY = 300; export const useTokenSearchDiscovery = () => { const recentSearches = useSelector(selectRecentTokenSearches); - const searchTokens = useCallback(async (params: TokenSearchParams) => { - const debouncedParams = useDebouncedValue(params, SEARCH_DEBOUNCE_DELAY); - const { TokenSearchDiscoveryController } = Engine.context; - return await TokenSearchDiscoveryController.searchTokens(debouncedParams); - }, []); + const searchTokens = useCallback( + debounce(async (params: TokenSearchParams) => { + const { TokenSearchDiscoveryController } = Engine.context; + return await TokenSearchDiscoveryController.searchTokens(params); + }, SEARCH_DEBOUNCE_DELAY), + [], + ); return { searchTokens, From 9e0ed48f973acc62fe27cfecd3ce2388c1b93d4b Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Thu, 30 Jan 2025 11:43:37 -0700 Subject: [PATCH 014/124] feat: manage result state internally --- .../useTokenSearchDiscovery.test.ts | 50 ++++++++++++------- .../useTokenSearchDiscovery.ts | 40 +++++++++++---- 2 files changed, 63 insertions(+), 27 deletions(-) diff --git a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts index db9288e8a313..53a6d69ec875 100644 --- a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts +++ b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts @@ -1,5 +1,4 @@ -import { renderHook } from '@testing-library/react-hooks'; -import { useSelector } from 'react-redux'; +import { act, renderHook } from '@testing-library/react-hooks'; import Engine from '../../../core/Engine'; import useTokenSearchDiscovery from './useTokenSearchDiscovery'; import { TokenSearchParams } from '@metamask/token-search-discovery-controller/dist/types.cjs'; @@ -18,42 +17,51 @@ jest.mock('../../../core/Engine', () => ({ })); describe('useTokenSearchDiscovery', () => { - const mockRecentSearches = ['0x123', '0x456']; - beforeEach(() => { jest.clearAllMocks(); - (useSelector as jest.Mock).mockReturnValue(mockRecentSearches); + jest.useFakeTimers(); }); - it('should return searchTokens function and recent searches', () => { - const { result } = renderHook(() => useTokenSearchDiscovery()); - - expect(result.current.searchTokens).toBeDefined(); - expect(result.current.recentSearches).toEqual(mockRecentSearches); + afterEach(() => { + jest.useRealTimers(); }); - it('should call TokenSearchDiscoveryController.searchTokens with correct params', async () => { + it('should update states correctly when searching tokens', async () => { const mockSearchParams: TokenSearchParams = { chains: ['0x1'], name: 'DAI', limit: '10', }; - const mockSearchResult = { tokens: [] }; + const mockSearchResult = [{ name: 'DAI', address: '0x123' }]; ( Engine.context.TokenSearchDiscoveryController.searchTokens as jest.Mock ).mockResolvedValueOnce(mockSearchResult); const { result } = renderHook(() => useTokenSearchDiscovery()); - const response = await result.current.searchTokens(mockSearchParams); + // Initial state + expect(result.current.isLoading).toBe(false); + expect(result.current.error).toBe(null); + expect(result.current.results).toEqual([]); + + // Call search + await act(async () => { + result.current.searchTokens(mockSearchParams); + jest.advanceTimersByTime(300); + await Promise.resolve(); + }); + + // Final state + expect(result.current.isLoading).toBe(false); + expect(result.current.error).toBe(null); + expect(result.current.results).toEqual(mockSearchResult); expect( Engine.context.TokenSearchDiscoveryController.searchTokens, ).toHaveBeenCalledWith(mockSearchParams); - expect(response).toEqual(mockSearchResult); }); - it('should handle search errors gracefully', async () => { + it('should handle search errors', async () => { const mockError = new Error('Search failed'); ( Engine.context.TokenSearchDiscoveryController.searchTokens as jest.Mock @@ -61,8 +69,14 @@ describe('useTokenSearchDiscovery', () => { const { result } = renderHook(() => useTokenSearchDiscovery()); - await expect(result.current.searchTokens({})).rejects.toThrow( - 'Search failed', - ); + await act(async () => { + result.current.searchTokens({}); + jest.advanceTimersByTime(300); + await Promise.resolve(); + }); + + expect(result.current.isLoading).toBe(false); + expect(result.current.error).toEqual(mockError); + expect(result.current.results).toEqual([]); }); }); diff --git a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts index 276d9e80da2d..6e3bd764e521 100644 --- a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts +++ b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts @@ -1,26 +1,48 @@ -import { useCallback } from 'react'; +import { useCallback, useState } from 'react'; import { useSelector } from 'react-redux'; import { debounce } from 'lodash'; import Engine from '../../../core/Engine'; import { selectRecentTokenSearches } from '../../../selectors/tokenSearchDiscoveryController'; -import type { TokenSearchParams } from '@metamask/token-search-discovery-controller/dist/types.d.cts'; +import type { + TokenSearchParams, + TokenSearchResponseItem, +} from '@metamask/token-search-discovery-controller/dist/types.d.cts'; const SEARCH_DEBOUNCE_DELAY = 300; export const useTokenSearchDiscovery = () => { const recentSearches = useSelector(selectRecentTokenSearches); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const [results, setResults] = useState([]); - const searchTokens = useCallback( - debounce(async (params: TokenSearchParams) => { - const { TokenSearchDiscoveryController } = Engine.context; - return await TokenSearchDiscoveryController.searchTokens(params); - }, SEARCH_DEBOUNCE_DELAY), - [], - ); + const searchTokens = useCallback((params: TokenSearchParams) => { + setIsLoading(true); + setError(null); + + const debouncedSearch = debounce(async () => { + try { + const { TokenSearchDiscoveryController } = Engine.context; + const result = await TokenSearchDiscoveryController.searchTokens( + params, + ); + setResults(result); + } catch (error) { + setError(error as Error); + } finally { + setIsLoading(false); + } + }, SEARCH_DEBOUNCE_DELAY); + + debouncedSearch(); + }, []); return { searchTokens, recentSearches, + isLoading, + error, + results, }; }; From 25b495b8f9abe0f0a61a1f82d6e35af218296c72 Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Thu, 30 Jan 2025 11:47:29 -0700 Subject: [PATCH 015/124] chore: fix shadow --- .../hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts index 6e3bd764e521..7e9c1b650bbc 100644 --- a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts +++ b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts @@ -27,8 +27,8 @@ export const useTokenSearchDiscovery = () => { params, ); setResults(result); - } catch (error) { - setError(error as Error); + } catch (err) { + setError(err as Error); } finally { setIsLoading(false); } From 8f2f3e79dffb9210a40a0934b0382d4571a976ed Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Fri, 31 Jan 2025 12:39:17 -0700 Subject: [PATCH 016/124] chore: add portfolio team to codeowners --- .github/CODEOWNERS | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index bd45695eab5d..0c4acb5c8cfb 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -56,10 +56,12 @@ app/components/UI/Swaps @MetaMask/swaps-engineers # Notifications Team app/components/Views/Notifications @MetaMask/notifications app/components/Views/Settings/NotificationsSettings @MetaMask/notifications -**/Notifications/** @MetaMask/notifications -**/Notification/** @MetaMask/notifications -**/notifications/** @MetaMask/notifications -**/notification/** @MetaMask/notifications +app/components/UI/Notifications @MetaMask/notifications +app/reducers/notification @MetaMask/notifications +app/actions/notification @MetaMask/notifications +app/selectors/notification @MetaMask/notifications +app/util/notifications @MetaMask/notifications +app/store/util/notifications @MetaMask/notifications # Identity Team app/actions/identity @MetaMask/identity @@ -71,6 +73,10 @@ e2e/specs/identity @MetaMask/identity ses.cjs @MetaMask/supply-chain patches/react-native+0.*.patch @MetaMask/supply-chain +# Portfolio Team +app/components/hooks/useTokenSearchDiscovery @MetaMask/portfolio +app/core/Engine/controllers/TokenSearchDiscoveryController @MetaMask/portfolio + # Snaps Team **/snaps/** @MetaMask/snaps-devs **/Snaps/** @MetaMask/snaps-devs From 43dce11ef4478752d2ccc144e90f211e015925b8 Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Fri, 31 Jan 2025 12:46:25 -0700 Subject: [PATCH 017/124] chore: fix unintended notification team codeowner update --- .github/CODEOWNERS | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0c4acb5c8cfb..3e43d7f88782 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -56,12 +56,10 @@ app/components/UI/Swaps @MetaMask/swaps-engineers # Notifications Team app/components/Views/Notifications @MetaMask/notifications app/components/Views/Settings/NotificationsSettings @MetaMask/notifications -app/components/UI/Notifications @MetaMask/notifications -app/reducers/notification @MetaMask/notifications -app/actions/notification @MetaMask/notifications -app/selectors/notification @MetaMask/notifications -app/util/notifications @MetaMask/notifications -app/store/util/notifications @MetaMask/notifications +**/Notifications/** @MetaMask/notifications +**/Notification/** @MetaMask/notifications +**/notifications/** @MetaMask/notifications +**/notification/** @MetaMask/notifications # Identity Team app/actions/identity @MetaMask/identity From cd4d72e42c2450e5cccac470f91b7f0ac21aac76 Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Mon, 3 Feb 2025 16:19:45 -0700 Subject: [PATCH 018/124] add tokensearchdiscoverycontroller to bg state for state change subscription --- app/core/Engine/constants.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/core/Engine/constants.ts b/app/core/Engine/constants.ts index cc33feb0f69f..820f46544e29 100644 --- a/app/core/Engine/constants.ts +++ b/app/core/Engine/constants.ts @@ -32,6 +32,7 @@ export const BACKGROUND_STATE_CHANGE_EVENT_NAMES = [ 'TokenListController:stateChange', 'TokenRatesController:stateChange', 'TokensController:stateChange', + 'TokenSearchDiscoveryController:stateChange', 'TransactionController:stateChange', ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) 'SnapController:stateChange', From c076d2470276633773f495facd8ee9457a1664e3 Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Mon, 3 Feb 2025 16:22:42 -0700 Subject: [PATCH 019/124] add controller to the get state() --- app/core/Engine/Engine.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/core/Engine/Engine.ts b/app/core/Engine/Engine.ts index d8acd7fd24f4..09afc8ceac43 100644 --- a/app/core/Engine/Engine.ts +++ b/app/core/Engine/Engine.ts @@ -2096,6 +2096,7 @@ export default { PPOMController, TokenBalancesController, TokenRatesController, + TokenSearchDiscoveryController, TransactionController, SmartTransactionsController, SwapsController, @@ -2131,6 +2132,7 @@ export default { PreferencesController, TokenBalancesController, TokenRatesController, + TokenSearchDiscoveryController, TokensController, TransactionController, SmartTransactionsController, @@ -2183,7 +2185,8 @@ export default { keyringState: KeyringControllerState | null = null, metaMetricsId?: string, ) { - instance = Engine.instance || new Engine(state, keyringState, metaMetricsId); + instance = + Engine.instance || new Engine(state, keyringState, metaMetricsId); Object.freeze(instance); return instance; }, From 8c73868a3a2682fe72516723dc7dfd5d1e6d023a Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Mon, 3 Feb 2025 16:33:44 -0700 Subject: [PATCH 020/124] add missing types for actions and events --- app/core/Engine/types.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/core/Engine/types.ts b/app/core/Engine/types.ts index d0346272ae95..252ce7e569fb 100644 --- a/app/core/Engine/types.ts +++ b/app/core/Engine/types.ts @@ -174,6 +174,10 @@ import { TokenSearchDiscoveryController, TokenSearchDiscoveryControllerState, } from '@metamask/token-search-discovery-controller'; +import { + TokenSearchDiscoveryControllerActions, + TokenSearchDiscoveryControllerEvents, +} from '@metamask/token-search-discovery-controller/dist/token-search-discovery-controller.cjs'; /** * Controllers that area always instantiated @@ -243,7 +247,8 @@ type GlobalActions = | SelectedNetworkControllerActions | SmartTransactionsControllerActions | AssetsContractControllerActions - | RemoteFeatureFlagControllerActions; + | RemoteFeatureFlagControllerActions + | TokenSearchDiscoveryControllerActions; type GlobalEvents = | ComposableControllerEvents @@ -277,7 +282,8 @@ type GlobalEvents = | SelectedNetworkControllerEvents | SmartTransactionsControllerEvents | AssetsContractControllerEvents - | RemoteFeatureFlagControllerEvents; + | RemoteFeatureFlagControllerEvents + | TokenSearchDiscoveryControllerEvents; // TODO: Abstract this into controller utils for TransactionController export interface TransactionEventPayload { From 55c156695d05241ec43a4d7368520ff1fc346368 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Mon, 3 Feb 2025 21:55:51 -0500 Subject: [PATCH 021/124] feat: Token search --- app/components/Nav/Main/MainNavigator.js | 14 +++- .../UrlAutocomplete.constants.ts | 2 +- app/components/UI/UrlAutocomplete/index.tsx | 76 ++++++++++++------- app/components/UI/UrlAutocomplete/types.ts | 23 +++++- app/components/Views/AssetLoader/index.tsx | 57 ++++++++++++++ .../Views/BrowserTab/BrowserTab.tsx | 14 +++- app/constants/navigation/Routes.ts | 2 + app/selectors/browser.ts | 4 +- app/selectors/tokensController.ts | 9 +++ package.json | 3 +- 10 files changed, 164 insertions(+), 40 deletions(-) create mode 100644 app/components/Views/AssetLoader/index.tsx diff --git a/app/components/Nav/Main/MainNavigator.js b/app/components/Nav/Main/MainNavigator.js index 7d4b9e7d089b..5772e24c380c 100644 --- a/app/components/Nav/Main/MainNavigator.js +++ b/app/components/Nav/Main/MainNavigator.js @@ -88,6 +88,7 @@ import NftDetailsFullImage from '../../Views/NftDetails/NFtDetailsFullImage'; import AccountPermissions from '../../../components/Views/AccountPermissions'; import { AccountPermissionsScreens } from '../../../components/Views/AccountPermissions/AccountPermissions.types'; import { StakeModalStack, StakeScreenStack } from '../../UI/Stake/routes'; +import { AssetLoader } from '../../Views/AssetLoader'; const Stack = createStackNavigator(); const Tab = createBottomTabNavigator(); @@ -197,7 +198,8 @@ const TransactionsHome = () => ( ); -const BrowserFlow = () => ( +/* eslint-disable react/prop-types */ +const BrowserFlow = (props) => ( ( component={Browser} options={{ headerShown: false }} /> + + ); diff --git a/app/components/UI/UrlAutocomplete/UrlAutocomplete.constants.ts b/app/components/UI/UrlAutocomplete/UrlAutocomplete.constants.ts index 46218e10bd44..3863f1bc3d98 100644 --- a/app/components/UI/UrlAutocomplete/UrlAutocomplete.constants.ts +++ b/app/components/UI/UrlAutocomplete/UrlAutocomplete.constants.ts @@ -1,2 +1,2 @@ export const MAX_RECENTS = 5; -export const ORDERED_CATEGORIES = ['sites', 'recents', 'favorites']; +export const ORDERED_CATEGORIES = ['sites', 'tokens', 'recents', 'favorites']; diff --git a/app/components/UI/UrlAutocomplete/index.tsx b/app/components/UI/UrlAutocomplete/index.tsx index a04d43b92f3a..02dbda01f49b 100644 --- a/app/components/UI/UrlAutocomplete/index.tsx +++ b/app/components/UI/UrlAutocomplete/index.tsx @@ -21,6 +21,8 @@ import { useStyles } from '../../../component-library/hooks'; import { UrlAutocompleteComponentProps, FuseSearchResult, + TokenSearchResult, + AutocompleteSearchResult, UrlAutocompleteRef, } from './types'; import { debounce } from 'lodash'; @@ -31,7 +33,7 @@ import { Result } from './Result'; export * from './types'; -const dappsWithType = dappUrlList.map(i => ({...i, type: 'sites'})); +const dappsWithType = dappUrlList.map(i => ({...i, type: 'sites'} as const)); /** * Autocomplete list that appears when the browser url bar is focused @@ -40,25 +42,20 @@ const UrlAutocomplete = forwardRef< UrlAutocompleteRef, UrlAutocompleteComponentProps >(({ onSelect, onDismiss }, ref) => { - const [resultsByCategory, setResultsByCategory] = useState<{category: string, data: FuseSearchResult[]}[]>([]); - const hasResults = resultsByCategory.length > 0; - - const browserHistory = useSelector(selectBrowserHistoryWithType); - const bookmarks = useSelector(selectBrowserBookmarksWithType); - const fuseRef = useRef | null>(null); - const resultsRef = useRef(null); - const { styles } = useStyles(styleSheet, {}); - - /** - * Show the results view - */ - const show = () => { - resultsRef.current?.setNativeProps({ style: { display: 'flex' } }); - }; + const [fuseResults, setFuseResults] = useState([]); + const [tokenResults, setTokenResults] = useState([]); + const hasResults = fuseResults.length > 0 || tokenResults.length > 0; + + const resultsByCategory: {category: string, data: AutocompleteSearchResult[]}[] = useMemo(() => ( + ORDERED_CATEGORIES.flatMap((category) => { + if (category === 'tokens') { + return { + category, + data: tokenResults, + }; + } - const updateResults = useCallback((results: FuseSearchResult[]) => { - const newResultsByCategory = ORDERED_CATEGORIES.flatMap((category) => { - let data = results.filter((result, index, self) => + let data = fuseResults.filter((result, index, self) => result.type === category && index === self.findIndex(r => r.url === result.url && r.type === result.type) ); @@ -72,28 +69,48 @@ const UrlAutocomplete = forwardRef< category, data, }; - }); + }) + ), [fuseResults, tokenResults]); + + const browserHistory = useSelector(selectBrowserHistoryWithType); + const bookmarks = useSelector(selectBrowserBookmarksWithType); + const fuseRef = useRef | null>(null); + const resultsRef = useRef(null); + const { styles } = useStyles(styleSheet, {}); - setResultsByCategory(newResultsByCategory); - }, []); + /** + * Show the results view + */ + const show = () => { + resultsRef.current?.setNativeProps({ style: { display: 'flex' } }); + }; const latestSearchTerm = useRef(null); const search = useCallback((text: string) => { latestSearchTerm.current = text; if (!text) { - updateResults([ + setFuseResults([ ...browserHistory, ...bookmarks, ]); + setTokenResults([ + { + type: 'tokens', + name: 'USDC', + symbol: 'USDC', + address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + chainId: '0x1', + } + ]); return; } const fuseSearchResult = fuseRef.current?.search(text); if (Array.isArray(fuseSearchResult)) { - updateResults([...fuseSearchResult]); + setFuseResults(fuseSearchResult); } else { - updateResults([]); + setFuseResults([]); } - }, [updateResults, browserHistory, bookmarks]); + }, [browserHistory, bookmarks]); /** * Debounce the search function @@ -107,7 +124,8 @@ const UrlAutocomplete = forwardRef< // Cancel the search debouncedSearch.cancel(); resultsRef.current?.setNativeProps({ style: { display: 'none' } }); - setResultsByCategory([]); + setFuseResults([]); + setTokenResults([]); }, [debouncedSearch]); const dismissAutocomplete = () => { @@ -157,7 +175,7 @@ const UrlAutocomplete = forwardRef< result={item} onPress={() => { hide(); - onSelect(item.url); + onSelect(item); }} /> ), [hide, onSelect]); @@ -177,7 +195,7 @@ const UrlAutocomplete = forwardRef< `${item.type}-${item.url}`} + keyExtractor={(item) => `${item.type}-${item.type === 'tokens' ? `${item.chainId}-${item.address}` : item.url}`} renderSectionHeader={renderSectionHeader} renderItem={renderItem} keyboardShouldPersistTaps="handled" diff --git a/app/components/UI/UrlAutocomplete/types.ts b/app/components/UI/UrlAutocomplete/types.ts index daa9e46cedee..0cda0e912ff7 100644 --- a/app/components/UI/UrlAutocomplete/types.ts +++ b/app/components/UI/UrlAutocomplete/types.ts @@ -1,13 +1,16 @@ /** * Props for the UrlAutocomplete component */ + +import { Hex } from '@metamask/utils'; + // eslint-disable-next-line @typescript-eslint/consistent-type-definitions export type UrlAutocompleteComponentProps = { /** * Callback that is triggered while * choosing one of the autocomplete options */ - onSelect: (url: string) => void; + onSelect: (item: AutocompleteSearchResult) => void; /** * Callback that is triggered while * tapping on the background @@ -35,11 +38,25 @@ export type UrlAutocompleteRef = { }; /** - * The result of an Fuse search + * The result of a Fuse search */ // eslint-disable-next-line @typescript-eslint/consistent-type-definitions export type FuseSearchResult = { + type: 'sites' | 'recents' | 'favorites'; url: string; name: string; - type: string; }; + +/** + * The result of a token search + */ +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions +export type TokenSearchResult = { + type: 'tokens'; + name: string; + symbol: string; + address: string; + chainId: Hex; +}; + +export type AutocompleteSearchResult = FuseSearchResult | TokenSearchResult; diff --git a/app/components/Views/AssetLoader/index.tsx b/app/components/Views/AssetLoader/index.tsx new file mode 100644 index 000000000000..aa24c3e6cfcb --- /dev/null +++ b/app/components/Views/AssetLoader/index.tsx @@ -0,0 +1,57 @@ +import React, { useEffect } from 'react'; +import { Hex } from '@metamask/utils'; +import { useSelector } from 'react-redux'; +import { selectSearchedToken } from '../../../selectors/tokensController'; +import { RootState } from '../../../reducers'; +import { ActivityIndicator, Text, View } from 'react-native'; +import Engine from '../../../core/Engine/Engine'; +import Asset from '../Asset'; +import { useNavigation } from '@react-navigation/native'; +import Routes from '../../../constants/navigation/Routes'; + +export interface AssetLoaderProps { + route: { + params: { + address: string; + chainId: Hex; + } + } +} + +export const AssetLoader: React.FC = ({ route: { params: { address, chainId } } }) => { + const tokenResult = useSelector((state: RootState) => selectSearchedToken(state, chainId, address)); + + const navigation = useNavigation(); + + useEffect(() => { + if (!tokenResult) { + Engine.context.TokensController.addSearchedToken(address, chainId); + } else if (tokenResult.found) { + navigation.goBack(); + navigation.navigate(Routes.BROWSER.ASSET_VIEW, { + ...tokenResult.token, + chainId + }); + } + }, [tokenResult, address, chainId, navigation]); + + if (!tokenResult) { + return ( + + + + ); + } + + if (!tokenResult.found) { + return ( + + Token not found + + ); + } + + return ( + + ); +} \ No newline at end of file diff --git a/app/components/Views/BrowserTab/BrowserTab.tsx b/app/components/Views/BrowserTab/BrowserTab.tsx index a60d347e0bca..57ca3346febe 100644 --- a/app/components/Views/BrowserTab/BrowserTab.tsx +++ b/app/components/Views/BrowserTab/BrowserTab.tsx @@ -101,7 +101,7 @@ import { getURLProtocol } from '../../../util/general'; import { PROTOCOLS } from '../../../constants/deeplinks'; import Options from './components/Options'; import IpfsBanner from './components/IpfsBanner'; -import UrlAutocomplete, { UrlAutocompleteRef } from '../../UI/UrlAutocomplete'; +import UrlAutocomplete, { AutocompleteSearchResult, UrlAutocompleteRef } from '../../UI/UrlAutocomplete'; import { selectSearchEngine } from '../../../reducers/browser/selectors'; /** @@ -1174,10 +1174,18 @@ export const BrowserTab: React.FC = ({ /** * Handle autocomplete selection */ - const onSelect = (url: string) => { + const onSelect = (item: AutocompleteSearchResult) => { // Unfocus the url bar and hide the autocomplete results urlBarRef.current?.hide(); - onSubmitEditing(url); + + if (item.type === 'tokens') { + navigation.navigate(Routes.BROWSER.ASSET_LOADER, { + chainId: item.chainId, + address: item.address, + }); + } else { + onSubmitEditing(item.url); + } }; /** diff --git a/app/constants/navigation/Routes.ts b/app/constants/navigation/Routes.ts index 7a38bbf048d2..b61c044be045 100644 --- a/app/constants/navigation/Routes.ts +++ b/app/constants/navigation/Routes.ts @@ -119,6 +119,8 @@ const Routes = { BROWSER: { HOME: 'BrowserTabHome', VIEW: 'BrowserView', + ASSET_LOADER: 'AssetLoader', + ASSET_VIEW: 'AssetView', }, WEBVIEW: { MAIN: 'Webview', diff --git a/app/selectors/browser.ts b/app/selectors/browser.ts index 458cf90d6ba8..b31f2c08b2fc 100644 --- a/app/selectors/browser.ts +++ b/app/selectors/browser.ts @@ -8,10 +8,10 @@ interface SiteItem { export const selectBrowserHistoryWithType = createDeepEqualSelector( (state: RootState) => state.browser.history, - (history: SiteItem[]) => history.map(item => ({...item, type: 'recents'})).reverse() + (history: SiteItem[]) => history.map(item => ({...item, type: 'recents'} as const)).reverse() ); export const selectBrowserBookmarksWithType = createDeepEqualSelector( (state: RootState) => state.bookmarks, - (bookmarks: SiteItem[]) => bookmarks.map(item => ({...item, type: 'favorites'})) + (bookmarks: SiteItem[]) => bookmarks.map(item => ({...item, type: 'favorites'} as const)) ); \ No newline at end of file diff --git a/app/selectors/tokensController.ts b/app/selectors/tokensController.ts index 101609136843..ed75fb0d789d 100644 --- a/app/selectors/tokensController.ts +++ b/app/selectors/tokensController.ts @@ -199,3 +199,12 @@ export const selectTransformedTokens = createSelector( return flatList; }, ); + +export const selectSearchedToken = createSelector( + selectTokensControllerState, + (_state: RootState, chainId: Hex) => chainId, + (_state: RootState, _chainId: Hex, address: string) => address, + (tokensControllerState: TokensControllerState, chainId: Hex, address: string) => ( + tokensControllerState.allSearchedTokens.find((token) => token.chainId === chainId && token.address === address) + ), +); diff --git a/package.json b/package.json index ba6cb495a7e7..e299a18b8b6b 100644 --- a/package.json +++ b/package.json @@ -141,7 +141,8 @@ "sha256-uint8array": "0.10.3", "express": "4.21.2", "nanoid": "^3.3.8", - "undici": "5.28.5" + "undici": "5.28.5", + "@metamask/assets-controllers": "file:../metamask-core/packages/assets-controllers" }, "dependencies": { "@config-plugins/detox": "^8.0.0", From b08259f246634c1def6906921943e02c217b2804 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Tue, 4 Feb 2025 11:49:07 -0500 Subject: [PATCH 022/124] replace navigation --- app/components/UI/UrlAutocomplete/index.tsx | 8 ++++---- app/components/Views/AssetLoader/index.tsx | 18 ++++++++---------- locales/languages/en.json | 3 ++- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/app/components/UI/UrlAutocomplete/index.tsx b/app/components/UI/UrlAutocomplete/index.tsx index 02dbda01f49b..cfebbe1e010d 100644 --- a/app/components/UI/UrlAutocomplete/index.tsx +++ b/app/components/UI/UrlAutocomplete/index.tsx @@ -96,10 +96,10 @@ const UrlAutocomplete = forwardRef< setTokenResults([ { type: 'tokens', - name: 'USDC', - symbol: 'USDC', - address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', - chainId: '0x1', + name: 'DEGEN', + symbol: 'DEGEN', + address: '0x4ed4e862860bed51a9570b96d89af5e1b0efefed', + chainId: '0x2105', } ]); return; diff --git a/app/components/Views/AssetLoader/index.tsx b/app/components/Views/AssetLoader/index.tsx index aa24c3e6cfcb..ea665b17082e 100644 --- a/app/components/Views/AssetLoader/index.tsx +++ b/app/components/Views/AssetLoader/index.tsx @@ -5,8 +5,7 @@ import { selectSearchedToken } from '../../../selectors/tokensController'; import { RootState } from '../../../reducers'; import { ActivityIndicator, Text, View } from 'react-native'; import Engine from '../../../core/Engine/Engine'; -import Asset from '../Asset'; -import { useNavigation } from '@react-navigation/native'; +import { StackActions, useNavigation } from '@react-navigation/native'; import Routes from '../../../constants/navigation/Routes'; export interface AssetLoaderProps { @@ -27,11 +26,12 @@ export const AssetLoader: React.FC = ({ route: { params: { add if (!tokenResult) { Engine.context.TokensController.addSearchedToken(address, chainId); } else if (tokenResult.found) { - navigation.goBack(); - navigation.navigate(Routes.BROWSER.ASSET_VIEW, { - ...tokenResult.token, - chainId - }); + navigation.dispatch( + StackActions.replace(Routes.BROWSER.ASSET_VIEW, { + ...tokenResult.token, + chainId + }) + ); } }, [tokenResult, address, chainId, navigation]); @@ -51,7 +51,5 @@ export const AssetLoader: React.FC = ({ route: { params: { add ); } - return ( - - ); + return null; } \ No newline at end of file diff --git a/locales/languages/en.json b/locales/languages/en.json index da828e8afb8b..cc20bcbfdd2a 100644 --- a/locales/languages/en.json +++ b/locales/languages/en.json @@ -51,7 +51,8 @@ "placeholder": "Search by site or address", "recents": "Recents", "favorites": "Favorites", - "sites": "Sites" + "sites": "Sites", + "tokens": "Tokens" }, "navigation": { "back": "Back", From cd6999c538ac5f15c1c815ff453cb03c32668d5c Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Thu, 6 Feb 2025 11:03:31 -0700 Subject: [PATCH 023/124] chore: bump token controller version --- package.json | 5 ++--- yarn.lock | 12 ++++++------ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index ff96a5a953cf..7cfe2207c7c2 100644 --- a/package.json +++ b/package.json @@ -210,7 +210,7 @@ "@metamask/stake-sdk": "^1.0.0", "@metamask/swappable-obj-proxy": "^2.1.0", "@metamask/swaps-controller": "^12.0.0", - "@metamask/token-search-discovery-controller": "^1.0.0", + "@metamask/token-search-discovery-controller": "^2.0.0", "@metamask/transaction-controller": "^43.0.0", "@metamask/utils": "^11.0.1", "@ngraveio/bc-ur": "^1.1.6", @@ -607,8 +607,7 @@ "@react-native-firebase/app>firebase>@firebase/firestore>@grpc/proto-loader>protobufjs": false, "appium": false, "appium>@appium/support>sharp": false, - "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service>eip55>keccak": false, - "@storybook/builder-webpack5>@storybook/core-common>esbuild": false + "@metamask/eth-ledger-bridge-keyring>@ledgerhq/hw-app-eth>@ledgerhq/domain-service>eip55>keccak": false } }, "packageManager": "yarn@1.22.22" diff --git a/yarn.lock b/yarn.lock index c4ed441dd0fe..afbd352ff8e4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5579,13 +5579,13 @@ resolved "https://registry.yarnpkg.com/@metamask/test-dapp/-/test-dapp-8.9.0.tgz#bac680e8f0007b3a11440f7e311674d6457d37ed" integrity sha512-N/WfmdrzJm+xbpuqJsfMrlrAhiNDsllIpwt9gDDeEKDlQAfJnMtT9xvOvBJbXY7zgMdtGZuD+KY64jNKabbuVQ== -"@metamask/token-search-discovery-controller@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@metamask/token-search-discovery-controller/-/token-search-discovery-controller-1.0.0.tgz#f36a9db5d3003ad75374fa515c193acfba122512" - integrity sha512-Cbu8i1mEZViFsy/OyeOfOvDGOUUgIRxecU8PCO/IXW04gBj7vwQ9EZPLuW+/z8zqTeiwupd7JMx1rgZMMdma7Q== +"@metamask/token-search-discovery-controller@^2.0.0": + version "2.0.0" + resolved "https://consensys.jfrog.io/artifactory/api/npm/npm/@metamask/token-search-discovery-controller/-/token-search-discovery-controller-2.0.0.tgz#f0f87bde6675371e52e6b2874f8540b8703d0cd4" + integrity sha512-YYb1gNFPh/07DMKH7kU2+XRKjDrr1CTGa+Sxk29qM7r46eDOv7lAIuZW4FsYX1M8rT4tKrCsYFX5nCHER54Ogw== dependencies: "@metamask/base-controller" "^7.1.1" - "@metamask/utils" "^11.0.1" + "@metamask/utils" "^11.1.0" "@metamask/transaction-controller@^43.0.0": version "43.0.0" @@ -5612,7 +5612,7 @@ lodash "^4.17.21" uuid "^8.3.2" -"@metamask/utils@^10.0.0", "@metamask/utils@^10.0.1", "@metamask/utils@^11.0.1", "@metamask/utils@^8.2.0", "@metamask/utils@^8.3.0", "@metamask/utils@^9.0.0", "@metamask/utils@^9.1.0", "@metamask/utils@^9.2.1", "@metamask/utils@^9.3.0": +"@metamask/utils@^10.0.0", "@metamask/utils@^10.0.1", "@metamask/utils@^11.0.1", "@metamask/utils@^11.1.0", "@metamask/utils@^8.2.0", "@metamask/utils@^8.3.0", "@metamask/utils@^9.0.0", "@metamask/utils@^9.1.0", "@metamask/utils@^9.2.1", "@metamask/utils@^9.3.0": version "11.0.1" resolved "https://registry.yarnpkg.com/@metamask/utils/-/utils-11.0.1.tgz#16c4135489204fefe128b5e6c2b92c014453e1d5" integrity sha512-tZlBvEJ6VhhfEiMV+Ad8rWRMjHKpbMogG01YU22JlsIeJptgIdZX1G8jJzhZH0Gxrixa2BeARh7m9lZWQo6rMg== From 9ad0b53c13a35f2f24ad6e264864d7fb490d5b0d Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Thu, 6 Feb 2025 11:04:49 -0700 Subject: [PATCH 024/124] chore: fix unnecessary format --- app/core/Engine/Engine.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/core/Engine/Engine.ts b/app/core/Engine/Engine.ts index 09afc8ceac43..5472d0e0d245 100644 --- a/app/core/Engine/Engine.ts +++ b/app/core/Engine/Engine.ts @@ -2185,8 +2185,7 @@ export default { keyringState: KeyringControllerState | null = null, metaMetricsId?: string, ) { - instance = - Engine.instance || new Engine(state, keyringState, metaMetricsId); + instance = Engine.instance || new Engine(state, keyringState, metaMetricsId); Object.freeze(instance); return instance; }, From f3a936fa9a6057b4013410bed2884db37c8daec5 Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Thu, 6 Feb 2025 11:05:38 -0700 Subject: [PATCH 025/124] chore: import from index where we can --- .../TokenSearchDiscoveryController/utils.test.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts b/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts index 1698121c6a83..c0d220dc677b 100644 --- a/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts +++ b/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts @@ -1,11 +1,12 @@ import { createTokenSearchDiscoveryController } from './utils'; import { ControllerMessenger } from '@metamask/base-controller'; +import Logger from '../../../../util/Logger'; + import { - TokenSearchDiscoveryControllerMessenger, + TokenSearchApiService, TokenSearchDiscoveryControllerState, -} from '@metamask/token-search-discovery-controller/dist/token-search-discovery-controller.cjs'; -import Logger from '../../../../util/Logger'; -import { TokenSearchApiService } from '@metamask/token-search-discovery-controller'; +} from '@metamask/token-search-discovery-controller'; +import { TokenSearchDiscoveryControllerMessenger } from '@metamask/token-search-discovery-controller/dist/token-search-discovery-controller.cjs'; const mockError = new Error('Controller creation failed'); From e580b752d0553abf9ad0ca3bf8822350bd694220 Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Thu, 6 Feb 2025 11:15:44 -0700 Subject: [PATCH 026/124] chore: use testing best practices as a guideline --- .../useTokenSearchDiscovery.test.ts | 8 ++++---- .../TokenSearchDiscoveryController/utils.test.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts index 53a6d69ec875..65d26e33072b 100644 --- a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts +++ b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts @@ -1,7 +1,7 @@ import { act, renderHook } from '@testing-library/react-hooks'; import Engine from '../../../core/Engine'; import useTokenSearchDiscovery from './useTokenSearchDiscovery'; -import { TokenSearchParams } from '@metamask/token-search-discovery-controller/dist/types.cjs'; +import { TokenSearchParams } from '@metamask/token-search-discovery-controller'; jest.mock('react-redux', () => ({ ...jest.requireActual('react-redux'), @@ -26,10 +26,10 @@ describe('useTokenSearchDiscovery', () => { jest.useRealTimers(); }); - it('should update states correctly when searching tokens', async () => { + it('updates states correctly when searching tokens', async () => { const mockSearchParams: TokenSearchParams = { chains: ['0x1'], - name: 'DAI', + query: 'DAI', limit: '10', }; const mockSearchResult = [{ name: 'DAI', address: '0x123' }]; @@ -61,7 +61,7 @@ describe('useTokenSearchDiscovery', () => { ).toHaveBeenCalledWith(mockSearchParams); }); - it('should handle search errors', async () => { + it('returns error and empty results if search failed', async () => { const mockError = new Error('Search failed'); ( Engine.context.TokenSearchDiscoveryController.searchTokens as jest.Mock diff --git a/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts b/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts index c0d220dc677b..68d4619cac3d 100644 --- a/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts +++ b/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts @@ -94,7 +94,7 @@ describe('TokenSearchDiscoveryController utils', () => { expect(controller.state).toStrictEqual(initialState); }); - it('controller keeps initial extra data in its state', () => { + it('keeps initial extra data in controller state', () => { const initialState = { extraData: true, }; From d46e0c37669285a0d6713f0feec81ab2d6f8779d Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Thu, 6 Feb 2025 11:17:07 -0700 Subject: [PATCH 027/124] add missing discovery service --- .../controllers/TokenSearchDiscoveryController/utils.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.ts b/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.ts index af80f9cc0dfb..7712bbca538e 100644 --- a/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.ts +++ b/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.ts @@ -2,6 +2,7 @@ import Logger from '../../../../util/Logger'; import { TokenSearchApiService, TokenSearchDiscoveryController, + TokenDiscoveryApiService, } from '@metamask/token-search-discovery-controller'; import { TokenSearchDiscoveryControllerParams } from './types'; import { PORTFOLIO_API_URL } from './constants'; @@ -24,10 +25,12 @@ export const createTokenSearchDiscoveryController = ({ messenger, }: TokenSearchDiscoveryControllerParams) => { try { + const baseUrl = getPortfolioApiBaseUrl(); const controller = new TokenSearchDiscoveryController({ state, messenger, - tokenSearchService: new TokenSearchApiService(getPortfolioApiBaseUrl()), + tokenSearchService: new TokenSearchApiService(baseUrl), + tokenDiscoveryService: new TokenDiscoveryApiService(baseUrl), }); return controller; } catch (error) { From d6645dcf087061212c749b1f560e3f5980f48a36 Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Thu, 6 Feb 2025 11:17:43 -0700 Subject: [PATCH 028/124] prefer imports from index --- .../useTokenSearchDiscovery/useTokenSearchDiscovery.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts index 7e9c1b650bbc..69308f75664b 100644 --- a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts +++ b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts @@ -3,10 +3,10 @@ import { useSelector } from 'react-redux'; import { debounce } from 'lodash'; import Engine from '../../../core/Engine'; import { selectRecentTokenSearches } from '../../../selectors/tokenSearchDiscoveryController'; -import type { - TokenSearchParams, +import { TokenSearchResponseItem, -} from '@metamask/token-search-discovery-controller/dist/types.d.cts'; + TokenSearchParams, +} from '@metamask/token-search-discovery-controller'; const SEARCH_DEBOUNCE_DELAY = 300; From 5c34e89b04539ca3981519a33232aa1296065f48 Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Thu, 6 Feb 2025 13:18:24 -0700 Subject: [PATCH 029/124] update yarn lock to use yarnpkg > jfrog --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index afbd352ff8e4..534fe954449c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5581,7 +5581,7 @@ "@metamask/token-search-discovery-controller@^2.0.0": version "2.0.0" - resolved "https://consensys.jfrog.io/artifactory/api/npm/npm/@metamask/token-search-discovery-controller/-/token-search-discovery-controller-2.0.0.tgz#f0f87bde6675371e52e6b2874f8540b8703d0cd4" + resolved "https://registry.yarnpkg.com/@metamask/token-search-discovery-controller/-/token-search-discovery-controller-2.0.0.tgz#f0f87bde6675371e52e6b2874f8540b8703d0cd4" integrity sha512-YYb1gNFPh/07DMKH7kU2+XRKjDrr1CTGa+Sxk29qM7r46eDOv7lAIuZW4FsYX1M8rT4tKrCsYFX5nCHER54Ogw== dependencies: "@metamask/base-controller" "^7.1.1" From 0b05b3039edb96c14dbddaf05adbd720abf6cffd Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Thu, 6 Feb 2025 13:20:24 -0700 Subject: [PATCH 030/124] fix: prevent race conditions by tracking request IDs and ensuring only latest search results update state --- .../useTokenSearchDiscovery.ts | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts index 69308f75664b..88ce3ab8d570 100644 --- a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts +++ b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts @@ -1,4 +1,4 @@ -import { useCallback, useState } from 'react'; +import { useCallback, useState, useRef } from 'react'; import { useSelector } from 'react-redux'; import { debounce } from 'lodash'; import Engine from '../../../core/Engine'; @@ -15,27 +15,40 @@ export const useTokenSearchDiscovery = () => { const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [results, setResults] = useState([]); + const latestRequestId = useRef(0); - const searchTokens = useCallback((params: TokenSearchParams) => { - setIsLoading(true); - setError(null); - - const debouncedSearch = debounce(async () => { + const debouncedSearch = useCallback( + debounce(async (params: TokenSearchParams, requestId: number) => { try { const { TokenSearchDiscoveryController } = Engine.context; const result = await TokenSearchDiscoveryController.searchTokens( params, ); - setResults(result); + if (requestId === latestRequestId.current) { + setResults(result); + } } catch (err) { - setError(err as Error); + if (requestId === latestRequestId.current) { + setError(err as Error); + } } finally { - setIsLoading(false); + if (requestId === latestRequestId.current) { + setIsLoading(false); + } } - }, SEARCH_DEBOUNCE_DELAY); + }, SEARCH_DEBOUNCE_DELAY), + [], + ); - debouncedSearch(); - }, []); + const searchTokens = useCallback( + (params: TokenSearchParams) => { + setIsLoading(true); + setError(null); + latestRequestId.current += 1; + debouncedSearch(params, latestRequestId.current); + }, + [debouncedSearch], + ); return { searchTokens, From f3647e3d71a54c7662143f322d0813ddb93b8ee4 Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Thu, 6 Feb 2025 13:28:18 -0700 Subject: [PATCH 031/124] add missing deps --- .../hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts index 88ce3ab8d570..25ec3f616a71 100644 --- a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts +++ b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts @@ -37,7 +37,7 @@ export const useTokenSearchDiscovery = () => { } } }, SEARCH_DEBOUNCE_DELAY), - [], + [setResults, setError, setIsLoading, latestRequestId], ); const searchTokens = useCallback( From 4fa402284e1f866ea7069ff476b48f6e1644d64c Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Thu, 6 Feb 2025 13:48:54 -0700 Subject: [PATCH 032/124] fix: add mock of discovery service for tests --- .../TokenSearchDiscoveryController/utils.test.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts b/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts index 68d4619cac3d..ecdef47557fa 100644 --- a/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts +++ b/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts @@ -16,7 +16,12 @@ jest.mock('../../../../util/Logger', () => ({ })); jest.mock('@metamask/token-search-discovery-controller', () => ({ - TokenSearchApiService: jest.fn(), + TokenSearchApiService: jest.fn().mockImplementation(function () { + return {}; + }), + TokenDiscoveryApiService: jest.fn().mockImplementation(function () { + return {}; + }), TokenSearchDiscoveryController: jest .fn() .mockImplementation(function ( From f8029ecc5295685296384f8621001f3531803fe4 Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Thu, 6 Feb 2025 14:17:34 -0700 Subject: [PATCH 033/124] linter prefers arrow functions --- .../utils.test.ts | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts b/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts index ecdef47557fa..23699fa5abe9 100644 --- a/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts +++ b/app/core/Engine/controllers/TokenSearchDiscoveryController/utils.test.ts @@ -16,25 +16,19 @@ jest.mock('../../../../util/Logger', () => ({ })); jest.mock('@metamask/token-search-discovery-controller', () => ({ - TokenSearchApiService: jest.fn().mockImplementation(function () { - return {}; - }), - TokenDiscoveryApiService: jest.fn().mockImplementation(function () { - return {}; - }), + TokenSearchApiService: jest.fn().mockImplementation(() => ({})), + TokenDiscoveryApiService: jest.fn().mockImplementation(() => ({})), TokenSearchDiscoveryController: jest .fn() - .mockImplementation(function ( - this: { state: TokenSearchDiscoveryControllerState }, - params: { state?: TokenSearchDiscoveryControllerState }, - ) { - this.state = { - lastSearchTimestamp: null, - recentSearches: [], - ...params.state, - }; - return this; - }), + .mockImplementation( + (params: { state?: TokenSearchDiscoveryControllerState }) => ({ + state: { + lastSearchTimestamp: null, + recentSearches: [], + ...params.state, + }, + }), + ), })); describe('TokenSearchDiscoveryController utils', () => { From a746b8d0978ecbcbf975f3eccc3c959e11478283 Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Thu, 6 Feb 2025 14:18:00 -0700 Subject: [PATCH 034/124] use memo for better deps tracking --- .../useTokenSearchDiscovery.ts | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts index 25ec3f616a71..5cbc58d2e787 100644 --- a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts +++ b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts @@ -1,4 +1,4 @@ -import { useCallback, useState, useRef } from 'react'; +import { useCallback, useState, useRef, useMemo } from 'react'; import { useSelector } from 'react-redux'; import { debounce } from 'lodash'; import Engine from '../../../core/Engine'; @@ -17,26 +17,27 @@ export const useTokenSearchDiscovery = () => { const [results, setResults] = useState([]); const latestRequestId = useRef(0); - const debouncedSearch = useCallback( - debounce(async (params: TokenSearchParams, requestId: number) => { - try { - const { TokenSearchDiscoveryController } = Engine.context; - const result = await TokenSearchDiscoveryController.searchTokens( - params, - ); - if (requestId === latestRequestId.current) { - setResults(result); + const debouncedSearch = useMemo( + () => + debounce(async (params: TokenSearchParams, requestId: number) => { + try { + const { TokenSearchDiscoveryController } = Engine.context; + const result = await TokenSearchDiscoveryController.searchTokens( + params, + ); + if (requestId === latestRequestId.current) { + setResults(result); + } + } catch (err) { + if (requestId === latestRequestId.current) { + setError(err as Error); + } + } finally { + if (requestId === latestRequestId.current) { + setIsLoading(false); + } } - } catch (err) { - if (requestId === latestRequestId.current) { - setError(err as Error); - } - } finally { - if (requestId === latestRequestId.current) { - setIsLoading(false); - } - } - }, SEARCH_DEBOUNCE_DELAY), + }, SEARCH_DEBOUNCE_DELAY), [setResults, setError, setIsLoading, latestRequestId], ); From 40b5ecf29997a081c5c598882b9a142cc7b92ca5 Mon Sep 17 00:00:00 2001 From: Bigshmow Date: Thu, 6 Feb 2025 15:10:52 -0700 Subject: [PATCH 035/124] remove wrapper and isolate within single scope where we can --- .../useTokenSearchDiscovery.ts | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts index 5cbc58d2e787..bf752842c79a 100644 --- a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts +++ b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts @@ -1,4 +1,4 @@ -import { useCallback, useState, useRef, useMemo } from 'react'; +import { useState, useRef, useMemo } from 'react'; import { useSelector } from 'react-redux'; import { debounce } from 'lodash'; import Engine from '../../../core/Engine'; @@ -17,9 +17,13 @@ export const useTokenSearchDiscovery = () => { const [results, setResults] = useState([]); const latestRequestId = useRef(0); - const debouncedSearch = useMemo( + const searchTokens = useMemo( () => - debounce(async (params: TokenSearchParams, requestId: number) => { + debounce(async (params: TokenSearchParams) => { + setIsLoading(true); + setError(null); + const requestId = ++latestRequestId.current; + try { const { TokenSearchDiscoveryController } = Engine.context; const result = await TokenSearchDiscoveryController.searchTokens( @@ -38,17 +42,7 @@ export const useTokenSearchDiscovery = () => { } } }, SEARCH_DEBOUNCE_DELAY), - [setResults, setError, setIsLoading, latestRequestId], - ); - - const searchTokens = useCallback( - (params: TokenSearchParams) => { - setIsLoading(true); - setError(null); - latestRequestId.current += 1; - debouncedSearch(params, latestRequestId.current); - }, - [debouncedSearch], + [], ); return { From 3a47c1771ed66adb815124ad41b967e748133f9a Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Mon, 10 Feb 2025 13:27:17 -0500 Subject: [PATCH 036/124] add local token search controller --- package.json | 4 ++-- yarn.lock | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 0b5dce133dcb..bf8770487e60 100644 --- a/package.json +++ b/package.json @@ -141,8 +141,7 @@ "sha256-uint8array": "0.10.3", "express": "4.21.2", "nanoid": "^3.3.8", - "undici": "5.28.5", - "@metamask/assets-controllers": "file:../metamask-core/packages/assets-controllers" + "undici": "5.28.5" }, "dependencies": { "@config-plugins/detox": "^8.0.0", @@ -211,6 +210,7 @@ "@metamask/stake-sdk": "^1.0.0", "@metamask/swappable-obj-proxy": "^2.1.0", "@metamask/swaps-controller": "^12.0.0", + "@metamask/token-search-discovery-controller": "file:../metamask-core/packages/token-search-discovery-controller", "@metamask/transaction-controller": "^43.0.0", "@metamask/utils": "^11.0.1", "@ngraveio/bc-ur": "^1.1.6", diff --git a/yarn.lock b/yarn.lock index 8d196ce269b3..b2690771b5d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5579,6 +5579,12 @@ resolved "https://registry.yarnpkg.com/@metamask/test-dapp/-/test-dapp-8.9.0.tgz#bac680e8f0007b3a11440f7e311674d6457d37ed" integrity sha512-N/WfmdrzJm+xbpuqJsfMrlrAhiNDsllIpwt9gDDeEKDlQAfJnMtT9xvOvBJbXY7zgMdtGZuD+KY64jNKabbuVQ== +"@metamask/token-search-discovery-controller@file:../metamask-core/packages/token-search-discovery-controller": + version "2.0.0" + dependencies: + "@metamask/base-controller" "^7.1.1" + "@metamask/utils" "^11.1.0" + "@metamask/transaction-controller@^43.0.0": version "43.0.0" resolved "https://registry.yarnpkg.com/@metamask/transaction-controller/-/transaction-controller-43.0.0.tgz#d4206cf671c4b9938dcf5fc0a190c7b8bf062967" @@ -5604,7 +5610,7 @@ lodash "^4.17.21" uuid "^8.3.2" -"@metamask/utils@^10.0.0", "@metamask/utils@^10.0.1", "@metamask/utils@^11.0.1", "@metamask/utils@^8.2.0", "@metamask/utils@^8.3.0", "@metamask/utils@^9.0.0", "@metamask/utils@^9.1.0", "@metamask/utils@^9.2.1", "@metamask/utils@^9.3.0": +"@metamask/utils@^10.0.0", "@metamask/utils@^10.0.1", "@metamask/utils@^11.0.1", "@metamask/utils@^11.1.0", "@metamask/utils@^8.2.0", "@metamask/utils@^8.3.0", "@metamask/utils@^9.0.0", "@metamask/utils@^9.1.0", "@metamask/utils@^9.2.1", "@metamask/utils@^9.3.0": version "11.0.1" resolved "https://registry.yarnpkg.com/@metamask/utils/-/utils-11.0.1.tgz#16c4135489204fefe128b5e6c2b92c014453e1d5" integrity sha512-tZlBvEJ6VhhfEiMV+Ad8rWRMjHKpbMogG01YU22JlsIeJptgIdZX1G8jJzhZH0Gxrixa2BeARh7m9lZWQo6rMg== From 89a9f801c536aa1581a2777abeee86248f0b74b2 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Mon, 10 Feb 2025 13:30:21 -0500 Subject: [PATCH 037/124] undo changes to tokens controller selectors (will be moved to discovery controller) --- app/selectors/tokensController.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/app/selectors/tokensController.ts b/app/selectors/tokensController.ts index ed75fb0d789d..101609136843 100644 --- a/app/selectors/tokensController.ts +++ b/app/selectors/tokensController.ts @@ -199,12 +199,3 @@ export const selectTransformedTokens = createSelector( return flatList; }, ); - -export const selectSearchedToken = createSelector( - selectTokensControllerState, - (_state: RootState, chainId: Hex) => chainId, - (_state: RootState, _chainId: Hex, address: string) => address, - (tokensControllerState: TokensControllerState, chainId: Hex, address: string) => ( - tokensControllerState.allSearchedTokens.find((token) => token.chainId === chainId && token.address === address) - ), -); From d3a6b3c22e59c81cee9c892a263adf746477baba Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Wed, 12 Feb 2025 14:10:28 -0500 Subject: [PATCH 038/124] update --- app/components/Views/AssetLoader/index.tsx | 20 +++++++++---------- app/core/Engine/Engine.ts | 12 +++++++++++ app/core/Engine/types.ts | 4 ++++ .../tokenSearchDiscoveryDataController.ts | 16 +++++++++++++++ package.json | 7 ++++--- yarn.lock | 12 +++++------ 6 files changed, 52 insertions(+), 19 deletions(-) create mode 100644 app/selectors/tokenSearchDiscoveryDataController.ts diff --git a/app/components/Views/AssetLoader/index.tsx b/app/components/Views/AssetLoader/index.tsx index ea665b17082e..7d16738e9342 100644 --- a/app/components/Views/AssetLoader/index.tsx +++ b/app/components/Views/AssetLoader/index.tsx @@ -1,13 +1,13 @@ import React, { useEffect } from 'react'; import { Hex } from '@metamask/utils'; -import { useSelector } from 'react-redux'; -import { selectSearchedToken } from '../../../selectors/tokensController'; -import { RootState } from '../../../reducers'; import { ActivityIndicator, Text, View } from 'react-native'; -import Engine from '../../../core/Engine/Engine'; import { StackActions, useNavigation } from '@react-navigation/native'; import Routes from '../../../constants/navigation/Routes'; - +import Logger from '../../../util/Logger'; +import Engine from '../../../core/Engine'; +import { useSelector } from 'react-redux'; +import { RootState } from '../../../reducers'; +import { selectTokenDisplayData } from '../../../selectors/tokenSearchDiscoveryDataController'; export interface AssetLoaderProps { route: { params: { @@ -18,13 +18,13 @@ export interface AssetLoaderProps { } export const AssetLoader: React.FC = ({ route: { params: { address, chainId } } }) => { - const tokenResult = useSelector((state: RootState) => selectSearchedToken(state, chainId, address)); - + const tokenResult = useSelector((state: RootState) => selectTokenDisplayData(state, chainId, address)); + Logger.log('>>>>>>>>> tokenResult', tokenResult); const navigation = useNavigation(); useEffect(() => { if (!tokenResult) { - Engine.context.TokensController.addSearchedToken(address, chainId); + Engine.context.TokenSearchDiscoveryDataController.fetchTokenDisplayData(chainId, address); } else if (tokenResult.found) { navigation.dispatch( StackActions.replace(Routes.BROWSER.ASSET_VIEW, { @@ -37,7 +37,7 @@ export const AssetLoader: React.FC = ({ route: { params: { add if (!tokenResult) { return ( - + ); @@ -52,4 +52,4 @@ export const AssetLoader: React.FC = ({ route: { params: { add } return null; -} \ No newline at end of file +}; diff --git a/app/core/Engine/Engine.ts b/app/core/Engine/Engine.ts index d706d4d199ae..6ac723799358 100644 --- a/app/core/Engine/Engine.ts +++ b/app/core/Engine/Engine.ts @@ -17,6 +17,7 @@ import { ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) MultichainBalancesControllerMessenger, ///: END:ONLY_INCLUDE_IF + TokenSearchDiscoveryDataController, } from '@metamask/assets-controllers'; ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) import { AppState } from 'react-native'; @@ -1326,6 +1327,14 @@ export class Engine { getMetaMetricsProps: () => Promise.resolve({}), // Return MetaMetrics props once we enable HW wallets for smart transactions. }); + const tokenSearchDiscoveryDataController = new TokenSearchDiscoveryDataController({ + messenger: this.controllerMessenger.getRestricted({ + name: 'TokenSearchDiscoveryDataController', + allowedActions: [], + allowedEvents: [], + }), + }); + this.context = { KeyringController: this.keyringController, AccountTrackerController: accountTrackerController, @@ -1559,6 +1568,7 @@ export class Engine { MultichainBalancesController: multichainBalancesController, RatesController: multichainRatesController, ///: END:ONLY_INCLUDE_IF + TokenSearchDiscoveryDataController: tokenSearchDiscoveryDataController, }; const childControllers = Object.assign({}, this.context); @@ -2184,6 +2194,7 @@ export default { MultichainBalancesController, RatesController, ///: END:ONLY_INCLUDE_IF + TokenSearchDiscoveryDataController, } = instance.datamodel.state; return { @@ -2224,6 +2235,7 @@ export default { MultichainBalancesController, RatesController, ///: END:ONLY_INCLUDE_IF + TokenSearchDiscoveryDataController, }; }, diff --git a/app/core/Engine/types.ts b/app/core/Engine/types.ts index 1c7ee7cbec26..5e8e059fa0fe 100644 --- a/app/core/Engine/types.ts +++ b/app/core/Engine/types.ts @@ -42,6 +42,8 @@ import { RatesController, RatesControllerEvents, RatesControllerActions, + TokenSearchDiscoveryDataController, + TokenSearchDiscoveryDataControllerState, ///: END:ONLY_INCLUDE_IF } from '@metamask/assets-controllers'; import { @@ -370,6 +372,7 @@ export type Controllers = { MultichainBalancesController: MultichainBalancesController; RatesController: RatesController; ///: END:ONLY_INCLUDE_IF + TokenSearchDiscoveryDataController: TokenSearchDiscoveryDataController; }; /** @@ -422,4 +425,5 @@ export type EngineState = { MultichainBalancesController: MultichainBalancesControllerState; RatesController: RatesControllerState; ///: END:ONLY_INCLUDE_IF + TokenSearchDiscoveryDataController: TokenSearchDiscoveryDataControllerState; }; diff --git a/app/selectors/tokenSearchDiscoveryDataController.ts b/app/selectors/tokenSearchDiscoveryDataController.ts new file mode 100644 index 000000000000..b867397c501d --- /dev/null +++ b/app/selectors/tokenSearchDiscoveryDataController.ts @@ -0,0 +1,16 @@ +import { createSelector } from 'reselect'; +import { RootState } from '../reducers'; +import { Hex } from '@metamask/utils'; + +const selectTokenSearchDiscoveryDataControllerState = (state: RootState) => + state.engine.backgroundState.TokenSearchDiscoveryDataController; + +export const selectTokenDisplayData = createSelector( + selectTokenSearchDiscoveryDataControllerState, + (_state: RootState, chainId: Hex) => chainId, + (_state: RootState, _chainId: Hex, address: string) => address, + (state, chainId, address) => { + console.log('>>>>>>>>> statez', state); + return state?.tokenDisplayData.find(d => d.chainId === chainId && d.address === address); + } +); diff --git a/package.json b/package.json index 9f3af3ef0ab1..b3675a4cd030 100644 --- a/package.json +++ b/package.json @@ -143,7 +143,8 @@ "nanoid": "^3.3.8", "undici": "5.28.5", "**/@ethersproject/signing-key/elliptic": "^6.6.0", - "**/@walletconnect/utils/elliptic": "^6.6.0" + "**/@walletconnect/utils/elliptic": "^6.6.0", + "@metamask/assets-controllers": "file:../metamask-core/packages/assets-controllers" }, "dependencies": { "@config-plugins/detox": "^8.0.0", @@ -155,7 +156,7 @@ "@metamask/accounts-controller": "^21.0.0", "@metamask/address-book-controller": "^6.0.1", "@metamask/approval-controller": "^7.1.0", - "@metamask/assets-controllers": "^46.0.0", + "@metamask/assets-controllers": "46.0.0", "@metamask/base-controller": "^7.1.1", "@metamask/bitcoin-wallet-snap": "^0.9.0", "@metamask/composable-controller": "^10.0.0", @@ -212,7 +213,7 @@ "@metamask/stake-sdk": "^1.0.0", "@metamask/swappable-obj-proxy": "^2.1.0", "@metamask/swaps-controller": "^12.0.0", - "@metamask/token-search-discovery-controller": "file:../metamask-core/packages/token-search-discovery-controller", + "@metamask/token-search-discovery-controller": "2.0.0", "@metamask/transaction-controller": "^43.0.0", "@metamask/utils": "^11.0.1", "@ngraveio/bc-ur": "^1.1.6", diff --git a/yarn.lock b/yarn.lock index af4cc0deec6d..c02af6f6b6cd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4481,10 +4481,8 @@ "@metamask/utils" "^10.0.0" nanoid "^3.1.31" -"@metamask/assets-controllers@^46.0.0": +"@metamask/assets-controllers@46.0.0", "@metamask/assets-controllers@file:../metamask-core/packages/assets-controllers": version "46.0.0" - resolved "https://registry.yarnpkg.com/@metamask/assets-controllers/-/assets-controllers-46.0.0.tgz#b3bb495eae490b24ea451d2e1b1a7fba7de9580e" - integrity sha512-iHaS+74ROQBynyLKUuzub5+kYVHuE29L8YJExpq4UJf4vnO7L5FTYEcNCplxBrCzS8qWinZ2RwS5OoUfvXYAKg== dependencies: "@ethereumjs/util" "^8.1.0" "@ethersproject/abi" "^5.7.0" @@ -5579,8 +5577,10 @@ resolved "https://registry.yarnpkg.com/@metamask/test-dapp/-/test-dapp-8.9.0.tgz#bac680e8f0007b3a11440f7e311674d6457d37ed" integrity sha512-N/WfmdrzJm+xbpuqJsfMrlrAhiNDsllIpwt9gDDeEKDlQAfJnMtT9xvOvBJbXY7zgMdtGZuD+KY64jNKabbuVQ== -"@metamask/token-search-discovery-controller@file:../metamask-core/packages/token-search-discovery-controller": +"@metamask/token-search-discovery-controller@2.0.0": version "2.0.0" + resolved "https://consensys.jfrog.io/artifactory/api/npm/npm/@metamask/token-search-discovery-controller/-/token-search-discovery-controller-2.0.0.tgz#f0f87bde6675371e52e6b2874f8540b8703d0cd4" + integrity sha512-YYb1gNFPh/07DMKH7kU2+XRKjDrr1CTGa+Sxk29qM7r46eDOv7lAIuZW4FsYX1M8rT4tKrCsYFX5nCHER54Ogw== dependencies: "@metamask/base-controller" "^7.1.1" "@metamask/utils" "^11.1.0" @@ -29783,5 +29783,5 @@ zstd-codec@^0.1.5: zxcvbn@4.4.2: version "4.4.2" - resolved "https://registry.yarnpkg.com/zxcvbn/-/zxcvbn-4.4.2.tgz#28ec17cf09743edcab056ddd8b1b06262cc73c30" - integrity sha1-KOwXzwl0PtyrBW3dixsGJizHPDA= + resolved "https://consensys.jfrog.io/artifactory/api/npm/npm/zxcvbn/-/zxcvbn-4.4.2.tgz#28ec17cf09743edcab056ddd8b1b06262cc73c30" + integrity sha512-Bq0B+ixT/DMyG8kgX2xWcI5jUvCwqrMxSFam7m0lAf78nf04hv6lNCsyLYdyYTrCVMqNDY/206K7eExYCeSyUQ== From ade13bdf1eaae81e4d29d96e2166c45bd3eb9c69 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Wed, 19 Feb 2025 17:58:54 -0500 Subject: [PATCH 039/124] wire up token search data to views - fetch price from token search data controller (tsdc) when user comes from a search - fetch market data from tsdc - fetch token details from tsdc - fetch swaps allowed tokens from tsdc --- .../UI/AssetOverview/AssetOverview.tsx | 7 +++++- .../TokenDetails/TokenDetails.tsx | 12 +++++++--- app/components/Views/Asset/index.js | 5 ++++- app/components/Views/AssetLoader/index.tsx | 9 ++++---- app/core/Engine/Engine.ts | 22 ++++++++----------- app/core/Engine/constants.ts | 17 ++++++++++++++ .../tokenSearchDiscoveryDataController.ts | 15 +++++++++---- 7 files changed, 60 insertions(+), 27 deletions(-) diff --git a/app/components/UI/AssetOverview/AssetOverview.tsx b/app/components/UI/AssetOverview/AssetOverview.tsx index 3bb73aa331c3..59980839c00f 100644 --- a/app/components/UI/AssetOverview/AssetOverview.tsx +++ b/app/components/UI/AssetOverview/AssetOverview.tsx @@ -69,6 +69,7 @@ import { useMetrics } from '../../../components/hooks/useMetrics'; import { createBuyNavigationDetails } from '../Ramp/routes/utils'; import { TokenI } from '../Tokens/types'; import AssetDetailsActions from '../../../components/Views/AssetDetails/AssetDetailsActions'; +import { isAssetFromSearch, selectTokenDisplayData } from '../../../selectors/tokenSearchDiscoveryDataController'; interface AssetOverviewProps { asset: TokenI; @@ -391,10 +392,14 @@ const AssetOverview: React.FC = ({ secondaryBalance = asset.balanceFiat || ''; } + const tokenResult = useSelector((state: RootState) => selectTokenDisplayData(state, asset.chainId as Hex, asset.address as Hex)); + let currentPrice = 0; let priceDiff = 0; - if (!isPortfolioViewEnabled()) { + if (isAssetFromSearch(asset) && tokenResult?.found) { + currentPrice = tokenResult.price?.price || 0; + } else if (!isPortfolioViewEnabled()) { if (asset.isETH) { currentPrice = conversionRate || 0; } else if (exchangeRate && conversionRate) { diff --git a/app/components/UI/AssetOverview/TokenDetails/TokenDetails.tsx b/app/components/UI/AssetOverview/TokenDetails/TokenDetails.tsx index 9256b3a43d45..1af138ed8f3f 100644 --- a/app/components/UI/AssetOverview/TokenDetails/TokenDetails.tsx +++ b/app/components/UI/AssetOverview/TokenDetails/TokenDetails.tsx @@ -30,6 +30,7 @@ import MarketDetailsList from './MarketDetailsList'; import { TokenI } from '../../Tokens/types'; import StakingEarnings from '../../Stake/components/StakingEarnings'; import { isPortfolioViewEnabled } from '../../../../util/networks'; +import { isAssetFromSearch, selectTokenDisplayData } from '../../../../selectors/tokenSearchDiscoveryDataController'; export interface TokenDetails { contractAddress: string | null; @@ -68,7 +69,7 @@ const TokenDetails: React.FC = ({ asset }) => { const tokenContractAddress = safeToChecksumAddress(asset.address); const tokenList = useSelector(selectTokenList); - const conversionRate = isPortfolioViewEnabled() + const conversionRate = isAssetFromSearch(asset) ? 1 : isPortfolioViewEnabled() ? conversionRateBySymbol : conversionRateLegacy; const tokenExchangeRates = isPortfolioViewEnabled() @@ -78,7 +79,12 @@ const TokenDetails: React.FC = ({ asset }) => { let tokenMetadata; let marketData; - if (asset.isETH) { + const tokenResult = useSelector((state: RootState) => selectTokenDisplayData(state, asset.chainId as Hex, asset.address as Hex)); + + if (isAssetFromSearch(asset) && tokenResult?.found && tokenResult.price) { + marketData = tokenResult.price; + tokenMetadata = tokenResult.token; + } else if (asset.isETH) { marketData = tokenExchangeRates?.[zeroAddress() as Hex]; } else if (tokenContractAddress) { tokenMetadata = tokenList?.[tokenContractAddress.toLowerCase()]; @@ -102,7 +108,7 @@ const TokenDetails: React.FC = ({ asset }) => { : { contractAddress: tokenContractAddress || null, tokenDecimal: tokenMetadata?.decimals || null, - tokenList: tokenMetadata?.aggregators.join(', ') || null, + tokenList: tokenMetadata?.aggregators?.join(', ') || null, }; const marketDetails: MarketDetails = { diff --git a/app/components/Views/Asset/index.js b/app/components/Views/Asset/index.js index 6a1a8fcb401b..f3fd5c9371c1 100644 --- a/app/components/Views/Asset/index.js +++ b/app/components/Views/Asset/index.js @@ -65,6 +65,7 @@ import { toChecksumHexAddress } from '@metamask/controller-utils'; import { selectSwapsTransactions } from '../../../selectors/transactionController'; import Logger from '../../../util/Logger'; import { TOKEN_CATEGORY_HASH } from '../../UI/TransactionElement/utils'; +import { isAssetFromSearch, selectSupportedSwapTokenAddresses } from '../../../selectors/tokenSearchDiscoveryDataController'; const createStyles = (colors) => StyleSheet.create({ @@ -157,6 +158,7 @@ class Asset extends PureComponent { tokens: PropTypes.array, swapsIsLive: PropTypes.bool, swapsTokens: PropTypes.object, + searchDiscoverySwapsTokens: PropTypes.array, swapsTransactions: PropTypes.object, /** * Object that represents the current route info like params passed to it @@ -519,7 +521,7 @@ class Asset extends PureComponent { const isAssetAllowed = asset.isETH || asset.isNative || - asset.address?.toLowerCase() in this.props.swapsTokens; + (isAssetFromSearch(asset) ? this.props.searchDiscoverySwapsTokens?.includes(asset.address?.toLowerCase()) : asset.address?.toLowerCase() in this.props.swapsTokens); const displaySwapsButton = isNetworkAllowed && isAssetAllowed && AppConstants.SWAPS.ACTIVE; @@ -573,6 +575,7 @@ const mapStateToProps = (state, { route }) => ({ swapsTokens: isPortfolioViewEnabled() ? swapsTokensMultiChainObjectSelector(state) : swapsTokensObjectSelector(state), + searchDiscoverySwapsTokens: selectSupportedSwapTokenAddresses(state, route.params.chainId), swapsTransactions: selectSwapsTransactions(state), conversionRate: selectConversionRate(state), currentCurrency: selectCurrentCurrency(state), diff --git a/app/components/Views/AssetLoader/index.tsx b/app/components/Views/AssetLoader/index.tsx index 7d16738e9342..43201be2111e 100644 --- a/app/components/Views/AssetLoader/index.tsx +++ b/app/components/Views/AssetLoader/index.tsx @@ -19,17 +19,16 @@ export interface AssetLoaderProps { export const AssetLoader: React.FC = ({ route: { params: { address, chainId } } }) => { const tokenResult = useSelector((state: RootState) => selectTokenDisplayData(state, chainId, address)); - Logger.log('>>>>>>>>> tokenResult', tokenResult); const navigation = useNavigation(); useEffect(() => { - if (!tokenResult) { - Engine.context.TokenSearchDiscoveryDataController.fetchTokenDisplayData(chainId, address); - } else if (tokenResult.found) { + Engine.context.TokenSearchDiscoveryDataController.fetchTokenDisplayData(chainId, address); + if (tokenResult?.found) { navigation.dispatch( StackActions.replace(Routes.BROWSER.ASSET_VIEW, { ...tokenResult.token, - chainId + chainId, + isFromSearch: true, }) ); } diff --git a/app/core/Engine/Engine.ts b/app/core/Engine/Engine.ts index 6ac723799358..4075fc40016f 100644 --- a/app/core/Engine/Engine.ts +++ b/app/core/Engine/Engine.ts @@ -218,6 +218,7 @@ import { import { BACKGROUND_STATE_CHANGE_EVENT_NAMES, STATELESS_NON_CONTROLLER_NAMES, + swapsSupportedChainIds, } from './constants'; import { getGlobalChainId, @@ -1328,9 +1329,15 @@ export class Engine { }); const tokenSearchDiscoveryDataController = new TokenSearchDiscoveryDataController({ + tokenPricesService: codefiTokenApiV2, + swapsSupportedChainIds, + fetchSwapsTokensThresholdMs: AppConstants.SWAPS.CACHE_TOKENS_THRESHOLD, + fetchTokens: swapsUtils.fetchTokens, messenger: this.controllerMessenger.getRestricted({ name: 'TokenSearchDiscoveryDataController', - allowedActions: [], + allowedActions: [ + 'CurrencyRateController:getState' + ], allowedEvents: [], }), }); @@ -1468,18 +1475,7 @@ export class Engine { AppConstants.SWAPS.CACHE_AGGREGATOR_METADATA_THRESHOLD, fetchTokensThreshold: AppConstants.SWAPS.CACHE_TOKENS_THRESHOLD, fetchTopAssetsThreshold: AppConstants.SWAPS.CACHE_TOP_ASSETS_THRESHOLD, - supportedChainIds: [ - swapsUtils.ETH_CHAIN_ID, - swapsUtils.BSC_CHAIN_ID, - swapsUtils.SWAPS_TESTNET_CHAIN_ID, - swapsUtils.POLYGON_CHAIN_ID, - swapsUtils.AVALANCHE_CHAIN_ID, - swapsUtils.ARBITRUM_CHAIN_ID, - swapsUtils.OPTIMISM_CHAIN_ID, - swapsUtils.ZKSYNC_ERA_CHAIN_ID, - swapsUtils.LINEA_CHAIN_ID, - swapsUtils.BASE_CHAIN_ID, - ], + supportedChainIds: swapsSupportedChainIds, messenger: this.controllerMessenger.getRestricted({ name: 'SwapsController', // TODO: allow these internal calls once GasFeeController diff --git a/app/core/Engine/constants.ts b/app/core/Engine/constants.ts index 0a34d7973adb..401a5dbb89d0 100644 --- a/app/core/Engine/constants.ts +++ b/app/core/Engine/constants.ts @@ -7,6 +7,9 @@ import { SnapControllerStateChangeEvent } from './controllers/SnapController/con import { MultichainBalancesControllerStateChangeEvent } from './controllers/MultichainBalancesController/constants'; import { RatesControllerStateChangeEvent } from './controllers/RatesController/constants'; ///: END:ONLY_INCLUDE_IF + +import { swapsUtils } from '@metamask/swaps-controller'; + /** * Messageable modules that are part of the Engine's context, but are not defined with state. * TODO: Replace with type guard once consistent inheritance for non-controllers is implemented. See: https://github.com/MetaMask/decisions/pull/41 @@ -42,6 +45,7 @@ export const BACKGROUND_STATE_CHANGE_EVENT_NAMES = [ 'TokenRatesController:stateChange', 'TokensController:stateChange', 'TokenSearchDiscoveryController:stateChange', + 'TokenSearchDiscoveryDataController:stateChange', 'TransactionController:stateChange', ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) SnapControllerStateChangeEvent, @@ -57,3 +61,16 @@ export const BACKGROUND_STATE_CHANGE_EVENT_NAMES = [ RatesControllerStateChangeEvent, ///: END:ONLY_INCLUDE_IF ] as const; + +export const swapsSupportedChainIds = [ + swapsUtils.ETH_CHAIN_ID, + swapsUtils.BSC_CHAIN_ID, + swapsUtils.SWAPS_TESTNET_CHAIN_ID, + swapsUtils.POLYGON_CHAIN_ID, + swapsUtils.AVALANCHE_CHAIN_ID, + swapsUtils.ARBITRUM_CHAIN_ID, + swapsUtils.OPTIMISM_CHAIN_ID, + swapsUtils.ZKSYNC_ERA_CHAIN_ID, + swapsUtils.LINEA_CHAIN_ID, + swapsUtils.BASE_CHAIN_ID, +]; diff --git a/app/selectors/tokenSearchDiscoveryDataController.ts b/app/selectors/tokenSearchDiscoveryDataController.ts index b867397c501d..bdf300afddb7 100644 --- a/app/selectors/tokenSearchDiscoveryDataController.ts +++ b/app/selectors/tokenSearchDiscoveryDataController.ts @@ -1,16 +1,23 @@ import { createSelector } from 'reselect'; import { RootState } from '../reducers'; import { Hex } from '@metamask/utils'; +import { selectCurrentCurrency } from './currencyRateController'; + +export const isAssetFromSearch = (asset: unknown) => typeof asset === 'object' && asset !== null && 'isFromSearch' in asset && asset.isFromSearch === true; const selectTokenSearchDiscoveryDataControllerState = (state: RootState) => state.engine.backgroundState.TokenSearchDiscoveryDataController; export const selectTokenDisplayData = createSelector( selectTokenSearchDiscoveryDataControllerState, + selectCurrentCurrency, (_state: RootState, chainId: Hex) => chainId, (_state: RootState, _chainId: Hex, address: string) => address, - (state, chainId, address) => { - console.log('>>>>>>>>> statez', state); - return state?.tokenDisplayData.find(d => d.chainId === chainId && d.address === address); - } + (state, currentCurrency, chainId, address) => state?.tokenDisplayData.find(d => d.chainId === chainId && d.address === address && d.currency === currentCurrency) +); + +export const selectSupportedSwapTokenAddresses = createSelector( + selectTokenSearchDiscoveryDataControllerState, + (_state: RootState, chainId: Hex) => chainId, + (state, chainId) => state?.swapsTokenAddressesByChainId[chainId]?.addresses, ); From 6b4fde7857c55c64405f9e4dba8ce552874ecdf2 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Wed, 19 Feb 2025 19:24:06 -0500 Subject: [PATCH 040/124] update assets-controllers local version --- yarn.lock | 43 +++++-------------------------------------- 1 file changed, 5 insertions(+), 38 deletions(-) diff --git a/yarn.lock b/yarn.lock index a4fef8c0f7a8..87764bd9b84e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4553,10 +4553,8 @@ "@metamask/utils" "^10.0.0" nanoid "^3.1.31" -"@metamask/assets-controllers@^50.0.0": +"@metamask/assets-controllers@^50.0.0", "@metamask/assets-controllers@file:../metamask-core/packages/assets-controllers": version "50.0.0" - resolved "https://registry.yarnpkg.com/@metamask/assets-controllers/-/assets-controllers-50.0.0.tgz#de2e30659a32ac9371cfc055c1c4107336145808" - integrity sha512-mPT5g0GovgxQrZp7n/U85qxECbbezSdEL7Da5tGlbE5LkEwcEaYYBwVnlrE7Tv1cOrrW/GENlR1eG0nyDPvVlQ== dependencies: "@ethereumjs/util" "^8.1.0" "@ethersproject/abi" "^5.7.0" @@ -4574,7 +4572,7 @@ "@metamask/polling-controller" "^12.0.3" "@metamask/rpc-errors" "^7.0.2" "@metamask/snaps-utils" "^8.10.0" - "@metamask/utils" "^11.1.0" + "@metamask/utils" "^11.2.0" "@types/bn.js" "^5.1.5" "@types/uuid" "^8.3.0" async-mutex "^0.5.0" @@ -4586,37 +4584,6 @@ single-call-balance-checker-abi "^1.0.0" uuid "^8.3.2" -"@metamask/assets-controllers@file:../metamask-core/packages/assets-controllers": - version "46.0.0" - dependencies: - "@ethereumjs/util" "^8.1.0" - "@ethersproject/abi" "^5.7.0" - "@ethersproject/address" "^5.7.0" - "@ethersproject/bignumber" "^5.7.0" - "@ethersproject/contracts" "^5.7.0" - "@ethersproject/providers" "^5.7.0" - "@metamask/abi-utils" "^2.0.3" - "@metamask/base-controller" "^7.1.1" - "@metamask/contract-metadata" "^2.4.0" - "@metamask/controller-utils" "^11.4.5" - "@metamask/eth-query" "^4.0.0" - "@metamask/metamask-eth-abis" "^3.1.1" - "@metamask/polling-controller" "^12.0.2" - "@metamask/rpc-errors" "^7.0.2" - "@metamask/snaps-utils" "^8.3.0" - "@metamask/utils" "^11.0.1" - "@types/bn.js" "^5.1.5" - "@types/uuid" "^8.3.0" - async-mutex "^0.5.0" - bitcoin-address-validation "^2.2.3" - bn.js "^5.2.1" - cockatiel "^3.1.2" - immer "^9.0.6" - lodash "^4.17.21" - multiformats "^13.1.0" - single-call-balance-checker-abi "^1.0.0" - uuid "^8.3.2" - "@metamask/base-controller@^7.0.1", "@metamask/base-controller@^7.0.2", "@metamask/base-controller@^7.0.3", "@metamask/base-controller@^7.1.0", "@metamask/base-controller@^7.1.1": version "7.1.1" resolved "https://registry.yarnpkg.com/@metamask/base-controller/-/base-controller-7.1.1.tgz#837216ee099563b2106202fa0ed376dc909dfbb9" @@ -4672,7 +4639,7 @@ resolved "https://registry.yarnpkg.com/@metamask/contract-metadata/-/contract-metadata-2.5.0.tgz#33921fa9c15eb1863f55dcd5f75467ae15614ebb" integrity sha512-+j7jEcp0P1OUMEpa/OIwfJs/ahBC/akwgWxaRTSWX2SWABvlUKBVRMtslfL94Qj2wN2xw8xjaUy5nSHqrznqDA== -"@metamask/controller-utils@^11.0.0", "@metamask/controller-utils@^11.3.0", "@metamask/controller-utils@^11.4.4", "@metamask/controller-utils@^11.4.5", "@metamask/controller-utils@^11.5.0": +"@metamask/controller-utils@^11.0.0", "@metamask/controller-utils@^11.3.0", "@metamask/controller-utils@^11.4.4", "@metamask/controller-utils@^11.5.0": version "11.5.0" resolved "https://registry.yarnpkg.com/@metamask/controller-utils/-/controller-utils-11.5.0.tgz#27daa18c6d2b63189bdb21cd7c6550d81b5e2581" integrity sha512-WXR6f33YzT4RSb5HK6RKg9CrE4sJO1mMrrtPZgmvdFRxPm+KE5tPrnEgnlhjrVzRB0eZov76hd+jSutezqRAbg== @@ -5286,7 +5253,7 @@ fastest-levenshtein "^1.0.16" punycode "^2.1.1" -"@metamask/polling-controller@^12.0.0", "@metamask/polling-controller@^12.0.2", "@metamask/polling-controller@^12.0.3": +"@metamask/polling-controller@^12.0.0", "@metamask/polling-controller@^12.0.3": version "12.0.3" resolved "https://consensys.jfrog.io/artifactory/api/npm/npm/@metamask/polling-controller/-/polling-controller-12.0.3.tgz#377716f6c8610373aed1319d2a10d75c12a50f6f" integrity sha512-YOsFgyqd5s32WQfTWwBS4hGOGrIVL92AUh+WeF3Gb2Q7vblnz7152PW354+fT9xG3Uk/zdtss35LrltxQvlc/A== @@ -5706,7 +5673,7 @@ lodash "^4.17.21" uuid "^8.3.2" -"@metamask/utils@^10.0.0", "@metamask/utils@^11.0.1", "@metamask/utils@^11.1.0", "@metamask/utils@^8.2.0", "@metamask/utils@^8.3.0", "@metamask/utils@^9.0.0", "@metamask/utils@^9.1.0", "@metamask/utils@^9.2.1", "@metamask/utils@^9.3.0": +"@metamask/utils@^10.0.0", "@metamask/utils@^11.0.1", "@metamask/utils@^11.1.0", "@metamask/utils@^11.2.0", "@metamask/utils@^8.2.0", "@metamask/utils@^8.3.0", "@metamask/utils@^9.0.0", "@metamask/utils@^9.1.0", "@metamask/utils@^9.2.1", "@metamask/utils@^9.3.0": version "11.2.0" resolved "https://registry.yarnpkg.com/@metamask/utils/-/utils-11.2.0.tgz#f2b35cbe6536c56071b0971f97f2395b860885c4" integrity sha512-5Y4bd8Axvi2kJKjp6Jlbb9wyoTrSZxQjWvVGPevpErAc7SCUYUuW0QOOPVu7YmT+bzisTpnFnRE8LjtwYCKGAg== From 44c9f0779f338a4b3004a35a663fd37245ec9a99 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Mon, 24 Feb 2025 12:10:51 -0500 Subject: [PATCH 041/124] update yarn.lock --- yarn.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index 87764bd9b84e..0b556759ccf1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4572,7 +4572,7 @@ "@metamask/polling-controller" "^12.0.3" "@metamask/rpc-errors" "^7.0.2" "@metamask/snaps-utils" "^8.10.0" - "@metamask/utils" "^11.2.0" + "@metamask/utils" "^11.1.0" "@types/bn.js" "^5.1.5" "@types/uuid" "^8.3.0" async-mutex "^0.5.0" @@ -5673,7 +5673,7 @@ lodash "^4.17.21" uuid "^8.3.2" -"@metamask/utils@^10.0.0", "@metamask/utils@^11.0.1", "@metamask/utils@^11.1.0", "@metamask/utils@^11.2.0", "@metamask/utils@^8.2.0", "@metamask/utils@^8.3.0", "@metamask/utils@^9.0.0", "@metamask/utils@^9.1.0", "@metamask/utils@^9.2.1", "@metamask/utils@^9.3.0": +"@metamask/utils@^10.0.0", "@metamask/utils@^11.0.1", "@metamask/utils@^11.1.0", "@metamask/utils@^8.2.0", "@metamask/utils@^8.3.0", "@metamask/utils@^9.0.0", "@metamask/utils@^9.1.0", "@metamask/utils@^9.2.1", "@metamask/utils@^9.3.0": version "11.2.0" resolved "https://registry.yarnpkg.com/@metamask/utils/-/utils-11.2.0.tgz#f2b35cbe6536c56071b0971f97f2395b860885c4" integrity sha512-5Y4bd8Axvi2kJKjp6Jlbb9wyoTrSZxQjWvVGPevpErAc7SCUYUuW0QOOPVu7YmT+bzisTpnFnRE8LjtwYCKGAg== From e4928bd28bd66ff91566cbbe45b97cf1f2ad665e Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Mon, 24 Feb 2025 22:12:24 -0500 Subject: [PATCH 042/124] Override Swap functionality for search & discovery assets --- app/components/Nav/Main/MainNavigator.js | 10 +++++ .../UI/AssetOverview/AssetOverview.tsx | 38 ++++++++++++++----- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/app/components/Nav/Main/MainNavigator.js b/app/components/Nav/Main/MainNavigator.js index 9c8fe3daccf3..c9f25b0123e2 100644 --- a/app/components/Nav/Main/MainNavigator.js +++ b/app/components/Nav/Main/MainNavigator.js @@ -229,6 +229,16 @@ const BrowserFlow = (props) => ( component={Asset} initialParams={props.route.params} /> + + ); diff --git a/app/components/UI/AssetOverview/AssetOverview.tsx b/app/components/UI/AssetOverview/AssetOverview.tsx index e6c031cb25d1..dfd70463e729 100644 --- a/app/components/UI/AssetOverview/AssetOverview.tsx +++ b/app/components/UI/AssetOverview/AssetOverview.tsx @@ -166,14 +166,26 @@ const AssetOverview: React.FC = ({ }; const handleSwapNavigation = useCallback(() => { - navigation.navigate('Swaps', { - screen: 'SwapsAmountView', - params: { - sourceToken: asset.address ?? swapsUtils.NATIVE_SWAPS_TOKEN_ADDRESS, + if (isAssetFromSearch(asset)) { + navigation.navigate('Swaps', { + screen: 'SwapsAmountView', + params: { + sourceToken: swapsUtils.NATIVE_SWAPS_TOKEN_ADDRESS, + destinationToken: asset.address, sourcePage: 'MainView', chainId: asset.chainId, }, }); + } else { + navigation.navigate('Swaps', { + screen: 'SwapsAmountView', + params: { + sourceToken: asset.address ?? swapsUtils.NATIVE_SWAPS_TOKEN_ADDRESS, + sourcePage: 'MainView', + chainId: asset.chainId, + }, + }); + } }, [navigation, asset.address, asset.chainId]); const handleBridgeNavigation = useCallback(() => { @@ -230,12 +242,14 @@ const AssetOverview: React.FC = ({ const goToSwaps = useCallback(() => { if (isPortfolioViewEnabled()) { - navigation.navigate(Routes.WALLET.HOME, { - screen: Routes.WALLET.TAB_STACK_FLOW, - params: { - screen: Routes.WALLET_VIEW, - }, - }); + if (!isAssetFromSearch(asset)) { + navigation.navigate(Routes.WALLET.HOME, { + screen: Routes.WALLET.TAB_STACK_FLOW, + params: { + screen: Routes.WALLET_VIEW, + }, + }); + } if (asset.chainId !== selectedChainId) { const { NetworkController, MultichainNetworkController } = Engine.context; @@ -243,6 +257,10 @@ const AssetOverview: React.FC = ({ NetworkController.getNetworkConfigurationByChainId( asset.chainId as Hex, ); + + if (!networkConfiguration) { + // TODO: Popup the "add network" modal + } const networkClientId = networkConfiguration?.rpcEndpoints?.[ From 4d935cb7d39784166f5048353682b482f3b69e0d Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Wed, 26 Feb 2025 14:45:53 -0500 Subject: [PATCH 043/124] Allow user to swap assets for tokens they don't own on networks they don't have added. --- .../UI/AssetOverview/AssetOverview.tsx | 36 +++++--- app/components/UI/NetworkModal/index.tsx | 86 +++++++++++-------- app/components/hooks/useAddNetwork.tsx | 45 ++++++++++ 3 files changed, 120 insertions(+), 47 deletions(-) create mode 100644 app/components/hooks/useAddNetwork.tsx diff --git a/app/components/UI/AssetOverview/AssetOverview.tsx b/app/components/UI/AssetOverview/AssetOverview.tsx index dfd70463e729..ec45b6affcf8 100644 --- a/app/components/UI/AssetOverview/AssetOverview.tsx +++ b/app/components/UI/AssetOverview/AssetOverview.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { TouchableOpacity, View } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import { useDispatch, useSelector } from 'react-redux'; @@ -70,6 +70,8 @@ import { createBuyNavigationDetails } from '../Ramp/routes/utils'; import { TokenI } from '../Tokens/types'; import AssetDetailsActions from '../../../components/Views/AssetDetails/AssetDetailsActions'; import { isAssetFromSearch, selectTokenDisplayData } from '../../../selectors/tokenSearchDiscoveryDataController'; +import { useAddNetwork } from '../../hooks/useAddNetwork'; +import { PopularList } from '../../../util/networks/customNetworks'; interface AssetOverviewProps { asset: TokenI; @@ -240,7 +242,9 @@ const AssetOverview: React.FC = ({ navigation.navigate('SendFlowView', {}); }; - const goToSwaps = useCallback(() => { + const { addPopularNetwork, networkModal } = useAddNetwork(); + + const goToSwaps = useCallback(async () => { if (isPortfolioViewEnabled()) { if (!isAssetFromSearch(asset)) { navigation.navigate(Routes.WALLET.HOME, { @@ -253,13 +257,23 @@ const AssetOverview: React.FC = ({ if (asset.chainId !== selectedChainId) { const { NetworkController, MultichainNetworkController } = Engine.context; - const networkConfiguration = + let networkConfiguration = NetworkController.getNetworkConfigurationByChainId( asset.chainId as Hex, ); if (!networkConfiguration) { - // TODO: Popup the "add network" modal + const network = PopularList.find((network) => network.chainId === asset.chainId); + if (network) { + try { + await addPopularNetwork(network); + } catch (error) { + return; + } + networkConfiguration = NetworkController.getNetworkConfigurationByChainId( + asset.chainId as Hex, + ); + } } const networkClientId = @@ -267,13 +281,13 @@ const AssetOverview: React.FC = ({ networkConfiguration.defaultRpcEndpointIndex ]?.networkClientId; - MultichainNetworkController.setActiveNetwork( + await MultichainNetworkController.setActiveNetwork( networkClientId as string, - ).then(() => { - setTimeout(() => { - handleSwapNavigation(); - }, 500); - }); + ); + + setTimeout(() => { + handleSwapNavigation(); + }, 500); } else { handleSwapNavigation(); } @@ -297,6 +311,7 @@ const AssetOverview: React.FC = ({ trackEvent, createEventBuilder, handleSwapNavigation, + addPopularNetwork, ]); const onBuy = () => { @@ -498,6 +513,7 @@ const AssetOverview: React.FC = ({ {/* // */} + {networkModal} )} diff --git a/app/components/UI/NetworkModal/index.tsx b/app/components/UI/NetworkModal/index.tsx index 10a5e625efb1..a6f67f4138db 100644 --- a/app/components/UI/NetworkModal/index.tsx +++ b/app/components/UI/NetworkModal/index.tsx @@ -67,6 +67,9 @@ interface NetworkProps { onNetworkSwitch?: () => void; showPopularNetworkModal: boolean; safeChains?: SafeChain[]; + onReject?: () => void; + onAccept?: () => void; + autoSwitchNetwork?: boolean; } const NetworkModals = (props: NetworkProps) => { @@ -86,6 +89,9 @@ const NetworkModals = (props: NetworkProps) => { shouldNetworkSwitchPopToWallet, onNetworkSwitch, safeChains, + onReject, + onAccept, + autoSwitchNetwork, } = props; const { trackEvent, createEventBuilder } = useMetrics(); const [showDetails, setShowDetails] = React.useState(false); @@ -138,42 +144,6 @@ const NetworkModals = (props: NetworkProps) => { } }, [customNetworkInformation.chainId, isAllNetworks, tokenNetworkFilter]); - const addNetwork = async () => { - const isValidUrl = validateRpcUrl(rpcUrl); - if (showPopularNetworkModal) { - // track popular network - trackEvent( - createEventBuilder(MetaMetricsEvents.NETWORK_ADDED) - .addProperties({ - chain_id: toHex(chainId), - source: 'Popular network list', - symbol: ticker, - }) - .build(), - ); - } else if (safeChains) { - const { safeChain, safeRPCUrl } = rpcIdentifierUtility( - rpcUrl, - safeChains, - ); - // track custom network, this shouldn't be in popular networks modal - trackEvent( - createEventBuilder(MetaMetricsEvents.NETWORK_ADDED) - .addProperties({ - chain_id: toHex(safeChain.chainId), - source: { anonymous: true, value: 'Custom Network Added' }, - symbol: safeChain.nativeCurrency.symbol, - }) - .addSensitiveProperties({ rpcUrl: safeRPCUrl }) - .build(), - ); - } else { - Logger.log('MetaMetrics - Unable to capture custom network'); - } - - setNetworkAdded(isValidUrl); - }; - const cancelButtonProps: ButtonProps = { variant: ButtonVariants.Secondary, label: strings('accountApproval.cancel'), @@ -269,6 +239,7 @@ const NetworkModals = (props: NetworkProps) => { } onClose(); + onAccept?.(); }; const handleExistingNetwork = async ( @@ -372,6 +343,47 @@ const NetworkModals = (props: NetworkProps) => { : navigation.goBack(); } dispatch(networkSwitched({ networkUrl: url.href, networkStatus: true })); + onAccept?.(); + }; + + const addNetwork = async () => { + const isValidUrl = validateRpcUrl(rpcUrl); + if (showPopularNetworkModal) { + // track popular network + trackEvent( + createEventBuilder(MetaMetricsEvents.NETWORK_ADDED) + .addProperties({ + chain_id: toHex(chainId), + source: 'Popular network list', + symbol: ticker, + }) + .build(), + ); + } else if (safeChains) { + const { safeChain, safeRPCUrl } = rpcIdentifierUtility( + rpcUrl, + safeChains, + ); + // track custom network, this shouldn't be in popular networks modal + trackEvent( + createEventBuilder(MetaMetricsEvents.NETWORK_ADDED) + .addProperties({ + chain_id: toHex(safeChain.chainId), + source: { anonymous: true, value: 'Custom Network Added' }, + symbol: safeChain.nativeCurrency.symbol, + }) + .addSensitiveProperties({ rpcUrl: safeRPCUrl }) + .build(), + ); + } else { + Logger.log('MetaMetrics - Unable to capture custom network'); + } + + if (autoSwitchNetwork) { + switchNetwork(); + } else { + setNetworkAdded(isValidUrl); + } }; return ( @@ -435,7 +447,7 @@ const NetworkModals = (props: NetworkProps) => { { onReject?.(); onClose(); }} onConfirm={addNetwork} isCustomNetwork={!showPopularNetworkModal} /> diff --git a/app/components/hooks/useAddNetwork.tsx b/app/components/hooks/useAddNetwork.tsx new file mode 100644 index 000000000000..46826d79dd61 --- /dev/null +++ b/app/components/hooks/useAddNetwork.tsx @@ -0,0 +1,45 @@ +import React, { useState } from 'react'; +import { PopularList } from '../../util/networks/customNetworks'; +import { noop } from 'lodash'; +import NetworkModals from '../UI/NetworkModal'; +import { useNavigation } from '@react-navigation/native'; +type PopularNetwork = typeof PopularList[number]; + +export const useAddNetwork = () => { + const [popularNetwork, setPopularNetwork] = useState(null); + const [resolveAddNetwork, setResolveAddNetwork] = useState<() => void>(noop); + const [rejectAddNetwork, setRejectAddNetwork] = useState<() => void>(noop); + const navigation = useNavigation(); + + const addPopularNetwork = (network: PopularNetwork) => new Promise((resolve, reject) => { + setResolveAddNetwork(() => resolve); + setRejectAddNetwork(() => reject); + setPopularNetwork(network); + }); + + const onCloseModal = () => { + setPopularNetwork(null); + setResolveAddNetwork(noop); + setRejectAddNetwork(noop); + }; + + const networkModal = !popularNetwork ? null : ( + + ); + + return { + addPopularNetwork, + networkModal, + }; +}; From 56f6c4b71aeebce4b7683be51965aa76fb4a190f Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Mon, 3 Mar 2025 12:47:32 -0500 Subject: [PATCH 044/124] Add token search to UrlAutocomplete --- app/components/UI/UrlAutocomplete/Result.tsx | 44 ++++++++++----- .../UrlAutocomplete.constants.ts | 2 +- app/components/UI/UrlAutocomplete/index.tsx | 53 +++++++++++++------ app/components/UI/UrlAutocomplete/styles.ts | 7 ++- app/components/UI/UrlAutocomplete/types.ts | 1 + .../useTokenSearchDiscovery.ts | 20 +++++-- 6 files changed, 92 insertions(+), 35 deletions(-) diff --git a/app/components/UI/UrlAutocomplete/Result.tsx b/app/components/UI/UrlAutocomplete/Result.tsx index a60ce926e1c5..d860d1ae216d 100644 --- a/app/components/UI/UrlAutocomplete/Result.tsx +++ b/app/components/UI/UrlAutocomplete/Result.tsx @@ -9,13 +9,15 @@ import { IconName } from '../../../component-library/components/Icons/Icon'; import { useDispatch } from 'react-redux'; import { removeBookmark } from '../../../actions/bookmarks'; import stylesheet from './styles'; +import { AutocompleteSearchResult } from './types'; +import AssetIcon from '../AssetIcon'; +import BadgeWrapper from '../../../component-library/components/Badges/BadgeWrapper'; +import Badge, { BadgeVariant } from '../../../component-library/components/Badges/Badge'; +import { NetworkBadgeSource } from '../AssetOverview/Balance/Balance'; +import AvatarToken from '../../../component-library/components/Avatars/Avatar/variants/AvatarToken'; interface ResultProps { - result: { - url: string; - name: string; - type: string; - }; + result: AutocompleteSearchResult; onPress: () => void; } @@ -37,18 +39,34 @@ export const Result: React.FC = memo(({ result, onPress }) => { onPress={onPress} > - + {result.type === 'tokens' ? ( + + )} + > + + + ) : ( + + )} - {name} + {result.name} - {result.url} + {result.type === 'tokens' ? result.symbol : result.url} { diff --git a/app/components/UI/UrlAutocomplete/UrlAutocomplete.constants.ts b/app/components/UI/UrlAutocomplete/UrlAutocomplete.constants.ts index 3863f1bc3d98..d8cc019ca815 100644 --- a/app/components/UI/UrlAutocomplete/UrlAutocomplete.constants.ts +++ b/app/components/UI/UrlAutocomplete/UrlAutocomplete.constants.ts @@ -1,2 +1,2 @@ export const MAX_RECENTS = 5; -export const ORDERED_CATEGORIES = ['sites', 'tokens', 'recents', 'favorites']; +export const ORDERED_CATEGORIES = ['tokens', 'sites', 'recents', 'favorites']; diff --git a/app/components/UI/UrlAutocomplete/index.tsx b/app/components/UI/UrlAutocomplete/index.tsx index cfebbe1e010d..b40960b55f17 100644 --- a/app/components/UI/UrlAutocomplete/index.tsx +++ b/app/components/UI/UrlAutocomplete/index.tsx @@ -12,6 +12,8 @@ import { View, Text, SectionList, + ActivityIndicator, + SectionListRenderItem, } from 'react-native'; import dappUrlList from '../../../util/dapp-url-list'; import Fuse from 'fuse.js'; @@ -30,11 +32,15 @@ import { strings } from '../../../../locales/i18n'; import { selectBrowserBookmarksWithType, selectBrowserHistoryWithType } from '../../../selectors/browser'; import { MAX_RECENTS, ORDERED_CATEGORIES } from './UrlAutocomplete.constants'; import { Result } from './Result'; +import useTokenSearchDiscovery from '../../hooks/useTokenSearchDiscovery/useTokenSearchDiscovery'; +import { Hex } from '@metamask/utils'; export * from './types'; const dappsWithType = dappUrlList.map(i => ({...i, type: 'sites'} as const)); +const TOKEN_SEARCH_LIMIT = 10; + /** * Autocomplete list that appears when the browser url bar is focused */ @@ -43,12 +49,22 @@ const UrlAutocomplete = forwardRef< UrlAutocompleteComponentProps >(({ onSelect, onDismiss }, ref) => { const [fuseResults, setFuseResults] = useState([]); - const [tokenResults, setTokenResults] = useState([]); + const {searchTokens, results: tokenSearchResults, reset: resetTokenSearch, isLoading: isTokenSearchLoading} = useTokenSearchDiscovery(); + const tokenResults: TokenSearchResult[] = useMemo(() => tokenSearchResults.map(({tokenAddress, usdPricePercentChange: _, chainId, ...rest}) => ({ + ...rest, + type: 'tokens', + address: tokenAddress, + chainId: chainId as Hex, + })), [tokenSearchResults]); + const hasResults = fuseResults.length > 0 || tokenResults.length > 0; const resultsByCategory: {category: string, data: AutocompleteSearchResult[]}[] = useMemo(() => ( ORDERED_CATEGORIES.flatMap((category) => { if (category === 'tokens') { + if (tokenResults.length === 0 && !isTokenSearchLoading) { + return []; + } return { category, data: tokenResults, @@ -70,7 +86,7 @@ const UrlAutocomplete = forwardRef< data, }; }) - ), [fuseResults, tokenResults]); + ), [fuseResults, tokenResults, isTokenSearchLoading]); const browserHistory = useSelector(selectBrowserHistoryWithType); const bookmarks = useSelector(selectBrowserBookmarksWithType); @@ -93,15 +109,7 @@ const UrlAutocomplete = forwardRef< ...browserHistory, ...bookmarks, ]); - setTokenResults([ - { - type: 'tokens', - name: 'DEGEN', - symbol: 'DEGEN', - address: '0x4ed4e862860bed51a9570b96d89af5e1b0efefed', - chainId: '0x2105', - } - ]); + resetTokenSearch(); return; } const fuseSearchResult = fuseRef.current?.search(text); @@ -110,7 +118,13 @@ const UrlAutocomplete = forwardRef< } else { setFuseResults([]); } - }, [browserHistory, bookmarks]); + + searchTokens({ + query: text, + limit: TOKEN_SEARCH_LIMIT.toString(), + }); + + }, [browserHistory, bookmarks, resetTokenSearch, searchTokens]); /** * Debounce the search function @@ -125,8 +139,8 @@ const UrlAutocomplete = forwardRef< debouncedSearch.cancel(); resultsRef.current?.setNativeProps({ style: { display: 'none' } }); setFuseResults([]); - setTokenResults([]); - }, [debouncedSearch]); + resetTokenSearch(); + }, [debouncedSearch, resetTokenSearch]); const dismissAutocomplete = () => { hide(); @@ -167,10 +181,15 @@ const UrlAutocomplete = forwardRef< }, [browserHistory, bookmarks, search]); const renderSectionHeader = useCallback(({section: {category}}) => ( - {strings(`autocomplete.${category}`)} - ), [styles]); + + {strings(`autocomplete.${category}`)} + {category === 'tokens' && isTokenSearchLoading && ( + + )} + + ), [styles, isTokenSearchLoading]); - const renderItem = useCallback(({item}) => ( + const renderItem: SectionListRenderItem = useCallback(({item}) => ( { diff --git a/app/components/UI/UrlAutocomplete/styles.ts b/app/components/UI/UrlAutocomplete/styles.ts index 01a1e36e6635..7ca4d333b6a4 100644 --- a/app/components/UI/UrlAutocomplete/styles.ts +++ b/app/components/UI/UrlAutocomplete/styles.ts @@ -13,10 +13,15 @@ const styleSheet = ({ theme: { colors, typography } }: { theme: Theme }) => contentContainer: { paddingVertical: 15, }, + categoryWrapper: { + padding: 10, + flexDirection: 'row', + alignItems: 'center', + }, category: { color: colors.text.default, - padding: 10, backgroundColor: colors.background.default, + marginRight: 10, ...typography.lHeadingSM, } as TextStyle, bookmarkIco: { diff --git a/app/components/UI/UrlAutocomplete/types.ts b/app/components/UI/UrlAutocomplete/types.ts index 0cda0e912ff7..dc40daa517a7 100644 --- a/app/components/UI/UrlAutocomplete/types.ts +++ b/app/components/UI/UrlAutocomplete/types.ts @@ -57,6 +57,7 @@ export type TokenSearchResult = { symbol: string; address: string; chainId: Hex; + logoUrl?: string; }; export type AutocompleteSearchResult = FuseSearchResult | TokenSearchResult; diff --git a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts index bf752842c79a..aa27d5e52a24 100644 --- a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts +++ b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts @@ -1,4 +1,4 @@ -import { useState, useRef, useMemo } from 'react'; +import { useState, useRef, useMemo, useCallback } from 'react'; import { useSelector } from 'react-redux'; import { debounce } from 'lodash'; import Engine from '../../../core/Engine'; @@ -8,7 +8,8 @@ import { TokenSearchParams, } from '@metamask/token-search-discovery-controller'; -const SEARCH_DEBOUNCE_DELAY = 300; +const SEARCH_DEBOUNCE_DELAY = 50; +const MINIMUM_QUERY_LENGTH = 2; export const useTokenSearchDiscovery = () => { const recentSearches = useSelector(selectRecentTokenSearches); @@ -24,6 +25,12 @@ export const useTokenSearchDiscovery = () => { setError(null); const requestId = ++latestRequestId.current; + if (!params.query || params.query.length < MINIMUM_QUERY_LENGTH) { + setResults([]); + setIsLoading(false); + return; + } + try { const { TokenSearchDiscoveryController } = Engine.context; const result = await TokenSearchDiscoveryController.searchTokens( @@ -34,7 +41,7 @@ export const useTokenSearchDiscovery = () => { } } catch (err) { if (requestId === latestRequestId.current) { - setError(err as Error); + setError(err as Error); } } finally { if (requestId === latestRequestId.current) { @@ -45,12 +52,19 @@ export const useTokenSearchDiscovery = () => { [], ); + const reset = useCallback(() => { + setResults([]); + setError(null); + setIsLoading(false); + }, []); + return { searchTokens, recentSearches, isLoading, error, results, + reset, }; }; From b65f4f64a9bd35fd0fa7463f33703289165c93b0 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Tue, 4 Mar 2025 16:35:58 -0500 Subject: [PATCH 045/124] add swap button on token search results --- .../UI/AssetOverview/AssetOverview.tsx | 26 +++--- app/components/UI/UrlAutocomplete/Result.tsx | 29 ++++-- app/components/UI/UrlAutocomplete/index.tsx | 89 ++++++++++++++++++- app/components/UI/UrlAutocomplete/styles.ts | 2 +- app/components/Views/AssetLoader/index.tsx | 1 - 5 files changed, 123 insertions(+), 24 deletions(-) diff --git a/app/components/UI/AssetOverview/AssetOverview.tsx b/app/components/UI/AssetOverview/AssetOverview.tsx index 34468aa3e221..e78a3a18bfda 100644 --- a/app/components/UI/AssetOverview/AssetOverview.tsx +++ b/app/components/UI/AssetOverview/AssetOverview.tsx @@ -177,19 +177,17 @@ const AssetOverview: React.FC = ({ destinationToken: asset.address, sourcePage: 'MainView', chainId: asset.chainId, - }, - }); + }}); } else { - navigation.navigate('Swaps', { - screen: 'SwapsAmountView', - params: { - sourceToken: asset.address ?? swapsUtils.NATIVE_SWAPS_TOKEN_ADDRESS, - sourcePage: 'MainView', - chainId: asset.chainId, - }, - }); + navigation.navigate('Swaps', { + screen: 'SwapsAmountView', + params: { + sourceToken: asset.address ?? swapsUtils.NATIVE_SWAPS_TOKEN_ADDRESS, + sourcePage: 'MainView', + chainId: asset.chainId, + }}); } - }, [navigation, asset.address, asset.chainId]); + }, [navigation, asset]); const handleBridgeNavigation = useCallback(() => { navigation.navigate('Bridge', { @@ -263,9 +261,9 @@ const AssetOverview: React.FC = ({ NetworkController.getNetworkConfigurationByChainId( asset.chainId as Hex, ); - + if (!networkConfiguration) { - const network = PopularList.find((network) => network.chainId === asset.chainId); + const network = PopularList.find((popularNetwork) => popularNetwork.chainId === asset.chainId); if (network) { try { await addPopularNetwork(network); @@ -308,12 +306,12 @@ const AssetOverview: React.FC = ({ } }, [ navigation, - asset.chainId, selectedChainId, trackEvent, createEventBuilder, handleSwapNavigation, addPopularNetwork, + asset, ]); const onBuy = () => { diff --git a/app/components/UI/UrlAutocomplete/Result.tsx b/app/components/UI/UrlAutocomplete/Result.tsx index d860d1ae216d..e1fb28c310de 100644 --- a/app/components/UI/UrlAutocomplete/Result.tsx +++ b/app/components/UI/UrlAutocomplete/Result.tsx @@ -6,26 +6,30 @@ import WebsiteIcon from '../WebsiteIcon'; import ButtonIcon from '../../../component-library/components/Buttons/ButtonIcon'; import { deleteFavoriteTestId } from '../../../../wdio/screen-objects/testIDs/BrowserScreen/UrlAutocomplete.testIds'; import { IconName } from '../../../component-library/components/Icons/Icon'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { removeBookmark } from '../../../actions/bookmarks'; import stylesheet from './styles'; -import { AutocompleteSearchResult } from './types'; -import AssetIcon from '../AssetIcon'; +import { AutocompleteSearchResult, TokenSearchResult } from './types'; import BadgeWrapper from '../../../component-library/components/Badges/BadgeWrapper'; import Badge, { BadgeVariant } from '../../../component-library/components/Badges/Badge'; import { NetworkBadgeSource } from '../AssetOverview/Balance/Balance'; import AvatarToken from '../../../component-library/components/Avatars/Avatar/variants/AvatarToken'; +import { selectSupportedSwapTokenAddresses } from '../../../selectors/tokenSearchDiscoveryDataController'; +import { RootState } from '../../../reducers'; +import { isSwapsAllowed } from '../Swaps/utils'; +import AppConstants from '../../../core/AppConstants'; interface ResultProps { result: AutocompleteSearchResult; onPress: () => void; + onSwapPress: (result: TokenSearchResult) => void; } -export const Result: React.FC = memo(({ result, onPress }) => { +export const Result: React.FC = memo(({ result, onPress, onSwapPress }) => { const theme = useTheme(); const styles = stylesheet({theme}); - const name = typeof result.name === 'string' ? result.name : getHost(result.url); + const name = typeof result.name === 'string' || result.type === 'tokens' ? result.name : getHost(result.url); const dispatch = useDispatch(); @@ -33,6 +37,10 @@ export const Result: React.FC = memo(({ result, onPress }) => { dispatch(removeBookmark(result)); }, [dispatch, result]); + const swapTokenAddresses = useSelector((state: RootState) => selectSupportedSwapTokenAddresses(state, result.type === 'tokens' ? result.chainId : '0x')); + + const displaySwapButton = result.type === 'tokens' && isSwapsAllowed(result.chainId) && swapTokenAddresses?.includes(result.address) && AppConstants.SWAPS.ACTIVE; + return ( = memo(({ result, onPress }) => { result.type === 'favorites' && ( ) } + { + displaySwapButton && ( + onSwapPress(result)} + /> + ) + } ); diff --git a/app/components/UI/UrlAutocomplete/index.tsx b/app/components/UI/UrlAutocomplete/index.tsx index b40960b55f17..eb7240205dbf 100644 --- a/app/components/UI/UrlAutocomplete/index.tsx +++ b/app/components/UI/UrlAutocomplete/index.tsx @@ -34,6 +34,12 @@ import { MAX_RECENTS, ORDERED_CATEGORIES } from './UrlAutocomplete.constants'; import { Result } from './Result'; import useTokenSearchDiscovery from '../../hooks/useTokenSearchDiscovery/useTokenSearchDiscovery'; import { Hex } from '@metamask/utils'; +import Engine from '../../../core/Engine'; +import { selectEvmChainId } from '../../../selectors/networkController'; +import { useAddNetwork } from '../../hooks/useAddNetwork'; +import { PopularList } from '../../../util/networks/customNetworks'; +import { swapsUtils } from '@metamask/swaps-controller'; +import { useNavigation } from '@react-navigation/native'; export * from './types'; @@ -180,6 +186,83 @@ const UrlAutocomplete = forwardRef< } }, [browserHistory, bookmarks, search]); + useEffect(() => { + const chainIds = tokenResults.reduce((acc, result) => { + if (!acc.includes(result.chainId)) { + acc.push(result.chainId); + } + return acc; + }, [] as Hex[]); + + for (const chainId of chainIds) { + Engine.context.TokenSearchDiscoveryDataController.fetchSwapsTokens(chainId); + } + + }, [tokenResults]); + + const selectedChainId = useSelector(selectEvmChainId); + + const { addPopularNetwork, networkModal } = useAddNetwork(); + + const navigation = useNavigation(); + + const handleSwapNavigation = useCallback((result: TokenSearchResult) => { + hide(); + onDismiss(); + navigation.navigate('Swaps', { + screen: 'SwapsAmountView', + params: { + sourceToken: swapsUtils.NATIVE_SWAPS_TOKEN_ADDRESS, + destinationToken: result.address, + sourcePage: 'MainView', + chainId: result.chainId, + }}); + }, [navigation, hide, onDismiss]); + + const goToSwaps = useCallback(async (result: TokenSearchResult) => { + if (result.chainId !== selectedChainId) { + const { NetworkController, MultichainNetworkController } = + Engine.context; + let networkConfiguration = + NetworkController.getNetworkConfigurationByChainId( + result.chainId as Hex, + ); + + if (!networkConfiguration) { + const network = PopularList.find((popularNetwork) => popularNetwork.chainId === result.chainId); + if (network) { + try { + await addPopularNetwork(network); + } catch (error) { + return; + } + networkConfiguration = NetworkController.getNetworkConfigurationByChainId( + result.chainId, + ); + } + } + + const networkClientId = + networkConfiguration?.rpcEndpoints?.[ + networkConfiguration.defaultRpcEndpointIndex + ]?.networkClientId; + + await MultichainNetworkController.setActiveNetwork( + networkClientId as string, + ); + + setTimeout(() => { + handleSwapNavigation(result); + }, 500); + } else { + handleSwapNavigation(result); + } + }, [ + selectedChainId, + handleSwapNavigation, + addPopularNetwork, + ]); + const renderSectionHeader = useCallback(({section: {category}}) => ( {strings(`autocomplete.${category}`)} @@ -196,8 +279,9 @@ const UrlAutocomplete = forwardRef< hide(); onSelect(item); }} + onSwapPress={goToSwaps} /> - ), [hide, onSelect]); + ), [hide, onSelect, goToSwaps]); if (!hasResults) { return ( @@ -218,7 +302,8 @@ const UrlAutocomplete = forwardRef< renderSectionHeader={renderSectionHeader} renderItem={renderItem} keyboardShouldPersistTaps="handled" - /> + /> + {networkModal} ); }); diff --git a/app/components/UI/UrlAutocomplete/styles.ts b/app/components/UI/UrlAutocomplete/styles.ts index 7ca4d333b6a4..d5069af05351 100644 --- a/app/components/UI/UrlAutocomplete/styles.ts +++ b/app/components/UI/UrlAutocomplete/styles.ts @@ -56,7 +56,7 @@ const styleSheet = ({ theme: { colors, typography } }: { theme: Theme }) => bg: { flex: 1, }, - deleteFavorite: { + resultActionButton: { marginLeft: 10, }, }); diff --git a/app/components/Views/AssetLoader/index.tsx b/app/components/Views/AssetLoader/index.tsx index 43201be2111e..e31d97c6ac7b 100644 --- a/app/components/Views/AssetLoader/index.tsx +++ b/app/components/Views/AssetLoader/index.tsx @@ -3,7 +3,6 @@ import { Hex } from '@metamask/utils'; import { ActivityIndicator, Text, View } from 'react-native'; import { StackActions, useNavigation } from '@react-navigation/native'; import Routes from '../../../constants/navigation/Routes'; -import Logger from '../../../util/Logger'; import Engine from '../../../core/Engine'; import { useSelector } from 'react-redux'; import { RootState } from '../../../reducers'; From d388b750de1b3b59f5b4017f208c8217aba47dc2 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Tue, 4 Mar 2025 21:43:26 -0500 Subject: [PATCH 046/124] add token price and change % in search results --- app/components/UI/UrlAutocomplete/Result.tsx | 25 ++++++++++++++++--- app/components/UI/UrlAutocomplete/index.tsx | 16 ++++++++++-- app/components/UI/UrlAutocomplete/styles.ts | 11 ++++++++ app/components/UI/UrlAutocomplete/types.ts | 2 ++ .../currency-rate-controller-init.ts | 1 + app/selectors/currencyRateController.ts | 6 +++++ 6 files changed, 56 insertions(+), 5 deletions(-) diff --git a/app/components/UI/UrlAutocomplete/Result.tsx b/app/components/UI/UrlAutocomplete/Result.tsx index e1fb28c310de..40cfd052f5ff 100644 --- a/app/components/UI/UrlAutocomplete/Result.tsx +++ b/app/components/UI/UrlAutocomplete/Result.tsx @@ -18,6 +18,9 @@ import { selectSupportedSwapTokenAddresses } from '../../../selectors/tokenSearc import { RootState } from '../../../reducers'; import { isSwapsAllowed } from '../Swaps/utils'; import AppConstants from '../../../core/AppConstants'; +import { selectCurrentCurrency } from '../../../selectors/currencyRateController'; +import { addCurrencySymbol } from '../../../util/number'; +import PercentageChange from '../../../component-library/components-temp/Price/PercentageChange'; interface ResultProps { result: AutocompleteSearchResult; @@ -39,7 +42,9 @@ export const Result: React.FC = memo(({ result, onPress, onSwapPres const swapTokenAddresses = useSelector((state: RootState) => selectSupportedSwapTokenAddresses(state, result.type === 'tokens' ? result.chainId : '0x')); - const displaySwapButton = result.type === 'tokens' && isSwapsAllowed(result.chainId) && swapTokenAddresses?.includes(result.address) && AppConstants.SWAPS.ACTIVE; + const swapsEnabled = result.type === 'tokens' && isSwapsAllowed(result.chainId) && swapTokenAddresses?.includes(result.address) && AppConstants.SWAPS.ACTIVE; + + const currentCurrency = useSelector(selectCurrentCurrency); return ( = memo(({ result, onPress, onSwapPres ) } { - displaySwapButton && ( + result.type === 'tokens' && ( + + + {addCurrencySymbol(result.price, currentCurrency, true)} + + + + ) + } + { + result.type === 'tokens' && ( onSwapPress(result)} + disabled={!swapsEnabled} /> ) } diff --git a/app/components/UI/UrlAutocomplete/index.tsx b/app/components/UI/UrlAutocomplete/index.tsx index eb7240205dbf..5cd5919aa175 100644 --- a/app/components/UI/UrlAutocomplete/index.tsx +++ b/app/components/UI/UrlAutocomplete/index.tsx @@ -40,6 +40,7 @@ import { useAddNetwork } from '../../hooks/useAddNetwork'; import { PopularList } from '../../../util/networks/customNetworks'; import { swapsUtils } from '@metamask/swaps-controller'; import { useNavigation } from '@react-navigation/native'; +import { selectCurrentCurrency, selectUsdConversionRate } from '../../../selectors/currencyRateController'; export * from './types'; @@ -56,15 +57,26 @@ const UrlAutocomplete = forwardRef< >(({ onSelect, onDismiss }, ref) => { const [fuseResults, setFuseResults] = useState([]); const {searchTokens, results: tokenSearchResults, reset: resetTokenSearch, isLoading: isTokenSearchLoading} = useTokenSearchDiscovery(); - const tokenResults: TokenSearchResult[] = useMemo(() => tokenSearchResults.map(({tokenAddress, usdPricePercentChange: _, chainId, ...rest}) => ({ + const usdConversionRate = useSelector(selectUsdConversionRate); + const tokenResults: TokenSearchResult[] = useMemo(() => tokenSearchResults.map(({tokenAddress, usdPricePercentChange, usdPrice, chainId, ...rest}) => ({ ...rest, type: 'tokens', address: tokenAddress, chainId: chainId as Hex, - })), [tokenSearchResults]); + price: usdConversionRate ? usdPrice / usdConversionRate : -1, + percentChange: usdPricePercentChange.oneDay, + })), [tokenSearchResults, usdConversionRate]); const hasResults = fuseResults.length > 0 || tokenResults.length > 0; + const currentCurrency = useSelector(selectCurrentCurrency); + + useEffect(() => { + if (currentCurrency) { + Engine.context.CurrencyRateController.updateExchangeRate([currentCurrency]); + } + }, [currentCurrency]); + const resultsByCategory: {category: string, data: AutocompleteSearchResult[]}[] = useMemo(() => ( ORDERED_CATEGORIES.flatMap((category) => { if (category === 'tokens') { diff --git a/app/components/UI/UrlAutocomplete/styles.ts b/app/components/UI/UrlAutocomplete/styles.ts index d5069af05351..dca41a5343ef 100644 --- a/app/components/UI/UrlAutocomplete/styles.ts +++ b/app/components/UI/UrlAutocomplete/styles.ts @@ -17,6 +17,7 @@ const styleSheet = ({ theme: { colors, typography } }: { theme: Theme }) => padding: 10, flexDirection: 'row', alignItems: 'center', + backgroundColor: colors.background.default, }, category: { color: colors.text.default, @@ -59,6 +60,16 @@ const styleSheet = ({ theme: { colors, typography } }: { theme: Theme }) => resultActionButton: { marginLeft: 10, }, + hiddenButton: { + opacity: 0, + }, + priceContainer: { + flexDirection: 'column', + alignItems: 'flex-end', + }, + price: { + color: colors.text.default, + } }); export default styleSheet; diff --git a/app/components/UI/UrlAutocomplete/types.ts b/app/components/UI/UrlAutocomplete/types.ts index dc40daa517a7..2002907169ae 100644 --- a/app/components/UI/UrlAutocomplete/types.ts +++ b/app/components/UI/UrlAutocomplete/types.ts @@ -58,6 +58,8 @@ export type TokenSearchResult = { address: string; chainId: Hex; logoUrl?: string; + price: number; + percentChange: number; }; export type AutocompleteSearchResult = FuseSearchResult | TokenSearchResult; diff --git a/app/core/Engine/controllers/currency-rate-controller/currency-rate-controller-init.ts b/app/core/Engine/controllers/currency-rate-controller/currency-rate-controller-init.ts index 1dde935bceda..3c4d2831d878 100644 --- a/app/core/Engine/controllers/currency-rate-controller/currency-rate-controller-init.ts +++ b/app/core/Engine/controllers/currency-rate-controller/currency-rate-controller-init.ts @@ -56,6 +56,7 @@ export const currencyRateControllerInit: ControllerInitFunction< }); const controller = new CurrencyRateController({ + includeUsdRate: true, messenger: controllerMessenger, state: { ...persistedCurrencyRateState, diff --git a/app/selectors/currencyRateController.ts b/app/selectors/currencyRateController.ts index e2d3e1a240e1..57f7d41129e9 100644 --- a/app/selectors/currencyRateController.ts +++ b/app/selectors/currencyRateController.ts @@ -77,3 +77,9 @@ export const selectConversionRateByChainId = createSelector( return currencyRates?.[nativeCurrency]?.conversionRate; }, ); + +export const selectUsdConversionRate = createSelector( + selectCurrencyRates, + selectCurrentCurrency, + (currencyRates, currentCurrency) => currencyRates?.[currentCurrency]?.usdConversionRate, +); From de2ed9482bdb48e6e857e000a324a81e07769387 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Tue, 4 Mar 2025 21:48:00 -0500 Subject: [PATCH 047/124] adjust assetloader styles --- app/components/Views/AssetLoader/index.tsx | 9 +++++++-- app/components/Views/AssetLoader/styles.ts | 14 ++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 app/components/Views/AssetLoader/styles.ts diff --git a/app/components/Views/AssetLoader/index.tsx b/app/components/Views/AssetLoader/index.tsx index e31d97c6ac7b..fb66a457753d 100644 --- a/app/components/Views/AssetLoader/index.tsx +++ b/app/components/Views/AssetLoader/index.tsx @@ -7,6 +7,9 @@ import Engine from '../../../core/Engine'; import { useSelector } from 'react-redux'; import { RootState } from '../../../reducers'; import { selectTokenDisplayData } from '../../../selectors/tokenSearchDiscoveryDataController'; +import { useStyles } from '../../../component-library/hooks'; +import styleSheet from './styles'; + export interface AssetLoaderProps { route: { params: { @@ -20,6 +23,8 @@ export const AssetLoader: React.FC = ({ route: { params: { add const tokenResult = useSelector((state: RootState) => selectTokenDisplayData(state, chainId, address)); const navigation = useNavigation(); + const { styles } = useStyles(styleSheet, {}); + useEffect(() => { Engine.context.TokenSearchDiscoveryDataController.fetchTokenDisplayData(chainId, address); if (tokenResult?.found) { @@ -35,7 +40,7 @@ export const AssetLoader: React.FC = ({ route: { params: { add if (!tokenResult) { return ( - + ); @@ -43,7 +48,7 @@ export const AssetLoader: React.FC = ({ route: { params: { add if (!tokenResult.found) { return ( - + Token not found ); diff --git a/app/components/Views/AssetLoader/styles.ts b/app/components/Views/AssetLoader/styles.ts new file mode 100644 index 000000000000..ed95063c4df2 --- /dev/null +++ b/app/components/Views/AssetLoader/styles.ts @@ -0,0 +1,14 @@ +import { Theme } from '@metamask/design-tokens'; +import { StyleSheet } from 'react-native'; + +const styleSheet = ({ theme: { colors } }: { theme: Theme }) => + StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background.default, + alignItems: 'center', + justifyContent: 'center', + } + }); + +export default styleSheet; From 45ab0b2ec3029fb4ccae459646be07d70d756cd1 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Fri, 7 Mar 2025 16:51:59 -0500 Subject: [PATCH 048/124] Update assets-controllers To support the search & discovery feature, updating assets-controllers to 53.1.0, which includes `TokenSearchDiscoveryController`. This new version also has updated peer dependencies, so we update their versions here as well. --- package.json | 9 ++- yarn.lock | 151 +++++++++++++++++++++++++++++++++++---------------- 2 files changed, 107 insertions(+), 53 deletions(-) diff --git a/package.json b/package.json index 4a96fbc09339..d1708eb09e5d 100644 --- a/package.json +++ b/package.json @@ -141,7 +141,6 @@ "express": "4.21.2", "nanoid": "^3.3.8", "undici": "5.28.5", - "@metamask/assets-controllers": "file:../metamask-core/packages/assets-controllers", "**/@ethersproject/signing-key/elliptic": "^6.6.1", "**/@walletconnect/utils/elliptic": "^6.6.1" }, @@ -152,10 +151,10 @@ "@keystonehq/metamask-airgapped-keyring": "^0.13.1", "@keystonehq/ur-decoder": "^0.12.2", "@ledgerhq/react-native-hw-transport-ble": "^6.33.2", - "@metamask/accounts-controller": "^24.1.0", + "@metamask/accounts-controller": "^26.0.0", "@metamask/address-book-controller": "^6.0.3", "@metamask/approval-controller": "^7.1.0", - "@metamask/assets-controllers": "^51.0.2", + "@metamask/assets-controllers": "^53.1.0", "@metamask/base-controller": "^8.0.0", "@metamask/bitcoin-wallet-snap": "^0.9.0", "@metamask/bridge-controller": "^3.0.0", @@ -180,7 +179,7 @@ "@metamask/json-rpc-middleware-stream": "^8.0.6", "@metamask/key-tree": "^10.0.2", "@metamask/keyring-api": "^17.2.1", - "@metamask/keyring-controller": "^19.2.0", + "@metamask/keyring-controller": "^21.0.0", "@metamask/keyring-internal-api": "^4.0.3", "@metamask/keyring-snap-client": "^4.0.1", "@metamask/logging-controller": "^6.0.4", @@ -192,7 +191,7 @@ "@metamask/phishing-controller": "^12.0.3", "@metamask/post-message-stream": "^9.0.0", "@metamask/ppom-validator": "0.36.0", - "@metamask/preferences-controller": "^15.0.1", + "@metamask/preferences-controller": "^16.0.0", "@metamask/profile-sync-controller": "^9.0.0", "@metamask/react-native-actionsheet": "2.4.2", "@metamask/react-native-button": "^3.0.0", diff --git a/yarn.lock b/yarn.lock index 3883e0db0d53..cc2dc9cc1ece 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1984,7 +1984,7 @@ "@ethereumjs/util" "^8.1.0" ethereum-cryptography "^2.0.0" -"@ethereumjs/tx@^5.2.1": +"@ethereumjs/tx@^5.2.1", "@ethereumjs/tx@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethereumjs/tx/-/tx-5.4.0.tgz#6f47894cc3e2d4e63d87c62b41ed7e8180a1de58" integrity sha512-SCHnK7m/AouZ7nyoR0MEXw1OO/tQojSbp88t8oxhwes5iZkZCtfFdUrJaiIb72qIpH2FVw6s1k1uP7LXuH7PsA== @@ -4515,17 +4515,17 @@ "@metamask/superstruct" "^3.1.0" "@metamask/utils" "^11.0.1" -"@metamask/accounts-controller@^24.1.0": - version "24.1.0" - resolved "https://registry.yarnpkg.com/@metamask/accounts-controller/-/accounts-controller-24.1.0.tgz#749c2e9617610c058c38fa355a680152e81e08dd" - integrity sha512-Yip2kbWEc0p0axaIB5accPHsrnON8p2277xPIklRNcJnMxjnkzDdqUQy8nc2eSiQoFsWnQLErDLeoSt4pgfooA== +"@metamask/accounts-controller@^26.0.0": + version "26.0.0" + resolved "https://registry.yarnpkg.com/@metamask/accounts-controller/-/accounts-controller-26.0.0.tgz#fcb139cfebd275ed8976dd5d9983efb160f65c94" + integrity sha512-xnO+AYhUZzNfXm91ngPOVNOBGzi7fDVsh7KgeGvmntu2Cn7W1nW1c0WIY7yG0YQbBRlDLNcHFP+Z32d1O11Oyg== dependencies: - "@ethereumjs/util" "^8.1.0" + "@ethereumjs/util" "^9.1.0" "@metamask/base-controller" "^8.0.0" - "@metamask/eth-snap-keyring" "^11.1.0" + "@metamask/eth-snap-keyring" "^12.0.0" "@metamask/keyring-api" "^17.2.0" - "@metamask/keyring-internal-api" "^4.0.3" - "@metamask/keyring-utils" "^2.3.1" + "@metamask/keyring-internal-api" "^6.0.0" + "@metamask/keyring-utils" "^3.0.0" "@metamask/network-controller" "^22.2.1" "@metamask/snaps-sdk" "^6.17.1" "@metamask/snaps-utils" "^8.10.0" @@ -4554,10 +4554,12 @@ "@metamask/utils" "^11.1.0" nanoid "^3.3.8" -"@metamask/assets-controllers@^50.0.0", "@metamask/assets-controllers@file:../metamask-core/packages/assets-controllers": - version "50.0.0" +"@metamask/assets-controllers@^53.1.0": + version "53.1.0" + resolved "https://registry.yarnpkg.com/@metamask/assets-controllers/-/assets-controllers-53.1.0.tgz#29209cb62c70c424349fb1205d7fceb83e5cf139" + integrity sha512-u67s1j5rp99wQX7yO9EVv5TbHJ+EZHlKbDFg6l3vh87Ame7u8hH+HQXlQLrE0tZjj562FCxVyITjPGeYTAR2KQ== dependencies: - "@ethereumjs/util" "^8.1.0" + "@ethereumjs/util" "^9.1.0" "@ethersproject/abi" "^5.7.0" "@ethersproject/address" "^5.7.0" "@ethersproject/bignumber" "^5.7.0" @@ -4566,7 +4568,7 @@ "@metamask/abi-utils" "^2.0.3" "@metamask/base-controller" "^8.0.0" "@metamask/contract-metadata" "^2.4.0" - "@metamask/controller-utils" "^11.5.0" + "@metamask/controller-utils" "^11.6.0" "@metamask/eth-query" "^4.0.0" "@metamask/keyring-api" "^17.2.0" "@metamask/metamask-eth-abis" "^3.1.1" @@ -4685,6 +4687,23 @@ eth-ens-namehash "^2.0.8" fast-deep-equal "^3.1.3" +"@metamask/controller-utils@^11.6.0": + version "11.6.0" + resolved "https://registry.yarnpkg.com/@metamask/controller-utils/-/controller-utils-11.6.0.tgz#68bae4323ad4a68811befadc018043e6c15f6cc1" + integrity sha512-7dcaimnRxNzQBVXdadNH/oezBkfxYJ2bK2qB09d9mYjUY3+/dyWX2BYcUXSb1BOJlyJxtVDEN1+sqIRMnoqL/Q== + dependencies: + "@ethereumjs/util" "^9.1.0" + "@metamask/eth-query" "^4.0.0" + "@metamask/ethjs-unit" "^0.3.0" + "@metamask/utils" "^11.2.0" + "@spruceid/siwe-parser" "2.1.0" + "@types/bn.js" "^5.1.5" + bignumber.js "^9.1.2" + bn.js "^5.2.1" + cockatiel "^3.1.2" + eth-ens-namehash "^2.0.8" + fast-deep-equal "^3.1.3" + "@metamask/design-tokens@^5.0.0": version "5.0.0" resolved "https://registry.yarnpkg.com/@metamask/design-tokens/-/design-tokens-5.0.0.tgz#b749fc9d6556a0c2be159c6448b48796e0c14c6f" @@ -4721,10 +4740,10 @@ json-rpc-random-id "^1.0.1" pify "^5.0.0" -"@metamask/eth-hd-keyring@^10.0.0": - version "10.0.1" - resolved "https://registry.yarnpkg.com/@metamask/eth-hd-keyring/-/eth-hd-keyring-10.0.1.tgz#43e817a07b9f50f39b27155d697db55d5fa4237c" - integrity sha512-Ebzpd/Gejcsuz5zX+qL5Wou/JHT2gb5zxxHz30NJIHRO1xIe/nFOaNW5Q6InIddb1reemQe21xqHHPTnVghnwQ== +"@metamask/eth-hd-keyring@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@metamask/eth-hd-keyring/-/eth-hd-keyring-11.0.0.tgz#c89db5b4ff2f61fd6ea75dbeddbd9fd5dbbe6bf8" + integrity sha512-QYs5ieXdrwhXnbo4u8exseSsPtKfLpGDXT7BVivbjYCV6H0Ksn+DiIJ785CnGs/GWdao29KFiQ8mwrj76DDd1A== dependencies: "@ethereumjs/util" "^8.1.0" "@metamask/eth-sig-util" "^8.2.0" @@ -4733,12 +4752,12 @@ "@metamask/utils" "^11.1.0" ethereum-cryptography "^2.1.2" -"@metamask/eth-hd-keyring@^11.0.0": - version "11.0.0" - resolved "https://registry.yarnpkg.com/@metamask/eth-hd-keyring/-/eth-hd-keyring-11.0.0.tgz#c89db5b4ff2f61fd6ea75dbeddbd9fd5dbbe6bf8" - integrity sha512-QYs5ieXdrwhXnbo4u8exseSsPtKfLpGDXT7BVivbjYCV6H0Ksn+DiIJ785CnGs/GWdao29KFiQ8mwrj76DDd1A== +"@metamask/eth-hd-keyring@^12.0.0": + version "12.0.0" + resolved "https://registry.yarnpkg.com/@metamask/eth-hd-keyring/-/eth-hd-keyring-12.0.0.tgz#2b5f7d66e3129bf740cd69f48a8b5106624cc742" + integrity sha512-J/72cJoP+Y4wfDqeDmm2YNsuJFSKJnaU3bB9Jv3FZAyj1nl3qEY6Szj4o+ujdF0vi58U1tHF7FNTnD5tBxUQUQ== dependencies: - "@ethereumjs/util" "^8.1.0" + "@ethereumjs/util" "^9.1.0" "@metamask/eth-sig-util" "^8.2.0" "@metamask/key-tree" "^10.0.2" "@metamask/scure-bip39" "^2.1.1" @@ -4839,12 +4858,12 @@ ethereum-cryptography "^2.1.2" tweetnacl "^1.0.3" -"@metamask/eth-simple-keyring@^8.1.0": - version "8.1.1" - resolved "https://registry.yarnpkg.com/@metamask/eth-simple-keyring/-/eth-simple-keyring-8.1.1.tgz#23bc2813dad92bffef5d7e37b4eed78c780db81f" - integrity sha512-/qcnwkzjtluM96IGTeCN7X2efI4GurxW8sMpj/oxN5sb69CMsr22WiMe1cRHQ0iM+hzlWRyxySFye4wPZENgvw== +"@metamask/eth-simple-keyring@^10.0.0": + version "10.0.0" + resolved "https://registry.yarnpkg.com/@metamask/eth-simple-keyring/-/eth-simple-keyring-10.0.0.tgz#e9f9d01e8ef7afff9023e27888a69fc9d946ab34" + integrity sha512-zdPIhKsQDfLG5m8yFDHpaXe8nPYc3sBeTiVWw1BPxwGNIW7Y8ToeHMMIlOYFZzUP98ezrtrBjiB86tQdRZ377w== dependencies: - "@ethereumjs/util" "^8.1.0" + "@ethereumjs/util" "^9.1.0" "@metamask/eth-sig-util" "^8.2.0" "@metamask/utils" "^11.1.0" ethereum-cryptography "^2.1.2" @@ -4878,6 +4897,23 @@ "@types/uuid" "^9.0.8" uuid "^9.0.1" +"@metamask/eth-snap-keyring@^12.0.0": + version "12.0.0" + resolved "https://registry.yarnpkg.com/@metamask/eth-snap-keyring/-/eth-snap-keyring-12.0.0.tgz#f4f5c246abbaefc9e2aeacb1943dbf0c8473ffb5" + integrity sha512-WfCkdHDvFZUFDq/Hhf6XpQeWmCdzdN0GlPUNm82y+Qs/RJdya92+hCkO3efWVzuefSIFmQxPRbQQb0uvvbYrxA== + dependencies: + "@ethereumjs/tx" "^5.4.0" + "@metamask/base-controller" "^7.1.1" + "@metamask/eth-sig-util" "^8.2.0" + "@metamask/keyring-api" "^17.2.1" + "@metamask/keyring-internal-api" "^5.0.0" + "@metamask/keyring-internal-snap-client" "^4.0.1" + "@metamask/keyring-utils" "^3.0.0" + "@metamask/superstruct" "^3.1.0" + "@metamask/utils" "^11.1.0" + "@types/uuid" "^9.0.8" + uuid "^9.0.1" + "@metamask/etherscan-link@^2.0.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@metamask/etherscan-link/-/etherscan-link-2.1.0.tgz#c0be8e68445b7b83cf85bcc03a56cdf8e256c973" @@ -5029,40 +5065,40 @@ "@metamask/utils" "^11.1.0" bech32 "^2.0.0" -"@metamask/keyring-controller@^19.2.0": - version "19.2.1" - resolved "https://registry.yarnpkg.com/@metamask/keyring-controller/-/keyring-controller-19.2.1.tgz#47be936b39e8ac822d759b00f495cff067f857c7" - integrity sha512-Q0d3bM9SieJzfpMMYLzaVjN+EQNApnddTdufsYbzRYqBV8b5cdFCMoYapQvDFL/nPqBO9e4bXpei6ka6kiusBQ== +"@metamask/keyring-controller@^20.0.0": + version "20.0.0" + resolved "https://registry.yarnpkg.com/@metamask/keyring-controller/-/keyring-controller-20.0.0.tgz#cbb60fcd9d835401029367337d1a03040a844be3" + integrity sha512-YbSO1ciqRZF/+PwhMcVkz26NQ3EsnFNWz6JWvhfW66arOTMc0nFjfCHcmW0Yd1kF034hGajuBqXhsqZvh43ycg== dependencies: "@ethereumjs/util" "^8.1.0" "@keystonehq/metamask-airgapped-keyring" "^0.14.1" "@metamask/base-controller" "^8.0.0" "@metamask/browser-passworder" "^4.3.0" - "@metamask/eth-hd-keyring" "^10.0.0" + "@metamask/eth-hd-keyring" "^11.0.0" "@metamask/eth-sig-util" "^8.2.0" - "@metamask/eth-simple-keyring" "^8.1.0" + "@metamask/eth-simple-keyring" "^9.0.0" "@metamask/keyring-api" "^17.2.0" - "@metamask/keyring-internal-api" "^4.0.3" + "@metamask/keyring-internal-api" "^5.0.0" "@metamask/utils" "^11.2.0" async-mutex "^0.5.0" ethereumjs-wallet "^1.0.1" immer "^9.0.6" ulid "^2.3.0" -"@metamask/keyring-controller@^20.0.0": - version "20.0.0" - resolved "https://registry.yarnpkg.com/@metamask/keyring-controller/-/keyring-controller-20.0.0.tgz#cbb60fcd9d835401029367337d1a03040a844be3" - integrity sha512-YbSO1ciqRZF/+PwhMcVkz26NQ3EsnFNWz6JWvhfW66arOTMc0nFjfCHcmW0Yd1kF034hGajuBqXhsqZvh43ycg== +"@metamask/keyring-controller@^21.0.0": + version "21.0.0" + resolved "https://registry.yarnpkg.com/@metamask/keyring-controller/-/keyring-controller-21.0.0.tgz#eeace05df6c07baece0bea621b3c26269e1c2ca0" + integrity sha512-RXANJ9qaxn76XLjGvjQOECErUl6LqBd7jeK9JmFKg8zsVQHLWK9fbfO7GA3PdKs9c8DkBCZXx+fFE1J9LWh6sA== dependencies: - "@ethereumjs/util" "^8.1.0" + "@ethereumjs/util" "^9.1.0" "@keystonehq/metamask-airgapped-keyring" "^0.14.1" "@metamask/base-controller" "^8.0.0" "@metamask/browser-passworder" "^4.3.0" - "@metamask/eth-hd-keyring" "^11.0.0" + "@metamask/eth-hd-keyring" "^12.0.0" "@metamask/eth-sig-util" "^8.2.0" - "@metamask/eth-simple-keyring" "^9.0.0" + "@metamask/eth-simple-keyring" "^10.0.0" "@metamask/keyring-api" "^17.2.0" - "@metamask/keyring-internal-api" "^5.0.0" + "@metamask/keyring-internal-api" "^6.0.0" "@metamask/utils" "^11.2.0" async-mutex "^0.5.0" ethereumjs-wallet "^1.0.1" @@ -5088,6 +5124,15 @@ "@metamask/keyring-utils" "^2.3.1" "@metamask/superstruct" "^3.1.0" +"@metamask/keyring-internal-api@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@metamask/keyring-internal-api/-/keyring-internal-api-6.0.0.tgz#7129bbfa54ac0ac3e835a026752b5e24fc697245" + integrity sha512-hWBgX3+0UhNji/cm+Kt+h2g50Zw/+veoUAhq714iBTdVY4aycYdsD2XTBnRAuJ9A0Llw5Vbo5DSIelUU74j42g== + dependencies: + "@metamask/keyring-api" "^17.2.1" + "@metamask/keyring-utils" "^3.0.0" + "@metamask/superstruct" "^3.1.0" + "@metamask/keyring-internal-snap-client@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@metamask/keyring-internal-snap-client/-/keyring-internal-snap-client-4.0.1.tgz#dba7340b7f61b7dbf3724f38bd962128a291b922" @@ -5120,6 +5165,16 @@ "@metamask/utils" "^11.1.0" bitcoin-address-validation "^2.2.3" +"@metamask/keyring-utils@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@metamask/keyring-utils/-/keyring-utils-3.0.0.tgz#37ad4b948e7306ef7d157ed074377073526ea143" + integrity sha512-bo6Bgmz2uJ+DI5r9qyZjp435VCrc0A4nES/awTPnRWETqpbOBfMDVlLeQxQE88exCrE8efK2hTJ1by5fw8+N2g== + dependencies: + "@ethereumjs/tx" "^5.4.0" + "@metamask/superstruct" "^3.1.0" + "@metamask/utils" "^11.1.0" + bitcoin-address-validation "^2.2.3" + "@metamask/logging-controller@^6.0.4": version "6.0.4" resolved "https://registry.yarnpkg.com/@metamask/logging-controller/-/logging-controller-6.0.4.tgz#05b9b445e8c09f25eb0d4342e5c5281601d8d11c" @@ -5311,13 +5366,13 @@ eslint-plugin-n "^16.6.2" json-rpc-random-id "^1.0.1" -"@metamask/preferences-controller@^15.0.1": - version "15.0.1" - resolved "https://registry.yarnpkg.com/@metamask/preferences-controller/-/preferences-controller-15.0.1.tgz#4306099e4659591636304d81ed0954afb160ab81" - integrity sha512-y2rGKMr9fY2LCwEjvX7QKxOqxy5Tz6vA+QK8YEXuczeHd3n2jrh9DNBeM+BZzzOO2cXJgmbn0Jeotl09kDD94g== +"@metamask/preferences-controller@^16.0.0": + version "16.0.0" + resolved "https://registry.yarnpkg.com/@metamask/preferences-controller/-/preferences-controller-16.0.0.tgz#be7f3602181ff7a6d4c032fc8fd38b403836fc35" + integrity sha512-XzuhUMOY+JplgDVSJOV6LCgBB+/hl/lf67XMWI7jSEGLpAD632CwlEbMsfObzAqDmqsxkK3HFfocbsqrbSbCCg== dependencies: - "@metamask/base-controller" "^7.0.2" - "@metamask/controller-utils" "^11.4.4" + "@metamask/base-controller" "^8.0.0" + "@metamask/controller-utils" "^11.5.0" "@metamask/profile-sync-controller@^9.0.0": version "9.0.0" From 2929fe060bc0a7c3e02337d35bc04d085ae5e6ef Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Fri, 7 Mar 2025 17:23:44 -0500 Subject: [PATCH 049/124] remove jfrog from yarn lock --- yarn.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index cc2dc9cc1ece..bd08b9d8ad5b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5334,7 +5334,7 @@ "@metamask/polling-controller@^12.0.0", "@metamask/polling-controller@^12.0.3": version "12.0.3" - resolved "https://consensys.jfrog.io/artifactory/api/npm/npm/@metamask/polling-controller/-/polling-controller-12.0.3.tgz#377716f6c8610373aed1319d2a10d75c12a50f6f" + resolved "https://registry.yarnpkg.com/@metamask/polling-controller/-/polling-controller-12.0.3.tgz#377716f6c8610373aed1319d2a10d75c12a50f6f" integrity sha512-YOsFgyqd5s32WQfTWwBS4hGOGrIVL92AUh+WeF3Gb2Q7vblnz7152PW354+fT9xG3Uk/zdtss35LrltxQvlc/A== dependencies: "@metamask/base-controller" "^8.0.0" @@ -29980,5 +29980,5 @@ zstd-codec@^0.1.5: zxcvbn@4.4.2: version "4.4.2" - resolved "https://consensys.jfrog.io/artifactory/api/npm/npm/zxcvbn/-/zxcvbn-4.4.2.tgz#28ec17cf09743edcab056ddd8b1b06262cc73c30" + resolved "https://registry.yarnpkg.com/zxcvbn/-/zxcvbn-4.4.2.tgz#28ec17cf09743edcab056ddd8b1b06262cc73c30" integrity sha512-Bq0B+ixT/DMyG8kgX2xWcI5jUvCwqrMxSFam7m0lAf78nf04hv6lNCsyLYdyYTrCVMqNDY/206K7eExYCeSyUQ== From 4fe01f23949cbb4aa48ae736c0393e966488a843 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Fri, 7 Mar 2025 17:30:56 -0500 Subject: [PATCH 050/124] merge controller-utils deps in yarn lock --- yarn.lock | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/yarn.lock b/yarn.lock index bd08b9d8ad5b..6ab7b99ebf58 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4670,24 +4670,7 @@ resolved "https://registry.yarnpkg.com/@metamask/contract-metadata/-/contract-metadata-2.5.0.tgz#33921fa9c15eb1863f55dcd5f75467ae15614ebb" integrity sha512-+j7jEcp0P1OUMEpa/OIwfJs/ahBC/akwgWxaRTSWX2SWABvlUKBVRMtslfL94Qj2wN2xw8xjaUy5nSHqrznqDA== -"@metamask/controller-utils@^11.0.0", "@metamask/controller-utils@^11.3.0", "@metamask/controller-utils@^11.4.4", "@metamask/controller-utils@^11.5.0": - version "11.5.0" - resolved "https://registry.yarnpkg.com/@metamask/controller-utils/-/controller-utils-11.5.0.tgz#27daa18c6d2b63189bdb21cd7c6550d81b5e2581" - integrity sha512-WXR6f33YzT4RSb5HK6RKg9CrE4sJO1mMrrtPZgmvdFRxPm+KE5tPrnEgnlhjrVzRB0eZov76hd+jSutezqRAbg== - dependencies: - "@ethereumjs/util" "^8.1.0" - "@metamask/eth-query" "^4.0.0" - "@metamask/ethjs-unit" "^0.3.0" - "@metamask/utils" "^11.1.0" - "@spruceid/siwe-parser" "2.1.0" - "@types/bn.js" "^5.1.5" - bignumber.js "^9.1.2" - bn.js "^5.2.1" - cockatiel "^3.1.2" - eth-ens-namehash "^2.0.8" - fast-deep-equal "^3.1.3" - -"@metamask/controller-utils@^11.6.0": +"@metamask/controller-utils@^11.0.0", "@metamask/controller-utils@^11.3.0", "@metamask/controller-utils@^11.4.4", "@metamask/controller-utils@^11.5.0", "@metamask/controller-utils@^11.6.0": version "11.6.0" resolved "https://registry.yarnpkg.com/@metamask/controller-utils/-/controller-utils-11.6.0.tgz#68bae4323ad4a68811befadc018043e6c15f6cc1" integrity sha512-7dcaimnRxNzQBVXdadNH/oezBkfxYJ2bK2qB09d9mYjUY3+/dyWX2BYcUXSb1BOJlyJxtVDEN1+sqIRMnoqL/Q== From e00d2260f3e6954e8bc5401ae5a9df1c465b8b8b Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Fri, 7 Mar 2025 17:38:41 -0500 Subject: [PATCH 051/124] update to bogus version number --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d1708eb09e5d..8eafe902b020 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "metamask", - "version": "7.41.0", + "version": "7.41.99", "private": true, "scripts": { "audit:ci": "./scripts/yarn-audit.sh", From bbc591c0aea8fa3604892384671e1743c3c3b823 Mon Sep 17 00:00:00 2001 From: metamaskbot Date: Fri, 7 Mar 2025 22:39:59 +0000 Subject: [PATCH 052/124] Bump version number to 1596 --- android/app/build.gradle | 2 +- bitrise.yml | 4 ++-- ios/MetaMask.xcodeproj/project.pbxproj | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 78ab337df87e..c06b1d853e65 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -179,7 +179,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionName "7.41.0" - versionCode 1586 + versionCode 1596 testBuildType System.getProperty('testBuildType', 'debug') missingDimensionStrategy 'react-native-camera', 'general' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/bitrise.yml b/bitrise.yml index f08fab1bbad6..68e0fefe9381 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -1828,13 +1828,13 @@ app: VERSION_NAME: 7.41.0 - opts: is_expand: false - VERSION_NUMBER: 1586 + VERSION_NUMBER: 1596 - opts: is_expand: false FLASK_VERSION_NAME: 7.41.0 - opts: is_expand: false - FLASK_VERSION_NUMBER: 1586 + FLASK_VERSION_NUMBER: 1596 - opts: is_expand: false ANDROID_APK_LINK: '' diff --git a/ios/MetaMask.xcodeproj/project.pbxproj b/ios/MetaMask.xcodeproj/project.pbxproj index 50f71048c92f..b40a75692a2d 100644 --- a/ios/MetaMask.xcodeproj/project.pbxproj +++ b/ios/MetaMask.xcodeproj/project.pbxproj @@ -1380,7 +1380,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1586; + CURRENT_PROJECT_VERSION = 1596; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1449,7 +1449,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1586; + CURRENT_PROJECT_VERSION = 1596; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1514,7 +1514,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1586; + CURRENT_PROJECT_VERSION = 1596; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1580,7 +1580,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1586; + CURRENT_PROJECT_VERSION = 1596; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1739,7 +1739,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1586; + CURRENT_PROJECT_VERSION = 1596; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1808,7 +1808,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1586; + CURRENT_PROJECT_VERSION = 1596; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; From b1b4b63414ea5bbfe11be75ad8e9aad880665c37 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Thu, 13 Mar 2025 00:42:02 -0500 Subject: [PATCH 053/124] fix UrlAutocomplete tests --- .../UI/UrlAutocomplete/index.test.tsx | 93 ++++++++++++++++++- app/components/UI/UrlAutocomplete/index.tsx | 4 +- 2 files changed, 91 insertions(+), 6 deletions(-) diff --git a/app/components/UI/UrlAutocomplete/index.test.tsx b/app/components/UI/UrlAutocomplete/index.test.tsx index f47a6b266d9b..ee2e4a7dccbd 100644 --- a/app/components/UI/UrlAutocomplete/index.test.tsx +++ b/app/components/UI/UrlAutocomplete/index.test.tsx @@ -5,8 +5,36 @@ import { act, fireEvent, screen } from '@testing-library/react-native'; import renderWithProvider from '../../../util/test/renderWithProvider'; import { removeBookmark } from '../../../actions/bookmarks'; import { noop } from 'lodash'; +import { createStackNavigator } from '@react-navigation/stack'; +import { TokenSearchResponseItem } from '@metamask/token-search-discovery-controller'; -const defaultState = { browser: { history: [] }, bookmarks: [{url: 'https://www.bookmark.com', name: 'MyBookmark'}] }; +const defaultState = { + browser: { history: [] }, + bookmarks: [{url: 'https://www.bookmark.com', name: 'MyBookmark'}], + engine: { + backgroundState: { + PreferencesController: { + isIpfsGatewayEnabled: false, + }, + }, + } +}; + +type RenderWithProviderParams = Parameters; + +jest.mock('../../hooks/useTokenSearchDiscovery/useTokenSearchDiscovery'); + +const Stack = createStackNavigator(); +const render = (...args: RenderWithProviderParams) => { + const Component = () => args[0]; + return renderWithProvider( + + + , + args[1], + args[2], + ); +}; describe('UrlAutocomplete', () => { beforeAll(() => { @@ -19,7 +47,7 @@ describe('UrlAutocomplete', () => { it('should show sites from dapp list', async () => { const ref = React.createRef(); - renderWithProvider(, {state: defaultState}, false); + render(, {state: defaultState}); act(() => { ref.current?.search('uni'); @@ -31,7 +59,7 @@ describe('UrlAutocomplete', () => { it('should show sites from bookmarks', async () => { const ref = React.createRef(); - renderWithProvider(, {state: defaultState}, false); + render(, {state: defaultState}); act(() => { ref.current?.search('MyBook'); @@ -43,7 +71,7 @@ describe('UrlAutocomplete', () => { it('should delete a bookmark when pressing the trash icon', async () => { const ref = React.createRef(); - const { store } = renderWithProvider(, {state: defaultState}, false); + const { store } = render(, {state: defaultState}); store.dispatch = jest.fn(); act(() => { @@ -55,4 +83,61 @@ describe('UrlAutocomplete', () => { fireEvent.press(deleteFavorite); expect(store.dispatch).toHaveBeenCalledWith(removeBookmark({...defaultState.bookmarks[0], type: 'favorites'})); }); + + it('should show a loading indicator when searching tokens', async () => { + // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires + const useTSD = require('../../hooks/useTokenSearchDiscovery/useTokenSearchDiscovery'); + const searchTokens = jest.fn(); + const results: TokenSearchResponseItem[] = []; + const reset = jest.fn(); + useTSD.default.mockImplementationOnce(() => ({ + results, + isLoading: true, + reset, + searchTokens, + })); + const ref = React.createRef(); + render(, {state: defaultState}); + + act(() => { + ref.current?.search('doge'); + jest.runAllTimers(); + }); + + expect(await screen.findByTestId('loading-indicator', {includeHiddenElements: true})).toBeDefined(); + }); + + it('should display token search results', async () => { + // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires + const useTSD = require('../../hooks/useTokenSearchDiscovery/useTokenSearchDiscovery'); + const searchTokens = jest.fn(); + const results: TokenSearchResponseItem[] = [ + { + tokenAddress: '0x123', + chainId: '0x1', + name: 'Dogecoin', + symbol: 'DOGE', + usdPrice: 1, + usdPricePercentChange: { + oneDay: 1, + }, + } + ]; + const reset = jest.fn(); + useTSD.default.mockImplementationOnce(() => ({ + results, + isLoading: false, + reset, + searchTokens, + })); + const ref = React.createRef(); + render(, {state: defaultState}); + + act(() => { + ref.current?.search('doge'); + jest.runAllTimers(); + }); + + expect(await screen.findByText('Dogecoin', {includeHiddenElements: true})).toBeDefined(); + }); }); diff --git a/app/components/UI/UrlAutocomplete/index.tsx b/app/components/UI/UrlAutocomplete/index.tsx index 5cd5919aa175..1e2452e6e962 100644 --- a/app/components/UI/UrlAutocomplete/index.tsx +++ b/app/components/UI/UrlAutocomplete/index.tsx @@ -279,7 +279,7 @@ const UrlAutocomplete = forwardRef< {strings(`autocomplete.${category}`)} {category === 'tokens' && isTokenSearchLoading && ( - + )} ), [styles, isTokenSearchLoading]); @@ -295,7 +295,7 @@ const UrlAutocomplete = forwardRef< /> ), [hide, onSelect, goToSwaps]); - if (!hasResults) { + if (!hasResults && !isTokenSearchLoading) { return ( From b55e1406c3919d980fcfa6e2b253ea41f7e88f61 Mon Sep 17 00:00:00 2001 From: Michele Esposito Date: Thu, 13 Mar 2025 14:43:40 +0100 Subject: [PATCH 054/124] bump controllers --- package.json | 16 +++---- yarn.lock | 133 +++++++++++++++++---------------------------------- 2 files changed, 53 insertions(+), 96 deletions(-) diff --git a/package.json b/package.json index 202f8347bad4..0be09ea810f2 100644 --- a/package.json +++ b/package.json @@ -151,14 +151,14 @@ "@keystonehq/metamask-airgapped-keyring": "^0.13.1", "@keystonehq/ur-decoder": "^0.12.2", "@ledgerhq/react-native-hw-transport-ble": "^6.33.2", - "@metamask/accounts-controller": "^24.1.0", + "@metamask/accounts-controller": "^25.0.0", "@metamask/address-book-controller": "^6.0.3", "@metamask/approval-controller": "^7.1.0", "@metamask/assets-controllers": "^51.0.2", "@metamask/base-controller": "^8.0.0", "@metamask/bitcoin-wallet-snap": "^0.9.0", - "@metamask/bridge-controller": "^3.0.0", - "@metamask/bridge-status-controller": "^3.0.0", + "@metamask/bridge-controller": "^4.0.0", + "@metamask/bridge-status-controller": "^4.0.0", "@metamask/composable-controller": "^11.0.0", "@metamask/controller-utils": "^11.3.0", "@metamask/design-tokens": "^5.0.0", @@ -179,13 +179,13 @@ "@metamask/json-rpc-middleware-stream": "^8.0.6", "@metamask/key-tree": "^10.0.2", "@metamask/keyring-api": "^17.2.1", - "@metamask/keyring-controller": "^19.2.0", + "@metamask/keyring-controller": "^20.0.0", "@metamask/keyring-internal-api": "^4.0.3", "@metamask/keyring-snap-client": "^4.0.1", "@metamask/logging-controller": "^6.0.4", "@metamask/message-signing-snap": "^0.3.3", "@metamask/metamask-eth-abis": "3.1.1", - "@metamask/multichain-network-controller": "^0.1.1", + "@metamask/multichain-network-controller": "^0.2.0", "@metamask/multichain-transactions-controller": "^0.7.0", "@metamask/network-controller": "^22.1.0", "@metamask/notification-services-controller": "^2.0.0", @@ -193,7 +193,7 @@ "@metamask/phishing-controller": "^12.0.3", "@metamask/post-message-stream": "^9.0.0", "@metamask/ppom-validator": "0.36.0", - "@metamask/preferences-controller": "^15.0.1", + "@metamask/preferences-controller": "^16.0.0", "@metamask/profile-sync-controller": "^9.0.0", "@metamask/react-native-actionsheet": "2.4.2", "@metamask/react-native-button": "^3.0.0", @@ -205,7 +205,7 @@ "@metamask/scure-bip39": "^2.1.0", "@metamask/sdk-communication-layer": "0.29.0-wallet", "@metamask/selected-network-controller": "^21.0.0", - "@metamask/signature-controller": "^23.1.0", + "@metamask/signature-controller": "^24.0.0", "@metamask/slip44": "^4.1.0", "@metamask/smart-transactions-controller": "^16.0.1", "@metamask/snaps-controllers": "^10.0.0", @@ -218,7 +218,7 @@ "@metamask/swappable-obj-proxy": "^2.1.0", "@metamask/swaps-controller": "^12.1.0", "@metamask/token-search-discovery-controller": "^2.1.0", - "@metamask/transaction-controller": "45.0.0", + "@metamask/transaction-controller": "47.0.0", "@metamask/utils": "^11.2.0", "@ngraveio/bc-ur": "^1.1.6", "@noble/hashes": "^1.7.1", diff --git a/yarn.lock b/yarn.lock index f4196a82f258..5f32df4710d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4515,16 +4515,16 @@ "@metamask/superstruct" "^3.1.0" "@metamask/utils" "^11.0.1" -"@metamask/accounts-controller@^24.1.0": - version "24.1.0" - resolved "https://registry.yarnpkg.com/@metamask/accounts-controller/-/accounts-controller-24.1.0.tgz#749c2e9617610c058c38fa355a680152e81e08dd" - integrity sha512-Yip2kbWEc0p0axaIB5accPHsrnON8p2277xPIklRNcJnMxjnkzDdqUQy8nc2eSiQoFsWnQLErDLeoSt4pgfooA== +"@metamask/accounts-controller@^25.0.0": + version "25.0.0" + resolved "https://registry.yarnpkg.com/@metamask/accounts-controller/-/accounts-controller-25.0.0.tgz#721ca64e66c59b1820d60432c144036ba19fb582" + integrity sha512-y5OKNaJrQM52neBebuxTJBPWp3E04RGeAySm+coBFPa+XU2sQH1lme2fvWZ49/hs5OuGJ8FvMkTKW7bi8ZH6jg== dependencies: "@ethereumjs/util" "^8.1.0" "@metamask/base-controller" "^8.0.0" "@metamask/eth-snap-keyring" "^11.1.0" "@metamask/keyring-api" "^17.2.0" - "@metamask/keyring-internal-api" "^4.0.3" + "@metamask/keyring-internal-api" "^5.0.0" "@metamask/keyring-utils" "^2.3.1" "@metamask/network-controller" "^22.2.1" "@metamask/snaps-sdk" "^6.17.1" @@ -4587,7 +4587,7 @@ single-call-balance-checker-abi "^1.0.0" uuid "^8.3.2" -"@metamask/base-controller@^7.0.1", "@metamask/base-controller@^7.0.2", "@metamask/base-controller@^7.0.3", "@metamask/base-controller@^7.1.1": +"@metamask/base-controller@^7.0.1", "@metamask/base-controller@^7.0.3", "@metamask/base-controller@^7.1.1": version "7.1.1" resolved "https://registry.yarnpkg.com/@metamask/base-controller/-/base-controller-7.1.1.tgz#837216ee099563b2106202fa0ed376dc909dfbb9" integrity sha512-4nbA6RL9y0SdHdn4MmMTREX6ISJL7OGHn0GXXszv0tp1fdjsn+SBs28uu1a9ceg1J7R/lO6JH7jAAz8zRtt8Nw== @@ -4608,10 +4608,10 @@ resolved "https://registry.yarnpkg.com/@metamask/bitcoin-wallet-snap/-/bitcoin-wallet-snap-0.9.0.tgz#47014c75c1e1ed84fef57177901f458fd2b6510a" integrity sha512-983twhfOfSCQljT+eV8JLcV0bqGVMObXnEy8TOFAkwwRW2I1ZsVRAfylbGiSwxlhxLntcpLqVu0Sj29xNyA06Q== -"@metamask/bridge-controller@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@metamask/bridge-controller/-/bridge-controller-3.0.0.tgz#7dc45f8ba3a451d1e1c505dfb26f45aa4143cd09" - integrity sha512-7k2s3FxQorz/vi7XWmmXIjI7QNcmtoUBNZtXzAwu4W44YAZdY3ojVdA20qCnHQW1NkSHn44eaacfZynqiZmQ1Q== +"@metamask/bridge-controller@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@metamask/bridge-controller/-/bridge-controller-4.0.0.tgz#2dbb499738c2ed09bb56cc1477331b61f674f1c8" + integrity sha512-mls0kj0D47vA7B77ph4rriE0AlbEkP86LjjX2YkXVyI9Z1HDridVgI3xlM34db1U7PZFFvwZZ7iyV0NOY2E2Yw== dependencies: "@ethersproject/address" "^5.7.0" "@ethersproject/bignumber" "^5.7.0" @@ -4624,13 +4624,13 @@ "@metamask/polling-controller" "^12.0.3" "@metamask/utils" "^11.2.0" -"@metamask/bridge-status-controller@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@metamask/bridge-status-controller/-/bridge-status-controller-3.0.0.tgz#472416e8f8e2c3ea159560f95c3078a351f87c73" - integrity sha512-SRXiIk7TNs2vUmckILHkaev3IGtpwUwoX8b+Hcg0VQ3aEH/8+cQFSLh3JHRfFHTkj9vTrsYRUj4qobMGIjElUQ== +"@metamask/bridge-status-controller@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@metamask/bridge-status-controller/-/bridge-status-controller-4.0.0.tgz#e7e51d9330a04c0bce5d4c1339801b30e2a47762" + integrity sha512-ahFEkFMLQOlOAynnlIwVnzfjFOonyAc7NE40yGP3Dko61JePoL+w4vYV1etbaJhef7EyRHCJOCkuw1WRSpEBLQ== dependencies: "@metamask/base-controller" "^8.0.0" - "@metamask/bridge-controller" "^3.0.0" + "@metamask/bridge-controller" "^4.0.0" "@metamask/controller-utils" "^11.5.0" "@metamask/polling-controller" "^12.0.3" "@metamask/superstruct" "^3.1.0" @@ -4670,7 +4670,7 @@ resolved "https://registry.yarnpkg.com/@metamask/contract-metadata/-/contract-metadata-2.5.0.tgz#33921fa9c15eb1863f55dcd5f75467ae15614ebb" integrity sha512-+j7jEcp0P1OUMEpa/OIwfJs/ahBC/akwgWxaRTSWX2SWABvlUKBVRMtslfL94Qj2wN2xw8xjaUy5nSHqrznqDA== -"@metamask/controller-utils@^11.0.0", "@metamask/controller-utils@^11.3.0", "@metamask/controller-utils@^11.4.4", "@metamask/controller-utils@^11.5.0", "@metamask/controller-utils@^11.6.0": +"@metamask/controller-utils@^11.0.0", "@metamask/controller-utils@^11.3.0", "@metamask/controller-utils@^11.5.0", "@metamask/controller-utils@^11.6.0": version "11.6.0" resolved "https://registry.npmjs.org/@metamask/controller-utils/-/controller-utils-11.6.0.tgz#68bae4323ad4a68811befadc018043e6c15f6cc1" integrity sha512-7dcaimnRxNzQBVXdadNH/oezBkfxYJ2bK2qB09d9mYjUY3+/dyWX2BYcUXSb1BOJlyJxtVDEN1+sqIRMnoqL/Q== @@ -4723,18 +4723,6 @@ json-rpc-random-id "^1.0.1" pify "^5.0.0" -"@metamask/eth-hd-keyring@^10.0.0": - version "10.0.1" - resolved "https://registry.yarnpkg.com/@metamask/eth-hd-keyring/-/eth-hd-keyring-10.0.1.tgz#43e817a07b9f50f39b27155d697db55d5fa4237c" - integrity sha512-Ebzpd/Gejcsuz5zX+qL5Wou/JHT2gb5zxxHz30NJIHRO1xIe/nFOaNW5Q6InIddb1reemQe21xqHHPTnVghnwQ== - dependencies: - "@ethereumjs/util" "^8.1.0" - "@metamask/eth-sig-util" "^8.2.0" - "@metamask/key-tree" "^10.0.2" - "@metamask/scure-bip39" "^2.1.1" - "@metamask/utils" "^11.1.0" - ethereum-cryptography "^2.1.2" - "@metamask/eth-hd-keyring@^11.0.0": version "11.0.0" resolved "https://registry.yarnpkg.com/@metamask/eth-hd-keyring/-/eth-hd-keyring-11.0.0.tgz#c89db5b4ff2f61fd6ea75dbeddbd9fd5dbbe6bf8" @@ -4841,17 +4829,6 @@ ethereum-cryptography "^2.1.2" tweetnacl "^1.0.3" -"@metamask/eth-simple-keyring@^8.1.0": - version "8.1.1" - resolved "https://registry.yarnpkg.com/@metamask/eth-simple-keyring/-/eth-simple-keyring-8.1.1.tgz#23bc2813dad92bffef5d7e37b4eed78c780db81f" - integrity sha512-/qcnwkzjtluM96IGTeCN7X2efI4GurxW8sMpj/oxN5sb69CMsr22WiMe1cRHQ0iM+hzlWRyxySFye4wPZENgvw== - dependencies: - "@ethereumjs/util" "^8.1.0" - "@metamask/eth-sig-util" "^8.2.0" - "@metamask/utils" "^11.1.0" - ethereum-cryptography "^2.1.2" - randombytes "^2.1.0" - "@metamask/eth-simple-keyring@^9.0.0": version "9.0.0" resolved "https://registry.yarnpkg.com/@metamask/eth-simple-keyring/-/eth-simple-keyring-9.0.0.tgz#8f13b97bf527a3656cf57ac0acc5ecd4c4716701" @@ -5021,7 +4998,7 @@ "@noble/hashes" "^1.3.2" "@scure/base" "^1.0.0" -"@metamask/keyring-api@^17.0.0", "@metamask/keyring-api@^17.2.0", "@metamask/keyring-api@^17.2.1": +"@metamask/keyring-api@^17.2.0", "@metamask/keyring-api@^17.2.1": version "17.2.1" resolved "https://registry.yarnpkg.com/@metamask/keyring-api/-/keyring-api-17.2.1.tgz#315e14063abbe07b85a90d2933535d281ac7ec47" integrity sha512-VfFfIsLvGTMtHgyB4lPHxUSo6iumlYwKSBsFtz+9kUk4+Dx3OA9osPN+Xtse+bUWOu5VtOepk0L1MbajEvbLQA== @@ -5031,26 +5008,6 @@ "@metamask/utils" "^11.1.0" bech32 "^2.0.0" -"@metamask/keyring-controller@^19.2.0": - version "19.2.1" - resolved "https://registry.yarnpkg.com/@metamask/keyring-controller/-/keyring-controller-19.2.1.tgz#47be936b39e8ac822d759b00f495cff067f857c7" - integrity sha512-Q0d3bM9SieJzfpMMYLzaVjN+EQNApnddTdufsYbzRYqBV8b5cdFCMoYapQvDFL/nPqBO9e4bXpei6ka6kiusBQ== - dependencies: - "@ethereumjs/util" "^8.1.0" - "@keystonehq/metamask-airgapped-keyring" "^0.14.1" - "@metamask/base-controller" "^8.0.0" - "@metamask/browser-passworder" "^4.3.0" - "@metamask/eth-hd-keyring" "^10.0.0" - "@metamask/eth-sig-util" "^8.2.0" - "@metamask/eth-simple-keyring" "^8.1.0" - "@metamask/keyring-api" "^17.2.0" - "@metamask/keyring-internal-api" "^4.0.3" - "@metamask/utils" "^11.2.0" - async-mutex "^0.5.0" - ethereumjs-wallet "^1.0.1" - immer "^9.0.6" - ulid "^2.3.0" - "@metamask/keyring-controller@^20.0.0": version "20.0.0" resolved "https://registry.yarnpkg.com/@metamask/keyring-controller/-/keyring-controller-20.0.0.tgz#cbb60fcd9d835401029367337d1a03040a844be3" @@ -5173,14 +5130,14 @@ resolved "https://registry.yarnpkg.com/@metamask/mobile-provider/-/mobile-provider-3.0.0.tgz#8a6a5a0874c8cbe4b468f63dfc57117d207f9595" integrity sha512-XwFJk0rd9lAZR5xS3VC7ypEhD7DvZR2gi2Ch6PHnODIqeS9Te3OdVKK5+jHI4his8v/zs6LWdFdlRtx5/jL96w== -"@metamask/multichain-network-controller@^0.1.1": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@metamask/multichain-network-controller/-/multichain-network-controller-0.1.1.tgz#d91e98660b63f334e8df98f7ba68fe412c4e01e5" - integrity sha512-XDfTb9RZbyQ+1U+xqV3lJH6/8Msru4CmIL2iNJ8KQLwJJvfQsNgSkT4Y0+nYbiWKACOe/ahjBpx0Y7rgmqSjWg== +"@metamask/multichain-network-controller@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@metamask/multichain-network-controller/-/multichain-network-controller-0.2.0.tgz#f49f6a092c556251dc42ff700c36593ff3ffb011" + integrity sha512-ynuo/h4DMYx5ox2LYxTkYwAfUJ9LVTnSaamynlGqec6mhYPqO1D4n0ZNHzMjCmzqPXtoB64EvwEDbhcAhBkFmg== dependencies: "@metamask/base-controller" "^8.0.0" - "@metamask/keyring-api" "^17.0.0" - "@metamask/utils" "^11.1.0" + "@metamask/keyring-api" "^17.2.0" + "@metamask/utils" "^11.2.0" "@solana/addresses" "^2.0.0" "@metamask/multichain-transactions-controller@^0.7.0": @@ -5350,13 +5307,13 @@ eslint-plugin-n "^16.6.2" json-rpc-random-id "^1.0.1" -"@metamask/preferences-controller@^15.0.1": - version "15.0.1" - resolved "https://registry.yarnpkg.com/@metamask/preferences-controller/-/preferences-controller-15.0.1.tgz#4306099e4659591636304d81ed0954afb160ab81" - integrity sha512-y2rGKMr9fY2LCwEjvX7QKxOqxy5Tz6vA+QK8YEXuczeHd3n2jrh9DNBeM+BZzzOO2cXJgmbn0Jeotl09kDD94g== +"@metamask/preferences-controller@^16.0.0": + version "16.0.0" + resolved "https://registry.yarnpkg.com/@metamask/preferences-controller/-/preferences-controller-16.0.0.tgz#be7f3602181ff7a6d4c032fc8fd38b403836fc35" + integrity sha512-XzuhUMOY+JplgDVSJOV6LCgBB+/hl/lf67XMWI7jSEGLpAD632CwlEbMsfObzAqDmqsxkK3HFfocbsqrbSbCCg== dependencies: - "@metamask/base-controller" "^7.0.2" - "@metamask/controller-utils" "^11.4.4" + "@metamask/base-controller" "^8.0.0" + "@metamask/controller-utils" "^11.5.0" "@metamask/profile-sync-controller@^9.0.0": version "9.0.0" @@ -5518,15 +5475,15 @@ "@metamask/swappable-obj-proxy" "^2.3.0" "@metamask/utils" "^11.0.1" -"@metamask/signature-controller@^23.1.0": - version "23.1.0" - resolved "https://registry.yarnpkg.com/@metamask/signature-controller/-/signature-controller-23.1.0.tgz#45b3b545e5a4e890ff41a737b6526cb08fd9f1b3" - integrity sha512-HPUDjVjsZ/HU5QZlmllh1yN2Z1+VhqjTPmzxqXBqD28iKYwUU0YEMN+Jahyh9Ukl7BH/UgzGaaHytGXJ1Xf2Xw== +"@metamask/signature-controller@^24.0.0": + version "24.0.0" + resolved "https://registry.yarnpkg.com/@metamask/signature-controller/-/signature-controller-24.0.0.tgz#27e48f442b627be0d63573acf232dd774f3d97da" + integrity sha512-jjhoaB+eFY+uG8BC9Bb33gx0NHq0IimPCnhvMzqXtglV3gOei3sPE1C415C71kXIq9e//WfhD+YiV6vgKDUkZA== dependencies: - "@metamask/base-controller" "^7.0.2" - "@metamask/controller-utils" "^11.4.4" - "@metamask/eth-sig-util" "^8.0.0" - "@metamask/utils" "^10.0.0" + "@metamask/base-controller" "^8.0.0" + "@metamask/controller-utils" "^11.5.0" + "@metamask/eth-sig-util" "^8.2.0" + "@metamask/utils" "^11.2.0" jsonschema "^1.4.1" lodash "^4.17.21" uuid "^8.3.2" @@ -5794,24 +5751,24 @@ "@metamask/base-controller" "^8.0.0" "@metamask/utils" "^11.1.0" -"@metamask/transaction-controller@45.0.0": - version "45.0.0" - resolved "https://registry.yarnpkg.com/@metamask/transaction-controller/-/transaction-controller-45.0.0.tgz#960cdc9fcc6dc3f9f6fd3ec082def8d67696e742" - integrity sha512-x+5OFiX0sfnLrqN35VDHAB9g4YYLn0XVrYChPTY/Rb7E3pkPvWR4aY2St5ZG4vao6upf8ziPnMdYlpFgGQZxsQ== +"@metamask/transaction-controller@47.0.0": + version "47.0.0" + resolved "https://registry.yarnpkg.com/@metamask/transaction-controller/-/transaction-controller-47.0.0.tgz#385f811275a99e798e1bf8b581567e0b4b113ca2" + integrity sha512-6s9rgp9xlbMuS5uiw8a7Q9nHkomtGRKR4DI4I2HaJnvF/sn1Csjmzx0sAZer1o3S9eC67x+2ynzvhozveX4v1A== dependencies: - "@ethereumjs/common" "^3.2.0" - "@ethereumjs/tx" "^4.2.0" + "@ethereumjs/common" "^4.4.0" + "@ethereumjs/tx" "^5.4.0" "@ethereumjs/util" "^8.1.0" "@ethersproject/abi" "^5.7.0" "@ethersproject/contracts" "^5.7.0" "@ethersproject/providers" "^5.7.0" - "@metamask/base-controller" "^7.1.1" + "@metamask/base-controller" "^8.0.0" "@metamask/controller-utils" "^11.5.0" "@metamask/eth-query" "^4.0.0" "@metamask/metamask-eth-abis" "^3.1.1" "@metamask/nonce-tracker" "^6.0.0" "@metamask/rpc-errors" "^7.0.2" - "@metamask/utils" "^11.1.0" + "@metamask/utils" "^11.2.0" async-mutex "^0.5.0" bn.js "^5.2.1" eth-method-registry "^4.0.0" From 768c79a82ce972115244eca929e3c82952194e48 Mon Sep 17 00:00:00 2001 From: Michele Esposito Date: Thu, 13 Mar 2025 14:44:04 +0100 Subject: [PATCH 055/124] update `withKeyring` calls --- .../Views/ConnectQRHardware/index.tsx | 2 +- app/core/Ledger/Ledger.test.ts | 19 ++++--- app/core/Ledger/Ledger.ts | 53 ++++++++++++------- app/core/Vault.js | 2 +- app/util/importAdditionalAccounts.ts | 2 +- 5 files changed, 46 insertions(+), 32 deletions(-) diff --git a/app/components/Views/ConnectQRHardware/index.tsx b/app/components/Views/ConnectQRHardware/index.tsx index 82387c707260..23ae008b9458 100644 --- a/app/components/Views/ConnectQRHardware/index.tsx +++ b/app/components/Views/ConnectQRHardware/index.tsx @@ -118,7 +118,7 @@ async function initiateQRHardwareConnection( const qrInteractions = await KeyringController.withKeyring( { type: KeyringTypes.qr }, // @ts-expect-error The QR Keyring type is not compatible with our keyring type yet - async (keyring: QRKeyring) => ({ + async ({ keyring }: { keyring: QRKeyring }) => ({ cancelSync: keyring.cancelSync.bind(keyring), submitCryptoAccount: keyring.submitCryptoAccount.bind(keyring), submitCryptoHDKey: keyring.submitCryptoHDKey.bind(keyring), diff --git a/app/core/Ledger/Ledger.test.ts b/app/core/Ledger/Ledger.test.ts index 730d09329298..e5a0d5bf7e4e 100644 --- a/app/core/Ledger/Ledger.test.ts +++ b/app/core/Ledger/Ledger.test.ts @@ -70,7 +70,9 @@ describe('Ledger core', () => { const mockKeyringController = MockEngine.context.KeyringController; ledgerKeyring = { - addAccounts: jest.fn().mockResolvedValue(['0x49b6FFd1BD9d1c64EEf400a64a1e4bBC33E2CAB2']), + addAccounts: jest + .fn() + .mockResolvedValue(['0x49b6FFd1BD9d1c64EEf400a64a1e4bBC33E2CAB2']), bridge: { getAppNameAndVersion: jest .fn() @@ -131,8 +133,9 @@ describe('Ledger core', () => { }; mockKeyringController.withKeyring.mockImplementation( - // @ts-expect-error The Ledger keyring is not compatible with our keyring type yet - (_selector, operation) => operation(ledgerKeyring), + (_selector, operation) => + // @ts-expect-error The Ledger keyring is not compatible with our keyring type yet + operation({ keyring: ledgerKeyring, metadata: { id: '1234' } }), ); mockKeyringController.signTypedMessage.mockResolvedValue('signature'); }); @@ -277,7 +280,7 @@ describe('Ledger core', () => { // @ts-expect-error: The account metadata type is hard to mock metadata: { name: 'Ledger 1', - } + }, }); it(`calls keyring.setAccountToUnlock and addAccounts`, async () => { @@ -287,24 +290,20 @@ describe('Ledger core', () => { }); it(`throws an error if the account name has already exists`, async () => { - - mockAccountsController.state.internalAccounts.accounts = [ { // @ts-expect-error: The account metadata type is hard to mock metadata: { name: 'Ledger 1', }, - } + }, ]; try { await unlockLedgerWalletAccount(1); } catch (err) { expect(err).toBeInstanceOf(Error); - expect((err as Error).message).toBe( - `Account Ledger 1 already exists` - ); + expect((err as Error).message).toBe(`Account Ledger 1 already exists`); } }); }); diff --git a/app/core/Ledger/Ledger.ts b/app/core/Ledger/Ledger.ts index 49ec0a513c9a..14f0e849d61d 100644 --- a/app/core/Ledger/Ledger.ts +++ b/app/core/Ledger/Ledger.ts @@ -1,5 +1,8 @@ import type BleTransport from '@ledgerhq/react-native-hw-transport-ble'; -import { SignTypedDataVersion } from '@metamask/keyring-controller'; +import { + KeyringMetadata, + SignTypedDataVersion, +} from '@metamask/keyring-controller'; import ExtendedKeyringTypes from '../../constants/keyringTypes'; import Engine from '../Engine'; import { @@ -27,7 +30,10 @@ import { keyringTypeToName } from '@metamask/accounts-controller'; * @returns The stored Ledger Keyring */ export const withLedgerKeyring = async ( - operation: (keyring: LedgerKeyring) => Promise, + operation: (selectedKeyring: { + keyring: LedgerKeyring; + metadata: KeyringMetadata; + }) => Promise, ): Promise => { const keyringController = Engine.context.KeyringController; return await keyringController.withKeyring( @@ -217,8 +223,12 @@ export const ledgerSignTypedMessage = async ( */ export const checkAccountNameExists = async (accountName: string) => { const accountsController = Engine.context.AccountsController; - const accounts = Object.values(accountsController.state.internalAccounts.accounts); - const existingAccount = accounts.find((account) => account.metadata.name === accountName); + const accounts = Object.values( + accountsController.state.internalAccounts.accounts, + ); + const existingAccount = accounts.find( + (account) => account.metadata.name === accountName, + ); return !!existingAccount; }; @@ -229,25 +239,30 @@ export const checkAccountNameExists = async (accountName: string) => { */ export const unlockLedgerWalletAccount = async (index: number) => { const accountsController = Engine.context.AccountsController; - const { unlockAccount, name} = await withLedgerKeyring(async (keyring: LedgerKeyring) => { - const existingAccounts = await keyring.getAccounts(); - const keyringName = keyringTypeToName(ExtendedKeyringTypes.ledger); - const accountName = `${keyringName} ${existingAccounts.length + 1}`; + const { unlockAccount, name } = await withLedgerKeyring( + async (keyring: LedgerKeyring) => { + const existingAccounts = await keyring.getAccounts(); + const keyringName = keyringTypeToName(ExtendedKeyringTypes.ledger); + const accountName = `${keyringName} ${existingAccounts.length + 1}`; - if(await checkAccountNameExists(accountName)) { - throw new Error(strings('ledger.account_name_existed', { accountName })); - } + if (await checkAccountNameExists(accountName)) { + throw new Error( + strings('ledger.account_name_existed', { accountName }), + ); + } - keyring.setAccountToUnlock(index); - const accounts = await keyring.addAccounts(1); - return { unlockAccount: accounts[accounts.length - 1], name: accountName }; - }); + keyring.setAccountToUnlock(index); + const accounts = await keyring.addAccounts(1); + return { + unlockAccount: accounts[accounts.length - 1], + name: accountName, + }; + }, + ); - const account = - accountsController.getAccountByAddress(unlockAccount); + const account = accountsController.getAccountByAddress(unlockAccount); - if(account && name !== account.metadata.name) { + if (account && name !== account.metadata.name) { accountsController.setAccountName(account.id, name); } }; - diff --git a/app/core/Vault.js b/app/core/Vault.js index 351a14c59050..15195daee46e 100644 --- a/app/core/Vault.js +++ b/app/core/Vault.js @@ -164,7 +164,7 @@ function hasKeyringType(state, type) { */ async function getSerializedKeyring(type) { const { KeyringController } = Engine.context; - return await KeyringController.withKeyring({ type }, (keyring) => + return await KeyringController.withKeyring({ type }, ({ keyring }) => keyring.serialize(), ); } diff --git a/app/util/importAdditionalAccounts.ts b/app/util/importAdditionalAccounts.ts index ce6d612f68be..4f53b4e12060 100644 --- a/app/util/importAdditionalAccounts.ts +++ b/app/util/importAdditionalAccounts.ts @@ -37,7 +37,7 @@ export default async () => { await KeyringController.withKeyring( { type: ExtendedKeyringTypes.hd }, - async (primaryKeyring) => { + async ({ keyring: primaryKeyring }) => { for (let i = 0; i < MAX; i++) { const [newAccount] = await primaryKeyring.addAccounts(1); From dd2fd9ca83122a4c0f6f568118dd5753103b2cb0 Mon Sep 17 00:00:00 2001 From: Michele Esposito Date: Thu, 13 Mar 2025 14:50:56 +0100 Subject: [PATCH 056/124] update `addNewKeyring` calls --- app/core/Engine/Engine.test.ts | 5 +++-- app/core/Engine/Engine.ts | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/core/Engine/Engine.test.ts b/app/core/Engine/Engine.test.ts index 97ef31b12ab1..179b3c664039 100644 --- a/app/core/Engine/Engine.test.ts +++ b/app/core/Engine/Engine.test.ts @@ -191,11 +191,12 @@ describe('Engine', () => { jest .spyOn(engine.keyringController, 'getKeyringsByType') - .mockImplementation(() => []); + .mockImplementationOnce(() => []) + .mockImplementationOnce(() => [mockSnapKeyring]); jest .spyOn(engine.keyringController, 'addNewKeyring') - .mockImplementation(async () => mockSnapKeyring); + .mockResolvedValue({ id: '1234', name: 'Snap Keyring' }); const getSnapKeyringSpy = jest .spyOn(engine, 'getSnapKeyring') diff --git a/app/core/Engine/Engine.ts b/app/core/Engine/Engine.ts index e8bdbf74a900..91fe306aadf5 100644 --- a/app/core/Engine/Engine.ts +++ b/app/core/Engine/Engine.ts @@ -1669,11 +1669,14 @@ export class Engine { ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) getSnapKeyring = async () => { + // TODO: Replace `getKeyringsByType` with `withKeyring` let [snapKeyring] = this.keyringController.getKeyringsByType( KeyringTypes.snap, ); if (!snapKeyring) { - snapKeyring = await this.keyringController.addNewKeyring( + await this.keyringController.addNewKeyring(KeyringTypes.snap); + // TODO: Replace `getKeyringsByType` with `withKeyring` + [snapKeyring] = this.keyringController.getKeyringsByType( KeyringTypes.snap, ); } From 525e083ff0516b8009a95b16579a828222eef91d Mon Sep 17 00:00:00 2001 From: Michele Esposito Date: Thu, 13 Mar 2025 15:01:28 +0100 Subject: [PATCH 057/124] bump assets-controllers --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0be09ea810f2..9e5e9866923b 100644 --- a/package.json +++ b/package.json @@ -154,7 +154,7 @@ "@metamask/accounts-controller": "^25.0.0", "@metamask/address-book-controller": "^6.0.3", "@metamask/approval-controller": "^7.1.0", - "@metamask/assets-controllers": "^51.0.2", + "@metamask/assets-controllers": "^52.0.0", "@metamask/base-controller": "^8.0.0", "@metamask/bitcoin-wallet-snap": "^0.9.0", "@metamask/bridge-controller": "^4.0.0", From ec0090c2c18ff1322efc639b94507144e9ef489d Mon Sep 17 00:00:00 2001 From: Michele Esposito Date: Thu, 13 Mar 2025 15:19:57 +0100 Subject: [PATCH 058/124] fix lint:tsc --- app/core/Engine/Engine.ts | 2 - .../transaction-controller-init.ts | 2 - app/core/Engine/types.ts | 1 - app/core/Engine/utils/utils.ts | 1 - app/core/Ledger/Ledger.ts | 40 ++++++++----------- 5 files changed, 17 insertions(+), 29 deletions(-) diff --git a/app/core/Engine/Engine.ts b/app/core/Engine/Engine.ts index 91fe306aadf5..560713fcb1d4 100644 --- a/app/core/Engine/Engine.ts +++ b/app/core/Engine/Engine.ts @@ -286,7 +286,6 @@ export class Engine { }); const preferencesController = new PreferencesController({ - // @ts-expect-error TODO: Resolve mismatch between base-controller versions. messenger: this.controllerMessenger.getRestricted({ name: 'PreferencesController', allowedActions: [], @@ -1233,7 +1232,6 @@ export class Engine { RemoteFeatureFlagController: remoteFeatureFlagController, SelectedNetworkController: selectedNetworkController, SignatureController: new SignatureController({ - // @ts-expect-error TODO: Resolve mismatch between base-controller versions. messenger: this.controllerMessenger.getRestricted({ name: 'SignatureController', allowedActions: [ diff --git a/app/core/Engine/controllers/transaction-controller/transaction-controller-init.ts b/app/core/Engine/controllers/transaction-controller/transaction-controller-init.ts index 7f3a86028706..a6a956999d0d 100644 --- a/app/core/Engine/controllers/transaction-controller/transaction-controller-init.ts +++ b/app/core/Engine/controllers/transaction-controller/transaction-controller-init.ts @@ -27,7 +27,6 @@ import type { RootState } from '../../../../reducers'; export const TransactionControllerInit: ControllerInitFunction< TransactionController, - // @ts-expect-error TODO: Resolve mismatch between base-controller versions. TransactionControllerMessenger, TransactionControllerInitMessenger > = (request) => { @@ -155,7 +154,6 @@ function isIncomingTransactionsEnabled( function getControllers( request: ControllerInitRequest< - // @ts-expect-error TODO: Resolve mismatch between base-controller versions. TransactionControllerMessenger, TransactionControllerInitMessenger >, diff --git a/app/core/Engine/types.ts b/app/core/Engine/types.ts index 18873ad3c78b..a35fa36523fd 100644 --- a/app/core/Engine/types.ts +++ b/app/core/Engine/types.ts @@ -673,7 +673,6 @@ export type ControllerInitFunction< export type ControllerInitFunctionByControllerName = { [Name in ControllersToInitialize]: ControllerInitFunction< ControllerByName[Name], - // @ts-expect-error TODO: Resolve mismatch between base-controller versions. ReturnType<(typeof CONTROLLER_MESSENGERS)[Name]['getMessenger']>, ReturnType<(typeof CONTROLLER_MESSENGERS)[Name]['getInitMessenger']> >; diff --git a/app/core/Engine/utils/utils.ts b/app/core/Engine/utils/utils.ts index 8065ca0a1cf1..dd9ec1b375ea 100644 --- a/app/core/Engine/utils/utils.ts +++ b/app/core/Engine/utils/utils.ts @@ -21,7 +21,6 @@ type BaseControllerInitRequest = ControllerInitRequest< type InitFunction = ControllerInitFunction< ControllerByName[Name], - // @ts-expect-error TODO: Resolve mismatch between base-controller versions. ReturnType<(typeof CONTROLLER_MESSENGERS)[Name]['getMessenger']>, ReturnType<(typeof CONTROLLER_MESSENGERS)[Name]['getInitMessenger']> >; diff --git a/app/core/Ledger/Ledger.ts b/app/core/Ledger/Ledger.ts index 14f0e849d61d..5596c35be256 100644 --- a/app/core/Ledger/Ledger.ts +++ b/app/core/Ledger/Ledger.ts @@ -58,16 +58,14 @@ export const connectLedgerHardware = async ( transport: BleTransport, deviceId: string, ): Promise => { - const appAndVersion = await withLedgerKeyring( - async (keyring: LedgerKeyring) => { - keyring.setHdPath(LEDGER_LIVE_PATH); - keyring.setDeviceId(deviceId); + const appAndVersion = await withLedgerKeyring(async ({ keyring }) => { + keyring.setHdPath(LEDGER_LIVE_PATH); + keyring.setDeviceId(deviceId); - const bridge = keyring.bridge as LedgerMobileBridge; - await bridge.updateTransportMethod(transport); - return await bridge.getAppNameAndVersion(); - }, - ); + const bridge = keyring.bridge as LedgerMobileBridge; + await bridge.updateTransportMethod(transport); + return await bridge.getAppNameAndVersion(); + }); return appAndVersion.appName; }; @@ -76,7 +74,7 @@ export const connectLedgerHardware = async ( * Automatically opens the Ethereum app on the Ledger device. */ export const openEthereumAppOnLedger = async (): Promise => { - await withLedgerKeyring(async (keyring: LedgerKeyring) => { + await withLedgerKeyring(async ({ keyring }) => { const bridge = keyring.bridge as LedgerMobileBridge; await bridge.openEthApp(); }); @@ -86,7 +84,7 @@ export const openEthereumAppOnLedger = async (): Promise => { * Automatically closes the current app on the Ledger device. */ export const closeRunningAppOnLedger = async (): Promise => { - await withLedgerKeyring(async (keyring: LedgerKeyring) => { + await withLedgerKeyring(async ({ keyring }) => { const bridge = keyring.bridge as LedgerMobileBridge; await bridge.closeApps(); }); @@ -96,7 +94,7 @@ export const closeRunningAppOnLedger = async (): Promise => { * Forgets the ledger device. */ export const forgetLedger = async (): Promise => { - await withLedgerKeyring(async (keyring: LedgerKeyring) => { + await withLedgerKeyring(async ({ keyring }) => { keyring.forgetDevice(); }); }; @@ -107,9 +105,7 @@ export const forgetLedger = async (): Promise => { * @returns The DeviceId */ export const getDeviceId = async (): Promise => - await withLedgerKeyring(async (keyring: LedgerKeyring) => - keyring.getDeviceId(), - ); + await withLedgerKeyring(async ({ keyring }) => keyring.getDeviceId()); /** * Check if the path is valid @@ -134,7 +130,7 @@ export const isValidPath = (path: string): boolean => { * @param path - The HD Path to set */ export const setHDPath = async (path: string) => { - await withLedgerKeyring(async (keyring: LedgerKeyring) => { + await withLedgerKeyring(async ({ keyring }) => { if (isValidPath(path)) { keyring.setHdPath(path); } else { @@ -149,16 +145,14 @@ export const setHDPath = async (path: string) => { * @returns The HD Path */ export const getHDPath = async (): Promise => - await withLedgerKeyring(async (keyring: LedgerKeyring) => keyring.hdPath); + await withLedgerKeyring(async ({ keyring }) => keyring.hdPath); /** * Get Ledger Accounts * @returns The Ledger Accounts */ export const getLedgerAccounts = async (): Promise => - await withLedgerKeyring(async (keyring: LedgerKeyring) => - keyring.getAccounts(), - ); + await withLedgerKeyring(async ({ keyring }) => keyring.getAccounts()); /** * Unlock Ledger Accounts by page @@ -169,7 +163,7 @@ export const getLedgerAccountsByOperation = async ( operation: number, ): Promise<{ balance: string; address: string; index: number }[]> => { try { - const accounts = await withLedgerKeyring(async (keyring: LedgerKeyring) => { + const accounts = await withLedgerKeyring(async ({ keyring }) => { switch (operation) { case PAGINATION_OPERATIONS.GET_PREVIOUS_PAGE: return await keyring.getPreviousPage(); @@ -202,7 +196,7 @@ export const ledgerSignTypedMessage = async ( }, version: SignTypedDataVersion, ): Promise => { - await withLedgerKeyring(async (_keyring: LedgerKeyring) => { + await withLedgerKeyring(async () => { // This is just to trigger the keyring to get created if it doesn't exist already }); const keyringController = Engine.context.KeyringController; @@ -240,7 +234,7 @@ export const checkAccountNameExists = async (accountName: string) => { export const unlockLedgerWalletAccount = async (index: number) => { const accountsController = Engine.context.AccountsController; const { unlockAccount, name } = await withLedgerKeyring( - async (keyring: LedgerKeyring) => { + async ({ keyring }) => { const existingAccounts = await keyring.getAccounts(); const keyringName = keyringTypeToName(ExtendedKeyringTypes.ledger); const accountName = `${keyringName} ${existingAccounts.length + 1}`; From 665dc8827c6f85c6399920a2e7d50c9e69c61803 Mon Sep 17 00:00:00 2001 From: Michele Esposito Date: Thu, 13 Mar 2025 15:25:04 +0100 Subject: [PATCH 059/124] update lock --- yarn.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index 5f32df4710d1..b0b897b855e5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4554,10 +4554,10 @@ "@metamask/utils" "^11.1.0" nanoid "^3.3.8" -"@metamask/assets-controllers@^51.0.2": - version "51.0.2" - resolved "https://registry.yarnpkg.com/@metamask/assets-controllers/-/assets-controllers-51.0.2.tgz#fc8ed8981fce62e4c7327393faffc2017874644d" - integrity sha512-dEJJw6qc5HMEiMJUTOrKhTYTMS8tuf3+gofZ9PHGSvPibUx/DivC1L3bouiMNgi2xzzH9ktfmkFyCN1lzFk22w== +"@metamask/assets-controllers@^52.0.0": + version "52.0.0" + resolved "https://registry.yarnpkg.com/@metamask/assets-controllers/-/assets-controllers-52.0.0.tgz#f85c67ae10ec423a787188072f6ef4203cec8500" + integrity sha512-wN8n6nm2lucFs/k9wfGCcq4l5/cqFmpMuxcayHsBabA4/4C9b+fqF2N4+GqVoYvtHt4OGsrtVh65Hjj4wS8neQ== dependencies: "@ethereumjs/util" "^8.1.0" "@ethersproject/abi" "^5.7.0" From ea274b4b06d8874bb7fde47e5fa4bf294abc8252 Mon Sep 17 00:00:00 2001 From: Michele Esposito Date: Thu, 13 Mar 2025 15:33:19 +0100 Subject: [PATCH 060/124] remove unused ts directive --- .../transaction-controller/transaction-controller-init.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/app/core/Engine/controllers/transaction-controller/transaction-controller-init.test.ts b/app/core/Engine/controllers/transaction-controller/transaction-controller-init.test.ts index f94b8613c284..2ffa6f37fea8 100644 --- a/app/core/Engine/controllers/transaction-controller/transaction-controller-init.test.ts +++ b/app/core/Engine/controllers/transaction-controller/transaction-controller-init.test.ts @@ -46,7 +46,6 @@ function buildInitRequestMock( initRequestProperties: Record = {}, ): jest.Mocked< ControllerInitRequest< - // @ts-expect-error TODO: Resolve mismatch between base-controller versions. TransactionControllerMessenger, TransactionControllerInitMessenger > From 597933a4a8e099ab1c2a5145c61bbdee92ea8f89 Mon Sep 17 00:00:00 2001 From: Michele Esposito Date: Thu, 13 Mar 2025 15:44:50 +0100 Subject: [PATCH 061/124] fix unit import additional account test --- app/util/importAdditionalAccounts.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/util/importAdditionalAccounts.test.ts b/app/util/importAdditionalAccounts.test.ts index 1fc7fcaf3ff4..e1c78e8d17b1 100644 --- a/app/util/importAdditionalAccounts.test.ts +++ b/app/util/importAdditionalAccounts.test.ts @@ -13,7 +13,9 @@ const mockEthQuery = { jest.mock('../core/Engine', () => ({ context: { KeyringController: { - withKeyring: jest.fn((_keyring, callback) => callback(mockKeyring)), + withKeyring: jest.fn((_keyring, callback) => + callback({ keyring: mockKeyring, metadata: { id: '1234', name: '' } }), + ), }, }, })); From b75221c0acd54b8e26552e122af02dcef20c9301 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Thu, 13 Mar 2025 13:00:15 -0500 Subject: [PATCH 062/124] upgrade assets-controllers --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cc4621d76260..a95e883edeb8 100644 --- a/package.json +++ b/package.json @@ -154,7 +154,7 @@ "@metamask/accounts-controller": "^25.0.0", "@metamask/address-book-controller": "^6.0.3", "@metamask/approval-controller": "^7.1.0", - "@metamask/assets-controllers": "^52.0.0", + "@metamask/assets-controllers": "^53.1.0", "@metamask/base-controller": "^8.0.0", "@metamask/bitcoin-wallet-snap": "^0.9.0", "@metamask/bridge-controller": "^4.0.0", From c134e722e829c3eb8527c4955876ee47b31862a8 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Thu, 13 Mar 2025 13:00:50 -0500 Subject: [PATCH 063/124] update yarn lock --- yarn.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/yarn.lock b/yarn.lock index fd5e8b2cdd9b..b2d71c8b7aa3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4554,12 +4554,12 @@ "@metamask/utils" "^11.1.0" nanoid "^3.3.8" -"@metamask/assets-controllers@^52.0.0": - version "52.0.0" - resolved "https://registry.yarnpkg.com/@metamask/assets-controllers/-/assets-controllers-52.0.0.tgz#f85c67ae10ec423a787188072f6ef4203cec8500" - integrity sha512-wN8n6nm2lucFs/k9wfGCcq4l5/cqFmpMuxcayHsBabA4/4C9b+fqF2N4+GqVoYvtHt4OGsrtVh65Hjj4wS8neQ== +"@metamask/assets-controllers@^53.1.0": + version "53.1.0" + resolved "https://registry.yarnpkg.com/@metamask/assets-controllers/-/assets-controllers-53.1.0.tgz#29209cb62c70c424349fb1205d7fceb83e5cf139" + integrity sha512-u67s1j5rp99wQX7yO9EVv5TbHJ+EZHlKbDFg6l3vh87Ame7u8hH+HQXlQLrE0tZjj562FCxVyITjPGeYTAR2KQ== dependencies: - "@ethereumjs/util" "^8.1.0" + "@ethereumjs/util" "^9.1.0" "@ethersproject/abi" "^5.7.0" "@ethersproject/address" "^5.7.0" "@ethersproject/bignumber" "^5.7.0" @@ -4568,7 +4568,7 @@ "@metamask/abi-utils" "^2.0.3" "@metamask/base-controller" "^8.0.0" "@metamask/contract-metadata" "^2.4.0" - "@metamask/controller-utils" "^11.5.0" + "@metamask/controller-utils" "^11.6.0" "@metamask/eth-query" "^4.0.0" "@metamask/keyring-api" "^17.2.0" "@metamask/metamask-eth-abis" "^3.1.1" From 59fbf41d3c833d85867204bbb271f75739824cc8 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Thu, 13 Mar 2025 15:30:28 -0500 Subject: [PATCH 064/124] controllers --- package.json | 6 ++-- yarn.lock | 86 ++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 76 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index a95e883edeb8..7872204d6e09 100644 --- a/package.json +++ b/package.json @@ -151,7 +151,7 @@ "@keystonehq/metamask-airgapped-keyring": "^0.13.1", "@keystonehq/ur-decoder": "^0.12.2", "@ledgerhq/react-native-hw-transport-ble": "^6.33.2", - "@metamask/accounts-controller": "^25.0.0", + "@metamask/accounts-controller": "^26.0.0", "@metamask/address-book-controller": "^6.0.3", "@metamask/approval-controller": "^7.1.0", "@metamask/assets-controllers": "^53.1.0", @@ -179,7 +179,7 @@ "@metamask/json-rpc-middleware-stream": "^8.0.6", "@metamask/key-tree": "^10.0.2", "@metamask/keyring-api": "^17.2.1", - "@metamask/keyring-controller": "^20.0.0", + "@metamask/keyring-controller": "^21.0.0", "@metamask/keyring-internal-api": "^4.0.3", "@metamask/keyring-snap-client": "^4.0.1", "@metamask/logging-controller": "^6.0.4", @@ -193,7 +193,7 @@ "@metamask/phishing-controller": "^12.0.3", "@metamask/post-message-stream": "^9.0.0", "@metamask/ppom-validator": "0.36.0", - "@metamask/preferences-controller": "^16.0.0", + "@metamask/preferences-controller": "^17.0.0", "@metamask/profile-sync-controller": "^9.0.0", "@metamask/react-native-actionsheet": "2.4.2", "@metamask/react-native-button": "^3.0.0", diff --git a/yarn.lock b/yarn.lock index b2d71c8b7aa3..300478590847 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4515,17 +4515,17 @@ "@metamask/superstruct" "^3.1.0" "@metamask/utils" "^11.0.1" -"@metamask/accounts-controller@^25.0.0": - version "25.0.0" - resolved "https://registry.yarnpkg.com/@metamask/accounts-controller/-/accounts-controller-25.0.0.tgz#721ca64e66c59b1820d60432c144036ba19fb582" - integrity sha512-y5OKNaJrQM52neBebuxTJBPWp3E04RGeAySm+coBFPa+XU2sQH1lme2fvWZ49/hs5OuGJ8FvMkTKW7bi8ZH6jg== +"@metamask/accounts-controller@^26.0.0": + version "26.0.0" + resolved "https://registry.yarnpkg.com/@metamask/accounts-controller/-/accounts-controller-26.0.0.tgz#fcb139cfebd275ed8976dd5d9983efb160f65c94" + integrity sha512-xnO+AYhUZzNfXm91ngPOVNOBGzi7fDVsh7KgeGvmntu2Cn7W1nW1c0WIY7yG0YQbBRlDLNcHFP+Z32d1O11Oyg== dependencies: - "@ethereumjs/util" "^8.1.0" + "@ethereumjs/util" "^9.1.0" "@metamask/base-controller" "^8.0.0" - "@metamask/eth-snap-keyring" "^11.1.0" + "@metamask/eth-snap-keyring" "^12.0.0" "@metamask/keyring-api" "^17.2.0" - "@metamask/keyring-internal-api" "^5.0.0" - "@metamask/keyring-utils" "^2.3.1" + "@metamask/keyring-internal-api" "^6.0.0" + "@metamask/keyring-utils" "^3.0.0" "@metamask/network-controller" "^22.2.1" "@metamask/snaps-sdk" "^6.17.1" "@metamask/snaps-utils" "^8.10.0" @@ -4735,6 +4735,18 @@ "@metamask/utils" "^11.1.0" ethereum-cryptography "^2.1.2" +"@metamask/eth-hd-keyring@^12.0.0": + version "12.1.0" + resolved "https://registry.yarnpkg.com/@metamask/eth-hd-keyring/-/eth-hd-keyring-12.1.0.tgz#f70e421cc7fbecc039b3f6347172444dadb10168" + integrity sha512-jL7l1oPasTClknOuT0LleqyV9WbuXdM7fU5POramgjyh+sl++PyRh1W6cZzskNEXSpPH08pVy1Xh/LU7rNBPqQ== + dependencies: + "@ethereumjs/util" "^9.1.0" + "@metamask/eth-sig-util" "^8.2.0" + "@metamask/key-tree" "^10.0.2" + "@metamask/scure-bip39" "^2.1.1" + "@metamask/utils" "^11.1.0" + ethereum-cryptography "^2.1.2" + "@metamask/eth-hd-keyring@^9.0.0": version "9.0.0" resolved "https://registry.yarnpkg.com/@metamask/eth-hd-keyring/-/eth-hd-keyring-9.0.0.tgz#cba58308af5cfb9b7b1b958eeea380ec9c798e64" @@ -4829,6 +4841,17 @@ ethereum-cryptography "^2.1.2" tweetnacl "^1.0.3" +"@metamask/eth-simple-keyring@^10.0.0": + version "10.0.0" + resolved "https://registry.yarnpkg.com/@metamask/eth-simple-keyring/-/eth-simple-keyring-10.0.0.tgz#e9f9d01e8ef7afff9023e27888a69fc9d946ab34" + integrity sha512-zdPIhKsQDfLG5m8yFDHpaXe8nPYc3sBeTiVWw1BPxwGNIW7Y8ToeHMMIlOYFZzUP98ezrtrBjiB86tQdRZ377w== + dependencies: + "@ethereumjs/util" "^9.1.0" + "@metamask/eth-sig-util" "^8.2.0" + "@metamask/utils" "^11.1.0" + ethereum-cryptography "^2.1.2" + randombytes "^2.1.0" + "@metamask/eth-simple-keyring@^9.0.0": version "9.0.0" resolved "https://registry.yarnpkg.com/@metamask/eth-simple-keyring/-/eth-simple-keyring-9.0.0.tgz#8f13b97bf527a3656cf57ac0acc5ecd4c4716701" @@ -4857,6 +4880,23 @@ "@types/uuid" "^9.0.8" uuid "^9.0.1" +"@metamask/eth-snap-keyring@^12.0.0": + version "12.0.0" + resolved "https://registry.yarnpkg.com/@metamask/eth-snap-keyring/-/eth-snap-keyring-12.0.0.tgz#f4f5c246abbaefc9e2aeacb1943dbf0c8473ffb5" + integrity sha512-WfCkdHDvFZUFDq/Hhf6XpQeWmCdzdN0GlPUNm82y+Qs/RJdya92+hCkO3efWVzuefSIFmQxPRbQQb0uvvbYrxA== + dependencies: + "@ethereumjs/tx" "^5.4.0" + "@metamask/base-controller" "^7.1.1" + "@metamask/eth-sig-util" "^8.2.0" + "@metamask/keyring-api" "^17.2.1" + "@metamask/keyring-internal-api" "^5.0.0" + "@metamask/keyring-internal-snap-client" "^4.0.1" + "@metamask/keyring-utils" "^3.0.0" + "@metamask/superstruct" "^3.1.0" + "@metamask/utils" "^11.1.0" + "@types/uuid" "^9.0.8" + uuid "^9.0.1" + "@metamask/etherscan-link@^2.0.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@metamask/etherscan-link/-/etherscan-link-2.1.0.tgz#c0be8e68445b7b83cf85bcc03a56cdf8e256c973" @@ -5028,6 +5068,26 @@ immer "^9.0.6" ulid "^2.3.0" +"@metamask/keyring-controller@^21.0.0": + version "21.0.0" + resolved "https://registry.yarnpkg.com/@metamask/keyring-controller/-/keyring-controller-21.0.0.tgz#eeace05df6c07baece0bea621b3c26269e1c2ca0" + integrity sha512-RXANJ9qaxn76XLjGvjQOECErUl6LqBd7jeK9JmFKg8zsVQHLWK9fbfO7GA3PdKs9c8DkBCZXx+fFE1J9LWh6sA== + dependencies: + "@ethereumjs/util" "^9.1.0" + "@keystonehq/metamask-airgapped-keyring" "^0.14.1" + "@metamask/base-controller" "^8.0.0" + "@metamask/browser-passworder" "^4.3.0" + "@metamask/eth-hd-keyring" "^12.0.0" + "@metamask/eth-sig-util" "^8.2.0" + "@metamask/eth-simple-keyring" "^10.0.0" + "@metamask/keyring-api" "^17.2.0" + "@metamask/keyring-internal-api" "^6.0.0" + "@metamask/utils" "^11.2.0" + async-mutex "^0.5.0" + ethereumjs-wallet "^1.0.1" + immer "^9.0.6" + ulid "^2.3.0" + "@metamask/keyring-internal-api@^4.0.3": version "4.0.3" resolved "https://registry.yarnpkg.com/@metamask/keyring-internal-api/-/keyring-internal-api-4.0.3.tgz#af4a1e078603336e803a8d231b596685328fb0d9" @@ -5307,13 +5367,13 @@ eslint-plugin-n "^16.6.2" json-rpc-random-id "^1.0.1" -"@metamask/preferences-controller@^16.0.0": - version "16.0.0" - resolved "https://registry.yarnpkg.com/@metamask/preferences-controller/-/preferences-controller-16.0.0.tgz#be7f3602181ff7a6d4c032fc8fd38b403836fc35" - integrity sha512-XzuhUMOY+JplgDVSJOV6LCgBB+/hl/lf67XMWI7jSEGLpAD632CwlEbMsfObzAqDmqsxkK3HFfocbsqrbSbCCg== +"@metamask/preferences-controller@^17.0.0": + version "17.0.0" + resolved "https://registry.yarnpkg.com/@metamask/preferences-controller/-/preferences-controller-17.0.0.tgz#2d6a3b7772f3a126e1795ca614303e8a92eb2a54" + integrity sha512-1DH/KAJYH9T+JJe7VUqaPjhfNDfZdAMMqjXf+rx4iAq0oFWA1d0iC9SVpYlIktrya7IjqYhjpSSrN0MheN3t4A== dependencies: "@metamask/base-controller" "^8.0.0" - "@metamask/controller-utils" "^11.5.0" + "@metamask/controller-utils" "^11.6.0" "@metamask/profile-sync-controller@^9.0.0": version "9.0.0" From 1d8f040c149bdda086f6ae9584a2405872cdfc99 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Thu, 13 Mar 2025 16:00:28 -0500 Subject: [PATCH 065/124] add data controller events and actions --- app/core/Engine/types.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/core/Engine/types.ts b/app/core/Engine/types.ts index 324bf6b0ca0e..7542ba32707e 100644 --- a/app/core/Engine/types.ts +++ b/app/core/Engine/types.ts @@ -44,6 +44,8 @@ import { RatesControllerActions, TokenSearchDiscoveryDataController, TokenSearchDiscoveryDataControllerState, + TokenSearchDiscoveryDataControllerActions, + TokenSearchDiscoveryDataControllerEvents, MultichainAssetsController, MultichainAssetsControllerState, MultichainAssetsControllerEvents, @@ -340,7 +342,7 @@ type GlobalActions = | AssetsContractControllerActions | RemoteFeatureFlagControllerActions | TokenSearchDiscoveryControllerActions - | TokenSearchDiscoveryControllerActions + | TokenSearchDiscoveryDataControllerActions | MultichainNetworkControllerActions | BridgeControllerActions | BridgeStatusControllerActions @@ -389,7 +391,7 @@ type GlobalEvents = | AssetsContractControllerEvents | RemoteFeatureFlagControllerEvents | TokenSearchDiscoveryControllerEvents - | TokenSearchDiscoveryControllerEvents + | TokenSearchDiscoveryDataControllerEvents | SnapKeyringEvents | MultichainNetworkControllerEvents | BridgeControllerEvents From 03962ccbda28cc05e99a3f20ed67c90f55536d27 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Thu, 13 Mar 2025 16:28:52 -0500 Subject: [PATCH 066/124] remove unused import --- app/components/UI/AssetOverview/AssetOverview.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/UI/AssetOverview/AssetOverview.tsx b/app/components/UI/AssetOverview/AssetOverview.tsx index 2591e7695465..bf142ba9ebe3 100644 --- a/app/components/UI/AssetOverview/AssetOverview.tsx +++ b/app/components/UI/AssetOverview/AssetOverview.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useEffect } from 'react'; import { TouchableOpacity, View } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import { useDispatch, useSelector } from 'react-redux'; From 44f9565bfb54e821acac8a9db1d2e877517ecf9e Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Thu, 13 Mar 2025 16:30:04 -0500 Subject: [PATCH 067/124] remove unused import --- app/core/Engine/Engine.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/core/Engine/Engine.ts b/app/core/Engine/Engine.ts index c34af887390d..9a69431d93a5 100644 --- a/app/core/Engine/Engine.ts +++ b/app/core/Engine/Engine.ts @@ -12,9 +12,6 @@ import { TokenRatesController, TokensController, CodefiTokenPricesServiceV2, - ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) - MultichainBalancesControllerMessenger, - ///: END:ONLY_INCLUDE_IF TokenSearchDiscoveryDataController, } from '@metamask/assets-controllers'; import { AccountsController } from '@metamask/accounts-controller'; From 8ddb1aa270bd32efddc2038e424b77a5898ce43e Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Thu, 13 Mar 2025 16:42:59 -0500 Subject: [PATCH 068/124] Add data controller to initial background state --- app/util/test/initial-background-state.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/util/test/initial-background-state.json b/app/util/test/initial-background-state.json index 10abc81d9f7b..3dec9125279b 100644 --- a/app/util/test/initial-background-state.json +++ b/app/util/test/initial-background-state.json @@ -180,6 +180,10 @@ "lastSearchTimestamp": null, "recentSearches": [] }, + "TokenSearchDiscoveryDataController": { + "swapsTokenAddressesByChainId": {}, + "tokenDisplayData": [] + }, "TransactionController": { "lastFetchedBlockNumbers": {}, "methodData": {}, From ae815c27e3c9cb24c8707514baafdf1a7ebbbafe Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Thu, 13 Mar 2025 17:23:53 -0500 Subject: [PATCH 069/124] fix UrlAutocomplete tests --- .../UI/UrlAutocomplete/index.test.tsx | 73 +++++++++++++------ 1 file changed, 51 insertions(+), 22 deletions(-) diff --git a/app/components/UI/UrlAutocomplete/index.test.tsx b/app/components/UI/UrlAutocomplete/index.test.tsx index ee2e4a7dccbd..48a0d42516c4 100644 --- a/app/components/UI/UrlAutocomplete/index.test.tsx +++ b/app/components/UI/UrlAutocomplete/index.test.tsx @@ -22,7 +22,38 @@ const defaultState = { type RenderWithProviderParams = Parameters; -jest.mock('../../hooks/useTokenSearchDiscovery/useTokenSearchDiscovery'); +jest.mock('../../hooks/useTokenSearchDiscovery/useTokenSearchDiscovery', () => { + const searchTokens = jest.fn(); + const results: TokenSearchResponseItem[] = []; + const reset = jest.fn(); + return jest.fn(() => ({ + results, + isLoading: false, + reset, + searchTokens, + })); +}); + +const mockUseTSDReturnValue = ({ + results, + isLoading, + reset, + searchTokens, +}: { + results: TokenSearchResponseItem[]; + isLoading: boolean; + reset: () => void; + searchTokens: () => void; +}) => { + // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires + const useTSD = require('../../hooks/useTokenSearchDiscovery/useTokenSearchDiscovery'); + useTSD.mockReturnValue({ + results, + isLoading, + reset, + searchTokens, + }); +}; const Stack = createStackNavigator(); const render = (...args: RenderWithProviderParams) => { @@ -36,6 +67,14 @@ const render = (...args: RenderWithProviderParams) => { ); }; +jest.mock('../../../core/Engine', () => ({ + context: { + TokenSearchDiscoveryDataController: { + fetchSwapsTokens: jest.fn(), + } + }, +})); + describe('UrlAutocomplete', () => { beforeAll(() => { jest.useFakeTimers(); @@ -85,17 +124,12 @@ describe('UrlAutocomplete', () => { }); it('should show a loading indicator when searching tokens', async () => { - // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires - const useTSD = require('../../hooks/useTokenSearchDiscovery/useTokenSearchDiscovery'); - const searchTokens = jest.fn(); - const results: TokenSearchResponseItem[] = []; - const reset = jest.fn(); - useTSD.default.mockImplementationOnce(() => ({ - results, + mockUseTSDReturnValue({ + results: [], isLoading: true, - reset, - searchTokens, - })); + reset: jest.fn(), + searchTokens: jest.fn(), + }); const ref = React.createRef(); render(, {state: defaultState}); @@ -108,10 +142,8 @@ describe('UrlAutocomplete', () => { }); it('should display token search results', async () => { - // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires - const useTSD = require('../../hooks/useTokenSearchDiscovery/useTokenSearchDiscovery'); - const searchTokens = jest.fn(); - const results: TokenSearchResponseItem[] = [ + mockUseTSDReturnValue({ + results: [ { tokenAddress: '0x123', chainId: '0x1', @@ -122,14 +154,11 @@ describe('UrlAutocomplete', () => { oneDay: 1, }, } - ]; - const reset = jest.fn(); - useTSD.default.mockImplementationOnce(() => ({ - results, + ], isLoading: false, - reset, - searchTokens, - })); + reset: jest.fn(), + searchTokens: jest.fn(), + }); const ref = React.createRef(); render(, {state: defaultState}); From acfdc076183f809b80b718178c33a84083ba38a5 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Thu, 13 Mar 2025 17:48:56 -0500 Subject: [PATCH 070/124] fix useTokenSearchDiscovery test update to logic means no search if less than 2 characters typed --- .../useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts index 65d26e33072b..1982692ea884 100644 --- a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts +++ b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts @@ -70,7 +70,9 @@ describe('useTokenSearchDiscovery', () => { const { result } = renderHook(() => useTokenSearchDiscovery()); await act(async () => { - result.current.searchTokens({}); + result.current.searchTokens({ + query: 'doge', + }); jest.advanceTimersByTime(300); await Promise.resolve(); }); From f9b7f55ef842f881a7dea31a51ca8e4f7238b168 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Thu, 13 Mar 2025 18:38:06 -0500 Subject: [PATCH 071/124] update logs test snapshot --- app/util/logs/__snapshots__/index.test.ts.snap | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/util/logs/__snapshots__/index.test.ts.snap b/app/util/logs/__snapshots__/index.test.ts.snap index 458bbad329b4..fd1b82add88c 100644 --- a/app/util/logs/__snapshots__/index.test.ts.snap +++ b/app/util/logs/__snapshots__/index.test.ts.snap @@ -443,6 +443,10 @@ exports[`logs :: generateStateLogs generates a valid json export 1`] = ` "lastSearchTimestamp": null, "recentSearches": [], }, + "TokenSearchDiscoveryDataController": { + "swapsTokenAddressesByChainId": {}, + "tokenDisplayData": [], + }, "TransactionController": { "lastFetchedBlockNumbers": {}, "methodData": {}, From d9738126e49c31fe6fe6751cd1d3d7d29f8dc800 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Fri, 14 Mar 2025 11:56:52 -0500 Subject: [PATCH 072/124] update NetworkModal snapshot --- .../UI/NetworkModal/__snapshots__/index.test.tsx.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/UI/NetworkModal/__snapshots__/index.test.tsx.snap b/app/components/UI/NetworkModal/__snapshots__/index.test.tsx.snap index 7689b85fe2ff..5d58457c27be 100644 --- a/app/components/UI/NetworkModal/__snapshots__/index.test.tsx.snap +++ b/app/components/UI/NetworkModal/__snapshots__/index.test.tsx.snap @@ -488,7 +488,7 @@ exports[`NetworkDetails renders correctly 1`] = ` accessibilityRole="button" accessible={true} activeOpacity={1} - onPress={[MockFunction]} + onPress={[Function]} onPressIn={[Function]} onPressOut={[Function]} style={ From ea77cca25fe899bd25c224dd8513d024d02bf83c Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Fri, 14 Mar 2025 14:33:38 -0500 Subject: [PATCH 073/124] Only show network modal if we're on a search & discovery asset --- app/components/UI/AssetOverview/AssetOverview.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/UI/AssetOverview/AssetOverview.tsx b/app/components/UI/AssetOverview/AssetOverview.tsx index bf142ba9ebe3..e57bb09f1052 100644 --- a/app/components/UI/AssetOverview/AssetOverview.tsx +++ b/app/components/UI/AssetOverview/AssetOverview.tsx @@ -265,7 +265,7 @@ const AssetOverview: React.FC = ({ asset.chainId as Hex, ); - if (!networkConfiguration) { + if (!networkConfiguration && isAssetFromSearch(asset)) { const network = PopularList.find((popularNetwork) => popularNetwork.chainId === asset.chainId); if (network) { try { From 7930c4a00515c2f3c0eb7923827dba83b54c5754 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Mon, 17 Mar 2025 15:09:12 -0500 Subject: [PATCH 074/124] add useAddNetwork test --- app/components/hooks/useAddNetwork.test.ts | 30 ++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 app/components/hooks/useAddNetwork.test.ts diff --git a/app/components/hooks/useAddNetwork.test.ts b/app/components/hooks/useAddNetwork.test.ts new file mode 100644 index 000000000000..d2bbab459c99 --- /dev/null +++ b/app/components/hooks/useAddNetwork.test.ts @@ -0,0 +1,30 @@ +import { renderHook, act } from '@testing-library/react-hooks'; +import { useAddNetwork } from './useAddNetwork'; + +jest.mock('@react-navigation/native', () => { + const reactNavigation = jest.requireActual('@react-navigation/native'); + return { + ...reactNavigation, + useNavigation: jest.fn(), + }; +}); + +describe('useAddNetwork', () => { + it('should pop up the network modal when adding a network', () => { + const { result } = renderHook(() => useAddNetwork()); + act(() => { + result.current.addPopularNetwork({ + chainId: '0x1', + nickname: 'Ethereum', + rpcUrl: 'https://mainnet.infura.io/v3/YOUR_INFURA_API_KEY', + ticker: 'ETH', + rpcPrefs: { + blockExplorerUrl: 'https://etherscan.io', + imageUrl: 'https://etherscan.io/images/svg/brands/eth.svg', + imageSource: 'https://etherscan.io/images/svg/brands/eth.svg', + }, + }); + }); + expect(result.current.networkModal).toBeDefined(); + }); +}); From 6325e09fb12b7069044e5ab41afbc00759f5b830 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Mon, 17 Mar 2025 15:33:04 -0500 Subject: [PATCH 075/124] increase coverage of useTokenSearchDiscovery tests --- .../useTokenSearchDiscovery.test.ts | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts index 1982692ea884..926357920992 100644 --- a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts +++ b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts @@ -61,6 +61,43 @@ describe('useTokenSearchDiscovery', () => { ).toHaveBeenCalledWith(mockSearchParams); }); + it('does not search when less than two characters are queried', async () => { + const { result } = renderHook(() => useTokenSearchDiscovery()); + await act(async () => { + result.current.searchTokens({ query: 'a' }); + jest.advanceTimersByTime(300); + await Promise.resolve(); + }); + + expect(Engine.context.TokenSearchDiscoveryController.searchTokens).not.toHaveBeenCalled(); + }); + + it('resets the state when reset() is called', async () => { + const mockSearchResult = [{ name: 'DAI', address: '0x123' }]; + + ( + Engine.context.TokenSearchDiscoveryController.searchTokens as jest.Mock + ).mockResolvedValueOnce(mockSearchResult); + + const { result } = renderHook(() => useTokenSearchDiscovery()); + + await act(async () => { + result.current.searchTokens({ + query: 'doge', + }); + jest.advanceTimersByTime(300); + await Promise.resolve(); + }); + + expect(result.current.results).toEqual(mockSearchResult); + + await act(async () => { + result.current.reset(); + }); + + expect(result.current.results).toEqual([]); + }); + it('returns error and empty results if search failed', async () => { const mockError = new Error('Search failed'); ( From ca1d4dd796a05d656b88ed5908ae299b7c92be33 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Mon, 17 Mar 2025 19:59:14 -0500 Subject: [PATCH 076/124] add AssetOverview tests for searched assets --- .../UI/AssetOverview/AssetOverview.test.tsx | 68 +++++++++++++++++++ .../UI/AssetOverview/AssetOverview.tsx | 20 +++--- 2 files changed, 79 insertions(+), 9 deletions(-) diff --git a/app/components/UI/AssetOverview/AssetOverview.test.tsx b/app/components/UI/AssetOverview/AssetOverview.test.tsx index 854eaacbb258..9b691d18508b 100644 --- a/app/components/UI/AssetOverview/AssetOverview.test.tsx +++ b/app/components/UI/AssetOverview/AssetOverview.test.tsx @@ -22,6 +22,7 @@ import * as transactions from '../../../util/transactions'; import { mockNetworkState } from '../../../util/test/network'; import Engine from '../../../core/Engine'; import Routes from '../../../constants/navigation/Routes'; +import { swapsUtils } from '@metamask/swaps-controller'; const MOCK_CHAIN_ID = '0x1'; @@ -114,6 +115,14 @@ jest.mock('../../../core/Engine', () => ({ }, })); +const mockAddPopularNetwork = jest.fn().mockImplementation(() => Promise.resolve()); +jest.mock('../../../components/hooks/useAddNetwork', () => ({ + useAddNetwork: jest.fn().mockImplementation(() => ({ + addPopularNetwork: mockAddPopularNetwork, + networkModal: null, + })) +})); + const asset = { balance: '400', balanceFiat: '1500', @@ -129,6 +138,11 @@ const asset = { image: '', }; +const assetFromSearch = { + ...asset, + isFromSearch: true, +}; + describe('AssetOverview', () => { beforeEach(() => { jest.spyOn(networks, 'isPortfolioViewEnabled').mockReturnValue(false); @@ -368,6 +382,60 @@ describe('AssetOverview', () => { expect(container).toMatchSnapshot(); }); + it('should swap into the asset when coming from search', async () => { + const { getByTestId } = renderWithProvider( + , + { state: mockInitialState }, + ); + + const swapButton = getByTestId('token-swap-button'); + fireEvent.press(swapButton); + + // Wait for all promises to resolve + await Promise.resolve(); + + expect(navigate).toHaveBeenCalledWith('Swaps', { + screen: 'SwapsAmountView', + params: { + sourceToken: swapsUtils.NATIVE_SWAPS_TOKEN_ADDRESS, + destinationToken: assetFromSearch.address, + sourcePage: 'MainView', + chainId: assetFromSearch.chainId, + } + }); + }); + + it('should prompt to add the network if coming from search and on a different chain', async () => { + jest.spyOn(networks, 'isPortfolioViewEnabled').mockReturnValue(true); + (Engine.context.NetworkController.getNetworkConfigurationByChainId as jest.Mock).mockReturnValueOnce(null); + const differentChainAssetFromSearch = { + ...assetFromSearch, + chainId: '0xa', + }; + const { getByTestId } = renderWithProvider( + , + { state: mockInitialState }, + ); + + const swapButton = getByTestId('token-swap-button'); + fireEvent.press(swapButton); + + // Wait for all promises to resolve + await Promise.resolve(); + + expect(mockAddPopularNetwork).toHaveBeenCalled(); + }); + describe('Portfolio view network switching', () => { beforeEach(() => { jest.spyOn(networks, 'isPortfolioViewEnabled').mockReturnValue(true); diff --git a/app/components/UI/AssetOverview/AssetOverview.tsx b/app/components/UI/AssetOverview/AssetOverview.tsx index e57bb09f1052..fa50482270df 100644 --- a/app/components/UI/AssetOverview/AssetOverview.tsx +++ b/app/components/UI/AssetOverview/AssetOverview.tsx @@ -176,19 +176,21 @@ const AssetOverview: React.FC = ({ navigation.navigate('Swaps', { screen: 'SwapsAmountView', params: { - sourceToken: swapsUtils.NATIVE_SWAPS_TOKEN_ADDRESS, - destinationToken: asset.address, - sourcePage: 'MainView', - chainId: asset.chainId, - }}); + sourceToken: swapsUtils.NATIVE_SWAPS_TOKEN_ADDRESS, + destinationToken: asset.address, + sourcePage: 'MainView', + chainId: asset.chainId, + } + }); } else { navigation.navigate('Swaps', { screen: 'SwapsAmountView', params: { - sourceToken: asset.address ?? swapsUtils.NATIVE_SWAPS_TOKEN_ADDRESS, - sourcePage: 'MainView', - chainId: asset.chainId, - }}); + sourceToken: asset.address ?? swapsUtils.NATIVE_SWAPS_TOKEN_ADDRESS, + sourcePage: 'MainView', + chainId: asset.chainId, + }, + }); } }, [navigation, asset]); From 47dd32b44c4ef1cfa08b2fc78aebbb30d51c5d08 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Mon, 17 Mar 2025 20:17:24 -0500 Subject: [PATCH 077/124] add tsdd controller initial state --- app/components/UI/AssetOverview/AssetOverview.test.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/components/UI/AssetOverview/AssetOverview.test.tsx b/app/components/UI/AssetOverview/AssetOverview.test.tsx index 9b691d18508b..201a8932401b 100644 --- a/app/components/UI/AssetOverview/AssetOverview.test.tsx +++ b/app/components/UI/AssetOverview/AssetOverview.test.tsx @@ -63,6 +63,9 @@ const mockInitialState = { }, }, }, + TokenSearchDiscoveryDataController: { + tokenDisplayData: [], + }, }, settings: { primaryCurrency: 'ETH', From e932c08355edeb0aa812ca99b1346d41734298e6 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Mon, 17 Mar 2025 20:42:30 -0500 Subject: [PATCH 078/124] add AssetLoader tests --- .../Views/AssetLoader/index.test.tsx | 69 +++++++++++++++++++ app/components/Views/AssetLoader/index.tsx | 2 +- 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 app/components/Views/AssetLoader/index.test.tsx diff --git a/app/components/Views/AssetLoader/index.test.tsx b/app/components/Views/AssetLoader/index.test.tsx new file mode 100644 index 000000000000..f432998484a6 --- /dev/null +++ b/app/components/Views/AssetLoader/index.test.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { AssetLoader } from '.'; +import renderWithProvider from '../../../util/test/renderWithProvider'; +import { Hex } from '@metamask/utils'; +import { screen } from '@testing-library/react-native'; + +const mockDispatch = jest.fn(); +jest.mock('@react-navigation/native', () => { + const actual = jest.requireActual('@react-navigation/native'); + return { + ...actual, + useNavigation: jest.fn().mockImplementation(() => ({ + dispatch: mockDispatch, + })), + }; +}); + +jest.mock('../../../core/Engine', () => ({ + context: { + TokenSearchDiscoveryDataController: { + fetchTokenDisplayData: jest.fn(), + }, + }, +})); + +const mockSelectTokenDisplayData = jest.fn(); +jest.mock('../../../selectors/tokenSearchDiscoveryDataController', () => ({ + selectTokenDisplayData: () => mockSelectTokenDisplayData(), +})); + +const mockState = { + engine: { + backgroundState: { + TokenSearchDiscoveryDataController: { + tokenDisplayData: [], + }, + }, + }, +}; + +const assetData = { + address: '0x123', + chainId: '0x1' as Hex, +}; + +describe('AssetLoader', () => { + it('should show activity indicator while loading token', () => { + mockSelectTokenDisplayData.mockReturnValue(undefined); + renderWithProvider(, { state: mockState }); + expect(screen.getByTestId('asset-loader-spinner')).toBeDefined(); + }); + + it('should show a message saying the token was not found if the token is not found', () => { + mockSelectTokenDisplayData.mockReturnValue({ + found: false, + }); + renderWithProvider(, { state: mockState }); + expect(screen.getByText('Token not found')).toBeDefined(); + }); + + it('should navigate to the asset view if the token is found', () => { + mockSelectTokenDisplayData.mockReturnValue({ + found: true, + token: {} + }); + renderWithProvider(, { state: mockState }); + expect(mockDispatch).toHaveBeenCalled(); + }); +}); diff --git a/app/components/Views/AssetLoader/index.tsx b/app/components/Views/AssetLoader/index.tsx index fb66a457753d..21437d7771cb 100644 --- a/app/components/Views/AssetLoader/index.tsx +++ b/app/components/Views/AssetLoader/index.tsx @@ -41,7 +41,7 @@ export const AssetLoader: React.FC = ({ route: { params: { add if (!tokenResult) { return ( - + ); } From dc9580ed7e7399a14076810914707b99d595124e Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Mon, 17 Mar 2025 21:17:19 -0500 Subject: [PATCH 079/124] add token display data --- .../UI/AssetOverview/AssetOverview.test.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/components/UI/AssetOverview/AssetOverview.test.tsx b/app/components/UI/AssetOverview/AssetOverview.test.tsx index 201a8932401b..c742de7860b6 100644 --- a/app/components/UI/AssetOverview/AssetOverview.test.tsx +++ b/app/components/UI/AssetOverview/AssetOverview.test.tsx @@ -64,7 +64,18 @@ const mockInitialState = { }, }, TokenSearchDiscoveryDataController: { - tokenDisplayData: [], + tokenDisplayData: [ + { + chainId: MOCK_CHAIN_ID, + address: '0x123', + currency: 'ETH', + found: true, + logo: 'https://upload.wikimedia.org/wikipedia/commons/0/05/Ethereum_logo_2014.svg', + name: 'Ethereum', + symbol: 'ETH', + price: {} + } + ], }, }, settings: { From 6c741ecc80f739766a347c772cc3156aeed4b289 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Tue, 18 Mar 2025 14:15:57 -0500 Subject: [PATCH 080/124] Add UrlAutocomplete tests --- app/components/UI/UrlAutocomplete/Result.tsx | 1 + .../UI/UrlAutocomplete/index.test.tsx | 113 +++++++++++++++++- 2 files changed, 110 insertions(+), 4 deletions(-) diff --git a/app/components/UI/UrlAutocomplete/Result.tsx b/app/components/UI/UrlAutocomplete/Result.tsx index 40cfd052f5ff..44744f33c464 100644 --- a/app/components/UI/UrlAutocomplete/Result.tsx +++ b/app/components/UI/UrlAutocomplete/Result.tsx @@ -112,6 +112,7 @@ export const Result: React.FC = memo(({ result, onPress, onSwapPres iconName={IconName.SwapHorizontal} onPress={() => onSwapPress(result)} disabled={!swapsEnabled} + testID="autocomplete-result-swap-button" /> ) } diff --git a/app/components/UI/UrlAutocomplete/index.test.tsx b/app/components/UI/UrlAutocomplete/index.test.tsx index 48a0d42516c4..d840af6d1033 100644 --- a/app/components/UI/UrlAutocomplete/index.test.tsx +++ b/app/components/UI/UrlAutocomplete/index.test.tsx @@ -9,13 +9,18 @@ import { createStackNavigator } from '@react-navigation/stack'; import { TokenSearchResponseItem } from '@metamask/token-search-discovery-controller'; const defaultState = { - browser: { history: [] }, + browser: { history: [ + {url: 'https://www.google.com', name: 'Google'}, + ] }, bookmarks: [{url: 'https://www.bookmark.com', name: 'MyBookmark'}], engine: { backgroundState: { PreferencesController: { isIpfsGatewayEnabled: false, }, + CurrencyRateController: { + currentCurrency: 'USD', + } }, } }; @@ -71,10 +76,32 @@ jest.mock('../../../core/Engine', () => ({ context: { TokenSearchDiscoveryDataController: { fetchSwapsTokens: jest.fn(), - } + }, + CurrencyRateController: { + updateExchangeRate: jest.fn(), + }, }, })); +const mockNavigate = jest.fn(); +jest.mock('@react-navigation/native', () => { + const navigation = jest.requireActual('@react-navigation/native'); + return { + ...navigation, + useNavigation: jest.fn().mockImplementation(() => ({ + navigate: mockNavigate, + })), + }; +}); + +jest.mock('../../../selectors/tokenSearchDiscoveryDataController', () => { + const actual = jest.requireActual('../../../selectors/tokenSearchDiscoveryDataController'); + return { + ...actual, + selectSupportedSwapTokenAddresses: jest.fn().mockImplementation(() => ['0x123', '0x456']), + }; +}); + describe('UrlAutocomplete', () => { beforeAll(() => { jest.useFakeTimers(); @@ -108,6 +135,43 @@ describe('UrlAutocomplete', () => { expect(await screen.findByText('MyBookmark', {includeHiddenElements: true})).toBeDefined(); }); + it('should show sites from recents/history', async () => { + const ref = React.createRef(); + render(, {state: defaultState}); + + act(() => { + ref.current?.search('Goog'); + jest.runAllTimers(); + }); + + expect(await screen.findByText('Google', {includeHiddenElements: true})).toBeDefined(); + }); + + it('should show history and bookmarks when searching for an empty string', async () => { + const ref = React.createRef(); + render(, {state: defaultState}); + + act(() => { + ref.current?.search(''); + jest.runAllTimers(); + }); + + expect(await screen.findByText('Google', {includeHiddenElements: true})).toBeDefined(); + expect(await screen.findByText('MyBookmark', {includeHiddenElements: true})).toBeDefined(); + }); + + it('should not show Recents and Favorites when nothing is found', async () => { + const ref = React.createRef(); + render(, {state: defaultState}); + + act(() => { + ref.current?.search('nothing'); + jest.runAllTimers(); + }); + expect(await screen.queryByText('Recents', {includeHiddenElements: true})).toBeNull(); + expect(await screen.queryByText('Favorites', {includeHiddenElements: true})).toBeNull(); + }); + it('should delete a bookmark when pressing the trash icon', async () => { const ref = React.createRef(); const { store } = render(, {state: defaultState}); @@ -153,7 +217,17 @@ describe('UrlAutocomplete', () => { usdPricePercentChange: { oneDay: 1, }, - } + }, + { + tokenAddress: '0x456', + chainId: '0x1', + name: 'Dog Wif Hat', + symbol: 'WIF', + usdPrice: 1, + usdPricePercentChange: { + oneDay: 1, + }, + }, ], isLoading: false, reset: jest.fn(), @@ -163,10 +237,41 @@ describe('UrlAutocomplete', () => { render(, {state: defaultState}); act(() => { - ref.current?.search('doge'); + ref.current?.search('dog'); jest.runAllTimers(); }); expect(await screen.findByText('Dogecoin', {includeHiddenElements: true})).toBeDefined(); }); + + it('should swap a token when the swap button is pressed', async () => { + mockUseTSDReturnValue({ + results: [ + { + tokenAddress: '0x123', + chainId: '0x1', + name: 'Dogecoin', + symbol: 'DOGE', + usdPrice: 1, + usdPricePercentChange: { + oneDay: 1, + }, + }, + ], + isLoading: false, + reset: jest.fn(), + searchTokens: jest.fn(), + }); + const ref = React.createRef(); + render(, {state: defaultState}); + + act(() => { + ref.current?.search('dog'); + jest.runAllTimers(); + }); + + const swapButton = await screen.findByTestId('autocomplete-result-swap-button', {includeHiddenElements: true}); + fireEvent.press(swapButton); + expect(mockNavigate).toHaveBeenCalled(); + }); }); From 595d59c9fc4bd5f91eacf72f51ea890719fbecba Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Tue, 18 Mar 2025 14:34:18 -0500 Subject: [PATCH 081/124] address code quality issues --- .../UI/AssetOverview/TokenDetails/TokenDetails.tsx | 11 ++++++++--- app/components/UI/UrlAutocomplete/index.tsx | 8 ++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/app/components/UI/AssetOverview/TokenDetails/TokenDetails.tsx b/app/components/UI/AssetOverview/TokenDetails/TokenDetails.tsx index 1af138ed8f3f..eca5d3097113 100644 --- a/app/components/UI/AssetOverview/TokenDetails/TokenDetails.tsx +++ b/app/components/UI/AssetOverview/TokenDetails/TokenDetails.tsx @@ -69,9 +69,14 @@ const TokenDetails: React.FC = ({ asset }) => { const tokenContractAddress = safeToChecksumAddress(asset.address); const tokenList = useSelector(selectTokenList); - const conversionRate = isAssetFromSearch(asset) ? 1 : isPortfolioViewEnabled() - ? conversionRateBySymbol - : conversionRateLegacy; + let conversionRate; + if (isAssetFromSearch(asset)) { + conversionRate = 1; + } else if (isPortfolioViewEnabled()) { + conversionRate = conversionRateBySymbol; + } else { + conversionRate = conversionRateLegacy; + } const tokenExchangeRates = isPortfolioViewEnabled() ? tokenExchangeRatesByChainId : tokenExchangeRatesLegacy; diff --git a/app/components/UI/UrlAutocomplete/index.tsx b/app/components/UI/UrlAutocomplete/index.tsx index 1e2452e6e962..51fd23a2f1c7 100644 --- a/app/components/UI/UrlAutocomplete/index.tsx +++ b/app/components/UI/UrlAutocomplete/index.tsx @@ -237,7 +237,7 @@ const UrlAutocomplete = forwardRef< Engine.context; let networkConfiguration = NetworkController.getNetworkConfigurationByChainId( - result.chainId as Hex, + result.chainId, ); if (!networkConfiguration) { @@ -310,7 +310,11 @@ const UrlAutocomplete = forwardRef< `${item.type}-${item.type === 'tokens' ? `${item.chainId}-${item.address}` : item.url}`} + keyExtractor={(item) => + item.type === 'tokens' + ? `${item.type}-${item.chainId}-${item.address}` + : `${item.type}-${item.url}` + } renderSectionHeader={renderSectionHeader} renderItem={renderItem} keyboardShouldPersistTaps="handled" From 364b9463a29823855ee31fd6107773e24f8e846b Mon Sep 17 00:00:00 2001 From: sahar-fehri Date: Thu, 20 Mar 2025 02:30:02 +0100 Subject: [PATCH 082/124] chore: bump assets-controllers to v53 --- package.json | 2 +- ...tch => @metamask+assets-controllers+53.1.1.patch} | 0 yarn.lock | 12 ++++++------ 3 files changed, 7 insertions(+), 7 deletions(-) rename patches/{@metamask+assets-controllers+51.0.2.patch => @metamask+assets-controllers+53.1.1.patch} (100%) diff --git a/package.json b/package.json index 97e3be5e278e..9377224a8089 100644 --- a/package.json +++ b/package.json @@ -154,7 +154,7 @@ "@metamask/accounts-controller": "^24.1.0", "@metamask/address-book-controller": "^6.0.3", "@metamask/approval-controller": "^7.1.0", - "@metamask/assets-controllers": "^51.0.2", + "@metamask/assets-controllers": "^53.1.1", "@metamask/base-controller": "^8.0.0", "@metamask/bitcoin-wallet-snap": "^0.9.0", "@metamask/bridge-controller": "^3.0.0", diff --git a/patches/@metamask+assets-controllers+51.0.2.patch b/patches/@metamask+assets-controllers+53.1.1.patch similarity index 100% rename from patches/@metamask+assets-controllers+51.0.2.patch rename to patches/@metamask+assets-controllers+53.1.1.patch diff --git a/yarn.lock b/yarn.lock index 2b2854e4bffe..a7609c4dada0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4554,12 +4554,12 @@ "@metamask/utils" "^11.1.0" nanoid "^3.3.8" -"@metamask/assets-controllers@^51.0.2": - version "51.0.2" - resolved "https://registry.yarnpkg.com/@metamask/assets-controllers/-/assets-controllers-51.0.2.tgz#fc8ed8981fce62e4c7327393faffc2017874644d" - integrity sha512-dEJJw6qc5HMEiMJUTOrKhTYTMS8tuf3+gofZ9PHGSvPibUx/DivC1L3bouiMNgi2xzzH9ktfmkFyCN1lzFk22w== +"@metamask/assets-controllers@^53.1.1": + version "53.1.1" + resolved "https://registry.yarnpkg.com/@metamask/assets-controllers/-/assets-controllers-53.1.1.tgz#87e00c362247e0c0a4f3df8bd124d6e5bb0bb16c" + integrity sha512-AmB5/MoqCr+JJXZm0M1lmO1AUrkmSRWaVqGauBszQeXcd3aVmycZHMWEIFKNgo0bf5kX5xIkFg2CVLaMlsv+Xg== dependencies: - "@ethereumjs/util" "^8.1.0" + "@ethereumjs/util" "^9.1.0" "@ethersproject/abi" "^5.7.0" "@ethersproject/address" "^5.7.0" "@ethersproject/bignumber" "^5.7.0" @@ -4568,7 +4568,7 @@ "@metamask/abi-utils" "^2.0.3" "@metamask/base-controller" "^8.0.0" "@metamask/contract-metadata" "^2.4.0" - "@metamask/controller-utils" "^11.5.0" + "@metamask/controller-utils" "^11.6.0" "@metamask/eth-query" "^4.0.0" "@metamask/keyring-api" "^17.2.0" "@metamask/metamask-eth-abis" "^3.1.1" From 290cb10bc11965ab19a34e43381a85b074fd7038 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Mon, 24 Mar 2025 12:14:21 -0500 Subject: [PATCH 083/124] Only return search token results that are swappable --- app/components/UI/UrlAutocomplete/Result.tsx | 4 +- app/components/UI/UrlAutocomplete/index.tsx | 38 +++++++----------- app/components/Views/Asset/index.js | 4 +- .../useTokenSearchDiscovery.ts | 39 ++++++++++++++++--- .../tokenSearchDiscoveryDataController.ts | 8 +++- 5 files changed, 60 insertions(+), 33 deletions(-) diff --git a/app/components/UI/UrlAutocomplete/Result.tsx b/app/components/UI/UrlAutocomplete/Result.tsx index 44744f33c464..e2aa03ce6519 100644 --- a/app/components/UI/UrlAutocomplete/Result.tsx +++ b/app/components/UI/UrlAutocomplete/Result.tsx @@ -14,7 +14,7 @@ import BadgeWrapper from '../../../component-library/components/Badges/BadgeWrap import Badge, { BadgeVariant } from '../../../component-library/components/Badges/Badge'; import { NetworkBadgeSource } from '../AssetOverview/Balance/Balance'; import AvatarToken from '../../../component-library/components/Avatars/Avatar/variants/AvatarToken'; -import { selectSupportedSwapTokenAddresses } from '../../../selectors/tokenSearchDiscoveryDataController'; +import { selectSupportedSwapTokenAddressesForChainId } from '../../../selectors/tokenSearchDiscoveryDataController'; import { RootState } from '../../../reducers'; import { isSwapsAllowed } from '../Swaps/utils'; import AppConstants from '../../../core/AppConstants'; @@ -40,7 +40,7 @@ export const Result: React.FC = memo(({ result, onPress, onSwapPres dispatch(removeBookmark(result)); }, [dispatch, result]); - const swapTokenAddresses = useSelector((state: RootState) => selectSupportedSwapTokenAddresses(state, result.type === 'tokens' ? result.chainId : '0x')); + const swapTokenAddresses = useSelector((state: RootState) => selectSupportedSwapTokenAddressesForChainId(state, result.type === 'tokens' ? result.chainId : '0x')); const swapsEnabled = result.type === 'tokens' && isSwapsAllowed(result.chainId) && swapTokenAddresses?.includes(result.address) && AppConstants.SWAPS.ACTIVE; diff --git a/app/components/UI/UrlAutocomplete/index.tsx b/app/components/UI/UrlAutocomplete/index.tsx index 51fd23a2f1c7..060c82406575 100644 --- a/app/components/UI/UrlAutocomplete/index.tsx +++ b/app/components/UI/UrlAutocomplete/index.tsx @@ -58,14 +58,21 @@ const UrlAutocomplete = forwardRef< const [fuseResults, setFuseResults] = useState([]); const {searchTokens, results: tokenSearchResults, reset: resetTokenSearch, isLoading: isTokenSearchLoading} = useTokenSearchDiscovery(); const usdConversionRate = useSelector(selectUsdConversionRate); - const tokenResults: TokenSearchResult[] = useMemo(() => tokenSearchResults.map(({tokenAddress, usdPricePercentChange, usdPrice, chainId, ...rest}) => ({ - ...rest, - type: 'tokens', - address: tokenAddress, - chainId: chainId as Hex, - price: usdConversionRate ? usdPrice / usdConversionRate : -1, - percentChange: usdPricePercentChange.oneDay, - })), [tokenSearchResults, usdConversionRate]); + const tokenResults: TokenSearchResult[] = useMemo( + () => ( + tokenSearchResults + .map(({tokenAddress, usdPricePercentChange, usdPrice, chainId, ...rest}) => ({ + ...rest, + type: 'tokens' as const, + address: tokenAddress, + chainId: chainId as Hex, + price: usdConversionRate ? usdPrice / usdConversionRate : -1, + percentChange: usdPricePercentChange.oneDay, + })) + .slice(0, TOKEN_SEARCH_LIMIT) + ), + [tokenSearchResults, usdConversionRate] + ); const hasResults = fuseResults.length > 0 || tokenResults.length > 0; @@ -139,7 +146,6 @@ const UrlAutocomplete = forwardRef< searchTokens({ query: text, - limit: TOKEN_SEARCH_LIMIT.toString(), }); }, [browserHistory, bookmarks, resetTokenSearch, searchTokens]); @@ -198,20 +204,6 @@ const UrlAutocomplete = forwardRef< } }, [browserHistory, bookmarks, search]); - useEffect(() => { - const chainIds = tokenResults.reduce((acc, result) => { - if (!acc.includes(result.chainId)) { - acc.push(result.chainId); - } - return acc; - }, [] as Hex[]); - - for (const chainId of chainIds) { - Engine.context.TokenSearchDiscoveryDataController.fetchSwapsTokens(chainId); - } - - }, [tokenResults]); - const selectedChainId = useSelector(selectEvmChainId); const { addPopularNetwork, networkModal } = useAddNetwork(); diff --git a/app/components/Views/Asset/index.js b/app/components/Views/Asset/index.js index 8700369bbdfb..1f33e5f47368 100644 --- a/app/components/Views/Asset/index.js +++ b/app/components/Views/Asset/index.js @@ -66,7 +66,7 @@ import { toChecksumHexAddress } from '@metamask/controller-utils'; import { selectSwapsTransactions } from '../../../selectors/transactionController'; import Logger from '../../../util/Logger'; import { TOKEN_CATEGORY_HASH } from '../../UI/TransactionElement/utils'; -import { isAssetFromSearch, selectSupportedSwapTokenAddresses } from '../../../selectors/tokenSearchDiscoveryDataController'; +import { isAssetFromSearch, selectSupportedSwapTokenAddressesForChainId } from '../../../selectors/tokenSearchDiscoveryDataController'; import { isNonEvmChainId } from '../../../core/Multichain/utils'; const createStyles = (colors) => @@ -579,7 +579,7 @@ const mapStateToProps = (state, { route }) => ({ swapsTokens: isPortfolioViewEnabled() ? swapsTokensMultiChainObjectSelector(state) : swapsTokensObjectSelector(state), - searchDiscoverySwapsTokens: selectSupportedSwapTokenAddresses(state, route.params.chainId), + searchDiscoverySwapsTokens: selectSupportedSwapTokenAddressesForChainId(state, route.params.chainId), swapsTransactions: selectSwapsTransactions(state), conversionRate: selectConversionRate(state), currentCurrency: selectCurrentCurrency(state), diff --git a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts index aa27d5e52a24..c5a5c417d198 100644 --- a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts +++ b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts @@ -1,4 +1,4 @@ -import { useState, useRef, useMemo, useCallback } from 'react'; +import { useState, useRef, useMemo, useCallback, useEffect } from 'react'; import { useSelector } from 'react-redux'; import { debounce } from 'lodash'; import Engine from '../../../core/Engine'; @@ -7,6 +7,10 @@ import { TokenSearchResponseItem, TokenSearchParams, } from '@metamask/token-search-discovery-controller'; +import { Hex } from '@metamask/utils'; +import { selectSupportedSwapTokenAddressesByChainId } from '../../../selectors/tokenSearchDiscoveryDataController'; +import { RootState } from '../../../reducers'; +import { allowedChainIds } from '../../UI/Swaps/utils'; const SEARCH_DEBOUNCE_DELAY = 50; const MINIMUM_QUERY_LENGTH = 2; @@ -18,6 +22,30 @@ export const useTokenSearchDiscovery = () => { const [results, setResults] = useState([]); const latestRequestId = useRef(0); + useEffect(() => { + const chainIds = results.reduce((acc, result) => { + if (!acc.includes(result.chainId as Hex)) { + acc.push(result.chainId as Hex); + } + return acc; + }, [] as Hex[]); + + for (const chainId of chainIds) { + Engine.context.TokenSearchDiscoveryDataController.fetchSwapsTokens(chainId); + } + + }, [results]); + + const swapsTokenAddresses = useSelector((state: RootState) => selectSupportedSwapTokenAddressesByChainId(state)); + + const filteredResults = useMemo(() => { + return results.filter((result) => { + const chainId = result.chainId as Hex; + const tokenAddresses = swapsTokenAddresses[chainId]; + return tokenAddresses?.addresses.includes(result.tokenAddress); + }); + }, [results, swapsTokenAddresses]); + const searchTokens = useMemo( () => debounce(async (params: TokenSearchParams) => { @@ -33,9 +61,10 @@ export const useTokenSearchDiscovery = () => { try { const { TokenSearchDiscoveryController } = Engine.context; - const result = await TokenSearchDiscoveryController.searchTokens( - params, - ); + const result = await TokenSearchDiscoveryController.searchTokens({ + ...params, + limit: '100' + }); if (requestId === latestRequestId.current) { setResults(result); } @@ -63,7 +92,7 @@ export const useTokenSearchDiscovery = () => { recentSearches, isLoading, error, - results, + results: filteredResults, reset, }; }; diff --git a/app/selectors/tokenSearchDiscoveryDataController.ts b/app/selectors/tokenSearchDiscoveryDataController.ts index bdf300afddb7..7f24eb0d4cfa 100644 --- a/app/selectors/tokenSearchDiscoveryDataController.ts +++ b/app/selectors/tokenSearchDiscoveryDataController.ts @@ -16,7 +16,13 @@ export const selectTokenDisplayData = createSelector( (state, currentCurrency, chainId, address) => state?.tokenDisplayData.find(d => d.chainId === chainId && d.address === address && d.currency === currentCurrency) ); -export const selectSupportedSwapTokenAddresses = createSelector( +export const selectSupportedSwapTokenAddressesByChainId = createSelector( + selectTokenSearchDiscoveryDataControllerState, + (_state: RootState) => _state, + (state) => state?.swapsTokenAddressesByChainId, +); + +export const selectSupportedSwapTokenAddressesForChainId = createSelector( selectTokenSearchDiscoveryDataControllerState, (_state: RootState, chainId: Hex) => chainId, (state, chainId) => state?.swapsTokenAddressesByChainId[chainId]?.addresses, From 94703be1e781e952fa9f05e24b8ac9057eb3d307 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Mon, 24 Mar 2025 14:56:45 -0500 Subject: [PATCH 084/124] fix token search discovery tests --- .../useTokenSearchDiscovery.test.ts | 73 ++++++++++++++----- .../useTokenSearchDiscovery.ts | 11 +-- .../tokenSearchDiscoveryDataController.ts | 1 - 3 files changed, 62 insertions(+), 23 deletions(-) diff --git a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts index 926357920992..cd1b2a6a0beb 100644 --- a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts +++ b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts @@ -1,21 +1,38 @@ -import { act, renderHook } from '@testing-library/react-hooks'; +import { act } from '@testing-library/react-hooks'; import Engine from '../../../core/Engine'; -import useTokenSearchDiscovery from './useTokenSearchDiscovery'; -import { TokenSearchParams } from '@metamask/token-search-discovery-controller'; - -jest.mock('react-redux', () => ({ - ...jest.requireActual('react-redux'), - useSelector: jest.fn(), -})); +import useTokenSearchDiscovery, { MAX_RESULTS, SearchDiscoveryParams } from './useTokenSearchDiscovery'; +import { renderHookWithProvider } from '../../../util/test/renderWithProvider'; jest.mock('../../../core/Engine', () => ({ context: { TokenSearchDiscoveryController: { searchTokens: jest.fn(), }, + TokenSearchDiscoveryDataController: { + fetchSwapsTokens: jest.fn(), + }, }, })); +const mockInitialState = { + engine: { + backgroundState: { + TokenSearchDiscoveryController: { + recentSearches: [], + lastSearchTimestamp: 0, + }, + TokenSearchDiscoveryDataController: { + swapsTokenAddressesByChainId: { + '0x1': { + addresses: ['0x123'], + }, + }, + tokenDisplayData: [], + }, + }, + }, +}; + describe('useTokenSearchDiscovery', () => { beforeEach(() => { jest.clearAllMocks(); @@ -27,18 +44,22 @@ describe('useTokenSearchDiscovery', () => { }); it('updates states correctly when searching tokens', async () => { - const mockSearchParams: TokenSearchParams = { + const mockSearchParams: SearchDiscoveryParams = { chains: ['0x1'], query: 'DAI', - limit: '10', }; - const mockSearchResult = [{ name: 'DAI', address: '0x123' }]; + const mockSearchResult = [{ name: 'DAI', tokenAddress: '0x123', chainId: '0x1' }]; ( Engine.context.TokenSearchDiscoveryController.searchTokens as jest.Mock ).mockResolvedValueOnce(mockSearchResult); - const { result } = renderHook(() => useTokenSearchDiscovery()); + const { result } = renderHookWithProvider( + () => useTokenSearchDiscovery(), + { + state: mockInitialState, + } + ); // Initial state expect(result.current.isLoading).toBe(false); @@ -58,11 +79,19 @@ describe('useTokenSearchDiscovery', () => { expect(result.current.results).toEqual(mockSearchResult); expect( Engine.context.TokenSearchDiscoveryController.searchTokens, - ).toHaveBeenCalledWith(mockSearchParams); + ).toHaveBeenCalledWith({ + ...mockSearchParams, + limit: MAX_RESULTS, + }); }); it('does not search when less than two characters are queried', async () => { - const { result } = renderHook(() => useTokenSearchDiscovery()); + const { result } = renderHookWithProvider( + () => useTokenSearchDiscovery(), + { + state: mockInitialState, + } + ); await act(async () => { result.current.searchTokens({ query: 'a' }); jest.advanceTimersByTime(300); @@ -73,13 +102,18 @@ describe('useTokenSearchDiscovery', () => { }); it('resets the state when reset() is called', async () => { - const mockSearchResult = [{ name: 'DAI', address: '0x123' }]; + const mockSearchResult = [{ name: 'DAI', tokenAddress: '0x123', chainId: '0x1' }]; ( Engine.context.TokenSearchDiscoveryController.searchTokens as jest.Mock ).mockResolvedValueOnce(mockSearchResult); - const { result } = renderHook(() => useTokenSearchDiscovery()); + const { result } = renderHookWithProvider( + () => useTokenSearchDiscovery(), + { + state: mockInitialState, + } + ); await act(async () => { result.current.searchTokens({ @@ -104,7 +138,12 @@ describe('useTokenSearchDiscovery', () => { Engine.context.TokenSearchDiscoveryController.searchTokens as jest.Mock ).mockRejectedValueOnce(mockError); - const { result } = renderHook(() => useTokenSearchDiscovery()); + const { result } = renderHookWithProvider( + () => useTokenSearchDiscovery(), + { + state: mockInitialState, + } + ); await act(async () => { result.current.searchTokens({ diff --git a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts index c5a5c417d198..37ac43e7a832 100644 --- a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts +++ b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts @@ -9,11 +9,12 @@ import { } from '@metamask/token-search-discovery-controller'; import { Hex } from '@metamask/utils'; import { selectSupportedSwapTokenAddressesByChainId } from '../../../selectors/tokenSearchDiscoveryDataController'; -import { RootState } from '../../../reducers'; -import { allowedChainIds } from '../../UI/Swaps/utils'; const SEARCH_DEBOUNCE_DELAY = 50; const MINIMUM_QUERY_LENGTH = 2; +export const MAX_RESULTS = '100'; + +export type SearchDiscoveryParams = Omit; export const useTokenSearchDiscovery = () => { const recentSearches = useSelector(selectRecentTokenSearches); @@ -36,7 +37,7 @@ export const useTokenSearchDiscovery = () => { }, [results]); - const swapsTokenAddresses = useSelector((state: RootState) => selectSupportedSwapTokenAddressesByChainId(state)); + const swapsTokenAddresses = useSelector(selectSupportedSwapTokenAddressesByChainId); const filteredResults = useMemo(() => { return results.filter((result) => { @@ -48,7 +49,7 @@ export const useTokenSearchDiscovery = () => { const searchTokens = useMemo( () => - debounce(async (params: TokenSearchParams) => { + debounce(async (params: SearchDiscoveryParams) => { setIsLoading(true); setError(null); const requestId = ++latestRequestId.current; @@ -63,7 +64,7 @@ export const useTokenSearchDiscovery = () => { const { TokenSearchDiscoveryController } = Engine.context; const result = await TokenSearchDiscoveryController.searchTokens({ ...params, - limit: '100' + limit: MAX_RESULTS, }); if (requestId === latestRequestId.current) { setResults(result); diff --git a/app/selectors/tokenSearchDiscoveryDataController.ts b/app/selectors/tokenSearchDiscoveryDataController.ts index 7f24eb0d4cfa..d323006f37fb 100644 --- a/app/selectors/tokenSearchDiscoveryDataController.ts +++ b/app/selectors/tokenSearchDiscoveryDataController.ts @@ -18,7 +18,6 @@ export const selectTokenDisplayData = createSelector( export const selectSupportedSwapTokenAddressesByChainId = createSelector( selectTokenSearchDiscoveryDataControllerState, - (_state: RootState) => _state, (state) => state?.swapsTokenAddressesByChainId, ); From f2d0946efbbb42c256e5e51420eae1e8d5b7f9c8 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Tue, 25 Mar 2025 14:06:07 -0500 Subject: [PATCH 085/124] revert changes due to package updates --- .../Views/ConnectQRHardware/index.tsx | 2 +- app/core/Engine/Engine.test.ts | 5 +- app/core/Engine/Engine.ts | 7 +- .../transaction-controller-init.test.ts | 1 + .../transaction-controller-init.ts | 2 + app/core/Engine/types.ts | 1 + app/core/Engine/utils/utils.ts | 1 + app/core/Ledger/Ledger.test.ts | 19 ++-- app/core/Ledger/Ledger.ts | 93 +++++++++---------- app/core/Vault.js | 2 +- app/util/importAdditionalAccounts.test.ts | 4 +- app/util/importAdditionalAccounts.ts | 2 +- package.json | 14 +-- 13 files changed, 73 insertions(+), 80 deletions(-) diff --git a/app/components/Views/ConnectQRHardware/index.tsx b/app/components/Views/ConnectQRHardware/index.tsx index 23ae008b9458..82387c707260 100644 --- a/app/components/Views/ConnectQRHardware/index.tsx +++ b/app/components/Views/ConnectQRHardware/index.tsx @@ -118,7 +118,7 @@ async function initiateQRHardwareConnection( const qrInteractions = await KeyringController.withKeyring( { type: KeyringTypes.qr }, // @ts-expect-error The QR Keyring type is not compatible with our keyring type yet - async ({ keyring }: { keyring: QRKeyring }) => ({ + async (keyring: QRKeyring) => ({ cancelSync: keyring.cancelSync.bind(keyring), submitCryptoAccount: keyring.submitCryptoAccount.bind(keyring), submitCryptoHDKey: keyring.submitCryptoHDKey.bind(keyring), diff --git a/app/core/Engine/Engine.test.ts b/app/core/Engine/Engine.test.ts index 04e565ef961f..2e9292d90472 100644 --- a/app/core/Engine/Engine.test.ts +++ b/app/core/Engine/Engine.test.ts @@ -239,12 +239,11 @@ describe('Engine', () => { jest .spyOn(engine.keyringController, 'getKeyringsByType') - .mockImplementationOnce(() => []) - .mockImplementationOnce(() => [mockSnapKeyring]); + .mockImplementation(() => []); jest .spyOn(engine.keyringController, 'addNewKeyring') - .mockResolvedValue({ id: '1234', name: 'Snap Keyring' }); + .mockImplementation(async () => mockSnapKeyring); const getSnapKeyringSpy = jest .spyOn(engine, 'getSnapKeyring') diff --git a/app/core/Engine/Engine.ts b/app/core/Engine/Engine.ts index ea4ba56383fb..e810e6111cf5 100644 --- a/app/core/Engine/Engine.ts +++ b/app/core/Engine/Engine.ts @@ -289,6 +289,7 @@ export class Engine { }); const preferencesController = new PreferencesController({ + // @ts-expect-error TODO: Resolve mismatch between base-controller versions. messenger: this.controllerMessenger.getRestricted({ name: 'PreferencesController', allowedActions: [], @@ -1333,6 +1334,7 @@ export class Engine { RemoteFeatureFlagController: remoteFeatureFlagController, SelectedNetworkController: selectedNetworkController, SignatureController: new SignatureController({ + // @ts-expect-error TODO: Resolve mismatch between base-controller versions. messenger: this.controllerMessenger.getRestricted({ name: 'SignatureController', allowedActions: [ @@ -1796,14 +1798,11 @@ export class Engine { ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) getSnapKeyring = async () => { - // TODO: Replace `getKeyringsByType` with `withKeyring` let [snapKeyring] = this.keyringController.getKeyringsByType( KeyringTypes.snap, ); if (!snapKeyring) { - await this.keyringController.addNewKeyring(KeyringTypes.snap); - // TODO: Replace `getKeyringsByType` with `withKeyring` - [snapKeyring] = this.keyringController.getKeyringsByType( + snapKeyring = await this.keyringController.addNewKeyring( KeyringTypes.snap, ); } diff --git a/app/core/Engine/controllers/transaction-controller/transaction-controller-init.test.ts b/app/core/Engine/controllers/transaction-controller/transaction-controller-init.test.ts index c4fb236cf7f4..bcb989eab8db 100644 --- a/app/core/Engine/controllers/transaction-controller/transaction-controller-init.test.ts +++ b/app/core/Engine/controllers/transaction-controller/transaction-controller-init.test.ts @@ -56,6 +56,7 @@ function buildInitRequestMock( initRequestProperties: Record = {}, ): jest.Mocked< ControllerInitRequest< + // @ts-expect-error TODO: Resolve mismatch between base-controller versions. TransactionControllerMessenger, TransactionControllerInitMessenger > diff --git a/app/core/Engine/controllers/transaction-controller/transaction-controller-init.ts b/app/core/Engine/controllers/transaction-controller/transaction-controller-init.ts index 260573801be6..2e0547d94961 100644 --- a/app/core/Engine/controllers/transaction-controller/transaction-controller-init.ts +++ b/app/core/Engine/controllers/transaction-controller/transaction-controller-init.ts @@ -37,6 +37,7 @@ import type { TransactionMetrics } from './types'; export const TransactionControllerInit: ControllerInitFunction< TransactionController, + // @ts-expect-error TODO: Resolve mismatch between base-controller versions. TransactionControllerMessenger, TransactionControllerInitMessenger > = (request) => { @@ -170,6 +171,7 @@ function isIncomingTransactionsEnabled( function getControllers( request: ControllerInitRequest< + // @ts-expect-error TODO: Resolve mismatch between base-controller versions. TransactionControllerMessenger, TransactionControllerInitMessenger >, diff --git a/app/core/Engine/types.ts b/app/core/Engine/types.ts index 7542ba32707e..f67f910fe771 100644 --- a/app/core/Engine/types.ts +++ b/app/core/Engine/types.ts @@ -681,6 +681,7 @@ export type ControllerInitFunction< export type ControllerInitFunctionByControllerName = { [Name in ControllersToInitialize]: ControllerInitFunction< ControllerByName[Name], + // @ts-expect-error TODO: Resolve mismatch between base-controller versions. ReturnType<(typeof CONTROLLER_MESSENGERS)[Name]['getMessenger']>, ReturnType<(typeof CONTROLLER_MESSENGERS)[Name]['getInitMessenger']> >; diff --git a/app/core/Engine/utils/utils.ts b/app/core/Engine/utils/utils.ts index dd9ec1b375ea..8065ca0a1cf1 100644 --- a/app/core/Engine/utils/utils.ts +++ b/app/core/Engine/utils/utils.ts @@ -21,6 +21,7 @@ type BaseControllerInitRequest = ControllerInitRequest< type InitFunction = ControllerInitFunction< ControllerByName[Name], + // @ts-expect-error TODO: Resolve mismatch between base-controller versions. ReturnType<(typeof CONTROLLER_MESSENGERS)[Name]['getMessenger']>, ReturnType<(typeof CONTROLLER_MESSENGERS)[Name]['getInitMessenger']> >; diff --git a/app/core/Ledger/Ledger.test.ts b/app/core/Ledger/Ledger.test.ts index e5a0d5bf7e4e..730d09329298 100644 --- a/app/core/Ledger/Ledger.test.ts +++ b/app/core/Ledger/Ledger.test.ts @@ -70,9 +70,7 @@ describe('Ledger core', () => { const mockKeyringController = MockEngine.context.KeyringController; ledgerKeyring = { - addAccounts: jest - .fn() - .mockResolvedValue(['0x49b6FFd1BD9d1c64EEf400a64a1e4bBC33E2CAB2']), + addAccounts: jest.fn().mockResolvedValue(['0x49b6FFd1BD9d1c64EEf400a64a1e4bBC33E2CAB2']), bridge: { getAppNameAndVersion: jest .fn() @@ -133,9 +131,8 @@ describe('Ledger core', () => { }; mockKeyringController.withKeyring.mockImplementation( - (_selector, operation) => - // @ts-expect-error The Ledger keyring is not compatible with our keyring type yet - operation({ keyring: ledgerKeyring, metadata: { id: '1234' } }), + // @ts-expect-error The Ledger keyring is not compatible with our keyring type yet + (_selector, operation) => operation(ledgerKeyring), ); mockKeyringController.signTypedMessage.mockResolvedValue('signature'); }); @@ -280,7 +277,7 @@ describe('Ledger core', () => { // @ts-expect-error: The account metadata type is hard to mock metadata: { name: 'Ledger 1', - }, + } }); it(`calls keyring.setAccountToUnlock and addAccounts`, async () => { @@ -290,20 +287,24 @@ describe('Ledger core', () => { }); it(`throws an error if the account name has already exists`, async () => { + + mockAccountsController.state.internalAccounts.accounts = [ { // @ts-expect-error: The account metadata type is hard to mock metadata: { name: 'Ledger 1', }, - }, + } ]; try { await unlockLedgerWalletAccount(1); } catch (err) { expect(err).toBeInstanceOf(Error); - expect((err as Error).message).toBe(`Account Ledger 1 already exists`); + expect((err as Error).message).toBe( + `Account Ledger 1 already exists` + ); } }); }); diff --git a/app/core/Ledger/Ledger.ts b/app/core/Ledger/Ledger.ts index 5596c35be256..49ec0a513c9a 100644 --- a/app/core/Ledger/Ledger.ts +++ b/app/core/Ledger/Ledger.ts @@ -1,8 +1,5 @@ import type BleTransport from '@ledgerhq/react-native-hw-transport-ble'; -import { - KeyringMetadata, - SignTypedDataVersion, -} from '@metamask/keyring-controller'; +import { SignTypedDataVersion } from '@metamask/keyring-controller'; import ExtendedKeyringTypes from '../../constants/keyringTypes'; import Engine from '../Engine'; import { @@ -30,10 +27,7 @@ import { keyringTypeToName } from '@metamask/accounts-controller'; * @returns The stored Ledger Keyring */ export const withLedgerKeyring = async ( - operation: (selectedKeyring: { - keyring: LedgerKeyring; - metadata: KeyringMetadata; - }) => Promise, + operation: (keyring: LedgerKeyring) => Promise, ): Promise => { const keyringController = Engine.context.KeyringController; return await keyringController.withKeyring( @@ -58,14 +52,16 @@ export const connectLedgerHardware = async ( transport: BleTransport, deviceId: string, ): Promise => { - const appAndVersion = await withLedgerKeyring(async ({ keyring }) => { - keyring.setHdPath(LEDGER_LIVE_PATH); - keyring.setDeviceId(deviceId); - - const bridge = keyring.bridge as LedgerMobileBridge; - await bridge.updateTransportMethod(transport); - return await bridge.getAppNameAndVersion(); - }); + const appAndVersion = await withLedgerKeyring( + async (keyring: LedgerKeyring) => { + keyring.setHdPath(LEDGER_LIVE_PATH); + keyring.setDeviceId(deviceId); + + const bridge = keyring.bridge as LedgerMobileBridge; + await bridge.updateTransportMethod(transport); + return await bridge.getAppNameAndVersion(); + }, + ); return appAndVersion.appName; }; @@ -74,7 +70,7 @@ export const connectLedgerHardware = async ( * Automatically opens the Ethereum app on the Ledger device. */ export const openEthereumAppOnLedger = async (): Promise => { - await withLedgerKeyring(async ({ keyring }) => { + await withLedgerKeyring(async (keyring: LedgerKeyring) => { const bridge = keyring.bridge as LedgerMobileBridge; await bridge.openEthApp(); }); @@ -84,7 +80,7 @@ export const openEthereumAppOnLedger = async (): Promise => { * Automatically closes the current app on the Ledger device. */ export const closeRunningAppOnLedger = async (): Promise => { - await withLedgerKeyring(async ({ keyring }) => { + await withLedgerKeyring(async (keyring: LedgerKeyring) => { const bridge = keyring.bridge as LedgerMobileBridge; await bridge.closeApps(); }); @@ -94,7 +90,7 @@ export const closeRunningAppOnLedger = async (): Promise => { * Forgets the ledger device. */ export const forgetLedger = async (): Promise => { - await withLedgerKeyring(async ({ keyring }) => { + await withLedgerKeyring(async (keyring: LedgerKeyring) => { keyring.forgetDevice(); }); }; @@ -105,7 +101,9 @@ export const forgetLedger = async (): Promise => { * @returns The DeviceId */ export const getDeviceId = async (): Promise => - await withLedgerKeyring(async ({ keyring }) => keyring.getDeviceId()); + await withLedgerKeyring(async (keyring: LedgerKeyring) => + keyring.getDeviceId(), + ); /** * Check if the path is valid @@ -130,7 +128,7 @@ export const isValidPath = (path: string): boolean => { * @param path - The HD Path to set */ export const setHDPath = async (path: string) => { - await withLedgerKeyring(async ({ keyring }) => { + await withLedgerKeyring(async (keyring: LedgerKeyring) => { if (isValidPath(path)) { keyring.setHdPath(path); } else { @@ -145,14 +143,16 @@ export const setHDPath = async (path: string) => { * @returns The HD Path */ export const getHDPath = async (): Promise => - await withLedgerKeyring(async ({ keyring }) => keyring.hdPath); + await withLedgerKeyring(async (keyring: LedgerKeyring) => keyring.hdPath); /** * Get Ledger Accounts * @returns The Ledger Accounts */ export const getLedgerAccounts = async (): Promise => - await withLedgerKeyring(async ({ keyring }) => keyring.getAccounts()); + await withLedgerKeyring(async (keyring: LedgerKeyring) => + keyring.getAccounts(), + ); /** * Unlock Ledger Accounts by page @@ -163,7 +163,7 @@ export const getLedgerAccountsByOperation = async ( operation: number, ): Promise<{ balance: string; address: string; index: number }[]> => { try { - const accounts = await withLedgerKeyring(async ({ keyring }) => { + const accounts = await withLedgerKeyring(async (keyring: LedgerKeyring) => { switch (operation) { case PAGINATION_OPERATIONS.GET_PREVIOUS_PAGE: return await keyring.getPreviousPage(); @@ -196,7 +196,7 @@ export const ledgerSignTypedMessage = async ( }, version: SignTypedDataVersion, ): Promise => { - await withLedgerKeyring(async () => { + await withLedgerKeyring(async (_keyring: LedgerKeyring) => { // This is just to trigger the keyring to get created if it doesn't exist already }); const keyringController = Engine.context.KeyringController; @@ -217,12 +217,8 @@ export const ledgerSignTypedMessage = async ( */ export const checkAccountNameExists = async (accountName: string) => { const accountsController = Engine.context.AccountsController; - const accounts = Object.values( - accountsController.state.internalAccounts.accounts, - ); - const existingAccount = accounts.find( - (account) => account.metadata.name === accountName, - ); + const accounts = Object.values(accountsController.state.internalAccounts.accounts); + const existingAccount = accounts.find((account) => account.metadata.name === accountName); return !!existingAccount; }; @@ -233,30 +229,25 @@ export const checkAccountNameExists = async (accountName: string) => { */ export const unlockLedgerWalletAccount = async (index: number) => { const accountsController = Engine.context.AccountsController; - const { unlockAccount, name } = await withLedgerKeyring( - async ({ keyring }) => { - const existingAccounts = await keyring.getAccounts(); - const keyringName = keyringTypeToName(ExtendedKeyringTypes.ledger); - const accountName = `${keyringName} ${existingAccounts.length + 1}`; + const { unlockAccount, name} = await withLedgerKeyring(async (keyring: LedgerKeyring) => { + const existingAccounts = await keyring.getAccounts(); + const keyringName = keyringTypeToName(ExtendedKeyringTypes.ledger); + const accountName = `${keyringName} ${existingAccounts.length + 1}`; - if (await checkAccountNameExists(accountName)) { - throw new Error( - strings('ledger.account_name_existed', { accountName }), - ); - } + if(await checkAccountNameExists(accountName)) { + throw new Error(strings('ledger.account_name_existed', { accountName })); + } - keyring.setAccountToUnlock(index); - const accounts = await keyring.addAccounts(1); - return { - unlockAccount: accounts[accounts.length - 1], - name: accountName, - }; - }, - ); + keyring.setAccountToUnlock(index); + const accounts = await keyring.addAccounts(1); + return { unlockAccount: accounts[accounts.length - 1], name: accountName }; + }); - const account = accountsController.getAccountByAddress(unlockAccount); + const account = + accountsController.getAccountByAddress(unlockAccount); - if (account && name !== account.metadata.name) { + if(account && name !== account.metadata.name) { accountsController.setAccountName(account.id, name); } }; + diff --git a/app/core/Vault.js b/app/core/Vault.js index 15195daee46e..351a14c59050 100644 --- a/app/core/Vault.js +++ b/app/core/Vault.js @@ -164,7 +164,7 @@ function hasKeyringType(state, type) { */ async function getSerializedKeyring(type) { const { KeyringController } = Engine.context; - return await KeyringController.withKeyring({ type }, ({ keyring }) => + return await KeyringController.withKeyring({ type }, (keyring) => keyring.serialize(), ); } diff --git a/app/util/importAdditionalAccounts.test.ts b/app/util/importAdditionalAccounts.test.ts index e1c78e8d17b1..1fc7fcaf3ff4 100644 --- a/app/util/importAdditionalAccounts.test.ts +++ b/app/util/importAdditionalAccounts.test.ts @@ -13,9 +13,7 @@ const mockEthQuery = { jest.mock('../core/Engine', () => ({ context: { KeyringController: { - withKeyring: jest.fn((_keyring, callback) => - callback({ keyring: mockKeyring, metadata: { id: '1234', name: '' } }), - ), + withKeyring: jest.fn((_keyring, callback) => callback(mockKeyring)), }, }, })); diff --git a/app/util/importAdditionalAccounts.ts b/app/util/importAdditionalAccounts.ts index 4f53b4e12060..ce6d612f68be 100644 --- a/app/util/importAdditionalAccounts.ts +++ b/app/util/importAdditionalAccounts.ts @@ -37,7 +37,7 @@ export default async () => { await KeyringController.withKeyring( { type: ExtendedKeyringTypes.hd }, - async ({ keyring: primaryKeyring }) => { + async (primaryKeyring) => { for (let i = 0; i < MAX; i++) { const [newAccount] = await primaryKeyring.addAccounts(1); diff --git a/package.json b/package.json index 79ff9bdc89df..6a31f9cab0bb 100644 --- a/package.json +++ b/package.json @@ -151,10 +151,10 @@ "@keystonehq/metamask-airgapped-keyring": "^0.13.1", "@keystonehq/ur-decoder": "^0.12.2", "@ledgerhq/react-native-hw-transport-ble": "^6.33.2", - "@metamask/accounts-controller": "^26.0.0", + "@metamask/accounts-controller": "^24.1.0", "@metamask/address-book-controller": "^6.0.3", "@metamask/approval-controller": "^7.1.0", - "@metamask/assets-controllers": "^53.1.0", + "@metamask/assets-controllers": "^51.0.2", "@metamask/base-controller": "^8.0.0", "@metamask/bitcoin-wallet-snap": "^0.9.0", "@metamask/bridge-controller": "^7.0.0", @@ -179,13 +179,13 @@ "@metamask/json-rpc-middleware-stream": "^8.0.6", "@metamask/key-tree": "^10.1.1", "@metamask/keyring-api": "^17.2.1", - "@metamask/keyring-controller": "^21.0.0", + "@metamask/keyring-controller": "^19.2.2", "@metamask/keyring-internal-api": "^4.0.3", "@metamask/keyring-snap-client": "^4.0.1", "@metamask/logging-controller": "^6.0.4", "@metamask/message-signing-snap": "^0.3.3", "@metamask/metamask-eth-abis": "3.1.1", - "@metamask/multichain-network-controller": "^0.2.0", + "@metamask/multichain-network-controller": "^0.1.1", "@metamask/multichain-transactions-controller": "^0.7.2", "@metamask/network-controller": "^22.1.0", "@metamask/notification-services-controller": "^2.0.0", @@ -193,7 +193,7 @@ "@metamask/phishing-controller": "^12.0.3", "@metamask/post-message-stream": "^9.0.0", "@metamask/ppom-validator": "0.36.0", - "@metamask/preferences-controller": "^17.0.0", + "@metamask/preferences-controller": "^15.0.1", "@metamask/profile-sync-controller": "^10.1.0", "@metamask/react-native-actionsheet": "2.4.2", "@metamask/react-native-button": "^3.0.0", @@ -205,7 +205,7 @@ "@metamask/scure-bip39": "^2.1.0", "@metamask/sdk-communication-layer": "0.29.0-wallet", "@metamask/selected-network-controller": "^21.0.0", - "@metamask/signature-controller": "^24.0.0", + "@metamask/signature-controller": "^23.1.0", "@metamask/slip44": "^4.1.0", "@metamask/smart-transactions-controller": "^16.1.0", "@metamask/snaps-controllers": "^11.0.0", @@ -218,7 +218,7 @@ "@metamask/swappable-obj-proxy": "^2.1.0", "@metamask/swaps-controller": "^12.1.0", "@metamask/token-search-discovery-controller": "^2.1.0", - "@metamask/transaction-controller": "47.0.0", + "@metamask/transaction-controller": "45.0.0", "@metamask/utils": "^11.2.0", "@ngraveio/bc-ur": "^1.1.6", "@noble/hashes": "^1.7.1", From de1b3274f397ff0045c258807991fa5ef83e9e28 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Tue, 25 Mar 2025 14:23:12 -0500 Subject: [PATCH 086/124] remove unused ts expect error --- app/core/Engine/Engine.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/core/Engine/Engine.ts b/app/core/Engine/Engine.ts index e810e6111cf5..18c06659d502 100644 --- a/app/core/Engine/Engine.ts +++ b/app/core/Engine/Engine.ts @@ -289,7 +289,6 @@ export class Engine { }); const preferencesController = new PreferencesController({ - // @ts-expect-error TODO: Resolve mismatch between base-controller versions. messenger: this.controllerMessenger.getRestricted({ name: 'PreferencesController', allowedActions: [], @@ -507,7 +506,6 @@ export class Engine { allowedEvents: [], }), state: initialKeyringState || initialState.KeyringController, - // @ts-expect-error To Do: Update the type of QRHardwareKeyring to Keyring keyringBuilders: additionalKeyrings, cacheEncryptionKey: true, }); From b531a2d2dff209a753ac50a1320c0da59938de0a Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Tue, 25 Mar 2025 14:40:22 -0500 Subject: [PATCH 087/124] update yarn.lock --- yarn.lock | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/yarn.lock b/yarn.lock index 5600f1b972fb..dca5e59dd016 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4458,7 +4458,7 @@ single-call-balance-checker-abi "^1.0.0" uuid "^8.3.2" -"@metamask/base-controller@^7.0.1", "@metamask/base-controller@^7.0.3", "@metamask/base-controller@^7.1.1": +"@metamask/base-controller@^7.0.1", "@metamask/base-controller@^7.0.2", "@metamask/base-controller@^7.0.3", "@metamask/base-controller@^7.1.1": version "7.1.1" resolved "https://registry.yarnpkg.com/@metamask/base-controller/-/base-controller-7.1.1.tgz#837216ee099563b2106202fa0ed376dc909dfbb9" integrity sha512-4nbA6RL9y0SdHdn4MmMTREX6ISJL7OGHn0GXXszv0tp1fdjsn+SBs28uu1a9ceg1J7R/lO6JH7jAAz8zRtt8Nw== @@ -4541,7 +4541,7 @@ resolved "https://registry.yarnpkg.com/@metamask/contract-metadata/-/contract-metadata-2.5.0.tgz#33921fa9c15eb1863f55dcd5f75467ae15614ebb" integrity sha512-+j7jEcp0P1OUMEpa/OIwfJs/ahBC/akwgWxaRTSWX2SWABvlUKBVRMtslfL94Qj2wN2xw8xjaUy5nSHqrznqDA== -"@metamask/controller-utils@^11.0.0", "@metamask/controller-utils@^11.3.0", "@metamask/controller-utils@^11.5.0", "@metamask/controller-utils@^11.6.0": +"@metamask/controller-utils@^11.0.0", "@metamask/controller-utils@^11.3.0", "@metamask/controller-utils@^11.4.4", "@metamask/controller-utils@^11.5.0", "@metamask/controller-utils@^11.6.0": version "11.6.0" resolved "https://registry.npmjs.org/@metamask/controller-utils/-/controller-utils-11.6.0.tgz#68bae4323ad4a68811befadc018043e6c15f6cc1" integrity sha512-7dcaimnRxNzQBVXdadNH/oezBkfxYJ2bK2qB09d9mYjUY3+/dyWX2BYcUXSb1BOJlyJxtVDEN1+sqIRMnoqL/Q== @@ -4880,7 +4880,7 @@ "@noble/hashes" "^1.3.2" "@scure/base" "^1.0.0" -"@metamask/keyring-api@^17.2.0", "@metamask/keyring-api@^17.2.1": +"@metamask/keyring-api@^17.0.0", "@metamask/keyring-api@^17.2.0", "@metamask/keyring-api@^17.2.1": version "17.2.1" resolved "https://registry.yarnpkg.com/@metamask/keyring-api/-/keyring-api-17.2.1.tgz#315e14063abbe07b85a90d2933535d281ac7ec47" integrity sha512-VfFfIsLvGTMtHgyB4lPHxUSo6iumlYwKSBsFtz+9kUk4+Dx3OA9osPN+Xtse+bUWOu5VtOepk0L1MbajEvbLQA== @@ -5024,13 +5024,13 @@ integrity sha512-XwFJk0rd9lAZR5xS3VC7ypEhD7DvZR2gi2Ch6PHnODIqeS9Te3OdVKK5+jHI4his8v/zs6LWdFdlRtx5/jL96w== "@metamask/multichain-network-controller@^0.1.1": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@metamask/multichain-network-controller/-/multichain-network-controller-0.1.2.tgz#eb5b0016d07885806cdb99b4527fc85d6df56bd8" - integrity sha512-rq/yVDsQAxBvFjWOwV+B/dB5k2M7KVv+ptA4ZRnJbmuCNnmDbLIhEsCnLbKuwAxDQbc/1u0iq31sjocSjg2cXA== + version "0.1.1" + resolved "https://registry.yarnpkg.com/@metamask/multichain-network-controller/-/multichain-network-controller-0.1.1.tgz#d91e98660b63f334e8df98f7ba68fe412c4e01e5" + integrity sha512-XDfTb9RZbyQ+1U+xqV3lJH6/8Msru4CmIL2iNJ8KQLwJJvfQsNgSkT4Y0+nYbiWKACOe/ahjBpx0Y7rgmqSjWg== dependencies: "@metamask/base-controller" "^8.0.0" - "@metamask/keyring-api" "^17.2.0" - "@metamask/utils" "^11.2.0" + "@metamask/keyring-api" "^17.0.0" + "@metamask/utils" "^11.1.0" "@solana/addresses" "^2.0.0" "@metamask/multichain-transactions-controller@^0.7.2": @@ -5200,12 +5200,12 @@ json-rpc-random-id "^1.0.1" "@metamask/preferences-controller@^15.0.1": - version "15.0.2" - resolved "https://registry.yarnpkg.com/@metamask/preferences-controller/-/preferences-controller-15.0.2.tgz#a7b7fd36c31be711adaa9f23b61a990746efffbd" - integrity sha512-tn2fhfPo+bX5Qwd9geae5MI0uQ/x5x8HcKKoy0YW+QkGkcj3GU8NgLpIB36ody+xaclLKpvUHITk7ot5Zrp0dw== + version "15.0.1" + resolved "https://registry.yarnpkg.com/@metamask/preferences-controller/-/preferences-controller-15.0.1.tgz#4306099e4659591636304d81ed0954afb160ab81" + integrity sha512-y2rGKMr9fY2LCwEjvX7QKxOqxy5Tz6vA+QK8YEXuczeHd3n2jrh9DNBeM+BZzzOO2cXJgmbn0Jeotl09kDD94g== dependencies: - "@metamask/base-controller" "^8.0.0" - "@metamask/controller-utils" "^11.5.0" + "@metamask/base-controller" "^7.0.2" + "@metamask/controller-utils" "^11.4.4" "@metamask/profile-sync-controller@^10.1.0": version "10.1.0" @@ -5368,14 +5368,14 @@ "@metamask/utils" "^11.0.1" "@metamask/signature-controller@^23.1.0": - version "23.2.1" - resolved "https://registry.yarnpkg.com/@metamask/signature-controller/-/signature-controller-23.2.1.tgz#22e1028196f355cffe638aa11bd7be83f9c229d9" - integrity sha512-autZS8YKwhTwmHRl7Ghac92YHQbQAWnZ4nyrhwFehYfNyus8xllgJ9ey0S0s+SAuBSrR9znckDi4lR53iyc6hw== + version "23.1.0" + resolved "https://registry.yarnpkg.com/@metamask/signature-controller/-/signature-controller-23.1.0.tgz#45b3b545e5a4e890ff41a737b6526cb08fd9f1b3" + integrity sha512-HPUDjVjsZ/HU5QZlmllh1yN2Z1+VhqjTPmzxqXBqD28iKYwUU0YEMN+Jahyh9Ukl7BH/UgzGaaHytGXJ1Xf2Xw== dependencies: - "@metamask/base-controller" "^8.0.0" - "@metamask/controller-utils" "^11.5.0" + "@metamask/base-controller" "^7.0.2" + "@metamask/controller-utils" "^11.4.4" "@metamask/eth-sig-util" "^8.0.0" - "@metamask/utils" "^11.1.0" + "@metamask/utils" "^10.0.0" jsonschema "^1.4.1" lodash "^4.17.21" uuid "^8.3.2" From 4b0141207237e6eaae95ea536d55260185c8cf68 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Wed, 26 Mar 2025 11:48:46 -0500 Subject: [PATCH 088/124] convert token search selectors to createDeepEqual --- app/selectors/tokenSearchDiscoveryDataController.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/selectors/tokenSearchDiscoveryDataController.ts b/app/selectors/tokenSearchDiscoveryDataController.ts index d323006f37fb..1f83b6dc850b 100644 --- a/app/selectors/tokenSearchDiscoveryDataController.ts +++ b/app/selectors/tokenSearchDiscoveryDataController.ts @@ -1,6 +1,6 @@ -import { createSelector } from 'reselect'; -import { RootState } from '../reducers'; import { Hex } from '@metamask/utils'; +import { createDeepEqualSelector } from './util'; +import { RootState } from '../reducers'; import { selectCurrentCurrency } from './currencyRateController'; export const isAssetFromSearch = (asset: unknown) => typeof asset === 'object' && asset !== null && 'isFromSearch' in asset && asset.isFromSearch === true; @@ -8,7 +8,7 @@ export const isAssetFromSearch = (asset: unknown) => typeof asset === 'object' & const selectTokenSearchDiscoveryDataControllerState = (state: RootState) => state.engine.backgroundState.TokenSearchDiscoveryDataController; -export const selectTokenDisplayData = createSelector( +export const selectTokenDisplayData = createDeepEqualSelector( selectTokenSearchDiscoveryDataControllerState, selectCurrentCurrency, (_state: RootState, chainId: Hex) => chainId, @@ -16,12 +16,12 @@ export const selectTokenDisplayData = createSelector( (state, currentCurrency, chainId, address) => state?.tokenDisplayData.find(d => d.chainId === chainId && d.address === address && d.currency === currentCurrency) ); -export const selectSupportedSwapTokenAddressesByChainId = createSelector( +export const selectSupportedSwapTokenAddressesByChainId = createDeepEqualSelector( selectTokenSearchDiscoveryDataControllerState, (state) => state?.swapsTokenAddressesByChainId, ); -export const selectSupportedSwapTokenAddressesForChainId = createSelector( +export const selectSupportedSwapTokenAddressesForChainId = createDeepEqualSelector( selectTokenSearchDiscoveryDataControllerState, (_state: RootState, chainId: Hex) => chainId, (state, chainId) => state?.swapsTokenAddressesByChainId[chainId]?.addresses, From 1c44f629d4eb95a2ed2f4627615050dd908d1f38 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Wed, 26 Mar 2025 11:51:26 -0500 Subject: [PATCH 089/124] add explanation of isAssetFromSearch --- app/selectors/tokenSearchDiscoveryDataController.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/selectors/tokenSearchDiscoveryDataController.ts b/app/selectors/tokenSearchDiscoveryDataController.ts index 1f83b6dc850b..18b0a482ec8d 100644 --- a/app/selectors/tokenSearchDiscoveryDataController.ts +++ b/app/selectors/tokenSearchDiscoveryDataController.ts @@ -3,6 +3,14 @@ import { createDeepEqualSelector } from './util'; import { RootState } from '../reducers'; import { selectCurrentCurrency } from './currencyRateController'; +/** + * When loading the Asset view from a search, we add a property to the asset object + * to indicate that it is from a search. This function checks for that property, + * without any assumptions about the structure of the value it receives. + * + * This condition is used when displaying an asset, to determine where the information + * about the asset should be loaded from. + */ export const isAssetFromSearch = (asset: unknown) => typeof asset === 'object' && asset !== null && 'isFromSearch' in asset && asset.isFromSearch === true; const selectTokenSearchDiscoveryDataControllerState = (state: RootState) => From 429da9eb76faf7b1f0a3b3f4b07617b6a7944cd6 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Wed, 26 Mar 2025 12:01:41 -0500 Subject: [PATCH 090/124] convert some functions to useCallback in BrowserTab --- .../Views/BrowserTab/BrowserTab.tsx | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/app/components/Views/BrowserTab/BrowserTab.tsx b/app/components/Views/BrowserTab/BrowserTab.tsx index 1acbee114837..c014554f6e5f 100644 --- a/app/components/Views/BrowserTab/BrowserTab.tsx +++ b/app/components/Views/BrowserTab/BrowserTab.tsx @@ -1175,7 +1175,7 @@ export const BrowserTab: React.FC = ({ /** * Handle autocomplete selection */ - const onSelect = (item: AutocompleteSearchResult) => { + const onSelect = useCallback((item: AutocompleteSearchResult) => { // Unfocus the url bar and hide the autocomplete results urlBarRef.current?.hide(); @@ -1187,41 +1187,42 @@ export const BrowserTab: React.FC = ({ } else { onSubmitEditing(item.url); } - }; + }, [onSubmitEditing, navigation]); /** * Handle autocomplete dismissal */ - const onDismissAutocomplete = () => { + const onDismissAutocomplete = useCallback(() => { // Unfocus the url bar and hide the autocomplete results urlBarRef.current?.hide(); const hostName = new URLParse(resolvedUrlRef.current).hostname || resolvedUrlRef.current; urlBarRef.current?.setNativeProps({ text: hostName }); - }; + }, []); /** * Hide the autocomplete results */ - const hideAutocomplete = () => autocompleteRef.current?.hide(); + const hideAutocomplete = useCallback(() => autocompleteRef.current?.hide(), []); - const onCancelUrlBar = () => { + const onCancelUrlBar = useCallback(() => { hideAutocomplete(); // Reset the url bar to the current url const hostName = new URLParse(resolvedUrlRef.current).hostname || resolvedUrlRef.current; urlBarRef.current?.setNativeProps({ text: hostName }); - }; + }, [hideAutocomplete]); - const onFocusUrlBar = () => { + const onFocusUrlBar = useCallback(() => { // Show the autocomplete results autocompleteRef.current?.show(); urlBarRef.current?.setNativeProps({ text: resolvedUrlRef.current }); - }; + }, []); - const onChangeUrlBar = (text: string) => + const onChangeUrlBar = useCallback((text: string) => { // Search the autocomplete results autocompleteRef.current?.search(text); + }, []); const handleWebviewNavigationChange = useCallback( (eventName: WebViewNavigationEventName) => From 7b0d0017ac9947923a893734dc3e2a413e5cc770 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Mon, 31 Mar 2025 17:00:02 -0500 Subject: [PATCH 091/124] bogus version number --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6161dd2efd8b..fc1640d68871 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "metamask", - "version": "7.43.0", + "version": "7.43.99", "private": true, "scripts": { "audit:ci": "./scripts/yarn-audit.sh", From b7172b7fe29c719a0a1014e13ae524cf572f3090 Mon Sep 17 00:00:00 2001 From: metamaskbot Date: Mon, 31 Mar 2025 22:01:17 +0000 Subject: [PATCH 092/124] Bump version number to 1664 --- android/app/build.gradle | 2 +- bitrise.yml | 4 ++-- ios/MetaMask.xcodeproj/project.pbxproj | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index ad8436cb6e38..d4a8bc4f3f67 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -179,7 +179,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionName "7.43.0" - versionCode 1656 + versionCode 1664 testBuildType System.getProperty('testBuildType', 'debug') missingDimensionStrategy 'react-native-camera', 'general' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/bitrise.yml b/bitrise.yml index 5d38398587a3..0a029c2103c6 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -2175,13 +2175,13 @@ app: VERSION_NAME: 7.43.0 - opts: is_expand: false - VERSION_NUMBER: 1656 + VERSION_NUMBER: 1664 - opts: is_expand: false FLASK_VERSION_NAME: 7.43.0 - opts: is_expand: false - FLASK_VERSION_NUMBER: 1656 + FLASK_VERSION_NUMBER: 1664 - opts: is_expand: false ANDROID_APK_LINK: '' diff --git a/ios/MetaMask.xcodeproj/project.pbxproj b/ios/MetaMask.xcodeproj/project.pbxproj index 1b9f63c7eba4..0a0585787787 100644 --- a/ios/MetaMask.xcodeproj/project.pbxproj +++ b/ios/MetaMask.xcodeproj/project.pbxproj @@ -1437,7 +1437,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1656; + CURRENT_PROJECT_VERSION = 1664; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1506,7 +1506,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1656; + CURRENT_PROJECT_VERSION = 1664; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1571,7 +1571,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1656; + CURRENT_PROJECT_VERSION = 1664; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1637,7 +1637,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1656; + CURRENT_PROJECT_VERSION = 1664; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1796,7 +1796,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1656; + CURRENT_PROJECT_VERSION = 1664; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1865,7 +1865,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1656; + CURRENT_PROJECT_VERSION = 1664; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; From c9b2aada68e536745aec15544ea9e4ebd34f86aa Mon Sep 17 00:00:00 2001 From: metamaskbot Date: Mon, 31 Mar 2025 22:02:02 +0000 Subject: [PATCH 093/124] Bump version number to 1665 --- android/app/build.gradle | 2 +- bitrise.yml | 4 ++-- ios/MetaMask.xcodeproj/project.pbxproj | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index d4a8bc4f3f67..a99f62a02b3a 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -179,7 +179,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionName "7.43.0" - versionCode 1664 + versionCode 1665 testBuildType System.getProperty('testBuildType', 'debug') missingDimensionStrategy 'react-native-camera', 'general' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/bitrise.yml b/bitrise.yml index 0a029c2103c6..8fcac0079fec 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -2175,13 +2175,13 @@ app: VERSION_NAME: 7.43.0 - opts: is_expand: false - VERSION_NUMBER: 1664 + VERSION_NUMBER: 1665 - opts: is_expand: false FLASK_VERSION_NAME: 7.43.0 - opts: is_expand: false - FLASK_VERSION_NUMBER: 1664 + FLASK_VERSION_NUMBER: 1665 - opts: is_expand: false ANDROID_APK_LINK: '' diff --git a/ios/MetaMask.xcodeproj/project.pbxproj b/ios/MetaMask.xcodeproj/project.pbxproj index 0a0585787787..fc4077c2ef33 100644 --- a/ios/MetaMask.xcodeproj/project.pbxproj +++ b/ios/MetaMask.xcodeproj/project.pbxproj @@ -1437,7 +1437,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1664; + CURRENT_PROJECT_VERSION = 1665; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1506,7 +1506,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1664; + CURRENT_PROJECT_VERSION = 1665; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1571,7 +1571,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1664; + CURRENT_PROJECT_VERSION = 1665; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1637,7 +1637,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1664; + CURRENT_PROJECT_VERSION = 1665; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1796,7 +1796,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1664; + CURRENT_PROJECT_VERSION = 1665; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1865,7 +1865,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1664; + CURRENT_PROJECT_VERSION = 1665; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; From 612d8be0307104fb53c72515336150be0aedd18f Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Tue, 1 Apr 2025 13:03:31 -0500 Subject: [PATCH 094/124] Remove isPortfolioView from token details --- .../UI/AssetOverview/TokenDetails/TokenDetails.tsx | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/app/components/UI/AssetOverview/TokenDetails/TokenDetails.tsx b/app/components/UI/AssetOverview/TokenDetails/TokenDetails.tsx index b2fae3a1b84e..450636de688d 100644 --- a/app/components/UI/AssetOverview/TokenDetails/TokenDetails.tsx +++ b/app/components/UI/AssetOverview/TokenDetails/TokenDetails.tsx @@ -29,7 +29,6 @@ import TokenDetailsList from './TokenDetailsList'; import MarketDetailsList from './MarketDetailsList'; import { TokenI } from '../../Tokens/types'; import StakingEarnings from '../../Stake/components/StakingEarnings'; -import { isPortfolioViewEnabled } from '../../../../util/networks'; import { isAssetFromSearch, selectTokenDisplayData } from '../../../../selectors/tokenSearchDiscoveryDataController'; import { isSupportedLendingTokenByChainId } from '../../Earn/utils/token'; import EarnEmptyStateCta from '../../Earn/components/EmptyStateCta'; @@ -76,14 +75,11 @@ const TokenDetails: React.FC = ({ asset }) => { let conversionRate; if (isAssetFromSearch(asset)) { conversionRate = 1; - } else if (isPortfolioViewEnabled()) { - conversionRate = conversionRateBySymbol; } else { - conversionRate = conversionRateLegacy; + conversionRate = conversionRateBySymbol; } - const tokenExchangeRates = isPortfolioViewEnabled() - ? tokenExchangeRatesByChainId - : tokenExchangeRatesLegacy; + + const tokenExchangeRates = tokenExchangeRatesByChainId; let tokenMetadata; let marketData; From 6ffe882f3093004d350908b444087b0be12ad550 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Tue, 1 Apr 2025 16:12:02 -0500 Subject: [PATCH 095/124] restore ts-expect-error comments --- app/core/Engine/Engine.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/core/Engine/Engine.ts b/app/core/Engine/Engine.ts index 023a0838fc72..c15a61d79590 100644 --- a/app/core/Engine/Engine.ts +++ b/app/core/Engine/Engine.ts @@ -289,6 +289,7 @@ export class Engine { }); const preferencesController = new PreferencesController({ + // @ts-expect-error TODO: Resolve mismatch between base-controller versions. messenger: this.controllerMessenger.getRestricted({ name: 'PreferencesController', allowedActions: [], @@ -507,6 +508,7 @@ export class Engine { allowedEvents: [], }), state: initialKeyringState || initialState.KeyringController, + // @ts-expect-error To Do: Update the type of QRHardwareKeyring to Keyring keyringBuilders: additionalKeyrings, cacheEncryptionKey: true, }); From 180d0f7c935f96d991b30ae59b7b6e6344b225be Mon Sep 17 00:00:00 2001 From: metamaskbot Date: Tue, 1 Apr 2025 21:14:26 +0000 Subject: [PATCH 096/124] Bump version number to 1667 --- android/app/build.gradle | 2 +- bitrise.yml | 4 ++-- ios/MetaMask.xcodeproj/project.pbxproj | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index a99f62a02b3a..ca1c4e710da0 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -179,7 +179,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionName "7.43.0" - versionCode 1665 + versionCode 1667 testBuildType System.getProperty('testBuildType', 'debug') missingDimensionStrategy 'react-native-camera', 'general' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/bitrise.yml b/bitrise.yml index 7783ccd66af1..c399e8cee03b 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -2185,13 +2185,13 @@ app: VERSION_NAME: 7.43.0 - opts: is_expand: false - VERSION_NUMBER: 1665 + VERSION_NUMBER: 1667 - opts: is_expand: false FLASK_VERSION_NAME: 7.43.0 - opts: is_expand: false - FLASK_VERSION_NUMBER: 1665 + FLASK_VERSION_NUMBER: 1667 - opts: is_expand: false ANDROID_APK_LINK: '' diff --git a/ios/MetaMask.xcodeproj/project.pbxproj b/ios/MetaMask.xcodeproj/project.pbxproj index fc4077c2ef33..d45eefd6e0c2 100644 --- a/ios/MetaMask.xcodeproj/project.pbxproj +++ b/ios/MetaMask.xcodeproj/project.pbxproj @@ -1437,7 +1437,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1665; + CURRENT_PROJECT_VERSION = 1667; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1506,7 +1506,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1665; + CURRENT_PROJECT_VERSION = 1667; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1571,7 +1571,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1665; + CURRENT_PROJECT_VERSION = 1667; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1637,7 +1637,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1665; + CURRENT_PROJECT_VERSION = 1667; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1796,7 +1796,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1665; + CURRENT_PROJECT_VERSION = 1667; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1865,7 +1865,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1665; + CURRENT_PROJECT_VERSION = 1667; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; From e7b4229648b12ccd312e9e4d7473e818866f84b5 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Tue, 1 Apr 2025 16:22:29 -0500 Subject: [PATCH 097/124] fix lint errors --- .../UI/AssetOverview/TokenDetails/TokenDetails.tsx | 13 ++----------- .../useTokenSearchDiscovery.ts | 6 ++---- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/app/components/UI/AssetOverview/TokenDetails/TokenDetails.tsx b/app/components/UI/AssetOverview/TokenDetails/TokenDetails.tsx index 450636de688d..8abb605ade38 100644 --- a/app/components/UI/AssetOverview/TokenDetails/TokenDetails.tsx +++ b/app/components/UI/AssetOverview/TokenDetails/TokenDetails.tsx @@ -9,15 +9,8 @@ import { useStyles } from '../../../../component-library/hooks'; import styleSheet from './TokenDetails.styles'; import { safeToChecksumAddress } from '../../../../util/address'; import { selectTokenList } from '../../../../selectors/tokenListController'; -import { - selectTokenMarketDataByChainId, - selectContractExchangeRates, -} from '../../../../selectors/tokenRatesController'; -import { - selectConversionRateBySymbol, - selectCurrentCurrency, - selectConversionRate, -} from '../../../../selectors/currencyRateController'; +import { selectTokenMarketDataByChainId } from '../../../../selectors/tokenRatesController'; +import { selectConversionRateBySymbol, selectCurrentCurrency } from '../../../../selectors/currencyRateController'; import { selectNativeCurrencyByChainId } from '../../../../selectors/networkController'; import { convertDecimalToPercentage, @@ -63,8 +56,6 @@ const TokenDetails: React.FC = ({ asset }) => { const nativeCurrency = useSelector((state: RootState) => selectNativeCurrencyByChainId(state, asset.chainId as Hex), ); - const tokenExchangeRatesLegacy = useSelector(selectContractExchangeRates); - const conversionRateLegacy = useSelector(selectConversionRate); const conversionRateBySymbol = useSelector((state: RootState) => selectConversionRateBySymbol(state, nativeCurrency), ); diff --git a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts index 37ac43e7a832..f39b7da0da9e 100644 --- a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts +++ b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts @@ -39,13 +39,11 @@ export const useTokenSearchDiscovery = () => { const swapsTokenAddresses = useSelector(selectSupportedSwapTokenAddressesByChainId); - const filteredResults = useMemo(() => { - return results.filter((result) => { + const filteredResults = useMemo(() => results.filter((result) => { const chainId = result.chainId as Hex; const tokenAddresses = swapsTokenAddresses[chainId]; return tokenAddresses?.addresses.includes(result.tokenAddress); - }); - }, [results, swapsTokenAddresses]); + }), [results, swapsTokenAddresses]); const searchTokens = useMemo( () => From 19b41dbf264351be76f53c66e62ac06a094a3792 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Tue, 1 Apr 2025 16:49:04 -0500 Subject: [PATCH 098/124] update build version --- android/app/build.gradle | 2 +- bitrise.yml | 4 ++-- ios/MetaMask.xcodeproj/project.pbxproj | 12 ++++++------ package.json | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index ca1c4e710da0..9d1d3fae7423 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -178,7 +178,7 @@ android { applicationId "io.metamask" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionName "7.43.0" + versionName "7.43.99" versionCode 1667 testBuildType System.getProperty('testBuildType', 'debug') missingDimensionStrategy 'react-native-camera', 'general' diff --git a/bitrise.yml b/bitrise.yml index c399e8cee03b..a5909f8efe50 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -2182,13 +2182,13 @@ app: PROJECT_LOCATION_IOS: ios - opts: is_expand: false - VERSION_NAME: 7.43.0 + VERSION_NAME: 7.43.99 - opts: is_expand: false VERSION_NUMBER: 1667 - opts: is_expand: false - FLASK_VERSION_NAME: 7.43.0 + FLASK_VERSION_NAME: 7.43.99 - opts: is_expand: false FLASK_VERSION_NUMBER: 1667 diff --git a/ios/MetaMask.xcodeproj/project.pbxproj b/ios/MetaMask.xcodeproj/project.pbxproj index d45eefd6e0c2..89de2503988a 100644 --- a/ios/MetaMask.xcodeproj/project.pbxproj +++ b/ios/MetaMask.xcodeproj/project.pbxproj @@ -1475,7 +1475,7 @@ "${inherited}", ); LLVM_LTO = YES; - MARKETING_VERSION = 7.43.0; + MARKETING_VERSION = 7.43.99; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( @@ -1541,7 +1541,7 @@ "${inherited}", ); LLVM_LTO = YES; - MARKETING_VERSION = 7.43.0; + MARKETING_VERSION = 7.43.99; ONLY_ACTIVE_ARCH = NO; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( @@ -1608,7 +1608,7 @@ "\"$(SRCROOT)/MetaMask/System/Library/Frameworks\"", ); LLVM_LTO = YES; - MARKETING_VERSION = 7.43.0; + MARKETING_VERSION = 7.43.99; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( @@ -1672,7 +1672,7 @@ "\"$(SRCROOT)/MetaMask/System/Library/Frameworks\"", ); LLVM_LTO = YES; - MARKETING_VERSION = 7.43.0; + MARKETING_VERSION = 7.43.99; ONLY_ACTIVE_ARCH = NO; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( @@ -1833,7 +1833,7 @@ "\"$(SRCROOT)/MetaMask/System/Library/Frameworks\"", ); LLVM_LTO = YES; - MARKETING_VERSION = 7.43.0; + MARKETING_VERSION = 7.43.99; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = ( "$(inherited)", @@ -1900,7 +1900,7 @@ "\"$(SRCROOT)/MetaMask/System/Library/Frameworks\"", ); LLVM_LTO = YES; - MARKETING_VERSION = 7.43.0; + MARKETING_VERSION = 7.43.99; ONLY_ACTIVE_ARCH = NO; OTHER_CFLAGS = ( "$(inherited)", diff --git a/package.json b/package.json index 1339b6e3e0c2..2c8483f3405e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "metamask", - "version": "7.43.0", + "version": "7.43.99", "private": true, "scripts": { "audit:ci": "./scripts/yarn-audit.sh", From 9334c096dbe4e06e1a58396b426336d7ea833c99 Mon Sep 17 00:00:00 2001 From: metamaskbot Date: Tue, 1 Apr 2025 21:51:21 +0000 Subject: [PATCH 099/124] Bump version number to 1668 --- android/app/build.gradle | 2 +- bitrise.yml | 4 ++-- ios/MetaMask.xcodeproj/project.pbxproj | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 9d1d3fae7423..cb29422e2936 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -179,7 +179,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionName "7.43.99" - versionCode 1667 + versionCode 1668 testBuildType System.getProperty('testBuildType', 'debug') missingDimensionStrategy 'react-native-camera', 'general' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/bitrise.yml b/bitrise.yml index a5909f8efe50..77babdafe170 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -2185,13 +2185,13 @@ app: VERSION_NAME: 7.43.99 - opts: is_expand: false - VERSION_NUMBER: 1667 + VERSION_NUMBER: 1668 - opts: is_expand: false FLASK_VERSION_NAME: 7.43.99 - opts: is_expand: false - FLASK_VERSION_NUMBER: 1667 + FLASK_VERSION_NUMBER: 1668 - opts: is_expand: false ANDROID_APK_LINK: '' diff --git a/ios/MetaMask.xcodeproj/project.pbxproj b/ios/MetaMask.xcodeproj/project.pbxproj index 89de2503988a..1c0ec2e2bb3e 100644 --- a/ios/MetaMask.xcodeproj/project.pbxproj +++ b/ios/MetaMask.xcodeproj/project.pbxproj @@ -1437,7 +1437,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1667; + CURRENT_PROJECT_VERSION = 1668; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1506,7 +1506,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1667; + CURRENT_PROJECT_VERSION = 1668; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1571,7 +1571,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1667; + CURRENT_PROJECT_VERSION = 1668; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1637,7 +1637,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1667; + CURRENT_PROJECT_VERSION = 1668; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1796,7 +1796,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1667; + CURRENT_PROJECT_VERSION = 1668; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1865,7 +1865,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1667; + CURRENT_PROJECT_VERSION = 1668; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; From ad9b4f340587535da242c7784539b513276aee61 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Thu, 3 Apr 2025 16:24:16 -0500 Subject: [PATCH 100/124] update browser bar placehholder text --- locales/languages/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/languages/en.json b/locales/languages/en.json index fa7dd693998b..4de9b9d6c5a1 100644 --- a/locales/languages/en.json +++ b/locales/languages/en.json @@ -69,7 +69,7 @@ "connector": "at" }, "autocomplete": { - "placeholder": "Search by site or address", + "placeholder": "Search by token, site or address", "recents": "Recents", "favorites": "Favorites", "sites": "Sites", From e8b932b1b976162d5f287c4b615cc3963fd44df7 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Thu, 3 Apr 2025 16:50:34 -0500 Subject: [PATCH 101/124] update snapshots --- .../BrowserUrlBar/__snapshots__/BrowserUrlBar.test.tsx.snap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/components/UI/BrowserUrlBar/__snapshots__/BrowserUrlBar.test.tsx.snap b/app/components/UI/BrowserUrlBar/__snapshots__/BrowserUrlBar.test.tsx.snap index 121d74dc1340..0445ba385faa 100644 --- a/app/components/UI/BrowserUrlBar/__snapshots__/BrowserUrlBar.test.tsx.snap +++ b/app/components/UI/BrowserUrlBar/__snapshots__/BrowserUrlBar.test.tsx.snap @@ -39,7 +39,7 @@ exports[`BrowserUrlBar should render correctly 1`] = ` onChangeText={[Function]} onFocus={[Function]} onSubmitEditing={[Function]} - placeholder="Search by site or address" + placeholder="Search by token, site or address" placeholderTextColor="#9ca1af" returnKeyType="go" selectTextOnFocus={true} @@ -200,7 +200,7 @@ exports[`BrowserUrlBar should render correctly when url bar is not focused 1`] = onChangeText={[Function]} onFocus={[Function]} onSubmitEditing={[Function]} - placeholder="Search by site or address" + placeholder="Search by token, site or address" placeholderTextColor="#9ca1af" returnKeyType="go" selectTextOnFocus={true} From 86d5b12f9e84415ee7e06c0dcc37f4946f0cd37e Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Thu, 3 Apr 2025 16:53:04 -0500 Subject: [PATCH 102/124] revert version number changes --- android/app/build.gradle | 4 ++-- bitrise.yml | 8 ++++---- ios/MetaMask.xcodeproj/project.pbxproj | 24 ++++++++++++------------ package.json | 2 +- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index cb29422e2936..ad8436cb6e38 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -178,8 +178,8 @@ android { applicationId "io.metamask" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionName "7.43.99" - versionCode 1668 + versionName "7.43.0" + versionCode 1656 testBuildType System.getProperty('testBuildType', 'debug') missingDimensionStrategy 'react-native-camera', 'general' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/bitrise.yml b/bitrise.yml index 77babdafe170..7c2f8858b708 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -2182,16 +2182,16 @@ app: PROJECT_LOCATION_IOS: ios - opts: is_expand: false - VERSION_NAME: 7.43.99 + VERSION_NAME: 7.43.0 - opts: is_expand: false - VERSION_NUMBER: 1668 + VERSION_NUMBER: 1656 - opts: is_expand: false - FLASK_VERSION_NAME: 7.43.99 + FLASK_VERSION_NAME: 7.43.0 - opts: is_expand: false - FLASK_VERSION_NUMBER: 1668 + FLASK_VERSION_NUMBER: 1656 - opts: is_expand: false ANDROID_APK_LINK: '' diff --git a/ios/MetaMask.xcodeproj/project.pbxproj b/ios/MetaMask.xcodeproj/project.pbxproj index cc9e0912e96c..4054da37f005 100644 --- a/ios/MetaMask.xcodeproj/project.pbxproj +++ b/ios/MetaMask.xcodeproj/project.pbxproj @@ -1261,7 +1261,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1668; + CURRENT_PROJECT_VERSION = 1656; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1299,7 +1299,7 @@ "${inherited}", ); LLVM_LTO = YES; - MARKETING_VERSION = 7.43.99; + MARKETING_VERSION = 7.43.0; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( @@ -1330,7 +1330,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1668; + CURRENT_PROJECT_VERSION = 1656; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1365,7 +1365,7 @@ "${inherited}", ); LLVM_LTO = YES; - MARKETING_VERSION = 7.43.99; + MARKETING_VERSION = 7.43.0; ONLY_ACTIVE_ARCH = NO; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( @@ -1395,7 +1395,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1668; + CURRENT_PROJECT_VERSION = 1656; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1432,7 +1432,7 @@ "\"$(SRCROOT)/MetaMask/System/Library/Frameworks\"", ); LLVM_LTO = YES; - MARKETING_VERSION = 7.43.99; + MARKETING_VERSION = 7.43.0; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( @@ -1461,7 +1461,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1668; + CURRENT_PROJECT_VERSION = 1656; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1496,7 +1496,7 @@ "\"$(SRCROOT)/MetaMask/System/Library/Frameworks\"", ); LLVM_LTO = YES; - MARKETING_VERSION = 7.43.99; + MARKETING_VERSION = 7.43.0; ONLY_ACTIVE_ARCH = NO; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( @@ -1620,7 +1620,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1668; + CURRENT_PROJECT_VERSION = 1656; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1657,7 +1657,7 @@ "\"$(SRCROOT)/MetaMask/System/Library/Frameworks\"", ); LLVM_LTO = YES; - MARKETING_VERSION = 7.43.99; + MARKETING_VERSION = 7.43.0; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = ( "$(inherited)", @@ -1689,7 +1689,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1668; + CURRENT_PROJECT_VERSION = 1656; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1724,7 +1724,7 @@ "\"$(SRCROOT)/MetaMask/System/Library/Frameworks\"", ); LLVM_LTO = YES; - MARKETING_VERSION = 7.43.99; + MARKETING_VERSION = 7.43.0; ONLY_ACTIVE_ARCH = NO; OTHER_CFLAGS = ( "$(inherited)", diff --git a/package.json b/package.json index 47dbcc94821e..6e53a5269507 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "metamask", - "version": "7.43.99", + "version": "7.43.0", "private": true, "scripts": { "audit:ci": "./scripts/yarn-audit.sh", From 48c997b687aa78fcdc8c5f5fa9addad082bb42ed Mon Sep 17 00:00:00 2001 From: David Walsh Date: Mon, 14 Apr 2025 11:44:58 -0500 Subject: [PATCH 103/124] fix: Add feature flag for token search (#14599) --- .../useTokenSearchDiscovery.ts | 8 ++++++-- .../tokenSearchDiscovery/index.ts | 11 +++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 app/selectors/featureFlagController/tokenSearchDiscovery/index.ts diff --git a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts index f39b7da0da9e..bfee5b193453 100644 --- a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts +++ b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts @@ -9,6 +9,7 @@ import { } from '@metamask/token-search-discovery-controller'; import { Hex } from '@metamask/utils'; import { selectSupportedSwapTokenAddressesByChainId } from '../../../selectors/tokenSearchDiscoveryDataController'; +import { tokenSearchDiscoveryEnabled } from '../../../selectors/featureFlagController/tokenSearchDiscovery'; const SEARCH_DEBOUNCE_DELAY = 50; const MINIMUM_QUERY_LENGTH = 2; @@ -39,6 +40,8 @@ export const useTokenSearchDiscovery = () => { const swapsTokenAddresses = useSelector(selectSupportedSwapTokenAddressesByChainId); + const tokenSearchEnabled = useSelector(tokenSearchDiscoveryEnabled); + const filteredResults = useMemo(() => results.filter((result) => { const chainId = result.chainId as Hex; const tokenAddresses = swapsTokenAddresses[chainId]; @@ -50,14 +53,15 @@ export const useTokenSearchDiscovery = () => { debounce(async (params: SearchDiscoveryParams) => { setIsLoading(true); setError(null); - const requestId = ++latestRequestId.current; - if (!params.query || params.query.length < MINIMUM_QUERY_LENGTH) { + if (!params.query || params.query.length < MINIMUM_QUERY_LENGTH || !tokenSearchEnabled) { setResults([]); setIsLoading(false); return; } + const requestId = ++latestRequestId.current; + try { const { TokenSearchDiscoveryController } = Engine.context; const result = await TokenSearchDiscoveryController.searchTokens({ diff --git a/app/selectors/featureFlagController/tokenSearchDiscovery/index.ts b/app/selectors/featureFlagController/tokenSearchDiscovery/index.ts new file mode 100644 index 000000000000..6bef1f8ea16e --- /dev/null +++ b/app/selectors/featureFlagController/tokenSearchDiscovery/index.ts @@ -0,0 +1,11 @@ +import { createSelector } from 'reselect'; +import { selectRemoteFeatureFlags } from '..'; +import { hasProperty } from '@metamask/utils'; + +const DEFAULT_TOKEN_SEARCH_DISCOVERY_ENABLED = false; +export const FEATURE_FLAG_NAME = 'tokenSearchDiscoveryEnabled'; + +export const tokenSearchDiscoveryEnabled = createSelector( + selectRemoteFeatureFlags, + (remoteFeatureFlags) => hasProperty(remoteFeatureFlags, FEATURE_FLAG_NAME) ? remoteFeatureFlags[FEATURE_FLAG_NAME] as boolean : DEFAULT_TOKEN_SEARCH_DISCOVERY_ENABLED, +); From 51b21222bd2b049943f86c2f2e5b93addb864a45 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Mon, 14 Apr 2025 12:47:13 -0500 Subject: [PATCH 104/124] fix typescript errors --- app/components/UI/UrlAutocomplete/Result.tsx | 2 +- app/components/hooks/useAddNetwork.test.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/components/UI/UrlAutocomplete/Result.tsx b/app/components/UI/UrlAutocomplete/Result.tsx index e2aa03ce6519..0b3021efaec5 100644 --- a/app/components/UI/UrlAutocomplete/Result.tsx +++ b/app/components/UI/UrlAutocomplete/Result.tsx @@ -57,7 +57,7 @@ export const Result: React.FC = memo(({ result, onPress, onSwapPres badgeElement={( )} > diff --git a/app/components/hooks/useAddNetwork.test.ts b/app/components/hooks/useAddNetwork.test.ts index d2bbab459c99..c783248f65f7 100644 --- a/app/components/hooks/useAddNetwork.test.ts +++ b/app/components/hooks/useAddNetwork.test.ts @@ -23,6 +23,7 @@ describe('useAddNetwork', () => { imageUrl: 'https://etherscan.io/images/svg/brands/eth.svg', imageSource: 'https://etherscan.io/images/svg/brands/eth.svg', }, + failoverRpcUrls: [], }); }); expect(result.current.networkModal).toBeDefined(); From 64e3b09528f10282debdd60a0abbb6c3cbe13069 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Mon, 14 Apr 2025 13:54:38 -0500 Subject: [PATCH 105/124] add missing dep to useMemo --- .../hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts index bfee5b193453..0bb192827be2 100644 --- a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts +++ b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts @@ -81,7 +81,7 @@ export const useTokenSearchDiscovery = () => { } } }, SEARCH_DEBOUNCE_DELAY), - [], + [tokenSearchEnabled], ); const reset = useCallback(() => { From 424241de22cc217bd47e88bcdad317e6ba309861 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Mon, 14 Apr 2025 14:33:09 -0500 Subject: [PATCH 106/124] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b181f499023c..89a77feae344 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - feat(bridge): add Solana assets to bridge token pickers ([#14365](https://github.com/MetaMask/metamask-mobile/pull/14365)) - feat: add AppMetadataController controller ([#14513](https://github.com/MetaMask/metamask-mobile/pull/14513)) - feat(bridge): implement bridge quote fetching ([#14413](https://github.com/MetaMask/metamask-mobile/pull/14413)) +- feat: add token search and discovery to browser URL bar ([#13328](https://github.com/MetaMask/metamask-mobile/pull/13328)) ## [7.44.0] From 7a7a25540e79f4a5765aa4a17f0038cb0ad5574d Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Mon, 14 Apr 2025 14:45:51 -0500 Subject: [PATCH 107/124] add feature flag to use token search hook --- .../useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts index cd1b2a6a0beb..7257f47e451f 100644 --- a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts +++ b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts @@ -11,6 +11,11 @@ jest.mock('../../../core/Engine', () => ({ TokenSearchDiscoveryDataController: { fetchSwapsTokens: jest.fn(), }, + RemoteFeatureFlagController: { + remoteFeatureFlags: { + tokenSearchDiscoveryEnabled: true, + }, + }, }, })); From 86c772007c8e09b0f607d2caedf9e3b5365fc9e9 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Mon, 14 Apr 2025 15:00:33 -0500 Subject: [PATCH 108/124] fix mock state for use token search hook test --- .../useTokenSearchDiscovery.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts index 7257f47e451f..dd5ae817d67b 100644 --- a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts +++ b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts @@ -11,11 +11,6 @@ jest.mock('../../../core/Engine', () => ({ TokenSearchDiscoveryDataController: { fetchSwapsTokens: jest.fn(), }, - RemoteFeatureFlagController: { - remoteFeatureFlags: { - tokenSearchDiscoveryEnabled: true, - }, - }, }, })); @@ -34,6 +29,11 @@ const mockInitialState = { }, tokenDisplayData: [], }, + RemoteFeatureFlagController: { + remoteFeatureFlags: { + tokenSearchDiscoveryEnabled: true, + }, + }, }, }, }; From 8a2bff4e39a329ed02baf418c9df1fe55f4fc57b Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Wed, 16 Apr 2025 10:25:13 -0500 Subject: [PATCH 109/124] - Change property name `type` to `category` for search results - Change token search endpoint to swappable token search --- app/components/UI/UrlAutocomplete/Result.tsx | 18 +++---- app/components/UI/UrlAutocomplete/index.tsx | 18 ++++--- app/components/UI/UrlAutocomplete/types.ts | 4 +- .../Views/BrowserTab/BrowserTab.tsx | 2 +- .../useTokenSearchDiscovery.test.ts | 47 +++++-------------- .../useTokenSearchDiscovery.ts | 45 ++++-------------- app/selectors/browser.ts | 4 +- package.json | 2 +- yarn.lock | 10 ++-- 9 files changed, 47 insertions(+), 103 deletions(-) diff --git a/app/components/UI/UrlAutocomplete/Result.tsx b/app/components/UI/UrlAutocomplete/Result.tsx index 0b3021efaec5..ac22924e24b1 100644 --- a/app/components/UI/UrlAutocomplete/Result.tsx +++ b/app/components/UI/UrlAutocomplete/Result.tsx @@ -14,8 +14,6 @@ import BadgeWrapper from '../../../component-library/components/Badges/BadgeWrap import Badge, { BadgeVariant } from '../../../component-library/components/Badges/Badge'; import { NetworkBadgeSource } from '../AssetOverview/Balance/Balance'; import AvatarToken from '../../../component-library/components/Avatars/Avatar/variants/AvatarToken'; -import { selectSupportedSwapTokenAddressesForChainId } from '../../../selectors/tokenSearchDiscoveryDataController'; -import { RootState } from '../../../reducers'; import { isSwapsAllowed } from '../Swaps/utils'; import AppConstants from '../../../core/AppConstants'; import { selectCurrentCurrency } from '../../../selectors/currencyRateController'; @@ -32,7 +30,7 @@ export const Result: React.FC = memo(({ result, onPress, onSwapPres const theme = useTheme(); const styles = stylesheet({theme}); - const name = typeof result.name === 'string' || result.type === 'tokens' ? result.name : getHost(result.url); + const name = typeof result.name === 'string' || result.category === 'tokens' ? result.name : getHost(result.url); const dispatch = useDispatch(); @@ -40,9 +38,7 @@ export const Result: React.FC = memo(({ result, onPress, onSwapPres dispatch(removeBookmark(result)); }, [dispatch, result]); - const swapTokenAddresses = useSelector((state: RootState) => selectSupportedSwapTokenAddressesForChainId(state, result.type === 'tokens' ? result.chainId : '0x')); - - const swapsEnabled = result.type === 'tokens' && isSwapsAllowed(result.chainId) && swapTokenAddresses?.includes(result.address) && AppConstants.SWAPS.ACTIVE; + const swapsEnabled = result.category === 'tokens' && isSwapsAllowed(result.chainId) && AppConstants.SWAPS.ACTIVE; const currentCurrency = useSelector(selectCurrentCurrency); @@ -52,7 +48,7 @@ export const Result: React.FC = memo(({ result, onPress, onSwapPres onPress={onPress} > - {result.type === 'tokens' ? ( + {result.category === 'tokens' ? ( = memo(({ result, onPress, onSwapPres {result.name} - {result.type === 'tokens' ? result.symbol : result.url} + {result.category === 'tokens' ? result.symbol : result.url} { - result.type === 'favorites' && ( + result.category === 'favorites' && ( = memo(({ result, onPress, onSwapPres ) } { - result.type === 'tokens' && ( + result.category === 'tokens' && ( {addCurrencySymbol(result.price, currentCurrency, true)} @@ -103,7 +99,7 @@ export const Result: React.FC = memo(({ result, onPress, onSwapPres ) } { - result.type === 'tokens' && ( + result.category === 'tokens' && ( ({...i, type: 'sites'} as const)); +const dappsWithType: FuseSearchResult[] = dappUrlList.map(i => ({...i, category: 'sites'} as const)); const TOKEN_SEARCH_LIMIT = 10; @@ -63,7 +63,7 @@ const UrlAutocomplete = forwardRef< tokenSearchResults .map(({tokenAddress, usdPricePercentChange, usdPrice, chainId, ...rest}) => ({ ...rest, - type: 'tokens' as const, + category: 'tokens' as const, address: tokenAddress, chainId: chainId as Hex, price: usdConversionRate ? usdPrice / usdConversionRate : -1, @@ -97,8 +97,8 @@ const UrlAutocomplete = forwardRef< } let data = fuseResults.filter((result, index, self) => - result.type === category && - index === self.findIndex(r => r.url === result.url && r.type === result.type) + result.category === category && + index === self.findIndex(r => r.url === result.url && r.category === result.category) ); if (data.length === 0) { return []; @@ -144,9 +144,7 @@ const UrlAutocomplete = forwardRef< setFuseResults([]); } - searchTokens({ - query: text, - }); + searchTokens(text); }, [browserHistory, bookmarks, resetTokenSearch, searchTokens]); @@ -303,9 +301,9 @@ const UrlAutocomplete = forwardRef< contentContainerStyle={styles.contentContainer} sections={resultsByCategory} keyExtractor={(item) => - item.type === 'tokens' - ? `${item.type}-${item.chainId}-${item.address}` - : `${item.type}-${item.url}` + item.category === 'tokens' + ? `${item.category}-${item.chainId}-${item.address}` + : `${item.category}-${item.url}` } renderSectionHeader={renderSectionHeader} renderItem={renderItem} diff --git a/app/components/UI/UrlAutocomplete/types.ts b/app/components/UI/UrlAutocomplete/types.ts index 2002907169ae..289c006345e7 100644 --- a/app/components/UI/UrlAutocomplete/types.ts +++ b/app/components/UI/UrlAutocomplete/types.ts @@ -42,7 +42,7 @@ export type UrlAutocompleteRef = { */ // eslint-disable-next-line @typescript-eslint/consistent-type-definitions export type FuseSearchResult = { - type: 'sites' | 'recents' | 'favorites'; + category: 'sites' | 'recents' | 'favorites'; url: string; name: string; }; @@ -52,7 +52,7 @@ export type FuseSearchResult = { */ // eslint-disable-next-line @typescript-eslint/consistent-type-definitions export type TokenSearchResult = { - type: 'tokens'; + category: 'tokens'; name: string; symbol: string; address: string; diff --git a/app/components/Views/BrowserTab/BrowserTab.tsx b/app/components/Views/BrowserTab/BrowserTab.tsx index 1270899a635f..8f168a1354a2 100644 --- a/app/components/Views/BrowserTab/BrowserTab.tsx +++ b/app/components/Views/BrowserTab/BrowserTab.tsx @@ -1172,7 +1172,7 @@ export const BrowserTab: React.FC = ({ // Unfocus the url bar and hide the autocomplete results urlBarRef.current?.hide(); - if (item.type === 'tokens') { + if (item.category === 'tokens') { navigation.navigate(Routes.BROWSER.ASSET_LOADER, { chainId: item.chainId, address: item.address, diff --git a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts index dd5ae817d67b..f25b4e93819e 100644 --- a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts +++ b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.test.ts @@ -1,15 +1,12 @@ import { act } from '@testing-library/react-hooks'; import Engine from '../../../core/Engine'; -import useTokenSearchDiscovery, { MAX_RESULTS, SearchDiscoveryParams } from './useTokenSearchDiscovery'; +import useTokenSearchDiscovery, { MAX_RESULTS } from './useTokenSearchDiscovery'; import { renderHookWithProvider } from '../../../util/test/renderWithProvider'; jest.mock('../../../core/Engine', () => ({ context: { TokenSearchDiscoveryController: { - searchTokens: jest.fn(), - }, - TokenSearchDiscoveryDataController: { - fetchSwapsTokens: jest.fn(), + searchSwappableTokens: jest.fn(), }, }, })); @@ -21,14 +18,6 @@ const mockInitialState = { recentSearches: [], lastSearchTimestamp: 0, }, - TokenSearchDiscoveryDataController: { - swapsTokenAddressesByChainId: { - '0x1': { - addresses: ['0x123'], - }, - }, - tokenDisplayData: [], - }, RemoteFeatureFlagController: { remoteFeatureFlags: { tokenSearchDiscoveryEnabled: true, @@ -49,14 +38,11 @@ describe('useTokenSearchDiscovery', () => { }); it('updates states correctly when searching tokens', async () => { - const mockSearchParams: SearchDiscoveryParams = { - chains: ['0x1'], - query: 'DAI', - }; + const mockSearchQuery = 'DAI'; const mockSearchResult = [{ name: 'DAI', tokenAddress: '0x123', chainId: '0x1' }]; ( - Engine.context.TokenSearchDiscoveryController.searchTokens as jest.Mock + Engine.context.TokenSearchDiscoveryController.searchSwappableTokens as jest.Mock ).mockResolvedValueOnce(mockSearchResult); const { result } = renderHookWithProvider( @@ -73,7 +59,7 @@ describe('useTokenSearchDiscovery', () => { // Call search await act(async () => { - result.current.searchTokens(mockSearchParams); + result.current.searchTokens(mockSearchQuery); jest.advanceTimersByTime(300); await Promise.resolve(); }); @@ -83,11 +69,8 @@ describe('useTokenSearchDiscovery', () => { expect(result.current.error).toBe(null); expect(result.current.results).toEqual(mockSearchResult); expect( - Engine.context.TokenSearchDiscoveryController.searchTokens, - ).toHaveBeenCalledWith({ - ...mockSearchParams, - limit: MAX_RESULTS, - }); + Engine.context.TokenSearchDiscoveryController.searchSwappableTokens, + ).toHaveBeenCalledWith({ query: mockSearchQuery, limit: MAX_RESULTS }); }); it('does not search when less than two characters are queried', async () => { @@ -98,19 +81,19 @@ describe('useTokenSearchDiscovery', () => { } ); await act(async () => { - result.current.searchTokens({ query: 'a' }); + result.current.searchTokens('a'); jest.advanceTimersByTime(300); await Promise.resolve(); }); - expect(Engine.context.TokenSearchDiscoveryController.searchTokens).not.toHaveBeenCalled(); + expect(Engine.context.TokenSearchDiscoveryController.searchSwappableTokens).not.toHaveBeenCalled(); }); it('resets the state when reset() is called', async () => { const mockSearchResult = [{ name: 'DAI', tokenAddress: '0x123', chainId: '0x1' }]; ( - Engine.context.TokenSearchDiscoveryController.searchTokens as jest.Mock + Engine.context.TokenSearchDiscoveryController.searchSwappableTokens as jest.Mock ).mockResolvedValueOnce(mockSearchResult); const { result } = renderHookWithProvider( @@ -121,9 +104,7 @@ describe('useTokenSearchDiscovery', () => { ); await act(async () => { - result.current.searchTokens({ - query: 'doge', - }); + result.current.searchTokens('doge'); jest.advanceTimersByTime(300); await Promise.resolve(); }); @@ -140,7 +121,7 @@ describe('useTokenSearchDiscovery', () => { it('returns error and empty results if search failed', async () => { const mockError = new Error('Search failed'); ( - Engine.context.TokenSearchDiscoveryController.searchTokens as jest.Mock + Engine.context.TokenSearchDiscoveryController.searchSwappableTokens as jest.Mock ).mockRejectedValueOnce(mockError); const { result } = renderHookWithProvider( @@ -151,9 +132,7 @@ describe('useTokenSearchDiscovery', () => { ); await act(async () => { - result.current.searchTokens({ - query: 'doge', - }); + result.current.searchTokens('doge'); jest.advanceTimersByTime(300); await Promise.resolve(); }); diff --git a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts index 0bb192827be2..54abbe65e9d1 100644 --- a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts +++ b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts @@ -1,21 +1,14 @@ -import { useState, useRef, useMemo, useCallback, useEffect } from 'react'; +import { useState, useRef, useMemo, useCallback } from 'react'; import { useSelector } from 'react-redux'; import { debounce } from 'lodash'; import Engine from '../../../core/Engine'; import { selectRecentTokenSearches } from '../../../selectors/tokenSearchDiscoveryController'; -import { - TokenSearchResponseItem, - TokenSearchParams, -} from '@metamask/token-search-discovery-controller'; -import { Hex } from '@metamask/utils'; -import { selectSupportedSwapTokenAddressesByChainId } from '../../../selectors/tokenSearchDiscoveryDataController'; +import { TokenSearchResponseItem } from '@metamask/token-search-discovery-controller'; import { tokenSearchDiscoveryEnabled } from '../../../selectors/featureFlagController/tokenSearchDiscovery'; const SEARCH_DEBOUNCE_DELAY = 50; const MINIMUM_QUERY_LENGTH = 2; -export const MAX_RESULTS = '100'; - -export type SearchDiscoveryParams = Omit; +export const MAX_RESULTS = '20'; export const useTokenSearchDiscovery = () => { const recentSearches = useSelector(selectRecentTokenSearches); @@ -23,38 +16,16 @@ export const useTokenSearchDiscovery = () => { const [error, setError] = useState(null); const [results, setResults] = useState([]); const latestRequestId = useRef(0); - - useEffect(() => { - const chainIds = results.reduce((acc, result) => { - if (!acc.includes(result.chainId as Hex)) { - acc.push(result.chainId as Hex); - } - return acc; - }, [] as Hex[]); - - for (const chainId of chainIds) { - Engine.context.TokenSearchDiscoveryDataController.fetchSwapsTokens(chainId); - } - - }, [results]); - - const swapsTokenAddresses = useSelector(selectSupportedSwapTokenAddressesByChainId); - const tokenSearchEnabled = useSelector(tokenSearchDiscoveryEnabled); - const filteredResults = useMemo(() => results.filter((result) => { - const chainId = result.chainId as Hex; - const tokenAddresses = swapsTokenAddresses[chainId]; - return tokenAddresses?.addresses.includes(result.tokenAddress); - }), [results, swapsTokenAddresses]); const searchTokens = useMemo( () => - debounce(async (params: SearchDiscoveryParams) => { + debounce(async (query: string) => { setIsLoading(true); setError(null); - if (!params.query || params.query.length < MINIMUM_QUERY_LENGTH || !tokenSearchEnabled) { + if (query.length < MINIMUM_QUERY_LENGTH || !tokenSearchEnabled) { setResults([]); setIsLoading(false); return; @@ -64,8 +35,8 @@ export const useTokenSearchDiscovery = () => { try { const { TokenSearchDiscoveryController } = Engine.context; - const result = await TokenSearchDiscoveryController.searchTokens({ - ...params, + const result = await TokenSearchDiscoveryController.searchSwappableTokens({ + query, limit: MAX_RESULTS, }); if (requestId === latestRequestId.current) { @@ -95,7 +66,7 @@ export const useTokenSearchDiscovery = () => { recentSearches, isLoading, error, - results: filteredResults, + results, reset, }; }; diff --git a/app/selectors/browser.ts b/app/selectors/browser.ts index 66d452f55223..a22dd6beae3f 100644 --- a/app/selectors/browser.ts +++ b/app/selectors/browser.ts @@ -8,10 +8,10 @@ interface SiteItem { export const selectBrowserHistoryWithType = createDeepEqualSelector( (state: RootState) => state.browser.history, - (history: SiteItem[]) => history.map(item => ({...item, type: 'recents'} as const)).reverse() + (history: SiteItem[]) => history.map(item => ({...item, category: 'recents'} as const)).reverse() ); export const selectBrowserBookmarksWithType = createDeepEqualSelector( (state: RootState) => state.bookmarks, - (bookmarks: SiteItem[]) => bookmarks.map(item => ({...item, type: 'favorites'} as const)) + (bookmarks: SiteItem[]) => bookmarks.map(item => ({...item, category: 'favorites'} as const)) ); diff --git a/package.json b/package.json index 5ea96f692f0a..cc9290acf4b9 100644 --- a/package.json +++ b/package.json @@ -222,7 +222,7 @@ "@metamask/stake-sdk": "^1.0.0", "@metamask/swappable-obj-proxy": "^2.1.0", "@metamask/swaps-controller": "^13.1.0", - "@metamask/token-search-discovery-controller": "^2.1.0", + "@metamask/token-search-discovery-controller": "^3.0.0", "@metamask/transaction-controller": "54.0.0", "@metamask/utils": "^11.2.0", "@ngraveio/bc-ur": "^1.1.6", diff --git a/yarn.lock b/yarn.lock index f9776082805f..c6af2702a271 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5881,13 +5881,13 @@ resolved "https://registry.yarnpkg.com/@metamask/test-dapp/-/test-dapp-9.2.0.tgz#ee3a5a6466530dc266b5bdb1bd226393148329eb" integrity sha512-aMyctMgT8HdHCx7/9SqPfxW4PVKdEh2TtbjiE+fL6nfGv2U0ZnlZdT0OcaC3mEY/qzktip1UnBPmN9/Y5onThA== -"@metamask/token-search-discovery-controller@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@metamask/token-search-discovery-controller/-/token-search-discovery-controller-2.1.0.tgz#ff8f78134da5b2451603ccf957ab67a1ba399db2" - integrity sha512-mda8IZAZGb0IN91fZqn5KvuvF36iwLgkH5ZLiOwRy6kbMsNFat0Lmqtapg1R2QoHPAEm0LGs7RPepBu1NftURw== +"@metamask/token-search-discovery-controller@^3.0.0": + version "3.0.0" + resolved "https://consensys.jfrog.io/artifactory/api/npm/npm/@metamask/token-search-discovery-controller/-/token-search-discovery-controller-3.0.0.tgz#fb9af38ba02cdeb56c0e2f844acb828dcb6331be" + integrity sha512-E5GxQpG2SciAWru/+XcyIQ5Sh/8eOlSs3k+FtEU0Cfb8+UbrJU9/WkvUBzGVdRkxEI0UZ0jQpfq97gwy8nmgdA== dependencies: "@metamask/base-controller" "^8.0.0" - "@metamask/utils" "^11.1.0" + "@metamask/utils" "^11.2.0" "@metamask/transaction-controller@54.0.0": version "54.0.0" From 2da00fb689d6d7c4f31af559f54835d9837f3ee6 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Wed, 16 Apr 2025 10:40:52 -0500 Subject: [PATCH 110/124] Update token search controller to latest version --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index dde665bb7c06..51376ec3646d 100644 --- a/package.json +++ b/package.json @@ -222,7 +222,7 @@ "@metamask/stake-sdk": "^1.0.0", "@metamask/swappable-obj-proxy": "^2.1.0", "@metamask/swaps-controller": "^13.1.0", - "@metamask/token-search-discovery-controller": "^3.0.0", + "@metamask/token-search-discovery-controller": "^3.1.0", "@metamask/transaction-controller": "54.0.0", "@metamask/utils": "^11.2.0", "@ngraveio/bc-ur": "^1.1.6", diff --git a/yarn.lock b/yarn.lock index d22e21e74951..f5e0d1abfa7e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5813,10 +5813,10 @@ resolved "https://registry.yarnpkg.com/@metamask/test-dapp/-/test-dapp-9.2.0.tgz#ee3a5a6466530dc266b5bdb1bd226393148329eb" integrity sha512-aMyctMgT8HdHCx7/9SqPfxW4PVKdEh2TtbjiE+fL6nfGv2U0ZnlZdT0OcaC3mEY/qzktip1UnBPmN9/Y5onThA== -"@metamask/token-search-discovery-controller@^3.0.0": - version "3.0.0" - resolved "https://consensys.jfrog.io/artifactory/api/npm/npm/@metamask/token-search-discovery-controller/-/token-search-discovery-controller-3.0.0.tgz#fb9af38ba02cdeb56c0e2f844acb828dcb6331be" - integrity sha512-E5GxQpG2SciAWru/+XcyIQ5Sh/8eOlSs3k+FtEU0Cfb8+UbrJU9/WkvUBzGVdRkxEI0UZ0jQpfq97gwy8nmgdA== +"@metamask/token-search-discovery-controller@^3.1.0": + version "3.1.0" + resolved "https://consensys.jfrog.io/artifactory/api/npm/npm/@metamask/token-search-discovery-controller/-/token-search-discovery-controller-3.1.0.tgz#3d4c19e811653888efc5d1844e443c417b716a2b" + integrity sha512-+p/aJnn2WbraulbEevaMefzs0o5kgE4YQFr575Dk/oJxfcBFKVdjxGLojVXCzL3wDV3zJS2CM4kcaJCteMzdyQ== dependencies: "@metamask/base-controller" "^8.0.0" "@metamask/utils" "^11.2.0" From 0cc9afd7556b0a121ee7839015fcbe813ba9b8ef Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Wed, 16 Apr 2025 10:56:42 -0500 Subject: [PATCH 111/124] remove jfrog --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index f5e0d1abfa7e..df4c7707c1ab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5815,7 +5815,7 @@ "@metamask/token-search-discovery-controller@^3.1.0": version "3.1.0" - resolved "https://consensys.jfrog.io/artifactory/api/npm/npm/@metamask/token-search-discovery-controller/-/token-search-discovery-controller-3.1.0.tgz#3d4c19e811653888efc5d1844e443c417b716a2b" + resolved "https://registry.yarnpkg.com/@metamask/token-search-discovery-controller/-/token-search-discovery-controller-3.1.0.tgz#3d4c19e811653888efc5d1844e443c417b716a2b" integrity sha512-+p/aJnn2WbraulbEevaMefzs0o5kgE4YQFr575Dk/oJxfcBFKVdjxGLojVXCzL3wDV3zJS2CM4kcaJCteMzdyQ== dependencies: "@metamask/base-controller" "^8.0.0" From 87e3559f840cdbfbd1e33b1d495dc77f8d09e08f Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Wed, 16 Apr 2025 11:34:15 -0500 Subject: [PATCH 112/124] fix urlautocomplete test --- app/components/UI/UrlAutocomplete/index.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/UI/UrlAutocomplete/index.test.tsx b/app/components/UI/UrlAutocomplete/index.test.tsx index d840af6d1033..f706ed320859 100644 --- a/app/components/UI/UrlAutocomplete/index.test.tsx +++ b/app/components/UI/UrlAutocomplete/index.test.tsx @@ -184,7 +184,7 @@ describe('UrlAutocomplete', () => { const deleteFavorite = await screen.findByTestId(deleteFavoriteTestId(defaultState.bookmarks[0].url), {includeHiddenElements: true}); fireEvent.press(deleteFavorite); - expect(store.dispatch).toHaveBeenCalledWith(removeBookmark({...defaultState.bookmarks[0], type: 'favorites'})); + expect(store.dispatch).toHaveBeenCalledWith(removeBookmark({...defaultState.bookmarks[0], category: 'favorites'})); }); it('should show a loading indicator when searching tokens', async () => { From 5bde439beb4639b9ed2e7e3753fb685e38d943b2 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Thu, 17 Apr 2025 14:11:46 -0500 Subject: [PATCH 113/124] fix jittery animation on navigating to asset from search --- app/components/Nav/Main/MainNavigator.js | 2 +- app/components/Views/AssetLoader/index.tsx | 16 ++++++---------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/app/components/Nav/Main/MainNavigator.js b/app/components/Nav/Main/MainNavigator.js index 5d8d44d61655..d3c1ef1c77fb 100644 --- a/app/components/Nav/Main/MainNavigator.js +++ b/app/components/Nav/Main/MainNavigator.js @@ -226,7 +226,7 @@ const BrowserFlow = (props) => ( = ({ route: { params: { add } }, [tokenResult, address, chainId, navigation]); - if (!tokenResult) { - return ( - - - - ); - } - - if (!tokenResult.found) { + if (tokenResult && !tokenResult.found) { return ( Token not found @@ -54,5 +46,9 @@ export const AssetLoader: React.FC = ({ route: { params: { add ); } - return null; + return ( + + + + ); }; From 3947cc96632540bb4b58d20ddfb131679d74a339 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Thu, 17 Apr 2025 14:40:51 -0500 Subject: [PATCH 114/124] use enum for autocomplete search result categories --- app/components/UI/UrlAutocomplete/Result.tsx | 16 ++++++------- .../UrlAutocomplete.constants.ts | 9 ++++++- app/components/UI/UrlAutocomplete/index.tsx | 24 ++++++++++++------- app/components/UI/UrlAutocomplete/types.ts | 11 +++++++-- app/selectors/browser.ts | 5 ++-- 5 files changed, 43 insertions(+), 22 deletions(-) diff --git a/app/components/UI/UrlAutocomplete/Result.tsx b/app/components/UI/UrlAutocomplete/Result.tsx index ac22924e24b1..252071905bd8 100644 --- a/app/components/UI/UrlAutocomplete/Result.tsx +++ b/app/components/UI/UrlAutocomplete/Result.tsx @@ -9,7 +9,7 @@ import { IconName } from '../../../component-library/components/Icons/Icon'; import { useDispatch, useSelector } from 'react-redux'; import { removeBookmark } from '../../../actions/bookmarks'; import stylesheet from './styles'; -import { AutocompleteSearchResult, TokenSearchResult } from './types'; +import { AutocompleteSearchResult, TokenSearchResult, UrlAutocompleteCategory } from './types'; import BadgeWrapper from '../../../component-library/components/Badges/BadgeWrapper'; import Badge, { BadgeVariant } from '../../../component-library/components/Badges/Badge'; import { NetworkBadgeSource } from '../AssetOverview/Balance/Balance'; @@ -30,7 +30,7 @@ export const Result: React.FC = memo(({ result, onPress, onSwapPres const theme = useTheme(); const styles = stylesheet({theme}); - const name = typeof result.name === 'string' || result.category === 'tokens' ? result.name : getHost(result.url); + const name = typeof result.name === 'string' || result.category === UrlAutocompleteCategory.Tokens ? result.name : getHost(result.url); const dispatch = useDispatch(); @@ -38,7 +38,7 @@ export const Result: React.FC = memo(({ result, onPress, onSwapPres dispatch(removeBookmark(result)); }, [dispatch, result]); - const swapsEnabled = result.category === 'tokens' && isSwapsAllowed(result.chainId) && AppConstants.SWAPS.ACTIVE; + const swapsEnabled = result.category === UrlAutocompleteCategory.Tokens && isSwapsAllowed(result.chainId) && AppConstants.SWAPS.ACTIVE; const currentCurrency = useSelector(selectCurrentCurrency); @@ -48,7 +48,7 @@ export const Result: React.FC = memo(({ result, onPress, onSwapPres onPress={onPress} > - {result.category === 'tokens' ? ( + {result.category === UrlAutocompleteCategory.Tokens ? ( = memo(({ result, onPress, onSwapPres {result.name} - {result.category === 'tokens' ? result.symbol : result.url} + {result.category === UrlAutocompleteCategory.Tokens ? result.symbol : result.url} { - result.category === 'favorites' && ( + result.category === UrlAutocompleteCategory.Favorites && ( = memo(({ result, onPress, onSwapPres ) } { - result.category === 'tokens' && ( + result.category === UrlAutocompleteCategory.Tokens && ( {addCurrencySymbol(result.price, currentCurrency, true)} @@ -99,7 +99,7 @@ export const Result: React.FC = memo(({ result, onPress, onSwapPres ) } { - result.category === 'tokens' && ( + result.category === UrlAutocompleteCategory.Tokens && ( ({...i, category: 'sites'} as const)); +const dappsWithType: FuseSearchResult[] = dappUrlList.map(i => ({...i, category: UrlAutocompleteCategory.Sites} as const)); const TOKEN_SEARCH_LIMIT = 10; +interface ResultsWithCategory { + category: UrlAutocompleteCategory; + data: AutocompleteSearchResult[]; +} + /** * Autocomplete list that appears when the browser url bar is focused */ @@ -63,7 +69,7 @@ const UrlAutocomplete = forwardRef< tokenSearchResults .map(({tokenAddress, usdPricePercentChange, usdPrice, chainId, ...rest}) => ({ ...rest, - category: 'tokens' as const, + category: UrlAutocompleteCategory.Tokens as const, address: tokenAddress, chainId: chainId as Hex, price: usdConversionRate ? usdPrice / usdConversionRate : -1, @@ -84,9 +90,9 @@ const UrlAutocomplete = forwardRef< } }, [currentCurrency]); - const resultsByCategory: {category: string, data: AutocompleteSearchResult[]}[] = useMemo(() => ( + const resultsByCategory: ResultsWithCategory[] = useMemo(() => ( ORDERED_CATEGORIES.flatMap((category) => { - if (category === 'tokens') { + if (category === UrlAutocompleteCategory.Tokens) { if (tokenResults.length === 0 && !isTokenSearchLoading) { return []; } @@ -103,7 +109,7 @@ const UrlAutocomplete = forwardRef< if (data.length === 0) { return []; } - if (category === 'recents') { + if (category === UrlAutocompleteCategory.Recents) { data = data.slice(0, MAX_RECENTS); } return { @@ -265,10 +271,10 @@ const UrlAutocomplete = forwardRef< addPopularNetwork, ]); - const renderSectionHeader = useCallback(({section: {category}}) => ( + const renderSectionHeader = useCallback(({section: { category }}: {section: ResultsWithCategory}) => ( {strings(`autocomplete.${category}`)} - {category === 'tokens' && isTokenSearchLoading && ( + {category === UrlAutocompleteCategory.Tokens && isTokenSearchLoading && ( )} @@ -297,11 +303,11 @@ const UrlAutocomplete = forwardRef< return ( - contentContainerStyle={styles.contentContainer} sections={resultsByCategory} keyExtractor={(item) => - item.category === 'tokens' + item.category === UrlAutocompleteCategory.Tokens ? `${item.category}-${item.chainId}-${item.address}` : `${item.category}-${item.url}` } diff --git a/app/components/UI/UrlAutocomplete/types.ts b/app/components/UI/UrlAutocomplete/types.ts index 289c006345e7..39dbd1395b3c 100644 --- a/app/components/UI/UrlAutocomplete/types.ts +++ b/app/components/UI/UrlAutocomplete/types.ts @@ -18,6 +18,13 @@ export type UrlAutocompleteComponentProps = { onDismiss: () => void; }; +export enum UrlAutocompleteCategory { + Sites = 'sites', + Recents = 'recents', + Favorites = 'favorites', + Tokens = 'tokens', +} + /** * Ref for the UrlAutocomplete component */ @@ -42,7 +49,7 @@ export type UrlAutocompleteRef = { */ // eslint-disable-next-line @typescript-eslint/consistent-type-definitions export type FuseSearchResult = { - category: 'sites' | 'recents' | 'favorites'; + category: UrlAutocompleteCategory.Sites | UrlAutocompleteCategory.Recents | UrlAutocompleteCategory.Favorites; url: string; name: string; }; @@ -52,7 +59,7 @@ export type FuseSearchResult = { */ // eslint-disable-next-line @typescript-eslint/consistent-type-definitions export type TokenSearchResult = { - category: 'tokens'; + category: UrlAutocompleteCategory.Tokens; name: string; symbol: string; address: string; diff --git a/app/selectors/browser.ts b/app/selectors/browser.ts index a22dd6beae3f..a8fd35e7c10b 100644 --- a/app/selectors/browser.ts +++ b/app/selectors/browser.ts @@ -1,3 +1,4 @@ +import { UrlAutocompleteCategory } from '../components/UI/UrlAutocomplete'; import { RootState } from '../reducers'; import { createDeepEqualSelector } from './util'; @@ -8,10 +9,10 @@ interface SiteItem { export const selectBrowserHistoryWithType = createDeepEqualSelector( (state: RootState) => state.browser.history, - (history: SiteItem[]) => history.map(item => ({...item, category: 'recents'} as const)).reverse() + (history: SiteItem[]) => history.map(item => ({...item, category: UrlAutocompleteCategory.Recents} as const)).reverse() ); export const selectBrowserBookmarksWithType = createDeepEqualSelector( (state: RootState) => state.bookmarks, - (bookmarks: SiteItem[]) => bookmarks.map(item => ({...item, category: 'favorites'} as const)) + (bookmarks: SiteItem[]) => bookmarks.map(item => ({...item, category: UrlAutocompleteCategory.Favorites} as const)) ); From 8e0b4c2aab6f3b2ef5afedb89899680035f4fe52 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Thu, 17 Apr 2025 14:43:22 -0500 Subject: [PATCH 115/124] use constants for route and screen names in urlautocomplete --- app/components/UI/UrlAutocomplete/index.tsx | 5 +++-- app/constants/navigation/Routes.ts | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/components/UI/UrlAutocomplete/index.tsx b/app/components/UI/UrlAutocomplete/index.tsx index e23b26e34534..fd9a79d7100d 100644 --- a/app/components/UI/UrlAutocomplete/index.tsx +++ b/app/components/UI/UrlAutocomplete/index.tsx @@ -42,6 +42,7 @@ import { PopularList } from '../../../util/networks/customNetworks'; import { swapsUtils } from '@metamask/swaps-controller'; import { useNavigation } from '@react-navigation/native'; import { selectCurrentCurrency, selectUsdConversionRate } from '../../../selectors/currencyRateController'; +import Routes from '../../../constants/navigation/Routes'; export * from './types'; @@ -217,8 +218,8 @@ const UrlAutocomplete = forwardRef< const handleSwapNavigation = useCallback((result: TokenSearchResult) => { hide(); onDismiss(); - navigation.navigate('Swaps', { - screen: 'SwapsAmountView', + navigation.navigate(Routes.SWAPS, { + screen: Routes.SWAPS_AMOUNT_VIEW, params: { sourceToken: swapsUtils.NATIVE_SWAPS_TOKEN_ADDRESS, destinationToken: result.address, diff --git a/app/constants/navigation/Routes.ts b/app/constants/navigation/Routes.ts index 355b923e1ff6..71dade44b300 100644 --- a/app/constants/navigation/Routes.ts +++ b/app/constants/navigation/Routes.ts @@ -141,6 +141,7 @@ const Routes = { ADD_NETWORK: 'AddNetwork', EDIT_NETWORK: 'EditNetwork', SWAPS: 'Swaps', + SWAPS_AMOUNT_VIEW: 'SwapsAmountView', BRIDGE: { ROOT: 'Bridge', MODALS: { From e981b98fec0a9f93f8a304788dedf49eb65de225 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Thu, 17 Apr 2025 14:57:04 -0500 Subject: [PATCH 116/124] improve readability of complex nested ternary --- app/components/Views/Asset/index.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/components/Views/Asset/index.js b/app/components/Views/Asset/index.js index 43648fcae820..d065f961bba3 100644 --- a/app/components/Views/Asset/index.js +++ b/app/components/Views/Asset/index.js @@ -520,10 +520,14 @@ class Asset extends PureComponent { ? isSwapsAllowed(asset.chainId) : isSwapsAllowed(chainId); - const isAssetAllowed = - asset.isETH || - asset.isNative || - (isAssetFromSearch(asset) ? this.props.searchDiscoverySwapsTokens?.includes(asset.address?.toLowerCase()) : asset.address?.toLowerCase() in this.props.swapsTokens); + let isAssetAllowed; + if (asset.isETH || asset.isNative) { + isAssetAllowed = true; + } else if (isAssetFromSearch(asset)) { + isAssetAllowed = this.props.searchDiscoverySwapsTokens?.includes(asset.address?.toLowerCase()); + } else { + isAssetAllowed = asset.address?.toLowerCase() in this.props.swapsTokens; + } const displaySwapsButton = isNetworkAllowed && isAssetAllowed && AppConstants.SWAPS.ACTIVE; From 2512571bb91e83c62be147d1a4bca76df2a296db Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Mon, 21 Apr 2025 11:20:02 -0500 Subject: [PATCH 117/124] fix UrlAutocomplete tests --- app/components/UI/UrlAutocomplete/index.test.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/components/UI/UrlAutocomplete/index.test.tsx b/app/components/UI/UrlAutocomplete/index.test.tsx index f706ed320859..f44501d8c2d7 100644 --- a/app/components/UI/UrlAutocomplete/index.test.tsx +++ b/app/components/UI/UrlAutocomplete/index.test.tsx @@ -20,7 +20,10 @@ const defaultState = { }, CurrencyRateController: { currentCurrency: 'USD', - } + }, + MultichainNetworkController: { + isEvmSelected: true, + }, }, } }; From da60955171c94d79b10f298fb6a6d3579b66a5fd Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Mon, 21 Apr 2025 11:33:43 -0500 Subject: [PATCH 118/124] update AssetDetailsActions snapshot --- .../__snapshots__/AssetDetailsActions.test.tsx.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/Views/AssetDetails/AssetDetailsActions/__snapshots__/AssetDetailsActions.test.tsx.snap b/app/components/Views/AssetDetails/AssetDetailsActions/__snapshots__/AssetDetailsActions.test.tsx.snap index e737ab79c064..c59de76c318d 100644 --- a/app/components/Views/AssetDetails/AssetDetailsActions/__snapshots__/AssetDetailsActions.test.tsx.snap +++ b/app/components/Views/AssetDetails/AssetDetailsActions/__snapshots__/AssetDetailsActions.test.tsx.snap @@ -130,7 +130,7 @@ exports[`AssetDetailsActions should render correctly 1`] = ` > Date: Mon, 21 Apr 2025 11:41:58 -0500 Subject: [PATCH 119/124] fix WalletActions tests --- app/components/Views/WalletActions/WalletActions.test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/components/Views/WalletActions/WalletActions.test.tsx b/app/components/Views/WalletActions/WalletActions.test.tsx index 247638d13bcd..010b23246c08 100644 --- a/app/components/Views/WalletActions/WalletActions.test.tsx +++ b/app/components/Views/WalletActions/WalletActions.test.tsx @@ -75,6 +75,7 @@ jest.mock('../../../selectors/networkController', () => ({ nickname: 'Ethereum Mainnet', }), selectEvmTicker: jest.fn().mockReturnValue('ETH'), + selectNativeCurrencyByChainId: jest.fn(), })); jest.mock('../../../selectors/accountsController', () => { From e7828f1ea76cabbd4b183c36ff6145835cda8a70 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Mon, 21 Apr 2025 13:16:27 -0500 Subject: [PATCH 120/124] increase debounce delay --- .../hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts index 54abbe65e9d1..d21b759fdec9 100644 --- a/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts +++ b/app/components/hooks/useTokenSearchDiscovery/useTokenSearchDiscovery.ts @@ -6,7 +6,7 @@ import { selectRecentTokenSearches } from '../../../selectors/tokenSearchDiscove import { TokenSearchResponseItem } from '@metamask/token-search-discovery-controller'; import { tokenSearchDiscoveryEnabled } from '../../../selectors/featureFlagController/tokenSearchDiscovery'; -const SEARCH_DEBOUNCE_DELAY = 50; +const SEARCH_DEBOUNCE_DELAY = 150; const MINIMUM_QUERY_LENGTH = 2; export const MAX_RESULTS = '20'; From acb59e9229da860a935df5f2cca8b0b045f3600d Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Tue, 22 Apr 2025 12:32:17 -0500 Subject: [PATCH 121/124] Added GHSA-h9w6-f932-gq62 to .iyarc According to this thread https://consensys.slack.com/archives/C010Y3K38PM/p1745313131151649 we are not affected by it --- .iyarc | 1 + 1 file changed, 1 insertion(+) diff --git a/.iyarc b/.iyarc index e69de29bb2d1..a38f65c8f5ff 100644 --- a/.iyarc +++ b/.iyarc @@ -0,0 +1 @@ +GHSA-h9w6-f932-gq62 \ No newline at end of file From 186f589ad5aa84f239d5b8d8675c876e1aa4ae9f Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Tue, 22 Apr 2025 14:28:30 -0500 Subject: [PATCH 122/124] Add token search events --- app/components/UI/UrlAutocomplete/index.tsx | 20 +++++++++++++ .../Views/BrowserTab/BrowserTab.tsx | 28 ++++++++++++++++++- app/core/Analytics/MetaMetrics.events.ts | 16 +++++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/app/components/UI/UrlAutocomplete/index.tsx b/app/components/UI/UrlAutocomplete/index.tsx index fec59762f4f6..351e0f5bd0ca 100644 --- a/app/components/UI/UrlAutocomplete/index.tsx +++ b/app/components/UI/UrlAutocomplete/index.tsx @@ -38,6 +38,8 @@ import { Hex } from '@metamask/utils'; import Engine from '../../../core/Engine'; import { selectCurrentCurrency, selectUsdConversionRate } from '../../../selectors/currencyRateController'; import { SwapBridgeNavigationLocation, useSwapBridgeNavigation } from '../Bridge/hooks/useSwapBridgeNavigation'; +import { MetaMetricsEvents, useMetrics } from '../../hooks/useMetrics'; +import { JsonMap } from '../../../core/Analytics/MetaMetrics.types'; export * from './types'; @@ -211,8 +213,26 @@ const UrlAutocomplete = forwardRef< sourcePage: 'MainView', }); + const { trackEvent, createEventBuilder } = useMetrics(); + const goToSwaps = useCallback(async (result: TokenSearchResult) => { try { + let properties: JsonMap; + if (latestSearchTerm.current?.startsWith('0x')) { + properties = { + token_address: latestSearchTerm.current, + }; + } else { + properties = { + token_symbol: result.symbol, + }; + } + trackEvent( + createEventBuilder(MetaMetricsEvents.TOKEN_SEARCH_DISCOVERY_TOKEN_SWAP_OPENED) + .addProperties(properties) + .build() + ); + await goToSwapsHook(result); } catch (error) { return; diff --git a/app/components/Views/BrowserTab/BrowserTab.tsx b/app/components/Views/BrowserTab/BrowserTab.tsx index 8f168a1354a2..7ef29d17c81d 100644 --- a/app/components/Views/BrowserTab/BrowserTab.tsx +++ b/app/components/Views/BrowserTab/BrowserTab.tsx @@ -110,6 +110,7 @@ import IpfsBanner from './components/IpfsBanner'; import UrlAutocomplete, { AutocompleteSearchResult, UrlAutocompleteRef } from '../../UI/UrlAutocomplete'; import { selectSearchEngine } from '../../../reducers/browser/selectors'; import { getPhishingTestResult } from '../../../util/phishingDetection'; +import { JsonMap } from '../../../core/Analytics/MetaMetrics.types'; /** * Tab component for the in-app browser @@ -157,6 +158,7 @@ export const BrowserTab: React.FC = ({ // Track if webview is loaded for the first time const isWebViewReadyToLoad = useRef(false); const urlBarRef = useRef(null); + const urlBarText = useRef(''); const autocompleteRef = useRef(null); const onSubmitEditingRef = useRef<(text: string) => Promise>( async () => { @@ -1173,14 +1175,37 @@ export const BrowserTab: React.FC = ({ urlBarRef.current?.hide(); if (item.category === 'tokens') { + let properties: JsonMap; + if (urlBarText.current.startsWith('0x')) { + properties = { + token_address: urlBarText.current, + }; + } else { + properties = { + token_symbol: item.symbol, + }; + } + trackEvent( + createEventBuilder(MetaMetricsEvents.TOKEN_SEARCH_DISCOVERY_TOKEN_DETAILS_OPENED) + .addProperties(properties) + .build() + ); + navigation.navigate(Routes.BROWSER.ASSET_LOADER, { chainId: item.chainId, address: item.address, }); } else { + trackEvent( + createEventBuilder(MetaMetricsEvents.TOKEN_SEARCH_DISCOVERY_SITE_OPENED) + .addProperties({ + url: item.url, + }) + .build() + ); onSubmitEditing(item.url); } - }, [onSubmitEditing, navigation]); + }, [onSubmitEditing, navigation, trackEvent, createEventBuilder]); /** * Handle autocomplete dismissal @@ -1215,6 +1240,7 @@ export const BrowserTab: React.FC = ({ const onChangeUrlBar = useCallback((text: string) => { // Search the autocomplete results autocompleteRef.current?.search(text); + urlBarText.current = text; }, []); const handleWebviewNavigationChange = useCallback( diff --git a/app/core/Analytics/MetaMetrics.events.ts b/app/core/Analytics/MetaMetrics.events.ts index d6b3fd4a00cc..4ed952d61013 100644 --- a/app/core/Analytics/MetaMetrics.events.ts +++ b/app/core/Analytics/MetaMetrics.events.ts @@ -429,6 +429,11 @@ enum EVENT_NAME { // RPC Failover RPC_SERVICE_UNAVAILABLE = 'RPC Service Unavailable', RPC_SERVICE_DEGRADED = 'RPC Service Degraded', + + // Token Search and Discovery + TOKEN_SEARCH_DISCOVERY_SITE_OPENED = 'Token Search and Discovery Site Opened', + TOKEN_SEARCH_DISCOVERY_TOKEN_DETAILS_OPENED = 'Token Search and Discovery Token Details Opened', + TOKEN_SEARCH_DISCOVERY_TOKEN_SWAP_OPENED = 'Token Search and Discovery Token Swap Opened', } enum ACTIONS { @@ -1027,6 +1032,17 @@ const events = { // RPC Failover RPC_SERVICE_UNAVAILABLE: generateOpt(EVENT_NAME.RPC_SERVICE_UNAVAILABLE), RPC_SERVICE_DEGRADED: generateOpt(EVENT_NAME.RPC_SERVICE_DEGRADED), + + // Token Search and Discovery + TOKEN_SEARCH_DISCOVERY_SITE_OPENED: generateOpt( + EVENT_NAME.TOKEN_SEARCH_DISCOVERY_SITE_OPENED, + ), + TOKEN_SEARCH_DISCOVERY_TOKEN_DETAILS_OPENED: generateOpt( + EVENT_NAME.TOKEN_SEARCH_DISCOVERY_TOKEN_DETAILS_OPENED, + ), + TOKEN_SEARCH_DISCOVERY_TOKEN_SWAP_OPENED: generateOpt( + EVENT_NAME.TOKEN_SEARCH_DISCOVERY_TOKEN_SWAP_OPENED, + ), }; /** From 39370be7ff3ec3864c17fbda0eb5bcdd0bad9d39 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Tue, 22 Apr 2025 14:41:43 -0500 Subject: [PATCH 123/124] add missing useCallback deps --- app/components/UI/UrlAutocomplete/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/UI/UrlAutocomplete/index.tsx b/app/components/UI/UrlAutocomplete/index.tsx index 351e0f5bd0ca..1dddcb4f09c2 100644 --- a/app/components/UI/UrlAutocomplete/index.tsx +++ b/app/components/UI/UrlAutocomplete/index.tsx @@ -239,7 +239,7 @@ const UrlAutocomplete = forwardRef< } hide(); onDismiss(); - }, [hide, onDismiss, goToSwapsHook]); + }, [hide, onDismiss, goToSwapsHook, trackEvent, createEventBuilder]); const renderSectionHeader = useCallback(({section: { category }}: {section: ResultsWithCategory}) => ( From b52b0540592c20c7c49525dd586d74ac5128cdd5 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Tue, 29 Apr 2025 09:43:57 -0500 Subject: [PATCH 124/124] alignment --- .../Views/BrowserTab/BrowserTab.tsx | 62 ++++++++++--------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/app/components/Views/BrowserTab/BrowserTab.tsx b/app/components/Views/BrowserTab/BrowserTab.tsx index 49df52e4a862..ad8471dc95f7 100644 --- a/app/components/Views/BrowserTab/BrowserTab.tsx +++ b/app/components/Views/BrowserTab/BrowserTab.tsx @@ -1206,38 +1206,40 @@ export const BrowserTab: React.FC = ({ // Unfocus the url bar and hide the autocomplete results urlBarRef.current?.hide(); - if (item.category === 'tokens') { - let properties: JsonMap; - if (urlBarText.current.startsWith('0x')) { - properties = { - token_address: urlBarText.current, - }; + if (item.category === 'tokens') { + let properties: JsonMap; + if (urlBarText.current.startsWith('0x')) { + properties = { + token_address: urlBarText.current, + }; + } else { + properties = { + token_symbol: item.symbol, + }; + } + trackEvent( + createEventBuilder(MetaMetricsEvents.TOKEN_SEARCH_DISCOVERY_TOKEN_DETAILS_OPENED) + .addProperties(properties) + .build() + ); + + navigation.navigate(Routes.BROWSER.ASSET_LOADER, { + chainId: item.chainId, + address: item.address, + }); } else { - properties = { - token_symbol: item.symbol, - }; + trackEvent( + createEventBuilder(MetaMetricsEvents.TOKEN_SEARCH_DISCOVERY_SITE_OPENED) + .addProperties({ + url: item.url, + }) + .build() + ); + onSubmitEditing(item.url); } - trackEvent( - createEventBuilder(MetaMetricsEvents.TOKEN_SEARCH_DISCOVERY_TOKEN_DETAILS_OPENED) - .addProperties(properties) - .build() - ); - - navigation.navigate(Routes.BROWSER.ASSET_LOADER, { - chainId: item.chainId, - address: item.address, - }); - } else { - trackEvent( - createEventBuilder(MetaMetricsEvents.TOKEN_SEARCH_DISCOVERY_SITE_OPENED) - .addProperties({ - url: item.url, - }) - .build() - ); - onSubmitEditing(item.url); - } - }, [onSubmitEditing, navigation, trackEvent, createEventBuilder]); + }, + [onSubmitEditing, navigation, trackEvent, createEventBuilder] + ); /** * Handle autocomplete dismissal