diff --git a/.circleci/config.yml b/.circleci/config.yml index 6ad5cdd5384c..f13f3177de7b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -36,7 +36,6 @@ orbs: codecov: codecov/codecov@3.2.2 slack: circleci/slack@4.12.5 - rc_branch_only: &rc_branch_only filters: branches: @@ -481,6 +480,8 @@ jobs: - run: name: build:debug command: find dist/ -type f -exec md5sum {} \; | sort -k 2 + - store_artifacts: + path: builds - persist_to_workspace: root: . paths: @@ -698,6 +699,8 @@ jobs: - run: name: Move test zips to 'builds-test' to avoid conflict with production build command: mv ./builds ./builds-test + - store_artifacts: + path: builds-test - persist_to_workspace: root: . paths: diff --git a/.depcheckrc.yml b/.depcheckrc.yml index 064ce190a26d..7a048564edc9 100644 --- a/.depcheckrc.yml +++ b/.depcheckrc.yml @@ -37,6 +37,7 @@ ignores: - 'source-map-explorer' - 'playwright' - 'wait-on' + - 'tsx' #used in .devcontainer # development tool - 'nyc' # storybook diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000000..b23e99bbbe59 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,58 @@ +{ + "customizations": { + "vscode": { + "extensions": [ + "eamodio.gitlens", + "github.codespaces", + "github.copilot", + "github.copilot-chat", + "ms-azuretools.vscode-docker", + "rvest.vs-code-prettier-eslint" + ], + "settings": { + "editor.formatOnSave": true, + "git.autofetch": true, + "git.ignoreRebaseWarning": true, + "git.rebaseWhenSync": true, + "gitlens.showWelcomeOnInstall": false + }, + "tasks": [ + { + "label": "Open noVNC new tab", + "type": "shell", + "command": "xdg-open https://$CODESPACE_NAME-6080.$GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN", + "problemMatcher": [] + } + ] + } + }, + + "forwardPorts": [5901, 6080], + + "image": "mcr.microsoft.com/devcontainers/universal:2-linux", + + "name": "MM Extension Codespace", + + "otherPortsAttributes": { "onAutoForward": "ignore" }, + + "portsAttributes": { + "5901": { + "label": "local VNC", + "onAutoForward": "ignore" + }, + "6080": { + "label": "noVNC web", + "onAutoForward": "openPreview" + } + }, + + "postAttachCommand": "/usr/local/share/desktop-init.sh && git pull --rebase; yarn download-builds", + + // This is a working Infura key, but it's on the Free Plan and has very limited requests per second + // If you want to use your own INFURA_PROJECT_ID, follow the instructions in README.md + "postCreateCommand": "if [ -z \"$INFURA_PROJECT_ID\" ]; then echo 'INFURA_PROJECT_ID=3d110a0fce9e49b08d2ee584e19a05ba' > .metamaskrc; fi", + + "runArgs": ["--shm-size=1g"], + + "updateContentCommand": "sudo .devcontainer/install.sh && yarn --immutable && yarn tsx .devcontainer/setup-browsers.ts && echo 'export DISPLAY=:1' >> ~/.bashrc" +} diff --git a/.devcontainer/download-builds.ts b/.devcontainer/download-builds.ts new file mode 100644 index 000000000000..c533cebf5fa0 --- /dev/null +++ b/.devcontainer/download-builds.ts @@ -0,0 +1,153 @@ +import { execSync } from 'child_process'; +import util from 'util'; + +const exec = util.promisify(require('node:child_process').exec); + +function getGitBranch() { + const gitOutput = execSync('git status').toString(); + + const branchRegex = /On branch (?.*)\n/; + return gitOutput.match(branchRegex)?.groups?.branch || 'develop'; +} + +async function getCircleJobs(branch: string) { + let response = await fetch( + `https://circleci.com/api/v2/project/gh/MetaMask/metamask-extension/pipeline?branch=${branch}`, + ); + + const pipelineId = (await response.json()).items[0].id; + + console.log('pipelineId:', pipelineId); + + response = await fetch( + `https://circleci.com/api/v2/pipeline/${pipelineId}/workflow`, + ); + + const workflowId = (await response.json()).items[0].id; + + console.log('workflowId:', workflowId); + + response = await fetch( + `https://circleci.com/api/v2/workflow/${workflowId}/job`, + ); + + const jobs = (await response.json()).items; + + return jobs; +} + +async function getBuilds(branch: string, jobNames: string[]) { + const jobs = await getCircleJobs(branch); + let builds = [] as any[]; + + for (const jobName of jobNames) { + const jobId = jobs.find((job: any) => job.name === jobName).job_number; + + console.log(`jobName: ${jobName}, jobId: ${jobId}`); + + const response = await fetch( + `https://circleci.com/api/v2/project/gh/MetaMask/metamask-extension/${jobId}/artifacts`, + ); + + const artifacts = (await response.json()).items; + + if (!artifacts || artifacts.length === 0) { + return []; + } + + builds = builds.concat( + artifacts.filter((artifact: any) => artifact.path.endsWith('.zip')), + ); + } + + return builds; +} + +function getVersionNumber(builds: any[]) { + for (const build of builds) { + const versionRegex = /metamask-chrome-(?\d+\.\d+\.\d+).zip/; + + const versionNumber = build.path.match(versionRegex)?.groups?.version; + + if (versionNumber) { + return versionNumber; + } + } +} + +async function downloadBuilds(builds: any[]) { + if (!builds || builds.length === 0) { + console.log( + 'No builds found on CircleCI for the current branch, you will have to build the Extension yourself', + ); + return; + } + + const buildPromises = [] as Promise[]; + + for (const build of builds) { + if ( + build.path.startsWith('builds/') || + build.path.startsWith('builds-test/') + ) { + const { url } = build; + + console.log('downloading', build.path); + + buildPromises.push(exec(`curl -L --create-dirs -o ${build.path} ${url}`)); + } + } + + await Promise.all(buildPromises); + + console.log('downloads complete'); +} + +function unzipBuilds(folder: 'builds' | 'builds-test', versionNumber: string) { + if (!versionNumber) { + return; + } + + if (process.platform === 'win32') { + execSync(`rmdir /s /q dist & mkdir dist\\chrome & mkdir dist\\firefox`); + } else { + execSync('sudo rm -rf dist && mkdir -p dist'); + } + + for (const browser of ['chrome', 'firefox']) { + if (process.platform === 'win32') { + execSync( + `tar -xf ${folder}/metamask-${browser}-${versionNumber}.zip -C dist/${browser}`, + ); + } else { + execSync( + `unzip ${folder}/metamask-${browser}-${versionNumber}.zip -d dist/${browser}`, + ); + } + } + + console.log(`unzipped ${folder} into ./dist`); +} + +async function main(jobNames: string[]) { + const branch = getGitBranch(); + + const builds = await getBuilds(branch, jobNames); + + console.log('builds', builds); + + await downloadBuilds(builds); + + const versionNumber = getVersionNumber(builds); + const folder = builds[0].path.split('/')[0]; + + unzipBuilds(folder, versionNumber); +} + +let args = process.argv.slice(2); + +if (!args || args.length === 0) { + args = ['prep-build']; +} + +main(args); diff --git a/.devcontainer/first-run-notice.txt b/.devcontainer/first-run-notice.txt new file mode 100644 index 000000000000..7b093dbef40e --- /dev/null +++ b/.devcontainer/first-run-notice.txt @@ -0,0 +1,22 @@ +Welcome to MetaMask Codespaces! + +## Quickstart Instructions + +1. A "Simple Browser" will open inside the browser with noVNC -- click Connect + - Optional steps: + - Click the button at the upper-right of the Simple Browser tab to open the noVNC window in its own tab + - Open the noVNC sidebar on the left, click the gear icon, change the Scaling Mode to Remote Resizing +2. Wait about 20 extra seconds on the first launch, for the scripts to finish +3. Right-click on the noVNC desktop to launch Chrome or Firefox with MetaMask pre-installed +4. Change some code, then run `yarn start` to build in dev mode +5. After a minute or two, it will finish building, and you can see your changes in the noVNC desktop + +## Tips to keep your Codespaces usage lower + +- You are billed for both time spent running, and for storage used +- Codespaces pause after 30 minutes of inactivity, and auto-delete after 30 days of inactivity +- You can manage your Codespaces here: https://github.com/codespaces + - You may want to manually pause them before the 30 minute timeout + - If you have several idle Codespaces hanging around for several days, you can quickly run out of storage quota. + You should delete the ones you do not plan to use anymore, and probably keep only 1 or 2 in the long-term. + It's also possible to re-use old Codespaces and switch the branch, instead of creating new ones and deleting the old ones. \ No newline at end of file diff --git a/.devcontainer/install.sh b/.devcontainer/install.sh new file mode 100755 index 000000000000..8d198a9db794 --- /dev/null +++ b/.devcontainer/install.sh @@ -0,0 +1,440 @@ +#!/usr/bin/env bash +#------------------------------------------------------------------------------------------------------------- +# MIT License +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE +#------------------------------------------------------------------------------------------------------------- +# Docs: https://github.com/devcontainers/features/blob/main/src/desktop-lite/README.md +# Maintainer: The VS Code and Codespaces Teams +# +# Original file source: https://github.com/devcontainers/features/blob/main/src/desktop-lite/install.sh +# Adapted by the MetaMask Codespaces team in 2023 to support noVNC 1.4.0 +# +# shellcheck disable=SC1091,SC2086 + +NOVNC_VERSION="1.4.0" +VNC_PASSWORD='' +NOVNC_PORT="${WEBPORT:-6080}" +VNC_PORT="${VNCPORT:-5901}" + +INSTALL_NOVNC="${INSTALL_NOVNC:-"true"}" +USERNAME="${USERNAME:-"${_REMOTE_USER:-"automatic"}"}" + +WEBSOCKETIFY_VERSION=0.10.0 + +package_list=" + tigervnc-standalone-server \ + tigervnc-common \ + fluxbox \ + dbus-x11 \ + x11-utils \ + x11-xserver-utils \ + xdg-utils \ + fbautostart \ + at-spi2-core \ + xterm \ + eterm \ + nautilus\ + mousepad \ + seahorse \ + gnome-icon-theme \ + gnome-keyring \ + libx11-dev \ + libxkbfile-dev \ + libsecret-1-dev \ + libgbm-dev \ + libnotify4 \ + libnss3 \ + libxss1 \ + libasound2 \ + xfonts-base \ + xfonts-terminus \ + fonts-noto \ + fonts-wqy-microhei \ + fonts-droid-fallback \ + htop \ + ncdu \ + curl \ + ca-certificates\ + unzip \ + nano \ + locales" + +# Packages to attempt to install if essential tools are missing (ie: vncpasswd). +# This is useful, at least, for Ubuntu 22.04 (jammy) +package_list_additional=" + tigervnc-tools" + +set -e + +# Clean up +rm -rf /var/lib/apt/lists/* + +if [ "$(id -u)" -ne 0 ]; then + echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.' + exit 1 +fi + +# Determine the appropriate non-root user +if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then + USERNAME="" + POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") + for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do + if id -u ${CURRENT_USER} > /dev/null 2>&1; then + USERNAME=${CURRENT_USER} + break + fi + done + if [ "${USERNAME}" = "" ]; then + USERNAME=root + fi +elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then + USERNAME=root +fi +# Add default Fluxbox config files if none are already present +fluxbox_apps="$(cat \ +<< 'EOF' +[transient] (role=GtkFileChooserDialog) + [Dimensions] {70% 70%} + [Position] (CENTER) {0 0} +[end] +EOF +)" + +fluxbox_init="$(cat \ +<< 'EOF' +session.configVersion: 13 +session.menuFile: ~/.fluxbox/menu +session.keyFile: ~/.fluxbox/keys +session.styleFile: /usr/share/fluxbox/styles/qnx-photon +session.screen0.workspaces: 1 +session.screen0.workspacewarping: false +session.screen0.toolbar.widthPercent: 100 +session.screen0.strftimeFormat: %a %l:%M %p +session.screen0.toolbar.tools: RootMenu, clock, iconbar, systemtray +session.screen0.workspaceNames: One, +session.screen0.rootCommand: fbsetbg -c /workspaces/metamask-extension/app/images/icon-512.png +EOF +)" + +fluxbox_menu="$(cat \ +<< 'EOF' +[begin] ( Application Menu ) + [exec] (File Manager) { nautilus ~ } <> + [exec] (Text Editor) { mousepad } <> + [exec] (Terminal) { tilix -w ~ -e $(readlink -f /proc/$$/exe) -il } <> + [exec] (Chrome) { chrome-mm --disable-gpu --disable-dev-shm-usage } <> + [exec] (Firefox) { firefox-mm } <> + [submenu] (System) {} + [exec] (Set Resolution) { tilix -t "Set Resolution" -e bash /usr/local/bin/set-resolution } <> + [exec] (Edit Application Menu) { mousepad ~/.fluxbox/menu } <> + [exec] (Passwords and Keys) { seahorse } <> + [exec] (Top Processes) { tilix -t "Top" -e htop } <> + [exec] (Disk Utilization) { tilix -t "Disk Utilization" -e ncdu / } <> + [exec] (Editres) {editres} <> + [exec] (Xfontsel) {xfontsel} <> + [exec] (Xkill) {xkill} <> + [exec] (Xrefresh) {xrefresh} <> + [end] + [config] (Configuration) + [workspaces] (Workspaces) +[end] +EOF +)" + +# Copy config files if the don't already exist +copy_fluxbox_config() { + local target_dir="$1" + mkdir -p "${target_dir}/.fluxbox" + touch "${target_dir}/.Xmodmap" + if [ ! -e "${target_dir}/.fluxbox/apps" ]; then + echo "${fluxbox_apps}" > "${target_dir}/.fluxbox/apps" + fi + if [ ! -e "${target_dir}/.fluxbox/init" ]; then + echo "${fluxbox_init}" > "${target_dir}/.fluxbox/init" + fi + if [ ! -e "${target_dir}/.fluxbox/menu" ]; then + echo "${fluxbox_menu}" > "${target_dir}/.fluxbox/menu" + fi +} + +apt_get_update() +{ + if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then + echo "Running apt-get update..." + apt-get update -y + fi +} + +# Checks if packages are installed and installs them if not +check_packages() { + if ! dpkg -s "$@" > /dev/null 2>&1; then + apt_get_update + apt-get -y install --no-install-recommends "$@" + fi +} + +########################## +# Install starts here # +########################## + +# Ensure apt is in non-interactive to avoid prompts +export DEBIAN_FRONTEND=noninteractive + +apt_get_update + +# On older Ubuntu, Tilix is in a PPA. on Debian stretch its in backports. +if [[ -z $(apt-cache --names-only search ^tilix$) ]]; then + . /etc/os-release + if [ "${ID}" = "ubuntu" ]; then + check_packages apt-transport-https software-properties-common + add-apt-repository -y ppa:webupd8team/terminix + elif [ "${VERSION_CODENAME}" = "stretch" ]; then + echo "deb http://deb.debian.org/debian stretch-backports main" > /etc/apt/sources.list.d/stretch-backports.list + fi + apt-get update + if [[ -z $(apt-cache --names-only search ^tilix$) ]]; then + echo "(!) WARNING: Tilix not available on ${ID} ${VERSION_CODENAME} architecture $(uname -m). Skipping." + else + package_list="${package_list} tilix" + fi +else + package_list="${package_list} tilix" +fi + +# Install X11, fluxbox and VS Code dependencies +check_packages ${package_list} + +# On newer versions of Ubuntu (22.04), +# we need an additional package that isn't provided in earlier versions +if ! type vncpasswd > /dev/null 2>&1; then + check_packages ${package_list_additional} +fi + +# Install Emoji font if available in distro - Available in Debian 10+, Ubuntu 18.04+ +if dpkg-query -W fonts-noto-color-emoji > /dev/null 2>&1 && ! dpkg -s fonts-noto-color-emoji > /dev/null 2>&1; then + apt-get -y install --no-install-recommends fonts-noto-color-emoji +fi + +# Check at least one locale exists +if ! grep -o -E '^\s*en_US.UTF-8\s+UTF-8' /etc/locale.gen > /dev/null; then + echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen + locale-gen +fi + +# Install the Cascadia Code fonts - https://github.com/microsoft/cascadia-code +if [ ! -d "/usr/share/fonts/truetype/cascadia" ]; then + curl -sSL https://github.com/microsoft/cascadia-code/releases/download/v2008.25/CascadiaCode-2008.25.zip -o /tmp/cascadia-fonts.zip + unzip /tmp/cascadia-fonts.zip -d /tmp/cascadia-fonts + mkdir -p /usr/share/fonts/truetype/cascadia + mv /tmp/cascadia-fonts/ttf/* /usr/share/fonts/truetype/cascadia/ + rm -rf /tmp/cascadia-fonts.zip /tmp/cascadia-fonts +fi + +# Install noVNC +if [ "${INSTALL_NOVNC}" = "true" ] && [ ! -d "/usr/local/novnc" ]; then + mkdir -p /usr/local/novnc + curl -sSL https://github.com/novnc/noVNC/archive/v${NOVNC_VERSION}.zip -o /tmp/novnc-install.zip + unzip /tmp/novnc-install.zip -d /usr/local/novnc + cp /usr/local/novnc/noVNC-${NOVNC_VERSION}/vnc.html /usr/local/novnc/noVNC-${NOVNC_VERSION}/index.html + curl -sSL https://github.com/novnc/websockify/archive/v${WEBSOCKETIFY_VERSION}.zip -o /tmp/websockify-install.zip + unzip /tmp/websockify-install.zip -d /usr/local/novnc + ln -s /usr/local/novnc/websockify-${WEBSOCKETIFY_VERSION} /usr/local/novnc/noVNC-${NOVNC_VERSION}/utils/websockify + rm -f /tmp/websockify-install.zip /tmp/novnc-install.zip + + # Install noVNC dependencies and use them. + check_packages python3-minimal python3-numpy + sed -i -E 's/^python /python3 /' /usr/local/novnc/websockify-${WEBSOCKETIFY_VERSION}/run +fi + +# Set up folders for scripts and init files +mkdir -p /var/run/dbus /usr/local/etc/vscode-dev-containers/ +sudo cp .devcontainer/first-run-notice.txt /usr/local/etc/vscode-dev-containers/ + +# Script to change resolution of desktop +cat << EOF > /usr/local/bin/set-resolution +#!/bin/bash +RESOLUTION=\${1:-\${VNC_RESOLUTION:-1920x1080}} +DPI=\${2:-\${VNC_DPI:-96}} +IGNORE_ERROR=\${3:-"false"} +if [ -z "\$1" ]; then + echo -e "**Current Settings **\n" + xrandr + echo -n -e "\nEnter new resolution (WIDTHxHEIGHT, blank for \${RESOLUTION}, Ctrl+C to abort).\n> " + read NEW_RES + if [ "\${NEW_RES}" != "" ]; then + RESOLUTION=\${NEW_RES} + fi + if ! echo "\${RESOLUTION}" | grep -E '[0-9]+x[0-9]+' > /dev/null; then + echo -e "\nInvalid resolution format!\n" + exit 1 + fi + if [ -z "\$2" ]; then + echo -n -e "\nEnter new DPI (blank for \${DPI}, Ctrl+C to abort).\n> " + read NEW_DPI + if [ "\${NEW_DPI}" != "" ]; then + DPI=\${NEW_DPI} + fi + fi +fi + +xrandr --fb \${RESOLUTION} --dpi \${DPI} > /dev/null 2>&1 + +if [ \$? -ne 0 ] && [ "\${IGNORE_ERROR}" != "true" ]; then + echo -e "\nFAILED TO SET RESOLUTION!\n" + exit 1 +fi + +echo -e "\nSuccess!\n" +EOF + +# Container ENTRYPOINT script +cat << EOF > /usr/local/share/desktop-init.sh +#!/bin/bash + +user_name="${USERNAME}" +group_name="$(id -gn ${USERNAME})" +LOG=/tmp/container-init.log + +export DBUS_SESSION_BUS_ADDRESS="${DBUS_SESSION_BUS_ADDRESS:-"autolaunch:"}" +export DISPLAY=:1 +export VNC_RESOLUTION="${VNC_RESOLUTION:-1440x768x16}" +export LANG="${LANG:-"en_US.UTF-8"}" +export LANGUAGE="${LANGUAGE:-"en_US.UTF-8"}" + +# Execute the command it not already running +startInBackgroundIfNotRunning() +{ + log "Starting \$1." + echo -e "\n** \$(date) **" | sudoIf tee -a /tmp/\$1.log > /dev/null + if ! pgrep -x \$1 > /dev/null; then + keepRunningInBackground "\$@" + while ! pgrep -x \$1 > /dev/null; do + sleep 1 + done + log "\$1 started." + else + echo "\$1 is already running." | sudoIf tee -a /tmp/\$1.log > /dev/null + log "\$1 is already running." + fi +} + +# Keep command running in background +keepRunningInBackground() +{ + (\$2 bash -c "while :; do echo [\\\$(date)] Process started.; \$3; echo [\\\$(date)] Process exited!; sleep 5; done 2>&1" | sudoIf tee -a /tmp/\$1.log > /dev/null & echo "\$!" | sudoIf tee /tmp/\$1.pid > /dev/null) +} + +# Use sudo to run as root when required +sudoIf() +{ + if [ "\$(id -u)" -ne 0 ]; then + sudo "\$@" + else + "\$@" + fi +} + +# Use sudo to run as non-root user if not already running +sudoUserIf() +{ + if [ "\$(id -u)" -eq 0 ] && [ "\${user_name}" != "root" ]; then + sudo -u \${user_name} "\$@" + else + "\$@" + fi +} + +# Log messages +log() +{ + echo -e "[\$(date)] \$@" | sudoIf tee -a \$LOG > /dev/null +} + +log "** SCRIPT START **" + +# Start dbus. +log 'Running "/etc/init.d/dbus start".' +if [ -f "/var/run/dbus/pid" ] && ! pgrep -x dbus-daemon > /dev/null; then + sudoIf rm -f /var/run/dbus/pid +fi +sudoIf /etc/init.d/dbus start 2>&1 | sudoIf tee -a /tmp/dbus-daemon-system.log > /dev/null +while ! pgrep -x dbus-daemon > /dev/null; do + sleep 1 +done + +# Startup tigervnc server and fluxbox +sudoIf rm -rf /tmp/.X11-unix /tmp/.X*-lock +mkdir -p /tmp/.X11-unix +sudoIf chmod 1777 /tmp/.X11-unix +sudoIf chown root:\${group_name} /tmp/.X11-unix +if [ "\$(echo "\${VNC_RESOLUTION}" | tr -cd 'x' | wc -c)" = "1" ]; then VNC_RESOLUTION=\${VNC_RESOLUTION}x16; fi +screen_geometry="\${VNC_RESOLUTION%*x*}" +screen_depth="\${VNC_RESOLUTION##*x}" +startInBackgroundIfNotRunning "Xtigervnc" sudoUserIf "tigervncserver \${DISPLAY} -geometry \${screen_geometry} -depth \${screen_depth} -rfbport ${VNC_PORT} -dpi \${VNC_DPI:-96} -localhost -desktop fluxbox -fg -SecurityTypes none" + +# Spin up noVNC if installed and not running. +if [ -d "/usr/local/novnc" ] && [ "\$(ps -ef | grep /usr/local/novnc/noVNC*/utils/novnc_proxy | grep -v grep)" = "" ]; then + keepRunningInBackground "noVNC" sudoIf "/usr/local/novnc/noVNC*/utils/novnc_proxy --listen ${NOVNC_PORT} --vnc localhost:${VNC_PORT}" + log "noVNC started." +else + log "noVNC is already running or not installed." +fi + +# Set fox as wallpaper +fbsetbg -c /workspaces/metamask-extension/app/images/icon-512.png + +git config --global pull.rebase true + +# Run whatever was passed in +log "Executing \"\$@\"." +exec "\$@" +log "** SCRIPT EXIT **" +EOF + +echo "${VNC_PASSWORD}" | vncpasswd -f > /usr/local/etc/vscode-dev-containers/vnc-passwd +chmod +x /usr/local/share/desktop-init.sh /usr/local/bin/set-resolution +chgrp -R codespace . +chown -R codespace . + +# Set up fluxbox config +copy_fluxbox_config "/root" +if [ "${USERNAME}" != "root" ]; then + copy_fluxbox_config "/home/${USERNAME}" + chown -R ${USERNAME} /home/${USERNAME}/.Xmodmap /home/${USERNAME}/.fluxbox +fi + +# Clean up +rm -rf /var/lib/apt/lists/* + +cat << EOF + + +You now have a working desktop! Connect to in one of the following ways: + +- Forward port ${NOVNC_PORT} and use a web browser start the noVNC client (recommended) +- Forward port ${VNC_PORT} using VS Code client and connect using a VNC Viewer + +In both cases, use the password "${VNC_PASSWORD}" when connecting + +(*) Done! + +EOF \ No newline at end of file diff --git a/.devcontainer/launch-firefox.ts b/.devcontainer/launch-firefox.ts new file mode 100644 index 000000000000..c00719fd15b8 --- /dev/null +++ b/.devcontainer/launch-firefox.ts @@ -0,0 +1,10 @@ +import { Builder } from 'selenium-webdriver'; +import Firefox from 'selenium-webdriver/firefox'; + +const options: any = new Firefox.Options().setAcceptInsecureCerts(true); + +const builder = new Builder().forBrowser('firefox').setFirefoxOptions(options); + +const driver: any = builder.build(); + +driver.installAddon('./dist/firefox', true); diff --git a/.devcontainer/setup-browsers.ts b/.devcontainer/setup-browsers.ts new file mode 100644 index 000000000000..82a60693b7a8 --- /dev/null +++ b/.devcontainer/setup-browsers.ts @@ -0,0 +1,27 @@ +import { execSync } from 'child_process'; + +function addBrowserToPath(browserName: string) { + const seleniumOutput = execSync( + `node_modules/selenium-webdriver/bin/linux/selenium-manager --browser ${browserName}`, + ).toString(); + + let browserCommand = seleniumOutput.split('Browser path: ')[1]; + browserCommand = browserCommand.slice(0, -1); // cut off the newline + + execSync(`sudo ln -sf ${browserCommand} /usr/local/bin/${browserName}-ln`); +} + +addBrowserToPath('chrome'); +addBrowserToPath('firefox'); + +execSync( + `sudo bash -c "echo -e '#! /bin/bash\n/usr/local/bin/chrome-ln --load-extension=${process.cwd()}/dist/chrome' > /usr/local/bin/chrome-mm"`, +); + +execSync('sudo chmod 777 /usr/local/bin/chrome-mm'); + +execSync( + `sudo bash -c "echo -e '#! /bin/bash\ncd /workspaces/metamask-extension\nyarn tsx .devcontainer/launch-firefox.ts' > /usr/local/bin/firefox-mm"`, +); + +execSync('sudo chmod 777 /usr/local/bin/firefox-mm'); diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7073870aba1f..dcc43149b8b0 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -40,3 +40,7 @@ privacy-snapshot.json @MetaMask/extension-privacy-reviewers # that agreement and can only be approved by those with the knowledge # and responsibility to publish libraries under the MetaMask name. .github/CODEOWNERS @MetaMask/library-admins @kumavis + +# For now, restricting approvals inside the .devcontainer folder to devs +# who were involved with the Codespaces project. +.devcontainer/ @MetaMask/library-admins @HowardBraham @plasmacorral @brad-decker diff --git a/.github/pull-request-template.md b/.github/pull-request-template.md index d85eb69ba4f9..0c046fb560d8 100644 --- a/.github/pull-request-template.md +++ b/.github/pull-request-template.md @@ -6,6 +6,8 @@ Write a short description of the changes included in this pull request, also inc 2. What is the improvement/solution? --> +[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/PR?quickstart=1) + ## **Related issues** Fixes: diff --git a/.github/workflows/codespaces.yml b/.github/workflows/codespaces.yml new file mode 100644 index 000000000000..68f538dee512 --- /dev/null +++ b/.github/workflows/codespaces.yml @@ -0,0 +1,27 @@ +name: 'Codespaces: update yarn cache' + +on: + push: + branches: + - 'codespaces**' + - 'develop' + paths: + - '**/yarn.lock' + +jobs: + yarn-cache: + name: Generate cache image + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 # This retrieves only the latest commit. + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + + - run: yarn --immutable diff --git a/.github/workflows/fitness-functions.yml b/.github/workflows/fitness-functions.yml index 87e0bff2c96a..f80d27ad0577 100644 --- a/.github/workflows/fitness-functions.yml +++ b/.github/workflows/fitness-functions.yml @@ -10,12 +10,12 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Use Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' cache: yarn @@ -33,3 +33,28 @@ jobs: # then saved to a file called "diff". git diff "$(git merge-base "origin/$BASE_REF" HEAD)" HEAD -- . > ./diff npm run fitness-functions -- "ci" "./diff" + + update-codespaces-badge: + runs-on: ubuntu-latest + permissions: + pull-requests: write + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + + # This use of an ENV variable is neccessary to avoid an injection attack, see: + # https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions + BODY: ${{ github.event.pull_request.body }} + + steps: + - name: Replace the text in the Codespaces badge with the PR number + run: | + #!/bin/bash + + # Bash replace syntax + NEW_BODY="${BODY/PR?quickstart=1/$PR_NUMBER?quickstart=1}" + + # If the body has changed as a result of the replace, use the GitHub API to update the PR + if [ "$NEW_BODY" != "$BODY" ]; then + gh api /repos/MetaMask/metamask-extension/pulls/${{github.event.pull_request.number}} -f body="$NEW_BODY" + fi diff --git a/.gitignore b/.gitignore index 51d7cff64d71..642526e93a27 100644 --- a/.gitignore +++ b/.gitignore @@ -30,7 +30,7 @@ storybook-build/ coverage/ jest-coverage/ dist -builds/ +builds*/ builds.zip development/ts-migration-dashboard/build diff --git a/.storybook/test-data.js b/.storybook/test-data.js index 5f71cb064dcc..ec94784b9dfa 100644 --- a/.storybook/test-data.js +++ b/.storybook/test-data.js @@ -4,6 +4,7 @@ import { NetworkType } from '@metamask/controller-utils'; import { NetworkStatus } from '@metamask/network-controller'; import { EthAccountType, EthMethod } from '@metamask/keyring-api'; import { CHAIN_IDS } from '../shared/constants/network'; +import { copyable, divider, heading, panel, text } from '@metamask/snaps-sdk'; const state = { invalidCustomNetwork: { @@ -277,6 +278,24 @@ const state = { ], }, }, + interfaces: { + 'test-interface': { + content: panel([ + heading('Foo bar'), + text('Description'), + divider(), + text('More text'), + copyable('Text you can copy'), + ]), + state: {}, + snapId: 'local:http://localhost:8080/', + }, + 'error-interface': { + content: 'foo', + state: {}, + snapId: 'local:http://localhost:8080/', + }, + }, accountArray: [ { name: 'This is a Really Long Account Name', diff --git a/.yarn/patches/@metamask-assets-controllers-npm-24.0.0-dfef136464.patch b/.yarn/patches/@metamask-assets-controllers-npm-24.0.0-dfef136464.patch new file mode 100644 index 000000000000..ba44b083d53b --- /dev/null +++ b/.yarn/patches/@metamask-assets-controllers-npm-24.0.0-dfef136464.patch @@ -0,0 +1,13 @@ +diff --git a/dist/NftDetectionController.js b/dist/NftDetectionController.js +index aad63ecf91b2037a40dfb435e0363ab588d22fa3..24373e328d3600d1168914a3dc0bbbd905b19ebe 100644 +--- a/dist/NftDetectionController.js ++++ b/dist/NftDetectionController.js +@@ -65,8 +65,6 @@ class NftDetectionController extends polling_controller_1.StaticIntervalPollingC + if (selectedAddress !== previouslySelectedAddress || + !useNftDetection !== disabled) { + this.configure({ selectedAddress, disabled: !useNftDetection }); +- } +- if (useNftDetection !== undefined) { + if (useNftDetection) { + this.start(); + } diff --git a/.yarn/patches/tar-stream-npm-3.1.6-ce3ac17e49.patch b/.yarn/patches/tar-stream-npm-3.1.6-ce3ac17e49.patch deleted file mode 100644 index 27a502f71566..000000000000 --- a/.yarn/patches/tar-stream-npm-3.1.6-ce3ac17e49.patch +++ /dev/null @@ -1,22 +0,0 @@ -diff --git a/headers.js b/headers.js -index 56c3e9b26844d3e10a805cf850cdd09a172fa710..aa1a6c45d1623d9b94a5e065bd2872f0c4be4915 100644 ---- a/headers.js -+++ b/headers.js -@@ -34,7 +34,7 @@ exports.decodePax = function decodePax (buf) { - while (buf.length) { - let i = 0 - while (i < buf.length && buf[i] !== 32) i++ -- const len = parseInt(buf.subarray(0, i).toString(), 10) -+ const len = parseInt(b4a.toString(buf.subarray(0, i)), 10) - if (!len) return result - - const b = b4a.toString(buf.subarray(i + 1, len - 1)) -@@ -304,7 +304,7 @@ function decodeOct (val, offset, length) { - const end = clamp(indexOf(val, 32, offset, val.length), val.length, val.length) - while (offset < end && val[offset] === 0) offset++ - if (end === offset) return 0 -- return parseInt(val.subarray(offset, end).toString(), 8) -+ return parseInt(b4a.toString(val.subarray(offset, end)), 8) - } - } - diff --git a/CHANGELOG.md b/CHANGELOG.md index 802af6f48c6c..18db909ce3ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [11.10.0] +### Added +- Added preset network image avatars in the 'Select a network' pop-up ([#22643](https://github.com/MetaMask/metamask-extension/pull/22643)) +- Expanded Blockaid banner support to include send requests from wallets ([#22321](https://github.com/MetaMask/metamask-extension/pull/22321)) +- Expanded Blockaid banner support to include BNB chain, Polygon, Arbitrum, Optimism, Avalanche, and Linea networks ([#22633](https://github.com/MetaMask/metamask-extension/pull/22633)) +- Added support for imToken Wallet using EIP-4527 ([#21804](https://github.com/MetaMask/metamask-extension/pull/21804)) +- [FLASK] Introduced user operation support, enhancing transaction handling and alert display for user operations ([#22469](https://github.com/MetaMask/metamask-extension/pull/22469)) +- [FLASK] Added support for the Lattice hardware wallet in MV3 ([#22528](https://github.com/MetaMask/metamask-extension/pull/22528)) +- [FLASK] Added a 'You're sending to a contract' warning to the new send page ([#22551](https://github.com/MetaMask/metamask-extension/pull/22551)) + +### Changed +- Improved error messaging for Ledger connection issues ([#21038](https://github.com/MetaMask/metamask-extension/pull/21038)) +- Updated 'What's New' and 'Settings' to communicate the extension's additional network coverage ([#22618](https://github.com/MetaMask/metamask-extension/pull/22618)) +- Enhanced Token List layout for clearer token name and value display ([#22601](https://github.com/MetaMask/metamask-extension/pull/22601)) +- Updated the date for the upcoming user survey displayed on the home screen ([#22581](https://github.com/MetaMask/metamask-extension/pull/22581)) +- Improved QR code density for compatibility with the ZERO hardware wallet ([#22135](https://github.com/MetaMask/metamask-extension/pull/22135)) +- Updated snaps packages to the latest versions ([#22595](https://github.com/MetaMask/metamask-extension/pull/22595)) +- Updated the Gas API URL to ensure accurate gas fee calculations for Send and Swap transactions ([#22544](https://github.com/MetaMask/metamask-extension/pull/22544)) + +### Fixed +- Fixed an issue where clicking on the redirection link did not close the notification window ([#22583](https://github.com/MetaMask/metamask-extension/pull/22583)) +- Improved Blockaid 'What's New' image display for users in OS dark mode ([#22649](https://github.com/MetaMask/metamask-extension/pull/22649)) +- Added Blockaid dark mode support in the 'What's New' feature and introduced a new theme management utility ([#22613](https://github.com/MetaMask/metamask-extension/pull/22613)) +- Ensured all security alert banners correctly trigger events ([#22553](https://github.com/MetaMask/metamask-extension/pull/22553)) +- Updated 'What's New' Blockaid image to have a transparent background ([#22539](https://github.com/MetaMask/metamask-extension/pull/22539)) +- Fixed unresponsiveness on the phishing warning page ([#22645](https://github.com/MetaMask/metamask-extension/pull/22645)) +- Improved Trezor integration by updating to the correct version of the SDK ([#22591](https://github.com/MetaMask/metamask-extension/pull/22591)) +- Improved account list links to display the correct explorer domain based on the selected network ([#22483](https://github.com/MetaMask/metamask-extension/pull/22483)) +- Improved the reliability of snap installations by resolving an underlying technical issue ([#22602](https://github.com/MetaMask/metamask-extension/pull/22602)) + ## [11.9.5] ### Fixed - Fixed sometimes failing confirmation screen security validation checks ([$22978](https://github.com/MetaMask/metamask-extension/pull/22978)) @@ -4359,7 +4389,8 @@ Update styles and spacing on the critical error page ([#20350](https://github.c ### Uncategorized - Added the ability to restore accounts from seed words. -[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v11.9.5...HEAD +[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v11.10.0...HEAD +[11.10.0]: https://github.com/MetaMask/metamask-extension/compare/v11.9.5...v11.10.0 [11.9.5]: https://github.com/MetaMask/metamask-extension/compare/v11.9.4...v11.9.5 [11.9.4]: https://github.com/MetaMask/metamask-extension/compare/v11.9.3...v11.9.4 [11.9.3]: https://github.com/MetaMask/metamask-extension/compare/v11.9.2...v11.9.3 diff --git a/README.md b/README.md index 0d802f5a6542..aa24e4e5c396 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,38 @@ To learn how to develop MetaMask-compatible applications, visit our [Developer D To learn how to contribute to the MetaMask project itself, visit our [Internal Docs](https://github.com/MetaMask/metamask-extension/tree/develop/docs). -## Building locally +## GitHub Codespaces quickstart + +As an alternative to building on your local machine, there is a new option to get a development environment up and running in less than 5 minutes by using GitHub Codespaces. Please note that there is a [Limited Free Monthly Quota](https://docs.github.com/en/billing/managing-billing-for-github-codespaces/about-billing-for-github-codespaces), and after that GitHub will start charging you. + +_Note: You are billed for both time spent running, and for storage used_ + +[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension?quickstart=1) + +1. Start by clicking the button above +2. A new browser tab will open with a remote version of Visual Studio Code (this will take a few minutes to load) +3. A "Simple Browser" will open inside the browser with noVNC -- click Connect + - Optional steps: + - Click the button at the upper-right of the Simple Browser tab to open the noVNC window in its own tab + - Open the noVNC sidebar on the left, click the gear icon, change the Scaling Mode to Remote Resizing +4. Wait about 20 extra seconds on the first launch, for the scripts to finish +5. Right-click on the noVNC desktop to launch Chrome or Firefox with MetaMask pre-installed +6. Change some code, then run `yarn start` to build in dev mode +7. After a minute or two, it will finish building, and you can see your changes in the noVNC desktop + +### Tips to keep your Codespaces usage lower + +- You are billed for both time spent running, and for storage used +- Codespaces pause after 30 minutes of inactivity, and auto-delete after 30 days of inactivity +- You can manage your Codespaces here: https://github.com/codespaces + - You may want to manually pause them before the 30 minute timeout + - If you have several idle Codespaces hanging around for several days, you can quickly run out of storage quota. You should delete the ones you do not plan to use anymore, and probably keep only 1 or 2 in the long-term. It's also possible to re-use old Codespaces and switch the branch, instead of creating new ones and deleting the old ones. + +### Codespaces on a fork + +If you are not a MetaMask Internal Developer, or are otherwise developing on a fork, the default Infura key will be on the Free Plan and have very limited requests per second. If you want to use your own Infura key, follow the `.metamaskrc` and `INFURA_PROJECT_ID` instructions in the section [Building on your local machine](#building-on-your-local-machine). + +## Building on your local machine - Install [Node.js](https://nodejs.org) version 20 - If you are using [nvm](https://github.com/nvm-sh/nvm#installing-and-updating) (recommended) running `nvm use` will automatically choose the right node version for you. @@ -25,6 +56,7 @@ To learn how to contribute to the MetaMask project itself, visit our [Internal D - Optionally, replace the `PASSWORD` value with your development wallet password to avoid entering it each time you open the app. - Run `yarn install` to install the dependencies. - Build the project to the `./dist/` folder with `yarn dist`. + - Optionally, you may run `yarn start` to run dev mode. - Uncompressed builds can be found in `/dist`, compressed builds can be found in `/builds` once they're built. - See the [build system readme](./development/build/README.md) for build system usage information. @@ -33,7 +65,6 @@ To learn how to contribute to the MetaMask project itself, visit our [Internal D - [How to add custom build to Chrome](./docs/add-to-chrome.md) - [How to add custom build to Firefox](./docs/add-to-firefox.md) - ## Git Hooks To get quick feedback from our shared code quality fitness functions before committing the code, you can install our git hooks with Husky. @@ -111,16 +142,18 @@ For example, to run the `account-details` tests using Chrome, with debug logging #### Running specific builds types e2e test -Different build types have different e2e tests sets. In order to run them look in the `packaje.json` file. You will find: +Different build types have different e2e tests sets. In order to run them look in the `package.json` file. You will find: + ```console "test:e2e:chrome:mmi": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js --mmi", "test:e2e:chrome:snaps": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js --snaps", "test:e2e:chrome:mv3": "ENABLE_MV3=true SELENIUM_BROWSER=chrome node test/e2e/run-all.js", ``` - #### Note: Running MMI e2e tests + When running e2e on an MMI build you need to know that there are 2 separated set of tests: + - MMI runs a subset of MetaMask's e2e tests. To facilitate this, we have appended the `@no-mmi` tags to the names of those tests that are not applicable to this build type. - MMI runs another specific set of e2e legacy tests which are better documented [here](test/e2e/mmi/README.md) diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index 7b6977e25821..257611b700c5 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -556,7 +556,7 @@ "blockaidDescriptionBlurFarming": { "message": "Wenn Sie diese Anfrage genehmigen, kann jemand Ihre bei Blur aufgelisteten Assets stehlen." }, - "blockaidDescriptionFailed": { + "blockaidDescriptionErrored": { "message": "Aufgrund eines Fehlers wurde diese Anfrage vom Sicherheitsanbieter nicht überprüft. Gehen Sie mit Bedacht vor." }, "blockaidDescriptionMaliciousDomain": { diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json index 00bff68f0a7d..aa32480036bd 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -556,7 +556,7 @@ "blockaidDescriptionBlurFarming": { "message": "Εάν εγκρίνετε αυτό το αίτημα, κάποιος μπορεί να κλέψει τα περιουσιακά σας στοιχεία που είναι καταχωρημένα στο Blur." }, - "blockaidDescriptionFailed": { + "blockaidDescriptionErrored": { "message": "Λόγω κάποιου σφάλματος, αυτό το αίτημα δεν επαληθεύτηκε από τον πάροχο ασφαλείας. Προχωρήστε με προσοχή." }, "blockaidDescriptionMaliciousDomain": { diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 8a8b600b9a89..3f250997e913 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -352,9 +352,6 @@ "alerts": { "message": "Alerts" }, - "allConnections": { - "message": "All Connections" - }, "allCustodianAccountsConnectedSubtitle": { "message": "You have either already connected all your custodian accounts or don’t have any account to connect to MetaMask Institutional." }, @@ -365,6 +362,9 @@ "message": "All of your $1", "description": "$1 is the symbol or name of the token that the user is approving spending" }, + "allPermissions": { + "message": "All Permissions" + }, "allYourNFTsOf": { "message": "All of your NFTs from $1", "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" @@ -601,7 +601,7 @@ "blockaidDescriptionBlurFarming": { "message": "If you approve this request, someone can steal your assets listed on Blur." }, - "blockaidDescriptionFailed": { + "blockaidDescriptionErrored": { "message": "Because of an error, this request was not verified by the security provider. Proceed with caution." }, "blockaidDescriptionMaliciousDomain": { @@ -800,6 +800,9 @@ "connect": { "message": "Connect" }, + "connectAccount": { + "message": "Connect account" + }, "connectAccountOrCreate": { "message": "Connect account or create new" }, @@ -2265,6 +2268,9 @@ "lastSold": { "message": "Last sold" }, + "lavaDomeCopyWarning": { + "message": "For your safety, selecting this text is not available right now." + }, "layer1Fees": { "message": "Layer 1 fees" }, @@ -3488,6 +3494,19 @@ "permissions": { "message": "Permissions" }, + "permissionsPageEmptyContent": { + "message": "Nothing to see here" + }, + "permissionsPageEmptySubContent": { + "message": "This is where you can see the permissions you've given to installed Snaps or connected sites." + }, + + "permissionsPageTourDescription": { + "message": "This is your control panel for managing permissions given to connected sites and installed Snaps." + }, + "permissionsPageTourTitle": { + "message": "Connected sites are now permissions" + }, "permissionsTitle": { "message": "Permissions" }, diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index f7b538aca64a..b53d55eff620 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -556,7 +556,7 @@ "blockaidDescriptionBlurFarming": { "message": "Si aprueba esta solicitud, alguien puede robar sus activos enlistados en Blur." }, - "blockaidDescriptionFailed": { + "blockaidDescriptionErrored": { "message": "Debido a un error, el proveedor de seguridad no verificó esta solicitud. Proceda con precaución." }, "blockaidDescriptionMaliciousDomain": { diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index c1105e208141..d477599060dd 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -556,7 +556,7 @@ "blockaidDescriptionBlurFarming": { "message": "Si vous approuvez cette demande, quelqu’un pourrait s'emparer de vos actifs répertoriés sur Blur." }, - "blockaidDescriptionFailed": { + "blockaidDescriptionErrored": { "message": "À la suite d’une erreur, cette demande n’a pas été vérifiée par le fournisseur de services de sécurité. Veuillez agir avec prudence." }, "blockaidDescriptionMaliciousDomain": { diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json index 85d174911664..50027209b4f5 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -556,7 +556,7 @@ "blockaidDescriptionBlurFarming": { "message": "यदि आप इस रिक्वेस्ट को स्वीकार करते हैं, तो कोई Blur पर लिस्टेड आपके सारे एसेट चुरा सकता है।" }, - "blockaidDescriptionFailed": { + "blockaidDescriptionErrored": { "message": "कोई समस्या होने के कारण, इस रिक्वेस्ट को सिक्यूरिटी प्रोवाइडर द्वारा वेरीफ़ाई नहीं किया गया। सावधानी से आगे बढ़ें।" }, "blockaidDescriptionMaliciousDomain": { diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index 60a00061ec26..fdbd48de0e55 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -556,7 +556,7 @@ "blockaidDescriptionBlurFarming": { "message": "Jika Anda menyetujui permintaan ini, seseorang dapat mencuri aset Anda yang terdaftar di Blur." }, - "blockaidDescriptionFailed": { + "blockaidDescriptionErrored": { "message": "Karena terjadi kesalahan, permintaan ini tidak diverifikasi oleh penyedia keamanan. Lanjutkan dengan hati-hati." }, "blockaidDescriptionMaliciousDomain": { diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index ca9452e7092a..076254176f8b 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -556,7 +556,7 @@ "blockaidDescriptionBlurFarming": { "message": "このリクエストを承認すると、Blurに登録されている資産を誰かに盗まれる可能性があります。" }, - "blockaidDescriptionFailed": { + "blockaidDescriptionErrored": { "message": "エラーが発生したため、このリクエストはセキュリティプロバイダーにより確認されませんでした。慎重に進めてください。" }, "blockaidDescriptionMaliciousDomain": { diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index 03b0bf2861fc..668779dfc90d 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -556,7 +556,7 @@ "blockaidDescriptionBlurFarming": { "message": "이 요청을 승인하면, Blur에 있는 자산을 타인이 갈취할 수 있습니다." }, - "blockaidDescriptionFailed": { + "blockaidDescriptionErrored": { "message": "오류로 인해 보안업체에서 이 요청을 확인하지 못했습니다. 주의하여 진행하세요." }, "blockaidDescriptionMaliciousDomain": { diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json index 67d40144bf57..eac5878b5df5 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -556,7 +556,7 @@ "blockaidDescriptionBlurFarming": { "message": "Se você aprovar essa solicitação, alguém poderá roubar seus ativos listados na Blur." }, - "blockaidDescriptionFailed": { + "blockaidDescriptionErrored": { "message": "Em razão de um erro, essa solicitação não foi confirmada pelo provedor de segurança. Prossiga com cautela." }, "blockaidDescriptionMaliciousDomain": { diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index 993611e4dc9d..857be230dc68 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -556,7 +556,7 @@ "blockaidDescriptionBlurFarming": { "message": "Если вы одобрите этот запрос, кто-то может украсть ваши активы, указанные в Blur." }, - "blockaidDescriptionFailed": { + "blockaidDescriptionErrored": { "message": "Из-за ошибки этот запрос не был подтвержден поставщиком услуг безопасности. Действуйте осторожно." }, "blockaidDescriptionMaliciousDomain": { diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json index 701370abde42..1127db132f1c 100644 --- a/app/_locales/tl/messages.json +++ b/app/_locales/tl/messages.json @@ -556,7 +556,7 @@ "blockaidDescriptionBlurFarming": { "message": "Kung aaprubahan mo ang kahilingang ito, posibleng nakawin ng ibang tao ang mga asset mo na nakalista sa Blur." }, - "blockaidDescriptionFailed": { + "blockaidDescriptionErrored": { "message": "Dahil sa error, hindi na-verify ang kahilingang ito ng tagapagbigay ng seguridad. Magpatuloy nang may pag-iingat." }, "blockaidDescriptionMaliciousDomain": { diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index 8f549b74f9a6..429d838df65d 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -556,7 +556,7 @@ "blockaidDescriptionBlurFarming": { "message": "Bu talebi onaylarsanız birisi Blur üzerinde yer alan varlıklarınızı çalabilir." }, - "blockaidDescriptionFailed": { + "blockaidDescriptionErrored": { "message": "Bu talep bir hatadan dolayı güvenlik sağlayıcısı tarafından doğrulanmadı. Dikkatli bir şekilde ilerleyin." }, "blockaidDescriptionMaliciousDomain": { diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index 1c0386cbb69c..e0705352ea6b 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -556,7 +556,7 @@ "blockaidDescriptionBlurFarming": { "message": "Nếu bạn chấp thuận yêu cầu này, người khác có thể đánh cắp tài sản được niêm yết trên Blur của bạn." }, - "blockaidDescriptionFailed": { + "blockaidDescriptionErrored": { "message": "Do có lỗi, yêu cầu này đã không được nhà cung cấp dịch vụ bảo mật xác minh. Hãy thực hiện cẩn thận." }, "blockaidDescriptionMaliciousDomain": { diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index 99e51271cfe0..7b336018586b 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -556,7 +556,7 @@ "blockaidDescriptionBlurFarming": { "message": "如果您批准此请求,则有人可以窃取您列于Blur上的资产。" }, - "blockaidDescriptionFailed": { + "blockaidDescriptionErrored": { "message": "由于出现错误,安全提供程序无法验证此请求。请谨慎操作。" }, "blockaidDescriptionMaliciousDomain": { diff --git a/app/images/icons/user-circle-link.svg b/app/images/icons/user-circle-link.svg new file mode 100644 index 000000000000..acf97fc5f66b --- /dev/null +++ b/app/images/icons/user-circle-link.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/app/scripts/controllers/app-state.js b/app/scripts/controllers/app-state.js index ce890bb5672f..44d1a367362e 100644 --- a/app/scripts/controllers/app-state.js +++ b/app/scripts/controllers/app-state.js @@ -47,6 +47,7 @@ export default class AppStateController extends EventEmitter { nftsDetectionNoticeDismissed: false, showTestnetMessageInDropdown: true, showBetaHeader: isBeta(), + showPermissionsTour: true, showProductTour: true, showNetworkBanner: true, showAccountBanner: true, @@ -364,6 +365,15 @@ export default class AppStateController extends EventEmitter { this.store.updateState({ showBetaHeader }); } + /** + * Sets whether the permissions tour should be shown to the user + * + * @param showPermissionsTour + */ + setShowPermissionsTour(showPermissionsTour) { + this.store.updateState({ showPermissionsTour }); + } + /** * Sets whether the product tour should be shown * diff --git a/app/scripts/controllers/mmi-controller.js b/app/scripts/controllers/mmi-controller.js index 628c2e32d3aa..4fa767657a9e 100644 --- a/app/scripts/controllers/mmi-controller.js +++ b/app/scripts/controllers/mmi-controller.js @@ -113,6 +113,7 @@ export default class MMIController extends EventEmitter { custodyController: this.custodyController, trackTransactionEvent: this.trackTransactionEventFromCustodianEvent.bind(this), + captureException, }); } @@ -201,12 +202,14 @@ export default class MMIController extends EventEmitter { await this.mmiConfigurationController.storeConfiguration(); } catch (error) { log.error('Error while unlocking extension.', error); + captureException(error); } try { await this.transactionUpdateController.subscribeToEvents(); } catch (error) { log.error('Error while unlocking extension.', error); + captureException(error); } const mmiConfigData = @@ -544,14 +547,18 @@ export default class MMIController extends EventEmitter { } async handleMmiCheckIfTokenIsPresent(req) { - const { token, envName } = req.params; - // TODO (Bernardo) - Check if this is the case - const custodyType = 'Custody - JSONRPC'; // Only JSONRPC is supported for now + const { token, envName, address } = req.params; + + const currentAddress = + address || this.preferencesController.getSelectedAddress(); + const currentCustodyType = this.custodyController.getCustodyTypeByAddress( + toChecksumHexAddress(currentAddress), + ); // This can only work if the extension is unlocked await this.appStateController.getUnlockPromise(true); - const keyring = await this.addKeyringIfNotExists(custodyType); + const keyring = await this.addKeyringIfNotExists(currentCustodyType); return await this.custodyController.handleMmiCheckIfTokenIsPresent({ token, diff --git a/app/scripts/controllers/permissions/specifications.js b/app/scripts/controllers/permissions/specifications.js index cf5b0ebc5638..8d5b8ec8a789 100644 --- a/app/scripts/controllers/permissions/specifications.js +++ b/app/scripts/controllers/permissions/specifications.js @@ -3,8 +3,10 @@ import { PermissionType, } from '@metamask/permission-controller'; ///: BEGIN:ONLY_INCLUDE_IF(snaps) -import { endowmentCaveatSpecifications as snapsEndowmentCaveatSpecifications } from '@metamask/snaps-controllers'; -import { caveatSpecifications as snapsCaveatsSpecifications } from '@metamask/snaps-rpc-methods'; +import { + caveatSpecifications as snapsCaveatsSpecifications, + endowmentCaveatSpecifications as snapsEndowmentCaveatSpecifications, +} from '@metamask/snaps-rpc-methods'; ///: END:ONLY_INCLUDE_IF import { CaveatTypes, diff --git a/app/scripts/controllers/permissions/specifications.test.js b/app/scripts/controllers/permissions/specifications.test.js index aff1055272ad..a90ac4e87e63 100644 --- a/app/scripts/controllers/permissions/specifications.test.js +++ b/app/scripts/controllers/permissions/specifications.test.js @@ -17,7 +17,7 @@ describe('PermissionController specifications', () => { describe('caveat specifications', () => { it('getCaveatSpecifications returns the expected specifications object', () => { const caveatSpecifications = getCaveatSpecifications({}); - expect(Object.keys(caveatSpecifications)).toHaveLength(10); + expect(Object.keys(caveatSpecifications)).toHaveLength(12); expect( caveatSpecifications[CaveatTypes.restrictReturnedAccounts].type, ).toStrictEqual(CaveatTypes.restrictReturnedAccounts); @@ -49,6 +49,12 @@ describe('PermissionController specifications', () => { expect(caveatSpecifications.keyringOrigin.type).toStrictEqual( SnapCaveatType.KeyringOrigin, ); + expect(caveatSpecifications.maxRequestTime.type).toStrictEqual( + SnapCaveatType.MaxRequestTime, + ); + expect(caveatSpecifications.lookupMatchers.type).toStrictEqual( + SnapCaveatType.LookupMatchers, + ); }); describe('restrictReturnedAccounts', () => { diff --git a/app/scripts/lib/SnapsNameProvider.test.ts b/app/scripts/lib/SnapsNameProvider.test.ts index cef4356e379c..6d6f0dd6a120 100644 --- a/app/scripts/lib/SnapsNameProvider.test.ts +++ b/app/scripts/lib/SnapsNameProvider.test.ts @@ -16,6 +16,7 @@ const CHAIN_ID_MOCK = '0x1'; const NAME_MOCK = 'TestName'; const NAME_MOCK_2 = 'TestName2'; const ERROR_MOCK = 'TestError'; +const MOCK_PROTOCOL = 'TestProtocol'; const SNAP_MOCK = { id: 'testSnap1', @@ -126,10 +127,14 @@ describe('SnapsNameProvider', () => { const handleSnapRequest = jest .fn() .mockResolvedValueOnce({ - resolvedDomain: NAME_MOCK, + resolvedDomains: [ + { protocol: MOCK_PROTOCOL, resolvedDomain: NAME_MOCK }, + ], }) .mockResolvedValueOnce({ - resolvedDomain: NAME_MOCK_2, + resolvedDomains: [ + { protocol: MOCK_PROTOCOL, resolvedDomain: NAME_MOCK_2 }, + ], }); const provider = new SnapsNameProvider({ @@ -181,7 +186,9 @@ describe('SnapsNameProvider', () => { throw new Error(ERROR_MOCK); }) .mockResolvedValueOnce({ - resolvedDomain: NAME_MOCK_2, + resolvedDomains: [ + { protocol: MOCK_PROTOCOL, resolvedDomain: NAME_MOCK_2 }, + ], }); const errorMock = new Error('TestError'); diff --git a/app/scripts/lib/SnapsNameProvider.ts b/app/scripts/lib/SnapsNameProvider.ts index 93b0c06cfd90..ca610e474434 100644 --- a/app/scripts/lib/SnapsNameProvider.ts +++ b/app/scripts/lib/SnapsNameProvider.ts @@ -135,9 +135,12 @@ export class SnapsNameProvider implements NameProvider { }, )) as AddressLookupResult; - const domain = result?.resolvedDomain; + const domains = result?.resolvedDomains; - proposedNames = domain ? [domain] : []; + // TODO: Determine if this is what we want. + proposedNames = domains + ? [...new Set(domains.map((domain) => domain.resolvedDomain))] + : []; } catch (error) { log.error('Snap name provider request failed', { snapId: snap.id, diff --git a/app/scripts/lib/createRPCMethodTrackingMiddleware.js b/app/scripts/lib/createRPCMethodTrackingMiddleware.js index d16bb9f74b4f..02bbb7c55df8 100644 --- a/app/scripts/lib/createRPCMethodTrackingMiddleware.js +++ b/app/scripts/lib/createRPCMethodTrackingMiddleware.js @@ -216,6 +216,15 @@ export default function createRPCMethodTrackingMiddleware({ BlockaidResultType.NotApplicable; eventProperties.security_alert_reason = req.securityAlertResponse?.reason ?? BlockaidReason.notApplicable; + + if ( + req.securityAlertResponse?.result_type === + BlockaidResultType.Errored && + req.securityAlertResponse?.description + ) { + eventProperties.security_alert_description = + req.securityAlertResponse.description; + } ///: END:ONLY_INCLUDE_IF const snapAndHardwareInfo = await getSnapAndHardwareInfoForMetrics( diff --git a/app/scripts/lib/ppom/ppom-middleware.test.ts b/app/scripts/lib/ppom/ppom-middleware.test.ts index 281506421605..ff21e4502806 100644 --- a/app/scripts/lib/ppom/ppom-middleware.test.ts +++ b/app/scripts/lib/ppom/ppom-middleware.test.ts @@ -35,10 +35,16 @@ const createMiddleWare = ( const networkController = { state: { providerConfig: { chainId: chainId || CHAIN_IDS.MAINNET } }, }; + const appStateController = { + addSignatureSecurityAlertResponse: () => undefined, + }; + return createPPOMMiddleware( ppomController as any, preferenceController as any, networkController as any, + appStateController as any, + () => undefined, ); }; @@ -87,7 +93,7 @@ describe('PPOMMiddleware', () => { expect(req.securityAlertResponse).toBeUndefined(); }); - it('should set Failed type in response if usePPOM throw error', async () => { + it('should set error type in response if usePPOM throw error', async () => { const usePPOM = async () => { throw new Error('some error'); }; @@ -98,10 +104,10 @@ describe('PPOMMiddleware', () => { }; await middlewareFunction(req, undefined, () => undefined); expect((req.securityAlertResponse as any)?.result_type).toBe( - BlockaidResultType.Failed, + BlockaidResultType.Errored, ); expect((req.securityAlertResponse as any)?.reason).toBe( - BlockaidReason.failed, + BlockaidReason.errored, ); }); diff --git a/app/scripts/lib/ppom/ppom-middleware.ts b/app/scripts/lib/ppom/ppom-middleware.ts index 4f742e4af078..87c25d6f1fae 100644 --- a/app/scripts/lib/ppom/ppom-middleware.ts +++ b/app/scripts/lib/ppom/ppom-middleware.ts @@ -73,15 +73,16 @@ export function createPPOMMiddleware( .usePPOM(async (ppom: PPOM) => { try { const securityAlertResponse = await ppom.validateJsonRpc(req); + securityAlertResponse.securityAlertId = securityAlertId; return securityAlertResponse; } catch (error: any) { sentry?.captureException(error); + const errorObject = error as unknown as Error; console.error('Error validating JSON RPC using PPOM: ', error); const securityAlertResponse = { - result_type: BlockaidResultType.Failed, - reason: BlockaidReason.failed, - description: - 'Validating the confirmation failed by throwing error.', + result_type: BlockaidResultType.Errored, + reason: BlockaidReason.errored, + description: `${errorObject.name}: ${errorObject.message}`, }; return securityAlertResponse; @@ -114,12 +115,13 @@ export function createPPOMMiddleware( } } } catch (error: any) { + const errorObject = error as unknown as Error; sentry?.captureException(error); console.error('Error validating JSON RPC using PPOM: ', error); req.securityAlertResponse = { - result_type: BlockaidResultType.Failed, - reason: BlockaidReason.failed, - description: 'Validating the confirmation failed by throwing error.', + result_type: BlockaidResultType.Errored, + reason: BlockaidReason.errored, + description: `${errorObject.name}: ${errorObject.message}`, }; } finally { next(); diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index 8e650e6a674c..9a0eb9e9199d 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -85,6 +85,7 @@ export const SENTRY_BACKGROUND_STATE = { recoveryPhraseReminderHasBeenShown: true, recoveryPhraseReminderLastShown: true, showBetaHeader: true, + showPermissionsTour: true, showProductTour: true, showNetworkBanner: true, showAccountBanner: true, @@ -170,7 +171,6 @@ export const SENTRY_BACKGROUND_STATE = { seedPhraseBackedUp: true, }, PPOMController: { - chainStatus: true, securityAlertsEnabled: false, storageMetadata: [], versionFileETag: false, @@ -251,6 +251,9 @@ export const SENTRY_BACKGROUND_STATE = { snapStates: false, snaps: false, }, + SnapInterface: { + interfaces: false, + }, SnapsRegistry: { database: false, lastUpdated: false, diff --git a/app/scripts/lib/transaction/util.ts b/app/scripts/lib/transaction/util.ts index cb621794a08f..5c0516eb7c2e 100644 --- a/app/scripts/lib/transaction/util.ts +++ b/app/scripts/lib/transaction/util.ts @@ -119,7 +119,7 @@ const PPOM_EXCLUDED_TRANSACTION_TYPES = [ export async function addTransaction( request: AddTransactionRequest, ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - updateSecurityAlertResponseByTxId: ( + updateSecurityAlertResponseByTxId?: ( req: AddTransactionOptions | undefined, securityAlertResponse: SecurityAlertResponse, ) => void, @@ -169,19 +169,19 @@ export async function addTransaction( return securityAlertResponse; } catch (e) { captureException(e); + const errorObject = e as unknown as Error; console.error('Error validating JSON RPC using PPOM: ', e); const securityAlertResponse = { securityAlertId, - result_type: BlockaidResultType.Failed, - reason: BlockaidReason.failed, - description: - 'Validating the confirmation failed by throwing error.', + result_type: BlockaidResultType.Errored, + reason: BlockaidReason.errored, + description: `${errorObject.name}: ${errorObject.message}`, }; return securityAlertResponse; } }) .then((securityAlertResponse) => { - updateSecurityAlertResponseByTxId(request.transactionOptions, { + updateSecurityAlertResponseByTxId?.(request.transactionOptions, { ...securityAlertResponse, securityAlertId, }); @@ -193,6 +193,7 @@ export async function addTransaction( securityAlertId, }; } catch (e) { + console.error('Error validating JSON RPC using PPOM: ', e); captureException(e); } } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 3073cc8b84a8..4f9d2e67dea1 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -81,10 +81,13 @@ import { JsonSnapsRegistry, SnapController, IframeExecutionService, + SnapInterfaceController, +} from '@metamask/snaps-controllers'; +import { + createSnapsMethodMiddleware, buildSnapEndowmentSpecifications, buildSnapRestrictedMethodSpecifications, -} from '@metamask/snaps-controllers'; -import { createSnapsMethodMiddleware } from '@metamask/snaps-rpc-methods'; +} from '@metamask/snaps-rpc-methods'; ///: END:ONLY_INCLUDE_IF import { AccountsController } from '@metamask/accounts-controller'; @@ -1173,8 +1176,6 @@ export default class MetamaskController extends EventEmitter { `${this.permissionController.name}:grantPermissions`, `${this.subjectMetadataController.name}:getSubjectMetadata`, `${this.subjectMetadataController.name}:addSubjectMetadata`, - `${this.phishingController.name}:maybeUpdateState`, - `${this.phishingController.name}:testOrigin`, 'ExecutionService:executeSnap', 'ExecutionService:getRpcRequestHandler', 'ExecutionService:terminateSnap', @@ -1184,6 +1185,8 @@ export default class MetamaskController extends EventEmitter { 'SnapsRegistry:getMetadata', 'SnapsRegistry:update', 'SnapsRegistry:resolveVersion', + `SnapInterfaceController:createInterface`, + `SnapInterfaceController:getInterface`, ], }); @@ -1280,6 +1283,7 @@ export default class MetamaskController extends EventEmitter { allowedEvents: [], allowedActions: [], }); + this.snapsRegistry = new JsonSnapsRegistry({ state: initState.SnapsRegistry, messenger: snapsRegistryMessenger, @@ -1293,6 +1297,20 @@ export default class MetamaskController extends EventEmitter { '0x025b65308f0f0fb8bc7f7ff87bfc296e0330eee5d3c1d1ee4a048b2fd6a86fa0a6', }); + const snapInterfaceControllerMessenger = + this.controllerMessenger.getRestricted({ + name: 'SnapInterfaceController', + allowedActions: [ + `${this.phishingController.name}:maybeUpdateState`, + `${this.phishingController.name}:testOrigin`, + ], + }); + + this.snapInterfaceController = new SnapInterfaceController({ + state: initState.SnapInterfaceController, + messenger: snapInterfaceControllerMessenger, + }); + ///: END:ONLY_INCLUDE_IF // account tracker watches balances, nonces, and any code at their address @@ -1396,6 +1414,7 @@ export default class MetamaskController extends EventEmitter { ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) this.custodyController = new CustodyController({ initState: initState.CustodyController, + captureException, }); this.institutionalFeaturesController = new InstitutionalFeaturesController({ initState: initState.InstitutionalFeaturesController, @@ -1941,6 +1960,7 @@ export default class MetamaskController extends EventEmitter { CronjobController: this.cronjobController, SnapsRegistry: this.snapsRegistry, NotificationController: this.notificationController, + SnapInterfaceController: this.snapInterfaceController, ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(desktop) DesktopController: this.desktopController.store, @@ -1992,6 +2012,7 @@ export default class MetamaskController extends EventEmitter { CronjobController: this.cronjobController, SnapsRegistry: this.snapsRegistry, NotificationController: this.notificationController, + SnapInterfaceController: this.snapInterfaceController, ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(desktop) DesktopController: this.desktopController.store, @@ -2250,11 +2271,11 @@ export default class MetamaskController extends EventEmitter { this.controllerMessenger, 'SnapController:getSnapState', ), - showDialog: (origin, type, content, placeholder) => + showDialog: (origin, type, id, placeholder) => this.approvalController.addAndShowApprovalRequest({ origin, type: SNAP_DIALOG_TYPES[type], - requestData: { content, placeholder }, + requestData: { id, placeholder }, }), showNativeNotification: (origin, args) => this.controllerMessenger.call( @@ -2301,6 +2322,14 @@ export default class MetamaskController extends EventEmitter { origin, ).result; }, + createInterface: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SnapInterfaceController:createInterface', + ), + getInterface: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SnapInterfaceController:getInterface', + ), ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) getSnapKeyring: this.getSnapKeyring.bind(this), @@ -2391,6 +2420,49 @@ export default class MetamaskController extends EventEmitter { ); ///: BEGIN:ONLY_INCLUDE_IF(snaps) + + this.controllerMessenger.subscribe( + `${this.snapController.name}:snapInstallStarted`, + (snapId, origin, isUpdate) => { + const snapCategory = this._getSnapMetadata(snapId)?.category; + this.metaMetricsController.trackEvent({ + event: isUpdate + ? MetaMetricsEventName.SnapUpdateStarted + : MetaMetricsEventName.SnapInstallStarted, + category: MetaMetricsEventCategory.Snaps, + properties: { + snap_id: snapId, + origin, + snap_category: snapCategory, + }, + }); + }, + ); + + this.controllerMessenger.subscribe( + `${this.snapController.name}:snapInstallFailed`, + (snapId, origin, isUpdate, error) => { + const isRejected = error.includes('User rejected the request.'); + const failedEvent = isUpdate + ? MetaMetricsEventName.SnapUpdateFailed + : MetaMetricsEventName.SnapInstallFailed; + const rejectedEvent = isUpdate + ? MetaMetricsEventName.SnapUpdateRejected + : MetaMetricsEventName.SnapInstallRejected; + + const snapCategory = this._getSnapMetadata(snapId)?.category; + this.metaMetricsController.trackEvent({ + event: isRejected ? rejectedEvent : failedEvent, + category: MetaMetricsEventCategory.Snaps, + properties: { + snap_id: snapId, + origin, + snap_category: snapCategory, + }, + }); + }, + ); + this.controllerMessenger.subscribe( `${this.snapController.name}:snapInstalled`, (truncatedSnap, origin) => { @@ -2962,6 +3034,8 @@ export default class MetamaskController extends EventEmitter { ), setShowBetaHeader: appStateController.setShowBetaHeader.bind(appStateController), + setShowPermissionsTour: + appStateController.setShowPermissionsTour.bind(appStateController), setShowProductTour: appStateController.setShowProductTour.bind(appStateController), setShowAccountBanner: @@ -3114,14 +3188,6 @@ export default class MetamaskController extends EventEmitter { this.custodyController.setWaitForConfirmDeepLinkDialog.bind( this.custodyController, ), - setCustodianConnectRequest: - this.custodyController.setCustodianConnectRequest.bind( - this.custodyController, - ), - getCustodianConnectRequest: - this.custodyController.getCustodianConnectRequest.bind( - this.custodyController, - ), getMmiConfiguration: this.mmiConfigurationController.getConfiguration.bind( this.mmiConfigurationController, @@ -3181,6 +3247,14 @@ export default class MetamaskController extends EventEmitter { return phishingController.test(website); }, + deleteInterface: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SnapInterfaceController:deleteInterface', + ), + updateInterfaceState: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SnapInterfaceController:updateInterfaceState', + ), ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(desktop) // Desktop @@ -4941,6 +5015,22 @@ export default class MetamaskController extends EventEmitter { this.subjectMetadataController, origin, ), + createInterface: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SnapInterfaceController:createInterface', + origin, + ), + getInterfaceState: (...args) => + this.controllerMessenger.call( + 'SnapInterfaceController:getInterface', + origin, + ...args, + ).state, + updateInterface: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SnapInterfaceController:updateInterface', + origin, + ), ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(snaps) }), diff --git a/builds.yml b/builds.yml index 71847a1bbee6..978ae5086e35 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/3.5.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/4.0.1/index.html - ACCOUNT_SNAPS_DIRECTORY_URL: https://snaps.metamask.io/account-management # Main build uses the default browser manifest manifestOverrides: false @@ -65,7 +65,7 @@ buildTypes: - SEGMENT_FLASK_WRITE_KEY - ALLOW_LOCAL_SNAPS: true - REQUIRE_SNAPS_ALLOWLIST: false - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/3.5.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/4.0.1/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 @@ -86,7 +86,7 @@ buildTypes: - SEGMENT_FLASK_WRITE_KEY - ALLOW_LOCAL_SNAPS: true - REQUIRE_SNAPS_ALLOWLIST: false - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/3.5.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/4.0.1/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,10 +107,11 @@ 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/3.5.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/4.0.1/index.html - MMI_CONFIGURATION_SERVICE_URL: https://configuration.metamask-institutional.io/v2/configuration/default - SUPPORT_LINK: https://mmi-support.zendesk.com/hc/en-us - SUPPORT_REQUEST_LINK: https://mmi-support.zendesk.com/hc/en-us/requests/new + - SENTRY_DSN: SENTRY_MMI_DSN # For some reason, MMI uses this type of versioning # Leaving it on for backwards compatibility isPrerelease: true diff --git a/development/ts-migration-dashboard/app/styles/custom-elements.scss b/development/ts-migration-dashboard/app/styles/custom-elements.scss index e53562024291..8eec70c9c810 100644 --- a/development/ts-migration-dashboard/app/styles/custom-elements.scss +++ b/development/ts-migration-dashboard/app/styles/custom-elements.scss @@ -1,3 +1,7 @@ +/* +Disabling Stylelint's hex color rule here because the TypeScript migration dashboard, being external to the main app, doesn't use design tokens. +*/ +/* stylelint-disable color-no-hex */ :root { --blue-gray-350: hsl(209deg 13.7% 62.4%); --blue-gray-100: hsl(209.8deg 16.5% 89%); diff --git a/development/ts-migration-dashboard/app/styles/tippy.scss b/development/ts-migration-dashboard/app/styles/tippy.scss index 0ce28c5ea40d..40902536811e 100644 --- a/development/ts-migration-dashboard/app/styles/tippy.scss +++ b/development/ts-migration-dashboard/app/styles/tippy.scss @@ -1,3 +1,7 @@ +/* +Disabling Stylelint's hex color rule here because the TypeScript migration dashboard, being external to the main app, doesn't use design tokens. +*/ +/* stylelint-disable color-no-hex */ .tippy-touch { cursor: pointer !important; } diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index 36c8cc479675..079a449c78c6 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -759,7 +759,7 @@ }, "@metamask/address-book-controller>@metamask/controller-utils>ethjs-unit": { "packages": { - "@metamask/ethjs>number-to-bn": true, + "@metamask/ethjs>ethjs-abi>number-to-bn": true, "bn.js": true } }, @@ -1206,16 +1206,16 @@ "@metamask/ethjs>@metamask/ethjs-provider-http": true, "@metamask/ethjs>@metamask/ethjs-unit": true, "@metamask/ethjs>@metamask/ethjs-util": true, + "@metamask/ethjs>@metamask/number-to-bn": true, "@metamask/ethjs>ethjs-abi": true, "@metamask/ethjs>js-sha3": true, - "@metamask/ethjs>number-to-bn": true, "bn.js": true, "browserify>buffer": true } }, "@metamask/ethjs-contract": { "packages": { - "@metamask/ethjs-query>babel-runtime": true, + "@babel/runtime": true, "@metamask/ethjs>@metamask/ethjs-filter": true, "@metamask/ethjs>@metamask/ethjs-util": true, "@metamask/ethjs>ethjs-abi": true, @@ -1237,8 +1237,8 @@ "packages": { "@metamask/ethjs-query>@metamask/ethjs-format>ethjs-schema": true, "@metamask/ethjs>@metamask/ethjs-util": true, - "@metamask/ethjs>number-to-bn": true, - "@metamask/ethjs>number-to-bn>strip-hex-prefix": true + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, + "@metamask/ethjs>@metamask/number-to-bn": true } }, "@metamask/ethjs-query>@metamask/ethjs-rpc": { @@ -1246,27 +1246,6 @@ "promise-to-callback": true } }, - "@metamask/ethjs-query>babel-runtime": { - "packages": { - "@metamask/ethjs-query>babel-runtime>core-js": true, - "@metamask/ethjs-query>babel-runtime>regenerator-runtime": true - } - }, - "@metamask/ethjs-query>babel-runtime>core-js": { - "globals": { - "PromiseRejectionEvent": true, - "__e": "write", - "__g": "write", - "document.createTextNode": true, - "postMessage": true, - "setTimeout": true - } - }, - "@metamask/ethjs-query>babel-runtime>regenerator-runtime": { - "globals": { - "regeneratorRuntime": "write" - } - }, "@metamask/ethjs>@metamask/ethjs-filter": { "globals": { "clearInterval": true, @@ -1285,25 +1264,42 @@ }, "@metamask/ethjs>@metamask/ethjs-unit": { "packages": { - "@metamask/ethjs>number-to-bn": true, + "@metamask/ethjs>@metamask/number-to-bn": true, "bn.js": true } }, "@metamask/ethjs>@metamask/ethjs-util": { "packages": { "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true, - "@metamask/ethjs>number-to-bn>strip-hex-prefix": true, + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, "browserify>buffer": true } }, + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": { + "packages": { + "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true + } + }, + "@metamask/ethjs>@metamask/number-to-bn": { + "packages": { + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, + "bn.js": true + } + }, "@metamask/ethjs>ethjs-abi": { "packages": { + "@metamask/ethjs>ethjs-abi>number-to-bn": true, "@metamask/ethjs>js-sha3": true, - "@metamask/ethjs>number-to-bn": true, "bn.js": true, "browserify>buffer": true } }, + "@metamask/ethjs>ethjs-abi>number-to-bn": { + "packages": { + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, + "bn.js": true + } + }, "@metamask/ethjs>js-sha3": { "globals": { "define": true @@ -1312,17 +1308,6 @@ "browserify>process": true } }, - "@metamask/ethjs>number-to-bn": { - "packages": { - "@metamask/ethjs>number-to-bn>strip-hex-prefix": true, - "bn.js": true - } - }, - "@metamask/ethjs>number-to-bn>strip-hex-prefix": { - "packages": { - "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true - } - }, "@metamask/gas-fee-controller": { "globals": { "clearInterval": true, @@ -1668,20 +1653,8 @@ "removeEventListener": true }, "packages": { - "@metamask/post-message-stream>@metamask/utils": true, - "@metamask/post-message-stream>readable-stream": true - } - }, - "@metamask/post-message-stream>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true + "@metamask/post-message-stream>readable-stream": true, + "@metamask/utils": true } }, "@metamask/post-message-stream>readable-stream": { @@ -1891,7 +1864,7 @@ }, "packages": { "@metamask/providers>@metamask/rpc-errors": true, - "@metamask/snaps-sdk>is-svg": true, + "@metamask/snaps-sdk>fast-xml-parser": true, "@metamask/utils": true, "superstruct": true } @@ -1927,18 +1900,13 @@ "browserify>browser-resolve": true } }, - "@metamask/snaps-sdk>is-svg": { - "packages": { - "@metamask/snaps-sdk>is-svg>fast-xml-parser": true - } - }, - "@metamask/snaps-sdk>is-svg>fast-xml-parser": { + "@metamask/snaps-sdk>fast-xml-parser": { "globals": { "entityName": true, "val": true }, "packages": { - "@metamask/snaps-sdk>is-svg>fast-xml-parser>strnum": true + "@metamask/snaps-sdk>fast-xml-parser>strnum": true } }, "@metamask/snaps-utils": { @@ -1960,7 +1928,6 @@ "@metamask/providers>@metamask/rpc-errors": true, "@metamask/snaps-sdk": true, "@metamask/snaps-sdk>@metamask/key-tree": true, - "@metamask/snaps-sdk>is-svg": true, "@metamask/snaps-utils>@metamask/slip44": true, "@metamask/snaps-utils>cron-parser": true, "@metamask/snaps-utils>fast-json-stable-stringify": true, @@ -3169,7 +3136,8 @@ }, "eth-method-registry": { "packages": { - "@metamask/ethjs": true + "@metamask/ethjs-contract": true, + "@metamask/ethjs-query": true } }, "eth-rpc-errors": { @@ -3210,7 +3178,7 @@ "eth-sig-util>ethereumjs-util>ethjs-util": { "packages": { "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true, - "@metamask/ethjs>number-to-bn>strip-hex-prefix": true, + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, "browserify>buffer": true } }, diff --git a/lavamoat/browserify/desktop/policy.json b/lavamoat/browserify/desktop/policy.json index d792a0114e82..1fd9c4de9a36 100644 --- a/lavamoat/browserify/desktop/policy.json +++ b/lavamoat/browserify/desktop/policy.json @@ -759,7 +759,7 @@ }, "@metamask/address-book-controller>@metamask/controller-utils>ethjs-unit": { "packages": { - "@metamask/ethjs>number-to-bn": true, + "@metamask/ethjs>ethjs-abi>number-to-bn": true, "bn.js": true } }, @@ -1283,16 +1283,16 @@ "@metamask/ethjs>@metamask/ethjs-provider-http": true, "@metamask/ethjs>@metamask/ethjs-unit": true, "@metamask/ethjs>@metamask/ethjs-util": true, + "@metamask/ethjs>@metamask/number-to-bn": true, "@metamask/ethjs>ethjs-abi": true, "@metamask/ethjs>js-sha3": true, - "@metamask/ethjs>number-to-bn": true, "bn.js": true, "browserify>buffer": true } }, "@metamask/ethjs-contract": { "packages": { - "@metamask/ethjs-query>babel-runtime": true, + "@babel/runtime": true, "@metamask/ethjs>@metamask/ethjs-filter": true, "@metamask/ethjs>@metamask/ethjs-util": true, "@metamask/ethjs>ethjs-abi": true, @@ -1314,8 +1314,8 @@ "packages": { "@metamask/ethjs-query>@metamask/ethjs-format>ethjs-schema": true, "@metamask/ethjs>@metamask/ethjs-util": true, - "@metamask/ethjs>number-to-bn": true, - "@metamask/ethjs>number-to-bn>strip-hex-prefix": true + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, + "@metamask/ethjs>@metamask/number-to-bn": true } }, "@metamask/ethjs-query>@metamask/ethjs-rpc": { @@ -1323,27 +1323,6 @@ "promise-to-callback": true } }, - "@metamask/ethjs-query>babel-runtime": { - "packages": { - "@metamask/ethjs-query>babel-runtime>core-js": true, - "@metamask/ethjs-query>babel-runtime>regenerator-runtime": true - } - }, - "@metamask/ethjs-query>babel-runtime>core-js": { - "globals": { - "PromiseRejectionEvent": true, - "__e": "write", - "__g": "write", - "document.createTextNode": true, - "postMessage": true, - "setTimeout": true - } - }, - "@metamask/ethjs-query>babel-runtime>regenerator-runtime": { - "globals": { - "regeneratorRuntime": "write" - } - }, "@metamask/ethjs>@metamask/ethjs-filter": { "globals": { "clearInterval": true, @@ -1362,25 +1341,42 @@ }, "@metamask/ethjs>@metamask/ethjs-unit": { "packages": { - "@metamask/ethjs>number-to-bn": true, + "@metamask/ethjs>@metamask/number-to-bn": true, "bn.js": true } }, "@metamask/ethjs>@metamask/ethjs-util": { "packages": { "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true, - "@metamask/ethjs>number-to-bn>strip-hex-prefix": true, + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, "browserify>buffer": true } }, + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": { + "packages": { + "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true + } + }, + "@metamask/ethjs>@metamask/number-to-bn": { + "packages": { + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, + "bn.js": true + } + }, "@metamask/ethjs>ethjs-abi": { "packages": { + "@metamask/ethjs>ethjs-abi>number-to-bn": true, "@metamask/ethjs>js-sha3": true, - "@metamask/ethjs>number-to-bn": true, "bn.js": true, "browserify>buffer": true } }, + "@metamask/ethjs>ethjs-abi>number-to-bn": { + "packages": { + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, + "bn.js": true + } + }, "@metamask/ethjs>js-sha3": { "globals": { "define": true @@ -1389,17 +1385,6 @@ "browserify>process": true } }, - "@metamask/ethjs>number-to-bn": { - "packages": { - "@metamask/ethjs>number-to-bn>strip-hex-prefix": true, - "bn.js": true - } - }, - "@metamask/ethjs>number-to-bn>strip-hex-prefix": { - "packages": { - "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true - } - }, "@metamask/gas-fee-controller": { "globals": { "clearInterval": true, @@ -1772,20 +1757,8 @@ "removeEventListener": true }, "packages": { - "@metamask/post-message-stream>@metamask/utils": true, - "@metamask/post-message-stream>readable-stream": true - } - }, - "@metamask/post-message-stream>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true + "@metamask/post-message-stream>readable-stream": true, + "@metamask/utils": true } }, "@metamask/post-message-stream>readable-stream": { @@ -2006,6 +1979,7 @@ }, "@metamask/snaps-controllers": { "globals": { + "DecompressionStream": true, "URL": true, "chrome.offscreen.createDocument": true, "chrome.offscreen.hasDocument": true, @@ -2127,7 +2101,7 @@ }, "packages": { "@metamask/providers>@metamask/rpc-errors": true, - "@metamask/snaps-sdk>is-svg": true, + "@metamask/snaps-sdk>fast-xml-parser": true, "@metamask/utils": true, "superstruct": true } @@ -2163,18 +2137,13 @@ "browserify>browser-resolve": true } }, - "@metamask/snaps-sdk>is-svg": { - "packages": { - "@metamask/snaps-sdk>is-svg>fast-xml-parser": true - } - }, - "@metamask/snaps-sdk>is-svg>fast-xml-parser": { + "@metamask/snaps-sdk>fast-xml-parser": { "globals": { "entityName": true, "val": true }, "packages": { - "@metamask/snaps-sdk>is-svg>fast-xml-parser>strnum": true + "@metamask/snaps-sdk>fast-xml-parser>strnum": true } }, "@metamask/snaps-utils": { @@ -2196,7 +2165,6 @@ "@metamask/providers>@metamask/rpc-errors": true, "@metamask/snaps-sdk": true, "@metamask/snaps-sdk>@metamask/key-tree": true, - "@metamask/snaps-sdk>is-svg": true, "@metamask/snaps-utils>@metamask/slip44": true, "@metamask/snaps-utils>cron-parser": true, "@metamask/snaps-utils>fast-json-stable-stringify": true, @@ -3426,7 +3394,8 @@ }, "eth-method-registry": { "packages": { - "@metamask/ethjs": true + "@metamask/ethjs-contract": true, + "@metamask/ethjs-query": true } }, "eth-rpc-errors": { @@ -3467,7 +3436,7 @@ "eth-sig-util>ethereumjs-util>ethjs-util": { "packages": { "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true, - "@metamask/ethjs>number-to-bn>strip-hex-prefix": true, + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, "browserify>buffer": true } }, diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index 9250120ac60f..73cf28268c16 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -759,7 +759,7 @@ }, "@metamask/address-book-controller>@metamask/controller-utils>ethjs-unit": { "packages": { - "@metamask/ethjs>number-to-bn": true, + "@metamask/ethjs>ethjs-abi>number-to-bn": true, "bn.js": true } }, @@ -1283,16 +1283,16 @@ "@metamask/ethjs>@metamask/ethjs-provider-http": true, "@metamask/ethjs>@metamask/ethjs-unit": true, "@metamask/ethjs>@metamask/ethjs-util": true, + "@metamask/ethjs>@metamask/number-to-bn": true, "@metamask/ethjs>ethjs-abi": true, "@metamask/ethjs>js-sha3": true, - "@metamask/ethjs>number-to-bn": true, "bn.js": true, "browserify>buffer": true } }, "@metamask/ethjs-contract": { "packages": { - "@metamask/ethjs-query>babel-runtime": true, + "@babel/runtime": true, "@metamask/ethjs>@metamask/ethjs-filter": true, "@metamask/ethjs>@metamask/ethjs-util": true, "@metamask/ethjs>ethjs-abi": true, @@ -1314,8 +1314,8 @@ "packages": { "@metamask/ethjs-query>@metamask/ethjs-format>ethjs-schema": true, "@metamask/ethjs>@metamask/ethjs-util": true, - "@metamask/ethjs>number-to-bn": true, - "@metamask/ethjs>number-to-bn>strip-hex-prefix": true + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, + "@metamask/ethjs>@metamask/number-to-bn": true } }, "@metamask/ethjs-query>@metamask/ethjs-rpc": { @@ -1323,27 +1323,6 @@ "promise-to-callback": true } }, - "@metamask/ethjs-query>babel-runtime": { - "packages": { - "@metamask/ethjs-query>babel-runtime>core-js": true, - "@metamask/ethjs-query>babel-runtime>regenerator-runtime": true - } - }, - "@metamask/ethjs-query>babel-runtime>core-js": { - "globals": { - "PromiseRejectionEvent": true, - "__e": "write", - "__g": "write", - "document.createTextNode": true, - "postMessage": true, - "setTimeout": true - } - }, - "@metamask/ethjs-query>babel-runtime>regenerator-runtime": { - "globals": { - "regeneratorRuntime": "write" - } - }, "@metamask/ethjs>@metamask/ethjs-filter": { "globals": { "clearInterval": true, @@ -1362,25 +1341,42 @@ }, "@metamask/ethjs>@metamask/ethjs-unit": { "packages": { - "@metamask/ethjs>number-to-bn": true, + "@metamask/ethjs>@metamask/number-to-bn": true, "bn.js": true } }, "@metamask/ethjs>@metamask/ethjs-util": { "packages": { "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true, - "@metamask/ethjs>number-to-bn>strip-hex-prefix": true, + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, "browserify>buffer": true } }, + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": { + "packages": { + "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true + } + }, + "@metamask/ethjs>@metamask/number-to-bn": { + "packages": { + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, + "bn.js": true + } + }, "@metamask/ethjs>ethjs-abi": { "packages": { + "@metamask/ethjs>ethjs-abi>number-to-bn": true, "@metamask/ethjs>js-sha3": true, - "@metamask/ethjs>number-to-bn": true, "bn.js": true, "browserify>buffer": true } }, + "@metamask/ethjs>ethjs-abi>number-to-bn": { + "packages": { + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, + "bn.js": true + } + }, "@metamask/ethjs>js-sha3": { "globals": { "define": true @@ -1389,17 +1385,6 @@ "browserify>process": true } }, - "@metamask/ethjs>number-to-bn": { - "packages": { - "@metamask/ethjs>number-to-bn>strip-hex-prefix": true, - "bn.js": true - } - }, - "@metamask/ethjs>number-to-bn>strip-hex-prefix": { - "packages": { - "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true - } - }, "@metamask/gas-fee-controller": { "globals": { "clearInterval": true, @@ -1772,20 +1757,8 @@ "removeEventListener": true }, "packages": { - "@metamask/post-message-stream>@metamask/utils": true, - "@metamask/post-message-stream>readable-stream": true - } - }, - "@metamask/post-message-stream>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true + "@metamask/post-message-stream>readable-stream": true, + "@metamask/utils": true } }, "@metamask/post-message-stream>readable-stream": { @@ -1802,10 +1775,8 @@ "@metamask/ppom-validator": { "globals": { "URL": true, - "clearInterval": true, "console.error": true, - "crypto": true, - "setInterval": true + "crypto": true }, "packages": { "@metamask/controller-utils": true, @@ -2042,6 +2013,7 @@ }, "@metamask/snaps-controllers": { "globals": { + "DecompressionStream": true, "URL": true, "chrome.offscreen.createDocument": true, "chrome.offscreen.hasDocument": true, @@ -2163,7 +2135,7 @@ }, "packages": { "@metamask/providers>@metamask/rpc-errors": true, - "@metamask/snaps-sdk>is-svg": true, + "@metamask/snaps-sdk>fast-xml-parser": true, "@metamask/utils": true, "superstruct": true } @@ -2199,18 +2171,13 @@ "browserify>browser-resolve": true } }, - "@metamask/snaps-sdk>is-svg": { - "packages": { - "@metamask/snaps-sdk>is-svg>fast-xml-parser": true - } - }, - "@metamask/snaps-sdk>is-svg>fast-xml-parser": { + "@metamask/snaps-sdk>fast-xml-parser": { "globals": { "entityName": true, "val": true }, "packages": { - "@metamask/snaps-sdk>is-svg>fast-xml-parser>strnum": true + "@metamask/snaps-sdk>fast-xml-parser>strnum": true } }, "@metamask/snaps-utils": { @@ -2232,7 +2199,6 @@ "@metamask/providers>@metamask/rpc-errors": true, "@metamask/snaps-sdk": true, "@metamask/snaps-sdk>@metamask/key-tree": true, - "@metamask/snaps-sdk>is-svg": true, "@metamask/snaps-utils>@metamask/slip44": true, "@metamask/snaps-utils>cron-parser": true, "@metamask/snaps-utils>fast-json-stable-stringify": true, @@ -3462,7 +3428,8 @@ }, "eth-method-registry": { "packages": { - "@metamask/ethjs": true + "@metamask/ethjs-contract": true, + "@metamask/ethjs-query": true } }, "eth-rpc-errors": { @@ -3503,7 +3470,7 @@ "eth-sig-util>ethereumjs-util>ethjs-util": { "packages": { "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true, - "@metamask/ethjs>number-to-bn>strip-hex-prefix": true, + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, "browserify>buffer": true } }, diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index 6987a8dea4ff..475459af8e30 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -759,7 +759,7 @@ }, "@metamask/address-book-controller>@metamask/controller-utils>ethjs-unit": { "packages": { - "@metamask/ethjs>number-to-bn": true, + "@metamask/ethjs>ethjs-abi>number-to-bn": true, "bn.js": true } }, @@ -1206,16 +1206,16 @@ "@metamask/ethjs>@metamask/ethjs-provider-http": true, "@metamask/ethjs>@metamask/ethjs-unit": true, "@metamask/ethjs>@metamask/ethjs-util": true, + "@metamask/ethjs>@metamask/number-to-bn": true, "@metamask/ethjs>ethjs-abi": true, "@metamask/ethjs>js-sha3": true, - "@metamask/ethjs>number-to-bn": true, "bn.js": true, "browserify>buffer": true } }, "@metamask/ethjs-contract": { "packages": { - "@metamask/ethjs-query>babel-runtime": true, + "@babel/runtime": true, "@metamask/ethjs>@metamask/ethjs-filter": true, "@metamask/ethjs>@metamask/ethjs-util": true, "@metamask/ethjs>ethjs-abi": true, @@ -1237,8 +1237,8 @@ "packages": { "@metamask/ethjs-query>@metamask/ethjs-format>ethjs-schema": true, "@metamask/ethjs>@metamask/ethjs-util": true, - "@metamask/ethjs>number-to-bn": true, - "@metamask/ethjs>number-to-bn>strip-hex-prefix": true + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, + "@metamask/ethjs>@metamask/number-to-bn": true } }, "@metamask/ethjs-query>@metamask/ethjs-rpc": { @@ -1246,27 +1246,6 @@ "promise-to-callback": true } }, - "@metamask/ethjs-query>babel-runtime": { - "packages": { - "@metamask/ethjs-query>babel-runtime>core-js": true, - "@metamask/ethjs-query>babel-runtime>regenerator-runtime": true - } - }, - "@metamask/ethjs-query>babel-runtime>core-js": { - "globals": { - "PromiseRejectionEvent": true, - "__e": "write", - "__g": "write", - "document.createTextNode": true, - "postMessage": true, - "setTimeout": true - } - }, - "@metamask/ethjs-query>babel-runtime>regenerator-runtime": { - "globals": { - "regeneratorRuntime": "write" - } - }, "@metamask/ethjs>@metamask/ethjs-filter": { "globals": { "clearInterval": true, @@ -1285,25 +1264,42 @@ }, "@metamask/ethjs>@metamask/ethjs-unit": { "packages": { - "@metamask/ethjs>number-to-bn": true, + "@metamask/ethjs>@metamask/number-to-bn": true, "bn.js": true } }, "@metamask/ethjs>@metamask/ethjs-util": { "packages": { "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true, - "@metamask/ethjs>number-to-bn>strip-hex-prefix": true, + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, "browserify>buffer": true } }, + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": { + "packages": { + "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true + } + }, + "@metamask/ethjs>@metamask/number-to-bn": { + "packages": { + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, + "bn.js": true + } + }, "@metamask/ethjs>ethjs-abi": { "packages": { + "@metamask/ethjs>ethjs-abi>number-to-bn": true, "@metamask/ethjs>js-sha3": true, - "@metamask/ethjs>number-to-bn": true, "bn.js": true, "browserify>buffer": true } }, + "@metamask/ethjs>ethjs-abi>number-to-bn": { + "packages": { + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, + "bn.js": true + } + }, "@metamask/ethjs>js-sha3": { "globals": { "define": true @@ -1312,17 +1308,6 @@ "browserify>process": true } }, - "@metamask/ethjs>number-to-bn": { - "packages": { - "@metamask/ethjs>number-to-bn>strip-hex-prefix": true, - "bn.js": true - } - }, - "@metamask/ethjs>number-to-bn>strip-hex-prefix": { - "packages": { - "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true - } - }, "@metamask/gas-fee-controller": { "globals": { "clearInterval": true, @@ -1695,20 +1680,8 @@ "removeEventListener": true }, "packages": { - "@metamask/post-message-stream>@metamask/utils": true, - "@metamask/post-message-stream>readable-stream": true - } - }, - "@metamask/post-message-stream>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true + "@metamask/post-message-stream>readable-stream": true, + "@metamask/utils": true } }, "@metamask/post-message-stream>readable-stream": { @@ -1725,10 +1698,8 @@ "@metamask/ppom-validator": { "globals": { "URL": true, - "clearInterval": true, "console.error": true, - "crypto": true, - "setInterval": true + "crypto": true }, "packages": { "@metamask/controller-utils": true, @@ -1965,6 +1936,7 @@ }, "@metamask/snaps-controllers": { "globals": { + "DecompressionStream": true, "URL": true, "chrome.offscreen.createDocument": true, "chrome.offscreen.hasDocument": true, @@ -2086,7 +2058,7 @@ }, "packages": { "@metamask/providers>@metamask/rpc-errors": true, - "@metamask/snaps-sdk>is-svg": true, + "@metamask/snaps-sdk>fast-xml-parser": true, "@metamask/utils": true, "superstruct": true } @@ -2122,18 +2094,13 @@ "browserify>browser-resolve": true } }, - "@metamask/snaps-sdk>is-svg": { - "packages": { - "@metamask/snaps-sdk>is-svg>fast-xml-parser": true - } - }, - "@metamask/snaps-sdk>is-svg>fast-xml-parser": { + "@metamask/snaps-sdk>fast-xml-parser": { "globals": { "entityName": true, "val": true }, "packages": { - "@metamask/snaps-sdk>is-svg>fast-xml-parser>strnum": true + "@metamask/snaps-sdk>fast-xml-parser>strnum": true } }, "@metamask/snaps-utils": { @@ -2155,7 +2122,6 @@ "@metamask/providers>@metamask/rpc-errors": true, "@metamask/snaps-sdk": true, "@metamask/snaps-sdk>@metamask/key-tree": true, - "@metamask/snaps-sdk>is-svg": true, "@metamask/snaps-utils>@metamask/slip44": true, "@metamask/snaps-utils>cron-parser": true, "@metamask/snaps-utils>fast-json-stable-stringify": true, @@ -3385,7 +3351,8 @@ }, "eth-method-registry": { "packages": { - "@metamask/ethjs": true + "@metamask/ethjs-contract": true, + "@metamask/ethjs-query": true } }, "eth-rpc-errors": { @@ -3426,7 +3393,7 @@ "eth-sig-util>ethereumjs-util>ethjs-util": { "packages": { "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true, - "@metamask/ethjs>number-to-bn>strip-hex-prefix": true, + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, "browserify>buffer": true } }, diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index f8dce4372499..9167168c8b40 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -891,7 +891,7 @@ }, "@metamask/address-book-controller>@metamask/controller-utils>ethjs-unit": { "packages": { - "@metamask/ethjs>number-to-bn": true, + "@metamask/ethjs>ethjs-abi>number-to-bn": true, "bn.js": true } }, @@ -1338,16 +1338,16 @@ "@metamask/ethjs>@metamask/ethjs-provider-http": true, "@metamask/ethjs>@metamask/ethjs-unit": true, "@metamask/ethjs>@metamask/ethjs-util": true, + "@metamask/ethjs>@metamask/number-to-bn": true, "@metamask/ethjs>ethjs-abi": true, "@metamask/ethjs>js-sha3": true, - "@metamask/ethjs>number-to-bn": true, "bn.js": true, "browserify>buffer": true } }, "@metamask/ethjs-contract": { "packages": { - "@metamask/ethjs-query>babel-runtime": true, + "@babel/runtime": true, "@metamask/ethjs>@metamask/ethjs-filter": true, "@metamask/ethjs>@metamask/ethjs-util": true, "@metamask/ethjs>ethjs-abi": true, @@ -1369,8 +1369,8 @@ "packages": { "@metamask/ethjs-query>@metamask/ethjs-format>ethjs-schema": true, "@metamask/ethjs>@metamask/ethjs-util": true, - "@metamask/ethjs>number-to-bn": true, - "@metamask/ethjs>number-to-bn>strip-hex-prefix": true + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, + "@metamask/ethjs>@metamask/number-to-bn": true } }, "@metamask/ethjs-query>@metamask/ethjs-rpc": { @@ -1378,27 +1378,6 @@ "promise-to-callback": true } }, - "@metamask/ethjs-query>babel-runtime": { - "packages": { - "@metamask/ethjs-query>babel-runtime>core-js": true, - "@metamask/ethjs-query>babel-runtime>regenerator-runtime": true - } - }, - "@metamask/ethjs-query>babel-runtime>core-js": { - "globals": { - "PromiseRejectionEvent": true, - "__e": "write", - "__g": "write", - "document.createTextNode": true, - "postMessage": true, - "setTimeout": true - } - }, - "@metamask/ethjs-query>babel-runtime>regenerator-runtime": { - "globals": { - "regeneratorRuntime": "write" - } - }, "@metamask/ethjs>@metamask/ethjs-filter": { "globals": { "clearInterval": true, @@ -1417,25 +1396,42 @@ }, "@metamask/ethjs>@metamask/ethjs-unit": { "packages": { - "@metamask/ethjs>number-to-bn": true, + "@metamask/ethjs>@metamask/number-to-bn": true, "bn.js": true } }, "@metamask/ethjs>@metamask/ethjs-util": { "packages": { "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true, - "@metamask/ethjs>number-to-bn>strip-hex-prefix": true, + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, "browserify>buffer": true } }, + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": { + "packages": { + "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true + } + }, + "@metamask/ethjs>@metamask/number-to-bn": { + "packages": { + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, + "bn.js": true + } + }, "@metamask/ethjs>ethjs-abi": { "packages": { + "@metamask/ethjs>ethjs-abi>number-to-bn": true, "@metamask/ethjs>js-sha3": true, - "@metamask/ethjs>number-to-bn": true, "bn.js": true, "browserify>buffer": true } }, + "@metamask/ethjs>ethjs-abi>number-to-bn": { + "packages": { + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, + "bn.js": true + } + }, "@metamask/ethjs>js-sha3": { "globals": { "define": true @@ -1444,17 +1440,6 @@ "browserify>process": true } }, - "@metamask/ethjs>number-to-bn": { - "packages": { - "@metamask/ethjs>number-to-bn>strip-hex-prefix": true, - "bn.js": true - } - }, - "@metamask/ethjs>number-to-bn>strip-hex-prefix": { - "packages": { - "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true - } - }, "@metamask/gas-fee-controller": { "globals": { "clearInterval": true, @@ -1827,20 +1812,8 @@ "removeEventListener": true }, "packages": { - "@metamask/post-message-stream>@metamask/utils": true, - "@metamask/post-message-stream>readable-stream": true - } - }, - "@metamask/post-message-stream>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true + "@metamask/post-message-stream>readable-stream": true, + "@metamask/utils": true } }, "@metamask/post-message-stream>readable-stream": { @@ -2061,6 +2034,7 @@ }, "@metamask/snaps-controllers": { "globals": { + "DecompressionStream": true, "URL": true, "chrome.offscreen.createDocument": true, "chrome.offscreen.hasDocument": true, @@ -2182,7 +2156,7 @@ }, "packages": { "@metamask/providers>@metamask/rpc-errors": true, - "@metamask/snaps-sdk>is-svg": true, + "@metamask/snaps-sdk>fast-xml-parser": true, "@metamask/utils": true, "superstruct": true } @@ -2218,18 +2192,13 @@ "browserify>browser-resolve": true } }, - "@metamask/snaps-sdk>is-svg": { - "packages": { - "@metamask/snaps-sdk>is-svg>fast-xml-parser": true - } - }, - "@metamask/snaps-sdk>is-svg>fast-xml-parser": { + "@metamask/snaps-sdk>fast-xml-parser": { "globals": { "entityName": true, "val": true }, "packages": { - "@metamask/snaps-sdk>is-svg>fast-xml-parser>strnum": true + "@metamask/snaps-sdk>fast-xml-parser>strnum": true } }, "@metamask/snaps-utils": { @@ -2251,7 +2220,6 @@ "@metamask/providers>@metamask/rpc-errors": true, "@metamask/snaps-sdk": true, "@metamask/snaps-sdk>@metamask/key-tree": true, - "@metamask/snaps-sdk>is-svg": true, "@metamask/snaps-utils>@metamask/slip44": true, "@metamask/snaps-utils>cron-parser": true, "@metamask/snaps-utils>fast-json-stable-stringify": true, @@ -3481,7 +3449,8 @@ }, "eth-method-registry": { "packages": { - "@metamask/ethjs": true + "@metamask/ethjs-contract": true, + "@metamask/ethjs-query": true } }, "eth-rpc-errors": { @@ -3522,7 +3491,7 @@ "eth-sig-util>ethereumjs-util>ethjs-util": { "packages": { "@metamask/ethjs>@metamask/ethjs-util>is-hex-prefixed": true, - "@metamask/ethjs>number-to-bn>strip-hex-prefix": true, + "@metamask/ethjs>@metamask/ethjs-util>strip-hex-prefix": true, "browserify>buffer": true } }, diff --git a/package.json b/package.json index d783deb453c7..6d8f589a6a83 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "metamask-crx", - "version": "11.9.5", + "version": "11.10.0", "private": true, "repository": { "type": "git", @@ -114,6 +114,8 @@ "close-release-bug-report-issue": "ts-node ./.github/scripts/close-release-bug-report-issue.ts", "check-template-and-add-labels": "ts-node ./.github/scripts/check-template-and-add-labels.ts", "audit": "yarn npm audit --recursive --environment production --severity moderate --ignore '@metamask/types (deprecation)'", + "download-builds": "tsx .devcontainer/download-builds.ts prep-build", + "download-builds:test": "tsx .devcontainer/download-builds.ts prep-build-test", "master-sync": "node development/master-sync.js" }, "resolutions": { @@ -218,7 +220,7 @@ "nonce-tracker@npm:^3.0.0": "patch:nonce-tracker@npm%3A3.0.0#~/.yarn/patches/nonce-tracker-npm-3.0.0-c5e9a93f9d.patch", "@trezor/connect-web": "9.0.11", "lavamoat-core@npm:^15.1.1": "patch:lavamoat-core@npm%3A15.1.1#~/.yarn/patches/lavamoat-core-npm-15.1.1-51fbe39988.patch", - "tar-stream@npm:^3.1.6": "patch:tar-stream@npm%3A3.1.6#~/.yarn/patches/tar-stream-npm-3.1.6-ce3ac17e49.patch" + "@metamask/snaps-sdk": "^2.1.0" }, "dependencies": { "@babel/runtime": "^7.23.2", @@ -237,9 +239,9 @@ "@lavamoat/lavadome-react": "0.0.11", "@lavamoat/snow": "^2.0.1", "@material-ui/core": "^4.11.0", - "@metamask-institutional/custody-controller": "^0.2.20", + "@metamask-institutional/custody-controller": "^0.2.22", "@metamask-institutional/custody-keyring": "^1.0.10", - "@metamask-institutional/extension": "^0.3.13", + "@metamask-institutional/extension": "^0.3.16", "@metamask-institutional/institutional-features": "^1.2.11", "@metamask-institutional/portfolio-dashboard": "^1.4.0", "@metamask-institutional/rpc-allowlist": "^1.0.0", @@ -249,24 +251,24 @@ "@metamask/address-book-controller": "^3.0.0", "@metamask/announcement-controller": "^5.0.1", "@metamask/approval-controller": "^5.1.2", - "@metamask/assets-controllers": "^24.0.0", + "@metamask/assets-controllers": "patch:@metamask/assets-controllers@npm%3A24.0.0#~/.yarn/patches/@metamask-assets-controllers-npm-24.0.0-dfef136464.patch", "@metamask/base-controller": "^4.1.0", "@metamask/browser-passworder": "^4.3.0", "@metamask/contract-metadata": "^2.3.1", "@metamask/controller-utils": "^8.0.1", - "@metamask/design-tokens": "^2.0.3", + "@metamask/design-tokens": "^2.1.1", "@metamask/desktop": "^0.3.0", "@metamask/eth-json-rpc-middleware": "^12.1.0", "@metamask/eth-keyring-controller": "^15.1.0", "@metamask/eth-ledger-bridge-keyring": "^2.0.1", "@metamask/eth-query": "^4.0.0", "@metamask/eth-snap-keyring": "^2.1.2", - "@metamask/eth-token-tracker": "^7.0.1", + "@metamask/eth-token-tracker": "^7.0.2", "@metamask/eth-trezor-keyring": "^3.0.0", "@metamask/etherscan-link": "^3.0.0", - "@metamask/ethjs": "^0.5.1", - "@metamask/ethjs-contract": "^0.3.4", - "@metamask/ethjs-query": "^0.5.3", + "@metamask/ethjs": "^0.6.0", + "@metamask/ethjs-contract": "^0.4.1", + "@metamask/ethjs-query": "^0.7.1", "@metamask/gas-fee-controller": "^13.0.0", "@metamask/jazzicon": "^2.0.0", "@metamask/keyring-api": "^3.0.0", @@ -279,11 +281,11 @@ "@metamask/network-controller": "^17.2.0", "@metamask/notification-controller": "^3.0.0", "@metamask/obs-store": "^8.1.0", - "@metamask/permission-controller": "^7.1.0", + "@metamask/permission-controller": "^8.0.0", "@metamask/phishing-controller": "^8.0.0", "@metamask/polling-controller": "^4.0.0", - "@metamask/post-message-stream": "^7.0.0", - "@metamask/ppom-validator": "^0.24.0", + "@metamask/post-message-stream": "^8.0.0", + "@metamask/ppom-validator": "^0.26.0", "@metamask/providers": "^14.0.2", "@metamask/queued-request-controller": "^0.3.0", "@metamask/rate-limit-controller": "^3.0.0", @@ -292,11 +294,11 @@ "@metamask/selected-network-controller": "^6.0.0", "@metamask/signature-controller": "^12.0.0", "@metamask/smart-transactions-controller": "^6.2.2", - "@metamask/snaps-controllers": "^4.1.0", - "@metamask/snaps-rpc-methods": "^5.0.0", - "@metamask/snaps-sdk": "^1.4.0", - "@metamask/snaps-utils": "^5.2.0", - "@metamask/transaction-controller": "^21.1.0", + "@metamask/snaps-controllers": "^5.0.1", + "@metamask/snaps-rpc-methods": "^6.0.0", + "@metamask/snaps-sdk": "^2.1.0", + "@metamask/snaps-utils": "^6.1.0", + "@metamask/transaction-controller": "^22.0.0", "@metamask/user-operation-controller": "^1.0.0", "@metamask/utils": "^8.2.1", "@ngraveio/bc-ur": "^1.1.6", @@ -326,7 +328,7 @@ "eth-ens-namehash": "^2.0.8", "eth-json-rpc-filters": "^6.0.0", "eth-lattice-keyring": "^0.12.4", - "eth-method-registry": "^3.0.0", + "eth-method-registry": "^4.0.0", "eth-rpc-errors": "^4.0.2", "eth-sig-util": "^3.0.0", "ethereum-ens-network-map": "^1.0.2", @@ -574,6 +576,7 @@ "terser": "^5.7.0", "through2": "^4.0.2", "ts-node": "^10.9.2", + "tsx": "^4.7.1", "ttest": "^2.1.1", "typescript": "~5.3.3", "vinyl": "^2.2.1", @@ -656,9 +659,10 @@ "@trezor/connect-web>@trezor/connect>@trezor/utxo-lib>blake-hash": false, "@trezor/connect-web>@trezor/connect>@trezor/utxo-lib>tiny-secp256k1": false, "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/connect>@trezor/transport>usb": false, - "@metamask/ethjs-query>babel-runtime>core-js": false, + "@metamask/transaction-controller>babel-runtime>core-js": false, "@storybook/test-runner>@swc/core": false, - "@lavamoat/lavadome-react>@lavamoat/preinstall-always-fail": false + "@lavamoat/lavadome-react>@lavamoat/preinstall-always-fail": false, + "tsx>esbuild": false } }, "packageManager": "yarn@4.0.2" diff --git a/shared/constants/metametrics.ts b/shared/constants/metametrics.ts index 29f3c213add3..397fa0472039 100644 --- a/shared/constants/metametrics.ts +++ b/shared/constants/metametrics.ts @@ -542,6 +542,7 @@ export enum MetaMetricsEventName { NavAccountDetailsOpened = 'Account Details Opened', NavConnectedSitesOpened = 'Connected Sites Opened', NavMainMenuOpened = 'Main Menu Opened', + NavPermissionsOpened = 'Permissions Opened', NavNetworkMenuOpened = 'Network Menu Opened', NavSettingsOpened = 'Settings Opened', NavAccountSwitched = 'Account Switched', @@ -659,8 +660,14 @@ export enum MetaMetricsEventName { ExitedSwaps = 'Exited Swaps', SwapError = 'Swap Error', ///: BEGIN:ONLY_INCLUDE_IF(snaps) + SnapInstallStarted = 'Snap Install Started', + SnapInstallFailed = 'Snap Install Failed', + SnapInstallRejected = 'Snap Update Rejected', SnapInstalled = 'Snap Installed', SnapUninstalled = 'Snap Uninstalled', + SnapUpdateStarted = 'Snap Update Started', + SnapUpdateRejected = 'Snap Update Rejected', + SnapUpdateFailed = 'Snap Update Failed', SnapUpdated = 'Snap Updated', SnapExportUsed = 'Snap Export Used', ///: END:ONLY_INCLUDE_IF @@ -766,7 +773,7 @@ export enum MetaMetricsEventUiCustomization { FlaggedAsSafetyUnknown = 'flagged_as_safety_unknown', FlaggedAsWarning = 'flagged_as_warning', GasEstimationFailed = 'gas_estimation_failed', - SecurityAlertFailed = 'security_alert_failed', + SecurityAlertError = 'security_alert_error', Siwe = 'sign_in_with_ethereum', } diff --git a/shared/constants/network.ts b/shared/constants/network.ts index f08aac55d7a0..816d4578b48a 100644 --- a/shared/constants/network.ts +++ b/shared/constants/network.ts @@ -145,6 +145,7 @@ export const CHAIN_IDS = { GNOSIS: '0x64', ZKSYNC_ERA: '0x144', TEST_ETH: '0x539', + ARBITRUM_GOERLI: '0x66eed', } as const; const CHAINLIST_CHAIN_IDS_MAP = { @@ -203,7 +204,12 @@ const CHAINLIST_CHAIN_IDS_MAP = { // To add a deprecation warning to a network, add it to the array // `DEPRECATED_NETWORKS` and as a new case to `getDeprecationWarningCopy() in // `ui/components/ui/deprecated-networks/deprecated-networks.js`. -export const DEPRECATED_NETWORKS = [CHAIN_IDS.AURORA, CHAIN_IDS.GOERLI]; +export const DEPRECATED_NETWORKS = [ + CHAIN_IDS.AURORA, + CHAIN_IDS.GOERLI, + CHAIN_IDS.ARBITRUM_GOERLI, + CHAIN_IDS.OPTIMISM_TESTNET, +]; /** * The largest possible chain ID we can handle. @@ -848,6 +854,7 @@ export const BUYABLE_CHAINS_MAP: { | typeof CHAIN_IDS.SEPOLIA | typeof CHAIN_IDS.GNOSIS | typeof CHAIN_IDS.AURORA + | typeof CHAIN_IDS.ARBITRUM_GOERLI >]: BuyableChainSettings; } = { [CHAIN_IDS.MAINNET]: { diff --git a/shared/constants/permissions.test.js b/shared/constants/permissions.test.js index a5976d058c45..14b279d8c0e4 100644 --- a/shared/constants/permissions.test.js +++ b/shared/constants/permissions.test.js @@ -1,5 +1,7 @@ -import { endowmentPermissionBuilders } from '@metamask/snaps-controllers'; -import { restrictedMethodPermissionBuilders } from '@metamask/snaps-rpc-methods'; +import { + restrictedMethodPermissionBuilders, + endowmentPermissionBuilders, +} from '@metamask/snaps-rpc-methods'; import { EndowmentPermissions, ExcludedSnapEndowments, diff --git a/shared/constants/security-provider.ts b/shared/constants/security-provider.ts index e18888d25178..537d0bd41e5c 100644 --- a/shared/constants/security-provider.ts +++ b/shared/constants/security-provider.ts @@ -47,7 +47,7 @@ export enum BlockaidReason { other = 'other', // MetaMask defined reasons - failed = 'Failed', + errored = 'Error', notApplicable = 'NotApplicable', inProgress = 'validation_in_progress', } @@ -59,7 +59,6 @@ export enum BlockaidResultType { Errored = 'Error', // MetaMask defined result types - Failed = 'Failed', NotApplicable = 'NotApplicable', Loading = 'loading', } diff --git a/shared/constants/snaps/permissions.test.ts b/shared/constants/snaps/permissions.test.ts index e20f19f2d256..873bdf044261 100644 --- a/shared/constants/snaps/permissions.test.ts +++ b/shared/constants/snaps/permissions.test.ts @@ -1,7 +1,7 @@ import { buildSnapEndowmentSpecifications, buildSnapRestrictedMethodSpecifications, -} from '@metamask/snaps-controllers'; +} from '@metamask/snaps-rpc-methods'; import { RestrictedMethods } from '../permissions'; import { EndowmentPermissions, diff --git a/test/data/mock-state.json b/test/data/mock-state.json index 06ab82c99d9f..2b47b4412c65 100644 --- a/test/data/mock-state.json +++ b/test/data/mock-state.json @@ -2,6 +2,13 @@ "DNS": { "resolution": null }, + "activeTab": { + "id": 113, + "title": "E2E Test Dapp", + "origin": "https://metamask.github.io", + "protocol": "https:", + "url": "https://metamask.github.io/test-dapp/" + }, "appState": { "networkDropdownOpen": false, "importNftsModal": { diff --git a/test/e2e/fixture-builder.js b/test/e2e/fixture-builder.js index 189af02db271..d658741ef1e8 100644 --- a/test/e2e/fixture-builder.js +++ b/test/e2e/fixture-builder.js @@ -1560,6 +1560,137 @@ class FixtureBuilder { }); } + /* Steps to create fixture: + 1. Reinstall clean metamask & Onboard + 2. Create 4 more accounts in the wallet + 3. Connected to ENS dapp on Account 1 and 3 + 4. Connected to Uniswap dapp on Accounts 1 and 4 + 5. Connected to Dextools dapp on Accounts 1, 2, and 3 + 6. Connected to Coinmarketcap dapp on Account 1 (didnt log in) + 7. opened devtools and ran stateHooks.getCleanAppState() in console + */ + withConnectionsToManyDapps() { + return this.withPermissionController({ + subjects: { + 'https://app.ens.domains': { + origin: 'https://app.ens.domains', + permissions: { + eth_accounts: { + id: 'oKXoF_MNlffiR2u1Y3mDE', + parentCapability: 'eth_accounts', + invoker: 'https://app.ens.domains', + caveats: [ + { + type: 'restrictReturnedAccounts', + value: [ + '0xbee150bdc171c7d4190891e78234f791a3ac7b24', + '0xb9504634e5788208933b51ae7440b478bfadf865', + ], + }, + ], + date: 1708029792962, + }, + }, + }, + 'https://app.uniswap.org': { + origin: 'https://app.uniswap.org', + permissions: { + eth_accounts: { + id: 'vaa88u5Iv3VmsJwG3bDKW', + parentCapability: 'eth_accounts', + invoker: 'https://app.uniswap.org', + caveats: [ + { + type: 'restrictReturnedAccounts', + value: [ + '0xbee150bdc171c7d4190891e78234f791a3ac7b24', + '0xd1ca923697a701cba1364d803d72b4740fc39bc9', + ], + }, + ], + date: 1708029870079, + }, + }, + }, + 'https://www.dextools.io': { + origin: 'https://www.dextools.io', + permissions: { + eth_accounts: { + id: 'bvvPcFtIhkFyHyW0Tmwi4', + parentCapability: 'eth_accounts', + invoker: 'https://www.dextools.io', + caveats: [ + { + type: 'restrictReturnedAccounts', + value: [ + '0xbee150bdc171c7d4190891e78234f791a3ac7b24', + '0xa5c5293e124d04e2f85e8553851001fd2f192647', + '0xb9504634e5788208933b51ae7440b478bfadf865', + ], + }, + ], + date: 1708029948170, + }, + }, + }, + 'https://coinmarketcap.com': { + origin: 'https://coinmarketcap.com', + permissions: { + eth_accounts: { + id: 'AiblK84K1Cic-Y0FDSzMD', + parentCapability: 'eth_accounts', + invoker: 'https://coinmarketcap.com', + caveats: [ + { + type: 'restrictReturnedAccounts', + value: ['0xbee150bdc171c7d4190891e78234f791a3ac7b24'], + }, + ], + date: 1708030049641, + }, + }, + }, + }, + subjectMetadata: { + 'https://ens.domains': { + iconUrl: null, + name: 'ens.domains', + subjectType: 'website', + origin: 'https://ens.domains', + extensionId: null, + }, + 'https://app.ens.domains': { + iconUrl: 'https://app.ens.domains/favicon-32x32.png', + name: 'ENS', + subjectType: 'website', + origin: 'https://app.ens.domains', + extensionId: null, + }, + 'https://app.uniswap.org': { + iconUrl: 'https://app.uniswap.org/favicon.png', + name: 'Uniswap Interface', + subjectType: 'website', + origin: 'https://app.uniswap.org', + extensionId: null, + }, + 'https://www.dextools.io': { + iconUrl: 'https://www.dextools.io/app/favicon.ico', + name: 'DEXTools.io', + subjectType: 'website', + origin: 'https://www.dextools.io', + extensionId: null, + }, + 'https://coinmarketcap.com': { + iconUrl: 'https://coinmarketcap.com/favicon.ico', + name: 'CoinMarketCap', + subjectType: 'website', + origin: 'https://coinmarketcap.com', + extensionId: null, + }, + }, + }); + } + withNameController(data) { merge( this.fixture.data.NameController diff --git a/test/e2e/helpers.js b/test/e2e/helpers.js index e986c302df1d..210790844a50 100644 --- a/test/e2e/helpers.js +++ b/test/e2e/helpers.js @@ -719,6 +719,24 @@ const sendScreenToConfirmScreen = async ( await driver.fill('[data-testid="ens-input"]', recipientAddress); await driver.fill('.unit-input__input', quantity); if (process.env.MULTICHAIN) { + // check if element exists and click it + await driver + .findElement({ + text: 'I understand', + tag: 'button', + }) + .then( + (_found) => { + driver.clickElement({ + text: 'I understand', + tag: 'button', + }); + }, + (error) => { + console.error('Element not found.', error); + }, + ); + await driver.clickElement({ text: 'Continue', tag: 'button', diff --git a/test/e2e/mmi/specs/extension.visual.spec.ts b/test/e2e/mmi/specs/extension.visual.spec.ts index cb0bb9d88896..1f2176cf6b0b 100644 --- a/test/e2e/mmi/specs/extension.visual.spec.ts +++ b/test/e2e/mmi/specs/extension.visual.spec.ts @@ -76,7 +76,7 @@ test.describe('MMI extension', () => { ); }); - test('Custodian token management', async ({ page, context }) => { + test.skip('Custodian token management', async ({ page, context }) => { // Define const to compare in assertions const arrayWithoutCustodianAccounts = ['Account 1']; const arrayWithCustodianAccounts = [ diff --git a/test/e2e/mmi/specs/transactions.spec.ts b/test/e2e/mmi/specs/transactions.spec.ts index ecaeff9434c4..4cd6dab45361 100644 --- a/test/e2e/mmi/specs/transactions.spec.ts +++ b/test/e2e/mmi/specs/transactions.spec.ts @@ -72,7 +72,7 @@ const sendTransaction = async ( }; test.describe('MMI send', () => { - test('Send a transaction from one account to another and confirm it from custody', async ({ + test.skip('Send a transaction from one account to another and confirm it from custody', async ({ page, context, }) => { diff --git a/test/e2e/snaps/enums.js b/test/e2e/snaps/enums.js index 739150961d6d..3aa3b801b09d 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.2.3/', + TEST_SNAPS_WEBSITE_URL: 'https://metamask.github.io/snaps/test-snaps/2.4.0/', }; diff --git a/test/e2e/snaps/test-snap-dialog.spec.js b/test/e2e/snaps/test-snap-dialog.spec.js index ad45ba52d003..e6d3673a1585 100644 --- a/test/e2e/snaps/test-snap-dialog.spec.js +++ b/test/e2e/snaps/test-snap-dialog.spec.js @@ -193,7 +193,7 @@ describe('Test Snap Dialog', function () { ); // fill '2323' in form field - await driver.pasteIntoField('.MuiInput-input', '2323'); + await driver.pasteIntoField('.mm-input', '2323'); // click submit button await driver.clickElement({ diff --git a/test/e2e/snaps/test-snap-installed.spec.js b/test/e2e/snaps/test-snap-installed.spec.js index 8383f91ed8f2..24932fd0d49d 100644 --- a/test/e2e/snaps/test-snap-installed.spec.js +++ b/test/e2e/snaps/test-snap-installed.spec.js @@ -109,7 +109,7 @@ describe('Test Snap Installed', function () { assert.deepStrictEqual(events[0].properties, { snap_id: 'npm:@metamask/dialog-example-snap', origin: 'https://metamask.github.io', - version: '2.1.0', + version: '2.2.0', category: 'Snaps', locale: 'en', chain_id: '0x539', diff --git a/test/e2e/tests/account-token-list.spec.js b/test/e2e/tests/account-token-list.spec.js index 582a5cd59e90..1285027ed3e3 100644 --- a/test/e2e/tests/account-token-list.spec.js +++ b/test/e2e/tests/account-token-list.spec.js @@ -2,27 +2,24 @@ const { strict: assert } = require('assert'); const { withFixtures, defaultGanacheOptions, - unlockWallet, + logInWithBalanceValidation, } = require('../helpers'); + const FixtureBuilder = require('../fixture-builder'); -const { SMART_CONTRACTS } = require('../seeder/smart-contracts'); describe('Settings', function () { - const smartContract = SMART_CONTRACTS.ERC1155; it('Should match the value of token list item and account list item for eth conversion', async function () { await withFixtures( { - dapp: true, fixtures: new FixtureBuilder().build(), - defaultGanacheOptions, - smartContract, + ganacheOptions: defaultGanacheOptions, title: this.test.fullTitle(), }, - async ({ driver }) => { - await unlockWallet(driver); + async ({ driver, ganacheServer }) => { + await logInWithBalanceValidation(driver, ganacheServer); await driver.clickElement('[data-testid="home__asset-tab"]'); - const tokenValue = '0 ETH'; + const tokenValue = '25 ETH'; const tokenListAmount = await driver.findElement( '[data-testid="multichain-token-list-item-value"]', ); @@ -33,7 +30,7 @@ describe('Settings', function () { '.multichain-account-list-item .multichain-account-list-item__avatar-currency .currency-display-component__text', ); - assert.equal(await accountTokenValue.getText(), '0', 'ETH'); + assert.equal(await accountTokenValue.getText(), '25', 'ETH'); }, ); }); @@ -41,14 +38,12 @@ describe('Settings', function () { it('Should match the value of token list item and account list item for fiat conversion', async function () { await withFixtures( { - dapp: true, fixtures: new FixtureBuilder().build(), - defaultGanacheOptions, - smartContract, + ganacheOptions: defaultGanacheOptions, title: this.test.fullTitle(), }, - async ({ driver }) => { - await unlockWallet(driver); + async ({ driver, ganacheServer }) => { + await logInWithBalanceValidation(driver, ganacheServer); await driver.clickElement( '[data-testid="account-options-menu-button"]', @@ -65,18 +60,16 @@ describe('Settings', function () { ); await driver.clickElement('[data-testid="home__asset-tab"]'); - const tokenValue = '0 ETH'; const tokenListAmount = await driver.findElement( - '[data-testid="multichain-token-list-item-value"]', + '.eth-overview__primary-container', ); - assert.equal(await tokenListAmount.getText(), tokenValue); - + assert.equal(await tokenListAmount.getText(), '$42,500.00\nUSD'); await driver.clickElement('[data-testid="account-menu-icon"]'); const accountTokenValue = await driver.waitForSelector( - '.multichain-account-list-item .multichain-account-list-item__avatar-currency .currency-display-component__text', + '.multichain-account-list-item .multichain-account-list-item__asset', ); - assert.equal(await accountTokenValue.getText(), '0', 'ETH'); + assert.equal(await accountTokenValue.getText(), '$42,500.00USD'); }, ); }); diff --git a/test/e2e/tests/deprecated-networks.spec.js b/test/e2e/tests/deprecated-networks.spec.js index 36ae0519f796..3acf8507a05c 100644 --- a/test/e2e/tests/deprecated-networks.spec.js +++ b/test/e2e/tests/deprecated-networks.spec.js @@ -1,6 +1,12 @@ const { strict: assert } = require('assert'); const FixtureBuilder = require('../fixture-builder'); -const { withFixtures, unlockWallet } = require('../helpers'); +const { + withFixtures, + unlockWallet, + openDapp, + WINDOW_TITLES, +} = require('../helpers'); +const { CHAIN_IDS } = require('../../../shared/constants/network'); describe('Deprecated networks', function () { it('When selecting the Goerli test network, the users should see a warning message', async function () { @@ -31,4 +37,176 @@ describe('Deprecated networks', function () { }, ); }); + + it('Should show deprecation warning when switching to Arbitrum goerli testnet', async function () { + const TEST_CHAIN_ID = CHAIN_IDS.ARBITRUM_GOERLI; + async function mockRPCURLAndChainId(mockServer) { + return [ + await mockServer + .forPost('https://responsive-rpc.url/') + .thenCallback(() => ({ + statusCode: 200, + json: { + id: '1694444405781', + jsonrpc: '2.0', + result: TEST_CHAIN_ID, + }, + })), + ]; + } + + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .withPreferencesController({ useSafeChainsListValidation: false }) + .build(), + title: this.test.fullTitle(), + testSpecificMock: mockRPCURLAndChainId, + }, + async ({ driver }) => { + await unlockWallet(driver); + + await openDapp(driver); + await driver.executeScript(` + var params = [{ + chainId: "${TEST_CHAIN_ID}", + chainName: "Arbitrum Goerli", + nativeCurrency: { + name: "", + symbol: "ETH", + decimals: 18 + }, + rpcUrls: ["https://responsive-rpc.url/"], + blockExplorerUrls: [ "http://localhost:8080/api/customRPC" ] + }] + window.ethereum.request({ + method: 'wallet_addEthereumChain', + params + }) + `); + await driver.waitUntilXWindowHandles(3); + const windowHandles = await driver.getAllWindowHandles(); + const [extension] = windowHandles; + + await driver.switchToWindowWithTitle( + WINDOW_TITLES.Dialog, + windowHandles, + ); + + await driver.clickElement({ + tag: 'button', + text: 'Approve', + }); + + const switchNetworkBtn = await driver.findElement({ + tag: 'button', + text: 'Switch network', + }); + + await switchNetworkBtn.click(); + + await driver.waitUntilXWindowHandles(2); + await driver.switchToWindow(extension); + const deprecationWarningText = + 'Because of updates to the Ethereum system, the Goerli test network will be phased out soon.'; + const isDeprecationWarningDisplayed = await driver.isElementPresent({ + text: deprecationWarningText, + }); + + assert.equal( + isDeprecationWarningDisplayed, + true, + 'Goerli deprecation warning is not displayed', + ); + }, + ); + }); + + it('Should show deprecation warning when switching to Optimism goerli testnet', async function () { + const TEST_CHAIN_ID = CHAIN_IDS.OPTIMISM_TESTNET; + async function mockRPCURLAndChainId(mockServer) { + return [ + await mockServer + .forPost('https://responsive-rpc.url/') + .thenCallback(() => ({ + statusCode: 200, + json: { + id: '1694444405781', + jsonrpc: '2.0', + result: TEST_CHAIN_ID, + }, + })), + ]; + } + + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .withPreferencesController({ useSafeChainsListValidation: false }) + .build(), + title: this.test.fullTitle(), + testSpecificMock: mockRPCURLAndChainId, + }, + async ({ driver }) => { + await unlockWallet(driver); + + await openDapp(driver); + await driver.executeScript(` + var params = [{ + chainId: "${TEST_CHAIN_ID}", + chainName: "Optimism Goerli", + nativeCurrency: { + name: "", + symbol: "ETH", + decimals: 18 + }, + rpcUrls: ["https://responsive-rpc.url/"], + blockExplorerUrls: [ "http://localhost:8080/api/customRPC" ] + }] + window.ethereum.request({ + method: 'wallet_addEthereumChain', + params + }) + `); + await driver.waitUntilXWindowHandles(3); + const windowHandles = await driver.getAllWindowHandles(); + const [extension] = windowHandles; + + await driver.switchToWindowWithTitle( + WINDOW_TITLES.Dialog, + windowHandles, + ); + + await driver.clickElement({ + tag: 'button', + text: 'Approve', + }); + + const switchNetworkBtn = await driver.findElement({ + tag: 'button', + text: 'Switch network', + }); + + await switchNetworkBtn.click(); + + await driver.waitUntilXWindowHandles(2); + await driver.switchToWindow(extension); + const deprecationWarningText = + 'Because of updates to the Ethereum system, the Goerli test network will be phased out soon.'; + const isDeprecationWarningDisplayed = await driver.isElementPresent({ + text: deprecationWarningText, + }); + + assert.equal( + isDeprecationWarningDisplayed, + true, + 'Goerli deprecation warning is not displayed', + ); + }, + ); + }); }); diff --git a/test/e2e/tests/network/update-network.spec.ts b/test/e2e/tests/network/update-network.spec.ts new file mode 100644 index 000000000000..db5d2a43402b --- /dev/null +++ b/test/e2e/tests/network/update-network.spec.ts @@ -0,0 +1,113 @@ +import { strict as assert } from 'assert'; +import { Suite } from 'mocha'; +import FixtureBuilder from '../../fixture-builder'; +import { + defaultGanacheOptions, + unlockWallet, + withFixtures, +} from '../../helpers'; +import { Driver } from '../../webdriver/driver'; + +const selectors = { + accountOptionsMenuButton: '[data-testid="account-options-menu-button"]', + informationSymbol: '[data-testid="info-tooltip"]', + settingsOption: { text: 'Settings', tag: 'div' }, + networkOption: { text: 'Networks', tag: 'div' }, + generalOption: { text: 'General', tag: 'div' }, + ethereumNetwork: { text: 'Ethereum Mainnet', tag: 'div' }, + deleteButton: { text: 'Delete', tag: 'button' }, + cancelButton: { text: 'Cancel', tag: 'button' }, + saveButton: { text: 'Save', tag: 'button' }, + updatedNetworkDropDown: { tag: 'span', text: 'Update Network' }, + errorMessageInvalidUrl: { + tag: 'h6', + text: 'URLs require the appropriate HTTP/HTTPS prefix.', + }, + networkNameInputField: '[data-testid="network-form-network-name"]', + rpcUrlInputField: '[data-testid="network-form-rpc-url"]', + chainIdInputField: '[data-testid="network-form-chain-id"]', + errorContainer: '.settings-tab__error', +}; + +const inputData = { + networkName: 'Update Network', + rpcUrl: 'test', + chainId: '0x539', +}; + +async function navigateToEditNetwork(driver: Driver) { + await driver.clickElement(selectors.accountOptionsMenuButton); + await driver.clickElement(selectors.settingsOption); + await driver.clickElement(selectors.networkOption); +} +describe('Update Network:', function (this: Suite) { + it('update network details and validate the ui elements', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions: defaultGanacheOptions, + title: this.test?.fullTitle(), + }, + + async ({ driver }: { driver: Driver }) => { + await unlockWallet(driver); + await navigateToEditNetwork(driver); + await driver.fill( + selectors.networkNameInputField, + inputData.networkName, + ); + await driver.fill(selectors.chainIdInputField, inputData.chainId); + await driver.clickElement(selectors.saveButton); + + // Validate the network name is updated + const updatedNetworkNamePresent = await driver.isElementPresent( + selectors.updatedNetworkDropDown, + ); + assert.equal( + updatedNetworkNamePresent, + true, + 'Network name is not updated', + ); + + await navigateToEditNetwork(driver); + await driver.fill(selectors.rpcUrlInputField, inputData.rpcUrl); + + // Validate the error message that appears for the invalid url format + const errorMessage = await driver.isElementPresent( + selectors.errorMessageInvalidUrl, + ); + assert.equal( + errorMessage, + true, + 'Error message for the invalid url did not appear', + ); + + // Validate the Save button is disabled for the invalid url format + const saveButton = await driver.findElement(selectors.saveButton); + const saveButtonEnabled = await saveButton.isEnabled(); + assert.equal(saveButtonEnabled, false, 'Save button is enabled'); + + // Validate the information symbol appears for chain id + const informationSymbolAppears = await driver.isElementPresent( + selectors.informationSymbol, + ); + assert.equal( + informationSymbolAppears, + true, + 'Information symbol did not appear for chain id', + ); + + await driver.clickElement(selectors.ethereumNetwork); + + // Validate the Save,Cancel Delete button is not present for the default network + await driver.assertElementNotPresent(selectors.deleteButton); + await driver.assertElementNotPresent(selectors.cancelButton); + await driver.assertElementNotPresent(selectors.saveButton); + + // Validate the error does not appear for updating the network name and chain id + await driver.clickElement(selectors.generalOption); + await driver.assertElementNotPresent(selectors.errorContainer); + }, + ); + }); +}); \ No newline at end of file diff --git a/test/e2e/tests/ppom-blockaid-alert-erc20-approval.spec.js b/test/e2e/tests/ppom-blockaid-alert-erc20-approval.spec.js index 169372e5dbcf..5346a1fd67ad 100644 --- a/test/e2e/tests/ppom-blockaid-alert-erc20-approval.spec.js +++ b/test/e2e/tests/ppom-blockaid-alert-erc20-approval.spec.js @@ -210,7 +210,8 @@ async function mockInfura(mockServer) { } describe('PPOM Blockaid Alert - Malicious ERC20 Approval @no-mmi', function () { - it('should show banner alert', async function () { + // eslint-disable-next-line mocha/no-skipped-tests + it.skip('should show banner alert', async function () { await withFixtures( { dapp: true, @@ -232,7 +233,7 @@ describe('PPOM Blockaid Alert - Malicious ERC20 Approval @no-mmi', function () { const expectedTitle = 'This is a deceptive request'; const expectedDescription = - 'If you approve this request, a third party known for scams might take all your assets.'; + 'If you approve this request, you might lose your assets.'; // Click TestDapp button to send JSON-RPC request await driver.clickElement('#maliciousApprovalButton'); diff --git a/test/e2e/tests/ppom-blockaid-alert-erc20-transfer.spec.js b/test/e2e/tests/ppom-blockaid-alert-erc20-transfer.spec.js index 4935a5240bcd..2cedb98ebd51 100644 --- a/test/e2e/tests/ppom-blockaid-alert-erc20-transfer.spec.js +++ b/test/e2e/tests/ppom-blockaid-alert-erc20-transfer.spec.js @@ -161,7 +161,8 @@ async function mockInfura(mockServer) { } describe('PPOM Blockaid Alert - Malicious ERC20 Transfer @no-mmi', function () { - it('should show banner alert', async function () { + // eslint-disable-next-line mocha/no-skipped-tests + it.skip('should show banner alert', async function () { await withFixtures( { dapp: true, @@ -180,7 +181,7 @@ describe('PPOM Blockaid Alert - Malicious ERC20 Transfer @no-mmi', function () { async ({ driver }) => { const expectedTitle = 'This is a deceptive request'; const expectedDescription = - 'If you approve this request, a third party known for scams will take all your assets.'; + 'If you approve this request, you might lose your assets.'; await unlockWallet(driver); await openDapp(driver); diff --git a/test/e2e/tests/ppom-blockaid-alert-networks-support.spec.js b/test/e2e/tests/ppom-blockaid-alert-networks-support.spec.js index c3c0018205ec..f3ae72240a3a 100644 --- a/test/e2e/tests/ppom-blockaid-alert-networks-support.spec.js +++ b/test/e2e/tests/ppom-blockaid-alert-networks-support.spec.js @@ -47,7 +47,8 @@ async function mockInfuraWithMaliciousResponses(mockServer) { } describe('PPOM Blockaid Alert - Multiple Networks Support @no-mmi', function () { - it('should show banner alert after switchinig to another supported network', async function () { + // eslint-disable-next-line mocha/no-skipped-tests + it.skip('should show banner alert after switchinig to another supported network', async function () { await withFixtures( { dapp: true, @@ -65,12 +66,9 @@ describe('PPOM Blockaid Alert - Multiple Networks Support @no-mmi', function () async ({ driver }) => { const expectedTitle = 'This is a deceptive request'; - const expectedDescriptionMainnet = + const expectedDescription = 'If you approve this request, you might lose your assets.'; - const expectedDescriptionArbitrum = - 'If you approve this request, a third party known for scams will take all your assets.'; - await unlockWallet(driver); await openDapp(driver); @@ -97,8 +95,8 @@ describe('PPOM Blockaid Alert - Multiple Networks Support @no-mmi', function () `Banner alert not found. Expected Title: ${expectedTitle} \nExpected reason: approval_farming\n`, ); assert( - bannerAlertText.includes(expectedDescriptionMainnet), - `Unexpected banner alert description. Expected: ${expectedDescriptionMainnet} \nExpected reason: approval_farming\n`, + bannerAlertText.includes(expectedDescription), + `Unexpected banner alert description. Expected: ${expectedDescription} \nExpected reason: approval_farming\n`, ); await driver.clickElement({ text: 'Reject', tag: 'button' }); @@ -144,8 +142,8 @@ describe('PPOM Blockaid Alert - Multiple Networks Support @no-mmi', function () `Banner alert not found. Expected Title: ${expectedTitle} \nExpected reason: raw_native_token_transfer\n`, ); assert( - bannerAlertText.includes(expectedDescriptionArbitrum), - `Unexpected banner alert description. Expected: ${expectedDescriptionArbitrum} \nExpected reason: raw_native_token_transfer\n`, + bannerAlertText.includes(expectedDescription), + `Unexpected banner alert description. Expected: ${expectedDescription} \nExpected reason: raw_native_token_transfer\n`, ); }, ); diff --git a/test/e2e/tests/ppom-blockaid-alert-simple-send.spec.js b/test/e2e/tests/ppom-blockaid-alert-simple-send.spec.js index bf99c19c12c8..1dbebfea3c66 100644 --- a/test/e2e/tests/ppom-blockaid-alert-simple-send.spec.js +++ b/test/e2e/tests/ppom-blockaid-alert-simple-send.spec.js @@ -126,7 +126,8 @@ async function mockInfuraWithFailedResponses(mockServer) { * @see {@link https://wobbly-nutmeg-8a5.notion.site/MM-E2E-Testing-1e51b617f79240a49cd3271565c6e12d} */ describe('Simple Send Security Alert - Blockaid @no-mmi', function () { - it('should not show security alerts for benign requests', async function () { + // eslint-disable-next-line mocha/no-skipped-tests + it.skip('should not show security alerts for benign requests', async function () { await withFixtures( { dapp: true, @@ -158,7 +159,8 @@ describe('Simple Send Security Alert - Blockaid @no-mmi', function () { * 'malicious_domain'. Some other tests are found in other files: * e.g. test/e2e/flask/ppom-blockaid-alert-.spec.js */ - it('should show security alerts for malicious requests', async function () { + // eslint-disable-next-line mocha/no-skipped-tests + it.skip('should show security alerts for malicious requests', async function () { await withFixtures( { dapp: true, @@ -201,7 +203,8 @@ describe('Simple Send Security Alert - Blockaid @no-mmi', function () { ); }); - it('should show "Request may not be safe" if the PPOM request fails to check transaction', async function () { + // eslint-disable-next-line mocha/no-skipped-tests + it.skip('should show "Request may not be safe" if the PPOM request fails to check transaction', async function () { await withFixtures( { dapp: true, @@ -222,8 +225,8 @@ describe('Simple Send Security Alert - Blockaid @no-mmi', function () { await sendScreenToConfirmScreen( driver, - '0x985c30949c92df7a0bd42e0f3e3d539ece98db24', - '1', + '0xB8c77482e45F1F44dE1745F52C74426C631bDD52', + '1.1', ); // await driver.delay(100000) const expectedTitle = 'Request may not be safe'; diff --git a/test/e2e/tests/ppom-blockaid-alert-trade-order-farming.spec.js b/test/e2e/tests/ppom-blockaid-alert-trade-order-farming.spec.js index 39a6093741d6..26d67a999a56 100644 --- a/test/e2e/tests/ppom-blockaid-alert-trade-order-farming.spec.js +++ b/test/e2e/tests/ppom-blockaid-alert-trade-order-farming.spec.js @@ -90,7 +90,8 @@ async function mockInfura(mockServer) { } describe('PPOM Blockaid Alert - Set Trade farming order @no-mmi', function () { - it('should show banner alert', async function () { + // eslint-disable-next-line mocha/no-skipped-tests + it.skip('should show banner alert', async function () { await withFixtures( { dapp: true, diff --git a/test/e2e/tests/ppom-blockaid-alert.spec.js b/test/e2e/tests/ppom-blockaid-alert.spec.js index 1285665f977b..dcd2691b8be2 100644 --- a/test/e2e/tests/ppom-blockaid-alert.spec.js +++ b/test/e2e/tests/ppom-blockaid-alert.spec.js @@ -289,7 +289,8 @@ describe('Confirmation Security Alert - Blockaid @no-mmi', function () { ); }); - it('should show "Request may not be safe" if the PPOM request fails to check transaction', async function () { + // eslint-disable-next-line mocha/no-skipped-tests + it.skip('should show "Request may not be safe" if the PPOM request fails to check transaction', async function () { await withFixtures( { dapp: true, diff --git a/test/e2e/tests/ppom-blockaid-setApprovalForAll-farming.spec.js b/test/e2e/tests/ppom-blockaid-setApprovalForAll-farming.spec.js index 780ecc273d4f..d794c877bf81 100644 --- a/test/e2e/tests/ppom-blockaid-setApprovalForAll-farming.spec.js +++ b/test/e2e/tests/ppom-blockaid-setApprovalForAll-farming.spec.js @@ -248,7 +248,8 @@ async function mockInfura(mockServer) { } describe('PPOM Blockaid Alert - Set Approval to All @no-mmi', function () { - it('should show banner alert', async function () { + // eslint-disable-next-line mocha/no-skipped-tests + it.skip('should show banner alert', async function () { await withFixtures( { dapp: true, @@ -270,7 +271,7 @@ describe('PPOM Blockaid Alert - Set Approval to All @no-mmi', function () { const expectedTitle = 'This is a deceptive request'; const expectedDescription = - 'If you approve this request, a third party known for scams might take all your assets.'; + 'If you approve this request, you might lose your assets.'; // Click TestDapp button to send JSON-RPC request await driver.clickElement('#maliciousSetApprovalForAll'); diff --git a/test/e2e/tests/ppom-blockaid-toggle-metrics.spec.js b/test/e2e/tests/ppom-blockaid-toggle-metrics.spec.js index ec763422c112..fd4d3b6c9f05 100644 --- a/test/e2e/tests/ppom-blockaid-toggle-metrics.spec.js +++ b/test/e2e/tests/ppom-blockaid-toggle-metrics.spec.js @@ -52,7 +52,8 @@ async function mockServerCalls(mockServer) { } describe('PPOM Blockaid Alert - Metrics @no-mmi', function () { - it('Successfully track button toggle on/off', async function () { + // eslint-disable-next-line mocha/no-skipped-tests + it.skip('Successfully track button toggle on/off', async function () { await withFixtures( { dapp: true, diff --git a/test/e2e/tests/ppom-toggle-settings.spec.js b/test/e2e/tests/ppom-toggle-settings.spec.js index a355abed98a7..195d55654b7f 100644 --- a/test/e2e/tests/ppom-toggle-settings.spec.js +++ b/test/e2e/tests/ppom-toggle-settings.spec.js @@ -9,7 +9,8 @@ const { const FixtureBuilder = require('../fixture-builder'); describe('PPOM Settings @no-mmi', function () { - it('should not show the PPOM warning when toggle is off', async function () { + // eslint-disable-next-line mocha/no-skipped-tests + it.skip('should not show the PPOM warning when toggle is off', async function () { await withFixtures( { dapp: true, @@ -47,7 +48,8 @@ describe('PPOM Settings @no-mmi', function () { ); }); - it('should show the PPOM warning when the toggle is on', async function () { + // eslint-disable-next-line mocha/no-skipped-tests + it.skip('should show the PPOM warning when the toggle is on', async function () { await withFixtures( { dapp: true, diff --git a/test/e2e/tests/settings-search.spec.js b/test/e2e/tests/settings-search.spec.js index bffe62f7d18f..73572a6da761 100644 --- a/test/e2e/tests/settings-search.spec.js +++ b/test/e2e/tests/settings-search.spec.js @@ -14,7 +14,7 @@ describe('Settings Search', function () { security: 'Reveal Secret', alerts: 'Browsing a website', networks: 'Ethereum Mainnet', - experimental: 'Security alerts', + experimental: 'Nicknames', about: 'Terms of Use', }; @@ -98,7 +98,7 @@ describe('Settings Search', function () { }, ); }); - it('should find element inside the Security tab', async function () { + it('should find element inside the "Security & privacy" tab', async function () { await withFixtures( { fixtures: new FixtureBuilder().build(), diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json index 2c0573f4bc8b..d3269fd906e7 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json @@ -34,6 +34,7 @@ "showTestnetMessageInDropdown": true, "signatureSecurityAlertResponses": "object", "showBetaHeader": false, + "showPermissionsTour": true, "showProductTour": true, "showNetworkBanner": true, "showAccountBanner": true, @@ -129,7 +130,6 @@ }, "PPOMController": { "storageMetadata": {}, - "chainStatus": { "0x539": { "chainId": "0x539", "versionInfo": [] } }, "versionFileETag": "string" }, "PermissionController": { "subjects": "object" }, @@ -205,6 +205,7 @@ "snapStates": "object", "unencryptedSnapStates": "object" }, + "SnapInterfaceController": "object", "SnapsRegistry": { "database": "object", "lastUpdated": "object" }, "SubjectMetadataController": { "subjectMetadata": "object" }, "SwapsController": { diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json index 7b303208da63..de685a700d37 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -69,6 +69,7 @@ "showTestnetMessageInDropdown": true, "signatureSecurityAlertResponses": "object", "showBetaHeader": false, + "showPermissionsTour": true, "showProductTour": true, "showNetworkBanner": true, "showAccountBanner": true, @@ -173,6 +174,7 @@ "database": "object", "lastUpdated": "object", "notifications": "object", + "interfaces": "object", "names": "object", "nameSources": "object", "userOperations": "object", @@ -220,7 +222,6 @@ "pendingApprovalCount": "number", "approvalFlows": "object", "storageMetadata": {}, - "chainStatus": { "0x539": { "chainId": "0x539", "versionInfo": [] } }, "versionFileETag": "string" }, "send": "object", diff --git a/test/e2e/webdriver/chrome.js b/test/e2e/webdriver/chrome.js index 0a74ec09089f..e238f7b50521 100644 --- a/test/e2e/webdriver/chrome.js +++ b/test/e2e/webdriver/chrome.js @@ -20,6 +20,8 @@ class ChromeDriver { `--proxy-server=${HTTPS_PROXY_HOST}`, // Set proxy in the way that doesn't interfere with Selenium Manager '--disable-features=OptimizationGuideModelDownloading,OptimizationHintsFetching,OptimizationTargetPredicition,OptimizationHints,NetworkTimeServiceQuerying', // Stop chrome from calling home so much (auto-downloads of AI models; time sync) '--disable-component-update', // Stop chrome from calling home so much (auto-update) + `--disable-gpu`, + `--disable-dev-shm-usage`, ]; if (process.env.MULTIPROVIDER) { @@ -29,6 +31,7 @@ class ChromeDriver { } else { args.push(`load-extension=${process.cwd()}/dist/chrome`); } + if (openDevToolsForTabs) { args.push('--auto-open-devtools-for-tabs'); } diff --git a/test/scenarios/13. sign/sign typed data v3 with hardware wallet.csv b/test/scenarios/13. sign/sign typed data v3 with hardware wallet.csv new file mode 100644 index 000000000000..cebeaffd5c02 --- /dev/null +++ b/test/scenarios/13. sign/sign typed data v3 with hardware wallet.csv @@ -0,0 +1,25 @@ +Steps,Test Steps,Preconditions,Test Data,Expected Result,Notes +1,Open the extension.,,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,,password (8 characters min).,"The Ether balance is shown on the overview. +The wallet address is shown on the overview.", +3,"Click on account menu icon. Click ""Add account or hardware wallet"".",,,"The ""Add account"" modal is shown.", +4,"On ""Add account"" modal, click ""Add hardware wallet"" button.",,,"""Connect a hardware wallet"" screen is shown. User can choose between different options to connect a hardware: Ledger, Trezor, Lattice, or QR-based. ""Continue"" button is disabled.", +5,Choose an option to connect hardware wallet.,We need to have a QR-based hardware wallet set up to test this functionality.,"e.g. choose ""QR-based""","""Continue"" button is enabled.", +6,"Unlock the QR-based wallet and sync it with MetaMask.",,Password for hardware wallet,"Hardware wallet is detected by MetaMask. ""Select an account"" screen is shown on MetaMask, accounts on hardware wallet are shown on this screen.","Note: using Ledger or Trezor won't work for this flow, since they don't support Sign Typed Data V3." +7,"Choose one or multiples accounts that user wants to connect. Then click ""Unlock"".",,,, +8,Click account menu icon to open accounts list.,,,"In accounts list, all selected hardware wallet accounts are shown, and they are all flagged with harware wallet name to be distinguished from other accounts.", +9,Select one hardware wallet account.,,,"The Ether balance for the selected hardware wallet account is shown on the overview. +The selected account address is shown on the overview.", +10,Open the test dapp in another tab.,,https://metamask.github.io/test-dapp/,, +11,Click Connect.,,,"The MetaMask popup is opened with the Connect with MetaMask screen displayed. +Your imported hardware wallet account is selected.", +12,Click Next and Connect with the hardware wallet account.,,,"The MetaMask popup is closed. +You are connected to the test dapp.", +13,"Click ""Sign Typed Data V3"".",,,"The MetaMask popup is open. +The signature message is displayed in JSON formatting.", +14,"Click ""Sign"".",,,The QR code modal with the signature request is displayed., +15,"Scan the QR code with the QR-based wallet.",,,The signature QR code is displayed in the QR-based wallet., +15,"Click ""Get Signature"".",,,The computer camera is opened., +16,Scan the Signature from the QR-based wallet.,,,"The signature hash is displayed in the test-dapp result. +The MetaMask popup is closed.", +17,Verify signed hash.,,,The signed address is correctly verified.,The address shown in the test dapp is the same as the hardware wallet account. diff --git a/test/scenarios/13. sign/sign typed data v4 with hardware wallet.csv b/test/scenarios/13. sign/sign typed data v4 with hardware wallet.csv new file mode 100644 index 000000000000..e7a851260ecf --- /dev/null +++ b/test/scenarios/13. sign/sign typed data v4 with hardware wallet.csv @@ -0,0 +1,21 @@ +Steps,Test Steps,Preconditions,Test Data,Expected Result,Notes +1,Open the extension.,,,The Welcome Back screen is shown., +2,Proceed to Unlock the wallet.,,password (8 characters min).,"The Ether balance is shown on the overview. +The wallet address is shown on the overview.", +3,"Click on account menu icon. Click ""Add account or hardware wallet"".",,,"The ""Add account"" modal is shown.", +4,"On ""Add account"" modal, click ""Add hardware wallet"" button.",,,"""Connect a hardware wallet"" screen is shown. User can choose between different options to connect a hardware: Ledger, Trezor, Lattice, or QR-based. ""Continue"" button is disabled.", +5,Choose an option to connect hardware wallet.,We need to have a hardware wallet set up to test this functionality.,"e.g. choose ""Ledger""","""Continue"" button is enabled.", +6,"Plug the hardware wallet directly into computer, then unlock it.",,Password for hardware wallet,"Hardware wallet is detected by MetaMask. ""Select an account"" screen is shown on MetaMask, accounts on hardware wallet are shown on this screen.","If you use Ledger, you need to open the Ethereum app on Ledger. If you use Trezor, make sure you use the correct passphrase." +7,"Choose one or multiples accounts that user wants to connect. Then click ""Unlock"".",,,, +8,Click account menu icon to open accounts list.,,,"In accounts list, all selected hardware wallet accounts are shown, and they are all flagged with harware wallet name to be distinguished from other accounts.", +9,Select one hardware wallet account.,,,"The Ether balance for the selected hardware wallet account is shown on the overview. +The selected account address is shown on the overview.", +10,Open the test dapp in another tab.,,https://metamask.github.io/test-dapp/,, +11,Click Connect.,,,"The MetaMask popup is opened with the Connect with MetaMask screen displayed. +Your imported hardware wallet account is selected.", +12,Click Next and Connect with the hardware wallet account.,,,"The MetaMask popup is closed. +You are connected to the test dapp.", +13,"Click ""Sign Typed Data V4"".",,,"The info modal instructions for signing with hardware wallet is displayed. +The signature message is in JSON formatting.", +14,Accept the Signature in the hardware wallet device.,,,The signature hash is displayed on the test dapp., +15,Verify signed hash.,,,The signed address is correctly verified.,The address shown in the test dapp is the same as the hardware wallet account. diff --git a/test/scenarios/17. settings/advanced/show-hex-data.csv b/test/scenarios/17. settings/advanced/show-hex-data.csv new file mode 100644 index 000000000000..8f6be090fcef --- /dev/null +++ b/test/scenarios/17. settings/advanced/show-hex-data.csv @@ -0,0 +1,13 @@ +Step,Test Steps,Test Data,Expected Result,Notes +1,Open the extension.,,The Welcome Back screen is shown, +2,Proceed to Unlock the wallet.,password (8 characters min),, +3,Click on the right corner setting(three dots) and click on the Settings from menu.,,Settings page appears, +4,Click on the Advanced tab.,,Advanced settings page appears, +5,Validate that the 'Show hex data' is default to off.,,The toggle is off by default, +6,Toggle the Hex Data switch.,,The switch is turned on as expected, +7,Click on the MetaMask logo,,Home page appears, +7,Navigate to the Send transaction in the home menu.,,Send transaction appears, +8,"Enter an address ",0x56F2e03c8D30649818c022a9759CF43B240D08B3,Address is entered and the Hex Data text box is shown as expected, +9,"Enter the amount and hex data ",amount 0.1 and hex data '0xabc',Amount and hex data are entered as expected, +10,Click on the next page button.,,The next page is shown, +11," Validate that the hex data is shown in the tab.",,The hex data is shown as expected, \ No newline at end of file diff --git a/test/scenarios/17. settings/general/change-language.csv b/test/scenarios/17. settings/general/change-language.csv index 7e75b32b1bd8..374c9125f3bf 100644 --- a/test/scenarios/17. settings/general/change-language.csv +++ b/test/scenarios/17. settings/general/change-language.csv @@ -1,15 +1,13 @@ -Verify that the application handles different local times appropriately - Step,Test Steps,Test Data,Expected Result,Notes 1,Open the extension.,,The Welcome Back screen is shown, 2,Proceed to Unlock the wallet.,password (8 characters min),, 3,Click on the right corner setting(three dots).,,General settings page appears, 4,Check the default language selected in the drop down For example :- local time in the Mac machine is set to 3:30 pm EST,,Default selected language should be English, - -Verify that the user can scroll through the language dropdown after resizing the browser window. - -Step,Test Steps,Test Data,Expected Result,Notes -1,Open the extension.,,The Welcome Back screen is shown, -2,Proceed to Unlock the wallet.,password (8 characters min),, -3,Click on the right corner setting(three dots).,,General settings page appears, -4,Check the language drop down has a scroll functionality and resize the browser window,,Drop down works as expected and the scroll is working, \ No newline at end of file +5,Check the language drop down has a scroll functionality and resize the browser window,,Drop down works as expected and the scroll is working, +6,User selects Spanish language and verify that changing the language from the default,,Spanish changes the language of the application, +7,Verify that language persists with page refresh sessions and logout,,The language persists, +8,Select Dansk language and verify that navigating to a different page does not affect,,Danish the language of the application, +9,Navigate to the send screen and enter invalid account and verify that error messages are updated with the selected language,,Error messages are updated with the selected language change, +10,Select मानक हिन्दी language and verify that tooltips are updated for the home screen for account and bridge,,Tooltips are updated with the selected language change, +11,Select Magyar language and verify that hypertext are updated in nft tab,,Hypertext are updated with the selected language change, +12,User selects العربية language and verify that page indent with the selected language change,,Page indent with the selected language change, diff --git a/test/scenarios/2. keyring/connect hardware wallet.csv b/test/scenarios/2. keyring/connect hardware wallet.csv index 636a6df04ab8..fa11b9f58ced 100644 --- a/test/scenarios/2. keyring/connect hardware wallet.csv +++ b/test/scenarios/2. keyring/connect hardware wallet.csv @@ -4,7 +4,7 @@ Step,Test steps,Preconditions,Test data,Expected result,Notes 3,"Keep Ethereum Mainnet as the selected network. Click on account menu icon. Click ""Add account or hardware wallet"".",,,"The ""Add account"" modal is shown.", 4,"On ""Add account"" modal, click ""Add hardware wallet"" button.",,,"""Connect a hardware wallet"" screen is shown. User can choose between different options to connect a hardware: Ledger, Trezor, Lattice, or QR-based. ""Continue"" button is disabled.", 5,Choose an option to connect hardware wallet.,We need to have a hardware wallet setted up to test this functionality.,"e.g. choose ""Ledger""","""Continue"" button is enabled.", -6,"Plug the hardware wallet directly into computer, then unlock it.",,password for hardware wallet,"Hardware wallet is detected by MetaMask. ""Select an account"" screen is shown on MetaMask, accounts on hardware wallet are shown on this screen.","If you use Ledger, you need to open the Ethereum app on Ledger. If you use Trazor, make sure you use the correct passphrase." +6,"Plug the hardware wallet directly into computer, then unlock it.",,password for hardware wallet,"Hardware wallet is detected by MetaMask. ""Select an account"" screen is shown on MetaMask, accounts on hardware wallet are shown on this screen.","If you use Ledger, you need to open the Ethereum app on Ledger. If you use Trezor, make sure you use the correct passphrase." 7,"Choose one or multiples accounts that user wants to connect. Then click ""Unlock"".",,,, 8,Click account menu icon to open accounts list.,,,"In accounts list, all selected hardware wallet accounts are shown, and they are all flagged with harware wallet name to be distinguished from other accounts.", 9,"Select one hardware wallet account. ",,,"The Ether balance for the selected hardware wallet account is shown on the overview. The selected account address is shown on the overview. The selected network is Ethereum Mainnet. ", \ No newline at end of file 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 4786f35c4763..08c818d0e5ef 100644 --- a/ui/components/app/metamask-template-renderer/safe-component-list.js +++ b/ui/components/app/metamask-template-renderer/safe-component-list.js @@ -22,6 +22,9 @@ import { Copyable } from '../snaps/copyable'; import Spinner from '../../ui/spinner'; import { SnapUIMarkdown } from '../snaps/snap-ui-markdown'; import { SnapUIImage } from '../snaps/snap-ui-image'; +import { SnapUIInput } from '../snaps/snap-ui-input'; +import { SnapUIForm } from '../snaps/snap-ui-form'; +import { SnapUIButton } from '../snaps/snap-ui-button'; ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) import { CreateSnapAccount } from '../../../pages/create-snap-account'; @@ -66,6 +69,9 @@ export const safeComponentList = { Spinner, ConfirmInfoRow, ConfirmInfoRowAddress, + SnapUIInput, + SnapUIButton, + SnapUIForm, ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) CreateSnapAccount, diff --git a/ui/components/app/metamask-translation/README.mdx b/ui/components/app/metamask-translation/README.mdx deleted file mode 100644 index 79fb503e9089..000000000000 --- a/ui/components/app/metamask-translation/README.mdx +++ /dev/null @@ -1,36 +0,0 @@ -import { Story, Canvas, ArgsTable } from '@storybook/addon-docs'; - -import MetaMaskTranslation from '.'; - -# MetaMask Translation - -MetaMaskTranslation is a simple helper component for adding full translation -support to the template system. We do pass the translation function to the -template getValues function, but passing it React components as variables -would require React to be in scope, and breaks the object pattern paradigm. - -This component gets around that by converting variables that are templates -themselves into tiny React trees. This component does additional validation -to make sure that the tree has a single root node, with maximum two leaves. -Each subnode can have a maximum of one child that must be a string. - -This enforces a maximum recursion depth of 2, preventing translation strings -from being performance hogs. We could further limit this, and also attenuate -the safeComponentList for what kind of components we allow these special -trees to contain. - - - - - -## Props - -{/* -ArgsTable doesn't work with SectionShape - -*/} - -| Name | Description | -| ---------------- | ------------------------------------------------------------------------------------------------------- | -| `translationKey` | Translation object key `string` | -| `variables` | Array of variables for the MetaMaskTranslation. Can be an array of `string`, `number` or `SectionShape` | diff --git a/ui/components/app/metamask-translation/metamask-translation.stories.js b/ui/components/app/metamask-translation/metamask-translation.stories.js deleted file mode 100644 index 5ee88f85b15d..000000000000 --- a/ui/components/app/metamask-translation/metamask-translation.stories.js +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import { groupBy } from 'lodash'; -import en from '../../../../app/_locales/en/messages.json'; -import README from './README.mdx'; -import MetaMaskTranslation from './metamask-translation'; - -const { keysWithoutSubstitution } = groupBy(Object.keys(en), (key) => { - if (en[key].message.includes('$1')) { - return 'keysWithSubstitution'; - } - return 'keysWithoutSubstitution'; -}); - -export default { - title: 'Components/App/MetamaskTranslation', - - component: MetaMaskTranslation, - parameters: { - docs: { - page: README, - }, - }, - argTypes: { - translationKey: { options: keysWithoutSubstitution, control: 'select' }, - variables: { control: 'array' }, - }, -}; - -export const DefaultStory = (args) => { - return ; -}; - -DefaultStory.storyName = 'Default'; -DefaultStory.args = { - translationKey: keysWithoutSubstitution[0], -}; - -export const WithTemplate = (args) => ( - {args.translationKey}]} - /> -); - -WithTemplate.args = { - translationKey: keysWithoutSubstitution[0], -}; diff --git a/ui/components/app/snaps/insight-warnings/insight-warnings.js b/ui/components/app/snaps/insight-warnings/insight-warnings.js index ff0e87e63d85..f36649535ee8 100644 --- a/ui/components/app/snaps/insight-warnings/insight-warnings.js +++ b/ui/components/app/snaps/insight-warnings/insight-warnings.js @@ -72,12 +72,12 @@ export default function InsightWarnings({ return ( {warnings.map((warning, idx) => { - const { snapId, content } = warning; + const { snapId, id } = warning; return ( handleWarningClick(snapId)} isCollapsable diff --git a/ui/components/app/snaps/snap-home-page/snap-home-renderer.js b/ui/components/app/snaps/snap-home-page/snap-home-renderer.js index 97e2ea411974..01f5039f5959 100644 --- a/ui/components/app/snaps/snap-home-page/snap-home-renderer.js +++ b/ui/components/app/snaps/snap-home-page/snap-home-renderer.js @@ -1,6 +1,6 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import PropTypes from 'prop-types'; -import { useSelector } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { Box, Text } from '../../../component-library'; import { SnapUIRenderer } from '../snap-ui-renderer'; import { getTargetSubjectMetadata } from '../../../../selectors'; @@ -10,9 +10,11 @@ import { DelineatorType } from '../../../../helpers/constants/snaps'; import { TextVariant } from '../../../../helpers/constants/design-system'; import { Copyable } from '../copyable'; import { useI18nContext } from '../../../../hooks/useI18nContext'; +import { deleteInterface } from '../../../../store/actions'; import { useSnapHome } from './useSnapHome'; export const SnapHomeRenderer = ({ snapId }) => { + const dispatch = useDispatch(); const t = useI18nContext(); const targetSubjectMetadata = useSelector((state) => getTargetSubjectMetadata(state, snapId), @@ -21,7 +23,11 @@ export const SnapHomeRenderer = ({ snapId }) => { const snapName = getSnapName(snapId, targetSubjectMetadata); const { data, error, loading } = useSnapHome({ snapId }); - const content = !loading && !error && data?.content; + const interfaceId = !loading && !error ? data?.id : undefined; + + useEffect(() => { + return () => interfaceId && dispatch(deleteInterface(interfaceId)); + }, [interfaceId]); return ( @@ -33,8 +39,12 @@ export const SnapHomeRenderer = ({ snapId }) => { )} - {(content || loading) && ( - + {(interfaceId || loading) && ( + )} ); diff --git a/ui/components/app/snaps/snap-insight/snap-insight.js b/ui/components/app/snaps/snap-insight/snap-insight.js index 92db3a54de76..1a754994af09 100644 --- a/ui/components/app/snaps/snap-insight/snap-insight.js +++ b/ui/components/app/snaps/snap-insight/snap-insight.js @@ -1,17 +1,8 @@ -import React, { - ///: BEGIN:ONLY_INCLUDE_IF(build-flask) - useEffect, - ///: END:ONLY_INCLUDE_IF -} from 'react'; +import React, { useEffect } from 'react'; import PropTypes from 'prop-types'; -import { - useSelector, - ///: BEGIN:ONLY_INCLUDE_IF(build-flask) - useDispatch, - ///: END:ONLY_INCLUDE_IF -} from 'react-redux'; +import { useSelector, useDispatch } from 'react-redux'; import { Text } from '../../../component-library'; import { AlignItems, @@ -29,9 +20,14 @@ import { DelineatorType } from '../../../../helpers/constants/snaps'; import { getSnapName } from '../../../../helpers/utils/util'; import { Copyable } from '../copyable'; import { getTargetSubjectMetadata } from '../../../../selectors'; -///: BEGIN:ONLY_INCLUDE_IF(build-flask) -import { trackInsightSnapUsage } from '../../../../store/actions'; -///: END:ONLY_INCLUDE_IF +import { + ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-mmi,build-beta) + deleteInterface, + ///: END:ONLY_INCLUDE_IF + ///: BEGIN:ONLY_INCLUDE_IF(build-flask) + trackInsightSnapUsage, + ///: END:ONLY_INCLUDE_IF +} from '../../../../store/actions'; ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-mmi,build-beta) import { useTransactionInsightSnaps } from '../../../../hooks/snaps/useTransactionInsightSnaps'; ///: END:ONLY_INCLUDE_IF @@ -46,13 +42,13 @@ export const SnapInsight = ({ insightHookParams, ///: END:ONLY_INCLUDE_IF }) => { + const dispatch = useDispatch(); const t = useI18nContext(); - let error, content; + let error, interfaceId; let isLoading = loading; ///: BEGIN:ONLY_INCLUDE_IF(build-flask) error = data?.error; - content = data?.response?.content; - const dispatch = useDispatch(); + interfaceId = data?.response?.id; useEffect(() => { const trackInsightUsage = async () => { try { @@ -68,8 +64,14 @@ export const SnapInsight = ({ ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-mmi,build-beta) const insights = useTransactionInsightSnaps(insightHookParams); error = insights.data?.[0]?.error; - content = insights.data?.[0]?.response?.content; + interfaceId = insights.data?.[0]?.response?.id; isLoading = insights.loading; + + useEffect(() => { + return () => { + interfaceId && dispatch(deleteInterface(interfaceId)); + }; + }, [interfaceId]); ///: END:ONLY_INCLUDE_IF const targetSubjectMetadata = useSelector((state) => @@ -78,7 +80,8 @@ export const SnapInsight = ({ const snapName = getSnapName(snapId, targetSubjectMetadata); - const hasNoData = !error && !isLoading && !content; + const hasNoData = !error && !isLoading && !interfaceId; + return ( - {isLoading || content ? ( + {isLoading || interfaceId ? ( diff --git a/ui/components/app/snaps/snap-ui-button/index.ts b/ui/components/app/snaps/snap-ui-button/index.ts new file mode 100644 index 000000000000..aa43f1eb1d90 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-button/index.ts @@ -0,0 +1 @@ +export * from './snap-ui-button'; 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 new file mode 100644 index 000000000000..622cd67adcb8 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-button/snap-ui-button.tsx @@ -0,0 +1,37 @@ +import React, { FunctionComponent, MouseEvent as ReactMouseEvent } from 'react'; +import { ButtonType, UserInputEventType } from '@metamask/snaps-sdk'; +import { Button, ButtonProps } from '../../../component-library'; +import { useSnapInterfaceContext } from '../../../../contexts/snaps'; + +export type SnapUIButtonProps = { + name?: string; +}; + +export const SnapUIButton: FunctionComponent< + SnapUIButtonProps & ButtonProps<'button'> +> = ({ name, children, type, ...props }) => { + const { handleEvent } = useSnapInterfaceContext(); + + const handleClick = (event: ReactMouseEvent) => { + if (type === ButtonType.Button) { + event.preventDefault(); + } + + handleEvent(UserInputEventType.ButtonClickEvent, name); + }; + + return ( + + ); +}; diff --git a/ui/components/app/snaps/snap-ui-form/index.ts b/ui/components/app/snaps/snap-ui-form/index.ts new file mode 100644 index 000000000000..e3e4ad54897f --- /dev/null +++ b/ui/components/app/snaps/snap-ui-form/index.ts @@ -0,0 +1 @@ +export * from './snap-ui-form'; 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 new file mode 100644 index 000000000000..e2a50edc98a0 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-form/snap-ui-form.tsx @@ -0,0 +1,25 @@ +import React, { FormEvent, FunctionComponent } from 'react'; +import { UserInputEventType } from '@metamask/snaps-sdk'; +import { useSnapInterfaceContext } from '../../../../contexts/snaps'; + +export type SnapUIFormProps = { + name: string; +}; + +export const SnapUIForm: FunctionComponent = ({ + children, + name, +}) => { + const { handleEvent } = useSnapInterfaceContext(); + + const handleSubmit = (event: FormEvent) => { + event.preventDefault(); + handleEvent(UserInputEventType.FormSubmitEvent, name); + }; + + return ( +
+ {children} +
+ ); +}; diff --git a/ui/components/app/snaps/snap-ui-input/index.ts b/ui/components/app/snaps/snap-ui-input/index.ts new file mode 100644 index 000000000000..f696453186b0 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-input/index.ts @@ -0,0 +1 @@ +export * from './snap-ui-input'; 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 new file mode 100644 index 000000000000..741a18f65568 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-input/snap-ui-input.tsx @@ -0,0 +1,45 @@ +import React, { + ChangeEvent, + FunctionComponent, + useEffect, + useState, +} from 'react'; +import { useSnapInterfaceContext } from '../../../../contexts/snaps'; +import { FormTextField, FormTextFieldProps } from '../../../component-library'; + +export type SnapUIInputProps = { + name: string; + form?: string; +}; + +export const SnapUIInput: FunctionComponent< + SnapUIInputProps & FormTextFieldProps<'div'> +> = ({ name, form, ...props }) => { + const { handleInputChange, getValue } = useSnapInterfaceContext(); + + const initialValue = getValue(name, form); + + const [value, setValue] = useState(initialValue ?? ''); + + useEffect(() => { + if (initialValue) { + setValue(initialValue); + } + }, [initialValue]); + + const handleChange = (event: ChangeEvent) => { + setValue(event.target.value); + handleInputChange(name, event.target.value ?? null, form); + }; + return ( + + ); +}; diff --git a/ui/components/app/snaps/snap-ui-markdown/index.scss b/ui/components/app/snaps/snap-ui-markdown/index.scss index e7cad21bcfad..17cc5a212854 100644 --- a/ui/components/app/snaps/snap-ui-markdown/index.scss +++ b/ui/components/app/snaps/snap-ui-markdown/index.scss @@ -9,5 +9,10 @@ & > span:last-child { margin-inline-start: 2px; } + + & .mm-icon--size-inherit { + // Fixes the icon misalignment in ButtonLink when using ButtonLinkSize.Inherit + top: 0.1em; + } } } diff --git a/ui/components/app/snaps/snap-ui-markdown/snap-ui-markdown.js b/ui/components/app/snaps/snap-ui-markdown/snap-ui-markdown.js index 96c9d7e2be5a..d91b2b377c4e 100644 --- a/ui/components/app/snaps/snap-ui-markdown/snap-ui-markdown.js +++ b/ui/components/app/snaps/snap-ui-markdown/snap-ui-markdown.js @@ -29,8 +29,6 @@ const Paragraph = (props) => ( const Link = ({ onClick, children, ...rest }) => ( = ({ element }) => ({ + element: 'ConfirmInfoRowAddress', + props: { + address: element.value, + }, +}); diff --git a/ui/components/app/snaps/snap-ui-renderer/components/button.ts b/ui/components/app/snaps/snap-ui-renderer/components/button.ts new file mode 100644 index 000000000000..d2f4d6e2f5b3 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-renderer/components/button.ts @@ -0,0 +1,12 @@ +import { Button } from '@metamask/snaps-sdk'; +import { UIComponentFactory } from './types'; + +export const button: UIComponentFactory + +`; diff --git a/ui/components/multichain/badge-status/badge-status.stories.tsx b/ui/components/multichain/badge-status/badge-status.stories.tsx new file mode 100644 index 000000000000..b6cc485ef0e6 --- /dev/null +++ b/ui/components/multichain/badge-status/badge-status.stories.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import { + BackgroundColor, + BorderColor, + Color, +} from '../../../helpers/constants/design-system'; +import { BadgeStatus } from './badge-status'; + +export default { + title: 'Components/Multichain/BadgeStatus', + component: BadgeStatus, + argTypes: { + badgeBackgroundColor: { + control: 'text', + }, + badgeBorderColor: { + control: 'text', + }, + text: { + control: 'text', + }, + address: { + control: 'text', + }, + isConnectedAndNotActive: { + control: 'boolean', + }, + }, + args: { + badgeBackgroundColor: BackgroundColor.successDefault, + badgeBorderColor: BackgroundColor.backgroundDefault, + address: '0x1', + text: 'Tooltip', + }, +}; + +const Template = (args) => { + return ; +}; + +export const DefaultStory = Template.bind({}); + +export const NotConnectedStory = Template.bind({}); +NotConnectedStory.args = { + badgeBackgroundColor: Color.borderMuted, + badgeBorderColor: BackgroundColor.backgroundDefault, +}; + +export const ConnectedNotActiveStory = Template.bind({}); +ConnectedNotActiveStory.args = { + badgeBackgroundColor: BackgroundColor.backgroundDefault, + badgeBorderColor: BorderColor.successDefault, + isConnectedAndNotActive: true, +}; diff --git a/ui/components/multichain/badge-status/badge-status.test.js b/ui/components/multichain/badge-status/badge-status.test.js new file mode 100644 index 000000000000..7c1212444c69 --- /dev/null +++ b/ui/components/multichain/badge-status/badge-status.test.js @@ -0,0 +1,39 @@ +import React from 'react'; +import mockState from '../../../../test/data/mock-state.json'; + +import { + BackgroundColor, + BorderColor, +} from '../../../helpers/constants/design-system'; +import configureStore from '../../../store/store'; +import { renderWithProvider } from '../../../../test/jest'; +import { BadgeStatus } from './badge-status'; + +describe('Badge Status', () => { + const render = (props = {}, state = {}) => { + const store = configureStore({ + metamask: { + ...mockState.metamask, + providerConfig: { + chainId: '0x99', + }, + ...state, + }, + }); + const DEFAULT_PROPS = { + badgeBackgroundColor: BackgroundColor.backgroundDefault, + badgeBorderColor: BorderColor.successDefault, + isConnectedAndNotActive: true, + address: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', + text: 'Not Connected', + }; + return renderWithProvider( + , + store, + ); + }; + it('should render correctly', () => { + const { container } = render({}, { useBlockie: true }); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/ui/components/multichain/badge-status/badge-status.tsx b/ui/components/multichain/badge-status/badge-status.tsx new file mode 100644 index 000000000000..bccc3d229669 --- /dev/null +++ b/ui/components/multichain/badge-status/badge-status.tsx @@ -0,0 +1,85 @@ +import React from 'react'; +import classNames from 'classnames'; +import { useSelector } from 'react-redux'; +import { + AlignItems, + BackgroundColor, + BorderColor, + BorderRadius, + Display, + JustifyContent, +} from '../../../helpers/constants/design-system'; +import { + AvatarAccount, + AvatarAccountSize, + AvatarAccountVariant, + BadgeWrapper, + Box, + BoxProps, +} from '../../component-library'; +import { getUseBlockie } from '../../../selectors'; +import Tooltip from '../../ui/tooltip'; +import { BadgeStatusProps } from './badge-status.types'; + +export const BadgeStatus: React.FC = ({ + className = '', + badgeBackgroundColor = BackgroundColor.backgroundAlternative, + badgeBorderColor = BorderColor.borderMuted, + address, + isConnectedAndNotActive = false, + text, + ...props +}): JSX.Element => { + const useBlockie = useSelector(getUseBlockie); + + return ( + )} + > + + + } + > + + + + + ); +}; diff --git a/ui/components/multichain/badge-status/badge-status.types.tsx b/ui/components/multichain/badge-status/badge-status.types.tsx new file mode 100644 index 000000000000..5038148a370f --- /dev/null +++ b/ui/components/multichain/badge-status/badge-status.types.tsx @@ -0,0 +1,30 @@ +import { + BackgroundColor, + BorderColor, +} from '../../../helpers/constants/design-system'; +import type { StyleUtilityProps } from '../../component-library/box'; + +export interface BadgeStatusProps extends StyleUtilityProps { + /** * Additional class name for the ImportTokenLink component. */ + className?: string; + /** + * Border color based on the connection status + */ + badgeBorderColor?: BorderColor; + /** + * Background Color of Badge + */ + badgeBackgroundColor?: BackgroundColor; + /** + * Connection status message on Tooltip + */ + text: string; + /** + * To determine connection status + */ + isConnectedAndNotActive: boolean; + /** + * Address for AvatarAccount + */ + address: string; +} diff --git a/ui/components/multichain/badge-status/index.scss b/ui/components/multichain/badge-status/index.scss new file mode 100644 index 000000000000..02e5d35efed3 --- /dev/null +++ b/ui/components/multichain/badge-status/index.scss @@ -0,0 +1,30 @@ +.multichain-badge-status { + padding: 0; + + &__badge { + height: 16px; + width: 16px; + z-index: 1; + } + + .mm-badge-wrapper__badge-container { //Need to override the zIndex, can't do it with badgeProps. + z-index: 1; + } + + &__badge-not-connected { + height: 10px; + width: 10px; + } + + &__badge-not-connected::after { + content: ''; + position: absolute; + top: -3px; + left: -3px; + right: -3px; + bottom: -3px; + background: var(--color-background-default); + z-index: -1; + border-radius: 50%; + } +} diff --git a/ui/components/multichain/badge-status/index.ts b/ui/components/multichain/badge-status/index.ts new file mode 100644 index 000000000000..ad04f877e934 --- /dev/null +++ b/ui/components/multichain/badge-status/index.ts @@ -0,0 +1 @@ +export { BadgeStatus } from './badge-status'; diff --git a/ui/components/multichain/global-menu/global-menu.js b/ui/components/multichain/global-menu/global-menu.js index 5297312d0795..186d514fc5eb 100644 --- a/ui/components/multichain/global-menu/global-menu.js +++ b/ui/components/multichain/global-menu/global-menu.js @@ -9,6 +9,7 @@ import { ///: BEGIN:ONLY_INCLUDE_IF(snaps) NOTIFICATIONS_ROUTE, SNAPS_ROUTE, + PERMISSIONS, ///: END:ONLY_INCLUDE_IF(snaps) } from '../../../helpers/constants/routes'; import { lockMetamask } from '../../../store/actions'; @@ -180,6 +181,25 @@ export const GlobalMenu = ({ closeMenu, anchorElement, isOpen }) => { > {t('connectedSites')} + {process.env.MULTICHAIN ? ( + { + history.push(PERMISSIONS); + trackEvent({ + event: MetaMetricsEventName.NavPermissionsOpened, + category: MetaMetricsEventCategory.Navigation, + properties: { + location: METRICS_LOCATION, + }, + }); + closeMenu(); + }} + data-testid="global-menu-connected-sites" + > + {t('allPermissions')} + + ) : null} { ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) diff --git a/ui/components/multichain/multichain-components.scss b/ui/components/multichain/multichain-components.scss index d98e6fd7a139..64d9a1c5682d 100644 --- a/ui/components/multichain/multichain-components.scss +++ b/ui/components/multichain/multichain-components.scss @@ -21,6 +21,7 @@ @import 'network-list-menu'; @import 'product-tour-popover'; @import 'nft-item'; +@import 'badge-status'; @import 'import-tokens-modal'; @import 'asset-list-conversion-button'; @import 'asset-picker-amount'; diff --git a/ui/components/multichain/pages/all-connections/__snapshots__/all-connections.test.js.snap b/ui/components/multichain/pages/all-connections/__snapshots__/all-connections.test.js.snap deleted file mode 100644 index 1607c2f83a5f..000000000000 --- a/ui/components/multichain/pages/all-connections/__snapshots__/all-connections.test.js.snap +++ /dev/null @@ -1,234 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`All Connections render renders correctly 1`] = ` -
-
-
-

- Site Connections -

- -

- Snap Connections -

- - - -
-
-
-`; diff --git a/ui/components/multichain/pages/all-connections/all-connections.js b/ui/components/multichain/pages/all-connections/all-connections.js deleted file mode 100644 index 36410a3b2182..000000000000 --- a/ui/components/multichain/pages/all-connections/all-connections.js +++ /dev/null @@ -1,211 +0,0 @@ -import React, { useCallback, useMemo } from 'react'; -import { useHistory } from 'react-router-dom'; -import { useSelector } from 'react-redux'; -import { Header, Page } from '../page'; -import { - ButtonIcon, - ButtonIconSize, - IconName, - Text, -} from '../../../component-library'; -import { useI18nContext } from '../../../../hooks/useI18nContext'; -import { - BackgroundColor, - Color, - TextAlign, - TextVariant, -} from '../../../../helpers/constants/design-system'; -import { DEFAULT_ROUTE } from '../../../../helpers/constants/routes'; -import { - getAllConnectedAccounts, - getConnectedSubjectsForAllAddresses, - getSnapsList, -} from '../../../../selectors'; -import { Tab, Tabs } from '../../../ui/tabs'; -import { ConnectionListItem } from './connection-list-item'; - -const TABS_THRESHOLD = 5; - -export const AllConnections = () => { - const t = useI18nContext(); - const history = useHistory(); - let totalConnections = 0; - const connectedSubjectsForAllAddresses = useSelector( - getConnectedSubjectsForAllAddresses, - ); - const connectedAddresses = useSelector(getAllConnectedAccounts); - const connectedSnapsData = useSelector(getSnapsList); - - const connectedSiteData = useMemo(() => { - const siteData = {}; - connectedAddresses.forEach((connectedAddress) => { - connectedSubjectsForAllAddresses[connectedAddress].forEach((app) => { - if (!siteData[app.origin]) { - siteData[app.origin] = { ...app, addresses: [] }; - } - siteData[app.origin].addresses.push(connectedAddress); - }); - }); - return siteData; - }, [connectedAddresses, connectedSubjectsForAllAddresses]); - - const sitesConnectionsList = useMemo(() => { - const sitesList = {}; - Object.keys(connectedSiteData).forEach((siteKey) => { - const siteData = connectedSiteData[siteKey]; - const { name, iconUrl, origin, subjectType, extensionId, addresses } = - siteData; - - if (!sitesList[name]) { - sitesList[name] = { - name, - iconUrl, - origin, - subjectType, - extensionId, - addresses: [], - }; - totalConnections += 1; - } - - sitesList[name].addresses.push(...addresses); - }); - return sitesList; - }, [connectedSiteData]); - - const snapsConnectionsList = useMemo(() => { - const snapsList = {}; - Object.keys(connectedSnapsData).forEach((snap) => { - const snapData = connectedSnapsData[snap]; - const { id, name, packageName, iconUrl, subjectType } = snapData; - - if (!snapsList[name]) { - snapsList[name] = { - id, - name, - iconUrl, - packageName, - subjectType, - }; - totalConnections += 1; - } - }); - return snapsList; - }, [connectedSnapsData]); - - const shouldShowTabsView = useMemo(() => { - return ( - totalConnections > TABS_THRESHOLD && - Object.keys(sitesConnectionsList).length > 0 && - Object.keys(snapsConnectionsList).length > 0 - ); - }, [totalConnections, sitesConnectionsList, snapsConnectionsList]); - - const handleConnectionClick = useCallback((connection) => { - // TODO: go to connection details page - console.log('connection clicked: ', connection); - }, []); - - const renderConnectionsList = (connectionList) => - Object.entries(connectionList).map(([itemKey, connection]) => { - return ( - handleConnectionClick(connection)} - /> - ); - }); - - return ( - history.push(DEFAULT_ROUTE)} - size={ButtonIconSize.Sm} - /> - } - > - - {t('allConnections')} - - - } - > - {shouldShowTabsView ? ( - - - {renderConnectionsList(sitesConnectionsList)} - - - {renderConnectionsList(snapsConnectionsList)} - - - ) : ( - <> - {Object.keys(sitesConnectionsList).length > 0 && ( - <> - - {t('siteConnections')} - - {renderConnectionsList(sitesConnectionsList)} - - )} - {Object.keys(snapsConnectionsList).length > 0 && ( - <> - - {t('snapConnections')} - - {renderConnectionsList(snapsConnectionsList)} - - )} - - )} - {totalConnections === 0 ? ( - - {/* TODO: get copy for this edge case */} - No Connected Sites or Snaps - - ) : null} - - ); -}; diff --git a/ui/components/multichain/pages/all-connections/all-connections.stories.js b/ui/components/multichain/pages/all-connections/all-connections.stories.js deleted file mode 100644 index 016691cd7c60..000000000000 --- a/ui/components/multichain/pages/all-connections/all-connections.stories.js +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import { AllConnections } from './all-connections'; - -export default { - title: 'Components/Multichain/AllConnections', -}; -export const DefaultStory = () => ; - -DefaultStory.storyName = 'Default'; diff --git a/ui/components/multichain/pages/index.js b/ui/components/multichain/pages/index.js index a3471211eb26..a8e64d1fcf9b 100644 --- a/ui/components/multichain/pages/index.js +++ b/ui/components/multichain/pages/index.js @@ -1,2 +1,2 @@ export { Connections } from './connections'; -export { AllConnections } from './all-connections/all-connections'; +export { PermissionsPage } from './permissions-page/permissions-page'; diff --git a/ui/components/multichain/pages/index.scss b/ui/components/multichain/pages/index.scss index 427139ba1396..5bc43557f0bf 100644 --- a/ui/components/multichain/pages/index.scss +++ b/ui/components/multichain/pages/index.scss @@ -1,3 +1,3 @@ @import 'page/'; @import 'send/'; -@import 'all-connections/all-connections'; +@import 'permissions-page/permissions-page'; diff --git a/ui/components/multichain/pages/permissions-page/__snapshots__/permissions-page.test.js.snap b/ui/components/multichain/pages/permissions-page/__snapshots__/permissions-page.test.js.snap new file mode 100644 index 000000000000..9d8a12bf6245 --- /dev/null +++ b/ui/components/multichain/pages/permissions-page/__snapshots__/permissions-page.test.js.snap @@ -0,0 +1,271 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`All Connections render renders correctly 1`] = ` +
+
+
+
+
+ +
+
+

+ + Permissions + +

+
+
+
+
+

+ Site Connections +

+ +

+ Snap Connections +

+ + + +
+
+
+
+`; diff --git a/ui/components/multichain/pages/all-connections/connection-list-item.js b/ui/components/multichain/pages/permissions-page/connection-list-item.js similarity index 100% rename from ui/components/multichain/pages/all-connections/connection-list-item.js rename to ui/components/multichain/pages/permissions-page/connection-list-item.js diff --git a/ui/components/multichain/pages/all-connections/connection-list-item.stories.js b/ui/components/multichain/pages/permissions-page/connection-list-item.stories.js similarity index 100% rename from ui/components/multichain/pages/all-connections/connection-list-item.stories.js rename to ui/components/multichain/pages/permissions-page/connection-list-item.stories.js diff --git a/ui/components/multichain/pages/permissions-page/permissions-page.js b/ui/components/multichain/pages/permissions-page/permissions-page.js new file mode 100644 index 000000000000..6580c04b2ec4 --- /dev/null +++ b/ui/components/multichain/pages/permissions-page/permissions-page.js @@ -0,0 +1,204 @@ +import classnames from 'classnames'; +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; +import { useHistory } from 'react-router-dom'; +import { useSelector } from 'react-redux'; +import { Content, Header, Page } from '../page'; +import { + Box, + ButtonIcon, + ButtonIconSize, + IconName, + Text, +} from '../../../component-library'; +import { useI18nContext } from '../../../../hooks/useI18nContext'; +import { + BackgroundColor, + BlockSize, + Color, + Display, + FlexDirection, + JustifyContent, + TextAlign, + TextColor, + TextVariant, +} from '../../../../helpers/constants/design-system'; +import { DEFAULT_ROUTE } from '../../../../helpers/constants/routes'; +import { + getOnboardedInThisUISession, + getShowPermissionsTour, + getConnectedSitesList, + getConnectedSnapsList, +} from '../../../../selectors'; +import { Tab, Tabs } from '../../../ui/tabs'; +import { ProductTour } from '../../product-tour-popover'; +import { hidePermissionsTour } from '../../../../store/actions'; +import { ConnectionListItem } from './connection-list-item'; + +const TABS_THRESHOLD = 5; + +export const PermissionsPage = () => { + const t = useI18nContext(); + const history = useHistory(); + const headerRef = useRef(); + const [totalConnections, setTotalConnections] = useState(0); + const sitesConnectionsList = useSelector(getConnectedSitesList); + const snapsConnectionsList = useSelector(getConnectedSnapsList); + const showPermissionsTour = useSelector(getShowPermissionsTour); + const onboardedInThisUISession = useSelector(getOnboardedInThisUISession); + + useEffect(() => { + setTotalConnections( + Object.keys(sitesConnectionsList).length + + Object.keys(snapsConnectionsList).length, + ); + }, [sitesConnectionsList, snapsConnectionsList]); + + const shouldShowTabsView = useMemo(() => { + return ( + totalConnections > TABS_THRESHOLD && + Object.keys(sitesConnectionsList).length > 0 && + Object.keys(snapsConnectionsList).length > 0 + ); + }, [totalConnections, sitesConnectionsList, snapsConnectionsList]); + + const handleConnectionClick = useCallback((connection) => { + // TODO: go to connection details page + console.log('connection clicked: ', connection); + }, []); + + const renderConnectionsList = (connectionList) => + Object.entries(connectionList).map(([itemKey, connection]) => { + return ( + handleConnectionClick(connection)} + /> + ); + }); + + return ( + +
history.push(DEFAULT_ROUTE)} + size={ButtonIconSize.Sm} + /> + } + > + + {t('permissions')} + +
+ {showPermissionsTour && !onboardedInThisUISession ? ( + + ) : null} + + + {shouldShowTabsView ? ( + + + {renderConnectionsList(sitesConnectionsList)} + + + {renderConnectionsList(snapsConnectionsList)} + + + ) : ( + <> + {Object.keys(sitesConnectionsList).length > 0 && ( + <> + + {t('siteConnections')} + + {renderConnectionsList(sitesConnectionsList)} + + )} + {Object.keys(snapsConnectionsList).length > 0 && ( + <> + + {t('snapConnections')} + + {renderConnectionsList(snapsConnectionsList)} + + )} + + )} + {totalConnections === 0 ? ( + + + {t('permissionsPageEmptyContent')} + + + {t('permissionsPageEmptySubContent')} + + + ) : null} + +
+ ); +}; diff --git a/ui/components/multichain/pages/all-connections/all-connections.scss b/ui/components/multichain/pages/permissions-page/permissions-page.scss similarity index 79% rename from ui/components/multichain/pages/all-connections/all-connections.scss rename to ui/components/multichain/pages/permissions-page/permissions-page.scss index 1d2c0ce7c2b9..4f3f5a7c3d86 100644 --- a/ui/components/multichain/pages/all-connections/all-connections.scss +++ b/ui/components/multichain/pages/permissions-page/permissions-page.scss @@ -1,4 +1,4 @@ -.all-connections { +.permissions-page { &__tabs { padding: 0 16px; diff --git a/ui/components/multichain/pages/permissions-page/permissions-page.stories.js b/ui/components/multichain/pages/permissions-page/permissions-page.stories.js new file mode 100644 index 000000000000..9a016d81b760 --- /dev/null +++ b/ui/components/multichain/pages/permissions-page/permissions-page.stories.js @@ -0,0 +1,9 @@ +import React from 'react'; +import { PermissionsPage } from './permissions-page'; + +export default { + title: 'Components/Multichain/PermissionsPage', +}; +export const DefaultStory = () => ; + +DefaultStory.storyName = 'Default'; diff --git a/ui/components/multichain/pages/all-connections/all-connections.test.js b/ui/components/multichain/pages/permissions-page/permissions-page.test.js similarity index 88% rename from ui/components/multichain/pages/all-connections/all-connections.test.js rename to ui/components/multichain/pages/permissions-page/permissions-page.test.js index e689ab0d8f07..6b26ad5e089c 100644 --- a/ui/components/multichain/pages/all-connections/all-connections.test.js +++ b/ui/components/multichain/pages/permissions-page/permissions-page.test.js @@ -2,7 +2,7 @@ import React from 'react'; import configureStore from '../../../../store/store'; import mockState from '../../../../../test/data/mock-state.json'; import { renderWithProvider } from '../../../../../test/lib/render-helpers'; -import { AllConnections } from './all-connections'; +import { PermissionsPage } from './permissions-page'; mockState.metamask.subjectMetadata = { 'https://metamask.github.io': { @@ -84,16 +84,16 @@ describe('All Connections', () => { describe('render', () => { it('renders correctly', () => { const { container, getByTestId } = renderWithProvider( - , + , store, ); expect(container).toMatchSnapshot(); - expect(getByTestId('all-connections')).toBeInTheDocument(); + expect(getByTestId('permissions-page')).toBeInTheDocument(); }); it('renders sections when user has 5 or less connections', () => { - const { getByTestId } = renderWithProvider(, store); + const { getByTestId } = renderWithProvider(, store); expect(getByTestId('sites-connections')).toBeInTheDocument(); expect(getByTestId('snaps-connections')).toBeInTheDocument(); }); @@ -134,16 +134,16 @@ describe('All Connections', () => { }, }; store = configureStore(mockState); - const { getByTestId } = renderWithProvider(, store); - expect(getByTestId('all-connections-sites-tab')).toBeInTheDocument(); - expect(getByTestId('all-connections-snaps-tab')).toBeInTheDocument(); + const { getByTestId } = renderWithProvider(, store); + expect(getByTestId('permissions-page-sites-tab')).toBeInTheDocument(); + expect(getByTestId('permissions-page-snaps-tab')).toBeInTheDocument(); }); it('renders no connections message when user has no connections', () => { mockState.metamask.snaps = {}; mockState.metamask.subjectMetadata = {}; mockState.metamask.subjects = {}; store = configureStore(mockState); - const { getByTestId } = renderWithProvider(, store); + const { getByTestId } = renderWithProvider(, store); expect(getByTestId('no-connections')).toBeInTheDocument(); }); }); diff --git a/ui/components/multichain/pages/send/components/__snapshots__/your-accounts.test.tsx.snap b/ui/components/multichain/pages/send/components/__snapshots__/your-accounts.test.tsx.snap index 04ea196e505b..d213c98faec4 100644 --- a/ui/components/multichain/pages/send/components/__snapshots__/your-accounts.test.tsx.snap +++ b/ui/components/multichain/pages/send/components/__snapshots__/your-accounts.test.tsx.snap @@ -74,12 +74,12 @@ exports[`SendPageYourAccounts render renders correctly 1`] = ` >
- $537,761.36 + $880.18 { }, }, }, + activeTab: { + origin: 'https://uniswap.org/', + }, appState: { sendInputCurrencySwitched: false, }, diff --git a/ui/components/multichain/product-tour-popover/index.scss b/ui/components/multichain/product-tour-popover/index.scss index c8b597d7c42a..731a97a25932 100644 --- a/ui/components/multichain/product-tour-popover/index.scss +++ b/ui/components/multichain/product-tour-popover/index.scss @@ -9,6 +9,11 @@ right: 6px !important; } + &__permissions-page-tour { + left: auto !important; //important required for permissions page popup center alignment, overrides .multichain-product-tour-menu -7px left styling + top: 0 !important; //important required for permissions page styling, since the anchorElement is inside Page Content + } + &__arrow, &__arrow::before { position: absolute; diff --git a/ui/components/multichain/product-tour-popover/product-tour-popover.js b/ui/components/multichain/product-tour-popover/product-tour-popover.js index 3da95feef3c9..411821a4da08 100644 --- a/ui/components/multichain/product-tour-popover/product-tour-popover.js +++ b/ui/components/multichain/product-tour-popover/product-tour-popover.js @@ -111,7 +111,9 @@ export const ProductTour = ({ color={TextColor.infoInverse} variant={TextVariant.bodyMd} > - {currentStep}/{totalSteps} + {currentStep && totalSteps + ? { currentStep } / { totalSteps } + : null} +
+
+ + + Test Title + +
+ Test +
+
+
+
+`; + +exports[`Disclosure matches snapshot without title prop 1`] = ` +
+
+ Test +
+
+`; diff --git a/ui/components/ui/disclosure/disclosure.js b/ui/components/ui/disclosure/disclosure.js index 297646c0956a..30d4bbb76192 100644 --- a/ui/components/ui/disclosure/disclosure.js +++ b/ui/components/ui/disclosure/disclosure.js @@ -47,24 +47,32 @@ const renderSummaryByType = (variant, title, size) => { } }; -const Disclosure = ({ children, title, size, variant }) => { +const Disclosure = ({ + children, + isScrollToBottomOnOpen, + title, + size, + variant, +}) => { const disclosureFooterEl = useRef(null); const [open, setOpen] = useState(false); const scrollToBottom = () => { - disclosureFooterEl && - disclosureFooterEl.current && - disclosureFooterEl.current.scrollIntoView({ behavior: 'smooth' }); + disclosureFooterEl?.current?.scrollIntoView({ behavior: 'smooth' }); }; useEffect(() => { - if (open) { + if (isScrollToBottomOnOpen && open) { scrollToBottom(); } - }, [open]); + }, [isScrollToBottomOnOpen, open]); return ( -
setOpen((state) => !state)}> +
setOpen((state) => !state)} + > {title ? (
{renderSummaryByType(variant, title)} @@ -83,12 +91,14 @@ const Disclosure = ({ children, title, size, variant }) => { Disclosure.propTypes = { children: PropTypes.node.isRequired, + isScrollToBottomOnOpen: PropTypes.bool, size: PropTypes.string, title: PropTypes.string, variant: PropTypes.string, }; Disclosure.defaultProps = { + isScrollToBottomOnOpen: false, size: 'normal', title: null, variant: DisclosureVariant.Default, diff --git a/ui/components/ui/disclosure/disclosure.test.js b/ui/components/ui/disclosure/disclosure.test.js new file mode 100644 index 000000000000..b659e1aae8d1 --- /dev/null +++ b/ui/components/ui/disclosure/disclosure.test.js @@ -0,0 +1,80 @@ +import React from 'react'; +import { fireEvent, render } from '@testing-library/react'; +import Disclosure from './disclosure'; + +describe('Disclosure', () => { + it('matches snapshot without title prop', () => { + const { container } = render(Test); + expect(container).toMatchSnapshot(); + }); + + it('matches snapshot with title prop', () => { + const { container } = render( + + Test + , + ); + expect(container).toMatchSnapshot(); + }); + + it('renders content', () => { + const { container, getByText, rerender } = render( + Test, + ); + expect(getByText('Test Title')).toBeInTheDocument(); + expect(container.querySelector('.disclosure__content').textContent).toBe( + 'Test', + ); + + expect( + container.querySelector('.disclosure__content.normal'), + ).toBeInTheDocument(); + + rerender( + + Test + , + ); + + expect( + container.querySelector('.disclosure__content.small'), + ).toBeInTheDocument(); + }); + + describe('when clicking on disclosure', () => { + it('does not scroll down on open by default or when isScrollToBottomOnOpen is false', () => { + const mockScrollIntoView = jest.fn(); + const originalScrollIntoView = + window.HTMLElement.prototype.scrollIntoView; + window.HTMLElement.prototype.scrollIntoView = mockScrollIntoView; + + const { getByTestId } = render( + Test, + ); + const element = getByTestId('disclosure'); + fireEvent.click(element); + expect(mockScrollIntoView).not.toHaveBeenCalled(); + window.HTMLElement.prototype.scrollIntoView = originalScrollIntoView; + }); + + it('scrolls down on open when isScrollToBottomOnOpen is true', () => { + const mockScrollIntoView = jest.fn(); + const originalScrollIntoView = + window.HTMLElement.prototype.scrollIntoView; + window.HTMLElement.prototype.scrollIntoView = mockScrollIntoView; + + const { getByTestId } = render( + + Test + , + ); + const element = getByTestId('disclosure'); + + fireEvent.click(element); + expect(mockScrollIntoView).toHaveBeenCalledWith({ + behavior: 'smooth', + }); + window.HTMLElement.prototype.scrollIntoView = originalScrollIntoView; + }); + }); +}); diff --git a/ui/components/ui/page-container/page-container-footer/page-container-footer.component.test.js b/ui/components/ui/page-container/page-container-footer/page-container-footer.component.test.js index 7c9bc6758cf7..69eaa48860fb 100644 --- a/ui/components/ui/page-container/page-container-footer/page-container-footer.component.test.js +++ b/ui/components/ui/page-container/page-container-footer/page-container-footer.component.test.js @@ -51,5 +51,26 @@ describe('Page Footer', () => { expect(props.onSubmit).toHaveBeenCalled(); }); + + it('has danger class defined if type is danger', () => { + const { queryByTestId } = renderWithProvider( + , + ); + + const submitButton = queryByTestId('page-container-footer-next'); + + expect(submitButton.className).toContain('danger'); + }); + + it('has danger-primary class defined if type is danger-primary', () => { + const { queryByTestId } = renderWithProvider( + , + ); + + const submitButton = queryByTestId('page-container-footer-next'); + + console.log(submitButton.className); + expect(submitButton.className).toContain('danger-primary'); + }); }); }); diff --git a/ui/components/ui/popover/index.scss b/ui/components/ui/popover/index.scss index 4097588129ff..062cb042f96a 100644 --- a/ui/components/ui/popover/index.scss +++ b/ui/components/ui/popover/index.scss @@ -31,8 +31,7 @@ &-bg { width: 100%; height: 100%; - background: var(--color-overlay-alternative); - opacity: 0.2; + background: var(--color-overlay-default); } &-content { diff --git a/ui/contexts/snaps/index.ts b/ui/contexts/snaps/index.ts new file mode 100644 index 000000000000..c86849885fd5 --- /dev/null +++ b/ui/contexts/snaps/index.ts @@ -0,0 +1 @@ +export * from './snap-interface'; diff --git a/ui/contexts/snaps/snap-interface.tsx b/ui/contexts/snaps/snap-interface.tsx new file mode 100644 index 000000000000..f0d882668348 --- /dev/null +++ b/ui/contexts/snaps/snap-interface.tsx @@ -0,0 +1,166 @@ +import { + FormState, + InterfaceState, + UserInputEventType, +} from '@metamask/snaps-sdk'; +import { debounce } from 'lodash'; +import React, { + FunctionComponent, + createContext, + useContext, + useEffect, + useRef, +} from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { getMemoizedInterface } from '../../selectors'; +import { handleSnapRequest, updateInterfaceState } from '../../store/actions'; +import { mergeValue } from './utils'; + +export type HandleEvent = (event: UserInputEventType, name?: string) => void; + +export type HandleInputChange = ( + name: string, + value: string | null, + form?: string, +) => void; + +export type GetValue = (name: string, form?: string) => string | undefined; + +export type SnapInterfaceContextType = { + handleEvent: HandleEvent; + getValue: GetValue; + handleInputChange: HandleInputChange; +}; + +export const SnapInterfaceContext = + createContext(null); + +export type SnapInterfaceContextProviderProps = { + interfaceId: string; + snapId: string; +}; + +/** + * The Snap interface context provider that handles all the interface state operations. + * + * @param params - The context provider params. + * @param params.children - The childrens to wrap with the context provider. + * @param params.interfaceId - The interface ID to use. + * @param params.snapId - The Snap ID that requested the interface. + * @returns The context provider. + */ +export const SnapInterfaceContextProvider: FunctionComponent< + SnapInterfaceContextProviderProps +> = ({ children, interfaceId, snapId }) => { + const dispatch = useDispatch(); + const { state: initialState } = useSelector( + (state) => getMemoizedInterface(state, interfaceId), + // Prevents the selector update. + // We do this to avoid useless re-renders. + () => true, + ); + + // 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 + useEffect(() => { + internalState.current = initialState; + }, [initialState]); + + // The submittion of user input events is debounced to avoid crashing the snap if + // there's too much events sent at the same time + const snapRequestDebounced: HandleEvent = debounce( + (event, name) => + handleSnapRequest({ + snapId, + origin: '', + handler: 'onUserInput', + request: { + jsonrpc: '2.0', + method: ' ', + params: { + event: { + type: event, + name, + value: internalState.current[name], + }, + id: interfaceId, + }, + }, + }), + 200, + ); + + // The update of the state is debounced to avoid crashes due to too much + // updates in a short amount of time. + const updateStateDebounced = debounce( + (state) => dispatch(updateInterfaceState(interfaceId, state)), + 200, + ); + + /** + * Handle the submission of an user input event to the Snap. + * + * @param event - The event object. + * @param name - The name of the component emmitting the event. + */ + const handleEvent: HandleEvent = (event, name) => { + updateStateDebounced.flush(); + snapRequestDebounced(event, name); + }; + + /** + * Handle the value change of an input. + * + * @param name - The name of the input. + * @param value - The new value. + * @param form - The name of the form containing the input. + * Optional if the input is not contained in a form. + */ + const handleInputChange: HandleInputChange = (name, value, form) => { + const state = mergeValue(internalState.current, name, value, form); + + internalState.current = state; + updateStateDebounced(state); + }; + + /** + * Get the value of an input from the interface state. + * + * @param name - The name of the input. + * @param form - The name of the form containing the input. + * Optional if the input is not contained in a form. + * @returns The value of the input or undefinded if the input has no value. + */ + const getValue: GetValue = (name, form) => { + const value = form + ? (initialState[form] as FormState)?.[name] + : (initialState as FormState)?.[name]; + + if (value) { + return value; + } + + return undefined; + }; + + return ( + + {children} + + ); +}; + +/** + * The utility hook to consume the Snap inteface context. + * + * @returns The snap interface context. + */ +export function useSnapInterfaceContext() { + return useContext(SnapInterfaceContext) as SnapInterfaceContextType; +} diff --git a/ui/contexts/snaps/utils.ts b/ui/contexts/snaps/utils.ts new file mode 100644 index 000000000000..9d13c13eec1d --- /dev/null +++ b/ui/contexts/snaps/utils.ts @@ -0,0 +1,29 @@ +import { FormState, InterfaceState } from '@metamask/snaps-sdk'; + +/** + * Merge a new input value in the interface state. + * + * @param state - The current interface state. + * @param name - The input name. + * @param value - The input value. + * @param form - The name of the form containing the input. + * Optional if the input is not contained in a form. + * @returns The interface state with the new value merged in. + */ +export const mergeValue = ( + state: InterfaceState, + name: string, + value: string | null, + form?: string, +): InterfaceState => { + if (form) { + return { + ...state, + [form]: { + ...(state[form] as FormState), + [name]: value, + }, + }; + } + return { ...state, [name]: value }; +}; diff --git a/ui/css/design-system/pending-colors.scss b/ui/css/design-system/pending-colors.scss index cbbe52d6db52..322823d1dfb1 100644 --- a/ui/css/design-system/pending-colors.scss +++ b/ui/css/design-system/pending-colors.scss @@ -1,6 +1,9 @@ /** These colors are pending to be approved for the design system's design-token package + Disabling Stylelint's hex color rule so this file is ignored. + Before adding a color here make sure that there isn't a design token available. **/ +/* stylelint-disable color-no-hex */ /** * Dark Theme Colors diff --git a/ui/css/utilities/colors.scss b/ui/css/utilities/colors.scss index a127746e81ca..d625b155e640 100644 --- a/ui/css/utilities/colors.scss +++ b/ui/css/utilities/colors.scss @@ -1,3 +1,8 @@ +/* +Disabling Stylelint's hex color rule so this file is ignored. +Before adding a color here make sure that there isn't a design token available. +*/ +/* stylelint-disable color-no-hex */ :root { // Accents // Everything below this line is part of the new color system diff --git a/ui/ducks/domains.js b/ui/ducks/domains.js index e16eeb083a5f..1f40f95b585b 100644 --- a/ui/ducks/domains.js +++ b/ui/ducks/domains.js @@ -5,6 +5,7 @@ import { isConfusing } from 'unicode-confusables'; import { isHexString } from 'ethereumjs-util'; import { Web3Provider } from '@ethersproject/providers'; +import { getChainIdsCaveat } from '@metamask/snaps-rpc-methods'; import { getCurrentChainId, getNameLookupSnapsIds, @@ -185,9 +186,8 @@ export async function fetchResolutions({ domain, address, chainId, state }) { const filteredNameLookupSnapsIds = nameLookupSnaps.filter((snapId) => { const permission = subjects[snapId]?.permissions[NAME_LOOKUP_PERMISSION]; - // TODO: add a caveat getter to the snaps monorepo for name lookup similar to the other caveat getters - const nameLookupCaveat = permission.caveats[0].value; - return nameLookupCaveat.includes(chainId); + const chainIdCaveat = getChainIdsCaveat(permission); + return chainIdCaveat?.includes(chainId) ?? true; }); const snapRequestArgs = domain @@ -215,10 +215,13 @@ export async function fetchResolutions({ domain, address, chainId, state }) { const filteredResults = results.reduce( (successfulResolutions, result, idx) => { if (result.status !== 'rejected' && result.value !== null) { - successfulResolutions.push({ - ...result.value, - snapId: filteredNameLookupSnapsIds[idx], - }); + const resolutions = result.value.resolvedAddresses.map( + (resolution) => ({ + ...resolution, + snapId: filteredNameLookupSnapsIds[idx], + }), + ); + return successfulResolutions.concat(resolutions); } return successfulResolutions; }, diff --git a/ui/helpers/constants/routes.ts b/ui/helpers/constants/routes.ts index 0acecce59758..b490f8518237 100644 --- a/ui/helpers/constants/routes.ts +++ b/ui/helpers/constants/routes.ts @@ -37,7 +37,7 @@ const INTERACTIVE_REPLACEMENT_TOKEN_PAGE = ///: END:ONLY_INCLUDE_IF const SEND_ROUTE = '/send'; const CONNECTIONS = '/connections'; -const ALL_CONNECTIONS = '/all-connections'; +const PERMISSIONS = '/permissions'; const TOKEN_DETAILS = '/token-details'; const CONNECT_ROUTE = '/connect'; const CONNECT_CONFIRM_PERMISSIONS_ROUTE = '/confirm-permissions'; @@ -154,7 +154,7 @@ const PATH_NAME_MAP = { ///: END:ONLY_INCLUDE_IF [SEND_ROUTE]: 'Send Page', [CONNECTIONS]: 'Connections', - [ALL_CONNECTIONS]: 'All Connections', + [PERMISSIONS]: 'Permissions', [`${TOKEN_DETAILS}/:address`]: 'Token Details Page', [`${CONNECT_ROUTE}/:id`]: 'Connect To Site Confirmation Page', [`${CONNECT_ROUTE}/:id${CONNECT_CONFIRM_PERMISSIONS_ROUTE}`]: @@ -214,7 +214,7 @@ export { CONNECT_HARDWARE_ROUTE, SEND_ROUTE, CONNECTIONS, - ALL_CONNECTIONS, + PERMISSIONS, TOKEN_DETAILS, CONFIRM_TRANSACTION_ROUTE, CONFIRM_SEND_ETHER_PATH, diff --git a/ui/helpers/constants/settings.js b/ui/helpers/constants/settings.js index 73e63331ea0f..f4dce45a1ed5 100644 --- a/ui/helpers/constants/settings.js +++ b/ui/helpers/constants/settings.js @@ -263,11 +263,30 @@ export const SETTINGS_CONSTANTS = [ route: `${SECURITY_ROUTE}#proposed-nicknames`, icon: 'fa fa-lock', }, - /** - * settingsRefs 15-17 will be handled in a future PR - * - * @see {@link https://github.com/MetaMask/metamask-extension/pull/22967} - */ + // securityAndPrivacy settingsRefs[15] + { + tabMessage: (t) => t('securityAndPrivacy'), + sectionMessage: (t) => t('securityAlerts'), + descriptionMessage: (t) => t('securityAlertsDescription'), + route: `${SECURITY_ROUTE}#security-alerts`, + icon: 'fa fa-lock', + }, + // securityAndPrivacy settingsRefs[16] + { + tabMessage: (t) => t('securityAndPrivacy'), + sectionMessage: (t) => t('blockaid'), + descriptionMessage: (t) => t('blockaidMessage'), + route: `${SECURITY_ROUTE}#security-alerts-blockaid`, + icon: 'fa fa-lock', + }, + // securityAndPrivacy settingsRefs[17] + { + tabMessage: (t) => t('securityAndPrivacy'), + sectionMessage: (t) => t('openSeaLabel'), + descriptionMessage: (t) => t('openSeaMessage'), + route: `${SECURITY_ROUTE}#security-alerts-opensea`, + icon: 'fa fa-lock', + }, { tabMessage: (t) => t('alerts'), sectionMessage: (t) => t('alertSettingsUnconnectedAccount'), @@ -405,11 +424,24 @@ export const SETTINGS_CONSTANTS = [ route: `${ADVANCED_ROUTE}#restore-userdata`, icon: 'fas fa-upload', }, + // experimental settingsRefs[0] { tabMessage: (t) => t('experimental'), - sectionMessage: (t) => t('securityAlerts'), - descriptionMessage: (t) => t('securityAlertsDescription'), - route: `${EXPERIMENTAL_ROUTE}#security-alerts`, - icon: 'fa fa-flask', + sectionMessage: (t) => t('petnamesEnabledToggle'), + descriptionMessage: (t) => t('petnamesEnabledToggleDescription'), + route: `${EXPERIMENTAL_ROUTE}#nicknames`, + icon: 'fas fa-flask', + }, + + ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) + // since this route is only included with keyring-snaps feature flag, this needs to be the last settingsRef for the experimental tab + // experimental settingsRefs[1] + { + tabMessage: (t) => t('experimental'), + sectionMessage: (t) => t('snaps'), + descriptionMessage: (t) => t('addSnapAccountToggle'), + route: `${EXPERIMENTAL_ROUTE}#snaps`, + icon: 'fas fa-flask', }, + ///: END:ONLY_INCLUDE_IF ]; diff --git a/ui/helpers/utils/institutional/find-by-custodian-name.test.ts b/ui/helpers/utils/institutional/find-by-custodian-name.test.ts index 86cdb977e4ba..b0e880b46ae4 100644 --- a/ui/helpers/utils/institutional/find-by-custodian-name.test.ts +++ b/ui/helpers/utils/institutional/find-by-custodian-name.test.ts @@ -1,6 +1,6 @@ -import { findCustodianByDisplayName } from './find-by-custodian-name'; +import { findCustodianByEnvName } from './find-by-custodian-name'; -describe('findCustodianByDisplayName', () => { +describe('findCustodianByEnvName', () => { const custodians = [ { type: 'JSONRPC', @@ -33,21 +33,21 @@ describe('findCustodianByDisplayName', () => { version: 2, }, ]; - it('should return the custodian if the display name is found in custodianKey', () => { - const displayName = 'Qredo'; - const custodian = findCustodianByDisplayName(displayName, custodians); - expect(custodian?.name).toBe('Qredo'); + it('should return the custodian if the env name is found in custodianKey', () => { + const envName = 'Qredo'; + const custodian = findCustodianByEnvName(envName, custodians); + expect(custodian?.envName).toBe('qredo'); }); - it('should return the custodian if the display name is found in custodianDisplayName', () => { - const displayName = 'Saturn Custody'; - const custodian = findCustodianByDisplayName(displayName, custodians); - expect(custodian?.name).toContain('Saturn'); + it('should return the custodian if the env name is found in custodianDisplayName', () => { + const envName = 'Saturn Custody'; + const custodian = findCustodianByEnvName(envName, custodians); + expect(custodian?.envName).toContain('saturn'); }); it('should return null if no matching custodian is found', () => { - const displayName = 'Non-existent Custodian'; - const custodian = findCustodianByDisplayName(displayName, custodians); + const envName = 'Non-existent Custodian'; + const custodian = findCustodianByEnvName(envName, custodians); expect(custodian).toBeNull(); }); }); diff --git a/ui/helpers/utils/institutional/find-by-custodian-name.ts b/ui/helpers/utils/institutional/find-by-custodian-name.ts index 5109e0281c8d..fdbc4ab4bd1f 100644 --- a/ui/helpers/utils/institutional/find-by-custodian-name.ts +++ b/ui/helpers/utils/institutional/find-by-custodian-name.ts @@ -13,24 +13,20 @@ type Custodian = { version: number; }; -// TODO (Bernardo) - There can be multiple custodian with the same name, envName should be used instead -export function findCustodianByDisplayName( - displayName: string, +export function findCustodianByEnvName( + envName: string, custodians: Custodian[], ): Custodian | null { - const formatedDisplayName = displayName.toLowerCase(); + const formatedEnvName = envName.toLowerCase(); if (!custodians) { return null; } for (const custodian of custodians) { - const custodianName = custodian.name.toLowerCase(); + const custodianName = custodian.envName.toLowerCase(); - if ( - custodianName.length !== 0 && - formatedDisplayName.includes(custodianName) - ) { + if (custodianName.length !== 0 && formatedEnvName.includes(custodianName)) { return custodian; } } diff --git a/ui/helpers/utils/metrics.js b/ui/helpers/utils/metrics.js index 8902f1bed336..ad50da93ff5a 100644 --- a/ui/helpers/utils/metrics.js +++ b/ui/helpers/utils/metrics.js @@ -35,12 +35,12 @@ export function formatAccountType(accountType) { const getBlockaidMetricUiCustomization = (resultType) => { let uiCustomization; - if (resultType === BlockaidResultType.Failed) { - uiCustomization = [MetaMetricsEventUiCustomization.SecurityAlertFailed]; - } else if (resultType === BlockaidResultType.Malicious) { + if (resultType === BlockaidResultType.Malicious) { uiCustomization = [MetaMetricsEventUiCustomization.FlaggedAsMalicious]; } else if (resultType === BlockaidResultType.Warning) { uiCustomization = [MetaMetricsEventUiCustomization.FlaggedAsWarning]; + } else if (resultType === BlockaidResultType.Errored) { + uiCustomization = [MetaMetricsEventUiCustomization.SecurityAlertError]; } return uiCustomization; @@ -60,6 +60,7 @@ export const getBlockaidMetricsProps = ({ securityAlertResponse }) => { providerRequestsCount, reason, result_type: resultType, + description, } = securityAlertResponse; const uiCustomization = getBlockaidMetricUiCustomization(resultType); @@ -70,6 +71,11 @@ export const getBlockaidMetricsProps = ({ securityAlertResponse }) => { if (resultType !== BlockaidResultType.Benign) { params.security_alert_reason = reason ?? BlockaidReason.notApplicable; } + + if (resultType === BlockaidResultType.Errored && description) { + params.security_alert_description = description; + } + params.security_alert_response = resultType ?? BlockaidResultType.NotApplicable; diff --git a/ui/helpers/utils/metrics.test.js b/ui/helpers/utils/metrics.test.js index b75f5b7b2693..d1a2f1a5d72e 100644 --- a/ui/helpers/utils/metrics.test.js +++ b/ui/helpers/utils/metrics.test.js @@ -54,18 +54,18 @@ describe('getBlockaidMetricsProps', () => { ); }); - it('includes "security_alert_failed" ui_customization when type is failed', () => { + it('includes "security_alert_error" ui_customization when type is error', () => { const result = getBlockaidMetricsProps({ securityAlertResponse: { ...securityAlertResponse, - result_type: BlockaidResultType.Failed, + result_type: BlockaidResultType.Errored, }, }); expect(result).toStrictEqual({ security_alert_reason: BlockaidReason.setApprovalForAll, - security_alert_response: BlockaidResultType.Failed, - ui_customizations: ['security_alert_failed'], + security_alert_response: BlockaidResultType.Errored, + ui_customizations: ['security_alert_error'], }); }); @@ -132,6 +132,21 @@ describe('getBlockaidMetricsProps', () => { }); }); + it('includes "security_alert_error" ui_customization when type is an error', () => { + const result = getBlockaidMetricsProps({ + securityAlertResponse: { + ...securityAlertResponse, + result_type: BlockaidResultType.Errored, + reason: 'error: error message', + }, + }); + expect(result).toStrictEqual({ + ui_customizations: ['security_alert_error'], + security_alert_response: BlockaidResultType.Errored, + security_alert_reason: 'error: error message', + }); + }); + it('excludes eth call counts if providerRequestsCount is empty', () => { const result = getBlockaidMetricsProps({ securityAlertResponse: { diff --git a/ui/helpers/utils/permission.js b/ui/helpers/utils/permission.js index 952bf02f25de..53617075c6e8 100644 --- a/ui/helpers/utils/permission.js +++ b/ui/helpers/utils/permission.js @@ -2,7 +2,7 @@ import deepFreeze from 'deep-freeze-strict'; import React from 'react'; ///: BEGIN:ONLY_INCLUDE_IF(snaps) -import { getRpcCaveatOrigins } from '@metamask/snaps-controllers'; +import { getRpcCaveatOrigins } from '@metamask/snaps-rpc-methods'; import { SnapCaveatType, getSlip44ProtocolName, diff --git a/ui/helpers/utils/settings-search.test.js b/ui/helpers/utils/settings-search.test.js index d9ccc7aa4e5e..db70cd424873 100644 --- a/ui/helpers/utils/settings-search.test.js +++ b/ui/helpers/utils/settings-search.test.js @@ -159,10 +159,10 @@ describe('Settings Search Utils', () => { expect(getNumberOfSettingRoutesInTab(t, t('contacts'))).toStrictEqual(1); }); - it('returns "Security & Privacy" section count', () => { + it('returns "Security & privacy" section count', () => { expect( getNumberOfSettingRoutesInTab(t, t('securityAndPrivacy')), - ).toStrictEqual(15); + ).toStrictEqual(18); }); it('returns "Alerts" section count', () => { @@ -175,7 +175,7 @@ describe('Settings Search Utils', () => { it('returns "Experimental" section count', () => { expect(getNumberOfSettingRoutesInTab(t, t('experimental'))).toStrictEqual( - 1, + 2, ); }); diff --git a/ui/hooks/snaps/useSignatureInsights.js b/ui/hooks/snaps/useSignatureInsights.js index 9c0a418613ea..ea85cd4de47e 100644 --- a/ui/hooks/snaps/useSignatureInsights.js +++ b/ui/hooks/snaps/useSignatureInsights.js @@ -1,8 +1,8 @@ import { useEffect, useState } from 'react'; -import { useSelector } from 'react-redux'; -import { getSignatureOriginCaveat } from '@metamask/snaps-controllers'; +import { useDispatch, useSelector } from 'react-redux'; +import { getSignatureOriginCaveat } from '@metamask/snaps-rpc-methods'; import { SeverityLevel } from '@metamask/snaps-sdk'; -import { handleSnapRequest } from '../../store/actions'; +import { deleteInterface, handleSnapRequest } from '../../store/actions'; import { getSignatureInsightSnapIds, getPermissionSubjectsDeepEqual, @@ -11,6 +11,7 @@ import { const SIGNATURE_INSIGHT_PERMISSION = 'endowment:signature-insight'; export function useSignatureInsights({ txData }) { + const dispatch = useDispatch(); const subjects = useSelector(getPermissionSubjectsDeepEqual); const snapIds = useSelector(getSignatureInsightSnapIds); const [loading, setLoading] = useState(true); @@ -87,9 +88,9 @@ export function useSignatureInsights({ txData }) { if (promise.response?.severity === SeverityLevel.Critical) { const { snapId, - response: { content }, + response: { id }, } = promise; - warningsArr.push({ snapId, content }); + warningsArr.push({ snapId, id }); } return warningsArr; }, []); @@ -109,5 +110,14 @@ export function useSignatureInsights({ txData }) { // eslint-disable-next-line react-hooks/exhaustive-deps }, [txData, JSON.stringify(snapIds), subjects]); + useEffect(() => { + return () => { + data?.map( + ({ response }) => + response?.id && dispatch(deleteInterface(response.id)), + ); + }; + }, [data]); + return { data, loading, warnings }; } diff --git a/ui/hooks/snaps/useTransactionInsightSnaps.js b/ui/hooks/snaps/useTransactionInsightSnaps.js index f214f79a96f4..9b4414837287 100644 --- a/ui/hooks/snaps/useTransactionInsightSnaps.js +++ b/ui/hooks/snaps/useTransactionInsightSnaps.js @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; -import { getTransactionOriginCaveat } from '@metamask/snaps-controllers'; +import { getTransactionOriginCaveat } from '@metamask/snaps-rpc-methods'; import { handleSnapRequest } from '../../store/actions'; import { getPermissionSubjectsDeepEqual } from '../../selectors'; diff --git a/ui/hooks/useAccountTotalFiatBalance.js b/ui/hooks/useAccountTotalFiatBalance.js index d0c55b8a8a5a..ce7eb852ccad 100644 --- a/ui/hooks/useAccountTotalFiatBalance.js +++ b/ui/hooks/useAccountTotalFiatBalance.js @@ -83,10 +83,11 @@ export const useAccountTotalFiatBalance = ( }); // Create an object with native token info. NOTE: Native token info is fetched from a separate controller - const nativeTokenValues = {}; - nativeTokenValues.iconUrl = primaryTokenImage; - nativeTokenValues.symbol = nativeCurrency; - nativeTokenValues.fiatBalance = nativeFiat; + const nativeTokenValues = { + iconUrl: primaryTokenImage, + symbol: nativeCurrency, + fiatBalance: nativeFiat, + }; // To match the list of detected tokens with the entire token list to find the image for tokens const findMatchingTokens = (array1, array2) => { diff --git a/ui/hooks/useTransactionInsights.js b/ui/hooks/useTransactionInsights.js index 24a25e10c177..5e94b37c8e6d 100644 --- a/ui/hooks/useTransactionInsights.js +++ b/ui/hooks/useTransactionInsights.js @@ -1,5 +1,10 @@ import React, { useEffect, useState } from 'react'; -import { useSelector } from 'react-redux'; +import { + ///: BEGIN:ONLY_INCLUDE_IF(build-flask) + useDispatch, + ///: END:ONLY_INCLUDE_IF + useSelector, +} from 'react-redux'; import { SeverityLevel } from '@metamask/snaps-sdk'; import { TransactionType } from '@metamask/transaction-controller'; @@ -13,6 +18,10 @@ import { getSubjectMetadataDeepEqual, } from '../selectors'; import { getSnapName } from '../helpers/utils/util'; + +///: BEGIN:ONLY_INCLUDE_IF(build-flask) +import { deleteInterface } from '../store/actions'; +///: END:ONLY_INCLUDE_IF import { useTransactionInsightSnaps } from './snaps/useTransactionInsightSnaps'; const isAllowedTransactionTypes = (transactionType) => @@ -26,6 +35,9 @@ const isAllowedTransactionTypes = (transactionType) => // https://github.com/MetaMask/metamask-extension/blob/develop/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js#L129 // Thus it is not possible to use React Component here const useTransactionInsights = ({ txData }) => { + ///: BEGIN:ONLY_INCLUDE_IF(build-flask) + const dispatch = useDispatch(); + ///: END:ONLY_INCLUDE_IF const { txParams, chainId, origin } = txData; const caip2ChainId = `eip155:${stripHexPrefix(chainId)}`; const insightSnaps = useSelector(getInsightSnaps); @@ -59,6 +71,17 @@ const useTransactionInsights = ({ txData }) => { } }, [insightSnapIds, selectedInsightSnapId, setSelectedInsightSnapId]); + ///: BEGIN:ONLY_INCLUDE_IF(build-flask) + useEffect(() => { + return () => { + data?.map( + ({ response }) => + response?.id && dispatch(deleteInterface(response.id)), + ); + }; + }, [data]); + ///: END:ONLY_INCLUDE_IF + if (!isAllowedTransactionTypes(txData.type) || !insightSnaps.length) { return null; } @@ -129,9 +152,9 @@ const useTransactionInsights = ({ txData }) => { if (promise.response?.severity === SeverityLevel.Critical) { const { snapId, - response: { content }, + response: { id }, } = promise; - warningsArr.push({ snapId, content }); + warningsArr.push({ snapId, id }); } return warningsArr; }, []); diff --git a/ui/pages/confirmations/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js b/ui/pages/confirmations/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js index cebfa3460ad0..87d826069f9e 100644 --- a/ui/pages/confirmations/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js +++ b/ui/pages/confirmations/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js @@ -17,6 +17,7 @@ import { } from '../../../../../helpers/constants/error-keys'; import { Severity } from '../../../../../helpers/constants/design-system'; +import { BlockaidResultType } from '../../../../../../shared/constants/security-provider'; import { ConfirmPageContainerSummary, ConfirmPageContainerWarning } from '.'; export default class ConfirmPageContainerContent extends Component { @@ -64,6 +65,7 @@ export default class ConfirmPageContainerContent extends Component { ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) noteComponent: PropTypes.node, ///: END:ONLY_INCLUDE_IF + txData: PropTypes.object, }; renderContent() { @@ -181,6 +183,7 @@ export default class ConfirmPageContainerContent extends Component { ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) openBuyCryptoInPdapp, ///: END:ONLY_INCLUDE_IF + txData, } = this.props; const { t } = this.context; @@ -191,6 +194,12 @@ export default class ConfirmPageContainerContent extends Component { const showIsSigningOrSubmittingError = errorKey === IS_SIGNING_OR_SUBMITTING; + const submitButtonType = + txData?.securityAlertResponse?.result_type === + BlockaidResultType.Malicious + ? 'danger-primary' + : 'primary'; + return (
{unapprovedTxCount > 1 ? ( {rejectNText} diff --git a/ui/pages/confirmations/components/confirm-page-container/confirm-page-container.component.js b/ui/pages/confirmations/components/confirm-page-container/confirm-page-container.component.js index 0b77d2708202..841a12d0e232 100644 --- a/ui/pages/confirmations/components/confirm-page-container/confirm-page-container.component.js +++ b/ui/pages/confirmations/components/confirm-page-container/confirm-page-container.component.js @@ -61,6 +61,7 @@ import { } from '../../../../../shared/constants/metametrics'; ///: END:ONLY_INCLUDE_IF +import { BlockaidResultType } from '../../../../../shared/constants/security-provider'; import { ConfirmPageContainerHeader, ConfirmPageContainerContent, @@ -196,6 +197,9 @@ const ConfirmPageContainer = (props) => { collectionBalance, ]); + const isMaliciousRequest = + txData.securityAlertResponse?.result_type === BlockaidResultType.Malicious; + return (
@@ -348,7 +352,8 @@ const ConfirmPageContainer = (props) => { onSubmit={topLevelHandleSubmit} submitText={t('confirm')} submitButtonType={ - isSetApproveForAll && isApprovalOrRejection + (isSetApproveForAll && isApprovalOrRejection) || + isMaliciousRequest ? 'danger-primary' : 'primary' } diff --git a/ui/pages/confirmations/components/security-provider-banner-alert/__snapshots__/security-provider-banner-alert.test.js.snap b/ui/pages/confirmations/components/security-provider-banner-alert/__snapshots__/security-provider-banner-alert.test.js.snap index 5981db85d4c0..7a17475bdde3 100644 --- a/ui/pages/confirmations/components/security-provider-banner-alert/__snapshots__/security-provider-banner-alert.test.js.snap +++ b/ui/pages/confirmations/components/security-provider-banner-alert/__snapshots__/security-provider-banner-alert.test.js.snap @@ -25,6 +25,7 @@ exports[`Security Provider Banner Alert should match snapshot 1`] = `

`; -exports[`Blockaid Banner Alert should render 'warning' UI when securityAlertResponse.result_type is 'Failed 1`] = ` +exports[`Blockaid Banner Alert should render 'warning' UI when securityAlertResponse.result_type is 'Error 1`] = `
) : null; - const isFailedResultType = resultType === BlockaidResultType.Failed; + const isFailedResultType = resultType === BlockaidResultType.Errored; const severity = resultType === BlockaidResultType.Malicious diff --git a/ui/pages/confirmations/components/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.test.js b/ui/pages/confirmations/components/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.test.js index b34d9d9b0e0a..4abd67e10685 100644 --- a/ui/pages/confirmations/components/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.test.js +++ b/ui/pages/confirmations/components/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.test.js @@ -70,13 +70,13 @@ describe('Blockaid Banner Alert', () => { expect(container.querySelector('.mm-banner-alert')).toBeNull(); }); - it(`should render '${Severity.Warning}' UI when securityAlertResponse.result_type is '${BlockaidResultType.Failed}`, () => { + it(`should render '${Severity.Warning}' UI when securityAlertResponse.result_type is '${BlockaidResultType.Errored}`, () => { const { container } = renderWithProvider( , @@ -140,13 +140,13 @@ describe('Blockaid Banner Alert', () => { expect(getByText('This is a deceptive request')).toBeInTheDocument(); }); - it(`should render title, "This is a suspicious request", when the reason is "${BlockaidReason.failed}"`, () => { + it(`should render title, "This is a suspicious request", when the reason is "${BlockaidReason.errored}"`, () => { const { getByText } = renderWithProvider( , @@ -248,14 +248,14 @@ describe('Blockaid Banner Alert', () => { }); describe('when constructing the Blockaid Report URL', () => { - describe(`when result_type='${BlockaidResultType.Failed}'`, () => { + describe(`when result_type='${BlockaidResultType.Errored}'`, () => { it('should pass the classification as "error" and the resultType as "Error"', () => { const { getByRole } = renderWithProvider( , @@ -276,7 +276,7 @@ describe('Blockaid Banner Alert', () => { 'If you approve this request, a third party known for scams might take all your assets.', [BlockaidReason.blurFarming]: 'If you approve this request, someone can steal your assets listed on Blur.', - [BlockaidReason.failed]: + [BlockaidReason.errored]: 'Because of an error, this request was not verified by the security provider. Proceed with caution.', [BlockaidReason.maliciousDomain]: "You're interacting with a malicious domain. If you approve this request, you might lose your assets.", 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 e0c63ebe51d4..4ea8bc7704ab 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 @@ -58,6 +58,7 @@ import SnapLegacyAuthorshipHeader from '../../../../components/app/snaps/snap-le ///: BEGIN:ONLY_INCLUDE_IF(build-flask) import InsightWarnings from '../../../../components/app/snaps/insight-warnings'; ///: END:ONLY_INCLUDE_IF +import { BlockaidResultType } from '../../../../../shared/constants/security-provider'; import SignatureRequestOriginalWarning from './signature-request-original-warning'; export default class SignatureRequestOriginal extends Component { @@ -323,6 +324,10 @@ export default class SignatureRequestOriginal extends Component { } = this.props; const { t } = this.context; + const submitButtonType = + txData.securityAlertResponse?.result_type === BlockaidResultType.Malicious + ? 'danger-primary' + : 'primary'; return ( ); }; diff --git a/ui/pages/confirmations/components/signature-request-siwe/signature-request-siwe.js b/ui/pages/confirmations/components/signature-request-siwe/signature-request-siwe.js index 01dd866790de..38f5abf5d4fc 100644 --- a/ui/pages/confirmations/components/signature-request-siwe/signature-request-siwe.js +++ b/ui/pages/confirmations/components/signature-request-siwe/signature-request-siwe.js @@ -47,6 +47,7 @@ import SignatureRequestHeader from '../signature-request-header'; ///: BEGIN:ONLY_INCLUDE_IF(build-flask) import InsightWarnings from '../../../../components/app/snaps/insight-warnings'; ///: END:ONLY_INCLUDE_IF +import { BlockaidResultType } from '../../../../../shared/constants/security-provider'; import Header from './signature-request-siwe-header'; import Message from './signature-request-siwe-message'; @@ -137,6 +138,12 @@ export default function SignatureRequestSIWE({ const rejectNText = t('rejectRequestsN', [messagesCount]); + const submitButtonType = + txData.securityAlertResponse?.result_type === + BlockaidResultType.Malicious || !isSIWEDomainValid + ? 'danger-primary' + : 'primary'; + return ( <>
@@ -211,7 +218,7 @@ export default function SignatureRequestSIWE({ }} cancelText={t('cancel')} submitText={t('signin')} - submitButtonType={isSIWEDomainValid ? 'primary' : 'danger-primary'} + submitButtonType={submitButtonType} /> {messagesCount > 1 ? (
@@ -90,6 +91,7 @@ exports[`Add Network Modal should render 1`] = `
@@ -144,6 +146,7 @@ exports[`Add Network Modal should render 1`] = `
@@ -201,6 +204,7 @@ exports[`Add Network Modal should render 1`] = `
diff --git a/ui/pages/routes/routes.component.js b/ui/pages/routes/routes.component.js index 7fc00a46191a..57090aea3550 100644 --- a/ui/pages/routes/routes.component.js +++ b/ui/pages/routes/routes.component.js @@ -12,7 +12,10 @@ import SendTransactionScreen from '../confirmations/send'; import Swaps from '../swaps'; import ConfirmTransaction from '../confirmations/confirm-transaction'; import Home from '../home'; -import { AllConnections, Connections } from '../../components/multichain/pages'; +import { + PermissionsPage, + Connections, +} from '../../components/multichain/pages'; import Settings from '../settings'; import Authenticated from '../../helpers/higher-order-components/authenticated'; import Initialized from '../../helpers/higher-order-components/initialized'; @@ -79,7 +82,7 @@ import { ONBOARDING_UNLOCK_ROUTE, TOKEN_DETAILS, CONNECTIONS, - ALL_CONNECTIONS, + PERMISSIONS, ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) INSTITUTIONAL_FEATURES_DONE_ROUTE, CUSTODY_ACCOUNT_DONE_ROUTE, @@ -378,11 +381,7 @@ export default class Routes extends Component { )} {process.env.MULTICHAIN && ( - + )} @@ -474,14 +473,14 @@ export default class Routes extends Component { return true; } - const isAllConnectionsPage = Boolean( + const isPermissionsPage = Boolean( matchPath(location.pathname, { - path: ALL_CONNECTIONS, + path: PERMISSIONS, exact: false, }), ); - if (isAllConnectionsPage) { + if (isPermissionsPage) { return true; } diff --git a/ui/pages/settings/index.scss b/ui/pages/settings/index.scss index c69b05861fbc..ec06ed6c994b 100644 --- a/ui/pages/settings/index.scss +++ b/ui/pages/settings/index.scss @@ -95,7 +95,6 @@ gap: 8px; &__icon { - background: var(--color-background-alternative); height: 15px; width: 15px; margin-right: 16px; diff --git a/ui/pages/settings/networks-tab/networks-form/networks-form.js b/ui/pages/settings/networks-tab/networks-form/networks-form.js index f7b892b894a0..e71f6bf3a0a7 100644 --- a/ui/pages/settings/networks-tab/networks-form/networks-form.js +++ b/ui/pages/settings/networks-tab/networks-form/networks-form.js @@ -709,6 +709,7 @@ const NetworksForm = ({ titleText={t('networkName')} value={networkName} disabled={viewOnly} + dataTestId="network-form-network-name" />
+ diff --git a/ui/pages/settings/security-tab/security-tab.component.js b/ui/pages/settings/security-tab/security-tab.component.js index d6ea786b8a77..150702b80e25 100644 --- a/ui/pages/settings/security-tab/security-tab.component.js +++ b/ui/pages/settings/security-tab/security-tab.component.js @@ -201,7 +201,12 @@ export default class SecurityTab extends PureComponent { ref={this.settingsRefs[15]} className="settings-page__security-tab-sub-header" > - + + {t('securityAlerts')}
diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index 50434c17d9e2..9ae1f5ba6ae9 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -894,6 +894,72 @@ export const getAnySnapUpdateAvailable = createSelector( return [...snapMap.values()].some((value) => value === true); }, ); + +/** + * Get a memoized version of the target subject metadata. + */ +export const getMemoizedTargetSubjectMetadata = createDeepEqualSelector( + getTargetSubjectMetadata, + (interfaces) => interfaces, +); + +/** + * Get the Snap interfaces from the redux state. + * + * @param state - Redux state object. + * @returns the Snap interfaces. + */ +const getInterfaces = (state) => state.metamask.interfaces; + +/** + * Input selector providing a way to pass a Snap interface ID as an argument. + * + * @param _state - Redux state object. + * @param interfaceId - ID of a Snap interface. + * @returns ID of a Snap Interface that can be used as input selector. + */ +const selectInterfaceId = (_state, interfaceId) => interfaceId; + +/** + * Get a memoized version of the Snap interfaces. + */ +export const getMemoizedInterfaces = createDeepEqualSelector( + getInterfaces, + (interfaces) => interfaces, +); + +/** + * Get a Snap Interface with a given ID. + */ +export const getInterface = createSelector( + [getMemoizedInterfaces, selectInterfaceId], + (interfaces, id) => interfaces[id], +); + +/** + * Get a memoized version of a Snap interface with a given ID + */ +export const getMemoizedInterface = createDeepEqualSelector( + getInterface, + (snapInterface) => snapInterface, +); + +/** + * Get the content from a Snap interface with a given ID. + */ +export const getInterfaceContent = createSelector( + [getMemoizedInterfaces, selectInterfaceId], + (interfaces, id) => interfaces[id]?.content, +); + +/** + * Get a memoized version of the content from a Snap interface with a given ID. + */ +export const getMemoizedInterfaceContent = createDeepEqualSelector( + getInterfaceContent, + (content) => content, +); + ///: END:ONLY_INCLUDE_IF export function getRpcPrefsForCurrentProvider(state) { @@ -1122,6 +1188,41 @@ export const getAllConnectedAccounts = createDeepEqualSelector( return Object.keys(connectedSubjects); }, ); +export const getConnectedSitesList = createDeepEqualSelector( + getConnectedSubjectsForAllAddresses, + getAllConnectedAccounts, + (connectedSubjectsForAllAddresses, connectedAddresses) => { + const sitesList = {}; + connectedAddresses.forEach((connectedAddress) => { + connectedSubjectsForAllAddresses[connectedAddress].forEach((app) => { + const siteKey = app.origin; + + if (sitesList[siteKey]) { + sitesList[siteKey].addresses.push(connectedAddress); + } else { + sitesList[siteKey] = { ...app, addresses: [connectedAddress] }; + } + }); + }); + + return sitesList; + }, +); + +export const getConnectedSnapsList = createDeepEqualSelector( + getSnapsList, + (snapsData) => { + const snapsList = {}; + + Object.values(snapsData).forEach((snap) => { + if (!snapsList[snap.name]) { + snapsList[snap.name] = snap; + } + }); + + return snapsList; + }, +); export const getMemoizedCurrentChainId = createDeepEqualSelector( getCurrentChainId, @@ -1384,6 +1485,10 @@ export function getShowBetaHeader(state) { return state.metamask.showBetaHeader; } +export function getShowPermissionsTour(state) { + return state.metamask.showPermissionsTour; +} + export function getShowProductTour(state) { return state.metamask.showProductTour; } @@ -1972,6 +2077,11 @@ export const useSafeChainsListValidationSelector = (state) => { return state.metamask.useSafeChainsListValidation; }; +export function getShowFiatInTestnets(state) { + const { showFiatInTestnets } = getPreferences(state); + return showFiatInTestnets; +} + /** * To get the useCurrencyRateCheck flag which to check if the user prefers currency conversion * diff --git a/ui/store/actions.ts b/ui/store/actions.ts index 6003470c4f0e..7364336a8cd4 100644 --- a/ui/store/actions.ts +++ b/ui/store/actions.ts @@ -32,6 +32,7 @@ import { TransactionType, } from '@metamask/transaction-controller'; import { NetworkClientId } from '@metamask/network-controller'; +import { InterfaceState } from '@metamask/snaps-sdk'; import { getMethodDataAsync } from '../helpers/utils/transactions.util'; import switchDirection from '../../shared/lib/switch-direction'; import { @@ -4570,6 +4571,10 @@ export function hideBetaHeader() { return submitRequestToBackground('setShowBetaHeader', [false]); } +export function hidePermissionsTour() { + return submitRequestToBackground('setShowPermissionsTour', [false]); +} + export function hideProductTour() { return submitRequestToBackground('setShowProductTour', [false]); } @@ -4793,6 +4798,38 @@ export function setSnapsInstallPrivacyWarningShownStatus(shown: boolean) { ); }; } + +/** + * Update the state of a given Snap interface. + * + * @param id - The Snap interface ID. + * @param state - The interface state. + * @returns Promise Resolved on successfully submitted background request. + */ +export function updateInterfaceState( + id: string, + state: InterfaceState, +): ThunkAction { + return (async (dispatch: MetaMaskReduxDispatch) => { + await submitRequestToBackground('updateInterfaceState', [id, state]); + await forceUpdateMetamaskState(dispatch); + }) as any; +} + +/** + * Delete the Snap interface from state. + * + * @param id - The Snap interface ID. + * @returns Promise Resolved on successfully submitted background request. + */ +export function deleteInterface( + id: string, +): ThunkAction { + return (async (dispatch: MetaMaskReduxDispatch) => { + await submitRequestToBackground('deleteInterface', [id]); + await forceUpdateMetamaskState(dispatch); + }) as any; +} ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(build-flask) diff --git a/ui/store/institutional/institution-background.test.js b/ui/store/institutional/institution-background.test.js index 8fe56eefce70..75792512e9ad 100644 --- a/ui/store/institutional/institution-background.test.js +++ b/ui/store/institutional/institution-background.test.js @@ -40,8 +40,6 @@ describe('Institution Actions', () => { getCustodianToken: jest.fn(), getCustodianJWTList: jest.fn(), removeAddTokenConnectRequest: jest.fn(), - setCustodianConnectRequest: jest.fn(), - getCustodianConnectRequest: jest.fn(), getMmiConfiguration: jest.fn(), getAllCustodianAccountsWithToken: jest.fn(), setWaitForConfirmDeepLinkDialog: jest.fn(), @@ -85,7 +83,6 @@ describe('Institution Actions', () => { custodians: [], }); mmiActions.getCustodianToken({}); - mmiActions.getCustodianConnectRequest(); mmiActions.getCustodianTransactionDeepLink('0xAddress', 'txId'); mmiActions.getCustodianConfirmDeepLink('txId'); mmiActions.getCustodianSignMessageDeepLink('0xAddress', 'custodyTxId'); @@ -99,11 +96,6 @@ describe('Institution Actions', () => { token: 'token', environment: 'jupiter', }); - mmiActions.setCustodianConnectRequest({ - token: 'token', - custodianType: 'custodianType', - envName: 'jupiter', - }); const setWaitForConfirmDeepLinkDialog = mmiActions.setWaitForConfirmDeepLinkDialog(true); mmiActions.setCustodianNewRefreshToken('address', 'refreshToken'); diff --git a/ui/store/institutional/institution-background.ts b/ui/store/institutional/institution-background.ts index cee2924ce424..cd437d09f651 100644 --- a/ui/store/institutional/institution-background.ts +++ b/ui/store/institutional/institution-background.ts @@ -219,20 +219,6 @@ export function mmiActionsFactory() { environment, token, }), - setCustodianConnectRequest: ({ - token, - custodianType, - envName, - }: { - token: string; - custodianType: string; - envName: string; - }) => - createAsyncAction('setCustodianConnectRequest', [ - { token, custodianType, envName }, - ]), - getCustodianConnectRequest: () => - createAsyncAction('getCustodianConnectRequest', []), getMmiConfiguration: () => createAsyncAction('getMmiConfiguration', []), getAllCustodianAccountsWithToken: (custodyType: string, token: string) => createAsyncAction('getAllCustodianAccountsWithToken', [ diff --git a/yarn.lock b/yarn.lock index 4ba24f6122ad..f194eae49234 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1933,6 +1933,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/aix-ppc64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/aix-ppc64@npm:0.19.12" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/android-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/android-arm64@npm:0.18.20" @@ -1940,6 +1947,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/android-arm64@npm:0.19.12" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/android-arm@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/android-arm@npm:0.18.20" @@ -1947,6 +1961,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/android-arm@npm:0.19.12" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@esbuild/android-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/android-x64@npm:0.18.20" @@ -1954,6 +1975,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/android-x64@npm:0.19.12" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + "@esbuild/darwin-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/darwin-arm64@npm:0.18.20" @@ -1961,6 +1989,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-arm64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/darwin-arm64@npm:0.19.12" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/darwin-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/darwin-x64@npm:0.18.20" @@ -1968,6 +2003,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/darwin-x64@npm:0.19.12" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@esbuild/freebsd-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/freebsd-arm64@npm:0.18.20" @@ -1975,6 +2017,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-arm64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/freebsd-arm64@npm:0.19.12" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/freebsd-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/freebsd-x64@npm:0.18.20" @@ -1982,6 +2031,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/freebsd-x64@npm:0.19.12" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/linux-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-arm64@npm:0.18.20" @@ -1989,6 +2045,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-arm64@npm:0.19.12" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/linux-arm@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-arm@npm:0.18.20" @@ -1996,6 +2059,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-arm@npm:0.19.12" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@esbuild/linux-ia32@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-ia32@npm:0.18.20" @@ -2003,6 +2073,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ia32@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-ia32@npm:0.19.12" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/linux-loong64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-loong64@npm:0.18.20" @@ -2010,6 +2087,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-loong64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-loong64@npm:0.19.12" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + "@esbuild/linux-mips64el@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-mips64el@npm:0.18.20" @@ -2017,6 +2101,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-mips64el@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-mips64el@npm:0.19.12" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + "@esbuild/linux-ppc64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-ppc64@npm:0.18.20" @@ -2024,6 +2115,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ppc64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-ppc64@npm:0.19.12" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/linux-riscv64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-riscv64@npm:0.18.20" @@ -2031,6 +2129,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-riscv64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-riscv64@npm:0.19.12" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + "@esbuild/linux-s390x@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-s390x@npm:0.18.20" @@ -2038,6 +2143,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-s390x@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-s390x@npm:0.19.12" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + "@esbuild/linux-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-x64@npm:0.18.20" @@ -2045,6 +2157,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-x64@npm:0.19.12" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + "@esbuild/netbsd-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/netbsd-x64@npm:0.18.20" @@ -2052,6 +2171,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/netbsd-x64@npm:0.19.12" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/openbsd-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/openbsd-x64@npm:0.18.20" @@ -2059,6 +2185,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/openbsd-x64@npm:0.19.12" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/sunos-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/sunos-x64@npm:0.18.20" @@ -2066,6 +2199,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/sunos-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/sunos-x64@npm:0.19.12" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + "@esbuild/win32-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/win32-arm64@npm:0.18.20" @@ -2073,6 +2213,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-arm64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/win32-arm64@npm:0.19.12" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/win32-ia32@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/win32-ia32@npm:0.18.20" @@ -2080,6 +2227,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-ia32@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/win32-ia32@npm:0.19.12" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/win32-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/win32-x64@npm:0.18.20" @@ -2087,6 +2241,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/win32-x64@npm:0.19.12" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@eslint-community/eslint-utils@npm:^4.1.2, @eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": version: 4.4.0 resolution: "@eslint-community/eslint-utils@npm:4.4.0" @@ -3454,20 +3615,20 @@ __metadata: languageName: node linkType: hard -"@metamask-institutional/custody-controller@npm:^0.2.19, @metamask-institutional/custody-controller@npm:^0.2.20": - version: 0.2.20 - resolution: "@metamask-institutional/custody-controller@npm:0.2.20" +"@metamask-institutional/custody-controller@npm:^0.2.22": + version: 0.2.22 + resolution: "@metamask-institutional/custody-controller@npm:0.2.22" dependencies: "@ethereumjs/util": "npm:^8.0.5" - "@metamask-institutional/custody-keyring": "npm:^1.0.9" + "@metamask-institutional/custody-keyring": "npm:^1.0.10" "@metamask-institutional/sdk": "npm:^0.1.24" "@metamask-institutional/types": "npm:^1.0.4" "@metamask/obs-store": "npm:^8.0.0" - checksum: 8b6a25b4d870ea9bd3f235d45c82226cecbbef3bbbe6b635114e28642c847dbd1258982d0b0501b0e4109de0d16024c4c192dcd7ff018d281f7e075f1de23b1d + checksum: e5ee0ce9dfca87ddaff977a48e4914263de9f930797ad9adc0568381ec2a4373f8e307bafc1162c0215eccfa93b8b42ce684bac4b294758c53164958e44a7d69 languageName: node linkType: hard -"@metamask-institutional/custody-keyring@npm:^1.0.10, @metamask-institutional/custody-keyring@npm:^1.0.8, @metamask-institutional/custody-keyring@npm:^1.0.9": +"@metamask-institutional/custody-keyring@npm:^1.0.10, @metamask-institutional/custody-keyring@npm:^1.0.8": version: 1.0.10 resolution: "@metamask-institutional/custody-keyring@npm:1.0.10" dependencies: @@ -3483,20 +3644,20 @@ __metadata: languageName: node linkType: hard -"@metamask-institutional/extension@npm:^0.3.13": - version: 0.3.13 - resolution: "@metamask-institutional/extension@npm:0.3.13" +"@metamask-institutional/extension@npm:^0.3.16": + version: 0.3.16 + resolution: "@metamask-institutional/extension@npm:0.3.16" dependencies: "@ethereumjs/util": "npm:^8.0.5" - "@metamask-institutional/custody-controller": "npm:^0.2.19" - "@metamask-institutional/custody-keyring": "npm:^1.0.8" + "@metamask-institutional/custody-controller": "npm:^0.2.22" + "@metamask-institutional/custody-keyring": "npm:^1.0.10" "@metamask-institutional/portfolio-dashboard": "npm:^1.4.0" "@metamask-institutional/sdk": "npm:^0.1.24" - "@metamask-institutional/transaction-update": "npm:^0.1.34" + "@metamask-institutional/transaction-update": "npm:^0.1.36" "@metamask-institutional/types": "npm:^1.0.4" jest-create-mock-instance: "npm:^2.0.0" jest-fetch-mock: "npm:3.0.3" - checksum: a32e93bb8e2d31850b900f0077b502e5f6afc786dd9da621f1a8ed040ad800972838167a2db1991510d1e029ea9c9365e5fe3c2a0c421738a3f0a2b698886661 + checksum: 099b941dba62a503829e8806a6892ff49672492379f3d6230f6c562cf3e70da7ffc077598ff2acf8fe286a6fa8df3d8eaa63e9d40eca943fed3c609150b43af2 languageName: node linkType: hard @@ -3545,17 +3706,17 @@ __metadata: languageName: node linkType: hard -"@metamask-institutional/transaction-update@npm:^0.1.32, @metamask-institutional/transaction-update@npm:^0.1.34": - version: 0.1.34 - resolution: "@metamask-institutional/transaction-update@npm:0.1.34" +"@metamask-institutional/transaction-update@npm:^0.1.32, @metamask-institutional/transaction-update@npm:^0.1.36": + version: 0.1.36 + resolution: "@metamask-institutional/transaction-update@npm:0.1.36" dependencies: - "@metamask-institutional/custody-keyring": "npm:^1.0.8" + "@metamask-institutional/custody-keyring": "npm:^1.0.10" "@metamask-institutional/sdk": "npm:^0.1.24" "@metamask-institutional/types": "npm:^1.0.4" - "@metamask-institutional/websocket-client": "npm:^0.1.36" + "@metamask-institutional/websocket-client": "npm:^0.1.38" "@metamask/obs-store": "npm:^8.0.0" ethereumjs-util: "npm:^7.1.5" - checksum: 9dbf4374d8c7698f85fc6513641f7fc73d802afb0ef1e89e1dc3b3bc74d716705d8370bac7c2f20dfdeefd2a739da22c8903946f1972d5b51e6db0d23211fe04 + checksum: e35fad2e51a541679a36d3f8db11a16114a2516fed7c740ff77560a35cacf67880c8d90e57ba13fd9f179136a9fbdd108b3b58f0dc38c64a85797e8738749ad4 languageName: node linkType: hard @@ -3566,15 +3727,15 @@ __metadata: languageName: node linkType: hard -"@metamask-institutional/websocket-client@npm:^0.1.36": - version: 0.1.36 - resolution: "@metamask-institutional/websocket-client@npm:0.1.36" +"@metamask-institutional/websocket-client@npm:^0.1.38": + version: 0.1.38 + resolution: "@metamask-institutional/websocket-client@npm:0.1.38" dependencies: - "@metamask-institutional/custody-keyring": "npm:^1.0.8" + "@metamask-institutional/custody-keyring": "npm:^1.0.10" "@metamask-institutional/sdk": "npm:^0.1.24" "@metamask-institutional/types": "npm:^1.0.4" mock-socket: "npm:^9.2.1" - checksum: a8b56e681a2bcc858067a8b5fd7c4e586459f3cee9e4165f2cf1e08c278139b015fc9195b961ddb0bf8fc5169afa362c596624f7caaea1a8d59c491ac3973c28 + checksum: 6b1cb6798f58f83b128e55348fbb738d3cfb54c76d557731a8fd1bd3c1dde5604d958699d8c030ede225017fdc8977112d2397d161a5f9da6d9fced8452494e8 languageName: node linkType: hard @@ -3662,7 +3823,7 @@ __metadata: languageName: node linkType: hard -"@metamask/assets-controllers@npm:^24.0.0": +"@metamask/assets-controllers@npm:24.0.0": version: 24.0.0 resolution: "@metamask/assets-controllers@npm:24.0.0" dependencies: @@ -3698,6 +3859,42 @@ __metadata: languageName: node linkType: hard +"@metamask/assets-controllers@patch:@metamask/assets-controllers@npm%3A24.0.0#~/.yarn/patches/@metamask-assets-controllers-npm-24.0.0-dfef136464.patch": + version: 24.0.0 + resolution: "@metamask/assets-controllers@patch:@metamask/assets-controllers@npm%3A24.0.0#~/.yarn/patches/@metamask-assets-controllers-npm-24.0.0-dfef136464.patch::version=24.0.0&hash=f321cb" + dependencies: + "@ethersproject/address": "npm:^5.7.0" + "@ethersproject/bignumber": "npm:^5.7.0" + "@ethersproject/contracts": "npm:^5.7.0" + "@ethersproject/providers": "npm:^5.7.0" + "@metamask/abi-utils": "npm:^2.0.2" + "@metamask/approval-controller": "npm:^5.1.1" + "@metamask/base-controller": "npm:^4.0.1" + "@metamask/contract-metadata": "npm:^2.4.0" + "@metamask/controller-utils": "npm:^8.0.1" + "@metamask/eth-query": "npm:^4.0.0" + "@metamask/metamask-eth-abis": "npm:3.0.0" + "@metamask/network-controller": "npm:^17.1.0" + "@metamask/polling-controller": "npm:^4.0.0" + "@metamask/preferences-controller": "npm:^6.0.0" + "@metamask/rpc-errors": "npm:^6.1.0" + "@metamask/utils": "npm:^8.2.0" + "@types/uuid": "npm:^8.3.0" + async-mutex: "npm:^0.2.6" + cockatiel: "npm:^3.1.2" + ethereumjs-util: "npm:^7.0.10" + lodash: "npm:^4.17.21" + multiformats: "npm:^9.5.2" + single-call-balance-checker-abi: "npm:^1.0.0" + uuid: "npm:^8.3.2" + peerDependencies: + "@metamask/approval-controller": ^5.1.1 + "@metamask/network-controller": ^17.1.0 + "@metamask/preferences-controller": ^6.0.0 + checksum: fd23afac75f432daf108b3ae8aed915466bd1d91d3c306580b262cb2886ac0e3874bc367a24f21a3179d720610ce6b2e8ff9d1b34ddcdd9179d65679c1ced6fd + languageName: node + linkType: hard + "@metamask/auto-changelog@npm:^2.1.0": version: 2.6.1 resolution: "@metamask/auto-changelog@npm:2.6.1" @@ -3789,18 +3986,18 @@ __metadata: languageName: node linkType: hard -"@metamask/controller-utils@npm:^8.0.1, @metamask/controller-utils@npm:^8.0.2": - version: 8.0.2 - resolution: "@metamask/controller-utils@npm:8.0.2" +"@metamask/controller-utils@npm:^8.0.1, @metamask/controller-utils@npm:^8.0.2, @metamask/controller-utils@npm:^8.0.3": + version: 8.0.3 + resolution: "@metamask/controller-utils@npm:8.0.3" dependencies: "@metamask/eth-query": "npm:^4.0.0" - "@metamask/ethjs-unit": "npm:^0.2.1" + "@metamask/ethjs-unit": "npm:^0.3.0" "@metamask/utils": "npm:^8.3.0" "@spruceid/siwe-parser": "npm:1.1.3" eth-ens-namehash: "npm:^2.0.8" ethereumjs-util: "npm:^7.0.10" fast-deep-equal: "npm:^3.1.3" - checksum: 6a8099b883c51b47494678998fb14291cd0ea9904823b8e3a8cd1621dfc321b59b071e0f264225901177e4826499c32243d5b18388c521bbef351ab87a9d332b + checksum: 01c0fbddc3d1fbc56b72b9ea234104e81394efbb4487f60cf0a8316e1a3b79e2d0e6ccf071ea8e4d35a84a34004a4e654f3007e3c4151ac3dbe69dd280969af4 languageName: node linkType: hard @@ -3811,10 +4008,10 @@ __metadata: languageName: node linkType: hard -"@metamask/design-tokens@npm:^2.0.3": - version: 2.0.3 - resolution: "@metamask/design-tokens@npm:2.0.3" - checksum: 83dc20a911981050719861319837c5945d48668d21efe0e6b508f485f914fb2793415df8b32ec8e4ebde29f73cb4d489460b4b477e0fd4eed679abc5095bed04 +"@metamask/design-tokens@npm:^2.1.1": + version: 2.1.1 + resolution: "@metamask/design-tokens@npm:2.1.1" + checksum: 0f219d7568fbdab4278994dc1e10a77aaab8f6edabbecf0e9cfa3e3593d457aea7c65d5b9ec9973d123246f335d96f4145b5109d7760148052269e2e4c7568a0 languageName: node linkType: hard @@ -4059,18 +4256,20 @@ __metadata: languageName: node linkType: hard -"@metamask/eth-token-tracker@npm:^7.0.1": - version: 7.0.1 - resolution: "@metamask/eth-token-tracker@npm:7.0.1" +"@metamask/eth-token-tracker@npm:^7.0.2": + version: 7.0.2 + resolution: "@metamask/eth-token-tracker@npm:7.0.2" dependencies: - "@metamask/ethjs-contract": "npm:^0.3.4" - "@metamask/ethjs-query": "npm:^0.5.2" + "@metamask/ethjs-contract": "npm:^0.4.1" + "@metamask/ethjs-query": "npm:^0.7.1" "@metamask/safe-event-emitter": "npm:^3.0.0" - bn.js: "npm:^4.12.0" + bn.js: "npm:^5.2.1" deep-equal: "npm:^2.2.0" eth-block-tracker: "npm:^8.0.0" human-standard-token-abi: "npm:^2.0.0" - checksum: 5ce697ba2eae9dc8df942b5cee477cce4e4a1d5dbbe5f7e2268c8880135b45dc7cd86d888595a692c35d257586b950b9474caa62db49357452b22f3744d83b9e + peerDependencies: + "@babel/runtime": ^7.21.0 + checksum: eec5b953de3449344107408ea820f95e95a1921da34259a80a6b6ae201fe0d7a4e87abf028b91aa865e904c88c96e7a39407386d89904076e7f1fd632960a86f languageName: node linkType: hard @@ -4095,7 +4294,7 @@ __metadata: languageName: node linkType: hard -"@metamask/ethjs-contract@npm:^0.3.3, @metamask/ethjs-contract@npm:^0.3.4": +"@metamask/ethjs-contract@npm:^0.3.3": version: 0.3.4 resolution: "@metamask/ethjs-contract@npm:0.3.4" dependencies: @@ -4109,6 +4308,21 @@ __metadata: languageName: node linkType: hard +"@metamask/ethjs-contract@npm:^0.4.1": + version: 0.4.1 + resolution: "@metamask/ethjs-contract@npm:0.4.1" + dependencies: + "@metamask/ethjs-filter": "npm:^0.3.0" + "@metamask/ethjs-util": "npm:^0.3.0" + ethjs-abi: "npm:^0.2.0" + js-sha3: "npm:^0.9.2" + promise-to-callback: "npm:^1.0.0" + peerDependencies: + "@babel/runtime": ^7.0.0 + checksum: 2187f62336c2afd46b51bda87b96e1537dd7dfe0f621defc3cb5ccb68add482004674f3c11ced95c65b7ecbe845592541f1b268c97b0b42c939d8d5677523097 + languageName: node + linkType: hard + "@metamask/ethjs-filter@npm:^0.2.0": version: 0.2.0 resolution: "@metamask/ethjs-filter@npm:0.2.0" @@ -4116,6 +4330,15 @@ __metadata: languageName: node linkType: hard +"@metamask/ethjs-filter@npm:^0.3.0": + version: 0.3.0 + resolution: "@metamask/ethjs-filter@npm:0.3.0" + peerDependencies: + "@babel/runtime": ^7.0.0 + checksum: 1b7565045e638f60d39ae8d8ca020b6e3ac2e4e91f220f343e499b09a7adeb9e4273249e3c3f7366acc2e44bc0b7fe3dd3ac84c66e9a0853352bfb0e116c1d87 + languageName: node + linkType: hard + "@metamask/ethjs-format@npm:^0.2.9": version: 0.2.9 resolution: "@metamask/ethjs-format@npm:0.2.9" @@ -4130,6 +4353,22 @@ __metadata: languageName: node linkType: hard +"@metamask/ethjs-format@npm:^0.3.0": + version: 0.3.0 + resolution: "@metamask/ethjs-format@npm:0.3.0" + dependencies: + "@metamask/ethjs-util": "npm:^0.3.0" + "@metamask/number-to-bn": "npm:^1.7.1" + bn.js: "npm:^5.2.1" + ethjs-schema: "npm:0.2.1" + is-hex-prefixed: "npm:1.0.0" + strip-hex-prefix: "npm:1.0.0" + peerDependencies: + "@babel/runtime": ^7.0.0 + checksum: 4ae73716e0dd76f7b780ede79b3989d241ee8654b7aa9ec23678529a1916f77ab101552f7aabe56c8e26506a33f34181f40624a68a317e7cd3ae45fb0da2fa73 + languageName: node + linkType: hard + "@metamask/ethjs-provider-http@npm:^0.2.0": version: 0.2.0 resolution: "@metamask/ethjs-provider-http@npm:0.2.0" @@ -4139,7 +4378,18 @@ __metadata: languageName: node linkType: hard -"@metamask/ethjs-query@npm:^0.5.2, @metamask/ethjs-query@npm:^0.5.3": +"@metamask/ethjs-provider-http@npm:^0.3.0": + version: 0.3.0 + resolution: "@metamask/ethjs-provider-http@npm:0.3.0" + dependencies: + xhr2: "npm:0.2.1" + peerDependencies: + "@babel/runtime": ^7.0.0 + checksum: cece5d20836dadbe31f0c7118f26bd5330465c28f9a39ab8ac4a3fbce25094ae53fe8b737e3d95ae0cf4c7e075c9349127cf8bd066f138278ffc9fd7f9a8a101 + languageName: node + linkType: hard + +"@metamask/ethjs-query@npm:^0.5.2": version: 0.5.3 resolution: "@metamask/ethjs-query@npm:0.5.3" dependencies: @@ -4151,6 +4401,19 @@ __metadata: languageName: node linkType: hard +"@metamask/ethjs-query@npm:^0.7.1": + version: 0.7.1 + resolution: "@metamask/ethjs-query@npm:0.7.1" + dependencies: + "@metamask/ethjs-format": "npm:^0.3.0" + "@metamask/ethjs-rpc": "npm:^0.4.0" + promise-to-callback: "npm:^1.0.0" + peerDependencies: + "@babel/runtime": ^7.0.0 + checksum: a9d54677cf062267f7e781996edfadfc2a5a2f3589266b7f20386e8109b7cecdfdc47c6aba4bb68d0c4cb387cfdb463d6316d506c06138531ca1e81ab201e5dc + languageName: node + linkType: hard + "@metamask/ethjs-rpc@npm:0.3.0 || ^0.3.2": version: 0.3.2 resolution: "@metamask/ethjs-rpc@npm:0.3.2" @@ -4160,6 +4423,17 @@ __metadata: languageName: node linkType: hard +"@metamask/ethjs-rpc@npm:^0.4.0": + version: 0.4.0 + resolution: "@metamask/ethjs-rpc@npm:0.4.0" + dependencies: + promise-to-callback: "npm:^1.0.0" + peerDependencies: + "@babel/runtime": ^7.0.0 + checksum: e126535ff7599ef584b0084dae846294d505b69141a143543d7580cdce4b297646e796fbd0cecc5a824dce8b5e3b179bb177ceff06fd50ad584e7fce00ab081f + languageName: node + linkType: hard + "@metamask/ethjs-unit@npm:^0.2.1": version: 0.2.1 resolution: "@metamask/ethjs-unit@npm:0.2.1" @@ -4170,6 +4444,18 @@ __metadata: languageName: node linkType: hard +"@metamask/ethjs-unit@npm:^0.3.0": + version: 0.3.0 + resolution: "@metamask/ethjs-unit@npm:0.3.0" + dependencies: + "@metamask/number-to-bn": "npm:^1.7.1" + bn.js: "npm:^5.2.1" + peerDependencies: + "@babel/runtime": ^7.0.0 + checksum: a027d69d66b7eb45666c3bffbedd76dc25dbdd11b177a6b0a7c6735a06d1f123cd4c4c2e301149f6068450a49d41c24c6f93d665552757408fafefbd8720346e + languageName: node + linkType: hard + "@metamask/ethjs-util@npm:^0.2.0": version: 0.2.0 resolution: "@metamask/ethjs-util@npm:0.2.0" @@ -4180,7 +4466,19 @@ __metadata: languageName: node linkType: hard -"@metamask/ethjs@npm:^0.5.0, @metamask/ethjs@npm:^0.5.1": +"@metamask/ethjs-util@npm:^0.3.0": + version: 0.3.0 + resolution: "@metamask/ethjs-util@npm:0.3.0" + dependencies: + is-hex-prefixed: "npm:1.0.0" + strip-hex-prefix: "npm:1.0.0" + peerDependencies: + "@babel/runtime": ^7.0.0 + checksum: aaecae091dd16362f048cbba27629d67d10530c704facf80f430db546c8ea32fa627cd9560bd3eca1e6ce651d2b7a7df9fdc3677e0ae2814954e3c950cee70da + languageName: node + linkType: hard + +"@metamask/ethjs@npm:^0.5.0": version: 0.5.1 resolution: "@metamask/ethjs@npm:0.5.1" dependencies: @@ -4198,6 +4496,26 @@ __metadata: languageName: node linkType: hard +"@metamask/ethjs@npm:^0.6.0": + version: 0.6.0 + resolution: "@metamask/ethjs@npm:0.6.0" + dependencies: + "@metamask/ethjs-contract": "npm:^0.4.1" + "@metamask/ethjs-filter": "npm:^0.3.0" + "@metamask/ethjs-provider-http": "npm:^0.3.0" + "@metamask/ethjs-query": "npm:^0.7.1" + "@metamask/ethjs-unit": "npm:^0.3.0" + "@metamask/ethjs-util": "npm:^0.3.0" + "@metamask/number-to-bn": "npm:1.7.1" + bn.js: "npm:5.2.1" + ethjs-abi: "npm:0.2.1" + js-sha3: "npm:^0.9.2" + peerDependencies: + "@babel/runtime": ^7.0.0 + checksum: 11379cb4a1151fc8f8e939eb38c15ea6cfce042bc10a580a3fbe87b7e0234fb5cdf5b2458a7bbc8db3502032ac42054bf393154cfe967352b129fe8115719a91 + languageName: node + linkType: hard + "@metamask/forwarder@npm:^1.1.0": version: 1.1.0 resolution: "@metamask/forwarder@npm:1.1.0" @@ -4225,14 +4543,14 @@ __metadata: languageName: node linkType: hard -"@metamask/gas-fee-controller@npm:^13.0.0": - version: 13.0.0 - resolution: "@metamask/gas-fee-controller@npm:13.0.0" +"@metamask/gas-fee-controller@npm:^13.0.0, @metamask/gas-fee-controller@npm:^13.0.1": + version: 13.0.1 + resolution: "@metamask/gas-fee-controller@npm:13.0.1" dependencies: "@metamask/base-controller": "npm:^4.1.1" - "@metamask/controller-utils": "npm:^8.0.2" + "@metamask/controller-utils": "npm:^8.0.3" "@metamask/eth-query": "npm:^4.0.0" - "@metamask/ethjs-unit": "npm:^0.2.1" + "@metamask/ethjs-unit": "npm:^0.3.0" "@metamask/network-controller": "npm:^17.2.0" "@metamask/polling-controller": "npm:^5.0.0" "@metamask/utils": "npm:^8.3.0" @@ -4241,7 +4559,7 @@ __metadata: uuid: "npm:^8.3.2" peerDependencies: "@metamask/network-controller": ^17.2.0 - checksum: 8edbe4412a1bf80eb3a3c6305c35b45ee38f9262a4dd3b38c30d2e81e0f84d2f8ee261e3548fb25a049f53cf76ccf8238c4aca31ec6cd0c5b46ba9c58a705bc8 + checksum: c50617702b455f6314ea76f72a130f5bca6142586169f050c26ab1da3d2ea54fac358bc33ed7a01898a9c2927e729edf22b5a716dad84ccad0789e3f5cb199b6 languageName: node linkType: hard @@ -4457,6 +4775,16 @@ __metadata: languageName: node linkType: hard +"@metamask/number-to-bn@npm:1.7.1, @metamask/number-to-bn@npm:^1.7.1": + version: 1.7.1 + resolution: "@metamask/number-to-bn@npm:1.7.1" + dependencies: + bn.js: "npm:5.2.1" + strip-hex-prefix: "npm:1.0.0" + checksum: 0c8fbdf2c9df0e2070206b7c4ea7dfa771ec660604b408b183fff20e54d933371f8e7fc5ebdf89b3439e6e3bee97a00caf0e268ccc936052ccae2f639eac411d + languageName: node + linkType: hard + "@metamask/object-multiplex@npm:^2.0.0": version: 2.0.0 resolution: "@metamask/object-multiplex@npm:2.0.0" @@ -4527,16 +4855,35 @@ __metadata: languageName: node linkType: hard -"@metamask/phishing-controller@npm:^8.0.0, @metamask/phishing-controller@npm:^8.0.1": - version: 8.0.1 - resolution: "@metamask/phishing-controller@npm:8.0.1" +"@metamask/permission-controller@npm:^8.0.0": + version: 8.0.0 + resolution: "@metamask/permission-controller@npm:8.0.0" dependencies: - "@metamask/base-controller": "npm:^4.0.1" - "@metamask/controller-utils": "npm:^8.0.1" + "@metamask/base-controller": "npm:^4.1.1" + "@metamask/controller-utils": "npm:^8.0.2" + "@metamask/json-rpc-engine": "npm:^7.3.2" + "@metamask/rpc-errors": "npm:^6.1.0" + "@metamask/utils": "npm:^8.3.0" + "@types/deep-freeze-strict": "npm:^1.1.0" + deep-freeze-strict: "npm:^1.1.1" + immer: "npm:^9.0.6" + nanoid: "npm:^3.1.31" + peerDependencies: + "@metamask/approval-controller": ^5.1.2 + checksum: cbd418473fc90c210c5d1a50278be2adb62737ad027bd68433651e4de290d178f53e7018c60ac749227f6951c6cab8fef54073b375a5391461a4ae63a9b67706 + languageName: node + linkType: hard + +"@metamask/phishing-controller@npm:^8.0.0, @metamask/phishing-controller@npm:^8.0.1, @metamask/phishing-controller@npm:^8.0.2": + version: 8.0.2 + resolution: "@metamask/phishing-controller@npm:8.0.2" + dependencies: + "@metamask/base-controller": "npm:^4.1.1" + "@metamask/controller-utils": "npm:^8.0.2" "@types/punycode": "npm:^2.1.0" eth-phishing-detect: "npm:^1.2.0" punycode: "npm:^2.1.1" - checksum: 1167edda6c6a534d519648a2980e3ac04e57799133b496ae087f526e907e08d27d81a8fb9678b7b276f2a0acd504b6b8269dcf7f29893414ec53e62879bd12af + checksum: f00b70a7b50d2093c035c1c974ffb42d61cc998efbab35f0bc7d9ea406388cf2b8b9303aa3c14def12a0f7eefb47fb24a0938ed734525f2bc1d6bf9b24aa85c1 languageName: node linkType: hard @@ -4600,19 +4947,30 @@ __metadata: languageName: node linkType: hard -"@metamask/ppom-validator@npm:^0.24.0": - version: 0.24.0 - resolution: "@metamask/ppom-validator@npm:0.24.0" +"@metamask/post-message-stream@npm:^8.0.0": + version: 8.0.0 + resolution: "@metamask/post-message-stream@npm:8.0.0" + dependencies: + "@metamask/utils": "npm:^8.1.0" + readable-stream: "npm:3.6.2" + checksum: cd891a597c1249fbeb4f60c98b84a2d8f54e8826ab66907667bbf5dfd5b123715d71c1c11a0629b76c59c85da921ef7288c203eea86e6a50a1b8510e332e910f + languageName: node + linkType: hard + +"@metamask/ppom-validator@npm:^0.26.0": + version: 0.26.0 + resolution: "@metamask/ppom-validator@npm:0.26.0" dependencies: "@metamask/base-controller": "npm:^3.0.0" "@metamask/controller-utils": "npm:^8.0.1" "@metamask/network-controller": "npm:^17.0.0" + "@metamask/utils": "npm:^8.3.0" await-semaphore: "npm:^0.1.3" crypto-js: "npm:^4.2.0" elliptic: "npm:^6.5.4" eslint-plugin-n: "npm:^16.6.2" json-rpc-random-id: "npm:^1.0.1" - checksum: cb488ccaffd119820536450519a0fffbdcb710a082de05a26d7e8a0f782b3024a747c36c5f16b9ca84343a0b234d3d0ebfd3e493bcb858e966129a3f40d218a0 + checksum: 9857444051a62aa3e6424102d9b8b808cb708c98f499c1e6628eba1635d15fba6c38c22d25954ec734979a5fef267c4aedd8cd290b39eece5c4f5f46fda644be languageName: node linkType: hard @@ -4842,6 +5200,42 @@ __metadata: languageName: node linkType: hard +"@metamask/snaps-controllers@npm:^5.0.1": + version: 5.0.1 + resolution: "@metamask/snaps-controllers@npm:5.0.1" + dependencies: + "@metamask/approval-controller": "npm:^5.1.2" + "@metamask/base-controller": "npm:^4.1.0" + "@metamask/json-rpc-engine": "npm:^7.3.2" + "@metamask/object-multiplex": "npm:^2.0.0" + "@metamask/permission-controller": "npm:^8.0.0" + "@metamask/phishing-controller": "npm:^8.0.2" + "@metamask/post-message-stream": "npm:^8.0.0" + "@metamask/rpc-errors": "npm:^6.1.0" + "@metamask/snaps-registry": "npm:^3.0.0" + "@metamask/snaps-rpc-methods": "npm:^6.0.0" + "@metamask/snaps-sdk": "npm:^2.1.0" + "@metamask/snaps-utils": "npm:^6.1.0" + "@metamask/utils": "npm:^8.3.0" + "@xstate/fsm": "npm:^2.0.0" + browserify-zlib: "npm:^0.2.0" + concat-stream: "npm:^2.0.0" + get-npm-tarball-url: "npm:^2.0.3" + immer: "npm:^9.0.6" + json-rpc-middleware-stream: "npm:^5.0.0" + nanoid: "npm:^3.1.31" + readable-stream: "npm:^3.6.2" + readable-web-to-node-stream: "npm:^3.0.2" + tar-stream: "npm:^3.1.7" + peerDependencies: + "@metamask/snaps-execution-environments": ^4.0.1 + peerDependenciesMeta: + "@metamask/snaps-execution-environments": + optional: true + checksum: b6607a5f15081b62273282d9b462be945277db54b1e772f75df4d68d01a7a3b1d5214d9b4bb619759ac2159121bbe59bfbbeab0513f27e663984afee26060187 + languageName: node + linkType: hard + "@metamask/snaps-registry@npm:^3.0.0": version: 3.0.0 resolution: "@metamask/snaps-registry@npm:3.0.0" @@ -4886,17 +5280,33 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-sdk@npm:^1.2.0, @metamask/snaps-sdk@npm:^1.3.1, @metamask/snaps-sdk@npm:^1.3.2, @metamask/snaps-sdk@npm:^1.4.0": - version: 1.4.0 - resolution: "@metamask/snaps-sdk@npm:1.4.0" +"@metamask/snaps-rpc-methods@npm:^6.0.0": + version: 6.0.0 + resolution: "@metamask/snaps-rpc-methods@npm:6.0.0" + dependencies: + "@metamask/key-tree": "npm:^9.0.0" + "@metamask/permission-controller": "npm:^8.0.0" + "@metamask/rpc-errors": "npm:^6.1.0" + "@metamask/snaps-sdk": "npm:^2.0.0" + "@metamask/snaps-utils": "npm:^6.0.0" + "@metamask/utils": "npm:^8.3.0" + "@noble/hashes": "npm:^1.3.1" + superstruct: "npm:^1.0.3" + checksum: 4eaf8f8170d986331a45f158a5dea1051ab602d82057afe9d6464fc43f66015442ab29ecc72e1a9914af87c5627c9d215294f269c46951ecc803f295f2647d0d + languageName: node + linkType: hard + +"@metamask/snaps-sdk@npm:^2.1.0": + version: 2.1.0 + resolution: "@metamask/snaps-sdk@npm:2.1.0" dependencies: "@metamask/key-tree": "npm:^9.0.0" "@metamask/providers": "npm:^14.0.2" "@metamask/rpc-errors": "npm:^6.1.0" "@metamask/utils": "npm:^8.3.0" - is-svg: "npm:^4.4.0" + fast-xml-parser: "npm:^4.3.4" superstruct: "npm:^1.0.3" - checksum: 08c2a2de2166cf97b31122c4ce486d92c9b14a5524c1e59da28f93ae79f9d8b1ecf645fd487e9c5a619a83cd710f83f5ad195bbdcf665858696f94f3f977149c + checksum: 539e1a478c89a249c0ed56d705fd4c3614c4d45d0fd8fa9a2c0d781b18d171d60944d3106e65afc68a9f52997d5ff1a4728be697e2405e56bcf2df8bf9af0bea languageName: node linkType: hard @@ -4930,6 +5340,35 @@ __metadata: languageName: node linkType: hard +"@metamask/snaps-utils@npm:^6.0.0, @metamask/snaps-utils@npm:^6.1.0": + version: 6.1.0 + resolution: "@metamask/snaps-utils@npm:6.1.0" + dependencies: + "@babel/core": "npm:^7.23.2" + "@babel/types": "npm:^7.23.0" + "@metamask/base-controller": "npm:^4.1.0" + "@metamask/key-tree": "npm:^9.0.0" + "@metamask/permission-controller": "npm:^8.0.0" + "@metamask/rpc-errors": "npm:^6.1.0" + "@metamask/slip44": "npm:^3.1.0" + "@metamask/snaps-registry": "npm:^3.0.0" + "@metamask/snaps-sdk": "npm:^2.1.0" + "@metamask/utils": "npm:^8.3.0" + "@noble/hashes": "npm:^1.3.1" + "@scure/base": "npm:^1.1.1" + chalk: "npm:^4.1.2" + cron-parser: "npm:^4.5.0" + fast-deep-equal: "npm:^3.1.3" + fast-json-stable-stringify: "npm:^2.1.0" + rfdc: "npm:^1.3.0" + semver: "npm:^7.5.4" + ses: "npm:^1.1.0" + superstruct: "npm:^1.0.3" + validate-npm-package-name: "npm:^5.0.0" + checksum: 8e2b25abbca14571de58eb5c4aa8d1289ed7f2406bb1961214e1a04bbb29bba99c49c59f43fec227c07cca4c6506a1446c4163e4cd8f1ddf3d25b6fd4ff1b697 + languageName: node + linkType: hard + "@metamask/swappable-obj-proxy@npm:^2.1.0, @metamask/swappable-obj-proxy@npm:^2.2.0": version: 2.2.0 resolution: "@metamask/swappable-obj-proxy@npm:2.2.0" @@ -4976,35 +5415,35 @@ __metadata: languageName: node linkType: hard -"@metamask/transaction-controller@npm:^21.1.0": - version: 21.1.0 - resolution: "@metamask/transaction-controller@npm:21.1.0" +"@metamask/transaction-controller@npm:^22.0.0": + version: 22.0.0 + resolution: "@metamask/transaction-controller@npm:22.0.0" dependencies: "@ethereumjs/common": "npm:^3.2.0" "@ethereumjs/tx": "npm:^4.2.0" "@ethersproject/abi": "npm:^5.7.0" "@metamask/approval-controller": "npm:^5.1.2" "@metamask/base-controller": "npm:^4.1.1" - "@metamask/controller-utils": "npm:^8.0.2" + "@metamask/controller-utils": "npm:^8.0.3" "@metamask/eth-query": "npm:^4.0.0" - "@metamask/gas-fee-controller": "npm:^13.0.0" + "@metamask/gas-fee-controller": "npm:^13.0.1" "@metamask/metamask-eth-abis": "npm:^3.0.0" "@metamask/network-controller": "npm:^17.2.0" "@metamask/rpc-errors": "npm:^6.1.0" "@metamask/utils": "npm:^8.3.0" async-mutex: "npm:^0.2.6" - eth-method-registry: "npm:^3.0.0" + eth-method-registry: "npm:^4.0.0" ethereumjs-util: "npm:^7.0.10" fast-json-patch: "npm:^3.1.1" lodash: "npm:^4.17.21" nonce-tracker: "npm:^3.0.0" uuid: "npm:^8.3.2" peerDependencies: + "@babel/runtime": ^7.23.9 "@metamask/approval-controller": ^5.1.2 "@metamask/gas-fee-controller": ^13.0.0 "@metamask/network-controller": ^17.2.0 - babel-runtime: ^6.26.0 - checksum: 5dbe811870fb0775d017470ec4529fbf46969f072527e3ac7ce18370faafffda32056eeec02ff8b56ed35463aec8ceaeb667c64362db90529faf4b8e2c9c2631 + checksum: 58c212278bcb0abcfb9114f56f5d5167bd1b1a6873811583e327aa89008ff67a9362d1cd0a4481d503b1f81a25495c3c05cbd666502e35e48a45b54d82b740c9 languageName: node linkType: hard @@ -16044,6 +16483,86 @@ __metadata: languageName: node linkType: hard +"esbuild@npm:~0.19.10": + version: 0.19.12 + resolution: "esbuild@npm:0.19.12" + dependencies: + "@esbuild/aix-ppc64": "npm:0.19.12" + "@esbuild/android-arm": "npm:0.19.12" + "@esbuild/android-arm64": "npm:0.19.12" + "@esbuild/android-x64": "npm:0.19.12" + "@esbuild/darwin-arm64": "npm:0.19.12" + "@esbuild/darwin-x64": "npm:0.19.12" + "@esbuild/freebsd-arm64": "npm:0.19.12" + "@esbuild/freebsd-x64": "npm:0.19.12" + "@esbuild/linux-arm": "npm:0.19.12" + "@esbuild/linux-arm64": "npm:0.19.12" + "@esbuild/linux-ia32": "npm:0.19.12" + "@esbuild/linux-loong64": "npm:0.19.12" + "@esbuild/linux-mips64el": "npm:0.19.12" + "@esbuild/linux-ppc64": "npm:0.19.12" + "@esbuild/linux-riscv64": "npm:0.19.12" + "@esbuild/linux-s390x": "npm:0.19.12" + "@esbuild/linux-x64": "npm:0.19.12" + "@esbuild/netbsd-x64": "npm:0.19.12" + "@esbuild/openbsd-x64": "npm:0.19.12" + "@esbuild/sunos-x64": "npm:0.19.12" + "@esbuild/win32-arm64": "npm:0.19.12" + "@esbuild/win32-ia32": "npm:0.19.12" + "@esbuild/win32-x64": "npm:0.19.12" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 861fa8eb2428e8d6521a4b7c7930139e3f45e8d51a86985cc29408172a41f6b18df7b3401e7e5e2d528cdf83742da601ddfdc77043ddc4f1c715a8ddb2d8a255 + languageName: node + linkType: hard + "escalade@npm:^3.1.1": version: 3.1.1 resolution: "escalade@npm:3.1.1" @@ -16746,6 +17265,18 @@ __metadata: languageName: node linkType: hard +"eth-method-registry@npm:^4.0.0": + version: 4.0.0 + resolution: "eth-method-registry@npm:4.0.0" + dependencies: + "@metamask/ethjs-contract": "npm:^0.4.1" + "@metamask/ethjs-query": "npm:^0.7.1" + peerDependencies: + "@babel/runtime": ^7.0.0 + checksum: 959e833327960f40fab8693997e071def75103a694e931ab11313e642b6958d4f2878000d91ddfd6ba3c158bdc3f381c63eae0dd26738790e2aca5004ceb0387 + languageName: node + linkType: hard + "eth-phishing-detect@npm:^1.2.0": version: 1.2.0 resolution: "eth-phishing-detect@npm:1.2.0" @@ -17456,14 +17987,14 @@ __metadata: languageName: node linkType: hard -"fast-xml-parser@npm:^4.1.3": - version: 4.2.4 - resolution: "fast-xml-parser@npm:4.2.4" +"fast-xml-parser@npm:^4.1.3, fast-xml-parser@npm:^4.3.4": + version: 4.3.4 + resolution: "fast-xml-parser@npm:4.3.4" dependencies: strnum: "npm:^1.0.5" bin: fxparser: src/cli/cli.js - checksum: 157f64a142d37f2c937d5308d62668119e40218dab41a07d1a9563c3f92663c81fd08db0efc9fe484e0bc4dfea59827f319adc510426ff9b97c83a779d511b6f + checksum: ef859101980cdd02b111fce09e25949a80e373654a6c424091355930f0d364abec144d8bb722d250a0c070416566518e621e198204a6b976db68f20c16d9300b languageName: node linkType: hard @@ -18218,7 +18749,7 @@ __metadata: languageName: node linkType: hard -"fsevents@npm:2.3.2, fsevents@npm:^2.3.2, fsevents@npm:~2.3.2": +"fsevents@npm:2.3.2": version: 2.3.2 resolution: "fsevents@npm:2.3.2" dependencies: @@ -18239,7 +18770,17 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin": +"fsevents@npm:^2.3.2, fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: "npm:latest" + checksum: 4c1ade961ded57cdbfbb5cac5106ec17bc8bccd62e16343c569a0ceeca83b9dfef87550b4dc5cbb89642da412b20c5071f304c8c464b80415446e8e155a038c0 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin": version: 2.3.2 resolution: "fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin::version=2.3.2&hash=df0bf1" dependencies: @@ -18258,6 +18799,15 @@ __metadata: languageName: node linkType: hard +"fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + "function-bind@npm:^1.1.1, function-bind@npm:^1.1.2": version: 1.1.2 resolution: "function-bind@npm:1.1.2" @@ -18522,7 +19072,7 @@ __metadata: languageName: node linkType: hard -"get-tsconfig@npm:^4.7.0": +"get-tsconfig@npm:^4.7.0, get-tsconfig@npm:^4.7.2": version: 4.7.2 resolution: "get-tsconfig@npm:4.7.2" dependencies: @@ -24075,9 +24625,9 @@ __metadata: "@lavamoat/lavapack": "npm:^6.1.0" "@lavamoat/snow": "npm:^2.0.1" "@material-ui/core": "npm:^4.11.0" - "@metamask-institutional/custody-controller": "npm:^0.2.20" + "@metamask-institutional/custody-controller": "npm:^0.2.22" "@metamask-institutional/custody-keyring": "npm:^1.0.10" - "@metamask-institutional/extension": "npm:^0.3.13" + "@metamask-institutional/extension": "npm:^0.3.16" "@metamask-institutional/institutional-features": "npm:^1.2.11" "@metamask-institutional/portfolio-dashboard": "npm:^1.4.0" "@metamask-institutional/rpc-allowlist": "npm:^1.0.0" @@ -24087,14 +24637,14 @@ __metadata: "@metamask/address-book-controller": "npm:^3.0.0" "@metamask/announcement-controller": "npm:^5.0.1" "@metamask/approval-controller": "npm:^5.1.2" - "@metamask/assets-controllers": "npm:^24.0.0" + "@metamask/assets-controllers": "patch:@metamask/assets-controllers@npm%3A24.0.0#~/.yarn/patches/@metamask-assets-controllers-npm-24.0.0-dfef136464.patch" "@metamask/auto-changelog": "npm:^2.1.0" "@metamask/base-controller": "npm:^4.1.0" "@metamask/browser-passworder": "npm:^4.3.0" "@metamask/build-utils": "npm:^1.0.0" "@metamask/contract-metadata": "npm:^2.3.1" "@metamask/controller-utils": "npm:^8.0.1" - "@metamask/design-tokens": "npm:^2.0.3" + "@metamask/design-tokens": "npm:^2.1.1" "@metamask/desktop": "npm:^0.3.0" "@metamask/eslint-config": "npm:^9.0.0" "@metamask/eslint-config-jest": "npm:^9.0.0" @@ -24106,12 +24656,12 @@ __metadata: "@metamask/eth-ledger-bridge-keyring": "npm:^2.0.1" "@metamask/eth-query": "npm:^4.0.0" "@metamask/eth-snap-keyring": "npm:^2.1.2" - "@metamask/eth-token-tracker": "npm:^7.0.1" + "@metamask/eth-token-tracker": "npm:^7.0.2" "@metamask/eth-trezor-keyring": "npm:^3.0.0" "@metamask/etherscan-link": "npm:^3.0.0" - "@metamask/ethjs": "npm:^0.5.1" - "@metamask/ethjs-contract": "npm:^0.3.4" - "@metamask/ethjs-query": "npm:^0.5.3" + "@metamask/ethjs": "npm:^0.6.0" + "@metamask/ethjs-contract": "npm:^0.4.1" + "@metamask/ethjs-query": "npm:^0.7.1" "@metamask/forwarder": "npm:^1.1.0" "@metamask/gas-fee-controller": "npm:^13.0.0" "@metamask/jazzicon": "npm:^2.0.0" @@ -24125,12 +24675,12 @@ __metadata: "@metamask/network-controller": "npm:^17.2.0" "@metamask/notification-controller": "npm:^3.0.0" "@metamask/obs-store": "npm:^8.1.0" - "@metamask/permission-controller": "npm:^7.1.0" + "@metamask/permission-controller": "npm:^8.0.0" "@metamask/phishing-controller": "npm:^8.0.0" "@metamask/phishing-warning": "npm:^3.0.3" "@metamask/polling-controller": "npm:^4.0.0" - "@metamask/post-message-stream": "npm:^7.0.0" - "@metamask/ppom-validator": "npm:^0.24.0" + "@metamask/post-message-stream": "npm:^8.0.0" + "@metamask/ppom-validator": "npm:^0.26.0" "@metamask/providers": "npm:^14.0.2" "@metamask/queued-request-controller": "npm:^0.3.0" "@metamask/rate-limit-controller": "npm:^3.0.0" @@ -24139,12 +24689,12 @@ __metadata: "@metamask/selected-network-controller": "npm:^6.0.0" "@metamask/signature-controller": "npm:^12.0.0" "@metamask/smart-transactions-controller": "npm:^6.2.2" - "@metamask/snaps-controllers": "npm:^4.1.0" - "@metamask/snaps-rpc-methods": "npm:^5.0.0" - "@metamask/snaps-sdk": "npm:^1.4.0" - "@metamask/snaps-utils": "npm:^5.2.0" + "@metamask/snaps-controllers": "npm:^5.0.1" + "@metamask/snaps-rpc-methods": "npm:^6.0.0" + "@metamask/snaps-sdk": "npm:^2.1.0" + "@metamask/snaps-utils": "npm:^6.1.0" "@metamask/test-dapp": "npm:^7.3.1" - "@metamask/transaction-controller": "npm:^21.1.0" + "@metamask/transaction-controller": "npm:^22.0.0" "@metamask/user-operation-controller": "npm:^1.0.0" "@metamask/utils": "npm:^8.2.1" "@ngraveio/bc-ur": "npm:^1.1.6" @@ -24265,7 +24815,7 @@ __metadata: eth-ens-namehash: "npm:^2.0.8" eth-json-rpc-filters: "npm:^6.0.0" eth-lattice-keyring: "npm:^0.12.4" - eth-method-registry: "npm:^3.0.0" + eth-method-registry: "npm:^4.0.0" eth-rpc-errors: "npm:^4.0.2" eth-sig-util: "npm:^3.0.0" ethereum-ens-network-map: "npm:^1.0.2" @@ -24392,6 +24942,7 @@ __metadata: terser: "npm:^5.7.0" through2: "npm:^4.0.2" ts-node: "npm:^10.9.2" + tsx: "npm:^4.7.1" ttest: "npm:^2.1.1" typescript: "npm:~5.3.3" unicode-confusables: "npm:^0.1.1" @@ -32233,17 +32784,6 @@ __metadata: languageName: node linkType: hard -"tar-stream@npm:3.1.6": - version: 3.1.6 - resolution: "tar-stream@npm:3.1.6" - dependencies: - b4a: "npm:^1.6.4" - fast-fifo: "npm:^1.2.0" - streamx: "npm:^2.15.0" - checksum: 2c32e0d57de778ae415357bfb126a512a384e9bfb8e234920455ad65282181a3765515bbd80392ab8e7e630158376ec7de46b18ab86a33d256a7dcc43b0648b7 - languageName: node - linkType: hard - "tar-stream@npm:^2.1.4": version: 2.2.0 resolution: "tar-stream@npm:2.2.0" @@ -32257,14 +32797,14 @@ __metadata: languageName: node linkType: hard -"tar-stream@patch:tar-stream@npm%3A3.1.6#~/.yarn/patches/tar-stream-npm-3.1.6-ce3ac17e49.patch": - version: 3.1.6 - resolution: "tar-stream@patch:tar-stream@npm%3A3.1.6#~/.yarn/patches/tar-stream-npm-3.1.6-ce3ac17e49.patch::version=3.1.6&hash=f45d58" +"tar-stream@npm:^3.1.6, tar-stream@npm:^3.1.7": + version: 3.1.7 + resolution: "tar-stream@npm:3.1.7" dependencies: b4a: "npm:^1.6.4" fast-fifo: "npm:^1.2.0" streamx: "npm:^2.15.0" - checksum: 517d101950f47c96c581f2bf4886dcb48ee55a1dc47dd8aec6d3512450d6470fc9fbe0704e22379a289caee5034ea3e8586596b2c15cd5ee135588481ac415b1 + checksum: b21a82705a72792544697c410451a4846af1f744176feb0ff11a7c3dd0896961552e3def5e1c9a6bbee4f0ae298b8252a1f4c9381e9f991553b9e4847976f05c languageName: node linkType: hard @@ -32881,6 +33421,22 @@ __metadata: languageName: node linkType: hard +"tsx@npm:^4.7.1": + version: 4.7.1 + resolution: "tsx@npm:4.7.1" + dependencies: + esbuild: "npm:~0.19.10" + fsevents: "npm:~2.3.3" + get-tsconfig: "npm:^4.7.2" + dependenciesMeta: + fsevents: + optional: true + bin: + tsx: dist/cli.mjs + checksum: 3a462b595f31ae58b31f9c6e8c450577dc87660b1225012bd972b6b58d7d2f6c4034728763ebc53bb731acff68de8b0fa50586e4c1ec4c086226f1788ccf9b7d + languageName: node + linkType: hard + "ttest@npm:^2.1.1": version: 2.1.1 resolution: "ttest@npm:2.1.1"