diff --git a/.eslintrc.js b/.eslintrc.js index d57cc7bf7aef..53235d12c55a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -135,6 +135,7 @@ module.exports = { path.resolve(__dirname, '.eslintrc.typescript-compat.js'), ], rules: { + '@typescript-eslint/no-explicit-any': 'error', // this rule is new, but we didn't use it before, so it's off now '@typescript-eslint/no-duplicate-enum-values': 'off', '@typescript-eslint/no-shadow': [ diff --git a/.storybook/main.js b/.storybook/main.js index 074cf78043dd..2a48b3b88654 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -84,6 +84,15 @@ module.exports = { config.plugins.push( new CopyWebpackPlugin({ patterns: [ + { + from: path.join( + 'ui', + 'css', + 'utilities', + 'fonts/', + ), + to: 'fonts', + }, { from: path.join( 'node_modules', diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index c7acae9705c9..1d20250bc6d3 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -443,6 +443,10 @@ "approveButtonText": { "message": "Approve" }, + "approveIncreaseAllowance": { + "message": "Increase $1 spending cap", + "description": "The token symbol that is being approved" + }, "approveSpendingCap": { "message": "Approve $1 spending cap", "description": "The token symbol that is being approved" @@ -630,7 +634,7 @@ "message": "If you approve this request, a third party known for scams will take all your assets." }, "blockaidMessage": { - "message": "Privacy preserving - no data is shared with third parties. Available on Arbitrum, Avalanche, BNB chain, Ethereum Mainnet, Linea, Optimism, Polygon, Base and Sepolia." + "message": "Privacy preserving - no data is shared with third parties. Available on Arbitrum, Avalanche, BNB chain, Ethereum Mainnet, Linea, Optimism, Polygon and Sepolia." }, "blockaidTitleDeceptive": { "message": "This is a deceptive request" @@ -3047,7 +3051,7 @@ "message": "Got it" }, "notificationsBlockaidDefaultDescriptionOne": { - "message": "Steer clear of known scams while still preserving your privacy with security alerts powered by Blockaid. This feature is available on Arbitrum, Avalanche, BNB chain, Ethereum Mainnet, Linea, Optimism, Polygon, Base and Sepolia." + "message": "Steer clear of known scams while still preserving your privacy with security alerts powered by Blockaid. This feature is available on Arbitrum, Avalanche, BNB chain, Ethereum Mainnet, Linea, Optimism, Polygon and Sepolia." }, "notificationsBlockaidDefaultDescriptionTwo": { "message": "Always do your own due diligence before approving requests." @@ -3075,6 +3079,14 @@ "notificationsEmptyText": { "message": "This is where you can find notifications from your installed snaps." }, + "notificationsFeatureToggle": { + "message": "Enable Wallet Notifications", + "description": "Experimental feature title" + }, + "notificationsFeatureToggleDescription": { + "message": "This enables wallet notifications like send/receive funds or nfts and feature announcements.", + "description": "Description of the experimental notifications feature" + }, "notificationsHeader": { "message": "Notifications" }, @@ -3317,6 +3329,15 @@ "openSeaNew": { "message": "OpenSea" }, + "openSeaToBlockaidBtnLabel": { + "message": "Explore Snaps" + }, + "openSeaToBlockaidDescription": { + "message": "Security alerts are no longer available on this network. Installing a Snap may improve your security." + }, + "openSeaToBlockaidTitle": { + "message": "Heads up!" + }, "operationFailed": { "message": "Operation Failed" }, @@ -5291,6 +5312,13 @@ "switchToThisAccount": { "message": "Switch to this account" }, + "switchedNetworkToastDecline": { + "message": "Don't show again" + }, + "switchedNetworkToastMessage": { + "message": "$1 is now active on $2", + "description": "$1 represents the account name, $2 represents the network name" + }, "switchedTo": { "message": "You're now using" }, diff --git a/app/background.html b/app/background.html index 148842e8f868..c0068295d730 100644 --- a/app/background.html +++ b/app/background.html @@ -4,7 +4,7 @@ - - + + diff --git a/app/home.html b/app/home.html index 3210c30475c9..b94800b13d78 100644 --- a/app/home.html +++ b/app/home.html @@ -8,7 +8,7 @@ <% } else { %> MetaMask <% } %> - +
@@ -16,6 +16,6 @@
- + diff --git a/app/loading.html b/app/loading.html index dd5902a09a39..600fa01960d1 100644 --- a/app/loading.html +++ b/app/loading.html @@ -1,9 +1,9 @@ - - + + MetaMask Loading diff --git a/app/manifest/v2/_base.json b/app/manifest/v2/_base.json index c0b25efeeb94..68ed57a4e27c 100644 --- a/app/manifest/v2/_base.json +++ b/app/manifest/v2/_base.json @@ -31,12 +31,12 @@ { "matches": ["file://*/*", "http://*/*", "https://*/*"], "js": [ - "disable-console.js", - "lockdown-install.js", - "lockdown-run.js", - "lockdown-more.js", - "contentscript.js", - "inpage.js" + "scripts/disable-console.js", + "scripts/lockdown-install.js", + "scripts/lockdown-run.js", + "scripts/lockdown-more.js", + "scripts/contentscript.js", + "scripts/inpage.js" ], "run_at": "document_start", "all_frames": true diff --git a/app/manifest/v3/_base.json b/app/manifest/v3/_base.json index 42df2d437671..39e96825add8 100644 --- a/app/manifest/v3/_base.json +++ b/app/manifest/v3/_base.json @@ -14,7 +14,7 @@ }, "author": "https://metamask.io", "background": { - "service_worker": "app-init.js" + "service_worker": "scripts/app-init.js" }, "commands": { "_execute_browser_action": { @@ -30,11 +30,11 @@ { "matches": ["file://*/*", "http://*/*", "https://*/*"], "js": [ - "disable-console.js", - "lockdown-install.js", - "lockdown-run.js", - "lockdown-more.js", - "contentscript.js" + "scripts/disable-console.js", + "scripts/lockdown-install.js", + "scripts/lockdown-run.js", + "scripts/lockdown-more.js", + "scripts/contentscript.js" ], "run_at": "document_start", "all_frames": true diff --git a/app/notification.html b/app/notification.html index a0e0f6dcf5a7..70f02e9ab421 100644 --- a/app/notification.html +++ b/app/notification.html @@ -32,7 +32,7 @@ margin-top: 1rem; } - +
@@ -50,11 +50,6 @@ />
- + diff --git a/app/popup.html b/app/popup.html index c27442faa6af..296b0ceae711 100644 --- a/app/popup.html +++ b/app/popup.html @@ -4,7 +4,7 @@ MetaMask - +
@@ -12,6 +12,6 @@
- + diff --git a/app/scripts/app-init.js b/app/scripts/app-init.js index 579f2b96bdca..f5904e653cbe 100644 --- a/app/scripts/app-init.js +++ b/app/scripts/app-init.js @@ -57,27 +57,27 @@ function importAllScripts() { throw new Error('Missing APPLY_LAVAMOAT environment variable'); } - loadFile('./sentry-install.js'); + loadFile('./scripts/sentry-install.js'); // eslint-disable-next-line no-undef const isWorker = !self.document; if (!isWorker) { - loadFile('./snow.js'); + loadFile('./scripts/snow.js'); } - loadFile('./use-snow.js'); + loadFile('./scripts/use-snow.js'); // Always apply LavaMoat in e2e test builds, so that we can capture initialization stats if (testMode || applyLavaMoat) { - loadFile('./runtime-lavamoat.js'); - loadFile('./lockdown-more.js'); - loadFile('./policy-load.js'); + loadFile('./scripts/runtime-lavamoat.js'); + loadFile('./scripts/lockdown-more.js'); + loadFile('./scripts/policy-load.js'); } else { - loadFile('./init-globals.js'); - loadFile('./lockdown-install.js'); - loadFile('./lockdown-run.js'); - loadFile('./lockdown-more.js'); - loadFile('./runtime-cjs.js'); + loadFile('./scripts/init-globals.js'); + loadFile('./scripts/lockdown-install.js'); + loadFile('./scripts/lockdown-run.js'); + loadFile('./scripts/lockdown-more.js'); + loadFile('./scripts/runtime-cjs.js'); } // This environment variable is set to a string of comma-separated relative file paths. @@ -145,7 +145,7 @@ const registerInPageContentScript = async () => { { id: 'inpage', matches: ['file://*/*', 'http://*/*', 'https://*/*'], - js: ['inpage.js'], + js: ['scripts/inpage.js'], runAt: 'document_start', world: 'MAIN', }, diff --git a/app/scripts/controllers/app-state.js b/app/scripts/controllers/app-state.js index 44d1a367362e..ef959ce919ed 100644 --- a/app/scripts/controllers/app-state.js +++ b/app/scripts/controllers/app-state.js @@ -67,6 +67,9 @@ export default class AppStateController extends EventEmitter { }, surveyLinkLastClickedOrClosed: null, signatureSecurityAlertResponses: {}, + // States used for displaying the changed network toast + switchedNetworkDetails: null, + switchedNetworkNeverShowMessage: false, }); this.timer = null; @@ -401,6 +404,35 @@ export default class AppStateController extends EventEmitter { this.store.updateState({ showAccountBanner }); } + /** + * Track which network MetaMask has just automatically switched to, or call this with `null` to clear that state. + * + * @param {{ origin: string, networkClientId: string } | null} switchedNetworkDetails - Details about the network that MetaMask just switched to. + */ + setSwitchedNetworkDetails(switchedNetworkDetails) { + this.store.updateState({ switchedNetworkDetails }); + } + + /** + * Clears the switched network details in state + */ + clearSwitchedNetworkDetails() { + this.store.updateState({ switchedNetworkDetails: null }); + } + + /** + * Remembers if the user prefers to never see the + * network switched message again + * + * @param {boolean} switchedNetworkNeverShowMessage + */ + setSwitchedNetworkNeverShowMessage(switchedNetworkNeverShowMessage) { + this.store.updateState({ + switchedNetworkDetails: null, + switchedNetworkNeverShowMessage, + }); + } + /** * Sets a property indicating the model of the user's Trezor hardware wallet * diff --git a/app/scripts/controllers/authentication/auth-snap-requests.ts b/app/scripts/controllers/authentication/auth-snap-requests.ts new file mode 100644 index 000000000000..81c8beaafd40 --- /dev/null +++ b/app/scripts/controllers/authentication/auth-snap-requests.ts @@ -0,0 +1,32 @@ +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 new file mode 100644 index 000000000000..52cac9329a04 --- /dev/null +++ b/app/scripts/controllers/authentication/authentication-controller.test.ts @@ -0,0 +1,291 @@ +import { ControllerMessenger } from '@metamask/base-controller'; +import type { HandleSnapRequest } from '@metamask/snaps-controllers'; +import AuthenticationController, { + AuthenticationControllerMessenger, + AuthenticationControllerState, +} from './authentication-controller'; +import { + MOCK_ACCESS_TOKEN, + MOCK_LOGIN_RESPONSE, + mockEndpointAccessToken, + mockEndpointGetNonce, + mockEndpointLogin, +} from './mocks/mockServices'; + +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, + metametricsId: MOCK_LOGIN_RESPONSE.profile.metametrics_id, + }, + }, +}); + +describe('authentication/authentication-controller - constructor() tests', () => { + test('should initialize with default state', () => { + const controller = new AuthenticationController({ + messenger: createAuthenticationMessenger(), + }); + + expect(controller.state.isSignedIn).toBe(false); + expect(controller.state.sessionData).toBeUndefined(); + }); + + test('should initialize with override state', () => { + const controller = new AuthenticationController({ + messenger: createAuthenticationMessenger(), + state: mockSignedInState(), + }); + + 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 mockEndpoints = mockAuthenticationFlowEndpoints(); + const { messenger, mockSnapGetPublicKey, mockSnapSignMessage } = + createMockAuthenticationMessenger(); + + const controller = new AuthenticationController({ messenger }); + + 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'); + }); + + async function testAndAssertFailingEndpoints( + endpointFail: 'nonce' | 'login' | 'token', + ) { + const mockEndpoints = mockAuthenticationFlowEndpoints({ + endpointFail, + }); + const { messenger } = createMockAuthenticationMessenger(); + const controller = new AuthenticationController({ messenger }); + + 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 { messenger } = createMockAuthenticationMessenger(); + const controller = new AuthenticationController({ + messenger, + state: mockSignedInState(), + }); + + 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 { messenger } = createMockAuthenticationMessenger(); + const controller = new AuthenticationController({ + messenger, + state: { isSignedIn: false }, + }); + + expect(() => controller.performSignOut()).toThrow(); + }); +}); + +describe('authentication/authentication-controller - getBearerToken() tests', () => { + test('Should throw error if not logged in', async () => { + const { messenger } = createMockAuthenticationMessenger(); + const controller = new AuthenticationController({ + messenger, + state: { isSignedIn: false }, + }); + + await expect(controller.getBearerToken()).rejects.toThrow(); + }); + + test('Should return original access token in state', async () => { + const { messenger } = createMockAuthenticationMessenger(); + const originalState = mockSignedInState(); + const controller = new AuthenticationController({ + messenger, + state: originalState, + }); + + 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 { messenger } = createMockAuthenticationMessenger(); + mockAuthenticationFlowEndpoints(); + 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, + }); + + const result = await controller.getBearerToken(); + expect(result).toBeDefined(); + expect(result).toBe(MOCK_ACCESS_TOKEN); + }); +}); + +describe('authentication/authentication-controller - getSessionProfile() tests', () => { + test('Should throw error if not logged in', async () => { + const { messenger } = createMockAuthenticationMessenger(); + const controller = new AuthenticationController({ + messenger, + state: { isSignedIn: false }, + }); + + await expect(controller.getSessionProfile()).rejects.toThrow(); + }); + + test('Should return original access token in state', async () => { + const { messenger } = createMockAuthenticationMessenger(); + const originalState = mockSignedInState(); + const controller = new AuthenticationController({ + messenger, + state: originalState, + }); + + 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 { messenger } = createMockAuthenticationMessenger(); + mockAuthenticationFlowEndpoints(); + 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, + }); + + 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); + expect(result.metametricsId).toBe( + MOCK_LOGIN_RESPONSE.profile.metametrics_id, + ); + }); +}); + +function createAuthenticationMessenger() { + const messenger = new ControllerMessenger(); + return messenger.getRestricted({ + name: 'AuthenticationController', + allowedActions: [`SnapController:handleRequest`], + }) as AuthenticationControllerMessenger; +} + +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'); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + mockCall.mockImplementation(((actionType: any, params: any) => { + if ( + actionType === 'SnapController:handleRequest' && + params?.request.method === 'getPublicKey' + ) { + return mockSnapGetPublicKey(); + } + + if ( + actionType === 'SnapController:handleRequest' && + params?.request.method === 'signMessage' + ) { + return mockSnapSignMessage(); + } + + return ''; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + }) as any); + + return { messenger, mockSnapGetPublicKey, mockSnapSignMessage }; +} + +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, + }; +} diff --git a/app/scripts/controllers/authentication/authentication-controller.ts b/app/scripts/controllers/authentication/authentication-controller.ts new file mode 100644 index 000000000000..585538086d9c --- /dev/null +++ b/app/scripts/controllers/authentication/authentication-controller.ts @@ -0,0 +1,266 @@ +import { + BaseController, + RestrictedControllerMessenger, + StateMetadata, +} from '@metamask/base-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; + metametricsId: string; +}; + +export type AuthenticationControllerState = { + /** + * Global isSignedIn state. + * Can be used to determine if "Profile Syncing" is enabled or not. + */ + isSignedIn: boolean; + + /** + * These tokens & session data will expire every 30 mins. + * + * @property profile - anonymous profile data for the given logged in user + * @property accessToken - used to make requests authorized endpoints + * @property expiresIn - string date to determine if new access token is required + */ + sessionData?: { + profile: SessionProfile; + accessToken: string; + expiresIn: string; + }; +}; +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' +>; +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']; + +// Allowed Actions +type AllowedActions = HandleSnapRequest; + +// Messenger +export type AuthenticationControllerMessenger = RestrictedControllerMessenger< + typeof controllerName, + Actions | AllowedActions, + never, + AllowedActions['type'], + never +>; + +/** + * Controller that enables authentication for restricted endpoints. + * Used for Global Profile Syncing and Notifications + */ +export default class AuthenticationController extends BaseController< + typeof controllerName, + AuthenticationControllerState, + AuthenticationControllerMessenger +> { + constructor({ + messenger, + state, + }: { + messenger: AuthenticationControllerMessenger; + state?: AuthenticationControllerState; + }) { + super({ + messenger, + metadata, + name: controllerName, + state: { ...defaultState, ...state }, + }); + } + + 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.#hasValidAuthTokens(this.state.sessionData)) { + return this.state.sessionData.accessToken; + } + + const { accessToken } = await this.#performAuthenticationFlow(); + return accessToken; + } + + /** + * NOTE this will be changed to use profileId in future task. + * + * @returns the identifier id + */ + public async getSessionProfile(): Promise { + this.#assertLoggedIn(); + + if (this.#hasValidAuthTokens(this.state.sessionData)) { + return this.state.sessionData.profile; + } + + const { profile } = await this.#performAuthenticationFlow(); + return profile; + } + + #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); + if (!loginResponse?.token) { + throw new Error(`Unable to login`); + } + + const profile: SessionProfile = { + identifierId: loginResponse.profile.identifier_id, + profileId: loginResponse.profile.profile_id, + metametricsId: loginResponse.profile.metametrics_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) { + const errorMessage = + e instanceof Error ? e.message : JSON.stringify(e ?? ''); + throw new Error( + `${controllerName}: Failed to authenticate - ${errorMessage}`, + ); + } + } + + #hasValidAuthTokens( + ephemeralTokens: AuthenticationControllerState['sessionData'], + ): ephemeralTokens is NonNullable< + AuthenticationControllerState['sessionData'] + > { + if (!ephemeralTokens) { + return false; + } + + const prevDate = Date.parse(ephemeralTokens.expiresIn); + if (isNaN(prevDate)) { + return false; + } + + const currentDate = new Date(); + const diffMs = Math.abs(currentDate.getTime() - prevDate); + + return THIRTY_MIN_MS > diffMs; + } + + /** + * Returns the auth snap public key. + * + * @returns The snap public key. + */ + #snapGetPublicKey(): Promise { + return this.messagingSystem.call( + 'SnapController:handleRequest', + createSnapPublicKeyRequest(), + ) as Promise; + } + + /** + * Signs a specific message using an underlying auth snap. + * + * @param message - A specific tagged message to sign. + * @returns A Signature created by the snap. + */ + #snapSignMessage(message: `metamask:${string}`): Promise { + return this.messagingSystem.call( + 'SnapController:handleRequest', + createSnapSignMessageRequest(message), + ) as Promise; + } +} diff --git a/app/scripts/controllers/authentication/mocks/mockServices.ts b/app/scripts/controllers/authentication/mocks/mockServices.ts new file mode 100644 index 000000000000..5db1690517dd --- /dev/null +++ b/app/scripts/controllers/authentication/mocks/mockServices.ts @@ -0,0 +1,62 @@ +import nock from 'nock'; +import { + AUTH_LOGIN_ENDPOINT, + AUTH_NONCE_ENDPOINT, + LoginResponse, + NonceResponse, + OAuthTokenResponse, + OIDC_TOKENS_ENDPOINT, +} from '../services'; + +type MockReply = { + status: nock.StatusCode; + body?: nock.Body; +}; + +export const MOCK_NONCE = '4cbfqzoQpcNxVImGv'; +const MOCK_NONCE_RESPONSE: NonceResponse = { + nonce: MOCK_NONCE, +}; +export function mockEndpointGetNonce(mockReply?: MockReply) { + const reply = mockReply ?? { status: 200, body: MOCK_NONCE_RESPONSE }; + const mockNonceEndpoint = nock(AUTH_NONCE_ENDPOINT) + .get('') + .query(true) + .reply(reply.status, reply.body); + + return mockNonceEndpoint; +} + +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', + metametrics_id: 'MOCK_METAMETRICS_ID', + }, +}; +export function mockEndpointLogin(mockReply?: MockReply) { + const reply = mockReply ?? { status: 200, body: MOCK_LOGIN_RESPONSE }; + const mockLoginEndpoint = nock(AUTH_LOGIN_ENDPOINT) + .post('') + .reply(reply.status, reply.body); + + return mockLoginEndpoint; +} + +export const MOCK_ACCESS_TOKEN = `MOCK_ACCESS_TOKEN-${MOCK_JWT}`; +const MOCK_OATH_TOKEN_RESPONSE: OAuthTokenResponse = { + access_token: MOCK_ACCESS_TOKEN, + expires_in: new Date().getTime(), +}; +export function mockEndpointAccessToken(mockReply?: MockReply) { + const reply = mockReply ?? { status: 200, body: MOCK_OATH_TOKEN_RESPONSE }; + const mockOidcTokensEndpoint = nock(OIDC_TOKENS_ENDPOINT) + .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 new file mode 100644 index 000000000000..4806eb4159a1 --- /dev/null +++ b/app/scripts/controllers/authentication/services.test.ts @@ -0,0 +1,100 @@ +import { + MOCK_ACCESS_TOKEN, + MOCK_JWT, + MOCK_NONCE, + mockEndpointAccessToken, + mockEndpointGetNonce, + mockEndpointLogin, +} from './mocks/mockServices'; +import { + createLoginRawMessage, + getAccessToken, + getNonce, + login, +} from './services'; + +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'); + + 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'); + + 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 new file mode 100644 index 000000000000..272bffc51339 --- /dev/null +++ b/app/scripts/controllers/authentication/services.ts @@ -0,0 +1,116 @@ +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/snaps/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; + metametrics_id: string; + }; +}; +export async function login( + rawMessage: string, + signature: string, +): Promise { + try { + const response = await fetch(AUTH_LOGIN_ENDPOINT, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + signature, + raw_message: rawMessage, + }), + }); + + 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/decrypt-message.test.ts b/app/scripts/controllers/decrypt-message.test.ts index a1cb0a58f037..848fe3f9986d 100644 --- a/app/scripts/controllers/decrypt-message.test.ts +++ b/app/scripts/controllers/decrypt-message.test.ts @@ -38,6 +38,8 @@ const createMessengerMock = () => registerInitialEventPayload: jest.fn(), publish: jest.fn(), call: jest.fn(), + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any as jest.Mocked); const createDecryptMessageManagerMock = () => @@ -57,6 +59,8 @@ const createDecryptMessageManagerMock = () => hub: { on: jest.fn(), }, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any as jest.Mocked); describe('DecryptMessageController', () => { @@ -81,6 +85,8 @@ describe('DecryptMessageController', () => { const mockMessengerAction = ( action: string, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any callback: (actionName: string, ...args: any[]) => any, ) => { messengerMock.call.mockImplementation((actionName, ...rest) => { @@ -100,9 +106,17 @@ describe('DecryptMessageController', () => { ); decryptMessageController = new MockDecryptMessageController({ + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any getState: getStateMock as any, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any keyringController: keyringControllerMock as any, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any messenger: messengerMock as any, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any metricsEvent: metricsEventMock as any, } as DecryptMessageControllerOptions); }); @@ -116,6 +130,8 @@ describe('DecryptMessageController', () => { decryptMessageController.update(() => ({ unapprovedDecryptMsgs: { [messageIdMock]: messageMock, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any, unapprovedDecryptMsgCount: 1, })); @@ -131,6 +147,8 @@ describe('DecryptMessageController', () => { it('should add unapproved messages', async () => { await decryptMessageController.newRequestDecryptMessage( messageMock, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any undefined as any, ); @@ -220,6 +238,8 @@ describe('DecryptMessageController', () => { const messageToDecrypt = { ...messageMock, data: messageDataMock, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any; decryptMessageManagerMock.getMessage.mockReturnValue(messageToDecrypt); mockMessengerAction( @@ -271,6 +291,8 @@ describe('DecryptMessageController', () => { it('should be able to reject all unapproved messages', async () => { decryptMessageManagerMock.getUnapprovedMessages.mockReturnValue({ [messageIdMock]: messageMock, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any); await decryptMessageController.rejectUnapproved('reason to cancel'); diff --git a/app/scripts/controllers/decrypt-message.ts b/app/scripts/controllers/decrypt-message.ts index 5dbb4d8b43c6..51bf9c4b1250 100644 --- a/app/scripts/controllers/decrypt-message.ts +++ b/app/scripts/controllers/decrypt-message.ts @@ -117,8 +117,12 @@ export type DecryptMessageControllerMessenger = RestrictedControllerMessenger< >; export type DecryptMessageControllerOptions = { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any getState: () => any; messenger: DecryptMessageControllerMessenger; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any metricsEvent: (payload: any, options?: any) => void; }; @@ -132,8 +136,12 @@ export default class DecryptMessageController extends BaseController< > { hub: EventEmitter; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any private _getState: () => any; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any private _metricsEvent: (payload: any, options?: any) => void; private _decryptMessageManager: DecryptMessageManager; @@ -363,6 +371,8 @@ export default class DecryptMessageController extends BaseController< ) { messageManager.subscribe((state: MessageManagerState) => { const newMessages = this._migrateMessages( + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any state.unapprovedMessages as any, ); this.update((draftState) => { diff --git a/app/scripts/controllers/encryption-public-key.test.ts b/app/scripts/controllers/encryption-public-key.test.ts index c36418abffb1..274f0c67d7e9 100644 --- a/app/scripts/controllers/encryption-public-key.test.ts +++ b/app/scripts/controllers/encryption-public-key.test.ts @@ -34,6 +34,8 @@ const messageMock = { status: 'unapproved', type: 'testType', rawSig: undefined, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any as AbstractMessage; const coreMessageMock = { @@ -56,6 +58,8 @@ const createMessengerMock = () => registerActionHandler: jest.fn(), publish: jest.fn(), call: jest.fn(), + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any as jest.Mocked); const createEncryptionPublicKeyManagerMock = () => @@ -71,6 +75,8 @@ const createEncryptionPublicKeyManagerMock = () => hub: { on: jest.fn(), }, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any as jest.Mocked); describe('EncryptionPublicKeyController', () => { @@ -96,10 +102,20 @@ describe('EncryptionPublicKeyController', () => { ); encryptionPublicKeyController = new EncryptionPublicKeyController({ + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any messenger: messengerMock as any, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any getEncryptionPublicKey: getEncryptionPublicKeyMock as any, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any getAccountKeyringType: getAccountKeyringTypeMock as any, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any getState: getStateMock as any, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any metricsEvent: metricsEventMock as any, } as EncryptionPublicKeyControllerOptions); }); @@ -120,6 +136,8 @@ describe('EncryptionPublicKeyController', () => { encryptionPublicKeyController.update(() => ({ unapprovedEncryptionPublicKeyMsgs: { [messageIdMock]: messageMock, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any, unapprovedEncryptionPublicKeyMsgCount: 1, })); @@ -140,11 +158,15 @@ describe('EncryptionPublicKeyController', () => { [messageIdMock2]: messageMock, }; encryptionPublicKeyManagerMock.getUnapprovedMessages.mockReturnValueOnce( + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any messages as any, ); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore encryptionPublicKeyController.update(() => ({ + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any unapprovedEncryptionPublicKeyMsgs: messages as any, })); }); @@ -353,6 +375,8 @@ describe('EncryptionPublicKeyController', () => { const mockListener = jest.fn(); encryptionPublicKeyController.hub.on('updateBadge', mockListener); + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any (encryptionPublicKeyManagerMock.hub.on as any).mock.calls[0][1](); expect(mockListener).toHaveBeenCalledTimes(1); @@ -361,6 +385,8 @@ describe('EncryptionPublicKeyController', () => { it('requires approval on unapproved message event from EncryptionPublicKeyManager', () => { messengerMock.call.mockResolvedValueOnce({}); + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any (encryptionPublicKeyManagerMock.hub.on as any).mock.calls[1][1]( messageParamsMock, ); @@ -379,12 +405,16 @@ describe('EncryptionPublicKeyController', () => { it('updates state on EncryptionPublicKeyManager state change', async () => { await encryptionPublicKeyManagerMock.subscribe.mock.calls[0][0]({ + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any unapprovedMessages: { [messageIdMock]: coreMessageMock as any }, unapprovedMessagesCount: 3, }); expect(encryptionPublicKeyController.state).toEqual({ unapprovedEncryptionPublicKeyMsgs: { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any [messageIdMock]: stateMessageMock as any, }, unapprovedEncryptionPublicKeyMsgCount: 3, diff --git a/app/scripts/controllers/encryption-public-key.ts b/app/scripts/controllers/encryption-public-key.ts index 4bb019a10b72..2864bab34600 100644 --- a/app/scripts/controllers/encryption-public-key.ts +++ b/app/scripts/controllers/encryption-public-key.ts @@ -87,7 +87,11 @@ export type EncryptionPublicKeyControllerOptions = { messenger: EncryptionPublicKeyControllerMessenger; getEncryptionPublicKey: (address: string) => Promise; getAccountKeyringType: (account: string) => Promise; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any getState: () => any; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any metricsEvent: (payload: any, options?: any) => void; }; @@ -105,10 +109,14 @@ export default class EncryptionPublicKeyController extends BaseController< private _getAccountKeyringType: (account: string) => Promise; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any private _getState: () => any; private _encryptionPublicKeyManager: EncryptionPublicKeyManager; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any private _metricsEvent: (payload: any, options?: any) => void; /** @@ -352,6 +360,8 @@ export default class EncryptionPublicKeyController extends BaseController< ) { messageManager.subscribe((state: MessageManagerState) => { const newMessages = this._migrateMessages( + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any state.unapprovedMessages as any, ); this.update((draftState) => { diff --git a/app/scripts/controllers/mmi-controller.ts b/app/scripts/controllers/mmi-controller.ts index b2d66f36afdf..4f8b77de2f52 100644 --- a/app/scripts/controllers/mmi-controller.ts +++ b/app/scripts/controllers/mmi-controller.ts @@ -48,6 +48,8 @@ type UpdateCustodianTransactionsParameters = { txList: string[]; custodyController: CustodyController; transactionUpdateController: TransactionUpdateController; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any txStateManager: any; getPendingNonce: (address: string) => Promise; setTxHash: (txId: string, txHash: string) => void; @@ -58,6 +60,8 @@ export default class MMIController extends EventEmitter { public mmiConfigurationController: MmiConfigurationController; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any public keyringController: any; public preferencesController: PreferencesController; @@ -68,8 +72,12 @@ export default class MMIController extends EventEmitter { private custodyController: CustodyController; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any private getState: () => any; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any private getPendingNonce: (address: string) => Promise; private accountTracker: AccountTracker; @@ -78,28 +86,42 @@ export default class MMIController extends EventEmitter { private networkController: NetworkController; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any private permissionController: any; private signatureController: SignatureController; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any private messenger: any; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any private platform: any; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any private extension: any; private updateTransactionHash: (txId: string, txHash: string) => void; public trackTransactionEvents: ( args: { transactionMeta: TransactionMeta }, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any event: any, ) => void; private txStateManager: { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any getTransactions: (query?: any, opts?: any, fullTx?: boolean) => any[]; setTxStatusSigned: (txId: string) => void; setTxStatusSubmitted: (txId: string) => void; setTxStatusFailed: (txId: string) => void; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any updateTransaction: (txMeta: any) => void; }; @@ -171,6 +193,8 @@ export default class MMIController extends EventEmitter { async trackTransactionEventFromCustodianEvent( txMeta: TransactionMeta, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any event: any, ) { // transactionMetricsRequest parameter is already bound in the constructor @@ -322,6 +346,8 @@ export default class MMIController extends EventEmitter { string, { name: string; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any custodianDetails: any; labels: Label[]; token: string; diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index b12f02c4da03..e01cc5129af4 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -53,6 +53,7 @@ export default class PreferencesController { eth_sign: false, }, useMultiAccountBalanceChecker: true, + hasDismissedOpenSeaToBlockaidBanner: false, useSafeChainsListValidation: true, // set to true means the dynamic list from the API is being used // set to false will be using the static list from contract-metadata @@ -93,6 +94,7 @@ export default class PreferencesController { useNativeCurrencyAsPrimaryCurrency: true, hideZeroBalanceTokens: false, petnamesEnabled: true, + featureNotificationsEnabled: false, }, // ENS decentralized website resolution ipfsGateway: IPFS_DEFAULT_GATEWAY_URL, @@ -192,6 +194,14 @@ export default class PreferencesController { this.store.updateState({ useMultiAccountBalanceChecker: val }); } + /** + * Setter for the `dismissOpenSeaToBlockaidBanner` property + * + */ + dismissOpenSeaToBlockaidBanner() { + this.store.updateState({ hasDismissedOpenSeaToBlockaidBanner: true }); + } + /** * Setter for the `useSafeChainsListValidation` property * diff --git a/app/scripts/controllers/preferences.test.js b/app/scripts/controllers/preferences.test.js index bca97e6657e9..4f666c03e527 100644 --- a/app/scripts/controllers/preferences.test.js +++ b/app/scripts/controllers/preferences.test.js @@ -199,6 +199,23 @@ describe('preferences controller', () => { }); }); + describe('dismissOpenSeaToBlockaidBanner', () => { + it('hasDismissedOpenSeaToBlockaidBanner should default to false', () => { + expect( + preferencesController.store.getState() + .hasDismissedOpenSeaToBlockaidBanner, + ).toStrictEqual(false); + }); + + it('should set the hasDismissedOpenSeaToBlockaidBanner property in state', () => { + preferencesController.dismissOpenSeaToBlockaidBanner(); + expect( + preferencesController.store.getState() + .hasDismissedOpenSeaToBlockaidBanner, + ).toStrictEqual(true); + }); + }); + describe('setUseSafeChainsListValidation', function () { it('should default to true', function () { const state = preferencesController.store.getState(); diff --git a/app/scripts/lib/AbstractPetnamesBridge.test.ts b/app/scripts/lib/AbstractPetnamesBridge.test.ts index 608454b38afe..3347c90f13f5 100644 --- a/app/scripts/lib/AbstractPetnamesBridge.test.ts +++ b/app/scripts/lib/AbstractPetnamesBridge.test.ts @@ -94,6 +94,8 @@ function createNameControllerMock(state: NameControllerState) { function createMessengerMock(): jest.Mocked { return { subscribe: jest.fn(), + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any; } diff --git a/app/scripts/lib/AccountIdentitiesPetnamesBridge.test.ts b/app/scripts/lib/AccountIdentitiesPetnamesBridge.test.ts index 6431381d7099..b7ac979f3a1c 100644 --- a/app/scripts/lib/AccountIdentitiesPetnamesBridge.test.ts +++ b/app/scripts/lib/AccountIdentitiesPetnamesBridge.test.ts @@ -105,16 +105,22 @@ function createNameControllerMock( return { state, setName: jest.fn(), + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any; } function simulateSubscribe( messenger: jest.Mocked, stateChange: AccountsControllerState, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any patch: any[], ) { const listener = messenger.subscribe.mock.calls[0][1] as ( stateChange: AccountsControllerState, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any patch: any[], ) => void; listener(stateChange, patch); diff --git a/app/scripts/lib/AddressBookPetnamesBridge.test.ts b/app/scripts/lib/AddressBookPetnamesBridge.test.ts index c98e6a5e309e..f95726fba7b2 100644 --- a/app/scripts/lib/AddressBookPetnamesBridge.test.ts +++ b/app/scripts/lib/AddressBookPetnamesBridge.test.ts @@ -37,12 +37,16 @@ function createNameControllerMock( return { state, setName: jest.fn(), + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any; } function createMessengerMock(): jest.Mocked { return { subscribe: jest.fn(), + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any; } diff --git a/app/scripts/lib/AddressBookPetnamesBridge.ts b/app/scripts/lib/AddressBookPetnamesBridge.ts index e6f7e9813fa1..141814a9ef62 100644 --- a/app/scripts/lib/AddressBookPetnamesBridge.ts +++ b/app/scripts/lib/AddressBookPetnamesBridge.ts @@ -35,9 +35,13 @@ export class AddressBookPetnamesBridge extends AbstractPetnamesBridge { const entries: PetnameEntry[] = []; const { state } = this.#addressBookController; for (const chainId of Object.keys(state.addressBook)) { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any const chainEntries = state.addressBook[chainId as any]; for (const address of Object.keys(chainEntries)) { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any const entry = state.addressBook[chainId as any][address]; const normalizedChainId = chainId.toLowerCase(); const { name, isEns } = entry; @@ -64,11 +68,17 @@ export class AddressBookPetnamesBridge extends AbstractPetnamesBridge { */ protected updateSourceEntry(type: ChangeType, entry: PetnameEntry): void { if (type === ChangeType.DELETED) { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any this.#addressBookController.delete(entry.variation as any, entry.value); } else { this.#addressBookController.set( entry.value, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any entry.name as any, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any entry.variation as any, ); } diff --git a/app/scripts/lib/SnapsNameProvider.test.ts b/app/scripts/lib/SnapsNameProvider.test.ts index 6d6f0dd6a120..25a5f22a3f80 100644 --- a/app/scripts/lib/SnapsNameProvider.test.ts +++ b/app/scripts/lib/SnapsNameProvider.test.ts @@ -94,6 +94,8 @@ function createMockMessenger({ return { call: callMock, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any; } diff --git a/app/scripts/lib/keyring-snaps-permissions.test.ts b/app/scripts/lib/keyring-snaps-permissions.test.ts index afe4b40fefa6..3e457df2e258 100644 --- a/app/scripts/lib/keyring-snaps-permissions.test.ts +++ b/app/scripts/lib/keyring-snaps-permissions.test.ts @@ -15,6 +15,8 @@ describe('keyringSnapPermissionsBuilder', () => { registerActionHandler: jest.fn(), registerInitialEventPayload: jest.fn(), publish: jest.fn(), + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any, state: {}, }); @@ -85,6 +87,8 @@ describe('keyringSnapPermissionsBuilder', () => { ])('"%s" cannot call any methods', (origin) => { const permissions = keyringSnapPermissionsBuilder( mockController, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any origin as any, ); expect(permissions()).toStrictEqual([]); @@ -106,6 +110,8 @@ describe('isProtocolAllowed', () => { [1, false], [0, false], [-1, false], + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any ])('"%s" cannot call any methods', (origin: any, expected: boolean) => { expect(isProtocolAllowed(origin)).toBe(expected); }); diff --git a/app/scripts/lib/offscreen-bridge/lattice-offscreen-keyring.ts b/app/scripts/lib/offscreen-bridge/lattice-offscreen-keyring.ts index c690c1004382..27993e04d03c 100644 --- a/app/scripts/lib/offscreen-bridge/lattice-offscreen-keyring.ts +++ b/app/scripts/lib/offscreen-bridge/lattice-offscreen-keyring.ts @@ -61,6 +61,8 @@ class LatticeKeyringOffscreen extends LatticeKeyring { }); return creds; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (err: any) { throw new Error(err); } diff --git a/app/scripts/lib/ppom/indexed-db-backend.ts b/app/scripts/lib/ppom/indexed-db-backend.ts index 41c5af2629d1..8fbf63c1aced 100644 --- a/app/scripts/lib/ppom/indexed-db-backend.ts +++ b/app/scripts/lib/ppom/indexed-db-backend.ts @@ -38,6 +38,8 @@ export class IndexedDBPPOMStorage implements StorageBackend { reject( new Error( `Failed to open database ${this.storeName}: ${ + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any (event.target as any)?.error }`, ), @@ -65,8 +67,12 @@ export class IndexedDBPPOMStorage implements StorageBackend { private async objectStoreAction( method: 'get' | 'delete' | 'put' | 'getAllKeys', + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any args?: any, mode: IDBTransactionMode = 'readonly', + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any ): Promise { return new Promise((resolve, reject) => { this.#getObjectStore(mode) @@ -81,6 +87,8 @@ export class IndexedDBPPOMStorage implements StorageBackend { reject( new Error( `Error in indexDB operation ${method}: ${ + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any (event.target as any)?.error }`, ), @@ -95,6 +103,8 @@ export class IndexedDBPPOMStorage implements StorageBackend { async read(key: StorageKey, checksum: string): Promise { const event = await this.objectStoreAction('get', [key.name, key.chainId]); + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any const data = (event.target as any)?.result?.data; await validateChecksum(key, data, checksum); return data; @@ -119,6 +129,8 @@ export class IndexedDBPPOMStorage implements StorageBackend { async dir(): Promise { const event = await this.objectStoreAction('getAllKeys'); + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any return (event.target as any)?.result.map(([name, chainId]: string[]) => ({ name, chainId, diff --git a/app/scripts/lib/ppom/ppom-middleware.test.ts b/app/scripts/lib/ppom/ppom-middleware.test.ts index ab64ea6a9a08..370acc51e944 100644 --- a/app/scripts/lib/ppom/ppom-middleware.test.ts +++ b/app/scripts/lib/ppom/ppom-middleware.test.ts @@ -26,10 +26,14 @@ Object.defineProperty(globalThis, 'performance', { }); const createMiddleWare = ( + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any usePPOM?: any, options: { securityAlertsEnabled?: boolean; chainId?: Hex; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any mockUpdateSecurityAlertResponseByTxId?: any; } = { mockUpdateSecurityAlertResponseByTxId: () => undefined, @@ -60,9 +64,17 @@ const createMiddleWare = ( }; return createPPOMMiddleware( + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any ppomController as any, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any preferenceController as any, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any networkController as any, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any appStateController as any, mockUpdateSecurityAlertResponseByTxId, ); @@ -119,6 +131,8 @@ describe('PPOMMiddleware', () => { const ppom = { validateJsonRpc: validateMock, }; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any const usePPOM = async (callback: any) => { callback(ppom); }; @@ -223,6 +237,8 @@ describe('PPOMMiddleware', () => { const ppom = { validateJsonRpc: () => undefined, }; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any const usePPOM = async (callback: any) => { callback(ppom); }; @@ -237,6 +253,8 @@ describe('PPOMMiddleware', () => { }); it('calls next method when ppomController.usePPOM throws error', async () => { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any const usePPOM = async (_callback: any) => { throw Error('Some error'); }; @@ -254,6 +272,8 @@ describe('PPOMMiddleware', () => { const ppom = { validateJsonRpc: validateMock, }; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any const usePPOM = async (callback: any) => { callback(ppom); }; @@ -271,6 +291,8 @@ describe('PPOMMiddleware', () => { const ppom = { validateJsonRpc: validateMock, }; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any const usePPOM = async (callback: any) => { callback(ppom); }; @@ -303,6 +325,8 @@ describe('PPOMMiddleware', () => { validateJsonRpc: validateMock, }; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any const usePPOM = async (callback: any) => { callback(ppom); }; diff --git a/app/scripts/lib/ppom/ppom-middleware.ts b/app/scripts/lib/ppom/ppom-middleware.ts index ffb20a5b73b6..54e31f6704aa 100644 --- a/app/scripts/lib/ppom/ppom-middleware.ts +++ b/app/scripts/lib/ppom/ppom-middleware.ts @@ -31,7 +31,7 @@ const CONFIRMATION_METHODS = Object.freeze([ export const SUPPORTED_CHAIN_IDS: Hex[] = [ CHAIN_IDS.ARBITRUM, CHAIN_IDS.AVALANCHE, - CHAIN_IDS.BASE, + // CHAIN_IDS.BASE, CHAIN_IDS.BSC, CHAIN_IDS.LINEA_MAINNET, CHAIN_IDS.MAINNET, @@ -63,6 +63,8 @@ export function createPPOMMiddleware< ppomController: PPOMController, preferencesController: PreferencesController, networkController: NetworkController, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any appStateController: any, updateSecurityAlertResponseByTxId: ( req: JsonRpcRequest & { diff --git a/app/scripts/lib/ppom/ppom-util.ts b/app/scripts/lib/ppom/ppom-util.ts index d0f9141996c0..95806d41bb4a 100644 --- a/app/scripts/lib/ppom/ppom-util.ts +++ b/app/scripts/lib/ppom/ppom-util.ts @@ -2,6 +2,8 @@ import { normalizeTransactionParams } from '@metamask/transaction-controller'; const METHOD_SEND_TRANSACTION = 'eth_sendTransaction'; +// TODO: Replace `any` with type +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function normalizePPOMRequest(request: any) { if (request.method !== METHOD_SEND_TRANSACTION) { return request; diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index ad1e0b9f22a7..4430ed40b7e2 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -102,6 +102,8 @@ export const SENTRY_BACKGROUND_STATE = { showProductTour: true, showNetworkBanner: true, showAccountBanner: true, + switchedNetworkDetails: false, + switchedNetworkNeverShowMessage: false, showTestnetMessageInDropdown: true, surveyLinkLastClickedOrClosed: true, snapsInstallPrivacyWarningShown: true, @@ -237,6 +239,7 @@ export const SENTRY_BACKGROUND_STATE = { useTokenDetection: true, useRequestQueue: true, useTransactionSimulations: true, + hasDismissedOpenSeaToBlockaidBanner: true, }, SelectedNetworkController: { domains: false }, SignatureController: { @@ -380,6 +383,8 @@ export const SENTRY_UI_STATE = { addSnapAccountEnabled: false, snapsAddSnapAccountModalDismissed: false, ///: END:ONLY_INCLUDE_IF + switchedNetworkDetails: false, + switchedNetworkNeverShowMessage: false, }, unconnectedAccount: true, }; diff --git a/app/scripts/lib/snap-keyring/metrics.test.ts b/app/scripts/lib/snap-keyring/metrics.test.ts index e0ef57fc2b59..fe49b2be73e4 100644 --- a/app/scripts/lib/snap-keyring/metrics.test.ts +++ b/app/scripts/lib/snap-keyring/metrics.test.ts @@ -3,6 +3,8 @@ import { getSnapAndHardwareInfoForMetrics } from './metrics'; describe('getSnapAndHardwareInfoForMetrics', () => { let getAccountType: jest.Mock; let getDeviceModel: jest.Mock; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any let messenger: any; beforeEach(() => { diff --git a/app/scripts/lib/snap-keyring/snap-keyring.ts b/app/scripts/lib/snap-keyring/snap-keyring.ts index 17683dee0a52..62b61c8ed335 100644 --- a/app/scripts/lib/snap-keyring/snap-keyring.ts +++ b/app/scripts/lib/snap-keyring/snap-keyring.ts @@ -51,14 +51,22 @@ export const snapKeyringBuilder = ( getSnapController: () => SnapController, persistKeyringHelper: () => Promise, setSelectedAccountHelper: (address: string) => void, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any removeAccountHelper: (address: string) => Promise, trackEvent: ( + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any payload: Record, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any options?: Record, ) => void, getSnapName: (snapId: string) => string, ) => { const builder = (() => { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any return new SnapKeyring(getSnapController() as any, { addressExists: async (address) => { const addresses = await controllerMessenger.call( @@ -348,6 +356,8 @@ export const snapKeyringBuilder = ( } }, }); + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any }) as any; builder.type = SnapKeyring.type; return builder; diff --git a/app/scripts/lib/snap-keyring/utils/isBlockedUrl.test.ts b/app/scripts/lib/snap-keyring/utils/isBlockedUrl.test.ts index 839176dca5a1..03d949a814d1 100644 --- a/app/scripts/lib/snap-keyring/utils/isBlockedUrl.test.ts +++ b/app/scripts/lib/snap-keyring/utils/isBlockedUrl.test.ts @@ -36,6 +36,8 @@ describe('isBlockedUrl', () => { [1, true], [0, true], [-1, true], + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any ])('"%s" is blocked: %s', async (url: any, expected: boolean) => { const result = await isBlockedUrl( url, diff --git a/app/scripts/lib/snap-keyring/utils/showResult.ts b/app/scripts/lib/snap-keyring/utils/showResult.ts index c0b4c530ed09..086f9fe07174 100644 --- a/app/scripts/lib/snap-keyring/utils/showResult.ts +++ b/app/scripts/lib/snap-keyring/utils/showResult.ts @@ -42,6 +42,8 @@ export const showError = ( controllerMessenger: SnapKeyringBuilderMessenger, snapId: string, opts: ResultComponentOptions, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any properties: Record, ): Promise => { return controllerMessenger.call('ApprovalController:showError', { @@ -70,6 +72,8 @@ export const showSuccess = ( controllerMessenger: SnapKeyringBuilderMessenger, snapId: string, opts: ResultComponentOptions, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any properties: Record, ): Promise => { return controllerMessenger.call('ApprovalController:showSuccess', { diff --git a/app/scripts/lib/transaction/metrics.test.ts b/app/scripts/lib/transaction/metrics.test.ts index a4eb13918d7d..9163feefb291 100644 --- a/app/scripts/lib/transaction/metrics.test.ts +++ b/app/scripts/lib/transaction/metrics.test.ts @@ -68,6 +68,8 @@ const mockTransactionMetricsRequest = { getTokenStandardAndDetails: jest.fn(), getTransaction: jest.fn(), provider: provider as Provider, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any snapAndHardwareMessenger: jest.fn() as any, trackEvent: jest.fn(), } as TransactionMetricsRequest; @@ -77,9 +79,17 @@ describe('Transaction metrics', () => { mockChainId, mockNetworkId, mockTransactionMeta: TransactionMeta, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any mockTransactionMetaWithBlockaid: any, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any expectedProperties: any, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any expectedSensitiveProperties: any, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any mockActionId: any; beforeEach(() => { @@ -160,6 +170,8 @@ describe('Transaction metrics', () => { describe('handleTransactionAdded', () => { it('should return if transaction meta is not defined', async () => { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any await handleTransactionAdded(mockTransactionMetricsRequest, {} as any); expect( mockTransactionMetricsRequest.createEventFragment, @@ -168,6 +180,8 @@ describe('Transaction metrics', () => { it('should create event fragment', async () => { await handleTransactionAdded(mockTransactionMetricsRequest, { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any transactionMeta: mockTransactionMeta as any, actionId: mockActionId, }); @@ -195,6 +209,8 @@ describe('Transaction metrics', () => { }; await handleTransactionAdded(mockTransactionMetricsRequest, { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any transactionMeta: mockTransactionMeta as any, actionId: mockActionId, }); @@ -221,6 +237,8 @@ describe('Transaction metrics', () => { it('should create event fragment with blockaid', async () => { await handleTransactionAdded(mockTransactionMetricsRequest, { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any transactionMeta: mockTransactionMetaWithBlockaid as any, actionId: mockActionId, }); @@ -251,6 +269,8 @@ describe('Transaction metrics', () => { describe('handleTransactionApproved', () => { it('should return if transaction meta is not defined', async () => { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any await handleTransactionApproved(mockTransactionMetricsRequest, {} as any); expect( mockTransactionMetricsRequest.createEventFragment, @@ -265,6 +285,8 @@ describe('Transaction metrics', () => { it('should create, update, finalize event fragment', async () => { await handleTransactionApproved(mockTransactionMetricsRequest, { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any transactionMeta: mockTransactionMeta as any, actionId: mockActionId, }); @@ -306,6 +328,8 @@ describe('Transaction metrics', () => { it('should create, update, finalize event fragment with blockaid', async () => { await handleTransactionApproved(mockTransactionMetricsRequest, { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any transactionMeta: mockTransactionMetaWithBlockaid as any, actionId: mockActionId, }); @@ -362,6 +386,8 @@ describe('Transaction metrics', () => { describe('handleTransactionFailed', () => { it('should return if transaction meta is not defined', async () => { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any await handleTransactionFailed(mockTransactionMetricsRequest, {} as any); expect( mockTransactionMetricsRequest.createEventFragment, @@ -386,6 +412,8 @@ describe('Transaction metrics', () => { transactionMeta: mockTransactionMeta, actionId: mockActionId, error: mockErrorMessage, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any); const expectedUniqueId = 'transaction-submitted-1'; @@ -440,6 +468,8 @@ describe('Transaction metrics', () => { transactionMeta: mockTransactionMetaWithBlockaid, actionId: mockActionId, error: mockErrorMessage, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any); const expectedUniqueId = 'transaction-submitted-1'; @@ -503,6 +533,8 @@ describe('Transaction metrics', () => { transactionMeta: mockTransactionMeta, actionId: mockActionId, error: mockErrorMessage, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any); const expectedUniqueId = 'transaction-submitted-1'; @@ -550,6 +582,8 @@ describe('Transaction metrics', () => { it('should return if transaction meta is not defined', async () => { await handleTransactionConfirmed( mockTransactionMetricsRequest, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any {} as any, ); expect( @@ -573,6 +607,8 @@ describe('Transaction metrics', () => { await handleTransactionConfirmed(mockTransactionMetricsRequest, { transactionMeta: mockTransactionMeta, actionId: mockActionId, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any); const expectedUniqueId = 'transaction-submitted-1'; @@ -629,6 +665,8 @@ describe('Transaction metrics', () => { await handleTransactionConfirmed(mockTransactionMetricsRequest, { transactionMeta: mockTransactionMetaWithBlockaid, actionId: mockActionId, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any); const expectedUniqueId = 'transaction-submitted-1'; @@ -692,6 +730,8 @@ describe('Transaction metrics', () => { describe('handleTransactionDropped', () => { it('should return if transaction meta is not defined', async () => { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any await handleTransactionDropped(mockTransactionMetricsRequest, {} as any); expect( mockTransactionMetricsRequest.createEventFragment, @@ -708,6 +748,8 @@ describe('Transaction metrics', () => { await handleTransactionDropped(mockTransactionMetricsRequest, { transactionMeta: mockTransactionMeta, actionId: mockActionId, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any); const expectedUniqueId = 'transaction-submitted-1'; @@ -756,6 +798,8 @@ describe('Transaction metrics', () => { await handleTransactionDropped(mockTransactionMetricsRequest, { transactionMeta: mockTransactionMetaWithBlockaid, actionId: mockActionId, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any); const expectedUniqueId = 'transaction-submitted-1'; @@ -817,6 +861,8 @@ describe('Transaction metrics', () => { describe('handleTransactionRejected', () => { it('should return if transaction meta is not defined', async () => { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any await handleTransactionRejected(mockTransactionMetricsRequest, {} as any); expect( mockTransactionMetricsRequest.createEventFragment, @@ -833,6 +879,8 @@ describe('Transaction metrics', () => { await handleTransactionRejected(mockTransactionMetricsRequest, { transactionMeta: mockTransactionMeta, actionId: mockActionId, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any); const expectedUniqueId = 'transaction-added-1'; @@ -876,6 +924,8 @@ describe('Transaction metrics', () => { await handleTransactionRejected(mockTransactionMetricsRequest, { transactionMeta: mockTransactionMetaWithBlockaid, actionId: mockActionId, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any); const expectedUniqueId = 'transaction-added-1'; @@ -934,6 +984,8 @@ describe('Transaction metrics', () => { it('should return if transaction meta is not defined', async () => { await handleTransactionSubmitted( mockTransactionMetricsRequest, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any {} as any, ); expect( @@ -943,6 +995,8 @@ describe('Transaction metrics', () => { it('should only create event fragment', async () => { await handleTransactionSubmitted(mockTransactionMetricsRequest, { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any transactionMeta: mockTransactionMeta as any, actionId: mockActionId, }); diff --git a/app/scripts/lib/transaction/metrics.ts b/app/scripts/lib/transaction/metrics.ts index ece6c5d62317..085f542b6ee7 100644 --- a/app/scripts/lib/transaction/metrics.ts +++ b/app/scripts/lib/transaction/metrics.ts @@ -69,6 +69,8 @@ export type TransactionMetricsRequest = { // According to the type GasFeeState returned from getEIP1559GasFeeEstimates // doesn't include some properties used in buildEventFragmentProperties, // hence returning any here to avoid type errors. + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any getEIP1559GasFeeEstimates(options?: FetchGasFeeEstimateOptions): Promise; getParticipateInMetrics: () => boolean; getSelectedAddress: () => string; @@ -81,6 +83,8 @@ export type TransactionMetricsRequest = { getTransaction: (transactionId: string) => TransactionMeta; provider: Provider; snapAndHardwareMessenger: SnapAndHardwareMessenger; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any trackEvent: (payload: any) => void; }; @@ -161,6 +165,8 @@ export const handleTransactionFailed = async ( return; } + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any const extraParams = {} as Record; if (transactionEventPayload.error) { // This is a failed transaction @@ -191,6 +197,8 @@ export const handleTransactionConfirmed = async ( return; } + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any const extraParams = {} as Record; const { transactionMeta } = transactionEventPayload; const { txReceipt } = transactionMeta; @@ -484,6 +492,8 @@ function createTransactionEventFragment({ eventName: TransactionMetaMetricsEvent; transactionEventPayload: TransactionEventPayload; transactionMetricsRequest: TransactionMetricsRequest; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any payload: any; }) { if ( @@ -597,6 +607,8 @@ function updateTransactionEventFragment({ eventName: TransactionMetaMetricsEvent; transactionEventPayload: TransactionEventPayload; transactionMetricsRequest: TransactionMetricsRequest; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any payload: any; }) { const uniqueId = getUniqueId(eventName, transactionMeta.id); @@ -666,6 +678,8 @@ async function createUpdateFinalizeTransactionEventFragment({ eventName: TransactionMetaMetricsEvent; transactionEventPayload: TransactionEventPayload; transactionMetricsRequest: TransactionMetricsRequest; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any extraParams?: Record; }) { const { properties, sensitiveProperties } = @@ -703,6 +717,8 @@ async function createUpdateFinalizeTransactionEventFragment({ } function hasFragment( + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any getEventFragmentById: (arg0: string) => any, eventName: TransactionMetaMetricsEvent, transactionMeta: TransactionMeta, @@ -731,6 +747,8 @@ async function buildEventFragmentProperties({ transactionMetricsRequest, extraParams = {}, }: { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any extraParams?: Record; transactionEventPayload: TransactionEventPayload; transactionMetricsRequest: TransactionMetricsRequest; @@ -770,6 +788,8 @@ async function buildEventFragmentProperties({ transactionMetricsRequest.getTokenStandardAndDetails, ); + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any const gasParams = {} as Record; if (isEIP1559Transaction(transactionMeta)) { @@ -844,6 +864,7 @@ async function buildEventFragmentProperties({ [ TransactionType.contractInteraction, TransactionType.tokenMethodApprove, + TransactionType.tokenMethodIncreaseAllowance, TransactionType.tokenMethodSafeTransferFrom, TransactionType.tokenMethodSetApprovalForAll, TransactionType.tokenMethodTransfer, @@ -934,6 +955,8 @@ async function buildEventFragmentProperties({ } ///: BEGIN:ONLY_INCLUDE_IF(blockaid) + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any const blockaidProperties: any = getBlockaidMetricsProps(transactionMeta); if (blockaidProperties?.ui_customizations?.length > 0) { @@ -971,6 +994,8 @@ async function buildEventFragmentProperties({ ///: END:ONLY_INCLUDE_IF // ui_customizations must come after ...blockaidProperties ui_customizations: uiCustomizations.length > 0 ? uiCustomizations : null, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as Record; const snapAndHardwareInfo = await getSnapAndHardwareInfoForMetrics( @@ -997,6 +1022,8 @@ async function buildEventFragmentProperties({ transaction_replaced: transactionReplaced, ...extraParams, ...gasParamsInGwei, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as Record; if (transactionContractMethod === contractMethodNames.APPROVE) { @@ -1012,7 +1039,11 @@ async function buildEventFragmentProperties({ return { properties, sensitiveProperties }; } +// TODO: Replace `any` with type +// eslint-disable-next-line @typescript-eslint/no-explicit-any function getGasValuesInGWEI(gasParams: Record) { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any const gasValuesInGwei = {} as Record; for (const param in gasParams) { if (isHexString(gasParams[param])) { diff --git a/app/scripts/lib/transaction/mmi-hooks.test.ts b/app/scripts/lib/transaction/mmi-hooks.test.ts index 210c3093ada8..fee5dff83de5 100644 --- a/app/scripts/lib/transaction/mmi-hooks.test.ts +++ b/app/scripts/lib/transaction/mmi-hooks.test.ts @@ -13,6 +13,8 @@ describe('MMI hooks', () => { const custodyIdMocked = '123'; describe('afterTransactionSign', () => { it('returns false if txMeta has no custodyStatus', () => { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any const txMeta = { to: toMocked } as any; const signedEthTx = {}; const result = afterTransactionSign(txMeta, signedEthTx, jest.fn()); @@ -24,6 +26,8 @@ describe('MMI hooks', () => { custodyStatus: TransactionStatus.approved, custodyId: custodyIdMocked, txParams: { from: fromMocked }, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any; const signedEthTx = { custodian_transactionId: custodyIdMocked, @@ -47,12 +51,16 @@ describe('MMI hooks', () => { describe('beforeTransactionPublish', () => { it('returns true if txMeta has custodyStatus', () => { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any const txMeta = { custodyStatus: TransactionStatus.approved } as any; const result = beforeTransactionPublish(txMeta); expect(result).toBe(false); }); it('returns false if txMeta has no custodyStatus', () => { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any const txMeta = { to: toMocked } as any; const result = beforeTransactionPublish(txMeta); expect(result).toBe(true); @@ -61,12 +69,16 @@ describe('MMI hooks', () => { describe('getAdditionalSignArguments', () => { it('returns an array with txMeta when custodyStatus is truthy', () => { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any const txMeta = { custodyStatus: TransactionStatus.approved } as any; const result = getAdditionalSignArguments(txMeta); expect(result).toEqual([txMeta]); }); it('returns an empty array when custodyStatus is falsy', () => { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any const txMeta = { to: toMocked } as any; const result = getAdditionalSignArguments(txMeta); expect(result).toEqual([]); @@ -75,12 +87,16 @@ describe('MMI hooks', () => { describe('beforeTransactionApproveOnInit', () => { it('returns true if txMeta has custodyStatus', () => { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any const txMeta = { custodyStatus: TransactionStatus.approved } as any; const result = beforeTransactionApproveOnInit(txMeta); expect(result).toBe(false); }); it('returns false if txMeta has no custodyStatus', () => { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any const txMeta = { to: toMocked } as any; const result = beforeTransactionApproveOnInit(txMeta); expect(result).toBe(true); @@ -92,12 +108,16 @@ describe('MMI hooks', () => { const txMeta = { custodyStatus: TransactionStatus.approved, custodyId: 1, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any; const result = beforeCheckPendingTransaction(txMeta); expect(result).toBe(false); }); it('returns false if txMeta has no custodyStatus', () => { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any const txMeta = { to: toMocked } as any; const result = beforeCheckPendingTransaction(txMeta); expect(result).toBe(true); diff --git a/app/scripts/lib/transaction/mmi-hooks.ts b/app/scripts/lib/transaction/mmi-hooks.ts index 92ea0cf792b9..d4dc3cc4dcda 100644 --- a/app/scripts/lib/transaction/mmi-hooks.ts +++ b/app/scripts/lib/transaction/mmi-hooks.ts @@ -9,6 +9,8 @@ import { TransactionMeta } from '@metamask/transaction-controller'; */ export function afterTransactionSign( txMeta: TransactionMeta, + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any signedEthTx: any, addTransactionToWatchList: ( custodianTransactionId: string | undefined, diff --git a/app/scripts/lib/transaction/util.test.ts b/app/scripts/lib/transaction/util.test.ts index b69fec9ae959..9b31caff37e0 100644 --- a/app/scripts/lib/transaction/util.test.ts +++ b/app/scripts/lib/transaction/util.test.ts @@ -515,6 +515,8 @@ describe('Transaction Utils', () => { request.securityAlertsEnabled = true; request.chainId = '0x1'; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any request.ppomController.usePPOM = (callback: any) => callback(ppomMock); await addTransaction(request); diff --git a/app/scripts/lib/transaction/util.ts b/app/scripts/lib/transaction/util.ts index 34350d114d63..1781dd19a416 100644 --- a/app/scripts/lib/transaction/util.ts +++ b/app/scripts/lib/transaction/util.ts @@ -80,6 +80,8 @@ export type AddTransactionRequest = FinalAddTransactionRequest & { }; export type AddDappTransactionRequest = BaseAddTransactionRequest & { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any dappRequest: Record; }; @@ -272,6 +274,8 @@ async function addUserOperationWithController( } = request; const { maxFeePerGas, maxPriorityFeePerGas } = transactionParams; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any const { origin, requireApproval, type } = transactionOptions as any; const normalisedTransaction: TransactionParams = { diff --git a/app/scripts/lib/util.ts b/app/scripts/lib/util.ts index 77c2e8543b64..c523f6c03bbd 100644 --- a/app/scripts/lib/util.ts +++ b/app/scripts/lib/util.ts @@ -181,6 +181,8 @@ export const isValidDate = (d: Date | number) => { */ type DeferredPromise = { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any promise: Promise; resolve?: () => void; reject?: () => void; diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 5ab12ea3e351..398ee8343ddd 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -304,6 +304,10 @@ import { snapKeyringBuilder, getAccountsBySnapId } from './lib/snap-keyring'; import { encryptorFactory } from './lib/encryptor-factory'; import { addDappTransaction, addTransaction } from './lib/transaction/util'; import { LatticeKeyringOffscreen } from './lib/offscreen-bridge/lattice-offscreen-keyring'; +///: BEGIN:ONLY_INCLUDE_IF(snaps) +import PREINSTALLED_SNAPS from './snaps/preinstalled-snaps'; +///: END:ONLY_INCLUDE_IF +import AuthenticationController from './controllers/authentication/authentication-controller'; export const METAMASK_CONTROLLER_EVENTS = { // Fired after state changes that impact the extension badge (unapproved msg count) @@ -486,13 +490,7 @@ export default class MetamaskController extends EventEmitter { this.networkController.getProviderAndBlockTracker().provider; this.blockTracker = this.networkController.getProviderAndBlockTracker().blockTracker; - - // TODO: Delete when ready to remove `networkVersion` from provider object - this.deprecatedNetworkId = null; - networkControllerMessenger.subscribe( - 'NetworkController:networkDidChange', - () => this.updateDeprecatedNetworkId(), - ); + this.deprecatedNetworkVersions = {}; const tokenListMessenger = this.controllerMessenger.getRestricted({ name: 'TokenListController', @@ -1285,6 +1283,7 @@ export default class MetamaskController extends EventEmitter { allowLocalSnaps, requireAllowlist, }, + preinstalledSnaps: PREINSTALLED_SNAPS, }); this.notificationController = new NotificationController({ @@ -1391,6 +1390,15 @@ export default class MetamaskController extends EventEmitter { ///: END:ONLY_INCLUDE_IF + // Notification Controllers + this.authenticationController = new AuthenticationController({ + state: initState.AuthenticationController, + messenger: this.controllerMessenger.getRestricted({ + name: 'AuthenticationController', + allowedActions: [`${this.snapController.name}:handleRequest`], + }), + }); + // account tracker watches balances, nonces, and any code at their address this.accountTracker = new AccountTracker({ provider: this.provider, @@ -2202,7 +2210,6 @@ export default class MetamaskController extends EventEmitter { } postOnboardingInitialization() { - this.updateDeprecatedNetworkId(); this.networkController.lookupNetwork(); } @@ -2676,20 +2683,21 @@ export default class MetamaskController extends EventEmitter { // subset of state for metamask inpage provider const publicConfigStore = new ObservableStore(); - const selectPublicState = (chainId, { isUnlocked }) => { + const selectPublicState = async ({ isUnlocked }) => { + const { chainId, networkVersion } = await this.getProviderNetworkState(); + return { isUnlocked, chainId, - networkVersion: this.deprecatedNetworkId ?? 'loading', + networkVersion: networkVersion ?? 'loading', }; }; - const updatePublicConfigStore = (memState) => { + const updatePublicConfigStore = async (memState) => { const networkStatus = memState.networksMetadata[memState.selectedNetworkClientId]?.status; - const { chainId } = this.networkController.state.providerConfig; if (networkStatus === NetworkStatus.Available) { - publicConfigStore.putState(selectPublicState(chainId, memState)); + publicConfigStore.putState(await selectPublicState(memState)); } }; @@ -2707,12 +2715,14 @@ export default class MetamaskController extends EventEmitter { * @returns {Promise<{ isUnlocked: boolean, networkVersion: string, chainId: string, accounts: string[] }>} An object with relevant state properties. */ async getProviderState(origin) { + const providerNetworkState = await this.getProviderNetworkState( + this.preferencesController.getUseRequestQueue() ? origin : undefined, + ); + return { isUnlocked: this.isUnlocked(), accounts: await this.getPermittedAccounts(origin), - ...this.getProviderNetworkState( - this.preferencesController.getUseRequestQueue() ? origin : undefined, - ), + ...providerNetworkState, }; } @@ -2722,71 +2732,41 @@ export default class MetamaskController extends EventEmitter { * @param {string} origin - The origin identifier for which network state is requested (default: 'metamask'). * @returns {object} An object containing important network state properties, including chainId and networkVersion. */ - getProviderNetworkState(origin = METAMASK_DOMAIN) { - let chainId; - if ( - this.preferencesController.getUseRequestQueue() && - origin !== METAMASK_DOMAIN - ) { - const networkClientId = this.controllerMessenger.call( - 'SelectedNetworkController:getNetworkClientIdForDomain', - origin, - ); - - const networkClient = this.controllerMessenger.call( - 'NetworkController:getNetworkClientById', - networkClientId, - ); - chainId = networkClient.configuration.chainId; - } else { - chainId = this.networkController.state.providerConfig.chainId; - } + async getProviderNetworkState(origin = METAMASK_DOMAIN) { + const networkClientId = this.controllerMessenger.call( + 'SelectedNetworkController:getNetworkClientIdForDomain', + origin, + ); - return { - chainId, - networkVersion: this.deprecatedNetworkId ?? 'loading', - }; - } + const networkClient = this.controllerMessenger.call( + 'NetworkController:getNetworkClientById', + networkClientId, + ); - /** - * TODO: Delete when ready to remove `networkVersion` from provider object - * Updates the `deprecatedNetworkId` value - */ - async updateDeprecatedNetworkId() { - try { - this.deprecatedNetworkId = await this.deprecatedGetNetworkId(); - } catch (error) { - console.error(error); - this.deprecatedNetworkId = null; - } - this._notifyChainChange(); - } + const { chainId } = networkClient.configuration; - /** - * TODO: Delete when ready to remove `networkVersion` from provider object - * Gets current networkId as returned by `net_version` - * - * @returns {string} The networkId for the current network or null on failure - * @throws Will throw if there is a problem getting the network version - */ - async deprecatedGetNetworkId() { - const ethQuery = this.controllerMessenger.call( - 'NetworkController:getEthQuery', - ); + const { completedOnboarding } = this.onboardingController.store.getState(); - if (!ethQuery) { - throw new Error('Provider has not been initialized'); + let networkVersion = this.deprecatedNetworkVersions[networkClientId]; + if (!networkVersion && completedOnboarding) { + const ethQuery = new EthQuery(networkClient.provider); + networkVersion = await new Promise((resolve) => { + ethQuery.sendAsync({ method: 'net_version' }, (error, result) => { + if (error) { + console.error(error); + resolve(null); + } else { + resolve(convertNetworkId(result)); + } + }); + }); + this.deprecatedNetworkVersions[networkClientId] = networkVersion; } - return new Promise((resolve, reject) => { - ethQuery.sendAsync({ method: 'net_version' }, (error, result) => { - if (error) { - reject(error); - } else { - resolve(convertNetworkId(result)); - } - }); - }); + return { + chainId, + networkVersion: networkVersion ?? 'loading', + }; } //============================================================================= @@ -2879,6 +2859,10 @@ export default class MetamaskController extends EventEmitter { preferencesController.setUseMultiAccountBalanceChecker.bind( preferencesController, ), + dismissOpenSeaToBlockaidBanner: + preferencesController.dismissOpenSeaToBlockaidBanner.bind( + preferencesController, + ), setUseSafeChainsListValidation: preferencesController.setUseSafeChainsListValidation.bind( preferencesController, @@ -3162,6 +3146,14 @@ export default class MetamaskController extends EventEmitter { appStateController.updateNftDropDownState.bind(appStateController), setFirstTimeUsedNetwork: appStateController.setFirstTimeUsedNetwork.bind(appStateController), + setSwitchedNetworkDetails: + appStateController.setSwitchedNetworkDetails.bind(appStateController), + clearSwitchedNetworkDetails: + appStateController.clearSwitchedNetworkDetails.bind(appStateController), + setSwitchedNetworkNeverShowMessage: + appStateController.setSwitchedNetworkNeverShowMessage.bind( + appStateController, + ), // EnsController tryReverseResolveAddress: @@ -5299,8 +5291,12 @@ export default class MetamaskController extends EventEmitter { Object.keys(this.connections).forEach((origin) => { Object.values(this.connections[origin]).forEach(async (conn) => { - if (conn.engine) { - conn.engine.emit('notification', await getPayload(origin)); + try { + if (conn.engine) { + conn.engine.emit('notification', await getPayload(origin)); + } + } catch (err) { + console.error(err); } }); }); @@ -5844,16 +5840,16 @@ export default class MetamaskController extends EventEmitter { this.permissionLogController.updateAccountsHistory(origin, newAccounts); } - _notifyChainChange() { + async _notifyChainChange() { if (this.preferencesController.getUseRequestQueue()) { - this.notifyAllConnections((origin) => ({ + this.notifyAllConnections(async (origin) => ({ method: NOTIFICATION_NAMES.chainChanged, - params: this.getProviderNetworkState(origin), + params: await this.getProviderNetworkState(origin), })); } else { this.notifyAllConnections({ method: NOTIFICATION_NAMES.chainChanged, - params: this.getProviderNetworkState(), + params: await this.getProviderNetworkState(), }); } } diff --git a/app/scripts/migrations/081.ts b/app/scripts/migrations/081.ts index 0162a43b6e49..aa7e48a8c9fa 100644 --- a/app/scripts/migrations/081.ts +++ b/app/scripts/migrations/081.ts @@ -91,7 +91,7 @@ function transformState(state: Record) { // Adding the snap name to the wallet_snap permission's caveat value const snapId = permissionName.slice(snapPrefix.length); const caveat = ( - (updatedPermissions.wallet_snap as Record) + (updatedPermissions.wallet_snap as Record) .caveats as unknown[] )[0]; diff --git a/app/scripts/migrations/095.ts b/app/scripts/migrations/095.ts index 39128284e03f..6361bba388fc 100644 --- a/app/scripts/migrations/095.ts +++ b/app/scripts/migrations/095.ts @@ -35,7 +35,11 @@ function migrateData(state: Record): void { removeIncomingTransactionsControllerState(state); } +// TODO: Replace `any` with type +// eslint-disable-next-line @typescript-eslint/no-explicit-any function moveIncomingTransactions(state: Record) { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any const incomingTransactions: Record = state.IncomingTransactionsController?.incomingTransactions || {}; @@ -46,6 +50,8 @@ function moveIncomingTransactions(state: Record) { const transactions = state.TransactionController?.transactions || {}; const updatedTransactions = Object.values(incomingTransactions).reduce( + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any (result: Record, tx: any) => { result[tx.id] = tx; return result; @@ -59,7 +65,11 @@ function moveIncomingTransactions(state: Record) { }; } +// TODO: Replace `any` with type +// eslint-disable-next-line @typescript-eslint/no-explicit-any function generateLastFetchedBlockNumbers(state: Record) { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any const incomingTransactions: Record = state.IncomingTransactionsController?.incomingTransactions || {}; diff --git a/app/scripts/migrations/096.ts b/app/scripts/migrations/096.ts index 749956a726b2..b04b7613e008 100644 --- a/app/scripts/migrations/096.ts +++ b/app/scripts/migrations/096.ts @@ -4,6 +4,8 @@ import { CHAIN_IDS } from '../../../shared/constants/network'; type VersionedData = { meta: { version: number }; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any data: Record; }; @@ -34,6 +36,8 @@ export async function migrate( } type NetworkConfiguration = { + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any chainId: Record; }; @@ -48,10 +52,18 @@ function transformState(state: Record) { return state; } const { PreferencesController, NetworkController } = state; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any const { featureFlags }: Record = PreferencesController; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any const { showIncomingTransactions }: any = featureFlags; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any const { networkConfigurations }: Record = NetworkController; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any const addedNetwork: Record[] = Object.values(networkConfigurations).map( (network) => network.chainId, @@ -63,6 +75,8 @@ function transformState(state: Record) { CHAIN_IDS.SEPOLIA, CHAIN_IDS.LINEA_GOERLI, ]; + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any const allSavedNetworks: Record = [ ...mainNetworks, ...addedNetwork, diff --git a/app/scripts/migrations/097.ts b/app/scripts/migrations/097.ts index 9e99cf61b40a..56a11679449d 100644 --- a/app/scripts/migrations/097.ts +++ b/app/scripts/migrations/097.ts @@ -21,6 +21,8 @@ export async function migrate( return versionedData; } +// TODO: Replace `any` with type +// eslint-disable-next-line @typescript-eslint/no-explicit-any function transformState(state: Record) { const transactionControllerState = state?.TransactionController || {}; const transactions = transactionControllerState?.transactions || {}; diff --git a/app/scripts/migrations/098.ts b/app/scripts/migrations/098.ts index 3085827b4c6a..2fe77148e052 100644 --- a/app/scripts/migrations/098.ts +++ b/app/scripts/migrations/098.ts @@ -25,6 +25,8 @@ export async function migrate( return versionedData; } +// TODO: Replace `any` with type +// eslint-disable-next-line @typescript-eslint/no-explicit-any function transformState(state: Record) { const transactionControllerState = state?.TransactionController || {}; const transactions = transactionControllerState?.transactions || {}; diff --git a/app/scripts/migrations/099.test.ts b/app/scripts/migrations/099.test.ts index 1feba98e6fa3..3b98f4f72a26 100644 --- a/app/scripts/migrations/099.test.ts +++ b/app/scripts/migrations/099.test.ts @@ -72,6 +72,8 @@ describe('migration #99', () => { const newStorage = await migrate(oldStorage); + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any const migratedTransactions = (newStorage.data.TransactionController as any) .transactions; diff --git a/app/scripts/migrations/099.ts b/app/scripts/migrations/099.ts index 9464d19a64df..38018c97919d 100644 --- a/app/scripts/migrations/099.ts +++ b/app/scripts/migrations/099.ts @@ -24,6 +24,8 @@ export async function migrate( return versionedData; } +// TODO: Replace `any` with type +// eslint-disable-next-line @typescript-eslint/no-explicit-any function transformState(state: Record) { const transactionControllerState = state?.TransactionController || {}; const transactions = transactionControllerState?.transactions || {}; @@ -33,6 +35,8 @@ function transformState(state: Record) { } const newTxs = Object.keys(transactions).reduce( + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any (txs: { [key: string]: any }, oldTransactionId) => { // Clone the transaction const transaction = cloneDeep(transactions[oldTransactionId]); diff --git a/app/scripts/migrations/100.ts b/app/scripts/migrations/100.ts index c9b4a99afceb..89dbe0d5670d 100644 --- a/app/scripts/migrations/100.ts +++ b/app/scripts/migrations/100.ts @@ -25,6 +25,8 @@ export async function migrate( return versionedData; } +// TODO: Replace `any` with type +// eslint-disable-next-line @typescript-eslint/no-explicit-any function transformState(state: Record) { const addressBook = state?.AddressBookController?.addressBook ?? {}; const names = state?.NameController?.names?.ethereumAddress ?? {}; diff --git a/app/scripts/migrations/102.ts b/app/scripts/migrations/102.ts index 820e67605251..a1fde4f27f7f 100644 --- a/app/scripts/migrations/102.ts +++ b/app/scripts/migrations/102.ts @@ -23,6 +23,8 @@ export async function migrate( return versionedData; } +// TODO: Replace `any` with type +// eslint-disable-next-line @typescript-eslint/no-explicit-any function transformState(state: Record) { const transactionControllerState = state?.TransactionController || {}; const transactions = transactionControllerState?.transactions || {}; @@ -32,6 +34,8 @@ function transformState(state: Record) { } const newTxs = Object.keys(transactions).reduce( + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any (txs: { [key: string]: any }, txId) => { // Clone the transaction const transaction = cloneDeep(transactions[txId]); diff --git a/app/scripts/migrations/104.ts b/app/scripts/migrations/104.ts index 340d167ccd5f..38ab3c0f57c8 100644 --- a/app/scripts/migrations/104.ts +++ b/app/scripts/migrations/104.ts @@ -22,6 +22,8 @@ export async function migrate( return versionedData; } +// TODO: Replace `any` with type +// eslint-disable-next-line @typescript-eslint/no-explicit-any function transformState(state: Record) { const transactionControllerState = state?.TransactionController; @@ -32,6 +34,8 @@ function transformState(state: Record) { const transactionsObject = transactionControllerState?.transactions || {}; const transactionsArray = Object.values(transactionsObject).sort( + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any (a: any, b: any) => (a.time > b.time ? -1 : 1), // Descending ); diff --git a/app/scripts/migrations/105.ts b/app/scripts/migrations/105.ts index 10be55e69e0a..482bc5a73d15 100644 --- a/app/scripts/migrations/105.ts +++ b/app/scripts/migrations/105.ts @@ -48,6 +48,8 @@ function migrateData(state: Record): void { createSelectedAccountForAccountsController(state); } +// TODO: Replace `any` with type +// eslint-disable-next-line @typescript-eslint/no-explicit-any function createDefaultAccountsController(state: Record) { state.AccountsController = { internalAccounts: { @@ -58,6 +60,8 @@ function createDefaultAccountsController(state: Record) { } function createInternalAccountsForAccountsController( + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any state: Record, ) { const identities: { @@ -98,6 +102,8 @@ function createInternalAccountsForAccountsController( } function createSelectedAccountForAccountsController( + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any state: Record, ) { const selectedAddress = state.PreferencesController?.selectedAddress; diff --git a/app/scripts/migrations/108.ts b/app/scripts/migrations/108.ts index 1ea75957b73a..4b12de5f9004 100644 --- a/app/scripts/migrations/108.ts +++ b/app/scripts/migrations/108.ts @@ -27,6 +27,8 @@ export async function migrate( return versionedData; } +// TODO: Replace `any` with type +// eslint-disable-next-line @typescript-eslint/no-explicit-any function transformState(state: Record) { const addressBook = state?.AddressBookController?.addressBook ?? {}; const names = state?.NameController?.names?.ethereumAddress ?? {}; diff --git a/app/scripts/migrations/109.ts b/app/scripts/migrations/109.ts index 5c40b07d4b09..13e268b8e061 100644 --- a/app/scripts/migrations/109.ts +++ b/app/scripts/migrations/109.ts @@ -27,6 +27,8 @@ export async function migrate( return versionedData; } +// TODO: Replace `any` with type +// eslint-disable-next-line @typescript-eslint/no-explicit-any function transformState(state: Record) { const identities: PreferencesControllerState['identities'] = state?.PreferencesController?.identities ?? {}; diff --git a/app/scripts/migrations/110.ts b/app/scripts/migrations/110.ts index bc941ac87695..7a677ed8b9cd 100644 --- a/app/scripts/migrations/110.ts +++ b/app/scripts/migrations/110.ts @@ -35,6 +35,8 @@ export async function migrate( return versionedData; } +// TODO: Replace `any` with type +// eslint-disable-next-line @typescript-eslint/no-explicit-any function transformState(state: Record) { const NetworkController = state?.NetworkController || {}; const provider = NetworkController?.providerConfig || {}; diff --git a/app/scripts/migrations/111.ts b/app/scripts/migrations/111.ts index a45c24fa168f..1a06e655cabb 100644 --- a/app/scripts/migrations/111.ts +++ b/app/scripts/migrations/111.ts @@ -28,6 +28,8 @@ export async function migrate( return versionedData; } +// TODO: Replace `any` with type +// eslint-disable-next-line @typescript-eslint/no-explicit-any function transformState(state: Record) { if (!hasProperty(state, 'SelectedNetworkController')) { return state; diff --git a/app/scripts/migrations/112.ts b/app/scripts/migrations/112.ts index 6313462be199..519be68c9ca3 100644 --- a/app/scripts/migrations/112.ts +++ b/app/scripts/migrations/112.ts @@ -26,6 +26,8 @@ export async function migrate( return versionedData; } +// TODO: Replace `any` with type +// eslint-disable-next-line @typescript-eslint/no-explicit-any function transformState(state: Record) { if (!hasProperty(state, 'SelectedNetworkController')) { return state; diff --git a/app/scripts/migrations/114.test.ts b/app/scripts/migrations/114.test.ts index 0184d753652f..10474bc2c262 100644 --- a/app/scripts/migrations/114.test.ts +++ b/app/scripts/migrations/114.test.ts @@ -21,7 +21,7 @@ describe('migration #114', () => { }); describe('deprecates transactionSecurityCheckEnabled in PreferencesController', () => { - it('sets securityAlertsEnabled to true if transactionSecurityCheckEnabled is true', async () => { + it('sets securityAlertsEnabled and hasMigratedFromOpenSeaToBlockaid to true if transactionSecurityCheckEnabled is true', async () => { const oldStorage = { PreferencesController: { transactionSecurityCheckEnabled: true, @@ -32,6 +32,7 @@ describe('migration #114', () => { const expectedState = { PreferencesController: { securityAlertsEnabled: true, + hasMigratedFromOpenSeaToBlockaid: true, }, }; diff --git a/app/scripts/migrations/114.ts b/app/scripts/migrations/114.ts index 9052e44a8efa..bed9c70a8850 100644 --- a/app/scripts/migrations/114.ts +++ b/app/scripts/migrations/114.ts @@ -26,6 +26,8 @@ export async function migrate( return versionedData; } +// TODO: Replace `any` with type +// eslint-disable-next-line @typescript-eslint/no-explicit-any function transformState(state: Record) { if (!hasProperty(state, 'PreferencesController')) { return state; @@ -43,6 +45,7 @@ function transformState(state: Record) { ) { if (state.PreferencesController.transactionSecurityCheckEnabled) { state.PreferencesController.securityAlertsEnabled = true; + state.PreferencesController.hasMigratedFromOpenSeaToBlockaid = true; } delete state.PreferencesController.transactionSecurityCheckEnabled; diff --git a/app/scripts/snaps/preinstalled-snaps.ts b/app/scripts/snaps/preinstalled-snaps.ts new file mode 100644 index 000000000000..67144b87177f --- /dev/null +++ b/app/scripts/snaps/preinstalled-snaps.ts @@ -0,0 +1,55 @@ +import type { PreinstalledSnap } from '@metamask/snaps-controllers'; + +// Preinstalled snaps requires fs and require to read and use any installed snaps. +// We can switch `require` to `import`, but then this file gets transpiled to ESM & won't have access to `require.resolve`. +// Work is needed to create the require object in ESM - specifically the import url. See snippet +// import { createRequire } from 'node:module' +// const require = createRequire(import.meta.url) +// ^ The 'import.meta' meta-property is not allowed in files which will build into CommonJS output +/* eslint-disable-next-line @typescript-eslint/no-require-imports,@typescript-eslint/no-var-requires */ +const fs = require('fs'); + +const PREINSTALLED_SNAPS: PreinstalledSnap[] = []; + +///: BEGIN:ONLY_INCLUDE_IF(snaps) +PREINSTALLED_SNAPS.push( + getPreinstalledSnap( + '@metamask/message-signing-snap', + fs.readFileSync( + require.resolve('@metamask/message-signing-snap/snap.manifest.json'), + 'utf-8', + ), + [ + { + path: 'images/icon.svg', + value: fs.readFileSync( + require.resolve('@metamask/message-signing-snap/images/icon.svg'), + ), + }, + { + path: 'dist/bundle.js', + value: fs.readFileSync( + require.resolve('@metamask/message-signing-snap/dist/bundle.js'), + ), + }, + ], + ), +); + +function getPreinstalledSnap( + npmPackage: string, + manifest: PreinstalledSnap['manifest'], + files: PreinstalledSnap['files'], +): PreinstalledSnap { + return { + snapId: `npm:${npmPackage}` as PreinstalledSnap['snapId'], + manifest: JSON.parse(manifest), + files, + removable: false, + }; +} +///: END:ONLY_INCLUDE_IF + +Object.freeze(PREINSTALLED_SNAPS); + +export default PREINSTALLED_SNAPS; diff --git a/app/trezor-usb-permissions.html b/app/trezor-usb-permissions.html index b3c6ce72b11c..fa116f591fc2 100644 --- a/app/trezor-usb-permissions.html +++ b/app/trezor-usb-permissions.html @@ -1,9 +1,9 @@ - - + + TrezorConnect | Trezor @@ -31,5 +31,5 @@ - + diff --git a/builds.yml b/builds.yml index 53802bf4d6f4..8a6c5f15f556 100644 --- a/builds.yml +++ b/builds.yml @@ -27,7 +27,7 @@ buildTypes: - SEGMENT_WRITE_KEY_REF: SEGMENT_PROD_WRITE_KEY - ALLOW_LOCAL_SNAPS: false - REQUIRE_SNAPS_ALLOWLIST: true - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/5.0.3/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/5.0.4/index.html - ACCOUNT_SNAPS_DIRECTORY_URL: https://snaps.metamask.io/account-management # Main build uses the default browser manifest manifestOverrides: false @@ -64,7 +64,7 @@ buildTypes: - SEGMENT_FLASK_WRITE_KEY - ALLOW_LOCAL_SNAPS: true - REQUIRE_SNAPS_ALLOWLIST: false - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/5.0.3/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/5.0.4/index.html - SUPPORT_LINK: https://metamask-flask.zendesk.com/hc - SUPPORT_REQUEST_LINK: https://metamask-flask.zendesk.com/hc/en-us/requests/new - INFURA_ENV_KEY_REF: INFURA_FLASK_PROJECT_ID @@ -85,7 +85,7 @@ buildTypes: - SEGMENT_FLASK_WRITE_KEY - ALLOW_LOCAL_SNAPS: true - REQUIRE_SNAPS_ALLOWLIST: false - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/5.0.3/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/5.0.4/index.html - SUPPORT_LINK: https://metamask-flask.zendesk.com/hc - SUPPORT_REQUEST_LINK: https://metamask-flask.zendesk.com/hc/en-us/requests/new - INFURA_ENV_KEY_REF: INFURA_FLASK_PROJECT_ID @@ -107,7 +107,7 @@ buildTypes: - SEGMENT_WRITE_KEY_REF: SEGMENT_MMI_WRITE_KEY - ALLOW_LOCAL_SNAPS: false - REQUIRE_SNAPS_ALLOWLIST: true - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/5.0.3/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/5.0.4/index.html - MMI_CONFIGURATION_SERVICE_URL: https://configuration.metamask-institutional.io/v2/configuration/default - SUPPORT_LINK: https://mmi-support.metamask.io/hc/en-us - SUPPORT_REQUEST_LINK: https://mmi-support.metamask.io/hc/en-us/requests/new @@ -196,6 +196,14 @@ env: # This variable is read by Trezor's source and breaks build if not included - ASSET_PREFIX: null + ### + # Notifications Feature + ### + - AUTH_API: https://authentication.api.cx.metamask.io + - OIDC_API: https://oidc.api.cx.metamask.io + - OIDC_CLIENT_ID: 77285a0b-a0c6-4681-ba36-cc7d3a21aece + - OIDC_GRANT_TYPE: urn:ietf:params:oauth:grant-type:jwt-bearer + ### # API keys to 3rd party services ### @@ -261,8 +269,13 @@ env: - MULTICHAIN: '' # Determines if feature flagged Multichain Transactions should be used - TRANSACTION_MULTICHAIN: '' + # Used to enable confirmation redesigned pages - ENABLE_CONFIRMATION_REDESIGN: '' + + # Enables the notifications feature within the build: + - NOTIFICATIONS: '' + ### # Meta variables ### diff --git a/development/build/scripts.js b/development/build/scripts.js index 9e0ebd53715c..1baa6e9d7f27 100644 --- a/development/build/scripts.js +++ b/development/build/scripts.js @@ -4,7 +4,6 @@ const { callbackify } = require('util'); const path = require('path'); const { writeFileSync, readFileSync, unlinkSync } = require('fs'); const EventEmitter = require('events'); -const assert = require('assert'); const gulp = require('gulp'); const watch = require('gulp-watch'); const Vinyl = require('vinyl'); @@ -30,9 +29,9 @@ const terser = require('terser'); const bifyModuleGroups = require('bify-module-groups'); -const phishingWarningManifest = require('@metamask/phishing-warning/package.json'); const { streamFlatMap } = require('../stream-flat-map'); -const { BUILD_TARGETS, ENVIRONMENT } = require('./constants'); +const { setEnvironmentVariables } = require('./set-environment-variables'); +const { BUILD_TARGETS } = require('./constants'); const { getConfig } = require('./config'); const { isDevBuild, @@ -41,8 +40,6 @@ const { logError, wrapAgainstScuttling, getBuildName, - getBuildAppId, - getBuildIcon, makeSelfInjecting, } = require('./utils'); @@ -58,7 +55,7 @@ const { // map dist files to bag of needed native APIs against LM scuttling const scuttlingConfigBase = { - 'sentry-install.js': { + 'scripts/sentry-install.js': { // globals sentry need to function window: '', navigator: '', @@ -96,128 +93,12 @@ const mv3ScuttlingConfig = { ...scuttlingConfigBase }; const standardScuttlingConfig = { ...scuttlingConfigBase, - 'sentry-install.js': { - ...scuttlingConfigBase['sentry-install.js'], + 'scripts/sentry-install.js': { + ...scuttlingConfigBase['scripts/sentry-install.js'], document: '', }, }; -/** - * Get the appropriate Infura project ID. - * - * @param {object} options - The Infura project ID options. - * @param {string} options.buildType - The current build type. - * @param {ENVIRONMENT[keyof ENVIRONMENT]} options.environment - The build environment. - * @param {boolean} options.testing - Whether this is a test build or not. - * @param options.variables - * @returns {string} The Infura project ID. - */ -function getInfuraProjectId({ buildType, variables, environment, testing }) { - const EMPTY_PROJECT_ID = '00000000000000000000000000000000'; - if (testing) { - return EMPTY_PROJECT_ID; - } else if (environment !== ENVIRONMENT.PRODUCTION) { - // Skip validation because this is unset on PRs from forks. - // For forks, return empty project ID if we don't have one. - if ( - !variables.isDefined('INFURA_PROJECT_ID') && - environment === ENVIRONMENT.PULL_REQUEST - ) { - return EMPTY_PROJECT_ID; - } - return variables.get('INFURA_PROJECT_ID'); - } - /** @type {string|undefined} */ - const infuraKeyReference = variables.get('INFURA_ENV_KEY_REF'); - assert( - typeof infuraKeyReference === 'string' && infuraKeyReference.length > 0, - `Build type "${buildType}" has improperly set INFURA_ENV_KEY_REF in builds.yml. Current value: "${infuraKeyReference}"`, - ); - /** @type {string|undefined} */ - const infuraProjectId = variables.get(infuraKeyReference); - assert( - typeof infuraProjectId === 'string' && infuraProjectId.length > 0, - `Infura Project ID environmental variable "${infuraKeyReference}" is set improperly.`, - ); - return infuraProjectId; -} - -/** - * Get the appropriate Segment write key. - * - * @param {object} options - The Segment write key options. - * @param {string} options.buildType - The current build type. - * @param {keyof ENVIRONMENT} options.environment - The current build environment. - * @param {import('../lib/variables').Variables} options.variables - Object containing all variables that modify the build pipeline - * @returns {string} The Segment write key. - */ -function getSegmentWriteKey({ buildType, variables, environment }) { - if (environment !== ENVIRONMENT.PRODUCTION) { - // Skip validation because this is unset on PRs from forks, and isn't necessary for development builds. - return variables.get('SEGMENT_WRITE_KEY'); - } - - const segmentKeyReference = variables.get('SEGMENT_WRITE_KEY_REF'); - assert( - typeof segmentKeyReference === 'string' && segmentKeyReference.length > 0, - `Build type "${buildType}" has improperly set SEGMENT_WRITE_KEY_REF in builds.yml. Current value: "${segmentKeyReference}"`, - ); - - const segmentWriteKey = variables.get(segmentKeyReference); - assert( - typeof segmentWriteKey === 'string' && segmentWriteKey.length > 0, - `Segment Write Key environmental variable "${segmentKeyReference}" is set improperly.`, - ); - return segmentWriteKey; -} - -/** - * Get the URL for the phishing warning page, if it has been set. - * - * @param {object} options - The phishing warning page options. - * @param {boolean} options.testing - Whether this is a test build or not. - * @param {import('../lib/variables').Variables} options.variables - Object containing all variables that modify the build pipeline - * @returns {string} The URL for the phishing warning page, or `undefined` if no URL is set. - */ -function getPhishingWarningPageUrl({ variables, testing }) { - let phishingWarningPageUrl = variables.get('PHISHING_WARNING_PAGE_URL'); - - assert( - phishingWarningPageUrl === null || - typeof phishingWarningPageUrl === 'string', - ); - if (phishingWarningPageUrl === null) { - phishingWarningPageUrl = testing - ? 'http://localhost:9999/' - : `https://metamask.github.io/phishing-warning/v${phishingWarningManifest.version}/`; - } - - // We add a hash/fragment to the URL dynamically, so we need to ensure it - // has a valid pathname to append a hash to. - const normalizedUrl = phishingWarningPageUrl.endsWith('/') - ? phishingWarningPageUrl - : `${phishingWarningPageUrl}/`; - - let phishingWarningPageUrlObject; - try { - // eslint-disable-next-line no-new - phishingWarningPageUrlObject = new URL(normalizedUrl); - } catch (error) { - throw new Error( - `Invalid phishing warning page URL: '${normalizedUrl}'`, - error, - ); - } - if (phishingWarningPageUrlObject.hash) { - // The URL fragment must be set dynamically - throw new Error( - `URL fragment not allowed in phishing warning page URL: '${normalizedUrl}'`, - ); - } - - return normalizedUrl; -} - const noopWriteStream = through.obj((_file, _fileEncoding, callback) => callback(), ); @@ -400,7 +281,7 @@ function createScriptTasks({ browserPlatforms, buildTarget, buildType, - destFilepath: `${label}.js`, + destFilepath: `scripts/${label}.js`, entryFilepath: `./app/scripts/${label}.js`, ignoredFiles, label, @@ -424,7 +305,7 @@ function createScriptTasks({ browserPlatforms, buildTarget, buildType, - destFilepath: `${label}.js`, + destFilepath: `scripts/${label}.js`, entryFilepath: `./app/scripts/${label}.js`, ignoredFiles, label, @@ -452,7 +333,7 @@ function createScriptTasks({ buildTarget, buildType, browserPlatforms, - destFilepath: `${inpage}.js`, + destFilepath: `scripts/${inpage}.js`, entryFilepath: `./app/scripts/${inpage}.js`, label: inpage, ignoredFiles, @@ -467,25 +348,29 @@ function createScriptTasks({ if (process.env.ENABLE_MV3) { return; } - // stringify inpage.js into itself, and then make it inject itself into the page + // stringify scripts/inpage.js into itself, and then make it inject itself into the page browserPlatforms.forEach((browser) => { makeSelfInjecting( - path.join(__dirname, `../../dist/${browser}/${inpage}.js`), + path.join(__dirname, `../../dist/${browser}/scripts/${inpage}.js`), ); }); - // delete the inpage.js source map, as it no longer represents inpage.js - // and so `yarn source-map-explorer` can't handle it. It's also not - // useful anyway, as inpage.js is injected as a `script.textContent`, - // and not tracked in Sentry or browsers devtools anyway. + // delete the scripts/inpage.js source map, as it no longer represents + // scripts/inpage.js and so `yarn source-map-explorer` can't handle it. + // It's also not useful anyway, as scripts/inpage.js is injected as a + // `script.textContent`, and not tracked in Sentry or browsers devtools + // anyway. unlinkSync( - path.join(__dirname, `../../dist/sourcemaps/${inpage}.js.map`), + path.join( + __dirname, + `../../dist/sourcemaps/scripts/${inpage}.js.map`, + ), ); }, createNormalBundle({ buildTarget, buildType, browserPlatforms, - destFilepath: `${contentscript}.js`, + destFilepath: `scripts/${contentscript}.js`, entryFilepath: `./app/scripts/${contentscript}.js`, label: contentscript, ignoredFiles, @@ -559,7 +444,7 @@ async function createManifestV3AppInitializationBundle({ browserPlatforms: mv3BrowserPlatforms, buildTarget, buildType, - destFilepath: 'app-init.js', + destFilepath: 'scripts/app-init.js', entryFilepath: './app/scripts/app-init.js', extraEnvironmentVariables, ignoredFiles, @@ -635,12 +520,16 @@ function createFactoredBuild({ const environment = getEnvironment({ buildTarget }); const config = await getConfig(buildType, environment); const { variables, activeBuild } = config; - await setEnvironmentVariables({ - buildTarget, + setEnvironmentVariables({ + isDevBuild: reloadOnChange, + isTestBuild: isTestBuild(buildTarget), + buildName: getBuildName({ + environment, + buildType, + }), buildType, environment, variables, - activeBuild, version, }); const features = { @@ -718,7 +607,7 @@ function createFactoredBuild({ // add lavamoat policy loader file to packer output moduleGroupPackerStream.push( new Vinyl({ - path: 'policy-load.js', + path: 'scripts/policy-load.js', contents: lavapack.makePolicyLoaderStream(lavamoatOpts), }), ); @@ -900,12 +789,16 @@ function createNormalBundle({ const environment = getEnvironment({ buildTarget }); const config = await getConfig(buildType, environment); const { activeBuild, variables } = config; - await setEnvironmentVariables({ - buildTarget, + setEnvironmentVariables({ + buildName: getBuildName({ + environment, + buildType, + }), + isDevBuild: devMode, + isTestBuild: isTestBuild(buildTarget), buildType, variables, environment, - activeBuild, version, }); Object.entries(extraEnvironmentVariables ?? {}).forEach(([key, value]) => @@ -1198,70 +1091,6 @@ async function createBundle(buildConfiguration, { reloadOnChange }) { } } -/** - * Sets environment variables to inject in the current build. - * - * @param {object} options - Build options. - * @param {BUILD_TARGETS} options.buildTarget - The current build target. - * @param {string} options.buildType - The current build type (e.g. "main", - * "flask", etc.). - * @param {string} options.version - The current version of the extension. - * @param options.activeBuild - * @param options.variables - * @param options.environment - */ -async function setEnvironmentVariables({ - buildTarget, - buildType, - activeBuild, - environment, - variables, - version, -}) { - const devMode = isDevBuild(buildTarget); - const testing = isTestBuild(buildTarget); - - variables.set({ - DEBUG: devMode || testing ? variables.getMaybe('DEBUG') : undefined, - EIP_4337_ENTRYPOINT: - variables.getMaybe('EIP_4337_ENTRYPOINT') || - (testing ? '0x18b06605539dc02ecD3f7AB314e38eB7c1dA5c9b' : undefined), - IN_TEST: testing, - INFURA_PROJECT_ID: getInfuraProjectId({ - buildType, - activeBuild, - variables, - environment, - testing, - }), - METAMASK_DEBUG: devMode || variables.getMaybe('METAMASK_DEBUG') === true, - METAMASK_BUILD_NAME: getBuildName({ - environment, - buildType, - }), - METAMASK_BUILD_APP_ID: getBuildAppId({ - buildType, - }), - METAMASK_BUILD_ICON: getBuildIcon({ - buildType, - }), - METAMASK_ENVIRONMENT: environment, - METAMASK_VERSION: version, - METAMASK_BUILD_TYPE: buildType, - NODE_ENV: devMode ? ENVIRONMENT.DEVELOPMENT : ENVIRONMENT.PRODUCTION, - PHISHING_WARNING_PAGE_URL: getPhishingWarningPageUrl({ - variables, - testing, - }), - SEGMENT_WRITE_KEY: getSegmentWriteKey({ - buildType, - activeBuild, - variables, - environment, - }), - }); -} - function renderJavaScriptLoader({ groupSet, commonSet, @@ -1280,18 +1109,22 @@ function renderJavaScriptLoader({ ); const securityScripts = applyLavaMoat - ? ['./runtime-lavamoat.js', './lockdown-more.js', './policy-load.js'] + ? [ + './scripts/runtime-lavamoat.js', + './scripts/lockdown-more.js', + './scripts/policy-load.js', + ] : [ - './lockdown-install.js', - './lockdown-run.js', - './lockdown-more.js', - './runtime-cjs.js', + './scripts/lockdown-install.js', + './scripts/lockdown-run.js', + './scripts/lockdown-more.js', + './scripts/runtime-cjs.js', ]; const requiredScripts = [ - './snow.js', - './use-snow.js', - './sentry-install.js', + './scripts/snow.js', + './scripts/use-snow.js', + './scripts/sentry-install.js', ...securityScripts, ...jsBundles, ]; diff --git a/development/build/set-environment-variables.js b/development/build/set-environment-variables.js new file mode 100644 index 000000000000..83f0016f5f3e --- /dev/null +++ b/development/build/set-environment-variables.js @@ -0,0 +1,215 @@ +const { readFileSync } = require('node:fs'); +const assert = require('node:assert'); +const { ENVIRONMENT } = require('./constants'); + +/** + * Sets environment variables to inject in the current build. + * + * @param {object} options - Build options. + * @param {string} options.buildName - The name of the build. + * @param {boolean} options.isDevBuild - Whether the build is a development build. + * @param {boolean} options.isTestBuild - Whether the build is a test build. + * @param {string} options.buildType - The current build type (e.g. "main", + * "flask", etc.). + * @param {string} options.version - The current version of the extension. + * @param {import('../lib/variables').Variables} options.variables + * @param {ENVIRONMENT[keyof ENVIRONMENT]} options.environment - The build environment. + */ +module.exports.setEnvironmentVariables = function setEnvironmentVariables({ + buildName, + isDevBuild, + isTestBuild, + buildType, + environment, + variables, + version, +}) { + variables.set({ + DEBUG: isDevBuild || isTestBuild ? variables.getMaybe('DEBUG') : undefined, + EIP_4337_ENTRYPOINT: + variables.getMaybe('EIP_4337_ENTRYPOINT') || + (isTestBuild ? '0x18b06605539dc02ecD3f7AB314e38eB7c1dA5c9b' : undefined), + IN_TEST: isTestBuild, + INFURA_PROJECT_ID: getInfuraProjectId({ + buildType, + variables, + environment, + testing: isTestBuild, + }), + METAMASK_DEBUG: isDevBuild || variables.getMaybe('METAMASK_DEBUG') === true, + METAMASK_BUILD_NAME: buildName, + METAMASK_BUILD_APP_ID: getBuildAppId({ + buildType, + }), + METAMASK_BUILD_ICON: getBuildIcon({ + buildType, + }), + METAMASK_ENVIRONMENT: environment, + METAMASK_VERSION: version, + METAMASK_BUILD_TYPE: buildType, + NODE_ENV: isDevBuild ? ENVIRONMENT.DEVELOPMENT : ENVIRONMENT.PRODUCTION, + PHISHING_WARNING_PAGE_URL: getPhishingWarningPageUrl({ + variables, + testing: isTestBuild, + }), + SEGMENT_WRITE_KEY: getSegmentWriteKey({ + buildType, + variables, + environment, + }), + }); +}; + +const BUILD_TYPES_TO_SVG_LOGO_PATH = { + main: './app/images/logo/metamask-fox.svg', + beta: './app/build-types/beta/images/logo/metamask-fox.svg', + flask: './app/build-types/flask/images/logo/metamask-fox.svg', + mmi: './app/build-types/mmi/images/logo/mmi-logo.svg', + desktop: './app/build-types/desktop/images/logo/metamask-fox.svg', +}; + +/** + * Get the image data uri for the svg icon for the current build. + * + * @param {object} options - The build options. + * @param {string} options.buildType - The build type of the current build. + * @returns {string} The image data uri for the icon. + */ +function getBuildIcon({ buildType }) { + const svgLogoPath = + BUILD_TYPES_TO_SVG_LOGO_PATH[buildType] || + BUILD_TYPES_TO_SVG_LOGO_PATH.main; + // encode as base64 as its more space-efficient for most SVGs than a data uri + return `data:image/svg+xml;base64,${readFileSync(svgLogoPath, 'base64')}`; +} + +/** + * Get the app ID for the current build. Should be valid reverse FQDN. + * + * @param {object} options - The build options. + * @param {string} options.buildType - The build type of the current build. + * @returns {string} The build app ID. + */ +function getBuildAppId({ buildType }) { + const baseDomain = 'io.metamask'; + return buildType === 'main' ? baseDomain : `${baseDomain}.${buildType}`; +} + +/** + * Get the appropriate Infura project ID. + * + * @param {object} options - The Infura project ID options. + * @param {string} options.buildType - The current build type. + * @param {ENVIRONMENT[keyof ENVIRONMENT]} options.environment - The build environment. + * @param {boolean} options.testing - Whether this is a test build or not. + * @param options.variables + * @returns {string} The Infura project ID. + */ +function getInfuraProjectId({ buildType, variables, environment, testing }) { + const EMPTY_PROJECT_ID = '00000000000000000000000000000000'; + if (testing) { + return EMPTY_PROJECT_ID; + } else if (environment !== ENVIRONMENT.PRODUCTION) { + // Skip validation because this is unset on PRs from forks. + // For forks, return empty project ID if we don't have one. + if ( + !variables.isDefined('INFURA_PROJECT_ID') && + environment === ENVIRONMENT.PULL_REQUEST + ) { + return EMPTY_PROJECT_ID; + } + return variables.get('INFURA_PROJECT_ID'); + } + /** @type {string|undefined} */ + const infuraKeyReference = variables.get('INFURA_ENV_KEY_REF'); + assert( + typeof infuraKeyReference === 'string' && infuraKeyReference.length > 0, + `Build type "${buildType}" has improperly set INFURA_ENV_KEY_REF in builds.yml. Current value: "${infuraKeyReference}"`, + ); + /** @type {string|undefined} */ + const infuraProjectId = variables.get(infuraKeyReference); + assert( + typeof infuraProjectId === 'string' && infuraProjectId.length > 0, + `Infura Project ID environmental variable "${infuraKeyReference}" is set improperly.`, + ); + return infuraProjectId; +} + +/** + * Get the appropriate Segment write key. + * + * @param {object} options - The Segment write key options. + * @param {string} options.buildType - The current build type. + * @param {keyof ENVIRONMENT} options.environment - The current build environment. + * @param {import('../lib/variables').Variables} options.variables - Object containing all variables that modify the build pipeline + * @returns {string} The Segment write key. + */ +function getSegmentWriteKey({ buildType, variables, environment }) { + if (environment !== ENVIRONMENT.PRODUCTION) { + // Skip validation because this is unset on PRs from forks, and isn't necessary for development builds. + return variables.get('SEGMENT_WRITE_KEY'); + } + + const segmentKeyReference = variables.get('SEGMENT_WRITE_KEY_REF'); + assert( + typeof segmentKeyReference === 'string' && segmentKeyReference.length > 0, + `Build type "${buildType}" has improperly set SEGMENT_WRITE_KEY_REF in builds.yml. Current value: "${segmentKeyReference}"`, + ); + + const segmentWriteKey = variables.get(segmentKeyReference); + assert( + typeof segmentWriteKey === 'string' && segmentWriteKey.length > 0, + `Segment Write Key environmental variable "${segmentKeyReference}" is set improperly.`, + ); + return segmentWriteKey; +} + +/** + * Get the URL for the phishing warning page, if it has been set. + * + * @param {object} options - The phishing warning page options. + * @param {boolean} options.testing - Whether this is a test build or not. + * @param {import('../lib/variables').Variables} options.variables - Object containing all variables that modify the build pipeline + * @returns {string} The URL for the phishing warning page, or `undefined` if no URL is set. + */ +function getPhishingWarningPageUrl({ variables, testing }) { + let phishingWarningPageUrl = variables.get('PHISHING_WARNING_PAGE_URL'); + + assert( + phishingWarningPageUrl === null || + typeof phishingWarningPageUrl === 'string', + ); + if (phishingWarningPageUrl === null) { + phishingWarningPageUrl = testing + ? 'http://localhost:9999/' + : `https://metamask.github.io/phishing-warning/v${ + // eslint-disable-next-line node/global-require + require('@metamask/phishing-warning/package.json').version + }/`; + } + + // We add a hash/fragment to the URL dynamically, so we need to ensure it + // has a valid pathname to append a hash to. + const normalizedUrl = phishingWarningPageUrl.endsWith('/') + ? phishingWarningPageUrl + : `${phishingWarningPageUrl}/`; + + let phishingWarningPageUrlObject; + try { + // eslint-disable-next-line no-new + phishingWarningPageUrlObject = new URL(normalizedUrl); + } catch (error) { + throw new Error( + `Invalid phishing warning page URL: '${normalizedUrl}'`, + error, + ); + } + if (phishingWarningPageUrlObject.hash) { + // The URL fragment must be set dynamically + throw new Error( + `URL fragment not allowed in phishing warning page URL: '${normalizedUrl}'`, + ); + } + + return normalizedUrl; +} diff --git a/development/build/static.js b/development/build/static.js index c8eb7d6d5ef2..54bef8f980ad 100644 --- a/development/build/static.js +++ b/development/build/static.js @@ -155,46 +155,46 @@ function getCopyTargets( src: shouldIncludeSnow ? `./node_modules/@lavamoat/snow/snow.prod.js` : EMPTY_JS_FILE, - dest: `snow.js`, + dest: `scripts/snow.js`, }, { src: shouldIncludeSnow ? `./app/scripts/use-snow.js` : EMPTY_JS_FILE, - dest: `use-snow.js`, + dest: `scripts/use-snow.js`, }, { src: shouldIncludeLockdown ? getPathInsideNodeModules('ses', 'dist/lockdown.umd.min.js') : EMPTY_JS_FILE, - dest: `lockdown-install.js`, + dest: `scripts/lockdown-install.js`, }, { src: './app/scripts/init-globals.js', - dest: 'init-globals.js', + dest: 'scripts/init-globals.js', }, { src: './app/scripts/load-app.js', - dest: 'load-app.js', + dest: 'scripts/load-app.js', }, { src: shouldIncludeLockdown ? `./app/scripts/lockdown-run.js` : EMPTY_JS_FILE, - dest: `lockdown-run.js`, + dest: `scripts/lockdown-run.js`, }, { src: shouldIncludeLockdown ? `./app/scripts/lockdown-more.js` : EMPTY_JS_FILE, - dest: `lockdown-more.js`, + dest: `scripts/lockdown-more.js`, }, { src: getPathInsideNodeModules('@lavamoat/lavapack', 'src/runtime-cjs.js'), - dest: `runtime-cjs.js`, + dest: `scripts/runtime-cjs.js`, pattern: '', }, { src: getPathInsideNodeModules('@lavamoat/lavapack', 'src/runtime.js'), - dest: `runtime-lavamoat.js`, + dest: `scripts/runtime-lavamoat.js`, pattern: '', }, { diff --git a/development/build/utils.js b/development/build/utils.js index 07349c88db28..746290fb8e2b 100644 --- a/development/build/utils.js +++ b/development/build/utils.js @@ -5,14 +5,6 @@ const { capitalize } = require('lodash'); const { loadBuildTypesConfig } = require('../lib/build-type'); const { BUILD_TARGETS, ENVIRONMENT } = require('./constants'); -const BUILD_TYPES_TO_SVG_LOGO_PATH = { - main: './app/images/logo/metamask-fox.svg', - beta: './app/build-types/beta/images/logo/metamask-fox.svg', - flask: './app/build-types/flask/images/logo/metamask-fox.svg', - mmi: './app/build-types/mmi/images/logo/mmi-logo.svg', - desktop: './app/build-types/desktop/images/logo/metamask-fox.svg', -}; - /** * Returns whether the current build is a development build or not. * @@ -261,32 +253,6 @@ function getBuildName({ return name; } -/** - * Get the app ID for the current build. Should be valid reverse FQDN. - * - * @param {object} options - The build options. - * @param {string} options.buildType - The build type of the current build. - * @returns {string} The build app ID. - */ -function getBuildAppId({ buildType }) { - const baseDomain = 'io.metamask'; - return buildType === 'main' ? baseDomain : `${baseDomain}.${buildType}`; -} - -/** - * Get the image data uri for the svg icon for the current build. - * - * @param {object} options - The build options. - * @param {string} options.buildType - The build type of the current build. - * @returns {string} The image data uri for the icon. - */ -function getBuildIcon({ buildType }) { - const svgLogoPath = - BUILD_TYPES_TO_SVG_LOGO_PATH[buildType] || - BUILD_TYPES_TO_SVG_LOGO_PATH.main; - const svg = readFileSync(svgLogoPath, 'utf8'); - return `data:image/svg+xml,${encodeURIComponent(svg)}`; -} /** * Takes the given JavaScript file at `filePath` and replaces its contents with * a script that injects the original file contents into the document in which @@ -306,8 +272,6 @@ function makeSelfInjecting(filePath) { module.exports = { getBrowserVersionMap, getBuildName, - getBuildAppId, - getBuildIcon, getEnvironment, isDevBuild, isTestBuild, diff --git a/development/charts/flamegraph/chart/index.html b/development/charts/flamegraph/chart/index.html index 7afe9f9d0dcb..ce53076ad9e4 100644 --- a/development/charts/flamegraph/chart/index.html +++ b/development/charts/flamegraph/chart/index.html @@ -9,7 +9,7 @@ rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" /> - +