diff --git a/app/scripts/controllers/preferences-controller.ts b/app/scripts/controllers/preferences-controller.ts index dce2ef3d0512..7bb2102399bf 100644 --- a/app/scripts/controllers/preferences-controller.ts +++ b/app/scripts/controllers/preferences-controller.ts @@ -164,6 +164,7 @@ export type PreferencesControllerState = Omit< enableMV3TimestampSave: boolean; useExternalServices: boolean; textDirection?: string; + hasFinishedAddingAccountsWithBalance?: boolean; }; /** @@ -454,6 +455,7 @@ const controllerMetadata = { }, isMultiAccountBalancesEnabled: { persist: true, anonymous: true }, showIncomingTransactions: { persist: true, anonymous: true }, + hasFinishedAddingAccountsWithBalance: { persist: true, anonymous: true }, }; export class PreferencesController extends BaseController< @@ -1057,6 +1059,12 @@ export class PreferencesController extends BaseController< } ///: END:ONLY_INCLUDE_IF + setHasFinishedAddingAccountsWithBalance(value: boolean): void { + this.update((state) => { + state.hasFinishedAddingAccountsWithBalance = value; + }); + } + #handleAccountsControllerSync( newAccountsControllerState: AccountsControllerState, ): void { diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 2f8a3a1fe807..b944a4f4a1ef 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1741,7 +1741,7 @@ export default class MetamaskController extends EventEmitter { if (!prevCompletedOnboarding && currCompletedOnboarding) { const { address } = this.accountsController.getSelectedAccount(); - this._addAccountsWithBalance(); + await this._addAccountsWithBalance(); this.postOnboardingInitialization(); this.triggerNetworkrequests(); @@ -4394,42 +4394,48 @@ export default class MetamaskController extends EventEmitter { } async _addAccountsWithBalance() { - // Scan accounts until we find an empty one - const chainId = getCurrentChainId({ - metamask: this.networkController.state, - }); - const ethQuery = new EthQuery(this.provider); - const accounts = await this.keyringController.getAccounts(); - let address = accounts[accounts.length - 1]; + try { + // Scan accounts until we find an empty one + const chainId = getCurrentChainId({ + metamask: this.networkController.state, + }); + const ethQuery = new EthQuery(this.provider); + const accounts = await this.keyringController.getAccounts(); + let address = accounts[accounts.length - 1]; - for (let count = accounts.length; ; count++) { - const balance = await this.getBalance(address, ethQuery); + for (let count = accounts.length; ; count++) { + const balance = await this.getBalance(address, ethQuery); - if (balance === '0x0') { - // This account has no balance, so check for tokens - await this.tokenDetectionController.detectTokens({ - selectedAddress: address, - }); + if (balance === '0x0') { + // This account has no balance, so check for tokens + await this.tokenDetectionController.detectTokens({ + selectedAddress: address, + }); - const tokens = - this.tokensController.state.allTokens?.[chainId]?.[address]; - const detectedTokens = - this.tokensController.state.allDetectedTokens?.[chainId]?.[address]; - - if ( - (tokens?.length ?? 0) === 0 && - (detectedTokens?.length ?? 0) === 0 - ) { - // This account has no balance or tokens - if (count !== 1) { - await this.removeAccount(address); + const tokens = + this.tokensController.state.allTokens?.[chainId]?.[address]; + const detectedTokens = + this.tokensController.state.allDetectedTokens?.[chainId]?.[address]; + + if ( + (tokens?.length ?? 0) === 0 && + (detectedTokens?.length ?? 0) === 0 + ) { + // This account has no balance or tokens + if (count !== 1) { + await this.removeAccount(address); + } + break; } - break; } - } - // This account has assets, so check the next one - address = await this.keyringController.addNewAccount(count); + // This account has assets, so check the next one + address = await this.keyringController.addNewAccount(count); + } + } catch (e) { + log.warn(`Failed to add accounts with balance. Error: ${e}`); + } finally { + this.preferencesController.setHasFinishedAddingAccountsWithBalance(true); } } diff --git a/package.json b/package.json index 5bd177799bb8..cde9b7a70986 100644 --- a/package.json +++ b/package.json @@ -337,7 +337,7 @@ "@metamask/post-message-stream": "^8.0.0", "@metamask/ppom-validator": "0.35.1", "@metamask/preinstalled-example-snap": "^0.2.0", - "@metamask/profile-sync-controller": "^0.9.7", + "@metamask/profile-sync-controller": "^1.0.1", "@metamask/providers": "^14.0.2", "@metamask/queued-request-controller": "^7.0.1", "@metamask/rate-limit-controller": "^6.0.0", diff --git a/test/e2e/helpers/user-storage/userStorageMockttpController.test.ts b/test/e2e/helpers/user-storage/userStorageMockttpController.test.ts index 1b6591899c0e..f4d8fdeb4e3d 100644 --- a/test/e2e/helpers/user-storage/userStorageMockttpController.test.ts +++ b/test/e2e/helpers/user-storage/userStorageMockttpController.test.ts @@ -1,4 +1,5 @@ import * as mockttp from 'mockttp'; +import { USER_STORAGE_FEATURE_NAMES } from '@metamask/profile-sync-controller/sdk'; import { UserStorageMockttpController } from './userStorageMockttpController'; describe('UserStorageMockttpController', () => { @@ -12,11 +13,14 @@ describe('UserStorageMockttpController', () => { it('handles GET requests that have empty response', async () => { const controller = new UserStorageMockttpController(); - controller.setupPath('accounts', mockServer); + controller.setupPath(USER_STORAGE_FEATURE_NAMES.accounts, mockServer); - const request = await controller.onGet('accounts', { - path: `${baseUrl}/accounts`, - }); + const request = await controller.onGet( + USER_STORAGE_FEATURE_NAMES.accounts, + { + path: `${baseUrl}/${USER_STORAGE_FEATURE_NAMES.accounts}`, + }, + ); expect(request.json).toEqual(null); }); @@ -36,13 +40,16 @@ describe('UserStorageMockttpController', () => { }, ]; - controller.setupPath('accounts', mockServer, { + controller.setupPath(USER_STORAGE_FEATURE_NAMES.accounts, mockServer, { getResponse: mockedData, }); - const request = await controller.onGet('accounts', { - path: `${baseUrl}/accounts`, - }); + const request = await controller.onGet( + USER_STORAGE_FEATURE_NAMES.accounts, + { + path: `${baseUrl}/${USER_STORAGE_FEATURE_NAMES.accounts}`, + }, + ); expect(request.json).toEqual(mockedData); }); @@ -62,13 +69,16 @@ describe('UserStorageMockttpController', () => { }, ]; - controller.setupPath('accounts', mockServer, { + controller.setupPath(USER_STORAGE_FEATURE_NAMES.accounts, mockServer, { getResponse: mockedData, }); - const request = await controller.onGet('accounts', { - path: `${baseUrl}/accounts`, - }); + const request = await controller.onGet( + USER_STORAGE_FEATURE_NAMES.accounts, + { + path: `${baseUrl}/${USER_STORAGE_FEATURE_NAMES.accounts}`, + }, + ); expect(request.json).toEqual(mockedData); }); @@ -88,13 +98,16 @@ describe('UserStorageMockttpController', () => { }, ]; - controller.setupPath('accounts', mockServer, { + controller.setupPath(USER_STORAGE_FEATURE_NAMES.accounts, mockServer, { getResponse: mockedData, }); - const request = await controller.onGet('accounts', { - path: `${baseUrl}/accounts/7f8a7963423985c50f75f6ad42a6cf4e7eac43a6c55e3c6fcd49d73f01c1471b`, - }); + const request = await controller.onGet( + USER_STORAGE_FEATURE_NAMES.accounts, + { + path: `${baseUrl}/${USER_STORAGE_FEATURE_NAMES.accounts}/7f8a7963423985c50f75f6ad42a6cf4e7eac43a6c55e3c6fcd49d73f01c1471b`, + }, + ); expect(request.json).toEqual(mockedData[0]); }); @@ -119,24 +132,30 @@ describe('UserStorageMockttpController', () => { Data: 'data3', }; - controller.setupPath('accounts', mockServer, { + controller.setupPath(USER_STORAGE_FEATURE_NAMES.accounts, mockServer, { getResponse: mockedData, }); - const putRequest = await controller.onPut('accounts', { - path: `${baseUrl}/accounts/6afbe024087495b4e0d56c4bdfc981c84eba44a7c284d4f455b5db4fcabc2173`, - body: { - getJson: async () => ({ - data: mockedAddedData.Data, - }), - } as unknown as mockttp.CompletedBody, - }); + const putRequest = await controller.onPut( + USER_STORAGE_FEATURE_NAMES.accounts, + { + path: `${baseUrl}/${USER_STORAGE_FEATURE_NAMES.accounts}/6afbe024087495b4e0d56c4bdfc981c84eba44a7c284d4f455b5db4fcabc2173`, + body: { + getJson: async () => ({ + data: mockedAddedData.Data, + }), + } as unknown as mockttp.CompletedBody, + }, + ); expect(putRequest.statusCode).toEqual(204); - const getRequest = await controller.onGet('accounts', { - path: `${baseUrl}/accounts`, - }); + const getRequest = await controller.onGet( + USER_STORAGE_FEATURE_NAMES.accounts, + { + path: `${baseUrl}/${USER_STORAGE_FEATURE_NAMES.accounts}`, + }, + ); expect(getRequest.json).toEqual([...mockedData, mockedAddedData]); }); @@ -161,24 +180,30 @@ describe('UserStorageMockttpController', () => { Data: 'data3', }; - controller.setupPath('accounts', mockServer, { + controller.setupPath(USER_STORAGE_FEATURE_NAMES.accounts, mockServer, { getResponse: mockedData, }); - const putRequest = await controller.onPut('accounts', { - path: `${baseUrl}/accounts/c236b92ea7d513b2beda062cb546986961dfa7ca4334a2913f7837e43d050468`, - body: { - getJson: async () => ({ - data: mockedUpdatedData.Data, - }), - } as unknown as mockttp.CompletedBody, - }); + const putRequest = await controller.onPut( + USER_STORAGE_FEATURE_NAMES.accounts, + { + path: `${baseUrl}/${USER_STORAGE_FEATURE_NAMES.accounts}/c236b92ea7d513b2beda062cb546986961dfa7ca4334a2913f7837e43d050468`, + body: { + getJson: async () => ({ + data: mockedUpdatedData.Data, + }), + } as unknown as mockttp.CompletedBody, + }, + ); expect(putRequest.statusCode).toEqual(204); - const getRequest = await controller.onGet('accounts', { - path: `${baseUrl}/accounts`, - }); + const getRequest = await controller.onGet( + USER_STORAGE_FEATURE_NAMES.accounts, + { + path: `${baseUrl}/${USER_STORAGE_FEATURE_NAMES.accounts}`, + }, + ); expect(getRequest.json).toEqual([mockedData[0], mockedUpdatedData]); }); @@ -210,7 +235,7 @@ describe('UserStorageMockttpController', () => { }, ]; - controller.setupPath('accounts', mockServer, { + controller.setupPath(USER_STORAGE_FEATURE_NAMES.accounts, mockServer, { getResponse: mockedData, }); @@ -219,20 +244,26 @@ describe('UserStorageMockttpController', () => { putData[entry.HashedKey] = entry.Data; }); - const putRequest = await controller.onPut('accounts', { - path: `${baseUrl}/accounts`, - body: { - getJson: async () => ({ - data: putData, - }), - } as unknown as mockttp.CompletedBody, - }); + const putRequest = await controller.onPut( + USER_STORAGE_FEATURE_NAMES.accounts, + { + path: `${baseUrl}/${USER_STORAGE_FEATURE_NAMES.accounts}`, + body: { + getJson: async () => ({ + data: putData, + }), + } as unknown as mockttp.CompletedBody, + }, + ); expect(putRequest.statusCode).toEqual(204); - const getRequest = await controller.onGet('accounts', { - path: `${baseUrl}/accounts`, - }); + const getRequest = await controller.onGet( + USER_STORAGE_FEATURE_NAMES.accounts, + { + path: `${baseUrl}/${USER_STORAGE_FEATURE_NAMES.accounts}`, + }, + ); expect(getRequest.json).toEqual(mockedUpdatedData); }); @@ -252,19 +283,25 @@ describe('UserStorageMockttpController', () => { }, ]; - controller.setupPath('accounts', mockServer, { + controller.setupPath(USER_STORAGE_FEATURE_NAMES.accounts, mockServer, { getResponse: mockedData, }); - const deleteRequest = await controller.onDelete('accounts', { - path: `${baseUrl}/accounts/c236b92ea7d513b2beda062cb546986961dfa7ca4334a2913f7837e43d050468`, - }); + const deleteRequest = await controller.onDelete( + USER_STORAGE_FEATURE_NAMES.accounts, + { + path: `${baseUrl}/${USER_STORAGE_FEATURE_NAMES.accounts}/c236b92ea7d513b2beda062cb546986961dfa7ca4334a2913f7837e43d050468`, + }, + ); expect(deleteRequest.statusCode).toEqual(204); - const getRequest = await controller.onGet('accounts', { - path: `${baseUrl}/accounts`, - }); + const getRequest = await controller.onGet( + USER_STORAGE_FEATURE_NAMES.accounts, + { + path: `${baseUrl}/${USER_STORAGE_FEATURE_NAMES.accounts}`, + }, + ); expect(getRequest.json).toEqual([mockedData[0]]); }); @@ -282,22 +319,76 @@ describe('UserStorageMockttpController', () => { 'c236b92ea7d513b2beda062cb546986961dfa7ca4334a2913f7837e43d050468', Data: 'data2', }, + { + HashedKey: + 'x236b92ea7d513b2beda062cb546986961dfa7ca4334a2913f7837e43d050468', + Data: 'data3', + }, ]; - controller.setupPath('accounts', mockServer, { + controller.setupPath(USER_STORAGE_FEATURE_NAMES.accounts, mockServer, { getResponse: mockedData, }); - const deleteRequest = await controller.onDelete('accounts', { - path: `${baseUrl}/accounts`, - }); + const deleteRequest = await controller.onPut( + USER_STORAGE_FEATURE_NAMES.accounts, + { + path: `${baseUrl}/${USER_STORAGE_FEATURE_NAMES.accounts}`, + body: { + getJson: async () => ({ + batch_delete: [mockedData[1].HashedKey, mockedData[2].HashedKey], + }), + } as unknown as mockttp.CompletedBody, + }, + ); expect(deleteRequest.statusCode).toEqual(204); - const getRequest = await controller.onGet('accounts', { - path: `${baseUrl}/accounts`, + const getRequest = await controller.onGet( + USER_STORAGE_FEATURE_NAMES.accounts, + { + path: `${baseUrl}/${USER_STORAGE_FEATURE_NAMES.accounts}`, + }, + ); + + expect(getRequest.json).toEqual([mockedData[0]]); + }); + + it('handles entire feature DELETE requests', async () => { + const controller = new UserStorageMockttpController(); + const mockedData = [ + { + HashedKey: + '7f8a7963423985c50f75f6ad42a6cf4e7eac43a6c55e3c6fcd49d73f01c1471b', + Data: 'data1', + }, + { + HashedKey: + 'c236b92ea7d513b2beda062cb546986961dfa7ca4334a2913f7837e43d050468', + Data: 'data2', + }, + ]; + + controller.setupPath(USER_STORAGE_FEATURE_NAMES.accounts, mockServer, { + getResponse: mockedData, }); + const deleteRequest = await controller.onDelete( + USER_STORAGE_FEATURE_NAMES.accounts, + { + path: `${baseUrl}/${USER_STORAGE_FEATURE_NAMES.accounts}`, + }, + ); + + expect(deleteRequest.statusCode).toEqual(204); + + const getRequest = await controller.onGet( + USER_STORAGE_FEATURE_NAMES.accounts, + { + path: `${baseUrl}/${USER_STORAGE_FEATURE_NAMES.accounts}`, + }, + ); + expect(getRequest.json).toEqual(null); }); }); diff --git a/test/e2e/helpers/user-storage/userStorageMockttpController.ts b/test/e2e/helpers/user-storage/userStorageMockttpController.ts index 970a10d11120..7337b6573a1a 100644 --- a/test/e2e/helpers/user-storage/userStorageMockttpController.ts +++ b/test/e2e/helpers/user-storage/userStorageMockttpController.ts @@ -1,13 +1,21 @@ import { CompletedRequest, Mockttp } from 'mockttp'; +import { USER_STORAGE_FEATURE_NAMES } from '@metamask/profile-sync-controller/sdk'; + +const baseUrl = 'https://user-storage.api.cx.metamask.io/api/v1/userstorage'; -// TODO: Export user storage schema from @metamask/profile-sync-controller export const pathRegexps = { - accounts: - /https:\/\/user-storage\.api\.cx\.metamask\.io\/api\/v1\/userstorage\/accounts/u, - networks: - /https:\/\/user-storage\.api\.cx\.metamask\.io\/api\/v1\/userstorage\/networks/u, - notifications: - /https:\/\/user-storage\.api\.cx\.metamask\.io\/api\/v1\/userstorage\/notifications/u, + [USER_STORAGE_FEATURE_NAMES.accounts]: new RegExp( + `${baseUrl}/${USER_STORAGE_FEATURE_NAMES.accounts}`, + 'u', + ), + [USER_STORAGE_FEATURE_NAMES.networks]: new RegExp( + `${baseUrl}/${USER_STORAGE_FEATURE_NAMES.networks}`, + 'u', + ), + [USER_STORAGE_FEATURE_NAMES.notifications]: new RegExp( + `${baseUrl}/${USER_STORAGE_FEATURE_NAMES.notifications}`, + 'u', + ), }; type UserStorageResponseData = { HashedKey: string; Data: string }; @@ -70,50 +78,75 @@ export class UserStorageMockttpController { const isFeatureEntry = determineIfFeatureEntryFromURL(request.path); const data = (await request.body.getJson()) as { - data: string | { [key: string]: string }; + data?: string | Record; + batch_delete?: string[]; }; - const newOrUpdatedSingleOrBatchEntries = - isFeatureEntry && typeof data?.data === 'string' - ? [ - { - HashedKey: request.path.split('/').pop() as string, - Data: data?.data, - }, - ] - : Object.entries(data?.data).map(([key, value]) => ({ - HashedKey: key, - Data: value, - })); - - newOrUpdatedSingleOrBatchEntries.forEach((entry) => { + // We're handling batch delete inside the PUT method due to API limitations + if (data?.batch_delete) { + const keysToDelete = data.batch_delete; + const internalPathData = this.paths.get(path); if (!internalPathData) { - return; + return { + statusCode, + }; } - const doesThisEntryExist = internalPathData.response?.find( - (existingEntry) => existingEntry.HashedKey === entry.HashedKey, - ); + this.paths.set(path, { + ...internalPathData, + response: internalPathData.response.filter( + (entry) => !keysToDelete.includes(entry.HashedKey), + ), + }); + } - if (doesThisEntryExist) { - this.paths.set(path, { - ...internalPathData, - response: internalPathData.response.map((existingEntry) => - existingEntry.HashedKey === entry.HashedKey ? entry : existingEntry, - ), - }); - } else { - this.paths.set(path, { - ...internalPathData, - response: [ - ...(internalPathData?.response || []), - entry as { HashedKey: string; Data: string }, - ], - }); - } - }); + if (data?.data) { + const newOrUpdatedSingleOrBatchEntries = + isFeatureEntry && typeof data?.data === 'string' + ? [ + { + HashedKey: request.path.split('/').pop() as string, + Data: data?.data, + }, + ] + : Object.entries(data?.data).map(([key, value]) => ({ + HashedKey: key, + Data: value, + })); + + newOrUpdatedSingleOrBatchEntries.forEach((entry) => { + const internalPathData = this.paths.get(path); + + if (!internalPathData) { + return; + } + + const doesThisEntryExist = internalPathData.response?.find( + (existingEntry) => existingEntry.HashedKey === entry.HashedKey, + ); + + if (doesThisEntryExist) { + this.paths.set(path, { + ...internalPathData, + response: internalPathData.response.map((existingEntry) => + existingEntry.HashedKey === entry.HashedKey + ? entry + : existingEntry, + ), + }); + } else { + this.paths.set(path, { + ...internalPathData, + response: [ + ...(internalPathData?.response || []), + entry as { HashedKey: string; Data: string }, + ], + }); + } + }); + } return { statusCode, diff --git a/test/e2e/tests/notifications/account-syncing/importing-private-key-account.spec.ts b/test/e2e/tests/notifications/account-syncing/importing-private-key-account.spec.ts index 7b9e2378b058..8829bff24e07 100644 --- a/test/e2e/tests/notifications/account-syncing/importing-private-key-account.spec.ts +++ b/test/e2e/tests/notifications/account-syncing/importing-private-key-account.spec.ts @@ -1,4 +1,5 @@ import { Mockttp } from 'mockttp'; +import { USER_STORAGE_FEATURE_NAMES } from '@metamask/profile-sync-controller/sdk'; import { withFixtures } from '../../../helpers'; import FixtureBuilder from '../../../fixture-builder'; import { mockNotificationServices } from '../mocks'; @@ -28,9 +29,13 @@ describe('Account syncing - Import With Private Key @no-mmi', function () { fixtures: new FixtureBuilder({ onboarding: true }).build(), title: this.test?.fullTitle(), testSpecificMock: (server: Mockttp) => { - userStorageMockttpController.setupPath('accounts', server, { - getResponse: accountsSyncMockResponse, - }); + userStorageMockttpController.setupPath( + USER_STORAGE_FEATURE_NAMES.accounts, + server, + { + getResponse: accountsSyncMockResponse, + }, + ); return mockNotificationServices( server, @@ -75,7 +80,10 @@ describe('Account syncing - Import With Private Key @no-mmi', function () { fixtures: new FixtureBuilder({ onboarding: true }).build(), title: this.test?.fullTitle(), testSpecificMock: (server: Mockttp) => { - userStorageMockttpController.setupPath('accounts', server); + userStorageMockttpController.setupPath( + USER_STORAGE_FEATURE_NAMES.accounts, + server, + ); return mockNotificationServices( server, userStorageMockttpController, diff --git a/test/e2e/tests/notifications/account-syncing/new-user-sync.spec.ts b/test/e2e/tests/notifications/account-syncing/new-user-sync.spec.ts index 992027dd7840..b60c95b625bc 100644 --- a/test/e2e/tests/notifications/account-syncing/new-user-sync.spec.ts +++ b/test/e2e/tests/notifications/account-syncing/new-user-sync.spec.ts @@ -1,4 +1,5 @@ import { Mockttp } from 'mockttp'; +import { USER_STORAGE_FEATURE_NAMES } from '@metamask/profile-sync-controller/sdk'; import { withFixtures } from '../../../helpers'; import FixtureBuilder from '../../../fixture-builder'; import { mockNotificationServices } from '../mocks'; @@ -28,7 +29,10 @@ describe('Account syncing - New User @no-mmi', function () { fixtures: new FixtureBuilder({ onboarding: true }).build(), title: this.test?.fullTitle(), testSpecificMock: (server: Mockttp) => { - userStorageMockttpController.setupPath('accounts', server); + userStorageMockttpController.setupPath( + USER_STORAGE_FEATURE_NAMES.accounts, + server, + ); return mockNotificationServices( server, @@ -77,7 +81,10 @@ describe('Account syncing - New User @no-mmi', function () { fixtures: new FixtureBuilder({ onboarding: true }).build(), title: this.test?.fullTitle(), testSpecificMock: (server: Mockttp) => { - userStorageMockttpController.setupPath('accounts', server); + userStorageMockttpController.setupPath( + USER_STORAGE_FEATURE_NAMES.accounts, + server, + ); return mockNotificationServices( server, userStorageMockttpController, diff --git a/test/e2e/tests/notifications/account-syncing/onboarding-with-opt-out.spec.ts b/test/e2e/tests/notifications/account-syncing/onboarding-with-opt-out.spec.ts index f9574a27cb10..8ad354599850 100644 --- a/test/e2e/tests/notifications/account-syncing/onboarding-with-opt-out.spec.ts +++ b/test/e2e/tests/notifications/account-syncing/onboarding-with-opt-out.spec.ts @@ -1,4 +1,5 @@ import { Mockttp } from 'mockttp'; +import { USER_STORAGE_FEATURE_NAMES } from '@metamask/profile-sync-controller/sdk'; import { withFixtures } from '../../../helpers'; import FixtureBuilder from '../../../fixture-builder'; import { mockNotificationServices } from '../mocks'; @@ -35,9 +36,13 @@ describe('Account syncing - Opt-out Profile Sync @no-mmi', function () { title: this.test?.fullTitle(), testSpecificMock: (server: Mockttp) => { // Mocks are still set up to ensure that requests are not matched - userStorageMockttpController.setupPath('accounts', server, { - getResponse: accountsSyncMockResponse, - }); + userStorageMockttpController.setupPath( + USER_STORAGE_FEATURE_NAMES.accounts, + server, + { + getResponse: accountsSyncMockResponse, + }, + ); return mockNotificationServices( server, userStorageMockttpController, @@ -94,7 +99,10 @@ describe('Account syncing - Opt-out Profile Sync @no-mmi', function () { title: this.test?.fullTitle(), testSpecificMock: (server: Mockttp) => { // Mocks are still set up to ensure that requests are not matched - userStorageMockttpController.setupPath('accounts', server); + userStorageMockttpController.setupPath( + USER_STORAGE_FEATURE_NAMES.accounts, + server, + ); return mockNotificationServices( server, userStorageMockttpController, @@ -146,7 +154,10 @@ describe('Account syncing - Opt-out Profile Sync @no-mmi', function () { title: this.test?.fullTitle(), testSpecificMock: (server: Mockttp) => { // Mocks are still set up to ensure that requests are not matched - userStorageMockttpController.setupPath('accounts', server); + userStorageMockttpController.setupPath( + USER_STORAGE_FEATURE_NAMES.accounts, + server, + ); return mockNotificationServices( server, userStorageMockttpController, diff --git a/test/e2e/tests/notifications/account-syncing/sync-after-adding-account.spec.ts b/test/e2e/tests/notifications/account-syncing/sync-after-adding-account.spec.ts index 31f92520f13e..d0fc7348e08e 100644 --- a/test/e2e/tests/notifications/account-syncing/sync-after-adding-account.spec.ts +++ b/test/e2e/tests/notifications/account-syncing/sync-after-adding-account.spec.ts @@ -1,4 +1,5 @@ import { Mockttp } from 'mockttp'; +import { USER_STORAGE_FEATURE_NAMES } from '@metamask/profile-sync-controller/sdk'; import { withFixtures } from '../../../helpers'; import FixtureBuilder from '../../../fixture-builder'; import { mockNotificationServices } from '../mocks'; @@ -27,9 +28,13 @@ describe('Account syncing - Add Account @no-mmi', function () { fixtures: new FixtureBuilder({ onboarding: true }).build(), title: this.test?.fullTitle(), testSpecificMock: (server: Mockttp) => { - userStorageMockttpController.setupPath('accounts', server, { - getResponse: accountsSyncMockResponse, - }); + userStorageMockttpController.setupPath( + USER_STORAGE_FEATURE_NAMES.accounts, + server, + { + getResponse: accountsSyncMockResponse, + }, + ); return mockNotificationServices( server, @@ -73,7 +78,10 @@ describe('Account syncing - Add Account @no-mmi', function () { fixtures: new FixtureBuilder({ onboarding: true }).build(), title: this.test?.fullTitle(), testSpecificMock: (server: Mockttp) => { - userStorageMockttpController.setupPath('accounts', server); + userStorageMockttpController.setupPath( + USER_STORAGE_FEATURE_NAMES.accounts, + server, + ); return mockNotificationServices( server, userStorageMockttpController, @@ -97,8 +105,9 @@ describe('Account syncing - Add Account @no-mmi', function () { const accountListPage = new AccountListPage(driver); await accountListPage.check_pageIsLoaded(); - const accountSyncResponse = - userStorageMockttpController.paths.get('accounts')?.response; + const accountSyncResponse = userStorageMockttpController.paths.get( + USER_STORAGE_FEATURE_NAMES.accounts, + )?.response; await accountListPage.check_numberOfAvailableAccounts( accountSyncResponse?.length as number, @@ -124,9 +133,13 @@ describe('Account syncing - Add Account @no-mmi', function () { fixtures: new FixtureBuilder({ onboarding: true }).build(), title: this.test?.fullTitle(), testSpecificMock: (server: Mockttp) => { - userStorageMockttpController.setupPath('accounts', server, { - getResponse: accountsSyncMockResponse, - }); + userStorageMockttpController.setupPath( + USER_STORAGE_FEATURE_NAMES.accounts, + server, + { + getResponse: accountsSyncMockResponse, + }, + ); return mockNotificationServices( server, @@ -168,7 +181,10 @@ describe('Account syncing - Add Account @no-mmi', function () { fixtures: new FixtureBuilder({ onboarding: true }).build(), title: this.test?.fullTitle(), testSpecificMock: (server: Mockttp) => { - userStorageMockttpController.setupPath('accounts', server); + userStorageMockttpController.setupPath( + USER_STORAGE_FEATURE_NAMES.accounts, + server, + ); return mockNotificationServices( server, userStorageMockttpController, @@ -192,8 +208,9 @@ describe('Account syncing - Add Account @no-mmi', function () { const accountListPage = new AccountListPage(driver); await accountListPage.check_pageIsLoaded(); - const accountSyncResponse = - userStorageMockttpController.paths.get('accounts')?.response; + const accountSyncResponse = userStorageMockttpController.paths.get( + USER_STORAGE_FEATURE_NAMES.accounts, + )?.response; await accountListPage.check_numberOfAvailableAccounts( accountSyncResponse?.length as number, diff --git a/test/e2e/tests/notifications/account-syncing/sync-after-modifying-account-name.spec.ts b/test/e2e/tests/notifications/account-syncing/sync-after-modifying-account-name.spec.ts index 45ee3ab23a85..db71e95681ef 100644 --- a/test/e2e/tests/notifications/account-syncing/sync-after-modifying-account-name.spec.ts +++ b/test/e2e/tests/notifications/account-syncing/sync-after-modifying-account-name.spec.ts @@ -1,4 +1,5 @@ import { Mockttp } from 'mockttp'; +import { USER_STORAGE_FEATURE_NAMES } from '@metamask/profile-sync-controller/sdk'; import { withFixtures } from '../../../helpers'; import FixtureBuilder from '../../../fixture-builder'; import { mockNotificationServices } from '../mocks'; @@ -27,9 +28,13 @@ describe('Account syncing - Rename Accounts @no-mmi', function () { fixtures: new FixtureBuilder({ onboarding: true }).build(), title: this.test?.fullTitle(), testSpecificMock: (server: Mockttp) => { - userStorageMockttpController.setupPath('accounts', server, { - getResponse: accountsSyncMockResponse, - }); + userStorageMockttpController.setupPath( + USER_STORAGE_FEATURE_NAMES.accounts, + server, + { + getResponse: accountsSyncMockResponse, + }, + ); return mockNotificationServices( server, @@ -72,7 +77,10 @@ describe('Account syncing - Rename Accounts @no-mmi', function () { fixtures: new FixtureBuilder({ onboarding: true }).build(), title: this.test?.fullTitle(), testSpecificMock: (server: Mockttp) => { - userStorageMockttpController.setupPath('accounts', server); + userStorageMockttpController.setupPath( + USER_STORAGE_FEATURE_NAMES.accounts, + server, + ); return mockNotificationServices( server, userStorageMockttpController, diff --git a/test/e2e/tests/notifications/account-syncing/sync-after-onboarding.spec.ts b/test/e2e/tests/notifications/account-syncing/sync-after-onboarding.spec.ts index 5bebe7220e49..3f3e2bd4baae 100644 --- a/test/e2e/tests/notifications/account-syncing/sync-after-onboarding.spec.ts +++ b/test/e2e/tests/notifications/account-syncing/sync-after-onboarding.spec.ts @@ -1,4 +1,5 @@ import { Mockttp } from 'mockttp'; +import { USER_STORAGE_FEATURE_NAMES } from '@metamask/profile-sync-controller/sdk'; import { withFixtures } from '../../../helpers'; import FixtureBuilder from '../../../fixture-builder'; import { mockNotificationServices } from '../mocks'; @@ -27,9 +28,13 @@ describe('Account syncing - Onboarding @no-mmi', function () { fixtures: new FixtureBuilder({ onboarding: true }).build(), title: this.test?.fullTitle(), testSpecificMock: (server: Mockttp) => { - userStorageMockttpController.setupPath('accounts', server, { - getResponse: accountsSyncMockResponse, - }); + userStorageMockttpController.setupPath( + USER_STORAGE_FEATURE_NAMES.accounts, + server, + { + getResponse: accountsSyncMockResponse, + }, + ); return mockNotificationServices( server, userStorageMockttpController, diff --git a/test/e2e/tests/notifications/mocks.ts b/test/e2e/tests/notifications/mocks.ts index ce2ced3df210..748084918272 100644 --- a/test/e2e/tests/notifications/mocks.ts +++ b/test/e2e/tests/notifications/mocks.ts @@ -4,6 +4,7 @@ import { NotificationServicesController, NotificationServicesPushController, } from '@metamask/notification-services-controller'; +import { USER_STORAGE_FEATURE_NAMES } from '@metamask/profile-sync-controller/sdk'; import { UserStorageMockttpController } from '../../helpers/user-storage/userStorageMockttpController'; const AuthMocks = AuthenticationController.Mocks; @@ -32,14 +33,35 @@ export async function mockNotificationServices( mockAPICall(server, AuthMocks.getMockAuthAccessTokenResponse()); // Storage - if (!userStorageMockttpControllerInstance?.paths.get('accounts')) { - userStorageMockttpControllerInstance.setupPath('accounts', server); + if ( + !userStorageMockttpControllerInstance?.paths.get( + USER_STORAGE_FEATURE_NAMES.accounts, + ) + ) { + userStorageMockttpControllerInstance.setupPath( + USER_STORAGE_FEATURE_NAMES.accounts, + server, + ); } - if (!userStorageMockttpControllerInstance?.paths.get('networks')) { - userStorageMockttpControllerInstance.setupPath('networks', server); + if ( + !userStorageMockttpControllerInstance?.paths.get( + USER_STORAGE_FEATURE_NAMES.networks, + ) + ) { + userStorageMockttpControllerInstance.setupPath( + USER_STORAGE_FEATURE_NAMES.networks, + server, + ); } - if (!userStorageMockttpControllerInstance?.paths.get('notifications')) { - userStorageMockttpControllerInstance.setupPath('notifications', server); + if ( + !userStorageMockttpControllerInstance?.paths.get( + USER_STORAGE_FEATURE_NAMES.notifications, + ) + ) { + userStorageMockttpControllerInstance.setupPath( + USER_STORAGE_FEATURE_NAMES.notifications, + server, + ); } // Notifications diff --git a/ui/hooks/metamask-notifications/useProfileSyncing/profileSyncing.ts b/ui/hooks/metamask-notifications/useProfileSyncing/profileSyncing.ts index 5c073fdf6d94..9f6ae6532b70 100644 --- a/ui/hooks/metamask-notifications/useProfileSyncing/profileSyncing.ts +++ b/ui/hooks/metamask-notifications/useProfileSyncing/profileSyncing.ts @@ -10,7 +10,10 @@ import { } from '../../../store/actions'; import { selectIsSignedIn } from '../../../selectors/metamask-notifications/authentication'; -import { selectIsProfileSyncingEnabled } from '../../../selectors/metamask-notifications/profile-syncing'; +import { + selectHasFinishedAddingAccountsWithBalance, + selectIsProfileSyncingEnabled, +} from '../../../selectors/metamask-notifications/profile-syncing'; import { getUseExternalServices } from '../../../selectors'; import { getIsUnlocked, @@ -120,6 +123,9 @@ export function useSetIsProfileSyncingEnabled(): { * @returns a boolean if internally we can perform syncing features or not. */ export const useShouldDispatchProfileSyncing = () => { + const hasFinishedAddingAccountsWithBalance = useSelector( + selectHasFinishedAddingAccountsWithBalance, + ); const isProfileSyncingEnabled = useSelector(selectIsProfileSyncingEnabled); const basicFunctionality: boolean | undefined = useSelector( getUseExternalServices, @@ -135,7 +141,8 @@ export const useShouldDispatchProfileSyncing = () => { isProfileSyncingEnabled && isUnlocked && isSignedIn && - completedOnboarding, + completedOnboarding && + hasFinishedAddingAccountsWithBalance, ); return shouldDispatchProfileSyncing; diff --git a/ui/selectors/metamask-notifications/profile-syncing.ts b/ui/selectors/metamask-notifications/profile-syncing.ts index 8b9b8f4997d6..83e69d45e857 100644 --- a/ui/selectors/metamask-notifications/profile-syncing.ts +++ b/ui/selectors/metamask-notifications/profile-syncing.ts @@ -2,7 +2,9 @@ import { createSelector } from 'reselect'; import type { UserStorageController } from '@metamask/profile-sync-controller'; type AppState = { - metamask: UserStorageController.UserStorageControllerState; + metamask: UserStorageController.UserStorageControllerState & { + hasFinishedAddingAccountsWithBalance: boolean; + }; }; const getMetamask = (state: AppState) => state.metamask; @@ -36,3 +38,20 @@ export const selectIsProfileSyncingUpdateLoading = createSelector( return metamask.isProfileSyncingUpdateLoading; }, ); + +/** + * Selector to determine if the method _addAccountsWithBalance has finished adding accounts after onboarding. + * This is needed for account syncing in order to prevent conflicts with accounts that are being added by the above method during onboarding. + * + * This selector uses the `createSelector` function from 'reselect' to compute whether the update process for profile syncing is currently in a loading state, + * based on the `hasFinishedAddingAccountsWithBalance` property of the `metamask` object in the Redux store. + * + * @param {AppState} state - The current state of the Redux store. + * @returns {boolean} Returns true if the profile syncing update is loading, false otherwise. + */ +export const selectHasFinishedAddingAccountsWithBalance = createSelector( + [getMetamask], + (metamask) => { + return metamask.hasFinishedAddingAccountsWithBalance; + }, +); diff --git a/ui/store/actions.test.js b/ui/store/actions.test.js index 22e8db2fa281..10391adad3df 100644 --- a/ui/store/actions.test.js +++ b/ui/store/actions.test.js @@ -4,6 +4,7 @@ import thunk from 'redux-thunk'; import { EthAccountType } from '@metamask/keyring-api'; import { TransactionStatus } from '@metamask/transaction-controller'; import { NotificationServicesController } from '@metamask/notification-services-controller'; +import { USER_STORAGE_FEATURE_NAMES } from '@metamask/profile-sync-controller/sdk'; // TODO: Remove restricted import // eslint-disable-next-line import/no-restricted-paths import enLocale from '../../app/_locales/en/messages.json'; @@ -2605,7 +2606,9 @@ describe('Actions', () => { await store.dispatch(actions.deleteAccountSyncingDataFromUserStorage()); expect( - deleteAccountSyncingDataFromUserStorageStub.calledOnceWith('accounts'), + deleteAccountSyncingDataFromUserStorageStub.calledOnceWith( + USER_STORAGE_FEATURE_NAMES.accounts, + ), ).toBe(true); }); }); diff --git a/ui/store/actions.ts b/ui/store/actions.ts index 67de497817c8..200309554833 100644 --- a/ui/store/actions.ts +++ b/ui/store/actions.ts @@ -42,6 +42,7 @@ import { import { InterfaceState } from '@metamask/snaps-sdk'; import { KeyringTypes } from '@metamask/keyring-controller'; import type { NotificationServicesController } from '@metamask/notification-services-controller'; +import { USER_STORAGE_FEATURE_NAMES } from '@metamask/profile-sync-controller/sdk'; import { Patch } from 'immer'; import { HandlerType } from '@metamask/snaps-utils'; import switchDirection from '../../shared/lib/switch-direction'; @@ -5549,7 +5550,7 @@ export function deleteAccountSyncingDataFromUserStorage(): ThunkAction< try { const response = await submitRequestToBackground( 'deleteAccountSyncingDataFromUserStorage', - ['accounts'], + [USER_STORAGE_FEATURE_NAMES.accounts], ); return response; } catch (error) { diff --git a/yarn.lock b/yarn.lock index b7afd1eb5884..bbc6a6539187 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5105,6 +5105,24 @@ __metadata: languageName: node linkType: hard +"@metamask/controller-utils@npm:^11.4.3": + version: 11.4.3 + resolution: "@metamask/controller-utils@npm:11.4.3" + dependencies: + "@ethereumjs/util": "npm:^8.1.0" + "@metamask/eth-query": "npm:^4.0.0" + "@metamask/ethjs-unit": "npm:^0.3.0" + "@metamask/utils": "npm:^10.0.0" + "@spruceid/siwe-parser": "npm:2.1.0" + "@types/bn.js": "npm:^5.1.5" + bignumber.js: "npm:^9.1.2" + bn.js: "npm:^5.2.1" + eth-ens-namehash: "npm:^2.0.8" + fast-deep-equal: "npm:^3.1.3" + checksum: 10/5703b0721daf679cf44affc690f2b313e40893b64b0aafaf203e69ee51438197cc3634ef7094145f580a8a8aaadcb79026b2fbd4065c1bb4a8c26627a2c4c69a + languageName: node + linkType: hard + "@metamask/design-tokens@npm:^4.0.0": version: 4.0.0 resolution: "@metamask/design-tokens@npm:4.0.0" @@ -5230,6 +5248,19 @@ __metadata: languageName: node linkType: hard +"@metamask/eth-block-tracker@npm:^11.0.2": + version: 11.0.2 + resolution: "@metamask/eth-block-tracker@npm:11.0.2" + dependencies: + "@metamask/eth-json-rpc-provider": "npm:^4.1.5" + "@metamask/safe-event-emitter": "npm:^3.1.1" + "@metamask/utils": "npm:^9.1.0" + json-rpc-random-id: "npm:^1.0.1" + pify: "npm:^5.0.0" + checksum: 10/11d22bd86056401aa41eff5a32e862f3644eaf03040d8aa54a95cb0c1dfd3e3ce7e650c25efabbe0954cc6ba5f92172c338b518df84f73c4601c4bbc960b588a + languageName: node + linkType: hard + "@metamask/eth-block-tracker@npm:^9.0.2": version: 9.0.3 resolution: "@metamask/eth-block-tracker@npm:9.0.3" @@ -5269,6 +5300,18 @@ __metadata: languageName: node linkType: hard +"@metamask/eth-json-rpc-infura@npm:^10.0.0": + version: 10.0.0 + resolution: "@metamask/eth-json-rpc-infura@npm:10.0.0" + dependencies: + "@metamask/eth-json-rpc-provider": "npm:^4.1.5" + "@metamask/json-rpc-engine": "npm:^10.0.0" + "@metamask/rpc-errors": "npm:^7.0.0" + "@metamask/utils": "npm:^9.1.0" + checksum: 10/17e0147ff86c48107983035e9bda4d16fba321ee0e29733347e9338a4c795c506a2ffd643c44c9d5334886696412cf288f852d06311fed0d76edc8847ee6b8de + languageName: node + linkType: hard + "@metamask/eth-json-rpc-infura@npm:^9.1.0": version: 9.1.0 resolution: "@metamask/eth-json-rpc-infura@npm:9.1.0" @@ -5320,6 +5363,25 @@ __metadata: languageName: node linkType: hard +"@metamask/eth-json-rpc-middleware@npm:^15.0.0": + version: 15.0.0 + resolution: "@metamask/eth-json-rpc-middleware@npm:15.0.0" + dependencies: + "@metamask/eth-block-tracker": "npm:^11.0.1" + "@metamask/eth-json-rpc-provider": "npm:^4.1.5" + "@metamask/eth-sig-util": "npm:^7.0.3" + "@metamask/json-rpc-engine": "npm:^10.0.0" + "@metamask/rpc-errors": "npm:^7.0.0" + "@metamask/utils": "npm:^9.1.0" + "@types/bn.js": "npm:^5.1.5" + bn.js: "npm:^5.2.1" + klona: "npm:^2.0.6" + pify: "npm:^5.0.0" + safe-stable-stringify: "npm:^2.4.3" + checksum: 10/3c48d34264c695535f2b4e819fb602d835b6ed37309116a06d04d1b706a7335e0205cd4ccdbf1d3e9dc15ebf40d88954a9a2dc18a91f223dcd6d6392e026a5e9 + languageName: node + linkType: hard + "@metamask/eth-json-rpc-middleware@patch:@metamask/eth-json-rpc-middleware@npm%3A14.0.1#~/.yarn/patches/@metamask-eth-json-rpc-middleware-npm-14.0.1-b6c2ccbe8c.patch": version: 14.0.1 resolution: "@metamask/eth-json-rpc-middleware@patch:@metamask/eth-json-rpc-middleware@npm%3A14.0.1#~/.yarn/patches/@metamask-eth-json-rpc-middleware-npm-14.0.1-b6c2ccbe8c.patch::version=14.0.1&hash=96e7e0" @@ -5374,6 +5436,19 @@ __metadata: languageName: node linkType: hard +"@metamask/eth-json-rpc-provider@npm:^4.1.5, @metamask/eth-json-rpc-provider@npm:^4.1.6": + version: 4.1.6 + resolution: "@metamask/eth-json-rpc-provider@npm:4.1.6" + dependencies: + "@metamask/json-rpc-engine": "npm:^10.0.1" + "@metamask/rpc-errors": "npm:^7.0.1" + "@metamask/safe-event-emitter": "npm:^3.0.0" + "@metamask/utils": "npm:^10.0.0" + uuid: "npm:^8.3.2" + checksum: 10/aeec2c362a5386357e9f8c707da9baa4326e83889633723656b6801b6461ea8ab8f020b0d9ed0bbc2d8fd6add4af4c99cc9c9a1cbedca267a033a9f19da41200 + languageName: node + linkType: hard + "@metamask/eth-ledger-bridge-keyring@npm:^5.0.1": version: 5.0.1 resolution: "@metamask/eth-ledger-bridge-keyring@npm:5.0.1" @@ -5776,6 +5851,27 @@ __metadata: languageName: node linkType: hard +"@metamask/keyring-controller@npm:^18.0.0": + version: 18.0.0 + resolution: "@metamask/keyring-controller@npm:18.0.0" + dependencies: + "@ethereumjs/util": "npm:^8.1.0" + "@keystonehq/metamask-airgapped-keyring": "npm:^0.14.1" + "@metamask/base-controller": "npm:^7.0.2" + "@metamask/browser-passworder": "npm:^4.3.0" + "@metamask/eth-hd-keyring": "npm:^7.0.4" + "@metamask/eth-sig-util": "npm:^8.0.0" + "@metamask/eth-simple-keyring": "npm:^6.0.5" + "@metamask/keyring-api": "npm:^8.1.3" + "@metamask/message-manager": "npm:^11.0.1" + "@metamask/utils": "npm:^10.0.0" + async-mutex: "npm:^0.5.0" + ethereumjs-wallet: "npm:^1.0.1" + immer: "npm:^9.0.6" + checksum: 10/c301e4e8b9ac9da914bfaa371a43342aa37f5bb8ad107bbbd92f1d21a13c22351619f8bd6176493b808f4194aa9934bce5618ff0aed12325933f4330cdfd308e + languageName: node + linkType: hard + "@metamask/logging-controller@npm:^6.0.0": version: 6.0.0 resolution: "@metamask/logging-controller@npm:6.0.0" @@ -5871,6 +5967,31 @@ __metadata: languageName: node linkType: hard +"@metamask/network-controller@npm:^22.0.2": + version: 22.0.2 + resolution: "@metamask/network-controller@npm:22.0.2" + dependencies: + "@metamask/base-controller": "npm:^7.0.2" + "@metamask/controller-utils": "npm:^11.4.3" + "@metamask/eth-block-tracker": "npm:^11.0.2" + "@metamask/eth-json-rpc-infura": "npm:^10.0.0" + "@metamask/eth-json-rpc-middleware": "npm:^15.0.0" + "@metamask/eth-json-rpc-provider": "npm:^4.1.6" + "@metamask/eth-query": "npm:^4.0.0" + "@metamask/json-rpc-engine": "npm:^10.0.1" + "@metamask/rpc-errors": "npm:^7.0.1" + "@metamask/swappable-obj-proxy": "npm:^2.2.0" + "@metamask/utils": "npm:^10.0.0" + async-mutex: "npm:^0.5.0" + immer: "npm:^9.0.6" + loglevel: "npm:^1.8.1" + reselect: "npm:^5.1.1" + uri-js: "npm:^4.4.1" + uuid: "npm:^8.3.2" + checksum: 10/9da27189a4263ef7fa4596ada2000d7f944bc3f4dea63a77cf6f8b2ea89412d499068cf0542785088d19437263bd0b3b3bb3299533f87439729ccd8ecee2b625 + languageName: node + linkType: hard + "@metamask/network-controller@patch:@metamask/network-controller@npm%3A21.0.0#~/.yarn/patches/@metamask-network-controller-npm-21.0.0-559aa8e395.patch": version: 21.0.0 resolution: "@metamask/network-controller@patch:@metamask/network-controller@npm%3A21.0.0#~/.yarn/patches/@metamask-network-controller-npm-21.0.0-559aa8e395.patch::version=21.0.0&hash=1a5039" @@ -6169,13 +6290,14 @@ __metadata: languageName: node linkType: hard -"@metamask/profile-sync-controller@npm:^0.9.7": - version: 0.9.7 - resolution: "@metamask/profile-sync-controller@npm:0.9.7" +"@metamask/profile-sync-controller@npm:^1.0.1": + version: 1.0.1 + resolution: "@metamask/profile-sync-controller@npm:1.0.1" dependencies: - "@metamask/base-controller": "npm:^7.0.1" + "@metamask/base-controller": "npm:^7.0.2" "@metamask/keyring-api": "npm:^8.1.3" - "@metamask/keyring-controller": "npm:^17.2.2" + "@metamask/keyring-controller": "npm:^18.0.0" + "@metamask/network-controller": "npm:^22.0.2" "@metamask/snaps-sdk": "npm:^6.5.0" "@metamask/snaps-utils": "npm:^8.1.1" "@noble/ciphers": "npm:^0.5.2" @@ -6184,10 +6306,11 @@ __metadata: loglevel: "npm:^1.8.1" siwe: "npm:^2.3.2" peerDependencies: - "@metamask/accounts-controller": ^18.1.1 - "@metamask/keyring-controller": ^17.2.0 + "@metamask/accounts-controller": ^19.0.0 + "@metamask/keyring-controller": ^18.0.0 + "@metamask/network-controller": ^22.0.0 "@metamask/snaps-controllers": ^9.7.0 - checksum: 10/e53888533b2aae937bbe4e385dca2617c324b34e3e60af218cd98c26d514fb725f4c67b649f126e055f6a50a554817b229d37488115b98d70e8aee7b3a910bde + checksum: 10/4a4b0e95e4a03d1ba45343dae38c2c2670ab4931f742f1b3eca0af3d8736204b84258621917b30987e8ab6e633d9b9c255acb989609b9c6d2d341441b73b4ade languageName: node linkType: hard @@ -26755,7 +26878,7 @@ __metadata: "@metamask/ppom-validator": "npm:0.35.1" "@metamask/preferences-controller": "npm:^13.0.2" "@metamask/preinstalled-example-snap": "npm:^0.2.0" - "@metamask/profile-sync-controller": "npm:^0.9.7" + "@metamask/profile-sync-controller": "npm:^1.0.1" "@metamask/providers": "npm:^14.0.2" "@metamask/queued-request-controller": "npm:^7.0.1" "@metamask/rate-limit-controller": "npm:^6.0.0"