diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 8a5b75b3524f..53961ff86b35 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -6173,6 +6173,12 @@ "updatedWithDate": { "message": "Updated $1" }, + "uploadDropFile": { + "message": "Drop your file here" + }, + "uploadFile": { + "message": "Upload file" + }, "urlErrorMsg": { "message": "URLs require the appropriate HTTP/HTTPS prefix." }, diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 771f164b7b4c..5509d804b6fd 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -61,9 +61,7 @@ export default class PreferencesController { useCurrencyRateCheck: true, useRequestQueue: true, openSeaEnabled: true, // todo set this to true - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) securityAlertsEnabled: true, - ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) addSnapAccountEnabled: false, ///: END:ONLY_INCLUDE_IF @@ -276,7 +274,6 @@ export default class PreferencesController { }); } - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) /** * Setter for the `securityAlertsEnabled` property * @@ -287,7 +284,6 @@ export default class PreferencesController { securityAlertsEnabled, }); } - ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) /** diff --git a/app/scripts/lib/createRPCMethodTrackingMiddleware.js b/app/scripts/lib/createRPCMethodTrackingMiddleware.js index 195f9c05fed5..1d7f42710cd6 100644 --- a/app/scripts/lib/createRPCMethodTrackingMiddleware.js +++ b/app/scripts/lib/createRPCMethodTrackingMiddleware.js @@ -11,18 +11,13 @@ import { parseTypedDataMessage } from '../../../shared/modules/transaction.utils import { BlockaidResultType, - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) BlockaidReason, - ///: END:ONLY_INCLUDE_IF } from '../../../shared/constants/security-provider'; - -///: BEGIN:ONLY_INCLUDE_IF(blockaid) import { EIP712_PRIMARY_TYPE_PERMIT, SIGNING_METHODS, } from '../../../shared/constants/transaction'; import { getBlockaidMetricsProps } from '../../../ui/helpers/utils/metrics'; -///: END:ONLY_INCLUDE_IF import { REDESIGN_APPROVAL_TYPES } from '../../../ui/pages/confirmations/utils/confirm'; import { getSnapAndHardwareInfoForMetrics } from './snap-keyring/metrics'; @@ -135,7 +130,6 @@ const TRANSFORM_PARAMS_MAP = { const rateLimitTimeoutsByMethod = {}; let globalRateLimitCount = 0; -///: BEGIN:ONLY_INCLUDE_IF(blockaid) /** * Returns a middleware that tracks inpage_provider usage using sampling for * each type of event except those that require user interaction, such as @@ -161,7 +155,6 @@ let globalRateLimitCount = 0; * tracked within the globalRateLimitTimeout time window. * @returns {Function} */ -///: END:ONLY_INCLUDE_IF export default function createRPCMethodTrackingMiddleware({ trackEvent, @@ -174,9 +167,7 @@ export default function createRPCMethodTrackingMiddleware({ getDeviceModel, isConfirmationRedesignEnabled, snapAndHardwareMessenger, - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) appStateController, - ///: END:ONLY_INCLUDE_IF }) { return async function rpcMethodTrackingMiddleware( /** @type {any} */ req, @@ -254,7 +245,6 @@ export default function createRPCMethodTrackingMiddleware({ data = req?.params?.[1]; } - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) if (req.securityAlertResponse?.providerRequestsCount) { Object.keys(req.securityAlertResponse.providerRequestsCount).forEach( (key) => { @@ -275,7 +265,7 @@ export default function createRPCMethodTrackingMiddleware({ eventProperties.security_alert_description = req.securityAlertResponse.description; } - ///: END:ONLY_INCLUDE_IF + const isConfirmationRedesign = isConfirmationRedesignEnabled() && REDESIGN_APPROVAL_TYPES.find( @@ -380,8 +370,6 @@ export default function createRPCMethodTrackingMiddleware({ } let blockaidMetricProps = {}; - - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) if (!isDisabledRPCMethod) { if (SIGNING_METHODS.includes(method)) { const securityAlertResponse = @@ -394,7 +382,6 @@ export default function createRPCMethodTrackingMiddleware({ }); } } - ///: END:ONLY_INCLUDE_IF const properties = { ...eventProperties, diff --git a/app/scripts/lib/transaction/metrics.test.ts b/app/scripts/lib/transaction/metrics.test.ts index 74d5aaa13ca0..3fed7842378b 100644 --- a/app/scripts/lib/transaction/metrics.test.ts +++ b/app/scripts/lib/transaction/metrics.test.ts @@ -19,12 +19,10 @@ import { MetaMetricsEventCategory, } from '../../../../shared/constants/metametrics'; import { TRANSACTION_ENVELOPE_TYPE_NAMES } from '../../../../shared/lib/transactions-controller-utils'; -///: BEGIN:ONLY_INCLUDE_IF(blockaid) import { BlockaidReason, BlockaidResultType, } from '../../../../shared/constants/security-provider'; -///: END:ONLY_INCLUDE_IF(blockaid) import { handleTransactionAdded, handleTransactionApproved, diff --git a/app/scripts/lib/transaction/metrics.ts b/app/scripts/lib/transaction/metrics.ts index ff8b937c3b4c..15713a72b62e 100644 --- a/app/scripts/lib/transaction/metrics.ts +++ b/app/scripts/lib/transaction/metrics.ts @@ -36,9 +36,7 @@ import { getSwapsTokensReceivedFromTxMeta, TRANSACTION_ENVELOPE_TYPE_NAMES, } from '../../../../shared/lib/transactions-controller-utils'; -///: BEGIN:ONLY_INCLUDE_IF(blockaid) import { getBlockaidMetricsProps } from '../../../../ui/helpers/utils/metrics'; -///: END:ONLY_INCLUDE_IF import { getSmartTransactionMetricsProperties } from '../../../../shared/modules/metametrics'; import { getSnapAndHardwareInfoForMetrics, @@ -968,7 +966,6 @@ 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); @@ -976,7 +973,6 @@ async function buildEventFragmentProperties({ if (blockaidProperties?.ui_customizations?.length > 0) { uiCustomizations.push(...blockaidProperties.ui_customizations); } - ///: END:ONLY_INCLUDE_IF if (simulationFails) { uiCustomizations.push(MetaMetricsEventUiCustomization.GasEstimationFailed); @@ -1009,9 +1005,7 @@ async function buildEventFragmentProperties({ token_standard: tokenStandard, transaction_type: transactionType, transaction_speed_up: type === TransactionType.retry, - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) ...blockaidProperties, - ///: END:ONLY_INCLUDE_IF // ui_customizations must come after ...blockaidProperties ui_customizations: uiCustomizations.length > 0 ? uiCustomizations : null, ...smartTransactionMetricsProperties, diff --git a/app/scripts/lib/transaction/util.test.ts b/app/scripts/lib/transaction/util.test.ts index 782a27e87cb1..f8b07cc7b17c 100644 --- a/app/scripts/lib/transaction/util.test.ts +++ b/app/scripts/lib/transaction/util.test.ts @@ -95,10 +95,8 @@ describe('Transaction Utils', () => { let dappRequest: AddDappTransactionRequest; let transactionController: jest.Mocked; let userOperationController: jest.Mocked; - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) const validateRequestWithPPOMMock = jest.mocked(validateRequestWithPPOM); const generateSecurityAlertIdMock = jest.mocked(generateSecurityAlertId); - ///: END:ONLY_INCLUDE_IF beforeEach(() => { jest.resetAllMocks(); @@ -106,10 +104,8 @@ describe('Transaction Utils', () => { request = cloneDeep(TRANSACTION_REQUEST_MOCK); transactionController = createTransactionControllerMock(); userOperationController = createUserOperationControllerMock(); - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) // eslint-disable-next-line @typescript-eslint/no-explicit-any request.ppomController = {} as any; - ///: END:ONLY_INCLUDE_IF transactionController.addTransaction.mockResolvedValue({ result: Promise.resolve('testHash'), diff --git a/app/scripts/lib/transaction/util.ts b/app/scripts/lib/transaction/util.ts index adf24fea3ed3..d62ea2af02f3 100644 --- a/app/scripts/lib/transaction/util.ts +++ b/app/scripts/lib/transaction/util.ts @@ -11,7 +11,6 @@ import { } from '@metamask/user-operation-controller'; import type { Hex } from '@metamask/utils'; import { addHexPrefix } from 'ethereumjs-util'; -///: BEGIN:ONLY_INCLUDE_IF(blockaid) import { PPOMController } from '@metamask/ppom-validator'; import { @@ -25,7 +24,6 @@ import { SECURITY_PROVIDER_EXCLUDED_TRANSACTION_TYPES, SECURITY_PROVIDER_SUPPORTED_CHAIN_IDS, } from '../../../../shared/constants/security-provider'; -///: END:ONLY_INCLUDE_IF export type AddTransactionOptions = NonNullable< Parameters[1] @@ -66,10 +64,7 @@ export async function addDappTransaction( ): Promise { const { dappRequest } = request; const { id: actionId, method, origin } = dappRequest; - - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) const { securityAlertResponse } = dappRequest; - ///: END:ONLY_INCLUDE_IF const transactionOptions: AddTransactionOptions = { actionId, @@ -77,9 +72,7 @@ export async function addDappTransaction( origin, // This is the default behaviour but specified here for clarity requireApproval: true, - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) securityAlertResponse, - ///: END:ONLY_INCLUDE_IF }; const { waitForHash } = await addTransactionOrUserOperation({ @@ -223,7 +216,6 @@ function getTransactionByHash( } function validateSecurity(request: AddTransactionRequest) { - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) const { chainId, ppomController, @@ -290,5 +282,4 @@ function validateSecurity(request: AddTransactionRequest) { } catch (error) { handlePPOMError(error, 'Error validating JSON RPC using PPOM: '); } - ///: END:ONLY_INCLUDE_IF } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 989c0a97c094..e78b16be71c4 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -102,10 +102,7 @@ import { CustodyController } from '@metamask-institutional/custody-controller'; import { TransactionUpdateController } from '@metamask-institutional/transaction-update'; ///: END:ONLY_INCLUDE_IF import { SignatureController } from '@metamask/signature-controller'; -///: BEGIN:ONLY_INCLUDE_IF(blockaid) import { PPOMController } from '@metamask/ppom-validator'; -///: END:ONLY_INCLUDE_IF - import { ApprovalType, ERC1155, @@ -241,11 +238,8 @@ import { keyringSnapPermissionsBuilder } from './lib/snap-keyring/keyring-snaps- import { SnapsNameProvider } from './lib/SnapsNameProvider'; import { AddressBookPetnamesBridge } from './lib/AddressBookPetnamesBridge'; import { AccountIdentitiesPetnamesBridge } from './lib/AccountIdentitiesPetnamesBridge'; - -///: BEGIN:ONLY_INCLUDE_IF(blockaid) import { createPPOMMiddleware } from './lib/ppom/ppom-middleware'; import * as PPOMModule from './lib/ppom/ppom'; -///: END:ONLY_INCLUDE_IF import { onMessageReceived, checkForMultipleVersionsRunning, @@ -298,9 +292,7 @@ import { unrestrictedMethods, } from './controllers/permissions'; import createRPCMethodTrackingMiddleware from './lib/createRPCMethodTrackingMiddleware'; -///: BEGIN:ONLY_INCLUDE_IF(blockaid) import { IndexedDBPPOMStorage } from './lib/ppom/indexed-db-backend'; -///: END:ONLY_INCLUDE_IF import { updateCurrentLocale } from './translate'; import { TrezorOffscreenBridge } from './lib/offscreen-bridge/trezor-offscreen-bridge'; import { LedgerOffscreenBridge } from './lib/offscreen-bridge/ledger-offscreen-bridge'; @@ -879,7 +871,6 @@ export default class MetamaskController extends EventEmitter { stalelistRefreshInterval: process.env.IN_TEST ? 30 * SECOND : undefined, }); - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) this.ppomController = new PPOMController({ messenger: this.controllerMessenger.getRestricted({ name: 'PPOMController', @@ -898,7 +889,6 @@ export default class MetamaskController extends EventEmitter { cdnBaseUrl: process.env.BLOCKAID_FILE_CDN, blockaidPublicKey: process.env.BLOCKAID_PUBLIC_KEY, }); - ///: END:ONLY_INCLUDE_IF const announcementMessenger = this.controllerMessenger.getRestricted({ name: 'AnnouncementController', @@ -2207,9 +2197,7 @@ export default class MetamaskController extends EventEmitter { SwapsController: this.swapsController.store, EnsController: this.ensController, ApprovalController: this.approvalController, - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) PPOMController: this.ppomController, - ///: END:ONLY_INCLUDE_IF }; this.store.updateStructure({ @@ -2252,9 +2240,7 @@ export default class MetamaskController extends EventEmitter { this.institutionalFeaturesController.store, MmiConfigurationController: this.mmiConfigurationController.store, ///: END:ONLY_INCLUDE_IF - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) PPOMController: this.ppomController, - ///: END:ONLY_INCLUDE_IF NameController: this.nameController, UserOperationController: this.userOperationController, // Notification Controllers @@ -3091,13 +3077,10 @@ export default class MetamaskController extends EventEmitter { this.preferencesController, ), getProviderConfig: () => this.networkController.state.providerConfig, - - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) setSecurityAlertsEnabled: preferencesController.setSecurityAlertsEnabled.bind( preferencesController, ), - ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) setAddSnapAccountEnabled: preferencesController.setAddSnapAccountEnabled.bind( @@ -4679,13 +4662,11 @@ export default class MetamaskController extends EventEmitter { transactionOptions, transactionParams, userOperationController: this.userOperationController, - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) chainId: this.networkController.state.providerConfig.chainId, ppomController: this.ppomController, securityAlertsEnabled: this.preferencesController.store.getState()?.securityAlertsEnabled, updateSecurityAlertResponse: this.updateSecurityAlertResponse.bind(this), - ///: END:ONLY_INCLUDE_IF }; } @@ -5168,7 +5149,6 @@ export default class MetamaskController extends EventEmitter { engine.push(createTxVerificationMiddleware(this.networkController)); } - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) engine.push( createPPOMMiddleware( this.ppomController, @@ -5178,7 +5158,6 @@ export default class MetamaskController extends EventEmitter { this.updateSecurityAlertResponse.bind(this), ), ); - ///: END:ONLY_INCLUDE_IF const isConfirmationRedesignEnabled = () => { return this.preferencesController.store.getState().preferences @@ -5204,9 +5183,7 @@ export default class MetamaskController extends EventEmitter { 'AccountsController:getSelectedAccount', ], }), - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) appStateController: this.appStateController, - ///: END:ONLY_INCLUDE_IF }), ); diff --git a/builds.yml b/builds.yml index af64cfb8d0ef..35fa7341aa44 100644 --- a/builds.yml +++ b/builds.yml @@ -17,7 +17,6 @@ buildTypes: features: - build-main - keyring-snaps - - blockaid # Additional env variables that are specific to this build env: - INFURA_PROD_PROJECT_ID @@ -26,7 +25,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/6.4.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.5.0/index.html - ACCOUNT_SNAPS_DIRECTORY_URL: https://snaps.metamask.io/account-management - BTC_BETA_SUPPORT: false # Main build uses the default browser manifest @@ -39,7 +38,6 @@ buildTypes: features: - build-beta - keyring-snaps - - blockaid env: - INFURA_BETA_PROJECT_ID - SEGMENT_BETA_WRITE_KEY @@ -47,7 +45,7 @@ buildTypes: - SEGMENT_WRITE_KEY_REF: SEGMENT_BETA_WRITE_KEY - ALLOW_LOCAL_SNAPS: false - REQUIRE_SNAPS_ALLOWLIST: true - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.4.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.5.0/index.html - ACCOUNT_SNAPS_DIRECTORY_URL: https://snaps.metamask.io/account-management # Modifies how the version is displayed. # eg. instead of 10.25.0 -> 10.25.0-beta.2 @@ -62,13 +60,12 @@ buildTypes: features: - build-flask - keyring-snaps - - blockaid env: - INFURA_FLASK_PROJECT_ID - SEGMENT_FLASK_WRITE_KEY - ALLOW_LOCAL_SNAPS: true - REQUIRE_SNAPS_ALLOWLIST: false - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.4.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.5.0/index.html - SUPPORT_LINK: https://support.metamask.io/ - SUPPORT_REQUEST_LINK: https://support.metamask.io/ - INFURA_ENV_KEY_REF: INFURA_FLASK_PROJECT_ID @@ -83,7 +80,6 @@ buildTypes: mmi: features: - build-mmi - - blockaid env: - INFURA_MMI_PROJECT_ID - SEGMENT_MMI_WRITE_KEY @@ -92,7 +88,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/6.4.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.5.0/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 @@ -107,10 +103,6 @@ buildTypes: # Each feature can have code fences that add new code # as well declaring, defining and overriding env variables features: - blockaid: - env: - - BLOCKAID_FILE_CDN: static.cx.metamask.io/api/v1/confirmations/ppom - - BLOCKAID_PUBLIC_KEY: 066ad3e8af5583385e312c156d238055215d5f25247c1e91055afa756cb98a88 ### # Build Type code extensions. Things like different support links, warning pages, banners ### @@ -156,6 +148,10 @@ env: - SUPPORT_LINK: https://support.metamask.io - SUPPORT_REQUEST_LINK: https://support.metamask.io - SKIP_BACKGROUND_INITIALIZATION: false + # CDN for blockaid files + - BLOCKAID_FILE_CDN: static.cx.metamask.io/api/v1/confirmations/ppom + # Blockaid public key for verifying signatures of data files downloaded from CDN + - BLOCKAID_PUBLIC_KEY: 066ad3e8af5583385e312c156d238055215d5f25247c1e91055afa756cb98a88 - ENABLE_MV3: true # These are exclusively used for MV3 @@ -256,10 +252,6 @@ env: - NODE_DEBUG: '' # Used by react-devtools-core - EDITOR_URL: '' - # CDN for blockaid files - - BLOCKAID_FILE_CDN - # Blockaid public key for verifying signatures of data files downloaded from CDN - - BLOCKAID_PUBLIC_KEY # Determines if feature flagged Multichain Transactions should be used - TRANSACTION_MULTICHAIN: '' # Determines if feature flagged Chain permissions diff --git a/development/build/static.js b/development/build/static.js index ecff5d2501c0..cf0cd1078423 100644 --- a/development/build/static.js +++ b/development/build/static.js @@ -29,7 +29,6 @@ module.exports = function createStaticAssetTasks({ const [copyTargetsProd, copyTargetsDev] = getCopyTargets( shouldIncludeLockdown, shouldIncludeSnow, - activeFeatures, ); copyTargetsProds[browser] = copyTargetsProd; copyTargetsDevs[browser] = copyTargetsDev; @@ -108,11 +107,7 @@ module.exports = function createStaticAssetTasks({ } }; -function getCopyTargets( - shouldIncludeLockdown, - shouldIncludeSnow, - activeFeatures, -) { +function getCopyTargets(shouldIncludeLockdown, shouldIncludeSnow) { const allCopyTargets = [ { src: `./app/_locales/`, @@ -202,6 +197,15 @@ function getCopyTargets( pattern: `*.html`, dest: '', }, + { + src: getPathInsideNodeModules('@blockaid/ppom_release', '/'), + pattern: '*.wasm', + dest: + process.env.ENABLE_MV3 === 'true' || + process.env.ENABLE_MV3 === undefined + ? 'scripts/' + : '', + }, ...(process.env.ENABLE_MV3 === 'true' || process.env.ENABLE_MV3 === undefined ? [ @@ -225,18 +229,6 @@ function getCopyTargets( : []), ]; - if (activeFeatures.includes('blockaid')) { - allCopyTargets.push({ - src: getPathInsideNodeModules('@blockaid/ppom_release', '/'), - pattern: '*.wasm', - dest: - process.env.ENABLE_MV3 === 'true' || - process.env.ENABLE_MV3 === undefined - ? 'scripts/' - : '', - }); - } - const copyTargetsDev = [ ...allCopyTargets, { diff --git a/package.json b/package.json index e4f6a97f0f4e..f1c90da5fc8a 100644 --- a/package.json +++ b/package.json @@ -221,7 +221,7 @@ "semver@7.3.8": "^7.5.4", "@trezor/schema-utils@npm:1.0.2": "patch:@trezor/schema-utils@npm%3A1.0.2#~/.yarn/patches/@trezor-schema-utils-npm-1.0.2-7dd48689b2.patch", "lavamoat-core@npm:^15.1.1": "patch:lavamoat-core@npm%3A15.1.1#~/.yarn/patches/lavamoat-core-npm-15.1.1-51fbe39988.patch", - "@metamask/snaps-sdk": "^5.0.0", + "@metamask/snaps-sdk": "^6.0.0", "@metamask/transaction-controller": "^32.0.0", "@babel/runtime@npm:^7.7.6": "patch:@babel/runtime@npm%3A7.24.0#~/.yarn/patches/@babel-runtime-npm-7.24.0-7eb1dd11a2.patch", "@babel/runtime@npm:^7.9.2": "patch:@babel/runtime@npm%3A7.24.0#~/.yarn/patches/@babel-runtime-npm-7.24.0-7eb1dd11a2.patch", @@ -335,11 +335,11 @@ "@metamask/selected-network-controller": "^15.0.2", "@metamask/signature-controller": "^16.0.0", "@metamask/smart-transactions-controller": "^10.1.2", - "@metamask/snaps-controllers": "^9.0.0", - "@metamask/snaps-execution-environments": "^6.4.0", - "@metamask/snaps-rpc-methods": "^9.1.3", - "@metamask/snaps-sdk": "^5.0.0", - "@metamask/snaps-utils": "^7.6.0", + "@metamask/snaps-controllers": "^9.2.0", + "@metamask/snaps-execution-environments": "^6.5.0", + "@metamask/snaps-rpc-methods": "^9.1.4", + "@metamask/snaps-sdk": "^6.0.0", + "@metamask/snaps-utils": "^7.7.0", "@metamask/transaction-controller": "^32.0.0", "@metamask/user-operation-controller": "^10.0.0", "@metamask/utils": "^8.2.1", diff --git a/test/e2e/snaps/enums.js b/test/e2e/snaps/enums.js index daeaf9db924f..98bbdfea099a 100644 --- a/test/e2e/snaps/enums.js +++ b/test/e2e/snaps/enums.js @@ -1,3 +1,3 @@ module.exports = { - TEST_SNAPS_WEBSITE_URL: 'https://metamask.github.io/snaps/test-snaps/2.9.0/', + TEST_SNAPS_WEBSITE_URL: 'https://metamask.github.io/snaps/test-snaps/2.11.0/', }; diff --git a/test/e2e/tests/onboarding/onboarding.spec.js b/test/e2e/tests/onboarding/onboarding.spec.js index de0939ac2282..c9cb594f403d 100644 --- a/test/e2e/tests/onboarding/onboarding.spec.js +++ b/test/e2e/tests/onboarding/onboarding.spec.js @@ -372,7 +372,7 @@ describe('MetaMask onboarding @no-mmi', function () { json: { jsonrpc: '2.0', id: '1111111111111111', - result: '0x1', + result: '0x0', }, }; }), @@ -508,7 +508,7 @@ describe('MetaMask onboarding @no-mmi', function () { json: { jsonrpc: '2.0', id: '1111111111111111', - result: '0x1', + result: '0x0', }, }; }), diff --git a/ui/components/app/app-components.scss b/ui/components/app/app-components.scss index 34b0bce89369..63c916a5b256 100644 --- a/ui/components/app/app-components.scss +++ b/ui/components/app/app-components.scss @@ -23,6 +23,7 @@ @import 'snaps/snap-ui-renderer/index'; @import 'snaps/snap-ui-markdown/index'; @import 'snaps/snap-ui-button/index'; +@import 'snaps/snap-ui-file-input/index'; @import 'snaps/snap-delineator/index'; @import 'snaps/snap-list-item/index'; @import 'snaps/copyable/index'; diff --git a/ui/components/app/metamask-template-renderer/safe-component-list.js b/ui/components/app/metamask-template-renderer/safe-component-list.js index 2c7905c9d2a2..5c7f9218283a 100644 --- a/ui/components/app/metamask-template-renderer/safe-component-list.js +++ b/ui/components/app/metamask-template-renderer/safe-component-list.js @@ -27,10 +27,13 @@ import { SnapUIMarkdown } from '../snaps/snap-ui-markdown'; import { SnapUILink } from '../snaps/snap-ui-link'; import { SmartTransactionStatusPage } from '../../../pages/smart-transactions/smart-transaction-status-page'; import { SnapUIImage } from '../snaps/snap-ui-image'; +import { SnapUIFileInput } from '../snaps/snap-ui-file-input'; import { SnapUIInput } from '../snaps/snap-ui-input'; import { SnapUIForm } from '../snaps/snap-ui-form'; import { SnapUIButton } from '../snaps/snap-ui-button'; import { SnapUIDropdown } from '../snaps/snap-ui-dropdown'; +import { SnapUICheckbox } from '../snaps/snap-ui-checkbox'; +import { SnapUITooltip } from '../snaps/snap-ui-tooltip'; ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) import { SnapAccountSuccessMessage } from '../../../pages/confirmations/components/snap-account-success-message'; import { SnapAccountErrorMessage } from '../../../pages/confirmations/components/snap-account-error-message'; @@ -78,10 +81,13 @@ export const safeComponentList = { ConfirmInfoRow, ConfirmInfoRowAddress, ConfirmInfoRowValueDouble, + SnapUIFileInput, SnapUIInput, SnapUIButton, SnapUIForm, SnapUIDropdown, + SnapUICheckbox, + SnapUITooltip, ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) CreateSnapAccount, RemoveSnapAccount, diff --git a/ui/components/app/snaps/snap-ui-button/snap-ui-button.tsx b/ui/components/app/snaps/snap-ui-button/snap-ui-button.tsx index 5c6735d6d16a..998a7ca2d782 100644 --- a/ui/components/app/snaps/snap-ui-button/snap-ui-button.tsx +++ b/ui/components/app/snaps/snap-ui-button/snap-ui-button.tsx @@ -36,7 +36,10 @@ export const SnapUIButton: FunctionComponent< event.preventDefault(); } - handleEvent({ event: UserInputEventType.ButtonClickEvent, name }); + handleEvent({ + event: UserInputEventType.ButtonClickEvent, + name, + }); }; const overriddenVariant = disabled ? 'disabled' : variant; diff --git a/ui/components/app/snaps/snap-ui-checkbox/index.ts b/ui/components/app/snaps/snap-ui-checkbox/index.ts new file mode 100644 index 000000000000..e75fe2634f38 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-checkbox/index.ts @@ -0,0 +1 @@ +export * from './snap-ui-checkbox'; diff --git a/ui/components/app/snaps/snap-ui-checkbox/snap-ui-checkbox.tsx b/ui/components/app/snaps/snap-ui-checkbox/snap-ui-checkbox.tsx new file mode 100644 index 000000000000..39df1c0ff8f7 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-checkbox/snap-ui-checkbox.tsx @@ -0,0 +1,81 @@ +import React, { FunctionComponent, useEffect, useState } from 'react'; +import { useSnapInterfaceContext } from '../../../../contexts/snaps'; +import { + Display, + FlexDirection, +} from '../../../../helpers/constants/design-system'; +import { + Box, + Label, + HelpText, + HelpTextSeverity, + Checkbox, +} from '../../../component-library'; +import ToggleButton from '../../../ui/toggle-button'; + +export type SnapUICheckboxProps = { + name: string; + fieldLabel?: string; + variant?: 'default' | 'toggle'; + label?: string; + error?: string; + form?: string; +}; + +export const SnapUICheckbox: FunctionComponent = ({ + name, + variant, + fieldLabel, + label, + error, + form, + ...props +}) => { + const { handleInputChange, getValue } = useSnapInterfaceContext(); + + const initialValue = getValue(name, form); + + const [value, setValue] = useState(initialValue ?? false); + + useEffect(() => { + if (initialValue !== undefined && initialValue !== null) { + setValue(initialValue); + } + }, [initialValue]); + + const handleChange = () => { + setValue(!value); + handleInputChange(name, !value, form); + }; + + return ( + + {fieldLabel && } + {variant === 'toggle' ? ( + + ) : ( + + )} + {error && ( + + {error} + + )} + + ); +}; diff --git a/ui/components/app/snaps/snap-ui-dropdown/snap-ui-dropdown.tsx b/ui/components/app/snaps/snap-ui-dropdown/snap-ui-dropdown.tsx index f729436c776b..2e70f2809399 100644 --- a/ui/components/app/snaps/snap-ui-dropdown/snap-ui-dropdown.tsx +++ b/ui/components/app/snaps/snap-ui-dropdown/snap-ui-dropdown.tsx @@ -29,7 +29,7 @@ export const SnapUIDropdown: FunctionComponent = ({ }) => { const { handleInputChange, getValue } = useSnapInterfaceContext(); - const initialValue = getValue(name, form); + const initialValue = getValue(name, form); const [value, setValue] = useState(initialValue ?? ''); diff --git a/ui/components/app/snaps/snap-ui-file-input/index.scss b/ui/components/app/snaps/snap-ui-file-input/index.scss new file mode 100644 index 000000000000..787eb6150728 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-file-input/index.scss @@ -0,0 +1,21 @@ +.snap-ui-renderer { + &__file-input { + &__drop-zone { + background-color: var(--color-background-alternative); + + .mm-icon, + .mm-text { + color: var(--color-icon-alternative); + } + + &:hover .mm-icon, + &:hover .mm-text { + color: var(--color-info-default); + } + + &:hover { + background-color: var(--color-background-alternative-hover); + } + } + } +} diff --git a/ui/components/app/snaps/snap-ui-file-input/index.ts b/ui/components/app/snaps/snap-ui-file-input/index.ts new file mode 100644 index 000000000000..fa2b7b5ad2a1 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-file-input/index.ts @@ -0,0 +1 @@ +export * from './snap-ui-file-input'; diff --git a/ui/components/app/snaps/snap-ui-file-input/snap-ui-file-input.tsx b/ui/components/app/snaps/snap-ui-file-input/snap-ui-file-input.tsx new file mode 100644 index 000000000000..9508dae3e0d3 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-file-input/snap-ui-file-input.tsx @@ -0,0 +1,212 @@ +import React, { + ChangeEvent, + // eslint-disable-next-line @typescript-eslint/no-shadow + DragEvent, + FunctionComponent, + useRef, + useState, +} from 'react'; +import classnames from 'classnames'; +import { useSnapInterfaceContext } from '../../../../contexts/snaps'; +import { + Box, + ButtonIcon, + ButtonIconSize, + HelpText, + HelpTextSeverity, + Icon, + IconName, + IconSize, + Label, + Text, +} from '../../../component-library'; +import { + AlignItems, + BackgroundColor, + BorderColor, + BorderRadius, + BorderStyle, + Display, + FlexDirection, + IconColor, + JustifyContent, + TextAlign, +} from '../../../../helpers/constants/design-system'; +import { useI18nContext } from '../../../../hooks/useI18nContext'; + +export type SnapUIFileInputProps = { + name: string; + label?: string; + form?: string; + accept?: string[]; + compact?: boolean; + error?: boolean; + helpText?: string; +}; + +/** + * A file input component, which is used to create a file input field for Snaps + * user interfaces. + * + * @param props - The props of the component. + * @param props.name - The name of the file input. This is used to identify the + * file input field in the form data. + * @param props.label - The label of the file input, which is displayed above + * the file input field. + * @param props.form - The name of the form that the file input belongs to. This + * is used to group the file input field with other form fields. + * @param props.accept - The types of files that the file input can accept. This + * is used to filter the files that the user can select when the input field is + * clicked. + * @param props.compact - Whether the file input should be displayed in a + * compact mode. In compact mode, the file input is displayed as a button with + * an icon. + * @param props.error - Whether the file input has an error. If the file input + * has an error, the help text is displayed in red. + * @param props.helpText - The help text of the file input, which is displayed + * below the file input field. + * @returns A file input element. + */ +export const SnapUIFileInput: FunctionComponent = ({ + name, + label, + form, + accept, + compact, + error, + helpText, +}) => { + const t = useI18nContext(); + const { handleFileChange } = useSnapInterfaceContext(); + const ref = useRef(null); + const [active, setActive] = useState(false); + + const handleClick = () => { + ref.current?.click(); + }; + + const handleChange = (event: ChangeEvent) => { + const file = event.target.files?.[0] ?? null; + handleFileChange(name, file, form); + }; + + const handleDragOver = (event: DragEvent) => { + event.preventDefault(); + setActive(true); + }; + + const handleDragLeave = (event: DragEvent) => { + event.preventDefault(); + setActive(false); + }; + + const handleDrop = (event: DragEvent) => { + event.preventDefault(); + setActive(false); + + const file = event.dataTransfer?.files?.[0] ?? null; + handleFileChange(name, file, form); + }; + + const header = ( + <> + {label && ( + + )} + + + ); + + const footer = ( + <> + {helpText && ( + + {helpText} + + )} + + ); + + if (compact) { + return ( + + {header} + + {footer} + + ); + } + + return ( + + {header} + + + + {t('uploadDropFile')} + + + {footer} + + ); +}; diff --git a/ui/components/app/snaps/snap-ui-form/snap-ui-form.tsx b/ui/components/app/snaps/snap-ui-form/snap-ui-form.tsx index 4dfe245b854f..6f265bc71710 100644 --- a/ui/components/app/snaps/snap-ui-form/snap-ui-form.tsx +++ b/ui/components/app/snaps/snap-ui-form/snap-ui-form.tsx @@ -19,7 +19,10 @@ export const SnapUIForm: FunctionComponent = ({ const handleSubmit = (event: FormEvent) => { event.preventDefault(); - handleEvent({ event: UserInputEventType.FormSubmitEvent, name }); + handleEvent({ + event: UserInputEventType.FormSubmitEvent, + name, + }); }; return ( diff --git a/ui/components/app/snaps/snap-ui-input/snap-ui-input.tsx b/ui/components/app/snaps/snap-ui-input/snap-ui-input.tsx index 2374e00f3927..43c612517139 100644 --- a/ui/components/app/snaps/snap-ui-input/snap-ui-input.tsx +++ b/ui/components/app/snaps/snap-ui-input/snap-ui-input.tsx @@ -17,7 +17,7 @@ export const SnapUIInput: FunctionComponent< > = ({ name, form, ...props }) => { const { handleInputChange, getValue } = useSnapInterfaceContext(); - const initialValue = getValue(name, form); + const initialValue = getValue(name, form); const [value, setValue] = useState(initialValue ?? ''); diff --git a/ui/components/app/snaps/snap-ui-renderer/components/checkbox.ts b/ui/components/app/snaps/snap-ui-renderer/components/checkbox.ts new file mode 100644 index 000000000000..862ca5203950 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-renderer/components/checkbox.ts @@ -0,0 +1,16 @@ +import { CheckboxElement } from '@metamask/snaps-sdk/jsx'; + +import { UIComponentFactory } from './types'; + +export const checkbox: UIComponentFactory = ({ + element, + form, +}) => ({ + element: 'SnapUICheckbox', + props: { + name: element.props.name, + label: element.props.label, + variant: element.props.variant, + form, + }, +}); diff --git a/ui/components/app/snaps/snap-ui-renderer/components/field.ts b/ui/components/app/snaps/snap-ui-renderer/components/field.ts index 193f618346ca..eb5b0b0e2256 100644 --- a/ui/components/app/snaps/snap-ui-renderer/components/field.ts +++ b/ui/components/app/snaps/snap-ui-renderer/components/field.ts @@ -4,10 +4,12 @@ import { ButtonElement, JSXElement, DropdownElement, + CheckboxElement, } from '@metamask/snaps-sdk/jsx'; import { getJsxChildren } from '@metamask/snaps-utils'; import { button as buttonFn } from './button'; import { dropdown as dropdownFn } from './dropdown'; +import { checkbox as checkboxFn } from './checkbox'; import { UIComponentFactory, UIComponentParams } from './types'; export const field: UIComponentFactory = ({ element, form }) => { @@ -16,12 +18,28 @@ export const field: UIComponentFactory = ({ element, form }) => { const child = children[0] as JSXElement; switch (child.type) { + case 'FileInput': { + return { + element: 'SnapUIFileInput', + props: { + name: child.props.name, + accept: child.props.accept, + compact: child.props.compact, + label: element.props.label, + form, + error: element.props.error !== undefined, + helpText: element.props.error, + }, + }; + } + case 'Input': { const input = child as InputElement; const button = children[1] as ButtonElement; const buttonMapped = button && buttonFn({ element: button } as UIComponentParams); + return { element: 'SnapUIInput', props: { @@ -66,6 +84,22 @@ export const field: UIComponentFactory = ({ element, form }) => { }; } + case 'Checkbox': { + const checkbox = child as CheckboxElement; + const checkboxMapped = checkboxFn({ + element: checkbox, + } as UIComponentParams); + return { + element: 'SnapUICheckbox', + props: { + ...checkboxMapped.props, + fieldLabel: element.props.label, + form, + error: element.props.error, + }, + }; + } + default: throw new Error(`Invalid Field child: ${child.type}`); } diff --git a/ui/components/app/snaps/snap-ui-renderer/components/file-input.ts b/ui/components/app/snaps/snap-ui-renderer/components/file-input.ts new file mode 100644 index 000000000000..edeba6d89551 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-renderer/components/file-input.ts @@ -0,0 +1,19 @@ +import { FileInputElement } from '@metamask/snaps-sdk/jsx'; + +import { UIComponentFactory } from './types'; + +export const fileInput: UIComponentFactory = ({ + element, + form, +}) => ({ + element: 'SnapUIInput', + props: { + element: 'SnapUIFileInput', + props: { + name: element.props.name, + accept: element.props.accept, + compact: element.props.compact, + form, + }, + }, +}); diff --git a/ui/components/app/snaps/snap-ui-renderer/components/index.ts b/ui/components/app/snaps/snap-ui-renderer/components/index.ts index 9aa6796de1a3..eed08c75fb91 100644 --- a/ui/components/app/snaps/snap-ui-renderer/components/index.ts +++ b/ui/components/app/snaps/snap-ui-renderer/components/index.ts @@ -8,6 +8,7 @@ import { row } from './row'; import { address } from './address'; import { copyable } from './copyable'; import { button } from './button'; +import { fileInput } from './file-input'; import { form } from './form'; import { input } from './input'; import { bold } from './bold'; @@ -16,6 +17,8 @@ import { link } from './link'; import { field } from './field'; import { dropdown } from './dropdown'; import { value } from './value'; +import { checkbox } from './checkbox'; +import { tooltip } from './tooltip'; export const COMPONENT_MAPPING = { Box: box, @@ -28,6 +31,7 @@ export const COMPONENT_MAPPING = { Row: row, Address: address, Button: button, + FileInput: fileInput, Form: form, Input: input, Bold: bold, @@ -36,4 +40,6 @@ export const COMPONENT_MAPPING = { Field: field, Dropdown: dropdown, Value: value, + Checkbox: checkbox, + Tooltip: tooltip, }; diff --git a/ui/components/app/snaps/snap-ui-renderer/components/text.ts b/ui/components/app/snaps/snap-ui-renderer/components/text.ts index 7ed7d9be1236..9502df1aabaa 100644 --- a/ui/components/app/snaps/snap-ui-renderer/components/text.ts +++ b/ui/components/app/snaps/snap-ui-renderer/components/text.ts @@ -23,5 +23,6 @@ export const text: UIComponentFactory = ({ overflowWrap: OverflowWrap.Anywhere, color: TextColor.inherit, className: 'snap-ui-renderer__text', + textAlign: element.props.alignment, }, }); diff --git a/ui/components/app/snaps/snap-ui-renderer/components/tooltip.ts b/ui/components/app/snaps/snap-ui-renderer/components/tooltip.ts new file mode 100644 index 000000000000..facf3dcd7168 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-renderer/components/tooltip.ts @@ -0,0 +1,23 @@ +import { JSXElement, Text, TooltipElement } from '@metamask/snaps-sdk/jsx'; +import { getJsxChildren } from '@metamask/snaps-utils'; +import { mapToTemplate } from '../utils'; +import { UIComponentFactory } from './types'; + +export const tooltip: UIComponentFactory = ({ + element, + ...params +}) => ({ + element: 'SnapUITooltip', + children: getJsxChildren(element).map((children) => + mapToTemplate({ element: children as JSXElement, ...params }), + ), + propComponents: { + content: mapToTemplate({ + element: + typeof element.props.content === 'string' + ? Text({ children: element.props.content }) + : element.props.content, + ...params, + }), + }, +}); diff --git a/ui/components/app/snaps/snap-ui-renderer/components/types.ts b/ui/components/app/snaps/snap-ui-renderer/components/types.ts index ab1048d0b3a7..4e4824f686e1 100644 --- a/ui/components/app/snaps/snap-ui-renderer/components/types.ts +++ b/ui/components/app/snaps/snap-ui-renderer/components/types.ts @@ -1,4 +1,4 @@ -import { JSXElement, MaybeArray } from '@metamask/snaps-sdk/jsx'; +import { JSXElement, SnapsChildren } from '@metamask/snaps-sdk/jsx'; export type UIComponentParams = { map: Record; @@ -9,7 +9,7 @@ export type UIComponentParams = { export type UIComponent = { element: string; props?: Record; - children?: MaybeArray; + children?: SnapsChildren; key?: string; }; diff --git a/ui/components/app/snaps/snap-ui-tooltip/index.ts b/ui/components/app/snaps/snap-ui-tooltip/index.ts new file mode 100644 index 000000000000..8a97b4d74e26 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-tooltip/index.ts @@ -0,0 +1 @@ +export * from './snap-ui-tooltip'; diff --git a/ui/components/app/snaps/snap-ui-tooltip/snap-ui-tooltip.tsx b/ui/components/app/snaps/snap-ui-tooltip/snap-ui-tooltip.tsx new file mode 100644 index 000000000000..953ee50c87b5 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-tooltip/snap-ui-tooltip.tsx @@ -0,0 +1,22 @@ +import React, { FunctionComponent, ReactNode } from 'react'; +import Tooltip from '../../../ui/tooltip'; + +export type SnapUITooltipProps = { + content: ReactNode; +}; + +export const SnapUITooltip: FunctionComponent = ({ + content, + children, +}) => { + return ( + + {children} + + ); +}; diff --git a/ui/components/app/whats-new-popup/whats-new-popup.js b/ui/components/app/whats-new-popup/whats-new-popup.js index a56ad75fe7fd..9a316af62889 100644 --- a/ui/components/app/whats-new-popup/whats-new-popup.js +++ b/ui/components/app/whats-new-popup/whats-new-popup.js @@ -17,9 +17,7 @@ import { MetaMetricsContext } from '../../../contexts/metametrics'; import { getCurrentLocale } from '../../../ducks/locale/locale'; import { TextVariant } from '../../../helpers/constants/design-system'; import { useEqualityCheck } from '../../../hooks/useEqualityCheck'; -///: BEGIN:ONLY_INCLUDE_IF(blockaid) import { useTheme } from '../../../hooks/useTheme'; -///: END:ONLY_INCLUDE_IF import { getSortedAnnouncementsToShow } from '../../../selectors'; import { updateViewedNotifications } from '../../../store/actions'; import { ButtonPrimary, Text } from '../../component-library'; @@ -165,10 +163,7 @@ export default function WhatsNewPopup({ onClose }) { const notifications = useSelector(getSortedAnnouncementsToShow); const locale = useSelector(getCurrentLocale); - - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) const theme = useTheme(); - ///: END:ONLY_INCLUDE_IF const [seenNotifications, setSeenNotifications] = useState({}); const [shouldShowScrollButton, setShouldShowScrollButton] = useState(true); @@ -272,14 +267,9 @@ export default function WhatsNewPopup({ onClose }) { >
{notifications.map(({ id }, index) => { - const notification = getTranslatedUINotifications( - t, - locale, - - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - theme, - ///: END:ONLY_INCLUDE_IF - )[id]; + const notification = getTranslatedUINotifications(t, locale, theme)[ + id + ]; const isLast = index === notifications.length - 1; // Choose the appropriate rendering function based on the id const renderNotification = diff --git a/ui/contexts/snaps/snap-interface.tsx b/ui/contexts/snaps/snap-interface.tsx index 1943e35a2745..45af0572a03e 100644 --- a/ui/contexts/snaps/snap-interface.tsx +++ b/ui/contexts/snaps/snap-interface.tsx @@ -1,8 +1,10 @@ import { + File as FileObject, FormState, InterfaceState, UserInputEventType, } from '@metamask/snaps-sdk'; +import { encodeBase64 } from '@metamask/snaps-utils'; import { Json } from '@metamask/utils'; import { debounce, throttle } from 'lodash'; import React, { @@ -20,25 +22,32 @@ import { } from '../../store/actions'; import { mergeValue } from './utils'; -export type HandleEvent = (args: { +export type HandleEvent = (args: { event: UserInputEventType; name?: string; - value?: string; + value?: Type; flush?: boolean; }) => void; -export type HandleInputChange = ( +export type HandleInputChange = ( name: string, - value: string | null, + value: Type | null, form?: string, ) => void; -export type GetValue = (name: string, form?: string) => string | undefined; +export type GetValue = (name: string, form?: string) => Type | undefined; + +export type HandleFileChange = ( + name: string, + file: File | null, + form?: string, +) => void; export type SnapInterfaceContextType = { handleEvent: HandleEvent; getValue: GetValue; handleInputChange: HandleInputChange; + handleFileChange: HandleFileChange; }; export const SnapInterfaceContext = @@ -75,12 +84,13 @@ export const SnapInterfaceContextProvider: FunctionComponent< > = ({ children, interfaceId, snapId, initialState, context }) => { const dispatch = useDispatch(); - // We keep an internal copy of the state to speed-up the state update in the UI. - // It's kept in a ref to avoid useless re-rendering of the entire tree of components. + // We keep an internal copy of the state to speed up the state update in the + // UI. It's kept in a ref to avoid useless re-rendering of the entire tree of + // components. const internalState = useRef(initialState ?? {}); - // Since the internal state is kept in a reference, it won't update when the interface is updated. - // We have to manually update it + // Since the internal state is kept in a reference, it won't update when the + // interface is updated. We have to manually update it. useEffect(() => { internalState.current = initialState; }, [initialState]); @@ -88,8 +98,8 @@ export const SnapInterfaceContextProvider: FunctionComponent< const rawSnapRequestFunction = ( event: UserInputEventType, name?: string, - value?: string, - ) => + value?: unknown, + ) => { handleSnapRequest({ snapId, origin: '', @@ -109,13 +119,14 @@ export const SnapInterfaceContextProvider: FunctionComponent< }, }, }).then(() => forceUpdateMetamaskState(dispatch)); + }; - // The submittion of user input events is debounced or throttled to avoid crashing the snap if - // there's too much events sent at the same time + // The submission of user input events is debounced or throttled to avoid + // crashing the snap if there's too many events sent at the same time. const snapRequestDebounced = debounce(rawSnapRequestFunction, 200); const snapRequestThrottled = throttle(rawSnapRequestFunction, 200); - // The update of the state is debounced to avoid crashes due to too much + // The update of the state is debounced to avoid crashes due to too many // updates in a short amount of time. const updateStateDebounced = debounce( (state) => dispatch(updateInterfaceState(interfaceId, state)), @@ -129,7 +140,8 @@ export const SnapInterfaceContextProvider: FunctionComponent< * @param options.event - The event type. * @param options.name - The name of the component emitting the event. * @param options.value - The value of the component emitting the event. - * @param options.flush - Optional flag to indicate whether the debounce should be flushed. + * @param options.flush - Optional flag to indicate whether the debounce + * should be flushed. */ const handleEvent: HandleEvent = ({ event, @@ -139,9 +151,11 @@ export const SnapInterfaceContextProvider: FunctionComponent< }) => { // We always flush the debounced request for updating the state. updateStateDebounced.flush(); + const fn = THROTTLED_EVENTS.includes(event) ? snapRequestThrottled : snapRequestDebounced; + fn(event, name, value); // Certain events have their own debounce or throttling logic @@ -175,7 +189,73 @@ export const SnapInterfaceContextProvider: FunctionComponent< internalState.current = state; updateStateDebounced(state); - handleInputChangeDebounced(name, value ?? ''); + handleInputChangeDebounced(name, value); + }; + + const uploadFile = (name: string, file: File | null) => { + handleSnapRequest({ + snapId, + origin: '', + handler: 'onUserInput', + request: { + jsonrpc: '2.0', + method: ' ', + params: { + event: { + type: UserInputEventType.FileUploadEvent, + name, + file, + }, + id: interfaceId, + context, + }, + }, + }).then(() => forceUpdateMetamaskState(dispatch)); + }; + + /** + * Handle the file change of an input. + * + * @param name - The name of the input. + * @param file - The file to upload. + * @param form - The name of the form containing the input. + */ + const handleFileChange: HandleFileChange = (name, file, form) => { + if (file) { + file + .arrayBuffer() + .then((arrayBuffer) => new Uint8Array(arrayBuffer)) + .then((uint8Array) => encodeBase64(uint8Array)) + .then((base64) => { + const fileObject: FileObject = { + name: file.name, + size: file.size, + contentType: file.type, + contents: base64, + }; + + const state = mergeValue( + internalState.current, + name, + fileObject, + form, + ); + + internalState.current = state; + updateStateDebounced(state); + updateStateDebounced.flush(); + uploadFile(name, fileObject); + }); + + return; + } + + const state = mergeValue(internalState.current, name, null, form); + + internalState.current = state; + updateStateDebounced(state); + updateStateDebounced.flush(); + uploadFile(name, null); }; /** @@ -200,7 +280,7 @@ export const SnapInterfaceContextProvider: FunctionComponent< return ( {children} diff --git a/ui/contexts/snaps/utils.ts b/ui/contexts/snaps/utils.ts index 9d13c13eec1d..2dbf75be662a 100644 --- a/ui/contexts/snaps/utils.ts +++ b/ui/contexts/snaps/utils.ts @@ -10,10 +10,10 @@ import { FormState, InterfaceState } from '@metamask/snaps-sdk'; * Optional if the input is not contained in a form. * @returns The interface state with the new value merged in. */ -export const mergeValue = ( +export const mergeValue = ( state: InterfaceState, name: string, - value: string | null, + value: Type | null, form?: string, ): InterfaceState => { if (form) { diff --git a/ui/helpers/utils/metrics.js b/ui/helpers/utils/metrics.js index 36d80b689c5e..a890d8f06f73 100644 --- a/ui/helpers/utils/metrics.js +++ b/ui/helpers/utils/metrics.js @@ -1,10 +1,8 @@ -///: BEGIN:ONLY_INCLUDE_IF(blockaid) import { BlockaidReason, BlockaidResultType, } from '../../../shared/constants/security-provider'; import { MetaMetricsEventUiCustomization } from '../../../shared/constants/metametrics'; -///: END:ONLY_INCLUDE_IF export function getMethodName(camelCase) { if (!camelCase || typeof camelCase !== 'string') { @@ -25,7 +23,6 @@ export function formatAccountType(accountType) { return accountType; } -///: BEGIN:ONLY_INCLUDE_IF(blockaid) /** * Returns the ui_customization string value based on the result type * @@ -89,4 +86,3 @@ export const getBlockaidMetricsProps = ({ securityAlertResponse }) => { return params; }; -///: END:ONLY_INCLUDE_IF diff --git a/ui/pages/confirmations/components/confirm/blockaid-loading-indicator/__snapshots__/blockaid-loading-indicator.test.tsx.snap b/ui/pages/confirmations/components/confirm/blockaid-loading-indicator/__snapshots__/blockaid-loading-indicator.test.tsx.snap new file mode 100644 index 000000000000..b60f849b51f2 --- /dev/null +++ b/ui/pages/confirmations/components/confirm/blockaid-loading-indicator/__snapshots__/blockaid-loading-indicator.test.tsx.snap @@ -0,0 +1,49 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`BlockaidLoadingIndicator returns spinner when there blockaid validation is in progress for signature 1`] = ` +
+
+ + + + + + + + + +
+
+`; diff --git a/ui/pages/confirmations/components/confirm/blockaid-loading-indicator/blockaid-loading-indicator.test.tsx b/ui/pages/confirmations/components/confirm/blockaid-loading-indicator/blockaid-loading-indicator.test.tsx new file mode 100644 index 000000000000..5be896e98827 --- /dev/null +++ b/ui/pages/confirmations/components/confirm/blockaid-loading-indicator/blockaid-loading-indicator.test.tsx @@ -0,0 +1,79 @@ +import React from 'react'; +import configureStore from 'redux-mock-store'; +import { + TransactionStatus, + TransactionType, +} from '@metamask/transaction-controller'; + +import mockState from '../../../../../../test/data/mock-state.json'; +import { BlockaidResultType } from '../../../../../../shared/constants/security-provider'; +import { renderWithProvider } from '../../../../../../test/lib/render-helpers'; +import { SecurityAlertResponse } from '../../../types/confirm'; + +import BlockaidLoadingIndicator from './blockaid-loading-indicator'; + +const mockSecurityAlertResponse: SecurityAlertResponse = { + securityAlertId: 'test-id-mock', + reason: 'test-reason', + result_type: BlockaidResultType.Loading, +}; + +const render = ( + securityAlertResponse: SecurityAlertResponse = mockSecurityAlertResponse, +) => { + const currentConfirmationMock = { + id: '1', + status: TransactionStatus.unapproved, + time: new Date().getTime(), + type: TransactionType.personalSign, + securityAlertResponse, + chainId: '0x1', + }; + + const mockExpectedState = { + ...mockState, + metamask: { + ...mockState.metamask, + unapprovedPersonalMsgs: { + '1': { ...currentConfirmationMock, msgParams: {} }, + }, + pendingApprovals: { + '1': { + ...currentConfirmationMock, + origin: 'origin', + requestData: {}, + requestState: null, + expectsResult: false, + }, + }, + preferences: { redesignedConfirmationsEnabled: true }, + signatureSecurityAlertResponses: { + 'test-id-mock': securityAlertResponse, + }, + }, + confirm: { currentConfirmation: currentConfirmationMock }, + }; + + const defaultStore = configureStore()(mockExpectedState); + return renderWithProvider(, defaultStore); +}; + +describe('BlockaidLoadingIndicator', () => { + it('returns spinner when there blockaid validation is in progress for signature', () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it('returns null if blockaid validation is not in progress', () => { + const { container } = render({ + reason: 'test-reason', + result_type: BlockaidResultType.Benign, + }); + expect(container).toBeEmptyDOMElement(); + }); + + it('returns null if there is not blockaid validation response', () => { + const { container } = render({} as SecurityAlertResponse); + expect(container).toBeEmptyDOMElement(); + }); +}); diff --git a/ui/pages/confirmations/components/confirm/blockaid-loading-indicator/blockaid-loading-indicator.tsx b/ui/pages/confirmations/components/confirm/blockaid-loading-indicator/blockaid-loading-indicator.tsx new file mode 100644 index 000000000000..49f9c8b199cd --- /dev/null +++ b/ui/pages/confirmations/components/confirm/blockaid-loading-indicator/blockaid-loading-indicator.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; + +import Preloader from '../../../../../components/ui/icon/preloader'; +import { BlockaidResultType } from '../../../../../../shared/constants/security-provider'; +import { Box } from '../../../../../components/component-library'; + +import { currentSignatureRequestSecurityResponseSelector } from '../../../selectors'; + +const BlockaidLoadingIndicator = () => { + const signatureSecurityAlertResponse = useSelector( + currentSignatureRequestSecurityResponseSelector, + ); + + if ( + signatureSecurityAlertResponse?.result_type !== BlockaidResultType.Loading + ) { + return null; + } + + return ( + + + + ); +}; + +export default BlockaidLoadingIndicator; diff --git a/ui/pages/confirmations/components/confirm/blockaid-loading-indicator/index.ts b/ui/pages/confirmations/components/confirm/blockaid-loading-indicator/index.ts new file mode 100644 index 000000000000..331dd7ee53b6 --- /dev/null +++ b/ui/pages/confirmations/components/confirm/blockaid-loading-indicator/index.ts @@ -0,0 +1 @@ +export { default as BlockaidLoadingIndicator } from './blockaid-loading-indicator'; diff --git a/ui/pages/confirmations/components/signature-request-original/signature-request-original.component.js b/ui/pages/confirmations/components/signature-request-original/signature-request-original.component.js index 7f147a394e10..dbf451e0ff6f 100644 --- a/ui/pages/confirmations/components/signature-request-original/signature-request-original.component.js +++ b/ui/pages/confirmations/components/signature-request-original/signature-request-original.component.js @@ -42,10 +42,7 @@ import { ///: END:ONLY_INCLUDE_IF } from '../../../../components/component-library'; -///: BEGIN:ONLY_INCLUDE_IF(blockaid) import BlockaidBannerAlert from '../security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert'; -///: END:ONLY_INCLUDE_IF - import ConfirmPageContainerNavigation from '../confirm-page-container/confirm-page-container-navigation'; import SecurityProviderBannerMessage from '../security-provider-banner-message/security-provider-banner-message'; @@ -143,16 +140,12 @@ export default class SignatureRequestOriginal extends Component { return (
- { - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - - ///: END:ONLY_INCLUDE_IF - } + {isSuspiciousResponse(txData?.securityProviderResponse) && (
- { - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - - ///: END:ONLY_INCLUDE_IF - } + {showSecurityProviderBanner && ( {
- { - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - - ///: END:ONLY_INCLUDE_IF - } + {(txData?.securityProviderResponse?.flagAsDangerous !== undefined && txData?.securityProviderResponse?.flagAsDangerous !== diff --git a/ui/pages/confirmations/components/transaction-alerts/transaction-alerts.js b/ui/pages/confirmations/components/transaction-alerts/transaction-alerts.js index 58bd513d362a..534447781d25 100644 --- a/ui/pages/confirmations/components/transaction-alerts/transaction-alerts.js +++ b/ui/pages/confirmations/components/transaction-alerts/transaction-alerts.js @@ -20,9 +20,7 @@ import ZENDESK_URLS from '../../../../helpers/constants/zendesk-url'; ///: END:ONLY_INCLUDE_IF import { isSuspiciousResponse } from '../../../../../shared/modules/security-provider.utils'; -///: BEGIN:ONLY_INCLUDE_IF(blockaid) import BlockaidBannerAlert from '../security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert'; -///: END:ONLY_INCLUDE_IF import SecurityProviderBannerMessage from '../security-provider-banner-message/security-provider-banner-message'; import { getNativeCurrency } from '../../../../ducks/metamask/metamask'; import { parseStandardTokenTransactionData } from '../../../../../shared/modules/transaction.utils'; @@ -74,11 +72,7 @@ const TransactionAlerts = ({ return (
- { - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - - ///: END:ONLY_INCLUDE_IF - } + {isSuspiciousResponse(txData?.securityProviderResponse) && ( - { - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - - ///: END:ONLY_INCLUDE_IF - } + {isSuspiciousResponse(txData?.securityProviderResponse) && ( { const { toAddress: propsToAddress, @@ -163,7 +177,6 @@ const mapStateToProps = (state, ownProps) => { to: txParamsToAddress, gasPrice, gas: gasLimit, - value: amount, data, } = (transaction && transaction.txParams) || txParams; const accounts = getMetaMaskAccounts(state); @@ -171,20 +184,13 @@ const mapStateToProps = (state, ownProps) => { const currentChainSupportsSmartTransactions = getCurrentChainSupportsSmartTransactions(state); - const transactionData = parseStandardTokenTransactionData(data); - const tokenToAddress = getTokenAddressParam(transactionData); - const { balance } = accounts[fromAddress]; const fromInternalAccount = getInternalAccountByAddress(state, fromAddress); const fromName = fromInternalAccount?.metadata.name; const keyring = findKeyringForAddress(state, fromAddress); - const isSendingAmount = - type === TransactionType.simpleSend || !isEmptyHexString(amount); - - const toAddress = isSendingAmount - ? txParamsToAddress - : propsToAddress || tokenToAddress || txParamsToAddress; + const tokenToAddress = getTokenToAddress(data, type); + const toAddress = propsToAddress || tokenToAddress || txParamsToAddress; const toAccounts = getSendToAccounts(state); diff --git a/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.test.js b/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.test.js index bc36b2667576..918c24ed6681 100644 --- a/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.test.js +++ b/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.test.js @@ -775,7 +775,7 @@ describe('Confirm Transaction Base', () => { TransactionType.contractInteraction; }); - describe('when there is an amount being sent (it should be treated as a general contract intereaction rather than custom one)', () => { + describe('when there is an amount being sent (it should be treated as a general contract interaction rather than custom one)', () => { it('should use txParams.to address (contract address)', async () => { const state = mockedStoreWithConfirmTxParams(baseStore, { ...mockTxParams, @@ -795,33 +795,69 @@ describe('Confirm Transaction Base', () => { }); }); - describe(`when there is no amount being sent`, () => { - it('should use propToAddress (toAddress passed as prop)', async () => { + describe('when determines the recipient from transaction data args', () => { + const testCases = [ + { + type: TransactionType.tokenMethodTransfer, + data: `0xa9059cbb000000000000000000000000${mockParsedTxDataToAddressWithout0x}0000000000000000000000000000000000000000000000000000000000000001`, + description: 'tokenMethodTransfer', + }, + { + type: TransactionType.tokenMethodSafeTransferFrom, + data: `0x42842e0e000000000000000000000000806627172af48bd5b0765d3449a7def80d6576ff000000000000000000000000${mockParsedTxDataToAddressWithout0x}000000000000000000000000000000000000000000000000000000000009a7cc`, + description: 'tokenMethodSafeTransferFrom', + }, + { + type: TransactionType.tokenMethodTransferFrom, + data: `0x23b872dd000000000000000000000000ac9539a7d5c43940af498008a7c8f3abb35c3725000000000000000000000000${mockParsedTxDataToAddressWithout0x}000000000000000000000000000000000000000000000000000000000009a7b8`, + description: 'tokenMethodTransferFrom', + }, + ]; + + it.each(testCases)( + 'identifies correctly the recipient for $description transactions', + async ({ type, data }) => { + const state = mockedStoreWithConfirmTxParams(baseStore, { + ...mockTxParams, + data, + }); + state.confirmTransaction.txData = { + ...state.confirmTransaction.txData, + type, + }; + + const { container } = await render({ state }); + + const recipientElem = container.querySelector( + sendToRecipientSelector, + ); + expect(recipientElem).toHaveTextContent(mockParsedTxDataToAddress); + }, + ); + }); + + describe('when a non-transfer function matching the ABIs', () => { + it('does not determine the recipient from transaction data args', async () => { const state = mockedStoreWithConfirmTxParams(baseStore, { ...mockTxParams, - value: '0x0', + data: `0xa22cb465000000000000000000000000${mockParsedTxDataToAddressWithout0x}0000000000000000000000000000000000000000000000000000000000000001`, }); state.confirmTransaction.txData = { ...state.confirmTransaction.txData, type: TransactionType.contractInteraction, }; - const props = { - // we want to test toAddress provided by ownProps in mapStateToProps, but this - // currently overrides toAddress this should pan out fine when we refactor the - // component into a functional component and remove the container.js file - toAddress: mockPropsToAddress, - }; - - const { container } = await render({ props, state }); + const { container } = await render({ state }); const recipientElem = container.querySelector( sendToRecipientSelector, ); - expect(recipientElem).toHaveTextContent(mockPropsToAddressConcat); + expect(recipientElem).toHaveTextContent(mockTxParamsToAddressConcat); }); + }); - it('should use address parsed from transaction data if propToAddress is not provided', async () => { + describe(`when there is no amount being sent`, () => { + it('should use propToAddress (toAddress passed as prop)', async () => { const state = mockedStoreWithConfirmTxParams(baseStore, { ...mockTxParams, value: '0x0', @@ -831,14 +867,19 @@ describe('Confirm Transaction Base', () => { type: TransactionType.contractInteraction, }; - const props = {}; + const props = { + // we want to test toAddress provided by ownProps in mapStateToProps, but this + // currently overrides toAddress this should pan out fine when we refactor the + // component into a functional component and remove the container.js file + toAddress: mockPropsToAddress, + }; const { container } = await render({ props, state }); const recipientElem = container.querySelector( sendToRecipientSelector, ); - expect(recipientElem).toHaveTextContent(mockParsedTxDataToAddress); + expect(recipientElem).toHaveTextContent(mockPropsToAddressConcat); }); it('should use txParams.to if neither propToAddress is not provided nor the transaction data to address were provided', async () => { diff --git a/ui/pages/confirmations/confirm-transaction/confirm-transaction.transaction.test.js b/ui/pages/confirmations/confirm-transaction/confirm-transaction.transaction.test.js index 5ea10a6183de..f9101c0e7d7c 100644 --- a/ui/pages/confirmations/confirm-transaction/confirm-transaction.transaction.test.js +++ b/ui/pages/confirmations/confirm-transaction/confirm-transaction.transaction.test.js @@ -54,7 +54,7 @@ describe('Confirm Transaction', () => { )), ); - expect(result.getByText('0xb19Ac...f0c5e')).toBeInTheDocument(); + expect(result.getByText('Ledger Hardware 2')).toBeInTheDocument(); expect(result.getByRole('button', { name: 'Details' })).toBeInTheDocument(); expect(result.getByRole('button', { name: 'Hex' })).toBeInTheDocument(); }); diff --git a/ui/pages/confirmations/confirm/confirm.tsx b/ui/pages/confirmations/confirm/confirm.tsx index 96d6357fe88a..89a2fe5256be 100644 --- a/ui/pages/confirmations/confirm/confirm.tsx +++ b/ui/pages/confirmations/confirm/confirm.tsx @@ -5,6 +5,7 @@ import { Page } from '../../../components/multichain/pages/page'; import { MMISignatureMismatchBanner } from '../../../components/app/mmi-signature-mismatch-banner'; ///: END:ONLY_INCLUDE_IF +import { BlockaidLoadingIndicator } from '../components/confirm/blockaid-loading-indicator'; import ScrollToBottom from '../components/confirm/scroll-to-bottom'; import setCurrentConfirmation from '../hooks/setCurrentConfirmation'; import syncConfirmPath from '../hooks/syncConfirmPath'; @@ -46,6 +47,7 @@ const Confirm = () => { ///: END:ONLY_INCLUDE_IF } + <Info showAdvancedDetails={showAdvancedDetails} /> diff --git a/ui/pages/confirmations/hooks/alerts/useBlockaidAlerts.ts b/ui/pages/confirmations/hooks/alerts/useBlockaidAlerts.ts index ad93e1a87d5f..ae87e60d64f6 100644 --- a/ui/pages/confirmations/hooks/alerts/useBlockaidAlerts.ts +++ b/ui/pages/confirmations/hooks/alerts/useBlockaidAlerts.ts @@ -13,7 +13,10 @@ import { REDESIGN_TRANSACTION_TYPES, SIGNATURE_TRANSACTION_TYPES, } from '../../utils'; -import { currentConfirmationSelector } from '../../selectors'; +import { + currentConfirmationSelector, + currentSignatureRequestSecurityResponseSelector, +} from '../../selectors'; import { normalizeProviderAlert } from './utils'; const SUPPORTED_TRANSACTION_TYPES = [ @@ -47,8 +50,7 @@ const useBlockaidAlerts = (): Alert[] => { const transactionType = currentConfirmation?.type as TransactionType; const signatureSecurityAlertResponse = useSelector( - (state: SecurityAlertResponsesState) => - state.metamask.signatureSecurityAlertResponses?.[securityAlertId], + currentSignatureRequestSecurityResponseSelector, ); const transactionSecurityAlertResponse = useSelector( diff --git a/ui/pages/confirmations/selectors/confirm.test.ts b/ui/pages/confirmations/selectors/confirm.test.ts index e95da11e2cba..0aa56cb3f82e 100644 --- a/ui/pages/confirmations/selectors/confirm.test.ts +++ b/ui/pages/confirmations/selectors/confirm.test.ts @@ -1,12 +1,24 @@ import { ApprovalType } from '@metamask/controller-utils'; import { TransactionType } from '@metamask/transaction-controller'; -import { ConfirmMetamaskState } from '../types/confirm'; + +import { + BlockaidReason, + BlockaidResultType, +} from '../../../../shared/constants/security-provider'; +import { ConfirmMetamaskState, SecurityAlertResponse } from '../types/confirm'; import { currentConfirmationSelector, + currentSignatureRequestSecurityResponseSelector, latestPendingConfirmationSelector, pendingConfirmationsSelector, } from './confirm'; +const SECURITY_ALERT_RESPONSE_MOCK: SecurityAlertResponse = { + securityAlertId: '1', + result_type: BlockaidResultType.Malicious, + reason: BlockaidReason.permitFarming, +}; + describe('confirm selectors', () => { const mockedState: ConfirmMetamaskState = { confirm: { @@ -75,4 +87,28 @@ describe('confirm selectors', () => { expect(result).toStrictEqual(mockedState.confirm.currentConfirmation); }); }); + + describe('currentSignatureRequestSecurityResponseSelector', () => { + it('should return SecurityAlertResponse for current signature', () => { + const sigMockState: ConfirmMetamaskState = { + confirm: { + currentConfirmation: { + id: '1', + type: TransactionType.personalSign, + securityAlertResponse: SECURITY_ALERT_RESPONSE_MOCK, + }, + }, + metamask: { + pendingApprovals: {}, + approvalFlows: [], + signatureSecurityAlertResponses: { 1: SECURITY_ALERT_RESPONSE_MOCK }, + }, + }; + + const result = + currentSignatureRequestSecurityResponseSelector(sigMockState); + + expect(result).toStrictEqual(SECURITY_ALERT_RESPONSE_MOCK); + }); + }); }); diff --git a/ui/pages/confirmations/selectors/confirm.ts b/ui/pages/confirmations/selectors/confirm.ts index 8cc7dd19a95f..e4af9333e8d5 100644 --- a/ui/pages/confirmations/selectors/confirm.ts +++ b/ui/pages/confirmations/selectors/confirm.ts @@ -2,8 +2,13 @@ import { ApprovalType } from '@metamask/controller-utils'; import { createSelector } from 'reselect'; import { getPendingApprovals } from '../../../selectors/approvals'; -import { ConfirmMetamaskState } from '../types/confirm'; +import { + ConfirmMetamaskState, + Confirmation, + SecurityAlertResponse, +} from '../types/confirm'; import { createDeepEqualSelector } from '../../../selectors/util'; +import { isSignatureTransactionType } from '../utils'; const ConfirmationApprovalTypes = [ ApprovalType.EthSign, @@ -43,3 +48,23 @@ export const confirmSelector = (state: ConfirmMetamaskState) => state.confirm; export const currentConfirmationSelector = (state: ConfirmMetamaskState) => state.confirm.currentConfirmation; + +export const currentSignatureRequestSecurityResponseSelector = ( + state: ConfirmMetamaskState, +) => { + const currentConfirmation: Confirmation | undefined = + currentConfirmationSelector(state); + + if ( + !currentConfirmation || + !isSignatureTransactionType(currentConfirmation) + ) { + return undefined; + } + + const securityAlertId = ( + currentConfirmation?.securityAlertResponse as SecurityAlertResponse + )?.securityAlertId as string; + + return state.metamask.signatureSecurityAlertResponses?.[securityAlertId]; +}; diff --git a/ui/pages/confirmations/token-allowance/token-allowance.js b/ui/pages/confirmations/token-allowance/token-allowance.js index e8d6bbf5bbbe..56519615e00c 100644 --- a/ui/pages/confirmations/token-allowance/token-allowance.js +++ b/ui/pages/confirmations/token-allowance/token-allowance.js @@ -61,9 +61,7 @@ import { NUM_W_OPT_DECIMAL_COMMA_OR_DOT_REGEX, } from '../../../../shared/constants/tokens'; import { isSuspiciousResponse } from '../../../../shared/modules/security-provider.utils'; -///: BEGIN:ONLY_INCLUDE_IF(blockaid) import BlockaidBannerAlert from '../components/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert'; -///: END:ONLY_INCLUDE_IF import { ConfirmPageContainerNavigation } from '../components/confirm-page-container'; import { useSimulationFailureWarning } from '../hooks/useSimulationFailureWarning'; import SimulationErrorMessage from '../components/simulation-error-message'; @@ -381,16 +379,12 @@ export default function TokenAllowance({ accountAddress={userAddress} chainId={fullTxData.chainId} /> - { - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - <BlockaidBannerAlert - txData={txData} - marginTop={4} - marginLeft={4} - marginRight={4} - /> - ///: END:ONLY_INCLUDE_IF - } + <BlockaidBannerAlert + txData={txData} + marginTop={4} + marginLeft={4} + marginRight={4} + /> <BlockaidUnavailableBannerAlert /> {isSuspiciousResponse(txData?.securityProviderResponse) && ( <SecurityProviderBannerMessage diff --git a/ui/pages/confirmations/types/confirm.ts b/ui/pages/confirmations/types/confirm.ts index ab472318fcac..2050aae32c05 100644 --- a/ui/pages/confirmations/types/confirm.ts +++ b/ui/pages/confirmations/types/confirm.ts @@ -58,5 +58,6 @@ export type ConfirmMetamaskState = { metamask: { pendingApprovals: ApprovalControllerState['pendingApprovals']; approvalFlows: ApprovalControllerState['approvalFlows']; + signatureSecurityAlertResponses?: Record<string, SecurityAlertResponse>; }; }; diff --git a/ui/pages/settings/security-tab/security-tab.component.js b/ui/pages/settings/security-tab/security-tab.component.js index b20435e4b258..1ed946f259cb 100644 --- a/ui/pages/settings/security-tab/security-tab.component.js +++ b/ui/pages/settings/security-tab/security-tab.component.js @@ -99,9 +99,7 @@ export default class SecurityTab extends PureComponent { securityAlertsEnabled: PropTypes.bool, useExternalServices: PropTypes.bool, toggleExternalServices: PropTypes.func.isRequired, - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) setSecurityAlertsEnabled: PropTypes.func, - ///: END:ONLY_INCLUDE_IF }; state = { @@ -999,7 +997,6 @@ export default class SecurityTab extends PureComponent { ); } - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) /** * toggleSecurityAlert * @@ -1017,7 +1014,6 @@ export default class SecurityTab extends PureComponent { }); setSecurityAlertsEnabled(newValue); } - ///: END:ONLY_INCLUDE_IF renderUseExternalServices() { const { t } = this.context; @@ -1132,9 +1128,7 @@ export default class SecurityTab extends PureComponent { {this.context.t('security')} </span> {this.renderSeedWords()} - {/* ///: BEGIN:ONLY_INCLUDE_IF(blockaid) */} {this.renderSecurityAlertsToggle()} - {/* ///: END:ONLY_INCLUDE_IF */} <span className="settings-page__security-tab-sub-header__bold"> {this.context.t('privacy')} </span> diff --git a/ui/pages/settings/security-tab/security-tab.container.js b/ui/pages/settings/security-tab/security-tab.container.js index 98a2c7c9c3f5..66aee77ae962 100644 --- a/ui/pages/settings/security-tab/security-tab.container.js +++ b/ui/pages/settings/security-tab/security-tab.container.js @@ -19,15 +19,11 @@ import { setUseSafeChainsListValidation, setUseExternalNameSources, setUseTransactionSimulations, - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) setSecurityAlertsEnabled, - ///: END:ONLY_INCLUDE_IF } from '../../../store/actions'; import { getAllNetworks, - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) getIsSecurityAlertsEnabled, - ///: END:ONLY_INCLUDE_IF getPetnamesEnabled, } from '../../../selectors'; import { openBasicFunctionalityModal } from '../../../ducks/app/app'; @@ -80,9 +76,7 @@ const mapStateToProps = (state) => { useExternalNameSources, useExternalServices, petnamesEnabled, - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) securityAlertsEnabled: getIsSecurityAlertsEnabled(state), - ///: END:ONLY_INCLUDE_IF useTransactionSimulations: metamask.useTransactionSimulations, }; }; @@ -122,9 +116,7 @@ const mapDispatchToProps = (dispatch) => { setUseTransactionSimulations: (value) => { return dispatch(setUseTransactionSimulations(value)); }, - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) setSecurityAlertsEnabled: (value) => setSecurityAlertsEnabled(value), - ///: END:ONLY_INCLUDE_IF }; }; diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index 8653ac48d0ce..f613015efb7c 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -2247,7 +2247,6 @@ export function getUseRequestQueue(state) { return state.metamask.useRequestQueue; } -///: BEGIN:ONLY_INCLUDE_IF(blockaid) /** * To get the `getIsSecurityAlertsEnabled` value which determines whether security check is enabled * @@ -2257,7 +2256,6 @@ export function getUseRequestQueue(state) { export function getIsSecurityAlertsEnabled(state) { return state.metamask.securityAlertsEnabled; } -///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) /** diff --git a/ui/store/actions.ts b/ui/store/actions.ts index 8082409268c2..4a75d4828212 100644 --- a/ui/store/actions.ts +++ b/ui/store/actions.ts @@ -4956,7 +4956,6 @@ export function setNetworkClientIdForDomain( ]); } -///: BEGIN:ONLY_INCLUDE_IF(blockaid) export function setSecurityAlertsEnabled(val: boolean): void { try { submitRequestToBackground('setSecurityAlertsEnabled', [val]); @@ -4964,7 +4963,6 @@ export function setSecurityAlertsEnabled(val: boolean): void { logErrorWithMessage(error); } } -///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) export async function setAddSnapAccountEnabled(value: boolean): Promise<void> { diff --git a/yarn.lock b/yarn.lock index 60b89fd15a05..421768c83cc9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6284,9 +6284,9 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-controllers@npm:^9.0.0": - version: 9.0.0 - resolution: "@metamask/snaps-controllers@npm:9.0.0" +"@metamask/snaps-controllers@npm:^9.2.0": + version: 9.2.0 + resolution: "@metamask/snaps-controllers@npm:9.2.0" dependencies: "@metamask/approval-controller": "npm:^7.0.0" "@metamask/base-controller": "npm:^6.0.0" @@ -6298,9 +6298,9 @@ __metadata: "@metamask/post-message-stream": "npm:^8.1.0" "@metamask/rpc-errors": "npm:^6.2.1" "@metamask/snaps-registry": "npm:^3.1.0" - "@metamask/snaps-rpc-methods": "npm:^9.1.3" - "@metamask/snaps-sdk": "npm:^5.0.0" - "@metamask/snaps-utils": "npm:^7.6.0" + "@metamask/snaps-rpc-methods": "npm:^9.1.4" + "@metamask/snaps-sdk": "npm:^6.0.0" + "@metamask/snaps-utils": "npm:^7.7.0" "@metamask/utils": "npm:^8.3.0" "@xstate/fsm": "npm:^2.0.0" browserify-zlib: "npm:^0.2.0" @@ -6313,30 +6313,30 @@ __metadata: readable-web-to-node-stream: "npm:^3.0.2" tar-stream: "npm:^3.1.7" peerDependencies: - "@metamask/snaps-execution-environments": ^6.4.0 + "@metamask/snaps-execution-environments": ^6.5.0 peerDependenciesMeta: "@metamask/snaps-execution-environments": optional: true - checksum: 10/12e89e622865025e89c0f06f34e9dad278f91b3621e2b9944b154a7f190e5b47d5a899beb65e431627e023ebcd00969c492a280a5051239b57ed24cf5c3347ec + checksum: 10/3926bdfc3f064d57162825028aca2cf433e7adfe02ecdade174e2c75686c3dff407ee195f352662754846fc4edad8d2c6c5ce73471e3a09191a91070a98ba046 languageName: node linkType: hard -"@metamask/snaps-execution-environments@npm:^6.4.0": - version: 6.4.0 - resolution: "@metamask/snaps-execution-environments@npm:6.4.0" +"@metamask/snaps-execution-environments@npm:^6.5.0": + version: 6.5.0 + resolution: "@metamask/snaps-execution-environments@npm:6.5.0" dependencies: "@metamask/json-rpc-engine": "npm:^9.0.0" "@metamask/object-multiplex": "npm:^2.0.0" "@metamask/post-message-stream": "npm:^8.1.0" "@metamask/providers": "npm:^17.0.0" "@metamask/rpc-errors": "npm:^6.2.1" - "@metamask/snaps-sdk": "npm:^5.0.0" - "@metamask/snaps-utils": "npm:^7.6.0" + "@metamask/snaps-sdk": "npm:^6.0.0" + "@metamask/snaps-utils": "npm:^7.7.0" "@metamask/utils": "npm:^8.3.0" nanoid: "npm:^3.1.31" readable-stream: "npm:^3.6.2" superstruct: "npm:^1.0.3" - checksum: 10/ef487a7b8e2b1aa46c214d06e14d482a741abdc75f5904ff61f38b019cd253422cd5ae95fba7263b0fab57256eb081a3c44eeafcdff8ad71d5bf9163d65fd5dc + checksum: 10/f502fb1ff1c7c574bf4b7e189cc636d85aacb9e86116720a8c6305ce2054a3ebd966b2f73be242bf5f8f301c79f6b3461a7d2c631301b6593ad1d3f09e100c21 languageName: node linkType: hard @@ -6352,38 +6352,38 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-rpc-methods@npm:^9.1.2, @metamask/snaps-rpc-methods@npm:^9.1.3": - version: 9.1.3 - resolution: "@metamask/snaps-rpc-methods@npm:9.1.3" +"@metamask/snaps-rpc-methods@npm:^9.1.2, @metamask/snaps-rpc-methods@npm:^9.1.4": + version: 9.1.4 + resolution: "@metamask/snaps-rpc-methods@npm:9.1.4" dependencies: "@metamask/key-tree": "npm:^9.1.1" "@metamask/permission-controller": "npm:^10.0.0" "@metamask/rpc-errors": "npm:^6.2.1" - "@metamask/snaps-sdk": "npm:^5.0.0" - "@metamask/snaps-utils": "npm:^7.6.0" + "@metamask/snaps-sdk": "npm:^6.0.0" + "@metamask/snaps-utils": "npm:^7.7.0" "@metamask/utils": "npm:^8.3.0" "@noble/hashes": "npm:^1.3.1" superstruct: "npm:^1.0.3" - checksum: 10/d346f12fb170dac694b4b5d1580fe9bc212cd266cece063636be85c442be0a7d0fa98e1d93d999da3dc67a9b5ce49c5a67e5345567b97062dd94e7d10c4e2959 + checksum: 10/db4963c2eaf1763ca48be4f095b0adae29596efc9ebf7876ac92fa6e3ef8d2bc5d45634293566b8a4703109c569f2666b4350700139d0566cb29f65be77a17f1 languageName: node linkType: hard -"@metamask/snaps-sdk@npm:^5.0.0": - version: 5.0.0 - resolution: "@metamask/snaps-sdk@npm:5.0.0" +"@metamask/snaps-sdk@npm:^6.0.0": + version: 6.0.0 + resolution: "@metamask/snaps-sdk@npm:6.0.0" dependencies: "@metamask/key-tree": "npm:^9.1.1" "@metamask/providers": "npm:^17.0.0" "@metamask/rpc-errors": "npm:^6.2.1" "@metamask/utils": "npm:^8.3.0" superstruct: "npm:^1.0.3" - checksum: 10/2419edb193c32fbd7292ebddbe37e9af6b09dc175fa03b74e9b732b859cdd1b6ddd5d18d19dcf0d550fdcfdb6210ec89168e020c382d3cf41178f51e0e1e888c + checksum: 10/91774ea791423a3cfef4ddbe30fc0351d9bfb9bad071ebce6a74be85cdd9b864b2666785cb6f0e8257a9778bc630f2de71544258d0367a93917ffe3d72ef90a5 languageName: node linkType: hard -"@metamask/snaps-utils@npm:^7.4.0, @metamask/snaps-utils@npm:^7.5.0, @metamask/snaps-utils@npm:^7.6.0": - version: 7.6.0 - resolution: "@metamask/snaps-utils@npm:7.6.0" +"@metamask/snaps-utils@npm:^7.1.0, @metamask/snaps-utils@npm:^7.4.0, @metamask/snaps-utils@npm:^7.5.0, @metamask/snaps-utils@npm:^7.7.0": + version: 7.7.0 + resolution: "@metamask/snaps-utils@npm:7.7.0" dependencies: "@babel/core": "npm:^7.23.2" "@babel/types": "npm:^7.23.0" @@ -6393,7 +6393,7 @@ __metadata: "@metamask/rpc-errors": "npm:^6.2.1" "@metamask/slip44": "npm:^3.1.0" "@metamask/snaps-registry": "npm:^3.1.0" - "@metamask/snaps-sdk": "npm:^5.0.0" + "@metamask/snaps-sdk": "npm:^6.0.0" "@metamask/utils": "npm:^8.3.0" "@noble/hashes": "npm:^1.3.1" "@scure/base": "npm:^1.1.1" @@ -6408,7 +6408,7 @@ __metadata: ses: "npm:^1.1.0" superstruct: "npm:^1.0.3" validate-npm-package-name: "npm:^5.0.0" - checksum: 10/e81e0185ab1678822b47e400c4422023449be1641bea7ce3d679386257545091ecc73c9a4626c0ed585d93d9fe3a1354dc8e4b8310c27c33871f7fb0bb029506 + checksum: 10/ac5b216b81157f71244e5922befccb214be402f0f5b784086cea46d7b0142f14f555ec7986cb7c867d1b99bfe43b08a687d8db22db85fada55764c2f4afcff97 languageName: node linkType: hard @@ -25394,11 +25394,11 @@ __metadata: "@metamask/selected-network-controller": "npm:^15.0.2" "@metamask/signature-controller": "npm:^16.0.0" "@metamask/smart-transactions-controller": "npm:^10.1.2" - "@metamask/snaps-controllers": "npm:^9.0.0" - "@metamask/snaps-execution-environments": "npm:^6.4.0" - "@metamask/snaps-rpc-methods": "npm:^9.1.3" - "@metamask/snaps-sdk": "npm:^5.0.0" - "@metamask/snaps-utils": "npm:^7.6.0" + "@metamask/snaps-controllers": "npm:^9.2.0" + "@metamask/snaps-execution-environments": "npm:^6.5.0" + "@metamask/snaps-rpc-methods": "npm:^9.1.4" + "@metamask/snaps-sdk": "npm:^6.0.0" + "@metamask/snaps-utils": "npm:^7.7.0" "@metamask/test-bundler": "npm:^1.0.0" "@metamask/test-dapp": "npm:^8.4.0" "@metamask/transaction-controller": "npm:^32.0.0"