diff --git a/.yarn/patches/@metamask-snaps-controllers-npm-8.4.0-574cd5a8a9.patch b/.yarn/patches/@metamask-snaps-controllers-npm-9.2.0-09a31bab4f.patch similarity index 76% rename from .yarn/patches/@metamask-snaps-controllers-npm-8.4.0-574cd5a8a9.patch rename to .yarn/patches/@metamask-snaps-controllers-npm-9.2.0-09a31bab4f.patch index 4f07bb41d1ff..ab75de4b5022 100644 --- a/.yarn/patches/@metamask-snaps-controllers-npm-8.4.0-574cd5a8a9.patch +++ b/.yarn/patches/@metamask-snaps-controllers-npm-9.2.0-09a31bab4f.patch @@ -1,5 +1,5 @@ diff --git a/package.json b/package.json -index 6dedde043d1bd5fc195e72b3e06ec37cf6532476..3986b5b0c1f3bf7ff49e023c934bed26f44735ae 100644 +index 7e70a2db3606d673d92d38a2755e155bf2af5fd3..67cff443e63bd04e5699279d8101ef4ec547e9cb 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ diff --git a/app/scripts/controllers/authentication/auth-snap-requests.ts b/app/scripts/controllers/authentication/auth-snap-requests.ts deleted file mode 100644 index 81c8beaafd40..000000000000 --- a/app/scripts/controllers/authentication/auth-snap-requests.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { SnapId } from '@metamask/snaps-sdk'; -import type { HandleSnapRequest } from '@metamask/snaps-controllers'; -import { HandlerType } from '@metamask/snaps-utils'; - -type SnapRPCRequest = Parameters[0]; - -const snapId = 'npm:@metamask/message-signing-snap' as SnapId; - -export function createSnapPublicKeyRequest(): SnapRPCRequest { - return { - snapId, - origin: '', - handler: HandlerType.OnRpcRequest, - request: { - method: 'getPublicKey', - }, - }; -} - -export function createSnapSignMessageRequest( - message: `metamask:${string}`, -): SnapRPCRequest { - return { - snapId, - origin: '', - handler: HandlerType.OnRpcRequest, - request: { - method: 'signMessage', - params: { message }, - }, - }; -} diff --git a/app/scripts/controllers/authentication/authentication-controller.test.ts b/app/scripts/controllers/authentication/authentication-controller.test.ts deleted file mode 100644 index 1cd01dfb74fc..000000000000 --- a/app/scripts/controllers/authentication/authentication-controller.test.ts +++ /dev/null @@ -1,409 +0,0 @@ -import { ControllerMessenger } from '@metamask/base-controller'; -import AuthenticationController, { - AllowedActions, - AllowedEvents, - AuthenticationControllerState, -} from './authentication-controller'; -import { - mockEndpointAccessToken, - mockEndpointGetNonce, - mockEndpointLogin, -} from './mocks/mockServices'; -import { MOCK_ACCESS_TOKEN, MOCK_LOGIN_RESPONSE } from './mocks/mockResponses'; - -const mockSignedInState = (): AuthenticationControllerState => ({ - isSignedIn: true, - sessionData: { - accessToken: 'MOCK_ACCESS_TOKEN', - expiresIn: new Date().toString(), - profile: { - identifierId: MOCK_LOGIN_RESPONSE.profile.identifier_id, - profileId: MOCK_LOGIN_RESPONSE.profile.profile_id, - }, - }, -}); - -describe('authentication/authentication-controller - constructor() tests', () => { - test('should initialize with default state', () => { - const metametrics = createMockAuthMetaMetrics(); - const controller = new AuthenticationController({ - messenger: createMockAuthenticationMessenger().messenger, - metametrics, - }); - - expect(controller.state.isSignedIn).toBe(false); - expect(controller.state.sessionData).toBeUndefined(); - }); - - test('should initialize with override state', () => { - const metametrics = createMockAuthMetaMetrics(); - const controller = new AuthenticationController({ - messenger: createMockAuthenticationMessenger().messenger, - state: mockSignedInState(), - metametrics, - }); - - expect(controller.state.isSignedIn).toBe(true); - expect(controller.state.sessionData).toBeDefined(); - }); -}); - -describe('authentication/authentication-controller - performSignIn() tests', () => { - test('Should create access token and update state', async () => { - const metametrics = createMockAuthMetaMetrics(); - const mockEndpoints = mockAuthenticationFlowEndpoints(); - const { messenger, mockSnapGetPublicKey, mockSnapSignMessage } = - createMockAuthenticationMessenger(); - - const controller = new AuthenticationController({ messenger, metametrics }); - - const result = await controller.performSignIn(); - expect(mockSnapGetPublicKey).toBeCalled(); - expect(mockSnapSignMessage).toBeCalled(); - mockEndpoints.mockGetNonceEndpoint.done(); - mockEndpoints.mockLoginEndpoint.done(); - mockEndpoints.mockAccessTokenEndpoint.done(); - expect(result).toBe(MOCK_ACCESS_TOKEN); - - // Assert - state shows user is logged in - expect(controller.state.isSignedIn).toBe(true); - expect(controller.state.sessionData).toBeDefined(); - }); - - test('Should error when nonce endpoint fails', async () => { - await testAndAssertFailingEndpoints('nonce'); - }); - - test('Should error when login endpoint fails', async () => { - await testAndAssertFailingEndpoints('login'); - }); - - test('Should error when tokens endpoint fails', async () => { - await testAndAssertFailingEndpoints('token'); - }); - - // When the wallet is locked, we are unable to call the snap - test('Should error when wallet is locked', async () => { - const { messenger, mockKeyringControllerGetState } = - createMockAuthenticationMessenger(); - const metametrics = createMockAuthMetaMetrics(); - - // Mock wallet is locked - mockKeyringControllerGetState.mockReturnValue({ isUnlocked: false }); - - const controller = new AuthenticationController({ messenger, metametrics }); - - await expect(controller.performSignIn()).rejects.toThrow(); - }); - - async function testAndAssertFailingEndpoints( - endpointFail: 'nonce' | 'login' | 'token', - ) { - const mockEndpoints = mockAuthenticationFlowEndpoints({ - endpointFail, - }); - const { messenger } = createMockAuthenticationMessenger(); - const metametrics = createMockAuthMetaMetrics(); - const controller = new AuthenticationController({ messenger, metametrics }); - - await expect(controller.performSignIn()).rejects.toThrow(); - expect(controller.state.isSignedIn).toBe(false); - - const endpointsCalled = [ - mockEndpoints.mockGetNonceEndpoint.isDone(), - mockEndpoints.mockLoginEndpoint.isDone(), - mockEndpoints.mockAccessTokenEndpoint.isDone(), - ]; - if (endpointFail === 'nonce') { - expect(endpointsCalled).toEqual([true, false, false]); - } - - if (endpointFail === 'login') { - expect(endpointsCalled).toEqual([true, true, false]); - } - - if (endpointFail === 'token') { - expect(endpointsCalled).toEqual([true, true, true]); - } - } -}); - -describe('authentication/authentication-controller - performSignOut() tests', () => { - test('Should remove signed in user and any access tokens', () => { - const metametrics = createMockAuthMetaMetrics(); - const { messenger } = createMockAuthenticationMessenger(); - const controller = new AuthenticationController({ - messenger, - state: mockSignedInState(), - metametrics, - }); - - controller.performSignOut(); - expect(controller.state.isSignedIn).toBe(false); - expect(controller.state.sessionData).toBeUndefined(); - }); - - test('Should throw error if attempting to sign out when user is not logged in', () => { - const metametrics = createMockAuthMetaMetrics(); - const { messenger } = createMockAuthenticationMessenger(); - const controller = new AuthenticationController({ - messenger, - state: { isSignedIn: false }, - metametrics, - }); - - expect(() => controller.performSignOut()).toThrow(); - }); -}); - -describe('authentication/authentication-controller - getBearerToken() tests', () => { - test('Should throw error if not logged in', async () => { - const metametrics = createMockAuthMetaMetrics(); - const { messenger } = createMockAuthenticationMessenger(); - const controller = new AuthenticationController({ - messenger, - state: { isSignedIn: false }, - metametrics, - }); - - await expect(controller.getBearerToken()).rejects.toThrow(); - }); - - test('Should return original access token in state', async () => { - const metametrics = createMockAuthMetaMetrics(); - const { messenger } = createMockAuthenticationMessenger(); - const originalState = mockSignedInState(); - const controller = new AuthenticationController({ - messenger, - state: originalState, - metametrics, - }); - - const result = await controller.getBearerToken(); - expect(result).toBeDefined(); - expect(result).toBe(originalState.sessionData?.accessToken); - }); - - test('Should return new access token if state is invalid', async () => { - const metametrics = createMockAuthMetaMetrics(); - const { messenger } = createMockAuthenticationMessenger(); - mockAuthenticationFlowEndpoints(); - - // Invalid/old state - const originalState = mockSignedInState(); - if (originalState.sessionData) { - originalState.sessionData.accessToken = 'ACCESS_TOKEN_1'; - - const d = new Date(); - d.setMinutes(d.getMinutes() - 31); // expires at 30 mins - originalState.sessionData.expiresIn = d.toString(); - } - - const controller = new AuthenticationController({ - messenger, - state: originalState, - metametrics, - }); - - const result = await controller.getBearerToken(); - expect(result).toBeDefined(); - expect(result).toBe(MOCK_ACCESS_TOKEN); - }); - - // If the state is invalid, we need to re-login. - // But as wallet is locked, we will not be able to call the snap - test('Should throw error if wallet is locked', async () => { - const metametrics = createMockAuthMetaMetrics(); - const { messenger, mockKeyringControllerGetState } = - createMockAuthenticationMessenger(); - mockAuthenticationFlowEndpoints(); - - // Invalid/old state - const originalState = mockSignedInState(); - if (originalState.sessionData) { - originalState.sessionData.accessToken = 'ACCESS_TOKEN_1'; - - const d = new Date(); - d.setMinutes(d.getMinutes() - 31); // expires at 30 mins - originalState.sessionData.expiresIn = d.toString(); - } - - // Mock wallet is locked - mockKeyringControllerGetState.mockReturnValue({ isUnlocked: false }); - - const controller = new AuthenticationController({ - messenger, - state: originalState, - metametrics, - }); - - await expect(controller.getBearerToken()).rejects.toThrow(); - }); -}); - -describe('authentication/authentication-controller - getSessionProfile() tests', () => { - test('Should throw error if not logged in', async () => { - const metametrics = createMockAuthMetaMetrics(); - const { messenger } = createMockAuthenticationMessenger(); - const controller = new AuthenticationController({ - messenger, - state: { isSignedIn: false }, - metametrics, - }); - - await expect(controller.getSessionProfile()).rejects.toThrow(); - }); - - test('Should return original access token in state', async () => { - const metametrics = createMockAuthMetaMetrics(); - const { messenger } = createMockAuthenticationMessenger(); - const originalState = mockSignedInState(); - const controller = new AuthenticationController({ - messenger, - state: originalState, - metametrics, - }); - - const result = await controller.getSessionProfile(); - expect(result).toBeDefined(); - expect(result).toEqual(originalState.sessionData?.profile); - }); - - test('Should return new access token if state is invalid', async () => { - const metametrics = createMockAuthMetaMetrics(); - const { messenger } = createMockAuthenticationMessenger(); - mockAuthenticationFlowEndpoints(); - - // Invalid/old state - const originalState = mockSignedInState(); - if (originalState.sessionData) { - originalState.sessionData.profile.identifierId = 'ID_1'; - - const d = new Date(); - d.setMinutes(d.getMinutes() - 31); // expires at 30 mins - originalState.sessionData.expiresIn = d.toString(); - } - - const controller = new AuthenticationController({ - messenger, - state: originalState, - metametrics, - }); - - const result = await controller.getSessionProfile(); - expect(result).toBeDefined(); - expect(result.identifierId).toBe(MOCK_LOGIN_RESPONSE.profile.identifier_id); - expect(result.profileId).toBe(MOCK_LOGIN_RESPONSE.profile.profile_id); - }); - - // If the state is invalid, we need to re-login. - // But as wallet is locked, we will not be able to call the snap - test('Should throw error if wallet is locked', async () => { - const metametrics = createMockAuthMetaMetrics(); - const { messenger, mockKeyringControllerGetState } = - createMockAuthenticationMessenger(); - mockAuthenticationFlowEndpoints(); - - // Invalid/old state - const originalState = mockSignedInState(); - if (originalState.sessionData) { - originalState.sessionData.profile.identifierId = 'ID_1'; - - const d = new Date(); - d.setMinutes(d.getMinutes() - 31); // expires at 30 mins - originalState.sessionData.expiresIn = d.toString(); - } - - // Mock wallet is locked - mockKeyringControllerGetState.mockReturnValue({ isUnlocked: false }); - - const controller = new AuthenticationController({ - messenger, - state: originalState, - metametrics, - }); - - await expect(controller.getSessionProfile()).rejects.toThrow(); - }); -}); - -function createAuthenticationMessenger() { - const messenger = new ControllerMessenger(); - return messenger.getRestricted({ - name: 'AuthenticationController', - allowedActions: [ - `SnapController:handleRequest`, - 'KeyringController:getState', - ], - allowedEvents: ['KeyringController:lock', 'KeyringController:unlock'], - }); -} - -function createMockAuthenticationMessenger() { - const messenger = createAuthenticationMessenger(); - const mockCall = jest.spyOn(messenger, 'call'); - const mockSnapGetPublicKey = jest.fn().mockResolvedValue('MOCK_PUBLIC_KEY'); - const mockSnapSignMessage = jest - .fn() - .mockResolvedValue('MOCK_SIGNED_MESSAGE'); - - const mockKeyringControllerGetState = jest - .fn() - .mockReturnValue({ isUnlocked: true }); - - mockCall.mockImplementation((...args) => { - const [actionType, params] = args; - if (actionType === 'SnapController:handleRequest') { - if (params?.request.method === 'getPublicKey') { - return mockSnapGetPublicKey(); - } - - if (params?.request.method === 'signMessage') { - return mockSnapSignMessage(); - } - - throw new Error( - `MOCK_FAIL - unsupported SnapController:handleRequest call: ${params?.request.method}`, - ); - } - - if (actionType === 'KeyringController:getState') { - return mockKeyringControllerGetState(); - } - - throw new Error(`MOCK_FAIL - unsupported messenger call: ${actionType}`); - }); - - return { - messenger, - mockSnapGetPublicKey, - mockSnapSignMessage, - mockKeyringControllerGetState, - }; -} - -function mockAuthenticationFlowEndpoints(params?: { - endpointFail: 'nonce' | 'login' | 'token'; -}) { - const mockGetNonceEndpoint = mockEndpointGetNonce( - params?.endpointFail === 'nonce' ? { status: 500 } : undefined, - ); - const mockLoginEndpoint = mockEndpointLogin( - params?.endpointFail === 'login' ? { status: 500 } : undefined, - ); - const mockAccessTokenEndpoint = mockEndpointAccessToken( - params?.endpointFail === 'token' ? { status: 500 } : undefined, - ); - - return { - mockGetNonceEndpoint, - mockLoginEndpoint, - mockAccessTokenEndpoint, - }; -} - -function createMockAuthMetaMetrics() { - const getMetaMetricsId = jest.fn().mockReturnValue('MOCK_METAMETRICS_ID'); - - return { getMetaMetricsId }; -} diff --git a/app/scripts/controllers/authentication/authentication-controller.ts b/app/scripts/controllers/authentication/authentication-controller.ts deleted file mode 100644 index 4bcb1528c323..000000000000 --- a/app/scripts/controllers/authentication/authentication-controller.ts +++ /dev/null @@ -1,384 +0,0 @@ -import { - BaseController, - RestrictedControllerMessenger, - StateMetadata, -} from '@metamask/base-controller'; -import type { - KeyringControllerGetStateAction, - KeyringControllerLockEvent, - KeyringControllerUnlockEvent, -} from '@metamask/keyring-controller'; -import { HandleSnapRequest } from '@metamask/snaps-controllers'; -import { - createSnapPublicKeyRequest, - createSnapSignMessageRequest, -} from './auth-snap-requests'; -import { - createLoginRawMessage, - getAccessToken, - getNonce, - login, -} from './services'; - -const THIRTY_MIN_MS = 1000 * 60 * 30; - -const controllerName = 'AuthenticationController'; - -// State -type SessionProfile = { - identifierId: string; - profileId: string; -}; - -type SessionData = { - /** profile - anonymous profile data for the given logged in user */ - profile: SessionProfile; - /** accessToken - used to make requests authorized endpoints */ - accessToken: string; - /** expiresIn - string date to determine if new access token is required */ - expiresIn: string; -}; - -type MetaMetricsAuth = { - getMetaMetricsId: () => string; -}; - -export type AuthenticationControllerState = { - /** - * Global isSignedIn state. - * Can be used to determine if "Profile Syncing" is enabled. - */ - isSignedIn: boolean; - sessionData?: SessionData; -}; -const defaultState: AuthenticationControllerState = { isSignedIn: false }; -const metadata: StateMetadata = { - isSignedIn: { - persist: true, - anonymous: true, - }, - sessionData: { - persist: true, - anonymous: false, - }, -}; - -// Messenger Actions -type CreateActionsObj = { - [K in T]: { - type: `${typeof controllerName}:${K}`; - handler: AuthenticationController[K]; - }; -}; -type ActionsObj = CreateActionsObj< - | 'performSignIn' - | 'performSignOut' - | 'getBearerToken' - | 'getSessionProfile' - | 'isSignedIn' ->; -export type Actions = ActionsObj[keyof ActionsObj]; -export type AuthenticationControllerPerformSignIn = ActionsObj['performSignIn']; -export type AuthenticationControllerPerformSignOut = - ActionsObj['performSignOut']; -export type AuthenticationControllerGetBearerToken = - ActionsObj['getBearerToken']; -export type AuthenticationControllerGetSessionProfile = - ActionsObj['getSessionProfile']; -export type AuthenticationControllerIsSignedIn = ActionsObj['isSignedIn']; - -// Allowed Actions -export type AllowedActions = - | HandleSnapRequest - | KeyringControllerGetStateAction; - -export type AllowedEvents = - | KeyringControllerLockEvent - | KeyringControllerUnlockEvent; - -// Messenger -export type AuthenticationControllerMessenger = RestrictedControllerMessenger< - typeof controllerName, - Actions | AllowedActions, - AllowedEvents, - AllowedActions['type'], - AllowedEvents['type'] ->; - -/** - * Controller that enables authentication for restricted endpoints. - * Used for Global Profile Syncing and Notifications - */ -export default class AuthenticationController extends BaseController< - typeof controllerName, - AuthenticationControllerState, - AuthenticationControllerMessenger -> { - #metametrics: MetaMetricsAuth; - - #isUnlocked = false; - - #keyringController = { - setupLockedStateSubscriptions: () => { - const { isUnlocked } = this.messagingSystem.call( - 'KeyringController:getState', - ); - this.#isUnlocked = isUnlocked; - - this.messagingSystem.subscribe('KeyringController:unlock', () => { - this.#isUnlocked = true; - }); - - this.messagingSystem.subscribe('KeyringController:lock', () => { - this.#isUnlocked = false; - }); - }, - }; - - constructor({ - messenger, - state, - metametrics, - }: { - messenger: AuthenticationControllerMessenger; - state?: AuthenticationControllerState; - /** - * Not using the Messaging System as we - * do not want to tie this strictly to extension - */ - metametrics: MetaMetricsAuth; - }) { - super({ - messenger, - metadata, - name: controllerName, - state: { ...defaultState, ...state }, - }); - - this.#metametrics = metametrics; - - this.#keyringController.setupLockedStateSubscriptions(); - this.#registerMessageHandlers(); - } - - /** - * Constructor helper for registering this controller's messaging system - * actions. - */ - #registerMessageHandlers(): void { - this.messagingSystem.registerActionHandler( - 'AuthenticationController:getBearerToken', - this.getBearerToken.bind(this), - ); - - this.messagingSystem.registerActionHandler( - 'AuthenticationController:getSessionProfile', - this.getSessionProfile.bind(this), - ); - - this.messagingSystem.registerActionHandler( - 'AuthenticationController:isSignedIn', - this.isSignedIn.bind(this), - ); - - this.messagingSystem.registerActionHandler( - 'AuthenticationController:performSignIn', - this.performSignIn.bind(this), - ); - - this.messagingSystem.registerActionHandler( - 'AuthenticationController:performSignOut', - this.performSignOut.bind(this), - ); - } - - public async performSignIn(): Promise { - const { accessToken } = await this.#performAuthenticationFlow(); - return accessToken; - } - - public performSignOut(): void { - this.#assertLoggedIn(); - - this.update((state) => { - state.isSignedIn = false; - state.sessionData = undefined; - }); - } - - public async getBearerToken(): Promise { - this.#assertLoggedIn(); - - if (this.#hasValidSession(this.state.sessionData)) { - return this.state.sessionData.accessToken; - } - - const { accessToken } = await this.#performAuthenticationFlow(); - return accessToken; - } - - /** - * Will return a session profile. - * Throws if a user is not logged in. - * - * @returns profile for the session. - */ - public async getSessionProfile(): Promise { - this.#assertLoggedIn(); - - if (this.#hasValidSession(this.state.sessionData)) { - return this.state.sessionData.profile; - } - - const { profile } = await this.#performAuthenticationFlow(); - return profile; - } - - public isSignedIn(): boolean { - return this.state.isSignedIn; - } - - #assertLoggedIn(): void { - if (!this.state.isSignedIn) { - throw new Error( - `${controllerName}: Unable to call method, user is not authenticated`, - ); - } - } - - async #performAuthenticationFlow(): Promise<{ - profile: SessionProfile; - accessToken: string; - }> { - try { - // 1. Nonce - const publicKey = await this.#snapGetPublicKey(); - const nonce = await getNonce(publicKey); - if (!nonce) { - throw new Error(`Unable to get nonce`); - } - - // 2. Login - const rawMessage = createLoginRawMessage(nonce, publicKey); - const signature = await this.#snapSignMessage(rawMessage); - const loginResponse = await login( - rawMessage, - signature, - this.#metametrics.getMetaMetricsId(), - ); - if (!loginResponse?.token) { - throw new Error(`Unable to login`); - } - - const profile: SessionProfile = { - identifierId: loginResponse.profile.identifier_id, - profileId: loginResponse.profile.profile_id, - }; - - // 3. Trade for Access Token - const accessToken = await getAccessToken(loginResponse.token); - if (!accessToken) { - throw new Error(`Unable to get Access Token`); - } - - // Update Internal State - this.update((state) => { - state.isSignedIn = true; - const expiresIn = new Date(); - expiresIn.setTime(expiresIn.getTime() + THIRTY_MIN_MS); - state.sessionData = { - profile, - accessToken, - expiresIn: expiresIn.toString(), - }; - }); - - return { - profile, - accessToken, - }; - } catch (e) { - console.error('Failed to authenticate', e); - const errorMessage = - e instanceof Error ? e.message : JSON.stringify(e ?? ''); - throw new Error( - `${controllerName}: Failed to authenticate - ${errorMessage}`, - ); - } - } - - #hasValidSession( - sessionData: SessionData | undefined, - ): sessionData is SessionData { - if (!sessionData) { - return false; - } - - const prevDate = Date.parse(sessionData.expiresIn); - if (isNaN(prevDate)) { - return false; - } - - const currentDate = new Date(); - const diffMs = Math.abs(currentDate.getTime() - prevDate); - - return THIRTY_MIN_MS > diffMs; - } - - #_snapPublicKeyCache: string | undefined; - - /** - * Returns the auth snap public key. - * - * @returns The snap public key. - */ - async #snapGetPublicKey(): Promise { - if (this.#_snapPublicKeyCache) { - return this.#_snapPublicKeyCache; - } - - if (!this.#isUnlocked) { - throw new Error( - '#snapGetPublicKey - unable to call snap, wallet is locked', - ); - } - - const result = (await this.messagingSystem.call( - 'SnapController:handleRequest', - createSnapPublicKeyRequest(), - )) as string; - - this.#_snapPublicKeyCache = result; - - return result; - } - - #_snapSignMessageCache: Record<`metamask:${string}`, string> = {}; - - /** - * Signs a specific message using an underlying auth snap. - * - * @param message - A specific tagged message to sign. - * @returns A Signature created by the snap. - */ - async #snapSignMessage(message: `metamask:${string}`): Promise { - if (this.#_snapSignMessageCache[message]) { - return this.#_snapSignMessageCache[message]; - } - - if (!this.#isUnlocked) { - throw new Error( - '#snapSignMessage - unable to call snap, wallet is locked', - ); - } - - const result = (await this.messagingSystem.call( - 'SnapController:handleRequest', - createSnapSignMessageRequest(message), - )) as string; - - this.#_snapSignMessageCache[message] = result; - - return result; - } -} diff --git a/app/scripts/controllers/authentication/mocks/mockResponses.ts b/app/scripts/controllers/authentication/mocks/mockResponses.ts deleted file mode 100644 index 4882bd81d812..000000000000 --- a/app/scripts/controllers/authentication/mocks/mockResponses.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { - AUTH_LOGIN_ENDPOINT, - AUTH_NONCE_ENDPOINT, - LoginResponse, - NonceResponse, - OAuthTokenResponse, - OIDC_TOKENS_ENDPOINT, -} from '../services'; - -type MockResponse = { - url: string; - requestMethod: 'GET' | 'POST' | 'PUT'; - response: unknown; -}; - -export const MOCK_NONCE = '4cbfqzoQpcNxVImGv'; -export const MOCK_NONCE_RESPONSE: NonceResponse = { - nonce: MOCK_NONCE, -}; - -export function getMockAuthNonceResponse() { - return { - url: AUTH_NONCE_ENDPOINT, - requestMethod: 'GET', - response: MOCK_NONCE_RESPONSE, - } satisfies MockResponse; -} - -export const MOCK_JWT = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'; -export const MOCK_LOGIN_RESPONSE: LoginResponse = { - token: MOCK_JWT, - expires_in: new Date().toString(), - profile: { - identifier_id: 'MOCK_IDENTIFIER', - profile_id: 'MOCK_PROFILE_ID', - }, -}; - -export function getMockAuthLoginResponse() { - return { - url: AUTH_LOGIN_ENDPOINT, - requestMethod: 'POST', - response: MOCK_LOGIN_RESPONSE, - } satisfies MockResponse; -} - -export const MOCK_ACCESS_TOKEN = `MOCK_ACCESS_TOKEN-${MOCK_JWT}`; -export const MOCK_OATH_TOKEN_RESPONSE: OAuthTokenResponse = { - access_token: MOCK_ACCESS_TOKEN, - expires_in: new Date().getTime(), -}; - -export function getMockAuthAccessTokenResponse() { - return { - url: OIDC_TOKENS_ENDPOINT, - requestMethod: 'POST', - response: MOCK_OATH_TOKEN_RESPONSE, - } satisfies MockResponse; -} diff --git a/app/scripts/controllers/authentication/mocks/mockServices.ts b/app/scripts/controllers/authentication/mocks/mockServices.ts deleted file mode 100644 index 69d3cf56c5d5..000000000000 --- a/app/scripts/controllers/authentication/mocks/mockServices.ts +++ /dev/null @@ -1,42 +0,0 @@ -import nock from 'nock'; -import { - getMockAuthAccessTokenResponse, - getMockAuthLoginResponse, - getMockAuthNonceResponse, -} from './mockResponses'; - -type MockReply = { - status: nock.StatusCode; - body?: nock.Body; -}; - -export function mockEndpointGetNonce(mockReply?: MockReply) { - const mockResponse = getMockAuthNonceResponse(); - const reply = mockReply ?? { status: 200, body: mockResponse.response }; - const mockNonceEndpoint = nock(mockResponse.url) - .get('') - .query(true) - .reply(reply.status, reply.body); - - return mockNonceEndpoint; -} - -export function mockEndpointLogin(mockReply?: MockReply) { - const mockResponse = getMockAuthLoginResponse(); - const reply = mockReply ?? { status: 200, body: mockResponse.response }; - const mockLoginEndpoint = nock(mockResponse.url) - .post('') - .reply(reply.status, reply.body); - - return mockLoginEndpoint; -} - -export function mockEndpointAccessToken(mockReply?: MockReply) { - const mockResponse = getMockAuthAccessTokenResponse(); - const reply = mockReply ?? { status: 200, body: mockResponse.response }; - const mockOidcTokensEndpoint = nock(mockResponse.url) - .post('') - .reply(reply.status, reply.body); - - return mockOidcTokensEndpoint; -} diff --git a/app/scripts/controllers/authentication/services.test.ts b/app/scripts/controllers/authentication/services.test.ts deleted file mode 100644 index 759b3c535936..000000000000 --- a/app/scripts/controllers/authentication/services.test.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { MOCK_ACCESS_TOKEN, MOCK_JWT, MOCK_NONCE } from './mocks/mockResponses'; -import { - mockEndpointAccessToken, - mockEndpointGetNonce, - mockEndpointLogin, -} from './mocks/mockServices'; -import { - createLoginRawMessage, - getAccessToken, - getNonce, - login, -} from './services'; - -const MOCK_METAMETRICS_ID = '0x123'; - -describe('authentication/services.ts - getNonce() tests', () => { - test('returns nonce on valid request', async () => { - const mockNonceEndpoint = mockEndpointGetNonce(); - const response = await getNonce('MOCK_PUBLIC_KEY'); - - mockNonceEndpoint.done(); - expect(response).toBe(MOCK_NONCE); - }); - - test('returns null if request is invalid', async () => { - async function testInvalidResponse( - status: number, - body: Record, - ) { - const mockNonceEndpoint = mockEndpointGetNonce({ status, body }); - const response = await getNonce('MOCK_PUBLIC_KEY'); - - mockNonceEndpoint.done(); - expect(response).toBe(null); - } - - await testInvalidResponse(500, { error: 'mock server error' }); - await testInvalidResponse(400, { error: 'mock bad request' }); - }); -}); - -describe('authentication/services.ts - login() tests', () => { - test('returns single-use jwt if successful login', async () => { - const mockLoginEndpoint = mockEndpointLogin(); - const response = await login( - 'mock raw message', - 'mock signature', - MOCK_METAMETRICS_ID, - ); - - mockLoginEndpoint.done(); - expect(response?.token).toBe(MOCK_JWT); - expect(response?.profile).toBeDefined(); - }); - - test('returns null if request is invalid', async () => { - async function testInvalidResponse( - status: number, - body: Record, - ) { - const mockLoginEndpoint = mockEndpointLogin({ status, body }); - const response = await login( - 'mock raw message', - 'mock signature', - MOCK_METAMETRICS_ID, - ); - - mockLoginEndpoint.done(); - expect(response).toBe(null); - } - - await testInvalidResponse(500, { error: 'mock server error' }); - await testInvalidResponse(400, { error: 'mock bad request' }); - }); -}); - -describe('authentication/services.ts - getAccessToken() tests', () => { - test('returns access token jwt if successful OIDC token request', async () => { - const mockLoginEndpoint = mockEndpointAccessToken(); - const response = await getAccessToken('mock single-use jwt'); - - mockLoginEndpoint.done(); - expect(response).toBe(MOCK_ACCESS_TOKEN); - }); - - test('returns null if request is invalid', async () => { - async function testInvalidResponse( - status: number, - body: Record, - ) { - const mockLoginEndpoint = mockEndpointAccessToken({ status, body }); - const response = await getAccessToken('mock single-use jwt'); - - mockLoginEndpoint.done(); - expect(response).toBe(null); - } - - await testInvalidResponse(500, { error: 'mock server error' }); - await testInvalidResponse(400, { error: 'mock bad request' }); - }); -}); - -describe('authentication/services.ts - createLoginRawMessage() tests', () => { - test('creates the raw message format for login request', () => { - const message = createLoginRawMessage('NONCE', 'PUBLIC_KEY'); - expect(message).toBe('metamask:NONCE:PUBLIC_KEY'); - }); -}); diff --git a/app/scripts/controllers/authentication/services.ts b/app/scripts/controllers/authentication/services.ts deleted file mode 100644 index a4bbbf2f11cb..000000000000 --- a/app/scripts/controllers/authentication/services.ts +++ /dev/null @@ -1,120 +0,0 @@ -const AUTH_ENDPOINT = process.env.AUTH_API || ''; -export const AUTH_NONCE_ENDPOINT = `${AUTH_ENDPOINT}/api/v2/nonce`; -export const AUTH_LOGIN_ENDPOINT = `${AUTH_ENDPOINT}/api/v2/srp/login`; - -const OIDC_ENDPOINT = process.env.OIDC_API || ''; -export const OIDC_TOKENS_ENDPOINT = `${OIDC_ENDPOINT}/oauth2/token`; -const OIDC_CLIENT_ID = process.env.OIDC_CLIENT_ID || ''; -const OIDC_GRANT_TYPE = process.env.OIDC_GRANT_TYPE || ''; - -export type NonceResponse = { - nonce: string; -}; -export async function getNonce(publicKey: string): Promise { - const nonceUrl = new URL(AUTH_NONCE_ENDPOINT); - nonceUrl.searchParams.set('identifier', publicKey); - - try { - const nonceResponse = await fetch(nonceUrl.toString()); - if (!nonceResponse.ok) { - return null; - } - - const nonceJson: NonceResponse = await nonceResponse.json(); - return nonceJson?.nonce ?? null; - } catch (e) { - console.error('authentication-controller/services: unable to get nonce', e); - return null; - } -} - -export type LoginResponse = { - token: string; - expires_in: string; - /** - * Contains anonymous information about the logged in profile. - * - * @property identifier_id - a deterministic unique identifier on the method used to sign in - * @property profile_id - a unique id for a given profile - * @property metametrics_id - an anonymous server id - */ - profile: { - identifier_id: string; - profile_id: string; - }; -}; -export async function login( - rawMessage: string, - signature: string, - clientMetaMetricsId: string, -): Promise { - try { - const response = await fetch(AUTH_LOGIN_ENDPOINT, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - signature, - raw_message: rawMessage, - metametrics: { - metametrics_id: clientMetaMetricsId, - agent: 'extension', - }, - }), - }); - - if (!response.ok) { - return null; - } - - const loginResponse: LoginResponse = await response.json(); - return loginResponse ?? null; - } catch (e) { - console.error('authentication-controller/services: unable to login', e); - return null; - } -} - -export type OAuthTokenResponse = { - access_token: string; - expires_in: number; -}; -export async function getAccessToken(jwtToken: string): Promise { - const headers = new Headers({ - 'Content-Type': 'application/x-www-form-urlencoded', - }); - - const urlEncodedBody = new URLSearchParams(); - urlEncodedBody.append('grant_type', OIDC_GRANT_TYPE); - urlEncodedBody.append('client_id', OIDC_CLIENT_ID); - urlEncodedBody.append('assertion', jwtToken); - - try { - const response = await fetch(OIDC_TOKENS_ENDPOINT, { - method: 'POST', - headers, - body: urlEncodedBody.toString(), - }); - - if (!response.ok) { - return null; - } - - const accessTokenResponse: OAuthTokenResponse = await response.json(); - return accessTokenResponse?.access_token ?? null; - } catch (e) { - console.error( - 'authentication-controller/services: unable to get access token', - e, - ); - return null; - } -} - -export function createLoginRawMessage( - nonce: string, - publicKey: string, -): `metamask:${string}:${string}` { - return `metamask:${nonce}:${publicKey}` as const; -} diff --git a/app/scripts/controllers/metamask-notifications/metamask-notifications.test.ts b/app/scripts/controllers/metamask-notifications/metamask-notifications.test.ts index c96ccfe2e989..125b7f965bac 100644 --- a/app/scripts/controllers/metamask-notifications/metamask-notifications.test.ts +++ b/app/scripts/controllers/metamask-notifications/metamask-notifications.test.ts @@ -7,16 +7,9 @@ import { } from '@metamask/keyring-controller'; import { waitFor } from '@testing-library/react'; import { - AuthenticationControllerGetBearerToken, - AuthenticationControllerIsSignedIn, -} from '../authentication/authentication-controller'; -import { MOCK_ACCESS_TOKEN } from '../authentication/mocks/mockResponses'; -import { - UserStorageControllerGetStorageKey, - UserStorageControllerPerformGetStorage, - UserStorageControllerPerformSetStorage, - UserStorageControllerEnableProfileSyncing, -} from '../user-storage/user-storage-controller'; + AuthenticationController, + UserStorageController, +} from '@metamask/profile-sync-controller'; import { PushPlatformNotificationsControllerEnablePushNotifications, PushPlatformNotificationsControllerDisablePushNotifications, @@ -50,6 +43,8 @@ import * as OnChainNotifications from './services/onchain-notifications'; import { UserStorage } from './types/user-storage/user-storage'; import * as MetamaskNotificationsUtils from './utils/utils'; +const AuthMocks = AuthenticationController.Mocks; + // Mock type used for testing purposes // eslint-disable-next-line @typescript-eslint/no-explicit-any type MockVar = any; @@ -654,12 +649,14 @@ function mockNotificationMessenger() { typedMockAction().mockResolvedValue([]); const mockGetBearerToken = - typedMockAction().mockResolvedValue( - MOCK_ACCESS_TOKEN, + typedMockAction().mockResolvedValue( + AuthMocks.MOCK_ACCESS_TOKEN, ); const mockIsSignedIn = - typedMockAction().mockReturnValue(true); + typedMockAction().mockReturnValue( + true, + ); const mockDisablePushNotifications = typedMockAction(); @@ -671,20 +668,20 @@ function mockNotificationMessenger() { typedMockAction(); const mockGetStorageKey = - typedMockAction().mockResolvedValue( + typedMockAction().mockResolvedValue( 'MOCK_STORAGE_KEY', ); const mockEnableProfileSyncing = - typedMockAction(); + typedMockAction(); const mockPerformGetStorage = - typedMockAction().mockResolvedValue( + typedMockAction().mockResolvedValue( JSON.stringify(createMockFullUserStorage()), ); const mockPerformSetStorage = - typedMockAction(); + typedMockAction(); jest.spyOn(messenger, 'call').mockImplementation((...args) => { const [actionType] = args; @@ -746,10 +743,7 @@ function mockNotificationMessenger() { return { isUnlocked: true } as MockVar; } - function exhaustedMessengerMocks(action: never) { - return new Error(`MOCK_FAIL - unsupported messenger call: ${action}`); - } - throw exhaustedMessengerMocks(actionType); + throw new Error(`MOCK_FAIL - unsupported messenger call: ${actionType}`); }); return { diff --git a/app/scripts/controllers/metamask-notifications/metamask-notifications.ts b/app/scripts/controllers/metamask-notifications/metamask-notifications.ts index 6a28b96c0ad3..65b4aed86b37 100644 --- a/app/scripts/controllers/metamask-notifications/metamask-notifications.ts +++ b/app/scripts/controllers/metamask-notifications/metamask-notifications.ts @@ -14,21 +14,15 @@ import { KeyringControllerUnlockEvent, } from '@metamask/keyring-controller'; import { - AuthenticationControllerGetBearerToken, - AuthenticationControllerIsSignedIn, -} from '../authentication/authentication-controller'; + AuthenticationController, + UserStorageController, +} from '@metamask/profile-sync-controller'; import { PushPlatformNotificationsControllerEnablePushNotifications, PushPlatformNotificationsControllerDisablePushNotifications, PushPlatformNotificationsControllerUpdateTriggerPushNotifications, PushPlatformNotificationsControllerOnNewNotificationEvent, } from '../push-platform-notifications/push-platform-notifications'; -import { - UserStorageControllerEnableProfileSyncing, - UserStorageControllerGetStorageKey, - UserStorageControllerPerformGetStorage, - UserStorageControllerPerformSetStorage, -} from '../user-storage/user-storage-controller'; import { TRIGGER_TYPES, TRIGGER_TYPES_GROUPS, @@ -200,13 +194,13 @@ export type AllowedActions = | KeyringControllerGetAccountsAction | KeyringControllerGetStateAction // Auth Controller Requests - | AuthenticationControllerGetBearerToken - | AuthenticationControllerIsSignedIn + | AuthenticationController.AuthenticationControllerGetBearerToken + | AuthenticationController.AuthenticationControllerIsSignedIn // User Storage Controller Requests - | UserStorageControllerEnableProfileSyncing - | UserStorageControllerGetStorageKey - | UserStorageControllerPerformGetStorage - | UserStorageControllerPerformSetStorage + | UserStorageController.UserStorageControllerEnableProfileSyncing + | UserStorageController.UserStorageControllerGetStorageKey + | UserStorageController.UserStorageControllerPerformGetStorage + | UserStorageController.UserStorageControllerPerformSetStorage // Push Notifications Controller Requests | PushPlatformNotificationsControllerEnablePushNotifications | PushPlatformNotificationsControllerDisablePushNotifications @@ -295,13 +289,13 @@ export class MetamaskNotificationsController extends BaseController< getNotificationStorage: async () => { return await this.messagingSystem.call( 'UserStorageController:performGetStorage', - 'notification_settings', + 'notifications.notificationSettings', ); }, setNotificationStorage: async (state: string) => { return await this.messagingSystem.call( 'UserStorageController:performSetStorage', - 'notification_settings', + 'notifications.notificationSettings', state, ); }, diff --git a/app/scripts/controllers/metamask-notifications/services/onchain-notifications.ts b/app/scripts/controllers/metamask-notifications/services/onchain-notifications.ts index a43e678ac798..d4a95bf1aa4e 100644 --- a/app/scripts/controllers/metamask-notifications/services/onchain-notifications.ts +++ b/app/scripts/controllers/metamask-notifications/services/onchain-notifications.ts @@ -1,4 +1,5 @@ import log from 'loglevel'; +import { UserStorageController } from '@metamask/profile-sync-controller'; import type { UserStorage } from '../types/user-storage/user-storage'; import type { OnChainRawNotification } from '../types/on-chain-notification/on-chain-notification'; import { @@ -8,7 +9,8 @@ import { } from '../utils/utils'; import { TRIGGER_TYPES } from '../constants/notification-schema'; import type { components } from '../types/on-chain-notification/schema'; -import { createSHA256Hash } from '../../user-storage/encryption'; + +const { createSHA256Hash } = UserStorageController; export type NotificationTrigger = { id: string; diff --git a/app/scripts/controllers/push-platform-notifications/push-platform-notifications.test.ts b/app/scripts/controllers/push-platform-notifications/push-platform-notifications.test.ts index 2b0c97b5d9c2..2387c4dcfd9c 100644 --- a/app/scripts/controllers/push-platform-notifications/push-platform-notifications.test.ts +++ b/app/scripts/controllers/push-platform-notifications/push-platform-notifications.test.ts @@ -1,6 +1,6 @@ import { ControllerMessenger } from '@metamask/base-controller'; +import { AuthenticationController } from '@metamask/profile-sync-controller'; import { isManifestV3 } from '../../../../shared/modules/mv3.utils'; -import type { AuthenticationControllerGetBearerToken } from '../authentication/authentication-controller'; import type { PushPlatformNotificationsControllerMessenger, PushPlatformNotificationsControllerState, @@ -118,7 +118,7 @@ type WithControllerCallback = ({ function buildMessenger() { return new ControllerMessenger< - AuthenticationControllerGetBearerToken, + AuthenticationController.AuthenticationControllerGetBearerToken, never >(); } @@ -152,7 +152,8 @@ async function withController( function mockAuthBearerTokenCall( messenger: PushPlatformNotificationsControllerMessenger, ) { - type Fn = AuthenticationControllerGetBearerToken['handler']; + type Fn = + AuthenticationController.AuthenticationControllerGetBearerToken['handler']; const mockAuthGetBearerToken = jest .fn, Parameters>() .mockResolvedValue(MOCK_JWT); diff --git a/app/scripts/controllers/push-platform-notifications/push-platform-notifications.ts b/app/scripts/controllers/push-platform-notifications/push-platform-notifications.ts index dab9bce47a30..51cc81c14e7c 100644 --- a/app/scripts/controllers/push-platform-notifications/push-platform-notifications.ts +++ b/app/scripts/controllers/push-platform-notifications/push-platform-notifications.ts @@ -3,10 +3,11 @@ import { RestrictedControllerMessenger, ControllerGetStateAction, } from '@metamask/base-controller'; +import { AuthenticationController } from '@metamask/profile-sync-controller'; + import log from 'loglevel'; import { isManifestV3 } from '../../../../shared/modules/mv3.utils'; -import type { AuthenticationControllerGetBearerToken } from '../authentication/authentication-controller'; import type { Notification } from '../metamask-notifications/types/notification/notification'; import { activatePushNotifications, @@ -44,7 +45,8 @@ export type PushPlatformNotificationsControllerMessengerActions = | PushPlatformNotificationsControllerUpdateTriggerPushNotifications | ControllerGetStateAction<'state', PushPlatformNotificationsControllerState>; -type AllowedActions = AuthenticationControllerGetBearerToken; +type AllowedActions = + AuthenticationController.AuthenticationControllerGetBearerToken; export type PushPlatformNotificationsControllerOnNewNotificationEvent = { type: `${typeof controllerName}:onNewNotifications`; diff --git a/app/scripts/controllers/user-storage/encryption/cache.ts b/app/scripts/controllers/user-storage/encryption/cache.ts deleted file mode 100644 index 7e9d80c78442..000000000000 --- a/app/scripts/controllers/user-storage/encryption/cache.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { base64ToByteArray, byteArrayToBase64 } from './utils'; - -type CachedEntry = { - salt: Uint8Array; - base64Salt: string; - key: Uint8Array; -}; - -const MAX_PASSWORD_CACHES = 3; -const MAX_SALT_CACHES = 10; - -/** - * In-Memory Caching derived keys based from a given salt and password. - */ -type PasswordMemCachedKDF = { - [hashedPassword: string]: Map; -}; -let inMemCachedKDF: PasswordMemCachedKDF = {}; -const getPasswordCache = (hashedPassword: string) => { - inMemCachedKDF[hashedPassword] ??= new Map(); - return inMemCachedKDF[hashedPassword]; -}; - -/** - * Returns a given cached derived key from a hashed password and salt - * - * @param hashedPassword - hashed password for cache lookup - * @param salt - provide salt to receive cached key - * @returns cached key - */ -export function getCachedKeyBySalt( - hashedPassword: string, - salt: Uint8Array, -): CachedEntry | undefined { - const cache = getPasswordCache(hashedPassword); - const base64Salt = byteArrayToBase64(salt); - const cachedKey = cache.get(base64Salt); - if (!cachedKey) { - return undefined; - } - - return { - salt, - base64Salt, - key: cachedKey, - }; -} - -/** - * Gets any cached key for a given hashed password - * - * @param hashedPassword - hashed password for cache lookup - * @returns any (the first) cached key - */ -export function getAnyCachedKey( - hashedPassword: string, -): CachedEntry | undefined { - const cache = getPasswordCache(hashedPassword); - - // Takes 1 item from an Iterator via Map.entries() - const cachedEntry: [string, Uint8Array] | undefined = cache - .entries() - .next().value; - - if (!cachedEntry) { - return undefined; - } - - const base64Salt = cachedEntry[0]; - const bytesSalt = base64ToByteArray(base64Salt); - return { - salt: bytesSalt, - base64Salt, - key: cachedEntry[1], - }; -} - -/** - * Sets a key to the in memory cache. - * We have set an arbitrary size of 10 cached keys per hashed password. - * - * @param hashedPassword - hashed password for cache lookup - * @param salt - salt to set new derived key - * @param key - derived key we are setting - */ -export function setCachedKey( - hashedPassword: string, - salt: Uint8Array, - key: Uint8Array, -): void { - // Max password caches - if (Object.keys(inMemCachedKDF).length > MAX_PASSWORD_CACHES) { - inMemCachedKDF = {}; - } - - const cache = getPasswordCache(hashedPassword); - const base64Salt = byteArrayToBase64(salt); - - // Max salt caches - if (cache.size > MAX_SALT_CACHES) { - cache.clear(); - } - - cache.set(base64Salt, key); -} diff --git a/app/scripts/controllers/user-storage/encryption/encryption.test.ts b/app/scripts/controllers/user-storage/encryption/encryption.test.ts deleted file mode 100644 index dbe34a73a5e8..000000000000 --- a/app/scripts/controllers/user-storage/encryption/encryption.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { createMockFullUserStorage } from '../../metamask-notifications/mocks/mock-notification-user-storage'; -import encryption, { createSHA256Hash } from './encryption'; - -describe('encryption tests', () => { - const PASSWORD = '123'; - const DATA1 = 'Hello World'; - const DATA2 = JSON.stringify({ foo: 'bar' }); - const DATA3 = JSON.stringify(createMockFullUserStorage()); - - it('Should encrypt and decrypt data', () => { - function actEncryptDecrypt(data: string) { - const encryptedString = encryption.encryptString(data, PASSWORD); - const decryptString = encryption.decryptString(encryptedString, PASSWORD); - return decryptString; - } - - expect(actEncryptDecrypt(DATA1)).toBe(DATA1); - expect(actEncryptDecrypt(DATA2)).toBe(DATA2); - expect(actEncryptDecrypt(DATA3)).toBe(DATA3); - }); - - it('Should decrypt some existing data', () => { - const encryptedData = `{"v":"1","t":"scrypt","d":"WNEp1QXUZsxCfW9b27uzZ18CtsMvKP6+cqLq8NLAItXeYcFcUjtKprfvedHxf5JN9Q7pe50qnA==","o":{"N":131072,"r":8,"p":1,"dkLen":16},"saltLen":16}`; - const result = encryption.decryptString(encryptedData, PASSWORD); - expect(result).toBe(DATA1); - }); - - it('Should sha-256 hash a value and should be deterministic', () => { - const DATA = 'Hello World'; - const EXPECTED_HASH = - 'a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e'; - - const hash1 = createSHA256Hash(DATA); - expect(hash1).toBe(EXPECTED_HASH); - - // Hash should be deterministic (same output with same input) - const hash2 = createSHA256Hash(DATA); - expect(hash1).toBe(hash2); - }); -}); diff --git a/app/scripts/controllers/user-storage/encryption/encryption.ts b/app/scripts/controllers/user-storage/encryption/encryption.ts deleted file mode 100644 index cdd8feaf77b3..000000000000 --- a/app/scripts/controllers/user-storage/encryption/encryption.ts +++ /dev/null @@ -1,196 +0,0 @@ -import { scrypt } from '@noble/hashes/scrypt'; -import { sha256 } from '@noble/hashes/sha256'; -import { utf8ToBytes, concatBytes, bytesToHex } from '@noble/hashes/utils'; -import { gcm } from '@noble/ciphers/aes'; -import { randomBytes } from '@noble/ciphers/webcrypto'; -import { getAnyCachedKey, getCachedKeyBySalt, setCachedKey } from './cache'; -import { base64ToByteArray, byteArrayToBase64, bytesToUtf8 } from './utils'; - -export type EncryptedPayload = { - // version - v: '1'; - - // key derivation function algorithm - scrypt - t: 'scrypt'; - - // data - d: string; - - // encryption options - scrypt - o: { - N: number; - r: number; - p: number; - dkLen: number; - }; - - // Salt options - saltLen: number; -}; - -class EncryptorDecryptor { - #ALGORITHM_NONCE_SIZE: number = 12; // 12 bytes - - #ALGORITHM_KEY_SIZE: number = 16; // 16 bytes - - #SCRYPT_SALT_SIZE: number = 16; // 16 bytes - - // see: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#scrypt - #SCRYPT_N: number = 2 ** 17; // CPU/memory cost parameter (must be a power of 2, > 1) - - #SCRYPT_r: number = 8; // Block size parameter - - #SCRYPT_p: number = 1; // Parallelization parameter - - encryptString(plaintext: string, password: string): string { - try { - return this.#encryptStringV1(plaintext, password); - } catch (e) { - const errorMessage = e instanceof Error ? e.message : e; - throw new Error(`Unable to encrypt string - ${errorMessage}`); - } - } - - decryptString(encryptedDataStr: string, password: string): string { - try { - const encryptedData: EncryptedPayload = JSON.parse(encryptedDataStr); - if (encryptedData.v === '1') { - if (encryptedData.t === 'scrypt') { - return this.#decryptStringV1(encryptedData, password); - } - } - throw new Error( - `Unsupported encrypted data payload - ${encryptedDataStr}`, - ); - } catch (e) { - const errorMessage = e instanceof Error ? e.message : e; - throw new Error(`Unable to decrypt string - ${errorMessage}`); - } - } - - #encryptStringV1(plaintext: string, password: string): string { - const { key, salt } = this.#getOrGenerateScryptKey(password, { - N: this.#SCRYPT_N, - r: this.#SCRYPT_r, - p: this.#SCRYPT_p, - dkLen: this.#ALGORITHM_KEY_SIZE, - }); - - // Encrypt and prepend salt. - const plaintextRaw = utf8ToBytes(plaintext); - const ciphertextAndNonceAndSalt = concatBytes( - salt, - this.#encrypt(plaintextRaw, key), - ); - - // Convert to Base64 - const encryptedData = byteArrayToBase64(ciphertextAndNonceAndSalt); - - const encryptedPayload: EncryptedPayload = { - v: '1', - t: 'scrypt', - d: encryptedData, - o: { - N: this.#SCRYPT_N, - r: this.#SCRYPT_r, - p: this.#SCRYPT_p, - dkLen: this.#ALGORITHM_KEY_SIZE, - }, - saltLen: this.#SCRYPT_SALT_SIZE, - }; - - return JSON.stringify(encryptedPayload); - } - - #decryptStringV1(data: EncryptedPayload, password: string): string { - const { o, d: base64CiphertextAndNonceAndSalt, saltLen } = data; - - // Decode the base64. - const ciphertextAndNonceAndSalt = base64ToByteArray( - base64CiphertextAndNonceAndSalt, - ); - - // Create buffers of salt and ciphertextAndNonce. - const salt = ciphertextAndNonceAndSalt.slice(0, saltLen); - const ciphertextAndNonce = ciphertextAndNonceAndSalt.slice( - saltLen, - ciphertextAndNonceAndSalt.length, - ); - - // Derive the key. - const { key } = this.#getOrGenerateScryptKey( - password, - { - N: o.N, - r: o.r, - p: o.p, - dkLen: o.dkLen, - }, - salt, - ); - - // Decrypt and return result. - return bytesToUtf8(this.#decrypt(ciphertextAndNonce, key)); - } - - #encrypt(plaintext: Uint8Array, key: Uint8Array): Uint8Array { - const nonce = randomBytes(this.#ALGORITHM_NONCE_SIZE); - - // Encrypt and prepend nonce. - const ciphertext = gcm(key, nonce).encrypt(plaintext); - - return concatBytes(nonce, ciphertext); - } - - #decrypt(ciphertextAndNonce: Uint8Array, key: Uint8Array): Uint8Array { - // Create buffers of nonce and ciphertext. - const nonce = ciphertextAndNonce.slice(0, this.#ALGORITHM_NONCE_SIZE); - const ciphertext = ciphertextAndNonce.slice( - this.#ALGORITHM_NONCE_SIZE, - ciphertextAndNonce.length, - ); - - // Decrypt and return result. - return gcm(key, nonce).decrypt(ciphertext); - } - - #getOrGenerateScryptKey( - password: string, - o: EncryptedPayload['o'], - salt?: Uint8Array, - ) { - const hashedPassword = createSHA256Hash(password); - const cachedKey = salt - ? getCachedKeyBySalt(hashedPassword, salt) - : getAnyCachedKey(hashedPassword); - - if (cachedKey) { - return { - key: cachedKey.key, - salt: cachedKey.salt, - }; - } - - const newSalt = salt ?? randomBytes(this.#SCRYPT_SALT_SIZE); - const newKey = scrypt(password, newSalt, { - N: o.N, - r: o.r, - p: o.p, - dkLen: o.dkLen, - }); - setCachedKey(hashedPassword, newSalt, newKey); - - return { - key: newKey, - salt: newSalt, - }; - } -} - -const encryption = new EncryptorDecryptor(); -export default encryption; - -export function createSHA256Hash(data: string): string { - const hashedData = sha256(data); - return bytesToHex(hashedData); -} diff --git a/app/scripts/controllers/user-storage/encryption/index.ts b/app/scripts/controllers/user-storage/encryption/index.ts deleted file mode 100644 index 3582e3b9e2a1..000000000000 --- a/app/scripts/controllers/user-storage/encryption/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import Encryption from './encryption'; - -export * from './encryption'; -export default Encryption; diff --git a/app/scripts/controllers/user-storage/encryption/utils.ts b/app/scripts/controllers/user-storage/encryption/utils.ts deleted file mode 100644 index 76ceda77eb7b..000000000000 --- a/app/scripts/controllers/user-storage/encryption/utils.ts +++ /dev/null @@ -1,12 +0,0 @@ -export function byteArrayToBase64(byteArray: Uint8Array) { - return Buffer.from(byteArray).toString('base64'); -} - -export function base64ToByteArray(base64: string) { - return new Uint8Array(Buffer.from(base64, 'base64')); -} - -export function bytesToUtf8(byteArray: Uint8Array) { - const decoder = new TextDecoder('utf-8'); - return decoder.decode(byteArray); -} diff --git a/app/scripts/controllers/user-storage/mocks/mockResponses.ts b/app/scripts/controllers/user-storage/mocks/mockResponses.ts deleted file mode 100644 index c1b3896f446f..000000000000 --- a/app/scripts/controllers/user-storage/mocks/mockResponses.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { createEntryPath } from '../schema'; -import { GetUserStorageResponse, USER_STORAGE_ENDPOINT } from '../services'; -import { MOCK_ENCRYPTED_STORAGE_DATA, MOCK_STORAGE_KEY } from './mockStorage'; - -type MockResponse = { - url: string; - requestMethod: 'GET' | 'POST' | 'PUT'; - response: unknown; -}; - -export const MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT = `${USER_STORAGE_ENDPOINT}${createEntryPath( - 'notification_settings', - MOCK_STORAGE_KEY, -)}`; - -const MOCK_GET_USER_STORAGE_RESPONSE: GetUserStorageResponse = { - HashedKey: 'HASHED_KEY', - Data: MOCK_ENCRYPTED_STORAGE_DATA, -}; - -export function getMockUserStorageGetResponse() { - return { - url: MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT, - requestMethod: 'GET', - response: MOCK_GET_USER_STORAGE_RESPONSE, - } satisfies MockResponse; -} - -export function getMockUserStoragePutResponse() { - return { - url: MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT, - requestMethod: 'PUT', - response: null, - } satisfies MockResponse; -} diff --git a/app/scripts/controllers/user-storage/mocks/mockServices.ts b/app/scripts/controllers/user-storage/mocks/mockServices.ts deleted file mode 100644 index d612ebe6ed55..000000000000 --- a/app/scripts/controllers/user-storage/mocks/mockServices.ts +++ /dev/null @@ -1,34 +0,0 @@ -import nock from 'nock'; -import { - getMockUserStorageGetResponse, - getMockUserStoragePutResponse, -} from './mockResponses'; - -type MockReply = { - status: nock.StatusCode; - body?: nock.Body; -}; - -export function mockEndpointGetUserStorage(mockReply?: MockReply) { - const mockResponse = getMockUserStorageGetResponse(); - const reply = mockReply ?? { - status: 200, - body: mockResponse.response, - }; - - const mockEndpoint = nock(mockResponse.url) - .get('') - .reply(reply.status, reply.body); - - return mockEndpoint; -} - -export function mockEndpointUpsertUserStorage( - mockReply?: Pick, -) { - const mockResponse = getMockUserStoragePutResponse(); - const mockEndpoint = nock(mockResponse.url) - .put('') - .reply(mockReply?.status ?? 204); - return mockEndpoint; -} diff --git a/app/scripts/controllers/user-storage/mocks/mockStorage.ts b/app/scripts/controllers/user-storage/mocks/mockStorage.ts deleted file mode 100644 index 4a43a80556e1..000000000000 --- a/app/scripts/controllers/user-storage/mocks/mockStorage.ts +++ /dev/null @@ -1,9 +0,0 @@ -import encryption, { createSHA256Hash } from '../encryption'; - -export const MOCK_STORAGE_KEY_SIGNATURE = 'mockStorageKey'; -export const MOCK_STORAGE_KEY = createSHA256Hash(MOCK_STORAGE_KEY_SIGNATURE); -export const MOCK_STORAGE_DATA = JSON.stringify({ hello: 'world' }); -export const MOCK_ENCRYPTED_STORAGE_DATA = encryption.encryptString( - MOCK_STORAGE_DATA, - MOCK_STORAGE_KEY, -); diff --git a/app/scripts/controllers/user-storage/schema.test.ts b/app/scripts/controllers/user-storage/schema.test.ts deleted file mode 100644 index d08b0802bf49..000000000000 --- a/app/scripts/controllers/user-storage/schema.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { USER_STORAGE_ENTRIES, createEntryPath } from './schema'; - -describe('schema.ts - createEntryPath()', () => { - const MOCK_STORAGE_KEY = 'MOCK_STORAGE_KEY'; - - test('creates a valid entry path', () => { - const result = createEntryPath('notification_settings', MOCK_STORAGE_KEY); - - // Ensures that the path and the entry name are correct. - // If this differs then indicates a potential change on how this path is computed - const expected = `/${USER_STORAGE_ENTRIES.notification_settings.path}/50f65447980018849b991e038d7ad87de5cf07fbad9736b0280e93972e17bac8`; - expect(result).toBe(expected); - }); - - test('Should throw if using an entry that does not exist', () => { - expect(() => { - // @ts-expect-error mocking a fake entry for testing. - createEntryPath('fake_entry'); - }).toThrow(); - }); -}); diff --git a/app/scripts/controllers/user-storage/schema.ts b/app/scripts/controllers/user-storage/schema.ts deleted file mode 100644 index 19bc0ccfae52..000000000000 --- a/app/scripts/controllers/user-storage/schema.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { createSHA256Hash } from './encryption'; - -type UserStorageEntry = { path: string; entryName: string }; - -/** - * The User Storage Endpoint requires a path and an entry name. - * Developers can provide additional paths by extending this variable below - */ -export const USER_STORAGE_ENTRIES = { - notification_settings: { - path: 'notifications', - entryName: 'notification_settings', - }, -} satisfies Record; - -export type UserStorageEntryKeys = keyof typeof USER_STORAGE_ENTRIES; - -/** - * Constructs a unique entry path for a user. - * This can be done due to the uniqueness of the storage key (no users will share the same storage key). - * The users entry is a unique hash that cannot be reversed. - * - * @param entryKey - * @param storageKey - * @returns - */ -export function createEntryPath( - entryKey: UserStorageEntryKeys, - storageKey: string, -): string { - const entry = USER_STORAGE_ENTRIES[entryKey]; - if (!entry) { - throw new Error(`user-storage - invalid entry provided: ${entryKey}`); - } - - const hashedKey = createSHA256Hash(entry.entryName + storageKey); - return `/${entry.path}/${hashedKey}`; -} diff --git a/app/scripts/controllers/user-storage/services.test.ts b/app/scripts/controllers/user-storage/services.test.ts deleted file mode 100644 index a746dcee858f..000000000000 --- a/app/scripts/controllers/user-storage/services.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { - mockEndpointGetUserStorage, - mockEndpointUpsertUserStorage, -} from './mocks/mockServices'; -import { - MOCK_ENCRYPTED_STORAGE_DATA, - MOCK_STORAGE_DATA, - MOCK_STORAGE_KEY, -} from './mocks/mockStorage'; -import { - GetUserStorageResponse, - getUserStorage, - upsertUserStorage, -} from './services'; - -describe('user-storage/services.ts - getUserStorage() tests', () => { - test('returns user storage data', async () => { - const mockGetUserStorage = mockEndpointGetUserStorage(); - const result = await actCallGetUserStorage(); - - mockGetUserStorage.done(); - expect(result).toBe(MOCK_STORAGE_DATA); - }); - - test('returns null if endpoint does not have entry', async () => { - const mockGetUserStorage = mockEndpointGetUserStorage({ status: 404 }); - const result = await actCallGetUserStorage(); - - mockGetUserStorage.done(); - expect(result).toBe(null); - }); - - test('returns null if endpoint fails', async () => { - const mockGetUserStorage = mockEndpointGetUserStorage({ status: 500 }); - const result = await actCallGetUserStorage(); - - mockGetUserStorage.done(); - expect(result).toBe(null); - }); - - test('returns null if unable to decrypt data', async () => { - const badResponseData: GetUserStorageResponse = { - HashedKey: 'MOCK_HASH', - Data: 'Bad Encrypted Data', - }; - const mockGetUserStorage = mockEndpointGetUserStorage({ - status: 200, - body: badResponseData, - }); - const result = await actCallGetUserStorage(); - - mockGetUserStorage.done(); - expect(result).toBe(null); - }); - - function actCallGetUserStorage() { - return getUserStorage({ - bearerToken: 'MOCK_BEARER_TOKEN', - entryKey: 'notification_settings', - storageKey: MOCK_STORAGE_KEY, - }); - } -}); - -describe('user-storage/services.ts - upsertUserStorage() tests', () => { - test('invokes upsert endpoint with no errors', async () => { - const mockUpsertUserStorage = mockEndpointUpsertUserStorage(); - await actCallUpsertUserStorage(); - - expect(mockUpsertUserStorage.isDone()).toBe(true); - }); - - test('throws error if unable to upsert user storage', async () => { - const mockUpsertUserStorage = mockEndpointUpsertUserStorage({ - status: 500, - }); - - await expect(actCallUpsertUserStorage()).rejects.toThrow(); - mockUpsertUserStorage.done(); - }); - - function actCallUpsertUserStorage() { - return upsertUserStorage(MOCK_ENCRYPTED_STORAGE_DATA, { - bearerToken: 'MOCK_BEARER_TOKEN', - entryKey: 'notification_settings', - storageKey: MOCK_STORAGE_KEY, - }); - } -}); diff --git a/app/scripts/controllers/user-storage/services.ts b/app/scripts/controllers/user-storage/services.ts deleted file mode 100644 index 269009850079..000000000000 --- a/app/scripts/controllers/user-storage/services.ts +++ /dev/null @@ -1,83 +0,0 @@ -import log from 'loglevel'; - -import encryption from './encryption'; -import { UserStorageEntryKeys, createEntryPath } from './schema'; - -export const USER_STORAGE_API = process.env.USER_STORAGE_API || ''; -export const USER_STORAGE_ENDPOINT = `${USER_STORAGE_API}/api/v1/userstorage`; - -export type GetUserStorageResponse = { - HashedKey: string; - Data: string; -}; - -export type UserStorageOptions = { - bearerToken: string; - entryKey: UserStorageEntryKeys; - storageKey: string; -}; - -export async function getUserStorage( - opts: UserStorageOptions, -): Promise { - try { - const path = createEntryPath(opts.entryKey, opts.storageKey); - const url = new URL(`${USER_STORAGE_ENDPOINT}${path}`); - - const userStorageResponse = await fetch(url.toString(), { - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${opts.bearerToken}`, - }, - }); - - // Acceptable error - since indicates entry does not exist. - if (userStorageResponse.status === 404) { - return null; - } - - if (userStorageResponse.status !== 200) { - throw new Error('Unable to get User Storage'); - } - - const userStorage: GetUserStorageResponse | null = - await userStorageResponse.json(); - const encryptedData = userStorage?.Data ?? null; - - if (!encryptedData) { - return null; - } - - const decryptedData = encryption.decryptString( - encryptedData, - opts.storageKey, - ); - - return decryptedData; - } catch (e) { - log.error('Failed to get user storage', e); - return null; - } -} - -export async function upsertUserStorage( - data: string, - opts: UserStorageOptions, -): Promise { - const encryptedData = encryption.encryptString(data, opts.storageKey); - const path = createEntryPath(opts.entryKey, opts.storageKey); - const url = new URL(`${USER_STORAGE_ENDPOINT}${path}`); - - const res = await fetch(url.toString(), { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${opts.bearerToken}`, - }, - body: JSON.stringify({ data: encryptedData }), - }); - - if (!res.ok) { - throw new Error('user-storage - unable to upsert data'); - } -} diff --git a/app/scripts/controllers/user-storage/user-storage-controller.test.ts b/app/scripts/controllers/user-storage/user-storage-controller.test.ts deleted file mode 100644 index 8d3f61d6ab21..000000000000 --- a/app/scripts/controllers/user-storage/user-storage-controller.test.ts +++ /dev/null @@ -1,465 +0,0 @@ -import nock from 'nock'; -import { ControllerMessenger } from '@metamask/base-controller'; -import { - AuthenticationControllerGetBearerToken, - AuthenticationControllerGetSessionProfile, - AuthenticationControllerIsSignedIn, - AuthenticationControllerPerformSignIn, -} from '../authentication/authentication-controller'; -import { - MetamaskNotificationsControllerDisableMetamaskNotifications, - MetamaskNotificationsControllerSelectIsMetamaskNotificationsEnabled, -} from '../metamask-notifications/metamask-notifications'; -import { - MOCK_STORAGE_DATA, - MOCK_STORAGE_KEY, - MOCK_STORAGE_KEY_SIGNATURE, -} from './mocks/mockStorage'; -import UserStorageController, { - AllowedActions, - AllowedEvents, -} from './user-storage-controller'; -import { - mockEndpointGetUserStorage, - mockEndpointUpsertUserStorage, -} from './mocks/mockServices'; - -const typedMockFn = unknown>() => - jest.fn, Parameters>(); - -describe('user-storage/user-storage-controller - constructor() tests', () => { - test('Creates UserStorage with default state', () => { - const { messengerMocks } = arrangeMocks(); - const controller = new UserStorageController({ - messenger: messengerMocks.messenger, - getMetaMetricsState: () => true, - }); - - expect(controller.state.isProfileSyncingEnabled).toBe(true); - }); - - function arrangeMocks() { - return { - messengerMocks: mockUserStorageMessenger(), - }; - } -}); - -describe('user-storage/user-storage-controller - performGetStorage() tests', () => { - test('returns users notification storage', async () => { - const { messengerMocks, mockAPI } = arrangeMocks(); - const controller = new UserStorageController({ - messenger: messengerMocks.messenger, - getMetaMetricsState: () => true, - }); - - const result = await controller.performGetStorage('notification_settings'); - mockAPI.done(); - expect(result).toBe(MOCK_STORAGE_DATA); - }); - - test('rejects if UserStorage is not enabled', async () => { - const { messengerMocks } = arrangeMocks(); - const controller = new UserStorageController({ - messenger: messengerMocks.messenger, - getMetaMetricsState: () => true, - state: { - isProfileSyncingEnabled: false, - isProfileSyncingUpdateLoading: false, - }, - }); - - await expect( - controller.performGetStorage('notification_settings'), - ).rejects.toThrow(); - }); - - test('rejects if wallet is locked', async () => { - const { messengerMocks } = arrangeMocks(); - - // Mock wallet is locked - messengerMocks.mockKeyringControllerGetState.mockReturnValue({ - isUnlocked: false, - }); - - const controller = new UserStorageController({ - messenger: messengerMocks.messenger, - getMetaMetricsState: () => true, - }); - - await expect( - controller.performGetStorage('notification_settings'), - ).rejects.toThrow(); - }); - - // @ts-expect-error This is missing from the Mocha type definitions - test.each([ - [ - 'fails when no bearer token is found (auth errors)', - (messengerMocks: ReturnType) => - messengerMocks.mockAuthGetBearerToken.mockRejectedValue( - new Error('MOCK FAILURE'), - ), - ], - [ - 'fails when no session identifier is found (auth errors)', - (messengerMocks: ReturnType) => - messengerMocks.mockAuthGetSessionProfile.mockRejectedValue( - new Error('MOCK FAILURE'), - ), - ], - ])( - 'rejects on auth failure - %s', - async ( - _: string, - arrangeFailureCase: ( - messengerMocks: ReturnType, - ) => void, - ) => { - const { messengerMocks } = arrangeMocks(); - arrangeFailureCase(messengerMocks); - const controller = new UserStorageController({ - messenger: messengerMocks.messenger, - getMetaMetricsState: () => true, - }); - - await expect( - controller.performGetStorage('notification_settings'), - ).rejects.toThrow(); - }, - ); - - function arrangeMocks() { - return { - messengerMocks: mockUserStorageMessenger(), - mockAPI: mockEndpointGetUserStorage(), - }; - } -}); - -describe('user-storage/user-storage-controller - performSetStorage() tests', () => { - test('saves users storage', async () => { - const { messengerMocks, mockAPI } = arrangeMocks(); - const controller = new UserStorageController({ - messenger: messengerMocks.messenger, - getMetaMetricsState: () => true, - }); - - await controller.performSetStorage('notification_settings', 'new data'); - mockAPI.done(); - }); - - test('rejects if UserStorage is not enabled', async () => { - const { messengerMocks } = arrangeMocks(); - const controller = new UserStorageController({ - messenger: messengerMocks.messenger, - getMetaMetricsState: () => true, - state: { - isProfileSyncingEnabled: false, - isProfileSyncingUpdateLoading: false, - }, - }); - - await expect( - controller.performSetStorage('notification_settings', 'new data'), - ).rejects.toThrow(); - }); - - test('rejects if wallet is locked', async () => { - const { messengerMocks } = arrangeMocks(); - - // Mock wallet is locked - messengerMocks.mockKeyringControllerGetState.mockReturnValue({ - isUnlocked: false, - }); - - const controller = new UserStorageController({ - messenger: messengerMocks.messenger, - getMetaMetricsState: () => true, - }); - - await expect( - controller.performSetStorage('notification_settings', 'new data'), - ).rejects.toThrow(); - }); - - // @ts-expect-error This is missing from the Mocha type definitions - test.each([ - [ - 'fails when no bearer token is found (auth errors)', - (messengerMocks: ReturnType) => - messengerMocks.mockAuthGetBearerToken.mockRejectedValue( - new Error('MOCK FAILURE'), - ), - ], - [ - 'fails when no session identifier is found (auth errors)', - (messengerMocks: ReturnType) => - messengerMocks.mockAuthGetSessionProfile.mockRejectedValue( - new Error('MOCK FAILURE'), - ), - ], - ])( - 'rejects on auth failure - %s', - async ( - _: string, - arrangeFailureCase: ( - messengerMocks: ReturnType, - ) => void, - ) => { - const { messengerMocks } = arrangeMocks(); - arrangeFailureCase(messengerMocks); - const controller = new UserStorageController({ - messenger: messengerMocks.messenger, - getMetaMetricsState: () => true, - }); - - await expect( - controller.performSetStorage('notification_settings', 'new data'), - ).rejects.toThrow(); - }, - ); - - test('rejects if api call fails', async () => { - const { messengerMocks } = arrangeMocks({ - mockAPI: mockEndpointUpsertUserStorage({ status: 500 }), - }); - const controller = new UserStorageController({ - messenger: messengerMocks.messenger, - getMetaMetricsState: () => true, - }); - await expect( - controller.performSetStorage('notification_settings', 'new data'), - ).rejects.toThrow(); - }); - - function arrangeMocks(overrides?: { mockAPI?: nock.Scope }) { - return { - messengerMocks: mockUserStorageMessenger(), - mockAPI: overrides?.mockAPI ?? mockEndpointUpsertUserStorage(), - }; - } -}); - -describe('user-storage/user-storage-controller - performSetStorage() tests', () => { - test('Should return a storage key', async () => { - const { messengerMocks } = arrangeMocks(); - const controller = new UserStorageController({ - messenger: messengerMocks.messenger, - getMetaMetricsState: () => true, - }); - - const result = await controller.getStorageKey(); - expect(result).toBe(MOCK_STORAGE_KEY); - }); - - test('rejects if UserStorage is not enabled', async () => { - const { messengerMocks } = arrangeMocks(); - const controller = new UserStorageController({ - messenger: messengerMocks.messenger, - getMetaMetricsState: () => true, - state: { - isProfileSyncingEnabled: false, - isProfileSyncingUpdateLoading: false, - }, - }); - - await expect(controller.getStorageKey()).rejects.toThrow(); - }); - - function arrangeMocks() { - return { - messengerMocks: mockUserStorageMessenger(), - }; - } -}); - -describe('user-storage/user-storage-controller - disableProfileSyncing() tests', () => { - test('should disable user storage / profile syncing when called', async () => { - const { messengerMocks } = arrangeMocks(); - const controller = new UserStorageController({ - messenger: messengerMocks.messenger, - getMetaMetricsState: () => true, - }); - - expect(controller.state.isProfileSyncingEnabled).toBe(true); - await controller.disableProfileSyncing(); - expect(controller.state.isProfileSyncingEnabled).toBe(false); - }); - - function arrangeMocks() { - return { - messengerMocks: mockUserStorageMessenger(), - }; - } -}); - -describe('user-storage/user-storage-controller - enableProfileSyncing() tests', () => { - test('should enable user storage / profile syncing', async () => { - const { messengerMocks } = arrangeMocks(); - messengerMocks.mockAuthIsSignedIn.mockReturnValue(false); // mock that auth is not enabled - - const controller = new UserStorageController({ - messenger: messengerMocks.messenger, - getMetaMetricsState: () => true, - state: { - isProfileSyncingEnabled: false, - isProfileSyncingUpdateLoading: false, - }, - }); - - expect(controller.state.isProfileSyncingEnabled).toBe(false); - await controller.enableProfileSyncing(); - expect(controller.state.isProfileSyncingEnabled).toBe(true); - expect(messengerMocks.mockAuthIsSignedIn).toBeCalled(); - expect(messengerMocks.mockAuthPerformSignIn).toBeCalled(); - }); - - function arrangeMocks() { - return { - messengerMocks: mockUserStorageMessenger(), - }; - } -}); - -function mockUserStorageMessenger() { - const messenger = new ControllerMessenger< - AllowedActions, - AllowedEvents - >().getRestricted({ - name: 'UserStorageController', - allowedActions: [ - 'KeyringController:getState', - 'SnapController:handleRequest', - 'AuthenticationController:getBearerToken', - 'AuthenticationController:getSessionProfile', - 'AuthenticationController:isSignedIn', - 'AuthenticationController:performSignIn', - 'AuthenticationController:performSignOut', - 'MetamaskNotificationsController:disableMetamaskNotifications', - 'MetamaskNotificationsController:selectIsMetamaskNotificationsEnabled', - ], - allowedEvents: ['KeyringController:lock', 'KeyringController:unlock'], - }); - - const mockSnapGetPublicKey = jest.fn().mockResolvedValue('MOCK_PUBLIC_KEY'); - const mockSnapSignMessage = jest - .fn() - .mockResolvedValue(MOCK_STORAGE_KEY_SIGNATURE); - - const mockAuthGetBearerToken = - typedMockFn< - AuthenticationControllerGetBearerToken['handler'] - >().mockResolvedValue('MOCK_BEARER_TOKEN'); - - const mockAuthGetSessionProfile = typedMockFn< - AuthenticationControllerGetSessionProfile['handler'] - >().mockResolvedValue({ - identifierId: '', - profileId: 'MOCK_PROFILE_ID', - }); - - const mockAuthPerformSignIn = - typedMockFn< - AuthenticationControllerPerformSignIn['handler'] - >().mockResolvedValue('New Access Token'); - - const mockAuthIsSignedIn = - typedMockFn< - AuthenticationControllerIsSignedIn['handler'] - >().mockReturnValue(true); - - const mockAuthPerformSignOut = - typedMockFn< - AuthenticationControllerIsSignedIn['handler'] - >().mockReturnValue(true); - - const mockMetamaskNotificationsIsMetamaskNotificationsEnabled = - typedMockFn< - MetamaskNotificationsControllerSelectIsMetamaskNotificationsEnabled['handler'] - >().mockReturnValue(true); - - const mockMetamaskNotificationsDisableNotifications = - typedMockFn< - MetamaskNotificationsControllerDisableMetamaskNotifications['handler'] - >().mockResolvedValue(); - - const mockKeyringControllerGetState = typedMockFn< - () => { isUnlocked: boolean } - >().mockReturnValue({ isUnlocked: true }); - - jest.spyOn(messenger, 'call').mockImplementation((...args) => { - const [actionType, params] = args; - if (actionType === 'SnapController:handleRequest') { - if (params?.request.method === 'getPublicKey') { - return mockSnapGetPublicKey(); - } - - if (params?.request.method === 'signMessage') { - return mockSnapSignMessage(); - } - - throw new Error( - `MOCK_FAIL - unsupported SnapController:handleRequest call: ${params?.request.method}`, - ); - } - - if (actionType === 'AuthenticationController:getBearerToken') { - return mockAuthGetBearerToken(); - } - - if (actionType === 'AuthenticationController:getSessionProfile') { - return mockAuthGetSessionProfile(); - } - - if (actionType === 'AuthenticationController:performSignIn') { - return mockAuthPerformSignIn(); - } - - if (actionType === 'AuthenticationController:isSignedIn') { - return mockAuthIsSignedIn(); - } - - if ( - actionType === - 'MetamaskNotificationsController:selectIsMetamaskNotificationsEnabled' - ) { - return mockMetamaskNotificationsIsMetamaskNotificationsEnabled(); - } - - if ( - actionType === - 'MetamaskNotificationsController:disableMetamaskNotifications' - ) { - return mockMetamaskNotificationsDisableNotifications(); - } - - if (actionType === 'AuthenticationController:performSignOut') { - return mockAuthPerformSignOut(); - } - - if (actionType === 'KeyringController:getState') { - return mockKeyringControllerGetState(); - } - - function exhaustedMessengerMocks(action: never) { - throw new Error(`MOCK_FAIL - unsupported messenger call: ${action}`); - } - - return exhaustedMessengerMocks(actionType); - }); - - return { - messenger, - mockSnapGetPublicKey, - mockSnapSignMessage, - mockAuthGetBearerToken, - mockAuthGetSessionProfile, - mockAuthPerformSignIn, - mockAuthIsSignedIn, - mockMetamaskNotificationsIsMetamaskNotificationsEnabled, - mockMetamaskNotificationsDisableNotifications, - mockAuthPerformSignOut, - mockKeyringControllerGetState, - }; -} diff --git a/app/scripts/controllers/user-storage/user-storage-controller.ts b/app/scripts/controllers/user-storage/user-storage-controller.ts deleted file mode 100644 index 9a286f801330..000000000000 --- a/app/scripts/controllers/user-storage/user-storage-controller.ts +++ /dev/null @@ -1,434 +0,0 @@ -import { - BaseController, - RestrictedControllerMessenger, - StateMetadata, -} from '@metamask/base-controller'; -import type { - KeyringControllerGetStateAction, - KeyringControllerLockEvent, - KeyringControllerUnlockEvent, -} from '@metamask/keyring-controller'; -import { HandleSnapRequest } from '@metamask/snaps-controllers'; -import { - AuthenticationControllerGetBearerToken, - AuthenticationControllerGetSessionProfile, - AuthenticationControllerIsSignedIn, - AuthenticationControllerPerformSignIn, - AuthenticationControllerPerformSignOut, -} from '../authentication/authentication-controller'; -import { - MetamaskNotificationsControllerDisableMetamaskNotifications, - MetamaskNotificationsControllerSelectIsMetamaskNotificationsEnabled, -} from '../metamask-notifications/metamask-notifications'; -import { createSnapSignMessageRequest } from '../authentication/auth-snap-requests'; -import { getUserStorage, upsertUserStorage } from './services'; -import { UserStorageEntryKeys } from './schema'; -import { createSHA256Hash } from './encryption'; - -const controllerName = 'UserStorageController'; - -// State -export type UserStorageControllerState = { - /** - * Condition used by UI and to determine if we can use some of the User Storage methods. - */ - isProfileSyncingEnabled: boolean | null; - /** - * Loading state for the profile syncing update - */ - isProfileSyncingUpdateLoading: boolean; -}; - -const defaultState: UserStorageControllerState = { - isProfileSyncingEnabled: true, - isProfileSyncingUpdateLoading: false, -}; - -const metadata: StateMetadata = { - isProfileSyncingEnabled: { - persist: true, - anonymous: true, - }, - isProfileSyncingUpdateLoading: { - persist: false, - anonymous: false, - }, -}; - -// Messenger Actions -type CreateActionsObj = { - [K in T]: { - type: `${typeof controllerName}:${K}`; - handler: UserStorageController[K]; - }; -}; -type ActionsObj = CreateActionsObj< - | 'performGetStorage' - | 'performSetStorage' - | 'getStorageKey' - | 'enableProfileSyncing' - | 'disableProfileSyncing' ->; -export type Actions = ActionsObj[keyof ActionsObj]; -export type UserStorageControllerPerformGetStorage = - ActionsObj['performGetStorage']; -export type UserStorageControllerPerformSetStorage = - ActionsObj['performSetStorage']; -export type UserStorageControllerGetStorageKey = ActionsObj['getStorageKey']; -export type UserStorageControllerEnableProfileSyncing = - ActionsObj['enableProfileSyncing']; -export type UserStorageControllerDisableProfileSyncing = - ActionsObj['disableProfileSyncing']; - -export type AllowedActions = - // Keyring Requests - | KeyringControllerGetStateAction - // Snap Requests - | HandleSnapRequest - // Auth Requests - | AuthenticationControllerGetBearerToken - | AuthenticationControllerGetSessionProfile - | AuthenticationControllerPerformSignIn - | AuthenticationControllerIsSignedIn - | AuthenticationControllerPerformSignOut - // Metamask Notifications - | MetamaskNotificationsControllerDisableMetamaskNotifications - | MetamaskNotificationsControllerSelectIsMetamaskNotificationsEnabled; - -export type AllowedEvents = - | KeyringControllerLockEvent - | KeyringControllerUnlockEvent; - -// Messenger -export type UserStorageControllerMessenger = RestrictedControllerMessenger< - typeof controllerName, - Actions | AllowedActions, - AllowedEvents, - AllowedActions['type'], - AllowedEvents['type'] ->; - -/** - * Reusable controller that allows any team to store synchronized data for a given user. - * These can be settings shared cross MetaMask clients, or data we want to persist when uninstalling/reinstalling. - * - * NOTE: - * - data stored on UserStorage is FULLY encrypted, with the only keys stored/managed on the client. - * - No one can access this data unless they are have the SRP and are able to run the signing snap. - */ -export default class UserStorageController extends BaseController< - typeof controllerName, - UserStorageControllerState, - UserStorageControllerMessenger -> { - #auth = { - getBearerToken: async () => { - return await this.messagingSystem.call( - 'AuthenticationController:getBearerToken', - ); - }, - getProfileId: async () => { - const sessionProfile = await this.messagingSystem.call( - 'AuthenticationController:getSessionProfile', - ); - return sessionProfile?.profileId; - }, - isAuthEnabled: () => { - return this.messagingSystem.call('AuthenticationController:isSignedIn'); - }, - signIn: async () => { - return await this.messagingSystem.call( - 'AuthenticationController:performSignIn', - ); - }, - signOut: async () => { - return await this.messagingSystem.call( - 'AuthenticationController:performSignOut', - ); - }, - }; - - #metamaskNotifications = { - disableMetamaskNotifications: async () => { - return await this.messagingSystem.call( - 'MetamaskNotificationsController:disableMetamaskNotifications', - ); - }, - selectIsMetamaskNotificationsEnabled: async () => { - return await this.messagingSystem.call( - 'MetamaskNotificationsController:selectIsMetamaskNotificationsEnabled', - ); - }, - }; - - #isUnlocked = false; - - #keyringController = { - setupLockedStateSubscriptions: () => { - const { isUnlocked } = this.messagingSystem.call( - 'KeyringController:getState', - ); - this.#isUnlocked = isUnlocked; - - this.messagingSystem.subscribe('KeyringController:unlock', () => { - this.#isUnlocked = true; - }); - - this.messagingSystem.subscribe('KeyringController:lock', () => { - this.#isUnlocked = false; - }); - }, - }; - - getMetaMetricsState: () => boolean; - - constructor(params: { - messenger: UserStorageControllerMessenger; - state?: UserStorageControllerState; - getMetaMetricsState: () => boolean; - }) { - super({ - messenger: params.messenger, - metadata, - name: controllerName, - state: { ...defaultState, ...params.state }, - }); - - this.getMetaMetricsState = params.getMetaMetricsState; - this.#keyringController.setupLockedStateSubscriptions(); - this.#registerMessageHandlers(); - } - - /** - * Constructor helper for registering this controller's messaging system - * actions. - */ - #registerMessageHandlers(): void { - this.messagingSystem.registerActionHandler( - 'UserStorageController:performGetStorage', - this.performGetStorage.bind(this), - ); - - this.messagingSystem.registerActionHandler( - 'UserStorageController:performSetStorage', - this.performSetStorage.bind(this), - ); - - this.messagingSystem.registerActionHandler( - 'UserStorageController:getStorageKey', - this.getStorageKey.bind(this), - ); - - this.messagingSystem.registerActionHandler( - 'UserStorageController:enableProfileSyncing', - this.enableProfileSyncing.bind(this), - ); - - this.messagingSystem.registerActionHandler( - 'UserStorageController:disableProfileSyncing', - this.disableProfileSyncing.bind(this), - ); - } - - public async enableProfileSyncing(): Promise { - try { - this.#setIsProfileSyncingUpdateLoading(true); - - const authEnabled = this.#auth.isAuthEnabled(); - if (!authEnabled) { - await this.#auth.signIn(); - } - - this.update((state) => { - state.isProfileSyncingEnabled = true; - }); - - this.#setIsProfileSyncingUpdateLoading(false); - } catch (e) { - this.#setIsProfileSyncingUpdateLoading(false); - const errorMessage = e instanceof Error ? e.message : e; - throw new Error( - `${controllerName} - failed to enable profile syncing - ${errorMessage}`, - ); - } - } - - public async setIsProfileSyncingEnabled( - isProfileSyncingEnabled: boolean, - ): Promise { - this.update((state) => { - state.isProfileSyncingEnabled = isProfileSyncingEnabled; - }); - } - - public async disableProfileSyncing(): Promise { - const isAlreadyDisabled = !this.state.isProfileSyncingEnabled; - if (isAlreadyDisabled) { - return; - } - - try { - this.#setIsProfileSyncingUpdateLoading(true); - - const isMetamaskNotificationsEnabled = - await this.#metamaskNotifications.selectIsMetamaskNotificationsEnabled(); - - if (isMetamaskNotificationsEnabled) { - await this.#metamaskNotifications.disableMetamaskNotifications(); - } - - const isMetaMetricsParticipation = this.getMetaMetricsState(); - - if (!isMetaMetricsParticipation) { - await this.messagingSystem.call( - 'AuthenticationController:performSignOut', - ); - } - - this.#setIsProfileSyncingUpdateLoading(false); - - this.update((state) => { - state.isProfileSyncingEnabled = false; - }); - } catch (e) { - this.#setIsProfileSyncingUpdateLoading(false); - const errorMessage = e instanceof Error ? e.message : e; - throw new Error( - `${controllerName} - failed to disable profile syncing - ${errorMessage}`, - ); - } - } - - /** - * Allows retrieval of stored data. Data stored is string formatted. - * Developers can extend the entry path and entry name through the `schema.ts` file. - * - * @param entryKey - * @returns the decrypted string contents found from user storage (or null if not found) - */ - public async performGetStorage( - entryKey: UserStorageEntryKeys, - ): Promise { - this.#assertProfileSyncingEnabled(); - const { bearerToken, storageKey } = - await this.#getStorageKeyAndBearerToken(); - const result = await getUserStorage({ - entryKey, - bearerToken, - storageKey, - }); - - return result; - } - - /** - * Allows storage of user data. Data stored must be string formatted. - * Developers can extend the entry path and entry name through the `schema.ts` file. - * - * @param entryKey - * @param value - The string data you want to store. - * @returns nothing. NOTE that an error is thrown if fails to store data. - */ - public async performSetStorage( - entryKey: UserStorageEntryKeys, - value: string, - ): Promise { - this.#assertProfileSyncingEnabled(); - const { bearerToken, storageKey } = - await this.#getStorageKeyAndBearerToken(); - - await upsertUserStorage(value, { - entryKey, - bearerToken, - storageKey, - }); - } - - /** - * Retrieves the storage key, for internal use only! - * - * @returns the storage key - */ - public async getStorageKey(): Promise { - this.#assertProfileSyncingEnabled(); - const storageKey = await this.#createStorageKey(); - return storageKey; - } - - #assertProfileSyncingEnabled(): void { - if (!this.state.isProfileSyncingEnabled) { - throw new Error( - `${controllerName}: Unable to call method, user is not authenticated`, - ); - } - } - - /** - * Utility to get the bearer token and storage key - */ - async #getStorageKeyAndBearerToken(): Promise<{ - bearerToken: string; - storageKey: string; - }> { - const bearerToken = await this.#auth.getBearerToken(); - if (!bearerToken) { - throw new Error('UserStorageController - unable to get bearer token'); - } - const storageKey = await this.#createStorageKey(); - - return { bearerToken, storageKey }; - } - - /** - * Rather than storing the storage key, we can compute the storage key when needed. - * - * @returns the storage key - */ - async #createStorageKey(): Promise { - const id = await this.#auth.getProfileId(); - if (!id) { - throw new Error('UserStorageController - unable to create storage key'); - } - - const storageKeySignature = await this.#snapSignMessage(`metamask:${id}`); - const storageKey = createSHA256Hash(storageKeySignature); - return storageKey; - } - - #_snapSignMessageCache: Record<`metamask:${string}`, string> = {}; - - /** - * Signs a specific message using an underlying auth snap. - * - * @param message - A specific tagged message to sign. - * @returns A Signature created by the snap. - */ - async #snapSignMessage(message: `metamask:${string}`): Promise { - if (this.#_snapSignMessageCache[message]) { - return this.#_snapSignMessageCache[message]; - } - - if (!this.#isUnlocked) { - throw new Error( - '#snapSignMessage - unable to call snap, wallet is locked', - ); - } - - const result = (await this.messagingSystem.call( - 'SnapController:handleRequest', - createSnapSignMessageRequest(message), - )) as string; - - this.#_snapSignMessageCache[message] = result; - - return result; - } - - async #setIsProfileSyncingUpdateLoading( - isProfileSyncingUpdateLoading: boolean, - ): Promise { - this.update((state) => { - state.isProfileSyncingUpdateLoading = isProfileSyncingUpdateLoading; - }); - } -} diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index f185d227d9ee..3f8993da613d 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -142,6 +142,10 @@ import { import { Interface } from '@ethersproject/abi'; import { abiERC1155, abiERC721 } from '@metamask/metamask-eth-abis'; import { isEvmAccountType } from '@metamask/keyring-api'; +import { + AuthenticationController, + UserStorageController, +} from '@metamask/profile-sync-controller'; import { methodsRequiringNetworkSwitch, methodsWithConfirmation, @@ -320,8 +324,6 @@ import PREINSTALLED_SNAPS from './snaps/preinstalled-snaps'; import { WeakRefObjectMap } from './lib/WeakRefObjectMap'; // Notification controllers -import AuthenticationController from './controllers/authentication/authentication-controller'; -import UserStorageController from './controllers/user-storage/user-storage-controller'; import { PushPlatformNotificationsController } from './controllers/push-platform-notifications/push-platform-notifications'; import { MetamaskNotificationsController } from './controllers/metamask-notifications/metamask-notifications'; import { createTxVerificationMiddleware } from './lib/tx-verification/tx-verification-middleware'; @@ -1414,25 +1416,25 @@ export default class MetamaskController extends EventEmitter { }); // Notification Controllers - this.authenticationController = new AuthenticationController({ + this.authenticationController = new AuthenticationController.Controller({ state: initState.AuthenticationController, messenger: this.controllerMessenger.getRestricted({ name: 'AuthenticationController', allowedActions: [ 'KeyringController:getState', 'SnapController:handleRequest', - 'UserStorageController:disableProfileSyncing', ], allowedEvents: ['KeyringController:lock', 'KeyringController:unlock'], }), metametrics: { getMetaMetricsId: () => this.metaMetricsController.getMetaMetricsId(), + agent: 'extension', }, }); - this.userStorageController = new UserStorageController({ + this.userStorageController = new UserStorageController.Controller({ getMetaMetricsState: () => - this.metaMetricsController.state.participateInMetaMetrics, + this.metaMetricsController.state.participateInMetaMetrics ?? false, state: initState.UserStorageController, messenger: this.controllerMessenger.getRestricted({ name: 'UserStorageController', @@ -1444,8 +1446,8 @@ export default class MetamaskController extends EventEmitter { 'AuthenticationController:isSignedIn', 'AuthenticationController:performSignOut', 'AuthenticationController:performSignIn', - 'MetamaskNotificationsController:disableMetamaskNotifications', - 'MetamaskNotificationsController:selectIsMetamaskNotificationsEnabled', + 'NotificationServicesController:disableNotificationServices', + 'NotificationServicesController:selectIsNotificationServicesEnabled', ], allowedEvents: ['KeyringController:lock', 'KeyringController:unlock'], }), @@ -1518,6 +1520,17 @@ export default class MetamaskController extends EventEmitter { state: initState.MetamaskNotificationsController, }); + // Temporary add missing methods (due to notification controller migration) + this.controllerMessenger.registerActionHandler( + 'NotificationServicesController:disableNotificationServices', + () => this.metamaskNotificationsController.disableMetamaskNotifications(), + ); + this.controllerMessenger.registerActionHandler( + 'NotificationServicesController:selectIsNotificationServicesEnabled', + () => + this.metamaskNotificationsController.selectIsMetamaskNotificationsEnabled(), + ); + // account tracker watches balances, nonces, and any code at their address this.accountTracker = new AccountTracker({ provider: this.provider, diff --git a/app/scripts/migrations/088.test.ts b/app/scripts/migrations/088.test.ts index 51f9ebcd2ed1..45538047d470 100644 --- a/app/scripts/migrations/088.test.ts +++ b/app/scripts/migrations/088.test.ts @@ -9,6 +9,8 @@ global.sentry = { captureException: sentryCaptureExceptionMock, }; +const invalidKeys = ['null', 'undefined']; + describe('migration #88', () => { afterEach(() => { jest.resetAllMocks(); @@ -207,28 +209,55 @@ describe('migration #88', () => { }); }); - it('deletes undefined-keyed properties from state of NftController.allNftContracts', async () => { - const oldStorage = { - meta: { version: 87 }, - data: { + for (const invalidKey of invalidKeys) { + it(`deletes ${invalidKey}-keyed properties from state of NftController.allNftContracts`, async () => { + const oldStorage = { + meta: { version: 87 }, + data: { + NftController: { + allNftContracts: { + '0x111': { + '16': [ + { + name: 'Contract 1', + address: '0xaaa', + }, + ], + [invalidKey]: [ + { + name: 'Contract 2', + address: '0xbbb', + }, + ], + }, + '0x222': { + '64': [ + { + name: 'Contract 3', + address: '0xccc', + }, + ], + }, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ NftController: { allNftContracts: { '0x111': { - '16': [ + '0x10': [ { name: 'Contract 1', address: '0xaaa', }, ], - undefined: [ - { - name: 'Contract 2', - address: '0xbbb', - }, - ], }, '0x222': { - '64': [ + '0x40': [ { name: 'Contract 3', address: '0xccc', @@ -237,34 +266,9 @@ describe('migration #88', () => { }, }, }, - }, - }; - - const newStorage = await migrate(oldStorage); - - expect(newStorage.data).toStrictEqual({ - NftController: { - allNftContracts: { - '0x111': { - '0x10': [ - { - name: 'Contract 1', - address: '0xaaa', - }, - ], - }, - '0x222': { - '0x40': [ - { - name: 'Contract 3', - address: '0xccc', - }, - ], - }, - }, - }, + }); }); - }); + } it('does not convert chain IDs in NftController.allNftContracts which are already hex strings', async () => { const oldStorage = { @@ -525,14 +529,59 @@ describe('migration #88', () => { }); }); - it('deletes undefined-keyed properties from state of NftController.allNfts', async () => { - const oldStorage = { - meta: { version: 87 }, - data: { + for (const invalidKey of invalidKeys) { + it(`deletes ${invalidKey}-keyed properties from state of NftController.allNfts`, async () => { + const oldStorage = { + meta: { version: 87 }, + data: { + NftController: { + allNfts: { + '0x111': { + '16': [ + { + name: 'NFT 1', + description: 'Description for NFT 1', + image: 'nft1.jpg', + standard: 'ERC721', + tokenId: '1', + address: '0xaaa', + }, + ], + [invalidKey]: [ + { + name: 'NFT 2', + description: 'Description for NFT 2', + image: 'nft2.jpg', + standard: 'ERC721', + tokenId: '2', + address: '0xbbb', + }, + ], + }, + '0x222': { + '64': [ + { + name: 'NFT 3', + description: 'Description for NFT 3', + image: 'nft3.jpg', + standard: 'ERC721', + tokenId: '3', + address: '0xccc', + }, + ], + }, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ NftController: { allNfts: { '0x111': { - '16': [ + '0x10': [ { name: 'NFT 1', description: 'Description for NFT 1', @@ -542,19 +591,9 @@ describe('migration #88', () => { address: '0xaaa', }, ], - undefined: [ - { - name: 'NFT 2', - description: 'Description for NFT 2', - image: 'nft2.jpg', - standard: 'ERC721', - tokenId: '2', - address: '0xbbb', - }, - ], }, '0x222': { - '64': [ + '0x40': [ { name: 'NFT 3', description: 'Description for NFT 3', @@ -567,42 +606,9 @@ describe('migration #88', () => { }, }, }, - }, - }; - - const newStorage = await migrate(oldStorage); - - expect(newStorage.data).toStrictEqual({ - NftController: { - allNfts: { - '0x111': { - '0x10': [ - { - name: 'NFT 1', - description: 'Description for NFT 1', - image: 'nft1.jpg', - standard: 'ERC721', - tokenId: '1', - address: '0xaaa', - }, - ], - }, - '0x222': { - '0x40': [ - { - name: 'NFT 3', - description: 'Description for NFT 3', - image: 'nft3.jpg', - standard: 'ERC721', - tokenId: '3', - address: '0xccc', - }, - ], - }, - }, - }, + }); }); - }); + } it('does not convert chain IDs in NftController.allNfts which are already hex strings', async () => { const oldStorage = { @@ -946,13 +952,52 @@ describe('migration #88', () => { }); }); - it('deletes undefined-keyed properties from state of TokenListController.tokensChainsCache', async () => { - const oldStorage = { - meta: { version: 87 }, - data: { + for (const invalidKey of invalidKeys) { + it(`deletes ${invalidKey}-keyed properties from state of TokenListController.tokensChainsCache`, async () => { + const oldStorage = { + meta: { version: 87 }, + data: { + TokenListController: { + tokensChainsCache: { + '16': { + timestamp: 111111, + data: { + '0x111': { + address: '0x111', + symbol: 'TEST1', + decimals: 1, + occurrences: 1, + name: 'Token 1', + iconUrl: 'https://url/to/token1.png', + aggregators: [], + }, + }, + }, + [invalidKey]: { + timestamp: 222222, + data: { + '0x222': { + address: '0x222', + symbol: 'TEST2', + decimals: 1, + occurrences: 1, + name: 'Token 2', + iconUrl: 'https://url/to/token2.png', + aggregators: [], + }, + }, + }, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ TokenListController: { tokensChainsCache: { - '16': { + '0x10': { timestamp: 111111, data: { '0x111': { @@ -966,48 +1011,11 @@ describe('migration #88', () => { }, }, }, - undefined: { - timestamp: 222222, - data: { - '0x222': { - address: '0x222', - symbol: 'TEST2', - decimals: 1, - occurrences: 1, - name: 'Token 2', - iconUrl: 'https://url/to/token2.png', - aggregators: [], - }, - }, - }, }, }, - }, - }; - - const newStorage = await migrate(oldStorage); - - expect(newStorage.data).toStrictEqual({ - TokenListController: { - tokensChainsCache: { - '0x10': { - timestamp: 111111, - data: { - '0x111': { - address: '0x111', - symbol: 'TEST1', - decimals: 1, - occurrences: 1, - name: 'Token 1', - iconUrl: 'https://url/to/token1.png', - aggregators: [], - }, - }, - }, - }, - }, + }); }); - }); + } it('does not convert chain IDs in TokenListController.tokensChainsCache which are already hex strings', async () => { const oldStorage = { @@ -1241,13 +1249,51 @@ describe('migration #88', () => { }); }); - it('deletes undefined keyed properties from TokensController.allTokens', async () => { - const oldStorage = { - meta: { version: 87 }, - data: { + for (const invalidKey of invalidKeys) { + it(`deletes ${invalidKey} keyed properties from TokensController.allTokens`, async () => { + const oldStorage = { + meta: { version: 87 }, + data: { + TokensController: { + allTokens: { + '16': { + '0x111': [ + { + address: '0xaaa', + decimals: 1, + symbol: 'TEST1', + }, + ], + }, + '32': { + '0x222': [ + { + address: '0xbbb', + decimals: 1, + symbol: 'TEST2', + }, + ], + }, + [invalidKey]: { + '0x333': [ + { + address: '0xbbb', + decimals: 1, + symbol: 'TEST2', + }, + ], + }, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ TokensController: { allTokens: { - '16': { + '0x10': { '0x111': [ { address: '0xaaa', @@ -1256,7 +1302,7 @@ describe('migration #88', () => { }, ], }, - '32': { + '0x20': { '0x222': [ { address: '0xbbb', @@ -1265,47 +1311,11 @@ describe('migration #88', () => { }, ], }, - undefined: { - '0x333': [ - { - address: '0xbbb', - decimals: 1, - symbol: 'TEST2', - }, - ], - }, }, }, - }, - }; - - const newStorage = await migrate(oldStorage); - - expect(newStorage.data).toStrictEqual({ - TokensController: { - allTokens: { - '0x10': { - '0x111': [ - { - address: '0xaaa', - decimals: 1, - symbol: 'TEST1', - }, - ], - }, - '0x20': { - '0x222': [ - { - address: '0xbbb', - decimals: 1, - symbol: 'TEST2', - }, - ], - }, - }, - }, + }); }); - }); + } it('does not convert chain IDs in TokensController.allTokens which are already hex strings', async () => { const oldStorage = { @@ -1456,51 +1466,53 @@ describe('migration #88', () => { }); }); - it('deletes undefined-keyed properties from TokensController.allIgnoredTokens', async () => { - const oldStorage = { - meta: { version: 87 }, - data: { - TokensController: { - allIgnoredTokens: { - '16': { - '0x1': { - '0x111': ['0xaaa'], + for (const invalidKey of invalidKeys) { + it(`deletes ${invalidKey}-keyed properties from TokensController.allIgnoredTokens`, async () => { + const oldStorage = { + meta: { version: 87 }, + data: { + TokensController: { + allIgnoredTokens: { + '16': { + '0x1': { + '0x111': ['0xaaa'], + }, }, - }, - '32': { - '0x2': { - '0x222': ['0xbbb'], + '32': { + '0x2': { + '0x222': ['0xbbb'], + }, }, - }, - undefined: { - '0x2': { - '0x222': ['0xbbb'], + [invalidKey]: { + '0x2': { + '0x222': ['0xbbb'], + }, }, }, }, }, - }, - }; + }; - const newStorage = await migrate(oldStorage); + const newStorage = await migrate(oldStorage); - expect(newStorage.data).toStrictEqual({ - TokensController: { - allIgnoredTokens: { - '0x10': { - '0x1': { - '0x111': ['0xaaa'], + expect(newStorage.data).toStrictEqual({ + TokensController: { + allIgnoredTokens: { + '0x10': { + '0x1': { + '0x111': ['0xaaa'], + }, }, - }, - '0x20': { - '0x2': { - '0x222': ['0xbbb'], + '0x20': { + '0x2': { + '0x222': ['0xbbb'], + }, }, }, }, - }, + }); }); - }); + } it('does not convert chain IDs in TokensController.allIgnoredTokens which are already hex strings', async () => { const oldStorage = { @@ -1635,41 +1647,43 @@ describe('migration #88', () => { }); }); - it('deletes undefined-keyed properties from TokensController.allDetectedTokens', async () => { - const oldStorage = { - meta: { version: 87 }, - data: { - TokensController: { - allDetectedTokens: { - '16': { - '0x1': { - '0x111': ['0xaaa'], + for (const invalidKey of invalidKeys) { + it(`deletes ${invalidKey}-keyed properties from TokensController.allDetectedTokens`, async () => { + const oldStorage = { + meta: { version: 87 }, + data: { + TokensController: { + allDetectedTokens: { + '16': { + '0x1': { + '0x111': ['0xaaa'], + }, }, - }, - undefined: { - '0x2': { - '0x222': ['0xbbb'], + [invalidKey]: { + '0x2': { + '0x222': ['0xbbb'], + }, }, }, }, }, - }, - }; + }; - const newStorage = await migrate(oldStorage); + const newStorage = await migrate(oldStorage); - expect(newStorage.data).toStrictEqual({ - TokensController: { - allDetectedTokens: { - '0x10': { - '0x1': { - '0x111': ['0xaaa'], + expect(newStorage.data).toStrictEqual({ + TokensController: { + allDetectedTokens: { + '0x10': { + '0x1': { + '0x111': ['0xaaa'], + }, }, }, }, - }, + }); }); - }); + } it('does not convert chain IDs in TokensController.allDetectedTokens which are already hex strings', async () => { const oldStorage = { diff --git a/app/scripts/migrations/088.ts b/app/scripts/migrations/088.ts index 6fda62183d2e..274b93624a85 100644 --- a/app/scripts/migrations/088.ts +++ b/app/scripts/migrations/088.ts @@ -59,7 +59,11 @@ function migrateData(state: Record): void { if (isObject(nftContractsByChainId)) { for (const chainId of Object.keys(nftContractsByChainId)) { - if (chainId === 'undefined' || chainId === undefined) { + if ( + chainId === 'undefined' || + chainId === undefined || + chainId === 'null' + ) { delete nftContractsByChainId[chainId]; } } @@ -96,7 +100,11 @@ function migrateData(state: Record): void { if (isObject(nftsByChainId)) { for (const chainId of Object.keys(nftsByChainId)) { - if (chainId === 'undefined' || chainId === undefined) { + if ( + chainId === 'undefined' || + chainId === undefined || + chainId === 'null' + ) { delete nftsByChainId[chainId]; } } @@ -142,7 +150,11 @@ function migrateData(state: Record): void { for (const chainId of Object.keys( tokenListControllerState.tokensChainsCache, )) { - if (chainId === 'undefined' || chainId === undefined) { + if ( + chainId === 'undefined' || + chainId === undefined || + chainId === 'null' + ) { delete tokenListControllerState.tokensChainsCache[chainId]; } } @@ -183,7 +195,11 @@ function migrateData(state: Record): void { const { allTokens } = tokensControllerState; for (const chainId of Object.keys(allTokens)) { - if (chainId === 'undefined' || chainId === undefined) { + if ( + chainId === 'undefined' || + chainId === undefined || + chainId === 'null' + ) { delete allTokens[chainId]; } } @@ -212,7 +228,11 @@ function migrateData(state: Record): void { const { allIgnoredTokens } = tokensControllerState; for (const chainId of Object.keys(allIgnoredTokens)) { - if (chainId === 'undefined' || chainId === undefined) { + if ( + chainId === 'undefined' || + chainId === undefined || + chainId === 'null' + ) { delete allIgnoredTokens[chainId]; } } @@ -241,7 +261,11 @@ function migrateData(state: Record): void { const { allDetectedTokens } = tokensControllerState; for (const chainId of Object.keys(allDetectedTokens)) { - if (chainId === 'undefined' || chainId === undefined) { + if ( + chainId === 'undefined' || + chainId === undefined || + chainId === 'null' + ) { delete allDetectedTokens[chainId]; } } diff --git a/app/scripts/migrations/120.4.test.ts b/app/scripts/migrations/120.4.test.ts new file mode 100644 index 000000000000..d37098a325b7 --- /dev/null +++ b/app/scripts/migrations/120.4.test.ts @@ -0,0 +1,231 @@ +import { cloneDeep } from 'lodash'; +import { migrate, version } from './120.4'; + +const sentryCaptureExceptionMock = jest.fn(); + +global.sentry = { + captureException: sentryCaptureExceptionMock, +}; + +const oldVersion = 120.3; + +describe('migration #120.4', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + + it('updates the version metadata', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: {}, + }; + + const newStorage = await migrate(cloneDeep(oldStorage)); + + expect(newStorage.meta).toStrictEqual({ version }); + }); + + describe('CurrencyController', () => { + it('does nothing if CurrencyController state is not set', async () => { + const oldState = { + OtherController: {}, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: cloneDeep(oldState), + }); + + expect(transformedState.data).toEqual(oldState); + }); + + it('captures an error and leaves state unchanged if CurrencyController state is corrupted', async () => { + const oldState = { + CurrencyController: 'invalid', + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: cloneDeep(oldState), + }); + + expect(transformedState.data).toEqual(oldState); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error( + `Migration ${version}: Invalid CurrencyController state of type 'string'`, + ), + ); + }); + + it('deletes obsolete properties from the CurrencyController state', async () => { + const oldState = { + CurrencyController: { + conversionDate: 'test', + conversionRate: 'test', + nativeCurrency: 'test', + pendingCurrentCurrency: 'test', + pendingNativeCurrency: 'test', + usdConversionRate: 'test', + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: cloneDeep(oldState), + }); + + expect(transformedState.data).toEqual({ CurrencyController: {} }); + }); + + it('does not delete non-obsolete properties from the CurrencyController state', async () => { + const oldState = { + CurrencyController: { + currencyRates: { test: 123 }, + conversionRate: 'test', + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: cloneDeep(oldState), + }); + + expect(transformedState.data).toEqual({ + CurrencyController: { currencyRates: { test: 123 } }, + }); + }); + }); + + describe('PhishingController', () => { + it('does nothing if PhishingController state is not set', async () => { + const oldState = { + OtherController: {}, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: cloneDeep(oldState), + }); + + expect(transformedState.data).toEqual(oldState); + }); + + it('captures an error and leaves state unchanged if PhishingController state is corrupted', async () => { + const oldState = { + PhishingController: 'invalid', + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: cloneDeep(oldState), + }); + + expect(transformedState.data).toEqual(oldState); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error( + `Migration ${version}: Invalid PhishingController state of type 'string'`, + ), + ); + }); + + it('deletes obsolete properties from the PhishingController state', async () => { + const oldState = { + PhishingController: { + phishing: 'test', + lastFetched: 'test', + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: cloneDeep(oldState), + }); + + expect(transformedState.data).toEqual({ PhishingController: {} }); + }); + + it('does not delete non-obsolete properties from the PhishingController state', async () => { + const oldState = { + PhishingController: { + phishing: 'test', + phishingLists: 'test', + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: cloneDeep(oldState), + }); + + expect(transformedState.data).toEqual({ + PhishingController: { phishingLists: 'test' }, + }); + }); + }); + + describe('NetworkController', () => { + it('does nothing if NetworkController state is not set', async () => { + const oldState = { + OtherController: {}, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: cloneDeep(oldState), + }); + + expect(transformedState.data).toEqual(oldState); + }); + + it('captures an error and leaves state unchanged if NetworkController state is corrupted', async () => { + const oldState = { + NetworkController: 'invalid', + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: cloneDeep(oldState), + }); + + expect(transformedState.data).toEqual(oldState); + expect(sentryCaptureExceptionMock).toHaveBeenCalledWith( + new Error( + `Migration ${version}: Invalid NetworkController state of type 'string'`, + ), + ); + }); + + it('deletes obsolete properties from the NetworkController state', async () => { + const oldState = { + NetworkController: { + network: 'test', + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: cloneDeep(oldState), + }); + + expect(transformedState.data).toEqual({ NetworkController: {} }); + }); + + it('does not delete non-obsolete properties from the NetworkController state', async () => { + const oldState = { + NetworkController: { + network: 'test', + selectedNetworkClientId: 'test', + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: cloneDeep(oldState), + }); + + expect(transformedState.data).toEqual({ + NetworkController: { selectedNetworkClientId: 'test' }, + }); + }); + }); +}); diff --git a/app/scripts/migrations/120.4.ts b/app/scripts/migrations/120.4.ts new file mode 100644 index 000000000000..fc4162b61d7b --- /dev/null +++ b/app/scripts/migrations/120.4.ts @@ -0,0 +1,119 @@ +import { hasProperty, isObject } from '@metamask/utils'; +import { cloneDeep } from 'lodash'; + +type VersionedData = { + meta: { version: number }; + data: Record; +}; + +export const version = 120.4; + +/** + * This migration removes properties from the CurrencyController state that + * are no longer used. There presence in state causes "No metadata found" errors + * + * @param originalVersionedData - Versioned MetaMask extension state, exactly + * what we persist to dist. + * @param originalVersionedData.meta - State metadata. + * @param originalVersionedData.meta.version - The current state version. + * @param originalVersionedData.data - The persisted MetaMask state, keyed by + * controller. + * @returns Updated versioned MetaMask extension state. + */ +export async function migrate( + originalVersionedData: VersionedData, +): Promise { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + transformState(versionedData.data); + return versionedData; +} + +/** + * Remove obsolete CurrencyController state + * + * The six properties deleted here were no longer used as of + * assets-controllers v18.0.0 + * + * See https://github.com/MetaMask/core/pull/1805 for the removal of these + * properties from the controller. + * + * @param state - The persisted MetaMask state, keyed by controller. + */ +function removeObsoleteCurrencyControllerState( + state: Record, +): void { + if (!hasProperty(state, 'CurrencyController')) { + return; + } else if (!isObject(state.CurrencyController)) { + global.sentry?.captureException?.( + new Error( + `Migration ${version}: Invalid CurrencyController state of type '${typeof state.CurrencyController}'`, + ), + ); + return; + } + + delete state.CurrencyController.conversionDate; + delete state.CurrencyController.conversionRate; + delete state.CurrencyController.nativeCurrency; + delete state.CurrencyController.pendingCurrentCurrency; + delete state.CurrencyController.pendingNativeCurrency; + delete state.CurrencyController.usdConversionRate; +} + +/** + * Remove obsolete PhishingController state + * + * @param state - The persisted MetaMask state, keyed by controller. + */ +function removeObsoletePhishingControllerState( + state: Record, +): void { + if (!hasProperty(state, 'PhishingController')) { + return; + } else if (!isObject(state.PhishingController)) { + global.sentry?.captureException?.( + new Error( + `Migration ${version}: Invalid PhishingController state of type '${typeof state.PhishingController}'`, + ), + ); + return; + } + + delete state.PhishingController.phishing; + delete state.PhishingController.lastFetched; +} + +/** + * Remove obsolete NetworkController state + * + * @param state - The persisted MetaMask state, keyed by controller. + */ +function removeObsoleteNetworkControllerState( + state: Record, +): void { + if (!hasProperty(state, 'NetworkController')) { + return; + } else if (!isObject(state.NetworkController)) { + global.sentry?.captureException?.( + new Error( + `Migration ${version}: Invalid NetworkController state of type '${typeof state.NetworkController}'`, + ), + ); + return; + } + + delete state.NetworkController.network; +} + +/** + * Remove obsolete controller state. + * + * @param state - The persisted MetaMask state, keyed by controller. + */ +function transformState(state: Record): void { + removeObsoleteCurrencyControllerState(state); + removeObsoletePhishingControllerState(state); + removeObsoleteNetworkControllerState(state); +} diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index cb6c2683d9ea..b647ab3d0a44 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -134,6 +134,7 @@ const migrations = [ require('./120.1'), require('./120.2'), require('./120.3'), + require('./120.4'), require('./121'), require('./122'), require('./123'), diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index ad2d0589b1a9..6bd4b686477a 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -1035,12 +1035,8 @@ } }, "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": { - "globals": { - "mode": true - }, "packages": { - "browserify>buffer": true, - "browserify>insert-module-globals>is-buffer": true + "browserify>buffer": true } }, "@metamask/controllers>web3": { @@ -1114,8 +1110,8 @@ }, "packages": { "@metamask/eth-json-rpc-filters>@metamask/eth-query": true, + "@metamask/eth-json-rpc-filters>@metamask/json-rpc-engine": true, "@metamask/eth-json-rpc-filters>async-mutex": true, - "@metamask/providers>@metamask/json-rpc-engine": true, "@metamask/safe-event-emitter": true, "pify": true } @@ -1126,6 +1122,13 @@ "watchify>xtend": true } }, + "@metamask/eth-json-rpc-filters>@metamask/json-rpc-engine": { + "packages": { + "@metamask/rpc-errors": true, + "@metamask/safe-event-emitter": true, + "@metamask/utils": true + } + }, "@metamask/eth-json-rpc-filters>async-mutex": { "globals": { "setTimeout": true @@ -1141,10 +1144,10 @@ "setTimeout": true }, "packages": { + "@metamask/eth-json-rpc-middleware>@metamask/json-rpc-engine": true, "@metamask/eth-json-rpc-middleware>klona": true, "@metamask/eth-json-rpc-middleware>safe-stable-stringify": true, "@metamask/eth-sig-util": true, - "@metamask/providers>@metamask/json-rpc-engine": true, "@metamask/rpc-errors": true, "@metamask/utils": true, "pify": true @@ -1152,10 +1155,24 @@ }, "@metamask/eth-json-rpc-middleware>@metamask/eth-json-rpc-provider": { "packages": { - "@metamask/providers>@metamask/json-rpc-engine": true, + "@metamask/eth-json-rpc-middleware>@metamask/eth-json-rpc-provider>@metamask/json-rpc-engine": true, "@metamask/safe-event-emitter": true } }, + "@metamask/eth-json-rpc-middleware>@metamask/eth-json-rpc-provider>@metamask/json-rpc-engine": { + "packages": { + "@metamask/rpc-errors": true, + "@metamask/safe-event-emitter": true, + "@metamask/utils": true + } + }, + "@metamask/eth-json-rpc-middleware>@metamask/json-rpc-engine": { + "packages": { + "@metamask/rpc-errors": true, + "@metamask/safe-event-emitter": true, + "@metamask/utils": true + } + }, "@metamask/eth-ledger-bridge-keyring": { "globals": { "addEventListener": true, @@ -1810,6 +1827,13 @@ "browserify>url": true } }, + "@metamask/message-signing-snap>@noble/ciphers": { + "globals": { + "TextDecoder": true, + "TextEncoder": true, + "crypto": true + } + }, "@metamask/message-signing-snap>@noble/curves": { "globals": { "TextEncoder": true @@ -1894,9 +1918,9 @@ "@metamask/network-controller>@metamask/controller-utils": true, "@metamask/network-controller>@metamask/eth-json-rpc-infura": true, "@metamask/network-controller>@metamask/eth-json-rpc-provider": true, - "@metamask/network-controller>@metamask/json-rpc-engine": true, "@metamask/network-controller>@metamask/swappable-obj-proxy": true, "@metamask/rpc-errors": true, + "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, "@metamask/utils": true, "browserify>assert": true, "uuid": true @@ -1949,40 +1973,25 @@ }, "packages": { "@metamask/eth-json-rpc-middleware>@metamask/eth-json-rpc-provider": true, - "@metamask/providers>@metamask/json-rpc-engine": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/json-rpc-engine": true, "@metamask/rpc-errors": true, "@metamask/utils": true, "node-fetch": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-provider": { + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/json-rpc-engine": { "packages": { - "@metamask/network-controller>@metamask/json-rpc-engine": true, "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, - "uuid": true + "@metamask/utils": true } }, - "@metamask/network-controller>@metamask/json-rpc-engine": { + "@metamask/network-controller>@metamask/eth-json-rpc-provider": { "packages": { - "@metamask/network-controller>@metamask/json-rpc-engine>@metamask/utils": true, "@metamask/rpc-errors": true, - "@metamask/safe-event-emitter": true - } - }, - "@metamask/network-controller>@metamask/json-rpc-engine>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/rpc-errors>@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@metamask/safe-event-emitter": true, + "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, + "uuid": true } }, "@metamask/notification-controller": { @@ -2024,9 +2033,9 @@ "packages": { "@metamask/permission-controller>@metamask/base-controller": true, "@metamask/permission-controller>@metamask/controller-utils": true, - "@metamask/permission-controller>@metamask/json-rpc-engine": true, "@metamask/permission-controller>nanoid": true, "@metamask/rpc-errors": true, + "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, "@metamask/utils": true, "deep-freeze-strict": true, "immer": true @@ -2073,28 +2082,6 @@ "semver": true } }, - "@metamask/permission-controller>@metamask/json-rpc-engine": { - "packages": { - "@metamask/permission-controller>@metamask/json-rpc-engine>@metamask/utils": true, - "@metamask/rpc-errors": true, - "@metamask/safe-event-emitter": true - } - }, - "@metamask/permission-controller>@metamask/json-rpc-engine>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/rpc-errors>@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, "@metamask/permission-controller>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2217,50 +2204,90 @@ "ethereumjs-util>ethereum-cryptography>hash.js": true } }, - "@metamask/providers>@metamask/json-rpc-engine": { + "@metamask/profile-sync-controller": { + "globals": { + "Event": true, + "Headers": true, + "TextDecoder": true, + "URL": true, + "URLSearchParams": true, + "addEventListener": true, + "console.error": true, + "dispatchEvent": true, + "fetch": true, + "removeEventListener": true, + "setTimeout": true + }, "packages": { - "@metamask/rpc-errors": true, - "@metamask/safe-event-emitter": true, - "@metamask/utils": true + "@metamask/message-signing-snap>@noble/ciphers": true, + "@metamask/profile-sync-controller>@metamask/base-controller": true, + "@metamask/profile-sync-controller>siwe": true, + "@noble/hashes": true, + "browserify>buffer": true, + "loglevel": true } }, - "@metamask/queued-request-controller": { + "@metamask/profile-sync-controller>@metamask/base-controller": { + "globals": { + "setTimeout": true + }, "packages": { - "@metamask/queued-request-controller>@metamask/base-controller": true, - "@metamask/queued-request-controller>@metamask/json-rpc-engine": true, - "@metamask/rpc-errors": true, - "@metamask/selected-network-controller": true, - "@metamask/utils": true + "immer": true } }, - "@metamask/queued-request-controller>@metamask/base-controller": { + "@metamask/profile-sync-controller>siwe": { "globals": { - "setTimeout": true + "console.error": true, + "console.warn": true }, "packages": { - "immer": true + "@metamask/controller-utils>@spruceid/siwe-parser>valid-url": true, + "@metamask/profile-sync-controller>siwe>@spruceid/siwe-parser": true, + "@metamask/profile-sync-controller>siwe>@stablelib/random": true, + "@metamask/test-bundler>ethers": true + } + }, + "@metamask/profile-sync-controller>siwe>@spruceid/siwe-parser": { + "globals": { + "console.error": true, + "console.log": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": true, + "@noble/hashes": true + } + }, + "@metamask/profile-sync-controller>siwe>@stablelib/random": { + "globals": { + "crypto": true, + "msCrypto": true + }, + "packages": { + "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary": true, + "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/wipe": true, + "browserify>browser-resolve": true + } + }, + "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary": { + "packages": { + "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary>@stablelib/int": true } }, - "@metamask/queued-request-controller>@metamask/json-rpc-engine": { + "@metamask/queued-request-controller": { "packages": { - "@metamask/queued-request-controller>@metamask/json-rpc-engine>@metamask/utils": true, + "@metamask/queued-request-controller>@metamask/base-controller": true, "@metamask/rpc-errors": true, - "@metamask/safe-event-emitter": true + "@metamask/selected-network-controller": true, + "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, + "@metamask/utils": true } }, - "@metamask/queued-request-controller>@metamask/json-rpc-engine>@metamask/utils": { + "@metamask/queued-request-controller>@metamask/base-controller": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "setTimeout": true }, "packages": { - "@metamask/rpc-errors>@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "immer": true } }, "@metamask/rate-limit-controller": { @@ -2608,21 +2635,19 @@ "globals": { "DecompressionStream": true, "URL": true, - "chrome.offscreen.createDocument": true, - "chrome.offscreen.hasDocument": true, "clearTimeout": true, "document.getElementById": true, "fetch.bind": true, "setTimeout": true }, "packages": { - "@metamask/base-controller": true, "@metamask/object-multiplex": true, + "@metamask/permission-controller": true, "@metamask/post-message-stream": true, "@metamask/rpc-errors": true, + "@metamask/snaps-controllers>@metamask/base-controller": true, "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, "@metamask/snaps-controllers>@metamask/json-rpc-middleware-stream": true, - "@metamask/snaps-controllers>@metamask/permission-controller": true, "@metamask/snaps-controllers>@xstate/fsm": true, "@metamask/snaps-controllers>concat-stream": true, "@metamask/snaps-controllers>get-npm-tarball-url": true, @@ -2644,11 +2669,34 @@ "crypto.getRandomValues": true } }, + "@metamask/snaps-controllers>@metamask/base-controller": { + "globals": { + "setTimeout": true + }, + "packages": { + "immer": true + } + }, "@metamask/snaps-controllers>@metamask/json-rpc-engine": { "packages": { "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, - "@metamask/utils": true + "@metamask/snaps-controllers>@metamask/json-rpc-engine>@metamask/utils": true + } + }, + "@metamask/snaps-controllers>@metamask/json-rpc-engine>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/rpc-errors>@metamask/utils>@metamask/superstruct": true, + "@metamask/utils>@scure/base": true, + "@metamask/utils>pony-cause": true, + "@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true } }, "@metamask/snaps-controllers>@metamask/json-rpc-middleware-stream": { @@ -2658,22 +2706,23 @@ }, "packages": { "@metamask/safe-event-emitter": true, + "@metamask/snaps-controllers>@metamask/json-rpc-middleware-stream>@metamask/utils": true, "readable-stream": true } }, - "@metamask/snaps-controllers>@metamask/permission-controller": { + "@metamask/snaps-controllers>@metamask/json-rpc-middleware-stream>@metamask/utils": { "globals": { - "console.error": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/rpc-errors": true, - "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, - "@metamask/snaps-controllers>nanoid": true, - "@metamask/utils": true, - "deep-freeze-strict": true, - "immer": true + "@metamask/rpc-errors>@metamask/utils>@metamask/superstruct": true, + "@metamask/utils>@scure/base": true, + "@metamask/utils>pony-cause": true, + "@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true } }, "@metamask/snaps-controllers>concat-stream": { @@ -2757,11 +2806,26 @@ "packages": { "@metamask/message-signing-snap>@noble/curves": true, "@metamask/scure-bip39": true, - "@metamask/utils": true, + "@metamask/snaps-sdk>@metamask/key-tree>@metamask/utils": true, "@metamask/utils>@scure/base": true, "@noble/hashes": true } }, + "@metamask/snaps-sdk>@metamask/key-tree>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/rpc-errors>@metamask/utils>@metamask/superstruct": true, + "@metamask/utils>@scure/base": true, + "@metamask/utils>pony-cause": true, + "@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true + } + }, "@metamask/snaps-utils": { "globals": { "File": true, @@ -2800,9 +2864,24 @@ "@metamask/snaps-utils>@metamask/snaps-registry": { "packages": { "@metamask/message-signing-snap>@noble/curves": true, - "@metamask/utils": true, + "@metamask/rpc-errors>@metamask/utils>@metamask/superstruct": true, + "@metamask/snaps-utils>@metamask/snaps-registry>@metamask/utils": true, + "@noble/hashes": true + } + }, + "@metamask/snaps-utils>@metamask/snaps-registry>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/rpc-errors>@metamask/utils>@metamask/superstruct": true, + "@metamask/utils>@scure/base": true, + "@metamask/utils>pony-cause": true, "@noble/hashes": true, - "superstruct": true + "browserify>buffer": true, + "nock>debug": true, + "semver": true } }, "@metamask/snaps-utils>cron-parser": { @@ -2848,6 +2927,53 @@ "@ethersproject/abi>@ethersproject/logger": true } }, + "@metamask/test-bundler>ethers": { + "packages": { + "@ethersproject/abi": true, + "@ethersproject/abi>@ethersproject/address": true, + "@ethersproject/abi>@ethersproject/constants": true, + "@ethersproject/abi>@ethersproject/keccak256": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "@ethersproject/abi>@ethersproject/strings": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "@ethersproject/contracts": true, + "@ethersproject/hash": true, + "@ethersproject/hash>@ethersproject/abstract-signer": true, + "@ethersproject/hash>@ethersproject/base64": true, + "@ethersproject/hdnode": true, + "@ethersproject/hdnode>@ethersproject/basex": true, + "@ethersproject/hdnode>@ethersproject/sha2": true, + "@ethersproject/hdnode>@ethersproject/signing-key": true, + "@ethersproject/hdnode>@ethersproject/transactions": true, + "@ethersproject/hdnode>@ethersproject/wordlists": true, + "@ethersproject/providers": true, + "@ethersproject/providers>@ethersproject/rlp": true, + "@ethersproject/providers>@ethersproject/web": true, + "@ethersproject/wallet": true, + "@ethersproject/wallet>@ethersproject/json-wallets": true, + "@ethersproject/wallet>@ethersproject/random": true, + "@metamask/test-bundler>ethers>@ethersproject/solidity": true, + "@metamask/test-bundler>ethers>@ethersproject/units": true + } + }, + "@metamask/test-bundler>ethers>@ethersproject/solidity": { + "packages": { + "@ethersproject/abi>@ethersproject/keccak256": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/strings": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "@ethersproject/hdnode>@ethersproject/sha2": true + } + }, + "@metamask/test-bundler>ethers>@ethersproject/units": { + "packages": { + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/bignumber": true + } + }, "@metamask/transaction-controller": { "globals": { "clearTimeout": true, @@ -3062,13 +3188,6 @@ "define": true } }, - "@noble/ciphers": { - "globals": { - "TextDecoder": true, - "TextEncoder": true, - "crypto": true - } - }, "@noble/hashes": { "globals": { "TextEncoder": true, diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index ad2d0589b1a9..6bd4b686477a 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -1035,12 +1035,8 @@ } }, "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": { - "globals": { - "mode": true - }, "packages": { - "browserify>buffer": true, - "browserify>insert-module-globals>is-buffer": true + "browserify>buffer": true } }, "@metamask/controllers>web3": { @@ -1114,8 +1110,8 @@ }, "packages": { "@metamask/eth-json-rpc-filters>@metamask/eth-query": true, + "@metamask/eth-json-rpc-filters>@metamask/json-rpc-engine": true, "@metamask/eth-json-rpc-filters>async-mutex": true, - "@metamask/providers>@metamask/json-rpc-engine": true, "@metamask/safe-event-emitter": true, "pify": true } @@ -1126,6 +1122,13 @@ "watchify>xtend": true } }, + "@metamask/eth-json-rpc-filters>@metamask/json-rpc-engine": { + "packages": { + "@metamask/rpc-errors": true, + "@metamask/safe-event-emitter": true, + "@metamask/utils": true + } + }, "@metamask/eth-json-rpc-filters>async-mutex": { "globals": { "setTimeout": true @@ -1141,10 +1144,10 @@ "setTimeout": true }, "packages": { + "@metamask/eth-json-rpc-middleware>@metamask/json-rpc-engine": true, "@metamask/eth-json-rpc-middleware>klona": true, "@metamask/eth-json-rpc-middleware>safe-stable-stringify": true, "@metamask/eth-sig-util": true, - "@metamask/providers>@metamask/json-rpc-engine": true, "@metamask/rpc-errors": true, "@metamask/utils": true, "pify": true @@ -1152,10 +1155,24 @@ }, "@metamask/eth-json-rpc-middleware>@metamask/eth-json-rpc-provider": { "packages": { - "@metamask/providers>@metamask/json-rpc-engine": true, + "@metamask/eth-json-rpc-middleware>@metamask/eth-json-rpc-provider>@metamask/json-rpc-engine": true, "@metamask/safe-event-emitter": true } }, + "@metamask/eth-json-rpc-middleware>@metamask/eth-json-rpc-provider>@metamask/json-rpc-engine": { + "packages": { + "@metamask/rpc-errors": true, + "@metamask/safe-event-emitter": true, + "@metamask/utils": true + } + }, + "@metamask/eth-json-rpc-middleware>@metamask/json-rpc-engine": { + "packages": { + "@metamask/rpc-errors": true, + "@metamask/safe-event-emitter": true, + "@metamask/utils": true + } + }, "@metamask/eth-ledger-bridge-keyring": { "globals": { "addEventListener": true, @@ -1810,6 +1827,13 @@ "browserify>url": true } }, + "@metamask/message-signing-snap>@noble/ciphers": { + "globals": { + "TextDecoder": true, + "TextEncoder": true, + "crypto": true + } + }, "@metamask/message-signing-snap>@noble/curves": { "globals": { "TextEncoder": true @@ -1894,9 +1918,9 @@ "@metamask/network-controller>@metamask/controller-utils": true, "@metamask/network-controller>@metamask/eth-json-rpc-infura": true, "@metamask/network-controller>@metamask/eth-json-rpc-provider": true, - "@metamask/network-controller>@metamask/json-rpc-engine": true, "@metamask/network-controller>@metamask/swappable-obj-proxy": true, "@metamask/rpc-errors": true, + "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, "@metamask/utils": true, "browserify>assert": true, "uuid": true @@ -1949,40 +1973,25 @@ }, "packages": { "@metamask/eth-json-rpc-middleware>@metamask/eth-json-rpc-provider": true, - "@metamask/providers>@metamask/json-rpc-engine": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/json-rpc-engine": true, "@metamask/rpc-errors": true, "@metamask/utils": true, "node-fetch": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-provider": { + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/json-rpc-engine": { "packages": { - "@metamask/network-controller>@metamask/json-rpc-engine": true, "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, - "uuid": true + "@metamask/utils": true } }, - "@metamask/network-controller>@metamask/json-rpc-engine": { + "@metamask/network-controller>@metamask/eth-json-rpc-provider": { "packages": { - "@metamask/network-controller>@metamask/json-rpc-engine>@metamask/utils": true, "@metamask/rpc-errors": true, - "@metamask/safe-event-emitter": true - } - }, - "@metamask/network-controller>@metamask/json-rpc-engine>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/rpc-errors>@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@metamask/safe-event-emitter": true, + "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, + "uuid": true } }, "@metamask/notification-controller": { @@ -2024,9 +2033,9 @@ "packages": { "@metamask/permission-controller>@metamask/base-controller": true, "@metamask/permission-controller>@metamask/controller-utils": true, - "@metamask/permission-controller>@metamask/json-rpc-engine": true, "@metamask/permission-controller>nanoid": true, "@metamask/rpc-errors": true, + "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, "@metamask/utils": true, "deep-freeze-strict": true, "immer": true @@ -2073,28 +2082,6 @@ "semver": true } }, - "@metamask/permission-controller>@metamask/json-rpc-engine": { - "packages": { - "@metamask/permission-controller>@metamask/json-rpc-engine>@metamask/utils": true, - "@metamask/rpc-errors": true, - "@metamask/safe-event-emitter": true - } - }, - "@metamask/permission-controller>@metamask/json-rpc-engine>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/rpc-errors>@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, "@metamask/permission-controller>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2217,50 +2204,90 @@ "ethereumjs-util>ethereum-cryptography>hash.js": true } }, - "@metamask/providers>@metamask/json-rpc-engine": { + "@metamask/profile-sync-controller": { + "globals": { + "Event": true, + "Headers": true, + "TextDecoder": true, + "URL": true, + "URLSearchParams": true, + "addEventListener": true, + "console.error": true, + "dispatchEvent": true, + "fetch": true, + "removeEventListener": true, + "setTimeout": true + }, "packages": { - "@metamask/rpc-errors": true, - "@metamask/safe-event-emitter": true, - "@metamask/utils": true + "@metamask/message-signing-snap>@noble/ciphers": true, + "@metamask/profile-sync-controller>@metamask/base-controller": true, + "@metamask/profile-sync-controller>siwe": true, + "@noble/hashes": true, + "browserify>buffer": true, + "loglevel": true } }, - "@metamask/queued-request-controller": { + "@metamask/profile-sync-controller>@metamask/base-controller": { + "globals": { + "setTimeout": true + }, "packages": { - "@metamask/queued-request-controller>@metamask/base-controller": true, - "@metamask/queued-request-controller>@metamask/json-rpc-engine": true, - "@metamask/rpc-errors": true, - "@metamask/selected-network-controller": true, - "@metamask/utils": true + "immer": true } }, - "@metamask/queued-request-controller>@metamask/base-controller": { + "@metamask/profile-sync-controller>siwe": { "globals": { - "setTimeout": true + "console.error": true, + "console.warn": true }, "packages": { - "immer": true + "@metamask/controller-utils>@spruceid/siwe-parser>valid-url": true, + "@metamask/profile-sync-controller>siwe>@spruceid/siwe-parser": true, + "@metamask/profile-sync-controller>siwe>@stablelib/random": true, + "@metamask/test-bundler>ethers": true + } + }, + "@metamask/profile-sync-controller>siwe>@spruceid/siwe-parser": { + "globals": { + "console.error": true, + "console.log": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": true, + "@noble/hashes": true + } + }, + "@metamask/profile-sync-controller>siwe>@stablelib/random": { + "globals": { + "crypto": true, + "msCrypto": true + }, + "packages": { + "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary": true, + "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/wipe": true, + "browserify>browser-resolve": true + } + }, + "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary": { + "packages": { + "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary>@stablelib/int": true } }, - "@metamask/queued-request-controller>@metamask/json-rpc-engine": { + "@metamask/queued-request-controller": { "packages": { - "@metamask/queued-request-controller>@metamask/json-rpc-engine>@metamask/utils": true, + "@metamask/queued-request-controller>@metamask/base-controller": true, "@metamask/rpc-errors": true, - "@metamask/safe-event-emitter": true + "@metamask/selected-network-controller": true, + "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, + "@metamask/utils": true } }, - "@metamask/queued-request-controller>@metamask/json-rpc-engine>@metamask/utils": { + "@metamask/queued-request-controller>@metamask/base-controller": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "setTimeout": true }, "packages": { - "@metamask/rpc-errors>@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "immer": true } }, "@metamask/rate-limit-controller": { @@ -2608,21 +2635,19 @@ "globals": { "DecompressionStream": true, "URL": true, - "chrome.offscreen.createDocument": true, - "chrome.offscreen.hasDocument": true, "clearTimeout": true, "document.getElementById": true, "fetch.bind": true, "setTimeout": true }, "packages": { - "@metamask/base-controller": true, "@metamask/object-multiplex": true, + "@metamask/permission-controller": true, "@metamask/post-message-stream": true, "@metamask/rpc-errors": true, + "@metamask/snaps-controllers>@metamask/base-controller": true, "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, "@metamask/snaps-controllers>@metamask/json-rpc-middleware-stream": true, - "@metamask/snaps-controllers>@metamask/permission-controller": true, "@metamask/snaps-controllers>@xstate/fsm": true, "@metamask/snaps-controllers>concat-stream": true, "@metamask/snaps-controllers>get-npm-tarball-url": true, @@ -2644,11 +2669,34 @@ "crypto.getRandomValues": true } }, + "@metamask/snaps-controllers>@metamask/base-controller": { + "globals": { + "setTimeout": true + }, + "packages": { + "immer": true + } + }, "@metamask/snaps-controllers>@metamask/json-rpc-engine": { "packages": { "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, - "@metamask/utils": true + "@metamask/snaps-controllers>@metamask/json-rpc-engine>@metamask/utils": true + } + }, + "@metamask/snaps-controllers>@metamask/json-rpc-engine>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/rpc-errors>@metamask/utils>@metamask/superstruct": true, + "@metamask/utils>@scure/base": true, + "@metamask/utils>pony-cause": true, + "@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true } }, "@metamask/snaps-controllers>@metamask/json-rpc-middleware-stream": { @@ -2658,22 +2706,23 @@ }, "packages": { "@metamask/safe-event-emitter": true, + "@metamask/snaps-controllers>@metamask/json-rpc-middleware-stream>@metamask/utils": true, "readable-stream": true } }, - "@metamask/snaps-controllers>@metamask/permission-controller": { + "@metamask/snaps-controllers>@metamask/json-rpc-middleware-stream>@metamask/utils": { "globals": { - "console.error": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/rpc-errors": true, - "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, - "@metamask/snaps-controllers>nanoid": true, - "@metamask/utils": true, - "deep-freeze-strict": true, - "immer": true + "@metamask/rpc-errors>@metamask/utils>@metamask/superstruct": true, + "@metamask/utils>@scure/base": true, + "@metamask/utils>pony-cause": true, + "@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true } }, "@metamask/snaps-controllers>concat-stream": { @@ -2757,11 +2806,26 @@ "packages": { "@metamask/message-signing-snap>@noble/curves": true, "@metamask/scure-bip39": true, - "@metamask/utils": true, + "@metamask/snaps-sdk>@metamask/key-tree>@metamask/utils": true, "@metamask/utils>@scure/base": true, "@noble/hashes": true } }, + "@metamask/snaps-sdk>@metamask/key-tree>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/rpc-errors>@metamask/utils>@metamask/superstruct": true, + "@metamask/utils>@scure/base": true, + "@metamask/utils>pony-cause": true, + "@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true + } + }, "@metamask/snaps-utils": { "globals": { "File": true, @@ -2800,9 +2864,24 @@ "@metamask/snaps-utils>@metamask/snaps-registry": { "packages": { "@metamask/message-signing-snap>@noble/curves": true, - "@metamask/utils": true, + "@metamask/rpc-errors>@metamask/utils>@metamask/superstruct": true, + "@metamask/snaps-utils>@metamask/snaps-registry>@metamask/utils": true, + "@noble/hashes": true + } + }, + "@metamask/snaps-utils>@metamask/snaps-registry>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/rpc-errors>@metamask/utils>@metamask/superstruct": true, + "@metamask/utils>@scure/base": true, + "@metamask/utils>pony-cause": true, "@noble/hashes": true, - "superstruct": true + "browserify>buffer": true, + "nock>debug": true, + "semver": true } }, "@metamask/snaps-utils>cron-parser": { @@ -2848,6 +2927,53 @@ "@ethersproject/abi>@ethersproject/logger": true } }, + "@metamask/test-bundler>ethers": { + "packages": { + "@ethersproject/abi": true, + "@ethersproject/abi>@ethersproject/address": true, + "@ethersproject/abi>@ethersproject/constants": true, + "@ethersproject/abi>@ethersproject/keccak256": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "@ethersproject/abi>@ethersproject/strings": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "@ethersproject/contracts": true, + "@ethersproject/hash": true, + "@ethersproject/hash>@ethersproject/abstract-signer": true, + "@ethersproject/hash>@ethersproject/base64": true, + "@ethersproject/hdnode": true, + "@ethersproject/hdnode>@ethersproject/basex": true, + "@ethersproject/hdnode>@ethersproject/sha2": true, + "@ethersproject/hdnode>@ethersproject/signing-key": true, + "@ethersproject/hdnode>@ethersproject/transactions": true, + "@ethersproject/hdnode>@ethersproject/wordlists": true, + "@ethersproject/providers": true, + "@ethersproject/providers>@ethersproject/rlp": true, + "@ethersproject/providers>@ethersproject/web": true, + "@ethersproject/wallet": true, + "@ethersproject/wallet>@ethersproject/json-wallets": true, + "@ethersproject/wallet>@ethersproject/random": true, + "@metamask/test-bundler>ethers>@ethersproject/solidity": true, + "@metamask/test-bundler>ethers>@ethersproject/units": true + } + }, + "@metamask/test-bundler>ethers>@ethersproject/solidity": { + "packages": { + "@ethersproject/abi>@ethersproject/keccak256": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/strings": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "@ethersproject/hdnode>@ethersproject/sha2": true + } + }, + "@metamask/test-bundler>ethers>@ethersproject/units": { + "packages": { + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/bignumber": true + } + }, "@metamask/transaction-controller": { "globals": { "clearTimeout": true, @@ -3062,13 +3188,6 @@ "define": true } }, - "@noble/ciphers": { - "globals": { - "TextDecoder": true, - "TextEncoder": true, - "crypto": true - } - }, "@noble/hashes": { "globals": { "TextEncoder": true, diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index ad2d0589b1a9..6bd4b686477a 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -1035,12 +1035,8 @@ } }, "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": { - "globals": { - "mode": true - }, "packages": { - "browserify>buffer": true, - "browserify>insert-module-globals>is-buffer": true + "browserify>buffer": true } }, "@metamask/controllers>web3": { @@ -1114,8 +1110,8 @@ }, "packages": { "@metamask/eth-json-rpc-filters>@metamask/eth-query": true, + "@metamask/eth-json-rpc-filters>@metamask/json-rpc-engine": true, "@metamask/eth-json-rpc-filters>async-mutex": true, - "@metamask/providers>@metamask/json-rpc-engine": true, "@metamask/safe-event-emitter": true, "pify": true } @@ -1126,6 +1122,13 @@ "watchify>xtend": true } }, + "@metamask/eth-json-rpc-filters>@metamask/json-rpc-engine": { + "packages": { + "@metamask/rpc-errors": true, + "@metamask/safe-event-emitter": true, + "@metamask/utils": true + } + }, "@metamask/eth-json-rpc-filters>async-mutex": { "globals": { "setTimeout": true @@ -1141,10 +1144,10 @@ "setTimeout": true }, "packages": { + "@metamask/eth-json-rpc-middleware>@metamask/json-rpc-engine": true, "@metamask/eth-json-rpc-middleware>klona": true, "@metamask/eth-json-rpc-middleware>safe-stable-stringify": true, "@metamask/eth-sig-util": true, - "@metamask/providers>@metamask/json-rpc-engine": true, "@metamask/rpc-errors": true, "@metamask/utils": true, "pify": true @@ -1152,10 +1155,24 @@ }, "@metamask/eth-json-rpc-middleware>@metamask/eth-json-rpc-provider": { "packages": { - "@metamask/providers>@metamask/json-rpc-engine": true, + "@metamask/eth-json-rpc-middleware>@metamask/eth-json-rpc-provider>@metamask/json-rpc-engine": true, "@metamask/safe-event-emitter": true } }, + "@metamask/eth-json-rpc-middleware>@metamask/eth-json-rpc-provider>@metamask/json-rpc-engine": { + "packages": { + "@metamask/rpc-errors": true, + "@metamask/safe-event-emitter": true, + "@metamask/utils": true + } + }, + "@metamask/eth-json-rpc-middleware>@metamask/json-rpc-engine": { + "packages": { + "@metamask/rpc-errors": true, + "@metamask/safe-event-emitter": true, + "@metamask/utils": true + } + }, "@metamask/eth-ledger-bridge-keyring": { "globals": { "addEventListener": true, @@ -1810,6 +1827,13 @@ "browserify>url": true } }, + "@metamask/message-signing-snap>@noble/ciphers": { + "globals": { + "TextDecoder": true, + "TextEncoder": true, + "crypto": true + } + }, "@metamask/message-signing-snap>@noble/curves": { "globals": { "TextEncoder": true @@ -1894,9 +1918,9 @@ "@metamask/network-controller>@metamask/controller-utils": true, "@metamask/network-controller>@metamask/eth-json-rpc-infura": true, "@metamask/network-controller>@metamask/eth-json-rpc-provider": true, - "@metamask/network-controller>@metamask/json-rpc-engine": true, "@metamask/network-controller>@metamask/swappable-obj-proxy": true, "@metamask/rpc-errors": true, + "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, "@metamask/utils": true, "browserify>assert": true, "uuid": true @@ -1949,40 +1973,25 @@ }, "packages": { "@metamask/eth-json-rpc-middleware>@metamask/eth-json-rpc-provider": true, - "@metamask/providers>@metamask/json-rpc-engine": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/json-rpc-engine": true, "@metamask/rpc-errors": true, "@metamask/utils": true, "node-fetch": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-provider": { + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/json-rpc-engine": { "packages": { - "@metamask/network-controller>@metamask/json-rpc-engine": true, "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, - "uuid": true + "@metamask/utils": true } }, - "@metamask/network-controller>@metamask/json-rpc-engine": { + "@metamask/network-controller>@metamask/eth-json-rpc-provider": { "packages": { - "@metamask/network-controller>@metamask/json-rpc-engine>@metamask/utils": true, "@metamask/rpc-errors": true, - "@metamask/safe-event-emitter": true - } - }, - "@metamask/network-controller>@metamask/json-rpc-engine>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/rpc-errors>@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@metamask/safe-event-emitter": true, + "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, + "uuid": true } }, "@metamask/notification-controller": { @@ -2024,9 +2033,9 @@ "packages": { "@metamask/permission-controller>@metamask/base-controller": true, "@metamask/permission-controller>@metamask/controller-utils": true, - "@metamask/permission-controller>@metamask/json-rpc-engine": true, "@metamask/permission-controller>nanoid": true, "@metamask/rpc-errors": true, + "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, "@metamask/utils": true, "deep-freeze-strict": true, "immer": true @@ -2073,28 +2082,6 @@ "semver": true } }, - "@metamask/permission-controller>@metamask/json-rpc-engine": { - "packages": { - "@metamask/permission-controller>@metamask/json-rpc-engine>@metamask/utils": true, - "@metamask/rpc-errors": true, - "@metamask/safe-event-emitter": true - } - }, - "@metamask/permission-controller>@metamask/json-rpc-engine>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/rpc-errors>@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, "@metamask/permission-controller>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2217,50 +2204,90 @@ "ethereumjs-util>ethereum-cryptography>hash.js": true } }, - "@metamask/providers>@metamask/json-rpc-engine": { + "@metamask/profile-sync-controller": { + "globals": { + "Event": true, + "Headers": true, + "TextDecoder": true, + "URL": true, + "URLSearchParams": true, + "addEventListener": true, + "console.error": true, + "dispatchEvent": true, + "fetch": true, + "removeEventListener": true, + "setTimeout": true + }, "packages": { - "@metamask/rpc-errors": true, - "@metamask/safe-event-emitter": true, - "@metamask/utils": true + "@metamask/message-signing-snap>@noble/ciphers": true, + "@metamask/profile-sync-controller>@metamask/base-controller": true, + "@metamask/profile-sync-controller>siwe": true, + "@noble/hashes": true, + "browserify>buffer": true, + "loglevel": true } }, - "@metamask/queued-request-controller": { + "@metamask/profile-sync-controller>@metamask/base-controller": { + "globals": { + "setTimeout": true + }, "packages": { - "@metamask/queued-request-controller>@metamask/base-controller": true, - "@metamask/queued-request-controller>@metamask/json-rpc-engine": true, - "@metamask/rpc-errors": true, - "@metamask/selected-network-controller": true, - "@metamask/utils": true + "immer": true } }, - "@metamask/queued-request-controller>@metamask/base-controller": { + "@metamask/profile-sync-controller>siwe": { "globals": { - "setTimeout": true + "console.error": true, + "console.warn": true }, "packages": { - "immer": true + "@metamask/controller-utils>@spruceid/siwe-parser>valid-url": true, + "@metamask/profile-sync-controller>siwe>@spruceid/siwe-parser": true, + "@metamask/profile-sync-controller>siwe>@stablelib/random": true, + "@metamask/test-bundler>ethers": true + } + }, + "@metamask/profile-sync-controller>siwe>@spruceid/siwe-parser": { + "globals": { + "console.error": true, + "console.log": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": true, + "@noble/hashes": true + } + }, + "@metamask/profile-sync-controller>siwe>@stablelib/random": { + "globals": { + "crypto": true, + "msCrypto": true + }, + "packages": { + "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary": true, + "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/wipe": true, + "browserify>browser-resolve": true + } + }, + "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary": { + "packages": { + "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary>@stablelib/int": true } }, - "@metamask/queued-request-controller>@metamask/json-rpc-engine": { + "@metamask/queued-request-controller": { "packages": { - "@metamask/queued-request-controller>@metamask/json-rpc-engine>@metamask/utils": true, + "@metamask/queued-request-controller>@metamask/base-controller": true, "@metamask/rpc-errors": true, - "@metamask/safe-event-emitter": true + "@metamask/selected-network-controller": true, + "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, + "@metamask/utils": true } }, - "@metamask/queued-request-controller>@metamask/json-rpc-engine>@metamask/utils": { + "@metamask/queued-request-controller>@metamask/base-controller": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "setTimeout": true }, "packages": { - "@metamask/rpc-errors>@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "immer": true } }, "@metamask/rate-limit-controller": { @@ -2608,21 +2635,19 @@ "globals": { "DecompressionStream": true, "URL": true, - "chrome.offscreen.createDocument": true, - "chrome.offscreen.hasDocument": true, "clearTimeout": true, "document.getElementById": true, "fetch.bind": true, "setTimeout": true }, "packages": { - "@metamask/base-controller": true, "@metamask/object-multiplex": true, + "@metamask/permission-controller": true, "@metamask/post-message-stream": true, "@metamask/rpc-errors": true, + "@metamask/snaps-controllers>@metamask/base-controller": true, "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, "@metamask/snaps-controllers>@metamask/json-rpc-middleware-stream": true, - "@metamask/snaps-controllers>@metamask/permission-controller": true, "@metamask/snaps-controllers>@xstate/fsm": true, "@metamask/snaps-controllers>concat-stream": true, "@metamask/snaps-controllers>get-npm-tarball-url": true, @@ -2644,11 +2669,34 @@ "crypto.getRandomValues": true } }, + "@metamask/snaps-controllers>@metamask/base-controller": { + "globals": { + "setTimeout": true + }, + "packages": { + "immer": true + } + }, "@metamask/snaps-controllers>@metamask/json-rpc-engine": { "packages": { "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, - "@metamask/utils": true + "@metamask/snaps-controllers>@metamask/json-rpc-engine>@metamask/utils": true + } + }, + "@metamask/snaps-controllers>@metamask/json-rpc-engine>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/rpc-errors>@metamask/utils>@metamask/superstruct": true, + "@metamask/utils>@scure/base": true, + "@metamask/utils>pony-cause": true, + "@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true } }, "@metamask/snaps-controllers>@metamask/json-rpc-middleware-stream": { @@ -2658,22 +2706,23 @@ }, "packages": { "@metamask/safe-event-emitter": true, + "@metamask/snaps-controllers>@metamask/json-rpc-middleware-stream>@metamask/utils": true, "readable-stream": true } }, - "@metamask/snaps-controllers>@metamask/permission-controller": { + "@metamask/snaps-controllers>@metamask/json-rpc-middleware-stream>@metamask/utils": { "globals": { - "console.error": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/rpc-errors": true, - "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, - "@metamask/snaps-controllers>nanoid": true, - "@metamask/utils": true, - "deep-freeze-strict": true, - "immer": true + "@metamask/rpc-errors>@metamask/utils>@metamask/superstruct": true, + "@metamask/utils>@scure/base": true, + "@metamask/utils>pony-cause": true, + "@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true } }, "@metamask/snaps-controllers>concat-stream": { @@ -2757,11 +2806,26 @@ "packages": { "@metamask/message-signing-snap>@noble/curves": true, "@metamask/scure-bip39": true, - "@metamask/utils": true, + "@metamask/snaps-sdk>@metamask/key-tree>@metamask/utils": true, "@metamask/utils>@scure/base": true, "@noble/hashes": true } }, + "@metamask/snaps-sdk>@metamask/key-tree>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/rpc-errors>@metamask/utils>@metamask/superstruct": true, + "@metamask/utils>@scure/base": true, + "@metamask/utils>pony-cause": true, + "@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true + } + }, "@metamask/snaps-utils": { "globals": { "File": true, @@ -2800,9 +2864,24 @@ "@metamask/snaps-utils>@metamask/snaps-registry": { "packages": { "@metamask/message-signing-snap>@noble/curves": true, - "@metamask/utils": true, + "@metamask/rpc-errors>@metamask/utils>@metamask/superstruct": true, + "@metamask/snaps-utils>@metamask/snaps-registry>@metamask/utils": true, + "@noble/hashes": true + } + }, + "@metamask/snaps-utils>@metamask/snaps-registry>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/rpc-errors>@metamask/utils>@metamask/superstruct": true, + "@metamask/utils>@scure/base": true, + "@metamask/utils>pony-cause": true, "@noble/hashes": true, - "superstruct": true + "browserify>buffer": true, + "nock>debug": true, + "semver": true } }, "@metamask/snaps-utils>cron-parser": { @@ -2848,6 +2927,53 @@ "@ethersproject/abi>@ethersproject/logger": true } }, + "@metamask/test-bundler>ethers": { + "packages": { + "@ethersproject/abi": true, + "@ethersproject/abi>@ethersproject/address": true, + "@ethersproject/abi>@ethersproject/constants": true, + "@ethersproject/abi>@ethersproject/keccak256": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "@ethersproject/abi>@ethersproject/strings": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "@ethersproject/contracts": true, + "@ethersproject/hash": true, + "@ethersproject/hash>@ethersproject/abstract-signer": true, + "@ethersproject/hash>@ethersproject/base64": true, + "@ethersproject/hdnode": true, + "@ethersproject/hdnode>@ethersproject/basex": true, + "@ethersproject/hdnode>@ethersproject/sha2": true, + "@ethersproject/hdnode>@ethersproject/signing-key": true, + "@ethersproject/hdnode>@ethersproject/transactions": true, + "@ethersproject/hdnode>@ethersproject/wordlists": true, + "@ethersproject/providers": true, + "@ethersproject/providers>@ethersproject/rlp": true, + "@ethersproject/providers>@ethersproject/web": true, + "@ethersproject/wallet": true, + "@ethersproject/wallet>@ethersproject/json-wallets": true, + "@ethersproject/wallet>@ethersproject/random": true, + "@metamask/test-bundler>ethers>@ethersproject/solidity": true, + "@metamask/test-bundler>ethers>@ethersproject/units": true + } + }, + "@metamask/test-bundler>ethers>@ethersproject/solidity": { + "packages": { + "@ethersproject/abi>@ethersproject/keccak256": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/strings": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "@ethersproject/hdnode>@ethersproject/sha2": true + } + }, + "@metamask/test-bundler>ethers>@ethersproject/units": { + "packages": { + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/bignumber": true + } + }, "@metamask/transaction-controller": { "globals": { "clearTimeout": true, @@ -3062,13 +3188,6 @@ "define": true } }, - "@noble/ciphers": { - "globals": { - "TextDecoder": true, - "TextEncoder": true, - "crypto": true - } - }, "@noble/hashes": { "globals": { "TextEncoder": true, diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index cb10c2e972d3..015a2e6706a0 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -1127,12 +1127,8 @@ } }, "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": { - "globals": { - "mode": true - }, "packages": { - "browserify>buffer": true, - "browserify>insert-module-globals>is-buffer": true + "browserify>buffer": true } }, "@metamask/controllers>web3": { @@ -1206,8 +1202,8 @@ }, "packages": { "@metamask/eth-json-rpc-filters>@metamask/eth-query": true, + "@metamask/eth-json-rpc-filters>@metamask/json-rpc-engine": true, "@metamask/eth-json-rpc-filters>async-mutex": true, - "@metamask/providers>@metamask/json-rpc-engine": true, "@metamask/safe-event-emitter": true, "pify": true } @@ -1218,6 +1214,13 @@ "watchify>xtend": true } }, + "@metamask/eth-json-rpc-filters>@metamask/json-rpc-engine": { + "packages": { + "@metamask/rpc-errors": true, + "@metamask/safe-event-emitter": true, + "@metamask/utils": true + } + }, "@metamask/eth-json-rpc-filters>async-mutex": { "globals": { "setTimeout": true @@ -1233,10 +1236,10 @@ "setTimeout": true }, "packages": { + "@metamask/eth-json-rpc-middleware>@metamask/json-rpc-engine": true, "@metamask/eth-json-rpc-middleware>klona": true, "@metamask/eth-json-rpc-middleware>safe-stable-stringify": true, "@metamask/eth-sig-util": true, - "@metamask/providers>@metamask/json-rpc-engine": true, "@metamask/rpc-errors": true, "@metamask/utils": true, "pify": true @@ -1244,10 +1247,24 @@ }, "@metamask/eth-json-rpc-middleware>@metamask/eth-json-rpc-provider": { "packages": { - "@metamask/providers>@metamask/json-rpc-engine": true, + "@metamask/eth-json-rpc-middleware>@metamask/eth-json-rpc-provider>@metamask/json-rpc-engine": true, "@metamask/safe-event-emitter": true } }, + "@metamask/eth-json-rpc-middleware>@metamask/eth-json-rpc-provider>@metamask/json-rpc-engine": { + "packages": { + "@metamask/rpc-errors": true, + "@metamask/safe-event-emitter": true, + "@metamask/utils": true + } + }, + "@metamask/eth-json-rpc-middleware>@metamask/json-rpc-engine": { + "packages": { + "@metamask/rpc-errors": true, + "@metamask/safe-event-emitter": true, + "@metamask/utils": true + } + }, "@metamask/eth-ledger-bridge-keyring": { "globals": { "addEventListener": true, @@ -1902,6 +1919,13 @@ "browserify>url": true } }, + "@metamask/message-signing-snap>@noble/ciphers": { + "globals": { + "TextDecoder": true, + "TextEncoder": true, + "crypto": true + } + }, "@metamask/message-signing-snap>@noble/curves": { "globals": { "TextEncoder": true @@ -1986,9 +2010,9 @@ "@metamask/network-controller>@metamask/controller-utils": true, "@metamask/network-controller>@metamask/eth-json-rpc-infura": true, "@metamask/network-controller>@metamask/eth-json-rpc-provider": true, - "@metamask/network-controller>@metamask/json-rpc-engine": true, "@metamask/network-controller>@metamask/swappable-obj-proxy": true, "@metamask/rpc-errors": true, + "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, "@metamask/utils": true, "browserify>assert": true, "uuid": true @@ -2041,40 +2065,25 @@ }, "packages": { "@metamask/eth-json-rpc-middleware>@metamask/eth-json-rpc-provider": true, - "@metamask/providers>@metamask/json-rpc-engine": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/json-rpc-engine": true, "@metamask/rpc-errors": true, "@metamask/utils": true, "node-fetch": true } }, - "@metamask/network-controller>@metamask/eth-json-rpc-provider": { + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/json-rpc-engine": { "packages": { - "@metamask/network-controller>@metamask/json-rpc-engine": true, "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, - "uuid": true + "@metamask/utils": true } }, - "@metamask/network-controller>@metamask/json-rpc-engine": { + "@metamask/network-controller>@metamask/eth-json-rpc-provider": { "packages": { - "@metamask/network-controller>@metamask/json-rpc-engine>@metamask/utils": true, "@metamask/rpc-errors": true, - "@metamask/safe-event-emitter": true - } - }, - "@metamask/network-controller>@metamask/json-rpc-engine>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/rpc-errors>@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "@metamask/safe-event-emitter": true, + "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, + "uuid": true } }, "@metamask/notification-controller": { @@ -2116,9 +2125,9 @@ "packages": { "@metamask/permission-controller>@metamask/base-controller": true, "@metamask/permission-controller>@metamask/controller-utils": true, - "@metamask/permission-controller>@metamask/json-rpc-engine": true, "@metamask/permission-controller>nanoid": true, "@metamask/rpc-errors": true, + "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, "@metamask/utils": true, "deep-freeze-strict": true, "immer": true @@ -2165,28 +2174,6 @@ "semver": true } }, - "@metamask/permission-controller>@metamask/json-rpc-engine": { - "packages": { - "@metamask/permission-controller>@metamask/json-rpc-engine>@metamask/utils": true, - "@metamask/rpc-errors": true, - "@metamask/safe-event-emitter": true - } - }, - "@metamask/permission-controller>@metamask/json-rpc-engine>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "@metamask/rpc-errors>@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true - } - }, "@metamask/permission-controller>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2309,50 +2296,90 @@ "ethereumjs-util>ethereum-cryptography>hash.js": true } }, - "@metamask/providers>@metamask/json-rpc-engine": { + "@metamask/profile-sync-controller": { + "globals": { + "Event": true, + "Headers": true, + "TextDecoder": true, + "URL": true, + "URLSearchParams": true, + "addEventListener": true, + "console.error": true, + "dispatchEvent": true, + "fetch": true, + "removeEventListener": true, + "setTimeout": true + }, "packages": { - "@metamask/rpc-errors": true, - "@metamask/safe-event-emitter": true, - "@metamask/utils": true + "@metamask/message-signing-snap>@noble/ciphers": true, + "@metamask/profile-sync-controller>@metamask/base-controller": true, + "@metamask/profile-sync-controller>siwe": true, + "@noble/hashes": true, + "browserify>buffer": true, + "loglevel": true } }, - "@metamask/queued-request-controller": { + "@metamask/profile-sync-controller>@metamask/base-controller": { + "globals": { + "setTimeout": true + }, "packages": { - "@metamask/queued-request-controller>@metamask/base-controller": true, - "@metamask/queued-request-controller>@metamask/json-rpc-engine": true, - "@metamask/rpc-errors": true, - "@metamask/selected-network-controller": true, - "@metamask/utils": true + "immer": true } }, - "@metamask/queued-request-controller>@metamask/base-controller": { + "@metamask/profile-sync-controller>siwe": { "globals": { - "setTimeout": true + "console.error": true, + "console.warn": true }, "packages": { - "immer": true + "@metamask/controller-utils>@spruceid/siwe-parser>valid-url": true, + "@metamask/profile-sync-controller>siwe>@spruceid/siwe-parser": true, + "@metamask/profile-sync-controller>siwe>@stablelib/random": true, + "@metamask/test-bundler>ethers": true + } + }, + "@metamask/profile-sync-controller>siwe>@spruceid/siwe-parser": { + "globals": { + "console.error": true, + "console.log": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": true, + "@noble/hashes": true + } + }, + "@metamask/profile-sync-controller>siwe>@stablelib/random": { + "globals": { + "crypto": true, + "msCrypto": true + }, + "packages": { + "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary": true, + "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/wipe": true, + "browserify>browser-resolve": true + } + }, + "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary": { + "packages": { + "@metamask/profile-sync-controller>siwe>@stablelib/random>@stablelib/binary>@stablelib/int": true } }, - "@metamask/queued-request-controller>@metamask/json-rpc-engine": { + "@metamask/queued-request-controller": { "packages": { - "@metamask/queued-request-controller>@metamask/json-rpc-engine>@metamask/utils": true, + "@metamask/queued-request-controller>@metamask/base-controller": true, "@metamask/rpc-errors": true, - "@metamask/safe-event-emitter": true + "@metamask/selected-network-controller": true, + "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, + "@metamask/utils": true } }, - "@metamask/queued-request-controller>@metamask/json-rpc-engine>@metamask/utils": { + "@metamask/queued-request-controller>@metamask/base-controller": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "setTimeout": true }, "packages": { - "@metamask/rpc-errors>@metamask/utils>@metamask/superstruct": true, - "@metamask/utils>@scure/base": true, - "@metamask/utils>pony-cause": true, - "@noble/hashes": true, - "browserify>buffer": true, - "nock>debug": true, - "semver": true + "immer": true } }, "@metamask/rate-limit-controller": { @@ -2700,21 +2727,19 @@ "globals": { "DecompressionStream": true, "URL": true, - "chrome.offscreen.createDocument": true, - "chrome.offscreen.hasDocument": true, "clearTimeout": true, "document.getElementById": true, "fetch.bind": true, "setTimeout": true }, "packages": { - "@metamask/base-controller": true, "@metamask/object-multiplex": true, + "@metamask/permission-controller": true, "@metamask/post-message-stream": true, "@metamask/rpc-errors": true, + "@metamask/snaps-controllers>@metamask/base-controller": true, "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, "@metamask/snaps-controllers>@metamask/json-rpc-middleware-stream": true, - "@metamask/snaps-controllers>@metamask/permission-controller": true, "@metamask/snaps-controllers>@xstate/fsm": true, "@metamask/snaps-controllers>concat-stream": true, "@metamask/snaps-controllers>get-npm-tarball-url": true, @@ -2736,11 +2761,34 @@ "crypto.getRandomValues": true } }, + "@metamask/snaps-controllers>@metamask/base-controller": { + "globals": { + "setTimeout": true + }, + "packages": { + "immer": true + } + }, "@metamask/snaps-controllers>@metamask/json-rpc-engine": { "packages": { "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, - "@metamask/utils": true + "@metamask/snaps-controllers>@metamask/json-rpc-engine>@metamask/utils": true + } + }, + "@metamask/snaps-controllers>@metamask/json-rpc-engine>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/rpc-errors>@metamask/utils>@metamask/superstruct": true, + "@metamask/utils>@scure/base": true, + "@metamask/utils>pony-cause": true, + "@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true } }, "@metamask/snaps-controllers>@metamask/json-rpc-middleware-stream": { @@ -2750,22 +2798,23 @@ }, "packages": { "@metamask/safe-event-emitter": true, + "@metamask/snaps-controllers>@metamask/json-rpc-middleware-stream>@metamask/utils": true, "readable-stream": true } }, - "@metamask/snaps-controllers>@metamask/permission-controller": { + "@metamask/snaps-controllers>@metamask/json-rpc-middleware-stream>@metamask/utils": { "globals": { - "console.error": true + "TextDecoder": true, + "TextEncoder": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/rpc-errors": true, - "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, - "@metamask/snaps-controllers>nanoid": true, - "@metamask/utils": true, - "deep-freeze-strict": true, - "immer": true + "@metamask/rpc-errors>@metamask/utils>@metamask/superstruct": true, + "@metamask/utils>@scure/base": true, + "@metamask/utils>pony-cause": true, + "@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true } }, "@metamask/snaps-controllers>concat-stream": { @@ -2849,11 +2898,26 @@ "packages": { "@metamask/message-signing-snap>@noble/curves": true, "@metamask/scure-bip39": true, - "@metamask/utils": true, + "@metamask/snaps-sdk>@metamask/key-tree>@metamask/utils": true, "@metamask/utils>@scure/base": true, "@noble/hashes": true } }, + "@metamask/snaps-sdk>@metamask/key-tree>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/rpc-errors>@metamask/utils>@metamask/superstruct": true, + "@metamask/utils>@scure/base": true, + "@metamask/utils>pony-cause": true, + "@noble/hashes": true, + "browserify>buffer": true, + "nock>debug": true, + "semver": true + } + }, "@metamask/snaps-utils": { "globals": { "File": true, @@ -2892,9 +2956,24 @@ "@metamask/snaps-utils>@metamask/snaps-registry": { "packages": { "@metamask/message-signing-snap>@noble/curves": true, - "@metamask/utils": true, + "@metamask/rpc-errors>@metamask/utils>@metamask/superstruct": true, + "@metamask/snaps-utils>@metamask/snaps-registry>@metamask/utils": true, + "@noble/hashes": true + } + }, + "@metamask/snaps-utils>@metamask/snaps-registry>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/rpc-errors>@metamask/utils>@metamask/superstruct": true, + "@metamask/utils>@scure/base": true, + "@metamask/utils>pony-cause": true, "@noble/hashes": true, - "superstruct": true + "browserify>buffer": true, + "nock>debug": true, + "semver": true } }, "@metamask/snaps-utils>cron-parser": { @@ -2940,6 +3019,53 @@ "@ethersproject/abi>@ethersproject/logger": true } }, + "@metamask/test-bundler>ethers": { + "packages": { + "@ethersproject/abi": true, + "@ethersproject/abi>@ethersproject/address": true, + "@ethersproject/abi>@ethersproject/constants": true, + "@ethersproject/abi>@ethersproject/keccak256": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "@ethersproject/abi>@ethersproject/strings": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "@ethersproject/contracts": true, + "@ethersproject/hash": true, + "@ethersproject/hash>@ethersproject/abstract-signer": true, + "@ethersproject/hash>@ethersproject/base64": true, + "@ethersproject/hdnode": true, + "@ethersproject/hdnode>@ethersproject/basex": true, + "@ethersproject/hdnode>@ethersproject/sha2": true, + "@ethersproject/hdnode>@ethersproject/signing-key": true, + "@ethersproject/hdnode>@ethersproject/transactions": true, + "@ethersproject/hdnode>@ethersproject/wordlists": true, + "@ethersproject/providers": true, + "@ethersproject/providers>@ethersproject/rlp": true, + "@ethersproject/providers>@ethersproject/web": true, + "@ethersproject/wallet": true, + "@ethersproject/wallet>@ethersproject/json-wallets": true, + "@ethersproject/wallet>@ethersproject/random": true, + "@metamask/test-bundler>ethers>@ethersproject/solidity": true, + "@metamask/test-bundler>ethers>@ethersproject/units": true + } + }, + "@metamask/test-bundler>ethers>@ethersproject/solidity": { + "packages": { + "@ethersproject/abi>@ethersproject/keccak256": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/strings": true, + "@ethersproject/bignumber": true, + "@ethersproject/bytes": true, + "@ethersproject/hdnode>@ethersproject/sha2": true + } + }, + "@metamask/test-bundler>ethers>@ethersproject/units": { + "packages": { + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/bignumber": true + } + }, "@metamask/transaction-controller": { "globals": { "clearTimeout": true, @@ -3154,13 +3280,6 @@ "define": true } }, - "@noble/ciphers": { - "globals": { - "TextDecoder": true, - "TextEncoder": true, - "crypto": true - } - }, "@noble/hashes": { "globals": { "TextEncoder": true, diff --git a/package.json b/package.json index d8b69ce36eba..af20530a7bda 100644 --- a/package.json +++ b/package.json @@ -261,12 +261,8 @@ "@solana/web3.js/rpc-websockets": "^8.0.1", "@metamask/network-controller@npm:^19.0.0": "patch:@metamask/network-controller@npm%3A19.0.0#~/.yarn/patches/@metamask-network-controller-npm-19.0.0-a5e0d1fe14.patch", "@metamask/gas-fee-controller@npm:^15.1.1": "patch:@metamask/gas-fee-controller@npm%3A15.1.2#~/.yarn/patches/@metamask-gas-fee-controller-npm-15.1.2-db4d2976aa.patch", - "@metamask/snaps-controllers@npm:^4.1.0": "patch:@metamask/snaps-controllers@npm%3A8.4.0#~/.yarn/patches/@metamask-snaps-controllers-npm-8.4.0-574cd5a8a9.patch", - "@metamask/snaps-controllers@npm:^3.4.1": "patch:@metamask/snaps-controllers@npm%3A8.4.0#~/.yarn/patches/@metamask-snaps-controllers-npm-8.4.0-574cd5a8a9.patch", - "@metamask/snaps-controllers@npm:^7.0.1": "patch:@metamask/snaps-controllers@npm%3A8.4.0#~/.yarn/patches/@metamask-snaps-controllers-npm-8.4.0-574cd5a8a9.patch", - "@metamask/snaps-controllers@npm:^8.1.1": "patch:@metamask/snaps-controllers@npm%3A8.4.0#~/.yarn/patches/@metamask-snaps-controllers-npm-8.4.0-574cd5a8a9.patch", - "@metamask/snaps-controllers@npm:^9.0.0": "patch:@metamask/snaps-controllers@npm%3A8.4.0#~/.yarn/patches/@metamask-snaps-controllers-npm-8.4.0-574cd5a8a9.patch", - "@metamask/snaps-controllers@npm:^9.2.0": "patch:@metamask/snaps-controllers@npm%3A8.4.0#~/.yarn/patches/@metamask-snaps-controllers-npm-8.4.0-574cd5a8a9.patch", + "@metamask/snaps-controllers@npm:^8.1.1": "patch:@metamask/snaps-controllers@npm%3A9.2.0#~/.yarn/patches/@metamask-snaps-controllers-npm-9.2.0-09a31bab4f.patch", + "@metamask/snaps-controllers@npm:^9.2.0": "patch:@metamask/snaps-controllers@npm%3A9.2.0#~/.yarn/patches/@metamask-snaps-controllers-npm-9.2.0-09a31bab4f.patch", "@metamask/nonce-tracker@npm:^5.0.0": "patch:@metamask/nonce-tracker@npm%3A5.0.0#~/.yarn/patches/@metamask-nonce-tracker-npm-5.0.0-d81478218e.patch", "@metamask/snaps-utils@npm:^7.7.0": "patch:@metamask/snaps-utils@npm%3A7.7.0#~/.yarn/patches/@metamask-snaps-utils-npm-7.7.0-2cc1f044af.patch", "@metamask/snaps-utils@npm:^7.4.0": "patch:@metamask/snaps-utils@npm%3A7.7.0#~/.yarn/patches/@metamask-snaps-utils-npm-7.7.0-2cc1f044af.patch", @@ -347,6 +343,7 @@ "@metamask/phishing-controller": "^9.0.3", "@metamask/post-message-stream": "^8.0.0", "@metamask/ppom-validator": "^0.32.0", + "@metamask/profile-sync-controller": "^0.2.0", "@metamask/providers": "^14.0.2", "@metamask/queued-request-controller": "^2.0.0", "@metamask/rate-limit-controller": "^5.0.1", @@ -365,7 +362,6 @@ "@metamask/user-operation-controller": "^13.0.0", "@metamask/utils": "^8.2.1", "@ngraveio/bc-ur": "^1.1.12", - "@noble/ciphers": "^0.5.2", "@noble/hashes": "^1.3.3", "@popperjs/core": "^2.4.0", "@reduxjs/toolkit": "patch:@reduxjs/toolkit@npm%3A1.9.7#~/.yarn/patches/@reduxjs-toolkit-npm-1.9.7-b14925495c.patch", diff --git a/shared/constants/metametrics.ts b/shared/constants/metametrics.ts index 69571f2725ac..02cdde12d159 100644 --- a/shared/constants/metametrics.ts +++ b/shared/constants/metametrics.ts @@ -708,7 +708,7 @@ export enum MetaMetricsEventName { NotificationReceived = 'Notification Received', NotificationsSettingsUpdated = 'Notifications Settings Updated', NotificationClicked = 'Notification Clicked', - NotificationsEnablingFlowHandled = 'Notifications Enabling Flow Handled', + NotificationMenuOpened = 'Notification Menu Opened', NftAutoDetectionEnableModal = 'Nft Autodetection Enabled from modal', NftAutoDetectionDisableModal = 'Nft Autodetection Disabled from modal', @@ -756,7 +756,6 @@ export enum MetaMetricsEventCategory { Messages = 'Messages', Navigation = 'Navigation', Network = 'Network', - EnableNotifications = 'Enable Notifications', Onboarding = 'Onboarding', NotificationInteraction = 'Notification Interaction', NotificationSettings = 'Notification Settings', diff --git a/test/e2e/accounts/common.ts b/test/e2e/accounts/common.ts index 95f33a9bdfb0..d25983d0a816 100644 --- a/test/e2e/accounts/common.ts +++ b/test/e2e/accounts/common.ts @@ -183,7 +183,7 @@ async function switchToAccount2(driver: Driver) { await driver.clickElement({ tag: 'Button', - text: 'Snap Account 1', + text: 'SSK Account', }); await driver.assertElementNotPresent({ diff --git a/test/e2e/accounts/create-snap-account.spec.ts b/test/e2e/accounts/create-snap-account.spec.ts index 28bc4d44bb19..2a35b4b4c805 100644 --- a/test/e2e/accounts/create-snap-account.spec.ts +++ b/test/e2e/accounts/create-snap-account.spec.ts @@ -1,14 +1,9 @@ import { Suite } from 'mocha'; import FixtureBuilder from '../fixture-builder'; -import { - defaultGanacheOptions, - unlockWallet, - WINDOW_TITLES, - withFixtures, -} from '../helpers'; +import { defaultGanacheOptions, WINDOW_TITLES, withFixtures } from '../helpers'; import { Driver } from '../webdriver/driver'; -import { TEST_SNAPS_SIMPLE_KEYRING_WEBSITE_URL } from '../constants'; +import { installSnapSimpleKeyring } from './common'; /** * Starts the flow to create a Snap account, including unlocking the wallet, @@ -20,34 +15,7 @@ import { TEST_SNAPS_SIMPLE_KEYRING_WEBSITE_URL } from '../constants'; * @returns A promise that resolves when the setup steps are complete. */ async function startCreateSnapAccountFlow(driver: Driver): Promise { - await unlockWallet(driver); - - // navigate to test Snaps page and connect - await driver.openNewPage(TEST_SNAPS_SIMPLE_KEYRING_WEBSITE_URL); - await driver.clickElement('#connectButton'); - - // switch to metamask extension and click connect to start installing the snap - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.clickElement({ - text: 'Connect', - tag: 'button', - }); - - // scroll to the bottom of the page - await driver.waitForSelector({ text: 'Confirm' }); - await driver.clickElementSafe('[data-testid="snap-install-scroll"]'); - - // click the install button to install the snap - await driver.waitForSelector({ text: 'Confirm' }); - await driver.clickElement({ - text: 'Confirm', - tag: 'button', - }); - await driver.waitForSelector({ text: 'OK' }); - await driver.clickElement({ - text: 'OK', - tag: 'button', - }); + await installSnapSimpleKeyring(driver, false); // move back to the Snap window to test the create account flow await driver.waitAndSwitchToWindowWithTitle( @@ -125,14 +93,14 @@ describe('Create Snap Account', function (this: Suite) { '[data-testid="submit-add-account-with-name"]', ); - // success screen should show account created with the default name + // success screen should show account created with the snap suggested name await driver.findElement({ tag: 'h3', text: 'Account created', }); await driver.findElement({ css: '.multichain-account-list-item__account-name__button', - text: 'Snap Account 1', + text: 'SSK Account', }); // click the okay button @@ -155,15 +123,80 @@ describe('Create Snap Account', function (this: Suite) { WINDOW_TITLES.ExtensionInFullScreenView, ); - // account should be created with the default name + // account should be created with the snap suggested name await driver.findElement({ css: '[data-testid="account-menu-icon"]', - text: 'Snap Account 1', + text: 'SSK Account', }); }, ); }); + it('creates multiple Snap accounts with increasing numeric suffixes', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions: defaultGanacheOptions, + title: this.test?.fullTitle(), + }, + async ({ driver }: { driver: Driver }) => { + await installSnapSimpleKeyring(driver, false); + + const expectedNames = ['SSK Account', 'SSK Account 2', 'SSK Account 3']; + + for (const [index, expectedName] of expectedNames.entries()) { + // move to the dapp window + await driver.waitAndSwitchToWindowWithTitle( + 2, + WINDOW_TITLES.SnapSimpleKeyringDapp, + ); + + // create new account on dapp + if (index === 0) { + // Only click the div for the first snap account creation + await driver.clickElement({ + text: 'Create account', + tag: 'div', + }); + } + await driver.clickElement({ + text: 'Create Account', + tag: 'button', + }); + + // wait until dialog is opened before proceeding + await driver.waitAndSwitchToWindowWithTitle(3, WINDOW_TITLES.Dialog); + + // click the create button on the confirmation modal + await driver.clickElement( + '[data-testid="confirmation-submit-button"]', + ); + + // click the add account button on the naming modal + await driver.clickElement( + '[data-testid="submit-add-account-with-name"]', + ); + + // click the okay button on the success screen + await driver.clickElement( + '[data-testid="confirmation-submit-button"]', + ); + + // switch to extension full screen view + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + // verify the account is created with the expected name + await driver.findElement({ + css: '[data-testid="account-menu-icon"]', + text: expectedName, + }); + } + }, + ); + }); + it('create Snap account confirmation flow ends in approval success with custom name input', async function () { await withFixtures( { @@ -180,7 +213,7 @@ describe('Create Snap Account', function (this: Suite) { // Add a custom name to the account const newAccountLabel = 'Custom name'; - await driver.fill('[placeholder="Snap Account 1"]', newAccountLabel); + await driver.fill('[placeholder="SSK Account"]', newAccountLabel); // click the add account button on the naming modal await driver.clickElement( '[data-testid="submit-add-account-with-name"]', @@ -259,7 +292,7 @@ describe('Create Snap Account', function (this: Suite) { // account should not be created await driver.assertElementNotPresent({ css: '[data-testid="account-menu-icon"]', - text: 'Snap Account 1', + text: 'SSK Account', }); }, ); @@ -304,7 +337,7 @@ describe('Create Snap Account', function (this: Suite) { // account should not be created await driver.assertElementNotPresent({ css: '[data-testid="account-menu-icon"]', - text: 'Snap Account 1', + text: 'SSK Account', }); }, ); diff --git a/test/e2e/accounts/snap-account-contract-interaction.spec.ts b/test/e2e/accounts/snap-account-contract-interaction.spec.ts new file mode 100644 index 000000000000..4588d014802c --- /dev/null +++ b/test/e2e/accounts/snap-account-contract-interaction.spec.ts @@ -0,0 +1,87 @@ +import GanacheContractAddressRegistry from '../seeder/ganache-contract-address-registry'; +import { scrollAndConfirmAndAssertConfirm } from '../tests/confirmations/helpers'; +import { + createDepositTransaction, + TestSuiteArguments, +} from '../tests/confirmations/transactions/shared'; +import { + multipleGanacheOptionsForType2Transactions, + withFixtures, + openDapp, + WINDOW_TITLES, + locateAccountBalanceDOM, + clickNestedButton, + ACCOUNT_2, +} from '../helpers'; +import FixtureBuilder from '../fixture-builder'; +import { SMART_CONTRACTS } from '../seeder/smart-contracts'; +import { installSnapSimpleKeyring, importKeyAndSwitch } from './common'; + +describe('Snap Account Contract interaction', function () { + const smartContract = SMART_CONTRACTS.PIGGYBANK; + + it('deposits to piggybank contract', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerSnapAccountConnectedToTestDapp() + .withPreferencesController({ + preferences: { + redesignedConfirmationsEnabled: true, + isRedesignedConfirmationsDeveloperEnabled: true, + }, + }) + .build(), + ganacheOptions: multipleGanacheOptionsForType2Transactions, + smartContract, + title: this.test?.fullTitle(), + }, + async ({ + driver, + contractRegistry, + ganacheServer, + }: TestSuiteArguments) => { + // Install Snap Simple Keyring and import key + await installSnapSimpleKeyring(driver, false); + await importKeyAndSwitch(driver); + + // Open DApp with contract + const contractAddress = await ( + contractRegistry as GanacheContractAddressRegistry + ).getContractAddress(smartContract); + await openDapp(driver, contractAddress); + + // Create and confirm deposit transaction + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await createDepositTransaction(driver); + await driver.waitUntilXWindowHandles(4); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await driver.waitForSelector({ + css: 'h2', + text: 'Transaction request', + }); + await scrollAndConfirmAndAssertConfirm(driver); + + // Confirm the transaction activity + await driver.waitUntilXWindowHandles(3); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + await clickNestedButton(driver, 'Activity'); + await driver.waitForSelector( + '.transaction-list__completed-transactions .activity-list-item:nth-of-type(1)', + ); + await driver.waitForSelector({ + css: '[data-testid="transaction-list-item-primary-currency"]', + text: '-4 ETH', + }); + + // renders the correct ETH balance + await locateAccountBalanceDOM(driver, ganacheServer, ACCOUNT_2); + }, + ); + }); +}); diff --git a/test/e2e/constants.ts b/test/e2e/constants.ts index ca5d5cd88543..4dee1053aaee 100644 --- a/test/e2e/constants.ts +++ b/test/e2e/constants.ts @@ -31,7 +31,7 @@ export const SIMPLE_ACCOUNT_FACTORY = /* URL of the Snap Simple Keyring site. */ export const TEST_SNAPS_SIMPLE_KEYRING_WEBSITE_URL = - 'https://metamask.github.io/snap-simple-keyring/1.1.1/'; + 'https://metamask.github.io/snap-simple-keyring/1.1.2/'; /* Address of the VerifyingPaymaster smart contract deployed to Ganache. */ export const VERIFYING_PAYMASTER = '0xbdbDEc38ed168331b1F7004cc9e5392A2272C1D7'; diff --git a/test/e2e/fixture-builder.js b/test/e2e/fixture-builder.js index 018a75b8206b..798b570c5a2d 100644 --- a/test/e2e/fixture-builder.js +++ b/test/e2e/fixture-builder.js @@ -395,6 +395,32 @@ class FixtureBuilder { }); } + withPermissionControllerSnapAccountConnectedToTestDapp( + restrictReturnedAccounts = true, + ) { + return this.withPermissionController({ + subjects: { + [DAPP_URL]: { + origin: DAPP_URL, + permissions: { + eth_accounts: { + id: 'ZaqPEWxyhNCJYACFw93jE', + parentCapability: 'eth_accounts', + invoker: DAPP_URL, + caveats: restrictReturnedAccounts && [ + { + type: 'restrictReturnedAccounts', + value: ['0x09781764c08de8ca82e156bbf156a3ca217c7950'], + }, + ], + date: 1664388714636, + }, + }, + }, + }, + }); + } + withPermissionControllerConnectedToTwoTestDapps( restrictReturnedAccounts = true, ) { diff --git a/test/e2e/helpers.js b/test/e2e/helpers.js index d8ffc4f8cd87..15ff5dd64606 100644 --- a/test/e2e/helpers.js +++ b/test/e2e/helpers.js @@ -745,7 +745,7 @@ const PRIVATE_KEY = '0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC'; const PRIVATE_KEY_TWO = - '0xa444f52ea41e3a39586d7069cb8e8233e9f6b9dea9cbb700cce69ae860661cc8'; + '0xf444f52ea41e3a39586d7069cb8e8233e9f6b9dea9cbb700cce69ae860661cc8'; const ACCOUNT_1 = '0x5cfe73b6021e818b776b421b1c4db2474086a7e1'; const ACCOUNT_2 = '0x09781764c08de8ca82e156bbf156a3ca217c7950'; @@ -778,6 +778,12 @@ const multipleGanacheOptions = { ], }; +const multipleGanacheOptionsForType2Transactions = { + ...multipleGanacheOptions, + // EVM version that supports type 2 transactions (EIP1559) + hardfork: 'london', +}; + const generateGanacheOptions = ({ secretKey = PRIVATE_KEY, balance = convertETHToHexGwei(DEFAULT_GANACHE_ETH_BALANCE_DEC), @@ -887,7 +893,15 @@ const TEST_SEED_PHRASE = const TEST_SEED_PHRASE_TWO = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent'; -// Usually happens when onboarded to make sure the state is retrieved from metamaskState properly, or after txn is made +/** + * Checks the balance for a specific address. If no address is provided, it defaults to the first address. + * This function is typically used during onboarding to ensure the state is retrieved correctly from metamaskState, + * or after a transaction is made. + * + * @param {WebDriver} driver - The WebDriver instance. + * @param {Ganache} [ganacheServer] - The Ganache server instance (optional). + * @param {string} [address] - The address to check the balance for (optional). + */ const locateAccountBalanceDOM = async ( driver, ganacheServer, @@ -1230,6 +1244,8 @@ module.exports = { connectToDapp, multipleGanacheOptions, defaultGanacheOptions, + defaultGanacheOptionsForType2Transactions, + multipleGanacheOptionsForType2Transactions, sendTransaction, sendScreenToConfirmScreen, findAnotherAccountFromAccountList, @@ -1258,7 +1274,6 @@ module.exports = { getCleanAppState, editGasFeeForm, clickNestedButton, - defaultGanacheOptionsForType2Transactions, removeSelectedAccount, getSelectedAccountAddress, tempToggleSettingRedesignedConfirmations, diff --git a/test/e2e/tests/confirmations/helpers.ts b/test/e2e/tests/confirmations/helpers.ts index ba27cbb44da2..882990e4bf66 100644 --- a/test/e2e/tests/confirmations/helpers.ts +++ b/test/e2e/tests/confirmations/helpers.ts @@ -4,7 +4,7 @@ import { Mockttp } from '../../mock-e2e'; import { Driver } from '../../webdriver/driver'; export async function scrollAndConfirmAndAssertConfirm(driver: Driver) { - await driver.clickElement('.confirm-scroll-to-bottom__button'); + await driver.clickElementSafe('.confirm-scroll-to-bottom__button'); await driver.clickElement('[data-testid="confirm-footer-button"]'); } diff --git a/test/e2e/tests/confirmations/transactions/shared.ts b/test/e2e/tests/confirmations/transactions/shared.ts index b699e9c0d61a..bf3063e3809a 100644 --- a/test/e2e/tests/confirmations/transactions/shared.ts +++ b/test/e2e/tests/confirmations/transactions/shared.ts @@ -1,8 +1,9 @@ /* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */ import { MockedEndpoint } from 'mockttp'; +import { veryLargeDelayMs } from '../../../helpers'; +import { Ganache } from '../../../seeder/ganache'; import GanacheContractAddressRegistry from '../../../seeder/ganache-contract-address-registry'; import { Driver } from '../../../webdriver/driver'; -import { Ganache } from '../../../seeder/ganache'; const { logInWithBalanceValidation, @@ -109,6 +110,7 @@ export async function confirmDepositTransactionWithCustomNonce( text: 'Save', tag: 'button', }); + await driver.delay(veryLargeDelayMs); await scrollAndConfirmAndAssertConfirm(driver); // Confirm tx was submitted with the higher nonce diff --git a/test/e2e/tests/notifications/mocks.ts b/test/e2e/tests/notifications/mocks.ts index cb14382f8763..16fd122532f3 100644 --- a/test/e2e/tests/notifications/mocks.ts +++ b/test/e2e/tests/notifications/mocks.ts @@ -1,13 +1,8 @@ import { Mockttp, RequestRuleBuilder } from 'mockttp'; import { - getMockAuthNonceResponse, - getMockAuthLoginResponse, - getMockAuthAccessTokenResponse, -} from '../../../../app/scripts/controllers/authentication/mocks/mockResponses'; -import { - getMockUserStorageGetResponse, - getMockUserStoragePutResponse, -} from '../../../../app/scripts/controllers/user-storage/mocks/mockResponses'; + AuthenticationController, + UserStorageController, +} from '@metamask/profile-sync-controller'; import { getMockFeatureAnnouncementResponse, getMockBatchCreateTriggersResponse, @@ -22,6 +17,9 @@ import { getMockDeleteFCMRegistrationTokenResponse, } from '../../../../app/scripts/controllers/push-platform-notifications/mocks/mockResponse'; +const AuthMocks = AuthenticationController.Mocks; +const StorageMocks = UserStorageController.Mocks; + type MockResponse = { url: string | RegExp; requestMethod: 'GET' | 'POST' | 'PUT' | 'DELETE'; @@ -35,13 +33,13 @@ type MockResponse = { */ export function mockNotificationServices(server: Mockttp) { // Auth - mockAPICall(server, getMockAuthNonceResponse()); - mockAPICall(server, getMockAuthLoginResponse()); - mockAPICall(server, getMockAuthAccessTokenResponse()); + mockAPICall(server, AuthMocks.getMockAuthNonceResponse()); + mockAPICall(server, AuthMocks.getMockAuthLoginResponse()); + mockAPICall(server, AuthMocks.getMockAuthAccessTokenResponse()); // Storage - mockAPICall(server, getMockUserStorageGetResponse()); - mockAPICall(server, getMockUserStoragePutResponse()); + mockAPICall(server, StorageMocks.getMockUserStorageGetResponse()); + mockAPICall(server, StorageMocks.getMockUserStoragePutResponse()); // Notifications mockAPICall(server, getMockFeatureAnnouncementResponse()); diff --git a/ui/components/app/modals/turn-on-metamask-notifications/turn-on-metamask-notifications.tsx b/ui/components/app/modals/turn-on-metamask-notifications/turn-on-metamask-notifications.tsx index 46510d0584fb..39d554759b29 100644 --- a/ui/components/app/modals/turn-on-metamask-notifications/turn-on-metamask-notifications.tsx +++ b/ui/components/app/modals/turn-on-metamask-notifications/turn-on-metamask-notifications.tsx @@ -61,8 +61,8 @@ export default function TurnOnMetamaskNotifications() { setButtonState(true); await createNotifications(); trackEvent({ - category: MetaMetricsEventCategory.EnableNotifications, - event: MetaMetricsEventName.NotificationsEnablingFlowHandled, + category: MetaMetricsEventCategory.NotificationInteraction, + event: MetaMetricsEventName.NotificationMenuOpened, properties: { is_profile_syncing_enabled: isProfileSyncingEnabled, is_notifications_enabled: isNotificationEnabled, @@ -74,8 +74,8 @@ export default function TurnOnMetamaskNotifications() { const handleHideModal = () => { hideModal(); trackEvent({ - category: MetaMetricsEventCategory.EnableNotifications, - event: MetaMetricsEventName.NotificationsEnablingFlowHandled, + category: MetaMetricsEventCategory.NotificationInteraction, + event: MetaMetricsEventName.NotificationMenuOpened, properties: { is_profile_syncing_enabled: isProfileSyncingEnabled, is_notifications_enabled: isNotificationEnabled, diff --git a/ui/components/institutional/custody-confirm-link-modal/custody-confirm-link-modal.tsx b/ui/components/institutional/custody-confirm-link-modal/custody-confirm-link-modal.tsx index df333dff42e4..ec29138a5696 100644 --- a/ui/components/institutional/custody-confirm-link-modal/custody-confirm-link-modal.tsx +++ b/ui/components/institutional/custody-confirm-link-modal/custody-confirm-link-modal.tsx @@ -1,6 +1,5 @@ import React, { useContext } from 'react'; import { useSelector, useDispatch } from 'react-redux'; -import { ICustodianType } from '@metamask-institutional/types'; import { MetaMetricsContext } from '../../../contexts/metametrics'; import { MetaMetricsEventName, @@ -71,16 +70,15 @@ const CustodyConfirmLink: React.FC = ({ const trackEvent = useContext(MetaMetricsContext); const mmiAccounts = useSelector(getInternalAccounts); const address = useSelector(getMMIAddressFromModalOrAddress); - const custodyAccountDetails = useSelector(getCustodyAccountDetails); - const { custodians } = useSelector(getMMIConfiguration); - const { custodianName } = - custodyAccountDetails[toChecksumHexAddress(address)] || {}; - const { displayName, iconUrl } = - custodians.find((item: ICustodianType) => item.envName === custodianName) || - {}; + const custodyAccountDetails = useSelector(getCustodyAccountDetails) || {}; const { url, ethereum, text, action } = useSelector( (state: State) => state.appState.modal.modalState.props.link || {}, ); + const { custodians } = useSelector(getMMIConfiguration) || {}; + const { custodianName } = + custodyAccountDetails[toChecksumHexAddress(address)] || {}; + const { displayName, iconUrl } = + custodians?.find((item) => item.envName === custodianName) || {}; const onClick = () => { if (url) { @@ -127,7 +125,7 @@ const CustodyConfirmLink: React.FC = ({ {custodianName} ) : ( diff --git a/ui/components/institutional/custody-labels/custody-labels.tsx b/ui/components/institutional/custody-labels/custody-labels.tsx index b753e7939652..002b54c8f0a5 100644 --- a/ui/components/institutional/custody-labels/custody-labels.tsx +++ b/ui/components/institutional/custody-labels/custody-labels.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { Label, Text } from '../../component-library'; import { @@ -54,16 +53,4 @@ const CustodyLabels: React.FC = (props) => { ); }; -CustodyLabels.propTypes = { - labels: PropTypes.arrayOf( - PropTypes.shape({ - key: PropTypes.string.isRequired, - value: PropTypes.string.isRequired, - }).isRequired, - ).isRequired, - index: PropTypes.string, - background: PropTypes.string, - hideNetwork: PropTypes.bool, -}; - export default CustodyLabels; diff --git a/ui/components/institutional/interactive-replacement-token-notification/interactive-replacement-token-notification.tsx b/ui/components/institutional/interactive-replacement-token-notification/interactive-replacement-token-notification.tsx index 30b72fc2b882..10dc049b8678 100644 --- a/ui/components/institutional/interactive-replacement-token-notification/interactive-replacement-token-notification.tsx +++ b/ui/components/institutional/interactive-replacement-token-notification/interactive-replacement-token-notification.tsx @@ -102,7 +102,7 @@ const InteractiveReplacementTokenNotification: React.FC< handleShowNotification(); }, [ address, - interactiveReplacementToken.oldRefreshToken, + interactiveReplacementToken?.oldRefreshToken, isUnlocked, dispatch, keyring.type, diff --git a/ui/components/institutional/wrong-network-notification/wrong-network-notification.tsx b/ui/components/institutional/wrong-network-notification/wrong-network-notification.tsx index bf16a5598c9b..faf74c61d4e4 100644 --- a/ui/components/institutional/wrong-network-notification/wrong-network-notification.tsx +++ b/ui/components/institutional/wrong-network-notification/wrong-network-notification.tsx @@ -20,9 +20,7 @@ const WrongNetworkNotification: React.FC = () => { const providerConfig = useSelector(getProviderConfig); const balance = useSelector(getSelectedAccountCachedBalance); - const isCustodianSupportedChain = useSelector( - getIsCustodianSupportedChain, - ); + const isCustodianSupportedChain = useSelector(getIsCustodianSupportedChain); const network = providerConfig.nickname || providerConfig.type; diff --git a/ui/components/multichain/badge-status/badge-status.tsx b/ui/components/multichain/badge-status/badge-status.tsx index b35c9ecab5b4..d4e3668bccd8 100644 --- a/ui/components/multichain/badge-status/badge-status.tsx +++ b/ui/components/multichain/badge-status/badge-status.tsx @@ -37,6 +37,7 @@ export const BadgeStatus: React.FC = ({ ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) const custodianIcon = useSelector((state) => + // @ts-expect-error todo - https://consensyssoftware.atlassian.net/browse/MMI-5367 getCustodianIconForAddress(state, address), ); ///: END:ONLY_INCLUDE_IF diff --git a/ui/components/multichain/create-named-snap-account/create-named-snap-account.test.tsx b/ui/components/multichain/create-named-snap-account/create-named-snap-account.test.tsx index 95b1cd4a971b..6c3e546069af 100644 --- a/ui/components/multichain/create-named-snap-account/create-named-snap-account.test.tsx +++ b/ui/components/multichain/create-named-snap-account/create-named-snap-account.test.tsx @@ -57,6 +57,7 @@ const render = ( onActionComplete: jest.fn().mockResolvedValue({ success: true }), snapSuggestedAccountName: mockSnapSuggestedAccountName, }, + overrideAccountNames?: { [accountId: string]: string }, ) => { const store = configureStore({ ...mockState, @@ -67,8 +68,24 @@ const render = ( ...mockState.metamask.internalAccounts, accounts: { ...mockState.metamask.internalAccounts.accounts, - [mockSnapAccount1.id]: mockSnapAccount1, - [mockSnapAccount2.id]: mockSnapAccount2, + [mockSnapAccount1.id]: { + ...mockSnapAccount1, + metadata: { + ...mockSnapAccount1.metadata, + name: + overrideAccountNames?.[mockSnapAccount1.id] || + mockSnapAccount1.metadata.name, + }, + }, + [mockSnapAccount2.id]: { + ...mockSnapAccount2, + metadata: { + ...mockSnapAccount2.metadata, + name: + overrideAccountNames?.[mockSnapAccount2.id] || + mockSnapAccount2.metadata.name, + }, + }, }, options: {}, methods: ETH_EOA_METHODS, @@ -157,6 +174,30 @@ describe('CreateNamedSnapAccount', () => { }); }); + it('increases suffix on snap account names when suggested name is already taken', async () => { + const onActionComplete = jest.fn(); + const { getByText, getByPlaceholderText } = render( + { + onActionComplete, + snapSuggestedAccountName: mockSnapSuggestedAccountName, + }, + { [mockSnapAccount1.id]: mockSnapSuggestedAccountName }, + ); + + await waitFor(() => + getByPlaceholderText(`${mockSnapSuggestedAccountName} 2`), + ); + fireEvent.click(getByText(messages.addAccount.message)); + + await waitFor(() => { + expect(onActionComplete).toHaveBeenCalledTimes(1); + expect(onActionComplete).toHaveBeenCalledWith({ + success: true, + name: `${mockSnapSuggestedAccountName} 2`, + }); + }); + }); + it('fires onActionComplete with false when clicking Cancel', async () => { const onActionComplete = jest.fn(); const { getByText } = render({ diff --git a/ui/components/multichain/create-named-snap-account/create-named-snap-account.tsx b/ui/components/multichain/create-named-snap-account/create-named-snap-account.tsx index 99a0ff7f457a..ac0650dc6d93 100644 --- a/ui/components/multichain/create-named-snap-account/create-named-snap-account.tsx +++ b/ui/components/multichain/create-named-snap-account/create-named-snap-account.tsx @@ -42,17 +42,27 @@ export const CreateNamedSnapAccount: React.FC = ({ }, []); const getNextAccountName = useCallback( - async (_accounts: InternalAccount[]): Promise => { - // if snapSuggestedAccountName exists, return it immediately + async (accounts: InternalAccount[]): Promise => { + // If a snap-suggested account name exists, use it as a base if (snapSuggestedAccountName) { - return snapSuggestedAccountName; - } + let suffix = 1; + let candidateName = snapSuggestedAccountName; + + // Check if the name is already taken + const isNameTaken = (name: string) => + accounts.some((account) => account.metadata.name === name); - const nextAccountName = await getNextAvailableAccountName( - KeyringTypes.snap, - ); + // Keep incrementing suffix until we find an available name + while (isNameTaken(candidateName)) { + suffix += 1; + candidateName = `${snapSuggestedAccountName} ${suffix}`; + } + + return candidateName; + } - return nextAccountName; + // If no snap-suggested name, use the next available account name + return getNextAvailableAccountName(KeyringTypes.snap); }, [], ); diff --git a/ui/components/multichain/global-menu/global-menu.js b/ui/components/multichain/global-menu/global-menu.js index b8a251dd373a..342d810e1bf5 100644 --- a/ui/components/multichain/global-menu/global-menu.js +++ b/ui/components/multichain/global-menu/global-menu.js @@ -144,15 +144,24 @@ export const GlobalMenu = ({ closeMenu, anchorElement, isOpen }) => { if (shouldShowEnableModal) { trackEvent({ - category: MetaMetricsEventCategory.EnableNotifications, - event: MetaMetricsEventName.NotificationsEnablingFlowHandled, + category: MetaMetricsEventCategory.NotificationInteraction, + event: MetaMetricsEventName.NotificationMenuOpened, properties: { is_profile_syncing_enabled: isProfileSyncingEnabled, is_notifications_enabled: isMetamaskNotificationsEnabled, action_type: 'started', }, }); + trackEvent({ + category: MetaMetricsEventCategory.NotificationInteraction, + event: MetaMetricsEventName.NotificationMenuOpened, + properties: { + is_profile_syncing_enabled: isProfileSyncingEnabled, + is_notifications_enabled: isMetamaskNotificationsEnabled, + }, + }); dispatch(showConfirmTurnOnMetamaskNotifications()); + closeMenu(); return; } diff --git a/ui/ducks/institutional/institutional.js b/ui/ducks/institutional/institutional.js deleted file mode 100644 index ffb242e0978e..000000000000 --- a/ui/ducks/institutional/institutional.js +++ /dev/null @@ -1,22 +0,0 @@ -import { createSlice } from '@reduxjs/toolkit'; - -const name = 'institutionalFeatures'; - -const initialState = {}; - -const slice = createSlice({ - name, - initialState, -}); - -const { reducer } = slice; - -export default reducer; - -export const getInstitutionalConnectRequests = (state) => - state.metamask[name]?.connectRequests; - -export const getChannelId = (state) => state.metamask[name]?.channelId; - -export const getConnectionRequest = (state) => - state.metamask[name]?.connectionRequest; diff --git a/ui/ducks/institutional/institutional.test.js b/ui/ducks/institutional/institutional.test.js deleted file mode 100644 index c8af602ddd97..000000000000 --- a/ui/ducks/institutional/institutional.test.js +++ /dev/null @@ -1,24 +0,0 @@ -import InstitutionalReducer, { - getInstitutionalConnectRequests, -} from './institutional'; - -describe('Institutional Duck', () => { - const initState = {}; - - describe('InstitutionalReducer', () => { - it('should initialize state', () => { - expect(InstitutionalReducer(undefined, {})).toStrictEqual(initState); - }); - - it('should correctly return all getters values', async () => { - const state = { - metamask: { - institutionalFeatures: { - connectRequests: [{ id: 'id' }], - }, - }, - }; - expect(getInstitutionalConnectRequests(state)).toHaveLength(1); - }); - }); -}); diff --git a/ui/ducks/institutional/institutional.test.ts b/ui/ducks/institutional/institutional.test.ts new file mode 100644 index 000000000000..d4d71e0caf23 --- /dev/null +++ b/ui/ducks/institutional/institutional.test.ts @@ -0,0 +1,47 @@ +import InstitutionalReducer, { + getInstitutionalConnectRequests, +} from './institutional'; + +describe('Institutional Duck', () => { + const initState = { + metamask: { + institutionalFeatures: { + connectRequests: undefined, + channelId: undefined, + connectionRequest: undefined, + }, + }, + }; + + describe('InstitutionalReducer', () => { + it('should initialize state', () => { + expect(InstitutionalReducer(undefined, { type: 'init' })).toStrictEqual( + initState, + ); + }); + + it('should correctly return all getters values', async () => { + const state = { + metamask: { + institutionalFeatures: { + connectRequests: [ + { + channelId: 'channelId', + traceId: 'traceId', + token: 'token', + environment: 'environment', + feature: 'feature', + service: 'service', + origin: 'origin', + custodian: 'custodian', + chainId: 'chainId', + labels: [{ key: 'testKey', value: 'value' }], + }, + ], + }, + }, + }; + expect(getInstitutionalConnectRequests(state)).toHaveLength(1); + }); + }); +}); diff --git a/ui/ducks/institutional/institutional.ts b/ui/ducks/institutional/institutional.ts new file mode 100644 index 000000000000..016cb10c2f12 --- /dev/null +++ b/ui/ducks/institutional/institutional.ts @@ -0,0 +1,67 @@ +import { createSlice } from '@reduxjs/toolkit'; + +type InstitutionalFeaturesState = { + connectRequests?: ConnectRequest[]; + channelId?: string; + connectionRequest?: { + payload: string; + traceId: string; + channelId: string; + }; +}; +type Label = { + key: string; + value: string; +}; +type ConnectRequest = { + channelId: string; + traceId: string; + token: string; + environment: string; + feature: string; + service: string; + origin: string; + custodian: string; + chainId: string; + labels: Label[]; +}; + +type MetaMaskState = { + metamask: { + [key: string]: InstitutionalFeaturesState; + }; +}; + +const name = 'institutionalFeatures'; + +const initialState: MetaMaskState = { + metamask: { + [name]: { + connectRequests: undefined, + channelId: undefined, + connectionRequest: undefined, + }, + }, +}; + +const slice = createSlice({ + name, + initialState, + reducers: {}, +}); + +const { reducer } = slice; + +export default reducer; + +export const getInstitutionalConnectRequests = ( + state: MetaMaskState, +): ConnectRequest[] | undefined => state.metamask[name]?.connectRequests; + +export const getChannelId = (state: MetaMaskState): string | undefined => + state.metamask[name]?.channelId; + +export const getConnectionRequest = ( + state: MetaMaskState, +): { payload: string; traceId: string; channelId: string } | undefined => + state.metamask[name]?.connectionRequest; diff --git a/ui/helpers/utils/institutional/find-by-custodian-name.test.ts b/ui/helpers/utils/institutional/find-by-custodian-name.test.ts index b0e880b46ae4..69e949406a36 100644 --- a/ui/helpers/utils/institutional/find-by-custodian-name.test.ts +++ b/ui/helpers/utils/institutional/find-by-custodian-name.test.ts @@ -14,6 +14,7 @@ describe('findCustodianByEnvName', () => { refreshTokenUrl: null, websocketApiUrl: 'wss://websocket.dev.metamask-institutional.io/v1/ws', isNoteToTraderSupported: true, + isQRCodeSupported: false, version: 2, }, { @@ -30,6 +31,7 @@ describe('findCustodianByEnvName', () => { 'https://saturn-custody.dev.metamask-institutional.io/oauth/token', websocketApiUrl: 'wss://websocket.dev.metamask-institutional.io/v1/ws', isNoteToTraderSupported: true, + isQRCodeSupported: false, version: 2, }, ]; diff --git a/ui/helpers/utils/institutional/find-by-custodian-name.ts b/ui/helpers/utils/institutional/find-by-custodian-name.ts index fdbc4ab4bd1f..5f6c114bae39 100644 --- a/ui/helpers/utils/institutional/find-by-custodian-name.ts +++ b/ui/helpers/utils/institutional/find-by-custodian-name.ts @@ -11,11 +11,13 @@ type Custodian = { websocketApiUrl: string; isNoteToTraderSupported: boolean; version: number; + isManualTokenInputSupported?: boolean; + isQRCodeSupported: boolean; }; export function findCustodianByEnvName( envName: string, - custodians: Custodian[], + custodians: Custodian[] | undefined, ): Custodian | null { const formatedEnvName = envName.toLowerCase(); diff --git a/ui/pages/confirmations/components/confirm/info/shared/transaction-data/__snapshots__/transaction-data.test.tsx.snap b/ui/pages/confirmations/components/confirm/info/shared/transaction-data/__snapshots__/transaction-data.test.tsx.snap index 77a91913a95a..78ac188dbb82 100644 --- a/ui/pages/confirmations/components/confirm/info/shared/transaction-data/__snapshots__/transaction-data.test.tsx.snap +++ b/ui/pages/confirmations/components/confirm/info/shared/transaction-data/__snapshots__/transaction-data.test.tsx.snap @@ -6,9 +6,13 @@ exports[`TransactionData renders decoded data with names and descriptions 1`] = class="mm-box mm-box--margin-bottom-4 mm-box--padding-2 mm-box--background-color-background-default mm-box--rounded-md" >
+
@@ -881,21 +885,6 @@ exports[`TransactionData renders decoded data with names and descriptions 1`] =
-
-
- -
-
`; @@ -906,9 +895,13 @@ exports[`TransactionData renders decoded data with no names 1`] = ` class="mm-box mm-box--margin-bottom-4 mm-box--padding-2 mm-box--background-color-background-default mm-box--rounded-md" >
+
@@ -1078,21 +1071,6 @@ exports[`TransactionData renders decoded data with no names 1`] = `
-
-
- -
-
`; @@ -1103,9 +1081,13 @@ exports[`TransactionData renders decoded data with tuples and arrays 1`] = ` class="mm-box mm-box--margin-bottom-4 mm-box--padding-2 mm-box--background-color-background-default mm-box--rounded-md" >
+
@@ -2006,21 +1988,6 @@ exports[`TransactionData renders decoded data with tuples and arrays 1`] = `
-
-
- -
-
`; @@ -2033,9 +2000,13 @@ exports[`TransactionData renders raw hexadecimal if no decoded data 1`] = ` class="mm-box mm-box--margin-bottom-4 mm-box--padding-2 mm-box--background-color-background-default mm-box--rounded-md" >
+
@@ -2073,21 +2044,6 @@ exports[`TransactionData renders raw hexadecimal if no decoded data 1`] = `

-
-
- -
-
`; diff --git a/ui/pages/confirmations/components/confirm/info/shared/transaction-data/transaction-data.tsx b/ui/pages/confirmations/components/confirm/info/shared/transaction-data/transaction-data.tsx index 72ad745e6500..506104ef031b 100644 --- a/ui/pages/confirmations/components/confirm/info/shared/transaction-data/transaction-data.tsx +++ b/ui/pages/confirmations/components/confirm/info/shared/transaction-data/transaction-data.tsx @@ -1,4 +1,4 @@ -import React, { useCallback } from 'react'; +import React from 'react'; import { useSelector } from 'react-redux'; import { TransactionMeta } from '@metamask/transaction-controller'; import { hexStripZeros } from '@ethersproject/bytes'; @@ -18,15 +18,7 @@ import { FlexWrap, JustifyContent, } from '../../../../../../../helpers/constants/design-system'; -import { - Box, - Button, - ButtonSize, - ButtonVariant, - IconName, -} from '../../../../../../../components/component-library'; -import Tooltip from '../../../../../../../components/ui/tooltip'; -import { useCopyToClipboard } from '../../../../../../../hooks/useCopyToClipboard'; +import { Box } from '../../../../../../../components/component-library'; import { useI18nContext } from '../../../../../../../hooks/useI18nContext'; import { ConfirmInfoExpandableRow } from '../../../../../../../components/app/confirm/info/row/expandable-row'; import Preloader from '../../../../../../../components/ui/icon/preloader'; @@ -57,9 +49,8 @@ export const TransactionData = () => { if (!value) { return ( - + - ); } @@ -68,7 +59,7 @@ export const TransactionData = () => { const isExpandable = data.length > 1; return ( - + <> {data.map((method, index) => ( <> @@ -81,7 +72,6 @@ export const TransactionData = () => { {index < data.length - 1 && } ))} - ); @@ -90,16 +80,22 @@ export const TransactionData = () => { function Container({ children, isLoading, + transactionData, }: { children?: React.ReactNode; isLoading?: boolean; + transactionData?: string; }) { const t = useI18nContext(); return ( <> - + {isLoading && } {children} @@ -226,30 +222,6 @@ function ParamRow({ ); } -function CopyDataButton({ transactionData }: { transactionData: string }) { - const t = useI18nContext(); - const [copied, handleCopy] = useCopyToClipboard(); - - const handleClick = useCallback(() => { - handleCopy(transactionData); - }, [handleCopy, transactionData]); - - return ( - - - - - - ); -} - function UniswapPath({ pathPools }: { pathPools: UniswapPathPool[] }) { return ( { const t = useI18nContext(); const dispatch = useDispatch(); @@ -48,7 +64,8 @@ const ConfirmAddCustodianToken: React.FC = () => { const trackEvent = useContext(MetaMetricsContext); const mmiActions = mmiActionsFactory(); - const { custodians } = useSelector(getMMIConfiguration); + const mmiConfiguration = useSelector(getMMIConfiguration); + const custodians = mmiConfiguration?.custodians; const mostRecentOverviewPage = useSelector(getMostRecentOverviewPage); const connectRequests = useSelector( getInstitutionalConnectRequests, @@ -144,7 +161,7 @@ const ConfirmAddCustodianToken: React.FC = () => { const custodian = findCustodianByEnvName( connectRequest.environment || custodianLabel, - custodians, + custodians as Custodian[], ); return ( diff --git a/ui/pages/institutional/custody/custody.tsx b/ui/pages/institutional/custody/custody.tsx index 5e2e6ccbd732..c4bdc4b7a252 100644 --- a/ui/pages/institutional/custody/custody.tsx +++ b/ui/pages/institutional/custody/custody.tsx @@ -10,7 +10,6 @@ import { useHistory } from 'react-router-dom'; import { isEqual } from 'lodash'; import Fuse from 'fuse.js'; import { Location as HistoryLocation } from 'history'; -import { ICustodianType } from '@metamask-institutional/types'; import { useI18nContext } from '../../../hooks/useI18nContext'; import { mmiActionsFactory } from '../../../store/institutional/institution-background'; import { MetaMetricsContext } from '../../../contexts/metametrics'; @@ -101,6 +100,8 @@ type Custodian = { isQRCodeSupported: boolean; isManualTokenInputSupported?: boolean; custodianPublishesTransaction: boolean; + production: boolean; + version: number; }; type AccountDetails = { @@ -122,7 +123,8 @@ const CustodyPage = () => { const mmiActions = mmiActionsFactory(); const currentChainId = useSelector(getCurrentChainId); - const { custodians } = useSelector(getMMIConfiguration); + const mmiConfiguration = useSelector(getMMIConfiguration); + const custodians: Custodian[] = mmiConfiguration?.custodians || []; const [loading, setLoading] = useState(true); const [ @@ -185,11 +187,11 @@ const CustodyPage = () => { const custodianListViewItems = useMemo(() => { const custodianItems: React.ReactNode[] = []; - const sortedCustodians = [...custodians].sort((a, b) => + const sortedCustodians = [...(custodians || [])].sort((a, b) => a.envName.toLowerCase().localeCompare(b.envName.toLowerCase()), ); - function shouldShowInProduction(custodian: ICustodianType) { + function shouldShowInProduction(custodian: Custodian) { return ( 'production' in custodian && !custodian.production && @@ -197,11 +199,11 @@ const CustodyPage = () => { ); } - function isHidden(custodian: ICustodianType) { + function isHidden(custodian: Custodian) { return 'hidden' in custodian && custodian.hidden; } - function isNotSelectedCustodian(custodian: ICustodianType) { + function isNotSelectedCustodian(custodian: Custodian) { return ( 'envName' in custodian && connectRequest && @@ -214,8 +216,8 @@ const CustodyPage = () => { try { const custodianByDisplayName = findCustodianByEnvName( custodian.envName, - custodians, - ) as Custodian | null; + custodians as Custodian[], + ) as Custodian; // @ts-expect-error todo - come back later const jwtListValue: string[] = await dispatch( @@ -578,8 +580,8 @@ const CustodyPage = () => { selectedAccounts={selectedAccounts} onAddAccounts={async () => { try { - const selectedCustodian = custodians.find( - (custodian: ICustodianType) => + const selectedCustodian = custodians?.find( + (custodian: Custodian) => custodian.envName === selectedCustodianName, ); const firstAccountId: string | undefined = diff --git a/ui/pages/institutional/manual-connect-custodian/manual-connect-custodian.tsx b/ui/pages/institutional/manual-connect-custodian/manual-connect-custodian.tsx index 4de6f0d2735f..067f7ef66244 100644 --- a/ui/pages/institutional/manual-connect-custodian/manual-connect-custodian.tsx +++ b/ui/pages/institutional/manual-connect-custodian/manual-connect-custodian.tsx @@ -44,7 +44,7 @@ type ManualConnectCustodianProps = { handleConnectError: (e: Error) => void; setAccounts: Dispatch>; removeConnectRequest: () => void; - connectRequest: object; + connectRequest?: object; }; const ManualConnectCustodian: React.FC = ({ diff --git a/ui/pages/onboarding-flow/create-password/__snapshots__/create-password.test.js.snap b/ui/pages/onboarding-flow/create-password/__snapshots__/create-password.test.js.snap index ead806699d69..2e7bd1a2c04d 100644 --- a/ui/pages/onboarding-flow/create-password/__snapshots__/create-password.test.js.snap +++ b/ui/pages/onboarding-flow/create-password/__snapshots__/create-password.test.js.snap @@ -72,6 +72,7 @@ exports[`Onboarding Create Password Render should match snapshot 1`] = ` @@ -121,52 +122,57 @@ exports[`Onboarding Create Password Render should match snapshot 1`] = ` class="mm-box mm-box--margin-top-4 mm-box--margin-bottom-4 mm-box--justify-content-space-between mm-box--align-items-center" >