diff --git a/.circleci/config.yml b/.circleci/config.yml index 5c7c21d65b31..5ab0af8fb0cc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -13,6 +13,12 @@ executors: resource_class: medium environment: NODE_OPTIONS: --max_old_space_size=3072 + node-linux-medium: + machine: + image: ubuntu-2404:2024.05.1 + resource_class: medium #// linux medium: 2 CPUs, 7.5 GB RAM, 10 credits/min + environment: + NODE_OPTIONS: --max_old_space_size=6144 node-browsers-medium-plus: docker: - image: cimg/node:20.11-browsers @@ -25,7 +31,7 @@ executors: resource_class: small playwright: docker: - - image: mcr.microsoft.com/playwright:v1.42.1-focal + - image: mcr.microsoft.com/playwright:v1.44.1-focal resource_class: medium orbs: @@ -39,6 +45,14 @@ rc_branch_only: &rc_branch_only only: - /^Version-v(\d+)[.](\d+)[.](\d+)/ +develop_master_rc_only: &develop_master_rc_only + filters: + branches: + only: + - develop + - master + - /^Version-v(\d+)[.](\d+)[.](\d+)/ + aliases: # Shallow Git Clone - &shallow-git-clone @@ -137,12 +151,14 @@ workflows: requires: - prep-deps - prep-build-confirmation-redesign-test-mv2: + <<: *develop_master_rc_only requires: - prep-deps - prep-build-test-flask: requires: - prep-deps - prep-build-test-flask-mv2: + <<: *develop_master_rc_only requires: - prep-deps - prep-build-test-mmi: @@ -178,11 +194,15 @@ workflows: requires: - prep-build-test-mv2 - test-e2e-firefox-confirmation-redesign: + <<: *develop_master_rc_only requires: - prep-build-confirmation-redesign-test-mv2 - test-e2e-chrome-rpc: requires: - prep-build-test + - test-api-specs: + requires: + - prep-build-test - test-e2e-chrome-multiple-providers: requires: - prep-build-test @@ -190,6 +210,7 @@ workflows: requires: - prep-build-test-flask - test-e2e-firefox-flask: + <<: *develop_master_rc_only requires: - prep-build-test-flask-mv2 - test-e2e-chrome-mmi: @@ -209,20 +230,16 @@ workflows: - /^Version-v(\d+)[.](\d+)[.](\d+)/ requires: - prep-build - - test-unit-mocha: - requires: - - prep-deps - test-unit-jest-main: requires: - prep-deps - test-unit-jest-development: requires: - prep-deps - - upload-and-validate-coverage: + - upload-coverage: requires: - test-unit-jest-main - test-unit-jest-development - - test-unit-mocha - test-unit-global: requires: - prep-deps @@ -269,8 +286,7 @@ workflows: - test-unit-jest-main - test-unit-jest-development - test-unit-global - - test-unit-mocha - - upload-and-validate-coverage + - upload-coverage - validate-source-maps - validate-source-maps-beta - validate-source-maps-flask @@ -502,10 +518,10 @@ jobs: command: .circleci/scripts/check-working-tree.sh prep-build: - executor: node-browsers-medium-plus + executor: node-linux-medium steps: - run: *shallow-git-clone - - run: sudo corepack enable + - run: corepack enable - attach_workspace: at: . - when: @@ -539,10 +555,10 @@ jobs: - builds prep-build-mv2: - executor: node-browsers-medium-plus + executor: node-linux-medium steps: - run: *shallow-git-clone - - run: sudo corepack enable + - run: corepack enable - attach_workspace: at: . - when: @@ -582,10 +598,10 @@ jobs: - builds-mv2 prep-build-mmi: - executor: node-browsers-medium-plus + executor: node-linux-medium steps: - run: *shallow-git-clone - - run: sudo corepack enable + - run: corepack enable - attach_workspace: at: . - when: @@ -626,10 +642,10 @@ jobs: destination: builds-mmi prep-build-flask: - executor: node-browsers-medium-plus + executor: node-linux-medium steps: - run: *shallow-git-clone - - run: sudo corepack enable + - run: corepack enable - attach_workspace: at: . - when: @@ -667,10 +683,10 @@ jobs: - builds-flask prep-build-flask-mv2: - executor: node-browsers-medium-plus + executor: node-linux-medium steps: - run: *shallow-git-clone - - run: sudo corepack enable + - run: corepack enable - attach_workspace: at: . - when: @@ -708,10 +724,10 @@ jobs: - builds-flask-mv2 prep-build-test-flask: - executor: node-browsers-medium-plus + executor: node-linux-medium steps: - run: *shallow-git-clone - - run: sudo corepack enable + - run: corepack enable - attach_workspace: at: . - run: @@ -730,10 +746,10 @@ jobs: - builds-test-flask prep-build-test-flask-mv2: - executor: node-browsers-medium-plus + executor: node-linux-medium steps: - run: *shallow-git-clone - - run: sudo corepack enable + - run: corepack enable - attach_workspace: at: . - run: @@ -752,10 +768,10 @@ jobs: - builds-test-flask-mv2 prep-build-test-mmi: - executor: node-browsers-medium-plus + executor: node-linux-medium steps: - run: *shallow-git-clone - - run: sudo corepack enable + - run: corepack enable - attach_workspace: at: . - run: @@ -774,13 +790,13 @@ jobs: - builds-test-mmi prep-build-test-mmi-playwright: - executor: node-browsers-medium-plus + executor: node-linux-medium steps: + - run: *shallow-git-clone + - run: corepack enable - attach_workspace: at: . - run: *check-mmi-optional - - run: *shallow-git-clone - - run: sudo corepack enable - run: name: Build MMI extension for Playwright e2e command: | @@ -803,10 +819,10 @@ jobs: destination: builds-test-mmi-playwright prep-build-test: - executor: node-browsers-medium-plus + executor: node-linux-medium steps: - run: *shallow-git-clone - - run: sudo corepack enable + - run: corepack enable - attach_workspace: at: . - run: @@ -827,10 +843,10 @@ jobs: - builds-test prep-build-test-mv2: - executor: node-browsers-medium-plus + executor: node-linux-medium steps: - run: *shallow-git-clone - - run: sudo corepack enable + - run: corepack enable - attach_workspace: at: . - run: @@ -851,15 +867,15 @@ jobs: - builds-test-mv2 prep-build-confirmation-redesign-test: - executor: node-browsers-medium-plus + executor: node-linux-medium steps: - run: *shallow-git-clone - - run: sudo corepack enable + - run: corepack enable - attach_workspace: at: . - run: name: Build extension for testing - command: ENABLE_CONFIRMATION_REDESIGN=true yarn build:test + command: ENABLE_CONFIRMATION_REDESIGN="true" yarn build:test - run: name: Move test build to 'dist-test' to avoid conflict with production build command: mv ./dist ./dist-test-confirmations @@ -873,15 +889,15 @@ jobs: - builds-test-confirmations prep-build-confirmation-redesign-test-mv2: - executor: node-browsers-medium-plus + executor: node-linux-medium steps: - run: *shallow-git-clone - - run: sudo corepack enable + - run: corepack enable - attach_workspace: at: . - run: name: Build extension for testing - command: ENABLE_CONFIRMATION_REDESIGN=true yarn build:test:mv2 + command: ENABLE_CONFIRMATION_REDESIGN="true" yarn build:test:mv2 - run: name: Move test build to 'dist-test-confirmations-mv2' to avoid conflict with production build command: mv ./dist ./dist-test-confirmations-mv2 @@ -895,10 +911,10 @@ jobs: - builds-test-confirmations-mv2 prep-build-storybook: - executor: node-browsers-medium-plus + executor: node-linux-medium steps: - run: *shallow-git-clone - - run: sudo corepack enable + - run: corepack enable - attach_workspace: at: . - run: @@ -1035,6 +1051,38 @@ jobs: name: depcheck command: yarn depcheck + test-api-specs: + executor: node-browsers-medium-plus + steps: + - run: *shallow-git-clone + - run: sudo corepack enable + - attach_workspace: + at: . + - run: + name: Move test build to dist + command: mv ./dist-test ./dist + - run: + name: Move test zips to builds + command: mv ./builds-test ./builds + - gh/install + - run: + name: test:api-specs + command: | + timeout 20m yarn test:api-specs --retries 2 + no_output_timeout: 5m + - run: + name: Comment on PR + command: | + if [ -f html-report/index.html ]; then + gh pr comment "${CIRCLE_PR_NUMBER}" --body ":x: API Spec Test Failed. View the report [here](https://output.circle-artifacts.com/output/job/${CIRCLE_WORKFLOW_JOB_ID}/artifacts/${CIRCLE_NODE_INDEX}/html-report/index.html)." + else + echo "API Spec Report not found!" + fi + when: on_fail + - store_artifacts: + path: html-report + destination: html-report + test-e2e-chrome: executor: node-browsers-medium-plus parallelism: 20 @@ -1086,7 +1134,7 @@ jobs: fi no_output_timeout: 5m environment: - ENABLE_CONFIRMATION_REDESIGN: true + ENABLE_CONFIRMATION_REDESIGN: "true" - store_artifacts: path: test-artifacts destination: test-artifacts @@ -1191,6 +1239,11 @@ jobs: yarn test:e2e:single test/e2e/vault-decryption-chrome.spec.js --browser chrome --retries 1 fi no_output_timeout: 5m + - store_artifacts: + path: test-artifacts + destination: test-artifacts + - store_test_results: + path: test/test-results/e2e test-e2e-firefox-flask: executor: node-browsers-medium-plus @@ -1281,11 +1334,11 @@ jobs: executor: playwright parallelism: 2 steps: + - run: *shallow-git-clone + - run: corepack enable - attach_workspace: at: . - run: *check-mmi-optional - - run: *shallow-git-clone - - run: corepack enable - run: name: Move test build to dist command: mv ./dist-test-mmi-playwright ./dist @@ -1372,7 +1425,7 @@ jobs: fi no_output_timeout: 5m environment: - ENABLE_CONFIRMATION_REDESIGN: true + ENABLE_CONFIRMATION_REDESIGN: "true" - store_artifacts: path: test-artifacts destination: test-artifacts @@ -1589,22 +1642,6 @@ jobs: git config user.email metamaskbot@users.noreply.github.com yarn ts-migration:dashboard:deploy - test-unit-mocha: - executor: node-browsers-small - steps: - - run: *shallow-git-clone - - run: sudo corepack enable - - attach_workspace: - at: . - - run: - name: test:coverage:mocha - command: yarn test:coverage:mocha - - persist_to_workspace: - root: . - paths: - - .nyc_output - - coverage - test-unit-jest-development: executor: node-browsers-small steps: @@ -1640,7 +1677,7 @@ jobs: - store_test_results: path: test/test-results/junit.xml - upload-and-validate-coverage: + upload-coverage: executor: node-browsers-small steps: - run: *shallow-git-clone @@ -1648,9 +1685,6 @@ jobs: - attach_workspace: at: . - codecov/upload - - run: - name: test:coverage:validate - command: yarn test:coverage:validate - persist_to_workspace: root: . paths: diff --git a/.depcheckrc.yml b/.depcheckrc.yml index f2f9b37fd563..e4169c5436f0 100644 --- a/.depcheckrc.yml +++ b/.depcheckrc.yml @@ -39,8 +39,6 @@ ignores: - 'wait-on' - 'tsx' # used in .devcontainer - 'prettier-eslint' # used by the Prettier ESLint VSCode extension - # development tool - - 'nyc' # storybook - '@storybook/cli' - '@storybook/core' diff --git a/.eslintrc.js b/.eslintrc.js index a2207e6ba560..9f7fed5928ed 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -46,9 +46,7 @@ module.exports = { 'development/**/*.js', 'test/e2e/**/*.js', 'test/helpers/*.js', - 'test/lib/wait-until-called.js', 'test/run-unit-tests.js', - 'test/merge-coverage.js', ], extends: [ path.resolve(__dirname, '.eslintrc.base.js'), @@ -94,8 +92,6 @@ module.exports = { 'test/stub/**/*.js', 'test/unit-global/**/*.js', ], - // TODO: Convert these files to modern JS - excludedFiles: ['test/lib/wait-until-called.js'], extends: [ path.resolve(__dirname, '.eslintrc.base.js'), path.resolve(__dirname, '.eslintrc.node.js'), @@ -261,26 +257,7 @@ module.exports = { * Mocha library. */ { - files: [ - '**/*.test.js', - 'test/lib/wait-until-called.js', - 'test/e2e/**/*.spec.js', - ], - excludedFiles: [ - 'app/scripts/controllers/app-state.test.js', - 'app/scripts/controllers/mmi-controller.test.js', - 'app/scripts/controllers/permissions/**/*.test.js', - 'app/scripts/controllers/preferences.test.js', - 'app/scripts/lib/**/*.test.js', - 'app/scripts/metamask-controller.test.js', - 'app/scripts/migrations/*.test.js', - 'app/scripts/platforms/*.test.js', - 'development/**/*.test.js', - 'shared/**/*.test.js', - 'ui/**/*.test.js', - 'ui/__mocks__/*.js', - 'test/e2e/helpers.test.js', - ], + files: ['test/e2e/**/*.spec.js', 'test/unit-global/*.test.js'], extends: ['@metamask/eslint-config-mocha'], rules: { // In Mocha tests, it is common to use `this` to store values or do @@ -293,13 +270,19 @@ module.exports = { * Jest tests * * These are files that make use of globals and syntax introduced by the - * Jest library. The files in this section should match the Mocha excludedFiles section. + * Jest library. + * TODO: This list of files is incomplete, and should be replaced with globs that match the + * Jest config. */ { files: [ '**/__snapshots__/*.snap', 'app/scripts/controllers/app-state.test.js', 'app/scripts/controllers/mmi-controller.test.ts', + 'app/scripts/metamask-controller.actions.test.js', + 'app/scripts/detect-multiple-instances.test.js', + 'app/scripts/controllers/swaps.test.js', + 'app/scripts/controllers/metametrics.test.js', 'app/scripts/controllers/permissions/**/*.test.js', 'app/scripts/controllers/preferences.test.js', 'app/scripts/lib/**/*.test.js', @@ -383,7 +366,6 @@ module.exports = { 'test/e2e/benchmark.js', 'test/helpers/setup-helper.js', 'test/run-unit-tests.js', - 'test/merge-coverage.js', ], rules: { 'node/no-process-exit': 'off', diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 0eaf7de40fd9..133d2539dc64 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -4,5 +4,5 @@ contact_links: url: https://community.metamask.io/c/feature-requests-ideas/ about: Request new features and vote on the ones that are important to you - name: Get support or ask a question - url: https://metamask.zendesk.com/hc/en-us + url: https://support.metamask.io/ about: Use the MetaMask support system to get help and ask questions diff --git a/.github/scripts/add-team-label-to-pr.ts b/.github/scripts/add-team-label-to-pr.ts new file mode 100644 index 000000000000..fbbbe30cac37 --- /dev/null +++ b/.github/scripts/add-team-label-to-pr.ts @@ -0,0 +1,89 @@ +import * as core from '@actions/core'; +import { context, getOctokit } from '@actions/github'; +import { GitHub } from '@actions/github/lib/utils'; + +import { retrieveLabel } from './shared/label'; +import { Labelable, addLabelByIdToLabelable } from './shared/labelable'; +import { retrievePullRequest } from './shared/pull-request'; + +main().catch((error: Error): void => { + console.error(error); + process.exit(1); +}); + +async function main(): Promise { + // "GITHUB_TOKEN" is an automatically generated, repository-specific access token provided by GitHub Actions. + // We can't use "GITHUB_TOKEN" here, as its permissions are scoped to the repository where the action is running. + // "GITHUB_TOKEN" does not have access to other repositories, even when they belong to the same organization. + // As we want to get files which are not necessarily located in the same repository, + // we need to create our own "RELEASE_LABEL_TOKEN" with "repo" permissions. + // Such a token allows to access other repositories of the MetaMask organisation. + const personalAccessToken = process.env.RELEASE_LABEL_TOKEN; + if (!personalAccessToken) { + core.setFailed('RELEASE_LABEL_TOKEN not found'); + process.exit(1); + } + + // Initialise octokit, required to call Github GraphQL API + const octokit: InstanceType = getOctokit(personalAccessToken, { + previews: ['bane'], // The "bane" preview is required for adding, updating, creating and deleting labels. + }); + + // Retrieve pull request info from context + const pullRequestRepoOwner = context.repo.owner; + const pullRequestRepoName = context.repo.repo; + const pullRequestNumber = context.payload.pull_request?.number; + if (!pullRequestNumber) { + core.setFailed('Pull request number not found'); + process.exit(1); + } + + // Retrieve pull request + const pullRequest: Labelable = await retrievePullRequest( + octokit, + pullRequestRepoOwner, + pullRequestRepoName, + pullRequestNumber, + ); + + // Get the team label id based on the author of the pull request + const teamLabelId = await getTeamLabelIdByAuthor( + octokit, + pullRequestRepoOwner, + pullRequestRepoName, + pullRequest.author, + ); + + // Add the team label by id to the pull request + await addLabelByIdToLabelable(octokit, pullRequest, teamLabelId); +} + +// This helper function gets the team label id based on the author of the pull request +const getTeamLabelIdByAuthor = async ( + octokit: InstanceType, + repoOwner: string, + repoName: string, + author: string, +): Promise => { + // Retrieve the teams.json file from the repository + const { data } = (await octokit.request( + 'GET /repos/{owner}/{repo}/contents/{path}', + { owner: repoOwner, repo: 'MetaMask-planning', path: 'teams.json' }, + )) as { data: { content: string } }; + + // Parse the teams.json file content to json from base64 + const teamMembers: Record = JSON.parse(atob(data.content)); + + // Get the label name based on the author + const labelName = teamMembers[author]; + + if (!labelName) { + core.setFailed(`Team label not found for author: ${author}`); + process.exit(1); + } + + // Retrieve the label id based on the label name + const labelId = await retrieveLabel(octokit, repoOwner, repoName, labelName); + + return labelId; +}; diff --git a/.github/scripts/shared/label.ts b/.github/scripts/shared/label.ts index 2a0632a263de..d218dcf42570 100644 --- a/.github/scripts/shared/label.ts +++ b/.github/scripts/shared/label.ts @@ -93,7 +93,7 @@ async function createLabel( } // This function retrieves the label on a specific repo -async function retrieveLabel( +export async function retrieveLabel( octokit: InstanceType, repoOwner: string, repoName: string, diff --git a/.github/scripts/shared/labelable.ts b/.github/scripts/shared/labelable.ts index 9726297ea714..28d2307ecc11 100644 --- a/.github/scripts/shared/labelable.ts +++ b/.github/scripts/shared/labelable.ts @@ -27,14 +27,14 @@ export interface Labelable { export function findLabel( labelable: Labelable, labelToFind: Label, -): { - id: string; - name: string; -} | undefined { +): + | { + id: string; + name: string; + } + | undefined { // Check if label is present on labelable - return labelable.labels.find( - (label) => label.name === labelToFind.name, - ); + return labelable.labels.find((label) => label.name === labelToFind.name); } // This function adds label to a labelable object (i.e. a pull request or an issue) @@ -51,6 +51,15 @@ export async function addLabelToLabelable( label, ); + await addLabelByIdToLabelable(octokit, labelable, labelId); +} + +// This function adds label by id to a labelable object (i.e. a pull request or an issue) +export async function addLabelByIdToLabelable( + octokit: InstanceType, + labelable: Labelable, + labelId: string, +): Promise { const addLabelsToLabelableMutation = ` mutation AddLabelsToLabelable($labelableId: ID!, $labelIds: [ID!]!) { addLabelsToLabelable(input: {labelableId: $labelableId, labelIds: $labelIds}) { diff --git a/.github/workflows/add-team-label.yml b/.github/workflows/add-team-label.yml new file mode 100644 index 000000000000..eba9e48c1f15 --- /dev/null +++ b/.github/workflows/add-team-label.yml @@ -0,0 +1,37 @@ +name: Add team label to PR when it is opened + +on: + pull_request: + types: + - opened + +jobs: + add-team-label: + runs-on: ubuntu-latest + steps: + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 'lts/*' + + - run: corepack enable + + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 # This is needed to checkout all branches + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: yarn + + - name: Install dependencies + run: yarn --immutable + + - name: Add team label to PR + id: add-team-label-to-pr + env: + RELEASE_LABEL_TOKEN: ${{ secrets.RELEASE_LABEL_TOKEN }} + run: yarn run add-team-label-to-pr diff --git a/.gitignore b/.gitignore index a4291d3bbb9e..4f2d481b8807 100644 --- a/.gitignore +++ b/.gitignore @@ -55,13 +55,14 @@ test-results/ # This file is used to authenticate with the GitHub Package registry, to # enable the use of @metamask preview builds. .npmrc -#yarn + +# Yarn .yarn/* !.yarn/patches !.yarn/plugins !.yarn/sdks !.yarn/versions -**/.yarn/* +development/generate-attributions/.yarn/* # MMI Playwright public/playwright @@ -74,3 +75,7 @@ lavamoat/**/policy-debug.json # Attributions licenseInfos.json + +# API Spec tests +html-report/ + diff --git a/.mocharc.js b/.mocharc.js index 5febd260a6cd..ff6200513b96 100644 --- a/.mocharc.js +++ b/.mocharc.js @@ -1,19 +1,6 @@ module.exports = { // TODO: Remove the `exit` setting, it can hide broken tests. exit: true, - ignore: [ - './app/scripts/lib/**/*.test.js', - './app/scripts/migrations/*.test.js', - './app/scripts/platforms/*.test.js', - './app/scripts/controllers/app-state.test.js', - './app/scripts/controllers/permissions/**/*.test.js', - './app/scripts/controllers/mmi-controller.test.ts', - './app/scripts/controllers/preferences.test.js', - './app/scripts/constants/error-utils.test.js', - './app/scripts/metamask-controller.test.js', - './development/fitness-functions/**/*.test.ts', - './test/e2e/helpers.test.js', - ], recursive: true, require: ['test/env.js', 'test/setup.js'], }; diff --git a/.nvmrc b/.nvmrc index 9a2a0e219c9b..94cb37d604f2 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v20 +v20.14 diff --git a/.storybook/images/icons/info.svg b/.storybook/images/icons/info.svg new file mode 100644 index 000000000000..929561d16a83 --- /dev/null +++ b/.storybook/images/icons/info.svg @@ -0,0 +1,3 @@ + + + diff --git a/.storybook/images/icons/question.svg b/.storybook/images/icons/question.svg new file mode 100644 index 000000000000..a8ba55cd769d --- /dev/null +++ b/.storybook/images/icons/question.svg @@ -0,0 +1,3 @@ + + + diff --git a/.storybook/main.js b/.storybook/main.js index 2a48b3b88654..3c67bb2b5ce1 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -26,6 +26,10 @@ module.exports = { '@storybook/addon-designs', ], staticDirs: ['../app', './images'], + env: (config) => ({ + ...config, + ENABLE_CONFIRMATION_REDESIGN: true, + }), // Uses babel.config.js settings and prevents "Missing class properties transform" error babel: async (options) => ({ overrides: options.overrides, diff --git a/.yarn/patches/@metamask-assets-controllers-patch-a3b39b55a6.patch b/.yarn/patches/@metamask-assets-controllers-patch-a3b39b55a6.patch new file mode 100644 index 000000000000..a0e5a8b3c7e3 --- /dev/null +++ b/.yarn/patches/@metamask-assets-controllers-patch-a3b39b55a6.patch @@ -0,0 +1,251 @@ +diff --git a/dist/chunk-FMZML3V5.js b/dist/chunk-FMZML3V5.js +index ee6155cd938366918de155e8867c7d359b8ea826..b4dfe838c463a561b0e91532bcb674806fdc52bd 100644 +--- a/dist/chunk-FMZML3V5.js ++++ b/dist/chunk-FMZML3V5.js +@@ -5,6 +5,7 @@ + + + var _controllerutils = require('@metamask/controller-utils'); ++var _utils = require('@metamask/utils'); + var _pollingcontroller = require('@metamask/polling-controller'); + var DEFAULT_INTERVAL = 18e4; + var BlockaidResultType = /* @__PURE__ */ ((BlockaidResultType2) => { +@@ -14,6 +15,8 @@ var BlockaidResultType = /* @__PURE__ */ ((BlockaidResultType2) => { + BlockaidResultType2["Malicious"] = "Malicious"; + return BlockaidResultType2; + })(BlockaidResultType || {}); ++const supportedNftDetectionNetworks= [_controllerutils.ChainId.mainnet]; ++var inProcessNftFetchingUpdates; + var NftDetectionController = class extends _pollingcontroller.StaticIntervalPollingControllerV1 { + /** + * Creates an NftDetectionController instance. +@@ -50,6 +53,7 @@ var NftDetectionController = class extends _pollingcontroller.StaticIntervalPoll + * Name of this controller used during composition + */ + this.name = "NftDetectionController"; ++ this.inProcessNftFetchingUpdates= {}; + /** + * Checks whether network is mainnet or not. + * +@@ -72,11 +76,6 @@ var NftDetectionController = class extends _pollingcontroller.StaticIntervalPoll + const { selectedAddress: previouslySelectedAddress, disabled } = this.config; + if (selectedAddress !== previouslySelectedAddress || !useNftDetection !== disabled) { + this.configure({ selectedAddress, disabled: !useNftDetection }); +- if (useNftDetection) { +- this.start(); +- } else { +- this.stop(); +- } + } + }); + onNetworkStateChange(({ selectedNetworkClientId }) => { +@@ -92,34 +91,33 @@ var NftDetectionController = class extends _pollingcontroller.StaticIntervalPoll + this.setIntervalLength(this.config.interval); + } + getOwnerNftApi({ ++ chainId, + address, + next + }) { +- return `${_controllerutils.NFT_API_BASE_URL}/users/${address}/tokens?chainIds=1&limit=50&includeTopBid=true&continuation=${next ?? ""}`; ++ return `${_controllerutils.NFT_API_BASE_URL}/users/${address}/tokens?chainIds=${chainId}&limit=50&includeTopBid=true&continuation=${next ?? ""}`; + } +- async getOwnerNfts(address) { +- let nftApiResponse; +- let nfts = []; +- let next; +- do { +- nftApiResponse = await _controllerutils.fetchWithErrorHandling.call(void 0, { +- url: this.getOwnerNftApi({ address, next }), +- options: { +- headers: { +- Version: "1" +- } ++ async getOwnerNfts( ++ address, ++ chainId, ++ cursor, ++ ) { ++ // Convert hex chainId to number ++ const convertedChainId = (0, _controllerutils.convertHexToDecimal)(chainId).toString(); ++ const url = this.getOwnerNftApi({ ++ chainId: convertedChainId, ++ address, ++ next: cursor, ++ }); ++ ++ const nftApiResponse = await _controllerutils.handleFetch.call(void 0, url, ++ { ++ headers: { ++ Version: "1" + }, +- timeout: 15e3 +- }); +- if (!nftApiResponse) { +- return nfts; + } +- const newNfts = nftApiResponse.tokens.filter( +- (elm) => elm.token.isSpam === false && (elm.blockaidResult?.result_type ? elm.blockaidResult?.result_type === "Benign" /* Benign */ : true) +- ); +- nfts = [...nfts, ...newNfts]; +- } while (next = nftApiResponse.continuation); +- return nfts; ++ ); ++ return nftApiResponse; + } + async _executePoll(networkClientId, options) { + await this.detectNfts({ networkClientId, userAddress: options.address }); +@@ -169,62 +167,103 @@ var NftDetectionController = class extends _pollingcontroller.StaticIntervalPoll + networkClientId, + userAddress + } = { userAddress: this.config.selectedAddress }) { +- if (!this.isMainnet() || this.disabled) { ++ const { chainId } = this.config; ++ if (!supportedNftDetectionNetworks.includes(chainId) || this.disabled) { + return; + } + if (!userAddress) { + return; + } +- const apiNfts = await this.getOwnerNfts(userAddress); +- const addNftPromises = apiNfts.map(async (nft) => { +- const { +- tokenId: token_id, +- contract, +- kind, +- image: image_url, +- imageSmall: image_thumbnail_url, +- metadata: { imageOriginal: image_original_url } = {}, +- name, +- description, +- attributes, +- topBid, +- lastSale, +- rarityRank, +- rarityScore, +- collection +- } = nft.token; +- let ignored; +- const { ignoredNfts } = this.getNftState(); +- if (ignoredNfts.length) { +- ignored = ignoredNfts.find((c) => { +- return c.address === _controllerutils.toChecksumHexAddress.call(void 0, contract) && c.tokenId === token_id; +- }); +- } +- if (!ignored) { +- const nftMetadata = Object.assign( +- {}, +- { name }, +- description && { description }, +- image_url && { image: image_url }, +- image_thumbnail_url && { imageThumbnail: image_thumbnail_url }, +- image_original_url && { imageOriginal: image_original_url }, +- kind && { standard: kind.toUpperCase() }, +- lastSale && { lastSale }, +- attributes && { attributes }, +- topBid && { topBid }, +- rarityRank && { rarityRank }, +- rarityScore && { rarityScore }, +- collection && { collection } +- ); +- await this.addNft(contract, token_id, { +- nftMetadata, +- userAddress, +- source: "detected" /* Detected */, +- networkClientId ++ ++ const updateKey = `${chainId}:${userAddress}`; ++ if (updateKey in this.inProcessNftFetchingUpdates) { ++ // This prevents redundant updates ++ // This promise is resolved after the in-progress update has finished, ++ // and state has been updated. ++ await this.inProcessNftFetchingUpdates[updateKey]; ++ return; ++ } ++ const { ++ promise: inProgressUpdate, ++ resolve: updateSucceeded, ++ reject: updateFailed ++ } = _utils.createDeferredPromise.call(void 0, { suppressUnhandledRejection: true }); ++ this.inProcessNftFetchingUpdates[updateKey] = inProgressUpdate; ++ ++ let next; ++ let apiNfts= []; ++ let resultNftApi; ++ ++ try{ ++ do { ++ resultNftApi = await this.getOwnerNfts(userAddress, chainId, next) ++ apiNfts = resultNftApi.tokens.filter( ++ (elm) => ++ elm.token.isSpam === false && ++ (elm.blockaidResult?.result_type ++ ? elm.blockaidResult?.result_type === BlockaidResultType.Benign ++ : true), ++ ); ++ const addNftPromises = apiNfts.map(async (nft) => { ++ const { ++ tokenId: token_id, ++ contract, ++ kind, ++ image: image_url, ++ imageSmall: image_thumbnail_url, ++ metadata: { imageOriginal: image_original_url } = {}, ++ name, ++ description, ++ attributes, ++ topBid, ++ lastSale, ++ rarityRank, ++ rarityScore, ++ collection, ++ } = nft.token; ++ ++ let ignored; ++ /* istanbul ignore else */ ++ const { ignoredNfts } = this.getNftState(); ++ if (ignoredNfts.length) { ++ ignored = ignoredNfts.find((c) => { ++ return c.address === _controllerutils.toChecksumHexAddress.call(void 0, contract) && c.tokenId === token_id; ++ }); ++ } ++ /* istanbul ignore else */ ++ if (!ignored) { ++ const nftMetadata = Object.assign( ++ {}, ++ { name }, ++ description && { description }, ++ image_url && { image: image_url }, ++ image_thumbnail_url && { imageThumbnail: image_thumbnail_url }, ++ image_original_url && { imageOriginal: image_original_url }, ++ kind && { standard: kind.toUpperCase() }, ++ lastSale && { lastSale }, ++ attributes && { attributes }, ++ topBid && { topBid }, ++ rarityRank && { rarityRank }, ++ rarityScore && { rarityScore }, ++ collection && { collection } ++ ); ++ await this.addNft(contract, token_id, { ++ nftMetadata, ++ userAddress, ++ source: "detected" /* Detected */, ++ networkClientId ++ }); ++ } + }); +- } +- }); +- await Promise.all(addNftPromises); ++ await Promise.all(addNftPromises); ++ } while ((next = resultNftApi.continuation)); ++ updateSucceeded(); ++ } catch (error){ ++ updateFailed(error); ++ throw error; ++ } finally { ++ delete this.inProcessNftFetchingUpdates[updateKey]; ++ } + } + }; + var NftDetectionController_default = NftDetectionController; diff --git a/.yarn/patches/@metamask-gas-fee-controller-npm-15.1.2-db4d2976aa.patch b/.yarn/patches/@metamask-gas-fee-controller-npm-15.1.2-db4d2976aa.patch new file mode 100644 index 000000000000..e4e69c39609c --- /dev/null +++ b/.yarn/patches/@metamask-gas-fee-controller-npm-15.1.2-db4d2976aa.patch @@ -0,0 +1,2630 @@ +diff --git a/dist/GasFeeController.js b/dist/GasFeeController.js +index 089bba219bac937eeccb423f3e69ad5407ef9be0..b6bdda12ceeb0c38b344b20f80c3afc5120ce3f9 100644 +--- a/dist/GasFeeController.js ++++ b/dist/GasFeeController.js +@@ -3,12 +3,12 @@ + + + +-var _chunkH5WHAYLIjs = require('./chunk-H5WHAYLI.js'); +-require('./chunk-Q2YPK5SL.js'); ++var _chunkX74LQX2Yjs = require('./chunk-X74LQX2Y.js'); ++require('./chunk-2MFVV2BX.js'); + + + + + +-exports.GAS_API_BASE_URL = _chunkH5WHAYLIjs.GAS_API_BASE_URL; exports.GAS_ESTIMATE_TYPES = _chunkH5WHAYLIjs.GAS_ESTIMATE_TYPES; exports.GasFeeController = _chunkH5WHAYLIjs.GasFeeController; exports.default = _chunkH5WHAYLIjs.GasFeeController_default; ++exports.GAS_ESTIMATE_TYPES = _chunkX74LQX2Yjs.GAS_ESTIMATE_TYPES; exports.GasFeeController = _chunkX74LQX2Yjs.GasFeeController; exports.LEGACY_GAS_PRICES_API_URL = _chunkX74LQX2Yjs.LEGACY_GAS_PRICES_API_URL; exports.default = _chunkX74LQX2Yjs.GasFeeController_default; + //# sourceMappingURL=GasFeeController.js.map +\ No newline at end of file +diff --git a/dist/GasFeeController.mjs b/dist/GasFeeController.mjs +index 14ab557e85665a30cbd8b4cec41448d5b88fed91..9b9b90786ac35a4cf320d00a933ef151cdf03821 100644 +--- a/dist/GasFeeController.mjs ++++ b/dist/GasFeeController.mjs +@@ -1,14 +1,14 @@ + import { +- GAS_API_BASE_URL, + GAS_ESTIMATE_TYPES, + GasFeeController, +- GasFeeController_default +-} from "./chunk-BEVZS3YV.mjs"; +-import "./chunk-KORLXV32.mjs"; ++ GasFeeController_default, ++ LEGACY_GAS_PRICES_API_URL ++} from "./chunk-A7NHJBXX.mjs"; ++import "./chunk-R3IOI7AK.mjs"; + export { +- GAS_API_BASE_URL, + GAS_ESTIMATE_TYPES, + GasFeeController, ++ LEGACY_GAS_PRICES_API_URL, + GasFeeController_default as default + }; + //# sourceMappingURL=GasFeeController.mjs.map +\ No newline at end of file +diff --git a/dist/chunk-2MFVV2BX.js b/dist/chunk-2MFVV2BX.js +new file mode 100644 +index 0000000000000000000000000000000000000000..4f83a713baa28f3d687c50ed7ad3c79715a2b588 +--- /dev/null ++++ b/dist/chunk-2MFVV2BX.js +@@ -0,0 +1,156 @@ ++"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }var __accessCheck = (obj, member, msg) => { ++ if (!member.has(obj)) ++ throw TypeError("Cannot " + msg); ++}; ++var __privateGet = (obj, member, getter) => { ++ __accessCheck(obj, member, "read from private field"); ++ return getter ? getter.call(obj) : member.get(obj); ++}; ++var __privateAdd = (obj, member, value) => { ++ if (member.has(obj)) ++ throw TypeError("Cannot add the same private member more than once"); ++ member instanceof WeakSet ? member.add(obj) : member.set(obj, value); ++}; ++var __privateSet = (obj, member, value, setter) => { ++ __accessCheck(obj, member, "write to private field"); ++ setter ? setter.call(obj, value) : member.set(obj, value); ++ return value; ++}; ++var __privateMethod = (obj, member, method) => { ++ __accessCheck(obj, member, "access private method"); ++ return method; ++}; ++ ++// src/gas-util.ts ++ ++ ++ ++ ++ ++var _controllerutils = require('@metamask/controller-utils'); ++var _bnjs = require('bn.js'); var _bnjs2 = _interopRequireDefault(_bnjs); ++var makeClientIdHeader = (clientId) => ({ "X-Client-Id": clientId }); ++function normalizeGWEIDecimalNumbers(n) { ++ const numberAsWEIHex = _controllerutils.gweiDecToWEIBN.call(void 0, n).toString(16); ++ const numberAsGWEI = _controllerutils.weiHexToGweiDec.call(void 0, numberAsWEIHex); ++ return numberAsGWEI; ++} ++async function fetchGasEstimates(url, clientId) { ++ const estimates = await _controllerutils.handleFetch.call(void 0, ++ url, ++ clientId ? { headers: makeClientIdHeader(clientId) } : void 0 ++ ); ++ return { ++ low: { ++ ...estimates.low, ++ suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers( ++ estimates.low.suggestedMaxPriorityFeePerGas ++ ), ++ suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers( ++ estimates.low.suggestedMaxFeePerGas ++ ) ++ }, ++ medium: { ++ ...estimates.medium, ++ suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers( ++ estimates.medium.suggestedMaxPriorityFeePerGas ++ ), ++ suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers( ++ estimates.medium.suggestedMaxFeePerGas ++ ) ++ }, ++ high: { ++ ...estimates.high, ++ suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers( ++ estimates.high.suggestedMaxPriorityFeePerGas ++ ), ++ suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers( ++ estimates.high.suggestedMaxFeePerGas ++ ) ++ }, ++ estimatedBaseFee: normalizeGWEIDecimalNumbers(estimates.estimatedBaseFee), ++ historicalBaseFeeRange: estimates.historicalBaseFeeRange, ++ baseFeeTrend: estimates.baseFeeTrend, ++ latestPriorityFeeRange: estimates.latestPriorityFeeRange, ++ historicalPriorityFeeRange: estimates.historicalPriorityFeeRange, ++ priorityFeeTrend: estimates.priorityFeeTrend, ++ networkCongestion: estimates.networkCongestion ++ }; ++} ++async function fetchLegacyGasPriceEstimates(url, clientId) { ++ const result = await _controllerutils.handleFetch.call(void 0, url, { ++ referrer: url, ++ referrerPolicy: "no-referrer-when-downgrade", ++ method: "GET", ++ mode: "cors", ++ headers: { ++ "Content-Type": "application/json", ++ ...clientId && makeClientIdHeader(clientId) ++ } ++ }); ++ return { ++ low: result.SafeGasPrice, ++ medium: result.ProposeGasPrice, ++ high: result.FastGasPrice ++ }; ++} ++async function fetchEthGasPriceEstimate(ethQuery) { ++ const gasPrice = await _controllerutils.query.call(void 0, ethQuery, "gasPrice"); ++ return { ++ gasPrice: _controllerutils.weiHexToGweiDec.call(void 0, gasPrice).toString() ++ }; ++} ++function calculateTimeEstimate(maxPriorityFeePerGas, maxFeePerGas, gasFeeEstimates) { ++ const { low, medium, high, estimatedBaseFee } = gasFeeEstimates; ++ const maxPriorityFeePerGasInWEI = _controllerutils.gweiDecToWEIBN.call(void 0, maxPriorityFeePerGas); ++ const maxFeePerGasInWEI = _controllerutils.gweiDecToWEIBN.call(void 0, maxFeePerGas); ++ const estimatedBaseFeeInWEI = _controllerutils.gweiDecToWEIBN.call(void 0, estimatedBaseFee); ++ const effectiveMaxPriorityFee = _bnjs2.default.min( ++ maxPriorityFeePerGasInWEI, ++ maxFeePerGasInWEI.sub(estimatedBaseFeeInWEI) ++ ); ++ const lowMaxPriorityFeeInWEI = _controllerutils.gweiDecToWEIBN.call(void 0, ++ low.suggestedMaxPriorityFeePerGas ++ ); ++ const mediumMaxPriorityFeeInWEI = _controllerutils.gweiDecToWEIBN.call(void 0, ++ medium.suggestedMaxPriorityFeePerGas ++ ); ++ const highMaxPriorityFeeInWEI = _controllerutils.gweiDecToWEIBN.call(void 0, ++ high.suggestedMaxPriorityFeePerGas ++ ); ++ let lowerTimeBound; ++ let upperTimeBound; ++ if (effectiveMaxPriorityFee.lt(lowMaxPriorityFeeInWEI)) { ++ lowerTimeBound = null; ++ upperTimeBound = "unknown"; ++ } else if (effectiveMaxPriorityFee.gte(lowMaxPriorityFeeInWEI) && effectiveMaxPriorityFee.lt(mediumMaxPriorityFeeInWEI)) { ++ lowerTimeBound = low.minWaitTimeEstimate; ++ upperTimeBound = low.maxWaitTimeEstimate; ++ } else if (effectiveMaxPriorityFee.gte(mediumMaxPriorityFeeInWEI) && effectiveMaxPriorityFee.lt(highMaxPriorityFeeInWEI)) { ++ lowerTimeBound = medium.minWaitTimeEstimate; ++ upperTimeBound = medium.maxWaitTimeEstimate; ++ } else if (effectiveMaxPriorityFee.eq(highMaxPriorityFeeInWEI)) { ++ lowerTimeBound = high.minWaitTimeEstimate; ++ upperTimeBound = high.maxWaitTimeEstimate; ++ } else { ++ lowerTimeBound = 0; ++ upperTimeBound = high.maxWaitTimeEstimate; ++ } ++ return { ++ lowerTimeBound, ++ upperTimeBound ++ }; ++} ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++exports.__privateGet = __privateGet; exports.__privateAdd = __privateAdd; exports.__privateSet = __privateSet; exports.__privateMethod = __privateMethod; exports.normalizeGWEIDecimalNumbers = normalizeGWEIDecimalNumbers; exports.fetchGasEstimates = fetchGasEstimates; exports.fetchLegacyGasPriceEstimates = fetchLegacyGasPriceEstimates; exports.fetchEthGasPriceEstimate = fetchEthGasPriceEstimate; exports.calculateTimeEstimate = calculateTimeEstimate; ++//# sourceMappingURL=chunk-2MFVV2BX.js.map +\ No newline at end of file +diff --git a/dist/chunk-2MFVV2BX.js.map b/dist/chunk-2MFVV2BX.js.map +new file mode 100644 +index 0000000000000000000000000000000000000000..c6d83c1c168ee0b053be10a74334ac712bb6a6a1 +--- /dev/null ++++ b/dist/chunk-2MFVV2BX.js.map +@@ -0,0 +1 @@ ++{"version":3,"sources":["../src/gas-util.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,OAAO,QAAQ;AAUf,IAAM,qBAAqB,CAAC,cAAsB,EAAE,eAAe,SAAS;AAQrE,SAAS,4BAA4B,GAAoB;AAC9D,QAAM,iBAAiB,eAAe,CAAC,EAAE,SAAS,EAAE;AACpD,QAAM,eAAe,gBAAgB,cAAc;AACnD,SAAO;AACT;AASA,eAAsB,kBACpB,KACA,UAC0B;AAC1B,QAAM,YAAY,MAAM;AAAA,IACtB;AAAA,IACA,WAAW,EAAE,SAAS,mBAAmB,QAAQ,EAAE,IAAI;AAAA,EACzD;AACA,SAAO;AAAA,IACL,KAAK;AAAA,MACH,GAAG,UAAU;AAAA,MACb,+BAA+B;AAAA,QAC7B,UAAU,IAAI;AAAA,MAChB;AAAA,MACA,uBAAuB;AAAA,QACrB,UAAU,IAAI;AAAA,MAChB;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,MACN,GAAG,UAAU;AAAA,MACb,+BAA+B;AAAA,QAC7B,UAAU,OAAO;AAAA,MACnB;AAAA,MACA,uBAAuB;AAAA,QACrB,UAAU,OAAO;AAAA,MACnB;AAAA,IACF;AAAA,IACA,MAAM;AAAA,MACJ,GAAG,UAAU;AAAA,MACb,+BAA+B;AAAA,QAC7B,UAAU,KAAK;AAAA,MACjB;AAAA,MACA,uBAAuB;AAAA,QACrB,UAAU,KAAK;AAAA,MACjB;AAAA,IACF;AAAA,IACA,kBAAkB,4BAA4B,UAAU,gBAAgB;AAAA,IACxE,wBAAwB,UAAU;AAAA,IAClC,cAAc,UAAU;AAAA,IACxB,wBAAwB,UAAU;AAAA,IAClC,4BAA4B,UAAU;AAAA,IACtC,kBAAkB,UAAU;AAAA,IAC5B,mBAAmB,UAAU;AAAA,EAC/B;AACF;AAUA,eAAsB,6BACpB,KACA,UACiC;AACjC,QAAM,SAAS,MAAM,YAAY,KAAK;AAAA,IACpC,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,GAAI,YAAY,mBAAmB,QAAQ;AAAA,IAC7C;AAAA,EACF,CAAC;AACD,SAAO;AAAA,IACL,KAAK,OAAO;AAAA,IACZ,QAAQ,OAAO;AAAA,IACf,MAAM,OAAO;AAAA,EACf;AACF;AAQA,eAAsB,yBACpB,UAC8B;AAC9B,QAAM,WAAW,MAAM,MAAM,UAAU,UAAU;AACjD,SAAO;AAAA,IACL,UAAU,gBAAgB,QAAQ,EAAE,SAAS;AAAA,EAC/C;AACF;AAUO,SAAS,sBACd,sBACA,cACA,iBAC2B;AAC3B,QAAM,EAAE,KAAK,QAAQ,MAAM,iBAAiB,IAAI;AAEhD,QAAM,4BAA4B,eAAe,oBAAoB;AACrE,QAAM,oBAAoB,eAAe,YAAY;AACrD,QAAM,wBAAwB,eAAe,gBAAgB;AAE7D,QAAM,0BAA0B,GAAG;AAAA,IACjC;AAAA,IACA,kBAAkB,IAAI,qBAAqB;AAAA,EAC7C;AAEA,QAAM,yBAAyB;AAAA,IAC7B,IAAI;AAAA,EACN;AACA,QAAM,4BAA4B;AAAA,IAChC,OAAO;AAAA,EACT;AACA,QAAM,0BAA0B;AAAA,IAC9B,KAAK;AAAA,EACP;AAEA,MAAI;AACJ,MAAI;AAEJ,MAAI,wBAAwB,GAAG,sBAAsB,GAAG;AACtD,qBAAiB;AACjB,qBAAiB;AAAA,EACnB,WACE,wBAAwB,IAAI,sBAAsB,KAClD,wBAAwB,GAAG,yBAAyB,GACpD;AACA,qBAAiB,IAAI;AACrB,qBAAiB,IAAI;AAAA,EACvB,WACE,wBAAwB,IAAI,yBAAyB,KACrD,wBAAwB,GAAG,uBAAuB,GAClD;AACA,qBAAiB,OAAO;AACxB,qBAAiB,OAAO;AAAA,EAC1B,WAAW,wBAAwB,GAAG,uBAAuB,GAAG;AAC9D,qBAAiB,KAAK;AACtB,qBAAiB,KAAK;AAAA,EACxB,OAAO;AACL,qBAAiB;AACjB,qBAAiB,KAAK;AAAA,EACxB;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,EACF;AACF","sourcesContent":["import {\n query,\n handleFetch,\n gweiDecToWEIBN,\n weiHexToGweiDec,\n} from '@metamask/controller-utils';\nimport type EthQuery from '@metamask/eth-query';\nimport BN from 'bn.js';\n\nimport type {\n GasFeeEstimates,\n EthGasPriceEstimate,\n EstimatedGasFeeTimeBounds,\n unknownString,\n LegacyGasPriceEstimate,\n} from './GasFeeController';\n\nconst makeClientIdHeader = (clientId: string) => ({ 'X-Client-Id': clientId });\n\n/**\n * Convert a decimal GWEI value to a decimal string rounded to the nearest WEI.\n *\n * @param n - The input GWEI amount, as a decimal string or a number.\n * @returns The decimal string GWEI amount.\n */\nexport function normalizeGWEIDecimalNumbers(n: string | number) {\n const numberAsWEIHex = gweiDecToWEIBN(n).toString(16);\n const numberAsGWEI = weiHexToGweiDec(numberAsWEIHex);\n return numberAsGWEI;\n}\n\n/**\n * Fetch gas estimates from the given URL.\n *\n * @param url - The gas estimate URL.\n * @param clientId - The client ID used to identify to the API who is asking for estimates.\n * @returns The gas estimates.\n */\nexport async function fetchGasEstimates(\n url: string,\n clientId?: string,\n): Promise {\n const estimates = await handleFetch(\n url,\n clientId ? { headers: makeClientIdHeader(clientId) } : undefined,\n );\n return {\n low: {\n ...estimates.low,\n suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.low.suggestedMaxPriorityFeePerGas,\n ),\n suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.low.suggestedMaxFeePerGas,\n ),\n },\n medium: {\n ...estimates.medium,\n suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.medium.suggestedMaxPriorityFeePerGas,\n ),\n suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.medium.suggestedMaxFeePerGas,\n ),\n },\n high: {\n ...estimates.high,\n suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.high.suggestedMaxPriorityFeePerGas,\n ),\n suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.high.suggestedMaxFeePerGas,\n ),\n },\n estimatedBaseFee: normalizeGWEIDecimalNumbers(estimates.estimatedBaseFee),\n historicalBaseFeeRange: estimates.historicalBaseFeeRange,\n baseFeeTrend: estimates.baseFeeTrend,\n latestPriorityFeeRange: estimates.latestPriorityFeeRange,\n historicalPriorityFeeRange: estimates.historicalPriorityFeeRange,\n priorityFeeTrend: estimates.priorityFeeTrend,\n networkCongestion: estimates.networkCongestion,\n };\n}\n\n/**\n * Hit the legacy MetaSwaps gasPrices estimate api and return the low, medium\n * high values from that API.\n *\n * @param url - The URL to fetch gas price estimates from.\n * @param clientId - The client ID used to identify to the API who is asking for estimates.\n * @returns The gas price estimates.\n */\nexport async function fetchLegacyGasPriceEstimates(\n url: string,\n clientId?: string,\n): Promise {\n const result = await handleFetch(url, {\n referrer: url,\n referrerPolicy: 'no-referrer-when-downgrade',\n method: 'GET',\n mode: 'cors',\n headers: {\n 'Content-Type': 'application/json',\n ...(clientId && makeClientIdHeader(clientId)),\n },\n });\n return {\n low: result.SafeGasPrice,\n medium: result.ProposeGasPrice,\n high: result.FastGasPrice,\n };\n}\n\n/**\n * Get a gas price estimate from the network using the `eth_gasPrice` method.\n *\n * @param ethQuery - The EthQuery instance to call the network with.\n * @returns A gas price estimate.\n */\nexport async function fetchEthGasPriceEstimate(\n ethQuery: EthQuery,\n): Promise {\n const gasPrice = await query(ethQuery, 'gasPrice');\n return {\n gasPrice: weiHexToGweiDec(gasPrice).toString(),\n };\n}\n\n/**\n * Estimate the time it will take for a transaction to be confirmed.\n *\n * @param maxPriorityFeePerGas - The max priority fee per gas.\n * @param maxFeePerGas - The max fee per gas.\n * @param gasFeeEstimates - The gas fee estimates.\n * @returns The estimated lower and upper bounds for when this transaction will be confirmed.\n */\nexport function calculateTimeEstimate(\n maxPriorityFeePerGas: string,\n maxFeePerGas: string,\n gasFeeEstimates: GasFeeEstimates,\n): EstimatedGasFeeTimeBounds {\n const { low, medium, high, estimatedBaseFee } = gasFeeEstimates;\n\n const maxPriorityFeePerGasInWEI = gweiDecToWEIBN(maxPriorityFeePerGas);\n const maxFeePerGasInWEI = gweiDecToWEIBN(maxFeePerGas);\n const estimatedBaseFeeInWEI = gweiDecToWEIBN(estimatedBaseFee);\n\n const effectiveMaxPriorityFee = BN.min(\n maxPriorityFeePerGasInWEI,\n maxFeePerGasInWEI.sub(estimatedBaseFeeInWEI),\n );\n\n const lowMaxPriorityFeeInWEI = gweiDecToWEIBN(\n low.suggestedMaxPriorityFeePerGas,\n );\n const mediumMaxPriorityFeeInWEI = gweiDecToWEIBN(\n medium.suggestedMaxPriorityFeePerGas,\n );\n const highMaxPriorityFeeInWEI = gweiDecToWEIBN(\n high.suggestedMaxPriorityFeePerGas,\n );\n\n let lowerTimeBound;\n let upperTimeBound;\n\n if (effectiveMaxPriorityFee.lt(lowMaxPriorityFeeInWEI)) {\n lowerTimeBound = null;\n upperTimeBound = 'unknown' as unknownString;\n } else if (\n effectiveMaxPriorityFee.gte(lowMaxPriorityFeeInWEI) &&\n effectiveMaxPriorityFee.lt(mediumMaxPriorityFeeInWEI)\n ) {\n lowerTimeBound = low.minWaitTimeEstimate;\n upperTimeBound = low.maxWaitTimeEstimate;\n } else if (\n effectiveMaxPriorityFee.gte(mediumMaxPriorityFeeInWEI) &&\n effectiveMaxPriorityFee.lt(highMaxPriorityFeeInWEI)\n ) {\n lowerTimeBound = medium.minWaitTimeEstimate;\n upperTimeBound = medium.maxWaitTimeEstimate;\n } else if (effectiveMaxPriorityFee.eq(highMaxPriorityFeeInWEI)) {\n lowerTimeBound = high.minWaitTimeEstimate;\n upperTimeBound = high.maxWaitTimeEstimate;\n } else {\n lowerTimeBound = 0;\n upperTimeBound = high.maxWaitTimeEstimate;\n }\n\n return {\n lowerTimeBound,\n upperTimeBound,\n };\n}\n"]} +\ No newline at end of file +diff --git a/dist/chunk-A7NHJBXX.mjs b/dist/chunk-A7NHJBXX.mjs +new file mode 100644 +index 0000000000000000000000000000000000000000..1dd920c4a314888548db98885976b336d2fec79d +--- /dev/null ++++ b/dist/chunk-A7NHJBXX.mjs +@@ -0,0 +1,390 @@ ++import { ++ __privateAdd, ++ __privateGet, ++ __privateMethod, ++ __privateSet, ++ calculateTimeEstimate, ++ fetchEthGasPriceEstimate, ++ fetchGasEstimates, ++ fetchLegacyGasPriceEstimates ++} from "./chunk-R3IOI7AK.mjs"; ++ ++// src/GasFeeController.ts ++import { ++ convertHexToDecimal, ++ safelyExecute, ++ toHex ++} from "@metamask/controller-utils"; ++import EthQuery from "@metamask/eth-query"; ++import { StaticIntervalPollingController } from "@metamask/polling-controller"; ++import { v1 as random } from "uuid"; ++var LEGACY_GAS_PRICES_API_URL = `https://api.metaswap.codefi.network/gasPrices`; ++var GAS_ESTIMATE_TYPES = { ++ FEE_MARKET: "fee-market", ++ LEGACY: "legacy", ++ ETH_GASPRICE: "eth_gasPrice", ++ NONE: "none" ++}; ++var metadata = { ++ gasFeeEstimatesByChainId: { ++ persist: true, ++ anonymous: false ++ }, ++ gasFeeEstimates: { persist: true, anonymous: false }, ++ estimatedGasFeeTimeBounds: { persist: true, anonymous: false }, ++ gasEstimateType: { persist: true, anonymous: false }, ++ nonRPCGasFeeApisDisabled: { persist: true, anonymous: false } ++}; ++var name = "GasFeeController"; ++var defaultState = { ++ gasFeeEstimatesByChainId: {}, ++ gasFeeEstimates: {}, ++ estimatedGasFeeTimeBounds: {}, ++ gasEstimateType: GAS_ESTIMATE_TYPES.NONE, ++ nonRPCGasFeeApisDisabled: false ++}; ++var _getProvider, _onNetworkControllerDidChange, onNetworkControllerDidChange_fn; ++var GasFeeController = class extends StaticIntervalPollingController { ++ /** ++ * Creates a GasFeeController instance. ++ * ++ * @param options - The controller options. ++ * @param options.interval - The time in milliseconds to wait between polls. ++ * @param options.messenger - The controller messenger. ++ * @param options.state - The initial state. ++ * @param options.getCurrentNetworkEIP1559Compatibility - Determines whether or not the current ++ * network is EIP-1559 compatible. ++ * @param options.getCurrentNetworkLegacyGasAPICompatibility - Determines whether or not the ++ * current network is compatible with the legacy gas price API. ++ * @param options.getCurrentAccountEIP1559Compatibility - Determines whether or not the current ++ * account is EIP-1559 compatible. ++ * @param options.getChainId - Returns the current chain ID. ++ * @param options.getProvider - Returns a network provider for the current network. ++ * @param options.onNetworkDidChange - A function for registering an event handler for the ++ * network state change event. ++ * @param options.legacyAPIEndpoint - The legacy gas price API URL. This option is primarily for ++ * testing purposes. ++ * @param options.EIP1559APIEndpoint - The EIP-1559 gas price API URL. ++ * @param options.clientId - The client ID used to identify to the gas estimation API who is ++ * asking for estimates. ++ */ ++ constructor({ ++ interval = 15e3, ++ messenger, ++ state, ++ getCurrentNetworkEIP1559Compatibility, ++ getCurrentAccountEIP1559Compatibility, ++ getChainId, ++ getCurrentNetworkLegacyGasAPICompatibility, ++ getProvider, ++ onNetworkDidChange, ++ legacyAPIEndpoint = LEGACY_GAS_PRICES_API_URL, ++ EIP1559APIEndpoint, ++ clientId ++ }) { ++ super({ ++ name, ++ metadata, ++ messenger, ++ state: { ...defaultState, ...state } ++ }); ++ __privateAdd(this, _onNetworkControllerDidChange); ++ __privateAdd(this, _getProvider, void 0); ++ this.intervalDelay = interval; ++ this.setIntervalLength(interval); ++ this.pollTokens = /* @__PURE__ */ new Set(); ++ this.getCurrentNetworkEIP1559Compatibility = getCurrentNetworkEIP1559Compatibility; ++ this.getCurrentNetworkLegacyGasAPICompatibility = getCurrentNetworkLegacyGasAPICompatibility; ++ this.getCurrentAccountEIP1559Compatibility = getCurrentAccountEIP1559Compatibility; ++ __privateSet(this, _getProvider, getProvider); ++ this.EIP1559APIEndpoint = EIP1559APIEndpoint; ++ this.legacyAPIEndpoint = legacyAPIEndpoint; ++ this.clientId = clientId; ++ this.ethQuery = new EthQuery(__privateGet(this, _getProvider).call(this)); ++ if (onNetworkDidChange && getChainId) { ++ this.currentChainId = getChainId(); ++ onNetworkDidChange(async (networkControllerState) => { ++ await __privateMethod(this, _onNetworkControllerDidChange, onNetworkControllerDidChange_fn).call(this, networkControllerState); ++ }); ++ } else { ++ this.currentChainId = this.messagingSystem.call( ++ "NetworkController:getState" ++ ).providerConfig.chainId; ++ this.messagingSystem.subscribe( ++ "NetworkController:networkDidChange", ++ async (networkControllerState) => { ++ await __privateMethod(this, _onNetworkControllerDidChange, onNetworkControllerDidChange_fn).call(this, networkControllerState); ++ } ++ ); ++ } ++ } ++ async resetPolling() { ++ if (this.pollTokens.size !== 0) { ++ const tokens = Array.from(this.pollTokens); ++ this.stopPolling(); ++ await this.getGasFeeEstimatesAndStartPolling(tokens[0]); ++ tokens.slice(1).forEach((token) => { ++ this.pollTokens.add(token); ++ }); ++ } ++ } ++ async fetchGasFeeEstimates(options) { ++ return await this._fetchGasFeeEstimateData(options); ++ } ++ async getGasFeeEstimatesAndStartPolling(pollToken) { ++ const _pollToken = pollToken || random(); ++ this.pollTokens.add(_pollToken); ++ if (this.pollTokens.size === 1) { ++ await this._fetchGasFeeEstimateData(); ++ this._poll(); ++ } ++ return _pollToken; ++ } ++ /** ++ * Gets and sets gasFeeEstimates in state. ++ * ++ * @param options - The gas fee estimate options. ++ * @param options.shouldUpdateState - Determines whether the state should be updated with the ++ * updated gas estimates. ++ * @returns The gas fee estimates. ++ */ ++ async _fetchGasFeeEstimateData(options = {}) { ++ const { shouldUpdateState = true, networkClientId } = options; ++ let ethQuery, isEIP1559Compatible, isLegacyGasAPICompatible, decimalChainId; ++ if (networkClientId !== void 0) { ++ const networkClient = this.messagingSystem.call( ++ "NetworkController:getNetworkClientById", ++ networkClientId ++ ); ++ isLegacyGasAPICompatible = networkClient.configuration.chainId === "0x38"; ++ decimalChainId = convertHexToDecimal(networkClient.configuration.chainId); ++ try { ++ const result = await this.messagingSystem.call( ++ "NetworkController:getEIP1559Compatibility", ++ networkClientId ++ ); ++ isEIP1559Compatible = result || false; ++ } catch { ++ isEIP1559Compatible = false; ++ } ++ ethQuery = new EthQuery(networkClient.provider); ++ } ++ ethQuery ?? (ethQuery = this.ethQuery); ++ isLegacyGasAPICompatible ?? (isLegacyGasAPICompatible = this.getCurrentNetworkLegacyGasAPICompatibility()); ++ decimalChainId ?? (decimalChainId = convertHexToDecimal(this.currentChainId)); ++ try { ++ isEIP1559Compatible ?? (isEIP1559Compatible = await this.getEIP1559Compatibility()); ++ } catch (e) { ++ console.error(e); ++ isEIP1559Compatible ?? (isEIP1559Compatible = false); ++ } ++ const gasFeeCalculations = await determineGasFeeCalculations({ ++ isEIP1559Compatible, ++ isLegacyGasAPICompatible, ++ fetchGasEstimates, ++ fetchGasEstimatesUrl: this.EIP1559APIEndpoint.replace( ++ "", ++ `${decimalChainId}` ++ ), ++ fetchLegacyGasPriceEstimates, ++ fetchLegacyGasPriceEstimatesUrl: this.legacyAPIEndpoint.replace( ++ "", ++ `${decimalChainId}` ++ ), ++ fetchEthGasPriceEstimate, ++ calculateTimeEstimate, ++ clientId: this.clientId, ++ ethQuery, ++ nonRPCGasFeeApisDisabled: this.state.nonRPCGasFeeApisDisabled ++ }); ++ if (shouldUpdateState) { ++ const chainId = toHex(decimalChainId); ++ this.update((state) => { ++ if (this.currentChainId === chainId) { ++ state.gasFeeEstimates = gasFeeCalculations.gasFeeEstimates; ++ state.estimatedGasFeeTimeBounds = gasFeeCalculations.estimatedGasFeeTimeBounds; ++ state.gasEstimateType = gasFeeCalculations.gasEstimateType; ++ } ++ state.gasFeeEstimatesByChainId ?? (state.gasFeeEstimatesByChainId = {}); ++ state.gasFeeEstimatesByChainId[chainId] = { ++ gasFeeEstimates: gasFeeCalculations.gasFeeEstimates, ++ estimatedGasFeeTimeBounds: gasFeeCalculations.estimatedGasFeeTimeBounds, ++ gasEstimateType: gasFeeCalculations.gasEstimateType ++ }; ++ }); ++ } ++ return gasFeeCalculations; ++ } ++ /** ++ * Remove the poll token, and stop polling if the set of poll tokens is empty. ++ * ++ * @param pollToken - The poll token to disconnect. ++ */ ++ disconnectPoller(pollToken) { ++ this.pollTokens.delete(pollToken); ++ if (this.pollTokens.size === 0) { ++ this.stopPolling(); ++ } ++ } ++ stopPolling() { ++ if (this.intervalId) { ++ clearInterval(this.intervalId); ++ } ++ this.pollTokens.clear(); ++ this.resetState(); ++ } ++ /** ++ * Prepare to discard this controller. ++ * ++ * This stops any active polling. ++ */ ++ destroy() { ++ super.destroy(); ++ this.stopPolling(); ++ } ++ _poll() { ++ if (this.intervalId) { ++ clearInterval(this.intervalId); ++ } ++ this.intervalId = setInterval(async () => { ++ await safelyExecute(() => this._fetchGasFeeEstimateData()); ++ }, this.intervalDelay); ++ } ++ /** ++ * Fetching token list from the Token Service API. ++ * ++ * @private ++ * @param networkClientId - The ID of the network client triggering the fetch. ++ * @returns A promise that resolves when this operation completes. ++ */ ++ async _executePoll(networkClientId) { ++ await this._fetchGasFeeEstimateData({ networkClientId }); ++ } ++ resetState() { ++ this.update(() => { ++ return defaultState; ++ }); ++ } ++ async getEIP1559Compatibility() { ++ const currentNetworkIsEIP1559Compatible = await this.getCurrentNetworkEIP1559Compatibility(); ++ const currentAccountIsEIP1559Compatible = this.getCurrentAccountEIP1559Compatibility?.() ?? true; ++ return currentNetworkIsEIP1559Compatible && currentAccountIsEIP1559Compatible; ++ } ++ getTimeEstimate(maxPriorityFeePerGas, maxFeePerGas) { ++ if (!this.state.gasFeeEstimates || this.state.gasEstimateType !== GAS_ESTIMATE_TYPES.FEE_MARKET) { ++ return {}; ++ } ++ return calculateTimeEstimate( ++ maxPriorityFeePerGas, ++ maxFeePerGas, ++ this.state.gasFeeEstimates ++ ); ++ } ++ enableNonRPCGasFeeApis() { ++ this.update((state) => { ++ state.nonRPCGasFeeApisDisabled = false; ++ }); ++ } ++ disableNonRPCGasFeeApis() { ++ this.update((state) => { ++ state.nonRPCGasFeeApisDisabled = true; ++ }); ++ } ++}; ++_getProvider = new WeakMap(); ++_onNetworkControllerDidChange = new WeakSet(); ++onNetworkControllerDidChange_fn = async function(networkControllerState) { ++ const newChainId = networkControllerState.providerConfig.chainId; ++ if (newChainId !== this.currentChainId) { ++ this.ethQuery = new EthQuery(__privateGet(this, _getProvider).call(this)); ++ await this.resetPolling(); ++ this.currentChainId = newChainId; ++ } ++}; ++var GasFeeController_default = GasFeeController; ++ ++// src/determineGasFeeCalculations.ts ++async function determineGasFeeCalculations(args) { ++ try { ++ return await getEstimatesUsingFallbacks(args); ++ } catch (error) { ++ if (error instanceof Error) { ++ throw new Error( ++ `Gas fee/price estimation failed. Message: ${error.message}` ++ ); ++ } ++ throw error; ++ } ++} ++async function getEstimatesUsingFallbacks(request) { ++ const { ++ isEIP1559Compatible, ++ isLegacyGasAPICompatible, ++ nonRPCGasFeeApisDisabled ++ } = request; ++ try { ++ if (isEIP1559Compatible && !nonRPCGasFeeApisDisabled) { ++ return await getEstimatesUsingFeeMarketEndpoint(request); ++ } ++ if (isLegacyGasAPICompatible && !nonRPCGasFeeApisDisabled) { ++ return await getEstimatesUsingLegacyEndpoint(request); ++ } ++ throw new Error("Main gas fee/price estimation failed. Use fallback"); ++ } catch { ++ return await getEstimatesUsingProvider(request); ++ } ++} ++async function getEstimatesUsingFeeMarketEndpoint(request) { ++ const { ++ fetchGasEstimates: fetchGasEstimates2, ++ fetchGasEstimatesUrl, ++ clientId, ++ calculateTimeEstimate: calculateTimeEstimate2 ++ } = request; ++ const estimates = await fetchGasEstimates2(fetchGasEstimatesUrl, clientId); ++ const { suggestedMaxPriorityFeePerGas, suggestedMaxFeePerGas } = estimates.medium; ++ const estimatedGasFeeTimeBounds = calculateTimeEstimate2( ++ suggestedMaxPriorityFeePerGas, ++ suggestedMaxFeePerGas, ++ estimates ++ ); ++ return { ++ gasFeeEstimates: estimates, ++ estimatedGasFeeTimeBounds, ++ gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET ++ }; ++} ++async function getEstimatesUsingLegacyEndpoint(request) { ++ const { ++ fetchLegacyGasPriceEstimates: fetchLegacyGasPriceEstimates2, ++ fetchLegacyGasPriceEstimatesUrl, ++ clientId ++ } = request; ++ const estimates = await fetchLegacyGasPriceEstimates2( ++ fetchLegacyGasPriceEstimatesUrl, ++ clientId ++ ); ++ return { ++ gasFeeEstimates: estimates, ++ estimatedGasFeeTimeBounds: {}, ++ gasEstimateType: GAS_ESTIMATE_TYPES.LEGACY ++ }; ++} ++async function getEstimatesUsingProvider(request) { ++ const { ethQuery, fetchEthGasPriceEstimate: fetchEthGasPriceEstimate2 } = request; ++ const estimates = await fetchEthGasPriceEstimate2(ethQuery); ++ return { ++ gasFeeEstimates: estimates, ++ estimatedGasFeeTimeBounds: {}, ++ gasEstimateType: GAS_ESTIMATE_TYPES.ETH_GASPRICE ++ }; ++} ++ ++export { ++ determineGasFeeCalculations, ++ LEGACY_GAS_PRICES_API_URL, ++ GAS_ESTIMATE_TYPES, ++ GasFeeController, ++ GasFeeController_default ++}; ++//# sourceMappingURL=chunk-A7NHJBXX.mjs.map +\ No newline at end of file +diff --git a/dist/chunk-A7NHJBXX.mjs.map b/dist/chunk-A7NHJBXX.mjs.map +new file mode 100644 +index 0000000000000000000000000000000000000000..d40e21b8870bb3ef2d3a092e9abdbb83527764f3 +--- /dev/null ++++ b/dist/chunk-A7NHJBXX.mjs.map +@@ -0,0 +1 @@ ++{"version":3,"sources":["../src/GasFeeController.ts","../src/determineGasFeeCalculations.ts"],"sourcesContent":["import type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n RestrictedControllerMessenger,\n} from '@metamask/base-controller';\nimport {\n convertHexToDecimal,\n safelyExecute,\n toHex,\n} from '@metamask/controller-utils';\nimport EthQuery from '@metamask/eth-query';\nimport type {\n NetworkClientId,\n NetworkControllerGetEIP1559CompatibilityAction,\n NetworkControllerGetNetworkClientByIdAction,\n NetworkControllerGetStateAction,\n NetworkControllerNetworkDidChangeEvent,\n NetworkState,\n ProviderProxy,\n} from '@metamask/network-controller';\nimport { StaticIntervalPollingController } from '@metamask/polling-controller';\nimport type { Hex } from '@metamask/utils';\nimport { v1 as random } from 'uuid';\n\nimport determineGasFeeCalculations from './determineGasFeeCalculations';\nimport {\n fetchGasEstimates,\n fetchLegacyGasPriceEstimates,\n fetchEthGasPriceEstimate,\n calculateTimeEstimate,\n} from './gas-util';\n\nexport const LEGACY_GAS_PRICES_API_URL = `https://api.metaswap.codefi.network/gasPrices`;\n\nexport type unknownString = 'unknown';\n\n// Fee Market describes the way gas is set after the london hardfork, and was\n// defined by EIP-1559.\nexport type FeeMarketEstimateType = 'fee-market';\n// Legacy describes gasPrice estimates from before london hardfork, when the\n// user is connected to mainnet and are presented with fast/average/slow\n// estimate levels to choose from.\nexport type LegacyEstimateType = 'legacy';\n// EthGasPrice describes a gasPrice estimate received from eth_gasPrice. Post\n// london this value should only be used for legacy type transactions when on\n// networks that support EIP-1559. This type of estimate is the most accurate\n// to display on custom networks that don't support EIP-1559.\nexport type EthGasPriceEstimateType = 'eth_gasPrice';\n// NoEstimate describes the state of the controller before receiving its first\n// estimate.\nexport type NoEstimateType = 'none';\n\n/**\n * Indicates which type of gasEstimate the controller is currently returning.\n * This is useful as a way of asserting that the shape of gasEstimates matches\n * expectations. NONE is a special case indicating that no previous gasEstimate\n * has been fetched.\n */\nexport const GAS_ESTIMATE_TYPES = {\n FEE_MARKET: 'fee-market' as FeeMarketEstimateType,\n LEGACY: 'legacy' as LegacyEstimateType,\n ETH_GASPRICE: 'eth_gasPrice' as EthGasPriceEstimateType,\n NONE: 'none' as NoEstimateType,\n};\n\nexport type GasEstimateType =\n | FeeMarketEstimateType\n | EthGasPriceEstimateType\n | LegacyEstimateType\n | NoEstimateType;\n\nexport type EstimatedGasFeeTimeBounds = {\n lowerTimeBound: number | null;\n upperTimeBound: number | unknownString;\n};\n\n/**\n * @type EthGasPriceEstimate\n *\n * A single gas price estimate for networks and accounts that don't support EIP-1559\n * This estimate comes from eth_gasPrice but is converted to dec gwei to match other\n * return values\n * @property gasPrice - A GWEI dec string\n */\n\nexport type EthGasPriceEstimate = {\n gasPrice: string;\n};\n\n/**\n * @type LegacyGasPriceEstimate\n *\n * A set of gas price estimates for networks and accounts that don't support EIP-1559\n * These estimates include low, medium and high all as strings representing gwei in\n * decimal format.\n * @property high - gasPrice, in decimal gwei string format, suggested for fast inclusion\n * @property medium - gasPrice, in decimal gwei string format, suggested for avg inclusion\n * @property low - gasPrice, in decimal gwei string format, suggested for slow inclusion\n */\nexport type LegacyGasPriceEstimate = {\n high: string;\n medium: string;\n low: string;\n};\n\n/**\n * @type Eip1559GasFee\n *\n * Data necessary to provide an estimate of a gas fee with a specific tip\n * @property minWaitTimeEstimate - The fastest the transaction will take, in milliseconds\n * @property maxWaitTimeEstimate - The slowest the transaction will take, in milliseconds\n * @property suggestedMaxPriorityFeePerGas - A suggested \"tip\", a GWEI hex number\n * @property suggestedMaxFeePerGas - A suggested max fee, the most a user will pay. a GWEI hex number\n */\nexport type Eip1559GasFee = {\n minWaitTimeEstimate: number; // a time duration in milliseconds\n maxWaitTimeEstimate: number; // a time duration in milliseconds\n suggestedMaxPriorityFeePerGas: string; // a GWEI decimal number\n suggestedMaxFeePerGas: string; // a GWEI decimal number\n};\n\n/**\n * @type GasFeeEstimates\n *\n * Data necessary to provide multiple GasFee estimates, and supporting information, to the user\n * @property low - A GasFee for a minimum necessary combination of tip and maxFee\n * @property medium - A GasFee for a recommended combination of tip and maxFee\n * @property high - A GasFee for a high combination of tip and maxFee\n * @property estimatedBaseFee - An estimate of what the base fee will be for the pending/next block. A GWEI dec number\n * @property networkCongestion - A normalized number that can be used to gauge the congestion\n * level of the network, with 0 meaning not congested and 1 meaning extremely congested\n */\nexport type GasFeeEstimates = SourcedGasFeeEstimates | FallbackGasFeeEstimates;\n\ntype SourcedGasFeeEstimates = {\n low: Eip1559GasFee;\n medium: Eip1559GasFee;\n high: Eip1559GasFee;\n estimatedBaseFee: string;\n historicalBaseFeeRange: [string, string];\n baseFeeTrend: 'up' | 'down' | 'level';\n latestPriorityFeeRange: [string, string];\n historicalPriorityFeeRange: [string, string];\n priorityFeeTrend: 'up' | 'down' | 'level';\n networkCongestion: number;\n};\n\ntype FallbackGasFeeEstimates = {\n low: Eip1559GasFee;\n medium: Eip1559GasFee;\n high: Eip1559GasFee;\n estimatedBaseFee: string;\n historicalBaseFeeRange: null;\n baseFeeTrend: null;\n latestPriorityFeeRange: null;\n historicalPriorityFeeRange: null;\n priorityFeeTrend: null;\n networkCongestion: null;\n};\n\nconst metadata = {\n gasFeeEstimatesByChainId: {\n persist: true,\n anonymous: false,\n },\n gasFeeEstimates: { persist: true, anonymous: false },\n estimatedGasFeeTimeBounds: { persist: true, anonymous: false },\n gasEstimateType: { persist: true, anonymous: false },\n nonRPCGasFeeApisDisabled: { persist: true, anonymous: false },\n};\n\nexport type GasFeeStateEthGasPrice = {\n gasFeeEstimates: EthGasPriceEstimate;\n estimatedGasFeeTimeBounds: Record;\n gasEstimateType: EthGasPriceEstimateType;\n};\n\nexport type GasFeeStateFeeMarket = {\n gasFeeEstimates: GasFeeEstimates;\n estimatedGasFeeTimeBounds: EstimatedGasFeeTimeBounds | Record;\n gasEstimateType: FeeMarketEstimateType;\n};\n\nexport type GasFeeStateLegacy = {\n gasFeeEstimates: LegacyGasPriceEstimate;\n estimatedGasFeeTimeBounds: Record;\n gasEstimateType: LegacyEstimateType;\n};\n\nexport type GasFeeStateNoEstimates = {\n gasFeeEstimates: Record;\n estimatedGasFeeTimeBounds: Record;\n gasEstimateType: NoEstimateType;\n};\n\nexport type FetchGasFeeEstimateOptions = {\n shouldUpdateState?: boolean;\n networkClientId?: NetworkClientId;\n};\n\n/**\n * @type GasFeeState\n *\n * Gas Fee controller state\n * @property gasFeeEstimates - Gas fee estimate data based on new EIP-1559 properties\n * @property estimatedGasFeeTimeBounds - Estimates representing the minimum and maximum\n */\nexport type SingleChainGasFeeState =\n | GasFeeStateEthGasPrice\n | GasFeeStateFeeMarket\n | GasFeeStateLegacy\n | GasFeeStateNoEstimates;\n\nexport type GasFeeEstimatesByChainId = {\n gasFeeEstimatesByChainId?: Record;\n};\n\nexport type GasFeeState = GasFeeEstimatesByChainId &\n SingleChainGasFeeState & {\n nonRPCGasFeeApisDisabled?: boolean;\n };\n\nconst name = 'GasFeeController';\n\nexport type GasFeeStateChange = ControllerStateChangeEvent<\n typeof name,\n GasFeeState\n>;\n\nexport type GetGasFeeState = ControllerGetStateAction;\n\nexport type GasFeeControllerActions = GetGasFeeState;\n\nexport type GasFeeControllerEvents = GasFeeStateChange;\n\ntype AllowedActions =\n | NetworkControllerGetStateAction\n | NetworkControllerGetNetworkClientByIdAction\n | NetworkControllerGetEIP1559CompatibilityAction;\n\ntype GasFeeMessenger = RestrictedControllerMessenger<\n typeof name,\n GasFeeControllerActions | AllowedActions,\n GasFeeControllerEvents | NetworkControllerNetworkDidChangeEvent,\n AllowedActions['type'],\n NetworkControllerNetworkDidChangeEvent['type']\n>;\n\nconst defaultState: GasFeeState = {\n gasFeeEstimatesByChainId: {},\n gasFeeEstimates: {},\n estimatedGasFeeTimeBounds: {},\n gasEstimateType: GAS_ESTIMATE_TYPES.NONE,\n nonRPCGasFeeApisDisabled: false,\n};\n\n/**\n * Controller that retrieves gas fee estimate data and polls for updated data on a set interval\n */\nexport class GasFeeController extends StaticIntervalPollingController<\n typeof name,\n GasFeeState,\n GasFeeMessenger\n> {\n private intervalId?: ReturnType;\n\n private readonly intervalDelay;\n\n private readonly pollTokens: Set;\n\n private readonly legacyAPIEndpoint: string;\n\n private readonly EIP1559APIEndpoint: string;\n\n private readonly getCurrentNetworkEIP1559Compatibility;\n\n private readonly getCurrentNetworkLegacyGasAPICompatibility;\n\n private readonly getCurrentAccountEIP1559Compatibility;\n\n private currentChainId;\n\n private ethQuery?: EthQuery;\n\n private readonly clientId?: string;\n\n #getProvider: () => ProviderProxy;\n\n /**\n * Creates a GasFeeController instance.\n *\n * @param options - The controller options.\n * @param options.interval - The time in milliseconds to wait between polls.\n * @param options.messenger - The controller messenger.\n * @param options.state - The initial state.\n * @param options.getCurrentNetworkEIP1559Compatibility - Determines whether or not the current\n * network is EIP-1559 compatible.\n * @param options.getCurrentNetworkLegacyGasAPICompatibility - Determines whether or not the\n * current network is compatible with the legacy gas price API.\n * @param options.getCurrentAccountEIP1559Compatibility - Determines whether or not the current\n * account is EIP-1559 compatible.\n * @param options.getChainId - Returns the current chain ID.\n * @param options.getProvider - Returns a network provider for the current network.\n * @param options.onNetworkDidChange - A function for registering an event handler for the\n * network state change event.\n * @param options.legacyAPIEndpoint - The legacy gas price API URL. This option is primarily for\n * testing purposes.\n * @param options.EIP1559APIEndpoint - The EIP-1559 gas price API URL.\n * @param options.clientId - The client ID used to identify to the gas estimation API who is\n * asking for estimates.\n */\n constructor({\n interval = 15000,\n messenger,\n state,\n getCurrentNetworkEIP1559Compatibility,\n getCurrentAccountEIP1559Compatibility,\n getChainId,\n getCurrentNetworkLegacyGasAPICompatibility,\n getProvider,\n onNetworkDidChange,\n legacyAPIEndpoint = LEGACY_GAS_PRICES_API_URL,\n EIP1559APIEndpoint,\n clientId,\n }: {\n interval?: number;\n messenger: GasFeeMessenger;\n state?: GasFeeState;\n getCurrentNetworkEIP1559Compatibility: () => Promise;\n getCurrentNetworkLegacyGasAPICompatibility: () => boolean;\n getCurrentAccountEIP1559Compatibility?: () => boolean;\n getChainId?: () => Hex;\n getProvider: () => ProviderProxy;\n onNetworkDidChange?: (listener: (state: NetworkState) => void) => void;\n legacyAPIEndpoint?: string;\n // eslint-disable-next-line @typescript-eslint/naming-convention\n EIP1559APIEndpoint: string;\n clientId?: string;\n }) {\n super({\n name,\n metadata,\n messenger,\n state: { ...defaultState, ...state },\n });\n this.intervalDelay = interval;\n this.setIntervalLength(interval);\n this.pollTokens = new Set();\n this.getCurrentNetworkEIP1559Compatibility =\n getCurrentNetworkEIP1559Compatibility;\n this.getCurrentNetworkLegacyGasAPICompatibility =\n getCurrentNetworkLegacyGasAPICompatibility;\n this.getCurrentAccountEIP1559Compatibility =\n getCurrentAccountEIP1559Compatibility;\n this.#getProvider = getProvider;\n this.EIP1559APIEndpoint = EIP1559APIEndpoint;\n this.legacyAPIEndpoint = legacyAPIEndpoint;\n this.clientId = clientId;\n\n this.ethQuery = new EthQuery(this.#getProvider());\n\n if (onNetworkDidChange && getChainId) {\n this.currentChainId = getChainId();\n onNetworkDidChange(async (networkControllerState) => {\n await this.#onNetworkControllerDidChange(networkControllerState);\n });\n } else {\n this.currentChainId = this.messagingSystem.call(\n 'NetworkController:getState',\n ).providerConfig.chainId;\n this.messagingSystem.subscribe(\n 'NetworkController:networkDidChange',\n async (networkControllerState) => {\n await this.#onNetworkControllerDidChange(networkControllerState);\n },\n );\n }\n }\n\n async resetPolling() {\n if (this.pollTokens.size !== 0) {\n const tokens = Array.from(this.pollTokens);\n this.stopPolling();\n await this.getGasFeeEstimatesAndStartPolling(tokens[0]);\n tokens.slice(1).forEach((token) => {\n this.pollTokens.add(token);\n });\n }\n }\n\n async fetchGasFeeEstimates(options?: FetchGasFeeEstimateOptions) {\n return await this._fetchGasFeeEstimateData(options);\n }\n\n async getGasFeeEstimatesAndStartPolling(\n pollToken: string | undefined,\n ): Promise {\n const _pollToken = pollToken || random();\n\n this.pollTokens.add(_pollToken);\n\n if (this.pollTokens.size === 1) {\n await this._fetchGasFeeEstimateData();\n this._poll();\n }\n\n return _pollToken;\n }\n\n /**\n * Gets and sets gasFeeEstimates in state.\n *\n * @param options - The gas fee estimate options.\n * @param options.shouldUpdateState - Determines whether the state should be updated with the\n * updated gas estimates.\n * @returns The gas fee estimates.\n */\n async _fetchGasFeeEstimateData(\n options: FetchGasFeeEstimateOptions = {},\n ): Promise {\n const { shouldUpdateState = true, networkClientId } = options;\n\n let ethQuery,\n isEIP1559Compatible,\n isLegacyGasAPICompatible,\n decimalChainId: number;\n\n if (networkClientId !== undefined) {\n const networkClient = this.messagingSystem.call(\n 'NetworkController:getNetworkClientById',\n networkClientId,\n );\n isLegacyGasAPICompatible = networkClient.configuration.chainId === '0x38';\n\n decimalChainId = convertHexToDecimal(networkClient.configuration.chainId);\n\n try {\n const result = await this.messagingSystem.call(\n 'NetworkController:getEIP1559Compatibility',\n networkClientId,\n );\n isEIP1559Compatible = result || false;\n } catch {\n isEIP1559Compatible = false;\n }\n ethQuery = new EthQuery(networkClient.provider);\n }\n\n ethQuery ??= this.ethQuery;\n\n isLegacyGasAPICompatible ??=\n this.getCurrentNetworkLegacyGasAPICompatibility();\n\n decimalChainId ??= convertHexToDecimal(this.currentChainId);\n\n try {\n isEIP1559Compatible ??= await this.getEIP1559Compatibility();\n } catch (e) {\n console.error(e);\n isEIP1559Compatible ??= false;\n }\n\n const gasFeeCalculations = await determineGasFeeCalculations({\n isEIP1559Compatible,\n isLegacyGasAPICompatible,\n fetchGasEstimates,\n fetchGasEstimatesUrl: this.EIP1559APIEndpoint.replace(\n '',\n `${decimalChainId}`,\n ),\n fetchLegacyGasPriceEstimates,\n fetchLegacyGasPriceEstimatesUrl: this.legacyAPIEndpoint.replace(\n '',\n `${decimalChainId}`,\n ),\n fetchEthGasPriceEstimate,\n calculateTimeEstimate,\n clientId: this.clientId,\n ethQuery,\n nonRPCGasFeeApisDisabled: this.state.nonRPCGasFeeApisDisabled,\n });\n\n if (shouldUpdateState) {\n const chainId = toHex(decimalChainId);\n this.update((state) => {\n if (this.currentChainId === chainId) {\n state.gasFeeEstimates = gasFeeCalculations.gasFeeEstimates;\n state.estimatedGasFeeTimeBounds =\n gasFeeCalculations.estimatedGasFeeTimeBounds;\n state.gasEstimateType = gasFeeCalculations.gasEstimateType;\n }\n state.gasFeeEstimatesByChainId ??= {};\n state.gasFeeEstimatesByChainId[chainId] = {\n gasFeeEstimates: gasFeeCalculations.gasFeeEstimates,\n estimatedGasFeeTimeBounds:\n gasFeeCalculations.estimatedGasFeeTimeBounds,\n gasEstimateType: gasFeeCalculations.gasEstimateType,\n } as SingleChainGasFeeState;\n });\n }\n\n return gasFeeCalculations;\n }\n\n /**\n * Remove the poll token, and stop polling if the set of poll tokens is empty.\n *\n * @param pollToken - The poll token to disconnect.\n */\n disconnectPoller(pollToken: string) {\n this.pollTokens.delete(pollToken);\n if (this.pollTokens.size === 0) {\n this.stopPolling();\n }\n }\n\n stopPolling() {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n }\n this.pollTokens.clear();\n this.resetState();\n }\n\n /**\n * Prepare to discard this controller.\n *\n * This stops any active polling.\n */\n override destroy() {\n super.destroy();\n this.stopPolling();\n }\n\n private _poll() {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n }\n\n this.intervalId = setInterval(async () => {\n await safelyExecute(() => this._fetchGasFeeEstimateData());\n }, this.intervalDelay);\n }\n\n /**\n * Fetching token list from the Token Service API.\n *\n * @private\n * @param networkClientId - The ID of the network client triggering the fetch.\n * @returns A promise that resolves when this operation completes.\n */\n async _executePoll(networkClientId: string): Promise {\n await this._fetchGasFeeEstimateData({ networkClientId });\n }\n\n private resetState() {\n this.update(() => {\n return defaultState;\n });\n }\n\n private async getEIP1559Compatibility() {\n const currentNetworkIsEIP1559Compatible =\n await this.getCurrentNetworkEIP1559Compatibility();\n const currentAccountIsEIP1559Compatible =\n this.getCurrentAccountEIP1559Compatibility?.() ?? true;\n\n return (\n currentNetworkIsEIP1559Compatible && currentAccountIsEIP1559Compatible\n );\n }\n\n getTimeEstimate(\n maxPriorityFeePerGas: string,\n maxFeePerGas: string,\n ): EstimatedGasFeeTimeBounds | Record {\n if (\n !this.state.gasFeeEstimates ||\n this.state.gasEstimateType !== GAS_ESTIMATE_TYPES.FEE_MARKET\n ) {\n return {};\n }\n return calculateTimeEstimate(\n maxPriorityFeePerGas,\n maxFeePerGas,\n this.state.gasFeeEstimates,\n );\n }\n\n async #onNetworkControllerDidChange(networkControllerState: NetworkState) {\n const newChainId = networkControllerState.providerConfig.chainId;\n\n if (newChainId !== this.currentChainId) {\n this.ethQuery = new EthQuery(this.#getProvider());\n await this.resetPolling();\n\n this.currentChainId = newChainId;\n }\n }\n\n enableNonRPCGasFeeApis() {\n this.update((state) => {\n state.nonRPCGasFeeApisDisabled = false;\n });\n }\n\n disableNonRPCGasFeeApis() {\n this.update((state) => {\n state.nonRPCGasFeeApisDisabled = true;\n });\n }\n}\n\nexport default GasFeeController;\n","import type {\n EstimatedGasFeeTimeBounds,\n EthGasPriceEstimate,\n GasFeeEstimates,\n GasFeeState as GasFeeCalculations,\n LegacyGasPriceEstimate,\n} from './GasFeeController';\nimport { GAS_ESTIMATE_TYPES } from './GasFeeController';\n\ntype DetermineGasFeeCalculationsRequest = {\n isEIP1559Compatible: boolean;\n isLegacyGasAPICompatible: boolean;\n fetchGasEstimates: (\n url: string,\n clientId?: string,\n ) => Promise;\n fetchGasEstimatesUrl: string;\n fetchLegacyGasPriceEstimates: (\n url: string,\n clientId?: string,\n ) => Promise;\n fetchLegacyGasPriceEstimatesUrl: string;\n // TODO: Replace `any` with type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n fetchEthGasPriceEstimate: (ethQuery: any) => Promise;\n calculateTimeEstimate: (\n maxPriorityFeePerGas: string,\n maxFeePerGas: string,\n gasFeeEstimates: GasFeeEstimates,\n ) => EstimatedGasFeeTimeBounds;\n clientId: string | undefined;\n // TODO: Replace `any` with type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ethQuery: any;\n nonRPCGasFeeApisDisabled?: boolean;\n};\n\n/**\n * Obtains a set of max base and priority fee estimates along with time estimates so that we\n * can present them to users when they are sending transactions or making swaps.\n *\n * @param args - The arguments.\n * @param args.isEIP1559Compatible - Governs whether or not we can use an EIP-1559-only method to\n * produce estimates.\n * @param args.isLegacyGasAPICompatible - Governs whether or not we can use a non-EIP-1559 method to\n * produce estimates (for instance, testnets do not support estimates altogether).\n * @param args.fetchGasEstimates - A function that fetches gas estimates using an EIP-1559-specific\n * API.\n * @param args.fetchGasEstimatesUrl - The URL for the API we can use to obtain EIP-1559-specific\n * estimates.\n * @param args.fetchLegacyGasPriceEstimates - A function that fetches gas estimates using an\n * non-EIP-1559-specific API.\n * @param args.fetchLegacyGasPriceEstimatesUrl - The URL for the API we can use to obtain\n * non-EIP-1559-specific estimates.\n * @param args.fetchEthGasPriceEstimate - A function that fetches gas estimates using\n * `eth_gasPrice`.\n * @param args.calculateTimeEstimate - A function that determine time estimate bounds.\n * @param args.clientId - An identifier that an API can use to know who is asking for estimates.\n * @param args.ethQuery - An EthQuery instance we can use to talk to Ethereum directly.\n * @param args.nonRPCGasFeeApisDisabled - Whether to disable requests to the legacyAPIEndpoint and the EIP1559APIEndpoint\n * @returns The gas fee calculations.\n */\nexport default async function determineGasFeeCalculations(\n args: DetermineGasFeeCalculationsRequest,\n): Promise {\n try {\n return await getEstimatesUsingFallbacks(args);\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(\n `Gas fee/price estimation failed. Message: ${error.message}`,\n );\n }\n\n throw error;\n }\n}\n\n/**\n * Retrieve the gas fee estimates using a series of fallback mechanisms.\n * @param request - The request object.\n * @returns The gas fee estimates.\n */\nasync function getEstimatesUsingFallbacks(\n request: DetermineGasFeeCalculationsRequest,\n): Promise {\n const {\n isEIP1559Compatible,\n isLegacyGasAPICompatible,\n nonRPCGasFeeApisDisabled,\n } = request;\n\n try {\n if (isEIP1559Compatible && !nonRPCGasFeeApisDisabled) {\n return await getEstimatesUsingFeeMarketEndpoint(request);\n }\n\n if (isLegacyGasAPICompatible && !nonRPCGasFeeApisDisabled) {\n return await getEstimatesUsingLegacyEndpoint(request);\n }\n\n throw new Error('Main gas fee/price estimation failed. Use fallback');\n } catch {\n return await getEstimatesUsingProvider(request);\n }\n}\n\n/**\n * Retrieve gas fee estimates using the EIP-1559 endpoint of the gas API.\n * @param request - The request object.\n * @returns The gas fee estimates.\n */\nasync function getEstimatesUsingFeeMarketEndpoint(\n request: DetermineGasFeeCalculationsRequest,\n): Promise {\n const {\n fetchGasEstimates,\n fetchGasEstimatesUrl,\n clientId,\n calculateTimeEstimate,\n } = request;\n\n const estimates = await fetchGasEstimates(fetchGasEstimatesUrl, clientId);\n\n const { suggestedMaxPriorityFeePerGas, suggestedMaxFeePerGas } =\n estimates.medium;\n\n const estimatedGasFeeTimeBounds = calculateTimeEstimate(\n suggestedMaxPriorityFeePerGas,\n suggestedMaxFeePerGas,\n estimates,\n );\n\n return {\n gasFeeEstimates: estimates,\n estimatedGasFeeTimeBounds,\n gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET,\n };\n}\n\n/**\n * Retrieve gas fee estimates using the legacy endpoint of the gas API.\n * @param request - The request object.\n * @returns The gas fee estimates.\n */\nasync function getEstimatesUsingLegacyEndpoint(\n request: DetermineGasFeeCalculationsRequest,\n): Promise {\n const {\n fetchLegacyGasPriceEstimates,\n fetchLegacyGasPriceEstimatesUrl,\n clientId,\n } = request;\n\n const estimates = await fetchLegacyGasPriceEstimates(\n fetchLegacyGasPriceEstimatesUrl,\n clientId,\n );\n\n return {\n gasFeeEstimates: estimates,\n estimatedGasFeeTimeBounds: {},\n gasEstimateType: GAS_ESTIMATE_TYPES.LEGACY,\n };\n}\n\n/**\n * Retrieve gas fee estimates using an `eth_gasPrice` call to the RPC provider.\n * @param request - The request object.\n * @returns The gas fee estimates.\n */\nasync function getEstimatesUsingProvider(\n request: DetermineGasFeeCalculationsRequest,\n): Promise {\n const { ethQuery, fetchEthGasPriceEstimate } = request;\n\n const estimates = await fetchEthGasPriceEstimate(ethQuery);\n\n return {\n gasFeeEstimates: estimates,\n estimatedGasFeeTimeBounds: {},\n gasEstimateType: GAS_ESTIMATE_TYPES.ETH_GASPRICE,\n };\n}\n"],"mappings":";;;;;;;;;;;;AAKA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,OAAO,cAAc;AAUrB,SAAS,uCAAuC;AAEhD,SAAS,MAAM,cAAc;AAUtB,IAAM,4BAA4B;AA0BlC,IAAM,qBAAqB;AAAA,EAChC,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,cAAc;AAAA,EACd,MAAM;AACR;AAiGA,IAAM,WAAW;AAAA,EACf,0BAA0B;AAAA,IACxB,SAAS;AAAA,IACT,WAAW;AAAA,EACb;AAAA,EACA,iBAAiB,EAAE,SAAS,MAAM,WAAW,MAAM;AAAA,EACnD,2BAA2B,EAAE,SAAS,MAAM,WAAW,MAAM;AAAA,EAC7D,iBAAiB,EAAE,SAAS,MAAM,WAAW,MAAM;AAAA,EACnD,0BAA0B,EAAE,SAAS,MAAM,WAAW,MAAM;AAC9D;AAqDA,IAAM,OAAO;AA0Bb,IAAM,eAA4B;AAAA,EAChC,0BAA0B,CAAC;AAAA,EAC3B,iBAAiB,CAAC;AAAA,EAClB,2BAA2B,CAAC;AAAA,EAC5B,iBAAiB,mBAAmB;AAAA,EACpC,0BAA0B;AAC5B;AA9PA;AAmQO,IAAM,mBAAN,cAA+B,gCAIpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgDA,YAAY;AAAA,IACV,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,oBAAoB;AAAA,IACpB;AAAA,IACA;AAAA,EACF,GAcG;AACD,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,EAAE,GAAG,cAAc,GAAG,MAAM;AAAA,IACrC,CAAC;AAqPH,uBAAM;AA/SN;AA2DE,SAAK,gBAAgB;AACrB,SAAK,kBAAkB,QAAQ;AAC/B,SAAK,aAAa,oBAAI,IAAI;AAC1B,SAAK,wCACH;AACF,SAAK,6CACH;AACF,SAAK,wCACH;AACF,uBAAK,cAAe;AACpB,SAAK,qBAAqB;AAC1B,SAAK,oBAAoB;AACzB,SAAK,WAAW;AAEhB,SAAK,WAAW,IAAI,SAAS,mBAAK,cAAL,UAAmB;AAEhD,QAAI,sBAAsB,YAAY;AACpC,WAAK,iBAAiB,WAAW;AACjC,yBAAmB,OAAO,2BAA2B;AACnD,cAAM,sBAAK,gEAAL,WAAmC;AAAA,MAC3C,CAAC;AAAA,IACH,OAAO;AACL,WAAK,iBAAiB,KAAK,gBAAgB;AAAA,QACzC;AAAA,MACF,EAAE,eAAe;AACjB,WAAK,gBAAgB;AAAA,QACnB;AAAA,QACA,OAAO,2BAA2B;AAChC,gBAAM,sBAAK,gEAAL,WAAmC;AAAA,QAC3C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,eAAe;AACnB,QAAI,KAAK,WAAW,SAAS,GAAG;AAC9B,YAAM,SAAS,MAAM,KAAK,KAAK,UAAU;AACzC,WAAK,YAAY;AACjB,YAAM,KAAK,kCAAkC,OAAO,CAAC,CAAC;AACtD,aAAO,MAAM,CAAC,EAAE,QAAQ,CAAC,UAAU;AACjC,aAAK,WAAW,IAAI,KAAK;AAAA,MAC3B,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,MAAM,qBAAqB,SAAsC;AAC/D,WAAO,MAAM,KAAK,yBAAyB,OAAO;AAAA,EACpD;AAAA,EAEA,MAAM,kCACJ,WACiB;AACjB,UAAM,aAAa,aAAa,OAAO;AAEvC,SAAK,WAAW,IAAI,UAAU;AAE9B,QAAI,KAAK,WAAW,SAAS,GAAG;AAC9B,YAAM,KAAK,yBAAyB;AACpC,WAAK,MAAM;AAAA,IACb;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,yBACJ,UAAsC,CAAC,GACjB;AACtB,UAAM,EAAE,oBAAoB,MAAM,gBAAgB,IAAI;AAEtD,QAAI,UACF,qBACA,0BACA;AAEF,QAAI,oBAAoB,QAAW;AACjC,YAAM,gBAAgB,KAAK,gBAAgB;AAAA,QACzC;AAAA,QACA;AAAA,MACF;AACA,iCAA2B,cAAc,cAAc,YAAY;AAEnE,uBAAiB,oBAAoB,cAAc,cAAc,OAAO;AAExE,UAAI;AACF,cAAM,SAAS,MAAM,KAAK,gBAAgB;AAAA,UACxC;AAAA,UACA;AAAA,QACF;AACA,8BAAsB,UAAU;AAAA,MAClC,QAAQ;AACN,8BAAsB;AAAA,MACxB;AACA,iBAAW,IAAI,SAAS,cAAc,QAAQ;AAAA,IAChD;AAEA,4BAAa,KAAK;AAElB,4DACE,KAAK,2CAA2C;AAElD,wCAAmB,oBAAoB,KAAK,cAAc;AAE1D,QAAI;AACF,oDAAwB,MAAM,KAAK,wBAAwB;AAAA,IAC7D,SAAS,GAAG;AACV,cAAQ,MAAM,CAAC;AACf,oDAAwB;AAAA,IAC1B;AAEA,UAAM,qBAAqB,MAAM,4BAA4B;AAAA,MAC3D;AAAA,MACA;AAAA,MACA;AAAA,MACA,sBAAsB,KAAK,mBAAmB;AAAA,QAC5C;AAAA,QACA,GAAG,cAAc;AAAA,MACnB;AAAA,MACA;AAAA,MACA,iCAAiC,KAAK,kBAAkB;AAAA,QACtD;AAAA,QACA,GAAG,cAAc;AAAA,MACnB;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU,KAAK;AAAA,MACf;AAAA,MACA,0BAA0B,KAAK,MAAM;AAAA,IACvC,CAAC;AAED,QAAI,mBAAmB;AACrB,YAAM,UAAU,MAAM,cAAc;AACpC,WAAK,OAAO,CAAC,UAAU;AACrB,YAAI,KAAK,mBAAmB,SAAS;AACnC,gBAAM,kBAAkB,mBAAmB;AAC3C,gBAAM,4BACJ,mBAAmB;AACrB,gBAAM,kBAAkB,mBAAmB;AAAA,QAC7C;AACA,cAAM,6BAAN,MAAM,2BAA6B,CAAC;AACpC,cAAM,yBAAyB,OAAO,IAAI;AAAA,UACxC,iBAAiB,mBAAmB;AAAA,UACpC,2BACE,mBAAmB;AAAA,UACrB,iBAAiB,mBAAmB;AAAA,QACtC;AAAA,MACF,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBAAiB,WAAmB;AAClC,SAAK,WAAW,OAAO,SAAS;AAChC,QAAI,KAAK,WAAW,SAAS,GAAG;AAC9B,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA,EAEA,cAAc;AACZ,QAAI,KAAK,YAAY;AACnB,oBAAc,KAAK,UAAU;AAAA,IAC/B;AACA,SAAK,WAAW,MAAM;AACtB,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOS,UAAU;AACjB,UAAM,QAAQ;AACd,SAAK,YAAY;AAAA,EACnB;AAAA,EAEQ,QAAQ;AACd,QAAI,KAAK,YAAY;AACnB,oBAAc,KAAK,UAAU;AAAA,IAC/B;AAEA,SAAK,aAAa,YAAY,YAAY;AACxC,YAAM,cAAc,MAAM,KAAK,yBAAyB,CAAC;AAAA,IAC3D,GAAG,KAAK,aAAa;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,aAAa,iBAAwC;AACzD,UAAM,KAAK,yBAAyB,EAAE,gBAAgB,CAAC;AAAA,EACzD;AAAA,EAEQ,aAAa;AACnB,SAAK,OAAO,MAAM;AAChB,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,0BAA0B;AACtC,UAAM,oCACJ,MAAM,KAAK,sCAAsC;AACnD,UAAM,oCACJ,KAAK,wCAAwC,KAAK;AAEpD,WACE,qCAAqC;AAAA,EAEzC;AAAA,EAEA,gBACE,sBACA,cACmD;AACnD,QACE,CAAC,KAAK,MAAM,mBACZ,KAAK,MAAM,oBAAoB,mBAAmB,YAClD;AACA,aAAO,CAAC;AAAA,IACV;AACA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,KAAK,MAAM;AAAA,IACb;AAAA,EACF;AAAA,EAaA,yBAAyB;AACvB,SAAK,OAAO,CAAC,UAAU;AACrB,YAAM,2BAA2B;AAAA,IACnC,CAAC;AAAA,EACH;AAAA,EAEA,0BAA0B;AACxB,SAAK,OAAO,CAAC,UAAU;AACrB,YAAM,2BAA2B;AAAA,IACnC,CAAC;AAAA,EACH;AACF;AArUE;AA+SM;AAAA,kCAA6B,eAAC,wBAAsC;AACxE,QAAM,aAAa,uBAAuB,eAAe;AAEzD,MAAI,eAAe,KAAK,gBAAgB;AACtC,SAAK,WAAW,IAAI,SAAS,mBAAK,cAAL,UAAmB;AAChD,UAAM,KAAK,aAAa;AAExB,SAAK,iBAAiB;AAAA,EACxB;AACF;AAeF,IAAO,2BAAQ;;;ACviBf,eAAO,4BACL,MAC6B;AAC7B,MAAI;AACF,WAAO,MAAM,2BAA2B,IAAI;AAAA,EAC9C,SAAS,OAAO;AACd,QAAI,iBAAiB,OAAO;AAC1B,YAAM,IAAI;AAAA,QACR,6CAA6C,MAAM,OAAO;AAAA,MAC5D;AAAA,IACF;AAEA,UAAM;AAAA,EACR;AACF;AAOA,eAAe,2BACb,SAC6B;AAC7B,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,MAAI;AACF,QAAI,uBAAuB,CAAC,0BAA0B;AACpD,aAAO,MAAM,mCAAmC,OAAO;AAAA,IACzD;AAEA,QAAI,4BAA4B,CAAC,0BAA0B;AACzD,aAAO,MAAM,gCAAgC,OAAO;AAAA,IACtD;AAEA,UAAM,IAAI,MAAM,oDAAoD;AAAA,EACtE,QAAQ;AACN,WAAO,MAAM,0BAA0B,OAAO;AAAA,EAChD;AACF;AAOA,eAAe,mCACb,SAC6B;AAC7B,QAAM;AAAA,IACJ,mBAAAA;AAAA,IACA;AAAA,IACA;AAAA,IACA,uBAAAC;AAAA,EACF,IAAI;AAEJ,QAAM,YAAY,MAAMD,mBAAkB,sBAAsB,QAAQ;AAExE,QAAM,EAAE,+BAA+B,sBAAsB,IAC3D,UAAU;AAEZ,QAAM,4BAA4BC;AAAA,IAChC;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,SAAO;AAAA,IACL,iBAAiB;AAAA,IACjB;AAAA,IACA,iBAAiB,mBAAmB;AAAA,EACtC;AACF;AAOA,eAAe,gCACb,SAC6B;AAC7B,QAAM;AAAA,IACJ,8BAAAC;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,QAAM,YAAY,MAAMA;AAAA,IACtB;AAAA,IACA;AAAA,EACF;AAEA,SAAO;AAAA,IACL,iBAAiB;AAAA,IACjB,2BAA2B,CAAC;AAAA,IAC5B,iBAAiB,mBAAmB;AAAA,EACtC;AACF;AAOA,eAAe,0BACb,SAC6B;AAC7B,QAAM,EAAE,UAAU,0BAAAC,0BAAyB,IAAI;AAE/C,QAAM,YAAY,MAAMA,0BAAyB,QAAQ;AAEzD,SAAO;AAAA,IACL,iBAAiB;AAAA,IACjB,2BAA2B,CAAC;AAAA,IAC5B,iBAAiB,mBAAmB;AAAA,EACtC;AACF;","names":["fetchGasEstimates","calculateTimeEstimate","fetchLegacyGasPriceEstimates","fetchEthGasPriceEstimate"]} +\ No newline at end of file +diff --git a/dist/chunk-BEVZS3YV.mjs b/dist/chunk-BEVZS3YV.mjs +deleted file mode 100644 +index 3be5db0695ee6a2c71e64ed76c43a1f30cc9e5ac..0000000000000000000000000000000000000000 +--- a/dist/chunk-BEVZS3YV.mjs ++++ /dev/null +@@ -1,396 +0,0 @@ +-import { +- __privateAdd, +- __privateGet, +- __privateMethod, +- __privateSet, +- calculateTimeEstimate, +- fetchEthGasPriceEstimate, +- fetchGasEstimates, +- fetchLegacyGasPriceEstimates +-} from "./chunk-KORLXV32.mjs"; +- +-// src/GasFeeController.ts +-import { +- convertHexToDecimal, +- safelyExecute, +- toHex +-} from "@metamask/controller-utils"; +-import EthQuery from "@metamask/eth-query"; +-import { StaticIntervalPollingController } from "@metamask/polling-controller"; +-import { v1 as random } from "uuid"; +-var GAS_API_BASE_URL = "https://gas.api.infura.io"; +-var GAS_ESTIMATE_TYPES = { +- FEE_MARKET: "fee-market", +- LEGACY: "legacy", +- ETH_GASPRICE: "eth_gasPrice", +- NONE: "none" +-}; +-var metadata = { +- gasFeeEstimatesByChainId: { +- persist: true, +- anonymous: false +- }, +- gasFeeEstimates: { persist: true, anonymous: false }, +- estimatedGasFeeTimeBounds: { persist: true, anonymous: false }, +- gasEstimateType: { persist: true, anonymous: false }, +- nonRPCGasFeeApisDisabled: { persist: true, anonymous: false } +-}; +-var name = "GasFeeController"; +-var defaultState = { +- gasFeeEstimatesByChainId: {}, +- gasFeeEstimates: {}, +- estimatedGasFeeTimeBounds: {}, +- gasEstimateType: GAS_ESTIMATE_TYPES.NONE, +- nonRPCGasFeeApisDisabled: false +-}; +-var _getProvider, _onNetworkControllerDidChange, onNetworkControllerDidChange_fn; +-var GasFeeController = class extends StaticIntervalPollingController { +- /** +- * Creates a GasFeeController instance. +- * +- * @param options - The controller options. +- * @param options.interval - The time in milliseconds to wait between polls. +- * @param options.messenger - The controller messenger. +- * @param options.state - The initial state. +- * @param options.getCurrentNetworkEIP1559Compatibility - Determines whether or not the current +- * network is EIP-1559 compatible. +- * @param options.getCurrentNetworkLegacyGasAPICompatibility - Determines whether or not the +- * current network is compatible with the legacy gas price API. +- * @param options.getCurrentAccountEIP1559Compatibility - Determines whether or not the current +- * account is EIP-1559 compatible. +- * @param options.getChainId - Returns the current chain ID. +- * @param options.getProvider - Returns a network provider for the current network. +- * @param options.onNetworkDidChange - A function for registering an event handler for the +- * network state change event. +- * @param options.clientId - The client ID used to identify to the gas estimation API who is +- * asking for estimates. +- * @param options.infuraAPIKey - The Infura API key used for infura API requests. +- */ +- constructor({ +- interval = 15e3, +- messenger, +- state, +- getCurrentNetworkEIP1559Compatibility, +- getCurrentAccountEIP1559Compatibility, +- getChainId, +- getCurrentNetworkLegacyGasAPICompatibility, +- getProvider, +- onNetworkDidChange, +- clientId, +- infuraAPIKey +- }) { +- super({ +- name, +- metadata, +- messenger, +- state: { ...defaultState, ...state } +- }); +- __privateAdd(this, _onNetworkControllerDidChange); +- __privateAdd(this, _getProvider, void 0); +- this.intervalDelay = interval; +- this.setIntervalLength(interval); +- this.pollTokens = /* @__PURE__ */ new Set(); +- this.getCurrentNetworkEIP1559Compatibility = getCurrentNetworkEIP1559Compatibility; +- this.getCurrentNetworkLegacyGasAPICompatibility = getCurrentNetworkLegacyGasAPICompatibility; +- this.getCurrentAccountEIP1559Compatibility = getCurrentAccountEIP1559Compatibility; +- __privateSet(this, _getProvider, getProvider); +- this.EIP1559APIEndpoint = `${GAS_API_BASE_URL}/networks//suggestedGasFees`; +- this.legacyAPIEndpoint = `${GAS_API_BASE_URL}/networks//gasPrices`; +- this.clientId = clientId; +- this.infuraAPIKey = infuraAPIKey; +- this.ethQuery = new EthQuery(__privateGet(this, _getProvider).call(this)); +- if (onNetworkDidChange && getChainId) { +- this.currentChainId = getChainId(); +- onNetworkDidChange(async (networkControllerState) => { +- await __privateMethod(this, _onNetworkControllerDidChange, onNetworkControllerDidChange_fn).call(this, networkControllerState); +- }); +- } else { +- this.currentChainId = this.messagingSystem.call( +- "NetworkController:getState" +- ).providerConfig.chainId; +- this.messagingSystem.subscribe( +- "NetworkController:networkDidChange", +- async (networkControllerState) => { +- await __privateMethod(this, _onNetworkControllerDidChange, onNetworkControllerDidChange_fn).call(this, networkControllerState); +- } +- ); +- } +- } +- async resetPolling() { +- if (this.pollTokens.size !== 0) { +- const tokens = Array.from(this.pollTokens); +- this.stopPolling(); +- await this.getGasFeeEstimatesAndStartPolling(tokens[0]); +- tokens.slice(1).forEach((token) => { +- this.pollTokens.add(token); +- }); +- } +- } +- async fetchGasFeeEstimates(options) { +- return await this._fetchGasFeeEstimateData(options); +- } +- async getGasFeeEstimatesAndStartPolling(pollToken) { +- const _pollToken = pollToken || random(); +- this.pollTokens.add(_pollToken); +- if (this.pollTokens.size === 1) { +- await this._fetchGasFeeEstimateData(); +- this._poll(); +- } +- return _pollToken; +- } +- /** +- * Gets and sets gasFeeEstimates in state. +- * +- * @param options - The gas fee estimate options. +- * @param options.shouldUpdateState - Determines whether the state should be updated with the +- * updated gas estimates. +- * @returns The gas fee estimates. +- */ +- async _fetchGasFeeEstimateData(options = {}) { +- const { shouldUpdateState = true, networkClientId } = options; +- let ethQuery, isEIP1559Compatible, isLegacyGasAPICompatible, decimalChainId; +- if (networkClientId !== void 0) { +- const networkClient = this.messagingSystem.call( +- "NetworkController:getNetworkClientById", +- networkClientId +- ); +- isLegacyGasAPICompatible = networkClient.configuration.chainId === "0x38"; +- decimalChainId = convertHexToDecimal(networkClient.configuration.chainId); +- try { +- const result = await this.messagingSystem.call( +- "NetworkController:getEIP1559Compatibility", +- networkClientId +- ); +- isEIP1559Compatible = result || false; +- } catch { +- isEIP1559Compatible = false; +- } +- ethQuery = new EthQuery(networkClient.provider); +- } +- ethQuery ?? (ethQuery = this.ethQuery); +- isLegacyGasAPICompatible ?? (isLegacyGasAPICompatible = this.getCurrentNetworkLegacyGasAPICompatibility()); +- decimalChainId ?? (decimalChainId = convertHexToDecimal(this.currentChainId)); +- try { +- isEIP1559Compatible ?? (isEIP1559Compatible = await this.getEIP1559Compatibility()); +- } catch (e) { +- console.error(e); +- isEIP1559Compatible ?? (isEIP1559Compatible = false); +- } +- const gasFeeCalculations = await determineGasFeeCalculations({ +- isEIP1559Compatible, +- isLegacyGasAPICompatible, +- fetchGasEstimates, +- fetchGasEstimatesUrl: this.EIP1559APIEndpoint.replace( +- "", +- `${decimalChainId}` +- ), +- fetchLegacyGasPriceEstimates, +- fetchLegacyGasPriceEstimatesUrl: this.legacyAPIEndpoint.replace( +- "", +- `${decimalChainId}` +- ), +- fetchEthGasPriceEstimate, +- calculateTimeEstimate, +- clientId: this.clientId, +- ethQuery, +- infuraAPIKey: this.infuraAPIKey, +- nonRPCGasFeeApisDisabled: this.state.nonRPCGasFeeApisDisabled +- }); +- if (shouldUpdateState) { +- const chainId = toHex(decimalChainId); +- this.update((state) => { +- if (this.currentChainId === chainId) { +- state.gasFeeEstimates = gasFeeCalculations.gasFeeEstimates; +- state.estimatedGasFeeTimeBounds = gasFeeCalculations.estimatedGasFeeTimeBounds; +- state.gasEstimateType = gasFeeCalculations.gasEstimateType; +- } +- state.gasFeeEstimatesByChainId ?? (state.gasFeeEstimatesByChainId = {}); +- state.gasFeeEstimatesByChainId[chainId] = { +- gasFeeEstimates: gasFeeCalculations.gasFeeEstimates, +- estimatedGasFeeTimeBounds: gasFeeCalculations.estimatedGasFeeTimeBounds, +- gasEstimateType: gasFeeCalculations.gasEstimateType +- }; +- }); +- } +- return gasFeeCalculations; +- } +- /** +- * Remove the poll token, and stop polling if the set of poll tokens is empty. +- * +- * @param pollToken - The poll token to disconnect. +- */ +- disconnectPoller(pollToken) { +- this.pollTokens.delete(pollToken); +- if (this.pollTokens.size === 0) { +- this.stopPolling(); +- } +- } +- stopPolling() { +- if (this.intervalId) { +- clearInterval(this.intervalId); +- } +- this.pollTokens.clear(); +- this.resetState(); +- } +- /** +- * Prepare to discard this controller. +- * +- * This stops any active polling. +- */ +- destroy() { +- super.destroy(); +- this.stopPolling(); +- } +- _poll() { +- if (this.intervalId) { +- clearInterval(this.intervalId); +- } +- this.intervalId = setInterval(async () => { +- await safelyExecute(() => this._fetchGasFeeEstimateData()); +- }, this.intervalDelay); +- } +- /** +- * Fetching token list from the Token Service API. +- * +- * @private +- * @param networkClientId - The ID of the network client triggering the fetch. +- * @returns A promise that resolves when this operation completes. +- */ +- async _executePoll(networkClientId) { +- await this._fetchGasFeeEstimateData({ networkClientId }); +- } +- resetState() { +- this.update(() => { +- return defaultState; +- }); +- } +- async getEIP1559Compatibility() { +- const currentNetworkIsEIP1559Compatible = await this.getCurrentNetworkEIP1559Compatibility(); +- const currentAccountIsEIP1559Compatible = this.getCurrentAccountEIP1559Compatibility?.() ?? true; +- return currentNetworkIsEIP1559Compatible && currentAccountIsEIP1559Compatible; +- } +- getTimeEstimate(maxPriorityFeePerGas, maxFeePerGas) { +- if (!this.state.gasFeeEstimates || this.state.gasEstimateType !== GAS_ESTIMATE_TYPES.FEE_MARKET) { +- return {}; +- } +- return calculateTimeEstimate( +- maxPriorityFeePerGas, +- maxFeePerGas, +- this.state.gasFeeEstimates +- ); +- } +- enableNonRPCGasFeeApis() { +- this.update((state) => { +- state.nonRPCGasFeeApisDisabled = false; +- }); +- } +- disableNonRPCGasFeeApis() { +- this.update((state) => { +- state.nonRPCGasFeeApisDisabled = true; +- }); +- } +-}; +-_getProvider = new WeakMap(); +-_onNetworkControllerDidChange = new WeakSet(); +-onNetworkControllerDidChange_fn = async function(networkControllerState) { +- const newChainId = networkControllerState.providerConfig.chainId; +- if (newChainId !== this.currentChainId) { +- this.ethQuery = new EthQuery(__privateGet(this, _getProvider).call(this)); +- await this.resetPolling(); +- this.currentChainId = newChainId; +- } +-}; +-var GasFeeController_default = GasFeeController; +- +-// src/determineGasFeeCalculations.ts +-async function determineGasFeeCalculations(args) { +- try { +- return await getEstimatesUsingFallbacks(args); +- } catch (error) { +- if (error instanceof Error) { +- throw new Error( +- `Gas fee/price estimation failed. Message: ${error.message}` +- ); +- } +- throw error; +- } +-} +-async function getEstimatesUsingFallbacks(request) { +- const { +- isEIP1559Compatible, +- isLegacyGasAPICompatible, +- nonRPCGasFeeApisDisabled +- } = request; +- try { +- if (isEIP1559Compatible && !nonRPCGasFeeApisDisabled) { +- return await getEstimatesUsingFeeMarketEndpoint(request); +- } +- if (isLegacyGasAPICompatible && !nonRPCGasFeeApisDisabled) { +- return await getEstimatesUsingLegacyEndpoint(request); +- } +- throw new Error("Main gas fee/price estimation failed. Use fallback"); +- } catch { +- return await getEstimatesUsingProvider(request); +- } +-} +-async function getEstimatesUsingFeeMarketEndpoint(request) { +- const { +- fetchGasEstimates: fetchGasEstimates2, +- fetchGasEstimatesUrl, +- infuraAPIKey, +- clientId, +- calculateTimeEstimate: calculateTimeEstimate2 +- } = request; +- const estimates = await fetchGasEstimates2( +- fetchGasEstimatesUrl, +- infuraAPIKey, +- clientId +- ); +- const { suggestedMaxPriorityFeePerGas, suggestedMaxFeePerGas } = estimates.medium; +- const estimatedGasFeeTimeBounds = calculateTimeEstimate2( +- suggestedMaxPriorityFeePerGas, +- suggestedMaxFeePerGas, +- estimates +- ); +- return { +- gasFeeEstimates: estimates, +- estimatedGasFeeTimeBounds, +- gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET +- }; +-} +-async function getEstimatesUsingLegacyEndpoint(request) { +- const { +- fetchLegacyGasPriceEstimates: fetchLegacyGasPriceEstimates2, +- fetchLegacyGasPriceEstimatesUrl, +- infuraAPIKey, +- clientId +- } = request; +- const estimates = await fetchLegacyGasPriceEstimates2( +- fetchLegacyGasPriceEstimatesUrl, +- infuraAPIKey, +- clientId +- ); +- return { +- gasFeeEstimates: estimates, +- estimatedGasFeeTimeBounds: {}, +- gasEstimateType: GAS_ESTIMATE_TYPES.LEGACY +- }; +-} +-async function getEstimatesUsingProvider(request) { +- const { ethQuery, fetchEthGasPriceEstimate: fetchEthGasPriceEstimate2 } = request; +- const estimates = await fetchEthGasPriceEstimate2(ethQuery); +- return { +- gasFeeEstimates: estimates, +- estimatedGasFeeTimeBounds: {}, +- gasEstimateType: GAS_ESTIMATE_TYPES.ETH_GASPRICE +- }; +-} +- +-export { +- determineGasFeeCalculations, +- GAS_API_BASE_URL, +- GAS_ESTIMATE_TYPES, +- GasFeeController, +- GasFeeController_default +-}; +-//# sourceMappingURL=chunk-BEVZS3YV.mjs.map +\ No newline at end of file +diff --git a/dist/chunk-BEVZS3YV.mjs.map b/dist/chunk-BEVZS3YV.mjs.map +deleted file mode 100644 +index fc90025f10e73e5cdafc8964cd84365e51ad0c42..0000000000000000000000000000000000000000 +--- a/dist/chunk-BEVZS3YV.mjs.map ++++ /dev/null +@@ -1 +0,0 @@ +-{"version":3,"sources":["../src/GasFeeController.ts","../src/determineGasFeeCalculations.ts"],"sourcesContent":["import type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n RestrictedControllerMessenger,\n} from '@metamask/base-controller';\nimport {\n convertHexToDecimal,\n safelyExecute,\n toHex,\n} from '@metamask/controller-utils';\nimport EthQuery from '@metamask/eth-query';\nimport type {\n NetworkClientId,\n NetworkControllerGetEIP1559CompatibilityAction,\n NetworkControllerGetNetworkClientByIdAction,\n NetworkControllerGetStateAction,\n NetworkControllerNetworkDidChangeEvent,\n NetworkState,\n ProviderProxy,\n} from '@metamask/network-controller';\nimport { StaticIntervalPollingController } from '@metamask/polling-controller';\nimport type { Hex } from '@metamask/utils';\nimport { v1 as random } from 'uuid';\n\nimport determineGasFeeCalculations from './determineGasFeeCalculations';\nimport {\n calculateTimeEstimate,\n fetchGasEstimates,\n fetchLegacyGasPriceEstimates,\n fetchEthGasPriceEstimate,\n} from './gas-util';\n\nexport const GAS_API_BASE_URL = 'https://gas.api.infura.io';\n\nexport type unknownString = 'unknown';\n\n// Fee Market describes the way gas is set after the london hardfork, and was\n// defined by EIP-1559.\nexport type FeeMarketEstimateType = 'fee-market';\n// Legacy describes gasPrice estimates from before london hardfork, when the\n// user is connected to mainnet and are presented with fast/average/slow\n// estimate levels to choose from.\nexport type LegacyEstimateType = 'legacy';\n// EthGasPrice describes a gasPrice estimate received from eth_gasPrice. Post\n// london this value should only be used for legacy type transactions when on\n// networks that support EIP-1559. This type of estimate is the most accurate\n// to display on custom networks that don't support EIP-1559.\nexport type EthGasPriceEstimateType = 'eth_gasPrice';\n// NoEstimate describes the state of the controller before receiving its first\n// estimate.\nexport type NoEstimateType = 'none';\n\n/**\n * Indicates which type of gasEstimate the controller is currently returning.\n * This is useful as a way of asserting that the shape of gasEstimates matches\n * expectations. NONE is a special case indicating that no previous gasEstimate\n * has been fetched.\n */\nexport const GAS_ESTIMATE_TYPES = {\n FEE_MARKET: 'fee-market' as FeeMarketEstimateType,\n LEGACY: 'legacy' as LegacyEstimateType,\n ETH_GASPRICE: 'eth_gasPrice' as EthGasPriceEstimateType,\n NONE: 'none' as NoEstimateType,\n};\n\nexport type GasEstimateType =\n | FeeMarketEstimateType\n | EthGasPriceEstimateType\n | LegacyEstimateType\n | NoEstimateType;\n\nexport type EstimatedGasFeeTimeBounds = {\n lowerTimeBound: number | null;\n upperTimeBound: number | unknownString;\n};\n\n/**\n * @type EthGasPriceEstimate\n *\n * A single gas price estimate for networks and accounts that don't support EIP-1559\n * This estimate comes from eth_gasPrice but is converted to dec gwei to match other\n * return values\n * @property gasPrice - A GWEI dec string\n */\n\nexport type EthGasPriceEstimate = {\n gasPrice: string;\n};\n\n/**\n * @type LegacyGasPriceEstimate\n *\n * A set of gas price estimates for networks and accounts that don't support EIP-1559\n * These estimates include low, medium and high all as strings representing gwei in\n * decimal format.\n * @property high - gasPrice, in decimal gwei string format, suggested for fast inclusion\n * @property medium - gasPrice, in decimal gwei string format, suggested for avg inclusion\n * @property low - gasPrice, in decimal gwei string format, suggested for slow inclusion\n */\nexport type LegacyGasPriceEstimate = {\n high: string;\n medium: string;\n low: string;\n};\n\n/**\n * @type Eip1559GasFee\n *\n * Data necessary to provide an estimate of a gas fee with a specific tip\n * @property minWaitTimeEstimate - The fastest the transaction will take, in milliseconds\n * @property maxWaitTimeEstimate - The slowest the transaction will take, in milliseconds\n * @property suggestedMaxPriorityFeePerGas - A suggested \"tip\", a GWEI hex number\n * @property suggestedMaxFeePerGas - A suggested max fee, the most a user will pay. a GWEI hex number\n */\nexport type Eip1559GasFee = {\n minWaitTimeEstimate: number; // a time duration in milliseconds\n maxWaitTimeEstimate: number; // a time duration in milliseconds\n suggestedMaxPriorityFeePerGas: string; // a GWEI decimal number\n suggestedMaxFeePerGas: string; // a GWEI decimal number\n};\n\n/**\n * @type GasFeeEstimates\n *\n * Data necessary to provide multiple GasFee estimates, and supporting information, to the user\n * @property low - A GasFee for a minimum necessary combination of tip and maxFee\n * @property medium - A GasFee for a recommended combination of tip and maxFee\n * @property high - A GasFee for a high combination of tip and maxFee\n * @property estimatedBaseFee - An estimate of what the base fee will be for the pending/next block. A GWEI dec number\n * @property networkCongestion - A normalized number that can be used to gauge the congestion\n * level of the network, with 0 meaning not congested and 1 meaning extremely congested\n */\nexport type GasFeeEstimates = SourcedGasFeeEstimates | FallbackGasFeeEstimates;\n\ntype SourcedGasFeeEstimates = {\n low: Eip1559GasFee;\n medium: Eip1559GasFee;\n high: Eip1559GasFee;\n estimatedBaseFee: string;\n historicalBaseFeeRange: [string, string];\n baseFeeTrend: 'up' | 'down' | 'level';\n latestPriorityFeeRange: [string, string];\n historicalPriorityFeeRange: [string, string];\n priorityFeeTrend: 'up' | 'down' | 'level';\n networkCongestion: number;\n};\n\ntype FallbackGasFeeEstimates = {\n low: Eip1559GasFee;\n medium: Eip1559GasFee;\n high: Eip1559GasFee;\n estimatedBaseFee: string;\n historicalBaseFeeRange: null;\n baseFeeTrend: null;\n latestPriorityFeeRange: null;\n historicalPriorityFeeRange: null;\n priorityFeeTrend: null;\n networkCongestion: null;\n};\n\nconst metadata = {\n gasFeeEstimatesByChainId: {\n persist: true,\n anonymous: false,\n },\n gasFeeEstimates: { persist: true, anonymous: false },\n estimatedGasFeeTimeBounds: { persist: true, anonymous: false },\n gasEstimateType: { persist: true, anonymous: false },\n nonRPCGasFeeApisDisabled: { persist: true, anonymous: false },\n};\n\nexport type GasFeeStateEthGasPrice = {\n gasFeeEstimates: EthGasPriceEstimate;\n estimatedGasFeeTimeBounds: Record;\n gasEstimateType: EthGasPriceEstimateType;\n};\n\nexport type GasFeeStateFeeMarket = {\n gasFeeEstimates: GasFeeEstimates;\n estimatedGasFeeTimeBounds: EstimatedGasFeeTimeBounds | Record;\n gasEstimateType: FeeMarketEstimateType;\n};\n\nexport type GasFeeStateLegacy = {\n gasFeeEstimates: LegacyGasPriceEstimate;\n estimatedGasFeeTimeBounds: Record;\n gasEstimateType: LegacyEstimateType;\n};\n\nexport type GasFeeStateNoEstimates = {\n gasFeeEstimates: Record;\n estimatedGasFeeTimeBounds: Record;\n gasEstimateType: NoEstimateType;\n};\n\nexport type FetchGasFeeEstimateOptions = {\n shouldUpdateState?: boolean;\n networkClientId?: NetworkClientId;\n};\n\n/**\n * @type GasFeeState\n *\n * Gas Fee controller state\n * @property gasFeeEstimates - Gas fee estimate data based on new EIP-1559 properties\n * @property estimatedGasFeeTimeBounds - Estimates representing the minimum and maximum\n */\nexport type SingleChainGasFeeState =\n | GasFeeStateEthGasPrice\n | GasFeeStateFeeMarket\n | GasFeeStateLegacy\n | GasFeeStateNoEstimates;\n\nexport type GasFeeEstimatesByChainId = {\n gasFeeEstimatesByChainId?: Record;\n};\n\nexport type GasFeeState = GasFeeEstimatesByChainId &\n SingleChainGasFeeState & {\n nonRPCGasFeeApisDisabled?: boolean;\n };\n\nconst name = 'GasFeeController';\n\nexport type GasFeeStateChange = ControllerStateChangeEvent<\n typeof name,\n GasFeeState\n>;\n\nexport type GetGasFeeState = ControllerGetStateAction;\n\nexport type GasFeeControllerActions = GetGasFeeState;\n\nexport type GasFeeControllerEvents = GasFeeStateChange;\n\ntype AllowedActions =\n | NetworkControllerGetStateAction\n | NetworkControllerGetNetworkClientByIdAction\n | NetworkControllerGetEIP1559CompatibilityAction;\n\ntype GasFeeMessenger = RestrictedControllerMessenger<\n typeof name,\n GasFeeControllerActions | AllowedActions,\n GasFeeControllerEvents | NetworkControllerNetworkDidChangeEvent,\n AllowedActions['type'],\n NetworkControllerNetworkDidChangeEvent['type']\n>;\n\nconst defaultState: GasFeeState = {\n gasFeeEstimatesByChainId: {},\n gasFeeEstimates: {},\n estimatedGasFeeTimeBounds: {},\n gasEstimateType: GAS_ESTIMATE_TYPES.NONE,\n nonRPCGasFeeApisDisabled: false,\n};\n\n/**\n * Controller that retrieves gas fee estimate data and polls for updated data on a set interval\n */\nexport class GasFeeController extends StaticIntervalPollingController<\n typeof name,\n GasFeeState,\n GasFeeMessenger\n> {\n private intervalId?: ReturnType;\n\n private readonly intervalDelay;\n\n private readonly pollTokens: Set;\n\n private readonly legacyAPIEndpoint: string;\n\n private readonly EIP1559APIEndpoint: string;\n\n private readonly getCurrentNetworkEIP1559Compatibility;\n\n private readonly getCurrentNetworkLegacyGasAPICompatibility;\n\n private readonly getCurrentAccountEIP1559Compatibility;\n\n private readonly infuraAPIKey: string;\n\n private currentChainId;\n\n private ethQuery?: EthQuery;\n\n private readonly clientId?: string;\n\n #getProvider: () => ProviderProxy;\n\n /**\n * Creates a GasFeeController instance.\n *\n * @param options - The controller options.\n * @param options.interval - The time in milliseconds to wait between polls.\n * @param options.messenger - The controller messenger.\n * @param options.state - The initial state.\n * @param options.getCurrentNetworkEIP1559Compatibility - Determines whether or not the current\n * network is EIP-1559 compatible.\n * @param options.getCurrentNetworkLegacyGasAPICompatibility - Determines whether or not the\n * current network is compatible with the legacy gas price API.\n * @param options.getCurrentAccountEIP1559Compatibility - Determines whether or not the current\n * account is EIP-1559 compatible.\n * @param options.getChainId - Returns the current chain ID.\n * @param options.getProvider - Returns a network provider for the current network.\n * @param options.onNetworkDidChange - A function for registering an event handler for the\n * network state change event.\n * @param options.clientId - The client ID used to identify to the gas estimation API who is\n * asking for estimates.\n * @param options.infuraAPIKey - The Infura API key used for infura API requests.\n */\n constructor({\n interval = 15000,\n messenger,\n state,\n getCurrentNetworkEIP1559Compatibility,\n getCurrentAccountEIP1559Compatibility,\n getChainId,\n getCurrentNetworkLegacyGasAPICompatibility,\n getProvider,\n onNetworkDidChange,\n clientId,\n infuraAPIKey,\n }: {\n interval?: number;\n messenger: GasFeeMessenger;\n state?: GasFeeState;\n getCurrentNetworkEIP1559Compatibility: () => Promise;\n getCurrentNetworkLegacyGasAPICompatibility: () => boolean;\n getCurrentAccountEIP1559Compatibility?: () => boolean;\n getChainId?: () => Hex;\n getProvider: () => ProviderProxy;\n onNetworkDidChange?: (listener: (state: NetworkState) => void) => void;\n clientId?: string;\n infuraAPIKey: string;\n }) {\n super({\n name,\n metadata,\n messenger,\n state: { ...defaultState, ...state },\n });\n this.intervalDelay = interval;\n this.setIntervalLength(interval);\n this.pollTokens = new Set();\n this.getCurrentNetworkEIP1559Compatibility =\n getCurrentNetworkEIP1559Compatibility;\n this.getCurrentNetworkLegacyGasAPICompatibility =\n getCurrentNetworkLegacyGasAPICompatibility;\n this.getCurrentAccountEIP1559Compatibility =\n getCurrentAccountEIP1559Compatibility;\n this.#getProvider = getProvider;\n this.EIP1559APIEndpoint = `${GAS_API_BASE_URL}/networks//suggestedGasFees`;\n this.legacyAPIEndpoint = `${GAS_API_BASE_URL}/networks//gasPrices`;\n this.clientId = clientId;\n this.infuraAPIKey = infuraAPIKey;\n\n this.ethQuery = new EthQuery(this.#getProvider());\n\n if (onNetworkDidChange && getChainId) {\n this.currentChainId = getChainId();\n onNetworkDidChange(async (networkControllerState) => {\n await this.#onNetworkControllerDidChange(networkControllerState);\n });\n } else {\n this.currentChainId = this.messagingSystem.call(\n 'NetworkController:getState',\n ).providerConfig.chainId;\n this.messagingSystem.subscribe(\n 'NetworkController:networkDidChange',\n async (networkControllerState) => {\n await this.#onNetworkControllerDidChange(networkControllerState);\n },\n );\n }\n }\n\n async resetPolling() {\n if (this.pollTokens.size !== 0) {\n const tokens = Array.from(this.pollTokens);\n this.stopPolling();\n await this.getGasFeeEstimatesAndStartPolling(tokens[0]);\n tokens.slice(1).forEach((token) => {\n this.pollTokens.add(token);\n });\n }\n }\n\n async fetchGasFeeEstimates(options?: FetchGasFeeEstimateOptions) {\n return await this._fetchGasFeeEstimateData(options);\n }\n\n async getGasFeeEstimatesAndStartPolling(\n pollToken: string | undefined,\n ): Promise {\n const _pollToken = pollToken || random();\n\n this.pollTokens.add(_pollToken);\n\n if (this.pollTokens.size === 1) {\n await this._fetchGasFeeEstimateData();\n this._poll();\n }\n\n return _pollToken;\n }\n\n /**\n * Gets and sets gasFeeEstimates in state.\n *\n * @param options - The gas fee estimate options.\n * @param options.shouldUpdateState - Determines whether the state should be updated with the\n * updated gas estimates.\n * @returns The gas fee estimates.\n */\n async _fetchGasFeeEstimateData(\n options: FetchGasFeeEstimateOptions = {},\n ): Promise {\n const { shouldUpdateState = true, networkClientId } = options;\n\n let ethQuery,\n isEIP1559Compatible,\n isLegacyGasAPICompatible,\n decimalChainId: number;\n\n if (networkClientId !== undefined) {\n const networkClient = this.messagingSystem.call(\n 'NetworkController:getNetworkClientById',\n networkClientId,\n );\n isLegacyGasAPICompatible = networkClient.configuration.chainId === '0x38';\n\n decimalChainId = convertHexToDecimal(networkClient.configuration.chainId);\n\n try {\n const result = await this.messagingSystem.call(\n 'NetworkController:getEIP1559Compatibility',\n networkClientId,\n );\n isEIP1559Compatible = result || false;\n } catch {\n isEIP1559Compatible = false;\n }\n ethQuery = new EthQuery(networkClient.provider);\n }\n\n ethQuery ??= this.ethQuery;\n\n isLegacyGasAPICompatible ??=\n this.getCurrentNetworkLegacyGasAPICompatibility();\n\n decimalChainId ??= convertHexToDecimal(this.currentChainId);\n\n try {\n isEIP1559Compatible ??= await this.getEIP1559Compatibility();\n } catch (e) {\n console.error(e);\n isEIP1559Compatible ??= false;\n }\n\n const gasFeeCalculations = await determineGasFeeCalculations({\n isEIP1559Compatible,\n isLegacyGasAPICompatible,\n fetchGasEstimates,\n fetchGasEstimatesUrl: this.EIP1559APIEndpoint.replace(\n '',\n `${decimalChainId}`,\n ),\n fetchLegacyGasPriceEstimates,\n fetchLegacyGasPriceEstimatesUrl: this.legacyAPIEndpoint.replace(\n '',\n `${decimalChainId}`,\n ),\n fetchEthGasPriceEstimate,\n calculateTimeEstimate,\n clientId: this.clientId,\n ethQuery,\n infuraAPIKey: this.infuraAPIKey,\n nonRPCGasFeeApisDisabled: this.state.nonRPCGasFeeApisDisabled,\n });\n\n if (shouldUpdateState) {\n const chainId = toHex(decimalChainId);\n this.update((state) => {\n if (this.currentChainId === chainId) {\n state.gasFeeEstimates = gasFeeCalculations.gasFeeEstimates;\n state.estimatedGasFeeTimeBounds =\n gasFeeCalculations.estimatedGasFeeTimeBounds;\n state.gasEstimateType = gasFeeCalculations.gasEstimateType;\n }\n state.gasFeeEstimatesByChainId ??= {};\n state.gasFeeEstimatesByChainId[chainId] = {\n gasFeeEstimates: gasFeeCalculations.gasFeeEstimates,\n estimatedGasFeeTimeBounds:\n gasFeeCalculations.estimatedGasFeeTimeBounds,\n gasEstimateType: gasFeeCalculations.gasEstimateType,\n } as SingleChainGasFeeState;\n });\n }\n\n return gasFeeCalculations;\n }\n\n /**\n * Remove the poll token, and stop polling if the set of poll tokens is empty.\n *\n * @param pollToken - The poll token to disconnect.\n */\n disconnectPoller(pollToken: string) {\n this.pollTokens.delete(pollToken);\n if (this.pollTokens.size === 0) {\n this.stopPolling();\n }\n }\n\n stopPolling() {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n }\n this.pollTokens.clear();\n this.resetState();\n }\n\n /**\n * Prepare to discard this controller.\n *\n * This stops any active polling.\n */\n override destroy() {\n super.destroy();\n this.stopPolling();\n }\n\n private _poll() {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n }\n\n this.intervalId = setInterval(async () => {\n await safelyExecute(() => this._fetchGasFeeEstimateData());\n }, this.intervalDelay);\n }\n\n /**\n * Fetching token list from the Token Service API.\n *\n * @private\n * @param networkClientId - The ID of the network client triggering the fetch.\n * @returns A promise that resolves when this operation completes.\n */\n async _executePoll(networkClientId: string): Promise {\n await this._fetchGasFeeEstimateData({ networkClientId });\n }\n\n private resetState() {\n this.update(() => {\n return defaultState;\n });\n }\n\n private async getEIP1559Compatibility() {\n const currentNetworkIsEIP1559Compatible =\n await this.getCurrentNetworkEIP1559Compatibility();\n const currentAccountIsEIP1559Compatible =\n this.getCurrentAccountEIP1559Compatibility?.() ?? true;\n\n return (\n currentNetworkIsEIP1559Compatible && currentAccountIsEIP1559Compatible\n );\n }\n\n getTimeEstimate(\n maxPriorityFeePerGas: string,\n maxFeePerGas: string,\n ): EstimatedGasFeeTimeBounds | Record {\n if (\n !this.state.gasFeeEstimates ||\n this.state.gasEstimateType !== GAS_ESTIMATE_TYPES.FEE_MARKET\n ) {\n return {};\n }\n return calculateTimeEstimate(\n maxPriorityFeePerGas,\n maxFeePerGas,\n this.state.gasFeeEstimates,\n );\n }\n\n async #onNetworkControllerDidChange(networkControllerState: NetworkState) {\n const newChainId = networkControllerState.providerConfig.chainId;\n\n if (newChainId !== this.currentChainId) {\n this.ethQuery = new EthQuery(this.#getProvider());\n await this.resetPolling();\n\n this.currentChainId = newChainId;\n }\n }\n\n enableNonRPCGasFeeApis() {\n this.update((state) => {\n state.nonRPCGasFeeApisDisabled = false;\n });\n }\n\n disableNonRPCGasFeeApis() {\n this.update((state) => {\n state.nonRPCGasFeeApisDisabled = true;\n });\n }\n}\n\nexport default GasFeeController;\n","import type {\n EstimatedGasFeeTimeBounds,\n EthGasPriceEstimate,\n GasFeeEstimates,\n GasFeeState as GasFeeCalculations,\n LegacyGasPriceEstimate,\n} from './GasFeeController';\nimport { GAS_ESTIMATE_TYPES } from './GasFeeController';\n\ntype DetermineGasFeeCalculationsRequest = {\n isEIP1559Compatible: boolean;\n isLegacyGasAPICompatible: boolean;\n fetchGasEstimates: (\n url: string,\n infuraAPIKey: string,\n clientId?: string,\n ) => Promise;\n fetchGasEstimatesUrl: string;\n fetchLegacyGasPriceEstimates: (\n url: string,\n infuraAPIKey: string,\n clientId?: string,\n ) => Promise;\n fetchLegacyGasPriceEstimatesUrl: string;\n // TODO: Replace `any` with type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n fetchEthGasPriceEstimate: (ethQuery: any) => Promise;\n calculateTimeEstimate: (\n maxPriorityFeePerGas: string,\n maxFeePerGas: string,\n gasFeeEstimates: GasFeeEstimates,\n ) => EstimatedGasFeeTimeBounds;\n clientId: string | undefined;\n // TODO: Replace `any` with type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ethQuery: any;\n infuraAPIKey: string;\n nonRPCGasFeeApisDisabled?: boolean;\n};\n\n/**\n * Obtains a set of max base and priority fee estimates along with time estimates so that we\n * can present them to users when they are sending transactions or making swaps.\n *\n * @param args - The arguments.\n * @param args.isEIP1559Compatible - Governs whether or not we can use an EIP-1559-only method to\n * produce estimates.\n * @param args.isLegacyGasAPICompatible - Governs whether or not we can use a non-EIP-1559 method to\n * produce estimates (for instance, testnets do not support estimates altogether).\n * @param args.fetchGasEstimates - A function that fetches gas estimates using an EIP-1559-specific\n * API.\n * @param args.fetchGasEstimatesUrl - The URL for the API we can use to obtain EIP-1559-specific\n * estimates.\n * @param args.fetchLegacyGasPriceEstimates - A function that fetches gas estimates using an\n * non-EIP-1559-specific API.\n * @param args.fetchLegacyGasPriceEstimatesUrl - The URL for the API we can use to obtain\n * non-EIP-1559-specific estimates.\n * @param args.fetchEthGasPriceEstimate - A function that fetches gas estimates using\n * `eth_gasPrice`.\n * @param args.calculateTimeEstimate - A function that determine time estimate bounds.\n * @param args.clientId - An identifier that an API can use to know who is asking for estimates.\n * @param args.ethQuery - An EthQuery instance we can use to talk to Ethereum directly.\n * @param args.infuraAPIKey - Infura API key to use for requests to Infura.\n * @param args.nonRPCGasFeeApisDisabled - Whether to disable requests to the legacyAPIEndpoint and the EIP1559APIEndpoint\n * @returns The gas fee calculations.\n */\nexport default async function determineGasFeeCalculations(\n args: DetermineGasFeeCalculationsRequest,\n): Promise {\n try {\n return await getEstimatesUsingFallbacks(args);\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(\n `Gas fee/price estimation failed. Message: ${error.message}`,\n );\n }\n\n throw error;\n }\n}\n\n/**\n * Retrieve the gas fee estimates using a series of fallback mechanisms.\n * @param request - The request object.\n * @returns The gas fee estimates.\n */\nasync function getEstimatesUsingFallbacks(\n request: DetermineGasFeeCalculationsRequest,\n): Promise {\n const {\n isEIP1559Compatible,\n isLegacyGasAPICompatible,\n nonRPCGasFeeApisDisabled,\n } = request;\n\n try {\n if (isEIP1559Compatible && !nonRPCGasFeeApisDisabled) {\n return await getEstimatesUsingFeeMarketEndpoint(request);\n }\n\n if (isLegacyGasAPICompatible && !nonRPCGasFeeApisDisabled) {\n return await getEstimatesUsingLegacyEndpoint(request);\n }\n\n throw new Error('Main gas fee/price estimation failed. Use fallback');\n } catch {\n return await getEstimatesUsingProvider(request);\n }\n}\n\n/**\n * Retrieve gas fee estimates using the EIP-1559 endpoint of the gas API.\n * @param request - The request object.\n * @returns The gas fee estimates.\n */\nasync function getEstimatesUsingFeeMarketEndpoint(\n request: DetermineGasFeeCalculationsRequest,\n): Promise {\n const {\n fetchGasEstimates,\n fetchGasEstimatesUrl,\n infuraAPIKey,\n clientId,\n calculateTimeEstimate,\n } = request;\n\n const estimates = await fetchGasEstimates(\n fetchGasEstimatesUrl,\n infuraAPIKey,\n clientId,\n );\n\n const { suggestedMaxPriorityFeePerGas, suggestedMaxFeePerGas } =\n estimates.medium;\n\n const estimatedGasFeeTimeBounds = calculateTimeEstimate(\n suggestedMaxPriorityFeePerGas,\n suggestedMaxFeePerGas,\n estimates,\n );\n\n return {\n gasFeeEstimates: estimates,\n estimatedGasFeeTimeBounds,\n gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET,\n };\n}\n\n/**\n * Retrieve gas fee estimates using the legacy endpoint of the gas API.\n * @param request - The request object.\n * @returns The gas fee estimates.\n */\nasync function getEstimatesUsingLegacyEndpoint(\n request: DetermineGasFeeCalculationsRequest,\n): Promise {\n const {\n fetchLegacyGasPriceEstimates,\n fetchLegacyGasPriceEstimatesUrl,\n infuraAPIKey,\n clientId,\n } = request;\n\n const estimates = await fetchLegacyGasPriceEstimates(\n fetchLegacyGasPriceEstimatesUrl,\n infuraAPIKey,\n clientId,\n );\n\n return {\n gasFeeEstimates: estimates,\n estimatedGasFeeTimeBounds: {},\n gasEstimateType: GAS_ESTIMATE_TYPES.LEGACY,\n };\n}\n\n/**\n * Retrieve gas fee estimates using an `eth_gasPrice` call to the RPC provider.\n * @param request - The request object.\n * @returns The gas fee estimates.\n */\nasync function getEstimatesUsingProvider(\n request: DetermineGasFeeCalculationsRequest,\n): Promise {\n const { ethQuery, fetchEthGasPriceEstimate } = request;\n\n const estimates = await fetchEthGasPriceEstimate(ethQuery);\n\n return {\n gasFeeEstimates: estimates,\n estimatedGasFeeTimeBounds: {},\n gasEstimateType: GAS_ESTIMATE_TYPES.ETH_GASPRICE,\n };\n}\n"],"mappings":";;;;;;;;;;;;AAKA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,OAAO,cAAc;AAUrB,SAAS,uCAAuC;AAEhD,SAAS,MAAM,cAAc;AAUtB,IAAM,mBAAmB;AA0BzB,IAAM,qBAAqB;AAAA,EAChC,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,cAAc;AAAA,EACd,MAAM;AACR;AAiGA,IAAM,WAAW;AAAA,EACf,0BAA0B;AAAA,IACxB,SAAS;AAAA,IACT,WAAW;AAAA,EACb;AAAA,EACA,iBAAiB,EAAE,SAAS,MAAM,WAAW,MAAM;AAAA,EACnD,2BAA2B,EAAE,SAAS,MAAM,WAAW,MAAM;AAAA,EAC7D,iBAAiB,EAAE,SAAS,MAAM,WAAW,MAAM;AAAA,EACnD,0BAA0B,EAAE,SAAS,MAAM,WAAW,MAAM;AAC9D;AAqDA,IAAM,OAAO;AA0Bb,IAAM,eAA4B;AAAA,EAChC,0BAA0B,CAAC;AAAA,EAC3B,iBAAiB,CAAC;AAAA,EAClB,2BAA2B,CAAC;AAAA,EAC5B,iBAAiB,mBAAmB;AAAA,EACpC,0BAA0B;AAC5B;AA9PA;AAmQO,IAAM,mBAAN,cAA+B,gCAIpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgDA,YAAY;AAAA,IACV,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAYG;AACD,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,EAAE,GAAG,cAAc,GAAG,MAAM;AAAA,IACrC,CAAC;AAuPH,uBAAM;AA5SN;AAsDE,SAAK,gBAAgB;AACrB,SAAK,kBAAkB,QAAQ;AAC/B,SAAK,aAAa,oBAAI,IAAI;AAC1B,SAAK,wCACH;AACF,SAAK,6CACH;AACF,SAAK,wCACH;AACF,uBAAK,cAAe;AACpB,SAAK,qBAAqB,GAAG,gBAAgB;AAC7C,SAAK,oBAAoB,GAAG,gBAAgB;AAC5C,SAAK,WAAW;AAChB,SAAK,eAAe;AAEpB,SAAK,WAAW,IAAI,SAAS,mBAAK,cAAL,UAAmB;AAEhD,QAAI,sBAAsB,YAAY;AACpC,WAAK,iBAAiB,WAAW;AACjC,yBAAmB,OAAO,2BAA2B;AACnD,cAAM,sBAAK,gEAAL,WAAmC;AAAA,MAC3C,CAAC;AAAA,IACH,OAAO;AACL,WAAK,iBAAiB,KAAK,gBAAgB;AAAA,QACzC;AAAA,MACF,EAAE,eAAe;AACjB,WAAK,gBAAgB;AAAA,QACnB;AAAA,QACA,OAAO,2BAA2B;AAChC,gBAAM,sBAAK,gEAAL,WAAmC;AAAA,QAC3C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,eAAe;AACnB,QAAI,KAAK,WAAW,SAAS,GAAG;AAC9B,YAAM,SAAS,MAAM,KAAK,KAAK,UAAU;AACzC,WAAK,YAAY;AACjB,YAAM,KAAK,kCAAkC,OAAO,CAAC,CAAC;AACtD,aAAO,MAAM,CAAC,EAAE,QAAQ,CAAC,UAAU;AACjC,aAAK,WAAW,IAAI,KAAK;AAAA,MAC3B,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,MAAM,qBAAqB,SAAsC;AAC/D,WAAO,MAAM,KAAK,yBAAyB,OAAO;AAAA,EACpD;AAAA,EAEA,MAAM,kCACJ,WACiB;AACjB,UAAM,aAAa,aAAa,OAAO;AAEvC,SAAK,WAAW,IAAI,UAAU;AAE9B,QAAI,KAAK,WAAW,SAAS,GAAG;AAC9B,YAAM,KAAK,yBAAyB;AACpC,WAAK,MAAM;AAAA,IACb;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,yBACJ,UAAsC,CAAC,GACjB;AACtB,UAAM,EAAE,oBAAoB,MAAM,gBAAgB,IAAI;AAEtD,QAAI,UACF,qBACA,0BACA;AAEF,QAAI,oBAAoB,QAAW;AACjC,YAAM,gBAAgB,KAAK,gBAAgB;AAAA,QACzC;AAAA,QACA;AAAA,MACF;AACA,iCAA2B,cAAc,cAAc,YAAY;AAEnE,uBAAiB,oBAAoB,cAAc,cAAc,OAAO;AAExE,UAAI;AACF,cAAM,SAAS,MAAM,KAAK,gBAAgB;AAAA,UACxC;AAAA,UACA;AAAA,QACF;AACA,8BAAsB,UAAU;AAAA,MAClC,QAAQ;AACN,8BAAsB;AAAA,MACxB;AACA,iBAAW,IAAI,SAAS,cAAc,QAAQ;AAAA,IAChD;AAEA,4BAAa,KAAK;AAElB,4DACE,KAAK,2CAA2C;AAElD,wCAAmB,oBAAoB,KAAK,cAAc;AAE1D,QAAI;AACF,oDAAwB,MAAM,KAAK,wBAAwB;AAAA,IAC7D,SAAS,GAAG;AACV,cAAQ,MAAM,CAAC;AACf,oDAAwB;AAAA,IAC1B;AAEA,UAAM,qBAAqB,MAAM,4BAA4B;AAAA,MAC3D;AAAA,MACA;AAAA,MACA;AAAA,MACA,sBAAsB,KAAK,mBAAmB;AAAA,QAC5C;AAAA,QACA,GAAG,cAAc;AAAA,MACnB;AAAA,MACA;AAAA,MACA,iCAAiC,KAAK,kBAAkB;AAAA,QACtD;AAAA,QACA,GAAG,cAAc;AAAA,MACnB;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU,KAAK;AAAA,MACf;AAAA,MACA,cAAc,KAAK;AAAA,MACnB,0BAA0B,KAAK,MAAM;AAAA,IACvC,CAAC;AAED,QAAI,mBAAmB;AACrB,YAAM,UAAU,MAAM,cAAc;AACpC,WAAK,OAAO,CAAC,UAAU;AACrB,YAAI,KAAK,mBAAmB,SAAS;AACnC,gBAAM,kBAAkB,mBAAmB;AAC3C,gBAAM,4BACJ,mBAAmB;AACrB,gBAAM,kBAAkB,mBAAmB;AAAA,QAC7C;AACA,cAAM,6BAAN,MAAM,2BAA6B,CAAC;AACpC,cAAM,yBAAyB,OAAO,IAAI;AAAA,UACxC,iBAAiB,mBAAmB;AAAA,UACpC,2BACE,mBAAmB;AAAA,UACrB,iBAAiB,mBAAmB;AAAA,QACtC;AAAA,MACF,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBAAiB,WAAmB;AAClC,SAAK,WAAW,OAAO,SAAS;AAChC,QAAI,KAAK,WAAW,SAAS,GAAG;AAC9B,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA,EAEA,cAAc;AACZ,QAAI,KAAK,YAAY;AACnB,oBAAc,KAAK,UAAU;AAAA,IAC/B;AACA,SAAK,WAAW,MAAM;AACtB,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOS,UAAU;AACjB,UAAM,QAAQ;AACd,SAAK,YAAY;AAAA,EACnB;AAAA,EAEQ,QAAQ;AACd,QAAI,KAAK,YAAY;AACnB,oBAAc,KAAK,UAAU;AAAA,IAC/B;AAEA,SAAK,aAAa,YAAY,YAAY;AACxC,YAAM,cAAc,MAAM,KAAK,yBAAyB,CAAC;AAAA,IAC3D,GAAG,KAAK,aAAa;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,aAAa,iBAAwC;AACzD,UAAM,KAAK,yBAAyB,EAAE,gBAAgB,CAAC;AAAA,EACzD;AAAA,EAEQ,aAAa;AACnB,SAAK,OAAO,MAAM;AAChB,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,0BAA0B;AACtC,UAAM,oCACJ,MAAM,KAAK,sCAAsC;AACnD,UAAM,oCACJ,KAAK,wCAAwC,KAAK;AAEpD,WACE,qCAAqC;AAAA,EAEzC;AAAA,EAEA,gBACE,sBACA,cACmD;AACnD,QACE,CAAC,KAAK,MAAM,mBACZ,KAAK,MAAM,oBAAoB,mBAAmB,YAClD;AACA,aAAO,CAAC;AAAA,IACV;AACA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,KAAK,MAAM;AAAA,IACb;AAAA,EACF;AAAA,EAaA,yBAAyB;AACvB,SAAK,OAAO,CAAC,UAAU;AACrB,YAAM,2BAA2B;AAAA,IACnC,CAAC;AAAA,EACH;AAAA,EAEA,0BAA0B;AACxB,SAAK,OAAO,CAAC,UAAU;AACrB,YAAM,2BAA2B;AAAA,IACnC,CAAC;AAAA,EACH;AACF;AAlUE;AA4SM;AAAA,kCAA6B,eAAC,wBAAsC;AACxE,QAAM,aAAa,uBAAuB,eAAe;AAEzD,MAAI,eAAe,KAAK,gBAAgB;AACtC,SAAK,WAAW,IAAI,SAAS,mBAAK,cAAL,UAAmB;AAChD,UAAM,KAAK,aAAa;AAExB,SAAK,iBAAiB;AAAA,EACxB;AACF;AAeF,IAAO,2BAAQ;;;ACliBf,eAAO,4BACL,MAC6B;AAC7B,MAAI;AACF,WAAO,MAAM,2BAA2B,IAAI;AAAA,EAC9C,SAAS,OAAO;AACd,QAAI,iBAAiB,OAAO;AAC1B,YAAM,IAAI;AAAA,QACR,6CAA6C,MAAM,OAAO;AAAA,MAC5D;AAAA,IACF;AAEA,UAAM;AAAA,EACR;AACF;AAOA,eAAe,2BACb,SAC6B;AAC7B,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,MAAI;AACF,QAAI,uBAAuB,CAAC,0BAA0B;AACpD,aAAO,MAAM,mCAAmC,OAAO;AAAA,IACzD;AAEA,QAAI,4BAA4B,CAAC,0BAA0B;AACzD,aAAO,MAAM,gCAAgC,OAAO;AAAA,IACtD;AAEA,UAAM,IAAI,MAAM,oDAAoD;AAAA,EACtE,QAAQ;AACN,WAAO,MAAM,0BAA0B,OAAO;AAAA,EAChD;AACF;AAOA,eAAe,mCACb,SAC6B;AAC7B,QAAM;AAAA,IACJ,mBAAAA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,uBAAAC;AAAA,EACF,IAAI;AAEJ,QAAM,YAAY,MAAMD;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,EAAE,+BAA+B,sBAAsB,IAC3D,UAAU;AAEZ,QAAM,4BAA4BC;AAAA,IAChC;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,SAAO;AAAA,IACL,iBAAiB;AAAA,IACjB;AAAA,IACA,iBAAiB,mBAAmB;AAAA,EACtC;AACF;AAOA,eAAe,gCACb,SAC6B;AAC7B,QAAM;AAAA,IACJ,8BAAAC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,QAAM,YAAY,MAAMA;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,SAAO;AAAA,IACL,iBAAiB;AAAA,IACjB,2BAA2B,CAAC;AAAA,IAC5B,iBAAiB,mBAAmB;AAAA,EACtC;AACF;AAOA,eAAe,0BACb,SAC6B;AAC7B,QAAM,EAAE,UAAU,0BAAAC,0BAAyB,IAAI;AAE/C,QAAM,YAAY,MAAMA,0BAAyB,QAAQ;AAEzD,SAAO;AAAA,IACL,iBAAiB;AAAA,IACjB,2BAA2B,CAAC;AAAA,IAC5B,iBAAiB,mBAAmB;AAAA,EACtC;AACF;","names":["fetchGasEstimates","calculateTimeEstimate","fetchLegacyGasPriceEstimates","fetchEthGasPriceEstimate"]} +\ No newline at end of file +diff --git a/dist/chunk-H5WHAYLI.js b/dist/chunk-H5WHAYLI.js +deleted file mode 100644 +index 3d6f8458707153d0b3cdd19da2bce7cb32da8eef..0000000000000000000000000000000000000000 +--- a/dist/chunk-H5WHAYLI.js ++++ /dev/null +@@ -1,396 +0,0 @@ +-"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +- +- +- +- +- +- +- +- +-var _chunkQ2YPK5SLjs = require('./chunk-Q2YPK5SL.js'); +- +-// src/GasFeeController.ts +- +- +- +- +-var _controllerutils = require('@metamask/controller-utils'); +-var _ethquery = require('@metamask/eth-query'); var _ethquery2 = _interopRequireDefault(_ethquery); +-var _pollingcontroller = require('@metamask/polling-controller'); +-var _uuid = require('uuid'); +-var GAS_API_BASE_URL = "https://gas.api.infura.io"; +-var GAS_ESTIMATE_TYPES = { +- FEE_MARKET: "fee-market", +- LEGACY: "legacy", +- ETH_GASPRICE: "eth_gasPrice", +- NONE: "none" +-}; +-var metadata = { +- gasFeeEstimatesByChainId: { +- persist: true, +- anonymous: false +- }, +- gasFeeEstimates: { persist: true, anonymous: false }, +- estimatedGasFeeTimeBounds: { persist: true, anonymous: false }, +- gasEstimateType: { persist: true, anonymous: false }, +- nonRPCGasFeeApisDisabled: { persist: true, anonymous: false } +-}; +-var name = "GasFeeController"; +-var defaultState = { +- gasFeeEstimatesByChainId: {}, +- gasFeeEstimates: {}, +- estimatedGasFeeTimeBounds: {}, +- gasEstimateType: GAS_ESTIMATE_TYPES.NONE, +- nonRPCGasFeeApisDisabled: false +-}; +-var _getProvider, _onNetworkControllerDidChange, onNetworkControllerDidChange_fn; +-var GasFeeController = class extends _pollingcontroller.StaticIntervalPollingController { +- /** +- * Creates a GasFeeController instance. +- * +- * @param options - The controller options. +- * @param options.interval - The time in milliseconds to wait between polls. +- * @param options.messenger - The controller messenger. +- * @param options.state - The initial state. +- * @param options.getCurrentNetworkEIP1559Compatibility - Determines whether or not the current +- * network is EIP-1559 compatible. +- * @param options.getCurrentNetworkLegacyGasAPICompatibility - Determines whether or not the +- * current network is compatible with the legacy gas price API. +- * @param options.getCurrentAccountEIP1559Compatibility - Determines whether or not the current +- * account is EIP-1559 compatible. +- * @param options.getChainId - Returns the current chain ID. +- * @param options.getProvider - Returns a network provider for the current network. +- * @param options.onNetworkDidChange - A function for registering an event handler for the +- * network state change event. +- * @param options.clientId - The client ID used to identify to the gas estimation API who is +- * asking for estimates. +- * @param options.infuraAPIKey - The Infura API key used for infura API requests. +- */ +- constructor({ +- interval = 15e3, +- messenger, +- state, +- getCurrentNetworkEIP1559Compatibility, +- getCurrentAccountEIP1559Compatibility, +- getChainId, +- getCurrentNetworkLegacyGasAPICompatibility, +- getProvider, +- onNetworkDidChange, +- clientId, +- infuraAPIKey +- }) { +- super({ +- name, +- metadata, +- messenger, +- state: { ...defaultState, ...state } +- }); +- _chunkQ2YPK5SLjs.__privateAdd.call(void 0, this, _onNetworkControllerDidChange); +- _chunkQ2YPK5SLjs.__privateAdd.call(void 0, this, _getProvider, void 0); +- this.intervalDelay = interval; +- this.setIntervalLength(interval); +- this.pollTokens = /* @__PURE__ */ new Set(); +- this.getCurrentNetworkEIP1559Compatibility = getCurrentNetworkEIP1559Compatibility; +- this.getCurrentNetworkLegacyGasAPICompatibility = getCurrentNetworkLegacyGasAPICompatibility; +- this.getCurrentAccountEIP1559Compatibility = getCurrentAccountEIP1559Compatibility; +- _chunkQ2YPK5SLjs.__privateSet.call(void 0, this, _getProvider, getProvider); +- this.EIP1559APIEndpoint = `${GAS_API_BASE_URL}/networks//suggestedGasFees`; +- this.legacyAPIEndpoint = `${GAS_API_BASE_URL}/networks//gasPrices`; +- this.clientId = clientId; +- this.infuraAPIKey = infuraAPIKey; +- this.ethQuery = new (0, _ethquery2.default)(_chunkQ2YPK5SLjs.__privateGet.call(void 0, this, _getProvider).call(this)); +- if (onNetworkDidChange && getChainId) { +- this.currentChainId = getChainId(); +- onNetworkDidChange(async (networkControllerState) => { +- await _chunkQ2YPK5SLjs.__privateMethod.call(void 0, this, _onNetworkControllerDidChange, onNetworkControllerDidChange_fn).call(this, networkControllerState); +- }); +- } else { +- this.currentChainId = this.messagingSystem.call( +- "NetworkController:getState" +- ).providerConfig.chainId; +- this.messagingSystem.subscribe( +- "NetworkController:networkDidChange", +- async (networkControllerState) => { +- await _chunkQ2YPK5SLjs.__privateMethod.call(void 0, this, _onNetworkControllerDidChange, onNetworkControllerDidChange_fn).call(this, networkControllerState); +- } +- ); +- } +- } +- async resetPolling() { +- if (this.pollTokens.size !== 0) { +- const tokens = Array.from(this.pollTokens); +- this.stopPolling(); +- await this.getGasFeeEstimatesAndStartPolling(tokens[0]); +- tokens.slice(1).forEach((token) => { +- this.pollTokens.add(token); +- }); +- } +- } +- async fetchGasFeeEstimates(options) { +- return await this._fetchGasFeeEstimateData(options); +- } +- async getGasFeeEstimatesAndStartPolling(pollToken) { +- const _pollToken = pollToken || _uuid.v1.call(void 0, ); +- this.pollTokens.add(_pollToken); +- if (this.pollTokens.size === 1) { +- await this._fetchGasFeeEstimateData(); +- this._poll(); +- } +- return _pollToken; +- } +- /** +- * Gets and sets gasFeeEstimates in state. +- * +- * @param options - The gas fee estimate options. +- * @param options.shouldUpdateState - Determines whether the state should be updated with the +- * updated gas estimates. +- * @returns The gas fee estimates. +- */ +- async _fetchGasFeeEstimateData(options = {}) { +- const { shouldUpdateState = true, networkClientId } = options; +- let ethQuery, isEIP1559Compatible, isLegacyGasAPICompatible, decimalChainId; +- if (networkClientId !== void 0) { +- const networkClient = this.messagingSystem.call( +- "NetworkController:getNetworkClientById", +- networkClientId +- ); +- isLegacyGasAPICompatible = networkClient.configuration.chainId === "0x38"; +- decimalChainId = _controllerutils.convertHexToDecimal.call(void 0, networkClient.configuration.chainId); +- try { +- const result = await this.messagingSystem.call( +- "NetworkController:getEIP1559Compatibility", +- networkClientId +- ); +- isEIP1559Compatible = result || false; +- } catch { +- isEIP1559Compatible = false; +- } +- ethQuery = new (0, _ethquery2.default)(networkClient.provider); +- } +- ethQuery ?? (ethQuery = this.ethQuery); +- isLegacyGasAPICompatible ?? (isLegacyGasAPICompatible = this.getCurrentNetworkLegacyGasAPICompatibility()); +- decimalChainId ?? (decimalChainId = _controllerutils.convertHexToDecimal.call(void 0, this.currentChainId)); +- try { +- isEIP1559Compatible ?? (isEIP1559Compatible = await this.getEIP1559Compatibility()); +- } catch (e) { +- console.error(e); +- isEIP1559Compatible ?? (isEIP1559Compatible = false); +- } +- const gasFeeCalculations = await determineGasFeeCalculations({ +- isEIP1559Compatible, +- isLegacyGasAPICompatible, +- fetchGasEstimates: _chunkQ2YPK5SLjs.fetchGasEstimates, +- fetchGasEstimatesUrl: this.EIP1559APIEndpoint.replace( +- "", +- `${decimalChainId}` +- ), +- fetchLegacyGasPriceEstimates: _chunkQ2YPK5SLjs.fetchLegacyGasPriceEstimates, +- fetchLegacyGasPriceEstimatesUrl: this.legacyAPIEndpoint.replace( +- "", +- `${decimalChainId}` +- ), +- fetchEthGasPriceEstimate: _chunkQ2YPK5SLjs.fetchEthGasPriceEstimate, +- calculateTimeEstimate: _chunkQ2YPK5SLjs.calculateTimeEstimate, +- clientId: this.clientId, +- ethQuery, +- infuraAPIKey: this.infuraAPIKey, +- nonRPCGasFeeApisDisabled: this.state.nonRPCGasFeeApisDisabled +- }); +- if (shouldUpdateState) { +- const chainId = _controllerutils.toHex.call(void 0, decimalChainId); +- this.update((state) => { +- if (this.currentChainId === chainId) { +- state.gasFeeEstimates = gasFeeCalculations.gasFeeEstimates; +- state.estimatedGasFeeTimeBounds = gasFeeCalculations.estimatedGasFeeTimeBounds; +- state.gasEstimateType = gasFeeCalculations.gasEstimateType; +- } +- state.gasFeeEstimatesByChainId ?? (state.gasFeeEstimatesByChainId = {}); +- state.gasFeeEstimatesByChainId[chainId] = { +- gasFeeEstimates: gasFeeCalculations.gasFeeEstimates, +- estimatedGasFeeTimeBounds: gasFeeCalculations.estimatedGasFeeTimeBounds, +- gasEstimateType: gasFeeCalculations.gasEstimateType +- }; +- }); +- } +- return gasFeeCalculations; +- } +- /** +- * Remove the poll token, and stop polling if the set of poll tokens is empty. +- * +- * @param pollToken - The poll token to disconnect. +- */ +- disconnectPoller(pollToken) { +- this.pollTokens.delete(pollToken); +- if (this.pollTokens.size === 0) { +- this.stopPolling(); +- } +- } +- stopPolling() { +- if (this.intervalId) { +- clearInterval(this.intervalId); +- } +- this.pollTokens.clear(); +- this.resetState(); +- } +- /** +- * Prepare to discard this controller. +- * +- * This stops any active polling. +- */ +- destroy() { +- super.destroy(); +- this.stopPolling(); +- } +- _poll() { +- if (this.intervalId) { +- clearInterval(this.intervalId); +- } +- this.intervalId = setInterval(async () => { +- await _controllerutils.safelyExecute.call(void 0, () => this._fetchGasFeeEstimateData()); +- }, this.intervalDelay); +- } +- /** +- * Fetching token list from the Token Service API. +- * +- * @private +- * @param networkClientId - The ID of the network client triggering the fetch. +- * @returns A promise that resolves when this operation completes. +- */ +- async _executePoll(networkClientId) { +- await this._fetchGasFeeEstimateData({ networkClientId }); +- } +- resetState() { +- this.update(() => { +- return defaultState; +- }); +- } +- async getEIP1559Compatibility() { +- const currentNetworkIsEIP1559Compatible = await this.getCurrentNetworkEIP1559Compatibility(); +- const currentAccountIsEIP1559Compatible = this.getCurrentAccountEIP1559Compatibility?.() ?? true; +- return currentNetworkIsEIP1559Compatible && currentAccountIsEIP1559Compatible; +- } +- getTimeEstimate(maxPriorityFeePerGas, maxFeePerGas) { +- if (!this.state.gasFeeEstimates || this.state.gasEstimateType !== GAS_ESTIMATE_TYPES.FEE_MARKET) { +- return {}; +- } +- return _chunkQ2YPK5SLjs.calculateTimeEstimate.call(void 0, +- maxPriorityFeePerGas, +- maxFeePerGas, +- this.state.gasFeeEstimates +- ); +- } +- enableNonRPCGasFeeApis() { +- this.update((state) => { +- state.nonRPCGasFeeApisDisabled = false; +- }); +- } +- disableNonRPCGasFeeApis() { +- this.update((state) => { +- state.nonRPCGasFeeApisDisabled = true; +- }); +- } +-}; +-_getProvider = new WeakMap(); +-_onNetworkControllerDidChange = new WeakSet(); +-onNetworkControllerDidChange_fn = async function(networkControllerState) { +- const newChainId = networkControllerState.providerConfig.chainId; +- if (newChainId !== this.currentChainId) { +- this.ethQuery = new (0, _ethquery2.default)(_chunkQ2YPK5SLjs.__privateGet.call(void 0, this, _getProvider).call(this)); +- await this.resetPolling(); +- this.currentChainId = newChainId; +- } +-}; +-var GasFeeController_default = GasFeeController; +- +-// src/determineGasFeeCalculations.ts +-async function determineGasFeeCalculations(args) { +- try { +- return await getEstimatesUsingFallbacks(args); +- } catch (error) { +- if (error instanceof Error) { +- throw new Error( +- `Gas fee/price estimation failed. Message: ${error.message}` +- ); +- } +- throw error; +- } +-} +-async function getEstimatesUsingFallbacks(request) { +- const { +- isEIP1559Compatible, +- isLegacyGasAPICompatible, +- nonRPCGasFeeApisDisabled +- } = request; +- try { +- if (isEIP1559Compatible && !nonRPCGasFeeApisDisabled) { +- return await getEstimatesUsingFeeMarketEndpoint(request); +- } +- if (isLegacyGasAPICompatible && !nonRPCGasFeeApisDisabled) { +- return await getEstimatesUsingLegacyEndpoint(request); +- } +- throw new Error("Main gas fee/price estimation failed. Use fallback"); +- } catch { +- return await getEstimatesUsingProvider(request); +- } +-} +-async function getEstimatesUsingFeeMarketEndpoint(request) { +- const { +- fetchGasEstimates: fetchGasEstimates2, +- fetchGasEstimatesUrl, +- infuraAPIKey, +- clientId, +- calculateTimeEstimate: calculateTimeEstimate2 +- } = request; +- const estimates = await fetchGasEstimates2( +- fetchGasEstimatesUrl, +- infuraAPIKey, +- clientId +- ); +- const { suggestedMaxPriorityFeePerGas, suggestedMaxFeePerGas } = estimates.medium; +- const estimatedGasFeeTimeBounds = calculateTimeEstimate2( +- suggestedMaxPriorityFeePerGas, +- suggestedMaxFeePerGas, +- estimates +- ); +- return { +- gasFeeEstimates: estimates, +- estimatedGasFeeTimeBounds, +- gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET +- }; +-} +-async function getEstimatesUsingLegacyEndpoint(request) { +- const { +- fetchLegacyGasPriceEstimates: fetchLegacyGasPriceEstimates2, +- fetchLegacyGasPriceEstimatesUrl, +- infuraAPIKey, +- clientId +- } = request; +- const estimates = await fetchLegacyGasPriceEstimates2( +- fetchLegacyGasPriceEstimatesUrl, +- infuraAPIKey, +- clientId +- ); +- return { +- gasFeeEstimates: estimates, +- estimatedGasFeeTimeBounds: {}, +- gasEstimateType: GAS_ESTIMATE_TYPES.LEGACY +- }; +-} +-async function getEstimatesUsingProvider(request) { +- const { ethQuery, fetchEthGasPriceEstimate: fetchEthGasPriceEstimate2 } = request; +- const estimates = await fetchEthGasPriceEstimate2(ethQuery); +- return { +- gasFeeEstimates: estimates, +- estimatedGasFeeTimeBounds: {}, +- gasEstimateType: GAS_ESTIMATE_TYPES.ETH_GASPRICE +- }; +-} +- +- +- +- +- +- +- +-exports.determineGasFeeCalculations = determineGasFeeCalculations; exports.GAS_API_BASE_URL = GAS_API_BASE_URL; exports.GAS_ESTIMATE_TYPES = GAS_ESTIMATE_TYPES; exports.GasFeeController = GasFeeController; exports.GasFeeController_default = GasFeeController_default; +-//# sourceMappingURL=chunk-H5WHAYLI.js.map +\ No newline at end of file +diff --git a/dist/chunk-H5WHAYLI.js.map b/dist/chunk-H5WHAYLI.js.map +deleted file mode 100644 +index ed761f584584470a8176f029d9d860dc017428fc..0000000000000000000000000000000000000000 +--- a/dist/chunk-H5WHAYLI.js.map ++++ /dev/null +@@ -1 +0,0 @@ +-{"version":3,"sources":["../src/GasFeeController.ts","../src/determineGasFeeCalculations.ts"],"names":["fetchGasEstimates","calculateTimeEstimate","fetchLegacyGasPriceEstimates","fetchEthGasPriceEstimate"],"mappings":";;;;;;;;;;;;AAKA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,OAAO,cAAc;AAUrB,SAAS,uCAAuC;AAEhD,SAAS,MAAM,cAAc;AAUtB,IAAM,mBAAmB;AA0BzB,IAAM,qBAAqB;AAAA,EAChC,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,cAAc;AAAA,EACd,MAAM;AACR;AAiGA,IAAM,WAAW;AAAA,EACf,0BAA0B;AAAA,IACxB,SAAS;AAAA,IACT,WAAW;AAAA,EACb;AAAA,EACA,iBAAiB,EAAE,SAAS,MAAM,WAAW,MAAM;AAAA,EACnD,2BAA2B,EAAE,SAAS,MAAM,WAAW,MAAM;AAAA,EAC7D,iBAAiB,EAAE,SAAS,MAAM,WAAW,MAAM;AAAA,EACnD,0BAA0B,EAAE,SAAS,MAAM,WAAW,MAAM;AAC9D;AAqDA,IAAM,OAAO;AA0Bb,IAAM,eAA4B;AAAA,EAChC,0BAA0B,CAAC;AAAA,EAC3B,iBAAiB,CAAC;AAAA,EAClB,2BAA2B,CAAC;AAAA,EAC5B,iBAAiB,mBAAmB;AAAA,EACpC,0BAA0B;AAC5B;AA9PA;AAmQO,IAAM,mBAAN,cAA+B,gCAIpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgDA,YAAY;AAAA,IACV,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAYG;AACD,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,EAAE,GAAG,cAAc,GAAG,MAAM;AAAA,IACrC,CAAC;AAuPH,uBAAM;AA5SN;AAsDE,SAAK,gBAAgB;AACrB,SAAK,kBAAkB,QAAQ;AAC/B,SAAK,aAAa,oBAAI,IAAI;AAC1B,SAAK,wCACH;AACF,SAAK,6CACH;AACF,SAAK,wCACH;AACF,uBAAK,cAAe;AACpB,SAAK,qBAAqB,GAAG,gBAAgB;AAC7C,SAAK,oBAAoB,GAAG,gBAAgB;AAC5C,SAAK,WAAW;AAChB,SAAK,eAAe;AAEpB,SAAK,WAAW,IAAI,SAAS,mBAAK,cAAL,UAAmB;AAEhD,QAAI,sBAAsB,YAAY;AACpC,WAAK,iBAAiB,WAAW;AACjC,yBAAmB,OAAO,2BAA2B;AACnD,cAAM,sBAAK,gEAAL,WAAmC;AAAA,MAC3C,CAAC;AAAA,IACH,OAAO;AACL,WAAK,iBAAiB,KAAK,gBAAgB;AAAA,QACzC;AAAA,MACF,EAAE,eAAe;AACjB,WAAK,gBAAgB;AAAA,QACnB;AAAA,QACA,OAAO,2BAA2B;AAChC,gBAAM,sBAAK,gEAAL,WAAmC;AAAA,QAC3C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,eAAe;AACnB,QAAI,KAAK,WAAW,SAAS,GAAG;AAC9B,YAAM,SAAS,MAAM,KAAK,KAAK,UAAU;AACzC,WAAK,YAAY;AACjB,YAAM,KAAK,kCAAkC,OAAO,CAAC,CAAC;AACtD,aAAO,MAAM,CAAC,EAAE,QAAQ,CAAC,UAAU;AACjC,aAAK,WAAW,IAAI,KAAK;AAAA,MAC3B,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,MAAM,qBAAqB,SAAsC;AAC/D,WAAO,MAAM,KAAK,yBAAyB,OAAO;AAAA,EACpD;AAAA,EAEA,MAAM,kCACJ,WACiB;AACjB,UAAM,aAAa,aAAa,OAAO;AAEvC,SAAK,WAAW,IAAI,UAAU;AAE9B,QAAI,KAAK,WAAW,SAAS,GAAG;AAC9B,YAAM,KAAK,yBAAyB;AACpC,WAAK,MAAM;AAAA,IACb;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,yBACJ,UAAsC,CAAC,GACjB;AACtB,UAAM,EAAE,oBAAoB,MAAM,gBAAgB,IAAI;AAEtD,QAAI,UACF,qBACA,0BACA;AAEF,QAAI,oBAAoB,QAAW;AACjC,YAAM,gBAAgB,KAAK,gBAAgB;AAAA,QACzC;AAAA,QACA;AAAA,MACF;AACA,iCAA2B,cAAc,cAAc,YAAY;AAEnE,uBAAiB,oBAAoB,cAAc,cAAc,OAAO;AAExE,UAAI;AACF,cAAM,SAAS,MAAM,KAAK,gBAAgB;AAAA,UACxC;AAAA,UACA;AAAA,QACF;AACA,8BAAsB,UAAU;AAAA,MAClC,QAAQ;AACN,8BAAsB;AAAA,MACxB;AACA,iBAAW,IAAI,SAAS,cAAc,QAAQ;AAAA,IAChD;AAEA,4BAAa,KAAK;AAElB,4DACE,KAAK,2CAA2C;AAElD,wCAAmB,oBAAoB,KAAK,cAAc;AAE1D,QAAI;AACF,oDAAwB,MAAM,KAAK,wBAAwB;AAAA,IAC7D,SAAS,GAAG;AACV,cAAQ,MAAM,CAAC;AACf,oDAAwB;AAAA,IAC1B;AAEA,UAAM,qBAAqB,MAAM,4BAA4B;AAAA,MAC3D;AAAA,MACA;AAAA,MACA;AAAA,MACA,sBAAsB,KAAK,mBAAmB;AAAA,QAC5C;AAAA,QACA,GAAG,cAAc;AAAA,MACnB;AAAA,MACA;AAAA,MACA,iCAAiC,KAAK,kBAAkB;AAAA,QACtD;AAAA,QACA,GAAG,cAAc;AAAA,MACnB;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU,KAAK;AAAA,MACf;AAAA,MACA,cAAc,KAAK;AAAA,MACnB,0BAA0B,KAAK,MAAM;AAAA,IACvC,CAAC;AAED,QAAI,mBAAmB;AACrB,YAAM,UAAU,MAAM,cAAc;AACpC,WAAK,OAAO,CAAC,UAAU;AACrB,YAAI,KAAK,mBAAmB,SAAS;AACnC,gBAAM,kBAAkB,mBAAmB;AAC3C,gBAAM,4BACJ,mBAAmB;AACrB,gBAAM,kBAAkB,mBAAmB;AAAA,QAC7C;AACA,cAAM,6BAAN,MAAM,2BAA6B,CAAC;AACpC,cAAM,yBAAyB,OAAO,IAAI;AAAA,UACxC,iBAAiB,mBAAmB;AAAA,UACpC,2BACE,mBAAmB;AAAA,UACrB,iBAAiB,mBAAmB;AAAA,QACtC;AAAA,MACF,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBAAiB,WAAmB;AAClC,SAAK,WAAW,OAAO,SAAS;AAChC,QAAI,KAAK,WAAW,SAAS,GAAG;AAC9B,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA,EAEA,cAAc;AACZ,QAAI,KAAK,YAAY;AACnB,oBAAc,KAAK,UAAU;AAAA,IAC/B;AACA,SAAK,WAAW,MAAM;AACtB,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOS,UAAU;AACjB,UAAM,QAAQ;AACd,SAAK,YAAY;AAAA,EACnB;AAAA,EAEQ,QAAQ;AACd,QAAI,KAAK,YAAY;AACnB,oBAAc,KAAK,UAAU;AAAA,IAC/B;AAEA,SAAK,aAAa,YAAY,YAAY;AACxC,YAAM,cAAc,MAAM,KAAK,yBAAyB,CAAC;AAAA,IAC3D,GAAG,KAAK,aAAa;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,aAAa,iBAAwC;AACzD,UAAM,KAAK,yBAAyB,EAAE,gBAAgB,CAAC;AAAA,EACzD;AAAA,EAEQ,aAAa;AACnB,SAAK,OAAO,MAAM;AAChB,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,0BAA0B;AACtC,UAAM,oCACJ,MAAM,KAAK,sCAAsC;AACnD,UAAM,oCACJ,KAAK,wCAAwC,KAAK;AAEpD,WACE,qCAAqC;AAAA,EAEzC;AAAA,EAEA,gBACE,sBACA,cACmD;AACnD,QACE,CAAC,KAAK,MAAM,mBACZ,KAAK,MAAM,oBAAoB,mBAAmB,YAClD;AACA,aAAO,CAAC;AAAA,IACV;AACA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,KAAK,MAAM;AAAA,IACb;AAAA,EACF;AAAA,EAaA,yBAAyB;AACvB,SAAK,OAAO,CAAC,UAAU;AACrB,YAAM,2BAA2B;AAAA,IACnC,CAAC;AAAA,EACH;AAAA,EAEA,0BAA0B;AACxB,SAAK,OAAO,CAAC,UAAU;AACrB,YAAM,2BAA2B;AAAA,IACnC,CAAC;AAAA,EACH;AACF;AAlUE;AA4SM;AAAA,kCAA6B,eAAC,wBAAsC;AACxE,QAAM,aAAa,uBAAuB,eAAe;AAEzD,MAAI,eAAe,KAAK,gBAAgB;AACtC,SAAK,WAAW,IAAI,SAAS,mBAAK,cAAL,UAAmB;AAChD,UAAM,KAAK,aAAa;AAExB,SAAK,iBAAiB;AAAA,EACxB;AACF;AAeF,IAAO,2BAAQ;;;ACliBf,eAAO,4BACL,MAC6B;AAC7B,MAAI;AACF,WAAO,MAAM,2BAA2B,IAAI;AAAA,EAC9C,SAAS,OAAO;AACd,QAAI,iBAAiB,OAAO;AAC1B,YAAM,IAAI;AAAA,QACR,6CAA6C,MAAM,OAAO;AAAA,MAC5D;AAAA,IACF;AAEA,UAAM;AAAA,EACR;AACF;AAOA,eAAe,2BACb,SAC6B;AAC7B,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,MAAI;AACF,QAAI,uBAAuB,CAAC,0BAA0B;AACpD,aAAO,MAAM,mCAAmC,OAAO;AAAA,IACzD;AAEA,QAAI,4BAA4B,CAAC,0BAA0B;AACzD,aAAO,MAAM,gCAAgC,OAAO;AAAA,IACtD;AAEA,UAAM,IAAI,MAAM,oDAAoD;AAAA,EACtE,QAAQ;AACN,WAAO,MAAM,0BAA0B,OAAO;AAAA,EAChD;AACF;AAOA,eAAe,mCACb,SAC6B;AAC7B,QAAM;AAAA,IACJ,mBAAAA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,uBAAAC;AAAA,EACF,IAAI;AAEJ,QAAM,YAAY,MAAMD;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,EAAE,+BAA+B,sBAAsB,IAC3D,UAAU;AAEZ,QAAM,4BAA4BC;AAAA,IAChC;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,SAAO;AAAA,IACL,iBAAiB;AAAA,IACjB;AAAA,IACA,iBAAiB,mBAAmB;AAAA,EACtC;AACF;AAOA,eAAe,gCACb,SAC6B;AAC7B,QAAM;AAAA,IACJ,8BAAAC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,QAAM,YAAY,MAAMA;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,SAAO;AAAA,IACL,iBAAiB;AAAA,IACjB,2BAA2B,CAAC;AAAA,IAC5B,iBAAiB,mBAAmB;AAAA,EACtC;AACF;AAOA,eAAe,0BACb,SAC6B;AAC7B,QAAM,EAAE,UAAU,0BAAAC,0BAAyB,IAAI;AAE/C,QAAM,YAAY,MAAMA,0BAAyB,QAAQ;AAEzD,SAAO;AAAA,IACL,iBAAiB;AAAA,IACjB,2BAA2B,CAAC;AAAA,IAC5B,iBAAiB,mBAAmB;AAAA,EACtC;AACF","sourcesContent":["import type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n RestrictedControllerMessenger,\n} from '@metamask/base-controller';\nimport {\n convertHexToDecimal,\n safelyExecute,\n toHex,\n} from '@metamask/controller-utils';\nimport EthQuery from '@metamask/eth-query';\nimport type {\n NetworkClientId,\n NetworkControllerGetEIP1559CompatibilityAction,\n NetworkControllerGetNetworkClientByIdAction,\n NetworkControllerGetStateAction,\n NetworkControllerNetworkDidChangeEvent,\n NetworkState,\n ProviderProxy,\n} from '@metamask/network-controller';\nimport { StaticIntervalPollingController } from '@metamask/polling-controller';\nimport type { Hex } from '@metamask/utils';\nimport { v1 as random } from 'uuid';\n\nimport determineGasFeeCalculations from './determineGasFeeCalculations';\nimport {\n calculateTimeEstimate,\n fetchGasEstimates,\n fetchLegacyGasPriceEstimates,\n fetchEthGasPriceEstimate,\n} from './gas-util';\n\nexport const GAS_API_BASE_URL = 'https://gas.api.infura.io';\n\nexport type unknownString = 'unknown';\n\n// Fee Market describes the way gas is set after the london hardfork, and was\n// defined by EIP-1559.\nexport type FeeMarketEstimateType = 'fee-market';\n// Legacy describes gasPrice estimates from before london hardfork, when the\n// user is connected to mainnet and are presented with fast/average/slow\n// estimate levels to choose from.\nexport type LegacyEstimateType = 'legacy';\n// EthGasPrice describes a gasPrice estimate received from eth_gasPrice. Post\n// london this value should only be used for legacy type transactions when on\n// networks that support EIP-1559. This type of estimate is the most accurate\n// to display on custom networks that don't support EIP-1559.\nexport type EthGasPriceEstimateType = 'eth_gasPrice';\n// NoEstimate describes the state of the controller before receiving its first\n// estimate.\nexport type NoEstimateType = 'none';\n\n/**\n * Indicates which type of gasEstimate the controller is currently returning.\n * This is useful as a way of asserting that the shape of gasEstimates matches\n * expectations. NONE is a special case indicating that no previous gasEstimate\n * has been fetched.\n */\nexport const GAS_ESTIMATE_TYPES = {\n FEE_MARKET: 'fee-market' as FeeMarketEstimateType,\n LEGACY: 'legacy' as LegacyEstimateType,\n ETH_GASPRICE: 'eth_gasPrice' as EthGasPriceEstimateType,\n NONE: 'none' as NoEstimateType,\n};\n\nexport type GasEstimateType =\n | FeeMarketEstimateType\n | EthGasPriceEstimateType\n | LegacyEstimateType\n | NoEstimateType;\n\nexport type EstimatedGasFeeTimeBounds = {\n lowerTimeBound: number | null;\n upperTimeBound: number | unknownString;\n};\n\n/**\n * @type EthGasPriceEstimate\n *\n * A single gas price estimate for networks and accounts that don't support EIP-1559\n * This estimate comes from eth_gasPrice but is converted to dec gwei to match other\n * return values\n * @property gasPrice - A GWEI dec string\n */\n\nexport type EthGasPriceEstimate = {\n gasPrice: string;\n};\n\n/**\n * @type LegacyGasPriceEstimate\n *\n * A set of gas price estimates for networks and accounts that don't support EIP-1559\n * These estimates include low, medium and high all as strings representing gwei in\n * decimal format.\n * @property high - gasPrice, in decimal gwei string format, suggested for fast inclusion\n * @property medium - gasPrice, in decimal gwei string format, suggested for avg inclusion\n * @property low - gasPrice, in decimal gwei string format, suggested for slow inclusion\n */\nexport type LegacyGasPriceEstimate = {\n high: string;\n medium: string;\n low: string;\n};\n\n/**\n * @type Eip1559GasFee\n *\n * Data necessary to provide an estimate of a gas fee with a specific tip\n * @property minWaitTimeEstimate - The fastest the transaction will take, in milliseconds\n * @property maxWaitTimeEstimate - The slowest the transaction will take, in milliseconds\n * @property suggestedMaxPriorityFeePerGas - A suggested \"tip\", a GWEI hex number\n * @property suggestedMaxFeePerGas - A suggested max fee, the most a user will pay. a GWEI hex number\n */\nexport type Eip1559GasFee = {\n minWaitTimeEstimate: number; // a time duration in milliseconds\n maxWaitTimeEstimate: number; // a time duration in milliseconds\n suggestedMaxPriorityFeePerGas: string; // a GWEI decimal number\n suggestedMaxFeePerGas: string; // a GWEI decimal number\n};\n\n/**\n * @type GasFeeEstimates\n *\n * Data necessary to provide multiple GasFee estimates, and supporting information, to the user\n * @property low - A GasFee for a minimum necessary combination of tip and maxFee\n * @property medium - A GasFee for a recommended combination of tip and maxFee\n * @property high - A GasFee for a high combination of tip and maxFee\n * @property estimatedBaseFee - An estimate of what the base fee will be for the pending/next block. A GWEI dec number\n * @property networkCongestion - A normalized number that can be used to gauge the congestion\n * level of the network, with 0 meaning not congested and 1 meaning extremely congested\n */\nexport type GasFeeEstimates = SourcedGasFeeEstimates | FallbackGasFeeEstimates;\n\ntype SourcedGasFeeEstimates = {\n low: Eip1559GasFee;\n medium: Eip1559GasFee;\n high: Eip1559GasFee;\n estimatedBaseFee: string;\n historicalBaseFeeRange: [string, string];\n baseFeeTrend: 'up' | 'down' | 'level';\n latestPriorityFeeRange: [string, string];\n historicalPriorityFeeRange: [string, string];\n priorityFeeTrend: 'up' | 'down' | 'level';\n networkCongestion: number;\n};\n\ntype FallbackGasFeeEstimates = {\n low: Eip1559GasFee;\n medium: Eip1559GasFee;\n high: Eip1559GasFee;\n estimatedBaseFee: string;\n historicalBaseFeeRange: null;\n baseFeeTrend: null;\n latestPriorityFeeRange: null;\n historicalPriorityFeeRange: null;\n priorityFeeTrend: null;\n networkCongestion: null;\n};\n\nconst metadata = {\n gasFeeEstimatesByChainId: {\n persist: true,\n anonymous: false,\n },\n gasFeeEstimates: { persist: true, anonymous: false },\n estimatedGasFeeTimeBounds: { persist: true, anonymous: false },\n gasEstimateType: { persist: true, anonymous: false },\n nonRPCGasFeeApisDisabled: { persist: true, anonymous: false },\n};\n\nexport type GasFeeStateEthGasPrice = {\n gasFeeEstimates: EthGasPriceEstimate;\n estimatedGasFeeTimeBounds: Record;\n gasEstimateType: EthGasPriceEstimateType;\n};\n\nexport type GasFeeStateFeeMarket = {\n gasFeeEstimates: GasFeeEstimates;\n estimatedGasFeeTimeBounds: EstimatedGasFeeTimeBounds | Record;\n gasEstimateType: FeeMarketEstimateType;\n};\n\nexport type GasFeeStateLegacy = {\n gasFeeEstimates: LegacyGasPriceEstimate;\n estimatedGasFeeTimeBounds: Record;\n gasEstimateType: LegacyEstimateType;\n};\n\nexport type GasFeeStateNoEstimates = {\n gasFeeEstimates: Record;\n estimatedGasFeeTimeBounds: Record;\n gasEstimateType: NoEstimateType;\n};\n\nexport type FetchGasFeeEstimateOptions = {\n shouldUpdateState?: boolean;\n networkClientId?: NetworkClientId;\n};\n\n/**\n * @type GasFeeState\n *\n * Gas Fee controller state\n * @property gasFeeEstimates - Gas fee estimate data based on new EIP-1559 properties\n * @property estimatedGasFeeTimeBounds - Estimates representing the minimum and maximum\n */\nexport type SingleChainGasFeeState =\n | GasFeeStateEthGasPrice\n | GasFeeStateFeeMarket\n | GasFeeStateLegacy\n | GasFeeStateNoEstimates;\n\nexport type GasFeeEstimatesByChainId = {\n gasFeeEstimatesByChainId?: Record;\n};\n\nexport type GasFeeState = GasFeeEstimatesByChainId &\n SingleChainGasFeeState & {\n nonRPCGasFeeApisDisabled?: boolean;\n };\n\nconst name = 'GasFeeController';\n\nexport type GasFeeStateChange = ControllerStateChangeEvent<\n typeof name,\n GasFeeState\n>;\n\nexport type GetGasFeeState = ControllerGetStateAction;\n\nexport type GasFeeControllerActions = GetGasFeeState;\n\nexport type GasFeeControllerEvents = GasFeeStateChange;\n\ntype AllowedActions =\n | NetworkControllerGetStateAction\n | NetworkControllerGetNetworkClientByIdAction\n | NetworkControllerGetEIP1559CompatibilityAction;\n\ntype GasFeeMessenger = RestrictedControllerMessenger<\n typeof name,\n GasFeeControllerActions | AllowedActions,\n GasFeeControllerEvents | NetworkControllerNetworkDidChangeEvent,\n AllowedActions['type'],\n NetworkControllerNetworkDidChangeEvent['type']\n>;\n\nconst defaultState: GasFeeState = {\n gasFeeEstimatesByChainId: {},\n gasFeeEstimates: {},\n estimatedGasFeeTimeBounds: {},\n gasEstimateType: GAS_ESTIMATE_TYPES.NONE,\n nonRPCGasFeeApisDisabled: false,\n};\n\n/**\n * Controller that retrieves gas fee estimate data and polls for updated data on a set interval\n */\nexport class GasFeeController extends StaticIntervalPollingController<\n typeof name,\n GasFeeState,\n GasFeeMessenger\n> {\n private intervalId?: ReturnType;\n\n private readonly intervalDelay;\n\n private readonly pollTokens: Set;\n\n private readonly legacyAPIEndpoint: string;\n\n private readonly EIP1559APIEndpoint: string;\n\n private readonly getCurrentNetworkEIP1559Compatibility;\n\n private readonly getCurrentNetworkLegacyGasAPICompatibility;\n\n private readonly getCurrentAccountEIP1559Compatibility;\n\n private readonly infuraAPIKey: string;\n\n private currentChainId;\n\n private ethQuery?: EthQuery;\n\n private readonly clientId?: string;\n\n #getProvider: () => ProviderProxy;\n\n /**\n * Creates a GasFeeController instance.\n *\n * @param options - The controller options.\n * @param options.interval - The time in milliseconds to wait between polls.\n * @param options.messenger - The controller messenger.\n * @param options.state - The initial state.\n * @param options.getCurrentNetworkEIP1559Compatibility - Determines whether or not the current\n * network is EIP-1559 compatible.\n * @param options.getCurrentNetworkLegacyGasAPICompatibility - Determines whether or not the\n * current network is compatible with the legacy gas price API.\n * @param options.getCurrentAccountEIP1559Compatibility - Determines whether or not the current\n * account is EIP-1559 compatible.\n * @param options.getChainId - Returns the current chain ID.\n * @param options.getProvider - Returns a network provider for the current network.\n * @param options.onNetworkDidChange - A function for registering an event handler for the\n * network state change event.\n * @param options.clientId - The client ID used to identify to the gas estimation API who is\n * asking for estimates.\n * @param options.infuraAPIKey - The Infura API key used for infura API requests.\n */\n constructor({\n interval = 15000,\n messenger,\n state,\n getCurrentNetworkEIP1559Compatibility,\n getCurrentAccountEIP1559Compatibility,\n getChainId,\n getCurrentNetworkLegacyGasAPICompatibility,\n getProvider,\n onNetworkDidChange,\n clientId,\n infuraAPIKey,\n }: {\n interval?: number;\n messenger: GasFeeMessenger;\n state?: GasFeeState;\n getCurrentNetworkEIP1559Compatibility: () => Promise;\n getCurrentNetworkLegacyGasAPICompatibility: () => boolean;\n getCurrentAccountEIP1559Compatibility?: () => boolean;\n getChainId?: () => Hex;\n getProvider: () => ProviderProxy;\n onNetworkDidChange?: (listener: (state: NetworkState) => void) => void;\n clientId?: string;\n infuraAPIKey: string;\n }) {\n super({\n name,\n metadata,\n messenger,\n state: { ...defaultState, ...state },\n });\n this.intervalDelay = interval;\n this.setIntervalLength(interval);\n this.pollTokens = new Set();\n this.getCurrentNetworkEIP1559Compatibility =\n getCurrentNetworkEIP1559Compatibility;\n this.getCurrentNetworkLegacyGasAPICompatibility =\n getCurrentNetworkLegacyGasAPICompatibility;\n this.getCurrentAccountEIP1559Compatibility =\n getCurrentAccountEIP1559Compatibility;\n this.#getProvider = getProvider;\n this.EIP1559APIEndpoint = `${GAS_API_BASE_URL}/networks//suggestedGasFees`;\n this.legacyAPIEndpoint = `${GAS_API_BASE_URL}/networks//gasPrices`;\n this.clientId = clientId;\n this.infuraAPIKey = infuraAPIKey;\n\n this.ethQuery = new EthQuery(this.#getProvider());\n\n if (onNetworkDidChange && getChainId) {\n this.currentChainId = getChainId();\n onNetworkDidChange(async (networkControllerState) => {\n await this.#onNetworkControllerDidChange(networkControllerState);\n });\n } else {\n this.currentChainId = this.messagingSystem.call(\n 'NetworkController:getState',\n ).providerConfig.chainId;\n this.messagingSystem.subscribe(\n 'NetworkController:networkDidChange',\n async (networkControllerState) => {\n await this.#onNetworkControllerDidChange(networkControllerState);\n },\n );\n }\n }\n\n async resetPolling() {\n if (this.pollTokens.size !== 0) {\n const tokens = Array.from(this.pollTokens);\n this.stopPolling();\n await this.getGasFeeEstimatesAndStartPolling(tokens[0]);\n tokens.slice(1).forEach((token) => {\n this.pollTokens.add(token);\n });\n }\n }\n\n async fetchGasFeeEstimates(options?: FetchGasFeeEstimateOptions) {\n return await this._fetchGasFeeEstimateData(options);\n }\n\n async getGasFeeEstimatesAndStartPolling(\n pollToken: string | undefined,\n ): Promise {\n const _pollToken = pollToken || random();\n\n this.pollTokens.add(_pollToken);\n\n if (this.pollTokens.size === 1) {\n await this._fetchGasFeeEstimateData();\n this._poll();\n }\n\n return _pollToken;\n }\n\n /**\n * Gets and sets gasFeeEstimates in state.\n *\n * @param options - The gas fee estimate options.\n * @param options.shouldUpdateState - Determines whether the state should be updated with the\n * updated gas estimates.\n * @returns The gas fee estimates.\n */\n async _fetchGasFeeEstimateData(\n options: FetchGasFeeEstimateOptions = {},\n ): Promise {\n const { shouldUpdateState = true, networkClientId } = options;\n\n let ethQuery,\n isEIP1559Compatible,\n isLegacyGasAPICompatible,\n decimalChainId: number;\n\n if (networkClientId !== undefined) {\n const networkClient = this.messagingSystem.call(\n 'NetworkController:getNetworkClientById',\n networkClientId,\n );\n isLegacyGasAPICompatible = networkClient.configuration.chainId === '0x38';\n\n decimalChainId = convertHexToDecimal(networkClient.configuration.chainId);\n\n try {\n const result = await this.messagingSystem.call(\n 'NetworkController:getEIP1559Compatibility',\n networkClientId,\n );\n isEIP1559Compatible = result || false;\n } catch {\n isEIP1559Compatible = false;\n }\n ethQuery = new EthQuery(networkClient.provider);\n }\n\n ethQuery ??= this.ethQuery;\n\n isLegacyGasAPICompatible ??=\n this.getCurrentNetworkLegacyGasAPICompatibility();\n\n decimalChainId ??= convertHexToDecimal(this.currentChainId);\n\n try {\n isEIP1559Compatible ??= await this.getEIP1559Compatibility();\n } catch (e) {\n console.error(e);\n isEIP1559Compatible ??= false;\n }\n\n const gasFeeCalculations = await determineGasFeeCalculations({\n isEIP1559Compatible,\n isLegacyGasAPICompatible,\n fetchGasEstimates,\n fetchGasEstimatesUrl: this.EIP1559APIEndpoint.replace(\n '',\n `${decimalChainId}`,\n ),\n fetchLegacyGasPriceEstimates,\n fetchLegacyGasPriceEstimatesUrl: this.legacyAPIEndpoint.replace(\n '',\n `${decimalChainId}`,\n ),\n fetchEthGasPriceEstimate,\n calculateTimeEstimate,\n clientId: this.clientId,\n ethQuery,\n infuraAPIKey: this.infuraAPIKey,\n nonRPCGasFeeApisDisabled: this.state.nonRPCGasFeeApisDisabled,\n });\n\n if (shouldUpdateState) {\n const chainId = toHex(decimalChainId);\n this.update((state) => {\n if (this.currentChainId === chainId) {\n state.gasFeeEstimates = gasFeeCalculations.gasFeeEstimates;\n state.estimatedGasFeeTimeBounds =\n gasFeeCalculations.estimatedGasFeeTimeBounds;\n state.gasEstimateType = gasFeeCalculations.gasEstimateType;\n }\n state.gasFeeEstimatesByChainId ??= {};\n state.gasFeeEstimatesByChainId[chainId] = {\n gasFeeEstimates: gasFeeCalculations.gasFeeEstimates,\n estimatedGasFeeTimeBounds:\n gasFeeCalculations.estimatedGasFeeTimeBounds,\n gasEstimateType: gasFeeCalculations.gasEstimateType,\n } as SingleChainGasFeeState;\n });\n }\n\n return gasFeeCalculations;\n }\n\n /**\n * Remove the poll token, and stop polling if the set of poll tokens is empty.\n *\n * @param pollToken - The poll token to disconnect.\n */\n disconnectPoller(pollToken: string) {\n this.pollTokens.delete(pollToken);\n if (this.pollTokens.size === 0) {\n this.stopPolling();\n }\n }\n\n stopPolling() {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n }\n this.pollTokens.clear();\n this.resetState();\n }\n\n /**\n * Prepare to discard this controller.\n *\n * This stops any active polling.\n */\n override destroy() {\n super.destroy();\n this.stopPolling();\n }\n\n private _poll() {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n }\n\n this.intervalId = setInterval(async () => {\n await safelyExecute(() => this._fetchGasFeeEstimateData());\n }, this.intervalDelay);\n }\n\n /**\n * Fetching token list from the Token Service API.\n *\n * @private\n * @param networkClientId - The ID of the network client triggering the fetch.\n * @returns A promise that resolves when this operation completes.\n */\n async _executePoll(networkClientId: string): Promise {\n await this._fetchGasFeeEstimateData({ networkClientId });\n }\n\n private resetState() {\n this.update(() => {\n return defaultState;\n });\n }\n\n private async getEIP1559Compatibility() {\n const currentNetworkIsEIP1559Compatible =\n await this.getCurrentNetworkEIP1559Compatibility();\n const currentAccountIsEIP1559Compatible =\n this.getCurrentAccountEIP1559Compatibility?.() ?? true;\n\n return (\n currentNetworkIsEIP1559Compatible && currentAccountIsEIP1559Compatible\n );\n }\n\n getTimeEstimate(\n maxPriorityFeePerGas: string,\n maxFeePerGas: string,\n ): EstimatedGasFeeTimeBounds | Record {\n if (\n !this.state.gasFeeEstimates ||\n this.state.gasEstimateType !== GAS_ESTIMATE_TYPES.FEE_MARKET\n ) {\n return {};\n }\n return calculateTimeEstimate(\n maxPriorityFeePerGas,\n maxFeePerGas,\n this.state.gasFeeEstimates,\n );\n }\n\n async #onNetworkControllerDidChange(networkControllerState: NetworkState) {\n const newChainId = networkControllerState.providerConfig.chainId;\n\n if (newChainId !== this.currentChainId) {\n this.ethQuery = new EthQuery(this.#getProvider());\n await this.resetPolling();\n\n this.currentChainId = newChainId;\n }\n }\n\n enableNonRPCGasFeeApis() {\n this.update((state) => {\n state.nonRPCGasFeeApisDisabled = false;\n });\n }\n\n disableNonRPCGasFeeApis() {\n this.update((state) => {\n state.nonRPCGasFeeApisDisabled = true;\n });\n }\n}\n\nexport default GasFeeController;\n","import type {\n EstimatedGasFeeTimeBounds,\n EthGasPriceEstimate,\n GasFeeEstimates,\n GasFeeState as GasFeeCalculations,\n LegacyGasPriceEstimate,\n} from './GasFeeController';\nimport { GAS_ESTIMATE_TYPES } from './GasFeeController';\n\ntype DetermineGasFeeCalculationsRequest = {\n isEIP1559Compatible: boolean;\n isLegacyGasAPICompatible: boolean;\n fetchGasEstimates: (\n url: string,\n infuraAPIKey: string,\n clientId?: string,\n ) => Promise;\n fetchGasEstimatesUrl: string;\n fetchLegacyGasPriceEstimates: (\n url: string,\n infuraAPIKey: string,\n clientId?: string,\n ) => Promise;\n fetchLegacyGasPriceEstimatesUrl: string;\n // TODO: Replace `any` with type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n fetchEthGasPriceEstimate: (ethQuery: any) => Promise;\n calculateTimeEstimate: (\n maxPriorityFeePerGas: string,\n maxFeePerGas: string,\n gasFeeEstimates: GasFeeEstimates,\n ) => EstimatedGasFeeTimeBounds;\n clientId: string | undefined;\n // TODO: Replace `any` with type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ethQuery: any;\n infuraAPIKey: string;\n nonRPCGasFeeApisDisabled?: boolean;\n};\n\n/**\n * Obtains a set of max base and priority fee estimates along with time estimates so that we\n * can present them to users when they are sending transactions or making swaps.\n *\n * @param args - The arguments.\n * @param args.isEIP1559Compatible - Governs whether or not we can use an EIP-1559-only method to\n * produce estimates.\n * @param args.isLegacyGasAPICompatible - Governs whether or not we can use a non-EIP-1559 method to\n * produce estimates (for instance, testnets do not support estimates altogether).\n * @param args.fetchGasEstimates - A function that fetches gas estimates using an EIP-1559-specific\n * API.\n * @param args.fetchGasEstimatesUrl - The URL for the API we can use to obtain EIP-1559-specific\n * estimates.\n * @param args.fetchLegacyGasPriceEstimates - A function that fetches gas estimates using an\n * non-EIP-1559-specific API.\n * @param args.fetchLegacyGasPriceEstimatesUrl - The URL for the API we can use to obtain\n * non-EIP-1559-specific estimates.\n * @param args.fetchEthGasPriceEstimate - A function that fetches gas estimates using\n * `eth_gasPrice`.\n * @param args.calculateTimeEstimate - A function that determine time estimate bounds.\n * @param args.clientId - An identifier that an API can use to know who is asking for estimates.\n * @param args.ethQuery - An EthQuery instance we can use to talk to Ethereum directly.\n * @param args.infuraAPIKey - Infura API key to use for requests to Infura.\n * @param args.nonRPCGasFeeApisDisabled - Whether to disable requests to the legacyAPIEndpoint and the EIP1559APIEndpoint\n * @returns The gas fee calculations.\n */\nexport default async function determineGasFeeCalculations(\n args: DetermineGasFeeCalculationsRequest,\n): Promise {\n try {\n return await getEstimatesUsingFallbacks(args);\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(\n `Gas fee/price estimation failed. Message: ${error.message}`,\n );\n }\n\n throw error;\n }\n}\n\n/**\n * Retrieve the gas fee estimates using a series of fallback mechanisms.\n * @param request - The request object.\n * @returns The gas fee estimates.\n */\nasync function getEstimatesUsingFallbacks(\n request: DetermineGasFeeCalculationsRequest,\n): Promise {\n const {\n isEIP1559Compatible,\n isLegacyGasAPICompatible,\n nonRPCGasFeeApisDisabled,\n } = request;\n\n try {\n if (isEIP1559Compatible && !nonRPCGasFeeApisDisabled) {\n return await getEstimatesUsingFeeMarketEndpoint(request);\n }\n\n if (isLegacyGasAPICompatible && !nonRPCGasFeeApisDisabled) {\n return await getEstimatesUsingLegacyEndpoint(request);\n }\n\n throw new Error('Main gas fee/price estimation failed. Use fallback');\n } catch {\n return await getEstimatesUsingProvider(request);\n }\n}\n\n/**\n * Retrieve gas fee estimates using the EIP-1559 endpoint of the gas API.\n * @param request - The request object.\n * @returns The gas fee estimates.\n */\nasync function getEstimatesUsingFeeMarketEndpoint(\n request: DetermineGasFeeCalculationsRequest,\n): Promise {\n const {\n fetchGasEstimates,\n fetchGasEstimatesUrl,\n infuraAPIKey,\n clientId,\n calculateTimeEstimate,\n } = request;\n\n const estimates = await fetchGasEstimates(\n fetchGasEstimatesUrl,\n infuraAPIKey,\n clientId,\n );\n\n const { suggestedMaxPriorityFeePerGas, suggestedMaxFeePerGas } =\n estimates.medium;\n\n const estimatedGasFeeTimeBounds = calculateTimeEstimate(\n suggestedMaxPriorityFeePerGas,\n suggestedMaxFeePerGas,\n estimates,\n );\n\n return {\n gasFeeEstimates: estimates,\n estimatedGasFeeTimeBounds,\n gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET,\n };\n}\n\n/**\n * Retrieve gas fee estimates using the legacy endpoint of the gas API.\n * @param request - The request object.\n * @returns The gas fee estimates.\n */\nasync function getEstimatesUsingLegacyEndpoint(\n request: DetermineGasFeeCalculationsRequest,\n): Promise {\n const {\n fetchLegacyGasPriceEstimates,\n fetchLegacyGasPriceEstimatesUrl,\n infuraAPIKey,\n clientId,\n } = request;\n\n const estimates = await fetchLegacyGasPriceEstimates(\n fetchLegacyGasPriceEstimatesUrl,\n infuraAPIKey,\n clientId,\n );\n\n return {\n gasFeeEstimates: estimates,\n estimatedGasFeeTimeBounds: {},\n gasEstimateType: GAS_ESTIMATE_TYPES.LEGACY,\n };\n}\n\n/**\n * Retrieve gas fee estimates using an `eth_gasPrice` call to the RPC provider.\n * @param request - The request object.\n * @returns The gas fee estimates.\n */\nasync function getEstimatesUsingProvider(\n request: DetermineGasFeeCalculationsRequest,\n): Promise {\n const { ethQuery, fetchEthGasPriceEstimate } = request;\n\n const estimates = await fetchEthGasPriceEstimate(ethQuery);\n\n return {\n gasFeeEstimates: estimates,\n estimatedGasFeeTimeBounds: {},\n gasEstimateType: GAS_ESTIMATE_TYPES.ETH_GASPRICE,\n };\n}\n"]} +\ No newline at end of file +diff --git a/dist/chunk-KORLXV32.mjs b/dist/chunk-KORLXV32.mjs +deleted file mode 100644 +index a964582d5c161e6b650fe9447442ca9540f9fdb9..0000000000000000000000000000000000000000 +--- a/dist/chunk-KORLXV32.mjs ++++ /dev/null +@@ -1,165 +0,0 @@ +-var __accessCheck = (obj, member, msg) => { +- if (!member.has(obj)) +- throw TypeError("Cannot " + msg); +-}; +-var __privateGet = (obj, member, getter) => { +- __accessCheck(obj, member, "read from private field"); +- return getter ? getter.call(obj) : member.get(obj); +-}; +-var __privateAdd = (obj, member, value) => { +- if (member.has(obj)) +- throw TypeError("Cannot add the same private member more than once"); +- member instanceof WeakSet ? member.add(obj) : member.set(obj, value); +-}; +-var __privateSet = (obj, member, value, setter) => { +- __accessCheck(obj, member, "write to private field"); +- setter ? setter.call(obj, value) : member.set(obj, value); +- return value; +-}; +-var __privateMethod = (obj, member, method) => { +- __accessCheck(obj, member, "access private method"); +- return method; +-}; +- +-// src/gas-util.ts +-import { +- query, +- handleFetch, +- gweiDecToWEIBN, +- weiHexToGweiDec +-} from "@metamask/controller-utils"; +-import BN from "bn.js"; +-var makeClientIdHeader = (clientId) => ({ "X-Client-Id": clientId }); +-function normalizeGWEIDecimalNumbers(n) { +- const numberAsWEIHex = gweiDecToWEIBN(n).toString(16); +- const numberAsGWEI = weiHexToGweiDec(numberAsWEIHex); +- return numberAsGWEI; +-} +-async function fetchGasEstimates(url, infuraAPIKey, clientId) { +- const infuraAuthToken = buildInfuraAuthToken(infuraAPIKey); +- const estimates = await handleFetch(url, { +- headers: getHeaders(infuraAuthToken, clientId) +- }); +- return { +- low: { +- ...estimates.low, +- suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers( +- estimates.low.suggestedMaxPriorityFeePerGas +- ), +- suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers( +- estimates.low.suggestedMaxFeePerGas +- ) +- }, +- medium: { +- ...estimates.medium, +- suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers( +- estimates.medium.suggestedMaxPriorityFeePerGas +- ), +- suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers( +- estimates.medium.suggestedMaxFeePerGas +- ) +- }, +- high: { +- ...estimates.high, +- suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers( +- estimates.high.suggestedMaxPriorityFeePerGas +- ), +- suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers( +- estimates.high.suggestedMaxFeePerGas +- ) +- }, +- estimatedBaseFee: normalizeGWEIDecimalNumbers(estimates.estimatedBaseFee), +- historicalBaseFeeRange: estimates.historicalBaseFeeRange, +- baseFeeTrend: estimates.baseFeeTrend, +- latestPriorityFeeRange: estimates.latestPriorityFeeRange, +- historicalPriorityFeeRange: estimates.historicalPriorityFeeRange, +- priorityFeeTrend: estimates.priorityFeeTrend, +- networkCongestion: estimates.networkCongestion +- }; +-} +-async function fetchLegacyGasPriceEstimates(url, infuraAPIKey, clientId) { +- const infuraAuthToken = buildInfuraAuthToken(infuraAPIKey); +- const result = await handleFetch(url, { +- referrer: url, +- referrerPolicy: "no-referrer-when-downgrade", +- method: "GET", +- mode: "cors", +- headers: getHeaders(infuraAuthToken, clientId) +- }); +- return { +- low: result.SafeGasPrice, +- medium: result.ProposeGasPrice, +- high: result.FastGasPrice +- }; +-} +-async function fetchEthGasPriceEstimate(ethQuery) { +- const gasPrice = await query(ethQuery, "gasPrice"); +- return { +- gasPrice: weiHexToGweiDec(gasPrice).toString() +- }; +-} +-function calculateTimeEstimate(maxPriorityFeePerGas, maxFeePerGas, gasFeeEstimates) { +- const { low, medium, high, estimatedBaseFee } = gasFeeEstimates; +- const maxPriorityFeePerGasInWEI = gweiDecToWEIBN(maxPriorityFeePerGas); +- const maxFeePerGasInWEI = gweiDecToWEIBN(maxFeePerGas); +- const estimatedBaseFeeInWEI = gweiDecToWEIBN(estimatedBaseFee); +- const effectiveMaxPriorityFee = BN.min( +- maxPriorityFeePerGasInWEI, +- maxFeePerGasInWEI.sub(estimatedBaseFeeInWEI) +- ); +- const lowMaxPriorityFeeInWEI = gweiDecToWEIBN( +- low.suggestedMaxPriorityFeePerGas +- ); +- const mediumMaxPriorityFeeInWEI = gweiDecToWEIBN( +- medium.suggestedMaxPriorityFeePerGas +- ); +- const highMaxPriorityFeeInWEI = gweiDecToWEIBN( +- high.suggestedMaxPriorityFeePerGas +- ); +- let lowerTimeBound; +- let upperTimeBound; +- if (effectiveMaxPriorityFee.lt(lowMaxPriorityFeeInWEI)) { +- lowerTimeBound = null; +- upperTimeBound = "unknown"; +- } else if (effectiveMaxPriorityFee.gte(lowMaxPriorityFeeInWEI) && effectiveMaxPriorityFee.lt(mediumMaxPriorityFeeInWEI)) { +- lowerTimeBound = low.minWaitTimeEstimate; +- upperTimeBound = low.maxWaitTimeEstimate; +- } else if (effectiveMaxPriorityFee.gte(mediumMaxPriorityFeeInWEI) && effectiveMaxPriorityFee.lt(highMaxPriorityFeeInWEI)) { +- lowerTimeBound = medium.minWaitTimeEstimate; +- upperTimeBound = medium.maxWaitTimeEstimate; +- } else if (effectiveMaxPriorityFee.eq(highMaxPriorityFeeInWEI)) { +- lowerTimeBound = high.minWaitTimeEstimate; +- upperTimeBound = high.maxWaitTimeEstimate; +- } else { +- lowerTimeBound = 0; +- upperTimeBound = high.maxWaitTimeEstimate; +- } +- return { +- lowerTimeBound, +- upperTimeBound +- }; +-} +-function buildInfuraAuthToken(infuraAPIKey) { +- return Buffer.from(`${infuraAPIKey}:`).toString("base64"); +-} +-function getHeaders(infuraAuthToken, clientId) { +- return { +- "Content-Type": "application/json", +- Authorization: `Basic ${infuraAuthToken}`, +- // Only add the clientId header if clientId is a non-empty string +- ...clientId?.trim() ? makeClientIdHeader(clientId) : {} +- }; +-} +- +-export { +- __privateGet, +- __privateAdd, +- __privateSet, +- __privateMethod, +- normalizeGWEIDecimalNumbers, +- fetchGasEstimates, +- fetchLegacyGasPriceEstimates, +- fetchEthGasPriceEstimate, +- calculateTimeEstimate +-}; +-//# sourceMappingURL=chunk-KORLXV32.mjs.map +\ No newline at end of file +diff --git a/dist/chunk-KORLXV32.mjs.map b/dist/chunk-KORLXV32.mjs.map +deleted file mode 100644 +index b95130543a5b39392214c5fe873ad367e6fa8af7..0000000000000000000000000000000000000000 +--- a/dist/chunk-KORLXV32.mjs.map ++++ /dev/null +@@ -1 +0,0 @@ +-{"version":3,"sources":["../src/gas-util.ts"],"sourcesContent":["import {\n query,\n handleFetch,\n gweiDecToWEIBN,\n weiHexToGweiDec,\n} from '@metamask/controller-utils';\nimport type EthQuery from '@metamask/eth-query';\nimport BN from 'bn.js';\n\nimport type {\n GasFeeEstimates,\n EthGasPriceEstimate,\n EstimatedGasFeeTimeBounds,\n unknownString,\n LegacyGasPriceEstimate,\n} from './GasFeeController';\n\nconst makeClientIdHeader = (clientId: string) => ({ 'X-Client-Id': clientId });\n\n/**\n * Convert a decimal GWEI value to a decimal string rounded to the nearest WEI.\n *\n * @param n - The input GWEI amount, as a decimal string or a number.\n * @returns The decimal string GWEI amount.\n */\nexport function normalizeGWEIDecimalNumbers(n: string | number) {\n const numberAsWEIHex = gweiDecToWEIBN(n).toString(16);\n const numberAsGWEI = weiHexToGweiDec(numberAsWEIHex);\n return numberAsGWEI;\n}\n\n/**\n * Fetch gas estimates from the given URL.\n *\n * @param url - The gas estimate URL.\n * @param infuraAPIKey - The Infura API key used for infura API requests.\n * @param clientId - The client ID used to identify to the API who is asking for estimates.\n * @returns The gas estimates.\n */\nexport async function fetchGasEstimates(\n url: string,\n infuraAPIKey: string,\n clientId?: string,\n): Promise {\n const infuraAuthToken = buildInfuraAuthToken(infuraAPIKey);\n const estimates = await handleFetch(url, {\n headers: getHeaders(infuraAuthToken, clientId),\n });\n return {\n low: {\n ...estimates.low,\n suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.low.suggestedMaxPriorityFeePerGas,\n ),\n suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.low.suggestedMaxFeePerGas,\n ),\n },\n medium: {\n ...estimates.medium,\n suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.medium.suggestedMaxPriorityFeePerGas,\n ),\n suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.medium.suggestedMaxFeePerGas,\n ),\n },\n high: {\n ...estimates.high,\n suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.high.suggestedMaxPriorityFeePerGas,\n ),\n suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.high.suggestedMaxFeePerGas,\n ),\n },\n estimatedBaseFee: normalizeGWEIDecimalNumbers(estimates.estimatedBaseFee),\n historicalBaseFeeRange: estimates.historicalBaseFeeRange,\n baseFeeTrend: estimates.baseFeeTrend,\n latestPriorityFeeRange: estimates.latestPriorityFeeRange,\n historicalPriorityFeeRange: estimates.historicalPriorityFeeRange,\n priorityFeeTrend: estimates.priorityFeeTrend,\n networkCongestion: estimates.networkCongestion,\n };\n}\n\n/**\n * Hit the legacy MetaSwaps gasPrices estimate api and return the low, medium\n * high values from that API.\n *\n * @param url - The URL to fetch gas price estimates from.\n * @param infuraAPIKey - The Infura API key used for infura API requests.\n * @param clientId - The client ID used to identify to the API who is asking for estimates.\n * @returns The gas price estimates.\n */\nexport async function fetchLegacyGasPriceEstimates(\n url: string,\n infuraAPIKey: string,\n clientId?: string,\n): Promise {\n const infuraAuthToken = buildInfuraAuthToken(infuraAPIKey);\n const result = await handleFetch(url, {\n referrer: url,\n referrerPolicy: 'no-referrer-when-downgrade',\n method: 'GET',\n mode: 'cors',\n headers: getHeaders(infuraAuthToken, clientId),\n });\n return {\n low: result.SafeGasPrice,\n medium: result.ProposeGasPrice,\n high: result.FastGasPrice,\n };\n}\n\n/**\n * Get a gas price estimate from the network using the `eth_gasPrice` method.\n *\n * @param ethQuery - The EthQuery instance to call the network with.\n * @returns A gas price estimate.\n */\nexport async function fetchEthGasPriceEstimate(\n ethQuery: EthQuery,\n): Promise {\n const gasPrice = await query(ethQuery, 'gasPrice');\n return {\n gasPrice: weiHexToGweiDec(gasPrice).toString(),\n };\n}\n\n/**\n * Estimate the time it will take for a transaction to be confirmed.\n *\n * @param maxPriorityFeePerGas - The max priority fee per gas.\n * @param maxFeePerGas - The max fee per gas.\n * @param gasFeeEstimates - The gas fee estimates.\n * @returns The estimated lower and upper bounds for when this transaction will be confirmed.\n */\nexport function calculateTimeEstimate(\n maxPriorityFeePerGas: string,\n maxFeePerGas: string,\n gasFeeEstimates: GasFeeEstimates,\n): EstimatedGasFeeTimeBounds {\n const { low, medium, high, estimatedBaseFee } = gasFeeEstimates;\n\n const maxPriorityFeePerGasInWEI = gweiDecToWEIBN(maxPriorityFeePerGas);\n const maxFeePerGasInWEI = gweiDecToWEIBN(maxFeePerGas);\n const estimatedBaseFeeInWEI = gweiDecToWEIBN(estimatedBaseFee);\n\n const effectiveMaxPriorityFee = BN.min(\n maxPriorityFeePerGasInWEI,\n maxFeePerGasInWEI.sub(estimatedBaseFeeInWEI),\n );\n\n const lowMaxPriorityFeeInWEI = gweiDecToWEIBN(\n low.suggestedMaxPriorityFeePerGas,\n );\n const mediumMaxPriorityFeeInWEI = gweiDecToWEIBN(\n medium.suggestedMaxPriorityFeePerGas,\n );\n const highMaxPriorityFeeInWEI = gweiDecToWEIBN(\n high.suggestedMaxPriorityFeePerGas,\n );\n\n let lowerTimeBound;\n let upperTimeBound;\n\n if (effectiveMaxPriorityFee.lt(lowMaxPriorityFeeInWEI)) {\n lowerTimeBound = null;\n upperTimeBound = 'unknown' as unknownString;\n } else if (\n effectiveMaxPriorityFee.gte(lowMaxPriorityFeeInWEI) &&\n effectiveMaxPriorityFee.lt(mediumMaxPriorityFeeInWEI)\n ) {\n lowerTimeBound = low.minWaitTimeEstimate;\n upperTimeBound = low.maxWaitTimeEstimate;\n } else if (\n effectiveMaxPriorityFee.gte(mediumMaxPriorityFeeInWEI) &&\n effectiveMaxPriorityFee.lt(highMaxPriorityFeeInWEI)\n ) {\n lowerTimeBound = medium.minWaitTimeEstimate;\n upperTimeBound = medium.maxWaitTimeEstimate;\n } else if (effectiveMaxPriorityFee.eq(highMaxPriorityFeeInWEI)) {\n lowerTimeBound = high.minWaitTimeEstimate;\n upperTimeBound = high.maxWaitTimeEstimate;\n } else {\n lowerTimeBound = 0;\n upperTimeBound = high.maxWaitTimeEstimate;\n }\n\n return {\n lowerTimeBound,\n upperTimeBound,\n };\n}\n\n/**\n * Build an infura auth token from the given API key and secret.\n *\n * @param infuraAPIKey - The Infura API key.\n * @returns The base64 encoded auth token.\n */\nfunction buildInfuraAuthToken(infuraAPIKey: string) {\n // We intentionally leave the password empty, as Infura does not require one\n return Buffer.from(`${infuraAPIKey}:`).toString('base64');\n}\n\n/**\n * Get the headers for a request to the gas fee API.\n *\n * @param infuraAuthToken - The Infura auth token to use for the request.\n * @param clientId - The client ID used to identify to the API who is asking for estimates.\n * @returns The headers for the request.\n */\nfunction getHeaders(infuraAuthToken: string, clientId?: string) {\n return {\n 'Content-Type': 'application/json',\n Authorization: `Basic ${infuraAuthToken}`,\n // Only add the clientId header if clientId is a non-empty string\n ...(clientId?.trim() ? makeClientIdHeader(clientId) : {}),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,OAAO,QAAQ;AAUf,IAAM,qBAAqB,CAAC,cAAsB,EAAE,eAAe,SAAS;AAQrE,SAAS,4BAA4B,GAAoB;AAC9D,QAAM,iBAAiB,eAAe,CAAC,EAAE,SAAS,EAAE;AACpD,QAAM,eAAe,gBAAgB,cAAc;AACnD,SAAO;AACT;AAUA,eAAsB,kBACpB,KACA,cACA,UAC0B;AAC1B,QAAM,kBAAkB,qBAAqB,YAAY;AACzD,QAAM,YAAY,MAAM,YAAY,KAAK;AAAA,IACvC,SAAS,WAAW,iBAAiB,QAAQ;AAAA,EAC/C,CAAC;AACD,SAAO;AAAA,IACL,KAAK;AAAA,MACH,GAAG,UAAU;AAAA,MACb,+BAA+B;AAAA,QAC7B,UAAU,IAAI;AAAA,MAChB;AAAA,MACA,uBAAuB;AAAA,QACrB,UAAU,IAAI;AAAA,MAChB;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,MACN,GAAG,UAAU;AAAA,MACb,+BAA+B;AAAA,QAC7B,UAAU,OAAO;AAAA,MACnB;AAAA,MACA,uBAAuB;AAAA,QACrB,UAAU,OAAO;AAAA,MACnB;AAAA,IACF;AAAA,IACA,MAAM;AAAA,MACJ,GAAG,UAAU;AAAA,MACb,+BAA+B;AAAA,QAC7B,UAAU,KAAK;AAAA,MACjB;AAAA,MACA,uBAAuB;AAAA,QACrB,UAAU,KAAK;AAAA,MACjB;AAAA,IACF;AAAA,IACA,kBAAkB,4BAA4B,UAAU,gBAAgB;AAAA,IACxE,wBAAwB,UAAU;AAAA,IAClC,cAAc,UAAU;AAAA,IACxB,wBAAwB,UAAU;AAAA,IAClC,4BAA4B,UAAU;AAAA,IACtC,kBAAkB,UAAU;AAAA,IAC5B,mBAAmB,UAAU;AAAA,EAC/B;AACF;AAWA,eAAsB,6BACpB,KACA,cACA,UACiC;AACjC,QAAM,kBAAkB,qBAAqB,YAAY;AACzD,QAAM,SAAS,MAAM,YAAY,KAAK;AAAA,IACpC,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,SAAS,WAAW,iBAAiB,QAAQ;AAAA,EAC/C,CAAC;AACD,SAAO;AAAA,IACL,KAAK,OAAO;AAAA,IACZ,QAAQ,OAAO;AAAA,IACf,MAAM,OAAO;AAAA,EACf;AACF;AAQA,eAAsB,yBACpB,UAC8B;AAC9B,QAAM,WAAW,MAAM,MAAM,UAAU,UAAU;AACjD,SAAO;AAAA,IACL,UAAU,gBAAgB,QAAQ,EAAE,SAAS;AAAA,EAC/C;AACF;AAUO,SAAS,sBACd,sBACA,cACA,iBAC2B;AAC3B,QAAM,EAAE,KAAK,QAAQ,MAAM,iBAAiB,IAAI;AAEhD,QAAM,4BAA4B,eAAe,oBAAoB;AACrE,QAAM,oBAAoB,eAAe,YAAY;AACrD,QAAM,wBAAwB,eAAe,gBAAgB;AAE7D,QAAM,0BAA0B,GAAG;AAAA,IACjC;AAAA,IACA,kBAAkB,IAAI,qBAAqB;AAAA,EAC7C;AAEA,QAAM,yBAAyB;AAAA,IAC7B,IAAI;AAAA,EACN;AACA,QAAM,4BAA4B;AAAA,IAChC,OAAO;AAAA,EACT;AACA,QAAM,0BAA0B;AAAA,IAC9B,KAAK;AAAA,EACP;AAEA,MAAI;AACJ,MAAI;AAEJ,MAAI,wBAAwB,GAAG,sBAAsB,GAAG;AACtD,qBAAiB;AACjB,qBAAiB;AAAA,EACnB,WACE,wBAAwB,IAAI,sBAAsB,KAClD,wBAAwB,GAAG,yBAAyB,GACpD;AACA,qBAAiB,IAAI;AACrB,qBAAiB,IAAI;AAAA,EACvB,WACE,wBAAwB,IAAI,yBAAyB,KACrD,wBAAwB,GAAG,uBAAuB,GAClD;AACA,qBAAiB,OAAO;AACxB,qBAAiB,OAAO;AAAA,EAC1B,WAAW,wBAAwB,GAAG,uBAAuB,GAAG;AAC9D,qBAAiB,KAAK;AACtB,qBAAiB,KAAK;AAAA,EACxB,OAAO;AACL,qBAAiB;AACjB,qBAAiB,KAAK;AAAA,EACxB;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,EACF;AACF;AAQA,SAAS,qBAAqB,cAAsB;AAElD,SAAO,OAAO,KAAK,GAAG,YAAY,GAAG,EAAE,SAAS,QAAQ;AAC1D;AASA,SAAS,WAAW,iBAAyB,UAAmB;AAC9D,SAAO;AAAA,IACL,gBAAgB;AAAA,IAChB,eAAe,SAAS,eAAe;AAAA;AAAA,IAEvC,GAAI,UAAU,KAAK,IAAI,mBAAmB,QAAQ,IAAI,CAAC;AAAA,EACzD;AACF;","names":[]} +\ No newline at end of file +diff --git a/dist/chunk-Q2YPK5SL.js b/dist/chunk-Q2YPK5SL.js +deleted file mode 100644 +index 154d3eafb98cdb84cda1a2ea60f6be8a75f4e7a6..0000000000000000000000000000000000000000 +--- a/dist/chunk-Q2YPK5SL.js ++++ /dev/null +@@ -1,165 +0,0 @@ +-"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }var __accessCheck = (obj, member, msg) => { +- if (!member.has(obj)) +- throw TypeError("Cannot " + msg); +-}; +-var __privateGet = (obj, member, getter) => { +- __accessCheck(obj, member, "read from private field"); +- return getter ? getter.call(obj) : member.get(obj); +-}; +-var __privateAdd = (obj, member, value) => { +- if (member.has(obj)) +- throw TypeError("Cannot add the same private member more than once"); +- member instanceof WeakSet ? member.add(obj) : member.set(obj, value); +-}; +-var __privateSet = (obj, member, value, setter) => { +- __accessCheck(obj, member, "write to private field"); +- setter ? setter.call(obj, value) : member.set(obj, value); +- return value; +-}; +-var __privateMethod = (obj, member, method) => { +- __accessCheck(obj, member, "access private method"); +- return method; +-}; +- +-// src/gas-util.ts +- +- +- +- +- +-var _controllerutils = require('@metamask/controller-utils'); +-var _bnjs = require('bn.js'); var _bnjs2 = _interopRequireDefault(_bnjs); +-var makeClientIdHeader = (clientId) => ({ "X-Client-Id": clientId }); +-function normalizeGWEIDecimalNumbers(n) { +- const numberAsWEIHex = _controllerutils.gweiDecToWEIBN.call(void 0, n).toString(16); +- const numberAsGWEI = _controllerutils.weiHexToGweiDec.call(void 0, numberAsWEIHex); +- return numberAsGWEI; +-} +-async function fetchGasEstimates(url, infuraAPIKey, clientId) { +- const infuraAuthToken = buildInfuraAuthToken(infuraAPIKey); +- const estimates = await _controllerutils.handleFetch.call(void 0, url, { +- headers: getHeaders(infuraAuthToken, clientId) +- }); +- return { +- low: { +- ...estimates.low, +- suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers( +- estimates.low.suggestedMaxPriorityFeePerGas +- ), +- suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers( +- estimates.low.suggestedMaxFeePerGas +- ) +- }, +- medium: { +- ...estimates.medium, +- suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers( +- estimates.medium.suggestedMaxPriorityFeePerGas +- ), +- suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers( +- estimates.medium.suggestedMaxFeePerGas +- ) +- }, +- high: { +- ...estimates.high, +- suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers( +- estimates.high.suggestedMaxPriorityFeePerGas +- ), +- suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers( +- estimates.high.suggestedMaxFeePerGas +- ) +- }, +- estimatedBaseFee: normalizeGWEIDecimalNumbers(estimates.estimatedBaseFee), +- historicalBaseFeeRange: estimates.historicalBaseFeeRange, +- baseFeeTrend: estimates.baseFeeTrend, +- latestPriorityFeeRange: estimates.latestPriorityFeeRange, +- historicalPriorityFeeRange: estimates.historicalPriorityFeeRange, +- priorityFeeTrend: estimates.priorityFeeTrend, +- networkCongestion: estimates.networkCongestion +- }; +-} +-async function fetchLegacyGasPriceEstimates(url, infuraAPIKey, clientId) { +- const infuraAuthToken = buildInfuraAuthToken(infuraAPIKey); +- const result = await _controllerutils.handleFetch.call(void 0, url, { +- referrer: url, +- referrerPolicy: "no-referrer-when-downgrade", +- method: "GET", +- mode: "cors", +- headers: getHeaders(infuraAuthToken, clientId) +- }); +- return { +- low: result.SafeGasPrice, +- medium: result.ProposeGasPrice, +- high: result.FastGasPrice +- }; +-} +-async function fetchEthGasPriceEstimate(ethQuery) { +- const gasPrice = await _controllerutils.query.call(void 0, ethQuery, "gasPrice"); +- return { +- gasPrice: _controllerutils.weiHexToGweiDec.call(void 0, gasPrice).toString() +- }; +-} +-function calculateTimeEstimate(maxPriorityFeePerGas, maxFeePerGas, gasFeeEstimates) { +- const { low, medium, high, estimatedBaseFee } = gasFeeEstimates; +- const maxPriorityFeePerGasInWEI = _controllerutils.gweiDecToWEIBN.call(void 0, maxPriorityFeePerGas); +- const maxFeePerGasInWEI = _controllerutils.gweiDecToWEIBN.call(void 0, maxFeePerGas); +- const estimatedBaseFeeInWEI = _controllerutils.gweiDecToWEIBN.call(void 0, estimatedBaseFee); +- const effectiveMaxPriorityFee = _bnjs2.default.min( +- maxPriorityFeePerGasInWEI, +- maxFeePerGasInWEI.sub(estimatedBaseFeeInWEI) +- ); +- const lowMaxPriorityFeeInWEI = _controllerutils.gweiDecToWEIBN.call(void 0, +- low.suggestedMaxPriorityFeePerGas +- ); +- const mediumMaxPriorityFeeInWEI = _controllerutils.gweiDecToWEIBN.call(void 0, +- medium.suggestedMaxPriorityFeePerGas +- ); +- const highMaxPriorityFeeInWEI = _controllerutils.gweiDecToWEIBN.call(void 0, +- high.suggestedMaxPriorityFeePerGas +- ); +- let lowerTimeBound; +- let upperTimeBound; +- if (effectiveMaxPriorityFee.lt(lowMaxPriorityFeeInWEI)) { +- lowerTimeBound = null; +- upperTimeBound = "unknown"; +- } else if (effectiveMaxPriorityFee.gte(lowMaxPriorityFeeInWEI) && effectiveMaxPriorityFee.lt(mediumMaxPriorityFeeInWEI)) { +- lowerTimeBound = low.minWaitTimeEstimate; +- upperTimeBound = low.maxWaitTimeEstimate; +- } else if (effectiveMaxPriorityFee.gte(mediumMaxPriorityFeeInWEI) && effectiveMaxPriorityFee.lt(highMaxPriorityFeeInWEI)) { +- lowerTimeBound = medium.minWaitTimeEstimate; +- upperTimeBound = medium.maxWaitTimeEstimate; +- } else if (effectiveMaxPriorityFee.eq(highMaxPriorityFeeInWEI)) { +- lowerTimeBound = high.minWaitTimeEstimate; +- upperTimeBound = high.maxWaitTimeEstimate; +- } else { +- lowerTimeBound = 0; +- upperTimeBound = high.maxWaitTimeEstimate; +- } +- return { +- lowerTimeBound, +- upperTimeBound +- }; +-} +-function buildInfuraAuthToken(infuraAPIKey) { +- return Buffer.from(`${infuraAPIKey}:`).toString("base64"); +-} +-function getHeaders(infuraAuthToken, clientId) { +- return { +- "Content-Type": "application/json", +- Authorization: `Basic ${infuraAuthToken}`, +- // Only add the clientId header if clientId is a non-empty string +- ...clientId?.trim() ? makeClientIdHeader(clientId) : {} +- }; +-} +- +- +- +- +- +- +- +- +- +- +- +-exports.__privateGet = __privateGet; exports.__privateAdd = __privateAdd; exports.__privateSet = __privateSet; exports.__privateMethod = __privateMethod; exports.normalizeGWEIDecimalNumbers = normalizeGWEIDecimalNumbers; exports.fetchGasEstimates = fetchGasEstimates; exports.fetchLegacyGasPriceEstimates = fetchLegacyGasPriceEstimates; exports.fetchEthGasPriceEstimate = fetchEthGasPriceEstimate; exports.calculateTimeEstimate = calculateTimeEstimate; +-//# sourceMappingURL=chunk-Q2YPK5SL.js.map +\ No newline at end of file +diff --git a/dist/chunk-Q2YPK5SL.js.map b/dist/chunk-Q2YPK5SL.js.map +deleted file mode 100644 +index dc1a17f2cd5fdd749b46fbb546375ea3a1925081..0000000000000000000000000000000000000000 +--- a/dist/chunk-Q2YPK5SL.js.map ++++ /dev/null +@@ -1 +0,0 @@ +-{"version":3,"sources":["../src/gas-util.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,OAAO,QAAQ;AAUf,IAAM,qBAAqB,CAAC,cAAsB,EAAE,eAAe,SAAS;AAQrE,SAAS,4BAA4B,GAAoB;AAC9D,QAAM,iBAAiB,eAAe,CAAC,EAAE,SAAS,EAAE;AACpD,QAAM,eAAe,gBAAgB,cAAc;AACnD,SAAO;AACT;AAUA,eAAsB,kBACpB,KACA,cACA,UAC0B;AAC1B,QAAM,kBAAkB,qBAAqB,YAAY;AACzD,QAAM,YAAY,MAAM,YAAY,KAAK;AAAA,IACvC,SAAS,WAAW,iBAAiB,QAAQ;AAAA,EAC/C,CAAC;AACD,SAAO;AAAA,IACL,KAAK;AAAA,MACH,GAAG,UAAU;AAAA,MACb,+BAA+B;AAAA,QAC7B,UAAU,IAAI;AAAA,MAChB;AAAA,MACA,uBAAuB;AAAA,QACrB,UAAU,IAAI;AAAA,MAChB;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,MACN,GAAG,UAAU;AAAA,MACb,+BAA+B;AAAA,QAC7B,UAAU,OAAO;AAAA,MACnB;AAAA,MACA,uBAAuB;AAAA,QACrB,UAAU,OAAO;AAAA,MACnB;AAAA,IACF;AAAA,IACA,MAAM;AAAA,MACJ,GAAG,UAAU;AAAA,MACb,+BAA+B;AAAA,QAC7B,UAAU,KAAK;AAAA,MACjB;AAAA,MACA,uBAAuB;AAAA,QACrB,UAAU,KAAK;AAAA,MACjB;AAAA,IACF;AAAA,IACA,kBAAkB,4BAA4B,UAAU,gBAAgB;AAAA,IACxE,wBAAwB,UAAU;AAAA,IAClC,cAAc,UAAU;AAAA,IACxB,wBAAwB,UAAU;AAAA,IAClC,4BAA4B,UAAU;AAAA,IACtC,kBAAkB,UAAU;AAAA,IAC5B,mBAAmB,UAAU;AAAA,EAC/B;AACF;AAWA,eAAsB,6BACpB,KACA,cACA,UACiC;AACjC,QAAM,kBAAkB,qBAAqB,YAAY;AACzD,QAAM,SAAS,MAAM,YAAY,KAAK;AAAA,IACpC,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,SAAS,WAAW,iBAAiB,QAAQ;AAAA,EAC/C,CAAC;AACD,SAAO;AAAA,IACL,KAAK,OAAO;AAAA,IACZ,QAAQ,OAAO;AAAA,IACf,MAAM,OAAO;AAAA,EACf;AACF;AAQA,eAAsB,yBACpB,UAC8B;AAC9B,QAAM,WAAW,MAAM,MAAM,UAAU,UAAU;AACjD,SAAO;AAAA,IACL,UAAU,gBAAgB,QAAQ,EAAE,SAAS;AAAA,EAC/C;AACF;AAUO,SAAS,sBACd,sBACA,cACA,iBAC2B;AAC3B,QAAM,EAAE,KAAK,QAAQ,MAAM,iBAAiB,IAAI;AAEhD,QAAM,4BAA4B,eAAe,oBAAoB;AACrE,QAAM,oBAAoB,eAAe,YAAY;AACrD,QAAM,wBAAwB,eAAe,gBAAgB;AAE7D,QAAM,0BAA0B,GAAG;AAAA,IACjC;AAAA,IACA,kBAAkB,IAAI,qBAAqB;AAAA,EAC7C;AAEA,QAAM,yBAAyB;AAAA,IAC7B,IAAI;AAAA,EACN;AACA,QAAM,4BAA4B;AAAA,IAChC,OAAO;AAAA,EACT;AACA,QAAM,0BAA0B;AAAA,IAC9B,KAAK;AAAA,EACP;AAEA,MAAI;AACJ,MAAI;AAEJ,MAAI,wBAAwB,GAAG,sBAAsB,GAAG;AACtD,qBAAiB;AACjB,qBAAiB;AAAA,EACnB,WACE,wBAAwB,IAAI,sBAAsB,KAClD,wBAAwB,GAAG,yBAAyB,GACpD;AACA,qBAAiB,IAAI;AACrB,qBAAiB,IAAI;AAAA,EACvB,WACE,wBAAwB,IAAI,yBAAyB,KACrD,wBAAwB,GAAG,uBAAuB,GAClD;AACA,qBAAiB,OAAO;AACxB,qBAAiB,OAAO;AAAA,EAC1B,WAAW,wBAAwB,GAAG,uBAAuB,GAAG;AAC9D,qBAAiB,KAAK;AACtB,qBAAiB,KAAK;AAAA,EACxB,OAAO;AACL,qBAAiB;AACjB,qBAAiB,KAAK;AAAA,EACxB;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,EACF;AACF;AAQA,SAAS,qBAAqB,cAAsB;AAElD,SAAO,OAAO,KAAK,GAAG,YAAY,GAAG,EAAE,SAAS,QAAQ;AAC1D;AASA,SAAS,WAAW,iBAAyB,UAAmB;AAC9D,SAAO;AAAA,IACL,gBAAgB;AAAA,IAChB,eAAe,SAAS,eAAe;AAAA;AAAA,IAEvC,GAAI,UAAU,KAAK,IAAI,mBAAmB,QAAQ,IAAI,CAAC;AAAA,EACzD;AACF","sourcesContent":["import {\n query,\n handleFetch,\n gweiDecToWEIBN,\n weiHexToGweiDec,\n} from '@metamask/controller-utils';\nimport type EthQuery from '@metamask/eth-query';\nimport BN from 'bn.js';\n\nimport type {\n GasFeeEstimates,\n EthGasPriceEstimate,\n EstimatedGasFeeTimeBounds,\n unknownString,\n LegacyGasPriceEstimate,\n} from './GasFeeController';\n\nconst makeClientIdHeader = (clientId: string) => ({ 'X-Client-Id': clientId });\n\n/**\n * Convert a decimal GWEI value to a decimal string rounded to the nearest WEI.\n *\n * @param n - The input GWEI amount, as a decimal string or a number.\n * @returns The decimal string GWEI amount.\n */\nexport function normalizeGWEIDecimalNumbers(n: string | number) {\n const numberAsWEIHex = gweiDecToWEIBN(n).toString(16);\n const numberAsGWEI = weiHexToGweiDec(numberAsWEIHex);\n return numberAsGWEI;\n}\n\n/**\n * Fetch gas estimates from the given URL.\n *\n * @param url - The gas estimate URL.\n * @param infuraAPIKey - The Infura API key used for infura API requests.\n * @param clientId - The client ID used to identify to the API who is asking for estimates.\n * @returns The gas estimates.\n */\nexport async function fetchGasEstimates(\n url: string,\n infuraAPIKey: string,\n clientId?: string,\n): Promise {\n const infuraAuthToken = buildInfuraAuthToken(infuraAPIKey);\n const estimates = await handleFetch(url, {\n headers: getHeaders(infuraAuthToken, clientId),\n });\n return {\n low: {\n ...estimates.low,\n suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.low.suggestedMaxPriorityFeePerGas,\n ),\n suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.low.suggestedMaxFeePerGas,\n ),\n },\n medium: {\n ...estimates.medium,\n suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.medium.suggestedMaxPriorityFeePerGas,\n ),\n suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.medium.suggestedMaxFeePerGas,\n ),\n },\n high: {\n ...estimates.high,\n suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.high.suggestedMaxPriorityFeePerGas,\n ),\n suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.high.suggestedMaxFeePerGas,\n ),\n },\n estimatedBaseFee: normalizeGWEIDecimalNumbers(estimates.estimatedBaseFee),\n historicalBaseFeeRange: estimates.historicalBaseFeeRange,\n baseFeeTrend: estimates.baseFeeTrend,\n latestPriorityFeeRange: estimates.latestPriorityFeeRange,\n historicalPriorityFeeRange: estimates.historicalPriorityFeeRange,\n priorityFeeTrend: estimates.priorityFeeTrend,\n networkCongestion: estimates.networkCongestion,\n };\n}\n\n/**\n * Hit the legacy MetaSwaps gasPrices estimate api and return the low, medium\n * high values from that API.\n *\n * @param url - The URL to fetch gas price estimates from.\n * @param infuraAPIKey - The Infura API key used for infura API requests.\n * @param clientId - The client ID used to identify to the API who is asking for estimates.\n * @returns The gas price estimates.\n */\nexport async function fetchLegacyGasPriceEstimates(\n url: string,\n infuraAPIKey: string,\n clientId?: string,\n): Promise {\n const infuraAuthToken = buildInfuraAuthToken(infuraAPIKey);\n const result = await handleFetch(url, {\n referrer: url,\n referrerPolicy: 'no-referrer-when-downgrade',\n method: 'GET',\n mode: 'cors',\n headers: getHeaders(infuraAuthToken, clientId),\n });\n return {\n low: result.SafeGasPrice,\n medium: result.ProposeGasPrice,\n high: result.FastGasPrice,\n };\n}\n\n/**\n * Get a gas price estimate from the network using the `eth_gasPrice` method.\n *\n * @param ethQuery - The EthQuery instance to call the network with.\n * @returns A gas price estimate.\n */\nexport async function fetchEthGasPriceEstimate(\n ethQuery: EthQuery,\n): Promise {\n const gasPrice = await query(ethQuery, 'gasPrice');\n return {\n gasPrice: weiHexToGweiDec(gasPrice).toString(),\n };\n}\n\n/**\n * Estimate the time it will take for a transaction to be confirmed.\n *\n * @param maxPriorityFeePerGas - The max priority fee per gas.\n * @param maxFeePerGas - The max fee per gas.\n * @param gasFeeEstimates - The gas fee estimates.\n * @returns The estimated lower and upper bounds for when this transaction will be confirmed.\n */\nexport function calculateTimeEstimate(\n maxPriorityFeePerGas: string,\n maxFeePerGas: string,\n gasFeeEstimates: GasFeeEstimates,\n): EstimatedGasFeeTimeBounds {\n const { low, medium, high, estimatedBaseFee } = gasFeeEstimates;\n\n const maxPriorityFeePerGasInWEI = gweiDecToWEIBN(maxPriorityFeePerGas);\n const maxFeePerGasInWEI = gweiDecToWEIBN(maxFeePerGas);\n const estimatedBaseFeeInWEI = gweiDecToWEIBN(estimatedBaseFee);\n\n const effectiveMaxPriorityFee = BN.min(\n maxPriorityFeePerGasInWEI,\n maxFeePerGasInWEI.sub(estimatedBaseFeeInWEI),\n );\n\n const lowMaxPriorityFeeInWEI = gweiDecToWEIBN(\n low.suggestedMaxPriorityFeePerGas,\n );\n const mediumMaxPriorityFeeInWEI = gweiDecToWEIBN(\n medium.suggestedMaxPriorityFeePerGas,\n );\n const highMaxPriorityFeeInWEI = gweiDecToWEIBN(\n high.suggestedMaxPriorityFeePerGas,\n );\n\n let lowerTimeBound;\n let upperTimeBound;\n\n if (effectiveMaxPriorityFee.lt(lowMaxPriorityFeeInWEI)) {\n lowerTimeBound = null;\n upperTimeBound = 'unknown' as unknownString;\n } else if (\n effectiveMaxPriorityFee.gte(lowMaxPriorityFeeInWEI) &&\n effectiveMaxPriorityFee.lt(mediumMaxPriorityFeeInWEI)\n ) {\n lowerTimeBound = low.minWaitTimeEstimate;\n upperTimeBound = low.maxWaitTimeEstimate;\n } else if (\n effectiveMaxPriorityFee.gte(mediumMaxPriorityFeeInWEI) &&\n effectiveMaxPriorityFee.lt(highMaxPriorityFeeInWEI)\n ) {\n lowerTimeBound = medium.minWaitTimeEstimate;\n upperTimeBound = medium.maxWaitTimeEstimate;\n } else if (effectiveMaxPriorityFee.eq(highMaxPriorityFeeInWEI)) {\n lowerTimeBound = high.minWaitTimeEstimate;\n upperTimeBound = high.maxWaitTimeEstimate;\n } else {\n lowerTimeBound = 0;\n upperTimeBound = high.maxWaitTimeEstimate;\n }\n\n return {\n lowerTimeBound,\n upperTimeBound,\n };\n}\n\n/**\n * Build an infura auth token from the given API key and secret.\n *\n * @param infuraAPIKey - The Infura API key.\n * @returns The base64 encoded auth token.\n */\nfunction buildInfuraAuthToken(infuraAPIKey: string) {\n // We intentionally leave the password empty, as Infura does not require one\n return Buffer.from(`${infuraAPIKey}:`).toString('base64');\n}\n\n/**\n * Get the headers for a request to the gas fee API.\n *\n * @param infuraAuthToken - The Infura auth token to use for the request.\n * @param clientId - The client ID used to identify to the API who is asking for estimates.\n * @returns The headers for the request.\n */\nfunction getHeaders(infuraAuthToken: string, clientId?: string) {\n return {\n 'Content-Type': 'application/json',\n Authorization: `Basic ${infuraAuthToken}`,\n // Only add the clientId header if clientId is a non-empty string\n ...(clientId?.trim() ? makeClientIdHeader(clientId) : {}),\n };\n}\n"]} +\ No newline at end of file +diff --git a/dist/chunk-R3IOI7AK.mjs b/dist/chunk-R3IOI7AK.mjs +new file mode 100644 +index 0000000000000000000000000000000000000000..005f6ca08cfdc55aabcf1849e2a3b651ee7d9303 +--- /dev/null ++++ b/dist/chunk-R3IOI7AK.mjs +@@ -0,0 +1,156 @@ ++var __accessCheck = (obj, member, msg) => { ++ if (!member.has(obj)) ++ throw TypeError("Cannot " + msg); ++}; ++var __privateGet = (obj, member, getter) => { ++ __accessCheck(obj, member, "read from private field"); ++ return getter ? getter.call(obj) : member.get(obj); ++}; ++var __privateAdd = (obj, member, value) => { ++ if (member.has(obj)) ++ throw TypeError("Cannot add the same private member more than once"); ++ member instanceof WeakSet ? member.add(obj) : member.set(obj, value); ++}; ++var __privateSet = (obj, member, value, setter) => { ++ __accessCheck(obj, member, "write to private field"); ++ setter ? setter.call(obj, value) : member.set(obj, value); ++ return value; ++}; ++var __privateMethod = (obj, member, method) => { ++ __accessCheck(obj, member, "access private method"); ++ return method; ++}; ++ ++// src/gas-util.ts ++import { ++ query, ++ handleFetch, ++ gweiDecToWEIBN, ++ weiHexToGweiDec ++} from "@metamask/controller-utils"; ++import BN from "bn.js"; ++var makeClientIdHeader = (clientId) => ({ "X-Client-Id": clientId }); ++function normalizeGWEIDecimalNumbers(n) { ++ const numberAsWEIHex = gweiDecToWEIBN(n).toString(16); ++ const numberAsGWEI = weiHexToGweiDec(numberAsWEIHex); ++ return numberAsGWEI; ++} ++async function fetchGasEstimates(url, clientId) { ++ const estimates = await handleFetch( ++ url, ++ clientId ? { headers: makeClientIdHeader(clientId) } : void 0 ++ ); ++ return { ++ low: { ++ ...estimates.low, ++ suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers( ++ estimates.low.suggestedMaxPriorityFeePerGas ++ ), ++ suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers( ++ estimates.low.suggestedMaxFeePerGas ++ ) ++ }, ++ medium: { ++ ...estimates.medium, ++ suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers( ++ estimates.medium.suggestedMaxPriorityFeePerGas ++ ), ++ suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers( ++ estimates.medium.suggestedMaxFeePerGas ++ ) ++ }, ++ high: { ++ ...estimates.high, ++ suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers( ++ estimates.high.suggestedMaxPriorityFeePerGas ++ ), ++ suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers( ++ estimates.high.suggestedMaxFeePerGas ++ ) ++ }, ++ estimatedBaseFee: normalizeGWEIDecimalNumbers(estimates.estimatedBaseFee), ++ historicalBaseFeeRange: estimates.historicalBaseFeeRange, ++ baseFeeTrend: estimates.baseFeeTrend, ++ latestPriorityFeeRange: estimates.latestPriorityFeeRange, ++ historicalPriorityFeeRange: estimates.historicalPriorityFeeRange, ++ priorityFeeTrend: estimates.priorityFeeTrend, ++ networkCongestion: estimates.networkCongestion ++ }; ++} ++async function fetchLegacyGasPriceEstimates(url, clientId) { ++ const result = await handleFetch(url, { ++ referrer: url, ++ referrerPolicy: "no-referrer-when-downgrade", ++ method: "GET", ++ mode: "cors", ++ headers: { ++ "Content-Type": "application/json", ++ ...clientId && makeClientIdHeader(clientId) ++ } ++ }); ++ return { ++ low: result.SafeGasPrice, ++ medium: result.ProposeGasPrice, ++ high: result.FastGasPrice ++ }; ++} ++async function fetchEthGasPriceEstimate(ethQuery) { ++ const gasPrice = await query(ethQuery, "gasPrice"); ++ return { ++ gasPrice: weiHexToGweiDec(gasPrice).toString() ++ }; ++} ++function calculateTimeEstimate(maxPriorityFeePerGas, maxFeePerGas, gasFeeEstimates) { ++ const { low, medium, high, estimatedBaseFee } = gasFeeEstimates; ++ const maxPriorityFeePerGasInWEI = gweiDecToWEIBN(maxPriorityFeePerGas); ++ const maxFeePerGasInWEI = gweiDecToWEIBN(maxFeePerGas); ++ const estimatedBaseFeeInWEI = gweiDecToWEIBN(estimatedBaseFee); ++ const effectiveMaxPriorityFee = BN.min( ++ maxPriorityFeePerGasInWEI, ++ maxFeePerGasInWEI.sub(estimatedBaseFeeInWEI) ++ ); ++ const lowMaxPriorityFeeInWEI = gweiDecToWEIBN( ++ low.suggestedMaxPriorityFeePerGas ++ ); ++ const mediumMaxPriorityFeeInWEI = gweiDecToWEIBN( ++ medium.suggestedMaxPriorityFeePerGas ++ ); ++ const highMaxPriorityFeeInWEI = gweiDecToWEIBN( ++ high.suggestedMaxPriorityFeePerGas ++ ); ++ let lowerTimeBound; ++ let upperTimeBound; ++ if (effectiveMaxPriorityFee.lt(lowMaxPriorityFeeInWEI)) { ++ lowerTimeBound = null; ++ upperTimeBound = "unknown"; ++ } else if (effectiveMaxPriorityFee.gte(lowMaxPriorityFeeInWEI) && effectiveMaxPriorityFee.lt(mediumMaxPriorityFeeInWEI)) { ++ lowerTimeBound = low.minWaitTimeEstimate; ++ upperTimeBound = low.maxWaitTimeEstimate; ++ } else if (effectiveMaxPriorityFee.gte(mediumMaxPriorityFeeInWEI) && effectiveMaxPriorityFee.lt(highMaxPriorityFeeInWEI)) { ++ lowerTimeBound = medium.minWaitTimeEstimate; ++ upperTimeBound = medium.maxWaitTimeEstimate; ++ } else if (effectiveMaxPriorityFee.eq(highMaxPriorityFeeInWEI)) { ++ lowerTimeBound = high.minWaitTimeEstimate; ++ upperTimeBound = high.maxWaitTimeEstimate; ++ } else { ++ lowerTimeBound = 0; ++ upperTimeBound = high.maxWaitTimeEstimate; ++ } ++ return { ++ lowerTimeBound, ++ upperTimeBound ++ }; ++} ++ ++export { ++ __privateGet, ++ __privateAdd, ++ __privateSet, ++ __privateMethod, ++ normalizeGWEIDecimalNumbers, ++ fetchGasEstimates, ++ fetchLegacyGasPriceEstimates, ++ fetchEthGasPriceEstimate, ++ calculateTimeEstimate ++}; ++//# sourceMappingURL=chunk-R3IOI7AK.mjs.map +\ No newline at end of file +diff --git a/dist/chunk-R3IOI7AK.mjs.map b/dist/chunk-R3IOI7AK.mjs.map +new file mode 100644 +index 0000000000000000000000000000000000000000..151a4aa1146e106a273c2e7cbfe8e97c7e6ae6b6 +--- /dev/null ++++ b/dist/chunk-R3IOI7AK.mjs.map +@@ -0,0 +1 @@ ++{"version":3,"sources":["../src/gas-util.ts"],"sourcesContent":["import {\n query,\n handleFetch,\n gweiDecToWEIBN,\n weiHexToGweiDec,\n} from '@metamask/controller-utils';\nimport type EthQuery from '@metamask/eth-query';\nimport BN from 'bn.js';\n\nimport type {\n GasFeeEstimates,\n EthGasPriceEstimate,\n EstimatedGasFeeTimeBounds,\n unknownString,\n LegacyGasPriceEstimate,\n} from './GasFeeController';\n\nconst makeClientIdHeader = (clientId: string) => ({ 'X-Client-Id': clientId });\n\n/**\n * Convert a decimal GWEI value to a decimal string rounded to the nearest WEI.\n *\n * @param n - The input GWEI amount, as a decimal string or a number.\n * @returns The decimal string GWEI amount.\n */\nexport function normalizeGWEIDecimalNumbers(n: string | number) {\n const numberAsWEIHex = gweiDecToWEIBN(n).toString(16);\n const numberAsGWEI = weiHexToGweiDec(numberAsWEIHex);\n return numberAsGWEI;\n}\n\n/**\n * Fetch gas estimates from the given URL.\n *\n * @param url - The gas estimate URL.\n * @param clientId - The client ID used to identify to the API who is asking for estimates.\n * @returns The gas estimates.\n */\nexport async function fetchGasEstimates(\n url: string,\n clientId?: string,\n): Promise {\n const estimates = await handleFetch(\n url,\n clientId ? { headers: makeClientIdHeader(clientId) } : undefined,\n );\n return {\n low: {\n ...estimates.low,\n suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.low.suggestedMaxPriorityFeePerGas,\n ),\n suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.low.suggestedMaxFeePerGas,\n ),\n },\n medium: {\n ...estimates.medium,\n suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.medium.suggestedMaxPriorityFeePerGas,\n ),\n suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.medium.suggestedMaxFeePerGas,\n ),\n },\n high: {\n ...estimates.high,\n suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.high.suggestedMaxPriorityFeePerGas,\n ),\n suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.high.suggestedMaxFeePerGas,\n ),\n },\n estimatedBaseFee: normalizeGWEIDecimalNumbers(estimates.estimatedBaseFee),\n historicalBaseFeeRange: estimates.historicalBaseFeeRange,\n baseFeeTrend: estimates.baseFeeTrend,\n latestPriorityFeeRange: estimates.latestPriorityFeeRange,\n historicalPriorityFeeRange: estimates.historicalPriorityFeeRange,\n priorityFeeTrend: estimates.priorityFeeTrend,\n networkCongestion: estimates.networkCongestion,\n };\n}\n\n/**\n * Hit the legacy MetaSwaps gasPrices estimate api and return the low, medium\n * high values from that API.\n *\n * @param url - The URL to fetch gas price estimates from.\n * @param clientId - The client ID used to identify to the API who is asking for estimates.\n * @returns The gas price estimates.\n */\nexport async function fetchLegacyGasPriceEstimates(\n url: string,\n clientId?: string,\n): Promise {\n const result = await handleFetch(url, {\n referrer: url,\n referrerPolicy: 'no-referrer-when-downgrade',\n method: 'GET',\n mode: 'cors',\n headers: {\n 'Content-Type': 'application/json',\n ...(clientId && makeClientIdHeader(clientId)),\n },\n });\n return {\n low: result.SafeGasPrice,\n medium: result.ProposeGasPrice,\n high: result.FastGasPrice,\n };\n}\n\n/**\n * Get a gas price estimate from the network using the `eth_gasPrice` method.\n *\n * @param ethQuery - The EthQuery instance to call the network with.\n * @returns A gas price estimate.\n */\nexport async function fetchEthGasPriceEstimate(\n ethQuery: EthQuery,\n): Promise {\n const gasPrice = await query(ethQuery, 'gasPrice');\n return {\n gasPrice: weiHexToGweiDec(gasPrice).toString(),\n };\n}\n\n/**\n * Estimate the time it will take for a transaction to be confirmed.\n *\n * @param maxPriorityFeePerGas - The max priority fee per gas.\n * @param maxFeePerGas - The max fee per gas.\n * @param gasFeeEstimates - The gas fee estimates.\n * @returns The estimated lower and upper bounds for when this transaction will be confirmed.\n */\nexport function calculateTimeEstimate(\n maxPriorityFeePerGas: string,\n maxFeePerGas: string,\n gasFeeEstimates: GasFeeEstimates,\n): EstimatedGasFeeTimeBounds {\n const { low, medium, high, estimatedBaseFee } = gasFeeEstimates;\n\n const maxPriorityFeePerGasInWEI = gweiDecToWEIBN(maxPriorityFeePerGas);\n const maxFeePerGasInWEI = gweiDecToWEIBN(maxFeePerGas);\n const estimatedBaseFeeInWEI = gweiDecToWEIBN(estimatedBaseFee);\n\n const effectiveMaxPriorityFee = BN.min(\n maxPriorityFeePerGasInWEI,\n maxFeePerGasInWEI.sub(estimatedBaseFeeInWEI),\n );\n\n const lowMaxPriorityFeeInWEI = gweiDecToWEIBN(\n low.suggestedMaxPriorityFeePerGas,\n );\n const mediumMaxPriorityFeeInWEI = gweiDecToWEIBN(\n medium.suggestedMaxPriorityFeePerGas,\n );\n const highMaxPriorityFeeInWEI = gweiDecToWEIBN(\n high.suggestedMaxPriorityFeePerGas,\n );\n\n let lowerTimeBound;\n let upperTimeBound;\n\n if (effectiveMaxPriorityFee.lt(lowMaxPriorityFeeInWEI)) {\n lowerTimeBound = null;\n upperTimeBound = 'unknown' as unknownString;\n } else if (\n effectiveMaxPriorityFee.gte(lowMaxPriorityFeeInWEI) &&\n effectiveMaxPriorityFee.lt(mediumMaxPriorityFeeInWEI)\n ) {\n lowerTimeBound = low.minWaitTimeEstimate;\n upperTimeBound = low.maxWaitTimeEstimate;\n } else if (\n effectiveMaxPriorityFee.gte(mediumMaxPriorityFeeInWEI) &&\n effectiveMaxPriorityFee.lt(highMaxPriorityFeeInWEI)\n ) {\n lowerTimeBound = medium.minWaitTimeEstimate;\n upperTimeBound = medium.maxWaitTimeEstimate;\n } else if (effectiveMaxPriorityFee.eq(highMaxPriorityFeeInWEI)) {\n lowerTimeBound = high.minWaitTimeEstimate;\n upperTimeBound = high.maxWaitTimeEstimate;\n } else {\n lowerTimeBound = 0;\n upperTimeBound = high.maxWaitTimeEstimate;\n }\n\n return {\n lowerTimeBound,\n upperTimeBound,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,OAAO,QAAQ;AAUf,IAAM,qBAAqB,CAAC,cAAsB,EAAE,eAAe,SAAS;AAQrE,SAAS,4BAA4B,GAAoB;AAC9D,QAAM,iBAAiB,eAAe,CAAC,EAAE,SAAS,EAAE;AACpD,QAAM,eAAe,gBAAgB,cAAc;AACnD,SAAO;AACT;AASA,eAAsB,kBACpB,KACA,UAC0B;AAC1B,QAAM,YAAY,MAAM;AAAA,IACtB;AAAA,IACA,WAAW,EAAE,SAAS,mBAAmB,QAAQ,EAAE,IAAI;AAAA,EACzD;AACA,SAAO;AAAA,IACL,KAAK;AAAA,MACH,GAAG,UAAU;AAAA,MACb,+BAA+B;AAAA,QAC7B,UAAU,IAAI;AAAA,MAChB;AAAA,MACA,uBAAuB;AAAA,QACrB,UAAU,IAAI;AAAA,MAChB;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,MACN,GAAG,UAAU;AAAA,MACb,+BAA+B;AAAA,QAC7B,UAAU,OAAO;AAAA,MACnB;AAAA,MACA,uBAAuB;AAAA,QACrB,UAAU,OAAO;AAAA,MACnB;AAAA,IACF;AAAA,IACA,MAAM;AAAA,MACJ,GAAG,UAAU;AAAA,MACb,+BAA+B;AAAA,QAC7B,UAAU,KAAK;AAAA,MACjB;AAAA,MACA,uBAAuB;AAAA,QACrB,UAAU,KAAK;AAAA,MACjB;AAAA,IACF;AAAA,IACA,kBAAkB,4BAA4B,UAAU,gBAAgB;AAAA,IACxE,wBAAwB,UAAU;AAAA,IAClC,cAAc,UAAU;AAAA,IACxB,wBAAwB,UAAU;AAAA,IAClC,4BAA4B,UAAU;AAAA,IACtC,kBAAkB,UAAU;AAAA,IAC5B,mBAAmB,UAAU;AAAA,EAC/B;AACF;AAUA,eAAsB,6BACpB,KACA,UACiC;AACjC,QAAM,SAAS,MAAM,YAAY,KAAK;AAAA,IACpC,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,GAAI,YAAY,mBAAmB,QAAQ;AAAA,IAC7C;AAAA,EACF,CAAC;AACD,SAAO;AAAA,IACL,KAAK,OAAO;AAAA,IACZ,QAAQ,OAAO;AAAA,IACf,MAAM,OAAO;AAAA,EACf;AACF;AAQA,eAAsB,yBACpB,UAC8B;AAC9B,QAAM,WAAW,MAAM,MAAM,UAAU,UAAU;AACjD,SAAO;AAAA,IACL,UAAU,gBAAgB,QAAQ,EAAE,SAAS;AAAA,EAC/C;AACF;AAUO,SAAS,sBACd,sBACA,cACA,iBAC2B;AAC3B,QAAM,EAAE,KAAK,QAAQ,MAAM,iBAAiB,IAAI;AAEhD,QAAM,4BAA4B,eAAe,oBAAoB;AACrE,QAAM,oBAAoB,eAAe,YAAY;AACrD,QAAM,wBAAwB,eAAe,gBAAgB;AAE7D,QAAM,0BAA0B,GAAG;AAAA,IACjC;AAAA,IACA,kBAAkB,IAAI,qBAAqB;AAAA,EAC7C;AAEA,QAAM,yBAAyB;AAAA,IAC7B,IAAI;AAAA,EACN;AACA,QAAM,4BAA4B;AAAA,IAChC,OAAO;AAAA,EACT;AACA,QAAM,0BAA0B;AAAA,IAC9B,KAAK;AAAA,EACP;AAEA,MAAI;AACJ,MAAI;AAEJ,MAAI,wBAAwB,GAAG,sBAAsB,GAAG;AACtD,qBAAiB;AACjB,qBAAiB;AAAA,EACnB,WACE,wBAAwB,IAAI,sBAAsB,KAClD,wBAAwB,GAAG,yBAAyB,GACpD;AACA,qBAAiB,IAAI;AACrB,qBAAiB,IAAI;AAAA,EACvB,WACE,wBAAwB,IAAI,yBAAyB,KACrD,wBAAwB,GAAG,uBAAuB,GAClD;AACA,qBAAiB,OAAO;AACxB,qBAAiB,OAAO;AAAA,EAC1B,WAAW,wBAAwB,GAAG,uBAAuB,GAAG;AAC9D,qBAAiB,KAAK;AACtB,qBAAiB,KAAK;AAAA,EACxB,OAAO;AACL,qBAAiB;AACjB,qBAAiB,KAAK;AAAA,EACxB;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,EACF;AACF;","names":[]} +\ No newline at end of file +diff --git a/dist/chunk-X74LQX2Y.js b/dist/chunk-X74LQX2Y.js +new file mode 100644 +index 0000000000000000000000000000000000000000..11d4e4b36c51872b5b529eb4e7d4f7eea91e87c7 +--- /dev/null ++++ b/dist/chunk-X74LQX2Y.js +@@ -0,0 +1,390 @@ ++"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } ++ ++ ++ ++ ++ ++ ++ ++ ++var _chunk2MFVV2BXjs = require('./chunk-2MFVV2BX.js'); ++ ++// src/GasFeeController.ts ++ ++ ++ ++ ++var _controllerutils = require('@metamask/controller-utils'); ++var _ethquery = require('@metamask/eth-query'); var _ethquery2 = _interopRequireDefault(_ethquery); ++var _pollingcontroller = require('@metamask/polling-controller'); ++var _uuid = require('uuid'); ++var LEGACY_GAS_PRICES_API_URL = `https://api.metaswap.codefi.network/gasPrices`; ++var GAS_ESTIMATE_TYPES = { ++ FEE_MARKET: "fee-market", ++ LEGACY: "legacy", ++ ETH_GASPRICE: "eth_gasPrice", ++ NONE: "none" ++}; ++var metadata = { ++ gasFeeEstimatesByChainId: { ++ persist: true, ++ anonymous: false ++ }, ++ gasFeeEstimates: { persist: true, anonymous: false }, ++ estimatedGasFeeTimeBounds: { persist: true, anonymous: false }, ++ gasEstimateType: { persist: true, anonymous: false }, ++ nonRPCGasFeeApisDisabled: { persist: true, anonymous: false } ++}; ++var name = "GasFeeController"; ++var defaultState = { ++ gasFeeEstimatesByChainId: {}, ++ gasFeeEstimates: {}, ++ estimatedGasFeeTimeBounds: {}, ++ gasEstimateType: GAS_ESTIMATE_TYPES.NONE, ++ nonRPCGasFeeApisDisabled: false ++}; ++var _getProvider, _onNetworkControllerDidChange, onNetworkControllerDidChange_fn; ++var GasFeeController = class extends _pollingcontroller.StaticIntervalPollingController { ++ /** ++ * Creates a GasFeeController instance. ++ * ++ * @param options - The controller options. ++ * @param options.interval - The time in milliseconds to wait between polls. ++ * @param options.messenger - The controller messenger. ++ * @param options.state - The initial state. ++ * @param options.getCurrentNetworkEIP1559Compatibility - Determines whether or not the current ++ * network is EIP-1559 compatible. ++ * @param options.getCurrentNetworkLegacyGasAPICompatibility - Determines whether or not the ++ * current network is compatible with the legacy gas price API. ++ * @param options.getCurrentAccountEIP1559Compatibility - Determines whether or not the current ++ * account is EIP-1559 compatible. ++ * @param options.getChainId - Returns the current chain ID. ++ * @param options.getProvider - Returns a network provider for the current network. ++ * @param options.onNetworkDidChange - A function for registering an event handler for the ++ * network state change event. ++ * @param options.legacyAPIEndpoint - The legacy gas price API URL. This option is primarily for ++ * testing purposes. ++ * @param options.EIP1559APIEndpoint - The EIP-1559 gas price API URL. ++ * @param options.clientId - The client ID used to identify to the gas estimation API who is ++ * asking for estimates. ++ */ ++ constructor({ ++ interval = 15e3, ++ messenger, ++ state, ++ getCurrentNetworkEIP1559Compatibility, ++ getCurrentAccountEIP1559Compatibility, ++ getChainId, ++ getCurrentNetworkLegacyGasAPICompatibility, ++ getProvider, ++ onNetworkDidChange, ++ legacyAPIEndpoint = LEGACY_GAS_PRICES_API_URL, ++ EIP1559APIEndpoint, ++ clientId ++ }) { ++ super({ ++ name, ++ metadata, ++ messenger, ++ state: { ...defaultState, ...state } ++ }); ++ _chunk2MFVV2BXjs.__privateAdd.call(void 0, this, _onNetworkControllerDidChange); ++ _chunk2MFVV2BXjs.__privateAdd.call(void 0, this, _getProvider, void 0); ++ this.intervalDelay = interval; ++ this.setIntervalLength(interval); ++ this.pollTokens = /* @__PURE__ */ new Set(); ++ this.getCurrentNetworkEIP1559Compatibility = getCurrentNetworkEIP1559Compatibility; ++ this.getCurrentNetworkLegacyGasAPICompatibility = getCurrentNetworkLegacyGasAPICompatibility; ++ this.getCurrentAccountEIP1559Compatibility = getCurrentAccountEIP1559Compatibility; ++ _chunk2MFVV2BXjs.__privateSet.call(void 0, this, _getProvider, getProvider); ++ this.EIP1559APIEndpoint = EIP1559APIEndpoint; ++ this.legacyAPIEndpoint = legacyAPIEndpoint; ++ this.clientId = clientId; ++ this.ethQuery = new (0, _ethquery2.default)(_chunk2MFVV2BXjs.__privateGet.call(void 0, this, _getProvider).call(this)); ++ if (onNetworkDidChange && getChainId) { ++ this.currentChainId = getChainId(); ++ onNetworkDidChange(async (networkControllerState) => { ++ await _chunk2MFVV2BXjs.__privateMethod.call(void 0, this, _onNetworkControllerDidChange, onNetworkControllerDidChange_fn).call(this, networkControllerState); ++ }); ++ } else { ++ this.currentChainId = this.messagingSystem.call( ++ "NetworkController:getState" ++ ).providerConfig.chainId; ++ this.messagingSystem.subscribe( ++ "NetworkController:networkDidChange", ++ async (networkControllerState) => { ++ await _chunk2MFVV2BXjs.__privateMethod.call(void 0, this, _onNetworkControllerDidChange, onNetworkControllerDidChange_fn).call(this, networkControllerState); ++ } ++ ); ++ } ++ } ++ async resetPolling() { ++ if (this.pollTokens.size !== 0) { ++ const tokens = Array.from(this.pollTokens); ++ this.stopPolling(); ++ await this.getGasFeeEstimatesAndStartPolling(tokens[0]); ++ tokens.slice(1).forEach((token) => { ++ this.pollTokens.add(token); ++ }); ++ } ++ } ++ async fetchGasFeeEstimates(options) { ++ return await this._fetchGasFeeEstimateData(options); ++ } ++ async getGasFeeEstimatesAndStartPolling(pollToken) { ++ const _pollToken = pollToken || _uuid.v1.call(void 0, ); ++ this.pollTokens.add(_pollToken); ++ if (this.pollTokens.size === 1) { ++ await this._fetchGasFeeEstimateData(); ++ this._poll(); ++ } ++ return _pollToken; ++ } ++ /** ++ * Gets and sets gasFeeEstimates in state. ++ * ++ * @param options - The gas fee estimate options. ++ * @param options.shouldUpdateState - Determines whether the state should be updated with the ++ * updated gas estimates. ++ * @returns The gas fee estimates. ++ */ ++ async _fetchGasFeeEstimateData(options = {}) { ++ const { shouldUpdateState = true, networkClientId } = options; ++ let ethQuery, isEIP1559Compatible, isLegacyGasAPICompatible, decimalChainId; ++ if (networkClientId !== void 0) { ++ const networkClient = this.messagingSystem.call( ++ "NetworkController:getNetworkClientById", ++ networkClientId ++ ); ++ isLegacyGasAPICompatible = networkClient.configuration.chainId === "0x38"; ++ decimalChainId = _controllerutils.convertHexToDecimal.call(void 0, networkClient.configuration.chainId); ++ try { ++ const result = await this.messagingSystem.call( ++ "NetworkController:getEIP1559Compatibility", ++ networkClientId ++ ); ++ isEIP1559Compatible = result || false; ++ } catch { ++ isEIP1559Compatible = false; ++ } ++ ethQuery = new (0, _ethquery2.default)(networkClient.provider); ++ } ++ ethQuery ?? (ethQuery = this.ethQuery); ++ isLegacyGasAPICompatible ?? (isLegacyGasAPICompatible = this.getCurrentNetworkLegacyGasAPICompatibility()); ++ decimalChainId ?? (decimalChainId = _controllerutils.convertHexToDecimal.call(void 0, this.currentChainId)); ++ try { ++ isEIP1559Compatible ?? (isEIP1559Compatible = await this.getEIP1559Compatibility()); ++ } catch (e) { ++ console.error(e); ++ isEIP1559Compatible ?? (isEIP1559Compatible = false); ++ } ++ const gasFeeCalculations = await determineGasFeeCalculations({ ++ isEIP1559Compatible, ++ isLegacyGasAPICompatible, ++ fetchGasEstimates: _chunk2MFVV2BXjs.fetchGasEstimates, ++ fetchGasEstimatesUrl: this.EIP1559APIEndpoint.replace( ++ "", ++ `${decimalChainId}` ++ ), ++ fetchLegacyGasPriceEstimates: _chunk2MFVV2BXjs.fetchLegacyGasPriceEstimates, ++ fetchLegacyGasPriceEstimatesUrl: this.legacyAPIEndpoint.replace( ++ "", ++ `${decimalChainId}` ++ ), ++ fetchEthGasPriceEstimate: _chunk2MFVV2BXjs.fetchEthGasPriceEstimate, ++ calculateTimeEstimate: _chunk2MFVV2BXjs.calculateTimeEstimate, ++ clientId: this.clientId, ++ ethQuery, ++ nonRPCGasFeeApisDisabled: this.state.nonRPCGasFeeApisDisabled ++ }); ++ if (shouldUpdateState) { ++ const chainId = _controllerutils.toHex.call(void 0, decimalChainId); ++ this.update((state) => { ++ if (this.currentChainId === chainId) { ++ state.gasFeeEstimates = gasFeeCalculations.gasFeeEstimates; ++ state.estimatedGasFeeTimeBounds = gasFeeCalculations.estimatedGasFeeTimeBounds; ++ state.gasEstimateType = gasFeeCalculations.gasEstimateType; ++ } ++ state.gasFeeEstimatesByChainId ?? (state.gasFeeEstimatesByChainId = {}); ++ state.gasFeeEstimatesByChainId[chainId] = { ++ gasFeeEstimates: gasFeeCalculations.gasFeeEstimates, ++ estimatedGasFeeTimeBounds: gasFeeCalculations.estimatedGasFeeTimeBounds, ++ gasEstimateType: gasFeeCalculations.gasEstimateType ++ }; ++ }); ++ } ++ return gasFeeCalculations; ++ } ++ /** ++ * Remove the poll token, and stop polling if the set of poll tokens is empty. ++ * ++ * @param pollToken - The poll token to disconnect. ++ */ ++ disconnectPoller(pollToken) { ++ this.pollTokens.delete(pollToken); ++ if (this.pollTokens.size === 0) { ++ this.stopPolling(); ++ } ++ } ++ stopPolling() { ++ if (this.intervalId) { ++ clearInterval(this.intervalId); ++ } ++ this.pollTokens.clear(); ++ this.resetState(); ++ } ++ /** ++ * Prepare to discard this controller. ++ * ++ * This stops any active polling. ++ */ ++ destroy() { ++ super.destroy(); ++ this.stopPolling(); ++ } ++ _poll() { ++ if (this.intervalId) { ++ clearInterval(this.intervalId); ++ } ++ this.intervalId = setInterval(async () => { ++ await _controllerutils.safelyExecute.call(void 0, () => this._fetchGasFeeEstimateData()); ++ }, this.intervalDelay); ++ } ++ /** ++ * Fetching token list from the Token Service API. ++ * ++ * @private ++ * @param networkClientId - The ID of the network client triggering the fetch. ++ * @returns A promise that resolves when this operation completes. ++ */ ++ async _executePoll(networkClientId) { ++ await this._fetchGasFeeEstimateData({ networkClientId }); ++ } ++ resetState() { ++ this.update(() => { ++ return defaultState; ++ }); ++ } ++ async getEIP1559Compatibility() { ++ const currentNetworkIsEIP1559Compatible = await this.getCurrentNetworkEIP1559Compatibility(); ++ const currentAccountIsEIP1559Compatible = this.getCurrentAccountEIP1559Compatibility?.() ?? true; ++ return currentNetworkIsEIP1559Compatible && currentAccountIsEIP1559Compatible; ++ } ++ getTimeEstimate(maxPriorityFeePerGas, maxFeePerGas) { ++ if (!this.state.gasFeeEstimates || this.state.gasEstimateType !== GAS_ESTIMATE_TYPES.FEE_MARKET) { ++ return {}; ++ } ++ return _chunk2MFVV2BXjs.calculateTimeEstimate.call(void 0, ++ maxPriorityFeePerGas, ++ maxFeePerGas, ++ this.state.gasFeeEstimates ++ ); ++ } ++ enableNonRPCGasFeeApis() { ++ this.update((state) => { ++ state.nonRPCGasFeeApisDisabled = false; ++ }); ++ } ++ disableNonRPCGasFeeApis() { ++ this.update((state) => { ++ state.nonRPCGasFeeApisDisabled = true; ++ }); ++ } ++}; ++_getProvider = new WeakMap(); ++_onNetworkControllerDidChange = new WeakSet(); ++onNetworkControllerDidChange_fn = async function(networkControllerState) { ++ const newChainId = networkControllerState.providerConfig.chainId; ++ if (newChainId !== this.currentChainId) { ++ this.ethQuery = new (0, _ethquery2.default)(_chunk2MFVV2BXjs.__privateGet.call(void 0, this, _getProvider).call(this)); ++ await this.resetPolling(); ++ this.currentChainId = newChainId; ++ } ++}; ++var GasFeeController_default = GasFeeController; ++ ++// src/determineGasFeeCalculations.ts ++async function determineGasFeeCalculations(args) { ++ try { ++ return await getEstimatesUsingFallbacks(args); ++ } catch (error) { ++ if (error instanceof Error) { ++ throw new Error( ++ `Gas fee/price estimation failed. Message: ${error.message}` ++ ); ++ } ++ throw error; ++ } ++} ++async function getEstimatesUsingFallbacks(request) { ++ const { ++ isEIP1559Compatible, ++ isLegacyGasAPICompatible, ++ nonRPCGasFeeApisDisabled ++ } = request; ++ try { ++ if (isEIP1559Compatible && !nonRPCGasFeeApisDisabled) { ++ return await getEstimatesUsingFeeMarketEndpoint(request); ++ } ++ if (isLegacyGasAPICompatible && !nonRPCGasFeeApisDisabled) { ++ return await getEstimatesUsingLegacyEndpoint(request); ++ } ++ throw new Error("Main gas fee/price estimation failed. Use fallback"); ++ } catch { ++ return await getEstimatesUsingProvider(request); ++ } ++} ++async function getEstimatesUsingFeeMarketEndpoint(request) { ++ const { ++ fetchGasEstimates: fetchGasEstimates2, ++ fetchGasEstimatesUrl, ++ clientId, ++ calculateTimeEstimate: calculateTimeEstimate2 ++ } = request; ++ const estimates = await fetchGasEstimates2(fetchGasEstimatesUrl, clientId); ++ const { suggestedMaxPriorityFeePerGas, suggestedMaxFeePerGas } = estimates.medium; ++ const estimatedGasFeeTimeBounds = calculateTimeEstimate2( ++ suggestedMaxPriorityFeePerGas, ++ suggestedMaxFeePerGas, ++ estimates ++ ); ++ return { ++ gasFeeEstimates: estimates, ++ estimatedGasFeeTimeBounds, ++ gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET ++ }; ++} ++async function getEstimatesUsingLegacyEndpoint(request) { ++ const { ++ fetchLegacyGasPriceEstimates: fetchLegacyGasPriceEstimates2, ++ fetchLegacyGasPriceEstimatesUrl, ++ clientId ++ } = request; ++ const estimates = await fetchLegacyGasPriceEstimates2( ++ fetchLegacyGasPriceEstimatesUrl, ++ clientId ++ ); ++ return { ++ gasFeeEstimates: estimates, ++ estimatedGasFeeTimeBounds: {}, ++ gasEstimateType: GAS_ESTIMATE_TYPES.LEGACY ++ }; ++} ++async function getEstimatesUsingProvider(request) { ++ const { ethQuery, fetchEthGasPriceEstimate: fetchEthGasPriceEstimate2 } = request; ++ const estimates = await fetchEthGasPriceEstimate2(ethQuery); ++ return { ++ gasFeeEstimates: estimates, ++ estimatedGasFeeTimeBounds: {}, ++ gasEstimateType: GAS_ESTIMATE_TYPES.ETH_GASPRICE ++ }; ++} ++ ++ ++ ++ ++ ++ ++ ++exports.determineGasFeeCalculations = determineGasFeeCalculations; exports.LEGACY_GAS_PRICES_API_URL = LEGACY_GAS_PRICES_API_URL; exports.GAS_ESTIMATE_TYPES = GAS_ESTIMATE_TYPES; exports.GasFeeController = GasFeeController; exports.GasFeeController_default = GasFeeController_default; ++//# sourceMappingURL=chunk-X74LQX2Y.js.map +\ No newline at end of file +diff --git a/dist/chunk-X74LQX2Y.js.map b/dist/chunk-X74LQX2Y.js.map +new file mode 100644 +index 0000000000000000000000000000000000000000..c330267c1f74907a70d669611fb5d8eff71305aa +--- /dev/null ++++ b/dist/chunk-X74LQX2Y.js.map +@@ -0,0 +1 @@ ++{"version":3,"sources":["../src/GasFeeController.ts","../src/determineGasFeeCalculations.ts"],"names":["fetchGasEstimates","calculateTimeEstimate","fetchLegacyGasPriceEstimates","fetchEthGasPriceEstimate"],"mappings":";;;;;;;;;;;;AAKA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,OAAO,cAAc;AAUrB,SAAS,uCAAuC;AAEhD,SAAS,MAAM,cAAc;AAUtB,IAAM,4BAA4B;AA0BlC,IAAM,qBAAqB;AAAA,EAChC,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,cAAc;AAAA,EACd,MAAM;AACR;AAiGA,IAAM,WAAW;AAAA,EACf,0BAA0B;AAAA,IACxB,SAAS;AAAA,IACT,WAAW;AAAA,EACb;AAAA,EACA,iBAAiB,EAAE,SAAS,MAAM,WAAW,MAAM;AAAA,EACnD,2BAA2B,EAAE,SAAS,MAAM,WAAW,MAAM;AAAA,EAC7D,iBAAiB,EAAE,SAAS,MAAM,WAAW,MAAM;AAAA,EACnD,0BAA0B,EAAE,SAAS,MAAM,WAAW,MAAM;AAC9D;AAqDA,IAAM,OAAO;AA0Bb,IAAM,eAA4B;AAAA,EAChC,0BAA0B,CAAC;AAAA,EAC3B,iBAAiB,CAAC;AAAA,EAClB,2BAA2B,CAAC;AAAA,EAC5B,iBAAiB,mBAAmB;AAAA,EACpC,0BAA0B;AAC5B;AA9PA;AAmQO,IAAM,mBAAN,cAA+B,gCAIpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgDA,YAAY;AAAA,IACV,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,oBAAoB;AAAA,IACpB;AAAA,IACA;AAAA,EACF,GAcG;AACD,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,EAAE,GAAG,cAAc,GAAG,MAAM;AAAA,IACrC,CAAC;AAqPH,uBAAM;AA/SN;AA2DE,SAAK,gBAAgB;AACrB,SAAK,kBAAkB,QAAQ;AAC/B,SAAK,aAAa,oBAAI,IAAI;AAC1B,SAAK,wCACH;AACF,SAAK,6CACH;AACF,SAAK,wCACH;AACF,uBAAK,cAAe;AACpB,SAAK,qBAAqB;AAC1B,SAAK,oBAAoB;AACzB,SAAK,WAAW;AAEhB,SAAK,WAAW,IAAI,SAAS,mBAAK,cAAL,UAAmB;AAEhD,QAAI,sBAAsB,YAAY;AACpC,WAAK,iBAAiB,WAAW;AACjC,yBAAmB,OAAO,2BAA2B;AACnD,cAAM,sBAAK,gEAAL,WAAmC;AAAA,MAC3C,CAAC;AAAA,IACH,OAAO;AACL,WAAK,iBAAiB,KAAK,gBAAgB;AAAA,QACzC;AAAA,MACF,EAAE,eAAe;AACjB,WAAK,gBAAgB;AAAA,QACnB;AAAA,QACA,OAAO,2BAA2B;AAChC,gBAAM,sBAAK,gEAAL,WAAmC;AAAA,QAC3C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,eAAe;AACnB,QAAI,KAAK,WAAW,SAAS,GAAG;AAC9B,YAAM,SAAS,MAAM,KAAK,KAAK,UAAU;AACzC,WAAK,YAAY;AACjB,YAAM,KAAK,kCAAkC,OAAO,CAAC,CAAC;AACtD,aAAO,MAAM,CAAC,EAAE,QAAQ,CAAC,UAAU;AACjC,aAAK,WAAW,IAAI,KAAK;AAAA,MAC3B,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,MAAM,qBAAqB,SAAsC;AAC/D,WAAO,MAAM,KAAK,yBAAyB,OAAO;AAAA,EACpD;AAAA,EAEA,MAAM,kCACJ,WACiB;AACjB,UAAM,aAAa,aAAa,OAAO;AAEvC,SAAK,WAAW,IAAI,UAAU;AAE9B,QAAI,KAAK,WAAW,SAAS,GAAG;AAC9B,YAAM,KAAK,yBAAyB;AACpC,WAAK,MAAM;AAAA,IACb;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,yBACJ,UAAsC,CAAC,GACjB;AACtB,UAAM,EAAE,oBAAoB,MAAM,gBAAgB,IAAI;AAEtD,QAAI,UACF,qBACA,0BACA;AAEF,QAAI,oBAAoB,QAAW;AACjC,YAAM,gBAAgB,KAAK,gBAAgB;AAAA,QACzC;AAAA,QACA;AAAA,MACF;AACA,iCAA2B,cAAc,cAAc,YAAY;AAEnE,uBAAiB,oBAAoB,cAAc,cAAc,OAAO;AAExE,UAAI;AACF,cAAM,SAAS,MAAM,KAAK,gBAAgB;AAAA,UACxC;AAAA,UACA;AAAA,QACF;AACA,8BAAsB,UAAU;AAAA,MAClC,QAAQ;AACN,8BAAsB;AAAA,MACxB;AACA,iBAAW,IAAI,SAAS,cAAc,QAAQ;AAAA,IAChD;AAEA,4BAAa,KAAK;AAElB,4DACE,KAAK,2CAA2C;AAElD,wCAAmB,oBAAoB,KAAK,cAAc;AAE1D,QAAI;AACF,oDAAwB,MAAM,KAAK,wBAAwB;AAAA,IAC7D,SAAS,GAAG;AACV,cAAQ,MAAM,CAAC;AACf,oDAAwB;AAAA,IAC1B;AAEA,UAAM,qBAAqB,MAAM,4BAA4B;AAAA,MAC3D;AAAA,MACA;AAAA,MACA;AAAA,MACA,sBAAsB,KAAK,mBAAmB;AAAA,QAC5C;AAAA,QACA,GAAG,cAAc;AAAA,MACnB;AAAA,MACA;AAAA,MACA,iCAAiC,KAAK,kBAAkB;AAAA,QACtD;AAAA,QACA,GAAG,cAAc;AAAA,MACnB;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU,KAAK;AAAA,MACf;AAAA,MACA,0BAA0B,KAAK,MAAM;AAAA,IACvC,CAAC;AAED,QAAI,mBAAmB;AACrB,YAAM,UAAU,MAAM,cAAc;AACpC,WAAK,OAAO,CAAC,UAAU;AACrB,YAAI,KAAK,mBAAmB,SAAS;AACnC,gBAAM,kBAAkB,mBAAmB;AAC3C,gBAAM,4BACJ,mBAAmB;AACrB,gBAAM,kBAAkB,mBAAmB;AAAA,QAC7C;AACA,cAAM,6BAAN,MAAM,2BAA6B,CAAC;AACpC,cAAM,yBAAyB,OAAO,IAAI;AAAA,UACxC,iBAAiB,mBAAmB;AAAA,UACpC,2BACE,mBAAmB;AAAA,UACrB,iBAAiB,mBAAmB;AAAA,QACtC;AAAA,MACF,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBAAiB,WAAmB;AAClC,SAAK,WAAW,OAAO,SAAS;AAChC,QAAI,KAAK,WAAW,SAAS,GAAG;AAC9B,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA,EAEA,cAAc;AACZ,QAAI,KAAK,YAAY;AACnB,oBAAc,KAAK,UAAU;AAAA,IAC/B;AACA,SAAK,WAAW,MAAM;AACtB,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOS,UAAU;AACjB,UAAM,QAAQ;AACd,SAAK,YAAY;AAAA,EACnB;AAAA,EAEQ,QAAQ;AACd,QAAI,KAAK,YAAY;AACnB,oBAAc,KAAK,UAAU;AAAA,IAC/B;AAEA,SAAK,aAAa,YAAY,YAAY;AACxC,YAAM,cAAc,MAAM,KAAK,yBAAyB,CAAC;AAAA,IAC3D,GAAG,KAAK,aAAa;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,aAAa,iBAAwC;AACzD,UAAM,KAAK,yBAAyB,EAAE,gBAAgB,CAAC;AAAA,EACzD;AAAA,EAEQ,aAAa;AACnB,SAAK,OAAO,MAAM;AAChB,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,0BAA0B;AACtC,UAAM,oCACJ,MAAM,KAAK,sCAAsC;AACnD,UAAM,oCACJ,KAAK,wCAAwC,KAAK;AAEpD,WACE,qCAAqC;AAAA,EAEzC;AAAA,EAEA,gBACE,sBACA,cACmD;AACnD,QACE,CAAC,KAAK,MAAM,mBACZ,KAAK,MAAM,oBAAoB,mBAAmB,YAClD;AACA,aAAO,CAAC;AAAA,IACV;AACA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,KAAK,MAAM;AAAA,IACb;AAAA,EACF;AAAA,EAaA,yBAAyB;AACvB,SAAK,OAAO,CAAC,UAAU;AACrB,YAAM,2BAA2B;AAAA,IACnC,CAAC;AAAA,EACH;AAAA,EAEA,0BAA0B;AACxB,SAAK,OAAO,CAAC,UAAU;AACrB,YAAM,2BAA2B;AAAA,IACnC,CAAC;AAAA,EACH;AACF;AArUE;AA+SM;AAAA,kCAA6B,eAAC,wBAAsC;AACxE,QAAM,aAAa,uBAAuB,eAAe;AAEzD,MAAI,eAAe,KAAK,gBAAgB;AACtC,SAAK,WAAW,IAAI,SAAS,mBAAK,cAAL,UAAmB;AAChD,UAAM,KAAK,aAAa;AAExB,SAAK,iBAAiB;AAAA,EACxB;AACF;AAeF,IAAO,2BAAQ;;;ACviBf,eAAO,4BACL,MAC6B;AAC7B,MAAI;AACF,WAAO,MAAM,2BAA2B,IAAI;AAAA,EAC9C,SAAS,OAAO;AACd,QAAI,iBAAiB,OAAO;AAC1B,YAAM,IAAI;AAAA,QACR,6CAA6C,MAAM,OAAO;AAAA,MAC5D;AAAA,IACF;AAEA,UAAM;AAAA,EACR;AACF;AAOA,eAAe,2BACb,SAC6B;AAC7B,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,MAAI;AACF,QAAI,uBAAuB,CAAC,0BAA0B;AACpD,aAAO,MAAM,mCAAmC,OAAO;AAAA,IACzD;AAEA,QAAI,4BAA4B,CAAC,0BAA0B;AACzD,aAAO,MAAM,gCAAgC,OAAO;AAAA,IACtD;AAEA,UAAM,IAAI,MAAM,oDAAoD;AAAA,EACtE,QAAQ;AACN,WAAO,MAAM,0BAA0B,OAAO;AAAA,EAChD;AACF;AAOA,eAAe,mCACb,SAC6B;AAC7B,QAAM;AAAA,IACJ,mBAAAA;AAAA,IACA;AAAA,IACA;AAAA,IACA,uBAAAC;AAAA,EACF,IAAI;AAEJ,QAAM,YAAY,MAAMD,mBAAkB,sBAAsB,QAAQ;AAExE,QAAM,EAAE,+BAA+B,sBAAsB,IAC3D,UAAU;AAEZ,QAAM,4BAA4BC;AAAA,IAChC;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,SAAO;AAAA,IACL,iBAAiB;AAAA,IACjB;AAAA,IACA,iBAAiB,mBAAmB;AAAA,EACtC;AACF;AAOA,eAAe,gCACb,SAC6B;AAC7B,QAAM;AAAA,IACJ,8BAAAC;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,QAAM,YAAY,MAAMA;AAAA,IACtB;AAAA,IACA;AAAA,EACF;AAEA,SAAO;AAAA,IACL,iBAAiB;AAAA,IACjB,2BAA2B,CAAC;AAAA,IAC5B,iBAAiB,mBAAmB;AAAA,EACtC;AACF;AAOA,eAAe,0BACb,SAC6B;AAC7B,QAAM,EAAE,UAAU,0BAAAC,0BAAyB,IAAI;AAE/C,QAAM,YAAY,MAAMA,0BAAyB,QAAQ;AAEzD,SAAO;AAAA,IACL,iBAAiB;AAAA,IACjB,2BAA2B,CAAC;AAAA,IAC5B,iBAAiB,mBAAmB;AAAA,EACtC;AACF","sourcesContent":["import type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n RestrictedControllerMessenger,\n} from '@metamask/base-controller';\nimport {\n convertHexToDecimal,\n safelyExecute,\n toHex,\n} from '@metamask/controller-utils';\nimport EthQuery from '@metamask/eth-query';\nimport type {\n NetworkClientId,\n NetworkControllerGetEIP1559CompatibilityAction,\n NetworkControllerGetNetworkClientByIdAction,\n NetworkControllerGetStateAction,\n NetworkControllerNetworkDidChangeEvent,\n NetworkState,\n ProviderProxy,\n} from '@metamask/network-controller';\nimport { StaticIntervalPollingController } from '@metamask/polling-controller';\nimport type { Hex } from '@metamask/utils';\nimport { v1 as random } from 'uuid';\n\nimport determineGasFeeCalculations from './determineGasFeeCalculations';\nimport {\n fetchGasEstimates,\n fetchLegacyGasPriceEstimates,\n fetchEthGasPriceEstimate,\n calculateTimeEstimate,\n} from './gas-util';\n\nexport const LEGACY_GAS_PRICES_API_URL = `https://api.metaswap.codefi.network/gasPrices`;\n\nexport type unknownString = 'unknown';\n\n// Fee Market describes the way gas is set after the london hardfork, and was\n// defined by EIP-1559.\nexport type FeeMarketEstimateType = 'fee-market';\n// Legacy describes gasPrice estimates from before london hardfork, when the\n// user is connected to mainnet and are presented with fast/average/slow\n// estimate levels to choose from.\nexport type LegacyEstimateType = 'legacy';\n// EthGasPrice describes a gasPrice estimate received from eth_gasPrice. Post\n// london this value should only be used for legacy type transactions when on\n// networks that support EIP-1559. This type of estimate is the most accurate\n// to display on custom networks that don't support EIP-1559.\nexport type EthGasPriceEstimateType = 'eth_gasPrice';\n// NoEstimate describes the state of the controller before receiving its first\n// estimate.\nexport type NoEstimateType = 'none';\n\n/**\n * Indicates which type of gasEstimate the controller is currently returning.\n * This is useful as a way of asserting that the shape of gasEstimates matches\n * expectations. NONE is a special case indicating that no previous gasEstimate\n * has been fetched.\n */\nexport const GAS_ESTIMATE_TYPES = {\n FEE_MARKET: 'fee-market' as FeeMarketEstimateType,\n LEGACY: 'legacy' as LegacyEstimateType,\n ETH_GASPRICE: 'eth_gasPrice' as EthGasPriceEstimateType,\n NONE: 'none' as NoEstimateType,\n};\n\nexport type GasEstimateType =\n | FeeMarketEstimateType\n | EthGasPriceEstimateType\n | LegacyEstimateType\n | NoEstimateType;\n\nexport type EstimatedGasFeeTimeBounds = {\n lowerTimeBound: number | null;\n upperTimeBound: number | unknownString;\n};\n\n/**\n * @type EthGasPriceEstimate\n *\n * A single gas price estimate for networks and accounts that don't support EIP-1559\n * This estimate comes from eth_gasPrice but is converted to dec gwei to match other\n * return values\n * @property gasPrice - A GWEI dec string\n */\n\nexport type EthGasPriceEstimate = {\n gasPrice: string;\n};\n\n/**\n * @type LegacyGasPriceEstimate\n *\n * A set of gas price estimates for networks and accounts that don't support EIP-1559\n * These estimates include low, medium and high all as strings representing gwei in\n * decimal format.\n * @property high - gasPrice, in decimal gwei string format, suggested for fast inclusion\n * @property medium - gasPrice, in decimal gwei string format, suggested for avg inclusion\n * @property low - gasPrice, in decimal gwei string format, suggested for slow inclusion\n */\nexport type LegacyGasPriceEstimate = {\n high: string;\n medium: string;\n low: string;\n};\n\n/**\n * @type Eip1559GasFee\n *\n * Data necessary to provide an estimate of a gas fee with a specific tip\n * @property minWaitTimeEstimate - The fastest the transaction will take, in milliseconds\n * @property maxWaitTimeEstimate - The slowest the transaction will take, in milliseconds\n * @property suggestedMaxPriorityFeePerGas - A suggested \"tip\", a GWEI hex number\n * @property suggestedMaxFeePerGas - A suggested max fee, the most a user will pay. a GWEI hex number\n */\nexport type Eip1559GasFee = {\n minWaitTimeEstimate: number; // a time duration in milliseconds\n maxWaitTimeEstimate: number; // a time duration in milliseconds\n suggestedMaxPriorityFeePerGas: string; // a GWEI decimal number\n suggestedMaxFeePerGas: string; // a GWEI decimal number\n};\n\n/**\n * @type GasFeeEstimates\n *\n * Data necessary to provide multiple GasFee estimates, and supporting information, to the user\n * @property low - A GasFee for a minimum necessary combination of tip and maxFee\n * @property medium - A GasFee for a recommended combination of tip and maxFee\n * @property high - A GasFee for a high combination of tip and maxFee\n * @property estimatedBaseFee - An estimate of what the base fee will be for the pending/next block. A GWEI dec number\n * @property networkCongestion - A normalized number that can be used to gauge the congestion\n * level of the network, with 0 meaning not congested and 1 meaning extremely congested\n */\nexport type GasFeeEstimates = SourcedGasFeeEstimates | FallbackGasFeeEstimates;\n\ntype SourcedGasFeeEstimates = {\n low: Eip1559GasFee;\n medium: Eip1559GasFee;\n high: Eip1559GasFee;\n estimatedBaseFee: string;\n historicalBaseFeeRange: [string, string];\n baseFeeTrend: 'up' | 'down' | 'level';\n latestPriorityFeeRange: [string, string];\n historicalPriorityFeeRange: [string, string];\n priorityFeeTrend: 'up' | 'down' | 'level';\n networkCongestion: number;\n};\n\ntype FallbackGasFeeEstimates = {\n low: Eip1559GasFee;\n medium: Eip1559GasFee;\n high: Eip1559GasFee;\n estimatedBaseFee: string;\n historicalBaseFeeRange: null;\n baseFeeTrend: null;\n latestPriorityFeeRange: null;\n historicalPriorityFeeRange: null;\n priorityFeeTrend: null;\n networkCongestion: null;\n};\n\nconst metadata = {\n gasFeeEstimatesByChainId: {\n persist: true,\n anonymous: false,\n },\n gasFeeEstimates: { persist: true, anonymous: false },\n estimatedGasFeeTimeBounds: { persist: true, anonymous: false },\n gasEstimateType: { persist: true, anonymous: false },\n nonRPCGasFeeApisDisabled: { persist: true, anonymous: false },\n};\n\nexport type GasFeeStateEthGasPrice = {\n gasFeeEstimates: EthGasPriceEstimate;\n estimatedGasFeeTimeBounds: Record;\n gasEstimateType: EthGasPriceEstimateType;\n};\n\nexport type GasFeeStateFeeMarket = {\n gasFeeEstimates: GasFeeEstimates;\n estimatedGasFeeTimeBounds: EstimatedGasFeeTimeBounds | Record;\n gasEstimateType: FeeMarketEstimateType;\n};\n\nexport type GasFeeStateLegacy = {\n gasFeeEstimates: LegacyGasPriceEstimate;\n estimatedGasFeeTimeBounds: Record;\n gasEstimateType: LegacyEstimateType;\n};\n\nexport type GasFeeStateNoEstimates = {\n gasFeeEstimates: Record;\n estimatedGasFeeTimeBounds: Record;\n gasEstimateType: NoEstimateType;\n};\n\nexport type FetchGasFeeEstimateOptions = {\n shouldUpdateState?: boolean;\n networkClientId?: NetworkClientId;\n};\n\n/**\n * @type GasFeeState\n *\n * Gas Fee controller state\n * @property gasFeeEstimates - Gas fee estimate data based on new EIP-1559 properties\n * @property estimatedGasFeeTimeBounds - Estimates representing the minimum and maximum\n */\nexport type SingleChainGasFeeState =\n | GasFeeStateEthGasPrice\n | GasFeeStateFeeMarket\n | GasFeeStateLegacy\n | GasFeeStateNoEstimates;\n\nexport type GasFeeEstimatesByChainId = {\n gasFeeEstimatesByChainId?: Record;\n};\n\nexport type GasFeeState = GasFeeEstimatesByChainId &\n SingleChainGasFeeState & {\n nonRPCGasFeeApisDisabled?: boolean;\n };\n\nconst name = 'GasFeeController';\n\nexport type GasFeeStateChange = ControllerStateChangeEvent<\n typeof name,\n GasFeeState\n>;\n\nexport type GetGasFeeState = ControllerGetStateAction;\n\nexport type GasFeeControllerActions = GetGasFeeState;\n\nexport type GasFeeControllerEvents = GasFeeStateChange;\n\ntype AllowedActions =\n | NetworkControllerGetStateAction\n | NetworkControllerGetNetworkClientByIdAction\n | NetworkControllerGetEIP1559CompatibilityAction;\n\ntype GasFeeMessenger = RestrictedControllerMessenger<\n typeof name,\n GasFeeControllerActions | AllowedActions,\n GasFeeControllerEvents | NetworkControllerNetworkDidChangeEvent,\n AllowedActions['type'],\n NetworkControllerNetworkDidChangeEvent['type']\n>;\n\nconst defaultState: GasFeeState = {\n gasFeeEstimatesByChainId: {},\n gasFeeEstimates: {},\n estimatedGasFeeTimeBounds: {},\n gasEstimateType: GAS_ESTIMATE_TYPES.NONE,\n nonRPCGasFeeApisDisabled: false,\n};\n\n/**\n * Controller that retrieves gas fee estimate data and polls for updated data on a set interval\n */\nexport class GasFeeController extends StaticIntervalPollingController<\n typeof name,\n GasFeeState,\n GasFeeMessenger\n> {\n private intervalId?: ReturnType;\n\n private readonly intervalDelay;\n\n private readonly pollTokens: Set;\n\n private readonly legacyAPIEndpoint: string;\n\n private readonly EIP1559APIEndpoint: string;\n\n private readonly getCurrentNetworkEIP1559Compatibility;\n\n private readonly getCurrentNetworkLegacyGasAPICompatibility;\n\n private readonly getCurrentAccountEIP1559Compatibility;\n\n private currentChainId;\n\n private ethQuery?: EthQuery;\n\n private readonly clientId?: string;\n\n #getProvider: () => ProviderProxy;\n\n /**\n * Creates a GasFeeController instance.\n *\n * @param options - The controller options.\n * @param options.interval - The time in milliseconds to wait between polls.\n * @param options.messenger - The controller messenger.\n * @param options.state - The initial state.\n * @param options.getCurrentNetworkEIP1559Compatibility - Determines whether or not the current\n * network is EIP-1559 compatible.\n * @param options.getCurrentNetworkLegacyGasAPICompatibility - Determines whether or not the\n * current network is compatible with the legacy gas price API.\n * @param options.getCurrentAccountEIP1559Compatibility - Determines whether or not the current\n * account is EIP-1559 compatible.\n * @param options.getChainId - Returns the current chain ID.\n * @param options.getProvider - Returns a network provider for the current network.\n * @param options.onNetworkDidChange - A function for registering an event handler for the\n * network state change event.\n * @param options.legacyAPIEndpoint - The legacy gas price API URL. This option is primarily for\n * testing purposes.\n * @param options.EIP1559APIEndpoint - The EIP-1559 gas price API URL.\n * @param options.clientId - The client ID used to identify to the gas estimation API who is\n * asking for estimates.\n */\n constructor({\n interval = 15000,\n messenger,\n state,\n getCurrentNetworkEIP1559Compatibility,\n getCurrentAccountEIP1559Compatibility,\n getChainId,\n getCurrentNetworkLegacyGasAPICompatibility,\n getProvider,\n onNetworkDidChange,\n legacyAPIEndpoint = LEGACY_GAS_PRICES_API_URL,\n EIP1559APIEndpoint,\n clientId,\n }: {\n interval?: number;\n messenger: GasFeeMessenger;\n state?: GasFeeState;\n getCurrentNetworkEIP1559Compatibility: () => Promise;\n getCurrentNetworkLegacyGasAPICompatibility: () => boolean;\n getCurrentAccountEIP1559Compatibility?: () => boolean;\n getChainId?: () => Hex;\n getProvider: () => ProviderProxy;\n onNetworkDidChange?: (listener: (state: NetworkState) => void) => void;\n legacyAPIEndpoint?: string;\n // eslint-disable-next-line @typescript-eslint/naming-convention\n EIP1559APIEndpoint: string;\n clientId?: string;\n }) {\n super({\n name,\n metadata,\n messenger,\n state: { ...defaultState, ...state },\n });\n this.intervalDelay = interval;\n this.setIntervalLength(interval);\n this.pollTokens = new Set();\n this.getCurrentNetworkEIP1559Compatibility =\n getCurrentNetworkEIP1559Compatibility;\n this.getCurrentNetworkLegacyGasAPICompatibility =\n getCurrentNetworkLegacyGasAPICompatibility;\n this.getCurrentAccountEIP1559Compatibility =\n getCurrentAccountEIP1559Compatibility;\n this.#getProvider = getProvider;\n this.EIP1559APIEndpoint = EIP1559APIEndpoint;\n this.legacyAPIEndpoint = legacyAPIEndpoint;\n this.clientId = clientId;\n\n this.ethQuery = new EthQuery(this.#getProvider());\n\n if (onNetworkDidChange && getChainId) {\n this.currentChainId = getChainId();\n onNetworkDidChange(async (networkControllerState) => {\n await this.#onNetworkControllerDidChange(networkControllerState);\n });\n } else {\n this.currentChainId = this.messagingSystem.call(\n 'NetworkController:getState',\n ).providerConfig.chainId;\n this.messagingSystem.subscribe(\n 'NetworkController:networkDidChange',\n async (networkControllerState) => {\n await this.#onNetworkControllerDidChange(networkControllerState);\n },\n );\n }\n }\n\n async resetPolling() {\n if (this.pollTokens.size !== 0) {\n const tokens = Array.from(this.pollTokens);\n this.stopPolling();\n await this.getGasFeeEstimatesAndStartPolling(tokens[0]);\n tokens.slice(1).forEach((token) => {\n this.pollTokens.add(token);\n });\n }\n }\n\n async fetchGasFeeEstimates(options?: FetchGasFeeEstimateOptions) {\n return await this._fetchGasFeeEstimateData(options);\n }\n\n async getGasFeeEstimatesAndStartPolling(\n pollToken: string | undefined,\n ): Promise {\n const _pollToken = pollToken || random();\n\n this.pollTokens.add(_pollToken);\n\n if (this.pollTokens.size === 1) {\n await this._fetchGasFeeEstimateData();\n this._poll();\n }\n\n return _pollToken;\n }\n\n /**\n * Gets and sets gasFeeEstimates in state.\n *\n * @param options - The gas fee estimate options.\n * @param options.shouldUpdateState - Determines whether the state should be updated with the\n * updated gas estimates.\n * @returns The gas fee estimates.\n */\n async _fetchGasFeeEstimateData(\n options: FetchGasFeeEstimateOptions = {},\n ): Promise {\n const { shouldUpdateState = true, networkClientId } = options;\n\n let ethQuery,\n isEIP1559Compatible,\n isLegacyGasAPICompatible,\n decimalChainId: number;\n\n if (networkClientId !== undefined) {\n const networkClient = this.messagingSystem.call(\n 'NetworkController:getNetworkClientById',\n networkClientId,\n );\n isLegacyGasAPICompatible = networkClient.configuration.chainId === '0x38';\n\n decimalChainId = convertHexToDecimal(networkClient.configuration.chainId);\n\n try {\n const result = await this.messagingSystem.call(\n 'NetworkController:getEIP1559Compatibility',\n networkClientId,\n );\n isEIP1559Compatible = result || false;\n } catch {\n isEIP1559Compatible = false;\n }\n ethQuery = new EthQuery(networkClient.provider);\n }\n\n ethQuery ??= this.ethQuery;\n\n isLegacyGasAPICompatible ??=\n this.getCurrentNetworkLegacyGasAPICompatibility();\n\n decimalChainId ??= convertHexToDecimal(this.currentChainId);\n\n try {\n isEIP1559Compatible ??= await this.getEIP1559Compatibility();\n } catch (e) {\n console.error(e);\n isEIP1559Compatible ??= false;\n }\n\n const gasFeeCalculations = await determineGasFeeCalculations({\n isEIP1559Compatible,\n isLegacyGasAPICompatible,\n fetchGasEstimates,\n fetchGasEstimatesUrl: this.EIP1559APIEndpoint.replace(\n '',\n `${decimalChainId}`,\n ),\n fetchLegacyGasPriceEstimates,\n fetchLegacyGasPriceEstimatesUrl: this.legacyAPIEndpoint.replace(\n '',\n `${decimalChainId}`,\n ),\n fetchEthGasPriceEstimate,\n calculateTimeEstimate,\n clientId: this.clientId,\n ethQuery,\n nonRPCGasFeeApisDisabled: this.state.nonRPCGasFeeApisDisabled,\n });\n\n if (shouldUpdateState) {\n const chainId = toHex(decimalChainId);\n this.update((state) => {\n if (this.currentChainId === chainId) {\n state.gasFeeEstimates = gasFeeCalculations.gasFeeEstimates;\n state.estimatedGasFeeTimeBounds =\n gasFeeCalculations.estimatedGasFeeTimeBounds;\n state.gasEstimateType = gasFeeCalculations.gasEstimateType;\n }\n state.gasFeeEstimatesByChainId ??= {};\n state.gasFeeEstimatesByChainId[chainId] = {\n gasFeeEstimates: gasFeeCalculations.gasFeeEstimates,\n estimatedGasFeeTimeBounds:\n gasFeeCalculations.estimatedGasFeeTimeBounds,\n gasEstimateType: gasFeeCalculations.gasEstimateType,\n } as SingleChainGasFeeState;\n });\n }\n\n return gasFeeCalculations;\n }\n\n /**\n * Remove the poll token, and stop polling if the set of poll tokens is empty.\n *\n * @param pollToken - The poll token to disconnect.\n */\n disconnectPoller(pollToken: string) {\n this.pollTokens.delete(pollToken);\n if (this.pollTokens.size === 0) {\n this.stopPolling();\n }\n }\n\n stopPolling() {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n }\n this.pollTokens.clear();\n this.resetState();\n }\n\n /**\n * Prepare to discard this controller.\n *\n * This stops any active polling.\n */\n override destroy() {\n super.destroy();\n this.stopPolling();\n }\n\n private _poll() {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n }\n\n this.intervalId = setInterval(async () => {\n await safelyExecute(() => this._fetchGasFeeEstimateData());\n }, this.intervalDelay);\n }\n\n /**\n * Fetching token list from the Token Service API.\n *\n * @private\n * @param networkClientId - The ID of the network client triggering the fetch.\n * @returns A promise that resolves when this operation completes.\n */\n async _executePoll(networkClientId: string): Promise {\n await this._fetchGasFeeEstimateData({ networkClientId });\n }\n\n private resetState() {\n this.update(() => {\n return defaultState;\n });\n }\n\n private async getEIP1559Compatibility() {\n const currentNetworkIsEIP1559Compatible =\n await this.getCurrentNetworkEIP1559Compatibility();\n const currentAccountIsEIP1559Compatible =\n this.getCurrentAccountEIP1559Compatibility?.() ?? true;\n\n return (\n currentNetworkIsEIP1559Compatible && currentAccountIsEIP1559Compatible\n );\n }\n\n getTimeEstimate(\n maxPriorityFeePerGas: string,\n maxFeePerGas: string,\n ): EstimatedGasFeeTimeBounds | Record {\n if (\n !this.state.gasFeeEstimates ||\n this.state.gasEstimateType !== GAS_ESTIMATE_TYPES.FEE_MARKET\n ) {\n return {};\n }\n return calculateTimeEstimate(\n maxPriorityFeePerGas,\n maxFeePerGas,\n this.state.gasFeeEstimates,\n );\n }\n\n async #onNetworkControllerDidChange(networkControllerState: NetworkState) {\n const newChainId = networkControllerState.providerConfig.chainId;\n\n if (newChainId !== this.currentChainId) {\n this.ethQuery = new EthQuery(this.#getProvider());\n await this.resetPolling();\n\n this.currentChainId = newChainId;\n }\n }\n\n enableNonRPCGasFeeApis() {\n this.update((state) => {\n state.nonRPCGasFeeApisDisabled = false;\n });\n }\n\n disableNonRPCGasFeeApis() {\n this.update((state) => {\n state.nonRPCGasFeeApisDisabled = true;\n });\n }\n}\n\nexport default GasFeeController;\n","import type {\n EstimatedGasFeeTimeBounds,\n EthGasPriceEstimate,\n GasFeeEstimates,\n GasFeeState as GasFeeCalculations,\n LegacyGasPriceEstimate,\n} from './GasFeeController';\nimport { GAS_ESTIMATE_TYPES } from './GasFeeController';\n\ntype DetermineGasFeeCalculationsRequest = {\n isEIP1559Compatible: boolean;\n isLegacyGasAPICompatible: boolean;\n fetchGasEstimates: (\n url: string,\n clientId?: string,\n ) => Promise;\n fetchGasEstimatesUrl: string;\n fetchLegacyGasPriceEstimates: (\n url: string,\n clientId?: string,\n ) => Promise;\n fetchLegacyGasPriceEstimatesUrl: string;\n // TODO: Replace `any` with type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n fetchEthGasPriceEstimate: (ethQuery: any) => Promise;\n calculateTimeEstimate: (\n maxPriorityFeePerGas: string,\n maxFeePerGas: string,\n gasFeeEstimates: GasFeeEstimates,\n ) => EstimatedGasFeeTimeBounds;\n clientId: string | undefined;\n // TODO: Replace `any` with type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ethQuery: any;\n nonRPCGasFeeApisDisabled?: boolean;\n};\n\n/**\n * Obtains a set of max base and priority fee estimates along with time estimates so that we\n * can present them to users when they are sending transactions or making swaps.\n *\n * @param args - The arguments.\n * @param args.isEIP1559Compatible - Governs whether or not we can use an EIP-1559-only method to\n * produce estimates.\n * @param args.isLegacyGasAPICompatible - Governs whether or not we can use a non-EIP-1559 method to\n * produce estimates (for instance, testnets do not support estimates altogether).\n * @param args.fetchGasEstimates - A function that fetches gas estimates using an EIP-1559-specific\n * API.\n * @param args.fetchGasEstimatesUrl - The URL for the API we can use to obtain EIP-1559-specific\n * estimates.\n * @param args.fetchLegacyGasPriceEstimates - A function that fetches gas estimates using an\n * non-EIP-1559-specific API.\n * @param args.fetchLegacyGasPriceEstimatesUrl - The URL for the API we can use to obtain\n * non-EIP-1559-specific estimates.\n * @param args.fetchEthGasPriceEstimate - A function that fetches gas estimates using\n * `eth_gasPrice`.\n * @param args.calculateTimeEstimate - A function that determine time estimate bounds.\n * @param args.clientId - An identifier that an API can use to know who is asking for estimates.\n * @param args.ethQuery - An EthQuery instance we can use to talk to Ethereum directly.\n * @param args.nonRPCGasFeeApisDisabled - Whether to disable requests to the legacyAPIEndpoint and the EIP1559APIEndpoint\n * @returns The gas fee calculations.\n */\nexport default async function determineGasFeeCalculations(\n args: DetermineGasFeeCalculationsRequest,\n): Promise {\n try {\n return await getEstimatesUsingFallbacks(args);\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(\n `Gas fee/price estimation failed. Message: ${error.message}`,\n );\n }\n\n throw error;\n }\n}\n\n/**\n * Retrieve the gas fee estimates using a series of fallback mechanisms.\n * @param request - The request object.\n * @returns The gas fee estimates.\n */\nasync function getEstimatesUsingFallbacks(\n request: DetermineGasFeeCalculationsRequest,\n): Promise {\n const {\n isEIP1559Compatible,\n isLegacyGasAPICompatible,\n nonRPCGasFeeApisDisabled,\n } = request;\n\n try {\n if (isEIP1559Compatible && !nonRPCGasFeeApisDisabled) {\n return await getEstimatesUsingFeeMarketEndpoint(request);\n }\n\n if (isLegacyGasAPICompatible && !nonRPCGasFeeApisDisabled) {\n return await getEstimatesUsingLegacyEndpoint(request);\n }\n\n throw new Error('Main gas fee/price estimation failed. Use fallback');\n } catch {\n return await getEstimatesUsingProvider(request);\n }\n}\n\n/**\n * Retrieve gas fee estimates using the EIP-1559 endpoint of the gas API.\n * @param request - The request object.\n * @returns The gas fee estimates.\n */\nasync function getEstimatesUsingFeeMarketEndpoint(\n request: DetermineGasFeeCalculationsRequest,\n): Promise {\n const {\n fetchGasEstimates,\n fetchGasEstimatesUrl,\n clientId,\n calculateTimeEstimate,\n } = request;\n\n const estimates = await fetchGasEstimates(fetchGasEstimatesUrl, clientId);\n\n const { suggestedMaxPriorityFeePerGas, suggestedMaxFeePerGas } =\n estimates.medium;\n\n const estimatedGasFeeTimeBounds = calculateTimeEstimate(\n suggestedMaxPriorityFeePerGas,\n suggestedMaxFeePerGas,\n estimates,\n );\n\n return {\n gasFeeEstimates: estimates,\n estimatedGasFeeTimeBounds,\n gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET,\n };\n}\n\n/**\n * Retrieve gas fee estimates using the legacy endpoint of the gas API.\n * @param request - The request object.\n * @returns The gas fee estimates.\n */\nasync function getEstimatesUsingLegacyEndpoint(\n request: DetermineGasFeeCalculationsRequest,\n): Promise {\n const {\n fetchLegacyGasPriceEstimates,\n fetchLegacyGasPriceEstimatesUrl,\n clientId,\n } = request;\n\n const estimates = await fetchLegacyGasPriceEstimates(\n fetchLegacyGasPriceEstimatesUrl,\n clientId,\n );\n\n return {\n gasFeeEstimates: estimates,\n estimatedGasFeeTimeBounds: {},\n gasEstimateType: GAS_ESTIMATE_TYPES.LEGACY,\n };\n}\n\n/**\n * Retrieve gas fee estimates using an `eth_gasPrice` call to the RPC provider.\n * @param request - The request object.\n * @returns The gas fee estimates.\n */\nasync function getEstimatesUsingProvider(\n request: DetermineGasFeeCalculationsRequest,\n): Promise {\n const { ethQuery, fetchEthGasPriceEstimate } = request;\n\n const estimates = await fetchEthGasPriceEstimate(ethQuery);\n\n return {\n gasFeeEstimates: estimates,\n estimatedGasFeeTimeBounds: {},\n gasEstimateType: GAS_ESTIMATE_TYPES.ETH_GASPRICE,\n };\n}\n"]} +\ No newline at end of file +diff --git a/dist/determineGasFeeCalculations.js b/dist/determineGasFeeCalculations.js +index 87f583d091e574fc81b84ce35ee4b9979dbabdf6..ccfd460054f0affdec3857d8ec0015fe5c0814cb 100644 +--- a/dist/determineGasFeeCalculations.js ++++ b/dist/determineGasFeeCalculations.js +@@ -1,8 +1,8 @@ + "use strict";Object.defineProperty(exports, "__esModule", {value: true}); + +-var _chunkH5WHAYLIjs = require('./chunk-H5WHAYLI.js'); +-require('./chunk-Q2YPK5SL.js'); ++var _chunkX74LQX2Yjs = require('./chunk-X74LQX2Y.js'); ++require('./chunk-2MFVV2BX.js'); + + +-exports.default = _chunkH5WHAYLIjs.determineGasFeeCalculations; ++exports.default = _chunkX74LQX2Yjs.determineGasFeeCalculations; + //# sourceMappingURL=determineGasFeeCalculations.js.map +\ No newline at end of file +diff --git a/dist/determineGasFeeCalculations.mjs b/dist/determineGasFeeCalculations.mjs +index b372041262f1d8372632922919f52aa2c6c5ee89..e5b349a8f349076b85481dceec5795b07aeb19ca 100644 +--- a/dist/determineGasFeeCalculations.mjs ++++ b/dist/determineGasFeeCalculations.mjs +@@ -1,7 +1,7 @@ + import { + determineGasFeeCalculations +-} from "./chunk-BEVZS3YV.mjs"; +-import "./chunk-KORLXV32.mjs"; ++} from "./chunk-A7NHJBXX.mjs"; ++import "./chunk-R3IOI7AK.mjs"; + export { + determineGasFeeCalculations as default + }; +diff --git a/dist/gas-util.js b/dist/gas-util.js +index 74c93749878df29b40eea3d998d26d734112d75c..aad155e1a969ac01a09f8e18becac39a79860199 100644 +--- a/dist/gas-util.js ++++ b/dist/gas-util.js +@@ -4,12 +4,12 @@ + + + +-var _chunkQ2YPK5SLjs = require('./chunk-Q2YPK5SL.js'); ++var _chunk2MFVV2BXjs = require('./chunk-2MFVV2BX.js'); + + + + + + +-exports.calculateTimeEstimate = _chunkQ2YPK5SLjs.calculateTimeEstimate; exports.fetchEthGasPriceEstimate = _chunkQ2YPK5SLjs.fetchEthGasPriceEstimate; exports.fetchGasEstimates = _chunkQ2YPK5SLjs.fetchGasEstimates; exports.fetchLegacyGasPriceEstimates = _chunkQ2YPK5SLjs.fetchLegacyGasPriceEstimates; exports.normalizeGWEIDecimalNumbers = _chunkQ2YPK5SLjs.normalizeGWEIDecimalNumbers; ++exports.calculateTimeEstimate = _chunk2MFVV2BXjs.calculateTimeEstimate; exports.fetchEthGasPriceEstimate = _chunk2MFVV2BXjs.fetchEthGasPriceEstimate; exports.fetchGasEstimates = _chunk2MFVV2BXjs.fetchGasEstimates; exports.fetchLegacyGasPriceEstimates = _chunk2MFVV2BXjs.fetchLegacyGasPriceEstimates; exports.normalizeGWEIDecimalNumbers = _chunk2MFVV2BXjs.normalizeGWEIDecimalNumbers; + //# sourceMappingURL=gas-util.js.map +\ No newline at end of file +diff --git a/dist/gas-util.mjs b/dist/gas-util.mjs +index c0846d32a7fe2598691c5c0bcc19ce2b8af67bc3..60f38a1b3899f0223fa7651988e57fa659e713e8 100644 +--- a/dist/gas-util.mjs ++++ b/dist/gas-util.mjs +@@ -4,7 +4,7 @@ import { + fetchGasEstimates, + fetchLegacyGasPriceEstimates, + normalizeGWEIDecimalNumbers +-} from "./chunk-KORLXV32.mjs"; ++} from "./chunk-R3IOI7AK.mjs"; + export { + calculateTimeEstimate, + fetchEthGasPriceEstimate, +diff --git a/dist/index.js b/dist/index.js +index 499a7d57b68212db4402be72ab8d3384a841dba0..f172d463bef0093dd477dfad74ff50d30e42a7f0 100644 +--- a/dist/index.js ++++ b/dist/index.js +@@ -2,11 +2,11 @@ + + + +-var _chunkH5WHAYLIjs = require('./chunk-H5WHAYLI.js'); +-require('./chunk-Q2YPK5SL.js'); ++var _chunkX74LQX2Yjs = require('./chunk-X74LQX2Y.js'); ++require('./chunk-2MFVV2BX.js'); + + + + +-exports.GAS_API_BASE_URL = _chunkH5WHAYLIjs.GAS_API_BASE_URL; exports.GAS_ESTIMATE_TYPES = _chunkH5WHAYLIjs.GAS_ESTIMATE_TYPES; exports.GasFeeController = _chunkH5WHAYLIjs.GasFeeController; ++exports.GAS_ESTIMATE_TYPES = _chunkX74LQX2Yjs.GAS_ESTIMATE_TYPES; exports.GasFeeController = _chunkX74LQX2Yjs.GasFeeController; exports.LEGACY_GAS_PRICES_API_URL = _chunkX74LQX2Yjs.LEGACY_GAS_PRICES_API_URL; + //# sourceMappingURL=index.js.map +\ No newline at end of file +diff --git a/dist/index.mjs b/dist/index.mjs +index 47fbe488932996ec192dd04a6b92d72c038681cc..71847186d44947ec3b2d75d142afbeede0aa64fc 100644 +--- a/dist/index.mjs ++++ b/dist/index.mjs +@@ -1,12 +1,12 @@ + import { +- GAS_API_BASE_URL, + GAS_ESTIMATE_TYPES, +- GasFeeController +-} from "./chunk-BEVZS3YV.mjs"; +-import "./chunk-KORLXV32.mjs"; ++ GasFeeController, ++ LEGACY_GAS_PRICES_API_URL ++} from "./chunk-A7NHJBXX.mjs"; ++import "./chunk-R3IOI7AK.mjs"; + export { +- GAS_API_BASE_URL, + GAS_ESTIMATE_TYPES, +- GasFeeController ++ GasFeeController, ++ LEGACY_GAS_PRICES_API_URL + }; + //# sourceMappingURL=index.mjs.map +\ No newline at end of file +diff --git a/dist/tsconfig.build.tsbuildinfo b/dist/tsconfig.build.tsbuildinfo +index c226cf271a32a00bf6054fba381ed5f846ed9752..008d925dcefc659662cbfa15bd507ce773b8fc56 100644 +--- a/dist/tsconfig.build.tsbuildinfo ++++ b/dist/tsconfig.build.tsbuildinfo +@@ -1 +1 @@ +-{"program":{"fileNames":["../../../node_modules/typescript/lib/lib.es5.d.ts","../../../node_modules/typescript/lib/lib.es2015.d.ts","../../../node_modules/typescript/lib/lib.es2016.d.ts","../../../node_modules/typescript/lib/lib.es2017.d.ts","../../../node_modules/typescript/lib/lib.es2018.d.ts","../../../node_modules/typescript/lib/lib.es2019.d.ts","../../../node_modules/typescript/lib/lib.es2020.d.ts","../../../node_modules/typescript/lib/lib.dom.d.ts","../../../node_modules/typescript/lib/lib.es2015.core.d.ts","../../../node_modules/typescript/lib/lib.es2015.collection.d.ts","../../../node_modules/typescript/lib/lib.es2015.generator.d.ts","../../../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../../node_modules/typescript/lib/lib.es2015.promise.d.ts","../../../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../../node_modules/typescript/lib/lib.es2017.object.d.ts","../../../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../../node_modules/typescript/lib/lib.es2017.string.d.ts","../../../node_modules/typescript/lib/lib.es2017.intl.d.ts","../../../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../../node_modules/typescript/lib/lib.es2018.intl.d.ts","../../../node_modules/typescript/lib/lib.es2018.promise.d.ts","../../../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../../node_modules/typescript/lib/lib.es2019.array.d.ts","../../../node_modules/typescript/lib/lib.es2019.object.d.ts","../../../node_modules/typescript/lib/lib.es2019.string.d.ts","../../../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../../node_modules/typescript/lib/lib.es2019.intl.d.ts","../../../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../../node_modules/typescript/lib/lib.es2020.date.d.ts","../../../node_modules/typescript/lib/lib.es2020.promise.d.ts","../../../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../../node_modules/typescript/lib/lib.es2020.string.d.ts","../../../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../../node_modules/typescript/lib/lib.es2020.intl.d.ts","../../../node_modules/typescript/lib/lib.es2020.number.d.ts","../../../node_modules/typescript/lib/lib.esnext.intl.d.ts","../../../types/eth-ens-namehash.d.ts","../../../types/ethereum-ens-network-map.d.ts","../../../types/global.d.ts","../../../types/single-call-balance-checker-abi.d.ts","../../../types/@metamask/contract-metadata.d.ts","../../../types/@metamask/eth-hd-keyring.d.ts","../../../types/@metamask/eth-simple-keyring.d.ts","../../../types/@metamask/ethjs-provider-http.d.ts","../../../types/@metamask/ethjs-unit.d.ts","../../../types/@metamask/metamask-eth-abis.d.ts","../../../types/eth-json-rpc-infura/src/createProvider.d.ts","../../../types/eth-phishing-detect/src/config.json.d.ts","../../../types/eth-phishing-detect/src/detector.d.ts","../../base-controller/dist/types/BaseControllerV1.d.ts","../../../node_modules/superstruct/dist/error.d.ts","../../../node_modules/superstruct/dist/utils.d.ts","../../../node_modules/superstruct/dist/struct.d.ts","../../../node_modules/superstruct/dist/structs/coercions.d.ts","../../../node_modules/superstruct/dist/structs/refinements.d.ts","../../../node_modules/superstruct/dist/structs/types.d.ts","../../../node_modules/superstruct/dist/structs/utilities.d.ts","../../../node_modules/superstruct/dist/index.d.ts","../../../node_modules/@metamask/utils/dist/types/assert.d.ts","../../../node_modules/@metamask/utils/dist/types/base64.d.ts","../../../node_modules/@metamask/utils/dist/types/hex.d.ts","../../../node_modules/@metamask/utils/dist/types/bytes.d.ts","../../../node_modules/@metamask/utils/dist/types/caip-types.d.ts","../../../node_modules/@metamask/utils/dist/types/checksum.d.ts","../../../node_modules/@metamask/utils/dist/types/coercers.d.ts","../../../node_modules/@metamask/utils/dist/types/collections.d.ts","../../../node_modules/@metamask/utils/dist/types/encryption-types.d.ts","../../../node_modules/@metamask/utils/dist/types/errors.d.ts","../../../node_modules/@metamask/utils/dist/types/json.d.ts","../../../node_modules/@types/node/assert.d.ts","../../../node_modules/@types/node/assert/strict.d.ts","../../../node_modules/@types/node/globals.d.ts","../../../node_modules/@types/node/async_hooks.d.ts","../../../node_modules/@types/node/buffer.d.ts","../../../node_modules/@types/node/child_process.d.ts","../../../node_modules/@types/node/cluster.d.ts","../../../node_modules/@types/node/console.d.ts","../../../node_modules/@types/node/constants.d.ts","../../../node_modules/@types/node/crypto.d.ts","../../../node_modules/@types/node/dgram.d.ts","../../../node_modules/@types/node/diagnostics_channel.d.ts","../../../node_modules/@types/node/dns.d.ts","../../../node_modules/@types/node/dns/promises.d.ts","../../../node_modules/@types/node/dom-events.d.ts","../../../node_modules/@types/node/domain.d.ts","../../../node_modules/@types/node/events.d.ts","../../../node_modules/@types/node/fs.d.ts","../../../node_modules/@types/node/fs/promises.d.ts","../../../node_modules/@types/node/http.d.ts","../../../node_modules/@types/node/http2.d.ts","../../../node_modules/@types/node/https.d.ts","../../../node_modules/@types/node/inspector.d.ts","../../../node_modules/@types/node/module.d.ts","../../../node_modules/@types/node/net.d.ts","../../../node_modules/@types/node/os.d.ts","../../../node_modules/@types/node/path.d.ts","../../../node_modules/@types/node/perf_hooks.d.ts","../../../node_modules/@types/node/process.d.ts","../../../node_modules/@types/node/punycode.d.ts","../../../node_modules/@types/node/querystring.d.ts","../../../node_modules/@types/node/readline.d.ts","../../../node_modules/@types/node/repl.d.ts","../../../node_modules/@types/node/stream.d.ts","../../../node_modules/@types/node/stream/promises.d.ts","../../../node_modules/@types/node/stream/consumers.d.ts","../../../node_modules/@types/node/stream/web.d.ts","../../../node_modules/@types/node/string_decoder.d.ts","../../../node_modules/@types/node/test.d.ts","../../../node_modules/@types/node/timers.d.ts","../../../node_modules/@types/node/timers/promises.d.ts","../../../node_modules/@types/node/tls.d.ts","../../../node_modules/@types/node/trace_events.d.ts","../../../node_modules/@types/node/tty.d.ts","../../../node_modules/@types/node/url.d.ts","../../../node_modules/@types/node/util.d.ts","../../../node_modules/@types/node/v8.d.ts","../../../node_modules/@types/node/vm.d.ts","../../../node_modules/@types/node/wasi.d.ts","../../../node_modules/@types/node/worker_threads.d.ts","../../../node_modules/@types/node/zlib.d.ts","../../../node_modules/@types/node/globals.global.d.ts","../../../node_modules/@types/node/index.d.ts","../../../node_modules/@ethereumjs/common/dist/enums.d.ts","../../../node_modules/@ethereumjs/common/dist/types.d.ts","../../../node_modules/buffer/index.d.ts","../../../node_modules/@ethereumjs/util/dist/constants.d.ts","../../../node_modules/@ethereumjs/util/dist/units.d.ts","../../../node_modules/@ethereumjs/util/dist/address.d.ts","../../../node_modules/@ethereumjs/util/dist/bytes.d.ts","../../../node_modules/@ethereumjs/util/dist/types.d.ts","../../../node_modules/@ethereumjs/util/dist/account.d.ts","../../../node_modules/@ethereumjs/util/dist/withdrawal.d.ts","../../../node_modules/@ethereumjs/util/dist/signature.d.ts","../../../node_modules/@ethereumjs/util/dist/encoding.d.ts","../../../node_modules/@ethereumjs/util/dist/asyncEventEmitter.d.ts","../../../node_modules/@ethereumjs/util/dist/internal.d.ts","../../../node_modules/@ethereumjs/util/dist/lock.d.ts","../../../node_modules/@ethereumjs/util/dist/provider.d.ts","../../../node_modules/@ethereumjs/util/dist/index.d.ts","../../../node_modules/@ethereumjs/common/dist/common.d.ts","../../../node_modules/@ethereumjs/common/dist/utils.d.ts","../../../node_modules/@ethereumjs/common/dist/index.d.ts","../../../node_modules/@ethereumjs/tx/dist/eip2930Transaction.d.ts","../../../node_modules/@ethereumjs/tx/dist/legacyTransaction.d.ts","../../../node_modules/@ethereumjs/tx/dist/types.d.ts","../../../node_modules/@ethereumjs/tx/dist/baseTransaction.d.ts","../../../node_modules/@ethereumjs/tx/dist/eip1559Transaction.d.ts","../../../node_modules/@ethereumjs/tx/dist/transactionFactory.d.ts","../../../node_modules/@ethereumjs/tx/dist/index.d.ts","../../../node_modules/@metamask/utils/dist/types/keyring.d.ts","../../../node_modules/@types/ms/index.d.ts","../../../node_modules/@types/debug/index.d.ts","../../../node_modules/@metamask/utils/dist/types/logging.d.ts","../../../node_modules/@metamask/utils/dist/types/misc.d.ts","../../../node_modules/@metamask/utils/dist/types/number.d.ts","../../../node_modules/@metamask/utils/dist/types/opaque.d.ts","../../../node_modules/@metamask/utils/dist/types/promise.d.ts","../../../node_modules/@metamask/utils/dist/types/time.d.ts","../../../node_modules/@metamask/utils/dist/types/transaction-types.d.ts","../../../node_modules/@metamask/utils/dist/types/versions.d.ts","../../../node_modules/@metamask/utils/dist/types/index.d.ts","../../../node_modules/immer/dist/utils/env.d.ts","../../../node_modules/immer/dist/utils/errors.d.ts","../../../node_modules/immer/dist/types/types-external.d.ts","../../../node_modules/immer/dist/types/types-internal.d.ts","../../../node_modules/immer/dist/utils/common.d.ts","../../../node_modules/immer/dist/utils/plugins.d.ts","../../../node_modules/immer/dist/core/scope.d.ts","../../../node_modules/immer/dist/core/finalize.d.ts","../../../node_modules/immer/dist/core/proxy.d.ts","../../../node_modules/immer/dist/core/immerClass.d.ts","../../../node_modules/immer/dist/core/current.d.ts","../../../node_modules/immer/dist/internal.d.ts","../../../node_modules/immer/dist/plugins/es5.d.ts","../../../node_modules/immer/dist/plugins/patches.d.ts","../../../node_modules/immer/dist/plugins/mapset.d.ts","../../../node_modules/immer/dist/plugins/all.d.ts","../../../node_modules/immer/dist/immer.d.ts","../../base-controller/dist/types/RestrictedControllerMessenger.d.ts","../../base-controller/dist/types/ControllerMessenger.d.ts","../../base-controller/dist/types/BaseControllerV2.d.ts","../../base-controller/dist/types/index.d.ts","../../controller-utils/dist/types/types.d.ts","../../controller-utils/dist/types/constants.d.ts","../../../node_modules/@metamask/eth-query/index.d.ts","../../../node_modules/@types/bn.js/index.d.ts","../../controller-utils/dist/types/util.d.ts","../../../node_modules/@spruceid/siwe-parser/dist/abnf.d.ts","../../../node_modules/@spruceid/siwe-parser/dist/utils.d.ts","../../../node_modules/@spruceid/siwe-parser/dist/parsers.d.ts","../../controller-utils/dist/types/siwe.d.ts","../../controller-utils/dist/types/index.d.ts","../../../node_modules/@metamask/swappable-obj-proxy/dist/types.d.ts","../../../node_modules/@metamask/swappable-obj-proxy/dist/createEventEmitterProxy.d.ts","../../../node_modules/@metamask/swappable-obj-proxy/dist/createSwappableProxy.d.ts","../../../node_modules/@metamask/swappable-obj-proxy/dist/index.d.ts","../../network-controller/dist/types/constants.d.ts","../../../node_modules/@metamask/safe-event-emitter/dist/cjs/index.d.ts","../../json-rpc-engine/dist/types/JsonRpcEngine.d.ts","../../json-rpc-engine/dist/types/createAsyncMiddleware.d.ts","../../json-rpc-engine/dist/types/createScaffoldMiddleware.d.ts","../../json-rpc-engine/dist/types/getUniqueId.d.ts","../../json-rpc-engine/dist/types/idRemapMiddleware.d.ts","../../json-rpc-engine/dist/types/mergeMiddleware.d.ts","../../json-rpc-engine/dist/types/index.d.ts","../../eth-json-rpc-provider/dist/types/safe-event-emitter-provider.d.ts","../../eth-json-rpc-provider/dist/types/provider-from-engine.d.ts","../../eth-json-rpc-provider/dist/types/provider-from-middleware.d.ts","../../eth-json-rpc-provider/dist/types/index.d.ts","../../../node_modules/eth-block-tracker/dist/BlockTracker.d.ts","../../../node_modules/eth-block-tracker/dist/PollingBlockTracker.d.ts","../../../node_modules/eth-block-tracker/dist/SubscribeBlockTracker.d.ts","../../../node_modules/eth-block-tracker/dist/index.d.ts","../../network-controller/dist/types/types.d.ts","../../network-controller/dist/types/create-auto-managed-network-client.d.ts","../../network-controller/dist/types/NetworkController.d.ts","../../network-controller/dist/types/create-network-client.d.ts","../../network-controller/dist/types/index.d.ts","../../polling-controller/dist/types/types.d.ts","../../polling-controller/dist/types/BlockTrackerPollingController.d.ts","../../polling-controller/dist/types/StaticIntervalPollingController.d.ts","../../polling-controller/dist/types/index.d.ts","../../../node_modules/@types/uuid/index.d.ts","../src/determineGasFeeCalculations.ts","../src/gas-util.ts","../src/GasFeeController.ts","../src/index.ts","../../../node_modules/@babel/types/lib/index.d.ts","../../../node_modules/@types/babel__generator/index.d.ts","../../../node_modules/@babel/parser/typings/babel-parser.d.ts","../../../node_modules/@types/babel__template/index.d.ts","../../../node_modules/@types/babel__traverse/index.d.ts","../../../node_modules/@types/babel__core/index.d.ts","../../../node_modules/@types/deep-freeze-strict/index.d.ts","../../../node_modules/@types/eslint/helpers.d.ts","../../../node_modules/@types/estree/index.d.ts","../../../node_modules/@types/json-schema/index.d.ts","../../../node_modules/@types/eslint/index.d.ts","../../../node_modules/@types/graceful-fs/index.d.ts","../../../node_modules/@types/istanbul-lib-coverage/index.d.ts","../../../node_modules/@types/istanbul-lib-report/index.d.ts","../../../node_modules/@types/istanbul-reports/index.d.ts","../../../node_modules/chalk/index.d.ts","../../../node_modules/jest-diff/build/cleanupSemantic.d.ts","../../../node_modules/pretty-format/build/types.d.ts","../../../node_modules/pretty-format/build/index.d.ts","../../../node_modules/jest-diff/build/types.d.ts","../../../node_modules/jest-diff/build/diffLines.d.ts","../../../node_modules/jest-diff/build/printDiffs.d.ts","../../../node_modules/jest-diff/build/index.d.ts","../../../node_modules/jest-matcher-utils/build/index.d.ts","../../../node_modules/@types/jest/index.d.ts","../../../node_modules/@types/jest-when/index.d.ts","../../../node_modules/@types/json5/index.d.ts","../../../node_modules/@types/lodash/common/common.d.ts","../../../node_modules/@types/lodash/common/array.d.ts","../../../node_modules/@types/lodash/common/collection.d.ts","../../../node_modules/@types/lodash/common/date.d.ts","../../../node_modules/@types/lodash/common/function.d.ts","../../../node_modules/@types/lodash/common/lang.d.ts","../../../node_modules/@types/lodash/common/math.d.ts","../../../node_modules/@types/lodash/common/number.d.ts","../../../node_modules/@types/lodash/common/object.d.ts","../../../node_modules/@types/lodash/common/seq.d.ts","../../../node_modules/@types/lodash/common/string.d.ts","../../../node_modules/@types/lodash/common/util.d.ts","../../../node_modules/@types/lodash/index.d.ts","../../../node_modules/@types/minimatch/index.d.ts","../../../node_modules/@types/parse-json/index.d.ts","../../../node_modules/@types/pbkdf2/index.d.ts","../../../node_modules/@types/prettier/index.d.ts","../../../node_modules/@types/punycode/index.d.ts","../../../node_modules/@types/readable-stream/node_modules/safe-buffer/index.d.ts","../../../node_modules/@types/readable-stream/index.d.ts","../../../node_modules/@types/secp256k1/index.d.ts","../../../node_modules/@types/semver/classes/semver.d.ts","../../../node_modules/@types/semver/functions/parse.d.ts","../../../node_modules/@types/semver/functions/valid.d.ts","../../../node_modules/@types/semver/functions/clean.d.ts","../../../node_modules/@types/semver/functions/inc.d.ts","../../../node_modules/@types/semver/functions/diff.d.ts","../../../node_modules/@types/semver/functions/major.d.ts","../../../node_modules/@types/semver/functions/minor.d.ts","../../../node_modules/@types/semver/functions/patch.d.ts","../../../node_modules/@types/semver/functions/prerelease.d.ts","../../../node_modules/@types/semver/functions/compare.d.ts","../../../node_modules/@types/semver/functions/rcompare.d.ts","../../../node_modules/@types/semver/functions/compare-loose.d.ts","../../../node_modules/@types/semver/functions/compare-build.d.ts","../../../node_modules/@types/semver/functions/sort.d.ts","../../../node_modules/@types/semver/functions/rsort.d.ts","../../../node_modules/@types/semver/functions/gt.d.ts","../../../node_modules/@types/semver/functions/lt.d.ts","../../../node_modules/@types/semver/functions/eq.d.ts","../../../node_modules/@types/semver/functions/neq.d.ts","../../../node_modules/@types/semver/functions/gte.d.ts","../../../node_modules/@types/semver/functions/lte.d.ts","../../../node_modules/@types/semver/functions/cmp.d.ts","../../../node_modules/@types/semver/functions/coerce.d.ts","../../../node_modules/@types/semver/classes/comparator.d.ts","../../../node_modules/@types/semver/classes/range.d.ts","../../../node_modules/@types/semver/functions/satisfies.d.ts","../../../node_modules/@types/semver/ranges/max-satisfying.d.ts","../../../node_modules/@types/semver/ranges/min-satisfying.d.ts","../../../node_modules/@types/semver/ranges/to-comparators.d.ts","../../../node_modules/@types/semver/ranges/min-version.d.ts","../../../node_modules/@types/semver/ranges/valid.d.ts","../../../node_modules/@types/semver/ranges/outside.d.ts","../../../node_modules/@types/semver/ranges/gtr.d.ts","../../../node_modules/@types/semver/ranges/ltr.d.ts","../../../node_modules/@types/semver/ranges/intersects.d.ts","../../../node_modules/@types/semver/ranges/simplify.d.ts","../../../node_modules/@types/semver/ranges/subset.d.ts","../../../node_modules/@types/semver/internals/identifiers.d.ts","../../../node_modules/@types/semver/index.d.ts","../../../node_modules/@types/sinonjs__fake-timers/index.d.ts","../../../node_modules/@types/sinon/index.d.ts","../../../node_modules/@types/stack-utils/index.d.ts","../../../node_modules/@types/yargs-parser/index.d.ts","../../../node_modules/@types/yargs/index.d.ts"],"fileInfos":[{"version":"8730f4bf322026ff5229336391a18bcaa1f94d4f82416c8b2f3954e2ccaae2ba","affectsGlobalScope":true},"dc47c4fa66b9b9890cf076304de2a9c5201e94b740cffdf09f87296d877d71f6","7a387c58583dfca701b6c85e0adaf43fb17d590fb16d5b2dc0a2fbd89f35c467","8a12173c586e95f4433e0c6dc446bc88346be73ffe9ca6eec7aa63c8f3dca7f9","5f4e733ced4e129482ae2186aae29fde948ab7182844c3a5a51dd346182c7b06","4b421cbfb3a38a27c279dec1e9112c3d1da296f77a1a85ddadf7e7a425d45d18","1fc5ab7a764205c68fa10d381b08417795fc73111d6dd16b5b1ed36badb743d9",{"version":"3aafcb693fe5b5c3bd277bd4c3a617b53db474fe498fc5df067c5603b1eebde7","affectsGlobalScope":true},{"version":"adb996790133eb33b33aadb9c09f15c2c575e71fb57a62de8bf74dbf59ec7dfb","affectsGlobalScope":true},{"version":"8cc8c5a3bac513368b0157f3d8b31cfdcfe78b56d3724f30f80ed9715e404af8","affectsGlobalScope":true},{"version":"cdccba9a388c2ee3fd6ad4018c640a471a6c060e96f1232062223063b0a5ac6a","affectsGlobalScope":true},{"version":"c5c05907c02476e4bde6b7e76a79ffcd948aedd14b6a8f56e4674221b0417398","affectsGlobalScope":true},{"version":"5f406584aef28a331c36523df688ca3650288d14f39c5d2e555c95f0d2ff8f6f","affectsGlobalScope":true},{"version":"22f230e544b35349cfb3bd9110b6ef37b41c6d6c43c3314a31bd0d9652fcec72","affectsGlobalScope":true},{"version":"7ea0b55f6b315cf9ac2ad622b0a7813315bb6e97bf4bb3fbf8f8affbca7dc695","affectsGlobalScope":true},{"version":"3013574108c36fd3aaca79764002b3717da09725a36a6fc02eac386593110f93","affectsGlobalScope":true},{"version":"eb26de841c52236d8222f87e9e6a235332e0788af8c87a71e9e210314300410a","affectsGlobalScope":true},{"version":"3be5a1453daa63e031d266bf342f3943603873d890ab8b9ada95e22389389006","affectsGlobalScope":true},{"version":"17bb1fc99591b00515502d264fa55dc8370c45c5298f4a5c2083557dccba5a2a","affectsGlobalScope":true},{"version":"7ce9f0bde3307ca1f944119f6365f2d776d281a393b576a18a2f2893a2d75c98","affectsGlobalScope":true},{"version":"6a6b173e739a6a99629a8594bfb294cc7329bfb7b227f12e1f7c11bc163b8577","affectsGlobalScope":true},{"version":"81cac4cbc92c0c839c70f8ffb94eb61e2d32dc1c3cf6d95844ca099463cf37ea","affectsGlobalScope":true},{"version":"b0124885ef82641903d232172577f2ceb5d3e60aed4da1153bab4221e1f6dd4e","affectsGlobalScope":true},{"version":"0eb85d6c590b0d577919a79e0084fa1744c1beba6fd0d4e951432fa1ede5510a","affectsGlobalScope":true},{"version":"da233fc1c8a377ba9e0bed690a73c290d843c2c3d23a7bd7ec5cd3d7d73ba1e0","affectsGlobalScope":true},{"version":"d154ea5bb7f7f9001ed9153e876b2d5b8f5c2bb9ec02b3ae0d239ec769f1f2ae","affectsGlobalScope":true},{"version":"bb2d3fb05a1d2ffbca947cc7cbc95d23e1d053d6595391bd325deb265a18d36c","affectsGlobalScope":true},{"version":"c80df75850fea5caa2afe43b9949338ce4e2de086f91713e9af1a06f973872b8","affectsGlobalScope":true},{"version":"9d57b2b5d15838ed094aa9ff1299eecef40b190722eb619bac4616657a05f951","affectsGlobalScope":true},{"version":"6c51b5dd26a2c31dbf37f00cfc32b2aa6a92e19c995aefb5b97a3a64f1ac99de","affectsGlobalScope":true},{"version":"6e7997ef61de3132e4d4b2250e75343f487903ddf5370e7ce33cf1b9db9a63ed","affectsGlobalScope":true},{"version":"2ad234885a4240522efccd77de6c7d99eecf9b4de0914adb9a35c0c22433f993","affectsGlobalScope":true},{"version":"5e5e095c4470c8bab227dbbc61374878ecead104c74ab9960d3adcccfee23205","affectsGlobalScope":true},{"version":"09aa50414b80c023553090e2f53827f007a301bc34b0495bfb2c3c08ab9ad1eb","affectsGlobalScope":true},{"version":"d7f680a43f8cd12a6b6122c07c54ba40952b0c8aa140dcfcf32eb9e6cb028596","affectsGlobalScope":true},{"version":"3787b83e297de7c315d55d4a7c546ae28e5f6c0a361b7a1dcec1f1f50a54ef11","affectsGlobalScope":true},{"version":"e7e8e1d368290e9295ef18ca23f405cf40d5456fa9f20db6373a61ca45f75f40","affectsGlobalScope":true},{"version":"faf0221ae0465363c842ce6aa8a0cbda5d9296940a8e26c86e04cc4081eea21e","affectsGlobalScope":true},{"version":"06393d13ea207a1bfe08ec8d7be562549c5e2da8983f2ee074e00002629d1871","affectsGlobalScope":true},{"version":"2768ef564cfc0689a1b76106c421a2909bdff0acbe87da010785adab80efdd5c","affectsGlobalScope":true},{"version":"b248e32ca52e8f5571390a4142558ae4f203ae2f94d5bac38a3084d529ef4e58","affectsGlobalScope":true},{"version":"52d1bb7ab7a3306fd0375c8bff560feed26ed676a5b0457fa8027b563aecb9a4","affectsGlobalScope":true},"70bbfaec021ac4a0c805374225b55d70887f987df8b8dd7711d79464bb7b4385","869089d60b67219f63e6aca810284c89bae1b384b5cbc7ce64e53d82ad223ed5",{"version":"18338b6a4b920ec7d49b4ffafcbf0fa8a86b4bfd432966efd722dab611157cf4","affectsGlobalScope":true},"62a0875a0397b35a2364f1d401c0ce17975dfa4d47bf6844de858ae04da349f9","ee7491d0318d1fafcba97d5b72b450eb52671570f7a4ecd9e8898d40eaae9472","e3e7d217d89b380c1f34395eadc9289542851b0f0a64007dfe1fb7cf7423d24e","fd79909e93b4d50fd0ed9f3d39ddf8ba0653290bac25c295aac49f6befbd081b","345a9cc2945406f53051cd0e9b51f82e1e53929848eab046fdda91ee8aa7da31","9debe2de883da37a914e5e784a7be54c201b8f1d783822ad6f443ff409a5ea21","dee5d5c5440cda1f3668f11809a5503c30db0476ad117dd450f7ba5a45300e8f","f5e396c1424c391078c866d6f84afe0b4d2f7f85a160b9c756cd63b5b1775d93","5caa6f4fff16066d377d4e254f6c34c16540da3809cd66cd626a303bc33c419f","730d055528bdf12c8524870bb33d237991be9084c57634e56e5d8075f6605e02","75b22c74010ba649de1a1676a4c4b8b5bb4294fecd05089e2094429b16d7840c","5615ccf831db2ffc82145243081ebdb60ea8e1005ee8f975d1c0c1401a9c894e","38682ed3630bb6ecdace80d5a9adc811fc20a419f1940446e306c3a020d083b9","cc182e6e4f691cd6f7bf7cb491247a4c7818f9f1cb2db1d45c65ff906e3f741b","a50599c08934a62f11657bdbe0dc929ab66da1b1f09974408fd9a33ec1bb8060","5a20e7d6c630b91be15e9b837853173829d00273197481dc8d3e94df61105a71","8d478048d71cc16f806d4b71b252ecb67c7444ccf4f4b09b29a312712184f859","e0eda929c6b9b628cdeb0e54cd3582cb97e64f28aab34612fc1431c545899584","9df4662ca3dbc2522bc115833ee04faa1afbb4e249a85ef4a0a09c621346bd08","b25d9065cf1c1f537a140bbc508e953ed2262f77134574c432d206ff36f4bdbf","1b103313097041aa9cd705a682c652f08613cb5cf8663321061c0902f845e81c","68ccec8662818911d8a12b8ed028bc5729fb4f1d34793c4701265ba60bc73cf4","5f85b8b79dc4d36af672c035b2beb71545de63a5d60bccbeee64c260941672ab","b3d48529ae61dc27d0bfbfa2cb3e0dff8189644bd155bdf5df1e8e14669f7043","40fe4b689225816b31fe5794c0fbf3534568819709e40295ead998a2bc1ab237","f65b5e33b9ad545a1eebbd6afe857314725ad42aaf069913e33f928ab3e4990a","fb6f2a87beb7fb1f4c2b762d0c76a9459fc91f557231569b0ee21399e22aa13d","31c858dc85996fac4b7fa944e1016d5c72f514930a72357ab5001097bf6511c7","3de30a871b3340be8b679c52aa12f90dd1c8c60874517be58968fdbcc4d79445","6fd985bd31eaf77542625306fb0404d32bff978990f0a06428e5f0b9a3b58109","5b3cd03ae354ea96eff1f74d7c410fe4852e6382227e8b0ecf87ab5e3a5bbcd4","7394959e5a741b185456e1ef5d64599c36c60a323207450991e7a42e08911419",{"version":"056097110efd16869ec118cedb44ecbac9a019576eee808d61304ca6d5cb2cbe","affectsGlobalScope":true},"f51b4042a3ac86f1f707500a9768f88d0b0c1fc3f3e45a73333283dea720cdc6",{"version":"6fb8358e10ed92a7f515b7d79da3904c955a3ffd4e14aa9df6f0ea113041f1cf","affectsGlobalScope":true},"45c831238c6dac21c72da5f335747736a56a3847192bf03c84b958a7e9ec93e2","661a11d16ad2e3543a77c53bcd4017ee9a450f47ab7def3ab493a86eae4d550c",{"version":"8cdc646cec7819581ef343b83855b1bfe4fe674f2c84f4fb8dc90d82fb56bd3a","affectsGlobalScope":true},"a40826e8476694e90da94aa008283a7de50d1dafd37beada623863f1901cb7fb","9dd56225cc2d8cb8fe5ceb0043ff386987637e12fecc6078896058a99deae284","2375ed4b439215aa3b6d0c6fd175c78a4384b30cb43cbadaecbf0a18954c98cb","7693b90b3075deaccafd5efb467bf9f2b747a3075be888652ef73e64396d8628","41231da15bb5e3e806a8395bd15c7befd2ec90f9f4e3c9d0ae1356bccb76dbb0","fccfef201d057cb407fa515311bd608549bab6c7b8adcf8f2df31f5d3b796478",{"version":"ee1ee365d88c4c6c0c0a5a5701d66ebc27ccd0bcfcfaa482c6e2e7fe7b98edf7","affectsGlobalScope":true},"5f20d20b7607174caf1a6da9141aeb9f2142159ae2410ca30c7a0fccd1d19c99",{"version":"464762c6213566d072f1ced5e8e9a954785ec5e53883b7397198abb5ef5b8f71","affectsGlobalScope":true},"6387920dc3e18927335b086deec75bf8e50f879a5e273d32ee7bb7a55ba50572","9bba37424094688c4663c177a1379b229f919b8912889a472f32fdc5f08ddb4d","29a4be13b3a30d3e66667b75c58ec61fb2df8fa0422534fdee3cfb30c5dbf450","83366d901beda79d6eb37aaaf6ca248dcd88946302b2a7d975590783be51e88e","bf268a0aea37ad4ae3b7a9b58559190b6fc01ea16a31e35cd05817a0a60f895a","43ec77c369473e92e2ecebf0554a0fdaa9c256644a6070f28228dfcceec77351",{"version":"d7dad6db394a3d9f7b49755e4b610fbf8ed6eb0c9810ae5f1a119f6b5d76de45","affectsGlobalScope":true},"95ed02bacb4502c985b69742ec82a4576d4ff4a6620ecc91593f611d502ae546","bf755525c4e6f85a970b98c4755d98e8aa1b6dbd83a5d8fcc57d3d497351b936","dd67d2b5e4e8a182a38de8e69fb736945eaa4588e0909c14e01a14bd3cc1fd1e",{"version":"28084e15b63e6211769db2fe646d8bc5c4c6776321e0deffe2d12eefd52cb6b9","affectsGlobalScope":true},{"version":"aed37dabf86c99d6c8508700576ecede86688397bc12523541858705a0c737c2","affectsGlobalScope":true},"cc6ef5733d4ea6d2e06310a32dffd2c16418b467c5033d49cecc4f3a25de7497","94768454c3348b6ebe48e45fbad8c92e2bb7af4a35243edbe2b90823d0bd7f9a","0be79b3ff0f16b6c2f9bc8c4cc7097ea417d8d67f8267f7e1eec8e32b548c2ff","1c61ffa3a71b77363b30d19832c269ef62fba787f5610cac7254728d3b69ab2e","84da3c28344e621fd1d591f2c09e9595292d2b70018da28a553268ac122597d4","269929a24b2816343a178008ac9ae9248304d92a8ba8e233055e0ed6dbe6ef71","6e191fea1db6e9e4fa828259cf489e820ec9170effff57fb081a2f3295db4722","aed943465fbce1efe49ee16b5ea409050f15cd8eaf116f6fadb64ef0772e7d95","70d08483a67bf7050dbedace398ef3fee9f436fcd60517c97c4c1e22e3c6f3e8","c40fdf7b2e18df49ce0568e37f0292c12807a0748be79e272745e7216bed2606",{"version":"e933de8143e1d12dd51d89b398760fd5a9081896be366dad88a922d0b29f3c69","affectsGlobalScope":true},"4e228e78c1e9b0a75c70588d59288f63a6258e8b1fe4a67b0c53fe03461421d9","b38d55d08708c2410a3039687db70b4a5bfa69fc4845617c313b5a10d9c5c637","205d50c24359ead003dc537b9b65d2a64208dfdffe368f403cf9e0357831db9e","1265fddcd0c68be9d2a3b29805d0280484c961264dd95e0b675f7bd91f777e78",{"version":"a05e2d784c9be7051c4ac87a407c66d2106e23490c18c038bbd0712bde7602fd","affectsGlobalScope":true},{"version":"df90b9d0e9980762da8daf8adf6ffa0c853e76bfd269c377be0d07a9ad87acd2","affectsGlobalScope":true},"cf434b5c04792f62d6f4bdd5e2c8673f36e638e910333c172614d5def9b17f98","1d65d4798df9c2df008884035c41d3e67731f29db5ecb64cd7378797c7c53a2f","0faee6b555890a1cb106e2adc5d3ffd89545b1da894d474e9d436596d654998f","c6c01ea1c42508edf11a36d13b70f6e35774f74355ba5d358354d4a77cc67ea1","867f95abf1df444aab146b19847391fc2f922a55f6a970a27ed8226766cee29f",{"version":"ab9b9a36e5284fd8d3bf2f7d5fcbc60052f25f27e4d20954782099282c60d23e","affectsGlobalScope":true},"b0297b09e607bec9698cac7cf55463d6731406efb1161ee4d448293b47397c84","175323e2a79a6076e0bada8a390d535a3ea817158bf1b1f46e31efca9028a0a2","7a10053aadc19335532a4d02756db4865974fd69bea5439ddcc5bfdf062d9476","4967529644e391115ca5592184d4b63980569adf60ee685f968fd59ab1557188","aed9e712a9b168345362e8f3a949f16c99ca1e05d21328f05735dfdbb24414ef","b04fe6922ed3db93afdbd49cdda8576aa75f744592fceea96fb0d5f32158c4f5","ed8d6c8de90fc2a4faaebc28e91f2469928738efd5208fb75ade0fa607e892b7","d7c52b198d680fe65b1a8d1b001f0173ffa2536ca2e7082431d726ce1f6714cd","c07f251e1c4e415a838e5498380b55cfea94f3513229de292d2aa85ae52fc3e9","0ed401424892d6bf294a5374efe512d6951b54a71e5dd0290c55b6d0d915f6f7","b945be6da6a3616ef3a250bfe223362b1c7c6872e775b0c4d82a1bf7a28ff902","beea49237dd7c7110fabf3c7509919c9cb9da841d847c53cac162dc3479e2f87","0f45f8a529c450d8f394106cc622bff79e44a1716e1ac9c3cc68b43f7ecf65ee","c624ce90b04c27ce4f318ba6330d39bde3d4e306f0f497ce78d4bda5ab8e22ca","9b8253aa5cb2c82d505f72afdbf96e83b15cc6b9a6f4fadbbbab46210d5f1977","86a8f52e4b1ac49155e889376bcfa8528a634c90c27fec65aa0e949f77b740c5","aab5dd41c1e2316cc0b42a7dd15684f8582d5a1d16c0516276a2a8a7d0fecd9c","59948226626ee210045296ba1fc6cb0fe748d1ff613204e08e7157ab6862dee7","ec3e54d8b713c170fdc8110a7e4a6a97513a7ab6b05ac9e1100cb064d2bb7349","43beb30ecb39a603fde4376554887310b0699f25f7f39c5c91e3147b51bb3a26","666b77d7f06f49da114b090a399abbfa66d5b6c01a3fd9dc4f063a52ace28507","31997714a93fbc570f52d47d6a8ebfb021a34a68ea9ba58bbb69cdec9565657e","6032e4262822160128e644de3fc4410bcd7517c2f137525fd2623d2bb23cb0d3","8bd5c9b1016629c144fd228983395b9dbf0676a576716bc3d316cab612c33cd5","2ed90bd3925b23aed8f859ffd0e885250be0424ca2b57e9866dabef152e1d6b7","93f6bd17d92dab9db7897e1430a5aeaa03bcf51623156213d8397710367a76ce","3f62b770a42e8c47c7008726f95aa383e69d97e85e680d237b99fcb0ee601dd8","5b84cfe78028c35c3bb89c042f18bf08d09da11e82d275c378ae4d07d8477e6c","980d21b0081cbf81774083b1e3a46f4bbdcd2b68858df0f66d7fad9c82bc34bc","68cc8d6fcc2f270d7108f02f3ebc59480a54615be3e09a47e14527f349e9d53e","3eb11dbf3489064a47a2e1cf9d261b1f100ef0b3b50ffca6c44dd99d6dd81ac1","b17f3bb7d8333479c7e45e5f3d876761b9bca58f97594eca3f6a944fd825e632","3c1f1236cce6d6e0c4e2c1b4371e6f72d7c14842ecd76a98ed0748ee5730c8f3","6d7f58d5ea72d7834946fd7104a734dc7d40661be8b2e1eaced1ddce3268ebaf","4c26222991e6c97d5a8f541d4f2c67585eda9e8b33cf9f52931b098045236e88","277983d414aa99d78655186c3ee1e1c38c302e336aff1d77b47fcdc39d8273fe","47383b45796d525a4039cd22d2840ac55a1ff03a43d027f7f867ba7314a9cf53","6548773b3abbc18de29176c2141f766d4e437e40596ee480447abf83575445ad","6ddd27af0436ce59dd4c1896e2bfdb2bdb2529847d078b83ce67a144dff05491","816264799aef3fd5a09a3b6c25217d5ec26a9dfc7465eac7d6073bcdc7d88f3f","4df0891b133884cd9ed752d31c7d0ec0a09234e9ed5394abffd3c660761598db","b603b62d3dcd31ef757dc7339b4fa8acdbca318b0fb9ac485f9a1351955615f9","e642bd47b75ad6b53cbf0dfd7ddfa0f120bd10193f0c58ec37d87b59bf604aca","be90b24d2ee6f875ce3aaa482e7c41a54278856b03d04212681c4032df62baf9","78f5ff400b3cb37e7b90eef1ff311253ed31c8cb66505e9828fad099bffde021","372c47090e1131305d163469a895ff2938f33fa73aad988df31cd31743f9efb6","71c67dc6987bdbd5599353f90009ff825dd7db0450ef9a0aee5bb0c574d18512","6f12403b5eca6ae7ca8e3efe3eeb9c683b06ce3e3844ccfd04098d83cd7e4957","282c535df88175d64d9df4550d2fd1176fd940c1c6822f1e7584003237f179d3","c3a4752cf103e4c6034d5bd449c8f9d5e7b352d22a5f8f9a41a8efb11646f9c2","11a9e38611ac3c77c74240c58b6bd64a0032128b29354e999650f1de1e034b1c","4ed103ca6fff9cb244f7c4b86d1eb28ce8069c32db720784329946731badb5bb","d738f282842970e058672663311c6875482ee36607c88b98ffb6604fba99cb2a","ec859cd8226aa623e41bbb47c249a55ee16dc1b8647359585244d57d3a5ed0c7","8891c6e959d253a66434ff5dc9ae46058fb3493e84b4ca39f710ef2d350656b1","c4463cf02535444dcbc3e67ecd29f1972490f74e49957d6fd4282a1013796ba6","0cb0a957ff02de0b25fd0f3f37130ca7f22d1e0dea256569c714c1f73c6791f8","2f5075dc512d51786b1ba3b1696565641dfaae3ac854f5f13d61fa12ef81a47e","ca3353cc82b1981f0d25d71d7432d583a6ef882ccdea82d65fbe49af37be51cb","50679a8e27aacf72f8c40bcab15d7ef5e83494089b4726b83eec4554344d5cdc","45351e0d51780b6f4088277a4457b9879506ee2720a887de232df0f1efcb33d8","5d697a4b315cc5bb3042ae869abffd10c3b0d7b182cda0e4c45d8819937e5796","563fa27fdaec8f195b84f71a7af0ef48d30d5cc830575db86da86a63a470c8e6","6ee58aa536dabb19b09bc036f1abe83feb51e13d63b23d30b2d0631a2de99b8f","8aceb205dcc6f814ad99635baf1e40b6e01d06d3fe27b72fd766c6d0b8c0c600","299567f84bfedd1468dca2755a829cb19e607a6811673788807dc8921e211bc9","795d9fb85aad92221504db74dd179b506bd189bba0c104426f7e7bb8a66ffee5","1311bc194e0a69fe61031e852c1c0b439e2a2a3d1d5e2d8ff795499b9f283459","4b7ce19369d7e7fae76720c2c6c7f671bf3fa0f7093edb864f1ac358ca7c456c","c972ef44deca1fa8fab465915ffa00f82e126aacf3dfc8979c03b1b066ce5bb6","30285a1011c6d6b52f3ba3abb0a984be8148c05cdefb8eb6eb562335a3991f35","8e7adb22c0adecf7464861fc58ae3fc617b41ffbd70c97aa8493dc0966a82273","755f3cd1d9c1b564cff090e3b0e29200ae55690a91b87cb9e7a64c2dbeb314d3","d6bb7e0a6877b7856c183bff13d09dd9ae599ea43c6f6b33d3d5f72a830ed460","f1b51ae93c762d7c43f559933cd4842dd870367e8d92e90704ffa685dd5b29a3","3f450762fd7c34ed545e738abccb0af6a703572a10521643cf8fc88e3724c99c","fcc8beef29f39f09b1d9c9f99c42f9fed605ab1c28d2a630185f732b9ba53763","d6e6620a30d582182acc3f0a992a0c311adc589f111096aea11ab83fc09a5ccc","6213b8f686f56beab22b59a0f468590fd3a4c5fa931236a017efeca91d7c9584","c451cec9a588b1f105a5ea2c6063d4fca112b9d70105cacdadda0e1ef67e9379","cb047832dc68f5a2c41c62c5e95ddcacbae3a8b034d40cd15319a8cb7f25104a","980336ccdfc3c08f3c3b201aa6662e6016e20f15847f8465b68f3e8e67b4665c","5a3493939995f46ff3d9073cd534fb8961c3bf4e08c71db27066ff03d906dea8","bb5a2ac327605ebebf831c469b05bd34a33a6a46ee8c1edd9f3310aad32cf6a1","bf5d041f2440b4a9391e2b5eb3b8d94cbf1e3b8ff4703b6539d4e65e758c8f37","8516469eb90e723b0eb03df1be098f7e6a4709f6f48fd4532868d20a0a934f6e","d60e9ab369a72d234aac49adbe2900d8ef1408a6ea4db552cf2a48c9d8d6a1bc","0ebb4698803f01e2e7df6acce572fff068f4a20c47221721dafd70a27e372831","03460a54d0e0481d1e11097f66ad43f054bc95efdafe5f81bbc7a82be181af75","4070c2f1c3434fcf84886e04d30d82cd650ee443e53b82b404b144175cf8741e","2cea9689efa8591732096235abe7f084fc29c92badd5b0897a5e876b77e71887","4ed4e504126014fee13aaef5e3fc140f2ff7031ff3a8b5386717905820ea2d09","8129a34006218a6f3cdc81bbd438d5429eb18b08b4338a26977ac3b4df129d75","30d2170e1a718b5035611af55e3618b4ba8f42f0749bb52ee593da6082c4e2ce","98ef38666d88ec9699a722053e07ede65d3042f693fe7ff8c786e53dbb6fd43b","a3b8b6be7620897d1e481e8650c980a210a138fceb6e710eaf95fd9dd0dfe94a","12c89d0e32758c120a569045f21cf5b77244f86792611ced8de7f86b37e77781","14bd47270e654c8eb3b1489fa8c095912ee62a0a29bb92743393203722347c53","3d9297165e67fd59d9821cc93a9808213e33c56a8ac1c4273171f6afaaa2d4d5","e7af7d288b89287ad031b19583c597fcd9f5edc0b0d579b7b492f06cf57e058c","92cb686a9ca5eb5dd7d5d8d43a3707194c1e91ea07a027b3bcb60b6011b24632","fab58e600970e66547644a44bc9918e3223aa2cbd9e8763cec004b2cfb48827e",{"version":"f3e418819a6765dc6715ab2a51771c798543815d8499e29a857229bbf74ff419","signature":"b146c2211fb74ff8f672aa026d3992562e1c9c0d282cc712708b740c8f4cfb1a"},{"version":"d320adcc9e016b24e5873a7efb081d684a7990fbe23fe49c1c5354ba89bd129b","signature":"56c727d4c937865b6e7b114f58aa30f7678d5a9d924c0adb42368c403efb337d"},{"version":"fd1051fabcbc5740a5b3db8582c0bfee17883217121619fd765fbdd171f5d068","signature":"e4a433dbb39db571df95fa9677c8dede9cc6e21eb91a5a23e89ce2725cbd97da"},"d021f18758b28bda32bdaf0a987e0804cec074a9a4cfab8232ed81d96e75dfae","4489c6a9fde8934733aa7df6f7911461ee6e9e4ad092736bd416f6b2cc20b2c6","2c8e55457aaf4902941dfdba4061935922e8ee6e120539c9801cd7b400fae050","8041cfce439ff29d339742389de04c136e3029d6b1817f07b2d7fcbfb7534990","670a76db379b27c8ff42f1ba927828a22862e2ab0b0908e38b671f0e912cc5ed","9d38964b57191567a14b396422c87488cecd48f405c642daa734159875ee81d9","069bebfee29864e3955378107e243508b163e77ab10de6a5ee03ae06939f0bb9","8c95f96ccd4be0674944077aec1e4f2cccd515ca06d4327562dd017250e7d3fc",{"version":"64d4b35c5456adf258d2cf56c341e203a073253f229ef3208fc0d5020253b241","affectsGlobalScope":true},"ee7d8894904b465b072be0d2e4b45cf6b887cdba16a467645c4e200982ece7ea","f3d8c757e148ad968f0d98697987db363070abada5f503da3c06aefd9d4248c1","bc3cba7b0af2d52e7425299aee518db479d44004eff6fbbd206d1ee7e5ec3fb5","afe73051ff6a03a9565cbd8ebb0e956ee3df5e913ad5c1ded64218aabfa3dcb5","035a5df183489c2e22f3cf59fc1ed2b043d27f357eecc0eb8d8e840059d44245","a4809f4d92317535e6b22b01019437030077a76fec1d93b9881c9ed4738fcc54","5f53fa0bd22096d2a78533f94e02c899143b8f0f9891a46965294ee8b91a9434","0d14fa22c41fdc7277e6f71473b20ebc07f40f00e38875142335d5b63cdfc9d2","d8aab31ba8e618cc3eea10b0945de81cb93b7e8150a013a482332263b9305322","462bccdf75fcafc1ae8c30400c9425e1a4681db5d605d1a0edb4f990a54d8094","5923d8facbac6ecf7c84739a5c701a57af94a6f6648d6229a6c768cf28f0f8cb","7adecb2c3238794c378d336a8182d4c3dd2c4fa6fa1785e2797a3db550edea62","dc12dc0e5aa06f4e1a7692149b78f89116af823b9e1f1e4eae140cd3e0e674e6","1bfc6565b90c8771615cd8cfcf9b36efc0275e5e83ac7d9181307e96eb495161","8a8a96898906f065f296665e411f51010b51372fa260d5373bf9f64356703190","7f82ef88bdb67d9a850dd1c7cd2d690f33e0f0acd208e3c9eba086f3670d4f73",{"version":"ccfd8774cd9b929f63ff7dcf657977eb0652e3547f1fcac1b3a1dc5db22d4d58","affectsGlobalScope":true},"d92dc90fecd2552db74d8dc3c6fb4db9145b2aa0efe2c127236ba035969068d4","96d14f21b7652903852eef49379d04dbda28c16ed36468f8c9fa08f7c14c9538","b8442e9db28157344d1bc5d8a5a256f1692de213f0c0ddeb84359834015a008c","458111fc89d11d2151277c822dfdc1a28fa5b6b2493cf942e37d4cd0a6ee5f22","da2b6356b84a40111aaecb18304ea4e4fcb43d70efb1c13ca7d7a906445ee0d3","187119ff4f9553676a884e296089e131e8cc01691c546273b1d0089c3533ce42","febf0b2de54781102b00f61653b21377390a048fbf5262718c91860d11ff34a6","6f294731b495c65ecf46a5694f0082954b961cf05463bea823f8014098eaffa0","0aaef8cded245bf5036a7a40b65622dd6c4da71f7a35343112edbe112b348a1e","00baffbe8a2f2e4875367479489b5d43b5fc1429ecb4a4cc98cfc3009095f52a","68a0d0c508e1b6d8d23a519a8a0a3303dc5baa4849ca049f21e5bad41945e3fc","3c92b6dfd43cc1c2485d9eba5ff0b74a19bb8725b692773ef1d66dac48cda4bd","b03afe4bec768ae333582915146f48b161e567a81b5ebc31c4d78af089770ac9","df996e25faa505f85aeb294d15ebe61b399cf1d1e49959cdfaf2cc0815c203f9","4f6a12044ee6f458db11964153830abbc499e73d065c51c329ec97407f4b13dd","8841e2aa774b89bd23302dede20663306dc1b9902431ac64b24be8b8d0e3f649","916be7d770b0ae0406be9486ac12eb9825f21514961dd050594c4b250617d5a8","254d9fb8c872d73d34594be8a200fd7311dbfa10a4116bfc465fba408052f2b3","d88a5e779faf033be3d52142a04fbe1cb96009868e3bbdd296b2bc6c59e06c0e","2ccea88888048bbfcacbc9531a5596ea48a3e7dcd0a25f531a81bb717903ba4f","5e379df3d61561c2ed7789b5995b9ba2143bbba21a905e2381e16efe7d1fa424","f07a137bbe2de7a122c37bfea00e761975fb264c49f18003d398d71b3fb35a5f","d8f7109e14f20eb735225a62fd3f8366da1a8349e90331cdad57f4b04caf6c5a","cf3d384d082b933d987c4e2fe7bfb8710adfd9dc8155190056ed6695a25a559e","9871b7ee672bc16c78833bdab3052615834b08375cb144e4d2cba74473f4a589","c863198dae89420f3c552b5a03da6ed6d0acfa3807a64772b895db624b0de707","8b03a5e327d7db67112ebbc93b4f744133eda2c1743dbb0a990c61a8007823ef","86c73f2ee1752bac8eeeece234fd05dfcf0637a4fbd8032e4f5f43102faa8eec","42fad1f540271e35ca37cecda12c4ce2eef27f0f5cf0f8dd761d723c744d3159","ff3743a5de32bee10906aff63d1de726f6a7fd6ee2da4b8229054dfa69de2c34","83acd370f7f84f203e71ebba33ba61b7f1291ca027d7f9a662c6307d74e4ac22","1445cec898f90bdd18b2949b9590b3c012f5b7e1804e6e329fb0fe053946d5ec","0e5318ec2275d8da858b541920d9306650ae6ac8012f0e872fe66eb50321a669","cf530297c3fb3a92ec9591dd4fa229d58b5981e45fe6702a0bd2bea53a5e59be","c1f6f7d08d42148ddfe164d36d7aba91f467dbcb3caa715966ff95f55048b3a4","f4e9bf9103191ef3b3612d3ec0044ca4044ca5be27711fe648ada06fad4bcc85","0c1ee27b8f6a00097c2d6d91a21ee4d096ab52c1e28350f6362542b55380059a","7677d5b0db9e020d3017720f853ba18f415219fb3a9597343b1b1012cfd699f7","bc1c6bc119c1784b1a2be6d9c47addec0d83ef0d52c8fbe1f14a51b4dfffc675","52cf2ce99c2a23de70225e252e9822a22b4e0adb82643ab0b710858810e00bf1","770625067bb27a20b9826255a8d47b6b5b0a2d3dfcbd21f89904c731f671ba77","d1ed6765f4d7906a05968fb5cd6d1db8afa14dbe512a4884e8ea5c0f5e142c80","799c0f1b07c092626cf1efd71d459997635911bb5f7fc1196efe449bba87e965","2a184e4462b9914a30b1b5c41cf80c6d3428f17b20d3afb711fff3f0644001fd","9eabde32a3aa5d80de34af2c2206cdc3ee094c6504a8d0c2d6d20c7c179503cc","397c8051b6cfcb48aa22656f0faca2553c5f56187262135162ee79d2b2f6c966","a8ead142e0c87dcd5dc130eba1f8eeed506b08952d905c47621dc2f583b1bff9","a02f10ea5f73130efca046429254a4e3c06b5475baecc8f7b99a0014731be8b3","c2576a4083232b0e2d9bd06875dd43d371dee2e090325a9eac0133fd5650c1cb","4c9a0564bb317349de6a24eb4efea8bb79898fa72ad63a1809165f5bd42970dd","f40ac11d8859092d20f953aae14ba967282c3bb056431a37fced1866ec7a2681","cc11e9e79d4746cc59e0e17473a59d6f104692fd0eeea1bdb2e206eabed83b03","b444a410d34fb5e98aa5ee2b381362044f4884652e8bc8a11c8fe14bbd85518e","c35808c1f5e16d2c571aa65067e3cb95afeff843b259ecfa2fc107a9519b5392","14d5dc055143e941c8743c6a21fa459f961cbc3deedf1bfe47b11587ca4b3ef5","a3ad4e1fc542751005267d50a6298e6765928c0c3a8dce1572f2ba6ca518661c","f237e7c97a3a89f4591afd49ecb3bd8d14f51a1c4adc8fcae3430febedff5eb6","3ffdfbec93b7aed71082af62b8c3e0cc71261cc68d796665faa1e91604fbae8f","662201f943ed45b1ad600d03a90dffe20841e725203ced8b708c91fcd7f9379a","c9ef74c64ed051ea5b958621e7fb853fe3b56e8787c1587aefc6ea988b3c7e79","2462ccfac5f3375794b861abaa81da380f1bbd9401de59ffa43119a0b644253d","34baf65cfee92f110d6653322e2120c2d368ee64b3c7981dff08ed105c4f19b0","7d8ddf0f021c53099e34ee831a06c394d50371816caa98684812f089b4c6b3d4","7d2a0ba1297be385a89b5515b88cd31b4a1eeef5236f710166dc1b36b1741e1b","9d92b037978bb9525bc4b673ebddd443277542e010c0aef019c03a170ccdaa73","ab82804a14454734010dcdcd43f564ff7b0389bee4c5692eec76ff5b30d4cf66","bae8d023ef6b23df7da26f51cea44321f95817c190342a36882e93b80d07a960","ae271d475b632ce7b03fea6d9cf6da72439e57a109672671cbc79f54e1386938"],"options":{"composite":true,"declaration":true,"declarationMap":true,"emitDeclarationOnly":true,"esModuleInterop":true,"inlineSources":true,"module":1,"outDir":"./types","rootDir":"../src","sourceMap":true,"strict":true,"target":7},"fileIdsList":[[234],[92,128,129,130,145],[129,130,146,147],[128,129],[128,145,148,151],[128,148,151,152],[149,150,151,153,154],[128,151],[128,145,148,149,150,153],[128,136],[128],[92,128],[80,128],[132,133,134,135,136,137,138,139,140,141,142,143,144],[128,134,135],[128,134,136],[199],[199,200,201],[64],[67],[64,67],[65,66,67,68,69,70,71,72,73,74,75,156,159,160,161,162,163,164,165,166],[58,64,65],[67,73,75,155],[158],[67,68],[64,162],[194,195],[234,235,236,237,238],[234,236],[157],[241,242,243],[93,128],[246],[247],[258],[252,257],[261,263,264,265,266,267,268,269,270,271,272,273],[261,262,264,265,266,267,268,269,270,271,272,273],[262,263,264,265,266,267,268,269,270,271,272,273],[261,262,263,265,266,267,268,269,270,271,272,273],[261,262,263,264,266,267,268,269,270,271,272,273],[261,262,263,264,265,267,268,269,270,271,272,273],[261,262,263,264,265,266,268,269,270,271,272,273],[261,262,263,264,265,266,267,269,270,271,272,273],[261,262,263,264,265,266,267,268,270,271,272,273],[261,262,263,264,265,266,267,268,269,271,272,273],[261,262,263,264,265,266,267,268,269,270,272,273],[261,262,263,264,265,266,267,268,269,270,271,273],[261,262,263,264,265,266,267,268,269,270,271,272],[76],[79],[80,85,112],[81,92,93,100,109,120],[81,82,92,100],[83,121],[84,85,93,101],[85,109,117],[86,88,92,100],[87],[88,89],[92],[91,92],[79,92],[92,93,94,109,120],[92,93,94,109],[92,95,100,109,120],[92,93,95,96,100,109,117,120],[95,97,109,117,120],[76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127],[92,98],[99,120,125],[88,92,100,109],[101],[102],[79,103],[104,119,125],[105],[106],[92,107],[107,108,121,123],[80,92,109,110,111],[80,109,111],[109,110],[112],[113],[92,115,116],[115,116],[85,100,109,117],[118],[100,119],[80,95,106,120],[85,121],[109,122],[123],[124],[80,85,92,94,103,109,120,123,125],[109,126],[128,279],[282,321],[282,306,321],[321],[282],[282,307,321],[282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320],[307,321],[322],[325],[204],[204,215,216],[216,217,218],[179],[179,180,181,182,183],[168,169,170,171,172,173,174,175,176,177,178],[250,253],[250,253,254,255],[252],[249,256],[251],[57,59,60,61,62,63],[57,58],[59],[58,59],[57,59],[167,184,185,186],[185],[186],[56,185,186,187],[189],[189,190,193,197],[196],[167,191,192],[212,213,214],[211,212],[167,211,212],[167,204,211],[167,188,191,198,224,228,229,230,231],[232],[191,192,198,232],[167,204],[167,205],[205,206,207,208,209,210],[167,188,191,198,202,203,220,221],[220],[203,220,222,223],[167,198,215,219],[167,188,224,225],[128,167,188,224,225],[225,226,227],[167,224],[167,188,224,228],[191,232]],"referencedMap":[[236,1],[146,2],[148,3],[130,4],[152,5],[153,6],[149,6],[155,7],[150,6],[154,8],[151,9],[137,10],[134,11],[141,12],[135,10],[132,13],[145,14],[139,11],[136,15],[138,16],[204,12],[200,17],[201,17],[202,18],[65,19],[66,19],[68,20],[69,19],[70,19],[71,21],[67,19],[167,22],[75,23],[156,24],[159,25],[165,26],[166,27],[196,28],[239,29],[235,1],[237,30],[238,1],[192,11],[158,31],[244,32],[245,33],[247,34],[248,35],[259,36],[258,37],[262,38],[263,39],[261,40],[264,41],[265,42],[266,43],[267,44],[268,45],[269,46],[270,47],[271,48],[272,49],[273,50],[76,51],[77,51],[79,52],[80,53],[81,54],[82,55],[83,56],[84,57],[85,58],[86,59],[87,60],[88,61],[89,61],[90,62],[91,63],[92,64],[93,65],[94,66],[95,67],[96,68],[97,69],[128,70],[98,71],[99,72],[100,73],[101,74],[102,75],[103,76],[104,77],[105,78],[106,79],[107,80],[108,81],[109,82],[111,83],[110,84],[112,85],[113,86],[115,87],[116,88],[117,89],[118,90],[119,91],[120,92],[121,93],[122,94],[123,95],[124,96],[125,97],[126,98],[276,11],[280,99],[281,11],[306,100],[307,101],[282,102],[285,102],[304,100],[305,100],[295,100],[294,103],[292,100],[287,100],[300,100],[298,100],[302,100],[286,100],[299,100],[303,100],[288,100],[289,100],[301,100],[283,100],[290,100],[291,100],[293,100],[297,100],[308,104],[296,100],[284,100],[321,105],[315,104],[317,106],[316,104],[309,104],[310,104],[312,104],[314,104],[318,106],[319,106],[311,106],[313,106],[323,107],[326,108],[216,109],[217,110],[218,110],[219,111],[175,112],[177,112],[176,112],[174,112],[184,113],[179,114],[170,112],[171,112],[172,112],[173,112],[254,115],[256,116],[255,115],[253,117],[257,118],[252,119],[64,120],[59,121],[60,122],[61,122],[62,123],[63,123],[58,124],[187,125],[186,126],[185,127],[188,128],[190,129],[198,130],[197,131],[193,132],[215,133],[213,134],[214,135],[212,136],[232,137],[230,138],[231,139],[233,138],[205,140],[206,141],[207,141],[209,141],[211,142],[210,141],[222,143],[221,144],[223,144],[224,145],[220,146],[226,147],[227,148],[228,149],[225,150]],"exportedModulesMap":[[236,1],[146,2],[148,3],[130,4],[152,5],[153,6],[149,6],[155,7],[150,6],[154,8],[151,9],[137,10],[134,11],[141,12],[135,10],[132,13],[145,14],[139,11],[136,15],[138,16],[204,12],[200,17],[201,17],[202,18],[65,19],[66,19],[68,20],[69,19],[70,19],[71,21],[67,19],[167,22],[75,23],[156,24],[159,25],[165,26],[166,27],[196,28],[239,29],[235,1],[237,30],[238,1],[192,11],[158,31],[244,32],[245,33],[247,34],[248,35],[259,36],[258,37],[262,38],[263,39],[261,40],[264,41],[265,42],[266,43],[267,44],[268,45],[269,46],[270,47],[271,48],[272,49],[273,50],[76,51],[77,51],[79,52],[80,53],[81,54],[82,55],[83,56],[84,57],[85,58],[86,59],[87,60],[88,61],[89,61],[90,62],[91,63],[92,64],[93,65],[94,66],[95,67],[96,68],[97,69],[128,70],[98,71],[99,72],[100,73],[101,74],[102,75],[103,76],[104,77],[105,78],[106,79],[107,80],[108,81],[109,82],[111,83],[110,84],[112,85],[113,86],[115,87],[116,88],[117,89],[118,90],[119,91],[120,92],[121,93],[122,94],[123,95],[124,96],[125,97],[126,98],[276,11],[280,99],[281,11],[306,100],[307,101],[282,102],[285,102],[304,100],[305,100],[295,100],[294,103],[292,100],[287,100],[300,100],[298,100],[302,100],[286,100],[299,100],[303,100],[288,100],[289,100],[301,100],[283,100],[290,100],[291,100],[293,100],[297,100],[308,104],[296,100],[284,100],[321,105],[315,104],[317,106],[316,104],[309,104],[310,104],[312,104],[314,104],[318,106],[319,106],[311,106],[313,106],[323,107],[326,108],[216,109],[217,110],[218,110],[219,111],[175,112],[177,112],[176,112],[174,112],[184,113],[179,114],[170,112],[171,112],[172,112],[173,112],[254,115],[256,116],[255,115],[253,117],[257,118],[252,119],[64,120],[59,121],[60,122],[61,122],[62,123],[63,123],[58,124],[187,125],[186,126],[185,127],[188,128],[190,129],[198,130],[197,131],[193,132],[215,133],[213,134],[214,135],[212,136],[232,151],[230,138],[231,152],[233,138],[205,140],[206,141],[207,141],[209,141],[211,142],[210,141],[222,143],[221,144],[223,144],[224,145],[220,146],[226,147],[227,148],[228,149],[225,150]],"semanticDiagnosticsPerFile":[236,234,146,129,148,130,147,152,153,149,155,150,154,151,137,134,141,135,132,140,145,142,143,144,139,136,133,138,191,204,200,201,202,199,65,66,68,69,70,71,72,73,74,67,167,75,156,159,160,161,162,163,164,165,166,194,196,195,239,235,237,238,192,158,240,241,244,242,245,246,247,248,259,258,243,260,262,263,261,264,265,266,267,268,269,270,271,272,273,274,157,76,77,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,78,127,95,96,97,128,98,99,100,101,102,103,104,105,106,107,108,109,111,110,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,275,276,277,278,280,279,281,306,307,282,285,304,305,295,294,292,287,300,298,302,286,299,303,288,289,301,283,290,291,293,297,308,296,284,321,320,315,317,316,309,310,312,314,318,319,311,313,323,322,324,229,325,326,131,249,216,217,218,219,178,175,177,176,174,184,179,183,180,182,181,170,171,172,168,169,173,250,254,256,255,253,257,252,251,57,64,59,60,61,62,63,58,8,10,9,2,11,12,13,14,15,16,17,18,3,4,22,19,20,21,23,24,25,5,26,27,28,29,6,33,30,31,32,34,7,35,40,41,36,37,38,39,1,42,56,187,186,185,188,190,198,197,189,193,215,213,214,212,232,230,231,233,205,206,207,208,209,211,210,222,203,221,223,224,220,226,227,228,225,47,48,49,50,51,52,43,53,54,55,44,45,46],"latestChangedDtsFile":"./types/index.d.ts"},"version":"4.9.5"} +\ No newline at end of file ++{"program":{"fileNames":["../../../node_modules/typescript/lib/lib.es5.d.ts","../../../node_modules/typescript/lib/lib.es2015.d.ts","../../../node_modules/typescript/lib/lib.es2016.d.ts","../../../node_modules/typescript/lib/lib.es2017.d.ts","../../../node_modules/typescript/lib/lib.es2018.d.ts","../../../node_modules/typescript/lib/lib.es2019.d.ts","../../../node_modules/typescript/lib/lib.es2020.d.ts","../../../node_modules/typescript/lib/lib.dom.d.ts","../../../node_modules/typescript/lib/lib.es2015.core.d.ts","../../../node_modules/typescript/lib/lib.es2015.collection.d.ts","../../../node_modules/typescript/lib/lib.es2015.generator.d.ts","../../../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../../node_modules/typescript/lib/lib.es2015.promise.d.ts","../../../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../../node_modules/typescript/lib/lib.es2017.object.d.ts","../../../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../../node_modules/typescript/lib/lib.es2017.string.d.ts","../../../node_modules/typescript/lib/lib.es2017.intl.d.ts","../../../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../../node_modules/typescript/lib/lib.es2018.intl.d.ts","../../../node_modules/typescript/lib/lib.es2018.promise.d.ts","../../../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../../node_modules/typescript/lib/lib.es2019.array.d.ts","../../../node_modules/typescript/lib/lib.es2019.object.d.ts","../../../node_modules/typescript/lib/lib.es2019.string.d.ts","../../../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../../node_modules/typescript/lib/lib.es2019.intl.d.ts","../../../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../../node_modules/typescript/lib/lib.es2020.date.d.ts","../../../node_modules/typescript/lib/lib.es2020.promise.d.ts","../../../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../../node_modules/typescript/lib/lib.es2020.string.d.ts","../../../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../../node_modules/typescript/lib/lib.es2020.intl.d.ts","../../../node_modules/typescript/lib/lib.es2020.number.d.ts","../../../node_modules/typescript/lib/lib.esnext.intl.d.ts","../../../types/eth-ens-namehash.d.ts","../../../types/ethereum-ens-network-map.d.ts","../../../types/global.d.ts","../../../types/single-call-balance-checker-abi.d.ts","../../../types/@metamask/contract-metadata.d.ts","../../../types/@metamask/eth-hd-keyring.d.ts","../../../types/@metamask/eth-simple-keyring.d.ts","../../../types/@metamask/ethjs-provider-http.d.ts","../../../types/@metamask/ethjs-unit.d.ts","../../../types/@metamask/metamask-eth-abis.d.ts","../../../types/eth-json-rpc-infura/src/createprovider.d.ts","../../../types/eth-phishing-detect/src/config.json.d.ts","../../../types/eth-phishing-detect/src/detector.d.ts","../../base-controller/dist/types/basecontrollerv1.d.ts","../../../node_modules/superstruct/dist/error.d.ts","../../../node_modules/superstruct/dist/utils.d.ts","../../../node_modules/superstruct/dist/struct.d.ts","../../../node_modules/superstruct/dist/structs/coercions.d.ts","../../../node_modules/superstruct/dist/structs/refinements.d.ts","../../../node_modules/superstruct/dist/structs/types.d.ts","../../../node_modules/superstruct/dist/structs/utilities.d.ts","../../../node_modules/superstruct/dist/index.d.ts","../../../node_modules/@metamask/utils/dist/types/assert.d.ts","../../../node_modules/@metamask/utils/dist/types/base64.d.ts","../../../node_modules/@metamask/utils/dist/types/hex.d.ts","../../../node_modules/@metamask/utils/dist/types/bytes.d.ts","../../../node_modules/@metamask/utils/dist/types/caip-types.d.ts","../../../node_modules/@metamask/utils/dist/types/checksum.d.ts","../../../node_modules/@metamask/utils/dist/types/coercers.d.ts","../../../node_modules/@metamask/utils/dist/types/collections.d.ts","../../../node_modules/@metamask/utils/dist/types/encryption-types.d.ts","../../../node_modules/@metamask/utils/dist/types/errors.d.ts","../../../node_modules/@metamask/utils/dist/types/json.d.ts","../../../node_modules/@types/node/assert.d.ts","../../../node_modules/@types/node/assert/strict.d.ts","../../../node_modules/@types/node/globals.d.ts","../../../node_modules/@types/node/async_hooks.d.ts","../../../node_modules/@types/node/buffer.d.ts","../../../node_modules/@types/node/child_process.d.ts","../../../node_modules/@types/node/cluster.d.ts","../../../node_modules/@types/node/console.d.ts","../../../node_modules/@types/node/constants.d.ts","../../../node_modules/@types/node/crypto.d.ts","../../../node_modules/@types/node/dgram.d.ts","../../../node_modules/@types/node/diagnostics_channel.d.ts","../../../node_modules/@types/node/dns.d.ts","../../../node_modules/@types/node/dns/promises.d.ts","../../../node_modules/@types/node/dom-events.d.ts","../../../node_modules/@types/node/domain.d.ts","../../../node_modules/@types/node/events.d.ts","../../../node_modules/@types/node/fs.d.ts","../../../node_modules/@types/node/fs/promises.d.ts","../../../node_modules/@types/node/http.d.ts","../../../node_modules/@types/node/http2.d.ts","../../../node_modules/@types/node/https.d.ts","../../../node_modules/@types/node/inspector.d.ts","../../../node_modules/@types/node/module.d.ts","../../../node_modules/@types/node/net.d.ts","../../../node_modules/@types/node/os.d.ts","../../../node_modules/@types/node/path.d.ts","../../../node_modules/@types/node/perf_hooks.d.ts","../../../node_modules/@types/node/process.d.ts","../../../node_modules/@types/node/punycode.d.ts","../../../node_modules/@types/node/querystring.d.ts","../../../node_modules/@types/node/readline.d.ts","../../../node_modules/@types/node/repl.d.ts","../../../node_modules/@types/node/stream.d.ts","../../../node_modules/@types/node/stream/promises.d.ts","../../../node_modules/@types/node/stream/consumers.d.ts","../../../node_modules/@types/node/stream/web.d.ts","../../../node_modules/@types/node/string_decoder.d.ts","../../../node_modules/@types/node/test.d.ts","../../../node_modules/@types/node/timers.d.ts","../../../node_modules/@types/node/timers/promises.d.ts","../../../node_modules/@types/node/tls.d.ts","../../../node_modules/@types/node/trace_events.d.ts","../../../node_modules/@types/node/tty.d.ts","../../../node_modules/@types/node/url.d.ts","../../../node_modules/@types/node/util.d.ts","../../../node_modules/@types/node/v8.d.ts","../../../node_modules/@types/node/vm.d.ts","../../../node_modules/@types/node/wasi.d.ts","../../../node_modules/@types/node/worker_threads.d.ts","../../../node_modules/@types/node/zlib.d.ts","../../../node_modules/@types/node/globals.global.d.ts","../../../node_modules/@types/node/index.d.ts","../../../node_modules/@ethereumjs/common/dist/enums.d.ts","../../../node_modules/@ethereumjs/common/dist/types.d.ts","../../../node_modules/buffer/index.d.ts","../../../node_modules/@ethereumjs/util/dist/constants.d.ts","../../../node_modules/@ethereumjs/util/dist/units.d.ts","../../../node_modules/@ethereumjs/util/dist/address.d.ts","../../../node_modules/@ethereumjs/util/dist/bytes.d.ts","../../../node_modules/@ethereumjs/util/dist/types.d.ts","../../../node_modules/@ethereumjs/util/dist/account.d.ts","../../../node_modules/@ethereumjs/util/dist/withdrawal.d.ts","../../../node_modules/@ethereumjs/util/dist/signature.d.ts","../../../node_modules/@ethereumjs/util/dist/encoding.d.ts","../../../node_modules/@ethereumjs/util/dist/asynceventemitter.d.ts","../../../node_modules/@ethereumjs/util/dist/internal.d.ts","../../../node_modules/@ethereumjs/util/dist/lock.d.ts","../../../node_modules/@ethereumjs/util/dist/provider.d.ts","../../../node_modules/@ethereumjs/util/dist/index.d.ts","../../../node_modules/@ethereumjs/common/dist/common.d.ts","../../../node_modules/@ethereumjs/common/dist/utils.d.ts","../../../node_modules/@ethereumjs/common/dist/index.d.ts","../../../node_modules/@ethereumjs/tx/dist/eip2930transaction.d.ts","../../../node_modules/@ethereumjs/tx/dist/legacytransaction.d.ts","../../../node_modules/@ethereumjs/tx/dist/types.d.ts","../../../node_modules/@ethereumjs/tx/dist/basetransaction.d.ts","../../../node_modules/@ethereumjs/tx/dist/eip1559transaction.d.ts","../../../node_modules/@ethereumjs/tx/dist/transactionfactory.d.ts","../../../node_modules/@ethereumjs/tx/dist/index.d.ts","../../../node_modules/@metamask/utils/dist/types/keyring.d.ts","../../../node_modules/@types/ms/index.d.ts","../../../node_modules/@types/debug/index.d.ts","../../../node_modules/@metamask/utils/dist/types/logging.d.ts","../../../node_modules/@metamask/utils/dist/types/misc.d.ts","../../../node_modules/@metamask/utils/dist/types/number.d.ts","../../../node_modules/@metamask/utils/dist/types/opaque.d.ts","../../../node_modules/@metamask/utils/dist/types/promise.d.ts","../../../node_modules/@metamask/utils/dist/types/time.d.ts","../../../node_modules/@metamask/utils/dist/types/transaction-types.d.ts","../../../node_modules/@metamask/utils/dist/types/versions.d.ts","../../../node_modules/@metamask/utils/dist/types/index.d.ts","../../../node_modules/immer/dist/utils/env.d.ts","../../../node_modules/immer/dist/utils/errors.d.ts","../../../node_modules/immer/dist/types/types-external.d.ts","../../../node_modules/immer/dist/types/types-internal.d.ts","../../../node_modules/immer/dist/utils/common.d.ts","../../../node_modules/immer/dist/utils/plugins.d.ts","../../../node_modules/immer/dist/core/scope.d.ts","../../../node_modules/immer/dist/core/finalize.d.ts","../../../node_modules/immer/dist/core/proxy.d.ts","../../../node_modules/immer/dist/core/immerclass.d.ts","../../../node_modules/immer/dist/core/current.d.ts","../../../node_modules/immer/dist/internal.d.ts","../../../node_modules/immer/dist/plugins/es5.d.ts","../../../node_modules/immer/dist/plugins/patches.d.ts","../../../node_modules/immer/dist/plugins/mapset.d.ts","../../../node_modules/immer/dist/plugins/all.d.ts","../../../node_modules/immer/dist/immer.d.ts","../../base-controller/dist/types/restrictedcontrollermessenger.d.ts","../../base-controller/dist/types/controllermessenger.d.ts","../../base-controller/dist/types/basecontrollerv2.d.ts","../../base-controller/dist/types/index.d.ts","../../controller-utils/dist/types/types.d.ts","../../controller-utils/dist/types/constants.d.ts","../../../node_modules/@metamask/eth-query/index.d.ts","../../../node_modules/@types/bn.js/index.d.ts","../../controller-utils/dist/types/util.d.ts","../../../node_modules/@spruceid/siwe-parser/dist/abnf.d.ts","../../../node_modules/@spruceid/siwe-parser/dist/utils.d.ts","../../../node_modules/@spruceid/siwe-parser/dist/parsers.d.ts","../../controller-utils/dist/types/siwe.d.ts","../../controller-utils/dist/types/index.d.ts","../../../node_modules/@metamask/swappable-obj-proxy/dist/types.d.ts","../../../node_modules/@metamask/swappable-obj-proxy/dist/createeventemitterproxy.d.ts","../../../node_modules/@metamask/swappable-obj-proxy/dist/createswappableproxy.d.ts","../../../node_modules/@metamask/swappable-obj-proxy/dist/index.d.ts","../../network-controller/dist/types/constants.d.ts","../../../node_modules/@metamask/safe-event-emitter/dist/cjs/index.d.ts","../../json-rpc-engine/dist/types/jsonrpcengine.d.ts","../../json-rpc-engine/dist/types/createasyncmiddleware.d.ts","../../json-rpc-engine/dist/types/createscaffoldmiddleware.d.ts","../../json-rpc-engine/dist/types/getuniqueid.d.ts","../../json-rpc-engine/dist/types/idremapmiddleware.d.ts","../../json-rpc-engine/dist/types/mergemiddleware.d.ts","../../json-rpc-engine/dist/types/index.d.ts","../../eth-json-rpc-provider/dist/types/safe-event-emitter-provider.d.ts","../../eth-json-rpc-provider/dist/types/provider-from-engine.d.ts","../../eth-json-rpc-provider/dist/types/provider-from-middleware.d.ts","../../eth-json-rpc-provider/dist/types/index.d.ts","../../../node_modules/eth-block-tracker/dist/blocktracker.d.ts","../../../node_modules/eth-block-tracker/dist/pollingblocktracker.d.ts","../../../node_modules/eth-block-tracker/dist/subscribeblocktracker.d.ts","../../../node_modules/eth-block-tracker/dist/index.d.ts","../../network-controller/dist/types/types.d.ts","../../network-controller/dist/types/create-auto-managed-network-client.d.ts","../../network-controller/dist/types/networkcontroller.d.ts","../../network-controller/dist/types/create-network-client.d.ts","../../network-controller/dist/types/index.d.ts","../../polling-controller/dist/types/types.d.ts","../../polling-controller/dist/types/blocktrackerpollingcontroller.d.ts","../../polling-controller/dist/types/staticintervalpollingcontroller.d.ts","../../polling-controller/dist/types/index.d.ts","../../../node_modules/@types/uuid/index.d.ts","../src/determinegasfeecalculations.ts","../src/gas-util.ts","../src/gasfeecontroller.ts","../src/index.ts","../../../node_modules/@babel/types/lib/index.d.ts","../../../node_modules/@types/babel__generator/index.d.ts","../../../node_modules/@babel/parser/typings/babel-parser.d.ts","../../../node_modules/@types/babel__template/index.d.ts","../../../node_modules/@types/babel__traverse/index.d.ts","../../../node_modules/@types/babel__core/index.d.ts","../../../node_modules/@types/deep-freeze-strict/index.d.ts","../../../node_modules/@types/eslint/helpers.d.ts","../../../node_modules/@types/estree/index.d.ts","../../../node_modules/@types/json-schema/index.d.ts","../../../node_modules/@types/eslint/index.d.ts","../../../node_modules/@types/graceful-fs/index.d.ts","../../../node_modules/@types/istanbul-lib-coverage/index.d.ts","../../../node_modules/@types/istanbul-lib-report/index.d.ts","../../../node_modules/@types/istanbul-reports/index.d.ts","../../../node_modules/chalk/index.d.ts","../../../node_modules/jest-diff/build/cleanupsemantic.d.ts","../../../node_modules/pretty-format/build/types.d.ts","../../../node_modules/pretty-format/build/index.d.ts","../../../node_modules/jest-diff/build/types.d.ts","../../../node_modules/jest-diff/build/difflines.d.ts","../../../node_modules/jest-diff/build/printdiffs.d.ts","../../../node_modules/jest-diff/build/index.d.ts","../../../node_modules/jest-matcher-utils/build/index.d.ts","../../../node_modules/@types/jest/index.d.ts","../../../node_modules/@types/jest-when/index.d.ts","../../../node_modules/@types/json5/index.d.ts","../../../node_modules/@types/lodash/common/common.d.ts","../../../node_modules/@types/lodash/common/array.d.ts","../../../node_modules/@types/lodash/common/collection.d.ts","../../../node_modules/@types/lodash/common/date.d.ts","../../../node_modules/@types/lodash/common/function.d.ts","../../../node_modules/@types/lodash/common/lang.d.ts","../../../node_modules/@types/lodash/common/math.d.ts","../../../node_modules/@types/lodash/common/number.d.ts","../../../node_modules/@types/lodash/common/object.d.ts","../../../node_modules/@types/lodash/common/seq.d.ts","../../../node_modules/@types/lodash/common/string.d.ts","../../../node_modules/@types/lodash/common/util.d.ts","../../../node_modules/@types/lodash/index.d.ts","../../../node_modules/@types/minimatch/index.d.ts","../../../node_modules/@types/parse-json/index.d.ts","../../../node_modules/@types/pbkdf2/index.d.ts","../../../node_modules/@types/prettier/index.d.ts","../../../node_modules/@types/punycode/index.d.ts","../../../node_modules/@types/readable-stream/node_modules/safe-buffer/index.d.ts","../../../node_modules/@types/readable-stream/index.d.ts","../../../node_modules/@types/secp256k1/index.d.ts","../../../node_modules/@types/semver/classes/semver.d.ts","../../../node_modules/@types/semver/functions/parse.d.ts","../../../node_modules/@types/semver/functions/valid.d.ts","../../../node_modules/@types/semver/functions/clean.d.ts","../../../node_modules/@types/semver/functions/inc.d.ts","../../../node_modules/@types/semver/functions/diff.d.ts","../../../node_modules/@types/semver/functions/major.d.ts","../../../node_modules/@types/semver/functions/minor.d.ts","../../../node_modules/@types/semver/functions/patch.d.ts","../../../node_modules/@types/semver/functions/prerelease.d.ts","../../../node_modules/@types/semver/functions/compare.d.ts","../../../node_modules/@types/semver/functions/rcompare.d.ts","../../../node_modules/@types/semver/functions/compare-loose.d.ts","../../../node_modules/@types/semver/functions/compare-build.d.ts","../../../node_modules/@types/semver/functions/sort.d.ts","../../../node_modules/@types/semver/functions/rsort.d.ts","../../../node_modules/@types/semver/functions/gt.d.ts","../../../node_modules/@types/semver/functions/lt.d.ts","../../../node_modules/@types/semver/functions/eq.d.ts","../../../node_modules/@types/semver/functions/neq.d.ts","../../../node_modules/@types/semver/functions/gte.d.ts","../../../node_modules/@types/semver/functions/lte.d.ts","../../../node_modules/@types/semver/functions/cmp.d.ts","../../../node_modules/@types/semver/functions/coerce.d.ts","../../../node_modules/@types/semver/classes/comparator.d.ts","../../../node_modules/@types/semver/classes/range.d.ts","../../../node_modules/@types/semver/functions/satisfies.d.ts","../../../node_modules/@types/semver/ranges/max-satisfying.d.ts","../../../node_modules/@types/semver/ranges/min-satisfying.d.ts","../../../node_modules/@types/semver/ranges/to-comparators.d.ts","../../../node_modules/@types/semver/ranges/min-version.d.ts","../../../node_modules/@types/semver/ranges/valid.d.ts","../../../node_modules/@types/semver/ranges/outside.d.ts","../../../node_modules/@types/semver/ranges/gtr.d.ts","../../../node_modules/@types/semver/ranges/ltr.d.ts","../../../node_modules/@types/semver/ranges/intersects.d.ts","../../../node_modules/@types/semver/ranges/simplify.d.ts","../../../node_modules/@types/semver/ranges/subset.d.ts","../../../node_modules/@types/semver/internals/identifiers.d.ts","../../../node_modules/@types/semver/index.d.ts","../../../node_modules/@types/sinonjs__fake-timers/index.d.ts","../../../node_modules/@types/sinon/index.d.ts","../../../node_modules/@types/stack-utils/index.d.ts","../../../node_modules/@types/yargs-parser/index.d.ts","../../../node_modules/@types/yargs/index.d.ts"],"fileInfos":[{"version":"8730f4bf322026ff5229336391a18bcaa1f94d4f82416c8b2f3954e2ccaae2ba","affectsGlobalScope":true},"dc47c4fa66b9b9890cf076304de2a9c5201e94b740cffdf09f87296d877d71f6","7a387c58583dfca701b6c85e0adaf43fb17d590fb16d5b2dc0a2fbd89f35c467","8a12173c586e95f4433e0c6dc446bc88346be73ffe9ca6eec7aa63c8f3dca7f9","5f4e733ced4e129482ae2186aae29fde948ab7182844c3a5a51dd346182c7b06","4b421cbfb3a38a27c279dec1e9112c3d1da296f77a1a85ddadf7e7a425d45d18","1fc5ab7a764205c68fa10d381b08417795fc73111d6dd16b5b1ed36badb743d9",{"version":"3aafcb693fe5b5c3bd277bd4c3a617b53db474fe498fc5df067c5603b1eebde7","affectsGlobalScope":true},{"version":"adb996790133eb33b33aadb9c09f15c2c575e71fb57a62de8bf74dbf59ec7dfb","affectsGlobalScope":true},{"version":"8cc8c5a3bac513368b0157f3d8b31cfdcfe78b56d3724f30f80ed9715e404af8","affectsGlobalScope":true},{"version":"cdccba9a388c2ee3fd6ad4018c640a471a6c060e96f1232062223063b0a5ac6a","affectsGlobalScope":true},{"version":"c5c05907c02476e4bde6b7e76a79ffcd948aedd14b6a8f56e4674221b0417398","affectsGlobalScope":true},{"version":"5f406584aef28a331c36523df688ca3650288d14f39c5d2e555c95f0d2ff8f6f","affectsGlobalScope":true},{"version":"22f230e544b35349cfb3bd9110b6ef37b41c6d6c43c3314a31bd0d9652fcec72","affectsGlobalScope":true},{"version":"7ea0b55f6b315cf9ac2ad622b0a7813315bb6e97bf4bb3fbf8f8affbca7dc695","affectsGlobalScope":true},{"version":"3013574108c36fd3aaca79764002b3717da09725a36a6fc02eac386593110f93","affectsGlobalScope":true},{"version":"eb26de841c52236d8222f87e9e6a235332e0788af8c87a71e9e210314300410a","affectsGlobalScope":true},{"version":"3be5a1453daa63e031d266bf342f3943603873d890ab8b9ada95e22389389006","affectsGlobalScope":true},{"version":"17bb1fc99591b00515502d264fa55dc8370c45c5298f4a5c2083557dccba5a2a","affectsGlobalScope":true},{"version":"7ce9f0bde3307ca1f944119f6365f2d776d281a393b576a18a2f2893a2d75c98","affectsGlobalScope":true},{"version":"6a6b173e739a6a99629a8594bfb294cc7329bfb7b227f12e1f7c11bc163b8577","affectsGlobalScope":true},{"version":"81cac4cbc92c0c839c70f8ffb94eb61e2d32dc1c3cf6d95844ca099463cf37ea","affectsGlobalScope":true},{"version":"b0124885ef82641903d232172577f2ceb5d3e60aed4da1153bab4221e1f6dd4e","affectsGlobalScope":true},{"version":"0eb85d6c590b0d577919a79e0084fa1744c1beba6fd0d4e951432fa1ede5510a","affectsGlobalScope":true},{"version":"da233fc1c8a377ba9e0bed690a73c290d843c2c3d23a7bd7ec5cd3d7d73ba1e0","affectsGlobalScope":true},{"version":"d154ea5bb7f7f9001ed9153e876b2d5b8f5c2bb9ec02b3ae0d239ec769f1f2ae","affectsGlobalScope":true},{"version":"bb2d3fb05a1d2ffbca947cc7cbc95d23e1d053d6595391bd325deb265a18d36c","affectsGlobalScope":true},{"version":"c80df75850fea5caa2afe43b9949338ce4e2de086f91713e9af1a06f973872b8","affectsGlobalScope":true},{"version":"9d57b2b5d15838ed094aa9ff1299eecef40b190722eb619bac4616657a05f951","affectsGlobalScope":true},{"version":"6c51b5dd26a2c31dbf37f00cfc32b2aa6a92e19c995aefb5b97a3a64f1ac99de","affectsGlobalScope":true},{"version":"6e7997ef61de3132e4d4b2250e75343f487903ddf5370e7ce33cf1b9db9a63ed","affectsGlobalScope":true},{"version":"2ad234885a4240522efccd77de6c7d99eecf9b4de0914adb9a35c0c22433f993","affectsGlobalScope":true},{"version":"5e5e095c4470c8bab227dbbc61374878ecead104c74ab9960d3adcccfee23205","affectsGlobalScope":true},{"version":"09aa50414b80c023553090e2f53827f007a301bc34b0495bfb2c3c08ab9ad1eb","affectsGlobalScope":true},{"version":"d7f680a43f8cd12a6b6122c07c54ba40952b0c8aa140dcfcf32eb9e6cb028596","affectsGlobalScope":true},{"version":"3787b83e297de7c315d55d4a7c546ae28e5f6c0a361b7a1dcec1f1f50a54ef11","affectsGlobalScope":true},{"version":"e7e8e1d368290e9295ef18ca23f405cf40d5456fa9f20db6373a61ca45f75f40","affectsGlobalScope":true},{"version":"faf0221ae0465363c842ce6aa8a0cbda5d9296940a8e26c86e04cc4081eea21e","affectsGlobalScope":true},{"version":"06393d13ea207a1bfe08ec8d7be562549c5e2da8983f2ee074e00002629d1871","affectsGlobalScope":true},{"version":"2768ef564cfc0689a1b76106c421a2909bdff0acbe87da010785adab80efdd5c","affectsGlobalScope":true},{"version":"b248e32ca52e8f5571390a4142558ae4f203ae2f94d5bac38a3084d529ef4e58","affectsGlobalScope":true},{"version":"52d1bb7ab7a3306fd0375c8bff560feed26ed676a5b0457fa8027b563aecb9a4","affectsGlobalScope":true},"70bbfaec021ac4a0c805374225b55d70887f987df8b8dd7711d79464bb7b4385","869089d60b67219f63e6aca810284c89bae1b384b5cbc7ce64e53d82ad223ed5",{"version":"18338b6a4b920ec7d49b4ffafcbf0fa8a86b4bfd432966efd722dab611157cf4","affectsGlobalScope":true},"62a0875a0397b35a2364f1d401c0ce17975dfa4d47bf6844de858ae04da349f9","ee7491d0318d1fafcba97d5b72b450eb52671570f7a4ecd9e8898d40eaae9472","e3e7d217d89b380c1f34395eadc9289542851b0f0a64007dfe1fb7cf7423d24e","fd79909e93b4d50fd0ed9f3d39ddf8ba0653290bac25c295aac49f6befbd081b","345a9cc2945406f53051cd0e9b51f82e1e53929848eab046fdda91ee8aa7da31","9debe2de883da37a914e5e784a7be54c201b8f1d783822ad6f443ff409a5ea21","dee5d5c5440cda1f3668f11809a5503c30db0476ad117dd450f7ba5a45300e8f","f5e396c1424c391078c866d6f84afe0b4d2f7f85a160b9c756cd63b5b1775d93","5caa6f4fff16066d377d4e254f6c34c16540da3809cd66cd626a303bc33c419f","730d055528bdf12c8524870bb33d237991be9084c57634e56e5d8075f6605e02","75b22c74010ba649de1a1676a4c4b8b5bb4294fecd05089e2094429b16d7840c","5615ccf831db2ffc82145243081ebdb60ea8e1005ee8f975d1c0c1401a9c894e","38682ed3630bb6ecdace80d5a9adc811fc20a419f1940446e306c3a020d083b9","cc182e6e4f691cd6f7bf7cb491247a4c7818f9f1cb2db1d45c65ff906e3f741b","a50599c08934a62f11657bdbe0dc929ab66da1b1f09974408fd9a33ec1bb8060","5a20e7d6c630b91be15e9b837853173829d00273197481dc8d3e94df61105a71","8d478048d71cc16f806d4b71b252ecb67c7444ccf4f4b09b29a312712184f859","e0eda929c6b9b628cdeb0e54cd3582cb97e64f28aab34612fc1431c545899584","9df4662ca3dbc2522bc115833ee04faa1afbb4e249a85ef4a0a09c621346bd08","b25d9065cf1c1f537a140bbc508e953ed2262f77134574c432d206ff36f4bdbf","1b103313097041aa9cd705a682c652f08613cb5cf8663321061c0902f845e81c","68ccec8662818911d8a12b8ed028bc5729fb4f1d34793c4701265ba60bc73cf4","5f85b8b79dc4d36af672c035b2beb71545de63a5d60bccbeee64c260941672ab","b3d48529ae61dc27d0bfbfa2cb3e0dff8189644bd155bdf5df1e8e14669f7043","40fe4b689225816b31fe5794c0fbf3534568819709e40295ead998a2bc1ab237","f65b5e33b9ad545a1eebbd6afe857314725ad42aaf069913e33f928ab3e4990a","fb6f2a87beb7fb1f4c2b762d0c76a9459fc91f557231569b0ee21399e22aa13d","31c858dc85996fac4b7fa944e1016d5c72f514930a72357ab5001097bf6511c7","3de30a871b3340be8b679c52aa12f90dd1c8c60874517be58968fdbcc4d79445","6fd985bd31eaf77542625306fb0404d32bff978990f0a06428e5f0b9a3b58109","5b3cd03ae354ea96eff1f74d7c410fe4852e6382227e8b0ecf87ab5e3a5bbcd4","7394959e5a741b185456e1ef5d64599c36c60a323207450991e7a42e08911419",{"version":"056097110efd16869ec118cedb44ecbac9a019576eee808d61304ca6d5cb2cbe","affectsGlobalScope":true},"f51b4042a3ac86f1f707500a9768f88d0b0c1fc3f3e45a73333283dea720cdc6",{"version":"6fb8358e10ed92a7f515b7d79da3904c955a3ffd4e14aa9df6f0ea113041f1cf","affectsGlobalScope":true},"45c831238c6dac21c72da5f335747736a56a3847192bf03c84b958a7e9ec93e2","661a11d16ad2e3543a77c53bcd4017ee9a450f47ab7def3ab493a86eae4d550c",{"version":"8cdc646cec7819581ef343b83855b1bfe4fe674f2c84f4fb8dc90d82fb56bd3a","affectsGlobalScope":true},"a40826e8476694e90da94aa008283a7de50d1dafd37beada623863f1901cb7fb","9dd56225cc2d8cb8fe5ceb0043ff386987637e12fecc6078896058a99deae284","2375ed4b439215aa3b6d0c6fd175c78a4384b30cb43cbadaecbf0a18954c98cb","7693b90b3075deaccafd5efb467bf9f2b747a3075be888652ef73e64396d8628","41231da15bb5e3e806a8395bd15c7befd2ec90f9f4e3c9d0ae1356bccb76dbb0","fccfef201d057cb407fa515311bd608549bab6c7b8adcf8f2df31f5d3b796478",{"version":"ee1ee365d88c4c6c0c0a5a5701d66ebc27ccd0bcfcfaa482c6e2e7fe7b98edf7","affectsGlobalScope":true},"5f20d20b7607174caf1a6da9141aeb9f2142159ae2410ca30c7a0fccd1d19c99",{"version":"464762c6213566d072f1ced5e8e9a954785ec5e53883b7397198abb5ef5b8f71","affectsGlobalScope":true},"6387920dc3e18927335b086deec75bf8e50f879a5e273d32ee7bb7a55ba50572","9bba37424094688c4663c177a1379b229f919b8912889a472f32fdc5f08ddb4d","29a4be13b3a30d3e66667b75c58ec61fb2df8fa0422534fdee3cfb30c5dbf450","83366d901beda79d6eb37aaaf6ca248dcd88946302b2a7d975590783be51e88e","bf268a0aea37ad4ae3b7a9b58559190b6fc01ea16a31e35cd05817a0a60f895a","43ec77c369473e92e2ecebf0554a0fdaa9c256644a6070f28228dfcceec77351",{"version":"d7dad6db394a3d9f7b49755e4b610fbf8ed6eb0c9810ae5f1a119f6b5d76de45","affectsGlobalScope":true},"95ed02bacb4502c985b69742ec82a4576d4ff4a6620ecc91593f611d502ae546","bf755525c4e6f85a970b98c4755d98e8aa1b6dbd83a5d8fcc57d3d497351b936","dd67d2b5e4e8a182a38de8e69fb736945eaa4588e0909c14e01a14bd3cc1fd1e",{"version":"28084e15b63e6211769db2fe646d8bc5c4c6776321e0deffe2d12eefd52cb6b9","affectsGlobalScope":true},{"version":"aed37dabf86c99d6c8508700576ecede86688397bc12523541858705a0c737c2","affectsGlobalScope":true},"cc6ef5733d4ea6d2e06310a32dffd2c16418b467c5033d49cecc4f3a25de7497","94768454c3348b6ebe48e45fbad8c92e2bb7af4a35243edbe2b90823d0bd7f9a","0be79b3ff0f16b6c2f9bc8c4cc7097ea417d8d67f8267f7e1eec8e32b548c2ff","1c61ffa3a71b77363b30d19832c269ef62fba787f5610cac7254728d3b69ab2e","84da3c28344e621fd1d591f2c09e9595292d2b70018da28a553268ac122597d4","269929a24b2816343a178008ac9ae9248304d92a8ba8e233055e0ed6dbe6ef71","6e191fea1db6e9e4fa828259cf489e820ec9170effff57fb081a2f3295db4722","aed943465fbce1efe49ee16b5ea409050f15cd8eaf116f6fadb64ef0772e7d95","70d08483a67bf7050dbedace398ef3fee9f436fcd60517c97c4c1e22e3c6f3e8","c40fdf7b2e18df49ce0568e37f0292c12807a0748be79e272745e7216bed2606",{"version":"e933de8143e1d12dd51d89b398760fd5a9081896be366dad88a922d0b29f3c69","affectsGlobalScope":true},"4e228e78c1e9b0a75c70588d59288f63a6258e8b1fe4a67b0c53fe03461421d9","b38d55d08708c2410a3039687db70b4a5bfa69fc4845617c313b5a10d9c5c637","205d50c24359ead003dc537b9b65d2a64208dfdffe368f403cf9e0357831db9e","1265fddcd0c68be9d2a3b29805d0280484c961264dd95e0b675f7bd91f777e78",{"version":"a05e2d784c9be7051c4ac87a407c66d2106e23490c18c038bbd0712bde7602fd","affectsGlobalScope":true},{"version":"df90b9d0e9980762da8daf8adf6ffa0c853e76bfd269c377be0d07a9ad87acd2","affectsGlobalScope":true},"cf434b5c04792f62d6f4bdd5e2c8673f36e638e910333c172614d5def9b17f98","1d65d4798df9c2df008884035c41d3e67731f29db5ecb64cd7378797c7c53a2f","0faee6b555890a1cb106e2adc5d3ffd89545b1da894d474e9d436596d654998f","c6c01ea1c42508edf11a36d13b70f6e35774f74355ba5d358354d4a77cc67ea1","867f95abf1df444aab146b19847391fc2f922a55f6a970a27ed8226766cee29f",{"version":"ab9b9a36e5284fd8d3bf2f7d5fcbc60052f25f27e4d20954782099282c60d23e","affectsGlobalScope":true},"b0297b09e607bec9698cac7cf55463d6731406efb1161ee4d448293b47397c84","175323e2a79a6076e0bada8a390d535a3ea817158bf1b1f46e31efca9028a0a2","7a10053aadc19335532a4d02756db4865974fd69bea5439ddcc5bfdf062d9476","4967529644e391115ca5592184d4b63980569adf60ee685f968fd59ab1557188","aed9e712a9b168345362e8f3a949f16c99ca1e05d21328f05735dfdbb24414ef","b04fe6922ed3db93afdbd49cdda8576aa75f744592fceea96fb0d5f32158c4f5","ed8d6c8de90fc2a4faaebc28e91f2469928738efd5208fb75ade0fa607e892b7","d7c52b198d680fe65b1a8d1b001f0173ffa2536ca2e7082431d726ce1f6714cd","c07f251e1c4e415a838e5498380b55cfea94f3513229de292d2aa85ae52fc3e9","0ed401424892d6bf294a5374efe512d6951b54a71e5dd0290c55b6d0d915f6f7","b945be6da6a3616ef3a250bfe223362b1c7c6872e775b0c4d82a1bf7a28ff902","beea49237dd7c7110fabf3c7509919c9cb9da841d847c53cac162dc3479e2f87","0f45f8a529c450d8f394106cc622bff79e44a1716e1ac9c3cc68b43f7ecf65ee","c624ce90b04c27ce4f318ba6330d39bde3d4e306f0f497ce78d4bda5ab8e22ca","9b8253aa5cb2c82d505f72afdbf96e83b15cc6b9a6f4fadbbbab46210d5f1977","86a8f52e4b1ac49155e889376bcfa8528a634c90c27fec65aa0e949f77b740c5","aab5dd41c1e2316cc0b42a7dd15684f8582d5a1d16c0516276a2a8a7d0fecd9c","59948226626ee210045296ba1fc6cb0fe748d1ff613204e08e7157ab6862dee7","ec3e54d8b713c170fdc8110a7e4a6a97513a7ab6b05ac9e1100cb064d2bb7349","43beb30ecb39a603fde4376554887310b0699f25f7f39c5c91e3147b51bb3a26","666b77d7f06f49da114b090a399abbfa66d5b6c01a3fd9dc4f063a52ace28507","31997714a93fbc570f52d47d6a8ebfb021a34a68ea9ba58bbb69cdec9565657e","6032e4262822160128e644de3fc4410bcd7517c2f137525fd2623d2bb23cb0d3","8bd5c9b1016629c144fd228983395b9dbf0676a576716bc3d316cab612c33cd5","2ed90bd3925b23aed8f859ffd0e885250be0424ca2b57e9866dabef152e1d6b7","93f6bd17d92dab9db7897e1430a5aeaa03bcf51623156213d8397710367a76ce","3f62b770a42e8c47c7008726f95aa383e69d97e85e680d237b99fcb0ee601dd8","5b84cfe78028c35c3bb89c042f18bf08d09da11e82d275c378ae4d07d8477e6c","980d21b0081cbf81774083b1e3a46f4bbdcd2b68858df0f66d7fad9c82bc34bc","68cc8d6fcc2f270d7108f02f3ebc59480a54615be3e09a47e14527f349e9d53e","3eb11dbf3489064a47a2e1cf9d261b1f100ef0b3b50ffca6c44dd99d6dd81ac1","b17f3bb7d8333479c7e45e5f3d876761b9bca58f97594eca3f6a944fd825e632","3c1f1236cce6d6e0c4e2c1b4371e6f72d7c14842ecd76a98ed0748ee5730c8f3","6d7f58d5ea72d7834946fd7104a734dc7d40661be8b2e1eaced1ddce3268ebaf","4c26222991e6c97d5a8f541d4f2c67585eda9e8b33cf9f52931b098045236e88","277983d414aa99d78655186c3ee1e1c38c302e336aff1d77b47fcdc39d8273fe","47383b45796d525a4039cd22d2840ac55a1ff03a43d027f7f867ba7314a9cf53","6548773b3abbc18de29176c2141f766d4e437e40596ee480447abf83575445ad","6ddd27af0436ce59dd4c1896e2bfdb2bdb2529847d078b83ce67a144dff05491","816264799aef3fd5a09a3b6c25217d5ec26a9dfc7465eac7d6073bcdc7d88f3f","4df0891b133884cd9ed752d31c7d0ec0a09234e9ed5394abffd3c660761598db","b603b62d3dcd31ef757dc7339b4fa8acdbca318b0fb9ac485f9a1351955615f9","e642bd47b75ad6b53cbf0dfd7ddfa0f120bd10193f0c58ec37d87b59bf604aca","be90b24d2ee6f875ce3aaa482e7c41a54278856b03d04212681c4032df62baf9","78f5ff400b3cb37e7b90eef1ff311253ed31c8cb66505e9828fad099bffde021","372c47090e1131305d163469a895ff2938f33fa73aad988df31cd31743f9efb6","71c67dc6987bdbd5599353f90009ff825dd7db0450ef9a0aee5bb0c574d18512","6f12403b5eca6ae7ca8e3efe3eeb9c683b06ce3e3844ccfd04098d83cd7e4957","282c535df88175d64d9df4550d2fd1176fd940c1c6822f1e7584003237f179d3","c3a4752cf103e4c6034d5bd449c8f9d5e7b352d22a5f8f9a41a8efb11646f9c2","11a9e38611ac3c77c74240c58b6bd64a0032128b29354e999650f1de1e034b1c","4ed103ca6fff9cb244f7c4b86d1eb28ce8069c32db720784329946731badb5bb","d738f282842970e058672663311c6875482ee36607c88b98ffb6604fba99cb2a","ec859cd8226aa623e41bbb47c249a55ee16dc1b8647359585244d57d3a5ed0c7","8891c6e959d253a66434ff5dc9ae46058fb3493e84b4ca39f710ef2d350656b1","c4463cf02535444dcbc3e67ecd29f1972490f74e49957d6fd4282a1013796ba6","0cb0a957ff02de0b25fd0f3f37130ca7f22d1e0dea256569c714c1f73c6791f8","2f5075dc512d51786b1ba3b1696565641dfaae3ac854f5f13d61fa12ef81a47e","ca3353cc82b1981f0d25d71d7432d583a6ef882ccdea82d65fbe49af37be51cb","50679a8e27aacf72f8c40bcab15d7ef5e83494089b4726b83eec4554344d5cdc","45351e0d51780b6f4088277a4457b9879506ee2720a887de232df0f1efcb33d8","5d697a4b315cc5bb3042ae869abffd10c3b0d7b182cda0e4c45d8819937e5796","563fa27fdaec8f195b84f71a7af0ef48d30d5cc830575db86da86a63a470c8e6","6ee58aa536dabb19b09bc036f1abe83feb51e13d63b23d30b2d0631a2de99b8f","8aceb205dcc6f814ad99635baf1e40b6e01d06d3fe27b72fd766c6d0b8c0c600","299567f84bfedd1468dca2755a829cb19e607a6811673788807dc8921e211bc9","795d9fb85aad92221504db74dd179b506bd189bba0c104426f7e7bb8a66ffee5","1311bc194e0a69fe61031e852c1c0b439e2a2a3d1d5e2d8ff795499b9f283459","4b7ce19369d7e7fae76720c2c6c7f671bf3fa0f7093edb864f1ac358ca7c456c","c972ef44deca1fa8fab465915ffa00f82e126aacf3dfc8979c03b1b066ce5bb6","30285a1011c6d6b52f3ba3abb0a984be8148c05cdefb8eb6eb562335a3991f35","8e7adb22c0adecf7464861fc58ae3fc617b41ffbd70c97aa8493dc0966a82273","755f3cd1d9c1b564cff090e3b0e29200ae55690a91b87cb9e7a64c2dbeb314d3","d6bb7e0a6877b7856c183bff13d09dd9ae599ea43c6f6b33d3d5f72a830ed460","f1b51ae93c762d7c43f559933cd4842dd870367e8d92e90704ffa685dd5b29a3","3f450762fd7c34ed545e738abccb0af6a703572a10521643cf8fc88e3724c99c","fcc8beef29f39f09b1d9c9f99c42f9fed605ab1c28d2a630185f732b9ba53763","d6e6620a30d582182acc3f0a992a0c311adc589f111096aea11ab83fc09a5ccc","6213b8f686f56beab22b59a0f468590fd3a4c5fa931236a017efeca91d7c9584","c451cec9a588b1f105a5ea2c6063d4fca112b9d70105cacdadda0e1ef67e9379","cb047832dc68f5a2c41c62c5e95ddcacbae3a8b034d40cd15319a8cb7f25104a","980336ccdfc3c08f3c3b201aa6662e6016e20f15847f8465b68f3e8e67b4665c","5a3493939995f46ff3d9073cd534fb8961c3bf4e08c71db27066ff03d906dea8","bb5a2ac327605ebebf831c469b05bd34a33a6a46ee8c1edd9f3310aad32cf6a1","bf5d041f2440b4a9391e2b5eb3b8d94cbf1e3b8ff4703b6539d4e65e758c8f37","8516469eb90e723b0eb03df1be098f7e6a4709f6f48fd4532868d20a0a934f6e","d60e9ab369a72d234aac49adbe2900d8ef1408a6ea4db552cf2a48c9d8d6a1bc","0ebb4698803f01e2e7df6acce572fff068f4a20c47221721dafd70a27e372831","03460a54d0e0481d1e11097f66ad43f054bc95efdafe5f81bbc7a82be181af75","4070c2f1c3434fcf84886e04d30d82cd650ee443e53b82b404b144175cf8741e","2cea9689efa8591732096235abe7f084fc29c92badd5b0897a5e876b77e71887","4ed4e504126014fee13aaef5e3fc140f2ff7031ff3a8b5386717905820ea2d09","8129a34006218a6f3cdc81bbd438d5429eb18b08b4338a26977ac3b4df129d75","30d2170e1a718b5035611af55e3618b4ba8f42f0749bb52ee593da6082c4e2ce","98ef38666d88ec9699a722053e07ede65d3042f693fe7ff8c786e53dbb6fd43b","a3b8b6be7620897d1e481e8650c980a210a138fceb6e710eaf95fd9dd0dfe94a","12c89d0e32758c120a569045f21cf5b77244f86792611ced8de7f86b37e77781","14bd47270e654c8eb3b1489fa8c095912ee62a0a29bb92743393203722347c53","3d9297165e67fd59d9821cc93a9808213e33c56a8ac1c4273171f6afaaa2d4d5","e7af7d288b89287ad031b19583c597fcd9f5edc0b0d579b7b492f06cf57e058c","92cb686a9ca5eb5dd7d5d8d43a3707194c1e91ea07a027b3bcb60b6011b24632","fab58e600970e66547644a44bc9918e3223aa2cbd9e8763cec004b2cfb48827e",{"version":"d1a5f486914e3ead50534f48742c4e5e885d28909bec274017189d3284bcb833","signature":"b943b90c649678cec7987a6d56f4917db1712079294cffdf650699235ff9f37d"},{"version":"15433a2e97dcee760d97f8e56afe908fe932611ef00711aed9b246ba058ce0be","signature":"c5fc52cba80e6ba6fde33b0f9370b8ba6866831070cbc84b20a7c6e9e1163160"},{"version":"674d97345e4d79afd1248bdf3a032c9280f2ef163b1a074d14a1a927d93397a4","signature":"6991b558e53d59bb2160b64ee154678e069bd9bfe5131c628dcb6ac0363ca00d"},"d021f18758b28bda32bdaf0a987e0804cec074a9a4cfab8232ed81d96e75dfae","4489c6a9fde8934733aa7df6f7911461ee6e9e4ad092736bd416f6b2cc20b2c6","2c8e55457aaf4902941dfdba4061935922e8ee6e120539c9801cd7b400fae050","8041cfce439ff29d339742389de04c136e3029d6b1817f07b2d7fcbfb7534990","670a76db379b27c8ff42f1ba927828a22862e2ab0b0908e38b671f0e912cc5ed","9d38964b57191567a14b396422c87488cecd48f405c642daa734159875ee81d9","069bebfee29864e3955378107e243508b163e77ab10de6a5ee03ae06939f0bb9","8c95f96ccd4be0674944077aec1e4f2cccd515ca06d4327562dd017250e7d3fc",{"version":"64d4b35c5456adf258d2cf56c341e203a073253f229ef3208fc0d5020253b241","affectsGlobalScope":true},"ee7d8894904b465b072be0d2e4b45cf6b887cdba16a467645c4e200982ece7ea","f3d8c757e148ad968f0d98697987db363070abada5f503da3c06aefd9d4248c1","bc3cba7b0af2d52e7425299aee518db479d44004eff6fbbd206d1ee7e5ec3fb5","afe73051ff6a03a9565cbd8ebb0e956ee3df5e913ad5c1ded64218aabfa3dcb5","035a5df183489c2e22f3cf59fc1ed2b043d27f357eecc0eb8d8e840059d44245","a4809f4d92317535e6b22b01019437030077a76fec1d93b9881c9ed4738fcc54","5f53fa0bd22096d2a78533f94e02c899143b8f0f9891a46965294ee8b91a9434","0d14fa22c41fdc7277e6f71473b20ebc07f40f00e38875142335d5b63cdfc9d2","d8aab31ba8e618cc3eea10b0945de81cb93b7e8150a013a482332263b9305322","462bccdf75fcafc1ae8c30400c9425e1a4681db5d605d1a0edb4f990a54d8094","5923d8facbac6ecf7c84739a5c701a57af94a6f6648d6229a6c768cf28f0f8cb","7adecb2c3238794c378d336a8182d4c3dd2c4fa6fa1785e2797a3db550edea62","dc12dc0e5aa06f4e1a7692149b78f89116af823b9e1f1e4eae140cd3e0e674e6","1bfc6565b90c8771615cd8cfcf9b36efc0275e5e83ac7d9181307e96eb495161","8a8a96898906f065f296665e411f51010b51372fa260d5373bf9f64356703190","7f82ef88bdb67d9a850dd1c7cd2d690f33e0f0acd208e3c9eba086f3670d4f73",{"version":"ccfd8774cd9b929f63ff7dcf657977eb0652e3547f1fcac1b3a1dc5db22d4d58","affectsGlobalScope":true},"d92dc90fecd2552db74d8dc3c6fb4db9145b2aa0efe2c127236ba035969068d4","96d14f21b7652903852eef49379d04dbda28c16ed36468f8c9fa08f7c14c9538","b8442e9db28157344d1bc5d8a5a256f1692de213f0c0ddeb84359834015a008c","458111fc89d11d2151277c822dfdc1a28fa5b6b2493cf942e37d4cd0a6ee5f22","da2b6356b84a40111aaecb18304ea4e4fcb43d70efb1c13ca7d7a906445ee0d3","187119ff4f9553676a884e296089e131e8cc01691c546273b1d0089c3533ce42","febf0b2de54781102b00f61653b21377390a048fbf5262718c91860d11ff34a6","6f294731b495c65ecf46a5694f0082954b961cf05463bea823f8014098eaffa0","0aaef8cded245bf5036a7a40b65622dd6c4da71f7a35343112edbe112b348a1e","00baffbe8a2f2e4875367479489b5d43b5fc1429ecb4a4cc98cfc3009095f52a","68a0d0c508e1b6d8d23a519a8a0a3303dc5baa4849ca049f21e5bad41945e3fc","3c92b6dfd43cc1c2485d9eba5ff0b74a19bb8725b692773ef1d66dac48cda4bd","b03afe4bec768ae333582915146f48b161e567a81b5ebc31c4d78af089770ac9","df996e25faa505f85aeb294d15ebe61b399cf1d1e49959cdfaf2cc0815c203f9","4f6a12044ee6f458db11964153830abbc499e73d065c51c329ec97407f4b13dd","8841e2aa774b89bd23302dede20663306dc1b9902431ac64b24be8b8d0e3f649","916be7d770b0ae0406be9486ac12eb9825f21514961dd050594c4b250617d5a8","254d9fb8c872d73d34594be8a200fd7311dbfa10a4116bfc465fba408052f2b3","d88a5e779faf033be3d52142a04fbe1cb96009868e3bbdd296b2bc6c59e06c0e","2ccea88888048bbfcacbc9531a5596ea48a3e7dcd0a25f531a81bb717903ba4f","5e379df3d61561c2ed7789b5995b9ba2143bbba21a905e2381e16efe7d1fa424","f07a137bbe2de7a122c37bfea00e761975fb264c49f18003d398d71b3fb35a5f","d8f7109e14f20eb735225a62fd3f8366da1a8349e90331cdad57f4b04caf6c5a","cf3d384d082b933d987c4e2fe7bfb8710adfd9dc8155190056ed6695a25a559e","9871b7ee672bc16c78833bdab3052615834b08375cb144e4d2cba74473f4a589","c863198dae89420f3c552b5a03da6ed6d0acfa3807a64772b895db624b0de707","8b03a5e327d7db67112ebbc93b4f744133eda2c1743dbb0a990c61a8007823ef","86c73f2ee1752bac8eeeece234fd05dfcf0637a4fbd8032e4f5f43102faa8eec","42fad1f540271e35ca37cecda12c4ce2eef27f0f5cf0f8dd761d723c744d3159","ff3743a5de32bee10906aff63d1de726f6a7fd6ee2da4b8229054dfa69de2c34","83acd370f7f84f203e71ebba33ba61b7f1291ca027d7f9a662c6307d74e4ac22","1445cec898f90bdd18b2949b9590b3c012f5b7e1804e6e329fb0fe053946d5ec","0e5318ec2275d8da858b541920d9306650ae6ac8012f0e872fe66eb50321a669","cf530297c3fb3a92ec9591dd4fa229d58b5981e45fe6702a0bd2bea53a5e59be","c1f6f7d08d42148ddfe164d36d7aba91f467dbcb3caa715966ff95f55048b3a4","f4e9bf9103191ef3b3612d3ec0044ca4044ca5be27711fe648ada06fad4bcc85","0c1ee27b8f6a00097c2d6d91a21ee4d096ab52c1e28350f6362542b55380059a","7677d5b0db9e020d3017720f853ba18f415219fb3a9597343b1b1012cfd699f7","bc1c6bc119c1784b1a2be6d9c47addec0d83ef0d52c8fbe1f14a51b4dfffc675","52cf2ce99c2a23de70225e252e9822a22b4e0adb82643ab0b710858810e00bf1","770625067bb27a20b9826255a8d47b6b5b0a2d3dfcbd21f89904c731f671ba77","d1ed6765f4d7906a05968fb5cd6d1db8afa14dbe512a4884e8ea5c0f5e142c80","799c0f1b07c092626cf1efd71d459997635911bb5f7fc1196efe449bba87e965","2a184e4462b9914a30b1b5c41cf80c6d3428f17b20d3afb711fff3f0644001fd","9eabde32a3aa5d80de34af2c2206cdc3ee094c6504a8d0c2d6d20c7c179503cc","397c8051b6cfcb48aa22656f0faca2553c5f56187262135162ee79d2b2f6c966","a8ead142e0c87dcd5dc130eba1f8eeed506b08952d905c47621dc2f583b1bff9","a02f10ea5f73130efca046429254a4e3c06b5475baecc8f7b99a0014731be8b3","c2576a4083232b0e2d9bd06875dd43d371dee2e090325a9eac0133fd5650c1cb","4c9a0564bb317349de6a24eb4efea8bb79898fa72ad63a1809165f5bd42970dd","f40ac11d8859092d20f953aae14ba967282c3bb056431a37fced1866ec7a2681","cc11e9e79d4746cc59e0e17473a59d6f104692fd0eeea1bdb2e206eabed83b03","b444a410d34fb5e98aa5ee2b381362044f4884652e8bc8a11c8fe14bbd85518e","c35808c1f5e16d2c571aa65067e3cb95afeff843b259ecfa2fc107a9519b5392","14d5dc055143e941c8743c6a21fa459f961cbc3deedf1bfe47b11587ca4b3ef5","a3ad4e1fc542751005267d50a6298e6765928c0c3a8dce1572f2ba6ca518661c","f237e7c97a3a89f4591afd49ecb3bd8d14f51a1c4adc8fcae3430febedff5eb6","3ffdfbec93b7aed71082af62b8c3e0cc71261cc68d796665faa1e91604fbae8f","662201f943ed45b1ad600d03a90dffe20841e725203ced8b708c91fcd7f9379a","c9ef74c64ed051ea5b958621e7fb853fe3b56e8787c1587aefc6ea988b3c7e79","2462ccfac5f3375794b861abaa81da380f1bbd9401de59ffa43119a0b644253d","34baf65cfee92f110d6653322e2120c2d368ee64b3c7981dff08ed105c4f19b0","7d8ddf0f021c53099e34ee831a06c394d50371816caa98684812f089b4c6b3d4","7d2a0ba1297be385a89b5515b88cd31b4a1eeef5236f710166dc1b36b1741e1b","9d92b037978bb9525bc4b673ebddd443277542e010c0aef019c03a170ccdaa73","ab82804a14454734010dcdcd43f564ff7b0389bee4c5692eec76ff5b30d4cf66","bae8d023ef6b23df7da26f51cea44321f95817c190342a36882e93b80d07a960","ae271d475b632ce7b03fea6d9cf6da72439e57a109672671cbc79f54e1386938"],"options":{"composite":true,"declaration":true,"declarationMap":true,"emitDeclarationOnly":true,"esModuleInterop":true,"inlineSources":true,"module":1,"outDir":"./types","rootDir":"../src","sourceMap":true,"strict":true,"target":7},"fileIdsList":[[234],[92,128,129,130,145],[129,130,146,147],[128,129],[128,145,148,151],[128,148,151,152],[149,150,151,153,154],[128,151],[128,145,148,149,150,153],[128,136],[128],[92,128],[80,128],[132,133,134,135,136,137,138,139,140,141,142,143,144],[128,134,135],[128,134,136],[199],[199,200,201],[64],[67],[64,67],[65,66,67,68,69,70,71,72,73,74,75,156,159,160,161,162,163,164,165,166],[58,64,65],[67,73,75,155],[158],[67,68],[64,162],[194,195],[234,235,236,237,238],[234,236],[157],[241,242,243],[93,128],[246],[247],[258],[252,257],[261,263,264,265,266,267,268,269,270,271,272,273],[261,262,264,265,266,267,268,269,270,271,272,273],[262,263,264,265,266,267,268,269,270,271,272,273],[261,262,263,265,266,267,268,269,270,271,272,273],[261,262,263,264,266,267,268,269,270,271,272,273],[261,262,263,264,265,267,268,269,270,271,272,273],[261,262,263,264,265,266,268,269,270,271,272,273],[261,262,263,264,265,266,267,269,270,271,272,273],[261,262,263,264,265,266,267,268,270,271,272,273],[261,262,263,264,265,266,267,268,269,271,272,273],[261,262,263,264,265,266,267,268,269,270,272,273],[261,262,263,264,265,266,267,268,269,270,271,273],[261,262,263,264,265,266,267,268,269,270,271,272],[76],[79],[80,85,112],[81,92,93,100,109,120],[81,82,92,100],[83,121],[84,85,93,101],[85,109,117],[86,88,92,100],[87],[88,89],[92],[91,92],[79,92],[92,93,94,109,120],[92,93,94,109],[92,95,100,109,120],[92,93,95,96,100,109,117,120],[95,97,109,117,120],[76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127],[92,98],[99,120,125],[88,92,100,109],[101],[102],[79,103],[104,119,125],[105],[106],[92,107],[107,108,121,123],[80,92,109,110,111],[80,109,111],[109,110],[112],[113],[92,115,116],[115,116],[85,100,109,117],[118],[100,119],[80,95,106,120],[85,121],[109,122],[123],[124],[80,85,92,94,103,109,120,123,125],[109,126],[128,279],[282,321],[282,306,321],[321],[282],[282,307,321],[282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320],[307,321],[322],[325],[204],[216,217,218],[204,215,216],[179],[179,180,181,182,183],[168,169,170,171,172,173,174,175,176,177,178],[250,253],[250,253,254,255],[252],[249,256],[251],[57,59,60,61,62,63],[57,58],[59],[58,59],[57,59],[167,184,185,186],[185],[56,185,186,187],[186],[189],[189,190,193,197],[196],[167,191,192],[212,213,214],[211,212],[167,211,212],[167,204,211],[232],[191,192,198,232],[167,188,191,198,224,228,229,230,231],[167,205],[205,206,207,208,209,210],[167,204],[220],[203,220,222,223],[167,188,191,198,202,203,220,221],[167,198,215,219],[167,188,224,225],[225,226,227],[128,167,188,224,225],[167,224],[191,232],[167,188,224,228]],"referencedMap":[[236,1],[146,2],[148,3],[130,4],[152,5],[153,6],[149,6],[155,7],[150,6],[154,8],[151,9],[137,10],[134,11],[141,12],[135,10],[132,13],[145,14],[139,11],[136,15],[138,16],[204,12],[200,17],[201,17],[202,18],[65,19],[66,19],[68,20],[69,19],[70,19],[71,21],[67,19],[167,22],[75,23],[156,24],[159,25],[165,26],[166,27],[196,28],[239,29],[235,1],[237,30],[238,1],[192,11],[158,31],[244,32],[245,33],[247,34],[248,35],[259,36],[258,37],[262,38],[263,39],[261,40],[264,41],[265,42],[266,43],[267,44],[268,45],[269,46],[270,47],[271,48],[272,49],[273,50],[76,51],[77,51],[79,52],[80,53],[81,54],[82,55],[83,56],[84,57],[85,58],[86,59],[87,60],[88,61],[89,61],[90,62],[91,63],[92,64],[93,65],[94,66],[95,67],[96,68],[97,69],[128,70],[98,71],[99,72],[100,73],[101,74],[102,75],[103,76],[104,77],[105,78],[106,79],[107,80],[108,81],[109,82],[111,83],[110,84],[112,85],[113,86],[115,87],[116,88],[117,89],[118,90],[119,91],[120,92],[121,93],[122,94],[123,95],[124,96],[125,97],[126,98],[276,11],[280,99],[281,11],[306,100],[307,101],[282,102],[285,102],[304,100],[305,100],[295,100],[294,103],[292,100],[287,100],[300,100],[298,100],[302,100],[286,100],[299,100],[303,100],[288,100],[289,100],[301,100],[283,100],[290,100],[291,100],[293,100],[297,100],[308,104],[296,100],[284,100],[321,105],[315,104],[317,106],[316,104],[309,104],[310,104],[312,104],[314,104],[318,106],[319,106],[311,106],[313,106],[323,107],[326,108],[216,109],[219,110],[217,111],[218,111],[175,112],[177,112],[176,112],[174,112],[184,113],[179,114],[170,112],[171,112],[172,112],[173,112],[254,115],[256,116],[255,115],[253,117],[257,118],[252,119],[64,120],[59,121],[60,122],[61,122],[62,123],[63,123],[58,124],[187,125],[186,126],[188,127],[185,128],[190,129],[198,130],[197,131],[193,132],[215,133],[213,134],[214,135],[212,136],[230,137],[231,138],[232,139],[233,137],[206,140],[207,140],[209,140],[211,141],[205,142],[210,140],[221,143],[223,143],[224,144],[222,145],[220,146],[226,147],[228,148],[227,149],[225,150]],"exportedModulesMap":[[236,1],[146,2],[148,3],[130,4],[152,5],[153,6],[149,6],[155,7],[150,6],[154,8],[151,9],[137,10],[134,11],[141,12],[135,10],[132,13],[145,14],[139,11],[136,15],[138,16],[204,12],[200,17],[201,17],[202,18],[65,19],[66,19],[68,20],[69,19],[70,19],[71,21],[67,19],[167,22],[75,23],[156,24],[159,25],[165,26],[166,27],[196,28],[239,29],[235,1],[237,30],[238,1],[192,11],[158,31],[244,32],[245,33],[247,34],[248,35],[259,36],[258,37],[262,38],[263,39],[261,40],[264,41],[265,42],[266,43],[267,44],[268,45],[269,46],[270,47],[271,48],[272,49],[273,50],[76,51],[77,51],[79,52],[80,53],[81,54],[82,55],[83,56],[84,57],[85,58],[86,59],[87,60],[88,61],[89,61],[90,62],[91,63],[92,64],[93,65],[94,66],[95,67],[96,68],[97,69],[128,70],[98,71],[99,72],[100,73],[101,74],[102,75],[103,76],[104,77],[105,78],[106,79],[107,80],[108,81],[109,82],[111,83],[110,84],[112,85],[113,86],[115,87],[116,88],[117,89],[118,90],[119,91],[120,92],[121,93],[122,94],[123,95],[124,96],[125,97],[126,98],[276,11],[280,99],[281,11],[306,100],[307,101],[282,102],[285,102],[304,100],[305,100],[295,100],[294,103],[292,100],[287,100],[300,100],[298,100],[302,100],[286,100],[299,100],[303,100],[288,100],[289,100],[301,100],[283,100],[290,100],[291,100],[293,100],[297,100],[308,104],[296,100],[284,100],[321,105],[315,104],[317,106],[316,104],[309,104],[310,104],[312,104],[314,104],[318,106],[319,106],[311,106],[313,106],[323,107],[326,108],[216,109],[219,110],[217,111],[218,111],[175,112],[177,112],[176,112],[174,112],[184,113],[179,114],[170,112],[171,112],[172,112],[173,112],[254,115],[256,116],[255,115],[253,117],[257,118],[252,119],[64,120],[59,121],[60,122],[61,122],[62,123],[63,123],[58,124],[187,125],[186,126],[188,127],[185,128],[190,129],[198,130],[197,131],[193,132],[215,133],[213,134],[214,135],[212,136],[230,137],[231,151],[232,152],[233,137],[206,140],[207,140],[209,140],[211,141],[205,142],[210,140],[221,143],[223,143],[224,144],[222,145],[220,146],[226,147],[228,148],[227,149],[225,150]],"semanticDiagnosticsPerFile":[236,234,146,129,148,130,147,152,153,149,155,150,154,151,137,134,141,135,132,140,145,142,143,144,139,136,133,138,191,204,200,201,202,199,65,66,68,69,70,71,72,73,74,67,167,75,156,159,160,161,162,163,164,165,166,194,196,195,239,235,237,238,192,158,240,241,244,242,245,246,247,248,259,258,243,260,262,263,261,264,265,266,267,268,269,270,271,272,273,274,157,76,77,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,78,127,95,96,97,128,98,99,100,101,102,103,104,105,106,107,108,109,111,110,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,275,276,277,278,280,279,281,306,307,282,285,304,305,295,294,292,287,300,298,302,286,299,303,288,289,301,283,290,291,293,297,308,296,284,321,320,315,317,316,309,310,312,314,318,319,311,313,323,322,324,229,325,326,131,249,216,219,217,218,178,175,177,176,174,184,179,183,180,182,181,170,171,172,168,169,173,250,254,256,255,253,257,252,251,57,64,59,60,61,62,63,58,8,10,9,2,11,12,13,14,15,16,17,18,3,4,22,19,20,21,23,24,25,5,26,27,28,29,6,33,30,31,32,34,7,35,40,41,36,37,38,39,1,42,56,187,186,188,185,190,198,197,189,193,215,213,214,212,230,231,232,233,206,207,208,209,211,205,210,203,221,223,224,222,220,226,228,227,225,47,48,49,50,51,52,43,53,54,55,44,45,46],"latestChangedDtsFile":"./types/index.d.ts"},"version":"4.9.5"} +\ No newline at end of file +diff --git a/dist/types/GasFeeController.d.ts b/dist/types/GasFeeController.d.ts +index b6ffa7cd5b356ef6c5df6a79876308d21749bd06..56d9420b539cc8d98b8aeb0a84ba541fc3beae1b 100644 +--- a/dist/types/GasFeeController.d.ts ++++ b/dist/types/GasFeeController.d.ts +@@ -2,7 +2,7 @@ import type { ControllerGetStateAction, ControllerStateChangeEvent, RestrictedCo + import type { NetworkClientId, NetworkControllerGetEIP1559CompatibilityAction, NetworkControllerGetNetworkClientByIdAction, NetworkControllerGetStateAction, NetworkControllerNetworkDidChangeEvent, NetworkState, ProviderProxy } from '@metamask/network-controller'; + import { StaticIntervalPollingController } from '@metamask/polling-controller'; + import type { Hex } from '@metamask/utils'; +-export declare const GAS_API_BASE_URL = "https://gas.api.infura.io"; ++export declare const LEGACY_GAS_PRICES_API_URL = "https://api.metaswap.codefi.network/gasPrices"; + export type unknownString = 'unknown'; + export type FeeMarketEstimateType = 'fee-market'; + export type LegacyEstimateType = 'legacy'; +@@ -160,7 +160,6 @@ export declare class GasFeeController extends StaticIntervalPollingController Hex; + getProvider: () => ProviderProxy; + onNetworkDidChange?: (listener: (state: NetworkState) => void) => void; ++ legacyAPIEndpoint?: string; ++ EIP1559APIEndpoint: string; + clientId?: string; +- infuraAPIKey: string; + }); + resetPolling(): Promise; + fetchGasFeeEstimates(options?: FetchGasFeeEstimateOptions): Promise; +diff --git a/dist/types/GasFeeController.d.ts.map b/dist/types/GasFeeController.d.ts.map +index 0dd80816d0083029fcc5ef3a85a47f188a5b4c02..127cdbe0c0fbfb37938403190c49d2a5687cf9c4 100644 +--- a/dist/types/GasFeeController.d.ts.map ++++ b/dist/types/GasFeeController.d.ts.map +@@ -1 +1 @@ +-{"version":3,"file":"GasFeeController.d.ts","sourceRoot":"","sources":["../../src/GasFeeController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,0BAA0B,EAC1B,6BAA6B,EAC9B,MAAM,2BAA2B,CAAC;AAOnC,OAAO,KAAK,EACV,eAAe,EACf,8CAA8C,EAC9C,2CAA2C,EAC3C,+BAA+B,EAC/B,sCAAsC,EACtC,YAAY,EACZ,aAAa,EACd,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,+BAA+B,EAAE,MAAM,8BAA8B,CAAC;AAC/E,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAW3C,eAAO,MAAM,gBAAgB,8BAA8B,CAAC;AAE5D,MAAM,MAAM,aAAa,GAAG,SAAS,CAAC;AAItC,MAAM,MAAM,qBAAqB,GAAG,YAAY,CAAC;AAIjD,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC;AAK1C,MAAM,MAAM,uBAAuB,GAAG,cAAc,CAAC;AAGrD,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC;AAEpC;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB;;;;;CAK9B,CAAC;AAEF,MAAM,MAAM,eAAe,GACvB,qBAAqB,GACrB,uBAAuB,GACvB,kBAAkB,GAClB,cAAc,CAAC;AAEnB,MAAM,MAAM,yBAAyB,GAAG;IACtC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,cAAc,EAAE,MAAM,GAAG,aAAa,CAAC;CACxC,CAAC;AAEF;;;;;;;GAOG;AAEH,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,6BAA6B,EAAE,MAAM,CAAC;IACtC,qBAAqB,EAAE,MAAM,CAAC;CAC/B,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,MAAM,eAAe,GAAG,sBAAsB,GAAG,uBAAuB,CAAC;AAE/E,KAAK,sBAAsB,GAAG;IAC5B,GAAG,EAAE,aAAa,CAAC;IACnB,MAAM,EAAE,aAAa,CAAC;IACtB,IAAI,EAAE,aAAa,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,sBAAsB,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,YAAY,EAAE,IAAI,GAAG,MAAM,GAAG,OAAO,CAAC;IACtC,sBAAsB,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,0BAA0B,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C,gBAAgB,EAAE,IAAI,GAAG,MAAM,GAAG,OAAO,CAAC;IAC1C,iBAAiB,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,KAAK,uBAAuB,GAAG;IAC7B,GAAG,EAAE,aAAa,CAAC;IACnB,MAAM,EAAE,aAAa,CAAC;IACtB,IAAI,EAAE,aAAa,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,sBAAsB,EAAE,IAAI,CAAC;IAC7B,YAAY,EAAE,IAAI,CAAC;IACnB,sBAAsB,EAAE,IAAI,CAAC;IAC7B,0BAA0B,EAAE,IAAI,CAAC;IACjC,gBAAgB,EAAE,IAAI,CAAC;IACvB,iBAAiB,EAAE,IAAI,CAAC;CACzB,CAAC;AAaF,MAAM,MAAM,sBAAsB,GAAG;IACnC,eAAe,EAAE,mBAAmB,CAAC;IACrC,yBAAyB,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACjD,eAAe,EAAE,uBAAuB,CAAC;CAC1C,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,eAAe,EAAE,eAAe,CAAC;IACjC,yBAAyB,EAAE,yBAAyB,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC7E,eAAe,EAAE,qBAAqB,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,eAAe,EAAE,sBAAsB,CAAC;IACxC,yBAAyB,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACjD,eAAe,EAAE,kBAAkB,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACvC,yBAAyB,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACjD,eAAe,EAAE,cAAc,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,eAAe,CAAC,EAAE,eAAe,CAAC;CACnC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,sBAAsB,GAC9B,sBAAsB,GACtB,oBAAoB,GACpB,iBAAiB,GACjB,sBAAsB,CAAC;AAE3B,MAAM,MAAM,wBAAwB,GAAG;IACrC,wBAAwB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;CACnE,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,wBAAwB,GAChD,sBAAsB,GAAG;IACvB,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACpC,CAAC;AAEJ,QAAA,MAAM,IAAI,qBAAqB,CAAC;AAEhC,MAAM,MAAM,iBAAiB,GAAG,0BAA0B,CACxD,OAAO,IAAI,EACX,WAAW,CACZ,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,wBAAwB,CAAC,OAAO,IAAI,EAAE,WAAW,CAAC,CAAC;AAEhF,MAAM,MAAM,uBAAuB,GAAG,cAAc,CAAC;AAErD,MAAM,MAAM,sBAAsB,GAAG,iBAAiB,CAAC;AAEvD,KAAK,cAAc,GACf,+BAA+B,GAC/B,2CAA2C,GAC3C,8CAA8C,CAAC;AAEnD,KAAK,eAAe,GAAG,6BAA6B,CAClD,OAAO,IAAI,EACX,uBAAuB,GAAG,cAAc,EACxC,sBAAsB,GAAG,sCAAsC,EAC/D,cAAc,CAAC,MAAM,CAAC,EACtB,sCAAsC,CAAC,MAAM,CAAC,CAC/C,CAAC;AAUF;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,+BAA+B,CACnE,OAAO,IAAI,EACX,WAAW,EACX,eAAe,CAChB;;IACC,OAAO,CAAC,UAAU,CAAC,CAAgC;IAEnD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC;IAE/B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAc;IAEzC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAE3C,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAS;IAE5C,OAAO,CAAC,QAAQ,CAAC,qCAAqC,CAAC;IAEvD,OAAO,CAAC,QAAQ,CAAC,0CAA0C,CAAC;IAE5D,OAAO,CAAC,QAAQ,CAAC,qCAAqC,CAAC;IAEvD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IAEtC,OAAO,CAAC,cAAc,CAAC;IAEvB,OAAO,CAAC,QAAQ,CAAC,CAAW;IAE5B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAS;IAInC;;;;;;;;;;;;;;;;;;;;OAoBG;gBACS,EACV,QAAgB,EAChB,SAAS,EACT,KAAK,EACL,qCAAqC,EACrC,qCAAqC,EACrC,UAAU,EACV,0CAA0C,EAC1C,WAAW,EACX,kBAAkB,EAClB,QAAQ,EACR,YAAY,GACb,EAAE;QACD,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,eAAe,CAAC;QAC3B,KAAK,CAAC,EAAE,WAAW,CAAC;QACpB,qCAAqC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;QAC9D,0CAA0C,EAAE,MAAM,OAAO,CAAC;QAC1D,qCAAqC,CAAC,EAAE,MAAM,OAAO,CAAC;QACtD,UAAU,CAAC,EAAE,MAAM,GAAG,CAAC;QACvB,WAAW,EAAE,MAAM,aAAa,CAAC;QACjC,kBAAkB,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,KAAK,IAAI,CAAC;QACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;KACtB;IA0CK,YAAY;IAWZ,oBAAoB,CAAC,OAAO,CAAC,EAAE,0BAA0B;IAIzD,iCAAiC,CACrC,SAAS,EAAE,MAAM,GAAG,SAAS,GAC5B,OAAO,CAAC,MAAM,CAAC;IAalB;;;;;;;OAOG;IACG,wBAAwB,CAC5B,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,WAAW,CAAC;IAsFvB;;;;OAIG;IACH,gBAAgB,CAAC,SAAS,EAAE,MAAM;IAOlC,WAAW;IAQX;;;;OAIG;IACM,OAAO;IAKhB,OAAO,CAAC,KAAK;IAUb;;;;;;OAMG;IACG,YAAY,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1D,OAAO,CAAC,UAAU;YAMJ,uBAAuB;IAWrC,eAAe,CACb,oBAAoB,EAAE,MAAM,EAC5B,YAAY,EAAE,MAAM,GACnB,yBAAyB,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;IAyBpD,sBAAsB;IAMtB,uBAAuB;CAKxB;AAED,eAAe,gBAAgB,CAAC"} +\ No newline at end of file ++{"version":3,"file":"GasFeeController.d.ts","sourceRoot":"","sources":["../../src/GasFeeController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,0BAA0B,EAC1B,6BAA6B,EAC9B,MAAM,2BAA2B,CAAC;AAOnC,OAAO,KAAK,EACV,eAAe,EACf,8CAA8C,EAC9C,2CAA2C,EAC3C,+BAA+B,EAC/B,sCAAsC,EACtC,YAAY,EACZ,aAAa,EACd,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,+BAA+B,EAAE,MAAM,8BAA8B,CAAC;AAC/E,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAW3C,eAAO,MAAM,yBAAyB,kDAAkD,CAAC;AAEzF,MAAM,MAAM,aAAa,GAAG,SAAS,CAAC;AAItC,MAAM,MAAM,qBAAqB,GAAG,YAAY,CAAC;AAIjD,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC;AAK1C,MAAM,MAAM,uBAAuB,GAAG,cAAc,CAAC;AAGrD,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC;AAEpC;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB;;;;;CAK9B,CAAC;AAEF,MAAM,MAAM,eAAe,GACvB,qBAAqB,GACrB,uBAAuB,GACvB,kBAAkB,GAClB,cAAc,CAAC;AAEnB,MAAM,MAAM,yBAAyB,GAAG;IACtC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,cAAc,EAAE,MAAM,GAAG,aAAa,CAAC;CACxC,CAAC;AAEF;;;;;;;GAOG;AAEH,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,6BAA6B,EAAE,MAAM,CAAC;IACtC,qBAAqB,EAAE,MAAM,CAAC;CAC/B,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,MAAM,eAAe,GAAG,sBAAsB,GAAG,uBAAuB,CAAC;AAE/E,KAAK,sBAAsB,GAAG;IAC5B,GAAG,EAAE,aAAa,CAAC;IACnB,MAAM,EAAE,aAAa,CAAC;IACtB,IAAI,EAAE,aAAa,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,sBAAsB,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,YAAY,EAAE,IAAI,GAAG,MAAM,GAAG,OAAO,CAAC;IACtC,sBAAsB,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,0BAA0B,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C,gBAAgB,EAAE,IAAI,GAAG,MAAM,GAAG,OAAO,CAAC;IAC1C,iBAAiB,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,KAAK,uBAAuB,GAAG;IAC7B,GAAG,EAAE,aAAa,CAAC;IACnB,MAAM,EAAE,aAAa,CAAC;IACtB,IAAI,EAAE,aAAa,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,sBAAsB,EAAE,IAAI,CAAC;IAC7B,YAAY,EAAE,IAAI,CAAC;IACnB,sBAAsB,EAAE,IAAI,CAAC;IAC7B,0BAA0B,EAAE,IAAI,CAAC;IACjC,gBAAgB,EAAE,IAAI,CAAC;IACvB,iBAAiB,EAAE,IAAI,CAAC;CACzB,CAAC;AAaF,MAAM,MAAM,sBAAsB,GAAG;IACnC,eAAe,EAAE,mBAAmB,CAAC;IACrC,yBAAyB,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACjD,eAAe,EAAE,uBAAuB,CAAC;CAC1C,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,eAAe,EAAE,eAAe,CAAC;IACjC,yBAAyB,EAAE,yBAAyB,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC7E,eAAe,EAAE,qBAAqB,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,eAAe,EAAE,sBAAsB,CAAC;IACxC,yBAAyB,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACjD,eAAe,EAAE,kBAAkB,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACvC,yBAAyB,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACjD,eAAe,EAAE,cAAc,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,eAAe,CAAC,EAAE,eAAe,CAAC;CACnC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,sBAAsB,GAC9B,sBAAsB,GACtB,oBAAoB,GACpB,iBAAiB,GACjB,sBAAsB,CAAC;AAE3B,MAAM,MAAM,wBAAwB,GAAG;IACrC,wBAAwB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;CACnE,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,wBAAwB,GAChD,sBAAsB,GAAG;IACvB,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACpC,CAAC;AAEJ,QAAA,MAAM,IAAI,qBAAqB,CAAC;AAEhC,MAAM,MAAM,iBAAiB,GAAG,0BAA0B,CACxD,OAAO,IAAI,EACX,WAAW,CACZ,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,wBAAwB,CAAC,OAAO,IAAI,EAAE,WAAW,CAAC,CAAC;AAEhF,MAAM,MAAM,uBAAuB,GAAG,cAAc,CAAC;AAErD,MAAM,MAAM,sBAAsB,GAAG,iBAAiB,CAAC;AAEvD,KAAK,cAAc,GACf,+BAA+B,GAC/B,2CAA2C,GAC3C,8CAA8C,CAAC;AAEnD,KAAK,eAAe,GAAG,6BAA6B,CAClD,OAAO,IAAI,EACX,uBAAuB,GAAG,cAAc,EACxC,sBAAsB,GAAG,sCAAsC,EAC/D,cAAc,CAAC,MAAM,CAAC,EACtB,sCAAsC,CAAC,MAAM,CAAC,CAC/C,CAAC;AAUF;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,+BAA+B,CACnE,OAAO,IAAI,EACX,WAAW,EACX,eAAe,CAChB;;IACC,OAAO,CAAC,UAAU,CAAC,CAAgC;IAEnD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC;IAE/B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAc;IAEzC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAE3C,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAS;IAE5C,OAAO,CAAC,QAAQ,CAAC,qCAAqC,CAAC;IAEvD,OAAO,CAAC,QAAQ,CAAC,0CAA0C,CAAC;IAE5D,OAAO,CAAC,QAAQ,CAAC,qCAAqC,CAAC;IAEvD,OAAO,CAAC,cAAc,CAAC;IAEvB,OAAO,CAAC,QAAQ,CAAC,CAAW;IAE5B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAS;IAInC;;;;;;;;;;;;;;;;;;;;;;OAsBG;gBACS,EACV,QAAgB,EAChB,SAAS,EACT,KAAK,EACL,qCAAqC,EACrC,qCAAqC,EACrC,UAAU,EACV,0CAA0C,EAC1C,WAAW,EACX,kBAAkB,EAClB,iBAA6C,EAC7C,kBAAkB,EAClB,QAAQ,GACT,EAAE;QACD,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,eAAe,CAAC;QAC3B,KAAK,CAAC,EAAE,WAAW,CAAC;QACpB,qCAAqC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;QAC9D,0CAA0C,EAAE,MAAM,OAAO,CAAC;QAC1D,qCAAqC,CAAC,EAAE,MAAM,OAAO,CAAC;QACtD,UAAU,CAAC,EAAE,MAAM,GAAG,CAAC;QACvB,WAAW,EAAE,MAAM,aAAa,CAAC;QACjC,kBAAkB,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,KAAK,IAAI,CAAC;QACvE,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAE3B,kBAAkB,EAAE,MAAM,CAAC;QAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB;IAyCK,YAAY;IAWZ,oBAAoB,CAAC,OAAO,CAAC,EAAE,0BAA0B;IAIzD,iCAAiC,CACrC,SAAS,EAAE,MAAM,GAAG,SAAS,GAC5B,OAAO,CAAC,MAAM,CAAC;IAalB;;;;;;;OAOG;IACG,wBAAwB,CAC5B,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,WAAW,CAAC;IAqFvB;;;;OAIG;IACH,gBAAgB,CAAC,SAAS,EAAE,MAAM;IAOlC,WAAW;IAQX;;;;OAIG;IACM,OAAO;IAKhB,OAAO,CAAC,KAAK;IAUb;;;;;;OAMG;IACG,YAAY,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1D,OAAO,CAAC,UAAU;YAMJ,uBAAuB;IAWrC,eAAe,CACb,oBAAoB,EAAE,MAAM,EAC5B,YAAY,EAAE,MAAM,GACnB,yBAAyB,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;IAyBpD,sBAAsB;IAMtB,uBAAuB;CAKxB;AAED,eAAe,gBAAgB,CAAC"} +\ No newline at end of file +diff --git a/dist/types/determineGasFeeCalculations.d.ts b/dist/types/determineGasFeeCalculations.d.ts +index 8091a4d4290d6a3ca9ecbecc9ab8c6764a58cac9..6f4b2758a99e4d0d3fd2c8dce4064a2a2fc69c0a 100644 +--- a/dist/types/determineGasFeeCalculations.d.ts ++++ b/dist/types/determineGasFeeCalculations.d.ts +@@ -2,15 +2,14 @@ import type { EstimatedGasFeeTimeBounds, EthGasPriceEstimate, GasFeeEstimates, G + type DetermineGasFeeCalculationsRequest = { + isEIP1559Compatible: boolean; + isLegacyGasAPICompatible: boolean; +- fetchGasEstimates: (url: string, infuraAPIKey: string, clientId?: string) => Promise; ++ fetchGasEstimates: (url: string, clientId?: string) => Promise; + fetchGasEstimatesUrl: string; +- fetchLegacyGasPriceEstimates: (url: string, infuraAPIKey: string, clientId?: string) => Promise; ++ fetchLegacyGasPriceEstimates: (url: string, clientId?: string) => Promise; + fetchLegacyGasPriceEstimatesUrl: string; + fetchEthGasPriceEstimate: (ethQuery: any) => Promise; + calculateTimeEstimate: (maxPriorityFeePerGas: string, maxFeePerGas: string, gasFeeEstimates: GasFeeEstimates) => EstimatedGasFeeTimeBounds; + clientId: string | undefined; + ethQuery: any; +- infuraAPIKey: string; + nonRPCGasFeeApisDisabled?: boolean; + }; + /** +@@ -35,7 +34,6 @@ type DetermineGasFeeCalculationsRequest = { + * @param args.calculateTimeEstimate - A function that determine time estimate bounds. + * @param args.clientId - An identifier that an API can use to know who is asking for estimates. + * @param args.ethQuery - An EthQuery instance we can use to talk to Ethereum directly. +- * @param args.infuraAPIKey - Infura API key to use for requests to Infura. + * @param args.nonRPCGasFeeApisDisabled - Whether to disable requests to the legacyAPIEndpoint and the EIP1559APIEndpoint + * @returns The gas fee calculations. + */ +diff --git a/dist/types/determineGasFeeCalculations.d.ts.map b/dist/types/determineGasFeeCalculations.d.ts.map +index 44ce4dc7b691ea0316163952f99d426d417afd1a..17cec260fc0836f59929a053270164e5c10e4b89 100644 +--- a/dist/types/determineGasFeeCalculations.d.ts.map ++++ b/dist/types/determineGasFeeCalculations.d.ts.map +@@ -1 +1 @@ +-{"version":3,"file":"determineGasFeeCalculations.d.ts","sourceRoot":"","sources":["../../src/determineGasFeeCalculations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yBAAyB,EACzB,mBAAmB,EACnB,eAAe,EACf,WAAW,IAAI,kBAAkB,EACjC,sBAAsB,EACvB,MAAM,oBAAoB,CAAC;AAG5B,KAAK,kCAAkC,GAAG;IACxC,mBAAmB,EAAE,OAAO,CAAC;IAC7B,wBAAwB,EAAE,OAAO,CAAC;IAClC,iBAAiB,EAAE,CACjB,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE,MAAM,KACd,OAAO,CAAC,eAAe,CAAC,CAAC;IAC9B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,4BAA4B,EAAE,CAC5B,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE,MAAM,KACd,OAAO,CAAC,sBAAsB,CAAC,CAAC;IACrC,+BAA+B,EAAE,MAAM,CAAC;IAGxC,wBAAwB,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC1E,qBAAqB,EAAE,CACrB,oBAAoB,EAAE,MAAM,EAC5B,YAAY,EAAE,MAAM,EACpB,eAAe,EAAE,eAAe,KAC7B,yBAAyB,CAAC;IAC/B,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAG7B,QAAQ,EAAE,GAAG,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACpC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAA8B,2BAA2B,CACvD,IAAI,EAAE,kCAAkC,GACvC,OAAO,CAAC,kBAAkB,CAAC,CAY7B"} +\ No newline at end of file ++{"version":3,"file":"determineGasFeeCalculations.d.ts","sourceRoot":"","sources":["../../src/determineGasFeeCalculations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yBAAyB,EACzB,mBAAmB,EACnB,eAAe,EACf,WAAW,IAAI,kBAAkB,EACjC,sBAAsB,EACvB,MAAM,oBAAoB,CAAC;AAG5B,KAAK,kCAAkC,GAAG;IACxC,mBAAmB,EAAE,OAAO,CAAC;IAC7B,wBAAwB,EAAE,OAAO,CAAC;IAClC,iBAAiB,EAAE,CACjB,GAAG,EAAE,MAAM,EACX,QAAQ,CAAC,EAAE,MAAM,KACd,OAAO,CAAC,eAAe,CAAC,CAAC;IAC9B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,4BAA4B,EAAE,CAC5B,GAAG,EAAE,MAAM,EACX,QAAQ,CAAC,EAAE,MAAM,KACd,OAAO,CAAC,sBAAsB,CAAC,CAAC;IACrC,+BAA+B,EAAE,MAAM,CAAC;IAGxC,wBAAwB,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC1E,qBAAqB,EAAE,CACrB,oBAAoB,EAAE,MAAM,EAC5B,YAAY,EAAE,MAAM,EACpB,eAAe,EAAE,eAAe,KAC7B,yBAAyB,CAAC;IAC/B,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAG7B,QAAQ,EAAE,GAAG,CAAC;IACd,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACpC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAA8B,2BAA2B,CACvD,IAAI,EAAE,kCAAkC,GACvC,OAAO,CAAC,kBAAkB,CAAC,CAY7B"} +\ No newline at end of file +diff --git a/dist/types/gas-util.d.ts b/dist/types/gas-util.d.ts +index 3739e0ffc0f895d20b5ab1e706b893e8606ef62b..661c3a87fbb2160075801e676d216809b01ecf6e 100644 +--- a/dist/types/gas-util.d.ts ++++ b/dist/types/gas-util.d.ts +@@ -11,21 +11,19 @@ export declare function normalizeGWEIDecimalNumbers(n: string | number): any; + * Fetch gas estimates from the given URL. + * + * @param url - The gas estimate URL. +- * @param infuraAPIKey - The Infura API key used for infura API requests. + * @param clientId - The client ID used to identify to the API who is asking for estimates. + * @returns The gas estimates. + */ +-export declare function fetchGasEstimates(url: string, infuraAPIKey: string, clientId?: string): Promise; ++export declare function fetchGasEstimates(url: string, clientId?: string): Promise; + /** + * Hit the legacy MetaSwaps gasPrices estimate api and return the low, medium + * high values from that API. + * + * @param url - The URL to fetch gas price estimates from. +- * @param infuraAPIKey - The Infura API key used for infura API requests. + * @param clientId - The client ID used to identify to the API who is asking for estimates. + * @returns The gas price estimates. + */ +-export declare function fetchLegacyGasPriceEstimates(url: string, infuraAPIKey: string, clientId?: string): Promise; ++export declare function fetchLegacyGasPriceEstimates(url: string, clientId?: string): Promise; + /** + * Get a gas price estimate from the network using the `eth_gasPrice` method. + * +diff --git a/dist/types/gas-util.d.ts.map b/dist/types/gas-util.d.ts.map +index 8a24af1980fc16f7ecbe97222cab534b3c0f6c2c..ab417991781db0ad097218682c264d7aa738bf12 100644 +--- a/dist/types/gas-util.d.ts.map ++++ b/dist/types/gas-util.d.ts.map +@@ -1 +1 @@ +-{"version":3,"file":"gas-util.d.ts","sourceRoot":"","sources":["../../src/gas-util.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,QAAQ,MAAM,qBAAqB,CAAC;AAGhD,OAAO,KAAK,EACV,eAAe,EACf,mBAAmB,EACnB,yBAAyB,EAEzB,sBAAsB,EACvB,MAAM,oBAAoB,CAAC;AAI5B;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,OAI7D;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,eAAe,CAAC,CAyC1B;AAED;;;;;;;;GAQG;AACH,wBAAsB,4BAA4B,CAChD,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,sBAAsB,CAAC,CAcjC;AAED;;;;;GAKG;AACH,wBAAsB,wBAAwB,CAC5C,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,mBAAmB,CAAC,CAK9B;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,oBAAoB,EAAE,MAAM,EAC5B,YAAY,EAAE,MAAM,EACpB,eAAe,EAAE,eAAe,GAC/B,yBAAyB,CAoD3B"} +\ No newline at end of file ++{"version":3,"file":"gas-util.d.ts","sourceRoot":"","sources":["../../src/gas-util.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,QAAQ,MAAM,qBAAqB,CAAC;AAGhD,OAAO,KAAK,EACV,eAAe,EACf,mBAAmB,EACnB,yBAAyB,EAEzB,sBAAsB,EACvB,MAAM,oBAAoB,CAAC;AAI5B;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,OAI7D;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,MAAM,EACX,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,eAAe,CAAC,CAyC1B;AAED;;;;;;;GAOG;AACH,wBAAsB,4BAA4B,CAChD,GAAG,EAAE,MAAM,EACX,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,sBAAsB,CAAC,CAgBjC;AAED;;;;;GAKG;AACH,wBAAsB,wBAAwB,CAC5C,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,mBAAmB,CAAC,CAK9B;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,oBAAoB,EAAE,MAAM,EAC5B,YAAY,EAAE,MAAM,EACpB,eAAe,EAAE,eAAe,GAC/B,yBAAyB,CAoD3B"} +\ No newline at end of file diff --git a/.yarn/patches/@metamask-keyring-controller-npm-15.0.0-fa070ce311.patch b/.yarn/patches/@metamask-keyring-controller-npm-15.0.0-fa070ce311.patch deleted file mode 100644 index 27d866a74888..000000000000 --- a/.yarn/patches/@metamask-keyring-controller-npm-15.0.0-fa070ce311.patch +++ /dev/null @@ -1,120 +0,0 @@ -diff --git a/dist/chunk-52QZQQKP.mjs b/dist/chunk-52QZQQKP.mjs -index 934f432c8013a6af5303726e1495bed2335fa078..8de2560fe81dfc3dbfef83dd0079482306c425d9 100644 ---- a/dist/chunk-52QZQQKP.mjs -+++ b/dist/chunk-52QZQQKP.mjs -@@ -2,7 +2,8 @@ import { - __privateAdd, - __privateGet, - __privateMethod, -- __privateSet -+ __privateSet, -+ KeyringControllerError - } from "./chunk-NAAWD7HX.mjs"; - - // src/KeyringController.ts -@@ -582,6 +583,18 @@ var KeyringController = class extends BaseController { - }) - ); - serializedKeyrings.push(...__privateGet(this, _unsupportedKeyrings)); -+ /** -+ * ============================== PATCH INFORMATION ============================== -+ * The HD keyring is the default keyring for all wallets if this keyring is missing -+ * for some reason we should avoid saving the keyrings -+ * -+ * The upstream fix is here: https://github.com/MetaMask/core/pull/4168 -+ * -+ * This patch can be found on the core branch `extension-keyring-controller-v13-patch` -+ */ -+ if (!serializedKeyrings.some((keyring) => keyring.type === KeyringTypes.hd)) { -+ throw new Error(KeyringControllerError.NoHdKeyring); -+ } - let vault; - let newEncryptionKey; - if (__privateGet(this, _cacheEncryptionKey)) { -@@ -1087,9 +1100,16 @@ getKeyringBuilderForType_fn = function(type) { - }; - _addQRKeyring = new WeakSet(); - addQRKeyring_fn = async function() { -- const qrKeyring = await __privateMethod(this, _newKeyring, newKeyring_fn).call(this, "QR Hardware Wallet Device" /* qr */, { -- accounts: [] -- }); -+ /** -+ * Patch for @metamask/keyring-controller v13.0.0 -+ * Below code change will fix the issue 23804, The intial code added a empty accounts as argument when creating a new QR keyring. -+ * cause the new Keystone MetamaskKeyring default properties all are undefined during deserialise() process. -+ * Please refer to PR 23903 for detail. -+ * -+ * This patch can be found on the core branch `extension-keyring-controller-v13-patch` -+ */ -+ // @ts-expect-error See patch note -+ const qrKeyring = await __privateMethod(this, _newKeyring, newKeyring_fn).call(this, "QR Hardware Wallet Device"); - const accounts = await qrKeyring.getAccounts(); - await __privateMethod(this, _checkForDuplicate, checkForDuplicate_fn).call(this, "QR Hardware Wallet Device" /* qr */, accounts); - __privateGet(this, _keyrings).push(qrKeyring); -diff --git a/dist/chunk-CHLPTPMZ.js b/dist/chunk-CHLPTPMZ.js -index bef1a8e9dd5efe426f8aaaba1fe4501b124f7e87..7b48c000e54708da2a689e2d6cb1b61a279f1205 100644 ---- a/dist/chunk-CHLPTPMZ.js -+++ b/dist/chunk-CHLPTPMZ.js -@@ -50,6 +50,7 @@ var KeyringControllerError = /* @__PURE__ */ ((KeyringControllerError2) => { - KeyringControllerError2["ExpiredCredentials"] = "KeyringController - Encryption key and salt provided are expired"; - KeyringControllerError2["NoKeyringBuilder"] = "KeyringController - No keyringBuilder found for keyring"; - KeyringControllerError2["DataType"] = "KeyringController - Incorrect data type provided"; -+ KeyringControllerError2["NoHdKeyring"] = "KeyringController - No HD Keyring found"; - return KeyringControllerError2; - })(KeyringControllerError || {}); - -diff --git a/dist/chunk-GXM4O6HW.js b/dist/chunk-GXM4O6HW.js -index f7539e2e6354f418cbb095cc1a2cda01a5bdeae6..978f4426536c594568ecc56f1c27881db4bfa861 100644 ---- a/dist/chunk-GXM4O6HW.js -+++ b/dist/chunk-GXM4O6HW.js -@@ -582,6 +582,18 @@ var KeyringController = class extends _basecontroller.BaseController { - }) - ); - serializedKeyrings.push(..._chunkCHLPTPMZjs.__privateGet.call(void 0, this, _unsupportedKeyrings)); -+ /** -+ * ============================== PATCH INFORMATION ============================== -+ * The HD keyring is the default keyring for all wallets if this keyring is missing -+ * for some reason we should avoid saving the keyrings -+ * -+ * The upstream fix is here: https://github.com/MetaMask/core/pull/4168 -+ * -+ * This patch can be found on the core branch `extension-keyring-controller-v13-patch` -+ */ -+ if (!serializedKeyrings.some((keyring) => keyring.type === KeyringTypes.hd)) { -+ throw new Error(_chunkCHLPTPMZjs.KeyringControllerError.NoHdKeyring); -+ } - let vault; - let newEncryptionKey; - if (_chunkCHLPTPMZjs.__privateGet.call(void 0, this, _cacheEncryptionKey)) { -@@ -1087,9 +1099,16 @@ getKeyringBuilderForType_fn = function(type) { - }; - _addQRKeyring = new WeakSet(); - addQRKeyring_fn = async function() { -- const qrKeyring = await _chunkCHLPTPMZjs.__privateMethod.call(void 0, this, _newKeyring, newKeyring_fn).call(this, "QR Hardware Wallet Device" /* qr */, { -- accounts: [] -- }); -+ /** -+ * Patch for @metamask/keyring-controller v13.0.0 -+ * Below code change will fix the issue 23804, The intial code added a empty accounts as argument when creating a new QR keyring. -+ * cause the new Keystone MetamaskKeyring default properties all are undefined during deserialise() process. -+ * Please refer to PR 23903 for detail. -+ * -+ * This patch can be found on the core branch `extension-keyring-controller-v13-patch` -+ */ -+ // @ts-expect-error See patch note -+ const qrKeyring = await _chunkCHLPTPMZjs.__privateMethod.call(void 0, this, _newKeyring, newKeyring_fn).call(this, "QR Hardware Wallet Device"); - const accounts = await qrKeyring.getAccounts(); - await _chunkCHLPTPMZjs.__privateMethod.call(void 0, this, _checkForDuplicate, checkForDuplicate_fn).call(this, "QR Hardware Wallet Device" /* qr */, accounts); - _chunkCHLPTPMZjs.__privateGet.call(void 0, this, _keyrings).push(qrKeyring); -diff --git a/dist/chunk-NAAWD7HX.mjs b/dist/chunk-NAAWD7HX.mjs -index b5de23aabec9d502e8e6423480ffaaff26257bfc..a0c027a7c13828883ec5c05cbb7eab92c41f0dc3 100644 ---- a/dist/chunk-NAAWD7HX.mjs -+++ b/dist/chunk-NAAWD7HX.mjs -@@ -50,6 +50,7 @@ var KeyringControllerError = /* @__PURE__ */ ((KeyringControllerError2) => { - KeyringControllerError2["ExpiredCredentials"] = "KeyringController - Encryption key and salt provided are expired"; - KeyringControllerError2["NoKeyringBuilder"] = "KeyringController - No keyringBuilder found for keyring"; - KeyringControllerError2["DataType"] = "KeyringController - Incorrect data type provided"; -+ KeyringControllerError2["NoHdKeyring"] = "KeyringController - No HD Keyring found"; - return KeyringControllerError2; - })(KeyringControllerError || {}); - diff --git a/.yarn/patches/@metamask-nonce-tracker-npm-5.0.0-d81478218e.patch b/.yarn/patches/@metamask-nonce-tracker-npm-5.0.0-d81478218e.patch new file mode 100644 index 000000000000..fb9a5c1ef5f6 --- /dev/null +++ b/.yarn/patches/@metamask-nonce-tracker-npm-5.0.0-d81478218e.patch @@ -0,0 +1,30 @@ +diff --git a/dist/NonceTracker.js b/dist/NonceTracker.js +index 7cfa1e1962c930a425b3dbf6e1520450f0bf1747..2f4ff77a678fe0501e96a92d16f63a8e5f299401 100644 +--- a/dist/NonceTracker.js ++++ b/dist/NonceTracker.js +@@ -12,7 +12,6 @@ class NonceTracker { + constructor(opts) { + this.provider = opts.provider; + this.blockTracker = opts.blockTracker; +- this.web3 = new Web3Provider(opts.provider); + this.getPendingTransactions = opts.getPendingTransactions; + this.getConfirmedTransactions = opts.getConfirmedTransactions; + this.lockMap = {}; +@@ -96,7 +95,7 @@ class NonceTracker { + // we need to make sure our base count + // and pending count are from the same block + const blockNumber = await this.blockTracker.getLatestBlock(); +- const baseCount = await this.web3.getTransactionCount(address, blockNumber); ++ const baseCount = await new Web3Provider(this.provider).getTransactionCount(address, blockNumber); + assert_1.default(Number.isInteger(baseCount), `nonce-tracker - baseCount is not an integer - got: (${typeof baseCount}) "${baseCount}"`); + return { + name: 'network', +diff --git a/dist/NonceTracker.js.map b/dist/NonceTracker.js.map +index 70e16afc468187dddb2ba1a6752e60c0aadb0f82..0e9d43aed1f7c5ccc2e16f91318aaab88da104ea 100644 +--- a/dist/NonceTracker.js.map ++++ b/dist/NonceTracker.js.map +@@ -1 +1 @@ +-{"version":3,"file":"NonceTracker.js","sourceRoot":"","sources":["../src/NonceTracker.ts"],"names":[],"mappings":";;;;;;AAAA,oDAA4B;AAC5B,6CAAoC;AAGpC,qGAAqG;AACrG,MAAM,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAAC;AAqF7D,MAAa,YAAY;IAavB,YAAY,IAAyB;QACnC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC9B,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC;QACtC,IAAI,CAAC,IAAI,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC5C,IAAI,CAAC,sBAAsB,GAAG,IAAI,CAAC,sBAAsB,CAAC;QAC1D,IAAI,CAAC,wBAAwB,GAAG,IAAI,CAAC,wBAAwB,CAAC;QAC9D,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;IACpB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,aAAa;QACjB,MAAM,WAAW,GAAU,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;QACvD,0BAA0B;QAC1B,MAAM,WAAW,GAAiB,MAAM,WAAW,CAAC,OAAO,EAAE,CAAC;QAC9D,OAAO,EAAE,WAAW,EAAE,CAAC;IACzB,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,YAAY,CAAC,OAAe;QAChC,0BAA0B;QAC1B,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC9B,kCAAkC;QAClC,MAAM,WAAW,GAAiB,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QACjE,IAAI;YACF,yCAAyC;YACzC,MAAM,kBAAkB,GACtB,MAAM,IAAI,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;YAC3C,MAAM,uBAAuB,GAC3B,IAAI,CAAC,2BAA2B,CAAC,OAAO,CAAC,CAAC;YAC5C,MAAM,gBAAgB,GAAW,kBAAkB,CAAC,KAAK,CAAC;YAC1D,MAAM,gBAAgB,GAAW,IAAI,CAAC,GAAG,CACvC,gBAAgB,EAChB,uBAAuB,CACxB,CAAC;YAEF,MAAM,UAAU,GAAkB,IAAI,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC;YACvE,MAAM,gBAAgB,GACpB,IAAI,CAAC,yBAAyB,CAAC,UAAU,EAAE,gBAAgB,CAAC,CAAC;YAE/D,MAAM,YAAY,GAAiB;gBACjC,MAAM,EAAE;oBACN,uBAAuB;oBACvB,gBAAgB;oBAChB,gBAAgB;iBACjB;gBACD,KAAK,EAAE,gBAAgB;gBACvB,OAAO,EAAE,kBAAkB;aAC5B,CAAC;YAEF,MAAM,SAAS,GAAW,IAAI,CAAC,GAAG,CAChC,kBAAkB,CAAC,KAAK,EACxB,gBAAgB,CAAC,KAAK,CACvB,CAAC;YACF,gBAAM,CACJ,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,EAC3B,uDAAuD,OAAO,SAAS,MAAM,SAAS,GAAG,CAC1F,CAAC;YAEF,8BAA8B;YAC9B,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC;SACjD;QAAC,OAAO,GAAG,EAAE;YACZ,wCAAwC;YACxC,WAAW,EAAE,CAAC;YACd,MAAM,GAAG,CAAC;SACX;IACH,CAAC;IAED,KAAK,CAAC,gBAAgB;QACpB,MAAM,WAAW,GAAU,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;QACvD,MAAM,WAAW,GAAiB,MAAM,WAAW,CAAC,OAAO,EAAE,CAAC;QAC9D,WAAW,EAAE,CAAC;IAChB,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,MAAc;QAC7B,MAAM,KAAK,GAAU,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QAC/C,MAAM,WAAW,GAAiB,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC;QACxD,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,YAAY,CAAC,MAAc;QACzB,IAAI,KAAK,GAAU,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACxC,IAAI,CAAC,KAAK,EAAE;YACV,KAAK,GAAG,IAAI,mBAAK,EAAE,CAAC;YACpB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC;SAC9B;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,oBAAoB,CAAC,OAAe;QACxC,uBAAuB;QACvB,sCAAsC;QACtC,4CAA4C;QAC5C,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE,CAAC;QAC7D,MAAM,SAAS,GAAW,MAAM,IAAI,CAAC,IAAI,CAAC,mBAAmB,CAC3D,OAAO,EACP,WAAW,CACZ,CAAC;QACF,gBAAM,CACJ,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,EAC3B,uDAAuD,OAAO,SAAS,MAAM,SAAS,GAAG,CAC1F,CAAC;QACF,OAAO;YACL,IAAI,EAAE,SAAS;YACf,KAAK,EAAE,SAAS;YAChB,OAAO,EAAE,EAAE,WAAW,EAAE,SAAS,EAAE;SACpC,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,2BAA2B,CAAC,OAAe;QACzC,MAAM,qBAAqB,GACzB,IAAI,CAAC,wBAAwB,CAAC,OAAO,CAAC,CAAC;QACzC,MAAM,OAAO,GAAW,IAAI,CAAC,gBAAgB,CAAC,qBAAqB,CAAC,CAAC;QACrE,OAAO,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACrD,CAAC;IAED;;;;OAIG;IACH,gBAAgB,CAAC,MAAqB;QACpC,MAAM,MAAM,GAAa,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;YAC7C,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC;YAClC,gBAAM,CAAC,OAAO,KAAK,KAAK,QAAQ,EAAE,8BAA8B,CAAC,CAAC;YAClE,OAAO,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC7B,CAAC,CAAC,CAAC;QACH,MAAM,YAAY,GAAW,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC1D,OAAO,YAAY,CAAC;IACtB,CAAC;IAED;;;;;;OAMG;IACH,yBAAyB,CACvB,MAAqB,EACrB,UAAkB;QAElB,MAAM,MAAM,GAAa,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;YAC7C,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC;YAClC,gBAAM,CAAC,OAAO,KAAK,KAAK,QAAQ,EAAE,8BAA8B,CAAC,CAAC;YAClE,OAAO,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC7B,CAAC,CAAC,CAAC;QAEH,IAAI,OAAO,GAAW,UAAU,CAAC;QACjC,OAAO,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE;YAC/B,OAAO,IAAI,CAAC,CAAC;SACd;QAED,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC;IAC7E,CAAC;CACF;AA3LD,oCA2LC"} +\ No newline at end of file ++{"version":3,"file":"NonceTracker.js","sourceRoot":"","sources":["../src/NonceTracker.ts"],"names":[],"mappings":";;;;;;AAAA,oDAA4B;AAC5B,6CAAoC;AAGpC,qGAAqG;AACrG,MAAM,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAAC;AAqF7D,MAAa,YAAY;IAavB,YAAY,IAAyB;QACnC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC9B,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC;QACtC,IAAI,CAAC,sBAAsB,GAAG,IAAI,CAAC,sBAAsB,CAAC;QAC1D,IAAI,CAAC,wBAAwB,GAAG,IAAI,CAAC,wBAAwB,CAAC;QAC9D,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;IACpB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,aAAa;QACjB,MAAM,WAAW,GAAU,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;QACvD,0BAA0B;QAC1B,MAAM,WAAW,GAAiB,MAAM,WAAW,CAAC,OAAO,EAAE,CAAC;QAC9D,OAAO,EAAE,WAAW,EAAE,CAAC;IACzB,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,YAAY,CAAC,OAAe;QAChC,0BAA0B;QAC1B,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC9B,kCAAkC;QAClC,MAAM,WAAW,GAAiB,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QACjE,IAAI;YACF,yCAAyC;YACzC,MAAM,kBAAkB,GACtB,MAAM,IAAI,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;YAC3C,MAAM,uBAAuB,GAC3B,IAAI,CAAC,2BAA2B,CAAC,OAAO,CAAC,CAAC;YAC5C,MAAM,gBAAgB,GAAW,kBAAkB,CAAC,KAAK,CAAC;YAC1D,MAAM,gBAAgB,GAAW,IAAI,CAAC,GAAG,CACvC,gBAAgB,EAChB,uBAAuB,CACxB,CAAC;YAEF,MAAM,UAAU,GAAkB,IAAI,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC;YACvE,MAAM,gBAAgB,GACpB,IAAI,CAAC,yBAAyB,CAAC,UAAU,EAAE,gBAAgB,CAAC,CAAC;YAE/D,MAAM,YAAY,GAAiB;gBACjC,MAAM,EAAE;oBACN,uBAAuB;oBACvB,gBAAgB;oBAChB,gBAAgB;iBACjB;gBACD,KAAK,EAAE,gBAAgB;gBACvB,OAAO,EAAE,kBAAkB;aAC5B,CAAC;YAEF,MAAM,SAAS,GAAW,IAAI,CAAC,GAAG,CAChC,kBAAkB,CAAC,KAAK,EACxB,gBAAgB,CAAC,KAAK,CACvB,CAAC;YACF,gBAAM,CACJ,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,EAC3B,uDAAuD,OAAO,SAAS,MAAM,SAAS,GAAG,CAC1F,CAAC;YAEF,8BAA8B;YAC9B,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC;SACjD;QAAC,OAAO,GAAG,EAAE;YACZ,wCAAwC;YACxC,WAAW,EAAE,CAAC;YACd,MAAM,GAAG,CAAC;SACX;IACH,CAAC;IAED,KAAK,CAAC,gBAAgB;QACpB,MAAM,WAAW,GAAU,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;QACvD,MAAM,WAAW,GAAiB,MAAM,WAAW,CAAC,OAAO,EAAE,CAAC;QAC9D,WAAW,EAAE,CAAC;IAChB,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,MAAc;QAC7B,MAAM,KAAK,GAAU,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QAC/C,MAAM,WAAW,GAAiB,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC;QACxD,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,YAAY,CAAC,MAAc;QACzB,IAAI,KAAK,GAAU,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACxC,IAAI,CAAC,KAAK,EAAE;YACV,KAAK,GAAG,IAAI,mBAAK,EAAE,CAAC;YACpB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC;SAC9B;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,oBAAoB,CAAC,OAAe;QACxC,uBAAuB;QACvB,sCAAsC;QACtC,4CAA4C;QAC5C,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE,CAAC;QAC7D,MAAM,SAAS,GAAW,MAAM,IAAI,YAAY,CAC9C,IAAI,CAAC,QAAQ,CACd,CAAC,mBAAmB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;QAC5C,gBAAM,CACJ,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,EAC3B,uDAAuD,OAAO,SAAS,MAAM,SAAS,GAAG,CAC1F,CAAC;QACF,OAAO;YACL,IAAI,EAAE,SAAS;YACf,KAAK,EAAE,SAAS;YAChB,OAAO,EAAE,EAAE,WAAW,EAAE,SAAS,EAAE;SACpC,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,2BAA2B,CAAC,OAAe;QACzC,MAAM,qBAAqB,GACzB,IAAI,CAAC,wBAAwB,CAAC,OAAO,CAAC,CAAC;QACzC,MAAM,OAAO,GAAW,IAAI,CAAC,gBAAgB,CAAC,qBAAqB,CAAC,CAAC;QACrE,OAAO,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACrD,CAAC;IAED;;;;OAIG;IACH,gBAAgB,CAAC,MAAqB;QACpC,MAAM,MAAM,GAAa,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;YAC7C,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC;YAClC,gBAAM,CAAC,OAAO,KAAK,KAAK,QAAQ,EAAE,8BAA8B,CAAC,CAAC;YAClE,OAAO,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC7B,CAAC,CAAC,CAAC;QACH,MAAM,YAAY,GAAW,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC1D,OAAO,YAAY,CAAC;IACtB,CAAC;IAED;;;;;;OAMG;IACH,yBAAyB,CACvB,MAAqB,EACrB,UAAkB;QAElB,MAAM,MAAM,GAAa,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;YAC7C,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC;YAClC,gBAAM,CAAC,OAAO,KAAK,KAAK,QAAQ,EAAE,8BAA8B,CAAC,CAAC;YAClE,OAAO,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC7B,CAAC,CAAC,CAAC;QAEH,IAAI,OAAO,GAAW,UAAU,CAAC;QACjC,OAAO,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE;YAC/B,OAAO,IAAI,CAAC,CAAC;SACd;QAED,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC;IAC7E,CAAC;CACF;AAzLD,oCAyLC"} +\ No newline at end of file diff --git a/README.md b/README.md index efa0be51019b..5c3e8f5823c1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # MetaMask Browser Extension -You can find the latest version of MetaMask on [our official website](https://metamask.io/). For help using MetaMask, visit our [User Support Site](https://metamask.zendesk.com/hc/en-us). +You can find the latest version of MetaMask on [our official website](https://metamask.io/). For help using MetaMask, visit our [User Support Site](https://support.metamask.io/). For [general questions](https://community.metamask.io/c/learn/26), [feature requests](https://community.metamask.io/c/feature-requests-ideas/13), or [developer questions](https://community.metamask.io/c/developer-questions/11), visit our [Community Forum](https://community.metamask.io/). diff --git a/app/_locales/am/messages.json b/app/_locales/am/messages.json index a4a0dc58e868..cb193bc11c36 100644 --- a/app/_locales/am/messages.json +++ b/app/_locales/am/messages.json @@ -69,9 +69,6 @@ "asset": { "message": "ሐብት" }, - "attemptingConnect": { - "message": "ከ blockchain ጋር ለመገናኘት መሞከር።" - }, "attributions": { "message": "አይነታ" }, diff --git a/app/_locales/ar/messages.json b/app/_locales/ar/messages.json index bc33690c7945..e262cebf3537 100644 --- a/app/_locales/ar/messages.json +++ b/app/_locales/ar/messages.json @@ -79,9 +79,6 @@ "asset": { "message": "الأصل" }, - "attemptingConnect": { - "message": "محاولة الاتصال بسلسلة الكتل." - }, "attributions": { "message": "الصفات" }, diff --git a/app/_locales/bg/messages.json b/app/_locales/bg/messages.json index cfe0f8de68a3..2169f13ecf9d 100644 --- a/app/_locales/bg/messages.json +++ b/app/_locales/bg/messages.json @@ -75,9 +75,6 @@ "asset": { "message": "Актив" }, - "attemptingConnect": { - "message": "Опит за свързване с blockchain." - }, "attributions": { "message": "Функции" }, diff --git a/app/_locales/bn/messages.json b/app/_locales/bn/messages.json index 592a9a593f28..29e06173be17 100644 --- a/app/_locales/bn/messages.json +++ b/app/_locales/bn/messages.json @@ -75,9 +75,6 @@ "asset": { "message": "সম্পদ" }, - "attemptingConnect": { - "message": "ব্লকচেনে সংযোগ করার চেষ্টা করছে।" - }, "attributions": { "message": "গুণাবলী" }, diff --git a/app/_locales/ca/messages.json b/app/_locales/ca/messages.json index 93555423be6d..4393ea21f108 100644 --- a/app/_locales/ca/messages.json +++ b/app/_locales/ca/messages.json @@ -75,9 +75,6 @@ "asset": { "message": "Actius" }, - "attemptingConnect": { - "message": "Intentant conectar a blockchain." - }, "attributions": { "message": "Atribucions" }, diff --git a/app/_locales/cs/messages.json b/app/_locales/cs/messages.json index fc050b199d2d..f67d21b5cb52 100644 --- a/app/_locales/cs/messages.json +++ b/app/_locales/cs/messages.json @@ -39,9 +39,6 @@ "approved": { "message": "Schváleno" }, - "attemptingConnect": { - "message": "Pokouším se připojit k blockchainu." - }, "attributions": { "message": "Zásluhy" }, diff --git a/app/_locales/da/messages.json b/app/_locales/da/messages.json index 9d89b573a536..f5d7c9c89c3e 100644 --- a/app/_locales/da/messages.json +++ b/app/_locales/da/messages.json @@ -75,9 +75,6 @@ "asset": { "message": "Aktiv" }, - "attemptingConnect": { - "message": "Forsøger at oprette forbindelse til blokkæden." - }, "attributions": { "message": "Tilskrivninger" }, diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index eaa6a77b362d..2500ff05f254 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -524,9 +524,6 @@ "attemptToCancelSwapForFree": { "message": "Versuch, den Swap kostenlos zu stornieren" }, - "attemptingConnect": { - "message": "Versuch einer Verbindung zur Blockchain." - }, "attributions": { "message": "Zuschreibungen" }, @@ -2453,9 +2450,6 @@ "loading": { "message": "Wird geladen ..." }, - "loadingNFTs": { - "message": "NFTs werden geladen ..." - }, "loadingScreenHardwareWalletMessage": { "message": "Bitte schließen Sie die Transaktion im Hardware-Wallet ab." }, @@ -3212,24 +3206,12 @@ "onboardingMetametricsAgree": { "message": "Ich stimme zu" }, - "onboardingMetametricsAllowOptOutLegacy": { - "message": "Erlaubt Ihnen immer die Abmeldung über Einstellungen" - }, - "onboardingMetametricsDataTermsLegacy": { - "message": "Diese Daten werden gesammelt und sind daher im Rahmen der Datenschutz-Grundverordnung (EU) 2016/679 anonym." - }, "onboardingMetametricsDescription": { "message": "Wir würden gerne grundlegende Nutzungs- und Diagnosedaten sammeln, um MetaMask zu verbessern. Sie sollten wissen, dass wir die Daten, die Sie uns hier zur Verfügung stellen, niemals verkaufen." }, "onboardingMetametricsDescription2": { "message": "Wenn wir Metriken sammeln, wird es immer wie folgt sein ..." }, - "onboardingMetametricsDescription2Legacy": { - "message": "MetaMask wird ..." - }, - "onboardingMetametricsDescriptionLegacy": { - "message": "MetaMask möchte Nutzungsdaten sammeln, um ein besseres Verständnis zu erhalten, wie unsere Nutzer mit MetaMask interagieren. Diese Daten werden verwendet, um Dienste anzubieten, was auf Ihrer Nutzung basierte Dienstverbesserungen einschließt." - }, "onboardingMetametricsDisagree": { "message": "Nein, danke!" }, @@ -3237,19 +3219,9 @@ "message": "Wir werden Sie informieren, wenn wir beschließen, diese Daten für andere Zwecke zu verwenden. Für weitere Informationen können Sie unsere $1 einsehen. Vergessen Sie nicht, dass Sie jederzeit zu Einstellungen gehen und sich abmelden können.", "description": "$1 represents `onboardingMetametricsInfuraTermsPolicy`" }, - "onboardingMetametricsInfuraTermsLegacy": { - "message": "*Wenn Sie Infura als Standard-RPC-Anbieter in MetaMask vewenden, speichert Infura Ihre IP-Adresse und Ihre Etherum-Wallet-Adresse, wenn Sie eine Transaktion senden. Wir speichern diese Daten in keinster Weise in unserem System, um sie miteinander in Verbindung zu bringen. Für weitere Informationen darüber, wie MetaMask und Infura in Hinischt auf Datenspeicherung zusammenarbeiten, sehen Sie sich bitte unser Update $1 an. Für mehr Informationen bezüglich unseren allgemeinen Datenschutzpraktiken, sehen Sie sich bitte unsere $2 an.", - "description": "$1 represents `onboardingMetametricsInfuraTermsPolicyLink`, $2 represents `onboardingMetametricsInfuraTermsPolicy`" - }, "onboardingMetametricsInfuraTermsPolicy": { "message": "Datenschutzrichtlinie" }, - "onboardingMetametricsInfuraTermsPolicyLegacy": { - "message": "Datenschutzerklärung hier" - }, - "onboardingMetametricsInfuraTermsPolicyLinkLegacy": { - "message": "hier" - }, "onboardingMetametricsModalTitle": { "message": "Benutzerdefiniertes Netzwerk hinzufügen" }, @@ -3267,17 +3239,6 @@ "onboardingMetametricsNeverCollectIPEmphasis": { "message": "Allgemein:" }, - "onboardingMetametricsNeverCollectIPLegacy": { - "message": "$1 speichert Ihre vollständige IP-Adresse*", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsNeverCollectLegacy": { - "message": "$1 speichert Daten, die wir nicht benötigen, um die Dienstleistung zur Verfügung zu stellen (wie zum Beispiel Schlüssel, Adresse, Transaktions-Hashs oder Guthaben)", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsNeverEmphasisLegacy": { - "message": "Nie" - }, "onboardingMetametricsNeverSellData": { "message": "$1 Sie können jederzeit über die Einstellungen entscheiden, ob Sie Ihre Nutzungsdaten freigeben oder löschen möchten.", "description": "$1 represents `onboardingMetametricsNeverSellDataEmphasis`" @@ -3285,13 +3246,6 @@ "onboardingMetametricsNeverSellDataEmphasis": { "message": "Optional:" }, - "onboardingMetametricsNeverSellDataLegacy": { - "message": "$1 Daten verkaufen. Niemals!", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsSendAnonymizeLegacy": { - "message": "Anonymisierte Ereignisse für Klicks und Seitenaufrufe senden" - }, "onboardingMetametricsTitle": { "message": "Helfen Sie uns, MetaMask zu verbessern." }, diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json index 8cfbc2e3af61..bff686407193 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -524,9 +524,6 @@ "attemptToCancelSwapForFree": { "message": "Προσπάθεια ακύρωσης των ανταλλαγών δωρεάν" }, - "attemptingConnect": { - "message": "Προσπάθεια σύνδεσης στο blockchain." - }, "attributions": { "message": "Αποδόσεις" }, @@ -2453,9 +2450,6 @@ "loading": { "message": "Φόρτωση..." }, - "loadingNFTs": { - "message": "Φόρτωση των NFT..." - }, "loadingScreenHardwareWalletMessage": { "message": "Ολοκληρώστε τη συναλλαγή στο πορτοφόλι υλικού." }, @@ -3212,24 +3206,12 @@ "onboardingMetametricsAgree": { "message": "Συμφωνώ" }, - "onboardingMetametricsAllowOptOutLegacy": { - "message": "Σας επιτρέπεται πάντα να εξαιρεθείτε μέσω των Ρυθμίσεων" - }, - "onboardingMetametricsDataTermsLegacy": { - "message": "Τα δεδομένα αυτά είναι συγκεντρωτικά και συνεπώς ανώνυμα για τους σκοπούς του Γενικού Κανονισμού για την Προστασία Δεδομένων (ΕΕ) 2016/679." - }, "onboardingMetametricsDescription": { "message": "Θέλουμε να συλλέξουμε βασικά δεδομένα χρήσης και διάγνωσης για να βελτιώσουμε το MetaMask. Λάβετε υπόψη ότι δεν πουλάμε ποτέ τα δεδομένα που μας παρέχετε εδώ." }, "onboardingMetametricsDescription2": { "message": "Όταν συγκεντρώνουμε μετρήσεις, θα είναι πάντα..." }, - "onboardingMetametricsDescription2Legacy": { - "message": "Το MetaMask θα..." - }, - "onboardingMetametricsDescriptionLegacy": { - "message": "Το MetaMask θα ήθελε να συλλέγει δεδομένα χρήσης για να κατανοήσει καλύτερα τον τρόπο με τον οποίο οι χρήστες μας αλληλεπιδρούν με το MetaMask. Τα δεδομένα αυτά θα χρησιμοποιηθούν για την παροχή της υπηρεσίας, η οποία περιλαμβάνει τη βελτίωση της υπηρεσίας με βάση τη χρήση σας." - }, "onboardingMetametricsDisagree": { "message": "Όχι, ευχαριστώ" }, @@ -3237,19 +3219,9 @@ "message": "Θα σας ενημερώσουμε εάν αποφασίσουμε να χρησιμοποιήσουμε αυτά τα δεδομένα για άλλους σκοπούς. Για περισσότερες πληροφορίες, μπορείτε να ανατρέξετε στην $1. Να θυμάστε ότι μπορείτε να μεταβείτε στις ρυθμίσεις και να εξαιρεθείτε ανά πάσα στιγμή.", "description": "$1 represents `onboardingMetametricsInfuraTermsPolicy`" }, - "onboardingMetametricsInfuraTermsLegacy": { - "message": "* Όταν χρησιμοποιείτε την Infura ως τον προεπιλεγμένο πάροχο RPC στο MetaMask, η Infura θα συλλέγει τη διεύθυνση IP σας και τη διεύθυνση του πορτοφολιού σας στο Ethereum όταν αποστέλλετε μια συναλλαγή. Δεν αποθηκεύουμε αυτές τις πληροφορίες με τρόπο που να επιτρέπει στα συστήματά μας να συσχετίζουν αυτά τα δύο δεδομένα. Για περισσότερες πληροφορίες σχετικά με τον τρόπο με τον οποίο αλληλεπιδρούν το MetaMask και η Infura από την πλευρά της συλλογής δεδομένων, δείτε την ενημέρωσή μας $1. Για περισσότερες πληροφορίες σχετικά με τις πρακτικές απορρήτου μας γενικά, δείτε την ενημέρωσή μας $2.", - "description": "$1 represents `onboardingMetametricsInfuraTermsPolicyLink`, $2 represents `onboardingMetametricsInfuraTermsPolicy`" - }, "onboardingMetametricsInfuraTermsPolicy": { "message": "Πολιτική Απορρήτου" }, - "onboardingMetametricsInfuraTermsPolicyLegacy": { - "message": "Πολιτική Απορρήτου εδώ" - }, - "onboardingMetametricsInfuraTermsPolicyLinkLegacy": { - "message": "εδώ" - }, "onboardingMetametricsModalTitle": { "message": "Προσθήκη προσαρμοσμένου δικτύου" }, @@ -3267,17 +3239,6 @@ "onboardingMetametricsNeverCollectIPEmphasis": { "message": "Γενικές:" }, - "onboardingMetametricsNeverCollectIPLegacy": { - "message": "To $1 συλλέγει την πλήρη διεύθυνση IP σας*", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsNeverCollectLegacy": { - "message": "To $1 συλλέγει πληροφορίες που δεν χρειαζόμαστε για την παροχή της υπηρεσίας (όπως κλειδιά, διευθύνσεις, αναλύσεις συναλλαγών ή υπόλοιπα)", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsNeverEmphasisLegacy": { - "message": "Ποτέ" - }, "onboardingMetametricsNeverSellData": { "message": "$1 εσείς αποφασίζετε αν θέλετε να κοινοποιήσετε ή να διαγράψετε τα δεδομένα χρήσης σας μέσω των ρυθμίσεων ανά πάσα στιγμή.", "description": "$1 represents `onboardingMetametricsNeverSellDataEmphasis`" @@ -3285,13 +3246,6 @@ "onboardingMetametricsNeverSellDataEmphasis": { "message": "Προαιρετικές:" }, - "onboardingMetametricsNeverSellDataLegacy": { - "message": "To $1 πουλάει δεδομένα. Ποτέ!", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsSendAnonymizeLegacy": { - "message": "Αποστολή ανώνυμων συμβάντων κλικ και προβολής ιστοσελίδων" - }, "onboardingMetametricsTitle": { "message": "Βοηθήστε μας να βελτιώσουμε το MetaMask" }, diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 4d251becd20a..53961ff86b35 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -289,6 +289,9 @@ "addNfts": { "message": "Add NFTs" }, + "addRpcUrl": { + "message": "Add RPC URL" + }, "addSnapAccountToggle": { "message": "Enable \"Add account Snap (Beta)\"" }, @@ -332,6 +335,18 @@ "advancedConfiguration": { "message": "Advanced configuration" }, + "advancedDetailsDataDesc": { + "message": "Data" + }, + "advancedDetailsHexDesc": { + "message": "Hex" + }, + "advancedDetailsNonceDesc": { + "message": "Nonce" + }, + "advancedDetailsNonceTooltip": { + "message": "This is the transaction number of an account. Nonce for the first transaction is 0 and it increases in sequential order." + }, "advancedGasFeeDefaultOptIn": { "message": "Save these values as my default for the $1 network.", "description": "$1 is the current network name." @@ -355,6 +370,18 @@ "alert": { "message": "Alert" }, + "alertActionBuy": { + "message": "Buy ETH" + }, + "alertActionUpdateGas": { + "message": "Update gas limit" + }, + "alertActionUpdateGasFee": { + "message": "Update fee" + }, + "alertActionUpdateGasFeeLevel": { + "message": "Update gas options" + }, "alertBannerMultipleAlertsDescription": { "message": "If you approve this request, a third party known for scams might take all your assets." }, @@ -364,6 +391,30 @@ "alertDisableTooltip": { "message": "This can be changed in \"Settings > Alerts\"" }, + "alertMessageGasEstimateFailed": { + "message": "We’re unable to provide an accurate fee and this estimate might be high. We suggest you to input a custom gas limit, but there’s a risk the transaction will still fail." + }, + "alertMessageGasFeeLow": { + "message": "When choosing a low fee, expect slower transactions and longer wait times. For faster transactions, choose Market or Aggressive fee options." + }, + "alertMessageGasTooLow": { + "message": "To continue with this transaction, you’ll need to increase the gas limit to 21000 or higher." + }, + "alertMessageInsufficientBalance": { + "message": "You do not have enough ETH in your account to pay for transaction fees." + }, + "alertMessageNetworkBusy": { + "message": "Gas prices are high and estimates are less accurate." + }, + "alertMessageNoGasPrice": { + "message": "We can’t move forward with this transaction until you manually update the fee." + }, + "alertMessagePendingTransactions": { + "message": "This transaction won’t go through until a previous transaction is complete. Learn how to cancel or speed up a transaction." + }, + "alertMessageSigningOrSubmitting": { + "message": "This transaction will only go through once your previous transaction is complete." + }, "alertModalAcknowledge": { "message": "I have acknowledged the risk and still want to proceed" }, @@ -373,6 +424,27 @@ "alertModalReviewAllAlerts": { "message": "Review all alerts" }, + "alertReasonGasEstimateFailed": { + "message": "Inaccurate fee" + }, + "alertReasonGasFeeLow": { + "message": "Slow speed" + }, + "alertReasonGasTooLow": { + "message": "Low gas limit" + }, + "alertReasonInsufficientBalance": { + "message": "Insufficient funds" + }, + "alertReasonNetworkBusy": { + "message": "Network is busy" + }, + "alertReasonNoGasPrice": { + "message": "Fee estimate unavailable" + }, + "alertReasonPendingTransactions": { + "message": "Pending transaction" + }, "alertSettingsUnconnectedAccount": { "message": "Browsing a website with an unconnected account selected" }, @@ -417,6 +489,9 @@ "allow": { "message": "Allow" }, + "allowMetaMaskToDetectNFTs": { + "message": "Allow MetaMask to detect and display your NFTs with autodetection. You’ll be able to:" + }, "allowMetaMaskToDetectTokens": { "message": "Allow MetaMask to detect and display your tokens with autodetection. You’ll be able to:" }, @@ -539,9 +614,6 @@ "attemptToCancelSwapForFree": { "message": "Attempt to cancel swap for free" }, - "attemptingConnect": { - "message": "Attempting to connect to blockchain." - }, "attributions": { "message": "Attributions" }, @@ -894,6 +966,12 @@ "confirmConnectionTitle": { "message": "Confirm connection to $1" }, + "confirmFieldPaymaster": { + "message": "Fee paid by" + }, + "confirmFieldTooltipPaymaster": { + "message": "The fee for this transaction will be paid by the paymaster smart contract." + }, "confirmPassword": { "message": "Confirm password" }, @@ -906,12 +984,18 @@ "confirmTitleDescPermitSignature": { "message": "This site wants permission to spend your tokens." }, + "confirmTitleDescSIWESignature": { + "message": "A site wants you to sign in to prove you own this account." + }, "confirmTitleDescSignature": { "message": "Only confirm this message if you approve the content and trust the requesting site." }, "confirmTitlePermitSignature": { "message": "Spending cap request" }, + "confirmTitleSIWESignature": { + "message": "Sign-in request" + }, "confirmTitleSignature": { "message": "Signature request" }, @@ -1354,6 +1438,9 @@ "decryptRequest": { "message": "Decrypt request" }, + "defaultRpcUrl": { + "message": "Default RPC URL" + }, "delete": { "message": "Delete" }, @@ -1473,6 +1560,9 @@ "displayNftMediaDescription": { "message": "Displaying NFT media and data exposes your IP address to OpenSea or other third parties. This can allow attackers to associate your IP address with your Ethereum address. NFT autodetection relies on this setting, and won't be available when this is turned off." }, + "diveStraightIntoUsingYourNFTs": { + "message": "Dive straight into using your NFTs" + }, "diveStraightIntoUsingYourTokens": { "message": "Dive straight into using your tokens" }, @@ -1616,6 +1706,9 @@ "enableFromSettings": { "message": " Enable it from Settings." }, + "enableNftAutoDetection": { + "message": "Enable NFT autodetection" + }, "enableSnap": { "message": "Enable" }, @@ -1629,6 +1722,9 @@ "enabled": { "message": "Enabled" }, + "enabledNetworks": { + "message": "Enabled networks" + }, "encryptionPublicKeyNotice": { "message": "$1 would like your public encryption key. By consenting, this site will be able to compose encrypted messages to you.", "description": "$1 is the web3 site name" @@ -1775,7 +1871,7 @@ "message": "Proposed nicknames" }, "externalNameSourcesSettingDescription": { - "message": "We’ll fetch proposed nicknames for addresses you interact with from third-party sources like Etherscan, Infura, and Lens Protocol. These sources will be able to see the those addresses and your IP address. Your account address won’t be exposed to third parties." + "message": "We’ll fetch proposed nicknames for addresses you interact with from third-party sources like Etherscan, Infura, and Lens Protocol. These sources will be able to see those addresses and your IP address. Your account address won’t be exposed to third parties." }, "failed": { "message": "Failed" @@ -2114,6 +2210,9 @@ "imToken": { "message": "imToken" }, + "immediateAccessToYourNFTs": { + "message": "Immediately access your NFTs" + }, "immediateAccessToYourTokens": { "message": "Immediate access to your tokens" }, @@ -2524,9 +2623,6 @@ "loading": { "message": "Loading..." }, - "loadingNFTs": { - "message": "Loading NFTs..." - }, "loadingScreenHardwareWalletMessage": { "message": "Please complete the transaction on the hardware wallet." }, @@ -2861,6 +2957,9 @@ "networkNameZkSyncEra": { "message": "zkSync Era" }, + "networkOptions": { + "message": "Network options" + }, "networkProvider": { "message": "Network provider" }, @@ -2929,6 +3028,9 @@ "newNetworkAdded": { "message": "“$1” was successfully added!" }, + "newNetworkEdited": { + "message": "“$1” was successfully edited!" + }, "newNftAddedMessage": { "message": "NFT was successfully added!" }, @@ -2965,6 +3067,9 @@ "nftAlreadyAdded": { "message": "NFT has already been added." }, + "nftAutoDetectionEnabled": { + "message": "NFT autodetection enabled" + }, "nftDisclaimer": { "message": "Disclaimer: MetaMask pulls the media file from the source url. This url sometimes gets changed by the marketplace on which the NFT was minted." }, @@ -3330,24 +3435,12 @@ "onboardingMetametricsAgree": { "message": "I agree" }, - "onboardingMetametricsAllowOptOutLegacy": { - "message": "Always allow you to opt-out via Settings" - }, - "onboardingMetametricsDataTermsLegacy": { - "message": "This data is aggregated and is therefore anonymous for the purposes of General Data Protection Regulation (EU) 2016/679." - }, "onboardingMetametricsDescription": { "message": "We’d like to gather basic usage and diagnostics data to improve MetaMask. Know that we never sell the data you provide here." }, "onboardingMetametricsDescription2": { "message": "When we gather metrics, it will always be..." }, - "onboardingMetametricsDescription2Legacy": { - "message": "MetaMask will..." - }, - "onboardingMetametricsDescriptionLegacy": { - "message": "MetaMask would like to gather usage data to better understand how our users interact with MetaMask. This data will be used to provide the service, which includes improving the service based on your use." - }, "onboardingMetametricsDisagree": { "message": "No thanks" }, @@ -3355,19 +3448,9 @@ "message": "We’ll let you know if we decide to use this data for other purposes. You can review our $1 for more information. Remember, you can go to settings and opt out at any time.", "description": "$1 represents `onboardingMetametricsInfuraTermsPolicy`" }, - "onboardingMetametricsInfuraTermsLegacy": { - "message": "* When you use Infura as your default RPC provider in MetaMask, Infura will collect your IP address and your Ethereum wallet address when you send a transaction. We don’t store this information in a way that allows our systems to associate those two pieces of data. For more information on how MetaMask and Infura interact from a data collection perspective, see our update $1. For more information on our privacy practices in general, see our $2.", - "description": "$1 represents `onboardingMetametricsInfuraTermsPolicyLink`, $2 represents `onboardingMetametricsInfuraTermsPolicy`" - }, "onboardingMetametricsInfuraTermsPolicy": { "message": "Privacy Policy" }, - "onboardingMetametricsInfuraTermsPolicyLegacy": { - "message": "Privacy Policy here" - }, - "onboardingMetametricsInfuraTermsPolicyLinkLegacy": { - "message": "here" - }, "onboardingMetametricsModalTitle": { "message": "Add custom network" }, @@ -3385,17 +3468,6 @@ "onboardingMetametricsNeverCollectIPEmphasis": { "message": "General:" }, - "onboardingMetametricsNeverCollectIPLegacy": { - "message": "$1 collect your full IP address*", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsNeverCollectLegacy": { - "message": "$1 collect information we don’t need to provide the service (such as keys, addresses, transaction hashes, or balances)", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsNeverEmphasisLegacy": { - "message": "Never" - }, "onboardingMetametricsNeverSellData": { "message": "$1 you decide if you want to share or delete your usage data via settings any time.", "description": "$1 represents `onboardingMetametricsNeverSellDataEmphasis`" @@ -3403,13 +3475,6 @@ "onboardingMetametricsNeverSellDataEmphasis": { "message": "Optional:" }, - "onboardingMetametricsNeverSellDataLegacy": { - "message": "$1 sell data. Ever!", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsSendAnonymizeLegacy": { - "message": "Send anonymized click and pageview events" - }, "onboardingMetametricsTitle": { "message": "Help us improve MetaMask" }, @@ -3871,6 +3936,10 @@ "popularCustomNetworks": { "message": "Popular custom networks" }, + "popularNetworkAddToolTip": { + "message": "Some of these networks rely on third parties. The connections may be less reliable or enable third-parties to track activity. $1", + "description": "$1 is Learn more link" + }, "portfolio": { "message": "Portfolio" }, @@ -4471,7 +4540,7 @@ "message": "Select token" }, "selectNFTPrivacyPreference": { - "message": "Turn on NFT detection in Settings" + "message": "Enable NFT Autodetection" }, "selectPathHelp": { "message": "If you don't see the accounts you expect, try switching the HD path or current selected network." @@ -4692,7 +4761,7 @@ "message": "Resources" }, "siweSignatureSimulationDetailInfo": { - "message": "This type of signature is not able to move your assets and is used for signing in." + "message": "You’re signing into a site and there are no predicted changes to your account." }, "siweURI": { "message": "URL" @@ -5209,6 +5278,9 @@ "message": "Suggested by $1", "description": "$1 is the snap name" }, + "suggestedTokenName": { + "message": "Suggested name:" + }, "suggestedTokenSymbol": { "message": "Suggested ticker symbol:" }, @@ -6101,6 +6173,12 @@ "updatedWithDate": { "message": "Updated $1" }, + "uploadDropFile": { + "message": "Drop your file here" + }, + "uploadFile": { + "message": "Upload file" + }, "urlErrorMsg": { "message": "URLs require the appropriate HTTP/HTTPS prefix." }, @@ -6316,6 +6394,9 @@ "whatsThis": { "message": "What's this?" }, + "wrongNetworkName": { + "message": "According to our records, the network name may not correctly match this chain ID." + }, "xOfYPending": { "message": "$1 of $2 pending", "description": "$1 and $2 are intended to be two numbers, where $2 is a total number of pending confirmations, and $1 is a count towards that total" diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index db4765dce424..6a70bf40e68f 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -524,9 +524,6 @@ "attemptToCancelSwapForFree": { "message": "Intente cancelar el intercambio de forma gratuita" }, - "attemptingConnect": { - "message": "Intentando una conexión a la cadena de bloques." - }, "attributions": { "message": "Atribuciones" }, @@ -2450,9 +2447,6 @@ "loading": { "message": "Cargando…" }, - "loadingNFTs": { - "message": "Cargando NFT..." - }, "loadingScreenHardwareWalletMessage": { "message": "Por favor, complete la transacción en el monedero físico." }, @@ -3209,24 +3203,12 @@ "onboardingMetametricsAgree": { "message": "Acepto" }, - "onboardingMetametricsAllowOptOutLegacy": { - "message": "Siempre le permitirá excluirse a través de la Configuración" - }, - "onboardingMetametricsDataTermsLegacy": { - "message": "Estos datos se agrupan y, por lo tanto, son anónimos a los efectos del Reglamento general de protección de datos (UE) 2016/679." - }, "onboardingMetametricsDescription": { "message": "Nos gustaría recopilar datos básicos de uso y diagnóstico para mejorar MetaMask. Tenga presente que nunca venderemos los datos que nos proporcione aquí." }, "onboardingMetametricsDescription2": { "message": "Al recopilar métricas, siempre será..." }, - "onboardingMetametricsDescription2Legacy": { - "message": "MetaMask..." - }, - "onboardingMetametricsDescriptionLegacy": { - "message": "MetaMask quisiera recopilar datos de uso para comprender mejor cómo nuestros usuarios interactúan con MetaMask. Estos datos se utilizarán para proporcionar el servicio, lo que incluye mejorarlo en función de su uso." - }, "onboardingMetametricsDisagree": { "message": "No, gracias" }, @@ -3234,19 +3216,9 @@ "message": "Le informaremos si decidimos usar estos datos para otros fines. Puede consultar $1 para obtener más información. Recuerde que puede acceder a la configuración y excluirse en cualquier momento.", "description": "$1 represents `onboardingMetametricsInfuraTermsPolicy`" }, - "onboardingMetametricsInfuraTermsLegacy": { - "message": "*Al utilizar Infura como su proveedor de RPC predeterminado en MetaMask, Infura recopilará su dirección IP y la dirección de su monedero de Ethereum cuando envíe una transacción. No almacenamos esta información de una manera que permita qie nuestros sistemas asocien esos dos datos. Para obtener más información sobre cómo interactúan MetaMask e Infura desde la perspectiva de la recopilación de datos, consulte nuestra actualización $1. Para obtener más información sobre nuestras prácticas de privacidad en general, consulte nuestra $2.", - "description": "$1 represents `onboardingMetametricsInfuraTermsPolicyLink`, $2 represents `onboardingMetametricsInfuraTermsPolicy`" - }, "onboardingMetametricsInfuraTermsPolicy": { "message": "Política de privacidad" }, - "onboardingMetametricsInfuraTermsPolicyLegacy": { - "message": "Política de privacidad aquí" - }, - "onboardingMetametricsInfuraTermsPolicyLinkLegacy": { - "message": "aquí" - }, "onboardingMetametricsModalTitle": { "message": "Agregar red personalizada" }, @@ -3264,17 +3236,6 @@ "onboardingMetametricsNeverCollectIPEmphasis": { "message": "Generales:" }, - "onboardingMetametricsNeverCollectIPLegacy": { - "message": "$1 recopilará su dirección IP completa*", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsNeverCollectLegacy": { - "message": "$1 recopilará información que no necesitamos para brindar el servicio (como claves, direcciones, hashes de transacciones o saldos)", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsNeverEmphasisLegacy": { - "message": "Nunca" - }, "onboardingMetametricsNeverSellData": { "message": "$1 usted decide si desea compartir o eliminar sus datos de uso a través de la configuración en cualquier momento.", "description": "$1 represents `onboardingMetametricsNeverSellDataEmphasis`" @@ -3282,13 +3243,6 @@ "onboardingMetametricsNeverSellDataEmphasis": { "message": "Opcionales:" }, - "onboardingMetametricsNeverSellDataLegacy": { - "message": "$1 venderá datos. ¡Jamás!", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsSendAnonymizeLegacy": { - "message": "Enviará eventos de vistas de página y clics anónimos" - }, "onboardingMetametricsTitle": { "message": "Ayúdenos a mejorar MetaMask" }, diff --git a/app/_locales/es_419/messages.json b/app/_locales/es_419/messages.json index b2f763ced384..662a7f3ede27 100644 --- a/app/_locales/es_419/messages.json +++ b/app/_locales/es_419/messages.json @@ -214,9 +214,6 @@ "assetOptions": { "message": "Opciones de activos" }, - "attemptingConnect": { - "message": "Intentando una conexión a la cadena de bloques." - }, "attributions": { "message": "Atribuciones" }, diff --git a/app/_locales/et/messages.json b/app/_locales/et/messages.json index cba62eb1d7d9..dbfe1f88fb55 100644 --- a/app/_locales/et/messages.json +++ b/app/_locales/et/messages.json @@ -75,9 +75,6 @@ "asset": { "message": "Vara" }, - "attemptingConnect": { - "message": "Plokiahelaga ühenduse loomise katse." - }, "attributions": { "message": "Omistamised" }, diff --git a/app/_locales/fa/messages.json b/app/_locales/fa/messages.json index 3f2e3a703f01..9f1d89793c1b 100644 --- a/app/_locales/fa/messages.json +++ b/app/_locales/fa/messages.json @@ -75,9 +75,6 @@ "asset": { "message": "سرمایه" }, - "attemptingConnect": { - "message": "در حال تلاش برای اتصال با زنجیره بلوکی" - }, "attributions": { "message": "مواصفات" }, diff --git a/app/_locales/fi/messages.json b/app/_locales/fi/messages.json index a907aeedbd9e..4f193797912c 100644 --- a/app/_locales/fi/messages.json +++ b/app/_locales/fi/messages.json @@ -75,9 +75,6 @@ "asset": { "message": "Omaisuuserä" }, - "attemptingConnect": { - "message": "Yritetään yhdistää lohkoketjuun." - }, "attributions": { "message": "Attribuutiot" }, diff --git a/app/_locales/fil/messages.json b/app/_locales/fil/messages.json index 040a687f7633..ab098442e52e 100644 --- a/app/_locales/fil/messages.json +++ b/app/_locales/fil/messages.json @@ -66,9 +66,6 @@ "approved": { "message": "Inaprubahan" }, - "attemptingConnect": { - "message": "Sinusubukang kumonekta sa blockchain." - }, "attributions": { "message": "Mga Attribution" }, diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index b668b15ccffd..0cd50d4788f8 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -524,9 +524,6 @@ "attemptToCancelSwapForFree": { "message": "Tentative d’annuler gratuitement le swap" }, - "attemptingConnect": { - "message": "Tentative de connexion au réseau" - }, "attributions": { "message": "Attributions" }, @@ -2453,9 +2450,6 @@ "loading": { "message": "Chargement..." }, - "loadingNFTs": { - "message": "Chargement des NFT..." - }, "loadingScreenHardwareWalletMessage": { "message": "Veuillez conclure la transaction sur le portefeuille matériel." }, @@ -3212,24 +3206,12 @@ "onboardingMetametricsAgree": { "message": "J’accepte" }, - "onboardingMetametricsAllowOptOutLegacy": { - "message": "Vous pourrez toujours vous désinscrire depuis les Paramètres" - }, - "onboardingMetametricsDataTermsLegacy": { - "message": "Conformément au règlement général sur la protection des données (UE) 2016/679, ces données sont agrégées pour préserver l’anonymat des utilisateurs." - }, "onboardingMetametricsDescription": { "message": "Nous aimerions recueillir des données d’utilisation et de diagnostic de base afin d’améliorer MetaMask. Sachez que nous ne vendons jamais les données que vous nous fournissez ici." }, "onboardingMetametricsDescription2": { "message": "Lorsque nous recueillons des données, elles sont toujours…" }, - "onboardingMetametricsDescription2Legacy": { - "message": "MetaMask..." - }, - "onboardingMetametricsDescriptionLegacy": { - "message": "MetaMask souhaite recueillir des données d’utilisation afin de mieux comprendre comment les utilisateurs interagissent avec MetaMask. Ces données seront utilisées pour améliorer les services que nous proposons ainsi que l’expérience utilisateur." - }, "onboardingMetametricsDisagree": { "message": "Non merci" }, @@ -3237,19 +3219,9 @@ "message": "Nous vous informerons si nous décidons d’utiliser ces données à d’autres fins. Pour plus d’informations, vous pouvez consulter notre $1. N’oubliez pas que vous pouvez aller dans les paramètres et vous désinscrire à tout moment.", "description": "$1 represents `onboardingMetametricsInfuraTermsPolicy`" }, - "onboardingMetametricsInfuraTermsLegacy": { - "message": "* Sachez que lorsque vous définissez Infura comme fournisseur de RPC par défaut dans MetaMask, votre adresse IP et l’adresse de votre portefeuille Ethereum seront communiquées à Infura pour valider les transactions. Ces données sont stockées séparément sur nos systèmes pour préserver l’anonymat des utilisateurs. Pour plus d’informations sur la façon dont MetaMask et Infura collectent les données, consultez nos dernières mises à jour $1. Pour plus d’informations, consultez notre $2.", - "description": "$1 represents `onboardingMetametricsInfuraTermsPolicyLink`, $2 represents `onboardingMetametricsInfuraTermsPolicy`" - }, "onboardingMetametricsInfuraTermsPolicy": { "message": "Politique de confidentialité" }, - "onboardingMetametricsInfuraTermsPolicyLegacy": { - "message": "Politique de confidentialité ici" - }, - "onboardingMetametricsInfuraTermsPolicyLinkLegacy": { - "message": "ici" - }, "onboardingMetametricsModalTitle": { "message": "Ajouter un réseau personnalisé" }, @@ -3267,17 +3239,6 @@ "onboardingMetametricsNeverCollectIPEmphasis": { "message": "Général :" }, - "onboardingMetametricsNeverCollectIPLegacy": { - "message": "Nous ne collectons $1 l’intégralité de votre adresse IP*", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsNeverCollectLegacy": { - "message": "Nous ne collectons $1 d’informations dont nous n’avons pas besoin pour fournir nos services (comme les clés, les adresses, les hachages de transactions ou les soldes)", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsNeverEmphasisLegacy": { - "message": "Jamais" - }, "onboardingMetametricsNeverSellData": { "message": "$1 vous pouvez décider à tout moment de partager ou de supprimer vos données d’utilisation dans les paramètres.", "description": "$1 represents `onboardingMetametricsNeverSellDataEmphasis`" @@ -3285,13 +3246,6 @@ "onboardingMetametricsNeverSellDataEmphasis": { "message": "Facultatif :" }, - "onboardingMetametricsNeverSellDataLegacy": { - "message": "Nous ne vendons $1 vos données !", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsSendAnonymizeLegacy": { - "message": "Envoyer des données anonymisées sur les clics effectués et les pages vues" - }, "onboardingMetametricsTitle": { "message": "Aidez-nous à améliorer MetaMask" }, diff --git a/app/_locales/he/messages.json b/app/_locales/he/messages.json index 22f5ac53511e..e29f44c9f834 100644 --- a/app/_locales/he/messages.json +++ b/app/_locales/he/messages.json @@ -75,9 +75,6 @@ "asset": { "message": "נכס" }, - "attemptingConnect": { - "message": "מנסה להתחבר לבלוקצ'יין." - }, "attributions": { "message": "תכונות" }, diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json index af526ba040ce..5f83ad80bfdf 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -524,9 +524,6 @@ "attemptToCancelSwapForFree": { "message": "स्वैप को मुफ्त में कैंसिल करने की कोशिश करें" }, - "attemptingConnect": { - "message": "ब्लॉकचेन से कनेक्ट करने की कोशिश कर रहे हैं।" - }, "attributions": { "message": "एट्रीब्यूशन्स" }, @@ -2450,9 +2447,6 @@ "loading": { "message": "लोड हो रहा है..." }, - "loadingNFTs": { - "message": "NFTज़ लोड कर रहा है..." - }, "loadingScreenHardwareWalletMessage": { "message": "कृपया hardware wallet पर ट्रांजेक्शन पूरा करें।" }, @@ -3209,24 +3203,12 @@ "onboardingMetametricsAgree": { "message": "मैं सहमत हूं" }, - "onboardingMetametricsAllowOptOutLegacy": { - "message": "सेटिंग के माध्यम से आपको हमेशा ऑप्ट-आउट करने की अनुमति देता है" - }, - "onboardingMetametricsDataTermsLegacy": { - "message": "यह डेटा एग्रीगेट किया गया है और इसलिए सामान्य डेटा संरक्षण विनियम (EU) 2016/679 के उद्देश्यों के लिए अज्ञात है।" - }, "onboardingMetametricsDescription": { "message": "हम MetaMask को बेहतर बनाने के लिए बुनियादी यूसेज और डाएगोनोस्टिक्स डेटा कलेक्ट करना चाहेंगे। जान लें कि हम आपके द्वारा यहां उपलब्ध कराया गया डेटा कभी नहीं बेचते हैं।" }, "onboardingMetametricsDescription2": { "message": "जब हम मेट्रिक्स इकट्ठा करते हैं, तो यह हमेशा... रहेगा" }, - "onboardingMetametricsDescription2Legacy": { - "message": "MetaMask करेगा..." - }, - "onboardingMetametricsDescriptionLegacy": { - "message": "MetaMask यह समझने के लिए इस्तेमाल डेटा एकत्र करना चाहता है कि हमारे यूज़र MetaMask से कैसे इंटरैक्ट करते हैं। इस डेटा का इस्तेमाल सर्विस प्रदान करने के लिए किया जाएगा, जिसमें आपके इस्तेमाल के आधार पर सर्विस में सुधार करना शामिल है।" - }, "onboardingMetametricsDisagree": { "message": "जी नहीं, धन्यवाद" }, @@ -3234,19 +3216,9 @@ "message": "यदि हम इस डेटा का उपयोग अन्य उद्देश्यों के लिए करने का निर्णय लेते हैं तो हम आपको बताएंगे। अधिक जानकारी के लिए आप हमारे $1 की समीक्षा कर सकते हैं। याद रखें, आप किसी भी समय सेटिंग्स में जाकर ऑप्ट आउट कर सकते हैं।", "description": "$1 represents `onboardingMetametricsInfuraTermsPolicy`" }, - "onboardingMetametricsInfuraTermsLegacy": { - "message": "* जब आप MetaMask में अपने डिफॉल्ट RPC प्रोवाइडर के रूप में Infura का इस्तेमाल करते हैं, तो जब आप ट्रांसेक्शन भेजते हैं तो Infura आपका IP एड्रेस और आपके Ethereum वॉलेट का एड्रेस एकत्र कर लेगा। हम इस जानकारी को इस तरह से स्टोर नहीं करते हैं जिससे हमारे सिस्टम डेटा के उन दो टुकड़ों को जोड़ सकें। डेटा कलेक्शन के नज़रिए से MetaMask और Infura कैसे इंटरैक्ट करते हैं, इस बारे में अधिक जानकारी के लिए, हमारा अपडेट $1 देखें। सामान्य तौर पर हमारी गोपनीयता की कार्यप्रणाली के बारे में अधिक जानकारी के लिए, हमारा $2 देखें।", - "description": "$1 represents `onboardingMetametricsInfuraTermsPolicyLink`, $2 represents `onboardingMetametricsInfuraTermsPolicy`" - }, "onboardingMetametricsInfuraTermsPolicy": { "message": "गोपनीयता नीति" }, - "onboardingMetametricsInfuraTermsPolicyLegacy": { - "message": "यहां गोपनीयता नीति" - }, - "onboardingMetametricsInfuraTermsPolicyLinkLegacy": { - "message": "यहां" - }, "onboardingMetametricsModalTitle": { "message": "कस्टम नेटवर्क जोड़ें" }, @@ -3264,17 +3236,6 @@ "onboardingMetametricsNeverCollectIPEmphasis": { "message": "सामान्य:" }, - "onboardingMetametricsNeverCollectIPLegacy": { - "message": "$1 आपका पूरा IP एड्रेस एकत्र करते हैं*", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsNeverCollectLegacy": { - "message": "$1 ऐसी जानकारी एकत्र करते हैं जिसे हमें सर्विस प्रदान करने की आवश्यकता नहीं है (जैसे keys, एड्रेस, ट्रांसेक्शन हैशेज़ या बैलेंस)", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsNeverEmphasisLegacy": { - "message": "कभी नहीं" - }, "onboardingMetametricsNeverSellData": { "message": "$1 आप तय करते हैं कि आप किसी भी समय सेटिंग्स के माध्यम से अपना यूसेज डेटा शेयर करना चाहते हैं या हटाना चाहते हैं।", "description": "$1 represents `onboardingMetametricsNeverSellDataEmphasis`" @@ -3282,13 +3243,6 @@ "onboardingMetametricsNeverSellDataEmphasis": { "message": "वैकल्पिक:" }, - "onboardingMetametricsNeverSellDataLegacy": { - "message": "$1 डेटा बेचते हैं। कभी!", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsSendAnonymizeLegacy": { - "message": "अज्ञात क्लिक और पेजव्यू इवेंट भेजें" - }, "onboardingMetametricsTitle": { "message": "MetaMask को बेहतर बनाने में हमारी मदद करें" }, diff --git a/app/_locales/hn/messages.json b/app/_locales/hn/messages.json index 8dc338db6dc1..03c87498c87f 100644 --- a/app/_locales/hn/messages.json +++ b/app/_locales/hn/messages.json @@ -33,9 +33,6 @@ "approve": { "message": "मंजूर" }, - "attemptingConnect": { - "message": "ब्लॉकचैन से कनेक्ट करने का प्रयास करना होगा।सब्र करे।" - }, "attributions": { "message": "एट्रिब्यूशन" }, diff --git a/app/_locales/hr/messages.json b/app/_locales/hr/messages.json index 3d259868534a..d7e62da80ba4 100644 --- a/app/_locales/hr/messages.json +++ b/app/_locales/hr/messages.json @@ -75,9 +75,6 @@ "asset": { "message": "Imovina" }, - "attemptingConnect": { - "message": "Pokušaj povezivanja na podatkovni blok." - }, "attributions": { "message": "Svojstva" }, diff --git a/app/_locales/ht/messages.json b/app/_locales/ht/messages.json index 94bad1913fe6..02f06746a58a 100644 --- a/app/_locales/ht/messages.json +++ b/app/_locales/ht/messages.json @@ -60,9 +60,6 @@ "approved": { "message": "Apwouve" }, - "attemptingConnect": { - "message": "Eseye konekte nan blockchain." - }, "attributions": { "message": "Atribisyon" }, diff --git a/app/_locales/hu/messages.json b/app/_locales/hu/messages.json index 21d413aa64a3..456b63710dd6 100644 --- a/app/_locales/hu/messages.json +++ b/app/_locales/hu/messages.json @@ -75,9 +75,6 @@ "asset": { "message": "Eszköz" }, - "attemptingConnect": { - "message": "Próbálunk csatlakozni a blokklánchoz." - }, "attributions": { "message": "Attribúciók" }, diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index f5357cfa2106..45073d2af42b 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -524,9 +524,6 @@ "attemptToCancelSwapForFree": { "message": "Mencoba membatalkan pertukaran secara gratis" }, - "attemptingConnect": { - "message": "Mencoba terhubung ke blockchain." - }, "attributions": { "message": "Atribusi" }, @@ -2453,9 +2450,6 @@ "loading": { "message": "Memuat..." }, - "loadingNFTs": { - "message": "Memuat NFT..." - }, "loadingScreenHardwareWalletMessage": { "message": "Selesaikan transaksi di dompet perangkat keras." }, @@ -3212,24 +3206,12 @@ "onboardingMetametricsAgree": { "message": "Saya setuju" }, - "onboardingMetametricsAllowOptOutLegacy": { - "message": "Selalu izinkan Anda untuk keluar melalui Pengaturan" - }, - "onboardingMetametricsDataTermsLegacy": { - "message": "Data ini dikumpulkan sehingga bersifat anonim untuk tujuan Peraturan Perlindungan Data Umum (UE) 2016/679." - }, "onboardingMetametricsDescription": { "message": "Kami ingin mengumpulkan data penggunaan dasar dan diagnostik untuk meningkatkan MetaMask. Pahami bahwa kami tidak pernah menjual data yang Anda berikan di sini." }, "onboardingMetametricsDescription2": { "message": "Saat kami mengumpulkan metrik, maka akan selalu..." }, - "onboardingMetametricsDescription2Legacy": { - "message": "MetaMask akan..." - }, - "onboardingMetametricsDescriptionLegacy": { - "message": "MetaMask ingin mengumpulkan data penggunaan untuk lebih memahami cara pengguna kami berinteraksi dengan MetaMask. Data ini akan digunakan untuk menyediakan layanan, termasuk meningkatkan layanan berdasarkan penggunaan Anda." - }, "onboardingMetametricsDisagree": { "message": "Tidak, terima kasih" }, @@ -3237,19 +3219,9 @@ "message": "Kami akan memberitahukan keputusan untuk menggunakan data ini dengan tujuan lain. Anda dapat meninjau $1 kami untuk informasi selengkapnya. Ingat, Anda dapat membuka pengaturan dan memilih keluar setiap saat.", "description": "$1 represents `onboardingMetametricsInfuraTermsPolicy`" }, - "onboardingMetametricsInfuraTermsLegacy": { - "message": "* Saat Anda menggunakan Infura sebagai penyedia RPC awal di MetaMask, Infura akan mengumpulkan alamat IP dan alamat dompet Ethereum Anda saat mengirim transaksi. Kami tidak menyimpan informasi ini dengan cara yang memungkinkan sistem kami menghubungkan kedua bagian data tersebut. Untuk informasi lebih lanjut seputar cara MetaMask dan Infura berinteraksi dari perspektif pengumpulan data, lihat pembaruan $1. Untuk informasi lebih lanjut seputar praktik privasi kami secara umum, lihat $2 kami.", - "description": "$1 represents `onboardingMetametricsInfuraTermsPolicyLink`, $2 represents `onboardingMetametricsInfuraTermsPolicy`" - }, "onboardingMetametricsInfuraTermsPolicy": { "message": "Kebijakan Privasi" }, - "onboardingMetametricsInfuraTermsPolicyLegacy": { - "message": "Kebijakan Privasi di sini" - }, - "onboardingMetametricsInfuraTermsPolicyLinkLegacy": { - "message": "di sini" - }, "onboardingMetametricsModalTitle": { "message": "Tambahkan jaringan khusus" }, @@ -3267,17 +3239,6 @@ "onboardingMetametricsNeverCollectIPEmphasis": { "message": "Umum:" }, - "onboardingMetametricsNeverCollectIPLegacy": { - "message": "$1 mengumpulkan alamat IP lengkap Anda*", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsNeverCollectLegacy": { - "message": "$1 mengumpulkan informasi yang tidak kami perlukan untuk menyediakan layanan (seperti kunci, alamat, hash transaksi, atau saldo)", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsNeverEmphasisLegacy": { - "message": "Jangan" - }, "onboardingMetametricsNeverSellData": { "message": "$1 memutuskan jika Anda ingin membagikan atau menghapus data penggunaan melalui pengaturan setiap saat.", "description": "$1 represents `onboardingMetametricsNeverSellDataEmphasis`" @@ -3285,13 +3246,6 @@ "onboardingMetametricsNeverSellDataEmphasis": { "message": "Opsional:" }, - "onboardingMetametricsNeverSellDataLegacy": { - "message": "$1 menjual data. Jangan pernah!", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsSendAnonymizeLegacy": { - "message": "Kirim klik anonim dan acara tampilan laman" - }, "onboardingMetametricsTitle": { "message": "Bantu kami meningkatkan MetaMask" }, diff --git a/app/_locales/it/messages.json b/app/_locales/it/messages.json index 6a43d90d1624..2ca832f39006 100644 --- a/app/_locales/it/messages.json +++ b/app/_locales/it/messages.json @@ -297,9 +297,6 @@ "attemptSendingAssets": { "message": "Se si tenta di inviare risorse direttamente da una rete all'altra, ciò potrebbe comportare una perdita permanente della risorca coinvolta. Assicurati di usare un bridge." }, - "attemptingConnect": { - "message": "Tentativo di connessione alla blockchain." - }, "attributions": { "message": "Attribuzioni" }, diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index 866f6c544fd9..ad69880ffd8e 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -524,9 +524,6 @@ "attemptToCancelSwapForFree": { "message": "無料でスワップのキャンセルを試行" }, - "attemptingConnect": { - "message": "ブロックチェーンへの接続を試みています。" - }, "attributions": { "message": "属性" }, @@ -2450,9 +2447,6 @@ "loading": { "message": "ロードしています..." }, - "loadingNFTs": { - "message": "NFTをロードしています..." - }, "loadingScreenHardwareWalletMessage": { "message": "ハードウェアウォレットでトランザクションを完了させてください。" }, @@ -3209,24 +3203,12 @@ "onboardingMetametricsAgree": { "message": "同意します" }, - "onboardingMetametricsAllowOptOutLegacy": { - "message": "いつでも設定からオプトアウトできるようにします" - }, - "onboardingMetametricsDataTermsLegacy": { - "message": "一般データ保護規則 (EU) 2016/679 の目的に従い、このデータは集約され匿名化されます。" - }, "onboardingMetametricsDescription": { "message": "MetaMaskの改善を目的に、基本的な使用状況および診断データを収集したいと思います。ここで提供されるデータが販売されることはありません。" }, "onboardingMetametricsDescription2": { "message": "指標を収集する際、常に次の条件が適用されます..." }, - "onboardingMetametricsDescription2Legacy": { - "message": "MetaMaskは..." - }, - "onboardingMetametricsDescriptionLegacy": { - "message": "MetaMaskは、ユーザーによるMetaMaskの使用状況をより詳細に把握するため、使用データを収集したいと考えています。このデータは、使用状況に基づくサービスの改善を含め、サービスの提供を目的に使用されます。" - }, "onboardingMetametricsDisagree": { "message": "結構です" }, @@ -3234,19 +3216,9 @@ "message": "このデータを他の目的に使用する際は、お知らせします。詳細は当社の$1をご覧ください。設定でいつでもオプトアウトできます。", "description": "$1 represents `onboardingMetametricsInfuraTermsPolicy`" }, - "onboardingMetametricsInfuraTermsLegacy": { - "message": "* MetaMaskでInfuraをデフォルトのRPCプロバイダーとして使用する場合、Infuraはトランザクションの送信時にユーザーのIPアドレスおよびイーサリアムウォレットアドレスを収集します。当社がこれらの情報を、システムによりこれら2つのデータが関連付けられる形で保管することはありません。データ収集の観点から見たMetaMaskとInfuraとのやり取りに関する詳細は、当社の最新の$1をご覧ください。当社のプライバシー慣行全般に関する詳細は、$2をご覧ください。", - "description": "$1 represents `onboardingMetametricsInfuraTermsPolicyLink`, $2 represents `onboardingMetametricsInfuraTermsPolicy`" - }, "onboardingMetametricsInfuraTermsPolicy": { "message": "プライバシー ポリシー" }, - "onboardingMetametricsInfuraTermsPolicyLegacy": { - "message": "こちらのプライバシーポリシー" - }, - "onboardingMetametricsInfuraTermsPolicyLinkLegacy": { - "message": "こちら" - }, "onboardingMetametricsModalTitle": { "message": "カスタムネットワークを追加" }, @@ -3264,17 +3236,6 @@ "onboardingMetametricsNeverCollectIPEmphasis": { "message": "一般:" }, - "onboardingMetametricsNeverCollectIPLegacy": { - "message": "ユーザーの完全なIPアドレスを収集することは、$1*", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsNeverCollectLegacy": { - "message": "サービスの提供に不要な情報 (キー、アドレス、トランザクションハッシュ、残高) を収集することは、$1", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsNeverEmphasisLegacy": { - "message": "一切ありません" - }, "onboardingMetametricsNeverSellData": { "message": "$1 使用状況データを共有するか削除するかは、設定でいつでも指定できます。", "description": "$1 represents `onboardingMetametricsNeverSellDataEmphasis`" @@ -3282,13 +3243,6 @@ "onboardingMetametricsNeverSellDataEmphasis": { "message": "任意:" }, - "onboardingMetametricsNeverSellDataLegacy": { - "message": "データを販売することは$1。絶対です!", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsSendAnonymizeLegacy": { - "message": "匿名のクリックおよびページ閲覧イベントを送信" - }, "onboardingMetametricsTitle": { "message": "MetaMaskの改善にご協力ください" }, diff --git a/app/_locales/kn/messages.json b/app/_locales/kn/messages.json index 0e44c06e69ce..0b0479e3dddf 100644 --- a/app/_locales/kn/messages.json +++ b/app/_locales/kn/messages.json @@ -75,9 +75,6 @@ "asset": { "message": "ಆಸ್ತಿ" }, - "attemptingConnect": { - "message": "ಬ್ಲಾಕ್‌ಚೈನ್‌ಗೆ ಸಂಪರ್ಕಿಸಲು ಪ್ರಯತ್ನಿಸಲಾಗುತ್ತಿದೆ." - }, "attributions": { "message": "ಗುಣಲಕ್ಷಣಗಳು" }, diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index 4b4a187f2b3b..9121a9b84281 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -524,9 +524,6 @@ "attemptToCancelSwapForFree": { "message": "무료 스왑 취소 시도" }, - "attemptingConnect": { - "message": "블록체인에 연결 중입니다." - }, "attributions": { "message": "속성" }, @@ -2450,9 +2447,6 @@ "loading": { "message": "로드 중..." }, - "loadingNFTs": { - "message": "NFT 불러오는 중..." - }, "loadingScreenHardwareWalletMessage": { "message": "하드웨어 지갑에서 트랜잭션을 완료하세요." }, @@ -3209,24 +3203,12 @@ "onboardingMetametricsAgree": { "message": "동의함" }, - "onboardingMetametricsAllowOptOutLegacy": { - "message": "언제든 설정을 통해 옵트아웃할 수 있습니다." - }, - "onboardingMetametricsDataTermsLegacy": { - "message": "이 데이터는 집계 처리된 정보이며 일반 데이터 보호 규정 (EU) 2016/679의 목적에 따라 익명으로 관리됩니다." - }, "onboardingMetametricsDescription": { "message": "MetaMask를 개선하기 위해 기본적인 사용 및 진단 데이터를 수집하고자 합니다. MetaMask는 제공받은 데이터를 절대 판매하지 않습니다." }, "onboardingMetametricsDescription2": { "message": "메트릭을 수집할 때는 항상..." }, - "onboardingMetametricsDescription2Legacy": { - "message": "MetaMask에서는..." - }, - "onboardingMetametricsDescriptionLegacy": { - "message": "MetaMask는 사용자가 MetaMask를 어떻게 사용하는지 이해하기 위해 기본적인 사용 데이터를 수집하고자 합니다. 이 데이터는 사용자 경험을 통한 서비스 개선에 사용됩니다." - }, "onboardingMetametricsDisagree": { "message": "괜찮습니다" }, @@ -3234,19 +3216,9 @@ "message": "이 데이터를 다른 목적으로 사용하기로 결정하면 알려드리겠습니다. 자세한 내용은 $1을(를) 참고하세요. 언제든지 설정으로 이동하여 해제할 수 있습니다.", "description": "$1 represents `onboardingMetametricsInfuraTermsPolicy`" }, - "onboardingMetametricsInfuraTermsLegacy": { - "message": "* MetaMask에서 Infura를 기본 RPC 공급업체로 이용하는 경우, 트랜잭션 전송 시 Infura가 IP 주소와 이더리움 지갑 주소 정보를 수집합니다. MetaMask는 해당 두 정보를 연계할 수 있는 방식으로 정보를 저장하지 않습니다. 데이터 수집 관점에서 MetaMask와 Infura가 인터렉션하는 방법에 대한 자세한 내용은 $1 업데이트를 참조하세요. 일반적인 개인정보 처리방침에 대한 자세한 내용은 $2에서 확인하세요.", - "description": "$1 represents `onboardingMetametricsInfuraTermsPolicyLink`, $2 represents `onboardingMetametricsInfuraTermsPolicy`" - }, "onboardingMetametricsInfuraTermsPolicy": { "message": "개인정보 처리방침" }, - "onboardingMetametricsInfuraTermsPolicyLegacy": { - "message": "개인정보 처리방침" - }, - "onboardingMetametricsInfuraTermsPolicyLinkLegacy": { - "message": "여기" - }, "onboardingMetametricsModalTitle": { "message": "맞춤 네트워크 추가" }, @@ -3264,17 +3236,6 @@ "onboardingMetametricsNeverCollectIPEmphasis": { "message": "일반:" }, - "onboardingMetametricsNeverCollectIPLegacy": { - "message": "$1에서는 사용자의 IP 주소 전체를 수집합니다.", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsNeverCollectLegacy": { - "message": "$1에서는 서비스 제공에 필요하지 않은 정보(예: 키, 주소, 트랜잭션 해시 또는 잔액)를 수집합니다.", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsNeverEmphasisLegacy": { - "message": "절대로" - }, "onboardingMetametricsNeverSellData": { "message": "$1 언제든지 설정을 통해 사용 데이터를 공유할지 삭제할지 결정할 수 있습니다.", "description": "$1 represents `onboardingMetametricsNeverSellDataEmphasis`" @@ -3282,13 +3243,6 @@ "onboardingMetametricsNeverSellDataEmphasis": { "message": "선택 사항:" }, - "onboardingMetametricsNeverSellDataLegacy": { - "message": "$1에서는 데이터를 판매합니다. 항상요!", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsSendAnonymizeLegacy": { - "message": "익명 클릭 및 페이지뷰 이벤트 보내기" - }, "onboardingMetametricsTitle": { "message": "MetaMask 개선에 도움을 주세요" }, diff --git a/app/_locales/lt/messages.json b/app/_locales/lt/messages.json index 893d0a345fd5..0600ea96f32b 100644 --- a/app/_locales/lt/messages.json +++ b/app/_locales/lt/messages.json @@ -75,9 +75,6 @@ "asset": { "message": "Turtas" }, - "attemptingConnect": { - "message": "Mėginama prisijungti prie „blockchain“." - }, "attributions": { "message": "Požymiai" }, diff --git a/app/_locales/lv/messages.json b/app/_locales/lv/messages.json index bf058e6bb2fe..c0dfc6d573b8 100644 --- a/app/_locales/lv/messages.json +++ b/app/_locales/lv/messages.json @@ -75,9 +75,6 @@ "asset": { "message": "Aktīvs" }, - "attemptingConnect": { - "message": "Mēģina izveidot savienojumu ar bloku ķēdi." - }, "attributions": { "message": "Atribūti" }, diff --git a/app/_locales/ms/messages.json b/app/_locales/ms/messages.json index 61e4c37cae7a..b8a0cb2ac507 100644 --- a/app/_locales/ms/messages.json +++ b/app/_locales/ms/messages.json @@ -75,9 +75,6 @@ "asset": { "message": "Aset" }, - "attemptingConnect": { - "message": "Sedang cuba menyambungkan kepada rantaian blok." - }, "attributions": { "message": "Atribusi" }, diff --git a/app/_locales/nl/messages.json b/app/_locales/nl/messages.json index fc847537e00b..f3ae82f80616 100644 --- a/app/_locales/nl/messages.json +++ b/app/_locales/nl/messages.json @@ -33,9 +33,6 @@ "approve": { "message": "Goedkeuren" }, - "attemptingConnect": { - "message": "Poging om verbinding te maken met blockchain." - }, "attributions": { "message": "Bevoegdheden" }, diff --git a/app/_locales/no/messages.json b/app/_locales/no/messages.json index c2825ea5bfe7..56c05ffd958c 100644 --- a/app/_locales/no/messages.json +++ b/app/_locales/no/messages.json @@ -75,9 +75,6 @@ "asset": { "message": "Ressurs" }, - "attemptingConnect": { - "message": "Prøver å opprette forbindelse med blokkjede" - }, "attributions": { "message": "Henvisninger" }, diff --git a/app/_locales/ph/messages.json b/app/_locales/ph/messages.json index 3d3b2428bac3..fbace0f2117f 100644 --- a/app/_locales/ph/messages.json +++ b/app/_locales/ph/messages.json @@ -134,9 +134,6 @@ "assetOptions": { "message": "Mga opsyon sa asset" }, - "attemptingConnect": { - "message": "Sinusubukang kumonekta sa blockchain." - }, "attributions": { "message": "Mga Attribution" }, diff --git a/app/_locales/pl/messages.json b/app/_locales/pl/messages.json index a49b7bd23b16..dbc986a194e4 100644 --- a/app/_locales/pl/messages.json +++ b/app/_locales/pl/messages.json @@ -75,9 +75,6 @@ "asset": { "message": "Składnik aktywów" }, - "attemptingConnect": { - "message": "Próba połączenia z blockchainem." - }, "attributions": { "message": "Atrybuty" }, diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json index 06a8a69653d1..e83e1b6d4eea 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -524,9 +524,6 @@ "attemptToCancelSwapForFree": { "message": "Tentar cancelar a troca sem custo" }, - "attemptingConnect": { - "message": "Tentando conexão com a blockchain." - }, "attributions": { "message": "Atribuições" }, @@ -2453,9 +2450,6 @@ "loading": { "message": "Carregando..." }, - "loadingNFTs": { - "message": "Carregando NFTs..." - }, "loadingScreenHardwareWalletMessage": { "message": "Por favor, conclua a transação na carteira de hardware." }, @@ -3212,24 +3206,12 @@ "onboardingMetametricsAgree": { "message": "Concordo" }, - "onboardingMetametricsAllowOptOutLegacy": { - "message": "Permite que você cancele a inscrição a qualquer momento nas Configurações" - }, - "onboardingMetametricsDataTermsLegacy": { - "message": "Esses dados são agregados e, portanto, anônimos para os fins do Regulamento Geral sobre a Proteção de Dados (UE) de 2016/679." - }, "onboardingMetametricsDescription": { "message": "Gostaríamos de coletar dados básicos de uso para melhorar a MetaMask. Saiba que nunca vendemos os dados que você fornece aqui." }, "onboardingMetametricsDescription2": { "message": "Quando coletamos as métricas, elas sempre são..." }, - "onboardingMetametricsDescription2Legacy": { - "message": "A MetaMask..." - }, - "onboardingMetametricsDescriptionLegacy": { - "message": "A MetaMask gostaria de reunir dados de uso para entender melhor como nossos usuários interagem com a MetaMask. Esses dados serão usados para prestar o serviço, o que inclui melhorá-lo com base em seu uso." - }, "onboardingMetametricsDisagree": { "message": "Não, obrigado" }, @@ -3237,19 +3219,9 @@ "message": "Informaremos a você se decidirmos usar esses dados para outras finalidades. Você pode analisar nossa $1 para obter mais informações. Lembre-se: você pode acessar as configurações e revogar a permissão a qualquer momento.", "description": "$1 represents `onboardingMetametricsInfuraTermsPolicy`" }, - "onboardingMetametricsInfuraTermsLegacy": { - "message": "* Quando você usa a Infura como seu provedor RPC padrão na MetaMask, a Infura coleta seu endereço IP e da carteira de Ethereum quando você envia uma transação. Não armazenamos essas informações de forma que permita aos nossos sistemas cruzarem os dois fragmentos de dados. Para obter mais informações sobre como a MetaMask e a Infura interagem da perspectiva da coleta de dados, veja nossa atualização $1. Para obter mais informações sobre nossas práticas de privacidade em geral, veja nossa $2.", - "description": "$1 represents `onboardingMetametricsInfuraTermsPolicyLink`, $2 represents `onboardingMetametricsInfuraTermsPolicy`" - }, "onboardingMetametricsInfuraTermsPolicy": { "message": "Política de Privacidade" }, - "onboardingMetametricsInfuraTermsPolicyLegacy": { - "message": "Política de Privacidade aqui" - }, - "onboardingMetametricsInfuraTermsPolicyLinkLegacy": { - "message": "aqui" - }, "onboardingMetametricsModalTitle": { "message": "Adicionar rede personalizada" }, @@ -3267,17 +3239,6 @@ "onboardingMetametricsNeverCollectIPEmphasis": { "message": "Gerais:" }, - "onboardingMetametricsNeverCollectIPLegacy": { - "message": "$1 coletará seu endereço IP completo*", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsNeverCollectLegacy": { - "message": "$1 coletará informações de que não precisamos para prestar o serviço (tais como chaves, endereços, hashes de transações ou saldos)", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsNeverEmphasisLegacy": { - "message": "Nunca" - }, "onboardingMetametricsNeverSellData": { "message": "$1 você decide se quer compartilhar ou excluir seus dados de uso nas configurações, a qualquer momento.", "description": "$1 represents `onboardingMetametricsNeverSellDataEmphasis`" @@ -3285,13 +3246,6 @@ "onboardingMetametricsNeverSellDataEmphasis": { "message": "Opcionais:" }, - "onboardingMetametricsNeverSellDataLegacy": { - "message": "$1 venderá dados. Jamais!", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsSendAnonymizeLegacy": { - "message": "Enviará eventos de cliques e visualizações de páginas anonimizados" - }, "onboardingMetametricsTitle": { "message": "Ajude-nos a melhorar a MetaMask" }, diff --git a/app/_locales/pt_BR/messages.json b/app/_locales/pt_BR/messages.json index cc4c97e70ff6..688f5758ac26 100644 --- a/app/_locales/pt_BR/messages.json +++ b/app/_locales/pt_BR/messages.json @@ -214,9 +214,6 @@ "assetOptions": { "message": "Opções do ativo" }, - "attemptingConnect": { - "message": "Tentando conexão com o blockchain." - }, "attributions": { "message": "Atribuições" }, diff --git a/app/_locales/ro/messages.json b/app/_locales/ro/messages.json index fa02911ca8f3..a7f916825e57 100644 --- a/app/_locales/ro/messages.json +++ b/app/_locales/ro/messages.json @@ -75,9 +75,6 @@ "asset": { "message": "Activ" }, - "attemptingConnect": { - "message": "Se încearcă conectarea la blockchain." - }, "attributions": { "message": "Atribuții" }, diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index 584f6cfb2204..cb41df257fe1 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -524,9 +524,6 @@ "attemptToCancelSwapForFree": { "message": "Бесплатная попытка отменить своп" }, - "attemptingConnect": { - "message": "Попытка подключения к блокчейну..." - }, "attributions": { "message": "Атрибуции" }, @@ -2453,9 +2450,6 @@ "loading": { "message": "Загрузка..." }, - "loadingNFTs": { - "message": "Загрузка NFT..." - }, "loadingScreenHardwareWalletMessage": { "message": "Завершите транзакцию в аппаратном кошельке." }, @@ -3212,24 +3206,12 @@ "onboardingMetametricsAgree": { "message": "Я согласен(-на)" }, - "onboardingMetametricsAllowOptOutLegacy": { - "message": "Всегда разрешать вам отказываться в Настройках" - }, - "onboardingMetametricsDataTermsLegacy": { - "message": "Эти данные агрегированы и, следовательно, являются анонимными для целей Общего регламента по защите данных (ЕС) 2016/679." - }, "onboardingMetametricsDescription": { "message": "Мы хотели бы собрать базовые данные об использовании и диагностике для улучшения MetaMask. Помните, что мы никогда не продаем данные, которые вы здесь предоставляете." }, "onboardingMetametricsDescription2": { "message": "Когда мы собираем показатели, они всегда будут..." }, - "onboardingMetametricsDescription2Legacy": { - "message": "MetaMask..." - }, - "onboardingMetametricsDescriptionLegacy": { - "message": "MetaMask хотел бы собрать данные об использовании, чтобы лучше понять, как наши пользователи взаимодействуют с MetaMask. Эти данные будут использоваться для предоставления обслуживания, в том числе его улучшения услуги на основе вашего использования." - }, "onboardingMetametricsDisagree": { "message": "Нет, спасибо" }, @@ -3237,19 +3219,9 @@ "message": "Мы сообщим вам, если решим использовать эти данные для других целей. Вы можете ознакомиться с нашей $1 для получения дополнительной информации. Помните, что вы можете перейти в настройки и отказаться в любой момент.", "description": "$1 represents `onboardingMetametricsInfuraTermsPolicy`" }, - "onboardingMetametricsInfuraTermsLegacy": { - "message": "* Когда вы используете Infura в качестве поставщика RPC по умолчанию в MetaMask, Infura будет собирать ваш IP-адрес и адрес вашего кошелька Ethereum при отправке транзакции. Мы не храним эту информацию таким образом, чтобы наши системы могли связать эти две части данных. Для получения дополнительной информации о том, как MetaMask и Infura взаимодействуют с точки зрения сбора данных, см. нашу обновленную $1. Для получения дополнительной информации о нашей политике конфиденциальности в целом см. нашу $2.", - "description": "$1 represents `onboardingMetametricsInfuraTermsPolicyLink`, $2 represents `onboardingMetametricsInfuraTermsPolicy`" - }, "onboardingMetametricsInfuraTermsPolicy": { "message": "Политикой конфиденциальности" }, - "onboardingMetametricsInfuraTermsPolicyLegacy": { - "message": "Политику конфиденциальности здесь" - }, - "onboardingMetametricsInfuraTermsPolicyLinkLegacy": { - "message": "здесь" - }, "onboardingMetametricsModalTitle": { "message": "Добавить пользовательскую сеть" }, @@ -3267,17 +3239,6 @@ "onboardingMetametricsNeverCollectIPEmphasis": { "message": "Общедоступными:" }, - "onboardingMetametricsNeverCollectIPLegacy": { - "message": "$1 не сохраняет ваш полный IP-адрес*", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsNeverCollectLegacy": { - "message": "$1 не будет собирать информацию, которая нам не нужна для предоставления услуги (например, ключи, адреса, хэши транзакций или остатки)", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsNeverEmphasisLegacy": { - "message": "Никогда" - }, "onboardingMetametricsNeverSellData": { "message": "$1 вы в любое время решаете, хотите ли вы поделиться своими данными об использовании или удалить их в настройках.", "description": "$1 represents `onboardingMetametricsNeverSellDataEmphasis`" @@ -3285,13 +3246,6 @@ "onboardingMetametricsNeverSellDataEmphasis": { "message": "Необязательными:" }, - "onboardingMetametricsNeverSellDataLegacy": { - "message": "$1 не продает данные. Никогда!", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsSendAnonymizeLegacy": { - "message": "Отправлять анонимизированные события кликов и просмотров страниц" - }, "onboardingMetametricsTitle": { "message": "Помогите нам улучшить MetaMask" }, diff --git a/app/_locales/sk/messages.json b/app/_locales/sk/messages.json index a136cc940f79..79a185cf896c 100644 --- a/app/_locales/sk/messages.json +++ b/app/_locales/sk/messages.json @@ -72,9 +72,6 @@ "approved": { "message": "Schváleno" }, - "attemptingConnect": { - "message": "Pokouším se připojit k blockchainu." - }, "attributions": { "message": "Zásluhy" }, diff --git a/app/_locales/sl/messages.json b/app/_locales/sl/messages.json index bfc2a7d8b1d1..64e7c5662c3c 100644 --- a/app/_locales/sl/messages.json +++ b/app/_locales/sl/messages.json @@ -75,9 +75,6 @@ "asset": { "message": "Sredstva" }, - "attemptingConnect": { - "message": "Povezovanje z verigo blokov ..." - }, "attributions": { "message": "Dodelitve" }, diff --git a/app/_locales/sr/messages.json b/app/_locales/sr/messages.json index eaf957f685d7..1741b7567833 100644 --- a/app/_locales/sr/messages.json +++ b/app/_locales/sr/messages.json @@ -75,9 +75,6 @@ "asset": { "message": "Sredstva" }, - "attemptingConnect": { - "message": "Pokušava da se poveže na lanac blokova" - }, "attributions": { "message": "Atribucije" }, diff --git a/app/_locales/sv/messages.json b/app/_locales/sv/messages.json index 2f130ff20383..df3766108437 100644 --- a/app/_locales/sv/messages.json +++ b/app/_locales/sv/messages.json @@ -75,9 +75,6 @@ "asset": { "message": "Tillgång" }, - "attemptingConnect": { - "message": "Försöker ansluta till blockkedja." - }, "attributions": { "message": "Tillskrivningar" }, diff --git a/app/_locales/sw/messages.json b/app/_locales/sw/messages.json index 652a45bdcf27..7eb535bee573 100644 --- a/app/_locales/sw/messages.json +++ b/app/_locales/sw/messages.json @@ -75,9 +75,6 @@ "asset": { "message": "Rasilimali" }, - "attemptingConnect": { - "message": "Inajaribu kuunganisha kwenye blockchain." - }, "attributions": { "message": "Sifa" }, diff --git a/app/_locales/ta/messages.json b/app/_locales/ta/messages.json index d1277cdb0e52..f9c7274ac55c 100644 --- a/app/_locales/ta/messages.json +++ b/app/_locales/ta/messages.json @@ -45,9 +45,6 @@ "approved": { "message": "அங்கீகரிக்கப்பட்ட" }, - "attemptingConnect": { - "message": "இணைக்க முயற்சி செய்க ப்ளாக்சைன்" - }, "attributions": { "message": "பண்புகளும்" }, diff --git a/app/_locales/th/messages.json b/app/_locales/th/messages.json index 05f4970d383a..0b2dbb8664aa 100644 --- a/app/_locales/th/messages.json +++ b/app/_locales/th/messages.json @@ -39,9 +39,6 @@ "approve": { "message": "อนุมัติ" }, - "attemptingConnect": { - "message": "กำลังเชื่อมต่อกับบล็อกเชน" - }, "attributions": { "message": "อ้างถึง" }, diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json index c9ec6cedfd17..cbd81acfcbc6 100644 --- a/app/_locales/tl/messages.json +++ b/app/_locales/tl/messages.json @@ -524,9 +524,6 @@ "attemptToCancelSwapForFree": { "message": "Subukang kanselahin ang swap nang libre" }, - "attemptingConnect": { - "message": "Sinusubukang kumonekta sa blockchain." - }, "attributions": { "message": "Mga Attribution" }, @@ -2450,9 +2447,6 @@ "loading": { "message": "Nilo-load..." }, - "loadingNFTs": { - "message": "Nilo-load ang mga NFT..." - }, "loadingScreenHardwareWalletMessage": { "message": "Mangyaring kumpletuhin ang transaksyon sa hardware wallet." }, @@ -3209,24 +3203,12 @@ "onboardingMetametricsAgree": { "message": "Sumasang-ayon ako" }, - "onboardingMetametricsAllowOptOutLegacy": { - "message": "Palagi kang pinapayagan na mag-opt out sa pamamagitan ng Mga Setting" - }, - "onboardingMetametricsDataTermsLegacy": { - "message": "Ang datos na ito ay pinagsama-sama at samakatuwid ay hindi nagpapakilala para sa mga layunin ng General Data Protection Regulation (EU) 2016/679." - }, "onboardingMetametricsDescription": { "message": "Nais naming mangalap ng data para sa batayang paggamit upang mapahusay ang MetaMask. Dapat mong malaman na hindi namin ibebenta ang data na iyong ibibigay rito." }, "onboardingMetametricsDescription2": { "message": "Kapag kami ay nangangalap ng metrics, ito ay palaging..." }, - "onboardingMetametricsDescription2Legacy": { - "message": "Ang MetaMask ay..." - }, - "onboardingMetametricsDescriptionLegacy": { - "message": "Nais ng MetaMask na mangalap ng datos ng paggamit upang mas maunawaan kung paano nakikipag-ugnayan ang aming mga user sa MetaMask. Gagamitin ang datos na ito upang ibigay ang serbisyo, na kinabibilangan ng pagpapabuti ng serbisyo batay sa iyong paggamit." - }, "onboardingMetametricsDisagree": { "message": "Salamat nalang" }, @@ -3234,19 +3216,9 @@ "message": "Ipapaalam namin sa iyo kung magdesisyon kaming gamitin ang data na ito para sa ibang layunin. Maaari mong suriin ang aming $1 para sa karagdagang impormasyon. Tandaan, maaari kang magpunta sa mga setting at mag-opt out anumang oras.", "description": "$1 represents `onboardingMetametricsInfuraTermsPolicy`" }, - "onboardingMetametricsInfuraTermsLegacy": { - "message": "* Kapag ginamit mo ang Infura bilang iyong default na provider ng RPC sa MetaMask, kukunin ng Infura ang iyong IP address at ang iyong Ethereum wallet address kapag nagpadala ka ng transaksyon. Hindi namin iniimbak ang impormasyong ito sa paraan na nagpapahintulot sa aming mga system na iugnay ang dalawang piraso ng data na iyon. Para sa higit pang impormasyon kung paano nakikipag-ugnayan ang MetaMask at Infura mula sa pananaw ng pagkolekta ng data, tingnan ang aming update na $1. Para sa higit pang impormasyon sa aming mga kasanayan sa pagkapribado sa pangkalahatan, tingnan ang aming $2.", - "description": "$1 represents `onboardingMetametricsInfuraTermsPolicyLink`, $2 represents `onboardingMetametricsInfuraTermsPolicy`" - }, "onboardingMetametricsInfuraTermsPolicy": { "message": "Patakaran sa Pagkapribado" }, - "onboardingMetametricsInfuraTermsPolicyLegacy": { - "message": "Patakaran sa Pagkapribado dito" - }, - "onboardingMetametricsInfuraTermsPolicyLinkLegacy": { - "message": "dito" - }, "onboardingMetametricsModalTitle": { "message": "Magdagdag ng custom na network" }, @@ -3264,17 +3236,6 @@ "onboardingMetametricsNeverCollectIPEmphasis": { "message": "Pangkalahatan:" }, - "onboardingMetametricsNeverCollectIPLegacy": { - "message": "Kinokolekta ng $1 ang iyong buong IP address*", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsNeverCollectLegacy": { - "message": "Kinokolekta ng $1 ang impormasyon na hindi namin kailangan para ibigay ang serbisyo (tulad ng mga key, address, hash ng transaksyon, o balanse)", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsNeverEmphasisLegacy": { - "message": "Hindi kailanman" - }, "onboardingMetametricsNeverSellData": { "message": "$1 ikaw ang magdedesisyon kung nais mong ibahagi o burahin ang iyong data sa paggamit sa pamamagitan ng mga setting anumang oras.", "description": "$1 represents `onboardingMetametricsNeverSellDataEmphasis`" @@ -3282,13 +3243,6 @@ "onboardingMetametricsNeverSellDataEmphasis": { "message": "Opsyonal:" }, - "onboardingMetametricsNeverSellDataLegacy": { - "message": "Ang $1 ay nagbebenta ng datos. Kailanman!", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsSendAnonymizeLegacy": { - "message": "Magpapadala ng mga anonymous na kaganapang pag-click at pagtingin sa page" - }, "onboardingMetametricsTitle": { "message": "Tulungan kaming mapahusay ang MetaMask" }, diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index 913ae926635d..13887164bfd1 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -524,9 +524,6 @@ "attemptToCancelSwapForFree": { "message": "Swap işlemini ücretsiz iptal etme girişimi" }, - "attemptingConnect": { - "message": "Blockzincirine bağlanmaya çalışılıyor." - }, "attributions": { "message": "Özellikler" }, @@ -2453,9 +2450,6 @@ "loading": { "message": "Yükleniyor..." }, - "loadingNFTs": { - "message": "NFT'ler yükleniyor..." - }, "loadingScreenHardwareWalletMessage": { "message": "Lütfen işlemi donanım cüzdanında tamamlayın." }, @@ -3212,24 +3206,12 @@ "onboardingMetametricsAgree": { "message": "Kabul ediyorum" }, - "onboardingMetametricsAllowOptOutLegacy": { - "message": "Her zaman Ayarlar kısmından vazgeçebilmenize izin verir" - }, - "onboardingMetametricsDataTermsLegacy": { - "message": "Bu veriler toplanmıştır ve bu nedenle 2016/679 sayılı Genel Veri Koruma Tüzüğü (AB) maksadıyla isimsizdir." - }, "onboardingMetametricsDescription": { "message": "MetaMask'i iyileştirmek için temel kullanım ve tanılama verilerini toplamak istiyoruz. Burada sunduğunuz verileri asla satmadığımızı bilmenizi isteriz." }, "onboardingMetametricsDescription2": { "message": "Ölçümleri toplarken bu her zaman aşağıdaki gibi olacaktır..." }, - "onboardingMetametricsDescription2Legacy": { - "message": "MetaMask..." - }, - "onboardingMetametricsDescriptionLegacy": { - "message": "MetaMask kullanıcılarımızn MetaMask ile nasıl etkileşimde bulunduklarını daha iyi anlamak amacıyla kullanım verilerini toplamak ister. Bu veriler, hizmeti kullanımınıza göre geliştirmek de dahil olmak üzere hizmeti sunmak için kullanılacaktır." - }, "onboardingMetametricsDisagree": { "message": "Hayır, istemiyorum" }, @@ -3237,19 +3219,9 @@ "message": "Bu verileri başka amaçlar için kullanmaya karar vermemiz durumunda sizi bilgilendireceğiz. Daha fazla bilgi için $1 bölümümüzü inceleyebilirsiniz. Unutmayın, dilediğiniz zaman ayarlar kısmına giderek vazgeçebilirsiniz.", "description": "$1 represents `onboardingMetametricsInfuraTermsPolicy`" }, - "onboardingMetametricsInfuraTermsLegacy": { - "message": "* MetaMask'te varsayılan RPC sağlayıcınız olarak Infura'yı kullandığınızda, siz bir işlem gönderdiğinizde Infura tarafından IP adresiniz ve Ethereum cüzdan adresiniz toplanır. Bu bilgileri sistemlerimizin bu iki veri parçasını ilişkilendirebilmesini sağlayan şekilde saklamayız. MetaMask ve Infura'nın veri toplama açısından nasıl etkileşimde bulundukları hakkında daha fazla bilgi için lütfen güncel $1 bölümümüze bakın. Genel olarak gizlilik uygulamalarımız hakkında daha fazla bilgi için $2 bölümümüze bakın.", - "description": "$1 represents `onboardingMetametricsInfuraTermsPolicyLink`, $2 represents `onboardingMetametricsInfuraTermsPolicy`" - }, "onboardingMetametricsInfuraTermsPolicy": { "message": "Gizlilik Politikası" }, - "onboardingMetametricsInfuraTermsPolicyLegacy": { - "message": "buradan Gizlilik Politikası" - }, - "onboardingMetametricsInfuraTermsPolicyLinkLegacy": { - "message": "burada" - }, "onboardingMetametricsModalTitle": { "message": "Özel ağ ekle" }, @@ -3267,17 +3239,6 @@ "onboardingMetametricsNeverCollectIPEmphasis": { "message": "Genel:" }, - "onboardingMetametricsNeverCollectIPLegacy": { - "message": "$1 tam IP adresinizi toplar*", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsNeverCollectLegacy": { - "message": "$1 ihtiyacımız olmayan bilgileri (anahtarlar, adresler, işlem hash değerleri veya bakiyeler gibi) hizmeti sunmak için toplar", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsNeverEmphasisLegacy": { - "message": "Hiçbir Zaman" - }, "onboardingMetametricsNeverSellData": { "message": "$1 kullanım verilerinizi paylaşmak veya silmek isteyip istemediğinize ayarlar kısmından dilediğiniz zaman siz karar verirsiniz.", "description": "$1 represents `onboardingMetametricsNeverSellDataEmphasis`" @@ -3285,13 +3246,6 @@ "onboardingMetametricsNeverSellDataEmphasis": { "message": "İsteğe bağlı:" }, - "onboardingMetametricsNeverSellDataLegacy": { - "message": "$1 verileri satmaz. Asla!", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsSendAnonymizeLegacy": { - "message": "İsimsiz tıklama ve sayfa görüntüleme etkinlikleri gönder" - }, "onboardingMetametricsTitle": { "message": "MetaMask'i iyileştirmemize yardımcı olun" }, diff --git a/app/_locales/uk/messages.json b/app/_locales/uk/messages.json index cadac70b7102..59bb0b477008 100644 --- a/app/_locales/uk/messages.json +++ b/app/_locales/uk/messages.json @@ -75,9 +75,6 @@ "asset": { "message": "Актив" }, - "attemptingConnect": { - "message": "Спроба підключення до розподіленої бази даних." - }, "attributions": { "message": "Авторство" }, diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index 47ccc078d198..3afd6bf2403e 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -524,9 +524,6 @@ "attemptToCancelSwapForFree": { "message": "Cố gắng hủy hoán đổi miễn phí" }, - "attemptingConnect": { - "message": "Đang cố gắng kết nối với chuỗi khối." - }, "attributions": { "message": "Ghi nhận đóng góp" }, @@ -2450,9 +2447,6 @@ "loading": { "message": "Đang tải..." }, - "loadingNFTs": { - "message": "Đang tải NFT..." - }, "loadingScreenHardwareWalletMessage": { "message": "Vui lòng hoàn tất giao dịch trên ví cứng." }, @@ -3209,24 +3203,12 @@ "onboardingMetametricsAgree": { "message": "Tôi đồng ý" }, - "onboardingMetametricsAllowOptOutLegacy": { - "message": "Luôn cho phép bạn chọn không tham gia thông qua phần Cài đặt" - }, - "onboardingMetametricsDataTermsLegacy": { - "message": "Do đó, dữ liệu này được tổng hợp và ẩn danh theo các mục đích của Quy định bảo vệ dữ liệu chung (EU) 2016/679." - }, "onboardingMetametricsDescription": { "message": "Chúng tôi muốn thu thập dữ liệu sử dụng và chẩn đoán cơ bản để cải tiến MetaMask. Xin lưu ý, chúng tôi không bao giờ bán dữ liệu mà bạn cung cấp ở đây." }, "onboardingMetametricsDescription2": { "message": "Khi thu thập số liệu, chúng tôi sẽ luôn cam kết điều này..." }, - "onboardingMetametricsDescription2Legacy": { - "message": "MetaMask sẽ..." - }, - "onboardingMetametricsDescriptionLegacy": { - "message": "MetaMask muốn thu thập dữ liệu sử dụng để hiểu rõ hơn cách người dùng tương tác với MetaMask. Dữ liệu này sẽ được sử dụng để cung cấp dịch vụ, bao gồm cả cải thiện dịch vụ dựa trên quá trình sử dụng của bạn." - }, "onboardingMetametricsDisagree": { "message": "Không, cảm ơn" }, @@ -3234,19 +3216,9 @@ "message": "Chúng tôi sẽ thông báo cho bạn nếu chúng tôi quyết định sử dụng dữ liệu này cho các mục đích khác. Bạn có thể xem lại $1 của chúng tôi để biết thêm thông tin. Lưu ý, bạn có thể truy cập cài đặt và chọn không tham gia bất kỳ lúc nào.", "description": "$1 represents `onboardingMetametricsInfuraTermsPolicy`" }, - "onboardingMetametricsInfuraTermsLegacy": { - "message": "* Khi bạn sử dụng Infura làm nhà cung cấp RPC mặc định trong MetaMask, Infura sẽ thu thập địa chỉ IP và địa chỉ ví Ethereum của bạn khi bạn gửi giao dịch. Chúng tôi không lưu trữ thông tin này để cho phép hệ thống của chúng tôi liên kết hai loại dữ liệu đó. Để biết thêm thông tin về cách MetaMask và Infura tương tác trong việc thu thập dữ liệu, hãy xem bản cập nhật $1 của chúng tôi. Để biết thêm thông tin về các phương pháp bảo vệ quyền riêng tư của chúng tôi nói chung, hãy xem $2.", - "description": "$1 represents `onboardingMetametricsInfuraTermsPolicyLink`, $2 represents `onboardingMetametricsInfuraTermsPolicy`" - }, "onboardingMetametricsInfuraTermsPolicy": { "message": "Chính sách quyền riêng tư" }, - "onboardingMetametricsInfuraTermsPolicyLegacy": { - "message": "Chính sách quyền riêng tư tại đây" - }, - "onboardingMetametricsInfuraTermsPolicyLinkLegacy": { - "message": "tại đây" - }, "onboardingMetametricsModalTitle": { "message": "Thêm mạng tùy chỉnh" }, @@ -3264,17 +3236,6 @@ "onboardingMetametricsNeverCollectIPEmphasis": { "message": "Chung:" }, - "onboardingMetametricsNeverCollectIPLegacy": { - "message": "$1 thu thập địa chỉ IP đầy đủ của bạn*", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsNeverCollectLegacy": { - "message": "$1 thu thập các thông tin mà chúng tôi không cần để cung cấp dịch vụ (chẳng hạn như khóa, địa chỉ, mã băm giao dịch hoặc số dư)", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsNeverEmphasisLegacy": { - "message": "Không bao giờ" - }, "onboardingMetametricsNeverSellData": { "message": "$1 bạn có thể chọn chia sẻ hoặc xóa dữ liệu sử dụng của mình trong phần cài đặt bất kỳ lúc nào.", "description": "$1 represents `onboardingMetametricsNeverSellDataEmphasis`" @@ -3282,13 +3243,6 @@ "onboardingMetametricsNeverSellDataEmphasis": { "message": "Không bắt buộc:" }, - "onboardingMetametricsNeverSellDataLegacy": { - "message": "$1 không bao giờ bán dữ liệu!", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsSendAnonymizeLegacy": { - "message": "Gửi các sự kiện nhấp chuột & lượt xem trang ẩn danh" - }, "onboardingMetametricsTitle": { "message": "Giúp chúng tôi cải thiện MetaMask" }, diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index a95feb041140..6cdb6aaf1e3f 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -524,9 +524,6 @@ "attemptToCancelSwapForFree": { "message": "尝试免费取消兑换" }, - "attemptingConnect": { - "message": "正在尝试连接到区块链。" - }, "attributions": { "message": "参与者" }, @@ -2450,9 +2447,6 @@ "loading": { "message": "正在加载..." }, - "loadingNFTs": { - "message": "正在加载NFT......" - }, "loadingScreenHardwareWalletMessage": { "message": "请在硬件钱包上完成交易。" }, @@ -3209,24 +3203,12 @@ "onboardingMetametricsAgree": { "message": "我同意" }, - "onboardingMetametricsAllowOptOutLegacy": { - "message": "始终允许您通过设置选择退出" - }, - "onboardingMetametricsDataTermsLegacy": { - "message": "此数据是汇总数据,因而可以保持匿名,以遵守《通用数据保护条例》(欧盟)2016/679。" - }, "onboardingMetametricsDescription": { "message": "我们希望收集基本的使用和诊断数据,以改进 MetaMask。请注意,我们绝不会出卖您在此处提供的数据。" }, "onboardingMetametricsDescription2": { "message": "当我们收集指标时,总是..." }, - "onboardingMetametricsDescription2Legacy": { - "message": "MetaMask 将会......" - }, - "onboardingMetametricsDescriptionLegacy": { - "message": "MetaMask 希望收集使用数据,以更好地了解我们的用户如何与 MetaMask 交互。这些数据将用于提供服务,包括根据您的使用情况改进服务。" - }, "onboardingMetametricsDisagree": { "message": "不,谢谢" }, @@ -3234,19 +3216,9 @@ "message": "如果我们决定将这些数据用于其他目的,我们会通知您。您可以查看我们的 $1 以了解更多信息。请记住,您可以随时转到设置并选择退出。", "description": "$1 represents `onboardingMetametricsInfuraTermsPolicy`" }, - "onboardingMetametricsInfuraTermsLegacy": { - "message": "* 如您在 MetaMask中 使用 Infura 作为默认的 RPC 提供商,Infura 将在您发送交易时收集您的 IP 地址和以太坊钱包地址。我们不会以允许系统将这两项数据关联起来的方式存储这些信息。如需从数据收集角度进一步了解 MetaMask 和 Infura 如何进行交互,请参阅我们的更新版 $1。如需进一步了解我们的一般隐私准则,请参阅我们的 $2。", - "description": "$1 represents `onboardingMetametricsInfuraTermsPolicyLink`, $2 represents `onboardingMetametricsInfuraTermsPolicy`" - }, "onboardingMetametricsInfuraTermsPolicy": { "message": "隐私政策" }, - "onboardingMetametricsInfuraTermsPolicyLegacy": { - "message": "隐私政策在此处" - }, - "onboardingMetametricsInfuraTermsPolicyLinkLegacy": { - "message": "此处" - }, "onboardingMetametricsModalTitle": { "message": "添加自定义网络" }, @@ -3264,17 +3236,6 @@ "onboardingMetametricsNeverCollectIPEmphasis": { "message": "通用:" }, - "onboardingMetametricsNeverCollectIPLegacy": { - "message": "$1收集您的完整 IP 地址*", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsNeverCollectLegacy": { - "message": "$1 收集我们不需要提供服务的信息(如私钥、地址、交易散列或余额)", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsNeverEmphasisLegacy": { - "message": "永远不会发生" - }, "onboardingMetametricsNeverSellData": { "message": "$1 您可以随时决定是否通过设置共享或删除您的使用数据。", "description": "$1 represents `onboardingMetametricsNeverSellDataEmphasis`" @@ -3282,13 +3243,6 @@ "onboardingMetametricsNeverSellDataEmphasis": { "message": "可选:" }, - "onboardingMetametricsNeverSellDataLegacy": { - "message": "$1 出售数据。永远不会!", - "description": "$1 represents `onboardingMetametricsNeverEmphasis`" - }, - "onboardingMetametricsSendAnonymizeLegacy": { - "message": "发送匿名的点击和页面浏览事件" - }, "onboardingMetametricsTitle": { "message": "请帮助我们改进 MetaMask" }, diff --git a/app/_locales/zh_TW/messages.json b/app/_locales/zh_TW/messages.json index 26f086433a96..d91d749ccffa 100644 --- a/app/_locales/zh_TW/messages.json +++ b/app/_locales/zh_TW/messages.json @@ -130,9 +130,6 @@ "assetOptions": { "message": "資產選項" }, - "attemptingConnect": { - "message": "正在嘗試連結區塊鏈。" - }, "attributions": { "message": "來源" }, diff --git a/app/images/icons/customize.svg b/app/images/icons/customize.svg new file mode 100644 index 000000000000..4219d0c89f7b --- /dev/null +++ b/app/images/icons/customize.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/manifest/v3/_base.json b/app/manifest/v3/_base.json index 39e96825add8..f3ffbf07c173 100644 --- a/app/manifest/v3/_base.json +++ b/app/manifest/v3/_base.json @@ -75,5 +75,8 @@ "webRequest", "offscreen" ], + "sandbox": { + "pages": ["snaps/index.html"] + }, "short_name": "__MSG_appName__" } diff --git a/app/manifest/v3/chrome.json b/app/manifest/v3/chrome.json index 79656e26f0f9..2308cc912b55 100644 --- a/app/manifest/v3/chrome.json +++ b/app/manifest/v3/chrome.json @@ -1,6 +1,7 @@ { "content_security_policy": { - "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'none'; frame-ancestors 'none';" + "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'none'; frame-ancestors 'none';", + "sandbox": "sandbox allow-scripts; script-src 'self' 'unsafe-inline' 'unsafe-eval'; object-src 'none'; default-src 'none'; connect-src *;" }, "externally_connectable": { "matches": ["https://metamask.io/*"], diff --git a/app/scripts/app-init.js b/app/scripts/app-init.js index 3290dc59d84b..766ba15e2d0d 100644 --- a/app/scripts/app-init.js +++ b/app/scripts/app-init.js @@ -185,27 +185,3 @@ const registerInPageContentScript = async () => { }; registerInPageContentScript(); - -/** - * Creates an offscreen document that can be used to load additional scripts - * and iframes that can communicate with the extension through the chrome - * runtime API. Only one offscreen document may exist, so any iframes required - * by extension can be embedded in the offscreen.html file. See the offscreen - * folder for more details. - */ -async function createOffscreen() { - if (!chrome.offscreen || (await chrome.offscreen.hasDocument())) { - return; - } - - await chrome.offscreen.createDocument({ - url: './offscreen.html', - reasons: ['IFRAME_SCRIPTING'], - justification: - 'Used for Hardware Wallet and Snaps scripts to communicate with the extension.', - }); - - console.debug('Offscreen iframe loaded'); -} - -createOffscreen(); diff --git a/app/scripts/background.js b/app/scripts/background.js index cca8a57ab43a..2496e6da9696 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -15,9 +15,7 @@ import log from 'loglevel'; import browser from 'webextension-polyfill'; import { storeAsStream } from '@metamask/obs-store'; import { hasProperty, isObject } from '@metamask/utils'; -///: BEGIN:ONLY_INCLUDE_IF(snaps) import { ApprovalType } from '@metamask/controller-utils'; -///: END:ONLY_INCLUDE_IF import PortStream from 'extension-port-stream'; import { ethErrors } from 'eth-rpc-errors'; @@ -67,9 +65,19 @@ import { shouldEmitDappViewedEvent, } from './lib/util'; import { generateSkipOnboardingState } from './skip-onboarding'; +import { createOffscreen } from './offscreen'; /* eslint-enable import/first */ +import { TRIGGER_TYPES } from './controllers/metamask-notifications/constants/notification-schema'; + +// eslint-disable-next-line @metamask/design-tokens/color-no-hex +const BADGE_COLOR_APPROVAL = '#0376C9'; +// eslint-disable-next-line @metamask/design-tokens/color-no-hex +const BADGE_COLOR_NOTIFICATION = '#D73847'; +const BADGE_LABEL_APPROVAL = '\u22EF'; // unicode ellipsis +const BADGE_MAX_NOTIFICATION_COUNT = 9; + // Setup global hook for improved Sentry state snapshots during initialization const inTest = process.env.IN_TEST; const localStore = inTest ? new ReadOnlyNetworkStore() : new LocalStore(); @@ -261,6 +269,8 @@ function saveTimestamp() { */ async function initialize() { try { + const offscreenPromise = isManifestV3 ? createOffscreen() : null; + const initData = await loadStateFromPersistence(); const initState = initData.data; @@ -293,6 +303,7 @@ async function initialize() { {}, isFirstMetaMaskControllerSetup, initData.meta, + offscreenPromise, ); if (!isManifestV3) { await loadPhishingWarningPage(); @@ -507,6 +518,7 @@ function emitDappViewedMetricEvent( * @param {object} overrides - object with callbacks that are allowed to override the setup controller logic * @param isFirstMetaMaskControllerSetup * @param {object} stateMetadata - Metadata about the initial state and migrations, including the most recent migration version + * @param {Promise} offscreenPromise - A promise that resolves when the offscreen document has finished initialization. */ export function setupController( initState, @@ -514,6 +526,7 @@ export function setupController( overrides, isFirstMetaMaskControllerSetup, stateMetadata, + offscreenPromise, ) { // // MetaMask Controller @@ -542,6 +555,7 @@ export function setupController( isFirstMetaMaskControllerSetup, currentMigrationVersion: stateMetadata.version, featureFlags: {}, + offscreenPromise, }); setupEnsIpfsResolver({ @@ -774,6 +788,21 @@ export function setupController( updateBadge, ); + controller.controllerMessenger.subscribe( + METAMASK_CONTROLLER_EVENTS.METAMASK_NOTIFICATIONS_LIST_UPDATED, + updateBadge, + ); + + controller.controllerMessenger.subscribe( + METAMASK_CONTROLLER_EVENTS.METAMASK_NOTIFICATIONS_MARK_AS_READ, + updateBadge, + ); + + controller.controllerMessenger.subscribe( + METAMASK_CONTROLLER_EVENTS.NOTIFICATIONS_STATE_CHANGE, + updateBadge, + ); + controller.txController.initApprovals(); /** @@ -781,30 +810,90 @@ export function setupController( * The number reflects the current number of pending transactions or message signatures needing user approval. */ function updateBadge() { + const pendingApprovalCount = getPendingApprovalCount(); + const unreadNotificationsCount = getUnreadNotificationsCount(); + let label = ''; - const count = getUnapprovedTransactionCount(); - if (count) { - label = String(count); + let badgeColor = BADGE_COLOR_APPROVAL; + + if (pendingApprovalCount) { + label = BADGE_LABEL_APPROVAL; + } else if (unreadNotificationsCount > 0) { + label = + unreadNotificationsCount > BADGE_MAX_NOTIFICATION_COUNT + ? `${BADGE_MAX_NOTIFICATION_COUNT}+` + : String(unreadNotificationsCount); + badgeColor = BADGE_COLOR_NOTIFICATION; } - // browserAction has been replaced by action in MV3 - if (isManifestV3) { - browser.action.setBadgeText({ text: label }); - browser.action.setBadgeBackgroundColor({ color: '#037DD6' }); - } else { - browser.browserAction.setBadgeText({ text: label }); - browser.browserAction.setBadgeBackgroundColor({ color: '#037DD6' }); + + try { + const badgeText = { text: label }; + const badgeBackgroundColor = { color: badgeColor }; + + if (isManifestV3) { + browser.action.setBadgeText(badgeText); + browser.action.setBadgeBackgroundColor(badgeBackgroundColor); + } else { + browser.browserAction.setBadgeText(badgeText); + browser.browserAction.setBadgeBackgroundColor(badgeBackgroundColor); + } + } catch (error) { + console.error('Error updating browser badge:', error); } } - function getUnapprovedTransactionCount() { - let count = - controller.appStateController.waitingForUnlock.length + - controller.approvalController.getTotalApprovalCount(); + function getPendingApprovalCount() { + try { + let pendingApprovalCount = + controller.appStateController.waitingForUnlock.length + + controller.approvalController.getTotalApprovalCount(); + + if (controller.preferencesController.getUseRequestQueue()) { + pendingApprovalCount += + controller.queuedRequestController.state.queuedRequestCount; + } + return pendingApprovalCount; + } catch (error) { + console.error('Failed to get pending approval count:', error); + return 0; + } + } - if (controller.preferencesController.getUseRequestQueue()) { - count += controller.queuedRequestController.state.queuedRequestCount; + function getUnreadNotificationsCount() { + try { + const { isMetamaskNotificationsEnabled, isFeatureAnnouncementsEnabled } = + controller.metamaskNotificationsController.state; + + const snapNotificationCount = Object.values( + controller.notificationController.state.notifications, + ).filter((notification) => notification.readDate === null).length; + + const featureAnnouncementCount = isFeatureAnnouncementsEnabled + ? controller.metamaskNotificationsController.state.metamaskNotificationsList.filter( + (notification) => + !notification.isRead && + notification.type === TRIGGER_TYPES.FEATURES_ANNOUNCEMENT, + ).length + : 0; + + const walletNotificationCount = isMetamaskNotificationsEnabled + ? controller.metamaskNotificationsController.state.metamaskNotificationsList.filter( + (notification) => + !notification.isRead && + notification.type !== TRIGGER_TYPES.FEATURES_ANNOUNCEMENT, + ).length + : 0; + + const unreadNotificationsCount = + snapNotificationCount + + featureAnnouncementCount + + walletNotificationCount; + + return unreadNotificationsCount; + } catch (error) { + console.error('Failed to get unread notifications count:', error); + return 0; } - return count; } notificationManager.on( @@ -812,7 +901,7 @@ export function setupController( ({ automaticallyClosed }) => { if (!automaticallyClosed) { rejectUnapprovedNotifications(); - } else if (getUnapprovedTransactionCount() > 0) { + } else if (getPendingApprovalCount() > 0) { triggerUi(); } @@ -835,7 +924,6 @@ export function setupController( Object.values(controller.approvalController.state.pendingApprovals).forEach( ({ id, type }) => { switch (type) { - ///: BEGIN:ONLY_INCLUDE_IF(snaps) case ApprovalType.SnapDialogAlert: case ApprovalType.SnapDialogPrompt: controller.approvalController.accept(id, null); @@ -843,7 +931,6 @@ export function setupController( case ApprovalType.SnapDialogConfirmation: controller.approvalController.accept(id, false); break; - ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) case SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.confirmAccountCreation: case SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.confirmAccountRemoval: @@ -862,7 +949,6 @@ export function setupController( ); } - ///: BEGIN:ONLY_INCLUDE_IF(snaps) // Updates the snaps registry and check for newly blocked snaps to block if the user has at least one snap installed that isn't preinstalled. if ( Object.values(controller.snapController.state.snaps).some( @@ -871,7 +957,6 @@ export function setupController( ) { controller.snapController.updateBlockedSnaps(); } - ///: END:ONLY_INCLUDE_IF } // diff --git a/app/scripts/controllers/app-state.js b/app/scripts/controllers/app-state.js index 256dfba38438..1a511523778f 100644 --- a/app/scripts/controllers/app-state.js +++ b/app/scripts/controllers/app-state.js @@ -43,7 +43,7 @@ export default class AppStateController extends EventEmitter { fullScreenGasPollTokens: [], recoveryPhraseReminderHasBeenShown: false, recoveryPhraseReminderLastShown: new Date().getTime(), - outdatedBrowserWarningLastShown: new Date().getTime(), + outdatedBrowserWarningLastShown: null, nftsDetectionNoticeDismissed: false, showTestnetMessageInDropdown: true, showBetaHeader: isBeta(), @@ -227,7 +227,6 @@ export default class AppStateController extends EventEmitter { }); } - ///: BEGIN:ONLY_INCLUDE_IF(snaps) /** * Record if popover for snaps privacy warning has been shown * on the first install of a snap. @@ -239,7 +238,6 @@ export default class AppStateController extends EventEmitter { snapsInstallPrivacyWarningShown: shown, }); } - ///: END:ONLY_INCLUDE_IF /** * Record the timestamp of the last time the user has seen the outdated browser warning diff --git a/app/scripts/controllers/metamask-notifications/metamask-notifications.ts b/app/scripts/controllers/metamask-notifications/metamask-notifications.ts index f9929a049b54..b9ad4572742b 100644 --- a/app/scripts/controllers/metamask-notifications/metamask-notifications.ts +++ b/app/scripts/controllers/metamask-notifications/metamask-notifications.ts @@ -175,6 +175,16 @@ export declare type MetamaskNotificationsControllerSelectIsMetamaskNotifications handler: MetamaskNotificationsController['selectIsMetamaskNotificationsEnabled']; }; +export type MetamaskNotificationsControllerNotificationsListUpdatedEvent = { + type: `${typeof controllerName}:notificationsListUpdated`; + payload: [Notification[]]; +}; + +export type MetamaskNotificationsControllerMarkNotificationsAsRead = { + type: `${typeof controllerName}:markNotificationsAsRead`; + payload: [Notification[]]; +}; + // Messenger Actions export type Actions = | MetamaskNotificationsControllerUpdateMetamaskNotificationsList @@ -209,7 +219,9 @@ export type MetamaskNotificationsControllerMessengerEvents = // Allowed Events export type AllowedEvents = | KeyringControllerStateChangeEvent - | PushPlatformNotificationsControllerOnNewNotificationEvent; + | PushPlatformNotificationsControllerOnNewNotificationEvent + | MetamaskNotificationsControllerNotificationsListUpdatedEvent + | MetamaskNotificationsControllerMarkNotificationsAsRead; // Type for the messenger of MetamaskNotificationsController export type MetamaskNotificationsControllerMessenger = @@ -1004,6 +1016,11 @@ export class MetamaskNotificationsController extends BaseController< state.metamaskNotificationsList = metamaskNotifications; }); + this.messagingSystem.publish( + `${controllerName}:notificationsListUpdated`, + this.state.metamaskNotificationsList, + ); + this.#setIsFetchingMetamaskNotifications(false); return metamaskNotifications; } catch (err) { @@ -1068,7 +1085,7 @@ export class MetamaskNotificationsController extends BaseController< log.warn('Something failed when marking notifications as read', err); } - // Update the state (state is also used on counter & badge) + // Update the state this.update((state) => { const currentReadList = state.metamaskNotificationsReadList; const newReadIds = [...featureAnnouncementNotificationIds]; @@ -1088,6 +1105,12 @@ export class MetamaskNotificationsController extends BaseController< }, ); }); + + // Publish the event + this.messagingSystem.publish( + `${controllerName}:markNotificationsAsRead`, + this.state.metamaskNotificationsList, + ); } /** @@ -1120,6 +1143,10 @@ export class MetamaskNotificationsController extends BaseController< notification, ...state.metamaskNotificationsList, ]; + this.messagingSystem.publish( + `${controllerName}:notificationsListUpdated`, + state.metamaskNotificationsList, + ); } }); } diff --git a/app/scripts/controllers/metametrics.test.js b/app/scripts/controllers/metametrics.test.js index 431a5d152c7f..43834f01a4b9 100644 --- a/app/scripts/controllers/metametrics.test.js +++ b/app/scripts/controllers/metametrics.test.js @@ -1,5 +1,3 @@ -import { strict as assert } from 'assert'; -import sinon from 'sinon'; import { toHex } from '@metamask/controller-utils'; import { NameType } from '@metamask/name-controller'; import { ENVIRONMENT_TYPE_BACKGROUND } from '../../../shared/constants/app'; @@ -9,7 +7,6 @@ import { METAMETRICS_BACKGROUND_PAGE_OBJECT, MetaMetricsUserTrait, } from '../../../shared/constants/metametrics'; -import waitUntilCalled from '../../../test/lib/wait-until-called'; import { CHAIN_IDS, CURRENCY_SYMBOLS } from '../../../shared/constants/network'; import * as Utils from '../lib/util'; import MetaMetricsController from './metametrics'; @@ -78,13 +75,13 @@ function getMockPreferencesStore({ currentLocale = LOCALE } = {}) { let preferencesStore = { currentLocale, }; - const subscribe = sinon.stub(); + const subscribe = jest.fn(); const updateState = (newState) => { preferencesStore = { ...preferencesStore, ...newState }; - subscribe.getCall(0).args[0](preferencesStore); + subscribe.mock.calls[0][0](preferencesStore); }; return { - getState: sinon.stub().returns(preferencesStore), + getState: jest.fn().mockReturnValue(preferencesStore), updateState, subscribe, }; @@ -145,27 +142,36 @@ function getMetaMetricsController({ describe('MetaMetricsController', function () { const now = new Date(); - let clock; beforeEach(function () { globalThis.sentry = { - startSession: sinon.fake(() => { - /** NOOP */ - }), - endSession: sinon.fake(() => { - /** NOOP */ - }), + startSession: jest.fn(), + endSession: jest.fn(), }; - clock = sinon.useFakeTimers(now.getTime()); - sinon.stub(Utils, 'generateRandomId').returns('DUMMY_RANDOM_ID'); + jest.useFakeTimers().setSystemTime(now.getTime()); + jest.spyOn(Utils, 'generateRandomId').mockReturnValue('DUMMY_RANDOM_ID'); }); describe('constructor', function () { it('should properly initialize', function () { - const mock = sinon.mock(segment); - mock - .expects('track') - .once() - .withArgs({ + const spy = jest.spyOn(segment, 'track'); + const metaMetricsController = getMetaMetricsController(); + expect(metaMetricsController.version).toStrictEqual(VERSION); + expect(metaMetricsController.chainId).toStrictEqual(FAKE_CHAIN_ID); + expect( + metaMetricsController.state.participateInMetaMetrics, + ).toStrictEqual(true); + expect(metaMetricsController.state.metaMetricsId).toStrictEqual( + TEST_META_METRICS_ID, + ); + expect(metaMetricsController.locale).toStrictEqual( + LOCALE.replace('_', '-'), + ); + expect(metaMetricsController.state.fragments).toStrictEqual({ + testid: SAMPLE_PERSISTED_EVENT, + }); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith( + { event: 'sample non-persisted event failure', userId: TEST_META_METRICS_ID, context: DEFAULT_TEST_CONTEXT, @@ -175,26 +181,9 @@ describe('MetaMetricsController', function () { }, messageId: 'sample-non-persisted-event-failure', timestamp: new Date(), - }); - const metaMetricsController = getMetaMetricsController(); - assert.strictEqual(metaMetricsController.version, VERSION); - assert.strictEqual(metaMetricsController.chainId, FAKE_CHAIN_ID); - assert.strictEqual( - metaMetricsController.state.participateInMetaMetrics, - true, - ); - assert.strictEqual( - metaMetricsController.state.metaMetricsId, - TEST_META_METRICS_ID, - ); - assert.strictEqual( - metaMetricsController.locale, - LOCALE.replace('_', '-'), + }, + spy.mock.calls[0][1], ); - assert.deepStrictEqual(metaMetricsController.state.fragments, { - testid: SAMPLE_PERSISTED_EVENT, - }); - mock.verify(); }); it('should update when network changes', function () { @@ -211,7 +200,7 @@ describe('MetaMetricsController', function () { chainId = '0x222'; networkDidChangeListener(); - assert.strictEqual(metaMetricsController.chainId, '0x222'); + expect(metaMetricsController.chainId).toStrictEqual('0x222'); }); it('should update when preferences changes', function () { @@ -219,20 +208,17 @@ describe('MetaMetricsController', function () { const metaMetricsController = getMetaMetricsController({ preferencesStore, }); - preferencesStore.updateState({ - currentLocale: 'en_UK', - }); - assert.strictEqual(metaMetricsController.locale, 'en-UK'); + preferencesStore.updateState({ currentLocale: 'en_UK' }); + expect(metaMetricsController.locale).toStrictEqual('en-UK'); }); }); describe('generateMetaMetricsId', function () { it('should generate an 0x prefixed hex string', function () { const metaMetricsController = getMetaMetricsController(); - assert.equal( + expect( metaMetricsController.generateMetaMetricsId().startsWith('0x'), - true, - ); + ).toStrictEqual(true); }); }); @@ -244,92 +230,79 @@ describe('MetaMetricsController', function () { }); // Starts off being empty. - assert.equal(metaMetricsController.state.metaMetricsId, null); + expect(metaMetricsController.state.metaMetricsId).toStrictEqual(null); // Create a new metametrics id. const clientMetaMetricsId = metaMetricsController.getMetaMetricsId(); - assert.equal(clientMetaMetricsId.startsWith('0x'), true); + expect(clientMetaMetricsId.startsWith('0x')).toStrictEqual(true); // Return same metametrics id. const sameMetaMetricsId = metaMetricsController.getMetaMetricsId(); - assert.equal(clientMetaMetricsId, sameMetaMetricsId); + expect(clientMetaMetricsId).toStrictEqual(sameMetaMetricsId); }); }); describe('identify', function () { - it('should call segment.identify for valid traits if user is participating in metametrics', async function () { + it('should call segment.identify for valid traits if user is participating in metametrics', function () { + const spy = jest.spyOn(segment, 'identify'); const metaMetricsController = getMetaMetricsController({ participateInMetaMetrics: true, metaMetricsId: TEST_META_METRICS_ID, }); - const mock = sinon.mock(segment); - - mock.expects('identify').once().withArgs({ - userId: TEST_META_METRICS_ID, - traits: MOCK_TRAITS, - messageId: Utils.generateRandomId(), - timestamp: new Date(), - }); - metaMetricsController.identify({ ...MOCK_TRAITS, ...MOCK_INVALID_TRAITS, }); - - mock.verify(); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith( + { + userId: TEST_META_METRICS_ID, + traits: MOCK_TRAITS, + messageId: Utils.generateRandomId(), + timestamp: new Date(), + }, + spy.mock.calls[0][1], + ); }); - it('should transform date type traits into ISO-8601 timestamp strings', async function () { + it('should transform date type traits into ISO-8601 timestamp strings', function () { + const spy = jest.spyOn(segment, 'identify'); const metaMetricsController = getMetaMetricsController({ participateInMetaMetrics: true, metaMetricsId: TEST_META_METRICS_ID, }); - const mock = sinon.mock(segment); - - const mockDate = new Date(); - const mockDateISOString = mockDate.toISOString(); - - mock - .expects('identify') - .once() - .withArgs({ + metaMetricsController.identify({ test_date: new Date().toISOString() }); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith( + { userId: TEST_META_METRICS_ID, traits: { - test_date: mockDateISOString, + test_date: new Date().toISOString(), }, messageId: Utils.generateRandomId(), timestamp: new Date(), - }); - - metaMetricsController.identify({ - test_date: mockDate, - }); - mock.verify(); + }, + spy.mock.calls[0][1], + ); }); it('should not call segment.identify if user is not participating in metametrics', function () { + const spy = jest.spyOn(segment, 'identify'); const metaMetricsController = getMetaMetricsController({ participateInMetaMetrics: false, }); - const mock = sinon.mock(segment); - - mock.expects('identify').never(); - metaMetricsController.identify(MOCK_TRAITS); - mock.verify(); + expect(spy).toHaveBeenCalledTimes(0); }); - it('should not call segment.identify if there are no valid traits to identify', async function () { + it('should not call segment.identify if there are no valid traits to identify', function () { + const spy = jest.spyOn(segment, 'identify'); const metaMetricsController = getMetaMetricsController({ participateInMetaMetrics: true, metaMetricsId: TEST_META_METRICS_ID, }); - const mock = sinon.mock(segment); - - mock.expects('identify').never(); - metaMetricsController.identify(MOCK_INVALID_TRAITS); - mock.verify(); + expect(spy).toHaveBeenCalledTimes(0); }); }); @@ -339,28 +312,35 @@ describe('MetaMetricsController', function () { participateInMetaMetrics: null, metaMetricsId: null, }); - assert.equal(metaMetricsController.state.participateInMetaMetrics, null); + expect( + metaMetricsController.state.participateInMetaMetrics, + ).toStrictEqual(null); await metaMetricsController.setParticipateInMetaMetrics(true); - assert.ok(globalThis.sentry.startSession.calledOnce); - assert.equal(metaMetricsController.state.participateInMetaMetrics, true); + expect(globalThis.sentry.startSession).toHaveBeenCalledTimes(1); + expect( + metaMetricsController.state.participateInMetaMetrics, + ).toStrictEqual(true); await metaMetricsController.setParticipateInMetaMetrics(false); - assert.equal(metaMetricsController.state.participateInMetaMetrics, false); + expect( + metaMetricsController.state.participateInMetaMetrics, + ).toStrictEqual(false); }); it('should generate and update the metaMetricsId when set to true', async function () { const metaMetricsController = getMetaMetricsController({ participateInMetaMetrics: null, metaMetricsId: null, }); - assert.equal(metaMetricsController.state.metaMetricsId, null); + expect(metaMetricsController.state.metaMetricsId).toStrictEqual(null); await metaMetricsController.setParticipateInMetaMetrics(true); - assert.equal(typeof metaMetricsController.state.metaMetricsId, 'string'); + expect(typeof metaMetricsController.state.metaMetricsId).toStrictEqual( + 'string', + ); }); it('should not nullify the metaMetricsId when set to false', async function () { const metaMetricsController = getMetaMetricsController(); await metaMetricsController.setParticipateInMetaMetrics(false); - assert.ok(globalThis.sentry.endSession.calledOnce); - assert.equal( - metaMetricsController.state.metaMetricsId, + expect(globalThis.sentry.endSession).toHaveBeenCalledTimes(1); + expect(metaMetricsController.state.metaMetricsId).toStrictEqual( TEST_META_METRICS_ID, ); }); @@ -368,11 +348,10 @@ describe('MetaMetricsController', function () { describe('submitEvent', function () { it('should not track an event if user is not participating in metametrics', function () { - const mock = sinon.mock(segment); + const spy = jest.spyOn(segment, 'track'); const metaMetricsController = getMetaMetricsController({ participateInMetaMetrics: false, }); - mock.expects('track').never(); metaMetricsController.submitEvent({ event: 'Fake Event', category: 'Unit Test', @@ -380,18 +359,27 @@ describe('MetaMetricsController', function () { test: 1, }, }); - mock.verify(); + expect(spy).toHaveBeenCalledTimes(0); }); it('should track an event if user has not opted in, but isOptIn is true', function () { - const mock = sinon.mock(segment); const metaMetricsController = getMetaMetricsController({ participateInMetaMetrics: true, }); - mock - .expects('track') - .once() - .withArgs({ + const spy = jest.spyOn(segment, 'track'); + metaMetricsController.submitEvent( + { + event: 'Fake Event', + category: 'Unit Test', + properties: { + test: 1, + }, + }, + { isOptIn: true }, + ); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith( + { event: 'Fake Event', anonymousId: METAMETRICS_ANONYMOUS_ID, context: DEFAULT_TEST_CONTEXT, @@ -401,7 +389,16 @@ describe('MetaMetricsController', function () { }, messageId: Utils.generateRandomId(), timestamp: new Date(), - }); + }, + spy.mock.calls[0][1], + ); + }); + + it('should track an event during optin and allow for metaMetricsId override', function () { + const metaMetricsController = getMetaMetricsController({ + participateInMetaMetrics: true, + }); + const spy = jest.spyOn(segment, 'track'); metaMetricsController.submitEvent( { event: 'Fake Event', @@ -410,20 +407,11 @@ describe('MetaMetricsController', function () { test: 1, }, }, - { isOptIn: true }, + { isOptIn: true, metaMetricsId: 'TESTID' }, ); - mock.verify(); - }); - - it('should track an event during optin and allow for metaMetricsId override', function () { - const mock = sinon.mock(segment); - const metaMetricsController = getMetaMetricsController({ - participateInMetaMetrics: true, - }); - mock - .expects('track') - .once() - .withArgs({ + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith( + { event: 'Fake Event', userId: 'TESTID', context: DEFAULT_TEST_CONTEXT, @@ -433,7 +421,14 @@ describe('MetaMetricsController', function () { }, messageId: Utils.generateRandomId(), timestamp: new Date(), - }); + }, + spy.mock.calls[0][1], + ); + }); + + it('should track a legacy event', function () { + const metaMetricsController = getMetaMetricsController(); + const spy = jest.spyOn(segment, 'track'); metaMetricsController.submitEvent( { event: 'Fake Event', @@ -442,18 +437,11 @@ describe('MetaMetricsController', function () { test: 1, }, }, - { isOptIn: true, metaMetricsId: 'TESTID' }, + { matomoEvent: true }, ); - mock.verify(); - }); - - it('should track a legacy event', function () { - const mock = sinon.mock(segment); - const metaMetricsController = getMetaMetricsController(); - mock - .expects('track') - .once() - .withArgs({ + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith( + { event: 'Fake Event', userId: TEST_META_METRICS_ID, context: DEFAULT_TEST_CONTEXT, @@ -464,27 +452,24 @@ describe('MetaMetricsController', function () { }, messageId: Utils.generateRandomId(), timestamp: new Date(), - }); - metaMetricsController.submitEvent( - { - event: 'Fake Event', - category: 'Unit Test', - properties: { - test: 1, - }, }, - { matomoEvent: true }, + spy.mock.calls[0][1], ); - mock.verify(); }); it('should track a non legacy event', function () { - const mock = sinon.mock(segment); const metaMetricsController = getMetaMetricsController(); - mock - .expects('track') - .once() - .withArgs({ + const spy = jest.spyOn(segment, 'track'); + metaMetricsController.submitEvent({ + event: 'Fake Event', + category: 'Unit Test', + properties: { + test: 1, + }, + }); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith( + { event: 'Fake Event', properties: { test: 1, @@ -494,21 +479,14 @@ describe('MetaMetricsController', function () { userId: TEST_META_METRICS_ID, messageId: Utils.generateRandomId(), timestamp: new Date(), - }); - metaMetricsController.submitEvent({ - event: 'Fake Event', - category: 'Unit Test', - properties: { - test: 1, }, - }); - mock.verify(); + spy.mock.calls[0][1], + ); }); - it('should immediately flush queue if flushImmediately set to true', async function () { + it('should immediately flush queue if flushImmediately set to true', function () { const metaMetricsController = getMetaMetricsController(); - const flushStub = sinon.stub(segment, 'flush'); - const flushCalled = waitUntilCalled(flushStub, segment); + const spy = jest.spyOn(segment, 'flush'); metaMetricsController.submitEvent( { event: 'Fake Event', @@ -516,51 +494,48 @@ describe('MetaMetricsController', function () { }, { flushImmediately: true }, ); - assert.doesNotReject(flushCalled()); + expect(spy).not.toThrow(); }); - it('should throw if event or category not provided', function () { + it('should throw if event or category not provided', async function () { const metaMetricsController = getMetaMetricsController(); - assert.rejects( - () => metaMetricsController.submitEvent({ event: 'test' }), - /Must specify event and category\./u, - 'must specify category', - ); - assert.rejects( - () => metaMetricsController.submitEvent({ category: 'test' }), - /Must specify event and category\./u, - 'must specify event', - ); + await expect( + metaMetricsController.submitEvent({ event: 'test' }), + ).rejects.toThrow(/Must specify event and category\./u); + + await expect( + metaMetricsController.submitEvent({ category: 'test' }), + ).rejects.toThrow(/Must specify event and category\./u); }); - it('should throw if provided sensitiveProperties, when excludeMetaMetricsId is true', function () { + it('should throw if provided sensitiveProperties, when excludeMetaMetricsId is true', async function () { const metaMetricsController = getMetaMetricsController(); - assert.rejects( - () => - metaMetricsController.submitEvent( - { - event: 'Fake Event', - category: 'Unit Test', - sensitiveProperties: { foo: 'bar' }, - }, - { excludeMetaMetricsId: true }, - ), + await expect( + metaMetricsController.submitEvent( + { + event: 'Fake Event', + category: 'Unit Test', + sensitiveProperties: { foo: 'bar' }, + }, + { excludeMetaMetricsId: true }, + ), + ).rejects.toThrow( /sensitiveProperties was specified in an event payload that also set the excludeMetaMetricsId flag/u, ); }); it('should track sensitiveProperties in a separate, anonymous event', function () { const metaMetricsController = getMetaMetricsController(); - const spy = sinon.spy(segment, 'track'); + const spy = jest.spyOn(segment, 'track'); metaMetricsController.submitEvent({ event: 'Fake Event', category: 'Unit Test', sensitiveProperties: { foo: 'bar' }, }); - assert.ok(spy.calledTwice); - assert.ok( - spy.calledWith({ + expect(spy).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenCalledWith( + { event: 'Fake Event', anonymousId: METAMETRICS_ANONYMOUS_ID, context: DEFAULT_TEST_CONTEXT, @@ -570,17 +545,19 @@ describe('MetaMetricsController', function () { }, messageId: Utils.generateRandomId(), timestamp: new Date(), - }), + }, + spy.mock.calls[0][1], ); - assert.ok( - spy.calledWith({ + expect(spy).toHaveBeenCalledWith( + { event: 'Fake Event', userId: TEST_META_METRICS_ID, context: DEFAULT_TEST_CONTEXT, properties: DEFAULT_EVENT_PROPERTIES, messageId: Utils.generateRandomId(), timestamp: new Date(), - }), + }, + spy.mock.calls[1][1], ); }); }); @@ -588,15 +565,15 @@ describe('MetaMetricsController', function () { describe('Change Transaction XXX anonymous event namnes', function () { it('should change "Transaction Added" anonymous event names to "Transaction Added Anon"', function () { const metaMetricsController = getMetaMetricsController(); - const spy = sinon.spy(segment, 'track'); + const spy = jest.spyOn(segment, 'track'); metaMetricsController.submitEvent({ event: 'Transaction Added', category: 'Unit Test', sensitiveProperties: { foo: 'bar' }, }); - assert.ok(spy.calledTwice); - assert.ok( - spy.calledWith({ + expect(spy).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenCalledWith( + { event: `Transaction Added Anon`, anonymousId: METAMETRICS_ANONYMOUS_ID, context: DEFAULT_TEST_CONTEXT, @@ -606,21 +583,22 @@ describe('MetaMetricsController', function () { }, messageId: Utils.generateRandomId(), timestamp: new Date(), - }), + }, + spy.mock.calls[0][1], ); }); it('should change "Transaction Submitted" anonymous event names to "Transaction Added Anon"', function () { const metaMetricsController = getMetaMetricsController(); - const spy = sinon.spy(segment, 'track'); + const spy = jest.spyOn(segment, 'track'); metaMetricsController.submitEvent({ event: 'Transaction Submitted', category: 'Unit Test', sensitiveProperties: { foo: 'bar' }, }); - assert.ok(spy.calledTwice); - assert.ok( - spy.calledWith({ + expect(spy).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenCalledWith( + { event: `Transaction Submitted Anon`, anonymousId: METAMETRICS_ANONYMOUS_ID, context: DEFAULT_TEST_CONTEXT, @@ -630,21 +608,22 @@ describe('MetaMetricsController', function () { }, messageId: Utils.generateRandomId(), timestamp: new Date(), - }), + }, + spy.mock.calls[0][1], ); }); it('should change "Transaction Finalized" anonymous event names to "Transaction Added Anon"', function () { const metaMetricsController = getMetaMetricsController(); - const spy = sinon.spy(segment, 'track'); + const spy = jest.spyOn(segment, 'track'); metaMetricsController.submitEvent({ event: 'Transaction Finalized', category: 'Unit Test', sensitiveProperties: { foo: 'bar' }, }); - assert.ok(spy.calledTwice); - assert.ok( - spy.calledWith({ + expect(spy).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenCalledWith( + { event: `Transaction Finalized Anon`, anonymousId: METAMETRICS_ANONYMOUS_ID, context: DEFAULT_TEST_CONTEXT, @@ -654,19 +633,25 @@ describe('MetaMetricsController', function () { }, messageId: Utils.generateRandomId(), timestamp: new Date(), - }), + }, + spy.mock.calls[0][1], ); }); }); describe('trackPage', function () { it('should track a page view', function () { - const mock = sinon.mock(segment); const metaMetricsController = getMetaMetricsController(); - mock - .expects('page') - .once() - .withArgs({ + const spy = jest.spyOn(segment, 'page'); + metaMetricsController.trackPage({ + name: 'home', + params: null, + environmentType: ENVIRONMENT_TYPE_BACKGROUND, + page: METAMETRICS_BACKGROUND_PAGE_OBJECT, + }); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith( + { name: 'home', userId: TEST_META_METRICS_ID, context: DEFAULT_TEST_CONTEXT, @@ -676,42 +661,44 @@ describe('MetaMetricsController', function () { }, messageId: Utils.generateRandomId(), timestamp: new Date(), - }); - metaMetricsController.trackPage({ - name: 'home', - params: null, - environmentType: ENVIRONMENT_TYPE_BACKGROUND, - page: METAMETRICS_BACKGROUND_PAGE_OBJECT, - }); - mock.verify(); + }, + spy.mock.calls[0][1], + ); }); it('should not track a page view if user is not participating in metametrics', function () { - const mock = sinon.mock(segment); const metaMetricsController = getMetaMetricsController({ participateInMetaMetrics: false, }); - mock.expects('page').never(); + const spy = jest.spyOn(segment, 'page'); metaMetricsController.trackPage({ name: 'home', params: null, environmentType: ENVIRONMENT_TYPE_BACKGROUND, page: METAMETRICS_BACKGROUND_PAGE_OBJECT, }); - mock.verify(); + expect(spy).toHaveBeenCalledTimes(0); }); it('should track a page view if isOptInPath is true and user not yet opted in', function () { - const mock = sinon.mock(segment); const metaMetricsController = getMetaMetricsController({ preferencesStore: getMockPreferencesStore({ participateInMetaMetrics: null, }), }); - mock - .expects('page') - .once() - .withArgs({ + const spy = jest.spyOn(segment, 'page'); + metaMetricsController.trackPage( + { + name: 'home', + params: null, + environmentType: ENVIRONMENT_TYPE_BACKGROUND, + page: METAMETRICS_BACKGROUND_PAGE_OBJECT, + }, + { isOptInPath: true }, + ); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith( + { name: 'home', userId: TEST_META_METRICS_ID, context: DEFAULT_TEST_CONTEXT, @@ -721,40 +708,18 @@ describe('MetaMetricsController', function () { }, messageId: Utils.generateRandomId(), timestamp: new Date(), - }); - metaMetricsController.trackPage( - { - name: 'home', - params: null, - environmentType: ENVIRONMENT_TYPE_BACKGROUND, - page: METAMETRICS_BACKGROUND_PAGE_OBJECT, }, - { isOptInPath: true }, + spy.mock.calls[0][1], ); - mock.verify(); }); it('multiple trackPage call with same actionId should result in same messageId being sent to segment', function () { - const mock = sinon.mock(segment); const metaMetricsController = getMetaMetricsController({ preferencesStore: getMockPreferencesStore({ participateInMetaMetrics: null, }), }); - mock - .expects('page') - .twice() - .withArgs({ - name: 'home', - userId: TEST_META_METRICS_ID, - context: DEFAULT_TEST_CONTEXT, - properties: { - params: null, - ...DEFAULT_PAGE_PROPERTIES, - }, - messageId: DUMMY_ACTION_ID, - timestamp: new Date(), - }); + const spy = jest.spyOn(segment, 'page'); metaMetricsController.trackPage( { name: 'home', @@ -775,23 +740,37 @@ describe('MetaMetricsController', function () { }, { isOptInPath: true }, ); - mock.verify(); + expect(spy).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenCalledWith( + { + name: 'home', + userId: TEST_META_METRICS_ID, + context: DEFAULT_TEST_CONTEXT, + properties: { + params: null, + ...DEFAULT_PAGE_PROPERTIES, + }, + messageId: DUMMY_ACTION_ID, + timestamp: new Date(), + }, + spy.mock.calls[0][1], + ); }); }); describe('deterministic messageId', function () { it('should use the actionId as messageId when provided', function () { const metaMetricsController = getMetaMetricsController(); - const spy = sinon.spy(segment, 'track'); + const spy = jest.spyOn(segment, 'track'); metaMetricsController.submitEvent({ event: 'Fake Event', category: 'Unit Test', properties: { foo: 'bar' }, actionId: '0x001', }); - assert.ok(spy.calledOnce); - assert.ok( - spy.calledWith({ + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith( + { event: 'Fake Event', userId: TEST_META_METRICS_ID, context: DEFAULT_TEST_CONTEXT, @@ -801,23 +780,23 @@ describe('MetaMetricsController', function () { }, messageId: '0x001', timestamp: new Date(), - }), + }, + spy.mock.calls[0][1], ); }); it('should append 0x000 to the actionId of anonymized event when tracking sensitiveProperties', function () { const metaMetricsController = getMetaMetricsController(); - const spy = sinon.spy(segment, 'track'); + const spy = jest.spyOn(segment, 'track'); metaMetricsController.submitEvent({ event: 'Fake Event', category: 'Unit Test', sensitiveProperties: { foo: 'bar' }, actionId: '0x001', }); - assert.ok(spy.calledTwice); - - assert.ok( - spy.calledWith({ + expect(spy).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenCalledWith( + { event: 'Fake Event', anonymousId: METAMETRICS_ANONYMOUS_ID, context: DEFAULT_TEST_CONTEXT, @@ -827,10 +806,11 @@ describe('MetaMetricsController', function () { }, messageId: '0x001-0x000', timestamp: new Date(), - }), + }, + spy.mock.calls[0][1], ); - assert.ok( - spy.calledWith({ + expect(spy).toHaveBeenCalledWith( + { event: 'Fake Event', userId: TEST_META_METRICS_ID, context: DEFAULT_TEST_CONTEXT, @@ -839,22 +819,23 @@ describe('MetaMetricsController', function () { }, messageId: '0x001', timestamp: new Date(), - }), + }, + spy.mock.calls[1][1], ); }); it('should use the uniqueIdentifier as messageId when provided', function () { const metaMetricsController = getMetaMetricsController(); - const spy = sinon.spy(segment, 'track'); + const spy = jest.spyOn(segment, 'track'); metaMetricsController.submitEvent({ event: 'Fake Event', category: 'Unit Test', properties: { foo: 'bar' }, uniqueIdentifier: 'transaction-submitted-0000', }); - assert.ok(spy.calledOnce); - assert.ok( - spy.calledWith({ + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith( + { event: 'Fake Event', userId: TEST_META_METRICS_ID, context: DEFAULT_TEST_CONTEXT, @@ -864,22 +845,23 @@ describe('MetaMetricsController', function () { }, messageId: 'transaction-submitted-0000', timestamp: new Date(), - }), + }, + spy.mock.calls[0][1], ); }); it('should append 0x000 to the uniqueIdentifier of anonymized event when tracking sensitiveProperties', function () { const metaMetricsController = getMetaMetricsController(); - const spy = sinon.spy(segment, 'track'); + const spy = jest.spyOn(segment, 'track'); metaMetricsController.submitEvent({ event: 'Fake Event', category: 'Unit Test', sensitiveProperties: { foo: 'bar' }, uniqueIdentifier: 'transaction-submitted-0000', }); - assert.ok(spy.calledTwice); - assert.ok( - spy.calledWith({ + expect(spy).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenCalledWith( + { event: 'Fake Event', anonymousId: METAMETRICS_ANONYMOUS_ID, context: DEFAULT_TEST_CONTEXT, @@ -889,10 +871,11 @@ describe('MetaMetricsController', function () { }, messageId: 'transaction-submitted-0000-0x000', timestamp: new Date(), - }), + }, + spy.mock.calls[0][1], ); - assert.ok( - spy.calledWith({ + expect(spy).toHaveBeenCalledWith( + { event: 'Fake Event', userId: TEST_META_METRICS_ID, context: DEFAULT_TEST_CONTEXT, @@ -901,13 +884,14 @@ describe('MetaMetricsController', function () { }, messageId: 'transaction-submitted-0000', timestamp: new Date(), - }), + }, + spy.mock.calls[1][1], ); }); it('should combine the uniqueIdentifier and actionId as messageId when both provided', function () { const metaMetricsController = getMetaMetricsController(); - const spy = sinon.spy(segment, 'track'); + const spy = jest.spyOn(segment, 'track'); metaMetricsController.submitEvent({ event: 'Fake Event', category: 'Unit Test', @@ -915,9 +899,9 @@ describe('MetaMetricsController', function () { actionId: '0x001', uniqueIdentifier: 'transaction-submitted-0000', }); - assert.ok(spy.calledOnce); - assert.ok( - spy.calledWith({ + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith( + { event: 'Fake Event', userId: TEST_META_METRICS_ID, context: DEFAULT_TEST_CONTEXT, @@ -927,13 +911,14 @@ describe('MetaMetricsController', function () { }, messageId: 'transaction-submitted-0000-0x001', timestamp: new Date(), - }), + }, + spy.mock.calls[0][1], ); }); it('should append 0x000 to the combined uniqueIdentifier and actionId of anonymized event when tracking sensitiveProperties', function () { const metaMetricsController = getMetaMetricsController(); - const spy = sinon.spy(segment, 'track'); + const spy = jest.spyOn(segment, 'track'); metaMetricsController.submitEvent({ event: 'Fake Event', category: 'Unit Test', @@ -941,9 +926,9 @@ describe('MetaMetricsController', function () { actionId: '0x001', uniqueIdentifier: 'transaction-submitted-0000', }); - assert.ok(spy.calledTwice); - assert.ok( - spy.calledWith({ + expect(spy).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenCalledWith( + { event: 'Fake Event', anonymousId: METAMETRICS_ANONYMOUS_ID, context: DEFAULT_TEST_CONTEXT, @@ -953,10 +938,11 @@ describe('MetaMetricsController', function () { }, messageId: 'transaction-submitted-0000-0x001-0x000', timestamp: new Date(), - }), + }, + spy.mock.calls[0][1], ); - assert.ok( - spy.calledWith({ + expect(spy).toHaveBeenCalledWith( + { event: 'Fake Event', userId: TEST_META_METRICS_ID, context: DEFAULT_TEST_CONTEXT, @@ -965,7 +951,8 @@ describe('MetaMetricsController', function () { }, messageId: 'transaction-submitted-0000-0x001', timestamp: new Date(), - }), + }, + spy.mock.calls[1][1], ); }); }); @@ -1093,7 +1080,7 @@ describe('MetaMetricsController', function () { }, }); - assert.deepEqual(traits, { + expect(traits).toStrictEqual({ [MetaMetricsUserTrait.AddressBookEntries]: 3, [MetaMetricsUserTrait.InstallDateExt]: '', [MetaMetricsUserTrait.LedgerConnectionType]: 'web-hid', @@ -1182,7 +1169,7 @@ describe('MetaMetricsController', function () { useNativeCurrencyAsPrimaryCurrency: false, }); - assert.deepEqual(updatedTraits, { + expect(updatedTraits).toStrictEqual({ [MetaMetricsUserTrait.AddressBookEntries]: 4, [MetaMetricsUserTrait.NumberOfAccounts]: 3, [MetaMetricsUserTrait.NumberOfTokens]: 1, @@ -1242,8 +1229,7 @@ describe('MetaMetricsController', function () { useTokenDetection: true, useNativeCurrencyAsPrimaryCurrency: true, }); - - assert.equal(updatedTraits, null); + expect(updatedTraits).toStrictEqual(null); }); }); @@ -1252,7 +1238,7 @@ describe('MetaMetricsController', function () { const metaMetricsController = getMetaMetricsController({}); metaMetricsController.trackPage({}, { isOptIn: true }); const { segmentApiCalls } = metaMetricsController.store.getState(); - assert(Object.keys(segmentApiCalls).length > 0); + expect(Object.keys(segmentApiCalls).length > 0).toStrictEqual(true); }); it('should remove event from store when callback is invoked', function () { @@ -1260,22 +1246,22 @@ describe('MetaMetricsController', function () { const stubFn = (_, cb) => { cb(); }; - sinon.stub(segmentInstance, 'track').callsFake(stubFn); - sinon.stub(segmentInstance, 'page').callsFake(stubFn); + jest.spyOn(segmentInstance, 'track').mockImplementation(stubFn); + jest.spyOn(segmentInstance, 'page').mockImplementation(stubFn); const metaMetricsController = getMetaMetricsController({ segmentInstance, }); metaMetricsController.trackPage({}, { isOptIn: true }); const { segmentApiCalls } = metaMetricsController.store.getState(); - assert(Object.keys(segmentApiCalls).length === 0); + expect(Object.keys(segmentApiCalls).length === 0).toStrictEqual(true); }); }); afterEach(function () { // flush the queues manually after each test segment.flush(); - clock.restore(); - sinon.restore(); + jest.useRealTimers(); + jest.restoreAllMocks(); }); }); diff --git a/app/scripts/controllers/mmi-controller.test.ts b/app/scripts/controllers/mmi-controller.test.ts index 3616c7ae29fe..102cd63d52f3 100644 --- a/app/scripts/controllers/mmi-controller.test.ts +++ b/app/scripts/controllers/mmi-controller.test.ts @@ -29,6 +29,7 @@ jest.mock('@metamask-institutional/portfolio-dashboard', () => ({ })); jest.mock('./permissions', () => ({ + ...jest.requireActual('./permissions'), getPermissionBackgroundApiMethods: jest.fn().mockImplementation(() => { return { addPermittedAccount: jest.fn(), @@ -299,12 +300,12 @@ describe('MMIController', function () { const result = await mmiController.addKeyringIfNotExists(type); - expect(mmiController.keyringController.getKeyringsByType).toHaveBeenCalledWith( - type - ); - expect(mmiController.keyringController.addNewKeyring).toHaveBeenCalledWith( - type - ); + expect( + mmiController.keyringController.getKeyringsByType, + ).toHaveBeenCalledWith(type); + expect( + mmiController.keyringController.addNewKeyring, + ).toHaveBeenCalledWith(type); expect(result).toBe('new-keyring'); }); @@ -318,10 +319,12 @@ describe('MMIController', function () { const result = await mmiController.addKeyringIfNotExists(type); - expect(mmiController.keyringController.getKeyringsByType).toHaveBeenCalledWith( - type - ); - expect(mmiController.keyringController.addNewKeyring).not.toHaveBeenCalled(); + expect( + mmiController.keyringController.getKeyringsByType, + ).toHaveBeenCalledWith(type); + expect( + mmiController.keyringController.addNewKeyring, + ).not.toHaveBeenCalled(); expect(result).toBe(existingKeyring); }); }); @@ -331,27 +334,30 @@ describe('MMIController', function () { mmiController.custodyController.getAllCustodyTypes = jest .fn() .mockReturnValue(['mock-custody-type']); - mmiController.addKeyringIfNotExists = jest - .fn() - .mockResolvedValue({ - on: jest.fn(), - getAccounts: jest.fn().mockResolvedValue(['0x1']), - getSupportedChains: jest.fn().mockResolvedValue({}), - }); + mmiController.addKeyringIfNotExists = jest.fn().mockResolvedValue({ + on: jest.fn(), + getAccounts: jest.fn().mockResolvedValue(['0x1']), + getSupportedChains: jest.fn().mockResolvedValue({}), + }); mmiController.storeCustodianSupportedChains = jest.fn(); mmiController.txStateManager = { getTransactions: jest.fn().mockReturnValue([]), }; mmiController.transactionUpdateController.subscribeToEvents = jest.fn(); mmiController.mmiConfigurationController.storeConfiguration = jest.fn(); - mmiController.transactionUpdateController.getCustomerProofForAddresses = jest.fn(); + mmiController.transactionUpdateController.getCustomerProofForAddresses = + jest.fn(); await mmiController.onSubmitPassword(); expect(mmiController.addKeyringIfNotExists).toHaveBeenCalled(); expect(mmiController.storeCustodianSupportedChains).toHaveBeenCalled(); - expect(mmiController.transactionUpdateController.subscribeToEvents).toHaveBeenCalled(); - expect(mmiController.mmiConfigurationController.storeConfiguration).toHaveBeenCalled(); + expect( + mmiController.transactionUpdateController.subscribeToEvents, + ).toHaveBeenCalled(); + expect( + mmiController.mmiConfigurationController.storeConfiguration, + ).toHaveBeenCalled(); }); }); @@ -368,20 +374,20 @@ describe('MMIController', function () { chainId: 1, }, }; - CUSTODIAN_TYPES['MOCK-CUSTODIAN-TYPE'] = { keyringClass: { type: 'mock-keyring-class' } }; - mmiController.addKeyringIfNotExists = jest - .fn() - .mockResolvedValue({ - on: jest.fn(), - setSelectedAddresses: jest.fn(), - addAccounts: jest.fn(), - addNewAccountForKeyring: jest.fn(), - getStatusMap: jest.fn(), - }); + CUSTODIAN_TYPES['MOCK-CUSTODIAN-TYPE'] = { + keyringClass: { type: 'mock-keyring-class' }, + }; + mmiController.addKeyringIfNotExists = jest.fn().mockResolvedValue({ + on: jest.fn(), + setSelectedAddresses: jest.fn(), + addAccounts: jest.fn(), + addNewAccountForKeyring: jest.fn(), + getStatusMap: jest.fn(), + }); mmiController.keyringController.getAccounts = jest .fn() .mockResolvedValue(['0x2']); - mmiController.keyringController.addNewAccountForKeyring = jest.fn() + mmiController.keyringController.addNewAccountForKeyring = jest.fn(); mmiController.custodyController.setAccountDetails = jest.fn(); mmiController.accountTracker.syncWithAddresses = jest.fn(); @@ -391,51 +397,55 @@ describe('MMIController', function () { const result = await mmiController.connectCustodyAddresses( custodianType, custodianName, - accounts + accounts, ); expect(mmiController.addKeyringIfNotExists).toHaveBeenCalled(); expect(mmiController.keyringController.getAccounts).toHaveBeenCalled(); - expect(mmiController.custodyController.setAccountDetails).toHaveBeenCalled(); + expect( + mmiController.custodyController.setAccountDetails, + ).toHaveBeenCalled(); expect(mmiController.accountTracker.syncWithAddresses).toHaveBeenCalled(); expect(mmiController.storeCustodianSupportedChains).toHaveBeenCalled(); - expect(mmiController.custodyController.storeCustodyStatusMap).toHaveBeenCalled(); + expect( + mmiController.custodyController.storeCustodyStatusMap, + ).toHaveBeenCalled(); expect(result).toEqual(['0x1']); }); }); describe('getCustodianAccounts', () => { it('should return custodian accounts', async () => { - CUSTODIAN_TYPES['MOCK-CUSTODIAN-TYPE'] = { keyringClass: { type: 'mock-keyring-class' } }; - mmiController.addKeyringIfNotExists = jest - .fn() - .mockResolvedValue({ - getCustodianAccounts: jest.fn().mockResolvedValue(['account1']), - }); + CUSTODIAN_TYPES['MOCK-CUSTODIAN-TYPE'] = { + keyringClass: { type: 'mock-keyring-class' }, + }; + mmiController.addKeyringIfNotExists = jest.fn().mockResolvedValue({ + getCustodianAccounts: jest.fn().mockResolvedValue(['account1']), + }); const result = await mmiController.getCustodianAccounts( 'token', 'neptune-custody', 'ECA3', - true + true, ); expect(result).toEqual(['account1']); }); it('should return custodian accounts when custodianType is not provided', async () => { - CUSTODIAN_TYPES['CUSTODIAN-TYPE'] = { keyringClass: { type: 'mock-keyring-class' } }; + CUSTODIAN_TYPES['CUSTODIAN-TYPE'] = { + keyringClass: { type: 'mock-keyring-class' }, + }; mmiController.messenger.call = jest .fn() .mockReturnValue({ address: '0x1' }); mmiController.custodyController.getCustodyTypeByAddress = jest .fn() .mockReturnValue('custodian-type'); - mmiController.addKeyringIfNotExists = jest - .fn() - .mockResolvedValue({ - getCustodianAccounts: jest.fn().mockResolvedValue(['account1']), - }); + mmiController.addKeyringIfNotExists = jest.fn().mockResolvedValue({ + getCustodianAccounts: jest.fn().mockResolvedValue(['account1']), + }); const result = await mmiController.getCustodianAccounts( 'token', @@ -448,18 +458,18 @@ describe('MMIController', function () { describe('getCustodianAccountsByAddress', () => { it('should return custodian accounts by address', async () => { - CUSTODIAN_TYPES['MOCK-CUSTODIAN-TYPE'] = { keyringClass: { type: 'mock-keyring-class' } }; - mmiController.addKeyringIfNotExists = jest - .fn() - .mockResolvedValue({ - getCustodianAccounts: jest.fn().mockResolvedValue(['account1']), - }); + CUSTODIAN_TYPES['MOCK-CUSTODIAN-TYPE'] = { + keyringClass: { type: 'mock-keyring-class' }, + }; + mmiController.addKeyringIfNotExists = jest.fn().mockResolvedValue({ + getCustodianAccounts: jest.fn().mockResolvedValue(['account1']), + }); const result = await mmiController.getCustodianAccountsByAddress( 'token', 'envName', 'address', - 'mock-custodian-type' + 'mock-custodian-type', ); expect(result).toEqual(['account1']); @@ -471,17 +481,15 @@ describe('MMIController', function () { mmiController.custodyController.getCustodyTypeByAddress = jest .fn() .mockReturnValue('custodyType'); - mmiController.addKeyringIfNotExists = jest - .fn() - .mockResolvedValue({ - getTransactionDeepLink: jest - .fn() - .mockResolvedValue('transactionDeepLink'), - }); + mmiController.addKeyringIfNotExists = jest.fn().mockResolvedValue({ + getTransactionDeepLink: jest + .fn() + .mockResolvedValue('transactionDeepLink'), + }); const result = await mmiController.getCustodianTransactionDeepLink( 'address', - 'txId' + 'txId', ); expect(result).toEqual('transactionDeepLink'); @@ -502,13 +510,11 @@ describe('MMIController', function () { mmiController.custodyController.getCustodyTypeByAddress = jest .fn() .mockReturnValue('custodyType'); - mmiController.addKeyringIfNotExists = jest - .fn() - .mockResolvedValue({ - getTransactionDeepLink: jest - .fn() - .mockResolvedValue('transactionDeepLink'), - }); + mmiController.addKeyringIfNotExists = jest.fn().mockResolvedValue({ + getTransactionDeepLink: jest + .fn() + .mockResolvedValue('transactionDeepLink'), + }); const result = await mmiController.getCustodianConfirmDeepLink('txId'); @@ -524,17 +530,15 @@ describe('MMIController', function () { mmiController.custodyController.getCustodyTypeByAddress = jest .fn() .mockReturnValue('custodyType'); - mmiController.addKeyringIfNotExists = jest - .fn() - .mockResolvedValue({ - getTransactionDeepLink: jest - .fn() - .mockResolvedValue('transactionDeepLink'), - }); + mmiController.addKeyringIfNotExists = jest.fn().mockResolvedValue({ + getTransactionDeepLink: jest + .fn() + .mockResolvedValue('transactionDeepLink'), + }); const result = await mmiController.getCustodianSignMessageDeepLink( 'address', - 'custodyTxId' + 'custodyTxId', ); expect(result).toEqual('transactionDeepLink'); @@ -595,7 +599,7 @@ describe('MMIController', function () { ]); const result = await mmiController.getCustodianJWTList( - 'custodianEnvName' + 'custodianEnvName', ); expect(result).toEqual([]); @@ -614,7 +618,7 @@ describe('MMIController', function () { const result = await mmiController.getAllCustodianAccountsWithToken( 'custodyType', - 'token' + 'token', ); expect(result).toEqual(['account1']); @@ -629,14 +633,19 @@ describe('MMIController', function () { const keyringMock = { replaceRefreshTokenAuthDetails: jest.fn(), }; - mmiController.addKeyringIfNotExists = jest.fn().mockResolvedValue(keyringMock); + mmiController.addKeyringIfNotExists = jest + .fn() + .mockResolvedValue(keyringMock); await mmiController.setCustodianNewRefreshToken({ address: 'address', refreshToken: 'refreshToken', }); - expect(keyringMock.replaceRefreshTokenAuthDetails).toHaveBeenCalledWith('address', 'refreshToken'); + expect(keyringMock.replaceRefreshTokenAuthDetails).toHaveBeenCalledWith( + 'address', + 'refreshToken', + ); }); }); @@ -652,7 +661,8 @@ describe('MMIController', function () { .fn() .mockResolvedValue('keyring'); mmiController.appStateController.getUnlockPromise = jest.fn(); - mmiController.custodyController.handleMmiCheckIfTokenIsPresent = jest.fn(); + mmiController.custodyController.handleMmiCheckIfTokenIsPresent = + jest.fn(); await mmiController.handleMmiCheckIfTokenIsPresent({ params: { @@ -662,8 +672,12 @@ describe('MMIController', function () { }, }); - expect(mmiController.appStateController.getUnlockPromise).toHaveBeenCalled(); - expect(mmiController.custodyController.handleMmiCheckIfTokenIsPresent).toHaveBeenCalled(); + expect( + mmiController.appStateController.getUnlockPromise, + ).toHaveBeenCalled(); + expect( + mmiController.custodyController.handleMmiCheckIfTokenIsPresent, + ).toHaveBeenCalled(); }); }); @@ -673,7 +687,7 @@ describe('MMIController', function () { await mmiController.handleMmiDashboardData(); expect(controllerMessengerSpy).toHaveBeenCalledWith( - 'AccountsController:listAccounts' + 'AccountsController:listAccounts', ); expect(controllerMessengerSpy).toHaveReturnedWith([ mockAccount, @@ -689,7 +703,7 @@ describe('MMIController', function () { networks: expect.anything(), getAccountDetails: expect.anything(), extensionId: expect.anything(), - }) + }), ); }); }); @@ -710,7 +724,7 @@ describe('MMIController', function () { const result = await mmiController.newUnsignedMessage( message, request, - 'v4' + 'v4', ); expect(result).toEqual('unsignedTypedMessage'); @@ -733,16 +747,15 @@ describe('MMIController', function () { await mmiController.handleSigningEvents( signature, messageId, - 'signOperation' + 'signOperation', ); expect( - mmiController.transactionUpdateController.addTransactionToWatchList + mmiController.transactionUpdateController.addTransactionToWatchList, ).toHaveBeenCalledWith('custodianTxId', '0x1', 'signOperation', true); - expect(mmiController.signatureController.setMessageMetadata).toHaveBeenCalledWith( - messageId, - signature - ); + expect( + mmiController.signatureController.setMessageMetadata, + ).toHaveBeenCalledWith(messageId, signature); }); }); @@ -752,12 +765,12 @@ describe('MMIController', function () { await mmiController.setAccountAndNetwork( 'mock-origin', mockAccount2.address, - '0x1' + '0x1', ); expect(selectedAccountSpy).toHaveBeenCalledWith( 'AccountsController:setSelectedAccount', - mockAccount2.id + mockAccount2.id, ); const selectedAccount = accountsController.getSelectedAccount(); @@ -770,7 +783,7 @@ describe('MMIController', function () { await mmiController.setAccountAndNetwork( 'mock-origin', mockAccount.address, - '0x1' + '0x1', ); expect(selectedAccountSpy).toHaveBeenCalledTimes(1); @@ -786,10 +799,12 @@ describe('MMIController', function () { await mmiController.handleMmiOpenAddHardwareWallet(); - expect(mmiController.appStateController.getUnlockPromise).toHaveBeenCalled(); - expect(mmiController.platform.openExtensionInBrowser).toHaveBeenCalledWith( - '/new-account/connect' - ); + expect( + mmiController.appStateController.getUnlockPromise, + ).toHaveBeenCalled(); + expect( + mmiController.platform.openExtensionInBrowser, + ).toHaveBeenCalledWith('/new-account/connect'); }); }); }); diff --git a/app/scripts/controllers/permissions/specifications.js b/app/scripts/controllers/permissions/specifications.js index 4f9d402dcc03..eab746b42608 100644 --- a/app/scripts/controllers/permissions/specifications.js +++ b/app/scripts/controllers/permissions/specifications.js @@ -3,12 +3,11 @@ import { PermissionType, SubjectType, } from '@metamask/permission-controller'; -///: BEGIN:ONLY_INCLUDE_IF(snaps) import { caveatSpecifications as snapsCaveatsSpecifications, endowmentCaveatSpecifications as snapsEndowmentCaveatSpecifications, } from '@metamask/snaps-rpc-methods'; -///: END:ONLY_INCLUDE_IF +import { isValidHexAddress } from '@metamask/utils'; import { CaveatTypes, RestrictedMethods, @@ -81,10 +80,8 @@ export const getCaveatSpecifications = ({ validateCaveatNetworks(caveat.value, findNetworkClientIdByChainId), }, - ///: BEGIN:ONLY_INCLUDE_IF(snaps) ...snapsCaveatsSpecifications, ...snapsEndowmentCaveatSpecifications, - ///: END:ONLY_INCLUDE_IF }; }; @@ -141,7 +138,8 @@ export const getPermissionSpecifications = ({ }); }, methodImplementation: async (_args) => { - const accounts = await getAllAccounts(); + // We only consider EVM addresses here, hence the filtering: + const accounts = (await getAllAccounts()).filter(isValidHexAddress); const internalAccounts = getInternalAccounts(); return accounts.sort((firstAddress, secondAddress) => { @@ -308,6 +306,19 @@ function validateCaveatNetworks( }); } +/** + * Unrestricted methods for Ethereum, see {@link unrestrictedMethods} for more details. + */ +export const unrestrictedEthSigningMethods = Object.freeze([ + 'eth_sendRawTransaction', + 'eth_sendTransaction', + 'eth_sign', + 'eth_signTypedData', + 'eth_signTypedData_v1', + 'eth_signTypedData_v3', + 'eth_signTypedData_v4', +]); + /** * All unrestricted methods recognized by the PermissionController. * Unrestricted methods are ignored by the permission system, but every @@ -384,7 +395,6 @@ export const unrestrictedMethods = Object.freeze([ 'wallet_watchAsset', 'web3_clientVersion', 'web3_sha3', - ///: BEGIN:ONLY_INCLUDE_IF(snaps) 'wallet_getAllSnaps', 'wallet_getSnaps', 'wallet_requestSnaps', @@ -395,7 +405,6 @@ export const unrestrictedMethods = Object.freeze([ 'snap_createInterface', 'snap_updateInterface', 'snap_getInterfaceState', - ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) 'metamaskinstitutional_authenticate', 'metamaskinstitutional_reauthenticate', diff --git a/app/scripts/controllers/permissions/specifications.test.js b/app/scripts/controllers/permissions/specifications.test.js index 2a343c5f1689..29f9f4f1b8ce 100644 --- a/app/scripts/controllers/permissions/specifications.test.js +++ b/app/scripts/controllers/permissions/specifications.test.js @@ -361,7 +361,7 @@ describe('PermissionController specifications', () => { const getInternalAccounts = jest.fn().mockImplementationOnce(() => { return [ { - address: '0x1', + address: '0x7A2Bd22810088523516737b4Dc238A4bC37c23F2', id: '21066553-d8c8-4cdc-af33-efc921cd3ca9', metadata: { name: 'Test Account', @@ -375,7 +375,7 @@ describe('PermissionController specifications', () => { type: EthAccountType.Eoa, }, { - address: '0x2', + address: '0x7152f909e5EB3EF198f17e5Cb087c5Ced88294e3', id: '0bd7348e-bdfe-4f67-875c-de831a583857', metadata: { name: 'Test Account', @@ -388,7 +388,7 @@ describe('PermissionController specifications', () => { type: EthAccountType.Eoa, }, { - address: '0x3', + address: '0xDe70d2FF1995DC03EF1a3b584e3ae14da020C616', id: 'ff8fda69-d416-4d25-80a2-efb77bc7d4ad', metadata: { name: 'Test Account', @@ -402,7 +402,7 @@ describe('PermissionController specifications', () => { type: EthAccountType.Eoa, }, { - address: '0x4', + address: '0x04eBa9B766477d8eCA77F5f0e67AE1863C95a7E3', id: '0bd7348e-bdfe-4f67-875c-de831a583857', metadata: { name: 'Test Account', @@ -419,7 +419,12 @@ describe('PermissionController specifications', () => { }); const getAllAccounts = jest .fn() - .mockImplementationOnce(() => ['0x1', '0x2', '0x3', '0x4']); + .mockImplementationOnce(() => [ + '0x7A2Bd22810088523516737b4Dc238A4bC37c23F2', + '0x7152f909e5EB3EF198f17e5Cb087c5Ced88294e3', + '0xDe70d2FF1995DC03EF1a3b584e3ae14da020C616', + '0x04eBa9B766477d8eCA77F5f0e67AE1863C95a7E3', + ]); const { methodImplementation } = getPermissionSpecifications({ getInternalAccounts, @@ -427,10 +432,10 @@ describe('PermissionController specifications', () => { })[RestrictedMethods.eth_accounts]; expect(await methodImplementation()).toStrictEqual([ - '0x3', - '0x4', - '0x1', - '0x2', + '0xDe70d2FF1995DC03EF1a3b584e3ae14da020C616', + '0x04eBa9B766477d8eCA77F5f0e67AE1863C95a7E3', + '0x7A2Bd22810088523516737b4Dc238A4bC37c23F2', + '0x7152f909e5EB3EF198f17e5Cb087c5Ced88294e3', ]); }); @@ -438,7 +443,7 @@ describe('PermissionController specifications', () => { const getInternalAccounts = jest.fn().mockImplementationOnce(() => { return [ { - address: '0x2', + address: '0x7152f909e5EB3EF198f17e5Cb087c5Ced88294e3', id: '0bd7348e-bdfe-4f67-875c-de831a583857', metadata: { name: 'Test Account', @@ -452,7 +457,7 @@ describe('PermissionController specifications', () => { type: EthAccountType.Eoa, }, { - address: '0x3', + address: '0xDe70d2FF1995DC03EF1a3b584e3ae14da020C616', id: 'ff8fda69-d416-4d25-80a2-efb77bc7d4ad', metadata: { name: 'Test Account', @@ -469,7 +474,11 @@ describe('PermissionController specifications', () => { }); const getAllAccounts = jest .fn() - .mockImplementationOnce(() => ['0x1', '0x2', '0x3']); + .mockImplementationOnce(() => [ + '0x7A2Bd22810088523516737b4Dc238A4bC37c23F2', + '0x7152f909e5EB3EF198f17e5Cb087c5Ced88294e3', + '0xDe70d2FF1995DC03EF1a3b584e3ae14da020C616', + ]); const { methodImplementation } = getPermissionSpecifications({ getInternalAccounts, @@ -478,7 +487,7 @@ describe('PermissionController specifications', () => { })[RestrictedMethods.eth_accounts]; await expect(() => methodImplementation()).rejects.toThrow( - 'Missing identity for address: "0x1".', + 'Missing identity for address: "0x7A2Bd22810088523516737b4Dc238A4bC37c23F2".', ); }); @@ -486,7 +495,7 @@ describe('PermissionController specifications', () => { const getInternalAccounts = jest.fn().mockImplementationOnce(() => { return [ { - address: '0x1', + address: '0x7A2Bd22810088523516737b4Dc238A4bC37c23F2', id: 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3', metadata: { name: 'Test Account', @@ -500,7 +509,7 @@ describe('PermissionController specifications', () => { type: EthAccountType.Eoa, }, { - address: '0x3', + address: '0xDe70d2FF1995DC03EF1a3b584e3ae14da020C616', id: 'ff8fda69-d416-4d25-80a2-efb77bc7d4ad', metadata: { name: 'Test Account', @@ -517,7 +526,11 @@ describe('PermissionController specifications', () => { }); const getAllAccounts = jest .fn() - .mockImplementationOnce(() => ['0x1', '0x2', '0x3']); + .mockImplementationOnce(() => [ + '0x7A2Bd22810088523516737b4Dc238A4bC37c23F2', + '0x7152f909e5EB3EF198f17e5Cb087c5Ced88294e3', + '0xDe70d2FF1995DC03EF1a3b584e3ae14da020C616', + ]); const { methodImplementation } = getPermissionSpecifications({ getInternalAccounts, @@ -526,7 +539,7 @@ describe('PermissionController specifications', () => { })[RestrictedMethods.eth_accounts]; await expect(() => methodImplementation()).rejects.toThrow( - 'Missing identity for address: "0x2".', + 'Missing identity for address: "0x7152f909e5EB3EF198f17e5Cb087c5Ced88294e3".', ); }); }); diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 2bcf9d4697f2..5509d804b6fd 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -56,14 +56,12 @@ export default class PreferencesController { // set to true means the dynamic list from the API is being used // set to false will be using the static list from contract-metadata useTokenDetection: opts?.initState?.useTokenDetection ?? true, - useNftDetection: false, + useNftDetection: opts?.initState?.useTokenDetection ?? true, use4ByteResolution: true, useCurrencyRateCheck: true, useRequestQueue: true, - openSeaEnabled: false, - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) + openSeaEnabled: true, // todo set this to true securityAlertsEnabled: true, - ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) addSnapAccountEnabled: false, ///: END:ONLY_INCLUDE_IF @@ -96,6 +94,7 @@ export default class PreferencesController { redesignedConfirmationsEnabled: true, featureNotificationsEnabled: false, showTokenAutodetectModal: null, + showNftAutodetectModal: null, // null because we want to show the modal only the first time }, // ENS decentralized website resolution ipfsGateway: IPFS_DEFAULT_GATEWAY_URL, @@ -275,7 +274,6 @@ export default class PreferencesController { }); } - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) /** * Setter for the `securityAlertsEnabled` property * @@ -286,7 +284,6 @@ export default class PreferencesController { securityAlertsEnabled, }); } - ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) /** diff --git a/app/scripts/controllers/preferences.test.js b/app/scripts/controllers/preferences.test.js index c2a382725c0b..fc344ada1264 100644 --- a/app/scripts/controllers/preferences.test.js +++ b/app/scripts/controllers/preferences.test.js @@ -374,10 +374,10 @@ describe('preferences controller', () => { }); describe('setUseNftDetection', () => { - it('should default to false', () => { + it('should default to true', () => { expect( preferencesController.store.getState().useNftDetection, - ).toStrictEqual(false); + ).toStrictEqual(true); }); it('should set the useNftDetection property in state', () => { @@ -405,10 +405,10 @@ describe('preferences controller', () => { }); describe('setOpenSeaEnabled', () => { - it('should default to false', () => { + it('should default to true', () => { expect( preferencesController.store.getState().openSeaEnabled, - ).toStrictEqual(false); + ).toStrictEqual(true); }); it('should set the openSeaEnabled property in state', () => { diff --git a/app/scripts/controllers/push-platform-notifications/services/services.ts b/app/scripts/controllers/push-platform-notifications/services/services.ts index da15be11c8d8..392cd22ad051 100644 --- a/app/scripts/controllers/push-platform-notifications/services/services.ts +++ b/app/scripts/controllers/push-platform-notifications/services/services.ts @@ -195,18 +195,21 @@ export async function listenToPushNotifications( const unsubscribePushNotifications = onBackgroundMessage( messaging, async (payload: MessagePayload): Promise => { - const typedPayload = payload; - - // if the payload does not contain data, do nothing try { - const notificationData: NotificationUnion = typedPayload?.data?.data - ? JSON.parse(typedPayload?.data?.data) + const data = payload?.data?.data + ? JSON.parse(payload?.data?.data) : undefined; - if (!notificationData) { + // if the payload does not contain data, do nothing + if (!data) { return; } + const notificationData = { + ...data, + type: data?.type ?? data?.data?.kind, + } as NotificationUnion; + const notification = processNotification(notificationData); onNewNotification(notification); diff --git a/app/scripts/controllers/push-platform-notifications/utils/get-notification-image.ts b/app/scripts/controllers/push-platform-notifications/utils/get-notification-image.ts new file mode 100644 index 000000000000..3dbaf951b3d0 --- /dev/null +++ b/app/scripts/controllers/push-platform-notifications/utils/get-notification-image.ts @@ -0,0 +1,6 @@ +import browser from 'webextension-polyfill'; + +export async function getNotificationImage() { + const iconUrl = await browser.runtime.getURL('../../images/icon-64.png'); + return iconUrl; +} diff --git a/app/scripts/controllers/push-platform-notifications/utils/get-notification-message.ts b/app/scripts/controllers/push-platform-notifications/utils/get-notification-message.ts index e979e9cc5b53..514c5b40fadc 100644 --- a/app/scripts/controllers/push-platform-notifications/utils/get-notification-message.ts +++ b/app/scripts/controllers/push-platform-notifications/utils/get-notification-message.ts @@ -9,6 +9,7 @@ import { t } from '../../../translate'; import type { Notification } from '../../metamask-notifications/types/types'; import ExtensionPlatform from '../../../platforms/extension'; import { getAmount, formatAmount } from './get-notification-data'; +import { getNotificationImage } from './get-notification-image'; type PushNotificationMessage = { title: string; @@ -47,9 +48,11 @@ export async function onPushNotification( return; } + const iconUrl = await getNotificationImage(); + await registration.showNotification(notificationMessage.title, { body: notificationMessage.description, - icon: './images/icon-64.png', + icon: iconUrl, tag: notification?.id, data: notification, }); diff --git a/app/scripts/controllers/swaps.constants.ts b/app/scripts/controllers/swaps.constants.ts new file mode 100644 index 000000000000..d959c02c439e --- /dev/null +++ b/app/scripts/controllers/swaps.constants.ts @@ -0,0 +1,50 @@ +import { + FALLBACK_SMART_TRANSACTIONS_MAX_FEE_MULTIPLIER, + FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME, +} from '../../../shared/constants/smartTransactions'; +import { MINUTE } from '../../../shared/constants/time'; + +import type { SwapsControllerState } from './swaps.types'; + +// The MAX_GAS_LIMIT is a number that is higher than the maximum gas costs we have observed on any aggregator +export const MAX_GAS_LIMIT = 2500000; + +// To ensure that our serves are not spammed if MetaMask is left idle, we limit the number of fetches for quotes that are made on timed intervals. +// 3 seems to be an appropriate balance of giving users the time they need when MetaMask is not left idle, and turning polling off when it is. +export const POLL_COUNT_LIMIT = 3; + +// If for any reason the MetaSwap API fails to provide a refresh time, +// provide a reasonable fallback to avoid further errors +export const FALLBACK_QUOTE_REFRESH_TIME = MINUTE; + +export const swapsControllerInitialState: { swapsState: SwapsControllerState } = + { + swapsState: { + quotes: {}, + quotesPollingLimitEnabled: false, + fetchParams: null, + tokens: null, + tradeTxId: null, + approveTxId: null, + quotesLastFetched: null, + customMaxGas: '', + customGasPrice: null, + customMaxFeePerGas: null, + customMaxPriorityFeePerGas: null, + swapsUserFeeLevel: '', + selectedAggId: null, + customApproveTxData: '', + errorKey: '', + topAggId: null, + routeState: '', + swapsFeatureIsLive: true, + saveFetchedQuotes: false, + swapsQuoteRefreshTime: FALLBACK_QUOTE_REFRESH_TIME, + swapsQuotePrefetchingRefreshTime: FALLBACK_QUOTE_REFRESH_TIME, + swapsStxBatchStatusRefreshTime: FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME, + swapsStxGetTransactionsRefreshTime: + FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME, + swapsStxMaxFeeMultiplier: FALLBACK_SMART_TRANSACTIONS_MAX_FEE_MULTIPLIER, + swapsFeatureFlags: {}, + }, + }; diff --git a/app/scripts/controllers/swaps.test.js b/app/scripts/controllers/swaps.test.js index 1389a4b59a55..e5059467532d 100644 --- a/app/scripts/controllers/swaps.test.js +++ b/app/scripts/controllers/swaps.test.js @@ -1,6 +1,3 @@ -import { strict as assert } from 'assert'; -import sinon from 'sinon'; - import { BigNumber } from '@ethersproject/bignumber'; import { mapValues } from 'lodash'; import BigNumberjs from 'bignumber.js'; @@ -13,7 +10,8 @@ import { FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME, FALLBACK_SMART_TRANSACTIONS_MAX_FEE_MULTIPLIER, } from '../../../shared/constants/smartTransactions'; -import SwapsController, { utils } from './swaps'; +import SwapsController from './swaps'; +import { getMedianEthValueQuote } from './swaps.utils'; const MOCK_FETCH_PARAMS = { slippage: 3, @@ -127,21 +125,15 @@ const EMPTY_INIT_STATE = { }, }; -const sandbox = sinon.createSandbox(); -let fetchTradesInfoStub = sandbox.stub(); -const getCurrentChainIdStub = sandbox.stub(); -const getLayer1GasFeeStub = sandbox.stub(); -const getNetworkClientIdStub = sandbox.stub(); -getCurrentChainIdStub.returns(CHAIN_IDS.MAINNET); -getNetworkClientIdStub.returns('1'); -getLayer1GasFeeStub.resolves('0x1'); -const getEIP1559GasFeeEstimatesStub = sandbox.stub(() => { - return { - gasFeeEstimates: { - high: '150', - }, - gasEstimateType: GasEstimateTypes.legacy, - }; +const fetchTradesInfoStub = jest.fn(); +const getCurrentChainIdStub = jest.fn().mockReturnValue(CHAIN_IDS.MAINNET); +const getLayer1GasFeeStub = jest.fn().mockReturnValue('0x1'); +const getNetworkClientIdStub = jest.fn().mockReturnValue('1'); +const getEIP1559GasFeeEstimatesStub = jest.fn().mockReturnValue({ + gasFeeEstimates: { + high: '150', + }, + gasEstimateType: GasEstimateTypes.legacy, }); describe('SwapsController', function () { @@ -161,7 +153,7 @@ describe('SwapsController', function () { }); }; - before(function () { + beforeEach(function () { const providerResultStub = { // 1 gwei eth_gasPrice: '0x0de0b6b3a7640000', @@ -173,26 +165,23 @@ describe('SwapsController', function () { networkId: 1, chainId: 1, }).provider; + jest.useFakeTimers(); }); afterEach(function () { - sandbox.restore(); + jest.useRealTimers(); + jest.restoreAllMocks(); }); describe('constructor', function () { it('should setup correctly', function () { const swapsController = getSwapsController(); - assert.deepStrictEqual( - swapsController.store.getState(), - EMPTY_INIT_STATE, - ); - assert.deepStrictEqual( - swapsController.getBufferedGasLimit, + expect(swapsController.store.getState()).toStrictEqual(EMPTY_INIT_STATE); + expect(swapsController.getBufferedGasLimit).toStrictEqual( MOCK_GET_BUFFERED_GAS_LIMIT, ); - assert.strictEqual(swapsController.pollCount, 0); - assert.deepStrictEqual( - swapsController.getProviderConfig, + expect(swapsController._pollCount).toStrictEqual(0); + expect(swapsController.getProviderConfig).toStrictEqual( MOCK_GET_PROVIDER_CONFIG, ); }); @@ -208,64 +197,57 @@ describe('SwapsController', function () { it('should set selected quote agg id', function () { const selectedAggId = 'test'; swapsController.setSelectedQuoteAggId(selectedAggId); - assert.deepStrictEqual( + expect( swapsController.store.getState().swapsState.selectedAggId, - selectedAggId, - ); + ).toStrictEqual(selectedAggId); }); it('should set swaps tokens', function () { const tokens = []; swapsController.setSwapsTokens(tokens); - assert.deepStrictEqual( + expect( swapsController.store.getState().swapsState.tokens, - tokens, - ); + ).toStrictEqual(tokens); }); it('should set trade tx id', function () { const tradeTxId = 'test'; swapsController.setTradeTxId(tradeTxId); - assert.strictEqual( + expect( swapsController.store.getState().swapsState.tradeTxId, - tradeTxId, - ); + ).toStrictEqual(tradeTxId); }); it('should set swaps tx gas price', function () { const gasPrice = 1; swapsController.setSwapsTxGasPrice(gasPrice); - assert.deepStrictEqual( + expect( swapsController.store.getState().swapsState.customGasPrice, - gasPrice, - ); + ).toStrictEqual(gasPrice); }); it('should set swaps tx gas limit', function () { const gasLimit = '1'; swapsController.setSwapsTxGasLimit(gasLimit); - assert.deepStrictEqual( + expect( swapsController.store.getState().swapsState.customMaxGas, - gasLimit, - ); + ).toStrictEqual(gasLimit); }); it('should set background swap route state', function () { const routeState = 'test'; swapsController.setBackgroundSwapRouteState(routeState); - assert.deepStrictEqual( + expect( swapsController.store.getState().swapsState.routeState, - routeState, - ); + ).toStrictEqual(routeState); }); it('should set swaps error key', function () { const errorKey = 'test'; swapsController.setSwapsErrorKey(errorKey); - assert.deepStrictEqual( + expect( swapsController.store.getState().swapsState.errorKey, - errorKey, - ); + ).toStrictEqual(errorKey); }); it('should set initial gas estimate', async function () { @@ -288,9 +270,9 @@ describe('SwapsController', function () { await swapsController.getBufferedGasLimit(); const { gasEstimate, gasEstimateWithRefund } = swapsController.store.getState().swapsState.quotes[initialAggId]; - assert.strictEqual(gasEstimate, bufferedGasLimit); - assert.strictEqual( - gasEstimateWithRefund, + + expect(gasEstimate).toStrictEqual(bufferedGasLimit); + expect(gasEstimateWithRefund).toStrictEqual( `0x${new BigNumberjs(maxGas, 10) .minus(estimatedRefund, 10) .toString(16)}`, @@ -300,10 +282,9 @@ describe('SwapsController', function () { it('should set custom approve tx data', function () { const data = 'test'; swapsController.setCustomApproveTxData(data); - assert.deepStrictEqual( + expect( swapsController.store.getState().swapsState.customApproveTxData, - data, - ); + ).toStrictEqual(data); }); }); @@ -316,14 +297,13 @@ describe('SwapsController', function () { }); it('returns empty object if passed undefined or empty object', async function () { - assert.deepStrictEqual( + expect( await swapsController._findTopQuoteAndCalculateSavings(), - {}, - ); - assert.deepStrictEqual( + ).toStrictEqual({}); + + expect( await swapsController._findTopQuoteAndCalculateSavings({}), - {}, - ); + ).toStrictEqual({}); }); it('returns the top aggId and quotes with savings and fee values if passed necessary data and an even number of quotes', async function () { @@ -331,9 +311,8 @@ describe('SwapsController', function () { await swapsController._findTopQuoteAndCalculateSavings( getTopQuoteAndSavingsMockQuotes(), ); - assert.equal(topAggId, TEST_AGG_ID_1); - assert.deepStrictEqual( - resultQuotes, + expect(topAggId).toStrictEqual(TEST_AGG_ID_1); + expect(resultQuotes).toStrictEqual( getTopQuoteAndSavingsBaseExpectedResults(), ); }); @@ -353,8 +332,8 @@ describe('SwapsController', function () { const [topAggId, resultQuotes] = await swapsController._findTopQuoteAndCalculateSavings(testInput); - assert.equal(topAggId, TEST_AGG_ID_1); - assert.deepStrictEqual(resultQuotes, expectedResultQuotes); + expect(topAggId).toStrictEqual(TEST_AGG_ID_1); + expect(resultQuotes).toStrictEqual(expectedResultQuotes); }); it('returns the top aggId, without best quote flagged, and quotes with fee values if passed necessary data but no custom convert rate exists', async function () { @@ -394,8 +373,8 @@ describe('SwapsController', function () { const [topAggId, resultQuotes] = await swapsController._findTopQuoteAndCalculateSavings(testInput); - assert.equal(topAggId, TEST_AGG_ID_1); - assert.deepStrictEqual(resultQuotes, expectedResultQuotes); + expect(topAggId).toStrictEqual(TEST_AGG_ID_1); + expect(resultQuotes).toStrictEqual(expectedResultQuotes); }); it('returns the top aggId and quotes with savings and fee values if passed necessary data and the source token is ETH', async function () { @@ -457,8 +436,8 @@ describe('SwapsController', function () { const [topAggId, resultQuotes] = await swapsController._findTopQuoteAndCalculateSavings(testInput); - assert.equal(topAggId, TEST_AGG_ID_1); - assert.deepStrictEqual(resultQuotes, expectedResultQuotes); + expect(topAggId).toStrictEqual(TEST_AGG_ID_1); + expect(resultQuotes).toStrictEqual(expectedResultQuotes); }); it('returns the top aggId and quotes with savings and fee values if passed necessary data and the source token is ETH and an ETH fee is included in the trade value of what would be the best quote', async function () { @@ -533,8 +512,8 @@ describe('SwapsController', function () { const [topAggId, resultQuotes] = await swapsController._findTopQuoteAndCalculateSavings(testInput); - assert.equal(topAggId, TEST_AGG_ID_2); - assert.deepStrictEqual(resultQuotes, expectedResultQuotes); + expect(topAggId).toStrictEqual(TEST_AGG_ID_2); + expect(resultQuotes).toStrictEqual(expectedResultQuotes); }); it('returns the top aggId and quotes with savings and fee values if passed necessary data and the source token is not ETH and an ETH fee is included in the trade value of what would be the best quote', async function () { @@ -568,31 +547,36 @@ describe('SwapsController', function () { const [topAggId, resultQuotes] = await swapsController._findTopQuoteAndCalculateSavings(testInput); - assert.equal(topAggId, TEST_AGG_ID_2); - assert.deepStrictEqual(resultQuotes, expectedResultQuotes); + expect(topAggId).toStrictEqual(TEST_AGG_ID_2); + expect(resultQuotes).toStrictEqual(expectedResultQuotes); }); }); describe('fetchAndSetQuotes', function () { it('returns null if fetchParams is not provided', async function () { const quotes = await swapsController.fetchAndSetQuotes(undefined); - assert.strictEqual(quotes, null); + expect(quotes).toStrictEqual(null); }); it('calls fetchTradesInfo with the given fetchParams and returns the correct quotes', async function () { - fetchTradesInfoStub.resolves(getMockQuotes()); + const fetchTradesInfoSpy = jest + .spyOn(swapsController, '_fetchTradesInfo') + .mockReturnValue(getMockQuotes()); // Make it so approval is not required - sandbox - .stub(swapsController, '_getERC20Allowance') - .resolves(BigNumber.from(1)); + jest + .spyOn(swapsController, '_getERC20Allowance') + .mockReturnValue(BigNumber.from(1)); + + // Make the network fetch error message disappear + jest.spyOn(swapsController, '_setSwapsNetworkConfig').mockReturnValue(); const [newQuotes] = await swapsController.fetchAndSetQuotes( MOCK_FETCH_PARAMS, MOCK_FETCH_METADATA, ); - assert.deepStrictEqual(newQuotes[TEST_AGG_ID_BEST], { + expect(newQuotes[TEST_AGG_ID_BEST]).toStrictEqual({ ...getMockQuotes()[TEST_AGG_ID_BEST], sourceTokenInfo: undefined, destinationTokenInfo: { @@ -615,16 +599,15 @@ describe('SwapsController', function () { metaMaskFeeInEth: '0.50505050505050505050505050505050505', ethValueOfTokens: '50', }); - assert.strictEqual( - fetchTradesInfoStub.calledOnceWithExactly(MOCK_FETCH_PARAMS, { - ...MOCK_FETCH_METADATA, - }), - true, - ); + + expect(fetchTradesInfoSpy).toHaveBeenCalledTimes(1); + expect(fetchTradesInfoSpy).toHaveBeenCalledWith(MOCK_FETCH_PARAMS, { + ...MOCK_FETCH_METADATA, + }); }); it('calls returns the correct quotes on the optimism chain', async function () { - fetchTradesInfoStub.resetHistory(); + fetchTradesInfoStub.mockReset(); const OPTIMISM_MOCK_FETCH_METADATA = { ...MOCK_FETCH_METADATA, chainId: CHAIN_IDS.OPTIMISM, @@ -645,19 +628,24 @@ describe('SwapsController', function () { swapsController = getSwapsController(optimismProvider); - fetchTradesInfoStub.resolves(getMockQuotes()); + const fetchTradesInfoSpy = jest + .spyOn(swapsController, '_fetchTradesInfo') + .mockReturnValue(getMockQuotes()); // Make it so approval is not required - sandbox - .stub(swapsController, '_getERC20Allowance') - .resolves(BigNumber.from(1)); + jest + .spyOn(swapsController, '_getERC20Allowance') + .mockReturnValue(BigNumber.from(1)); + + // Make the network fetch error message disappear + jest.spyOn(swapsController, '_setSwapsNetworkConfig').mockReturnValue(); const [newQuotes] = await swapsController.fetchAndSetQuotes( MOCK_FETCH_PARAMS, OPTIMISM_MOCK_FETCH_METADATA, ); - assert.deepStrictEqual(newQuotes[TEST_AGG_ID_BEST], { + expect(newQuotes[TEST_AGG_ID_BEST]).toStrictEqual({ ...getMockQuotes()[TEST_AGG_ID_BEST], sourceTokenInfo: undefined, destinationTokenInfo: { @@ -681,49 +669,56 @@ describe('SwapsController', function () { metaMaskFeeInEth: '0.50505050505050505050505050505050505', ethValueOfTokens: '50', }); - assert.strictEqual( - fetchTradesInfoStub.calledOnceWithExactly(MOCK_FETCH_PARAMS, { - ...OPTIMISM_MOCK_FETCH_METADATA, - }), - true, - ); + + expect(fetchTradesInfoSpy).toHaveBeenCalledTimes(1); + expect(fetchTradesInfoSpy).toHaveBeenCalledWith(MOCK_FETCH_PARAMS, { + ...OPTIMISM_MOCK_FETCH_METADATA, + }); }); it('performs the allowance check', async function () { - fetchTradesInfoStub.resolves(getMockQuotes()); + jest + .spyOn(swapsController, '_fetchTradesInfo') + .mockReturnValue(getMockQuotes()); // Make it so approval is not required - const allowanceStub = sandbox - .stub(swapsController, '_getERC20Allowance') - .resolves(BigNumber.from(1)); + const getERC20AllowanceSpy = jest + .spyOn(swapsController, '_getERC20Allowance') + .mockReturnValue(BigNumber.from(1)); + + // Make the network fetch error message disappear + jest.spyOn(swapsController, '_setSwapsNetworkConfig').mockReturnValue(); await swapsController.fetchAndSetQuotes( MOCK_FETCH_PARAMS, MOCK_FETCH_METADATA, ); - assert.strictEqual( - allowanceStub.calledOnceWithExactly( - MOCK_FETCH_PARAMS.sourceToken, - MOCK_FETCH_PARAMS.fromAddress, - CHAIN_IDS.MAINNET, - ), - true, + expect(getERC20AllowanceSpy).toHaveBeenCalledTimes(1); + expect(getERC20AllowanceSpy).toHaveBeenCalledWith( + MOCK_FETCH_PARAMS.sourceToken, + MOCK_FETCH_PARAMS.fromAddress, + CHAIN_IDS.MAINNET, ); }); it('gets the gas limit if approval is required', async function () { - fetchTradesInfoStub.resolves(MOCK_QUOTES_APPROVAL_REQUIRED); + jest + .spyOn(swapsController, '_fetchTradesInfo') + .mockReturnValue(MOCK_QUOTES_APPROVAL_REQUIRED); // Ensure approval is required - sandbox - .stub(swapsController, '_getERC20Allowance') - .resolves(BigNumber.from(0)); + jest + .spyOn(swapsController, '_getERC20Allowance') + .mockReturnValue(BigNumber.from(0)); + + // Make the network fetch error message disappear + jest.spyOn(swapsController, '_setSwapsNetworkConfig').mockReturnValue(); const timedoutGasReturnResult = { gasLimit: 1000000 }; - const timedoutGasReturnStub = sandbox - .stub(swapsController, 'timedoutGasReturn') - .resolves(timedoutGasReturnResult); + const timedoutGasReturnSpy = jest + .spyOn(swapsController, '_timedoutGasReturn') + .mockReturnValue(timedoutGasReturnResult); await swapsController.fetchAndSetQuotes( MOCK_FETCH_PARAMS, @@ -731,30 +726,33 @@ describe('SwapsController', function () { ); // Mocked quotes approvalNeeded is null, so it will only be called with the gas - assert.strictEqual( - timedoutGasReturnStub.calledOnceWithExactly( - MOCK_APPROVAL_NEEDED, - TEST_AGG_ID_APPROVAL, - ), - true, + expect(timedoutGasReturnSpy).toHaveBeenCalledTimes(1); + expect(timedoutGasReturnSpy).toHaveBeenCalledWith( + MOCK_APPROVAL_NEEDED, + TEST_AGG_ID_APPROVAL, ); }); it('marks the best quote', async function () { - fetchTradesInfoStub.resolves(getMockQuotes()); + jest + .spyOn(swapsController, '_fetchTradesInfo') + .mockReturnValue(getMockQuotes()); // Make it so approval is not required - sandbox - .stub(swapsController, '_getERC20Allowance') - .resolves(BigNumber.from(1)); + jest + .spyOn(swapsController, '_getERC20Allowance') + .mockReturnValue(BigNumber.from(1)); + + // Make the network fetch error message disappear + jest.spyOn(swapsController, '_setSwapsNetworkConfig').mockReturnValue(); const [newQuotes, topAggId] = await swapsController.fetchAndSetQuotes( MOCK_FETCH_PARAMS, MOCK_FETCH_METADATA, ); - assert.strictEqual(topAggId, TEST_AGG_ID_BEST); - assert.strictEqual(newQuotes[topAggId].isBestQuote, true); + expect(topAggId).toStrictEqual(TEST_AGG_ID_BEST); + expect(newQuotes[topAggId].isBestQuote).toStrictEqual(true); }); it('selects the best quote', async function () { @@ -771,29 +769,38 @@ describe('SwapsController', function () { .toString(), }; const quotes = { ...getMockQuotes(), [bestAggId]: bestQuote }; - fetchTradesInfoStub.resolves(quotes); + + jest.spyOn(swapsController, '_fetchTradesInfo').mockReturnValue(quotes); // Make it so approval is not required - sandbox - .stub(swapsController, '_getERC20Allowance') - .resolves(BigNumber.from(1)); + jest + .spyOn(swapsController, '_getERC20Allowance') + .mockReturnValue(BigNumber.from(1)); + + // Make the network fetch error message disappear + jest.spyOn(swapsController, '_setSwapsNetworkConfig').mockReturnValue(); const [newQuotes, topAggId] = await swapsController.fetchAndSetQuotes( MOCK_FETCH_PARAMS, MOCK_FETCH_METADATA, ); - assert.strictEqual(topAggId, bestAggId); - assert.strictEqual(newQuotes[topAggId].isBestQuote, true); + expect(topAggId).toStrictEqual(bestAggId); + expect(newQuotes[topAggId].isBestQuote).toStrictEqual(true); }); it('does not mark as best quote if no conversion rate exists for destination token', async function () { - fetchTradesInfoStub.resolves(getMockQuotes()); + jest + .spyOn(swapsController, '_fetchTradesInfo') + .mockReturnValue(getMockQuotes()); // Make it so approval is not required - sandbox - .stub(swapsController, '_getERC20Allowance') - .resolves(BigNumber.from(1)); + jest + .spyOn(swapsController, '_getERC20Allowance') + .mockReturnValue(BigNumber.from(1)); + + // Make the network fetch error message disappear + jest.spyOn(swapsController, '_setSwapsNetworkConfig').mockReturnValue(); swapsController.getTokenRatesState = () => ({ marketData: { @@ -806,27 +813,28 @@ describe('SwapsController', function () { MOCK_FETCH_METADATA, ); - assert.strictEqual(newQuotes[topAggId].isBestQuote, undefined); + expect(newQuotes[topAggId].isBestQuote).toStrictEqual(undefined); }); it('should replace ethers instance when called with a different chainId than was current when the controller was instantiated', async function () { - fetchTradesInfoStub = sandbox.stub(); + fetchTradesInfoStub.mockReset(); const _swapsController = getSwapsController(); - const currentEthersInstance = _swapsController.ethersProvider; + const currentEthersInstance = _swapsController._ethersProvider; + + // Make the network fetch error message disappear + jest + .spyOn(_swapsController, '_setSwapsNetworkConfig') + .mockReturnValue(); await _swapsController.fetchAndSetQuotes(MOCK_FETCH_PARAMS, { ...MOCK_FETCH_METADATA, chainId: CHAIN_IDS.GOERLI, }); - const newEthersInstance = _swapsController.ethersProvider; - assert.notStrictEqual( - currentEthersInstance, - newEthersInstance, - 'Ethers provider should be replaced', - ); + const newEthersInstance = _swapsController._ethersProvider; + expect(currentEthersInstance).not.toStrictEqual(newEthersInstance); }); it('should not replace ethers instance when called with the same chainId that was current when the controller was instantiated', async function () { @@ -838,19 +846,18 @@ describe('SwapsController', function () { fetchTradesInfo: fetchTradesInfoStub, getCurrentChainId: getCurrentChainIdStub, }); - const currentEthersInstance = _swapsController.ethersProvider; + const currentEthersInstance = _swapsController._ethersProvider; + + // Make the network fetch error message disappear + jest.spyOn(swapsController, '_setSwapsNetworkConfig').mockReturnValue(); await swapsController.fetchAndSetQuotes(MOCK_FETCH_PARAMS, { ...MOCK_FETCH_METADATA, chainId: CHAIN_IDS.MAINNET, }); - const newEthersInstance = _swapsController.ethersProvider; - assert.strictEqual( - currentEthersInstance, - newEthersInstance, - 'Ethers provider should not be replaced', - ); + const newEthersInstance = _swapsController._ethersProvider; + expect(currentEthersInstance).toStrictEqual(newEthersInstance); }); it('should replace ethers instance, and _ethersProviderChainId, twice when called twice with two different chainIds, and successfully set the _ethersProviderChainId when returning to the original chain', async function () { @@ -864,28 +871,27 @@ describe('SwapsController', function () { getLayer1GasFee: getLayer1GasFeeStub, getNetworkClientId: getNetworkClientIdStub, }); - const firstEthersInstance = _swapsController.ethersProvider; + const firstEthersInstance = _swapsController._ethersProvider; const firstEthersProviderChainId = _swapsController._ethersProviderChainId; + // Make the network fetch error message disappear + jest + .spyOn(_swapsController, '_setSwapsNetworkConfig') + .mockReturnValue(); + await _swapsController.fetchAndSetQuotes(MOCK_FETCH_PARAMS, { ...MOCK_FETCH_METADATA, chainId: CHAIN_IDS.GOERLI, }); - const secondEthersInstance = _swapsController.ethersProvider; + const secondEthersInstance = _swapsController._ethersProvider; const secondEthersProviderChainId = _swapsController._ethersProviderChainId; - assert.notStrictEqual( - firstEthersInstance, - secondEthersInstance, - 'Ethers provider should be replaced', - ); - assert.notStrictEqual( - firstEthersInstance, + expect(firstEthersInstance).not.toStrictEqual(secondEthersInstance); + expect(firstEthersInstance).not.toStrictEqual( secondEthersProviderChainId, - 'Ethers provider chainId should be replaced', ); await _swapsController.fetchAndSetQuotes(MOCK_FETCH_PARAMS, { @@ -893,29 +899,19 @@ describe('SwapsController', function () { chainId: CHAIN_IDS.LOCALHOST, }); - const thirdEthersInstance = _swapsController.ethersProvider; + const thirdEthersInstance = _swapsController._ethersProvider; const thirdEthersProviderChainId = _swapsController._ethersProviderChainId; - assert.notStrictEqual( - firstEthersProviderChainId, - thirdEthersInstance, - 'Ethers provider should be replaced', - ); - assert.notStrictEqual( - secondEthersInstance, + expect(firstEthersProviderChainId).not.toStrictEqual( thirdEthersInstance, - 'Ethers provider should be replaced', ); - assert.notStrictEqual( - firstEthersInstance, + expect(secondEthersInstance).not.toStrictEqual(thirdEthersInstance); + expect(firstEthersInstance).not.toStrictEqual( thirdEthersProviderChainId, - 'Ethers provider chainId should be replaced', ); - assert.notStrictEqual( - secondEthersProviderChainId, + expect(secondEthersProviderChainId).not.toStrictEqual( thirdEthersProviderChainId, - 'Ethers provider chainId should be replaced', ); await _swapsController.fetchAndSetQuotes(MOCK_FETCH_PARAMS, { @@ -926,10 +922,8 @@ describe('SwapsController', function () { const lastEthersProviderChainId = _swapsController._ethersProviderChainId; - assert.strictEqual( - firstEthersProviderChainId, + expect(firstEthersProviderChainId).toStrictEqual( lastEthersProviderChainId, - 'Ethers provider chainId should match what it was originally', ); }); }); @@ -939,7 +933,8 @@ describe('SwapsController', function () { const { swapsState: old } = swapsController.store.getState(); swapsController.resetSwapsState(); const { swapsState } = swapsController.store.getState(); - assert.deepStrictEqual(swapsState, { + + expect(swapsState).toStrictEqual({ ...EMPTY_INIT_STATE.swapsState, tokens: old.tokens, swapsQuoteRefreshTime: old.swapsQuoteRefreshTime, @@ -952,41 +947,50 @@ describe('SwapsController', function () { }); it('clears polling timeout', function () { - swapsController.pollingTimeout = setTimeout( - () => assert.fail(), - POLLING_TIMEOUT, - ); + swapsController._pollingTimeout = setTimeout(() => { + throw new Error('Polling timeout not cleared'); + }, POLLING_TIMEOUT); + + // Reseting swaps state should clear the polling timeout swapsController.resetSwapsState(); - assert.strictEqual(swapsController.pollingTimeout._idleTimeout, -1); + + // Verify by ensuring the error is not thrown, indicating that the timer was cleared + expect(jest.runOnlyPendingTimers).not.toThrow(); }); }); describe('stopPollingForQuotes', function () { it('clears polling timeout', function () { - swapsController.pollingTimeout = setTimeout( - () => assert.fail(), - POLLING_TIMEOUT, - ); + swapsController._pollingTimeout = setTimeout(() => { + throw new Error('Polling timeout not cleared'); + }, POLLING_TIMEOUT); + + // Stop polling for quotes should clear the polling timeout swapsController.stopPollingForQuotes(); - assert.strictEqual(swapsController.pollingTimeout._idleTimeout, -1); + + // Verify by ensuring the error is not thrown, indicating that the timer was cleared + expect(jest.runOnlyPendingTimers).not.toThrow(); }); it('resets quotes state correctly', function () { swapsController.stopPollingForQuotes(); const { swapsState } = swapsController.store.getState(); - assert.deepStrictEqual(swapsState.quotes, {}); - assert.strictEqual(swapsState.quotesLastFetched, null); + expect(swapsState.quotes).toStrictEqual({}); + expect(swapsState.quotesLastFetched).toStrictEqual(null); }); }); describe('resetPostFetchState', function () { it('clears polling timeout', function () { - swapsController.pollingTimeout = setTimeout( - () => assert.fail(), - POLLING_TIMEOUT, - ); + swapsController._pollingTimeout = setTimeout(() => { + throw new Error('Polling timeout not cleared'); + }, POLLING_TIMEOUT); + + // Reset post fetch state should clear the polling timeout swapsController.resetPostFetchState(); - assert.strictEqual(swapsController.pollingTimeout._idleTimeout, -1); + + // Verify by ensuring the error is not thrown, indicating that the timer was cleared + expect(jest.runOnlyPendingTimers).not.toThrow(); }); it('updates state correctly', function () { @@ -1014,7 +1018,7 @@ describe('SwapsController', function () { swapsController.resetPostFetchState(); const { swapsState } = swapsController.store.getState(); - assert.deepStrictEqual(swapsState, { + expect(swapsState).toStrictEqual({ ...EMPTY_INIT_STATE.swapsState, tokens, fetchParams, @@ -1028,14 +1032,13 @@ describe('SwapsController', function () { describe('utils', function () { describe('getMedianEthValueQuote', function () { - const { getMedianEthValueQuote } = utils; - it('calculates median correctly with uneven sample', function () { const expectedResult = { ethFee: '10', metaMaskFeeInEth: '5', ethValueOfTokens: '0.3', }; + const values = [ { overallValueOfQuote: '3', @@ -1058,12 +1061,7 @@ describe('SwapsController', function () { ]; const median = getMedianEthValueQuote(values); - - assert.deepEqual( - median, - expectedResult, - 'should have returned correct median quote object', - ); + expect(median).toStrictEqual(expectedResult); }); it('calculates median correctly with even sample', function () { @@ -1072,6 +1070,7 @@ describe('SwapsController', function () { metaMaskFeeInEth: '6.5', ethValueOfTokens: '0.25', }; + const values = [ { overallValueOfQuote: '3', @@ -1098,13 +1097,9 @@ describe('SwapsController', function () { ethValueOfTokens: '0.6', }, ]; - const median = getMedianEthValueQuote(values); - assert.deepEqual( - median, - expectedResult, - 'should have returned correct median quote object', - ); + const median = getMedianEthValueQuote(values); + expect(median).toStrictEqual(expectedResult); }); it('calculates median correctly with an uneven sample where multiple quotes have the median overall value', function () { @@ -1158,13 +1153,9 @@ describe('SwapsController', function () { metaMaskFeeInEth: '0.8', }, ]; - const median = getMedianEthValueQuote(values); - assert.deepEqual( - median, - expectedResult, - 'should have returned correct median quote object', - ); + const median = getMedianEthValueQuote(values); + expect(median).toStrictEqual(expectedResult); }); it('calculates median correctly with an even sample where multiple quotes have the same overall value as either of the two middle values', function () { @@ -1212,29 +1203,22 @@ describe('SwapsController', function () { metaMaskFeeInEth: '0.8', }, ]; - const median = getMedianEthValueQuote(values); - assert.deepEqual( - median, - expectedResult, - 'should have returned correct median quote object', - ); + const median = getMedianEthValueQuote(values); + expect(median).toStrictEqual(expectedResult); }); it('throws on empty or non-array sample', function () { - assert.throws( - () => getMedianEthValueQuote([]), - 'should throw on empty array', + expect(() => getMedianEthValueQuote([])).toThrow( + 'Expected non-empty array param.', ); - assert.throws( - () => getMedianEthValueQuote(), - 'should throw on non-array param', + expect(() => getMedianEthValueQuote()).toThrow( + 'Expected non-empty array param.', ); - assert.throws( - () => getMedianEthValueQuote({}), - 'should throw on non-array param', + expect(() => getMedianEthValueQuote({})).toThrow( + 'Expected non-empty array param.', ); }); }); diff --git a/app/scripts/controllers/swaps.js b/app/scripts/controllers/swaps.ts similarity index 60% rename from app/scripts/controllers/swaps.js rename to app/scripts/controllers/swaps.ts index 9492b7e612de..b0915b2ed21f 100644 --- a/app/scripts/controllers/swaps.js +++ b/app/scripts/controllers/swaps.ts @@ -1,266 +1,195 @@ -import { Web3Provider } from '@ethersproject/providers'; import { Contract } from '@ethersproject/contracts'; -import BigNumber from 'bignumber.js'; +import { + ExternalProvider, + JsonRpcFetchFunc, + Web3Provider, +} from '@ethersproject/providers'; +import type { ChainId } from '@metamask/controller-utils'; +import { GasFeeState } from '@metamask/gas-fee-controller'; +import { ProviderConfig } from '@metamask/network-controller'; import { ObservableStore } from '@metamask/obs-store'; -import { mapValues, cloneDeep } from 'lodash'; -import abi from 'human-standard-token-abi'; +import { TransactionParams } from '@metamask/transaction-controller'; import { captureException } from '@sentry/browser'; - -import { - decGWEIToHexWEI, - sumHexes, -} from '../../../shared/modules/conversion.utils'; -import { - DEFAULT_ERC20_APPROVE_GAS, - QUOTES_EXPIRED_ERROR, - QUOTES_NOT_AVAILABLE_ERROR, - SWAPS_FETCH_ORDER_CONFLICT, - SWAPS_CHAINID_CONTRACT_ADDRESS_MAP, -} from '../../../shared/constants/swaps'; +import { BigNumber } from 'bignumber.js'; +import abi from 'human-standard-token-abi'; +import { cloneDeep, mapValues } from 'lodash'; +import { EtherDenomination } from '../../../shared/constants/common'; import { GasEstimateTypes } from '../../../shared/constants/gas'; -import { CHAIN_IDS } from '../../../shared/constants/network'; import { MetaMetricsEventCategory, - MetaMetricsEventName, MetaMetricsEventErrorType, + MetaMetricsEventName, } from '../../../shared/constants/metametrics'; +import { CHAIN_IDS } from '../../../shared/constants/network'; import { - FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME, - FALLBACK_SMART_TRANSACTIONS_DEADLINE, FALLBACK_SMART_TRANSACTIONS_MAX_FEE_MULTIPLIER, + FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME, } from '../../../shared/constants/smartTransactions'; - -import { isSwapsDefaultTokenAddress } from '../../../shared/modules/swaps.utils'; - +import { + DEFAULT_ERC20_APPROVE_GAS, + QUOTES_EXPIRED_ERROR, + QUOTES_NOT_AVAILABLE_ERROR, + SWAPS_CHAINID_CONTRACT_ADDRESS_MAP, + SWAPS_FETCH_ORDER_CONFLICT, +} from '../../../shared/constants/swaps'; +import { SECOND } from '../../../shared/constants/time'; +import fetchWithCache from '../../../shared/lib/fetch-with-cache'; import { fetchTradesInfo as defaultFetchTradesInfo, getBaseApi, } from '../../../shared/lib/swaps-utils'; -import fetchWithCache from '../../../shared/lib/fetch-with-cache'; -import { MINUTE, SECOND } from '../../../shared/constants/time'; -import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils'; import { calcGasTotal, calcTokenAmount, } from '../../../shared/lib/transactions-controller-utils'; - +import { + decGWEIToHexWEI, + sumHexes, +} from '../../../shared/modules/conversion.utils'; import { Numeric } from '../../../shared/modules/Numeric'; -import { EtherDenomination } from '../../../shared/constants/common'; +import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils'; +import { isSwapsDefaultTokenAddress } from '../../../shared/modules/swaps.utils'; +import { + FALLBACK_QUOTE_REFRESH_TIME, + MAX_GAS_LIMIT, + POLL_COUNT_LIMIT, + swapsControllerInitialState, +} from './swaps.constants'; +import type { + FetchTradesInfoParams, + FetchTradesInfoParamsMetadata, + Quote, + QuoteSavings, + SwapsControllerOptions, + SwapsControllerState, + SwapsControllerStore, + Trade, +} from './swaps.types'; +import { + calculateGasEstimateWithRefund, + getMedianEthValueQuote, +} from './swaps.utils'; -// The MAX_GAS_LIMIT is a number that is higher than the maximum gas costs we have observed on any aggregator -const MAX_GAS_LIMIT = 2500000; - -// To ensure that our serves are not spammed if MetaMask is left idle, we limit the number of fetches for quotes that are made on timed intervals. -// 3 seems to be an appropriate balance of giving users the time they need when MetaMask is not left idle, and turning polling off when it is. -const POLL_COUNT_LIMIT = 3; - -// If for any reason the MetaSwap API fails to provide a refresh time, -// provide a reasonable fallback to avoid further errors -const FALLBACK_QUOTE_REFRESH_TIME = MINUTE; - -function calculateGasEstimateWithRefund( - maxGas = MAX_GAS_LIMIT, - estimatedRefund = 0, - estimatedGas = 0, -) { - const maxGasMinusRefund = new BigNumber(maxGas, 10).minus( - estimatedRefund, - 10, - ); - const isMaxGasMinusRefundNegative = maxGasMinusRefund.lt(0); - - const gasEstimateWithRefund = - !isMaxGasMinusRefundNegative && maxGasMinusRefund.lt(estimatedGas, 16) - ? `0x${maxGasMinusRefund.toString(16)}` - : estimatedGas; - - return gasEstimateWithRefund; -} +export default class SwapsController { + public store: SwapsControllerStore; + + public getBufferedGasLimit: ( + params: { + txParams: { + value: string; + data: string; + to: string; + from: string; + }; + }, + factor: number, + ) => Promise<{ gasLimit: string; simulationFails: boolean }>; -const initialState = { - swapsState: { - quotes: {}, - quotesPollingLimitEnabled: false, - fetchParams: null, - tokens: null, - tradeTxId: null, - approveTxId: null, - quotesLastFetched: null, - customMaxGas: '', - customGasPrice: null, - customMaxFeePerGas: null, - customMaxPriorityFeePerGas: null, - swapsUserFeeLevel: '', - selectedAggId: null, - customApproveTxData: '', - errorKey: '', - topAggId: null, - routeState: '', - swapsFeatureIsLive: true, - saveFetchedQuotes: false, - swapsQuoteRefreshTime: FALLBACK_QUOTE_REFRESH_TIME, - swapsQuotePrefetchingRefreshTime: FALLBACK_QUOTE_REFRESH_TIME, - swapsStxBatchStatusRefreshTime: FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME, - swapsStxGetTransactionsRefreshTime: - FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME, - swapsStxMaxFeeMultiplier: FALLBACK_SMART_TRANSACTIONS_MAX_FEE_MULTIPLIER, - swapsFeatureFlags: {}, - }, -}; + public getProviderConfig: () => ProviderConfig; + + public getTokenRatesState: () => { + marketData: Record< + string, + { + [tokenAddress: string]: { + price: number; + }; + } + >; + }; + + public resetState: () => void; + + public trackMetaMetricsEvent: (event: { + event: MetaMetricsEventName; + category: MetaMetricsEventCategory; + properties: Record; + }) => void; + + private _ethersProvider: Web3Provider; + + private _ethersProviderChainId: ChainId; + + private _indexOfNewestCallInFlight: number; + + private _pollCount: number; + + private _pollingTimeout: ReturnType | null = null; + + private _provider: ExternalProvider | JsonRpcFetchFunc; + + private _fetchTradesInfo: ( + fetchParams: FetchTradesInfoParams, + fetchMetadata: { chainId: ChainId }, + ) => Promise<{ + [aggId: string]: Quote; + }> = defaultFetchTradesInfo; + + private _getCurrentChainId: () => ChainId; + + private _getEIP1559GasFeeEstimates: () => Promise; + + private _getLayer1GasFee: (params: { + transactionParams: TransactionParams; + chainId: ChainId; + }) => Promise; -export default class SwapsController { constructor( - { - getBufferedGasLimit, - provider, - getProviderConfig, - getTokenRatesState, - fetchTradesInfo = defaultFetchTradesInfo, - getCurrentChainId, - getLayer1GasFee, - getEIP1559GasFeeEstimates, - trackMetaMetricsEvent, - }, - state, + opts: SwapsControllerOptions, + state: { swapsState: SwapsControllerState }, ) { + // The store is initialized with the initial state, and then updated with the state from storage this.store = new ObservableStore({ swapsState: { - ...initialState.swapsState, + ...swapsControllerInitialState.swapsState, swapsFeatureFlags: state?.swapsState?.swapsFeatureFlags || {}, }, }); + this.getBufferedGasLimit = opts.getBufferedGasLimit; + this.getTokenRatesState = opts.getTokenRatesState; + this.getProviderConfig = opts.getProviderConfig; + this.trackMetaMetricsEvent = opts.trackMetaMetricsEvent; + + // The resetState function is used to reset the state to the initial state, but keep the swapsFeatureFlags this.resetState = () => { this.store.updateState({ swapsState: { - ...initialState.swapsState, + ...swapsControllerInitialState.swapsState, swapsFeatureFlags: state?.swapsState?.swapsFeatureFlags, }, }); }; - this._fetchTradesInfo = fetchTradesInfo; - this._getCurrentChainId = getCurrentChainId; - this._getEIP1559GasFeeEstimates = getEIP1559GasFeeEstimates; - - this._getLayer1GasFee = getLayer1GasFee; - - this.getBufferedGasLimit = getBufferedGasLimit; - this.getTokenRatesState = getTokenRatesState; - this.trackMetaMetricsEvent = trackMetaMetricsEvent; - - this.pollCount = 0; - this.getProviderConfig = getProviderConfig; - - this.indexOfNewestCallInFlight = 0; - - this.provider = provider; - this.ethersProvider = new Web3Provider(provider); + this._fetchTradesInfo = opts.fetchTradesInfo || defaultFetchTradesInfo; + this._getCurrentChainId = opts.getCurrentChainId; + this._getEIP1559GasFeeEstimates = opts.getEIP1559GasFeeEstimates; + this._getLayer1GasFee = opts.getLayer1GasFee; + this._ethersProvider = new Web3Provider(opts.provider); this._ethersProviderChainId = this._getCurrentChainId(); + this._indexOfNewestCallInFlight = 0; + this._pollCount = 0; + this._provider = opts.provider; } - async fetchSwapsNetworkConfig(chainId) { - const response = await fetchWithCache({ - url: getBaseApi('network', chainId), - fetchOptions: { method: 'GET' }, - cacheOptions: { cacheRefreshTime: 600000 }, - functionName: 'fetchSwapsNetworkConfig', - }); - const { refreshRates, parameters = {} } = response || {}; - if ( - !refreshRates || - typeof refreshRates.quotes !== 'number' || - typeof refreshRates.quotesPrefetching !== 'number' - ) { - throw new Error( - `MetaMask - invalid response for refreshRates: ${response}`, - ); - } - // We presently use milliseconds in the UI. - return { - quotes: refreshRates.quotes * 1000, - quotesPrefetching: refreshRates.quotesPrefetching * 1000, - stxGetTransactions: refreshRates.stxGetTransactions * 1000, - stxBatchStatus: refreshRates.stxBatchStatus * 1000, - stxStatusDeadline: refreshRates.stxStatusDeadline, - stxMaxFeeMultiplier: parameters.stxMaxFeeMultiplier, - }; - } - - // Sets the network config from the MetaSwap API. - async _setSwapsNetworkConfig() { - const chainId = this._getCurrentChainId(); - let swapsNetworkConfig; - try { - swapsNetworkConfig = await this.fetchSwapsNetworkConfig(chainId); - } catch (e) { - console.error('Request for Swaps network config failed: ', e); - } - const { swapsState: latestSwapsState } = this.store.getState(); - this.store.updateState({ - swapsState: { - ...latestSwapsState, - swapsQuoteRefreshTime: - swapsNetworkConfig?.quotes || FALLBACK_QUOTE_REFRESH_TIME, - swapsQuotePrefetchingRefreshTime: - swapsNetworkConfig?.quotesPrefetching || FALLBACK_QUOTE_REFRESH_TIME, - swapsStxGetTransactionsRefreshTime: - swapsNetworkConfig?.stxGetTransactions || - FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME, - swapsStxBatchStatusRefreshTime: - swapsNetworkConfig?.stxBatchStatus || - FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME, - swapsStxStatusDeadline: - swapsNetworkConfig?.stxStatusDeadline || - FALLBACK_SMART_TRANSACTIONS_DEADLINE, - swapsStxMaxFeeMultiplier: - swapsNetworkConfig?.stxMaxFeeMultiplier || - FALLBACK_SMART_TRANSACTIONS_MAX_FEE_MULTIPLIER, - }, - }); - } - - // Once quotes are fetched, we poll for new ones to keep the quotes up to date. Market and aggregator contract conditions can change fast enough - // that quotes will no longer be available after 1 or 2 minutes. When fetchAndSetQuotes is first called, it receives fetch parameters that are stored in - // state. These stored parameters are used on subsequent calls made during polling. - // Note: we stop polling after 3 requests, until new quotes are explicitly asked for. The logic that enforces that maximum is in the body of fetchAndSetQuotes - pollForNewQuotes() { - const { - swapsState: { - swapsQuoteRefreshTime, - swapsQuotePrefetchingRefreshTime, - quotesPollingLimitEnabled, - }, - } = this.store.getState(); - // swapsQuoteRefreshTime is used on the View Quote page, swapsQuotePrefetchingRefreshTime is used on the Build Quote page. - const quotesRefreshRateInMs = quotesPollingLimitEnabled - ? swapsQuoteRefreshTime - : swapsQuotePrefetchingRefreshTime; - this.pollingTimeout = setTimeout(() => { - const { swapsState } = this.store.getState(); - this.fetchAndSetQuotes( - swapsState.fetchParams, - swapsState.fetchParams?.metaData, - true, - ); - }, quotesRefreshRateInMs); + public clearSwapsQuotes() { + const { swapsState } = this.store.getState(); + this.store.updateState({ swapsState: { ...swapsState, quotes: {} } }); } - stopPollingForQuotes() { - if (this.pollingTimeout) { - clearTimeout(this.pollingTimeout); + public async fetchAndSetQuotes( + fetchParams: FetchTradesInfoParams, + fetchParamsMetaData: FetchTradesInfoParamsMetadata, + isPolledRequest = false, + ) { + if (!fetchParams) { + return null; } - } - async fetchAndSetQuotes( - fetchParams, - fetchParamsMetaData = {}, - isPolledRequest, - ) { const { chainId } = fetchParamsMetaData; if (chainId !== this._ethersProviderChainId) { - this.ethersProvider = new Web3Provider(this.provider); + this._ethersProvider = new Web3Provider(this._provider); this._ethersProviderChainId = chainId; } @@ -268,32 +197,29 @@ export default class SwapsController { swapsState: { quotesPollingLimitEnabled, saveFetchedQuotes }, } = this.store.getState(); - if (!fetchParams) { - return null; - } // Every time we get a new request that is not from the polling, we reset the poll count so we can poll for up to three more sets of quotes with these new params. if (!isPolledRequest) { - this.pollCount = 0; + this._pollCount = 0; } // If there are any pending poll requests, clear them so that they don't get call while this new fetch is in process - clearTimeout(this.pollingTimeout); + if (this._pollingTimeout) { + clearTimeout(this._pollingTimeout); + } if (!isPolledRequest) { this.setSwapsErrorKey(''); } - const indexOfCurrentCall = this.indexOfNewestCallInFlight + 1; - this.indexOfNewestCallInFlight = indexOfCurrentCall; + const indexOfCurrentCall = this._indexOfNewestCallInFlight + 1; + this._indexOfNewestCallInFlight = indexOfCurrentCall; if (!saveFetchedQuotes) { - this.setSaveFetchedQuotes(true); + this._setSaveFetchedQuotes(true); } let [newQuotes] = await Promise.all([ - this._fetchTradesInfo(fetchParams, { - ...fetchParamsMetaData, - }), + this._fetchTradesInfo(fetchParams, { ...fetchParamsMetaData }), this._setSwapsNetworkConfig(), ]); @@ -310,16 +236,16 @@ export default class SwapsController { ]; } - newQuotes = mapValues(newQuotes, (quote) => ({ + newQuotes = mapValues(newQuotes, (quote: Quote) => ({ ...quote, - sourceTokenInfo: fetchParamsMetaData.sourceTokenInfo, - destinationTokenInfo: fetchParamsMetaData.destinationTokenInfo, + sourceTokenInfo: fetchParamsMetaData?.sourceTokenInfo, + destinationTokenInfo: fetchParamsMetaData?.destinationTokenInfo, })); - if ( - (chainId === CHAIN_IDS.OPTIMISM || chainId === CHAIN_IDS.BASE) && - Object.values(newQuotes).length > 0 - ) { + const isOptimism = chainId === CHAIN_IDS.OPTIMISM.toString(); + const isBase = chainId === CHAIN_IDS.BASE.toString(); + + if ((isOptimism || isBase) && Object.values(newQuotes).length > 0) { await Promise.all( Object.values(newQuotes).map(async (quote) => { if (quote.trade) { @@ -362,19 +288,25 @@ export default class SwapsController { ...quote, approvalNeeded: null, })); - } else if (!isPolledRequest) { - const { gasLimit: approvalGas } = await this.timedoutGasReturn( + } else if (!isPolledRequest && firstQuote.approvalNeeded) { + const { gasLimit: approvalGas } = await this._timedoutGasReturn( firstQuote.approvalNeeded, firstQuote.aggregator, ); - newQuotes = mapValues(newQuotes, (quote) => ({ - ...quote, - approvalNeeded: { - ...quote.approvalNeeded, - gas: approvalGas || DEFAULT_ERC20_APPROVE_GAS, - }, - })); + newQuotes = mapValues(newQuotes, (quote) => + quote.approvalNeeded + ? { + ...quote, + approvalNeeded: { + // approvalNeeded is guaranteed to be defined here because of the conditional above, since all quotes are from the same source token + // the approvalNeeded object will be present for all quotes + ...quote.approvalNeeded, + gas: approvalGas || DEFAULT_ERC20_APPROVE_GAS, + }, + } + : quote, + ); } } @@ -383,27 +315,30 @@ export default class SwapsController { // We can reduce time on the loading screen by only doing this after the // loading screen and best quote have rendered. if (!approvalRequired && !fetchParams?.balanceError) { - newQuotes = await this.getAllQuotesWithGasEstimates(newQuotes); + newQuotes = await this._getAllQuotesWithGasEstimates(newQuotes); } if (Object.values(newQuotes).length === 0) { this.setSwapsErrorKey(QUOTES_NOT_AVAILABLE_ERROR); } else { - const [_topAggId, quotesWithSavingsAndFeeData] = - await this._findTopQuoteAndCalculateSavings(newQuotes); - topAggId = _topAggId; - newQuotes = quotesWithSavingsAndFeeData; + const topQuoteAndSavings = await this._findTopQuoteAndCalculateSavings( + newQuotes, + ); + if (Array.isArray(topQuoteAndSavings)) { + topAggId = topQuoteAndSavings[0]; + newQuotes = topQuoteAndSavings[1]; + } } // If a newer call has been made, don't update state with old information // Prevents timing conflicts between fetches - if (this.indexOfNewestCallInFlight !== indexOfCurrentCall) { + if (this._indexOfNewestCallInFlight !== indexOfCurrentCall) { throw new Error(SWAPS_FETCH_ORDER_CONFLICT); } const { swapsState } = this.store.getState(); let { selectedAggId } = swapsState; - if (!newQuotes[selectedAggId]) { + if (!selectedAggId || !newQuotes[selectedAggId]) { selectedAggId = null; } @@ -420,12 +355,12 @@ export default class SwapsController { if (quotesPollingLimitEnabled) { // We only want to do up to a maximum of three requests from polling if polling limit is enabled. - // Otherwise we won't increase pollCount, so polling will run without a limit. - this.pollCount += 1; + // Otherwise we won't increase _pollCount, so polling will run without a limit. + this._pollCount += 1; } - if (!quotesPollingLimitEnabled || this.pollCount < POLL_COUNT_LIMIT + 1) { - this.pollForNewQuotes(); + if (!quotesPollingLimitEnabled || this._pollCount < POLL_COUNT_LIMIT + 1) { + this._pollForNewQuotes(); } else { this.resetPostFetchState(); this.setSwapsErrorKey(QUOTES_EXPIRED_ERROR); @@ -435,129 +370,78 @@ export default class SwapsController { return [newQuotes, topAggId]; } - safeRefetchQuotes() { + public resetPostFetchState() { const { swapsState } = this.store.getState(); - if (!this.pollingTimeout && swapsState.fetchParams) { - this.fetchAndSetQuotes(swapsState.fetchParams); + this.store.updateState({ + swapsState: { + ...swapsControllerInitialState.swapsState, + tokens: swapsState.tokens, + fetchParams: swapsState.fetchParams, + swapsFeatureIsLive: swapsState.swapsFeatureIsLive, + swapsQuoteRefreshTime: swapsState.swapsQuoteRefreshTime, + swapsQuotePrefetchingRefreshTime: + swapsState.swapsQuotePrefetchingRefreshTime, + swapsFeatureFlags: swapsState.swapsFeatureFlags, + }, + }); + if (this._pollingTimeout) { + clearTimeout(this._pollingTimeout); } } - setSelectedQuoteAggId(selectedAggId) { + public resetSwapsState() { const { swapsState } = this.store.getState(); - this.store.updateState({ swapsState: { ...swapsState, selectedAggId } }); - } - - setSwapsTokens(tokens) { - const { swapsState } = this.store.getState(); - this.store.updateState({ swapsState: { ...swapsState, tokens } }); + this.store.updateState({ + swapsState: { + ...swapsControllerInitialState.swapsState, + swapsQuoteRefreshTime: swapsState.swapsQuoteRefreshTime, + swapsQuotePrefetchingRefreshTime: + swapsState.swapsQuotePrefetchingRefreshTime, + swapsFeatureFlags: swapsState.swapsFeatureFlags, + }, + }); + if (this._pollingTimeout) { + clearTimeout(this._pollingTimeout); + } } - clearSwapsQuotes() { + public safeRefetchQuotes() { const { swapsState } = this.store.getState(); - this.store.updateState({ swapsState: { ...swapsState, quotes: {} } }); + if (!this._pollingTimeout && swapsState.fetchParams) { + this.fetchAndSetQuotes(swapsState.fetchParams, { + ...swapsState.fetchParams.metaData, + }); + } } - setSwapsErrorKey(errorKey) { + public setApproveTxId(approveTxId: string | null) { const { swapsState } = this.store.getState(); - this.store.updateState({ swapsState: { ...swapsState, errorKey } }); - } - - async getAllQuotesWithGasEstimates(quotes) { - const quoteGasData = await Promise.all( - Object.values(quotes).map(async (quote) => { - const { gasLimit, simulationFails } = await this.timedoutGasReturn( - quote.trade, - quote.aggregator, - ); - return [gasLimit, simulationFails, quote.aggregator]; - }), - ); - - const newQuotes = {}; - quoteGasData.forEach(([gasLimit, simulationFails, aggId]) => { - if (gasLimit && !simulationFails) { - const gasEstimateWithRefund = calculateGasEstimateWithRefund( - quotes[aggId].maxGas, - quotes[aggId].estimatedRefund, - gasLimit, - ); - - newQuotes[aggId] = { - ...quotes[aggId], - gasEstimate: gasLimit, - gasEstimateWithRefund, - }; - } else if (quotes[aggId].approvalNeeded) { - // If gas estimation fails, but an ERC-20 approve is needed, then we do not add any estimate property to the quote object - // Such quotes will rely on the maxGas and averageGas properties from the api - newQuotes[aggId] = quotes[aggId]; - } - // If gas estimation fails and no approval is needed, then we filter that quote out, so that it is not shown to the user - }); - return newQuotes; - } - - timedoutGasReturn(tradeTxParams, aggregator = '') { - return new Promise((resolve) => { - let gasTimedOut = false; - - const gasTimeout = setTimeout(() => { - gasTimedOut = true; - this.trackMetaMetricsEvent({ - event: MetaMetricsEventName.QuoteError, - category: MetaMetricsEventCategory.Swaps, - properties: { - error_type: MetaMetricsEventErrorType.GasTimeout, - aggregator, - }, - }); - resolve({ - gasLimit: null, - simulationFails: true, - }); - }, SECOND * 5); - - // Remove gas from params that will be passed to the `estimateGas` call - // Including it can cause the estimate to fail if the actual gas needed - // exceeds the passed gas - const tradeTxParamsForGasEstimate = { - data: tradeTxParams.data, - from: tradeTxParams.from, - to: tradeTxParams.to, - value: tradeTxParams.value, - }; - - this.getBufferedGasLimit({ txParams: tradeTxParamsForGasEstimate }, 1) - .then(({ gasLimit, simulationFails }) => { - if (!gasTimedOut) { - clearTimeout(gasTimeout); - resolve({ gasLimit, simulationFails }); - } - }) - .catch((e) => { - captureException(e, { - extra: { - aggregator, - }, - }); - if (!gasTimedOut) { - clearTimeout(gasTimeout); - resolve({ gasLimit: null, simulationFails: true }); - } - }); + this.store.updateState({ swapsState: { ...swapsState, approveTxId } }); + } + + public setBackgroundSwapRouteState(routeState: string) { + const { swapsState } = this.store.getState(); + this.store.updateState({ swapsState: { ...swapsState, routeState } }); + } + + public setCustomApproveTxData(data: string) { + const { swapsState } = this.store.getState(); + this.store.updateState({ + swapsState: { ...swapsState, customApproveTxData: data }, }); } - async setInitialGasEstimate(initialAggId) { + public async setInitialGasEstimate(initialAggId: string) { const { swapsState } = this.store.getState(); const quoteToUpdate = { ...swapsState.quotes[initialAggId] }; - const { gasLimit: newGasEstimate, simulationFails } = - await this.timedoutGasReturn( - quoteToUpdate.trade, - quoteToUpdate.aggregator, - ); + const { gasLimit: newGasEstimate, simulationFails } = quoteToUpdate.trade + ? await this._timedoutGasReturn( + quoteToUpdate.trade, + quoteToUpdate.aggregator, + ) + : { gasLimit: null, simulationFails: true }; if (newGasEstimate && !simulationFails) { const gasEstimateWithRefund = calculateGasEstimateWithRefund( @@ -578,137 +462,137 @@ export default class SwapsController { }); } - setApproveTxId(approveTxId) { - const { swapsState } = this.store.getState(); - this.store.updateState({ swapsState: { ...swapsState, approveTxId } }); - } - - setTradeTxId(tradeTxId) { - const { swapsState } = this.store.getState(); - this.store.updateState({ swapsState: { ...swapsState, tradeTxId } }); - } - - setQuotesLastFetched(quotesLastFetched) { + public setSelectedQuoteAggId(selectedAggId: string) { const { swapsState } = this.store.getState(); - this.store.updateState({ - swapsState: { ...swapsState, quotesLastFetched }, - }); + this.store.updateState({ swapsState: { ...swapsState, selectedAggId } }); } - setSwapsTxGasPrice(gasPrice) { + public setSwapsFeatureFlags(swapsFeatureFlags: Record) { const { swapsState } = this.store.getState(); this.store.updateState({ - swapsState: { ...swapsState, customGasPrice: gasPrice }, + swapsState: { ...swapsState, swapsFeatureFlags }, }); } - setSwapsTxMaxFeePerGas(maxFeePerGas) { + public setSwapsErrorKey(errorKey: string) { const { swapsState } = this.store.getState(); - this.store.updateState({ - swapsState: { ...swapsState, customMaxFeePerGas: maxFeePerGas }, - }); + this.store.updateState({ swapsState: { ...swapsState, errorKey } }); } - setSwapsUserFeeLevel(swapsUserFeeLevel) { + public setSwapsLiveness(swapsLiveness: { swapsFeatureIsLive: boolean }) { const { swapsState } = this.store.getState(); + const { swapsFeatureIsLive } = swapsLiveness; this.store.updateState({ - swapsState: { ...swapsState, swapsUserFeeLevel }, + swapsState: { ...swapsState, swapsFeatureIsLive }, }); } - setSwapsQuotesPollingLimitEnabled(quotesPollingLimitEnabled) { + public setSwapsQuotesPollingLimitEnabled(quotesPollingLimitEnabled: boolean) { const { swapsState } = this.store.getState(); this.store.updateState({ swapsState: { ...swapsState, quotesPollingLimitEnabled }, }); } - setSwapsTxMaxFeePriorityPerGas(maxPriorityFeePerGas) { + public setSwapsTokens(tokens: string[]) { const { swapsState } = this.store.getState(); - this.store.updateState({ - swapsState: { - ...swapsState, - customMaxPriorityFeePerGas: maxPriorityFeePerGas, - }, - }); + this.store.updateState({ swapsState: { ...swapsState, tokens } }); } - setSwapsTxGasLimit(gasLimit) { + public setSwapsTxGasLimit(gasLimit: string) { const { swapsState } = this.store.getState(); this.store.updateState({ swapsState: { ...swapsState, customMaxGas: gasLimit }, }); } - setCustomApproveTxData(data) { + public setSwapsTxGasPrice(gasPrice: string | null) { const { swapsState } = this.store.getState(); this.store.updateState({ - swapsState: { ...swapsState, customApproveTxData: data }, + swapsState: { ...swapsState, customGasPrice: gasPrice }, }); } - setBackgroundSwapRouteState(routeState) { - const { swapsState } = this.store.getState(); - this.store.updateState({ swapsState: { ...swapsState, routeState } }); - } - - setSaveFetchedQuotes(status) { + public setSwapsTxMaxFeePerGas(maxFeePerGas: string | null) { const { swapsState } = this.store.getState(); this.store.updateState({ - swapsState: { ...swapsState, saveFetchedQuotes: status }, + swapsState: { ...swapsState, customMaxFeePerGas: maxFeePerGas }, }); } - setSwapsLiveness(swapsLiveness) { + public setSwapsTxMaxFeePriorityPerGas(maxPriorityFeePerGas: string | null) { const { swapsState } = this.store.getState(); - const { swapsFeatureIsLive } = swapsLiveness; this.store.updateState({ - swapsState: { ...swapsState, swapsFeatureIsLive }, + swapsState: { + ...swapsState, + customMaxPriorityFeePerGas: maxPriorityFeePerGas, + }, }); } - setSwapsFeatureFlags(swapsFeatureFlags) { + public setSwapsUserFeeLevel(swapsUserFeeLevel: string) { const { swapsState } = this.store.getState(); this.store.updateState({ - swapsState: { ...swapsState, swapsFeatureFlags }, + swapsState: { ...swapsState, swapsUserFeeLevel }, }); } - resetPostFetchState() { + public setTradeTxId(tradeTxId: string | null) { const { swapsState } = this.store.getState(); - this.store.updateState({ - swapsState: { - ...initialState.swapsState, - tokens: swapsState.tokens, - fetchParams: swapsState.fetchParams, - swapsFeatureIsLive: swapsState.swapsFeatureIsLive, - swapsQuoteRefreshTime: swapsState.swapsQuoteRefreshTime, - swapsQuotePrefetchingRefreshTime: - swapsState.swapsQuotePrefetchingRefreshTime, - swapsFeatureFlags: swapsState.swapsFeatureFlags, - }, - }); - clearTimeout(this.pollingTimeout); + this.store.updateState({ swapsState: { ...swapsState, tradeTxId } }); } - resetSwapsState() { - const { swapsState } = this.store.getState(); - this.store.updateState({ - swapsState: { - ...initialState.swapsState, - swapsQuoteRefreshTime: swapsState.swapsQuoteRefreshTime, - swapsQuotePrefetchingRefreshTime: - swapsState.swapsQuotePrefetchingRefreshTime, - swapsFeatureFlags: swapsState.swapsFeatureFlags, - }, + /** + * Once quotes are fetched, we poll for new ones to keep the quotes up to date. + * Market and aggregator contract conditions can change fast enough that quotes + * will no longer be available after 1 or 2 minutes. When `fetchAndSetQuotes` is + * first called, it receives fetch parameters that are stored in state. These stored + * parameters are used on subsequent calls made during polling. + * + * Note: We stop polling after 3 requests, until new quotes are explicitly asked for. + * The logic that enforces that maximum is in the body of `fetchAndSetQuotes`. + */ + public stopPollingForQuotes() { + if (this._pollingTimeout) { + clearTimeout(this._pollingTimeout); + } + } + + // Private Methods + + private async _fetchSwapsNetworkConfig(chainId: ChainId) { + const response = await fetchWithCache({ + url: getBaseApi('network', chainId), + fetchOptions: { method: 'GET' }, + cacheOptions: { cacheRefreshTime: 600000 }, + functionName: '_fetchSwapsNetworkConfig', }); - clearTimeout(this.pollingTimeout); + const { refreshRates, parameters = {} } = response || {}; + if ( + !refreshRates || + typeof refreshRates.quotes !== 'number' || + typeof refreshRates.quotesPrefetching !== 'number' + ) { + throw new Error( + `MetaMask - invalid response for refreshRates: ${response}`, + ); + } + // We presently use milliseconds in the UI. + return { + quotes: refreshRates.quotes * 1000, + quotesPrefetching: refreshRates.quotesPrefetching * 1000, + stxGetTransactions: refreshRates.stxGetTransactions * 1000, + stxBatchStatus: refreshRates.stxBatchStatus * 1000, + stxStatusDeadline: refreshRates.stxStatusDeadline, + stxMaxFeeMultiplier: parameters.stxMaxFeeMultiplier, + }; } - async _findTopQuoteAndCalculateSavings(quotes = {}) { + private async _findTopQuoteAndCalculateSavings( + quotes: Record = {}, + ): Promise<[string | null, Record] | Record> { const { marketData } = this.getTokenRatesState(); const chainId = this._getCurrentChainId(); - const tokenConversionRates = marketData[chainId]; const { @@ -716,7 +600,7 @@ export default class SwapsController { } = this.store.getState(); const numQuotes = Object.keys(quotes).length; - if (!numQuotes) { + if (numQuotes === 0) { return {}; } @@ -734,7 +618,7 @@ export default class SwapsController { } = gasFeeEstimates; const suggestedMaxPriorityFeePerGasInHexWEI = decGWEIToHexWEI( - suggestedMaxPriorityFeePerGas, + Number(suggestedMaxPriorityFeePerGas), ); const estimatedBaseFeeNumeric = new Numeric( estimatedBaseFee, @@ -750,14 +634,15 @@ export default class SwapsController { .round(6) .toString(); } else if (gasEstimateType === GasEstimateTypes.legacy) { - usedGasPrice = customGasPrice || decGWEIToHexWEI(gasFeeEstimates.high); + usedGasPrice = + customGasPrice || decGWEIToHexWEI(Number(gasFeeEstimates.high)); } else if (gasEstimateType === GasEstimateTypes.ethGasPrice) { usedGasPrice = - customGasPrice || decGWEIToHexWEI(gasFeeEstimates.gasPrice); + customGasPrice || decGWEIToHexWEI(Number(gasFeeEstimates.gasPrice)); } - let topAggId = null; - let overallValueOfBestQuoteForSorting = null; + let topAggId: string = ''; + let overallValueOfBestQuoteForSorting: BigNumber; Object.values(newQuotes).forEach((quote) => { const { @@ -775,6 +660,10 @@ export default class SwapsController { multiLayerL1TradeFeeTotal, } = quote; + if (!trade || !destinationToken) { + return; + } + const tradeGasLimitForCalculation = gasEstimateWithRefund ? new BigNumber(gasEstimateWithRefund, 16) : new BigNumber(averageGas || MAX_GAS_LIMIT, 10); @@ -833,13 +722,16 @@ export default class SwapsController { decimalAdjustedDestinationAmount, ); - const tokenConversionRate = - tokenConversionRates[ - Object.keys(tokenConversionRates).find((tokenAddress) => - isEqualCaseInsensitive(tokenAddress, destinationToken), - ) - ]?.price; - const conversionRateForSorting = tokenConversionRate || 1; + const tokenConversionRateKey = Object.keys(tokenConversionRates).find( + (tokenAddress) => + isEqualCaseInsensitive(tokenAddress, destinationToken), + ); + + const tokenConversionRate = tokenConversionRateKey + ? tokenConversionRates[tokenConversionRateKey] + : null; + + const conversionRateForSorting = tokenConversionRate?.price || 1; const ethValueOfTokens = decimalAdjustedDestinationAmount.times( conversionRateForSorting.toString(10), @@ -851,16 +743,15 @@ export default class SwapsController { chainId, ) ? 1 - : tokenConversionRate; + : tokenConversionRate?.price; - const overallValueOfQuoteForSorting = - conversionRateForCalculations === undefined - ? ethValueOfTokens - : ethValueOfTokens.minus(ethFee, 10); + const overallValueOfQuoteForSorting = conversionRateForCalculations + ? ethValueOfTokens.minus(ethFee, 10) + : ethValueOfTokens; quote.ethFee = ethFee.toString(10); - if (conversionRateForCalculations !== undefined) { + if (conversionRateForCalculations) { quote.ethValueOfTokens = ethValueOfTokens.toString(10); quote.overallValueOfQuote = overallValueOfQuoteForSorting.toString(10); quote.metaMaskFeeInEth = metaMaskFeeInTokens @@ -869,37 +760,35 @@ export default class SwapsController { } if ( - overallValueOfBestQuoteForSorting === null || - overallValueOfQuoteForSorting.gt(overallValueOfBestQuoteForSorting) + !overallValueOfBestQuoteForSorting || + overallValueOfQuoteForSorting.gt(overallValueOfBestQuoteForSorting || 0) ) { topAggId = aggregator; overallValueOfBestQuoteForSorting = overallValueOfQuoteForSorting; } }); + const tokenConversionRateKey = Object.keys(tokenConversionRates).find( + (tokenAddress) => + isEqualCaseInsensitive( + tokenAddress, + newQuotes[topAggId]?.destinationToken, + ), + ); + + const tokenConversionRate = tokenConversionRateKey + ? tokenConversionRates[tokenConversionRateKey] + : null; + const isBest = isSwapsDefaultTokenAddress( - newQuotes[topAggId].destinationToken, + newQuotes[topAggId]?.destinationToken, chainId, - ) || - Boolean( - tokenConversionRates[ - Object.keys(tokenConversionRates).find((tokenAddress) => - isEqualCaseInsensitive( - tokenAddress, - newQuotes[topAggId]?.destinationToken, - ), - ) - ], - ); - - let savings = null; + ) || Boolean(tokenConversionRate?.price); if (isBest) { const bestQuote = newQuotes[topAggId]; - savings = {}; - const { ethFee: medianEthFee, metaMaskFeeInEth: medianMetaMaskFee, @@ -908,26 +797,32 @@ export default class SwapsController { // Performance savings are calculated as: // (ethValueOfTokens for the best trade) - (ethValueOfTokens for the media trade) - savings.performance = new BigNumber(bestQuote.ethValueOfTokens, 10).minus( - medianEthValueOfTokens, - 10, - ); + const savingsPerformance = new BigNumber(bestQuote.ethValueOfTokens, 10) + .minus(medianEthValueOfTokens, 10) + .toString(10); // Fee savings are calculated as: // (fee for the median trade) - (fee for the best trade) - savings.fee = new BigNumber(medianEthFee).minus(bestQuote.ethFee, 10); + const fee = new BigNumber(medianEthFee) + .minus(bestQuote.ethFee, 10) + .toString(10); - savings.metaMaskFee = bestQuote.metaMaskFeeInEth; + const metaMaskFee = bestQuote.metaMaskFeeInEth; // Total savings are calculated as: // performance savings + fee savings - metamask fee - savings.total = savings.performance - .plus(savings.fee) - .minus(savings.metaMaskFee) + const total = new BigNumber(savingsPerformance) + .plus(fee) + .minus(metaMaskFee) .toString(10); - savings.performance = savings.performance.toString(10); - savings.fee = savings.fee.toString(10); - savings.medianMetaMaskFee = medianMetaMaskFee; + + const savings: QuoteSavings = { + performance: savingsPerformance, + fee, + total, + metaMaskFee, + medianMetaMaskFee, + }; newQuotes[topAggId].isBestQuote = true; newQuotes[topAggId].savings = savings; @@ -936,133 +831,174 @@ export default class SwapsController { return [topAggId, newQuotes]; } - async _getERC20Allowance(contractAddress, walletAddress, chainId) { - const contract = new Contract(contractAddress, abi, this.ethersProvider); + private async _getAllQuotesWithGasEstimates(quotes: Record) { + const quoteGasData = await Promise.all( + Object.values(quotes).map(async (quote) => { + if (!quote.trade) { + return { + gasLimit: null, + simulationFails: true, + aggId: quote.aggregator, + }; + } + const { gasLimit, simulationFails } = await this._timedoutGasReturn( + quote.trade, + quote.aggregator, + ); + return { gasLimit, simulationFails, aggId: quote.aggregator }; + }), + ); + + const newQuotes: Record = {}; + quoteGasData.forEach(({ gasLimit, simulationFails, aggId }) => { + if (gasLimit && !simulationFails) { + const gasEstimateWithRefund = calculateGasEstimateWithRefund( + quotes[aggId].maxGas, + quotes[aggId].estimatedRefund, + gasLimit, + ); + + // add to newQuotes object + + newQuotes[aggId] = { + ...quotes[aggId], + gasEstimate: gasLimit, + gasEstimateWithRefund, + }; + } else if (quotes[aggId].approvalNeeded) { + // If gas estimation fails, but an ERC-20 approve is needed, then we do not add any estimate property to the quote object + // Such quotes will rely on the maxGas and averageGas properties from the api + newQuotes[aggId] = quotes[aggId]; + } + // If gas estimation fails and no approval is needed, then we filter that quote out, so that it is not shown to the user + }); + return newQuotes; + } + + private async _getERC20Allowance( + contractAddress: string, + walletAddress: string, + chainId: ChainId, + ) { + const contract = new Contract(contractAddress, abi, this._ethersProvider); return await contract.allowance( walletAddress, - SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[chainId], + SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[ + chainId as keyof typeof SWAPS_CHAINID_CONTRACT_ADDRESS_MAP + ], ); } -} -/** - * Calculates the median overallValueOfQuote of a sample of quotes. - * - * @param {Array} _quotes - A sample of quote objects with overallValueOfQuote, ethFee, metaMaskFeeInEth, and ethValueOfTokens properties - * @returns {object} An object with the ethValueOfTokens, ethFee, and metaMaskFeeInEth of the quote with the median overallValueOfQuote - */ -function getMedianEthValueQuote(_quotes) { - if (!Array.isArray(_quotes) || _quotes.length === 0) { - throw new Error('Expected non-empty array param.'); + private _pollForNewQuotes() { + const { + swapsState: { + swapsQuoteRefreshTime, + swapsQuotePrefetchingRefreshTime, + quotesPollingLimitEnabled, + }, + } = this.store.getState(); + // swapsQuoteRefreshTime is used on the View Quote page, swapsQuotePrefetchingRefreshTime is used on the Build Quote page. + const quotesRefreshRateInMs = quotesPollingLimitEnabled + ? swapsQuoteRefreshTime + : swapsQuotePrefetchingRefreshTime; + this._pollingTimeout = setTimeout(() => { + const { swapsState } = this.store.getState(); + this.fetchAndSetQuotes( + swapsState.fetchParams as FetchTradesInfoParams, + swapsState.fetchParams?.metaData as FetchTradesInfoParamsMetadata, + true, + ); + }, quotesRefreshRateInMs); } - const quotes = [..._quotes]; + private _setSaveFetchedQuotes(status: boolean) { + const { swapsState } = this.store.getState(); + this.store.updateState({ + swapsState: { ...swapsState, saveFetchedQuotes: status }, + }); + } - quotes.sort((quoteA, quoteB) => { - const overallValueOfQuoteA = new BigNumber(quoteA.overallValueOfQuote, 10); - const overallValueOfQuoteB = new BigNumber(quoteB.overallValueOfQuote, 10); - if (overallValueOfQuoteA.equals(overallValueOfQuoteB)) { - return 0; + // Sets the network config from the MetaSwap API. + private async _setSwapsNetworkConfig() { + const chainId = this._getCurrentChainId(); + let swapsNetworkConfig; + try { + swapsNetworkConfig = await this._fetchSwapsNetworkConfig(chainId); + } catch (e) { + console.error('Request for Swaps network config failed: ', e); } - return overallValueOfQuoteA.lessThan(overallValueOfQuoteB) ? -1 : 1; - }); - - if (quotes.length % 2 === 1) { - // return middle values - const medianOverallValue = - quotes[(quotes.length - 1) / 2].overallValueOfQuote; - const quotesMatchingMedianQuoteValue = quotes.filter( - (quote) => medianOverallValue === quote.overallValueOfQuote, - ); - return meansOfQuotesFeesAndValue(quotesMatchingMedianQuoteValue); + const { swapsState: latestSwapsState } = this.store.getState(); + this.store.updateState({ + swapsState: { + ...latestSwapsState, + swapsQuoteRefreshTime: + swapsNetworkConfig?.quotes || FALLBACK_QUOTE_REFRESH_TIME, + swapsQuotePrefetchingRefreshTime: + swapsNetworkConfig?.quotesPrefetching || FALLBACK_QUOTE_REFRESH_TIME, + swapsStxGetTransactionsRefreshTime: + swapsNetworkConfig?.stxGetTransactions || + FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME, + swapsStxBatchStatusRefreshTime: + swapsNetworkConfig?.stxBatchStatus || + FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME, + swapsStxMaxFeeMultiplier: + swapsNetworkConfig?.stxMaxFeeMultiplier || + FALLBACK_SMART_TRANSACTIONS_MAX_FEE_MULTIPLIER, + }, + }); } - // return mean of middle two values - const upperIndex = quotes.length / 2; - const lowerIndex = upperIndex - 1; - - const overallValueAtUpperIndex = quotes[upperIndex].overallValueOfQuote; - const overallValueAtLowerIndex = quotes[lowerIndex].overallValueOfQuote; - - const quotesMatchingUpperIndexValue = quotes.filter( - (quote) => overallValueAtUpperIndex === quote.overallValueOfQuote, - ); - const quotesMatchingLowerIndexValue = quotes.filter( - (quote) => overallValueAtLowerIndex === quote.overallValueOfQuote, - ); - - const feesAndValueAtUpperIndex = meansOfQuotesFeesAndValue( - quotesMatchingUpperIndexValue, - ); - const feesAndValueAtLowerIndex = meansOfQuotesFeesAndValue( - quotesMatchingLowerIndexValue, - ); - - return { - ethFee: new BigNumber(feesAndValueAtUpperIndex.ethFee, 10) - .plus(feesAndValueAtLowerIndex.ethFee, 10) - .dividedBy(2) - .toString(10), - metaMaskFeeInEth: new BigNumber( - feesAndValueAtUpperIndex.metaMaskFeeInEth, - 10, - ) - .plus(feesAndValueAtLowerIndex.metaMaskFeeInEth, 10) - .dividedBy(2) - .toString(10), - ethValueOfTokens: new BigNumber( - feesAndValueAtUpperIndex.ethValueOfTokens, - 10, - ) - .plus(feesAndValueAtLowerIndex.ethValueOfTokens, 10) - .dividedBy(2) - .toString(10), - }; -} + private _timedoutGasReturn( + tradeTxParams: Trade, + aggregator = '', + ): Promise<{ gasLimit: string | null; simulationFails: boolean }> { + return new Promise((resolve) => { + let gasTimedOut = false; -/** - * Calculates the arithmetic mean for each of three properties - ethFee, metaMaskFeeInEth and ethValueOfTokens - across - * an array of objects containing those properties. - * - * @param {Array} quotes - A sample of quote objects with overallValueOfQuote, ethFee, metaMaskFeeInEth and - * ethValueOfTokens properties - * @returns {object} An object with the arithmetic mean each of the ethFee, metaMaskFeeInEth and ethValueOfTokens of - * the passed quote objects - */ -function meansOfQuotesFeesAndValue(quotes) { - const feeAndValueSumsAsBigNumbers = quotes.reduce( - (feeAndValueSums, quote) => ({ - ethFee: feeAndValueSums.ethFee.plus(quote.ethFee, 10), - metaMaskFeeInEth: feeAndValueSums.metaMaskFeeInEth.plus( - quote.metaMaskFeeInEth, - 10, - ), - ethValueOfTokens: feeAndValueSums.ethValueOfTokens.plus( - quote.ethValueOfTokens, - 10, - ), - }), - { - ethFee: new BigNumber(0, 10), - metaMaskFeeInEth: new BigNumber(0, 10), - ethValueOfTokens: new BigNumber(0, 10), - }, - ); - - return { - ethFee: feeAndValueSumsAsBigNumbers.ethFee - .div(quotes.length, 10) - .toString(10), - metaMaskFeeInEth: feeAndValueSumsAsBigNumbers.metaMaskFeeInEth - .div(quotes.length, 10) - .toString(10), - ethValueOfTokens: feeAndValueSumsAsBigNumbers.ethValueOfTokens - .div(quotes.length, 10) - .toString(10), - }; -} + const gasTimeout = setTimeout(() => { + gasTimedOut = true; + this.trackMetaMetricsEvent({ + event: MetaMetricsEventName.QuoteError, + category: MetaMetricsEventCategory.Swaps, + properties: { + error_type: MetaMetricsEventErrorType.GasTimeout, + aggregator, + }, + }); + resolve({ + gasLimit: null, + simulationFails: true, + }); + }, SECOND * 5); -export const utils = { - getMedianEthValueQuote, - meansOfQuotesFeesAndValue, -}; + // Remove gas from params that will be passed to the `estimateGas` call + // Including it can cause the estimate to fail if the actual gas needed + // exceeds the passed gas + const tradeTxParamsForGasEstimate = { + data: tradeTxParams.data, + from: tradeTxParams.from, + to: tradeTxParams.to, + value: tradeTxParams.value, + }; + + this.getBufferedGasLimit({ txParams: tradeTxParamsForGasEstimate }, 1) + .then(({ gasLimit, simulationFails }) => { + if (!gasTimedOut) { + clearTimeout(gasTimeout); + resolve({ gasLimit, simulationFails }); + } + }) + .catch((e) => { + captureException(e, { + extra: { + aggregator, + }, + }); + if (!gasTimedOut) { + clearTimeout(gasTimeout); + resolve({ gasLimit: null, simulationFails: true }); + } + }); + }); + } +} diff --git a/app/scripts/controllers/swaps.types.ts b/app/scripts/controllers/swaps.types.ts new file mode 100644 index 000000000000..0164e253678e --- /dev/null +++ b/app/scripts/controllers/swaps.types.ts @@ -0,0 +1,189 @@ +import { ExternalProvider, JsonRpcFetchFunc } from '@ethersproject/providers'; +import type { ChainId } from '@metamask/controller-utils'; +import { GasFeeState } from '@metamask/gas-fee-controller'; +import { ProviderConfig } from '@metamask/network-controller'; +import type { ObservableStore } from '@metamask/obs-store'; +import { TransactionParams } from '@metamask/transaction-controller'; +import type { + MetaMetricsEventCategory, + MetaMetricsEventName, +} from '../../../shared/constants/metametrics'; +import { fetchTradesInfo as defaultFetchTradesInfo } from '../../../shared/lib/swaps-utils'; + +export type SwapsControllerStore = ObservableStore<{ + swapsState: SwapsControllerState; +}>; + +export type SwapsControllerState = { + quotes: Record; + quotesPollingLimitEnabled: boolean; + fetchParams: + | (FetchTradesInfoParams & { + metaData: FetchTradesInfoParamsMetadata; + }) + | null; + tokens: string[] | null; + tradeTxId: string | null; + approveTxId: string | null; + quotesLastFetched: number | null; + customMaxGas: string; + customGasPrice: string | null; + customMaxFeePerGas: string | null; + customMaxPriorityFeePerGas: string | null; + swapsUserFeeLevel: string; + selectedAggId: string | null; + customApproveTxData: string; + errorKey: string; + topAggId: string | null; + routeState: string; + swapsFeatureIsLive: boolean; + saveFetchedQuotes: boolean; + swapsQuoteRefreshTime: number; + swapsQuotePrefetchingRefreshTime: number; + swapsStxBatchStatusRefreshTime: number; + swapsStxStatusDeadline?: number; + swapsStxGetTransactionsRefreshTime: number; + swapsStxMaxFeeMultiplier: number; + swapsFeatureFlags: Record; +}; + +export type SwapsControllerOptions = { + getBufferedGasLimit: ( + params: { + txParams: { + value: string; + data: string; + to: string; + from: string; + }; + }, + factor: number, + ) => Promise<{ gasLimit: string; simulationFails: boolean }>; + provider: ExternalProvider | JsonRpcFetchFunc; + getProviderConfig: () => ProviderConfig; + getTokenRatesState: () => { + marketData: Record< + string, + { + [tokenAddress: string]: { + price: number; + }; + } + >; + }; + fetchTradesInfo: typeof defaultFetchTradesInfo; + getCurrentChainId: () => ChainId; + getLayer1GasFee: (params: { + transactionParams: TransactionParams; + chainId: ChainId; + }) => Promise; + getEIP1559GasFeeEstimates: () => Promise; + trackMetaMetricsEvent: (event: { + event: MetaMetricsEventName; + category: MetaMetricsEventCategory; + properties: Record; + }) => void; +}; + +export type FetchTradesInfoParams = { + slippage: number; + sourceToken: string; + sourceDecimals: number; + destinationToken: string; + value: string; + fromAddress: string; + exchangeList: string; + balanceError: boolean; +}; + +export type FetchTradesInfoParamsMetadata = { + chainId: ChainId; + sourceTokenInfo: { + address: string; + symbol: string; + decimals: number; + iconUrl?: string; + }; + destinationTokenInfo: { + address: string; + symbol: string; + decimals: number; + iconUrl?: string; + }; +}; + +export type QuoteRequest = { + chainId: number; + destinationToken: string; + slippage: number; + sourceAmount: string; + sourceToken: string; + walletAddress: string; +}; + +export type AggType = 'DEX' | 'RFQ' | 'CONTRACT' | 'CNT' | 'AGG'; + +export type PriceSlippage = { + bucket: 'low' | 'medium' | 'high'; + calculationError: string; + destinationAmountInETH: number | null; + destinationAmountInNativeCurrency: number | null; + ratio: number | null; + sourceAmountInETH: number | null; + sourceAmountInNativeCurrency: number | null; + sourceAmountInUSD: number | null; + destinationAmountInUSD: number | null; +}; + +export type Trade = { + data: string; + from: string; + to: string; + value: string; + gas?: string; +}; + +export type QuoteSavings = { + performance: string; + fee: string; + metaMaskFee: string; + medianMetaMaskFee: string; + total: string; +}; +export type Quote = { + aggregator: string; + aggType: AggType; + approvalNeeded?: Trade | null; + averageGas: number; + destinationAmount: string | null; + destinationToken: string; + destinationTokenInfo: { + address: string; + symbol: string; + decimals: number; + iconUrl?: string; + }; + destinationTokenRate: number | null; + error: null | string; + estimatedRefund: string; + ethFee: string; + ethValueOfTokens: string; + fee: number; + fetchTime: number; + gasEstimate: string; + gasEstimateWithRefund: string; + gasMultiplier: number; + hasRoute: boolean; + isBestQuote: boolean; + maxGas: number; + metaMaskFeeInEth: string; + multiLayerL1TradeFeeTotal?: string; + overallValueOfQuote: string; + priceSlippage: PriceSlippage; + quoteRefreshSeconds: number; + savings?: QuoteSavings; + sourceAmount: string; + sourceToken: string; + sourceTokenRate: number; + trade: null | Trade; +}; diff --git a/app/scripts/controllers/swaps.utils.ts b/app/scripts/controllers/swaps.utils.ts new file mode 100644 index 000000000000..eb8a082272ff --- /dev/null +++ b/app/scripts/controllers/swaps.utils.ts @@ -0,0 +1,148 @@ +import { BigNumber } from 'bignumber.js'; +import { MAX_GAS_LIMIT } from './swaps.constants'; +import type { Quote } from './swaps.types'; + +/** + * Calculates the median overallValueOfQuote of a sample of quotes. + * + * @param _quotes - A sample of quote objects with overallValueOfQuote, ethFee, metaMaskFeeInEth, and ethValueOfTokens properties + * @returns An object with the ethValueOfTokens, ethFee, and metaMaskFeeInEth of the quote with the median overallValueOfQuote + */ +export function getMedianEthValueQuote(_quotes: Quote[]) { + if (!Array.isArray(_quotes) || _quotes.length === 0) { + throw new Error('Expected non-empty array param.'); + } + + const quotes = [..._quotes]; + + quotes.sort((quoteA, quoteB) => { + const overallValueOfQuoteA = new BigNumber(quoteA.overallValueOfQuote, 10); + const overallValueOfQuoteB = new BigNumber(quoteB.overallValueOfQuote, 10); + if (overallValueOfQuoteA.equals(overallValueOfQuoteB)) { + return 0; + } + return overallValueOfQuoteA.lessThan(overallValueOfQuoteB) ? -1 : 1; + }); + + if (quotes.length % 2 === 1) { + // return middle values + const medianOverallValue = + quotes[(quotes.length - 1) / 2].overallValueOfQuote; + const quotesMatchingMedianQuoteValue = quotes.filter( + (quote) => medianOverallValue === quote.overallValueOfQuote, + ); + return meansOfQuotesFeesAndValue(quotesMatchingMedianQuoteValue); + } + + // return mean of middle two values + const upperIndex = quotes.length / 2; + const lowerIndex = upperIndex - 1; + + const overallValueAtUpperIndex = quotes[upperIndex].overallValueOfQuote; + const overallValueAtLowerIndex = quotes[lowerIndex].overallValueOfQuote; + + const quotesMatchingUpperIndexValue = quotes.filter( + (quote) => overallValueAtUpperIndex === quote.overallValueOfQuote, + ); + const quotesMatchingLowerIndexValue = quotes.filter( + (quote) => overallValueAtLowerIndex === quote.overallValueOfQuote, + ); + + const feesAndValueAtUpperIndex = meansOfQuotesFeesAndValue( + quotesMatchingUpperIndexValue, + ); + const feesAndValueAtLowerIndex = meansOfQuotesFeesAndValue( + quotesMatchingLowerIndexValue, + ); + + return { + ethFee: new BigNumber(feesAndValueAtUpperIndex.ethFee, 10) + .plus(feesAndValueAtLowerIndex.ethFee, 10) + .dividedBy(2) + .toString(10), + metaMaskFeeInEth: new BigNumber( + feesAndValueAtUpperIndex.metaMaskFeeInEth, + 10, + ) + .plus(feesAndValueAtLowerIndex.metaMaskFeeInEth, 10) + .dividedBy(2) + .toString(10), + ethValueOfTokens: new BigNumber( + feesAndValueAtUpperIndex.ethValueOfTokens, + 10, + ) + .plus(feesAndValueAtLowerIndex.ethValueOfTokens, 10) + .dividedBy(2) + .toString(10), + }; +} + +/** + * Calculates the arithmetic mean for each of three properties - ethFee, metaMaskFeeInEth and ethValueOfTokens - across + * an array of objects containing those properties. + * + * @param quotes - A sample of quote objects with overallValueOfQuote, ethFee, metaMaskFeeInEth and + * ethValueOfTokens properties + * @returns An object with the arithmetic mean each of the ethFee, metaMaskFeeInEth and ethValueOfTokens of + * the passed quote objects + */ +export function meansOfQuotesFeesAndValue(quotes: Quote[]) { + const feeAndValueSumsAsBigNumbers = quotes.reduce( + (feeAndValueSums, quote) => ({ + ethFee: feeAndValueSums.ethFee.plus(quote.ethFee, 10), + metaMaskFeeInEth: feeAndValueSums.metaMaskFeeInEth.plus( + quote.metaMaskFeeInEth, + 10, + ), + ethValueOfTokens: feeAndValueSums.ethValueOfTokens.plus( + quote.ethValueOfTokens, + 10, + ), + }), + { + ethFee: new BigNumber(0, 10), + metaMaskFeeInEth: new BigNumber(0, 10), + ethValueOfTokens: new BigNumber(0, 10), + }, + ); + + return { + ethFee: feeAndValueSumsAsBigNumbers.ethFee + .div(quotes.length, 10) + .toString(10), + metaMaskFeeInEth: feeAndValueSumsAsBigNumbers.metaMaskFeeInEth + .div(quotes.length, 10) + .toString(10), + ethValueOfTokens: feeAndValueSumsAsBigNumbers.ethValueOfTokens + .div(quotes.length, 10) + .toString(10), + }; +} + +/** + * Calculates the gas estimate after subtracting a refund from the maximum gas limit. + * + * @param maxGas - The maximum gas limit, defaulting to MAX_GAS_LIMIT. + * @param estimatedRefund - The estimated refund to subtract from the maximum gas limit, represented as a string. + * @param estimatedGas - The estimated gas required for the transaction, represented as a string. + * @returns The gas estimate with refund applied, represented as a hexadecimal string. If the subtraction + * results in a negative value or is less than the estimated gas, returns the estimated gas. + */ +export function calculateGasEstimateWithRefund( + maxGas = MAX_GAS_LIMIT, + estimatedRefund = '0', + estimatedGas = '0', +) { + const maxGasMinusRefund = new BigNumber(maxGas, 10).minus( + estimatedRefund, + 10, + ); + const isMaxGasMinusRefundNegative = maxGasMinusRefund.lt(0); + + const gasEstimateWithRefund = + !isMaxGasMinusRefundNegative && maxGasMinusRefund.lt(estimatedGas, 16) + ? `0x${maxGasMinusRefund.toString(16)}` + : estimatedGas; + + return gasEstimateWithRefund; +} diff --git a/app/scripts/detect-multiple-instances.test.js b/app/scripts/detect-multiple-instances.test.js index f873adcfded7..29c6af2d9fe9 100644 --- a/app/scripts/detect-multiple-instances.test.js +++ b/app/scripts/detect-multiple-instances.test.js @@ -1,6 +1,4 @@ -import { strict as assert } from 'assert'; import browser from 'webextension-polyfill'; -import sinon from 'sinon'; import { PLATFORM_CHROME, PLATFORM_EDGE, @@ -19,98 +17,78 @@ import * as util from './lib/util'; describe('multiple instances running detector', function () { const PING_MESSAGE = 'isRunning'; - let sendMessageStub = sinon.stub(); + const sendMessageStub = jest.fn(); - beforeEach(async function () { - sinon.replace(browser, 'runtime', { + beforeEach(function () { + jest.replaceProperty(browser, 'runtime', { sendMessage: sendMessageStub, id: METAMASK_BETA_CHROME_ID, }); - - sinon.stub(util, 'getPlatform').callsFake((_) => { - return PLATFORM_CHROME; - }); + jest.spyOn(util, 'getPlatform').mockReturnValue(PLATFORM_CHROME); }); afterEach(function () { - sinon.restore(); + jest.restoreAllMocks(); }); describe('checkForMultipleVersionsRunning', function () { it('should send ping message to multiple instances', async function () { await checkForMultipleVersionsRunning(); - assert(sendMessageStub.callCount === 4); - assert( - sendMessageStub - .getCall(0) - .calledWithExactly(METAMASK_PROD_CHROME_ID, PING_MESSAGE), - ); - assert( - sendMessageStub - .getCall(1) - .calledWithExactly(METAMASK_FLASK_CHROME_ID, PING_MESSAGE), - ); - assert( - sendMessageStub - .getCall(2) - .calledWithExactly(METAMASK_MMI_BETA_CHROME_ID, PING_MESSAGE), - ); - assert( - sendMessageStub - .getCall(3) - .calledWithExactly(METAMASK_MMI_PROD_CHROME_ID, PING_MESSAGE), - ); + expect(sendMessageStub.mock.calls).toHaveLength(4); + expect( + sendMessageStub.mock.instances[0].sendMessage, + ).toHaveBeenCalledWith(METAMASK_PROD_CHROME_ID, PING_MESSAGE); + expect( + sendMessageStub.mock.instances[1].sendMessage, + ).toHaveBeenCalledWith(METAMASK_FLASK_CHROME_ID, PING_MESSAGE); + expect( + sendMessageStub.mock.instances[2].sendMessage, + ).toHaveBeenCalledWith(METAMASK_MMI_BETA_CHROME_ID, PING_MESSAGE); + expect( + sendMessageStub.mock.instances[3].sendMessage, + ).toHaveBeenCalledWith(METAMASK_MMI_PROD_CHROME_ID, PING_MESSAGE); }); it('should not send ping message if platform is not Chrome or Firefox', async function () { - util.getPlatform.restore(); - sendMessageStub = sinon.stub(); + sendMessageStub.mockRestore(); - sinon.stub(util, 'getPlatform').callsFake((_) => { - return PLATFORM_EDGE; - }); + jest.spyOn(util, 'getPlatform').mockReturnValue(PLATFORM_EDGE); await checkForMultipleVersionsRunning(); - assert(sendMessageStub.notCalled); + expect(sendMessageStub).not.toHaveBeenCalled(); }); it('should not expose an error outside if sendMessage throws', async function () { - sinon.restore(); - - sinon.replace(browser, 'runtime', { - sendMessage: sinon.stub().throws(), + jest.replaceProperty(browser, 'runtime', { + sendMessage: sendMessageStub.mockImplementation(() => { + throw new Error(); + }), id: METAMASK_BETA_CHROME_ID, }); - const spy = sinon.spy(checkForMultipleVersionsRunning); - - await checkForMultipleVersionsRunning(); - - assert(!spy.threw()); + expect(async () => await checkForMultipleVersionsRunning()).not.toThrow(); }); }); describe('onMessageReceived', function () { beforeEach(function () { - sinon.spy(console, 'warn'); + jest.spyOn(console, 'warn'); }); it('should print warning message to on ping message received', async function () { onMessageReceived(PING_MESSAGE); - assert( - console.warn.calledWithExactly( - 'Warning! You have multiple instances of MetaMask running!', - ), + expect(console.warn).toHaveBeenCalledWith( + 'Warning! You have multiple instances of MetaMask running!', ); }); it('should not print warning message if wrong message received', async function () { onMessageReceived(PING_MESSAGE.concat('wrong')); - assert(console.warn.notCalled); + expect(console.warn).not.toHaveBeenCalled(); }); }); }); diff --git a/app/scripts/lib/AccountIdentitiesPetnamesBridge.test.ts b/app/scripts/lib/AccountIdentitiesPetnamesBridge.test.ts index 7e1a23a5902e..c17ce27d68aa 100644 --- a/app/scripts/lib/AccountIdentitiesPetnamesBridge.test.ts +++ b/app/scripts/lib/AccountIdentitiesPetnamesBridge.test.ts @@ -27,7 +27,6 @@ const MOCK_INTERNAL_ACCOUNT = createMockInternalAccount({ address: ADDRESS_MOCK, name: NAME_MOCK, keyringType: KeyringTypes.hd, - is4337: false, snapOptions: undefined, }); diff --git a/app/scripts/lib/account-tracker.js b/app/scripts/lib/account-tracker.js index 7de8632e2b74..3918f835f5d8 100644 --- a/app/scripts/lib/account-tracker.js +++ b/app/scripts/lib/account-tracker.js @@ -96,7 +96,7 @@ export default class AccountTracker { ); this.controllerMessenger.subscribe( - 'AccountsController:selectedAccountChange', + 'AccountsController:selectedEvmAccountChange', (newAccount) => { const { useMultiAccountBalanceChecker } = this.preferencesController.store.getState(); diff --git a/app/scripts/lib/accounts/BalancesController.test.ts b/app/scripts/lib/accounts/BalancesController.test.ts new file mode 100644 index 000000000000..01ce1f88c608 --- /dev/null +++ b/app/scripts/lib/accounts/BalancesController.test.ts @@ -0,0 +1,122 @@ +import { ControllerMessenger } from '@metamask/base-controller'; +import { + Balance, + BtcAccountType, + CaipAssetType, + InternalAccount, +} from '@metamask/keyring-api'; +import { createMockInternalAccount } from '../../../../test/jest/mocks'; +import { + BalancesController, + AllowedActions, + BalancesControllerEvents, + BalancesControllerState, + defaultState, +} from './BalancesController'; +import { Poller } from './Poller'; + +const mockBtcAccount = createMockInternalAccount({ + address: '', + name: 'Btc Account', + // @ts-expect-error - account type may be btc or eth, mock file is not typed + type: BtcAccountType.P2wpkh, + snapOptions: { + id: 'mock-btc-snap', + name: 'mock-btc-snap', + enabled: true, + }, +}); + +const mockBalanceResult = { + 'bip122:000000000933ea01ad0ee984209779ba/slip44:0': { + amount: '0.00000000', + unit: 'BTC', + }, +}; + +const setupController = ({ + state = defaultState, + mocks, +}: { + state?: BalancesControllerState; + mocks?: { + listMultichainAccounts?: InternalAccount[]; + handleRequestReturnValue?: Record; + }; +} = {}) => { + const controllerMessenger = new ControllerMessenger< + AllowedActions, + BalancesControllerEvents + >(); + + const balancesControllerMessenger = controllerMessenger.getRestricted({ + name: 'BalancesController', + allowedActions: ['SnapController:handleRequest'], + allowedEvents: [], + }); + + const mockSnapHandleRequest = jest.fn(); + controllerMessenger.registerActionHandler( + 'SnapController:handleRequest', + mockSnapHandleRequest.mockReturnValue( + mocks?.handleRequestReturnValue ?? mockBalanceResult, + ), + ); + + // TODO: remove when listMultichainAccounts action is available + const mockListMultichainAccounts = jest + .fn() + .mockReturnValue(mocks?.listMultichainAccounts ?? [mockBtcAccount]); + + const controller = new BalancesController({ + messenger: balancesControllerMessenger, + state, + // TODO: remove when listMultichainAccounts action is available + listMultichainAccounts: mockListMultichainAccounts, + }); + + return { + controller, + mockSnapHandleRequest, + mockListMultichainAccounts, + }; +}; + +describe('BalancesController', () => { + it('initialize with default state', () => { + const { controller } = setupController({}); + expect(controller.state).toEqual({ balances: {} }); + }); + + it('starts polling when calling start', async () => { + const spyPoller = jest.spyOn(Poller.prototype, 'start'); + const { controller } = setupController(); + await controller.start(); + expect(spyPoller).toHaveBeenCalledTimes(1); + }); + + it('stops polling when calling stop', async () => { + const spyPoller = jest.spyOn(Poller.prototype, 'stop'); + const { controller } = setupController(); + await controller.start(); + await controller.stop(); + expect(spyPoller).toHaveBeenCalledTimes(1); + }); + + it('update balances when calling updateBalances', async () => { + const { controller } = setupController(); + + await controller.updateBalances(); + + expect(controller.state).toEqual({ + balances: { + [mockBtcAccount.id]: { + 'bip122:000000000933ea01ad0ee984209779ba/slip44:0': { + amount: '0.00000000', + unit: 'BTC', + }, + }, + }, + }); + }); +}); diff --git a/app/scripts/lib/accounts/BalancesController.ts b/app/scripts/lib/accounts/BalancesController.ts new file mode 100644 index 000000000000..eee4ac11889a --- /dev/null +++ b/app/scripts/lib/accounts/BalancesController.ts @@ -0,0 +1,255 @@ +import type { Json, JsonRpcRequest } from '@metamask/utils'; + +import { + BaseController, + type ControllerGetStateAction, + type ControllerStateChangeEvent, + type RestrictedControllerMessenger, +} from '@metamask/base-controller'; +import { + BtcAccountType, + KeyringClient, + type Balance, + type CaipAssetType, + type InternalAccount, +} from '@metamask/keyring-api'; +import type { HandleSnapRequest } from '@metamask/snaps-controllers'; +import type { SnapId } from '@metamask/snaps-sdk'; +import { HandlerType } from '@metamask/snaps-utils'; +import type { Draft } from 'immer'; +import { Poller } from './Poller'; + +const controllerName = 'BalancesController'; + +/** + * State used by the {@link BalancesController} to cache account balances. + */ +export type BalancesControllerState = { + balances: { + [account: string]: { + [asset: string]: { + amount: string; + unit: string; + }; + }; + }; +}; + +/** + * Default state of the {@link BalancesController}. + */ +export const defaultState: BalancesControllerState = { balances: {} }; + +/** + * Returns the state of the {@link BalancesController}. + */ +export type BalancesControllerGetStateAction = ControllerGetStateAction< + typeof controllerName, + BalancesControllerState +>; + +/** + * Updates the balances of all supported accounts. + */ +export type BalancesControllerUpdateBalancesAction = { + type: `${typeof controllerName}:updateBalances`; + handler: BalancesController['updateBalances']; +}; + +/** + * Event emitted when the state of the {@link BalancesController} changes. + */ +export type BalancesControllerStateChange = ControllerStateChangeEvent< + typeof controllerName, + BalancesControllerState +>; + +/** + * Actions exposed by the {@link BalancesController}. + */ +export type BalancesControllerActions = + | BalancesControllerGetStateAction + | BalancesControllerUpdateBalancesAction; + +/** + * Events emitted by {@link BalancesController}. + */ +export type BalancesControllerEvents = BalancesControllerStateChange; + +/** + * Actions that this controller is allowed to call. + */ +export type AllowedActions = HandleSnapRequest; + +/** + * Messenger type for the BalancesController. + */ +export type BalancesControllerMessenger = RestrictedControllerMessenger< + typeof controllerName, + BalancesControllerActions | AllowedActions, + BalancesControllerEvents, + AllowedActions['type'], + never +>; + +/** + * {@link BalancesController}'s metadata. + * + * This allows us to choose if fields of the state should be persisted or not + * using the `persist` flag; and if they can be sent to Sentry or not, using + * the `anonymous` flag. + */ +const balancesControllerMetadata = { + balances: { + persist: true, + anonymous: false, + }, +}; + +const BTC_TESTNET_ASSETS = ['bip122:000000000933ea01ad0ee984209779ba/slip44:0']; +const BTC_MAINNET_ASSETS = ['bip122:000000000019d6689c085ae165831e93/slip44:0']; +export const BTC_AVG_BLOCK_TIME = 600000; // 10 minutes in milliseconds + +/** + * Returns whether an address is on the Bitcoin mainnet. + * + * This function only checks the prefix of the address to determine if it's on + * the mainnet or not. It doesn't validate the address itself, and should only + * be used as a temporary solution until this information is included in the + * account object. + * + * @param address - The address to check. + * @returns `true` if the address is on the Bitcoin mainnet, `false` otherwise. + */ +function isBtcMainnet(address: string): boolean { + return address.startsWith('bc1') || address.startsWith('1'); +} + +/** + * The BalancesController is responsible for fetching and caching account + * balances. + */ +export class BalancesController extends BaseController< + typeof controllerName, + BalancesControllerState, + BalancesControllerMessenger +> { + #poller: Poller; + + // TODO: remove once action is implemented + #listMultichainAccounts: () => InternalAccount[]; + + constructor({ + messenger, + state, + listMultichainAccounts, + }: { + messenger: BalancesControllerMessenger; + state: BalancesControllerState; + listMultichainAccounts: () => InternalAccount[]; + }) { + super({ + messenger, + name: controllerName, + metadata: balancesControllerMetadata, + state: { + ...defaultState, + ...state, + }, + }); + + this.#listMultichainAccounts = listMultichainAccounts; + this.#poller = new Poller(() => this.updateBalances(), BTC_AVG_BLOCK_TIME); + } + + /** + * Starts the polling process. + */ + async start(): Promise { + this.#poller.start(); + } + + /** + * Stops the polling process. + */ + async stop(): Promise { + this.#poller.stop(); + } + + /** + * Lists the accounts that we should get balances for. + * + * Currently, we only get balances for P2WPKH accounts, but this will change + * in the future when we start support other non-EVM account types. + * + * @returns A list of accounts that we should get balances for. + */ + async #listAccounts(): Promise { + const accounts = this.#listMultichainAccounts(); + + return accounts.filter((account) => account.type === BtcAccountType.P2wpkh); + } + + /** + * Updates the balances of all supported accounts. This method doesn't return + * anything, but it updates the state of the controller. + */ + async updateBalances() { + const accounts = await this.#listAccounts(); + const partialState: BalancesControllerState = { balances: {} }; + + for (const account of accounts) { + if (account.metadata.snap) { + partialState.balances[account.id] = await this.#getBalances( + account.id, + account.metadata.snap.id, + isBtcMainnet(account.address) + ? BTC_MAINNET_ASSETS + : BTC_TESTNET_ASSETS, + ); + } + } + + this.update((state: Draft) => ({ + ...state, + ...partialState, + })); + } + + /** + * Get the balances for an account. + * + * @param accountId - ID of the account to get balances for. + * @param snapId - ID of the Snap which manages the account. + * @param assetTypes - Array of asset types to get balances for. + * @returns A map of asset types to balances. + */ + async #getBalances( + accountId: string, + snapId: string, + assetTypes: CaipAssetType[], + ): Promise> { + return await this.#getClient(snapId).getAccountBalances( + accountId, + assetTypes, + ); + } + + /** + * Gets a `KeyringClient` for a Snap. + * + * @param snapId - ID of the Snap to get the client for. + * @returns A `KeyringClient` for the Snap. + */ + #getClient(snapId: string): KeyringClient { + return new KeyringClient({ + send: async (request: JsonRpcRequest) => + (await this.messagingSystem.call('SnapController:handleRequest', { + snapId: snapId as SnapId, + origin: 'metamask', + handler: HandlerType.OnKeyringRequest, + request, + })) as Promise, + }); + } +} diff --git a/app/scripts/lib/accounts/Poller.test.ts b/app/scripts/lib/accounts/Poller.test.ts new file mode 100644 index 000000000000..e79d4961a0c8 --- /dev/null +++ b/app/scripts/lib/accounts/Poller.test.ts @@ -0,0 +1,59 @@ +import { Poller } from './Poller'; + +jest.useFakeTimers(); + +const interval = 1000; +const intervalPlus100ms = interval + 100; + +describe('Poller', () => { + let callback: jest.Mock; + + beforeEach(() => { + callback = jest.fn(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('calls the callback function after the specified interval', async () => { + const poller = new Poller(callback, interval); + poller.start(); + jest.advanceTimersByTime(intervalPlus100ms); + poller.stop(); + + expect(callback).toHaveBeenCalledTimes(1); + }); + + it('does not call the callback function if stopped before the interval', async () => { + const poller = new Poller(callback, interval); + poller.start(); + poller.stop(); + jest.advanceTimersByTime(intervalPlus100ms); + + expect(callback).not.toHaveBeenCalled(); + }); + + it('calls the callback function multiple times if started and stopped multiple times', async () => { + const poller = new Poller(callback, interval); + poller.start(); + jest.advanceTimersByTime(intervalPlus100ms); + poller.stop(); + jest.advanceTimersByTime(intervalPlus100ms); + poller.start(); + jest.advanceTimersByTime(intervalPlus100ms); + poller.stop(); + + expect(callback).toHaveBeenCalledTimes(2); + }); + + it('does not call the callback if the poller is stopped before the interval has passed', async () => { + const poller = new Poller(callback, interval); + poller.start(); + // Wait for some time, but resumes before reaching out + // the `interval` timeout + jest.advanceTimersByTime(interval / 2); + poller.stop(); + expect(callback).not.toHaveBeenCalled(); + }); +}); diff --git a/app/scripts/lib/accounts/Poller.ts b/app/scripts/lib/accounts/Poller.ts new file mode 100644 index 000000000000..600e2ea615d7 --- /dev/null +++ b/app/scripts/lib/accounts/Poller.ts @@ -0,0 +1,28 @@ +export class Poller { + #interval: number; + + #callback: () => void; + + #handle: NodeJS.Timeout | undefined = undefined; + + constructor(callback: () => void, interval: number) { + this.#interval = interval; + this.#callback = callback; + } + + start() { + if (this.#handle) { + return; + } + + this.#handle = setInterval(this.#callback, this.#interval); + } + + stop() { + if (!this.#handle) { + return; + } + clearInterval(this.#handle); + this.#handle = undefined; + } +} diff --git a/app/scripts/lib/createEvmMethodsToNonEvmAccountReqFilterMiddleware.test.ts b/app/scripts/lib/createEvmMethodsToNonEvmAccountReqFilterMiddleware.test.ts new file mode 100644 index 000000000000..3b677225a148 --- /dev/null +++ b/app/scripts/lib/createEvmMethodsToNonEvmAccountReqFilterMiddleware.test.ts @@ -0,0 +1,285 @@ +import { jsonrpc2 } from '@metamask/utils'; +import { BtcAccountType, EthAccountType } from '@metamask/keyring-api'; +import { Json } from 'json-rpc-engine'; +import createEvmMethodsToNonEvmAccountReqFilterMiddleware, { + EvmMethodsToNonEvmAccountFilterMessenger, +} from './createEvmMethodsToNonEvmAccountReqFilterMiddleware'; + +describe('createEvmMethodsToNonEvmAccountReqFilterMiddleware', () => { + const getMockRequest = (method: string, params?: Json) => ({ + jsonrpc: jsonrpc2, + id: 1, + method, + params, + }); + const getMockResponse = () => ({ jsonrpc: jsonrpc2, id: 'foo' }); + + // @ts-expect-error This function is missing from the Mocha type definitions + it.each([ + // EVM requests + { + accountType: BtcAccountType.P2wpkh, + method: 'eth_accounts', + calledNext: false, + }, + { + accountType: BtcAccountType.P2wpkh, + method: 'eth_sendRawTransaction', + calledNext: false, + }, + { + accountType: BtcAccountType.P2wpkh, + method: 'eth_sendTransaction', + calledNext: false, + }, + { + accountType: BtcAccountType.P2wpkh, + method: 'eth_sign', + calledNext: false, + }, + { + accountType: BtcAccountType.P2wpkh, + method: 'eth_signTypedData', + calledNext: false, + }, + { + accountType: BtcAccountType.P2wpkh, + method: 'eth_signTypedData_v1', + calledNext: false, + }, + { + accountType: BtcAccountType.P2wpkh, + method: 'eth_signTypedData_v3', + calledNext: false, + }, + { + accountType: BtcAccountType.P2wpkh, + method: 'eth_signTypedData_v4', + calledNext: false, + }, + { + accountType: EthAccountType.Eoa, + method: 'eth_accounts', + calledNext: true, + }, + { + accountType: EthAccountType.Eoa, + method: 'eth_sendRawTransaction', + calledNext: true, + }, + { + accountType: EthAccountType.Eoa, + method: 'eth_sendTransaction', + calledNext: true, + }, + { + accountType: EthAccountType.Eoa, + method: 'eth_sign', + calledNext: true, + }, + { + accountType: EthAccountType.Eoa, + method: 'eth_signTypedData', + calledNext: true, + }, + { + accountType: EthAccountType.Eoa, + method: 'eth_signTypedData_v1', + calledNext: true, + }, + { + accountType: EthAccountType.Eoa, + method: 'eth_signTypedData_v3', + calledNext: true, + }, + { + accountType: EthAccountType.Eoa, + method: 'eth_signTypedData_v4', + calledNext: true, + }, + + // EVM requests not associated with an account + { + accountType: BtcAccountType.P2wpkh, + method: 'eth_blockNumber', + calledNext: true, + }, + { + accountType: BtcAccountType.P2wpkh, + method: 'eth_chainId', + calledNext: true, + }, + { + accountType: EthAccountType.Eoa, + method: 'eth_blockNumber', + calledNext: true, + }, + { + accountType: EthAccountType.Eoa, + method: 'eth_chainId', + calledNext: true, + }, + + // other requests + { + accountType: BtcAccountType.P2wpkh, + method: 'wallet_getSnaps', + calledNext: true, + }, + { + accountType: BtcAccountType.P2wpkh, + method: 'wallet_invokeSnap', + calledNext: true, + }, + { + accountType: BtcAccountType.P2wpkh, + method: 'wallet_requestSnaps', + calledNext: true, + }, + { + accountType: BtcAccountType.P2wpkh, + method: 'snap_getClientStatus', + calledNext: true, + }, + { + accountType: BtcAccountType.P2wpkh, + method: 'wallet_addEthereumChain', + calledNext: true, + }, + { + accountType: BtcAccountType.P2wpkh, + method: 'wallet_getPermissions', + calledNext: true, + }, + { + accountType: BtcAccountType.P2wpkh, + method: 'wallet_requestPermissions', + calledNext: true, + }, + { + accountType: BtcAccountType.P2wpkh, + method: 'wallet_revokePermissions', + calledNext: true, + }, + { + accountType: BtcAccountType.P2wpkh, + method: 'wallet_switchEthereumChain', + calledNext: true, + }, + { + accountType: EthAccountType.Eoa, + method: 'wallet_getSnaps', + calledNext: true, + }, + { + accountType: EthAccountType.Eoa, + method: 'wallet_invokeSnap', + calledNext: true, + }, + { + accountType: EthAccountType.Eoa, + method: 'wallet_requestSnaps', + calledNext: true, + }, + { + accountType: EthAccountType.Eoa, + method: 'snap_getClientStatus', + calledNext: true, + }, + { + accountType: EthAccountType.Eoa, + method: 'wallet_addEthereumChain', + calledNext: true, + }, + { + accountType: EthAccountType.Eoa, + method: 'wallet_getPermissions', + calledNext: true, + }, + { + accountType: EthAccountType.Eoa, + method: 'wallet_requestPermissions', + calledNext: true, + }, + { + accountType: EthAccountType.Eoa, + method: 'wallet_revokePermissions', + calledNext: true, + }, + { + accountType: EthAccountType.Eoa, + method: 'wallet_switchEthereumChain', + calledNext: true, + }, + + // wallet_requestPermissions request + { + accountType: BtcAccountType.P2wpkh, + method: 'wallet_requestPermissions', + params: [{ eth_accounts: {} }], + calledNext: false, + }, + { + accountType: BtcAccountType.P2wpkh, + method: 'wallet_requestPermissions', + params: [{ snap_getClientStatus: {} }], + calledNext: true, + }, + { + accountType: BtcAccountType.P2wpkh, + method: 'wallet_requestPermissions', + params: [{ eth_accounts: {}, snap_getClientStatus: {} }], + calledNext: false, + }, + { + accountType: EthAccountType.Eoa, + method: 'wallet_requestPermissions', + params: [{ eth_accounts: {} }], + calledNext: true, + }, + + { + accountType: EthAccountType.Eoa, + method: 'wallet_requestPermissions', + params: [{ snap_getClientStatus: {} }], + calledNext: true, + }, + { + accountType: EthAccountType.Eoa, + method: 'wallet_requestPermissions', + params: [{ eth_accounts: {}, snap_getClientStatus: {} }], + calledNext: true, + }, + ])( + `accountType $accountType method $method with non-EVM account is passed to next called $calledNext times`, + ({ + accountType, + method, + params, + calledNext, + }: { + accountType: EthAccountType | BtcAccountType; + method: string; + params?: Json; + calledNext: number; + }) => { + const filterFn = createEvmMethodsToNonEvmAccountReqFilterMiddleware({ + messenger: { + call: jest.fn().mockReturnValue({ type: accountType }), + } as unknown as EvmMethodsToNonEvmAccountFilterMessenger, + }); + const mockNext = jest.fn(); + const mockEnd = jest.fn(); + + filterFn( + getMockRequest(method, params), + getMockResponse(), + mockNext, + mockEnd, + ); + + expect(mockNext).toHaveBeenCalledTimes(calledNext ? 1 : 0); + expect(mockEnd).toHaveBeenCalledTimes(calledNext ? 0 : 1); + }, + ); +}); diff --git a/app/scripts/lib/createEvmMethodsToNonEvmAccountReqFilterMiddleware.ts b/app/scripts/lib/createEvmMethodsToNonEvmAccountReqFilterMiddleware.ts new file mode 100644 index 000000000000..3e1eca86997e --- /dev/null +++ b/app/scripts/lib/createEvmMethodsToNonEvmAccountReqFilterMiddleware.ts @@ -0,0 +1,94 @@ +import { isEvmAccountType } from '@metamask/keyring-api'; +import { RestrictedControllerMessenger } from '@metamask/base-controller'; +import { AccountsControllerGetSelectedAccountAction } from '@metamask/accounts-controller'; +import { JsonRpcMiddleware } from 'json-rpc-engine'; +import { RestrictedEthMethods } from '../../../shared/constants/permissions'; +import { unrestrictedEthSigningMethods } from '../controllers/permissions'; + +type AllowedActions = AccountsControllerGetSelectedAccountAction; + +export type EvmMethodsToNonEvmAccountFilterMessenger = + RestrictedControllerMessenger< + 'EvmMethodsToNonEvmAccountFilterMessenger', + AllowedActions, + never, + AllowedActions['type'], + never + >; + +const METHODS_TO_CHECK = [ + ...Object.values(RestrictedEthMethods), + ...unrestrictedEthSigningMethods, +]; + +/** + * Returns a middleware that filters out requests whose requests are restricted to EVM accounts. + * + * @param opt - The middleware options. + * @param opt.messenger - The messenger object. + * @returns The middleware function. + */ +export default function createEvmMethodsToNonEvmAccountReqFilterMiddleware({ + messenger, +}: { + messenger: EvmMethodsToNonEvmAccountFilterMessenger; +}): JsonRpcMiddleware { + return function filterEvmRequestToNonEvmAccountsMiddleware( + req, + _res, + next, + end, + ) { + const selectedAccount = messenger.call( + 'AccountsController:getSelectedAccount', + ); + + // If it's an EVM account, there nothing to filter, so jump to the next + // middleware directly. + if (isEvmAccountType(selectedAccount.type)) { + return next(); + } + + const ethMethodsRequiringEthAccount = METHODS_TO_CHECK.includes(req.method); + if (ethMethodsRequiringEthAccount) { + return end( + new Error(`Non-EVM account cannot request this method: ${req.method}`), + ); + } + + // https://docs.metamask.io/wallet/reference/wallet_requestpermissions/ + // wallet_requestPermissions param is an array with one object. The object may contain + // multiple keys that represent the permissions being requested. + + // Example: + // { + // "method": "wallet_requestPermissions", + // "params": [ + // { + // "eth_accounts": {}, + // "anotherPermission": {} + // } + // ] + // } + + // TODO: Convert this to superstruct schema + const isWalletRequestPermission = + req.method === 'wallet_requestPermissions'; + if (isWalletRequestPermission && req?.params && Array.isArray(req.params)) { + const permissionsMethodRequest = Object.keys(req.params[0]); + + const isEvmPermissionRequest = METHODS_TO_CHECK.some((method) => + permissionsMethodRequest.includes(method), + ); + if (isEvmPermissionRequest) { + return end( + new Error( + `Non-EVM account cannot request this method: ${permissionsMethodRequest.toString()}`, + ), + ); + } + } + + return next(); + }; +} diff --git a/app/scripts/lib/createRPCMethodTrackingMiddleware.js b/app/scripts/lib/createRPCMethodTrackingMiddleware.js index ed2dfe48f2d9..1d7f42710cd6 100644 --- a/app/scripts/lib/createRPCMethodTrackingMiddleware.js +++ b/app/scripts/lib/createRPCMethodTrackingMiddleware.js @@ -11,18 +11,13 @@ import { parseTypedDataMessage } from '../../../shared/modules/transaction.utils import { BlockaidResultType, - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) BlockaidReason, - ///: END:ONLY_INCLUDE_IF } from '../../../shared/constants/security-provider'; - -///: BEGIN:ONLY_INCLUDE_IF(blockaid) import { EIP712_PRIMARY_TYPE_PERMIT, SIGNING_METHODS, } from '../../../shared/constants/transaction'; import { getBlockaidMetricsProps } from '../../../ui/helpers/utils/metrics'; -///: END:ONLY_INCLUDE_IF import { REDESIGN_APPROVAL_TYPES } from '../../../ui/pages/confirmations/utils/confirm'; import { getSnapAndHardwareInfoForMetrics } from './snap-keyring/metrics'; @@ -135,7 +130,6 @@ const TRANSFORM_PARAMS_MAP = { const rateLimitTimeoutsByMethod = {}; let globalRateLimitCount = 0; -///: BEGIN:ONLY_INCLUDE_IF(blockaid) /** * Returns a middleware that tracks inpage_provider usage using sampling for * each type of event except those that require user interaction, such as @@ -161,7 +155,6 @@ let globalRateLimitCount = 0; * tracked within the globalRateLimitTimeout time window. * @returns {Function} */ -///: END:ONLY_INCLUDE_IF export default function createRPCMethodTrackingMiddleware({ trackEvent, @@ -174,9 +167,7 @@ export default function createRPCMethodTrackingMiddleware({ getDeviceModel, isConfirmationRedesignEnabled, snapAndHardwareMessenger, - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) appStateController, - ///: END:ONLY_INCLUDE_IF }) { return async function rpcMethodTrackingMiddleware( /** @type {any} */ req, @@ -254,7 +245,6 @@ export default function createRPCMethodTrackingMiddleware({ data = req?.params?.[1]; } - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) if (req.securityAlertResponse?.providerRequestsCount) { Object.keys(req.securityAlertResponse.providerRequestsCount).forEach( (key) => { @@ -275,7 +265,7 @@ export default function createRPCMethodTrackingMiddleware({ eventProperties.security_alert_description = req.securityAlertResponse.description; } - ///: END:ONLY_INCLUDE_IF + const isConfirmationRedesign = isConfirmationRedesignEnabled() && REDESIGN_APPROVAL_TYPES.find( @@ -309,6 +299,7 @@ export default function createRPCMethodTrackingMiddleware({ } } else if (method === MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4) { const { primaryType } = parseTypedDataMessage(data); + eventProperties.eip712_primary_type = primaryType; if (primaryType === EIP712_PRIMARY_TYPE_PERMIT) { eventProperties.ui_customizations = [ ...(eventProperties.ui_customizations || []), @@ -379,8 +370,6 @@ export default function createRPCMethodTrackingMiddleware({ } let blockaidMetricProps = {}; - - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) if (!isDisabledRPCMethod) { if (SIGNING_METHODS.includes(method)) { const securityAlertResponse = @@ -393,7 +382,6 @@ export default function createRPCMethodTrackingMiddleware({ }); } } - ///: END:ONLY_INCLUDE_IF const properties = { ...eventProperties, diff --git a/app/scripts/lib/createRPCMethodTrackingMiddleware.test.js b/app/scripts/lib/createRPCMethodTrackingMiddleware.test.js index 595ef9349a8b..a0c0da552db1 100644 --- a/app/scripts/lib/createRPCMethodTrackingMiddleware.test.js +++ b/app/scripts/lib/createRPCMethodTrackingMiddleware.test.js @@ -531,6 +531,7 @@ describe('createRPCMethodTrackingMiddleware', () => { properties: { signature_type: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4, ui_customizations: [MetaMetricsEventUiCustomization.Permit], + eip712_primary_type: 'Permit', }, referrer: { url: 'some.dapp' }, }); diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index dfa04ab73236..92173fc5f72c 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -117,6 +117,9 @@ export const SENTRY_BACKGROUND_STATE = { trezorModel: true, usedNetworks: true, }, + MultichainBalancesController: { + balances: false, + }, CronjobController: { jobs: false, }, @@ -266,6 +269,14 @@ export const SENTRY_BACKGROUND_STATE = { PushPlatformNotificationsController: { fcmToken: false, }, + MultichainRatesController: { + fiatCurrency: true, + rates: true, + cryptocurrencies: true, + }, + QueuedRequestController: { + queuedRequestCount: true, + }, SelectedNetworkController: { domains: false }, SignatureController: { unapprovedMsgCount: true, @@ -629,6 +640,13 @@ export default function setupSentry({ release, getState }) { tracesSampleRate: 0.01, beforeSend: (report) => rewriteReport(report, getState), beforeBreadcrumb: beforeBreadcrumb(getState), + // Client reports are automatically sent when a page's visibility changes to + // "hidden", but cancelled (with an Error) that gets logged to the console. + // Our test infra sometimes reports these errors as unexpected failures, + // which results in test flakiness. We don't use these client reports, so + // we can safely turn them off by setting the `sendClientReports` option to + // `false`. + sendClientReports: false, }); /** diff --git a/app/scripts/lib/keyring-snaps-permissions.test.ts b/app/scripts/lib/snap-keyring/keyring-snaps-permissions.test.ts similarity index 100% rename from app/scripts/lib/keyring-snaps-permissions.test.ts rename to app/scripts/lib/snap-keyring/keyring-snaps-permissions.test.ts diff --git a/app/scripts/lib/keyring-snaps-permissions.ts b/app/scripts/lib/snap-keyring/keyring-snaps-permissions.ts similarity index 100% rename from app/scripts/lib/keyring-snaps-permissions.ts rename to app/scripts/lib/snap-keyring/keyring-snaps-permissions.ts diff --git a/app/scripts/lib/snap-keyring/snap-keyring.ts b/app/scripts/lib/snap-keyring/snap-keyring.ts index 667353eae2f8..1ce13ef07fe7 100644 --- a/app/scripts/lib/snap-keyring/snap-keyring.ts +++ b/app/scripts/lib/snap-keyring/snap-keyring.ts @@ -140,7 +140,7 @@ export const snapKeyringBuilder = ( }; const learnMoreLink = - 'https://support.metamask.io/hc/en-us/articles/360015289452-How-to-add-accounts-in-your-wallet'; + 'https://support.metamask.io/managing-my-wallet/accounts-and-addresses/how-to-add-accounts-in-your-wallet/'; // If snap is preinstalled and does not request confirmation, skip the confirmation dialog const skipConfirmation = @@ -269,7 +269,7 @@ export const snapKeyringBuilder = ( ); const learnMoreLink = - 'https://support.metamask.io/hc/en-us/articles/360057435092-How-to-remove-an-account-from-your-MetaMask-wallet'; + 'https://support.metamask.io/managing-my-wallet/accounts-and-addresses/how-to-remove-an-account-from-your-metamask-wallet/'; const trackSnapAccountEvent = (event: MetaMetricsEventName) => { trackEvent({ diff --git a/app/scripts/lib/snap-keyring/utils/isBlockedUrl.ts b/app/scripts/lib/snap-keyring/utils/isBlockedUrl.ts index f196faff0b96..50bc4bfa08eb 100644 --- a/app/scripts/lib/snap-keyring/utils/isBlockedUrl.ts +++ b/app/scripts/lib/snap-keyring/utils/isBlockedUrl.ts @@ -1,5 +1,5 @@ import { PhishingController } from '@metamask/phishing-controller'; -import { isProtocolAllowed } from '../../keyring-snaps-permissions'; +import { isProtocolAllowed } from '../keyring-snaps-permissions'; /** * Checks whether a given URL is blocked due to not using HTTPS or being diff --git a/app/scripts/lib/transaction/metrics.test.ts b/app/scripts/lib/transaction/metrics.test.ts index 74d5aaa13ca0..3fed7842378b 100644 --- a/app/scripts/lib/transaction/metrics.test.ts +++ b/app/scripts/lib/transaction/metrics.test.ts @@ -19,12 +19,10 @@ import { MetaMetricsEventCategory, } from '../../../../shared/constants/metametrics'; import { TRANSACTION_ENVELOPE_TYPE_NAMES } from '../../../../shared/lib/transactions-controller-utils'; -///: BEGIN:ONLY_INCLUDE_IF(blockaid) import { BlockaidReason, BlockaidResultType, } from '../../../../shared/constants/security-provider'; -///: END:ONLY_INCLUDE_IF(blockaid) import { handleTransactionAdded, handleTransactionApproved, diff --git a/app/scripts/lib/transaction/metrics.ts b/app/scripts/lib/transaction/metrics.ts index ff8b937c3b4c..15713a72b62e 100644 --- a/app/scripts/lib/transaction/metrics.ts +++ b/app/scripts/lib/transaction/metrics.ts @@ -36,9 +36,7 @@ import { getSwapsTokensReceivedFromTxMeta, TRANSACTION_ENVELOPE_TYPE_NAMES, } from '../../../../shared/lib/transactions-controller-utils'; -///: BEGIN:ONLY_INCLUDE_IF(blockaid) import { getBlockaidMetricsProps } from '../../../../ui/helpers/utils/metrics'; -///: END:ONLY_INCLUDE_IF import { getSmartTransactionMetricsProperties } from '../../../../shared/modules/metametrics'; import { getSnapAndHardwareInfoForMetrics, @@ -968,7 +966,6 @@ async function buildEventFragmentProperties({ ); } - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) // TODO: Replace `any` with type // eslint-disable-next-line @typescript-eslint/no-explicit-any const blockaidProperties: any = getBlockaidMetricsProps(transactionMeta); @@ -976,7 +973,6 @@ async function buildEventFragmentProperties({ if (blockaidProperties?.ui_customizations?.length > 0) { uiCustomizations.push(...blockaidProperties.ui_customizations); } - ///: END:ONLY_INCLUDE_IF if (simulationFails) { uiCustomizations.push(MetaMetricsEventUiCustomization.GasEstimationFailed); @@ -1009,9 +1005,7 @@ async function buildEventFragmentProperties({ token_standard: tokenStandard, transaction_type: transactionType, transaction_speed_up: type === TransactionType.retry, - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) ...blockaidProperties, - ///: END:ONLY_INCLUDE_IF // ui_customizations must come after ...blockaidProperties ui_customizations: uiCustomizations.length > 0 ? uiCustomizations : null, ...smartTransactionMetricsProperties, diff --git a/app/scripts/lib/transaction/smart-transactions.test.ts b/app/scripts/lib/transaction/smart-transactions.test.ts index 507ab83ecdf2..eed52a909f67 100644 --- a/app/scripts/lib/transaction/smart-transactions.test.ts +++ b/app/scripts/lib/transaction/smart-transactions.test.ts @@ -134,6 +134,20 @@ describe('submitSmartTransactionHook', () => { expect(result).toEqual({ transactionHash: undefined }); }); + it('falls back to regular transaction submit if the transaction type is "swapAndSend"', async () => { + const request: SubmitSmartTransactionRequestMocked = createRequest(); + request.transactionMeta.type = TransactionType.swapAndSend; + const result = await submitSmartTransactionHook(request); + expect(result).toEqual({ transactionHash: undefined }); + }); + + it('falls back to regular transaction submit if the transaction type is "swapApproval"', async () => { + const request: SubmitSmartTransactionRequestMocked = createRequest(); + request.transactionMeta.type = TransactionType.swapApproval; + const result = await submitSmartTransactionHook(request); + expect(result).toEqual({ transactionHash: undefined }); + }); + it('falls back to regular transaction submit if /getFees throws an error', async () => { const request: SubmitSmartTransactionRequestMocked = createRequest(); jest diff --git a/app/scripts/lib/transaction/smart-transactions.ts b/app/scripts/lib/transaction/smart-transactions.ts index 38f57f597947..21a95e94494d 100644 --- a/app/scripts/lib/transaction/smart-transactions.ts +++ b/app/scripts/lib/transaction/smart-transactions.ts @@ -10,6 +10,7 @@ import { TransactionController, TransactionMeta, TransactionParams, + TransactionType, } from '@metamask/transaction-controller'; import log from 'loglevel'; import { @@ -120,9 +121,19 @@ class SmartTransactionHook { } async submit() { + const isUnsupportedTransactionTypeForSmartTransaction = this + .#transactionMeta?.type + ? [TransactionType.swapAndSend, TransactionType.swapApproval].includes( + this.#transactionMeta.type, + ) + : false; + // Will cause TransactionController to publish to the RPC provider as normal. const useRegularTransactionSubmit = { transactionHash: undefined }; - if (!this.#isSmartTransaction) { + if ( + !this.#isSmartTransaction || + isUnsupportedTransactionTypeForSmartTransaction + ) { return useRegularTransactionSubmit; } const { id: approvalFlowId } = await this.#controllerMessenger.call( diff --git a/app/scripts/lib/transaction/util.test.ts b/app/scripts/lib/transaction/util.test.ts index 782a27e87cb1..f8b07cc7b17c 100644 --- a/app/scripts/lib/transaction/util.test.ts +++ b/app/scripts/lib/transaction/util.test.ts @@ -95,10 +95,8 @@ describe('Transaction Utils', () => { let dappRequest: AddDappTransactionRequest; let transactionController: jest.Mocked; let userOperationController: jest.Mocked; - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) const validateRequestWithPPOMMock = jest.mocked(validateRequestWithPPOM); const generateSecurityAlertIdMock = jest.mocked(generateSecurityAlertId); - ///: END:ONLY_INCLUDE_IF beforeEach(() => { jest.resetAllMocks(); @@ -106,10 +104,8 @@ describe('Transaction Utils', () => { request = cloneDeep(TRANSACTION_REQUEST_MOCK); transactionController = createTransactionControllerMock(); userOperationController = createUserOperationControllerMock(); - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) // eslint-disable-next-line @typescript-eslint/no-explicit-any request.ppomController = {} as any; - ///: END:ONLY_INCLUDE_IF transactionController.addTransaction.mockResolvedValue({ result: Promise.resolve('testHash'), diff --git a/app/scripts/lib/transaction/util.ts b/app/scripts/lib/transaction/util.ts index adf24fea3ed3..d62ea2af02f3 100644 --- a/app/scripts/lib/transaction/util.ts +++ b/app/scripts/lib/transaction/util.ts @@ -11,7 +11,6 @@ import { } from '@metamask/user-operation-controller'; import type { Hex } from '@metamask/utils'; import { addHexPrefix } from 'ethereumjs-util'; -///: BEGIN:ONLY_INCLUDE_IF(blockaid) import { PPOMController } from '@metamask/ppom-validator'; import { @@ -25,7 +24,6 @@ import { SECURITY_PROVIDER_EXCLUDED_TRANSACTION_TYPES, SECURITY_PROVIDER_SUPPORTED_CHAIN_IDS, } from '../../../../shared/constants/security-provider'; -///: END:ONLY_INCLUDE_IF export type AddTransactionOptions = NonNullable< Parameters[1] @@ -66,10 +64,7 @@ export async function addDappTransaction( ): Promise { const { dappRequest } = request; const { id: actionId, method, origin } = dappRequest; - - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) const { securityAlertResponse } = dappRequest; - ///: END:ONLY_INCLUDE_IF const transactionOptions: AddTransactionOptions = { actionId, @@ -77,9 +72,7 @@ export async function addDappTransaction( origin, // This is the default behaviour but specified here for clarity requireApproval: true, - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) securityAlertResponse, - ///: END:ONLY_INCLUDE_IF }; const { waitForHash } = await addTransactionOrUserOperation({ @@ -223,7 +216,6 @@ function getTransactionByHash( } function validateSecurity(request: AddTransactionRequest) { - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) const { chainId, ppomController, @@ -290,5 +282,4 @@ function validateSecurity(request: AddTransactionRequest) { } catch (error) { handlePPOMError(error, 'Error validating JSON RPC using PPOM: '); } - ///: END:ONLY_INCLUDE_IF } diff --git a/app/scripts/lib/tx-verification/tx-verification-middleware.test.ts b/app/scripts/lib/tx-verification/tx-verification-middleware.test.ts new file mode 100644 index 000000000000..110a2dc3040e --- /dev/null +++ b/app/scripts/lib/tx-verification/tx-verification-middleware.test.ts @@ -0,0 +1,255 @@ +import { NetworkController } from '@metamask/network-controller'; +import { JsonRpcParams, jsonrpc2, Hex } from '@metamask/utils'; +import { + EXPERIENCES_TYPE, + FIRST_PARTY_CONTRACT_NAMES, +} from '../../../../shared/constants/first-party-contracts'; +import { + createTxVerificationMiddleware, + TxParams, +} from './tx-verification-middleware'; + +const getMockNetworkController = (chainId: `0x${string}` = '0x1') => + ({ state: { providerConfig: { chainId } } } as unknown as NetworkController); + +const mockTrustedSigners: Partial> = { + [EXPERIENCES_TYPE.METAMASK_BRIDGE]: + '0xe672B534ccf9876a7554a1dD1685a2a5C2Cc8e8C', +}; + +const jsonRpcTemplate = { jsonrpc: jsonrpc2, id: 1 }; + +const getMiddlewareParams = (method: string, params: JsonRpcParams = []) => { + const req = { ...jsonRpcTemplate, method, params }; + const res = { ...jsonRpcTemplate, result: null }; + const next = jest.fn(); + const end = jest.fn(); + return { req, res, next, end }; +}; + +const getBridgeTxParams = (txParams: Partial = {}): [TxParams] => { + return [ + { + data: '0x1', + from: '0x1', + to: '0x1', + value: '0x1', + ...txParams, + }, + ]; +}; + +describe('tx verification middleware', () => { + it('ignores methods other than eth_sendTransaction', () => { + const middleware = createTxVerificationMiddleware( + getMockNetworkController(), + mockTrustedSigners, + ); + const { req, res, next, end } = getMiddlewareParams('foo'); + middleware(req, res, next, end); + + expect(next).toHaveBeenCalledTimes(1); + expect(end).not.toHaveBeenCalled(); + }); + + // @ts-expect-error Our test types are broken + it.each([ + ['null', null], + ['string', 'foo'], + ['plain object', {}], + ['empty array', []], + ['array with non-object', ['foo']], + ['non-string "data"', [{ data: 1 }]], + ['non-string "from"', [{ data: 'data', from: 1 }]], + ['non-string "to"', [{ data: 'data', from: 'from', to: 1 }]], + [ + 'non-string "value"', + [{ data: 'data', from: 'from', to: 'to', value: 1 }], + ], + [ + 'non-string "chainId"', + [{ data: 'data', from: 'from', to: 'to', value: 'value', chainId: 1 }], + ], + [ + 'non-"0x"-prefixed "chainId"', + [{ data: 'data', from: 'from', to: 'to', value: 'value', chainId: '1' }], + ], + ])( + 'ignores invalid params: %s', + (_: string, invalidParams: JsonRpcParams) => { + const middleware = createTxVerificationMiddleware( + getMockNetworkController(), + mockTrustedSigners, + ); + + const { req, res, next, end } = getMiddlewareParams( + 'eth_sendTransaction', + invalidParams, + ); + middleware(req, res, next, end); + + expect(next).toHaveBeenCalledTimes(1); + expect(end).not.toHaveBeenCalled(); + }, + ); + + // @ts-expect-error Our test types are broken + it.each(Object.keys(FIRST_PARTY_CONTRACT_NAMES['MetaMask Bridge']))( + 'ignores transactions that are not addressed to the bridge contract for chain %s', + (chainId: `0x${string}`) => { + const middleware = createTxVerificationMiddleware( + getMockNetworkController(), + mockTrustedSigners, + ); + + const { req, res, next, end } = getMiddlewareParams( + 'eth_sendTransaction', + getBridgeTxParams({ chainId, to: '0x1' }), + ); + middleware(req, res, next, end); + + expect(next).toHaveBeenCalledTimes(1); + expect(end).not.toHaveBeenCalled(); + }, + ); + + // @ts-expect-error Our test types are broken + it.each(['0x11111', '0x111', '0x222222'])( + 'ignores transactions that do not have a bridge contract deployed for chain %s', + (chainId: `0x${string}`) => { + const middleware = createTxVerificationMiddleware( + getMockNetworkController(), + mockTrustedSigners, + ); + + const { req, res, next, end } = getMiddlewareParams( + 'eth_sendTransaction', + getBridgeTxParams({ chainId, to: '0x1' }), + ); + middleware(req, res, next, end); + + expect(next).toHaveBeenCalledTimes(1); + expect(end).not.toHaveBeenCalled(); + }, + ); + + it('calls next() if reverse address mapping look up is undefined', () => { + const middleware = createTxVerificationMiddleware( + getMockNetworkController(), + mockTrustedSigners, + ); + + const { req, res, next, end } = getMiddlewareParams( + 'eth_sendTransaction', + getBridgeTxParams({ ...getFixtures().mapUndefined }), + ); + middleware(req, res, next, end); + + expect(next).toHaveBeenCalledTimes(1); + expect(end).not.toHaveBeenCalled(); + }); + + it('calls next() if chainId for `to` address does not match', () => { + const middleware = createTxVerificationMiddleware( + getMockNetworkController(), + mockTrustedSigners, + ); + + const { req, res, next, end } = getMiddlewareParams( + 'eth_sendTransaction', + getBridgeTxParams({ ...getFixtures().mapIncorrectChain }), + ); + middleware(req, res, next, end); + + expect(next).toHaveBeenCalledTimes(1); + expect(end).not.toHaveBeenCalled(); + }); + + it('calls next() if experience type for `to` address is not an experience to verify', () => { + const middleware = createTxVerificationMiddleware( + getMockNetworkController(), + mockTrustedSigners, + ); + + const { req, res, next, end } = getMiddlewareParams( + 'eth_sendTransaction', + getBridgeTxParams({ ...getFixtures().mapIncorrectExp }), + ); + middleware(req, res, next, end); + + expect(next).toHaveBeenCalledTimes(1); + expect(end).not.toHaveBeenCalled(); + }); + + it('passes through a valid bridge transaction', () => { + const middleware = createTxVerificationMiddleware( + getMockNetworkController(), + mockTrustedSigners, + ); + + const { req, res, next, end } = getMiddlewareParams( + 'eth_sendTransaction', + getBridgeTxParams({ ...getFixtures().valid }), + ); + middleware(req, res, next, end); + + expect(next).toHaveBeenCalledTimes(1); + expect(end).not.toHaveBeenCalled(); + }); + + it('rejects modified bridge transactions', () => { + const middleware = createTxVerificationMiddleware( + getMockNetworkController(), + mockTrustedSigners, + ); + + const { req, res, next, end } = getMiddlewareParams( + 'eth_sendTransaction', + getBridgeTxParams({ ...getFixtures().invalid }), + ); + middleware(req, res, next, end); + + expect(next).not.toHaveBeenCalled(); + expect(end).toHaveBeenCalledTimes(1); + }); +}); + +/** + * Returns bridge transaction validation fixtures. + * + * @returns The fixtures. + */ +function getFixtures() { + return { + mapIncorrectExp: { + data: '0x3ce33bff0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000470de4df82000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000f736f636b6574416461707465725632000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002800000000000000000000000003a23f943181408eac424116af7b7790c94cb97a50000000000000000000000003a23f943181408eac424116af7b7790c94cb97a5000000000000000000000000000000000000000000000000000000000000a4b10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000466ebb82ac1000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000009f295cd5f000000000000000000000000000e6b738da243e8fa2a0ed5915645789add5de515200000000000000000000000000000000000000000000000000000000000001280000019fd025dec0000000000000000000000000e672b534ccf9876a7554a1dd1685a2a5c2cc8e8c000000000000000000000000b8901acb165ed027e32754e0ffe830802919727f000000000000000000000000710bda329b2a6224e4b44833de30f38e7f81d564000000000000000000000000000000000000000000000000000000000000a4b100000000000000000000000000000000000000000000000000466ebb82ac1000000000000000000000000000000000000000000000000000004614942c423e000000000000000000000000000000000000000000000000000000886c98b760000000000000000000000000000000000000000000000000000000019012a41ba800000000000000000000000000000000000000000000000000000000000000c40000000000000000000000000000000000000000000000005dedaf7e04c3f5c842c30ed9a4a19baceb915cdd3e865f0dad99ffca277743a20bac00e0f366e7265f1fcad502791ff49e9c5c98e1841a090df23ce5555051da1c', + from: '0xe672b534ccf9876a7554a1dd1685a2a5c2cc8e8c', + to: '0xc7bE520a13dC023A1b34C03F4Abdab8A43653F7B', + value: '0x470de4df820000', + }, + mapIncorrectChain: { + data: '0x3ce33bff0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000470de4df82000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000f736f636b6574416461707465725632000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002800000000000000000000000003a23f943181408eac424116af7b7790c94cb97a50000000000000000000000003a23f943181408eac424116af7b7790c94cb97a5000000000000000000000000000000000000000000000000000000000000a4b10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000466ebb82ac1000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000009f295cd5f000000000000000000000000000e6b738da243e8fa2a0ed5915645789add5de515200000000000000000000000000000000000000000000000000000000000001280000019fd025dec0000000000000000000000000e672b534ccf9876a7554a1dd1685a2a5c2cc8e8c000000000000000000000000b8901acb165ed027e32754e0ffe830802919727f000000000000000000000000710bda329b2a6224e4b44833de30f38e7f81d564000000000000000000000000000000000000000000000000000000000000a4b100000000000000000000000000000000000000000000000000466ebb82ac1000000000000000000000000000000000000000000000000000004614942c423e000000000000000000000000000000000000000000000000000000886c98b760000000000000000000000000000000000000000000000000000000019012a41ba800000000000000000000000000000000000000000000000000000000000000c40000000000000000000000000000000000000000000000005dedaf7e04c3f5c842c30ed9a4a19baceb915cdd3e865f0dad99ffca277743a20bac00e0f366e7265f1fcad502791ff49e9c5c98e1841a090df23ce5555051da1c', + from: '0xe672b534ccf9876a7554a1dd1685a2a5c2cc8e8c', + to: `0xaEc23140408534b378bf5832defc426dF8604B59`, + value: '0x470de4df820000', + }, + mapUndefined: { + data: '0x3ce33bff0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000470de4df82000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000f736f636b6574416461707465725632000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002800000000000000000000000003a23f943181408eac424116af7b7790c94cb97a50000000000000000000000003a23f943181408eac424116af7b7790c94cb97a5000000000000000000000000000000000000000000000000000000000000a4b10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000466ebb82ac1000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000009f295cd5f000000000000000000000000000e6b738da243e8fa2a0ed5915645789add5de515200000000000000000000000000000000000000000000000000000000000001280000019fd025dec0000000000000000000000000e672b534ccf9876a7554a1dd1685a2a5c2cc8e8c000000000000000000000000b8901acb165ed027e32754e0ffe830802919727f000000000000000000000000710bda329b2a6224e4b44833de30f38e7f81d564000000000000000000000000000000000000000000000000000000000000a4b100000000000000000000000000000000000000000000000000466ebb82ac1000000000000000000000000000000000000000000000000000004614942c423e000000000000000000000000000000000000000000000000000000886c98b760000000000000000000000000000000000000000000000000000000019012a41ba800000000000000000000000000000000000000000000000000000000000000c40000000000000000000000000000000000000000000000005dedaf7e04c3f5c842c30ed9a4a19baceb915cdd3e865f0dad99ffca277743a20bac00e0f366e7265f1fcad502791ff49e9c5c98e1841a090df23ce5555051da1c', + from: '0xe672b534ccf9876a7554a1dd1685a2a5c2cc8e8c', + to: '0x0439e60F02a8900a951603950d8D4527f400C3f9', + value: '0x470de4df820000', + }, + valid: { + data: '0x3ce33bff0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000470de4df82000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000f736f636b6574416461707465725632000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002800000000000000000000000003a23f943181408eac424116af7b7790c94cb97a50000000000000000000000003a23f943181408eac424116af7b7790c94cb97a5000000000000000000000000000000000000000000000000000000000000a4b10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000466ebb82ac1000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000009f295cd5f000000000000000000000000000e6b738da243e8fa2a0ed5915645789add5de515200000000000000000000000000000000000000000000000000000000000001280000019fd025dec0000000000000000000000000e672b534ccf9876a7554a1dd1685a2a5c2cc8e8c000000000000000000000000b8901acb165ed027e32754e0ffe830802919727f000000000000000000000000710bda329b2a6224e4b44833de30f38e7f81d564000000000000000000000000000000000000000000000000000000000000a4b100000000000000000000000000000000000000000000000000466ebb82ac1000000000000000000000000000000000000000000000000000004614942c423e000000000000000000000000000000000000000000000000000000886c98b760000000000000000000000000000000000000000000000000000000019012a41ba800000000000000000000000000000000000000000000000000000000000000c40000000000000000000000000000000000000000000000005dedaf7e04c3f5c842c30ed9a4a19baceb915cdd3e865f0dad99ffca277743a20bac00e0f366e7265f1fcad502791ff49e9c5c98e1841a090df23ce5555051da1c', + from: '0xe672b534ccf9876a7554a1dd1685a2a5c2cc8e8c', + to: FIRST_PARTY_CONTRACT_NAMES['MetaMask Bridge']['0x1'], + value: '0x470de4df820000', + }, + invalid: { + data: '0x3ce33bff0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000470de4df82000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000d6c6966694164617074657256320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000e397c4883ec89ed4fc9d258f00c689708b2799c9000000000000000000000000e397c4883ec89ed4fc9d258f00c689708b2799c9000000000000000000000000000000000000000000000000000000000000a4b10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000466ebb82ac1000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000009f295cd5f000000000000000000000000000c8c0e780960f954c3426a32b6ab453248d632b59000000000000000000000000000000000000000000000000000000000000006c5a39b10a5d458d62482fa1e7e672b534ccf9876a7554a1dd1685a2a5c2cc8e8c0000a4b10002a9de92aa00576661f103ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd00dfeeddeadbeef8932eb23bad9bddb5cf81426f78279a53c6c3b710000000000000000000000000000000000000000', + from: '0xe672b534ccf9876a7554a1dd1685a2a5c2cc8e8c', + to: FIRST_PARTY_CONTRACT_NAMES['MetaMask Bridge']['0x1'], + value: '0x470de4df820000', + }, + } as const; +} diff --git a/app/scripts/lib/tx-verification/tx-verification-middleware.ts b/app/scripts/lib/tx-verification/tx-verification-middleware.ts new file mode 100644 index 000000000000..30782a98721b --- /dev/null +++ b/app/scripts/lib/tx-verification/tx-verification-middleware.ts @@ -0,0 +1,115 @@ +import { hashMessage } from '@ethersproject/hash'; +import { verifyMessage } from '@ethersproject/wallet'; +import type { NetworkController } from '@metamask/network-controller'; +import { rpcErrors } from '@metamask/rpc-errors'; +import { + Json, + JsonRpcParams, + hasProperty, + isObject, + Hex, +} from '@metamask/utils'; +import { + JsonRpcRequest, + JsonRpcResponse, + JsonRpcEngineEndCallback, + JsonRpcEngineNextCallback, +} from 'json-rpc-engine'; +import { + EXPERIENCES_TO_VERIFY, + getExperience, + TX_SIG_LEN, + TRUSTED_SIGNERS, +} from '../../../../shared/constants/verification'; +import { MESSAGE_TYPE } from '../../../../shared/constants/app'; + +export type TxParams = { + chainId?: `0x${string}`; + data: string; + from: string; + to: string; + value: string; +}; + +/** + * Creates a middleware function that verifies bridge transactions from the + * Portfolio. + * + * @param networkController - The network controller instance. + * @param trustedSigners + * @returns The middleware function. + */ +export function createTxVerificationMiddleware( + networkController: NetworkController, + trustedSigners = TRUSTED_SIGNERS, +) { + return function txVerificationMiddleware( + req: JsonRpcRequest, + _res: JsonRpcResponse, + next: JsonRpcEngineNextCallback, + end: JsonRpcEngineEndCallback, + ) { + if ( + req.method !== MESSAGE_TYPE.ETH_SEND_TRANSACTION || + !Array.isArray(req.params) || + !isValidParams(req.params) + ) { + return next(); + } + + // the tx object is the first element + const params = req.params[0]; + const chainId = + typeof params.chainId === 'string' + ? (params.chainId.toLowerCase() as Hex) + : networkController.state.providerConfig.chainId; + + const experienceType = getExperience( + params.to.toLowerCase() as Hex, + chainId, + ); + // if undefined then no address matched - skip OR if experience is not one we want to verify against - skip + if (!experienceType || !EXPERIENCES_TO_VERIFY.includes(experienceType)) { + return next(); + } + + const signature = `0x${params.data.slice(-TX_SIG_LEN)}`; + const addressToVerify = verifyMessage(hashParams(params), signature); + if (addressToVerify !== trustedSigners[experienceType]) { + return end(rpcErrors.invalidParams('Invalid transaction signature.')); + } + return next(); + }; +} + +function hashParams(params: TxParams): string { + const paramsToVerify = { + to: hashMessage(params.to.toLowerCase()), + from: hashMessage(params.from.toLowerCase()), + data: hashMessage( + params.data.toLowerCase().slice(0, params.data.length - TX_SIG_LEN), + ), + value: hashMessage(params.value.toLowerCase()), + }; + return hashMessage(JSON.stringify(paramsToVerify)); +} + +/** + * Checks if the params of a JSON-RPC request are valid `eth_sendTransaction` + * params. + * + * @param params - The params to validate. + * @returns Whether the params are valid. + */ +function isValidParams(params: Json[]): params is [TxParams] { + return ( + isObject(params[0]) && + typeof params[0].data === 'string' && + typeof params[0].from === 'string' && + typeof params[0].to === 'string' && + typeof params[0].value === 'string' && + (!hasProperty(params[0], 'chainId') || + (typeof params[0].chainId === 'string' && + params[0].chainId.startsWith('0x'))) + ); +} diff --git a/app/scripts/lib/util.ts b/app/scripts/lib/util.ts index 91aa9058b8a0..f44dc48628fa 100644 --- a/app/scripts/lib/util.ts +++ b/app/scripts/lib/util.ts @@ -364,3 +364,21 @@ export function formatTxMetaForRpcResult( return formattedTxMeta; } + +export const isValidAmount = (amount: number | null | undefined): boolean => + amount !== null && amount !== undefined && !Number.isNaN(amount); + +export function formatValue( + value: number | null | undefined, + includeParentheses: boolean, +): string { + if (!isValidAmount(value)) { + return ''; + } + + const numericValue = value as number; + const sign = numericValue >= 0 ? '+' : ''; + const formattedNumber = `${sign}${numericValue.toFixed(2)}%`; + + return includeParentheses ? `(${formattedNumber})` : formattedNumber; +} diff --git a/app/scripts/metamask-controller.actions.test.js b/app/scripts/metamask-controller.actions.test.js index 8d6bc1cfcf5a..29ba6f11515a 100644 --- a/app/scripts/metamask-controller.actions.test.js +++ b/app/scripts/metamask-controller.actions.test.js @@ -1,6 +1,6 @@ -import { strict as assert } from 'assert'; -import sinon from 'sinon'; -import proxyquire from 'proxyquire'; +/** + * @jest-environment node + */ import { ListNames, METAMASK_STALELIST_URL, @@ -13,6 +13,7 @@ import { ApprovalRequestNotFoundError } from '@metamask/approval-controller'; import { PermissionsRequestNotFoundError } from '@metamask/permission-controller'; import nock from 'nock'; import mockEncryptor from '../../test/lib/mock-encryptor'; +import MetaMaskController from './metamask-controller'; const { Ganache } = require('../../test/e2e/seeder/ganache'); @@ -31,13 +32,23 @@ const browserPolyfillMock = { }, storage: { local: { - get: sinon.stub().resolves({}), - set: sinon.stub().resolves(), + get: jest.fn().mockReturnValue({}), + set: jest.fn(), }, }, }; let loggerMiddlewareMock; +const initializeMockMiddlewareLog = () => { + loggerMiddlewareMock = { + requests: [], + responses: [], + }; +}; +const tearDownMockMiddlewareLog = () => { + loggerMiddlewareMock = undefined; +}; + const createLoggerMiddlewareMock = () => (req, res, next) => { if (loggerMiddlewareMock) { loggerMiddlewareMock.requests.push(req); @@ -49,20 +60,16 @@ const createLoggerMiddlewareMock = () => (req, res, next) => { } next(); }; +jest.mock('./lib/createLoggerMiddleware', () => createLoggerMiddlewareMock); const TEST_SEED = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium'; -const MetaMaskController = proxyquire('./metamask-controller', { - './lib/createLoggerMiddleware': { default: createLoggerMiddlewareMock }, -}).default; - describe('MetaMaskController', function () { let metamaskController; - const sandbox = sinon.createSandbox(); const noop = () => undefined; - before(async function () { + beforeAll(async function () { await ganacheServer.start(); }); @@ -106,26 +113,26 @@ describe('MetaMaskController', function () { browser: browserPolyfillMock, infuraProjectId: 'foo', }); + initializeMockMiddlewareLog(); }); afterEach(function () { - sandbox.restore(); + jest.restoreAllMocks(); nock.cleanAll(); + tearDownMockMiddlewareLog(); }); - after(async function () { + afterAll(async function () { await ganacheServer.quit(); }); describe('Phishing Detection Mock', function () { it('should be updated to use v1 of the API', function () { // Update the fixture above if this test fails - assert.equal( - METAMASK_STALELIST_URL, + expect(METAMASK_STALELIST_URL).toStrictEqual( 'https://phishing-detection.api.cx.metamask.io/v1/stalelist', ); - assert.equal( - METAMASK_HOTLIST_DIFF_URL, + expect(METAMASK_HOTLIST_DIFF_URL).toStrictEqual( 'https://phishing-detection.api.cx.metamask.io/v1/diffsSince', ); }); @@ -138,86 +145,74 @@ describe('MetaMaskController', function () { metamaskController.addNewAccount(1), metamaskController.addNewAccount(1), ]); - assert.equal(addNewAccountResult1, addNewAccountResult2); + expect(addNewAccountResult1).toStrictEqual(addNewAccountResult2); }); it('two successive calls with same accountCount give same result', async function () { await metamaskController.createNewVaultAndKeychain('test@123'); const addNewAccountResult1 = await metamaskController.addNewAccount(1); const addNewAccountResult2 = await metamaskController.addNewAccount(1); - assert.equal(addNewAccountResult1, addNewAccountResult2); + expect(addNewAccountResult1).toStrictEqual(addNewAccountResult2); }); it('two successive calls with different accountCount give different results', async function () { await metamaskController.createNewVaultAndKeychain('test@123'); const addNewAccountResult1 = await metamaskController.addNewAccount(1); const addNewAccountResult2 = await metamaskController.addNewAccount(2); - assert.notEqual(addNewAccountResult1, addNewAccountResult2); + expect(addNewAccountResult1).not.toStrictEqual(addNewAccountResult2); }); }); describe('#importAccountWithStrategy', function () { - it('two sequential calls with same strategy give same result', async function () { - let keyringControllerState1; - let keyringControllerState2; + it('throws an error when importing the same account twice', async function () { const importPrivkey = '4cfd3e90fc78b0f86bf7524722150bb8da9c60cd532564d7ff43f5716514f553'; - await metamaskController.createNewVaultAndKeychain('test@123'); - await Promise.all([ + + await metamaskController.importAccountWithStrategy('privateKey', [ + importPrivkey, + ]); + + await expect( metamaskController.importAccountWithStrategy('privateKey', [ importPrivkey, ]), - Promise.resolve(1).then(() => { - keyringControllerState1 = JSON.stringify( - metamaskController.keyringController.state, - ); - metamaskController.importAccountWithStrategy('privateKey', [ - importPrivkey, - ]); - }), - Promise.resolve(2).then(() => { - keyringControllerState2 = JSON.stringify( - metamaskController.keyringController.state, - ); - }), - ]); - assert.deepEqual(keyringControllerState1, keyringControllerState2); + ).rejects.toThrow( + 'KeyringController - The account you are trying to import is a duplicate', + ); }); }); describe('#createNewVaultAndRestore', function () { it('two successive calls with same inputs give same result', async function () { - const result1 = await metamaskController.createNewVaultAndRestore( - 'test@123', - TEST_SEED, - ); - const result2 = await metamaskController.createNewVaultAndRestore( - 'test@123', - TEST_SEED, - ); - assert.deepEqual(result1, result2); + await metamaskController.createNewVaultAndRestore('test@123', TEST_SEED); + const result1 = metamaskController.keyringController.state; + await metamaskController.createNewVaultAndRestore('test@123', TEST_SEED); + const result2 = metamaskController.keyringController.state; + expect(result1).toStrictEqual(result2); }); }); describe('#createNewVaultAndKeychain', function () { it('two successive calls with same inputs give same result', async function () { - const result1 = await metamaskController.createNewVaultAndKeychain( - 'test@123', - ); - const result2 = await metamaskController.createNewVaultAndKeychain( - 'test@123', - ); - assert.notEqual(result1, undefined); - assert.deepEqual(result1, result2); + await metamaskController.createNewVaultAndKeychain('test@123'); + const result1 = metamaskController.keyringController.state; + await metamaskController.createNewVaultAndKeychain('test@123'); + const result2 = metamaskController.keyringController.state; + expect(result1).not.toStrictEqual(undefined); + expect(result1).toStrictEqual(result2); }); }); describe('#setLocked', function () { it('should lock the wallet', async function () { - const { isUnlocked, keyrings } = await metamaskController.setLocked(); - assert(!isUnlocked); - assert.deepEqual(keyrings, []); + await metamaskController.setLocked(); + expect( + metamaskController.keyringController.state.isUnlocked, + ).toStrictEqual(false); + expect(metamaskController.keyringController.state.keyrings).toStrictEqual( + [], + ); }); }); @@ -227,38 +222,26 @@ describe('MetaMaskController', function () { const decimals = 18; it('two parallel calls with same token details give same result', async function () { - const supportsInterfaceStub = sinon - .stub() - .returns(Promise.resolve(false)); - sinon - .stub(metamaskController.tokensController, '_createEthersContract') - .callsFake(() => - Promise.resolve({ supportsInterface: supportsInterfaceStub }), - ); + const supportsInterfaceStub = jest.fn().mockResolvedValue(false); + jest + .spyOn(metamaskController.tokensController, '_createEthersContract') + .mockResolvedValue({ supportsInterface: supportsInterfaceStub }); const [token1, token2] = await Promise.all([ metamaskController.getApi().addToken({ address, symbol, decimals }), metamaskController.getApi().addToken({ address, symbol, decimals }), ]); - assert.deepEqual(token1, token2); + expect(token1).toStrictEqual(token2); }); it('networkClientId is used when provided', async function () { - const supportsInterfaceStub = sinon - .stub() - .returns(Promise.resolve(false)); - sinon - .stub(metamaskController.tokensController, '_createEthersContract') - .callsFake(() => - Promise.resolve({ supportsInterface: supportsInterfaceStub }), - ); - sinon - .stub(metamaskController.controllerMessenger, 'call') - .callsFake(() => ({ - configuration: { - chainId: '0xa', - }, - })); + const supportsInterfaceStub = jest.fn().mockResolvedValue(false); + jest + .spyOn(metamaskController.tokensController, '_createEthersContract') + .mockResolvedValue({ supportsInterface: supportsInterfaceStub }); + const callSpy = jest + .spyOn(metamaskController.controllerMessenger, 'call') + .mockReturnValue({ configuration: { chainId: '0xa' } }); await metamaskController.getApi().addToken({ address, @@ -266,10 +249,10 @@ describe('MetaMaskController', function () { decimals, networkClientId: 'networkClientId1', }); - assert.deepStrictEqual( - metamaskController.controllerMessenger.call.getCall(0).args, - ['NetworkController:getNetworkClientById', 'networkClientId1'], - ); + expect(callSpy.mock.calls[0]).toStrictEqual([ + 'NetworkController:getNetworkClientById', + 'networkClientId1', + ]); }); }); @@ -281,8 +264,9 @@ describe('MetaMaskController', function () { throw error; }, }; - // Line below will not throw error, in case it throws this test case will fail. - metamaskController.removePermissionsFor({ subject: 'test_subject' }); + expect(() => + metamaskController.removePermissionsFor({ subject: 'test_subject' }), + ).not.toThrow(error); }); it('should propagate Error other than PermissionsRequestNotFoundError', function () { @@ -292,9 +276,9 @@ describe('MetaMaskController', function () { throw error; }, }; - assert.throws(() => { - metamaskController.removePermissionsFor({ subject: 'test_subject' }); - }, error); + expect(() => + metamaskController.removePermissionsFor({ subject: 'test_subject' }), + ).toThrow(error); }); }); @@ -306,8 +290,9 @@ describe('MetaMaskController', function () { throw error; }, }; - // Line below will not throw error, in case it throws this test case will fail. - metamaskController.rejectPermissionsRequest('DUMMY_ID'); + expect(() => + metamaskController.rejectPermissionsRequest('DUMMY_ID'), + ).not.toThrow(error); }); it('should propagate Error other than PermissionsRequestNotFoundError', function () { @@ -317,9 +302,9 @@ describe('MetaMaskController', function () { throw error; }, }; - assert.throws(() => { - metamaskController.rejectPermissionsRequest('DUMMY_ID'); - }, error); + expect(() => + metamaskController.rejectPermissionsRequest('DUMMY_ID'), + ).toThrow(error); }); }); @@ -331,8 +316,9 @@ describe('MetaMaskController', function () { throw error; }, }; - // Line below will not throw error, in case it throws this test case will fail. - metamaskController.acceptPermissionsRequest('DUMMY_ID'); + expect(() => + metamaskController.acceptPermissionsRequest('DUMMY_ID'), + ).not.toThrow(error); }); it('should propagate Error other than PermissionsRequestNotFoundError', function () { @@ -342,9 +328,9 @@ describe('MetaMaskController', function () { throw error; }, }; - assert.throws(() => { - metamaskController.acceptPermissionsRequest('DUMMY_ID'); - }, error); + expect(() => + metamaskController.acceptPermissionsRequest('DUMMY_ID'), + ).toThrow(error); }); }); @@ -356,25 +342,21 @@ describe('MetaMaskController', function () { throw error; }, }; - // Line below will not throw error, in case it throws this test case will fail. - await metamaskController.resolvePendingApproval( - 'DUMMY_ID', - 'DUMMY_VALUE', - ); + await expect( + metamaskController.resolvePendingApproval('DUMMY_ID', 'DUMMY_VALUE'), + ).resolves.not.toThrow(error); }); - it('should propagate Error other than ApprovalRequestNotFoundError', function () { + it('should propagate Error other than ApprovalRequestNotFoundError', async function () { const error = new Error(); metamaskController.approvalController = { accept: () => { throw error; }, }; - assert.rejects( - () => - metamaskController.resolvePendingApproval('DUMMY_ID', 'DUMMY_VALUE'), - error, - ); + await expect( + metamaskController.resolvePendingApproval('DUMMY_ID', 'DUMMY_VALUE'), + ).rejects.toThrow(error); }); }); @@ -386,12 +368,13 @@ describe('MetaMaskController', function () { throw error; }, }; - // Line below will not throw error, in case it throws this test case will fail. - metamaskController.rejectPendingApproval('DUMMY_ID', { - code: 1, - message: 'DUMMY_MESSAGE', - data: 'DUMMY_DATA', - }); + expect(() => + metamaskController.rejectPendingApproval('DUMMY_ID', { + code: 1, + message: 'DUMMY_MESSAGE', + data: 'DUMMY_DATA', + }), + ).not.toThrow(error); }); it('should propagate Error other than ApprovalRequestNotFoundError', function () { @@ -401,13 +384,13 @@ describe('MetaMaskController', function () { throw error; }, }; - assert.throws(() => { + expect(() => metamaskController.rejectPendingApproval('DUMMY_ID', { code: 1, message: 'DUMMY_MESSAGE', data: 'DUMMY_DATA', - }); - }, error); + }), + ).toThrow(error); }); }); }); diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index c7abd76ec606..ab5fbdefe179 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -10,20 +10,15 @@ import { TokenRatesController, TokensController, CodefiTokenPricesServiceV2, + RatesController, + fetchMultiExchangeRate, } from '@metamask/assets-controllers'; import { ObservableStore } from '@metamask/obs-store'; import { storeAsStream } from '@metamask/obs-store/dist/asStream'; import { JsonRpcEngine } from 'json-rpc-engine'; import { createEngineStream } from 'json-rpc-middleware-stream'; import { providerAsMiddleware } from '@metamask/eth-json-rpc-middleware'; -import { - debounce, - ///: BEGIN:ONLY_INCLUDE_IF(snaps) - throttle, - memoize, - wrap, - ///: END:ONLY_INCLUDE_IF -} from 'lodash'; +import { debounce, throttle, memoize, wrap } from 'lodash'; import { KeyringController, keyringBuilderFactory, @@ -79,7 +74,6 @@ import { import { LoggingController, LogType } from '@metamask/logging-controller'; import { PermissionLogController } from '@metamask/permission-log-controller'; -///: BEGIN:ONLY_INCLUDE_IF(snaps) import { RateLimitController } from '@metamask/rate-limit-controller'; import { NotificationController } from '@metamask/notification-controller'; import { @@ -95,7 +89,6 @@ import { buildSnapEndowmentSpecifications, buildSnapRestrictedMethodSpecifications, } from '@metamask/snaps-rpc-methods'; -///: END:ONLY_INCLUDE_IF import { AccountsController } from '@metamask/accounts-controller'; @@ -109,10 +102,7 @@ import { CustodyController } from '@metamask-institutional/custody-controller'; import { TransactionUpdateController } from '@metamask-institutional/transaction-update'; ///: END:ONLY_INCLUDE_IF import { SignatureController } from '@metamask/signature-controller'; -///: BEGIN:ONLY_INCLUDE_IF(blockaid) import { PPOMController } from '@metamask/ppom-validator'; -///: END:ONLY_INCLUDE_IF - import { ApprovalType, ERC1155, @@ -149,6 +139,7 @@ import { } from '@metamask/snaps-utils'; ///: END:ONLY_INCLUDE_IF +import { isEvmAccountType } from '@metamask/keyring-api'; import { methodsRequiringNetworkSwitch, methodsWithConfirmation, @@ -159,7 +150,11 @@ import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils'; ///: END:ONLY_INCLUDE_IF import { AssetType, TokenStandard } from '../../shared/constants/transaction'; -import { SWAPS_CLIENT_ID } from '../../shared/constants/swaps'; +import { + GAS_API_BASE_URL, + GAS_DEV_API_BASE_URL, + SWAPS_CLIENT_ID, +} from '../../shared/constants/swaps'; import { CHAIN_IDS, NETWORK_TYPES, @@ -176,19 +171,15 @@ import { KeyringType } from '../../shared/constants/keyring'; import { CaveatTypes, RestrictedMethods, - ///: BEGIN:ONLY_INCLUDE_IF(snaps) EndowmentPermissions, ExcludedSnapPermissions, ExcludedSnapEndowments, - ///: END:ONLY_INCLUDE_IF } from '../../shared/constants/permissions'; import { UI_NOTIFICATIONS } from '../../shared/notifications'; import { MILLISECOND, SECOND } from '../../shared/constants/time'; import { ORIGIN_METAMASK, - ///: BEGIN:ONLY_INCLUDE_IF(snaps) SNAP_DIALOG_TYPES, - ///: END:ONLY_INCLUDE_IF POLLING_TOKEN_ENVIRONMENT_TYPES, } from '../../shared/constants/app'; import { @@ -214,6 +205,8 @@ import { getSmartTransactionsOptInStatus, getCurrentChainSupportsSmartTransactions, } from '../../shared/modules/selectors'; +import { BaseUrl } from '../../shared/constants/urls'; +import { BalancesController as MultichainBalancesController } from './lib/accounts/BalancesController'; import { ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) handleMMITransactionUpdate, @@ -239,17 +232,14 @@ import { ///: END:ONLY_INCLUDE_IF import { submitSmartTransactionHook } from './lib/transaction/smart-transactions'; ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) -import { keyringSnapPermissionsBuilder } from './lib/keyring-snaps-permissions'; +import { keyringSnapPermissionsBuilder } from './lib/snap-keyring/keyring-snaps-permissions'; ///: END:ONLY_INCLUDE_IF import { SnapsNameProvider } from './lib/SnapsNameProvider'; import { AddressBookPetnamesBridge } from './lib/AddressBookPetnamesBridge'; import { AccountIdentitiesPetnamesBridge } from './lib/AccountIdentitiesPetnamesBridge'; - -///: BEGIN:ONLY_INCLUDE_IF(blockaid) import { createPPOMMiddleware } from './lib/ppom/ppom-middleware'; import * as PPOMModule from './lib/ppom/ppom'; -///: END:ONLY_INCLUDE_IF import { onMessageReceived, checkForMultipleVersionsRunning, @@ -302,9 +292,7 @@ import { unrestrictedMethods, } from './controllers/permissions'; import createRPCMethodTrackingMiddleware from './lib/createRPCMethodTrackingMiddleware'; -///: BEGIN:ONLY_INCLUDE_IF(blockaid) import { IndexedDBPPOMStorage } from './lib/ppom/indexed-db-backend'; -///: END:ONLY_INCLUDE_IF import { updateCurrentLocale } from './translate'; import { TrezorOffscreenBridge } from './lib/offscreen-bridge/trezor-offscreen-bridge'; import { LedgerOffscreenBridge } from './lib/offscreen-bridge/ledger-offscreen-bridge'; @@ -314,9 +302,7 @@ import { snapKeyringBuilder, getAccountsBySnapId } from './lib/snap-keyring'; import { encryptorFactory } from './lib/encryptor-factory'; import { addDappTransaction, addTransaction } from './lib/transaction/util'; import { LatticeKeyringOffscreen } from './lib/offscreen-bridge/lattice-offscreen-keyring'; -///: BEGIN:ONLY_INCLUDE_IF(snaps) import PREINSTALLED_SNAPS from './snaps/preinstalled-snaps'; -///: END:ONLY_INCLUDE_IF import { WeakRefObjectMap } from './lib/WeakRefObjectMap'; // Notification controllers @@ -324,7 +310,10 @@ import AuthenticationController from './controllers/authentication/authenticatio import UserStorageController from './controllers/user-storage/user-storage-controller'; import { PushPlatformNotificationsController } from './controllers/push-platform-notifications/push-platform-notifications'; import { MetamaskNotificationsController } from './controllers/metamask-notifications/metamask-notifications'; +import { createTxVerificationMiddleware } from './lib/tx-verification/tx-verification-middleware'; import { updateSecurityAlertResponse } from './lib/ppom/ppom-util'; +import createEvmMethodsToNonEvmAccountReqFilterMiddleware from './lib/createEvmMethodsToNonEvmAccountReqFilterMiddleware'; +import { isEthAddress } from './lib/multichain/address'; export const METAMASK_CONTROLLER_EVENTS = { // Fired after state changes that impact the extension badge (unapproved msg count) @@ -333,6 +322,11 @@ export const METAMASK_CONTROLLER_EVENTS = { // TODO: Add this and similar enums to the `controllers` repo and export them APPROVAL_STATE_CHANGE: 'ApprovalController:stateChange', QUEUED_REQUEST_STATE_CHANGE: 'QueuedRequestController:stateChange', + METAMASK_NOTIFICATIONS_LIST_UPDATED: + 'MetamaskNotificationsController:notificationsListUpdated', + METAMASK_NOTIFICATIONS_MARK_AS_READ: + 'MetamaskNotificationsController:markNotificationsAsRead', + NOTIFICATIONS_STATE_CHANGE: 'NotificationController:stateChange', }; // stream channels @@ -366,6 +360,8 @@ export default class MetamaskController extends EventEmitter { // the only thing that uses controller connections are open metamask UI instances this.activeControllerConnections = 0; + this.offscreenPromise = opts.offscreenPromise ?? Promise.resolve(); + this.getRequestAccountTabIds = opts.getRequestAccountTabIds; this.getOpenMetamaskTabsIds = opts.getOpenMetamaskTabsIds; @@ -438,8 +434,10 @@ export default class MetamaskController extends EventEmitter { ], allowedEvents: ['SelectedNetworkController:stateChange'], }), - methodsRequiringNetworkSwitch, + shouldRequestSwitchNetwork: ({ method }) => + methodsRequiringNetworkSwitch.includes(method), clearPendingConfirmations, + showApprovalRequest: opts.showUserConfirmation, }); this.approvalController = new ApprovalController({ @@ -733,7 +731,7 @@ export default class MetamaskController extends EventEmitter { disabled: this.preferencesController.store.getState().useNftDetection === undefined - ? true + ? false // the detection is enabled by default : !this.preferencesController.store.getState().useNftDetection, selectedAddress: this.preferencesController.store.getState().selectedAddress, @@ -776,6 +774,10 @@ export default class MetamaskController extends EventEmitter { allowedEvents: ['NetworkController:stateChange'], }); + const gasApiBaseUrl = process.env.SWAPS_USE_DEV_APIS + ? GAS_DEV_API_BASE_URL + : GAS_API_BASE_URL; + this.gasFeeController = new GasFeeController({ state: initState.GasFeeController, interval: 10000, @@ -795,12 +797,13 @@ export default class MetamaskController extends EventEmitter { ), getCurrentAccountEIP1559Compatibility: this.getCurrentAccountEIP1559Compatibility.bind(this), + legacyAPIEndpoint: `${gasApiBaseUrl}/networks//gasPrices`, + EIP1559APIEndpoint: `${gasApiBaseUrl}/networks//suggestedGasFees`, getCurrentNetworkLegacyGasAPICompatibility: () => { const { chainId } = this.networkController.state.providerConfig; return chainId === CHAIN_IDS.BSC; }, getChainId: () => this.networkController.state.providerConfig.chainId, - infuraAPIKey: opts.infuraProjectId, }); this.appStateController = new AppStateController({ @@ -854,9 +857,6 @@ export default class MetamaskController extends EventEmitter { stalelistRefreshInterval: process.env.IN_TEST ? 30 * SECOND : undefined, }); - this.phishingController.maybeUpdateState(); - - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) this.ppomController = new PPOMController({ messenger: this.controllerMessenger.getRestricted({ name: 'PPOMController', @@ -875,7 +875,6 @@ export default class MetamaskController extends EventEmitter { cdnBaseUrl: process.env.BLOCKAID_FILE_CDN, blockaidPublicKey: process.env.BLOCKAID_PUBLIC_KEY, }); - ///: END:ONLY_INCLUDE_IF const announcementMessenger = this.controllerMessenger.getRestricted({ name: 'AnnouncementController', @@ -904,6 +903,34 @@ export default class MetamaskController extends EventEmitter { state: initState.AccountOrderController, }); + const multichainBalancesControllerMessenger = + this.controllerMessenger.getRestricted({ + name: 'BalancesController', + allowedEvents: [], + allowedActions: ['SnapController:handleRequest'], + }); + + this.multichainBalancesController = new MultichainBalancesController({ + messenger: multichainBalancesControllerMessenger, + state: {}, + // TODO: remove when listMultichainAccounts action is available + listMultichainAccounts: + this.accountsController.listMultichainAccounts.bind( + this.accountsController, + ), + }); + + const multichainRatesControllerMessenger = + this.controllerMessenger.getRestricted({ + name: 'RatesController', + }); + this.multichainRatesController = new RatesController({ + state: initState.MultichainRatesController, + messenger: multichainRatesControllerMessenger, + includeUsdRate: true, + fetchMultiExchangeRate, + }); + // token exchange rate tracker this.tokenRatesController = new TokenRatesController( { @@ -1173,9 +1200,7 @@ export default class MetamaskController extends EventEmitter { ); }, }), - ///: BEGIN:ONLY_INCLUDE_IF(snaps) ...this.getSnapPermissionSpecifications(), - ///: END:ONLY_INCLUDE_IF }, unrestrictedMethods, }); @@ -1220,7 +1245,6 @@ export default class MetamaskController extends EventEmitter { subjectCacheLimit: 100, }); - ///: BEGIN:ONLY_INCLUDE_IF(snaps) const shouldUseOffscreenExecutionService = isManifestV3 && typeof chrome !== 'undefined' && @@ -1241,9 +1265,8 @@ export default class MetamaskController extends EventEmitter { iframeUrl: new URL(process.env.IFRAME_EXECUTION_ENVIRONMENT_URL), }) : new OffscreenExecutionService({ - // eslint-disable-next-line no-undef - documentUrl: chrome.runtime.getURL('./offscreen.html'), ...snapExecutionServiceArgs, + offscreenPromise: this.offscreenPromise, }); const snapControllerMessenger = this.controllerMessenger.getRestricted({ @@ -1391,7 +1414,6 @@ export default class MetamaskController extends EventEmitter { state: initState.SnapsRegistry, messenger: snapsRegistryMessenger, refetchOnAllowlistMiss: requireAllowlist, - failOnUnavailableRegistry: requireAllowlist, url: { registry: 'https://acl.execution.metamask.io/latest/registry.json', signature: 'https://acl.execution.metamask.io/latest/signature.json', @@ -1414,8 +1436,6 @@ export default class MetamaskController extends EventEmitter { messenger: snapInterfaceControllerMessenger, }); - ///: END:ONLY_INCLUDE_IF - // Notification Controllers this.authenticationController = new AuthenticationController({ state: initState.AuthenticationController, @@ -1525,7 +1545,7 @@ export default class MetamaskController extends EventEmitter { onboardingController: this.onboardingController, controllerMessenger: this.controllerMessenger.getRestricted({ name: 'AccountTracker', - allowedEvents: ['AccountsController:selectedAccountChange'], + allowedEvents: ['AccountsController:selectedEvmAccountChange'], allowedActions: ['AccountsController:getSelectedAccount'], }), initState: { accounts: {} }, @@ -2117,6 +2137,7 @@ export default class MetamaskController extends EventEmitter { this.encryptionPublicKeyController.newRequestEncryptionPublicKey.bind( this.encryptionPublicKeyController, ), + processDecryptMessage: this.decryptMessageController.newRequestDecryptMessage.bind( this.decryptMessageController, @@ -2145,15 +2166,14 @@ export default class MetamaskController extends EventEmitter { SwapsController: this.swapsController.store, EnsController: this.ensController, ApprovalController: this.approvalController, - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) PPOMController: this.ppomController, - ///: END:ONLY_INCLUDE_IF }; this.store.updateStructure({ AccountsController: this.accountsController, AppStateController: this.appStateController.store, AppMetadataController: this.appMetadataController.store, + MultichainBalancesController: this.multichainBalancesController, TransactionController: this.txController, KeyringController: this.keyringController, PreferencesController: this.preferencesController.store, @@ -2177,23 +2197,19 @@ export default class MetamaskController extends EventEmitter { PhishingController: this.phishingController, SelectedNetworkController: this.selectedNetworkController, LoggingController: this.loggingController, - ///: BEGIN:ONLY_INCLUDE_IF(snaps) + MultichainRatesController: this.multichainRatesController, SnapController: this.snapController, CronjobController: this.cronjobController, SnapsRegistry: this.snapsRegistry, NotificationController: this.notificationController, SnapInterfaceController: this.snapInterfaceController, - ///: END:ONLY_INCLUDE_IF - ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) CustodyController: this.custodyController.store, InstitutionalFeaturesController: this.institutionalFeaturesController.store, MmiConfigurationController: this.mmiConfigurationController.store, ///: END:ONLY_INCLUDE_IF - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) PPOMController: this.ppomController, - ///: END:ONLY_INCLUDE_IF NameController: this.nameController, UserOperationController: this.userOperationController, // Notification Controllers @@ -2210,6 +2226,7 @@ export default class MetamaskController extends EventEmitter { AccountsController: this.accountsController, AppStateController: this.appStateController.store, AppMetadataController: this.appMetadataController.store, + MultichainBalancesController: this.multichainBalancesController, NetworkController: this.networkController, KeyringController: this.keyringController, PreferencesController: this.preferencesController.store, @@ -2232,13 +2249,12 @@ export default class MetamaskController extends EventEmitter { SelectedNetworkController: this.selectedNetworkController, LoggingController: this.loggingController, TxController: this.txController, - ///: BEGIN:ONLY_INCLUDE_IF(snaps) + MultichainRatesController: this.multichainRatesController, SnapController: this.snapController, CronjobController: this.cronjobController, SnapsRegistry: this.snapsRegistry, NotificationController: this.notificationController, SnapInterfaceController: this.snapInterfaceController, - ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) CustodyController: this.custodyController.store, InstitutionalFeaturesController: @@ -2251,6 +2267,7 @@ export default class MetamaskController extends EventEmitter { AuthenticationController: this.authenticationController, UserStorageController: this.userStorageController, MetamaskNotificationsController: this.metamaskNotificationsController, + QueuedRequestController: this.queuedRequestController, PushPlatformNotificationsController: this.pushPlatformNotificationsController, ...resetOnRestartStore, @@ -2311,6 +2328,7 @@ export default class MetamaskController extends EventEmitter { }); this.setupControllerEventSubscriptions(); + this.setupMultichainDataAndSubscriptions(); // For more information about these legacy streams, see here: // https://github.com/MetaMask/metamask-extension/issues/15491 @@ -2328,7 +2346,13 @@ export default class MetamaskController extends EventEmitter { } postOnboardingInitialization() { + const { usePhishDetect } = this.preferencesController.store.getState(); + this.networkController.lookupNetwork(); + + if (usePhishDetect) { + this.phishingController.maybeUpdateState(); + } } triggerNetworkrequests() { @@ -2339,12 +2363,7 @@ export default class MetamaskController extends EventEmitter { const preferencesControllerState = this.preferencesController.store.getState(); - const { useCurrencyRateCheck, useNftDetection } = - preferencesControllerState; - - if (useNftDetection) { - this.nftDetectionController.start(); - } + const { useCurrencyRateCheck } = preferencesControllerState; if (useCurrencyRateCheck) { this.tokenRatesController.start(); @@ -2359,7 +2378,6 @@ export default class MetamaskController extends EventEmitter { this.accountTracker.stop(); this.txController.stopIncomingTransactionPolling(); this.tokenDetectionController.disable(); - this.nftDetectionController.stop(); const preferencesControllerState = this.preferencesController.store.getState(); @@ -2404,7 +2422,6 @@ export default class MetamaskController extends EventEmitter { } ///: END:ONLY_INCLUDE_IF - ///: BEGIN:ONLY_INCLUDE_IF(snaps) trackInsightSnapView(snapId) { this.metaMetricsController.trackEvent({ event: MetaMetricsEventName.InsightSnapViewed, @@ -2414,9 +2431,6 @@ export default class MetamaskController extends EventEmitter { }, }); } - ///: END:ONLY_INCLUDE_IF - - ///: BEGIN:ONLY_INCLUDE_IF(snaps) /** * Get snap metadata from the current state without refreshing the registry database. @@ -2581,11 +2595,9 @@ export default class MetamaskController extends EventEmitter { this.controllerMessenger, 'SnapInterfaceController:getInterface', ), - ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) getSnapKeyring: this.getSnapKeyring.bind(this), ///: END:ONLY_INCLUDE_IF - ///: BEGIN:ONLY_INCLUDE_IF(snaps) }, ), }; @@ -2609,8 +2621,6 @@ export default class MetamaskController extends EventEmitter { this.notificationController.markRead(ids); } - ///: END:ONLY_INCLUDE_IF - /** * Sets up BaseController V2 event subscriptions. Currently, this includes * the subscriptions necessary to notify permission subjects of account @@ -2663,8 +2673,6 @@ export default class MetamaskController extends EventEmitter { }, ); - ///: BEGIN:ONLY_INCLUDE_IF(snaps) - this.controllerMessenger.subscribe( `${this.snapController.name}:snapInstallStarted`, (snapId, origin, isUpdate) => { @@ -2790,8 +2798,35 @@ export default class MetamaskController extends EventEmitter { }); }, ); + } - ///: END:ONLY_INCLUDE_IF + /** + * Sets up multichain data and subscriptions. + * This method is called during the MetaMaskController constructor. + * It starts the MultichainRatesController if selected account is non-EVM + * and subscribes to account changes. + */ + setupMultichainDataAndSubscriptions() { + if ( + !isEvmAccountType( + this.accountsController.getSelectedMultichainAccount().type, + ) + ) { + this.multichainRatesController.start(); + } + + this.controllerMessenger.subscribe( + 'AccountsController:selectedAccountChange', + (selectedAccount) => { + if (isEvmAccountType(selectedAccount.type)) { + this.multichainRatesController.stop(); + return; + } + this.multichainRatesController.start(); + }, + ); + this.multichainBalancesController.start(); + this.multichainBalancesController.updateBalances(); } /** @@ -2910,7 +2945,6 @@ export default class MetamaskController extends EventEmitter { return { isInitialized, ...flatState, - ///: BEGIN:ONLY_INCLUDE_IF(snaps) // Snap state, source code and other files are stripped out to prevent piping to the MetaMask UI. snapStates: {}, unencryptedSnapStates: {}, @@ -2920,7 +2954,6 @@ export default class MetamaskController extends EventEmitter { acc[snap.id] = rest; return acc; }, {}), - ///: END:ONLY_INCLUDE_IF }; } @@ -3013,13 +3046,10 @@ export default class MetamaskController extends EventEmitter { this.preferencesController, ), getProviderConfig: () => this.networkController.state.providerConfig, - - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) setSecurityAlertsEnabled: preferencesController.setSecurityAlertsEnabled.bind( preferencesController, ), - ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) setAddSnapAccountEnabled: preferencesController.setAddSnapAccountEnabled.bind( @@ -3270,12 +3300,10 @@ export default class MetamaskController extends EventEmitter { appStateController.setNewPrivacyPolicyToastShownDate.bind( appStateController, ), - ///: BEGIN:ONLY_INCLUDE_IF(snaps) setSnapsInstallPrivacyWarningShownStatus: appStateController.setSnapsInstallPrivacyWarningShownStatus.bind( appStateController, ), - ///: END:ONLY_INCLUDE_IF setOutdatedBrowserWarningLastShown: appStateController.setOutdatedBrowserWarningLastShown.bind( appStateController, @@ -3471,7 +3499,6 @@ export default class MetamaskController extends EventEmitter { appStateController.setCustodianDeepLink.bind(appStateController), ///: END:ONLY_INCLUDE_IF - ///: BEGIN:ONLY_INCLUDE_IF(snaps) // snaps disableSnap: this.controllerMessenger.call.bind( this.controllerMessenger, @@ -3522,7 +3549,6 @@ export default class MetamaskController extends EventEmitter { this.controllerMessenger, 'SnapInterfaceController:updateInterfaceState', ), - ///: END:ONLY_INCLUDE_IF // swaps fetchAndSetQuotes: @@ -3607,9 +3633,8 @@ export default class MetamaskController extends EventEmitter { finalizeEventFragment: metaMetricsController.finalizeEventFragment.bind( metaMetricsController, ), - ///: BEGIN:ONLY_INCLUDE_IF(snaps) trackInsightSnapView: this.trackInsightSnapView.bind(this), - ///: END:ONLY_INCLUDE_IF + // approval controller resolvePendingApproval: this.resolvePendingApproval, rejectPendingApproval: this.rejectPendingApproval, @@ -3913,12 +3938,10 @@ export default class MetamaskController extends EventEmitter { // clear permissions this.permissionController.clearState(); - ///: BEGIN:ONLY_INCLUDE_IF(snaps) // Clear snap state this.snapController.clearState(); // Clear notification state this.notificationController.clear(); - ///: END:ONLY_INCLUDE_IF // clear accounts in accountTracker this.accountTracker.clearAccounts(); @@ -3930,7 +3953,7 @@ export default class MetamaskController extends EventEmitter { } // create new vault - const vault = await this.keyringController.createNewVaultAndRestore( + await this.keyringController.createNewVaultAndRestore( password, this._convertMnemonicToWordlistIndices(seedPhraseAsBuffer), ); @@ -3944,8 +3967,6 @@ export default class MetamaskController extends EventEmitter { // Ledger Keyring GitHub downtime this.setLedgerTransportPreference(); } - - return vault; } finally { releaseLock(); } @@ -3985,8 +4006,7 @@ export default class MetamaskController extends EventEmitter { } // This account has assets, so check the next one - ({ addedAccountAddress: address } = - await this.keyringController.addNewAccount(count)); + address = await this.keyringController.addNewAccount(count); } } @@ -4052,6 +4072,10 @@ export default class MetamaskController extends EventEmitter { */ async submitPassword(password) { const { completedOnboarding } = this.onboardingController.store.getState(); + + // Before attempting to unlock the keyrings, we need the offscreen to have loaded. + await this.offscreenPromise; + await this.keyringController.submitPassword(password); ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) @@ -4251,6 +4275,7 @@ export default class MetamaskController extends EventEmitter { // Merge with existing accounts // and make sure addresses are not repeated const oldAccounts = await this.keyringController.getAccounts(); + const accountsToTrack = [ ...new Set( oldAccounts.concat(accounts.map((a) => a.address.toLowerCase())), @@ -4371,36 +4396,28 @@ export default class MetamaskController extends EventEmitter { const keyring = await this.getKeyringForDevice(deviceName, hdPath); keyring.setAccountToUnlock(index); - const oldAccounts = await this.keyringController.getAccounts(); - const keyState = await this.keyringController.addNewAccountForKeyring( - keyring, + const unlockedAccount = + await this.keyringController.addNewAccountForKeyring(keyring); + const label = this.getAccountLabel( + deviceName === HardwareDeviceNames.qr ? keyring.getName() : deviceName, + index, + hdPathDescription, ); - const newAccounts = await this.keyringController.getAccounts(); - newAccounts.forEach((address) => { - if (!oldAccounts.includes(address)) { - const label = this.getAccountLabel( - deviceName === HardwareDeviceNames.qr - ? keyring.getName() - : deviceName, - index, - hdPathDescription, - ); - // Set the account label to Trezor 1 / Ledger 1 / QR Hardware 1, etc - this.preferencesController.setAccountLabel(address, label); - // Select the account - this.preferencesController.setSelectedAddress(address); + // Set the account label to Trezor 1 / Ledger 1 / QR Hardware 1, etc + this.preferencesController.setAccountLabel(unlockedAccount, label); + // Select the account + this.preferencesController.setSelectedAddress(unlockedAccount); - // It is expected that the account also exist in the accounts-controller - // in other case, an error shall be thrown - const account = this.accountsController.getAccountByAddress(address); - this.accountsController.setAccountName(account.id, label); - } - }); + // It is expected that the account also exist in the accounts-controller + // in other case, an error shall be thrown + const account = + this.accountsController.getAccountByAddress(unlockedAccount); + this.accountsController.setAccountName(account.id, label); const accounts = this.accountsController.listAccounts(); const { identities } = this.preferencesController.store.getState(); - return { ...keyState, identities, accounts }; + return { unlockedAccount, identities, accounts }; } // @@ -4416,7 +4433,7 @@ export default class MetamaskController extends EventEmitter { async addNewAccount(accountCount) { const oldAccounts = await this.keyringController.getAccounts(); - const { addedAccountAddress } = await this.keyringController.addNewAccount( + const addedAccountAddress = await this.keyringController.addNewAccount( accountCount, ); @@ -4588,7 +4605,7 @@ export default class MetamaskController extends EventEmitter { * @param {any} args - The data required by that strategy to import an account. */ async importAccountWithStrategy(strategy, args) { - const { importedAccountAddress } = + const importedAccountAddress = await this.keyringController.importAccountWithStrategy(strategy, args); // set new account as selected this.preferencesController.setSelectedAddress(importedAccountAddress); @@ -4614,13 +4631,11 @@ export default class MetamaskController extends EventEmitter { transactionOptions, transactionParams, userOperationController: this.userOperationController, - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) chainId: this.networkController.state.providerConfig.chainId, ppomController: this.ppomController, securityAlertsEnabled: this.preferencesController.store.getState()?.securityAlertsEnabled, updateSecurityAlertResponse: this.updateSecurityAlertResponse.bind(this), - ///: END:ONLY_INCLUDE_IF }; } @@ -4962,13 +4977,9 @@ export default class MetamaskController extends EventEmitter { let origin; if (subjectType === SubjectType.Internal) { origin = ORIGIN_METAMASK; - } - ///: BEGIN:ONLY_INCLUDE_IF(snaps) - else if (subjectType === SubjectType.Snap) { + } else if (subjectType === SubjectType.Snap) { origin = sender.snapId; - } - ///: END:ONLY_INCLUDE_IF - else { + } else { origin = new URL(sender.url).origin; } @@ -5024,7 +5035,6 @@ export default class MetamaskController extends EventEmitter { } } - ///: BEGIN:ONLY_INCLUDE_IF(snaps) /** * For snaps running in workers. * @@ -5038,7 +5048,6 @@ export default class MetamaskController extends EventEmitter { subjectType: SubjectType.Snap, }); } - ///: END:ONLY_INCLUDE_IF /** * A method for creating a provider that is safely restricted for the requesting subject. @@ -5066,7 +5075,18 @@ export default class MetamaskController extends EventEmitter { useRequestQueue: this.preferencesController.getUseRequestQueue.bind( this.preferencesController, ), - methodsWithConfirmation, + shouldEnqueueRequest: (request) => { + if ( + request.method === 'eth_requestAccounts' && + this.permissionController.hasPermission( + request.origin, + PermissionNames.eth_accounts, + ) + ) { + return false; + } + return methodsWithConfirmation.includes(request.method); + }, }); engine.push(requestQueueMiddleware); @@ -5094,7 +5114,10 @@ export default class MetamaskController extends EventEmitter { engine.push(createLoggerMiddleware({ origin })); engine.push(this.permissionLogController.createMiddleware()); - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) + if (origin === BaseUrl.Portfolio) { + engine.push(createTxVerificationMiddleware(this.networkController)); + } + engine.push( createPPOMMiddleware( this.ppomController, @@ -5104,7 +5127,6 @@ export default class MetamaskController extends EventEmitter { this.updateSecurityAlertResponse.bind(this), ), ); - ///: END:ONLY_INCLUDE_IF const isConfirmationRedesignEnabled = () => { return this.preferencesController.store.getState().preferences @@ -5130,9 +5152,7 @@ export default class MetamaskController extends EventEmitter { 'AccountsController:getSelectedAccount', ], }), - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) appStateController: this.appStateController, - ///: END:ONLY_INCLUDE_IF }), ); @@ -5163,6 +5183,17 @@ export default class MetamaskController extends EventEmitter { ); } + // EVM requests and eth permissions should not be passed to non-EVM accounts + // this middleware intercepts these requests and returns an error. + engine.push( + createEvmMethodsToNonEvmAccountReqFilterMiddleware({ + messenger: this.controllerMessenger.getRestricted({ + name: 'EvmMethodsToNonEvmAccountFilterMessenger', + allowedActions: ['AccountsController:getSelectedAccount'], + }), + }), + ); + // Unrestricted/permissionless RPC method implementations. // They must nevertheless be placed _behind_ the permission middleware. engine.push( @@ -5328,7 +5359,6 @@ export default class MetamaskController extends EventEmitter { }), ); - ///: BEGIN:ONLY_INCLUDE_IF(snaps) engine.push( createSnapsMethodMiddleware(subjectType === SubjectType.Snap, { getUnlockPromise: this.appStateController.getUnlockPromise.bind( @@ -5366,7 +5396,6 @@ export default class MetamaskController extends EventEmitter { getIsLocked: () => { return !this.appStateController.isUnlocked(); }, - ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) hasPermission: this.permissionController.hasPermission.bind( this.permissionController, @@ -5403,10 +5432,8 @@ export default class MetamaskController extends EventEmitter { origin, ), ///: END:ONLY_INCLUDE_IF - ///: BEGIN:ONLY_INCLUDE_IF(snaps) }), ); - ///: END:ONLY_INCLUDE_IF engine.push(filterMiddleware); engine.push(subscriptionManager.middleware); @@ -5591,10 +5618,12 @@ export default class MetamaskController extends EventEmitter { */ async _onKeyringControllerUpdate(state) { const { keyrings } = state; - const addresses = keyrings.reduce( - (acc, { accounts }) => acc.concat(accounts), - [], - ); + + // The accounts tracker only supports EVM addresses and the keyring + // controller may pass non-EVM addresses, so we filter them out + const addresses = keyrings + .reduce((acc, { accounts }) => acc.concat(accounts), []) + .filter(isEthAddress); if (!addresses.length) { return; @@ -6011,7 +6040,6 @@ export default class MetamaskController extends EventEmitter { } }; - ///: BEGIN:ONLY_INCLUDE_IF(snaps) updateCaveat = (origin, target, caveatType, caveatValue) => { try { this.controllerMessenger.call( @@ -6027,7 +6055,6 @@ export default class MetamaskController extends EventEmitter { } } }; - ///: END:ONLY_INCLUDE_IF updateNetworksList = (sortedNetworkList) => { try { @@ -6214,7 +6241,6 @@ export default class MetamaskController extends EventEmitter { const { data, to: contractAddress, from: userAddress } = txParams; const transactionData = parseStandardTokenTransactionData(data); - // Sometimes the tokenId value is parsed as "_value" param. Not seeing this often any more, but still occasionally: // i.e. call approve() on BAYC contract - https://etherscan.io/token/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d#writeContract, and tokenId shows up as _value, // not sure why since it doesn't match the ERC721 ABI spec we use to parse these transactions - https://github.com/MetaMask/metamask-eth-abis/blob/d0474308a288f9252597b7c93a3a8deaad19e1b2/src/abis/abiERC721.ts#L62. diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js index 4c66e3bf8fac..06434643f93f 100644 --- a/app/scripts/metamask-controller.test.js +++ b/app/scripts/metamask-controller.test.js @@ -14,12 +14,21 @@ import { METAMASK_STALELIST_FILE, METAMASK_HOTLIST_DIFF_FILE, } from '@metamask/phishing-controller'; -import { EthAccountType } from '@metamask/keyring-api'; +import { + BtcAccountType, + BtcMethod, + EthAccountType, +} from '@metamask/keyring-api'; import { NetworkType } from '@metamask/controller-utils'; import { ControllerMessenger } from '@metamask/base-controller'; import { LoggingController, LogType } from '@metamask/logging-controller'; import { TransactionController } from '@metamask/transaction-controller'; -import { TokenListController } from '@metamask/assets-controllers'; +import { + RatesController, + TokenListController, +} from '@metamask/assets-controllers'; +import { TrezorKeyring } from '@metamask/eth-trezor-keyring'; +import { LedgerKeyring } from '@metamask/eth-ledger-bridge-keyring'; import { NETWORK_TYPES } from '../../shared/constants/network'; import { createTestProviderTools } from '../../test/stub/provider'; import { HardwareDeviceNames } from '../../shared/constants/hardware-wallets'; @@ -29,6 +38,11 @@ import mockEncryptor from '../../test/lib/mock-encryptor'; import * as tokenUtils from '../../shared/lib/token-util'; import { flushPromises } from '../../test/lib/timer-helpers'; import { ETH_EOA_METHODS } from '../../shared/constants/eth-methods'; +import { createMockInternalAccount } from '../../test/jest/mocks'; +import { + BalancesController as MultichainBalancesController, + BTC_AVG_BLOCK_TIME, +} from './lib/accounts/BalancesController'; import { deferredPromise } from './lib/util'; import MetaMaskController from './metamask-controller'; @@ -115,6 +129,64 @@ jest.mock( }, ); +const KNOWN_PUBLIC_KEY = + '02065bc80d3d12b3688e4ad5ab1e9eda6adf24aec2518bfc21b87c99d4c5077ab0'; + +const KNOWN_PUBLIC_KEY_ADDRESSES = [ + { + address: '0x0e122670701207DB7c6d7ba9aE07868a4572dB3f', + balance: null, + index: 0, + }, + { + address: '0x2ae19DAd8b2569F7Bb4606D951Cc9495631e818E', + balance: null, + index: 1, + }, + { + address: '0x0051140bAaDC3E9AC92A4a90D18Bb6760c87e7ac', + balance: null, + index: 2, + }, + { + address: '0x9DBCF67CC721dBd8Df28D7A0CbA0fa9b0aFc6472', + balance: null, + index: 3, + }, + { + address: '0x828B2c51c5C1bB0c57fCD2C108857212c95903DE', + balance: null, + index: 4, + }, +]; + +const buildMockKeyringBridge = (publicKeyPayload) => + jest.fn(() => ({ + init: jest.fn(), + dispose: jest.fn(), + updateTransportMethod: jest.fn(), + getPublicKey: jest.fn(async () => publicKeyPayload), + })); + +jest.mock('@metamask/eth-trezor-keyring', () => ({ + ...jest.requireActual('@metamask/eth-trezor-keyring'), + TrezorConnectBridge: buildMockKeyringBridge({ + success: true, + payload: { + publicKey: KNOWN_PUBLIC_KEY, + chainCode: '0x1', + }, + }), +})); + +jest.mock('@metamask/eth-ledger-bridge-keyring', () => ({ + ...jest.requireActual('@metamask/eth-ledger-bridge-keyring'), + LedgerIframeBridge: buildMockKeyringBridge({ + publicKey: KNOWN_PUBLIC_KEY, + chainCode: '0x1', + }), +})); + const mockIsManifestV3 = jest.fn().mockReturnValue(false); jest.mock('../../shared/modules/mv3.utils', () => ({ get isManifestV3() { @@ -742,257 +814,290 @@ describe('MetaMaskController', () => { }); }); - describe('connectHardware', () => { - it('should throw if it receives an unknown device name', async () => { - const result = metamaskController.connectHardware( - 'Some random device name', - 0, - `m/44/0'/0'`, - ); - - await expect(result).rejects.toThrow( - 'MetamaskController:getKeyringForDevice - Unknown device', - ); + describe('hardware keyrings', () => { + beforeEach(async () => { + await metamaskController.createNewVaultAndKeychain('test@123'); }); - it('should add the Trezor Hardware keyring', async () => { - jest.spyOn(metamaskController.keyringController, 'addNewKeyring'); - await metamaskController - .connectHardware(HardwareDeviceNames.trezor, 0) - .catch(() => null); - const keyrings = - await metamaskController.keyringController.getKeyringsByType( - KeyringType.trezor, + describe('connectHardware', () => { + it('should throw if it receives an unknown device name', async () => { + const result = metamaskController.connectHardware( + 'Some random device name', + 0, + `m/44/0'/0'`, ); - expect( - metamaskController.keyringController.addNewKeyring, - ).toHaveBeenCalledWith(KeyringType.trezor); - expect(keyrings).toHaveLength(1); - }); - it('should add the Ledger Hardware keyring', async () => { - jest.spyOn(metamaskController.keyringController, 'addNewKeyring'); - await metamaskController - .connectHardware(HardwareDeviceNames.ledger, 0) - .catch(() => null); - const keyrings = - await metamaskController.keyringController.getKeyringsByType( - KeyringType.ledger, + await expect(result).rejects.toThrow( + 'MetamaskController:getKeyringForDevice - Unknown device', ); - expect( - metamaskController.keyringController.addNewKeyring, - ).toHaveBeenCalledWith(KeyringType.ledger); - expect(keyrings).toHaveLength(1); - }); - }); + }); - describe('getPrimaryKeyringMnemonic', () => { - it('should return a mnemonic as a Uint8Array', () => { - const mockMnemonic = - 'above mercy benefit hospital call oval domain student sphere interest argue shock'; - const mnemonicIndices = mockMnemonic - .split(' ') - .map((word) => englishWordlist.indexOf(word)); - const uint8ArrayMnemonic = new Uint8Array( - new Uint16Array(mnemonicIndices).buffer, - ); + it('should add the Trezor Hardware keyring and return the first page of accounts', async () => { + jest.spyOn(metamaskController.keyringController, 'addNewKeyring'); - const mockHDKeyring = { - type: 'HD Key Tree', - mnemonic: uint8ArrayMnemonic, - }; - jest - .spyOn(metamaskController.keyringController, 'getKeyringsByType') - .mockReturnValue([mockHDKeyring]); + const firstPage = await metamaskController.connectHardware( + HardwareDeviceNames.trezor, + 0, + ); - const recoveredMnemonic = - metamaskController.getPrimaryKeyringMnemonic(); + expect( + metamaskController.keyringController.addNewKeyring, + ).toHaveBeenCalledWith(KeyringType.trezor); + expect( + metamaskController.keyringController.state.keyrings[1].type, + ).toBe(TrezorKeyring.type); + expect(firstPage).toStrictEqual(KNOWN_PUBLIC_KEY_ADDRESSES); + }); - expect(recoveredMnemonic).toStrictEqual(uint8ArrayMnemonic); - }); - }); + it('should add the Ledger Hardware keyring and return the first page of accounts', async () => { + jest.spyOn(metamaskController.keyringController, 'addNewKeyring'); - describe('checkHardwareStatus', () => { - it('should throw if it receives an unknown device name', async () => { - const result = metamaskController.checkHardwareStatus( - 'Some random device name', - `m/44/0'/0'`, - ); - await expect(result).rejects.toThrow( - 'MetamaskController:getKeyringForDevice - Unknown device', - ); - }); + const firstPage = await metamaskController.connectHardware( + HardwareDeviceNames.ledger, + 0, + ); - it('should be locked by default', async () => { - await metamaskController - .connectHardware(HardwareDeviceNames.trezor, 0) - .catch(() => null); - const status = await metamaskController.checkHardwareStatus( - HardwareDeviceNames.trezor, - ); - expect(status).toStrictEqual(false); + expect( + metamaskController.keyringController.addNewKeyring, + ).toHaveBeenCalledWith(KeyringType.ledger); + expect( + metamaskController.keyringController.state.keyrings[1].type, + ).toBe(LedgerKeyring.type); + expect(firstPage).toStrictEqual(KNOWN_PUBLIC_KEY_ADDRESSES); + }); }); - }); - describe('forgetDevice', () => { - it('should throw if it receives an unknown device name', async () => { - const result = metamaskController.forgetDevice( - 'Some random device name', - ); - await expect(result).rejects.toThrow( - 'MetamaskController:getKeyringForDevice - Unknown device', - ); - }); + describe('checkHardwareStatus', () => { + it('should throw if it receives an unknown device name', async () => { + const result = metamaskController.checkHardwareStatus( + 'Some random device name', + `m/44/0'/0'`, + ); + await expect(result).rejects.toThrow( + 'MetamaskController:getKeyringForDevice - Unknown device', + ); + }); - it('should remove the identities when the device is forgotten', async () => { - jest.spyOn(window, 'open').mockReturnValue(); + [HardwareDeviceNames.trezor, HardwareDeviceNames.ledger].forEach( + (device) => { + describe(`using ${device}`, () => { + it('should be unlocked by default', async () => { + await metamaskController.connectHardware(device, 0); - const localMetaMaskController = new MetaMaskController({ - showUserConfirmation: noop, - encryptor: mockEncryptor, - initState: { - ...cloneDeep(firstTimeState), - KeyringController: { - keyrings: [{ type: KeyringType.trezor, accounts: ['0x123'] }], - isUnlocked: true, - }, - PreferencesController: { - identities: { - '0x123': { name: 'Trezor 1', address: '0x123' }, - }, - selectedAddress: '0x123', - }, - }, - initLangCode: 'en_US', - platform: { - showTransactionNotification: () => undefined, - getVersion: () => 'foo', + const status = await metamaskController.checkHardwareStatus( + device, + ); + + expect(status).toStrictEqual(true); + }); + }); }, - browser: browserPolyfillMock, - infuraProjectId: 'foo', - isFirstMetaMaskControllerSetup: true, + ); + }); + + describe('forgetDevice', () => { + it('should throw if it receives an unknown device name', async () => { + const result = metamaskController.forgetDevice( + 'Some random device name', + ); + await expect(result).rejects.toThrow( + 'MetamaskController:getKeyringForDevice - Unknown device', + ); }); - await localMetaMaskController.keyringController.createNewVaultAndKeychain( - 'password', - ); + it('should remove the identities when the device is forgotten', async () => { + await metamaskController.connectHardware( + HardwareDeviceNames.trezor, + 0, + ); + await metamaskController.unlockHardwareWalletAccount( + 0, + HardwareDeviceNames.trezor, + ); + const hardwareKeyringAccount = + metamaskController.keyringController.state.keyrings[1].accounts[0]; - await localMetaMaskController.keyringController.addNewKeyring( - 'Trezor Hardware', - { - accounts: ['0x123'], - }, - ); + await metamaskController.forgetDevice(HardwareDeviceNames.trezor); - await localMetaMaskController.forgetDevice(HardwareDeviceNames.trezor); - const { identities: updatedIdentities } = - localMetaMaskController.preferencesController.store.getState(); - expect(updatedIdentities['0x123']).toBeUndefined(); - }); + expect( + Object.keys( + metamaskController.preferencesController.store.getState() + .identities, + ), + ).not.toContain(hardwareKeyringAccount); + expect( + metamaskController.accountsController + .listAccounts() + .some((account) => account.address === hardwareKeyringAccount), + ).toStrictEqual(false); + }); - it('should wipe all the keyring info', async () => { - await metamaskController - .connectHardware(HardwareDeviceNames.trezor, 0) - .catch(() => null); - await metamaskController.forgetDevice(HardwareDeviceNames.trezor); - const keyrings = - await metamaskController.keyringController.getKeyringsByType( - KeyringType.trezor, + it('should wipe all the keyring info', async () => { + await metamaskController.connectHardware( + HardwareDeviceNames.trezor, + 0, ); - expect(keyrings[0].accounts).toStrictEqual([]); - expect(keyrings[0].page).toStrictEqual(0); - expect(keyrings[0].isUnlocked()).toStrictEqual(false); + await metamaskController.forgetDevice(HardwareDeviceNames.trezor); + const keyrings = + await metamaskController.keyringController.getKeyringsByType( + KeyringType.trezor, + ); + + expect(keyrings[0].accounts).toStrictEqual([]); + expect(keyrings[0].page).toStrictEqual(0); + expect(keyrings[0].isUnlocked()).toStrictEqual(false); + }); }); - }); - describe('unlockHardwareWalletAccount', () => { - const accountToUnlock = 10; - beforeEach(async () => { - await metamaskController.keyringController.createNewVaultAndRestore( - 'password', - TEST_SEED, - ); - jest.spyOn(window, 'open').mockReturnValue(); - jest - .spyOn( - metamaskController.keyringController, - 'addNewAccountForKeyring', - ) - .mockReturnValue('0x123'); + describe('unlockHardwareWalletAccount', () => { + const accountToUnlock = 0; - jest - .spyOn(metamaskController.keyringController, 'getAccounts') - .mockResolvedValueOnce(['0x1']) - .mockResolvedValueOnce(['0x2']) - .mockResolvedValueOnce(['0x3']); - jest - .spyOn(metamaskController.preferencesController, 'setSelectedAddress') - .mockReturnValue(); - jest - .spyOn(metamaskController.preferencesController, 'setAccountLabel') - .mockReturnValue(); + [HardwareDeviceNames.trezor, HardwareDeviceNames.ledger].forEach( + (device) => { + describe(`using ${device}`, () => { + beforeEach(async () => { + await metamaskController.connectHardware(device, 0); + }); - jest - .spyOn(metamaskController.accountsController, 'getAccountByAddress') - .mockReturnValue({ - account: { - id: '2d47e693-26c2-47cb-b374-6151199bbe3f', - }, - }); - jest - .spyOn(metamaskController.accountsController, 'setAccountName') - .mockReturnValue(); + it('should return the unlocked account', async () => { + const { unlockedAccount } = + await metamaskController.unlockHardwareWalletAccount( + accountToUnlock, + device, + ); + + expect(unlockedAccount).toBe( + KNOWN_PUBLIC_KEY_ADDRESSES[ + accountToUnlock + ].address.toLowerCase(), + ); + }); - await metamaskController.unlockHardwareWalletAccount( - accountToUnlock, - HardwareDeviceNames.trezor, - `m/44'/1'/0'/0`, - ); - }); + it('should add the unlocked account to KeyringController', async () => { + await metamaskController.unlockHardwareWalletAccount( + accountToUnlock, + device, + ); - it('should set unlockedAccount in the keyring', async () => { - const keyrings = - await metamaskController.keyringController.getKeyringsByType( - KeyringType.trezor, - ); - expect(keyrings[0].unlockedAccount).toStrictEqual(accountToUnlock); - }); + expect( + metamaskController.keyringController.state.keyrings[1] + .accounts, + ).toStrictEqual([ + KNOWN_PUBLIC_KEY_ADDRESSES[ + accountToUnlock + ].address.toLowerCase(), + ]); + }); - it('should call keyringController.addNewAccount', async () => { - expect( - metamaskController.keyringController.addNewAccountForKeyring, - ).toHaveBeenCalledTimes(1); - }); + it('should call keyringController.addNewAccountForKeyring', async () => { + jest.spyOn( + metamaskController.keyringController, + 'addNewAccountForKeyring', + ); - it('should call keyringController.getAccounts', async () => { - expect( - metamaskController.keyringController.getAccounts, - ).toHaveBeenCalledTimes(3); - }); + await metamaskController.unlockHardwareWalletAccount( + accountToUnlock, + device, + ); - it('should call preferencesController.setSelectedAddress', async () => { - expect( - metamaskController.preferencesController.setSelectedAddress, - ).toHaveBeenCalledTimes(1); - }); + expect( + metamaskController.keyringController.addNewAccountForKeyring, + ).toHaveBeenCalledTimes(1); + }); - it('should call preferencesController.setAccountLabel', async () => { - expect( - metamaskController.preferencesController.setAccountLabel, - ).toHaveBeenCalledTimes(1); - }); + it('should call preferencesController.setSelectedAddress', async () => { + jest.spyOn( + metamaskController.preferencesController, + 'setSelectedAddress', + ); - it('should call accountsController.getAccountByAddress', async () => { - expect( - metamaskController.accountsController.getAccountByAddress, - ).toHaveBeenCalledTimes(1); + await metamaskController.unlockHardwareWalletAccount( + accountToUnlock, + device, + ); + + expect( + metamaskController.preferencesController.setSelectedAddress, + ).toHaveBeenCalledTimes(1); + }); + + it('should call preferencesController.setAccountLabel', async () => { + jest.spyOn( + metamaskController.preferencesController, + 'setAccountLabel', + ); + + await metamaskController.unlockHardwareWalletAccount( + accountToUnlock, + device, + ); + + expect( + metamaskController.preferencesController.setAccountLabel, + ).toHaveBeenCalledTimes(1); + }); + + it('should call accountsController.getAccountByAddress', async () => { + jest.spyOn( + metamaskController.accountsController, + 'getAccountByAddress', + ); + + await metamaskController.unlockHardwareWalletAccount( + accountToUnlock, + device, + ); + + expect( + metamaskController.accountsController.getAccountByAddress, + ).toHaveBeenCalledTimes(1); + }); + + it('should call accountsController.setAccountName', async () => { + jest.spyOn( + metamaskController.accountsController, + 'setAccountName', + ); + + await metamaskController.unlockHardwareWalletAccount( + accountToUnlock, + device, + ); + + expect( + metamaskController.accountsController.setAccountName, + ).toHaveBeenCalledTimes(1); + }); + }); + }, + ); }); + }); - it('should call accountsController.setAccountName', async () => { - expect( - metamaskController.accountsController.setAccountName, - ).toHaveBeenCalledTimes(1); + describe('getPrimaryKeyringMnemonic', () => { + it('should return a mnemonic as a Uint8Array', () => { + const mockMnemonic = + 'above mercy benefit hospital call oval domain student sphere interest argue shock'; + const mnemonicIndices = mockMnemonic + .split(' ') + .map((word) => englishWordlist.indexOf(word)); + const uint8ArrayMnemonic = new Uint8Array( + new Uint16Array(mnemonicIndices).buffer, + ); + + const mockHDKeyring = { + type: 'HD Key Tree', + mnemonic: uint8ArrayMnemonic, + }; + jest + .spyOn(metamaskController.keyringController, 'getKeyringsByType') + .mockReturnValue([mockHDKeyring]); + + const recoveredMnemonic = + metamaskController.getPrimaryKeyringMnemonic(); + + expect(recoveredMnemonic).toStrictEqual(uint8ArrayMnemonic); }); }); @@ -1325,6 +1430,11 @@ describe('MetaMaskController', () => { }); describe('#_onKeyringControllerUpdate', () => { + const accounts = [ + '0x603E83442BA54A2d0E080c34D6908ec228bef59f', + '0xDe95cE6E727692286E02A931d074efD1E5E2f03c', + ]; + it('should do nothing if there are no keyrings in state', async () => { jest .spyOn(metamaskController.accountTracker, 'syncWithAddresses') @@ -1348,14 +1458,14 @@ describe('MetaMaskController', () => { await metamaskController._onKeyringControllerUpdate({ keyrings: [ { - accounts: ['0x1', '0x2'], + accounts, }, ], }); expect( metamaskController.accountTracker.syncWithAddresses, - ).toHaveBeenCalledWith(['0x1', '0x2']); + ).toHaveBeenCalledWith(accounts); expect(metamaskController.getState()).toStrictEqual(oldState); }); @@ -1369,14 +1479,38 @@ describe('MetaMaskController', () => { isUnlocked: true, keyrings: [ { - accounts: ['0x1', '0x2'], + accounts, + }, + ], + }); + + expect( + metamaskController.accountTracker.syncWithAddresses, + ).toHaveBeenCalledWith(accounts); + expect(metamaskController.getState()).toStrictEqual(oldState); + }); + + it('filter out non-EVM addresses prior to calling syncWithAddresses', async () => { + jest + .spyOn(metamaskController.accountTracker, 'syncWithAddresses') + .mockReturnValue(); + + const oldState = metamaskController.getState(); + await metamaskController._onKeyringControllerUpdate({ + keyrings: [ + { + accounts: [ + ...accounts, + // Non-EVM address which should not be used by syncWithAddresses + 'bc1ql49ydapnjafl5t2cp9zqpjwe6pdgmxy98859v2', + ], }, ], }); expect( metamaskController.accountTracker.syncWithAddresses, - ).toHaveBeenCalledWith(['0x1', '0x2']); + ).toHaveBeenCalledWith(accounts); expect(metamaskController.getState()).toStrictEqual(oldState); }); }); @@ -1996,6 +2130,174 @@ describe('MetaMaskController', () => { expect(TokenListController.prototype.start).toHaveBeenCalledTimes(1); }); }); + + describe('MultichainRatesController start/stop', () => { + const mockEvmAccount = createMockInternalAccount(); + const mockNonEvmAccount = { + ...mockEvmAccount, + id: '21690786-6abd-45d8-a9f0-9ff1d8ca76a1', + type: BtcAccountType.P2wpkh, + methods: [BtcMethod.SendMany], + address: 'bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq', + }; + + beforeEach(() => { + jest.spyOn(metamaskController.multichainRatesController, 'start'); + jest.spyOn(metamaskController.multichainRatesController, 'stop'); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('starts MultichainRatesController if selected account is changed to non-EVM', async () => { + expect( + metamaskController.multichainRatesController.start, + ).not.toHaveBeenCalled(); + + metamaskController.controllerMessenger.publish( + 'AccountsController:selectedAccountChange', + mockNonEvmAccount, + ); + + expect( + metamaskController.multichainRatesController.start, + ).toHaveBeenCalledTimes(1); + }); + + it('stops MultichainRatesController if selected account is changed to EVM', async () => { + expect( + metamaskController.multichainRatesController.start, + ).not.toHaveBeenCalled(); + + metamaskController.controllerMessenger.publish( + 'AccountsController:selectedAccountChange', + mockNonEvmAccount, + ); + + expect( + metamaskController.multichainRatesController.start, + ).toHaveBeenCalledTimes(1); + + metamaskController.controllerMessenger.publish( + 'AccountsController:selectedAccountChange', + mockEvmAccount, + ); + expect( + metamaskController.multichainRatesController.start, + ).toHaveBeenCalledTimes(1); + expect( + metamaskController.multichainRatesController.stop, + ).toHaveBeenCalledTimes(1); + }); + + it('does not start MultichainRatesController if selected account is changed to EVM', async () => { + expect( + metamaskController.multichainRatesController.start, + ).not.toHaveBeenCalled(); + + metamaskController.controllerMessenger.publish( + 'AccountsController:selectedAccountChange', + mockEvmAccount, + ); + + expect( + metamaskController.multichainRatesController.start, + ).not.toHaveBeenCalled(); + }); + + it('starts MultichainRatesController if selected account is non-EVM account during initialization', async () => { + jest.spyOn(RatesController.prototype, 'start'); + const localMetamaskController = new MetaMaskController({ + showUserConfirmation: noop, + encryptor: mockEncryptor, + initState: { + ...cloneDeep(firstTimeState), + AccountsController: { + internalAccounts: { + accounts: { + [mockNonEvmAccount.id]: mockNonEvmAccount, + [mockEvmAccount.id]: mockEvmAccount, + }, + selectedAccount: mockNonEvmAccount.id, + }, + }, + }, + initLangCode: 'en_US', + platform: { + showTransactionNotification: () => undefined, + getVersion: () => 'foo', + }, + browser: browserPolyfillMock, + infuraProjectId: 'foo', + isFirstMetaMaskControllerSetup: true, + }); + + expect( + localMetamaskController.multichainRatesController.start, + ).toHaveBeenCalled(); + }); + }); + + describe('MultichainBalancesController', () => { + const mockEvmAccount = createMockInternalAccount(); + const mockNonEvmAccount = { + ...mockEvmAccount, + id: '21690786-6abd-45d8-a9f0-9ff1d8ca76a1', + type: BtcAccountType.P2wpkh, + methods: [BtcMethod.SendMany], + address: 'bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq', + }; + let localMetamaskController; + + beforeEach(() => { + jest.useFakeTimers(); + jest.spyOn(MultichainBalancesController.prototype, 'updateBalances'); + localMetamaskController = new MetaMaskController({ + showUserConfirmation: noop, + encryptor: mockEncryptor, + initState: { + ...cloneDeep(firstTimeState), + AccountsController: { + internalAccounts: { + accounts: { + [mockNonEvmAccount.id]: mockNonEvmAccount, + [mockEvmAccount.id]: mockEvmAccount, + }, + selectedAccount: mockNonEvmAccount.id, + }, + }, + }, + initLangCode: 'en_US', + platform: { + showTransactionNotification: () => undefined, + getVersion: () => 'foo', + }, + browser: browserPolyfillMock, + infuraProjectId: 'foo', + isFirstMetaMaskControllerSetup: true, + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + jest.useRealTimers(); + }); + + it('calls updateBalances during startup', async () => { + expect( + localMetamaskController.multichainBalancesController.updateBalances, + ).toHaveBeenCalled(); + }); + + it('calls updateBalances after the interval has passed', async () => { + jest.advanceTimersByTime(BTC_AVG_BLOCK_TIME); + // 2 calls because 1 is during startup + expect( + localMetamaskController.multichainBalancesController.updateBalances, + ).toHaveBeenCalledTimes(2); + }); + }); }); describe('MV3 Specific behaviour', () => { diff --git a/app/scripts/offscreen.js b/app/scripts/offscreen.js new file mode 100644 index 000000000000..ba796874f2fc --- /dev/null +++ b/app/scripts/offscreen.js @@ -0,0 +1,44 @@ +import { OffscreenCommunicationTarget } from '../../shared/constants/offscreen-communication'; + +/** + * Creates an offscreen document that can be used to load additional scripts + * and iframes that can communicate with the extension through the chrome + * runtime API. Only one offscreen document may exist, so any iframes required + * by extension can be embedded in the offscreen.html file. See the offscreen + * folder for more details. + */ +export async function createOffscreen() { + const { chrome } = globalThis; + if (!chrome.offscreen || (await chrome.offscreen.hasDocument())) { + return; + } + + const loadPromise = new Promise((resolve) => { + const messageListener = (msg) => { + if ( + msg.target === OffscreenCommunicationTarget.extensionMain && + msg.isBooted + ) { + chrome.runtime.onMessage.removeListener(messageListener); + resolve(); + } + }; + chrome.runtime.onMessage.addListener(messageListener); + }); + + await chrome.offscreen.createDocument({ + url: './offscreen.html', + reasons: ['IFRAME_SCRIPTING'], + justification: + 'Used for Hardware Wallet and Snaps scripts to communicate with the extension.', + }); + + // In case we are in a bad state where the offscreen document is not loading, timeout and let execution continue. + const timeoutPromise = new Promise((resolve) => { + setTimeout(resolve, 5000); + }); + + await Promise.race([loadPromise, timeoutPromise]); + + console.debug('Offscreen iframe loaded'); +} diff --git a/app/scripts/skip-onboarding.js b/app/scripts/skip-onboarding.js index 86b07aae66e5..39c3b0b61865 100644 --- a/app/scripts/skip-onboarding.js +++ b/app/scripts/skip-onboarding.js @@ -100,13 +100,13 @@ async function generateVaultAndAccount(encodedSeedPhrase, password) { return new Uint8Array(new Uint16Array(indices).buffer); }; - const res = await krCtrl.createNewVaultAndRestore( + await krCtrl.createNewVaultAndRestore( password, _convertMnemonicToWordlistIndices(seedPhraseAsBuffer), ); const { vault } = krCtrl.state; - const account = res.keyrings[0].accounts[0]; + const account = krCtrl.state.keyrings[0].accounts[0]; return { vault, account }; } diff --git a/builds.yml b/builds.yml index 7f6dff152578..35fa7341aa44 100644 --- a/builds.yml +++ b/builds.yml @@ -16,9 +16,7 @@ buildTypes: main: features: - build-main - - snaps - keyring-snaps - - blockaid # Additional env variables that are specific to this build env: - INFURA_PROD_PROJECT_ID @@ -27,7 +25,7 @@ buildTypes: - SEGMENT_WRITE_KEY_REF: SEGMENT_PROD_WRITE_KEY - ALLOW_LOCAL_SNAPS: false - REQUIRE_SNAPS_ALLOWLIST: true - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.3.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.5.0/index.html - ACCOUNT_SNAPS_DIRECTORY_URL: https://snaps.metamask.io/account-management - BTC_BETA_SUPPORT: false # Main build uses the default browser manifest @@ -39,11 +37,16 @@ buildTypes: beta: features: - build-beta + - keyring-snaps env: - INFURA_BETA_PROJECT_ID - SEGMENT_BETA_WRITE_KEY - INFURA_ENV_KEY_REF: INFURA_BETA_PROJECT_ID - SEGMENT_WRITE_KEY_REF: SEGMENT_BETA_WRITE_KEY + - ALLOW_LOCAL_SNAPS: false + - REQUIRE_SNAPS_ALLOWLIST: true + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.5.0/index.html + - ACCOUNT_SNAPS_DIRECTORY_URL: https://snaps.metamask.io/account-management # Modifies how the version is displayed. # eg. instead of 10.25.0 -> 10.25.0-beta.2 isPrerelease: true @@ -55,18 +58,16 @@ buildTypes: # Code surrounded using code fences for that feature # will not be removed features: - - snaps - build-flask - keyring-snaps - - blockaid env: - INFURA_FLASK_PROJECT_ID - SEGMENT_FLASK_WRITE_KEY - ALLOW_LOCAL_SNAPS: true - REQUIRE_SNAPS_ALLOWLIST: false - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.3.0/index.html - - SUPPORT_LINK: https://metamask-flask.zendesk.com/hc - - SUPPORT_REQUEST_LINK: https://metamask-flask.zendesk.com/hc/en-us/requests/new + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.5.0/index.html + - SUPPORT_LINK: https://support.metamask.io/ + - SUPPORT_REQUEST_LINK: https://support.metamask.io/ - INFURA_ENV_KEY_REF: INFURA_FLASK_PROJECT_ID - SEGMENT_WRITE_KEY_REF: SEGMENT_FLASK_WRITE_KEY - ACCOUNT_SNAPS_DIRECTORY_URL: https://metamask.github.io/snaps-directory-staging/main/account-management @@ -79,8 +80,6 @@ buildTypes: mmi: features: - build-mmi - - snaps - - blockaid env: - INFURA_MMI_PROJECT_ID - SEGMENT_MMI_WRITE_KEY @@ -89,7 +88,7 @@ buildTypes: - SEGMENT_WRITE_KEY_REF: SEGMENT_MMI_WRITE_KEY - ALLOW_LOCAL_SNAPS: false - REQUIRE_SNAPS_ALLOWLIST: true - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.3.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.5.0/index.html - MMI_CONFIGURATION_SERVICE_URL: https://configuration.metamask-institutional.io/v2/configuration/default - SUPPORT_LINK: https://mmi-support.metamask.io/hc/en-us - SUPPORT_REQUEST_LINK: https://mmi-support.metamask.io/hc/en-us/requests/new @@ -104,21 +103,6 @@ buildTypes: # Each feature can have code fences that add new code # as well declaring, defining and overriding env variables features: - snaps: - # Each feature might have variables that only exist when built with that feature active - env: - # Whether to allow snaps from localhost - local://localhost:8080/snap.manifest.json - # Enabled in Flask, will be disabled in Main - - ALLOW_LOCAL_SNAPS - # Whether to verify that a snap can be installed using an allow list - - REQUIRE_SNAPS_ALLOWLIST - - IFRAME_EXECUTION_ENVIRONMENT_URL - assets: - - ./{app,shared,ui}/**/snaps/** - blockaid: - env: - - BLOCKAID_FILE_CDN: static.cx.metamask.io/api/v1/confirmations/ppom - - BLOCKAID_PUBLIC_KEY: 066ad3e8af5583385e312c156d238055215d5f25247c1e91055afa756cb98a88 ### # Build Type code extensions. Things like different support links, warning pages, banners ### @@ -162,8 +146,12 @@ env: # Also see METAMASK_DEBUG and NODE_DEBUG - DEBUG: null - SUPPORT_LINK: https://support.metamask.io - - SUPPORT_REQUEST_LINK: https://metamask.zendesk.com/hc/en-us + - SUPPORT_REQUEST_LINK: https://support.metamask.io - SKIP_BACKGROUND_INITIALIZATION: false + # CDN for blockaid files + - BLOCKAID_FILE_CDN: static.cx.metamask.io/api/v1/confirmations/ppom + # Blockaid public key for verifying signatures of data files downloaded from CDN + - BLOCKAID_PUBLIC_KEY: 066ad3e8af5583385e312c156d238055215d5f25247c1e91055afa756cb98a88 - ENABLE_MV3: true # These are exclusively used for MV3 @@ -264,10 +252,6 @@ env: - NODE_DEBUG: '' # Used by react-devtools-core - EDITOR_URL: '' - # CDN for blockaid files - - BLOCKAID_FILE_CDN - # Blockaid public key for verifying signatures of data files downloaded from CDN - - BLOCKAID_PUBLIC_KEY # Determines if feature flagged Multichain Transactions should be used - TRANSACTION_MULTICHAIN: '' # Determines if feature flagged Chain permissions diff --git a/coverage-targets.js b/coverage-targets.js deleted file mode 100644 index 631a8b6e737d..000000000000 --- a/coverage-targets.js +++ /dev/null @@ -1,20 +0,0 @@ -// Codecov uses a yaml file for its configuration and it targets line coverage. -// To keep our policy in place we have thile file separate from our -// codecov.yml file that specifies coverage targets for each project in the -// codecov.yml file. These targets are read by the test/merge-coverage.js -// script, and the paths from the codecov.yml file are used to figure out which -// subset of files to check against these targets. -module.exports = { - global: { - lines: 70.85, - branches: 59.07, - statements: 70.3, - functions: 63.52, - }, - transforms: { - branches: 100, - functions: 100, - lines: 100, - statements: 100, - }, -}; diff --git a/development/build/index.js b/development/build/index.js index cc502607b743..f9a778ed8966 100755 --- a/development/build/index.js +++ b/development/build/index.js @@ -95,6 +95,7 @@ async function defineAndRunBuildTasks() { let scuttleGlobalThisExceptions = [ // globals used by different mm deps outside of lm compartment + 'Proxy', 'toString', 'getComputedStyle', 'addEventListener', diff --git a/development/build/static.js b/development/build/static.js index 5729828568d6..cf0cd1078423 100644 --- a/development/build/static.js +++ b/development/build/static.js @@ -29,7 +29,6 @@ module.exports = function createStaticAssetTasks({ const [copyTargetsProd, copyTargetsDev] = getCopyTargets( shouldIncludeLockdown, shouldIncludeSnow, - activeFeatures, ); copyTargetsProds[browser] = copyTargetsProd; copyTargetsDevs[browser] = copyTargetsDev; @@ -108,11 +107,7 @@ module.exports = function createStaticAssetTasks({ } }; -function getCopyTargets( - shouldIncludeLockdown, - shouldIncludeSnow, - activeFeatures, -) { +function getCopyTargets(shouldIncludeLockdown, shouldIncludeSnow) { const allCopyTargets = [ { src: `./app/_locales/`, @@ -202,10 +197,7 @@ function getCopyTargets( pattern: `*.html`, dest: '', }, - ]; - - if (activeFeatures.includes('blockaid')) { - allCopyTargets.push({ + { src: getPathInsideNodeModules('@blockaid/ppom_release', '/'), pattern: '*.wasm', dest: @@ -213,8 +205,29 @@ function getCopyTargets( process.env.ENABLE_MV3 === undefined ? 'scripts/' : '', - }); - } + }, + ...(process.env.ENABLE_MV3 === 'true' || + process.env.ENABLE_MV3 === undefined + ? [ + { + src: getPathInsideNodeModules( + '@metamask/snaps-execution-environments', + 'dist/browserify/iframe/index.html', + ), + dest: `snaps/index.html`, + pattern: '', + }, + { + src: getPathInsideNodeModules( + '@metamask/snaps-execution-environments', + 'dist/browserify/iframe/bundle.js', + ), + dest: `snaps/bundle.js`, + pattern: '', + }, + ] + : []), + ]; const copyTargetsDev = [ ...allCopyTargets, diff --git a/development/ts-migration-dashboard/files-to-convert.json b/development/ts-migration-dashboard/files-to-convert.json index df0feeb26e47..ed5615e8c9d0 100644 --- a/development/ts-migration-dashboard/files-to-convert.json +++ b/development/ts-migration-dashboard/files-to-convert.json @@ -316,7 +316,6 @@ "test/lib/mock-encryptor.js", "test/lib/render-helpers.js", "test/lib/tick.js", - "test/lib/wait-until-called.js", "test/mocks/permissions.js", "test/stub/provider.js", "test/stub/tx-meta-stub.js", diff --git a/docs/README.md b/docs/README.md index 4e87d90c13c6..80f5901b57ba 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,7 +4,7 @@ These docs relate to how to contribute to the MetaMask project itself. You can find the latest version of MetaMask on [our official website](https://metamask.io/). -For help using MetaMask, visit our [User Support Site](https://metamask.zendesk.com/hc/en-us). +For help using MetaMask, visit our [User Support Site](https://support.metamask.io/). For up to the minute news, follow our [Twitter](https://twitter.com/metamask_io) or [Medium](https://medium.com/metamask) pages. diff --git a/jest.config.js b/jest.config.js index f02cc20ab6b5..f52466a9d183 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,26 +1,8 @@ module.exports = { collectCoverageFrom: [ - '/app/scripts/constants/error-utils.js', - '/app/scripts/controllers/app-metadata.ts', - '/app/scripts/controllers/permissions/**/*.js', - '/app/scripts/controllers/sign.ts', - '/app/scripts/controllers/decrypt-message.ts', - '/app/scripts/controllers/encryption-public-key.ts', - '/app/scripts/controllers/transactions/etherscan.ts', - '/app/scripts/controllers/transactions/EtherscanRemoteTransactionSource.ts', - '/app/scripts/controllers/transactions/IncomingTransactionHelper.ts', - '/app/scripts/controllers/preferences.js', - '/app/scripts/flask/**/*.js', - '/app/scripts/lib/**/*.(js|ts)', - '/app/scripts/metamask-controller.js', - '/app/scripts/migrations/*.js', - '/app/scripts/migrations/*.ts', - '!/app/scripts/migrations/*.test.(js|ts)', - '/app/scripts/platforms/*.js', + '/app/scripts/**/*.(js|ts|tsx)', '/shared/**/*.(js|ts|tsx)', '/ui/**/*.(js|ts|tsx)', - '/development/fitness-functions/**/*.test.(js|ts|tsx)', - '/test/e2e/helpers.test.js', ], coverageDirectory: './coverage', coveragePathIgnorePatterns: ['.stories.*', '.snap'], @@ -41,31 +23,8 @@ module.exports = { setupFiles: ['/test/setup.js', '/test/env.js'], setupFilesAfterEnv: ['/test/jest/setup.js'], testMatch: [ - '/app/scripts/constants/error-utils.test.js', - '/app/scripts/controllers/app-metadata.test.ts', - '/app/scripts/controllers/app-state.test.js', - '/app/scripts/controllers/encryption-public-key.test.ts', - '/app/scripts/controllers/transactions/etherscan.test.ts', - '/app/scripts/controllers/transactions/EtherscanRemoteTransactionSource.test.ts', - '/app/scripts/controllers/transactions/IncomingTransactionHelper.test.ts', - '/app/scripts/controllers/onboarding.test.ts', - '/app/scripts/controllers/mmi-controller.test.ts', - '/app/scripts/controllers/permissions/**/*.test.js', - '/app/scripts/controllers/preferences.test.js', - '/app/scripts/controllers/sign.test.ts', - '/app/scripts/controllers/decrypt-message.test.ts', - '/app/scripts/controllers/authentication/**/*.test.ts', - '/app/scripts/controllers/push-platform-notifications/**/*.test.ts', - '/app/scripts/controllers/user-storage/**/*.test.ts', - '/app/scripts/controllers/metamask-notifications/**/*.test.ts', - '/app/scripts/flask/**/*.test.js', - '/app/scripts/lib/**/*.test.(js|ts)', - '/app/scripts/lib/createRPCMethodTrackingMiddleware.test.js', - '/app/scripts/metamask-controller.test.js', - '/app/scripts/migrations/*.test.(js|ts)', - '/app/scripts/platforms/*.test.js', - '/app/scripts/translate.test.ts', - '/shared/**/*.test.(js|ts)', + '/app/scripts/**/*.test.(js|ts|tsx)', + '/shared/**/*.test.(js|ts|tsx)', '/ui/**/*.test.(js|ts|tsx)', '/development/fitness-functions/**/*.test.(js|ts|tsx)', '/test/e2e/helpers.test.js', diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index 42b366d8270a..e1bbe63dca11 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -227,12 +227,12 @@ "@ethersproject/abi>@ethersproject/address": true, "@ethersproject/abi>@ethersproject/bytes": true, "@ethersproject/abi>@ethersproject/constants": true, - "@ethersproject/abi>@ethersproject/hash": true, "@ethersproject/abi>@ethersproject/keccak256": true, "@ethersproject/abi>@ethersproject/logger": true, "@ethersproject/abi>@ethersproject/properties": true, "@ethersproject/abi>@ethersproject/strings": true, - "@ethersproject/bignumber": true + "@ethersproject/bignumber": true, + "@ethersproject/hash": true } }, "@ethersproject/abi>@ethersproject/address": { @@ -254,18 +254,6 @@ "@ethersproject/bignumber": true } }, - "@ethersproject/abi>@ethersproject/hash": { - "packages": { - "@ethersproject/abi>@ethersproject/address": true, - "@ethersproject/abi>@ethersproject/bytes": true, - "@ethersproject/abi>@ethersproject/keccak256": true, - "@ethersproject/abi>@ethersproject/logger": true, - "@ethersproject/abi>@ethersproject/properties": true, - "@ethersproject/abi>@ethersproject/strings": true, - "@ethersproject/bignumber": true, - "@ethersproject/providers>@ethersproject/base64": true - } - }, "@ethersproject/abi>@ethersproject/keccak256": { "packages": { "@ethersproject/abi>@ethersproject/bytes": true, @@ -307,9 +295,36 @@ "@ethersproject/abi>@ethersproject/logger": true, "@ethersproject/abi>@ethersproject/properties": true, "@ethersproject/bignumber": true, - "@ethersproject/hdnode>@ethersproject/abstract-signer": true, + "@ethersproject/hash>@ethersproject/abstract-signer": true, "@ethersproject/hdnode>@ethersproject/transactions": true, - "@metamask/test-bundler>@ethersproject/abstract-provider": true + "@ethersproject/wallet>@ethersproject/abstract-provider": true + } + }, + "@ethersproject/hash": { + "packages": { + "@ethersproject/abi>@ethersproject/address": true, + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/keccak256": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "@ethersproject/abi>@ethersproject/strings": true, + "@ethersproject/bignumber": true, + "@ethersproject/hash>@ethersproject/base64": true + } + }, + "@ethersproject/hash>@ethersproject/abstract-signer": { + "packages": { + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true + } + }, + "@ethersproject/hash>@ethersproject/base64": { + "globals": { + "atob": true, + "btoa": true + }, + "packages": { + "@ethersproject/abi>@ethersproject/bytes": true } }, "@ethersproject/hdnode": { @@ -327,12 +342,6 @@ "@ethersproject/hdnode>@ethersproject/wordlists": true } }, - "@ethersproject/hdnode>@ethersproject/abstract-signer": { - "packages": { - "@ethersproject/abi>@ethersproject/logger": true, - "@ethersproject/abi>@ethersproject/properties": true - } - }, "@ethersproject/hdnode>@ethersproject/basex": { "packages": { "@ethersproject/abi>@ethersproject/bytes": true, @@ -376,10 +385,10 @@ "@ethersproject/hdnode>@ethersproject/wordlists": { "packages": { "@ethersproject/abi>@ethersproject/bytes": true, - "@ethersproject/abi>@ethersproject/hash": true, "@ethersproject/abi>@ethersproject/logger": true, "@ethersproject/abi>@ethersproject/properties": true, - "@ethersproject/abi>@ethersproject/strings": true + "@ethersproject/abi>@ethersproject/strings": true, + "@ethersproject/hash": true } }, "@ethersproject/providers": { @@ -396,39 +405,26 @@ "@ethersproject/abi>@ethersproject/address": true, "@ethersproject/abi>@ethersproject/bytes": true, "@ethersproject/abi>@ethersproject/constants": true, - "@ethersproject/abi>@ethersproject/hash": true, "@ethersproject/abi>@ethersproject/logger": true, "@ethersproject/abi>@ethersproject/properties": true, "@ethersproject/abi>@ethersproject/strings": true, "@ethersproject/bignumber": true, - "@ethersproject/hdnode>@ethersproject/abstract-signer": true, + "@ethersproject/hash": true, + "@ethersproject/hash>@ethersproject/abstract-signer": true, + "@ethersproject/hash>@ethersproject/base64": true, "@ethersproject/hdnode>@ethersproject/basex": true, "@ethersproject/hdnode>@ethersproject/sha2": true, "@ethersproject/hdnode>@ethersproject/transactions": true, - "@ethersproject/providers>@ethersproject/base64": true, - "@ethersproject/providers>@ethersproject/random": true, "@ethersproject/providers>@ethersproject/web": true, "@ethersproject/providers>bech32": true, - "@metamask/test-bundler>@ethersproject/abstract-provider": true, + "@ethersproject/wallet>@ethersproject/abstract-provider": true, + "@ethersproject/wallet>@ethersproject/random": true, "@metamask/test-bundler>@ethersproject/networks": true } }, - "@ethersproject/providers>@ethersproject/base64": { - "globals": { - "atob": true, - "btoa": true - }, - "packages": { - "@ethersproject/abi>@ethersproject/bytes": true - } - }, "@ethersproject/providers>@ethersproject/random": { "globals": { "crypto.getRandomValues": true - }, - "packages": { - "@ethersproject/abi>@ethersproject/bytes": true, - "@ethersproject/abi>@ethersproject/logger": true } }, "@ethersproject/providers>@ethersproject/rlp": { @@ -448,7 +444,59 @@ "@ethersproject/abi>@ethersproject/logger": true, "@ethersproject/abi>@ethersproject/properties": true, "@ethersproject/abi>@ethersproject/strings": true, - "@ethersproject/providers>@ethersproject/base64": true + "@ethersproject/hash>@ethersproject/base64": true + } + }, + "@ethersproject/wallet": { + "packages": { + "@ethersproject/abi>@ethersproject/address": true, + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/keccak256": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "@ethersproject/hash": true, + "@ethersproject/hash>@ethersproject/abstract-signer": true, + "@ethersproject/hdnode": true, + "@ethersproject/hdnode>@ethersproject/signing-key": true, + "@ethersproject/hdnode>@ethersproject/transactions": true, + "@ethersproject/wallet>@ethersproject/abstract-provider": true, + "@ethersproject/wallet>@ethersproject/json-wallets": true, + "@ethersproject/wallet>@ethersproject/random": true + } + }, + "@ethersproject/wallet>@ethersproject/abstract-provider": { + "packages": { + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "@ethersproject/bignumber": true + } + }, + "@ethersproject/wallet>@ethersproject/json-wallets": { + "packages": { + "@ethersproject/abi>@ethersproject/address": true, + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/keccak256": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "@ethersproject/abi>@ethersproject/strings": true, + "@ethersproject/hdnode": true, + "@ethersproject/hdnode>@ethersproject/pbkdf2": true, + "@ethersproject/hdnode>@ethersproject/transactions": true, + "@ethersproject/wallet>@ethersproject/json-wallets>aes-js": true, + "@ethersproject/wallet>@ethersproject/random": true, + "ethereumjs-util>ethereum-cryptography>scrypt-js": true + } + }, + "@ethersproject/wallet>@ethersproject/json-wallets>aes-js": { + "globals": { + "define": true + } + }, + "@ethersproject/wallet>@ethersproject/random": { + "packages": { + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/logger": true } }, "@keystonehq/bc-ur-registry-eth": { @@ -478,8 +526,8 @@ "@keystonehq/bc-ur-registry-eth": true, "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring": true, "@keystonehq/metamask-airgapped-keyring>@metamask/obs-store": true, - "@keystonehq/metamask-airgapped-keyring>rlp": true, "browserify>buffer": true, + "ethereumjs-util>rlp": true, "uuid": true, "webpack>events": true } @@ -489,12 +537,17 @@ "@ethereumjs/tx": true, "@ethereumjs/tx>@ethereumjs/util": true, "@keystonehq/bc-ur-registry-eth": true, + "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring>rlp": true, "@metamask/eth-trezor-keyring>hdkey": true, "browserify>buffer": true, - "eth-lattice-keyring>rlp": true, "uuid": true } }, + "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring>rlp": { + "globals": { + "TextEncoder": true + } + }, "@keystonehq/metamask-airgapped-keyring>@metamask/obs-store": { "packages": { "@keystonehq/metamask-airgapped-keyring>@metamask/obs-store>@metamask/safe-event-emitter": true, @@ -543,12 +596,6 @@ "@keystonehq/metamask-airgapped-keyring>@metamask/obs-store>through2>readable-stream>safe-buffer": true } }, - "@keystonehq/metamask-airgapped-keyring>rlp": { - "packages": { - "bn.js": true, - "browserify>buffer": true - } - }, "@lavamoat/lavadome-react": { "globals": { "Document.prototype": true, @@ -750,8 +797,8 @@ "@ethereumjs/tx>@ethereumjs/util": true, "@ethereumjs/tx>ethereum-cryptography": true, "@metamask/accounts-controller>@metamask/base-controller": true, - "@metamask/accounts-controller>@metamask/keyring-api": true, "@metamask/eth-snap-keyring": true, + "@metamask/keyring-api": true, "@metamask/keyring-controller": true, "@metamask/snaps-utils": true, "@metamask/utils": true, @@ -766,22 +813,6 @@ "immer": true } }, - "@metamask/accounts-controller>@metamask/keyring-api": { - "globals": { - "URL": true - }, - "packages": { - "@metamask/accounts-controller>@metamask/keyring-api>uuid": true, - "@metamask/keyring-api>bech32": true, - "@metamask/utils": true, - "superstruct": true - } - }, - "@metamask/accounts-controller>@metamask/keyring-api>uuid": { - "globals": { - "crypto": true - } - }, "@metamask/address-book-controller": { "packages": { "@metamask/address-book-controller>@metamask/controller-utils": true, @@ -826,7 +857,7 @@ "packages": { "@metamask/approval-controller>@metamask/base-controller": true, "@metamask/approval-controller>nanoid": true, - "@metamask/providers>@metamask/rpc-errors": true + "@metamask/rpc-errors": true } }, "@metamask/approval-controller>@metamask/base-controller": { @@ -870,7 +901,7 @@ "@metamask/controller-utils": true, "@metamask/eth-query": true, "@metamask/metamask-eth-abis": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/utils": true, "bn.js": true, "lodash": true, @@ -1028,9 +1059,9 @@ "console.error": true }, "packages": { - "@metamask/assets-controllers>async-mutex": true, "@metamask/eth-json-rpc-filters>@metamask/eth-query": true, "@metamask/eth-json-rpc-filters>@metamask/json-rpc-engine": true, + "@metamask/eth-json-rpc-filters>async-mutex": true, "@metamask/safe-event-emitter": true, "pify": true } @@ -1043,11 +1074,19 @@ }, "@metamask/eth-json-rpc-filters>@metamask/json-rpc-engine": { "packages": { - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, "@metamask/utils": true } }, + "@metamask/eth-json-rpc-filters>async-mutex": { + "globals": { + "setTimeout": true + }, + "packages": { + "@trezor/connect-web>tslib": true + } + }, "@metamask/eth-json-rpc-middleware": { "globals": { "URL": true, @@ -1058,7 +1097,7 @@ "@metamask/eth-json-rpc-middleware>@metamask/json-rpc-engine": true, "@metamask/eth-json-rpc-middleware>safe-stable-stringify": true, "@metamask/eth-sig-util": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/utils": true, "pify": true, "sass-loader>klona": true @@ -1072,14 +1111,14 @@ }, "@metamask/eth-json-rpc-middleware>@metamask/eth-json-rpc-provider>@metamask/json-rpc-engine": { "packages": { - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, "@metamask/utils": true } }, "@metamask/eth-json-rpc-middleware>@metamask/json-rpc-engine": { "packages": { - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, "@metamask/utils": true } @@ -1466,7 +1505,6 @@ "@metamask/eth-query": true, "@metamask/gas-fee-controller>@metamask/controller-utils": true, "bn.js": true, - "browserify>buffer": true, "uuid": true } }, @@ -1539,13 +1577,13 @@ "@metamask/keyring-controller": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask/assets-controllers>async-mutex": true, "@metamask/base-controller": true, "@metamask/browser-passworder": true, "@metamask/eth-sig-util": true, "@metamask/keyring-controller>@metamask/eth-hd-keyring": true, "@metamask/keyring-controller>@metamask/eth-simple-keyring": true, "@metamask/keyring-controller>ethereumjs-wallet": true, + "@metamask/name-controller>async-mutex": true, "@metamask/utils": true } }, @@ -1602,18 +1640,12 @@ "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util": { "packages": { "@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography": true, - "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util>rlp": true, "bn.js": true, "browserify>assert": true, "browserify>buffer": true, "browserify>insert-module-globals>is-buffer": true, - "ethereumjs-util>create-hash": true - } - }, - "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util>rlp": { - "packages": { - "bn.js": true, - "browserify>buffer": true + "ethereumjs-util>create-hash": true, + "ethereumjs-util>rlp": true } }, "@metamask/logging-controller": { @@ -1748,9 +1780,9 @@ "@metamask/network-controller>@metamask/controller-utils": true, "@metamask/network-controller>@metamask/eth-json-rpc-infura": true, "@metamask/network-controller>@metamask/eth-json-rpc-provider": true, - "@metamask/network-controller>@metamask/json-rpc-engine": true, "@metamask/network-controller>@metamask/swappable-obj-proxy": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, "@metamask/utils": true, "browserify>assert": true, "uuid": true @@ -1789,28 +1821,28 @@ "packages": { "@metamask/eth-json-rpc-middleware>@metamask/eth-json-rpc-provider": true, "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/json-rpc-engine": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/utils": true, "node-fetch": true } }, "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/json-rpc-engine": { "packages": { - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, "@metamask/utils": true } }, "@metamask/network-controller>@metamask/eth-json-rpc-provider": { "packages": { - "@metamask/network-controller>@metamask/json-rpc-engine": true, - "@metamask/safe-event-emitter": true + "@metamask/safe-event-emitter": true, + "@metamask/snaps-controllers>@metamask/json-rpc-engine": true } }, - "@metamask/network-controller>@metamask/json-rpc-engine": { + "@metamask/notification-controller": { "packages": { - "@metamask/providers>@metamask/rpc-errors": true, - "@metamask/safe-event-emitter": true, + "@metamask/base-controller": true, + "@metamask/notification-controller>nanoid": true, "@metamask/utils": true } }, @@ -1846,9 +1878,9 @@ "packages": { "@metamask/permission-controller>@metamask/base-controller": true, "@metamask/permission-controller>@metamask/controller-utils": true, - "@metamask/permission-controller>@metamask/json-rpc-engine": true, "@metamask/permission-controller>nanoid": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, "@metamask/utils": true, "deep-freeze-strict": true, "immer": true @@ -1880,13 +1912,6 @@ "eth-ens-namehash": true } }, - "@metamask/permission-controller>@metamask/json-rpc-engine": { - "packages": { - "@metamask/providers>@metamask/rpc-errors": true, - "@metamask/safe-event-emitter": true, - "@metamask/utils": true - } - }, "@metamask/permission-controller>nanoid": { "globals": { "crypto.getRandomValues": true @@ -1904,12 +1929,57 @@ }, "packages": { "@metamask/base-controller": true, - "@metamask/phishing-controller>@metamask/controller-utils": true, + "@metamask/controller-utils": true, "@metamask/phishing-warning>eth-phishing-detect": true, "punycode": true } }, - "@metamask/phishing-controller>@metamask/controller-utils": { + "@metamask/phishing-warning>eth-phishing-detect": { + "packages": { + "eslint>optionator>fast-levenshtein": true + } + }, + "@metamask/post-message-stream": { + "globals": { + "MessageEvent.prototype": true, + "WorkerGlobalScope": true, + "addEventListener": true, + "browser": true, + "chrome": true, + "location.origin": true, + "postMessage": true, + "removeEventListener": true + }, + "packages": { + "@metamask/utils": true, + "readable-stream": true + } + }, + "@metamask/ppom-validator": { + "globals": { + "URL": true, + "console.error": true, + "crypto": true + }, + "packages": { + "@metamask/eth-query>json-rpc-random-id": true, + "@metamask/ppom-validator>@metamask/base-controller": true, + "@metamask/ppom-validator>@metamask/controller-utils": true, + "@metamask/ppom-validator>crypto-js": true, + "@metamask/ppom-validator>elliptic": true, + "await-semaphore": true, + "browserify>buffer": true + } + }, + "@metamask/ppom-validator>@metamask/base-controller": { + "globals": { + "setTimeout": true + }, + "packages": { + "immer": true + } + }, + "@metamask/ppom-validator>@metamask/controller-utils": { "globals": { "URL": true, "console.error": true, @@ -1927,25 +1997,14 @@ "eth-ens-namehash": true } }, - "@metamask/phishing-warning>eth-phishing-detect": { - "packages": { - "eslint>optionator>fast-levenshtein": true - } - }, - "@metamask/post-message-stream": { + "@metamask/ppom-validator>crypto-js": { "globals": { - "MessageEvent.prototype": true, - "WorkerGlobalScope": true, - "addEventListener": true, - "browser": true, - "chrome": true, - "location.origin": true, - "postMessage": true, - "removeEventListener": true + "crypto": true, + "define": true, + "msCrypto": true }, "packages": { - "@metamask/utils": true, - "readable-stream": true + "browserify>browser-resolve": true } }, "@metamask/ppom-validator>elliptic": { @@ -1975,21 +2034,39 @@ "ethereumjs-util>ethereum-cryptography>hash.js": true } }, - "@metamask/providers>@metamask/rpc-errors": { + "@metamask/queued-request-controller": { "packages": { - "@metamask/utils": true, - "eth-rpc-errors>fast-safe-stringify": true + "@metamask/queued-request-controller>@metamask/base-controller": true, + "@metamask/rpc-errors": true, + "@metamask/selected-network-controller": true, + "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, + "@metamask/utils": true } }, - "@metamask/queued-request-controller": { + "@metamask/queued-request-controller>@metamask/base-controller": { + "globals": { + "setTimeout": true + }, + "packages": { + "immer": true + } + }, + "@metamask/rate-limit-controller": { + "globals": { + "setTimeout": true + }, "packages": { "@metamask/base-controller": true, - "@metamask/providers>@metamask/rpc-errors": true, - "@metamask/selected-network-controller": true, - "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, + "@metamask/rpc-errors": true, "@metamask/utils": true } }, + "@metamask/rpc-errors": { + "packages": { + "@metamask/utils": true, + "eth-rpc-errors>fast-safe-stringify": true + } + }, "@metamask/rpc-methods-flask>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2044,7 +2121,7 @@ "packages": { "@metamask/base-controller": true, "@metamask/logging-controller": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/signature-controller>@metamask/controller-utils": true, "@metamask/signature-controller>@metamask/message-manager": true, "@metamask/utils": true, @@ -2197,14 +2274,14 @@ "@ethersproject/providers": true, "@metamask/eth-query": true, "@metamask/metamask-eth-abis": true, + "@metamask/name-controller>async-mutex": true, "@metamask/network-controller": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/base-controller": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/controller-utils": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>async-mutex": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry": true, "@metamask/transaction-controller>@metamask/nonce-tracker": true, "@metamask/utils": true, @@ -2331,15 +2408,6 @@ "uuid": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>async-mutex": { - "globals": { - "clearTimeout": true, - "setTimeout": true - }, - "packages": { - "@trezor/connect-web>tslib": true - } - }, "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry": { "packages": { "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract": true, @@ -2398,23 +2466,114 @@ "define": true } }, + "@metamask/snaps-controllers": { + "globals": { + "DecompressionStream": true, + "URL": true, + "clearTimeout": true, + "document.getElementById": true, + "fetch.bind": true, + "setTimeout": true + }, + "packages": { + "@metamask/object-multiplex": true, + "@metamask/permission-controller": true, + "@metamask/post-message-stream": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-controllers>@metamask/base-controller": true, + "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, + "@metamask/snaps-controllers>@metamask/json-rpc-middleware-stream": true, + "@metamask/snaps-controllers>@xstate/fsm": true, + "@metamask/snaps-controllers>concat-stream": true, + "@metamask/snaps-controllers>get-npm-tarball-url": true, + "@metamask/snaps-controllers>nanoid": true, + "@metamask/snaps-controllers>readable-web-to-node-stream": true, + "@metamask/snaps-controllers>tar-stream": true, + "@metamask/snaps-rpc-methods": true, + "@metamask/snaps-sdk": true, + "@metamask/snaps-utils": true, + "@metamask/snaps-utils>@metamask/snaps-registry": true, + "@metamask/utils": true, + "browserify>browserify-zlib": true, + "eslint>fast-deep-equal": true, + "readable-stream": true + } + }, "@metamask/snaps-controllers-flask>nanoid": { "globals": { "crypto.getRandomValues": true } }, + "@metamask/snaps-controllers>@metamask/base-controller": { + "globals": { + "setTimeout": true + }, + "packages": { + "immer": true + } + }, "@metamask/snaps-controllers>@metamask/json-rpc-engine": { "packages": { - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, "@metamask/utils": true } }, + "@metamask/snaps-controllers>@metamask/json-rpc-middleware-stream": { + "globals": { + "console.warn": true, + "setTimeout": true + }, + "packages": { + "@metamask/safe-event-emitter": true, + "readable-stream": true + } + }, + "@metamask/snaps-controllers>concat-stream": { + "packages": { + "browserify>buffer": true, + "browserify>concat-stream>typedarray": true, + "pumpify>inherits": true, + "readable-stream": true, + "terser>source-map-support>buffer-from": true + } + }, "@metamask/snaps-controllers>nanoid": { "globals": { "crypto.getRandomValues": true } }, + "@metamask/snaps-controllers>readable-web-to-node-stream": { + "packages": { + "readable-stream": true + } + }, + "@metamask/snaps-controllers>tar-stream": { + "packages": { + "@metamask/snaps-controllers>tar-stream>b4a": true, + "@metamask/snaps-controllers>tar-stream>fast-fifo": true, + "@metamask/snaps-controllers>tar-stream>streamx": true, + "browserify>browser-resolve": true + } + }, + "@metamask/snaps-controllers>tar-stream>b4a": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + } + }, + "@metamask/snaps-controllers>tar-stream>streamx": { + "packages": { + "@metamask/snaps-controllers>tar-stream>fast-fifo": true, + "@metamask/snaps-controllers>tar-stream>streamx>queue-tick": true, + "webpack>events": true + } + }, + "@metamask/snaps-controllers>tar-stream>streamx>queue-tick": { + "globals": { + "queueMicrotask": true + } + }, "@metamask/snaps-execution-environments": { "globals": { "document.getElementById": true @@ -2427,8 +2586,8 @@ }, "@metamask/snaps-rpc-methods": { "packages": { - "@metamask/providers>@metamask/rpc-errors": true, - "@metamask/snaps-rpc-methods>@metamask/permission-controller": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, "@metamask/snaps-sdk": true, "@metamask/snaps-sdk>@metamask/key-tree": true, "@metamask/snaps-utils": true, @@ -2437,33 +2596,12 @@ "superstruct": true } }, - "@metamask/snaps-rpc-methods>@metamask/permission-controller": { - "globals": { - "console.error": true - }, - "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/providers>@metamask/rpc-errors": true, - "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, - "@metamask/snaps-rpc-methods>@metamask/permission-controller>nanoid": true, - "@metamask/utils": true, - "deep-freeze-strict": true, - "immer": true - } - }, - "@metamask/snaps-rpc-methods>@metamask/permission-controller>nanoid": { - "globals": { - "crypto.getRandomValues": true - } - }, "@metamask/snaps-sdk": { "globals": { "fetch": true }, "packages": { - "@metamask/providers>@metamask/rpc-errors": true, - "@metamask/snaps-sdk>fast-xml-parser": true, + "@metamask/rpc-errors": true, "@metamask/utils": true, "superstruct": true } @@ -2477,15 +2615,6 @@ "@noble/hashes": true } }, - "@metamask/snaps-sdk>fast-xml-parser": { - "globals": { - "entityName": true, - "val": true - }, - "packages": { - "@metamask/snaps-sdk>fast-xml-parser>strnum": true - } - }, "@metamask/snaps-utils": { "globals": { "File": true, @@ -2502,13 +2631,14 @@ "fetch": true }, "packages": { - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, "@metamask/snaps-sdk": true, "@metamask/snaps-sdk>@metamask/key-tree": true, - "@metamask/snaps-utils>@metamask/permission-controller": true, "@metamask/snaps-utils>@metamask/slip44": true, "@metamask/snaps-utils>cron-parser": true, "@metamask/snaps-utils>fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, "@metamask/snaps-utils>marked": true, "@metamask/snaps-utils>rfdc": true, "@metamask/snaps-utils>validate-npm-package-name": true, @@ -2520,24 +2650,12 @@ "superstruct": true } }, - "@metamask/snaps-utils>@metamask/permission-controller": { - "globals": { - "console.error": true - }, + "@metamask/snaps-utils>@metamask/snaps-registry": { "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/providers>@metamask/rpc-errors": true, - "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, - "@metamask/snaps-utils>@metamask/permission-controller>nanoid": true, + "@metamask/message-signing-snap>@noble/curves": true, "@metamask/utils": true, - "deep-freeze-strict": true, - "immer": true - } - }, - "@metamask/snaps-utils>@metamask/permission-controller>nanoid": { - "globals": { - "crypto.getRandomValues": true + "@noble/hashes": true, + "superstruct": true } }, "@metamask/snaps-utils>cron-parser": { @@ -2546,6 +2664,15 @@ "luxon": true } }, + "@metamask/snaps-utils>fast-xml-parser": { + "globals": { + "entityName": true, + "val": true + }, + "packages": { + "@metamask/snaps-utils>fast-xml-parser>strnum": true + } + }, "@metamask/snaps-utils>marked": { "globals": { "console.error": true, @@ -2569,14 +2696,6 @@ "semver": true } }, - "@metamask/test-bundler>@ethersproject/abstract-provider": { - "packages": { - "@ethersproject/abi>@ethersproject/bytes": true, - "@ethersproject/abi>@ethersproject/logger": true, - "@ethersproject/abi>@ethersproject/properties": true, - "@ethersproject/bignumber": true - } - }, "@metamask/test-bundler>@ethersproject/networks": { "packages": { "@ethersproject/abi>@ethersproject/logger": true @@ -2599,12 +2718,12 @@ "@metamask/eth-query": true, "@metamask/gas-fee-controller": true, "@metamask/metamask-eth-abis": true, + "@metamask/name-controller>async-mutex": true, "@metamask/network-controller": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/transaction-controller>@metamask/base-controller": true, "@metamask/transaction-controller>@metamask/controller-utils": true, "@metamask/transaction-controller>@metamask/nonce-tracker": true, - "@metamask/transaction-controller>async-mutex": true, "@metamask/utils": true, "bn.js": true, "browserify>buffer": true, @@ -2657,15 +2776,6 @@ "@trezor/connect-web>tslib": true } }, - "@metamask/transaction-controller>async-mutex": { - "globals": { - "clearTimeout": true, - "setTimeout": true - }, - "packages": { - "@trezor/connect-web>tslib": true - } - }, "@metamask/user-operation-controller": { "globals": { "fetch": true @@ -2675,7 +2785,7 @@ "@metamask/base-controller": true, "@metamask/eth-query": true, "@metamask/gas-fee-controller": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/transaction-controller": true, "@metamask/user-operation-controller>@metamask/controller-utils": true, "@metamask/utils": true, @@ -2948,6 +3058,11 @@ "browserify>process": true } }, + "@storybook/addon-docs>remark-external-links>mdast-util-definitions": { + "packages": { + "react-markdown>unist-util-visit": true + } + }, "@storybook/addon-knobs>qs": { "packages": { "string.prototype.matchall>side-channel": true @@ -3340,6 +3455,11 @@ "browserify>url": true } }, + "browserify>path-browserify": { + "packages": { + "browserify>process": true + } + }, "browserify>process": { "globals": { "clearTimeout": true, @@ -3707,9 +3827,9 @@ "eth-lattice-keyring>gridplus-sdk>bitwise": true, "eth-lattice-keyring>gridplus-sdk>borc": true, "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser": true, + "eth-lattice-keyring>gridplus-sdk>rlp": true, "eth-lattice-keyring>gridplus-sdk>secp256k1": true, "eth-lattice-keyring>gridplus-sdk>uuid": true, - "eth-lattice-keyring>rlp": true, "ethereumjs-util>ethereum-cryptography>bs58check": true, "ethereumjs-util>ethereum-cryptography>hash.js": true, "lodash": true @@ -3808,6 +3928,11 @@ "ganache>abstract-level>buffer": true } }, + "eth-lattice-keyring>gridplus-sdk>rlp": { + "globals": { + "TextEncoder": true + } + }, "eth-lattice-keyring>gridplus-sdk>secp256k1": { "packages": { "@metamask/ppom-validator>elliptic": true @@ -4158,6 +4283,11 @@ "string.prototype.matchall>has-symbols": true } }, + "he": { + "globals": { + "define": true + } + }, "json-rpc-engine": { "packages": { "eth-rpc-errors": true, @@ -4533,6 +4663,110 @@ "react": true } }, + "react-markdown": { + "globals": { + "console.warn": true + }, + "packages": { + "prop-types": true, + "react": true, + "react-markdown>comma-separated-tokens": true, + "react-markdown>property-information": true, + "react-markdown>react-is": true, + "react-markdown>remark-parse": true, + "react-markdown>remark-rehype": true, + "react-markdown>space-separated-tokens": true, + "react-markdown>style-to-object": true, + "react-markdown>unified": true, + "react-markdown>unist-util-visit": true, + "react-markdown>vfile": true + } + }, + "react-markdown>property-information": { + "packages": { + "watchify>xtend": true + } + }, + "react-markdown>react-is": { + "globals": { + "console": true + } + }, + "react-markdown>remark-parse": { + "packages": { + "react-markdown>remark-parse>mdast-util-from-markdown": true + } + }, + "react-markdown>remark-parse>mdast-util-from-markdown": { + "packages": { + "react-markdown>remark-parse>mdast-util-from-markdown>mdast-util-to-string": true, + "react-markdown>remark-parse>mdast-util-from-markdown>micromark": true, + "react-markdown>remark-parse>mdast-util-from-markdown>unist-util-stringify-position": true, + "react-syntax-highlighter>refractor>parse-entities": true + } + }, + "react-markdown>remark-parse>mdast-util-from-markdown>micromark": { + "packages": { + "react-syntax-highlighter>refractor>parse-entities": true + } + }, + "react-markdown>remark-rehype": { + "packages": { + "react-markdown>remark-rehype>mdast-util-to-hast": true + } + }, + "react-markdown>remark-rehype>mdast-util-to-hast": { + "globals": { + "console.warn": true + }, + "packages": { + "@storybook/addon-docs>remark-external-links>mdast-util-definitions": true, + "react-markdown>remark-rehype>mdast-util-to-hast>mdurl": true, + "react-markdown>remark-rehype>mdast-util-to-hast>unist-builder": true, + "react-markdown>remark-rehype>mdast-util-to-hast>unist-util-generated": true, + "react-markdown>remark-rehype>mdast-util-to-hast>unist-util-position": true, + "react-markdown>unist-util-visit": true + } + }, + "react-markdown>style-to-object": { + "packages": { + "react-markdown>style-to-object>inline-style-parser": true + } + }, + "react-markdown>unified": { + "packages": { + "mocha>yargs-unparser>is-plain-obj": true, + "react-markdown>unified>bail": true, + "react-markdown>unified>extend": true, + "react-markdown>unified>is-buffer": true, + "react-markdown>unified>trough": true, + "react-markdown>vfile": true + } + }, + "react-markdown>unist-util-visit": { + "packages": { + "react-markdown>unist-util-visit>unist-util-visit-parents": true + } + }, + "react-markdown>unist-util-visit>unist-util-visit-parents": { + "packages": { + "react-markdown>unist-util-visit>unist-util-is": true + } + }, + "react-markdown>vfile": { + "packages": { + "browserify>path-browserify": true, + "browserify>process": true, + "react-markdown>vfile>is-buffer": true, + "react-markdown>vfile>vfile-message": true, + "vinyl>replace-ext": true + } + }, + "react-markdown>vfile>vfile-message": { + "packages": { + "react-markdown>vfile>unist-util-stringify-position": true + } + }, "react-popper": { "globals": { "document": true @@ -4691,6 +4925,11 @@ "react": true } }, + "react-syntax-highlighter>refractor>parse-entities": { + "globals": { + "document.createElement": true + } + }, "react-tippy": { "globals": { "Element": true, @@ -4936,12 +5175,22 @@ "define": true } }, + "terser>source-map-support>buffer-from": { + "packages": { + "browserify>buffer": true + } + }, "uuid": { "globals": { "crypto": true, "msCrypto": true } }, + "vinyl>replace-ext": { + "packages": { + "browserify>path-browserify": true + } + }, "web3": { "globals": { "XMLHttpRequest": true diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index a79a242eb914..e1bbe63dca11 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -227,12 +227,12 @@ "@ethersproject/abi>@ethersproject/address": true, "@ethersproject/abi>@ethersproject/bytes": true, "@ethersproject/abi>@ethersproject/constants": true, - "@ethersproject/abi>@ethersproject/hash": true, "@ethersproject/abi>@ethersproject/keccak256": true, "@ethersproject/abi>@ethersproject/logger": true, "@ethersproject/abi>@ethersproject/properties": true, "@ethersproject/abi>@ethersproject/strings": true, - "@ethersproject/bignumber": true + "@ethersproject/bignumber": true, + "@ethersproject/hash": true } }, "@ethersproject/abi>@ethersproject/address": { @@ -254,18 +254,6 @@ "@ethersproject/bignumber": true } }, - "@ethersproject/abi>@ethersproject/hash": { - "packages": { - "@ethersproject/abi>@ethersproject/address": true, - "@ethersproject/abi>@ethersproject/bytes": true, - "@ethersproject/abi>@ethersproject/keccak256": true, - "@ethersproject/abi>@ethersproject/logger": true, - "@ethersproject/abi>@ethersproject/properties": true, - "@ethersproject/abi>@ethersproject/strings": true, - "@ethersproject/bignumber": true, - "@ethersproject/providers>@ethersproject/base64": true - } - }, "@ethersproject/abi>@ethersproject/keccak256": { "packages": { "@ethersproject/abi>@ethersproject/bytes": true, @@ -307,9 +295,36 @@ "@ethersproject/abi>@ethersproject/logger": true, "@ethersproject/abi>@ethersproject/properties": true, "@ethersproject/bignumber": true, - "@ethersproject/hdnode>@ethersproject/abstract-signer": true, + "@ethersproject/hash>@ethersproject/abstract-signer": true, "@ethersproject/hdnode>@ethersproject/transactions": true, - "@metamask/test-bundler>@ethersproject/abstract-provider": true + "@ethersproject/wallet>@ethersproject/abstract-provider": true + } + }, + "@ethersproject/hash": { + "packages": { + "@ethersproject/abi>@ethersproject/address": true, + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/keccak256": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "@ethersproject/abi>@ethersproject/strings": true, + "@ethersproject/bignumber": true, + "@ethersproject/hash>@ethersproject/base64": true + } + }, + "@ethersproject/hash>@ethersproject/abstract-signer": { + "packages": { + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true + } + }, + "@ethersproject/hash>@ethersproject/base64": { + "globals": { + "atob": true, + "btoa": true + }, + "packages": { + "@ethersproject/abi>@ethersproject/bytes": true } }, "@ethersproject/hdnode": { @@ -327,12 +342,6 @@ "@ethersproject/hdnode>@ethersproject/wordlists": true } }, - "@ethersproject/hdnode>@ethersproject/abstract-signer": { - "packages": { - "@ethersproject/abi>@ethersproject/logger": true, - "@ethersproject/abi>@ethersproject/properties": true - } - }, "@ethersproject/hdnode>@ethersproject/basex": { "packages": { "@ethersproject/abi>@ethersproject/bytes": true, @@ -376,10 +385,10 @@ "@ethersproject/hdnode>@ethersproject/wordlists": { "packages": { "@ethersproject/abi>@ethersproject/bytes": true, - "@ethersproject/abi>@ethersproject/hash": true, "@ethersproject/abi>@ethersproject/logger": true, "@ethersproject/abi>@ethersproject/properties": true, - "@ethersproject/abi>@ethersproject/strings": true + "@ethersproject/abi>@ethersproject/strings": true, + "@ethersproject/hash": true } }, "@ethersproject/providers": { @@ -396,39 +405,26 @@ "@ethersproject/abi>@ethersproject/address": true, "@ethersproject/abi>@ethersproject/bytes": true, "@ethersproject/abi>@ethersproject/constants": true, - "@ethersproject/abi>@ethersproject/hash": true, "@ethersproject/abi>@ethersproject/logger": true, "@ethersproject/abi>@ethersproject/properties": true, "@ethersproject/abi>@ethersproject/strings": true, "@ethersproject/bignumber": true, - "@ethersproject/hdnode>@ethersproject/abstract-signer": true, + "@ethersproject/hash": true, + "@ethersproject/hash>@ethersproject/abstract-signer": true, + "@ethersproject/hash>@ethersproject/base64": true, "@ethersproject/hdnode>@ethersproject/basex": true, "@ethersproject/hdnode>@ethersproject/sha2": true, "@ethersproject/hdnode>@ethersproject/transactions": true, - "@ethersproject/providers>@ethersproject/base64": true, - "@ethersproject/providers>@ethersproject/random": true, "@ethersproject/providers>@ethersproject/web": true, "@ethersproject/providers>bech32": true, - "@metamask/test-bundler>@ethersproject/abstract-provider": true, + "@ethersproject/wallet>@ethersproject/abstract-provider": true, + "@ethersproject/wallet>@ethersproject/random": true, "@metamask/test-bundler>@ethersproject/networks": true } }, - "@ethersproject/providers>@ethersproject/base64": { - "globals": { - "atob": true, - "btoa": true - }, - "packages": { - "@ethersproject/abi>@ethersproject/bytes": true - } - }, "@ethersproject/providers>@ethersproject/random": { "globals": { "crypto.getRandomValues": true - }, - "packages": { - "@ethersproject/abi>@ethersproject/bytes": true, - "@ethersproject/abi>@ethersproject/logger": true } }, "@ethersproject/providers>@ethersproject/rlp": { @@ -448,7 +444,59 @@ "@ethersproject/abi>@ethersproject/logger": true, "@ethersproject/abi>@ethersproject/properties": true, "@ethersproject/abi>@ethersproject/strings": true, - "@ethersproject/providers>@ethersproject/base64": true + "@ethersproject/hash>@ethersproject/base64": true + } + }, + "@ethersproject/wallet": { + "packages": { + "@ethersproject/abi>@ethersproject/address": true, + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/keccak256": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "@ethersproject/hash": true, + "@ethersproject/hash>@ethersproject/abstract-signer": true, + "@ethersproject/hdnode": true, + "@ethersproject/hdnode>@ethersproject/signing-key": true, + "@ethersproject/hdnode>@ethersproject/transactions": true, + "@ethersproject/wallet>@ethersproject/abstract-provider": true, + "@ethersproject/wallet>@ethersproject/json-wallets": true, + "@ethersproject/wallet>@ethersproject/random": true + } + }, + "@ethersproject/wallet>@ethersproject/abstract-provider": { + "packages": { + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "@ethersproject/bignumber": true + } + }, + "@ethersproject/wallet>@ethersproject/json-wallets": { + "packages": { + "@ethersproject/abi>@ethersproject/address": true, + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/keccak256": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "@ethersproject/abi>@ethersproject/strings": true, + "@ethersproject/hdnode": true, + "@ethersproject/hdnode>@ethersproject/pbkdf2": true, + "@ethersproject/hdnode>@ethersproject/transactions": true, + "@ethersproject/wallet>@ethersproject/json-wallets>aes-js": true, + "@ethersproject/wallet>@ethersproject/random": true, + "ethereumjs-util>ethereum-cryptography>scrypt-js": true + } + }, + "@ethersproject/wallet>@ethersproject/json-wallets>aes-js": { + "globals": { + "define": true + } + }, + "@ethersproject/wallet>@ethersproject/random": { + "packages": { + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/logger": true } }, "@keystonehq/bc-ur-registry-eth": { @@ -478,8 +526,8 @@ "@keystonehq/bc-ur-registry-eth": true, "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring": true, "@keystonehq/metamask-airgapped-keyring>@metamask/obs-store": true, - "@keystonehq/metamask-airgapped-keyring>rlp": true, "browserify>buffer": true, + "ethereumjs-util>rlp": true, "uuid": true, "webpack>events": true } @@ -489,12 +537,17 @@ "@ethereumjs/tx": true, "@ethereumjs/tx>@ethereumjs/util": true, "@keystonehq/bc-ur-registry-eth": true, + "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring>rlp": true, "@metamask/eth-trezor-keyring>hdkey": true, "browserify>buffer": true, - "eth-lattice-keyring>rlp": true, "uuid": true } }, + "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring>rlp": { + "globals": { + "TextEncoder": true + } + }, "@keystonehq/metamask-airgapped-keyring>@metamask/obs-store": { "packages": { "@keystonehq/metamask-airgapped-keyring>@metamask/obs-store>@metamask/safe-event-emitter": true, @@ -543,12 +596,6 @@ "@keystonehq/metamask-airgapped-keyring>@metamask/obs-store>through2>readable-stream>safe-buffer": true } }, - "@keystonehq/metamask-airgapped-keyring>rlp": { - "packages": { - "bn.js": true, - "browserify>buffer": true - } - }, "@lavamoat/lavadome-react": { "globals": { "Document.prototype": true, @@ -750,8 +797,8 @@ "@ethereumjs/tx>@ethereumjs/util": true, "@ethereumjs/tx>ethereum-cryptography": true, "@metamask/accounts-controller>@metamask/base-controller": true, - "@metamask/accounts-controller>@metamask/keyring-api": true, "@metamask/eth-snap-keyring": true, + "@metamask/keyring-api": true, "@metamask/keyring-controller": true, "@metamask/snaps-utils": true, "@metamask/utils": true, @@ -766,22 +813,6 @@ "immer": true } }, - "@metamask/accounts-controller>@metamask/keyring-api": { - "globals": { - "URL": true - }, - "packages": { - "@metamask/accounts-controller>@metamask/keyring-api>uuid": true, - "@metamask/keyring-api>bech32": true, - "@metamask/utils": true, - "superstruct": true - } - }, - "@metamask/accounts-controller>@metamask/keyring-api>uuid": { - "globals": { - "crypto": true - } - }, "@metamask/address-book-controller": { "packages": { "@metamask/address-book-controller>@metamask/controller-utils": true, @@ -826,7 +857,7 @@ "packages": { "@metamask/approval-controller>@metamask/base-controller": true, "@metamask/approval-controller>nanoid": true, - "@metamask/providers>@metamask/rpc-errors": true + "@metamask/rpc-errors": true } }, "@metamask/approval-controller>@metamask/base-controller": { @@ -870,7 +901,7 @@ "@metamask/controller-utils": true, "@metamask/eth-query": true, "@metamask/metamask-eth-abis": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/utils": true, "bn.js": true, "lodash": true, @@ -1028,9 +1059,9 @@ "console.error": true }, "packages": { - "@metamask/assets-controllers>async-mutex": true, "@metamask/eth-json-rpc-filters>@metamask/eth-query": true, "@metamask/eth-json-rpc-filters>@metamask/json-rpc-engine": true, + "@metamask/eth-json-rpc-filters>async-mutex": true, "@metamask/safe-event-emitter": true, "pify": true } @@ -1043,11 +1074,19 @@ }, "@metamask/eth-json-rpc-filters>@metamask/json-rpc-engine": { "packages": { - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, "@metamask/utils": true } }, + "@metamask/eth-json-rpc-filters>async-mutex": { + "globals": { + "setTimeout": true + }, + "packages": { + "@trezor/connect-web>tslib": true + } + }, "@metamask/eth-json-rpc-middleware": { "globals": { "URL": true, @@ -1058,7 +1097,7 @@ "@metamask/eth-json-rpc-middleware>@metamask/json-rpc-engine": true, "@metamask/eth-json-rpc-middleware>safe-stable-stringify": true, "@metamask/eth-sig-util": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/utils": true, "pify": true, "sass-loader>klona": true @@ -1072,14 +1111,14 @@ }, "@metamask/eth-json-rpc-middleware>@metamask/eth-json-rpc-provider>@metamask/json-rpc-engine": { "packages": { - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, "@metamask/utils": true } }, "@metamask/eth-json-rpc-middleware>@metamask/json-rpc-engine": { "packages": { - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, "@metamask/utils": true } @@ -1466,7 +1505,6 @@ "@metamask/eth-query": true, "@metamask/gas-fee-controller>@metamask/controller-utils": true, "bn.js": true, - "browserify>buffer": true, "uuid": true } }, @@ -1539,13 +1577,13 @@ "@metamask/keyring-controller": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask/assets-controllers>async-mutex": true, "@metamask/base-controller": true, "@metamask/browser-passworder": true, "@metamask/eth-sig-util": true, "@metamask/keyring-controller>@metamask/eth-hd-keyring": true, "@metamask/keyring-controller>@metamask/eth-simple-keyring": true, "@metamask/keyring-controller>ethereumjs-wallet": true, + "@metamask/name-controller>async-mutex": true, "@metamask/utils": true } }, @@ -1602,18 +1640,12 @@ "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util": { "packages": { "@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography": true, - "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util>rlp": true, "bn.js": true, "browserify>assert": true, "browserify>buffer": true, "browserify>insert-module-globals>is-buffer": true, - "ethereumjs-util>create-hash": true - } - }, - "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util>rlp": { - "packages": { - "bn.js": true, - "browserify>buffer": true + "ethereumjs-util>create-hash": true, + "ethereumjs-util>rlp": true } }, "@metamask/logging-controller": { @@ -1748,9 +1780,9 @@ "@metamask/network-controller>@metamask/controller-utils": true, "@metamask/network-controller>@metamask/eth-json-rpc-infura": true, "@metamask/network-controller>@metamask/eth-json-rpc-provider": true, - "@metamask/network-controller>@metamask/json-rpc-engine": true, "@metamask/network-controller>@metamask/swappable-obj-proxy": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, "@metamask/utils": true, "browserify>assert": true, "uuid": true @@ -1789,56 +1821,29 @@ "packages": { "@metamask/eth-json-rpc-middleware>@metamask/eth-json-rpc-provider": true, "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/json-rpc-engine": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/utils": true, "node-fetch": true } }, "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/json-rpc-engine": { "packages": { - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, "@metamask/utils": true } }, "@metamask/network-controller>@metamask/eth-json-rpc-provider": { "packages": { - "@metamask/network-controller>@metamask/json-rpc-engine": true, - "@metamask/safe-event-emitter": true - } - }, - "@metamask/network-controller>@metamask/json-rpc-engine": { - "packages": { - "@metamask/providers>@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, - "@metamask/utils": true + "@metamask/snaps-controllers>@metamask/json-rpc-engine": true } }, "@metamask/notification-controller": { "packages": { - "@metamask/notification-controller>@metamask/base-controller": true, - "@metamask/notification-controller>@metamask/utils": true, - "@metamask/notification-controller>nanoid": true - } - }, - "@metamask/notification-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, - "@metamask/notification-controller>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true + "@metamask/base-controller": true, + "@metamask/notification-controller>nanoid": true, + "@metamask/utils": true } }, "@metamask/notification-controller>nanoid": { @@ -1873,9 +1878,9 @@ "packages": { "@metamask/permission-controller>@metamask/base-controller": true, "@metamask/permission-controller>@metamask/controller-utils": true, - "@metamask/permission-controller>@metamask/json-rpc-engine": true, "@metamask/permission-controller>nanoid": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, "@metamask/utils": true, "deep-freeze-strict": true, "immer": true @@ -1907,13 +1912,6 @@ "eth-ens-namehash": true } }, - "@metamask/permission-controller>@metamask/json-rpc-engine": { - "packages": { - "@metamask/providers>@metamask/rpc-errors": true, - "@metamask/safe-event-emitter": true, - "@metamask/utils": true - } - }, "@metamask/permission-controller>nanoid": { "globals": { "crypto.getRandomValues": true @@ -1931,29 +1929,11 @@ }, "packages": { "@metamask/base-controller": true, - "@metamask/phishing-controller>@metamask/controller-utils": true, + "@metamask/controller-utils": true, "@metamask/phishing-warning>eth-phishing-detect": true, "punycode": true } }, - "@metamask/phishing-controller>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ethjs>@metamask/ethjs-unit": true, - "@metamask/utils": true, - "bn.js": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true - } - }, "@metamask/phishing-warning>eth-phishing-detect": { "packages": { "eslint>optionator>fast-levenshtein": true @@ -2054,31 +2034,39 @@ "ethereumjs-util>ethereum-cryptography>hash.js": true } }, - "@metamask/providers>@metamask/rpc-errors": { - "packages": { - "@metamask/utils": true, - "eth-rpc-errors>fast-safe-stringify": true - } - }, "@metamask/queued-request-controller": { "packages": { - "@metamask/base-controller": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/queued-request-controller>@metamask/base-controller": true, + "@metamask/rpc-errors": true, "@metamask/selected-network-controller": true, "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, "@metamask/utils": true } }, + "@metamask/queued-request-controller>@metamask/base-controller": { + "globals": { + "setTimeout": true + }, + "packages": { + "immer": true + } + }, "@metamask/rate-limit-controller": { "globals": { "setTimeout": true }, "packages": { "@metamask/base-controller": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/utils": true } }, + "@metamask/rpc-errors": { + "packages": { + "@metamask/utils": true, + "eth-rpc-errors>fast-safe-stringify": true + } + }, "@metamask/rpc-methods-flask>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2133,7 +2121,7 @@ "packages": { "@metamask/base-controller": true, "@metamask/logging-controller": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/signature-controller>@metamask/controller-utils": true, "@metamask/signature-controller>@metamask/message-manager": true, "@metamask/utils": true, @@ -2286,14 +2274,14 @@ "@ethersproject/providers": true, "@metamask/eth-query": true, "@metamask/metamask-eth-abis": true, + "@metamask/name-controller>async-mutex": true, "@metamask/network-controller": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/base-controller": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/controller-utils": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>async-mutex": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry": true, "@metamask/transaction-controller>@metamask/nonce-tracker": true, "@metamask/utils": true, @@ -2420,15 +2408,6 @@ "uuid": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>async-mutex": { - "globals": { - "clearTimeout": true, - "setTimeout": true - }, - "packages": { - "@trezor/connect-web>tslib": true - } - }, "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry": { "packages": { "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract": true, @@ -2491,21 +2470,19 @@ "globals": { "DecompressionStream": true, "URL": true, - "chrome.offscreen.createDocument": true, - "chrome.offscreen.hasDocument": true, "clearTimeout": true, "document.getElementById": true, "fetch.bind": true, "setTimeout": true }, "packages": { - "@metamask/base-controller": true, "@metamask/object-multiplex": true, + "@metamask/permission-controller": true, "@metamask/post-message-stream": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-controllers>@metamask/base-controller": true, "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, "@metamask/snaps-controllers>@metamask/json-rpc-middleware-stream": true, - "@metamask/snaps-controllers>@metamask/permission-controller": true, "@metamask/snaps-controllers>@xstate/fsm": true, "@metamask/snaps-controllers>concat-stream": true, "@metamask/snaps-controllers>get-npm-tarball-url": true, @@ -2527,9 +2504,17 @@ "crypto.getRandomValues": true } }, + "@metamask/snaps-controllers>@metamask/base-controller": { + "globals": { + "setTimeout": true + }, + "packages": { + "immer": true + } + }, "@metamask/snaps-controllers>@metamask/json-rpc-engine": { "packages": { - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, "@metamask/utils": true } @@ -2544,21 +2529,6 @@ "readable-stream": true } }, - "@metamask/snaps-controllers>@metamask/permission-controller": { - "globals": { - "console.error": true - }, - "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/providers>@metamask/rpc-errors": true, - "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, - "@metamask/snaps-controllers>nanoid": true, - "@metamask/utils": true, - "deep-freeze-strict": true, - "immer": true - } - }, "@metamask/snaps-controllers>concat-stream": { "packages": { "browserify>buffer": true, @@ -2616,8 +2586,8 @@ }, "@metamask/snaps-rpc-methods": { "packages": { - "@metamask/providers>@metamask/rpc-errors": true, - "@metamask/snaps-rpc-methods>@metamask/permission-controller": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, "@metamask/snaps-sdk": true, "@metamask/snaps-sdk>@metamask/key-tree": true, "@metamask/snaps-utils": true, @@ -2626,33 +2596,12 @@ "superstruct": true } }, - "@metamask/snaps-rpc-methods>@metamask/permission-controller": { - "globals": { - "console.error": true - }, - "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/providers>@metamask/rpc-errors": true, - "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, - "@metamask/snaps-rpc-methods>@metamask/permission-controller>nanoid": true, - "@metamask/utils": true, - "deep-freeze-strict": true, - "immer": true - } - }, - "@metamask/snaps-rpc-methods>@metamask/permission-controller>nanoid": { - "globals": { - "crypto.getRandomValues": true - } - }, "@metamask/snaps-sdk": { "globals": { "fetch": true }, "packages": { - "@metamask/providers>@metamask/rpc-errors": true, - "@metamask/snaps-sdk>fast-xml-parser": true, + "@metamask/rpc-errors": true, "@metamask/utils": true, "superstruct": true } @@ -2666,15 +2615,6 @@ "@noble/hashes": true } }, - "@metamask/snaps-sdk>fast-xml-parser": { - "globals": { - "entityName": true, - "val": true - }, - "packages": { - "@metamask/snaps-sdk>fast-xml-parser>strnum": true - } - }, "@metamask/snaps-utils": { "globals": { "File": true, @@ -2691,13 +2631,14 @@ "fetch": true }, "packages": { - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, "@metamask/snaps-sdk": true, "@metamask/snaps-sdk>@metamask/key-tree": true, - "@metamask/snaps-utils>@metamask/permission-controller": true, "@metamask/snaps-utils>@metamask/slip44": true, "@metamask/snaps-utils>cron-parser": true, "@metamask/snaps-utils>fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, "@metamask/snaps-utils>marked": true, "@metamask/snaps-utils>rfdc": true, "@metamask/snaps-utils>validate-npm-package-name": true, @@ -2709,26 +2650,6 @@ "superstruct": true } }, - "@metamask/snaps-utils>@metamask/permission-controller": { - "globals": { - "console.error": true - }, - "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/providers>@metamask/rpc-errors": true, - "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, - "@metamask/snaps-utils>@metamask/permission-controller>nanoid": true, - "@metamask/utils": true, - "deep-freeze-strict": true, - "immer": true - } - }, - "@metamask/snaps-utils>@metamask/permission-controller>nanoid": { - "globals": { - "crypto.getRandomValues": true - } - }, "@metamask/snaps-utils>@metamask/snaps-registry": { "packages": { "@metamask/message-signing-snap>@noble/curves": true, @@ -2743,6 +2664,15 @@ "luxon": true } }, + "@metamask/snaps-utils>fast-xml-parser": { + "globals": { + "entityName": true, + "val": true + }, + "packages": { + "@metamask/snaps-utils>fast-xml-parser>strnum": true + } + }, "@metamask/snaps-utils>marked": { "globals": { "console.error": true, @@ -2766,14 +2696,6 @@ "semver": true } }, - "@metamask/test-bundler>@ethersproject/abstract-provider": { - "packages": { - "@ethersproject/abi>@ethersproject/bytes": true, - "@ethersproject/abi>@ethersproject/logger": true, - "@ethersproject/abi>@ethersproject/properties": true, - "@ethersproject/bignumber": true - } - }, "@metamask/test-bundler>@ethersproject/networks": { "packages": { "@ethersproject/abi>@ethersproject/logger": true @@ -2796,12 +2718,12 @@ "@metamask/eth-query": true, "@metamask/gas-fee-controller": true, "@metamask/metamask-eth-abis": true, + "@metamask/name-controller>async-mutex": true, "@metamask/network-controller": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/transaction-controller>@metamask/base-controller": true, "@metamask/transaction-controller>@metamask/controller-utils": true, "@metamask/transaction-controller>@metamask/nonce-tracker": true, - "@metamask/transaction-controller>async-mutex": true, "@metamask/utils": true, "bn.js": true, "browserify>buffer": true, @@ -2854,15 +2776,6 @@ "@trezor/connect-web>tslib": true } }, - "@metamask/transaction-controller>async-mutex": { - "globals": { - "clearTimeout": true, - "setTimeout": true - }, - "packages": { - "@trezor/connect-web>tslib": true - } - }, "@metamask/user-operation-controller": { "globals": { "fetch": true @@ -2872,7 +2785,7 @@ "@metamask/base-controller": true, "@metamask/eth-query": true, "@metamask/gas-fee-controller": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/transaction-controller": true, "@metamask/user-operation-controller>@metamask/controller-utils": true, "@metamask/utils": true, @@ -3914,9 +3827,9 @@ "eth-lattice-keyring>gridplus-sdk>bitwise": true, "eth-lattice-keyring>gridplus-sdk>borc": true, "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser": true, + "eth-lattice-keyring>gridplus-sdk>rlp": true, "eth-lattice-keyring>gridplus-sdk>secp256k1": true, "eth-lattice-keyring>gridplus-sdk>uuid": true, - "eth-lattice-keyring>rlp": true, "ethereumjs-util>ethereum-cryptography>bs58check": true, "ethereumjs-util>ethereum-cryptography>hash.js": true, "lodash": true @@ -4015,6 +3928,11 @@ "ganache>abstract-level>buffer": true } }, + "eth-lattice-keyring>gridplus-sdk>rlp": { + "globals": { + "TextEncoder": true + } + }, "eth-lattice-keyring>gridplus-sdk>secp256k1": { "packages": { "@metamask/ppom-validator>elliptic": true diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index a79a242eb914..e1bbe63dca11 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -227,12 +227,12 @@ "@ethersproject/abi>@ethersproject/address": true, "@ethersproject/abi>@ethersproject/bytes": true, "@ethersproject/abi>@ethersproject/constants": true, - "@ethersproject/abi>@ethersproject/hash": true, "@ethersproject/abi>@ethersproject/keccak256": true, "@ethersproject/abi>@ethersproject/logger": true, "@ethersproject/abi>@ethersproject/properties": true, "@ethersproject/abi>@ethersproject/strings": true, - "@ethersproject/bignumber": true + "@ethersproject/bignumber": true, + "@ethersproject/hash": true } }, "@ethersproject/abi>@ethersproject/address": { @@ -254,18 +254,6 @@ "@ethersproject/bignumber": true } }, - "@ethersproject/abi>@ethersproject/hash": { - "packages": { - "@ethersproject/abi>@ethersproject/address": true, - "@ethersproject/abi>@ethersproject/bytes": true, - "@ethersproject/abi>@ethersproject/keccak256": true, - "@ethersproject/abi>@ethersproject/logger": true, - "@ethersproject/abi>@ethersproject/properties": true, - "@ethersproject/abi>@ethersproject/strings": true, - "@ethersproject/bignumber": true, - "@ethersproject/providers>@ethersproject/base64": true - } - }, "@ethersproject/abi>@ethersproject/keccak256": { "packages": { "@ethersproject/abi>@ethersproject/bytes": true, @@ -307,9 +295,36 @@ "@ethersproject/abi>@ethersproject/logger": true, "@ethersproject/abi>@ethersproject/properties": true, "@ethersproject/bignumber": true, - "@ethersproject/hdnode>@ethersproject/abstract-signer": true, + "@ethersproject/hash>@ethersproject/abstract-signer": true, "@ethersproject/hdnode>@ethersproject/transactions": true, - "@metamask/test-bundler>@ethersproject/abstract-provider": true + "@ethersproject/wallet>@ethersproject/abstract-provider": true + } + }, + "@ethersproject/hash": { + "packages": { + "@ethersproject/abi>@ethersproject/address": true, + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/keccak256": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "@ethersproject/abi>@ethersproject/strings": true, + "@ethersproject/bignumber": true, + "@ethersproject/hash>@ethersproject/base64": true + } + }, + "@ethersproject/hash>@ethersproject/abstract-signer": { + "packages": { + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true + } + }, + "@ethersproject/hash>@ethersproject/base64": { + "globals": { + "atob": true, + "btoa": true + }, + "packages": { + "@ethersproject/abi>@ethersproject/bytes": true } }, "@ethersproject/hdnode": { @@ -327,12 +342,6 @@ "@ethersproject/hdnode>@ethersproject/wordlists": true } }, - "@ethersproject/hdnode>@ethersproject/abstract-signer": { - "packages": { - "@ethersproject/abi>@ethersproject/logger": true, - "@ethersproject/abi>@ethersproject/properties": true - } - }, "@ethersproject/hdnode>@ethersproject/basex": { "packages": { "@ethersproject/abi>@ethersproject/bytes": true, @@ -376,10 +385,10 @@ "@ethersproject/hdnode>@ethersproject/wordlists": { "packages": { "@ethersproject/abi>@ethersproject/bytes": true, - "@ethersproject/abi>@ethersproject/hash": true, "@ethersproject/abi>@ethersproject/logger": true, "@ethersproject/abi>@ethersproject/properties": true, - "@ethersproject/abi>@ethersproject/strings": true + "@ethersproject/abi>@ethersproject/strings": true, + "@ethersproject/hash": true } }, "@ethersproject/providers": { @@ -396,39 +405,26 @@ "@ethersproject/abi>@ethersproject/address": true, "@ethersproject/abi>@ethersproject/bytes": true, "@ethersproject/abi>@ethersproject/constants": true, - "@ethersproject/abi>@ethersproject/hash": true, "@ethersproject/abi>@ethersproject/logger": true, "@ethersproject/abi>@ethersproject/properties": true, "@ethersproject/abi>@ethersproject/strings": true, "@ethersproject/bignumber": true, - "@ethersproject/hdnode>@ethersproject/abstract-signer": true, + "@ethersproject/hash": true, + "@ethersproject/hash>@ethersproject/abstract-signer": true, + "@ethersproject/hash>@ethersproject/base64": true, "@ethersproject/hdnode>@ethersproject/basex": true, "@ethersproject/hdnode>@ethersproject/sha2": true, "@ethersproject/hdnode>@ethersproject/transactions": true, - "@ethersproject/providers>@ethersproject/base64": true, - "@ethersproject/providers>@ethersproject/random": true, "@ethersproject/providers>@ethersproject/web": true, "@ethersproject/providers>bech32": true, - "@metamask/test-bundler>@ethersproject/abstract-provider": true, + "@ethersproject/wallet>@ethersproject/abstract-provider": true, + "@ethersproject/wallet>@ethersproject/random": true, "@metamask/test-bundler>@ethersproject/networks": true } }, - "@ethersproject/providers>@ethersproject/base64": { - "globals": { - "atob": true, - "btoa": true - }, - "packages": { - "@ethersproject/abi>@ethersproject/bytes": true - } - }, "@ethersproject/providers>@ethersproject/random": { "globals": { "crypto.getRandomValues": true - }, - "packages": { - "@ethersproject/abi>@ethersproject/bytes": true, - "@ethersproject/abi>@ethersproject/logger": true } }, "@ethersproject/providers>@ethersproject/rlp": { @@ -448,7 +444,59 @@ "@ethersproject/abi>@ethersproject/logger": true, "@ethersproject/abi>@ethersproject/properties": true, "@ethersproject/abi>@ethersproject/strings": true, - "@ethersproject/providers>@ethersproject/base64": true + "@ethersproject/hash>@ethersproject/base64": true + } + }, + "@ethersproject/wallet": { + "packages": { + "@ethersproject/abi>@ethersproject/address": true, + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/keccak256": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "@ethersproject/hash": true, + "@ethersproject/hash>@ethersproject/abstract-signer": true, + "@ethersproject/hdnode": true, + "@ethersproject/hdnode>@ethersproject/signing-key": true, + "@ethersproject/hdnode>@ethersproject/transactions": true, + "@ethersproject/wallet>@ethersproject/abstract-provider": true, + "@ethersproject/wallet>@ethersproject/json-wallets": true, + "@ethersproject/wallet>@ethersproject/random": true + } + }, + "@ethersproject/wallet>@ethersproject/abstract-provider": { + "packages": { + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "@ethersproject/bignumber": true + } + }, + "@ethersproject/wallet>@ethersproject/json-wallets": { + "packages": { + "@ethersproject/abi>@ethersproject/address": true, + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/keccak256": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "@ethersproject/abi>@ethersproject/strings": true, + "@ethersproject/hdnode": true, + "@ethersproject/hdnode>@ethersproject/pbkdf2": true, + "@ethersproject/hdnode>@ethersproject/transactions": true, + "@ethersproject/wallet>@ethersproject/json-wallets>aes-js": true, + "@ethersproject/wallet>@ethersproject/random": true, + "ethereumjs-util>ethereum-cryptography>scrypt-js": true + } + }, + "@ethersproject/wallet>@ethersproject/json-wallets>aes-js": { + "globals": { + "define": true + } + }, + "@ethersproject/wallet>@ethersproject/random": { + "packages": { + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/logger": true } }, "@keystonehq/bc-ur-registry-eth": { @@ -478,8 +526,8 @@ "@keystonehq/bc-ur-registry-eth": true, "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring": true, "@keystonehq/metamask-airgapped-keyring>@metamask/obs-store": true, - "@keystonehq/metamask-airgapped-keyring>rlp": true, "browserify>buffer": true, + "ethereumjs-util>rlp": true, "uuid": true, "webpack>events": true } @@ -489,12 +537,17 @@ "@ethereumjs/tx": true, "@ethereumjs/tx>@ethereumjs/util": true, "@keystonehq/bc-ur-registry-eth": true, + "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring>rlp": true, "@metamask/eth-trezor-keyring>hdkey": true, "browserify>buffer": true, - "eth-lattice-keyring>rlp": true, "uuid": true } }, + "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring>rlp": { + "globals": { + "TextEncoder": true + } + }, "@keystonehq/metamask-airgapped-keyring>@metamask/obs-store": { "packages": { "@keystonehq/metamask-airgapped-keyring>@metamask/obs-store>@metamask/safe-event-emitter": true, @@ -543,12 +596,6 @@ "@keystonehq/metamask-airgapped-keyring>@metamask/obs-store>through2>readable-stream>safe-buffer": true } }, - "@keystonehq/metamask-airgapped-keyring>rlp": { - "packages": { - "bn.js": true, - "browserify>buffer": true - } - }, "@lavamoat/lavadome-react": { "globals": { "Document.prototype": true, @@ -750,8 +797,8 @@ "@ethereumjs/tx>@ethereumjs/util": true, "@ethereumjs/tx>ethereum-cryptography": true, "@metamask/accounts-controller>@metamask/base-controller": true, - "@metamask/accounts-controller>@metamask/keyring-api": true, "@metamask/eth-snap-keyring": true, + "@metamask/keyring-api": true, "@metamask/keyring-controller": true, "@metamask/snaps-utils": true, "@metamask/utils": true, @@ -766,22 +813,6 @@ "immer": true } }, - "@metamask/accounts-controller>@metamask/keyring-api": { - "globals": { - "URL": true - }, - "packages": { - "@metamask/accounts-controller>@metamask/keyring-api>uuid": true, - "@metamask/keyring-api>bech32": true, - "@metamask/utils": true, - "superstruct": true - } - }, - "@metamask/accounts-controller>@metamask/keyring-api>uuid": { - "globals": { - "crypto": true - } - }, "@metamask/address-book-controller": { "packages": { "@metamask/address-book-controller>@metamask/controller-utils": true, @@ -826,7 +857,7 @@ "packages": { "@metamask/approval-controller>@metamask/base-controller": true, "@metamask/approval-controller>nanoid": true, - "@metamask/providers>@metamask/rpc-errors": true + "@metamask/rpc-errors": true } }, "@metamask/approval-controller>@metamask/base-controller": { @@ -870,7 +901,7 @@ "@metamask/controller-utils": true, "@metamask/eth-query": true, "@metamask/metamask-eth-abis": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/utils": true, "bn.js": true, "lodash": true, @@ -1028,9 +1059,9 @@ "console.error": true }, "packages": { - "@metamask/assets-controllers>async-mutex": true, "@metamask/eth-json-rpc-filters>@metamask/eth-query": true, "@metamask/eth-json-rpc-filters>@metamask/json-rpc-engine": true, + "@metamask/eth-json-rpc-filters>async-mutex": true, "@metamask/safe-event-emitter": true, "pify": true } @@ -1043,11 +1074,19 @@ }, "@metamask/eth-json-rpc-filters>@metamask/json-rpc-engine": { "packages": { - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, "@metamask/utils": true } }, + "@metamask/eth-json-rpc-filters>async-mutex": { + "globals": { + "setTimeout": true + }, + "packages": { + "@trezor/connect-web>tslib": true + } + }, "@metamask/eth-json-rpc-middleware": { "globals": { "URL": true, @@ -1058,7 +1097,7 @@ "@metamask/eth-json-rpc-middleware>@metamask/json-rpc-engine": true, "@metamask/eth-json-rpc-middleware>safe-stable-stringify": true, "@metamask/eth-sig-util": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/utils": true, "pify": true, "sass-loader>klona": true @@ -1072,14 +1111,14 @@ }, "@metamask/eth-json-rpc-middleware>@metamask/eth-json-rpc-provider>@metamask/json-rpc-engine": { "packages": { - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, "@metamask/utils": true } }, "@metamask/eth-json-rpc-middleware>@metamask/json-rpc-engine": { "packages": { - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, "@metamask/utils": true } @@ -1466,7 +1505,6 @@ "@metamask/eth-query": true, "@metamask/gas-fee-controller>@metamask/controller-utils": true, "bn.js": true, - "browserify>buffer": true, "uuid": true } }, @@ -1539,13 +1577,13 @@ "@metamask/keyring-controller": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask/assets-controllers>async-mutex": true, "@metamask/base-controller": true, "@metamask/browser-passworder": true, "@metamask/eth-sig-util": true, "@metamask/keyring-controller>@metamask/eth-hd-keyring": true, "@metamask/keyring-controller>@metamask/eth-simple-keyring": true, "@metamask/keyring-controller>ethereumjs-wallet": true, + "@metamask/name-controller>async-mutex": true, "@metamask/utils": true } }, @@ -1602,18 +1640,12 @@ "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util": { "packages": { "@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography": true, - "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util>rlp": true, "bn.js": true, "browserify>assert": true, "browserify>buffer": true, "browserify>insert-module-globals>is-buffer": true, - "ethereumjs-util>create-hash": true - } - }, - "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util>rlp": { - "packages": { - "bn.js": true, - "browserify>buffer": true + "ethereumjs-util>create-hash": true, + "ethereumjs-util>rlp": true } }, "@metamask/logging-controller": { @@ -1748,9 +1780,9 @@ "@metamask/network-controller>@metamask/controller-utils": true, "@metamask/network-controller>@metamask/eth-json-rpc-infura": true, "@metamask/network-controller>@metamask/eth-json-rpc-provider": true, - "@metamask/network-controller>@metamask/json-rpc-engine": true, "@metamask/network-controller>@metamask/swappable-obj-proxy": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, "@metamask/utils": true, "browserify>assert": true, "uuid": true @@ -1789,56 +1821,29 @@ "packages": { "@metamask/eth-json-rpc-middleware>@metamask/eth-json-rpc-provider": true, "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/json-rpc-engine": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/utils": true, "node-fetch": true } }, "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/json-rpc-engine": { "packages": { - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, "@metamask/utils": true } }, "@metamask/network-controller>@metamask/eth-json-rpc-provider": { "packages": { - "@metamask/network-controller>@metamask/json-rpc-engine": true, - "@metamask/safe-event-emitter": true - } - }, - "@metamask/network-controller>@metamask/json-rpc-engine": { - "packages": { - "@metamask/providers>@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, - "@metamask/utils": true + "@metamask/snaps-controllers>@metamask/json-rpc-engine": true } }, "@metamask/notification-controller": { "packages": { - "@metamask/notification-controller>@metamask/base-controller": true, - "@metamask/notification-controller>@metamask/utils": true, - "@metamask/notification-controller>nanoid": true - } - }, - "@metamask/notification-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, - "@metamask/notification-controller>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true + "@metamask/base-controller": true, + "@metamask/notification-controller>nanoid": true, + "@metamask/utils": true } }, "@metamask/notification-controller>nanoid": { @@ -1873,9 +1878,9 @@ "packages": { "@metamask/permission-controller>@metamask/base-controller": true, "@metamask/permission-controller>@metamask/controller-utils": true, - "@metamask/permission-controller>@metamask/json-rpc-engine": true, "@metamask/permission-controller>nanoid": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, "@metamask/utils": true, "deep-freeze-strict": true, "immer": true @@ -1907,13 +1912,6 @@ "eth-ens-namehash": true } }, - "@metamask/permission-controller>@metamask/json-rpc-engine": { - "packages": { - "@metamask/providers>@metamask/rpc-errors": true, - "@metamask/safe-event-emitter": true, - "@metamask/utils": true - } - }, "@metamask/permission-controller>nanoid": { "globals": { "crypto.getRandomValues": true @@ -1931,29 +1929,11 @@ }, "packages": { "@metamask/base-controller": true, - "@metamask/phishing-controller>@metamask/controller-utils": true, + "@metamask/controller-utils": true, "@metamask/phishing-warning>eth-phishing-detect": true, "punycode": true } }, - "@metamask/phishing-controller>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ethjs>@metamask/ethjs-unit": true, - "@metamask/utils": true, - "bn.js": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true - } - }, "@metamask/phishing-warning>eth-phishing-detect": { "packages": { "eslint>optionator>fast-levenshtein": true @@ -2054,31 +2034,39 @@ "ethereumjs-util>ethereum-cryptography>hash.js": true } }, - "@metamask/providers>@metamask/rpc-errors": { - "packages": { - "@metamask/utils": true, - "eth-rpc-errors>fast-safe-stringify": true - } - }, "@metamask/queued-request-controller": { "packages": { - "@metamask/base-controller": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/queued-request-controller>@metamask/base-controller": true, + "@metamask/rpc-errors": true, "@metamask/selected-network-controller": true, "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, "@metamask/utils": true } }, + "@metamask/queued-request-controller>@metamask/base-controller": { + "globals": { + "setTimeout": true + }, + "packages": { + "immer": true + } + }, "@metamask/rate-limit-controller": { "globals": { "setTimeout": true }, "packages": { "@metamask/base-controller": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/utils": true } }, + "@metamask/rpc-errors": { + "packages": { + "@metamask/utils": true, + "eth-rpc-errors>fast-safe-stringify": true + } + }, "@metamask/rpc-methods-flask>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2133,7 +2121,7 @@ "packages": { "@metamask/base-controller": true, "@metamask/logging-controller": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/signature-controller>@metamask/controller-utils": true, "@metamask/signature-controller>@metamask/message-manager": true, "@metamask/utils": true, @@ -2286,14 +2274,14 @@ "@ethersproject/providers": true, "@metamask/eth-query": true, "@metamask/metamask-eth-abis": true, + "@metamask/name-controller>async-mutex": true, "@metamask/network-controller": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/base-controller": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/controller-utils": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>async-mutex": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry": true, "@metamask/transaction-controller>@metamask/nonce-tracker": true, "@metamask/utils": true, @@ -2420,15 +2408,6 @@ "uuid": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>async-mutex": { - "globals": { - "clearTimeout": true, - "setTimeout": true - }, - "packages": { - "@trezor/connect-web>tslib": true - } - }, "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry": { "packages": { "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract": true, @@ -2491,21 +2470,19 @@ "globals": { "DecompressionStream": true, "URL": true, - "chrome.offscreen.createDocument": true, - "chrome.offscreen.hasDocument": true, "clearTimeout": true, "document.getElementById": true, "fetch.bind": true, "setTimeout": true }, "packages": { - "@metamask/base-controller": true, "@metamask/object-multiplex": true, + "@metamask/permission-controller": true, "@metamask/post-message-stream": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-controllers>@metamask/base-controller": true, "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, "@metamask/snaps-controllers>@metamask/json-rpc-middleware-stream": true, - "@metamask/snaps-controllers>@metamask/permission-controller": true, "@metamask/snaps-controllers>@xstate/fsm": true, "@metamask/snaps-controllers>concat-stream": true, "@metamask/snaps-controllers>get-npm-tarball-url": true, @@ -2527,9 +2504,17 @@ "crypto.getRandomValues": true } }, + "@metamask/snaps-controllers>@metamask/base-controller": { + "globals": { + "setTimeout": true + }, + "packages": { + "immer": true + } + }, "@metamask/snaps-controllers>@metamask/json-rpc-engine": { "packages": { - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, "@metamask/utils": true } @@ -2544,21 +2529,6 @@ "readable-stream": true } }, - "@metamask/snaps-controllers>@metamask/permission-controller": { - "globals": { - "console.error": true - }, - "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/providers>@metamask/rpc-errors": true, - "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, - "@metamask/snaps-controllers>nanoid": true, - "@metamask/utils": true, - "deep-freeze-strict": true, - "immer": true - } - }, "@metamask/snaps-controllers>concat-stream": { "packages": { "browserify>buffer": true, @@ -2616,8 +2586,8 @@ }, "@metamask/snaps-rpc-methods": { "packages": { - "@metamask/providers>@metamask/rpc-errors": true, - "@metamask/snaps-rpc-methods>@metamask/permission-controller": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, "@metamask/snaps-sdk": true, "@metamask/snaps-sdk>@metamask/key-tree": true, "@metamask/snaps-utils": true, @@ -2626,33 +2596,12 @@ "superstruct": true } }, - "@metamask/snaps-rpc-methods>@metamask/permission-controller": { - "globals": { - "console.error": true - }, - "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/providers>@metamask/rpc-errors": true, - "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, - "@metamask/snaps-rpc-methods>@metamask/permission-controller>nanoid": true, - "@metamask/utils": true, - "deep-freeze-strict": true, - "immer": true - } - }, - "@metamask/snaps-rpc-methods>@metamask/permission-controller>nanoid": { - "globals": { - "crypto.getRandomValues": true - } - }, "@metamask/snaps-sdk": { "globals": { "fetch": true }, "packages": { - "@metamask/providers>@metamask/rpc-errors": true, - "@metamask/snaps-sdk>fast-xml-parser": true, + "@metamask/rpc-errors": true, "@metamask/utils": true, "superstruct": true } @@ -2666,15 +2615,6 @@ "@noble/hashes": true } }, - "@metamask/snaps-sdk>fast-xml-parser": { - "globals": { - "entityName": true, - "val": true - }, - "packages": { - "@metamask/snaps-sdk>fast-xml-parser>strnum": true - } - }, "@metamask/snaps-utils": { "globals": { "File": true, @@ -2691,13 +2631,14 @@ "fetch": true }, "packages": { - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, "@metamask/snaps-sdk": true, "@metamask/snaps-sdk>@metamask/key-tree": true, - "@metamask/snaps-utils>@metamask/permission-controller": true, "@metamask/snaps-utils>@metamask/slip44": true, "@metamask/snaps-utils>cron-parser": true, "@metamask/snaps-utils>fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, "@metamask/snaps-utils>marked": true, "@metamask/snaps-utils>rfdc": true, "@metamask/snaps-utils>validate-npm-package-name": true, @@ -2709,26 +2650,6 @@ "superstruct": true } }, - "@metamask/snaps-utils>@metamask/permission-controller": { - "globals": { - "console.error": true - }, - "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/providers>@metamask/rpc-errors": true, - "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, - "@metamask/snaps-utils>@metamask/permission-controller>nanoid": true, - "@metamask/utils": true, - "deep-freeze-strict": true, - "immer": true - } - }, - "@metamask/snaps-utils>@metamask/permission-controller>nanoid": { - "globals": { - "crypto.getRandomValues": true - } - }, "@metamask/snaps-utils>@metamask/snaps-registry": { "packages": { "@metamask/message-signing-snap>@noble/curves": true, @@ -2743,6 +2664,15 @@ "luxon": true } }, + "@metamask/snaps-utils>fast-xml-parser": { + "globals": { + "entityName": true, + "val": true + }, + "packages": { + "@metamask/snaps-utils>fast-xml-parser>strnum": true + } + }, "@metamask/snaps-utils>marked": { "globals": { "console.error": true, @@ -2766,14 +2696,6 @@ "semver": true } }, - "@metamask/test-bundler>@ethersproject/abstract-provider": { - "packages": { - "@ethersproject/abi>@ethersproject/bytes": true, - "@ethersproject/abi>@ethersproject/logger": true, - "@ethersproject/abi>@ethersproject/properties": true, - "@ethersproject/bignumber": true - } - }, "@metamask/test-bundler>@ethersproject/networks": { "packages": { "@ethersproject/abi>@ethersproject/logger": true @@ -2796,12 +2718,12 @@ "@metamask/eth-query": true, "@metamask/gas-fee-controller": true, "@metamask/metamask-eth-abis": true, + "@metamask/name-controller>async-mutex": true, "@metamask/network-controller": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/transaction-controller>@metamask/base-controller": true, "@metamask/transaction-controller>@metamask/controller-utils": true, "@metamask/transaction-controller>@metamask/nonce-tracker": true, - "@metamask/transaction-controller>async-mutex": true, "@metamask/utils": true, "bn.js": true, "browserify>buffer": true, @@ -2854,15 +2776,6 @@ "@trezor/connect-web>tslib": true } }, - "@metamask/transaction-controller>async-mutex": { - "globals": { - "clearTimeout": true, - "setTimeout": true - }, - "packages": { - "@trezor/connect-web>tslib": true - } - }, "@metamask/user-operation-controller": { "globals": { "fetch": true @@ -2872,7 +2785,7 @@ "@metamask/base-controller": true, "@metamask/eth-query": true, "@metamask/gas-fee-controller": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/transaction-controller": true, "@metamask/user-operation-controller>@metamask/controller-utils": true, "@metamask/utils": true, @@ -3914,9 +3827,9 @@ "eth-lattice-keyring>gridplus-sdk>bitwise": true, "eth-lattice-keyring>gridplus-sdk>borc": true, "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser": true, + "eth-lattice-keyring>gridplus-sdk>rlp": true, "eth-lattice-keyring>gridplus-sdk>secp256k1": true, "eth-lattice-keyring>gridplus-sdk>uuid": true, - "eth-lattice-keyring>rlp": true, "ethereumjs-util>ethereum-cryptography>bs58check": true, "ethereumjs-util>ethereum-cryptography>hash.js": true, "lodash": true @@ -4015,6 +3928,11 @@ "ganache>abstract-level>buffer": true } }, + "eth-lattice-keyring>gridplus-sdk>rlp": { + "globals": { + "TextEncoder": true + } + }, "eth-lattice-keyring>gridplus-sdk>secp256k1": { "packages": { "@metamask/ppom-validator>elliptic": true diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index f97f4a34f66f..e3032155280f 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -227,12 +227,12 @@ "@ethersproject/abi>@ethersproject/address": true, "@ethersproject/abi>@ethersproject/bytes": true, "@ethersproject/abi>@ethersproject/constants": true, - "@ethersproject/abi>@ethersproject/hash": true, "@ethersproject/abi>@ethersproject/keccak256": true, "@ethersproject/abi>@ethersproject/logger": true, "@ethersproject/abi>@ethersproject/properties": true, "@ethersproject/abi>@ethersproject/strings": true, - "@ethersproject/bignumber": true + "@ethersproject/bignumber": true, + "@ethersproject/hash": true } }, "@ethersproject/abi>@ethersproject/address": { @@ -254,18 +254,6 @@ "@ethersproject/bignumber": true } }, - "@ethersproject/abi>@ethersproject/hash": { - "packages": { - "@ethersproject/abi>@ethersproject/address": true, - "@ethersproject/abi>@ethersproject/bytes": true, - "@ethersproject/abi>@ethersproject/keccak256": true, - "@ethersproject/abi>@ethersproject/logger": true, - "@ethersproject/abi>@ethersproject/properties": true, - "@ethersproject/abi>@ethersproject/strings": true, - "@ethersproject/bignumber": true, - "@ethersproject/providers>@ethersproject/base64": true - } - }, "@ethersproject/abi>@ethersproject/keccak256": { "packages": { "@ethersproject/abi>@ethersproject/bytes": true, @@ -307,9 +295,36 @@ "@ethersproject/abi>@ethersproject/logger": true, "@ethersproject/abi>@ethersproject/properties": true, "@ethersproject/bignumber": true, - "@ethersproject/hdnode>@ethersproject/abstract-signer": true, + "@ethersproject/hash>@ethersproject/abstract-signer": true, "@ethersproject/hdnode>@ethersproject/transactions": true, - "@metamask/test-bundler>@ethersproject/abstract-provider": true + "@ethersproject/wallet>@ethersproject/abstract-provider": true + } + }, + "@ethersproject/hash": { + "packages": { + "@ethersproject/abi>@ethersproject/address": true, + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/keccak256": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "@ethersproject/abi>@ethersproject/strings": true, + "@ethersproject/bignumber": true, + "@ethersproject/hash>@ethersproject/base64": true + } + }, + "@ethersproject/hash>@ethersproject/abstract-signer": { + "packages": { + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true + } + }, + "@ethersproject/hash>@ethersproject/base64": { + "globals": { + "atob": true, + "btoa": true + }, + "packages": { + "@ethersproject/abi>@ethersproject/bytes": true } }, "@ethersproject/hdnode": { @@ -327,12 +342,6 @@ "@ethersproject/hdnode>@ethersproject/wordlists": true } }, - "@ethersproject/hdnode>@ethersproject/abstract-signer": { - "packages": { - "@ethersproject/abi>@ethersproject/logger": true, - "@ethersproject/abi>@ethersproject/properties": true - } - }, "@ethersproject/hdnode>@ethersproject/basex": { "packages": { "@ethersproject/abi>@ethersproject/bytes": true, @@ -376,10 +385,10 @@ "@ethersproject/hdnode>@ethersproject/wordlists": { "packages": { "@ethersproject/abi>@ethersproject/bytes": true, - "@ethersproject/abi>@ethersproject/hash": true, "@ethersproject/abi>@ethersproject/logger": true, "@ethersproject/abi>@ethersproject/properties": true, - "@ethersproject/abi>@ethersproject/strings": true + "@ethersproject/abi>@ethersproject/strings": true, + "@ethersproject/hash": true } }, "@ethersproject/providers": { @@ -396,39 +405,26 @@ "@ethersproject/abi>@ethersproject/address": true, "@ethersproject/abi>@ethersproject/bytes": true, "@ethersproject/abi>@ethersproject/constants": true, - "@ethersproject/abi>@ethersproject/hash": true, "@ethersproject/abi>@ethersproject/logger": true, "@ethersproject/abi>@ethersproject/properties": true, "@ethersproject/abi>@ethersproject/strings": true, "@ethersproject/bignumber": true, - "@ethersproject/hdnode>@ethersproject/abstract-signer": true, + "@ethersproject/hash": true, + "@ethersproject/hash>@ethersproject/abstract-signer": true, + "@ethersproject/hash>@ethersproject/base64": true, "@ethersproject/hdnode>@ethersproject/basex": true, "@ethersproject/hdnode>@ethersproject/sha2": true, "@ethersproject/hdnode>@ethersproject/transactions": true, - "@ethersproject/providers>@ethersproject/base64": true, - "@ethersproject/providers>@ethersproject/random": true, "@ethersproject/providers>@ethersproject/web": true, "@ethersproject/providers>bech32": true, - "@metamask/test-bundler>@ethersproject/abstract-provider": true, + "@ethersproject/wallet>@ethersproject/abstract-provider": true, + "@ethersproject/wallet>@ethersproject/random": true, "@metamask/test-bundler>@ethersproject/networks": true } }, - "@ethersproject/providers>@ethersproject/base64": { - "globals": { - "atob": true, - "btoa": true - }, - "packages": { - "@ethersproject/abi>@ethersproject/bytes": true - } - }, "@ethersproject/providers>@ethersproject/random": { "globals": { "crypto.getRandomValues": true - }, - "packages": { - "@ethersproject/abi>@ethersproject/bytes": true, - "@ethersproject/abi>@ethersproject/logger": true } }, "@ethersproject/providers>@ethersproject/rlp": { @@ -448,7 +444,59 @@ "@ethersproject/abi>@ethersproject/logger": true, "@ethersproject/abi>@ethersproject/properties": true, "@ethersproject/abi>@ethersproject/strings": true, - "@ethersproject/providers>@ethersproject/base64": true + "@ethersproject/hash>@ethersproject/base64": true + } + }, + "@ethersproject/wallet": { + "packages": { + "@ethersproject/abi>@ethersproject/address": true, + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/keccak256": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "@ethersproject/hash": true, + "@ethersproject/hash>@ethersproject/abstract-signer": true, + "@ethersproject/hdnode": true, + "@ethersproject/hdnode>@ethersproject/signing-key": true, + "@ethersproject/hdnode>@ethersproject/transactions": true, + "@ethersproject/wallet>@ethersproject/abstract-provider": true, + "@ethersproject/wallet>@ethersproject/json-wallets": true, + "@ethersproject/wallet>@ethersproject/random": true + } + }, + "@ethersproject/wallet>@ethersproject/abstract-provider": { + "packages": { + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "@ethersproject/bignumber": true + } + }, + "@ethersproject/wallet>@ethersproject/json-wallets": { + "packages": { + "@ethersproject/abi>@ethersproject/address": true, + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/keccak256": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "@ethersproject/abi>@ethersproject/strings": true, + "@ethersproject/hdnode": true, + "@ethersproject/hdnode>@ethersproject/pbkdf2": true, + "@ethersproject/hdnode>@ethersproject/transactions": true, + "@ethersproject/wallet>@ethersproject/json-wallets>aes-js": true, + "@ethersproject/wallet>@ethersproject/random": true, + "ethereumjs-util>ethereum-cryptography>scrypt-js": true + } + }, + "@ethersproject/wallet>@ethersproject/json-wallets>aes-js": { + "globals": { + "define": true + } + }, + "@ethersproject/wallet>@ethersproject/random": { + "packages": { + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/logger": true } }, "@keystonehq/bc-ur-registry-eth": { @@ -478,8 +526,8 @@ "@keystonehq/bc-ur-registry-eth": true, "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring": true, "@keystonehq/metamask-airgapped-keyring>@metamask/obs-store": true, - "@keystonehq/metamask-airgapped-keyring>rlp": true, "browserify>buffer": true, + "ethereumjs-util>rlp": true, "uuid": true, "webpack>events": true } @@ -489,12 +537,17 @@ "@ethereumjs/tx": true, "@ethereumjs/tx>@ethereumjs/util": true, "@keystonehq/bc-ur-registry-eth": true, + "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring>rlp": true, "@metamask/eth-trezor-keyring>hdkey": true, "browserify>buffer": true, - "eth-lattice-keyring>rlp": true, "uuid": true } }, + "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring>rlp": { + "globals": { + "TextEncoder": true + } + }, "@keystonehq/metamask-airgapped-keyring>@metamask/obs-store": { "packages": { "@keystonehq/metamask-airgapped-keyring>@metamask/obs-store>@metamask/safe-event-emitter": true, @@ -543,12 +596,6 @@ "@keystonehq/metamask-airgapped-keyring>@metamask/obs-store>through2>readable-stream>safe-buffer": true } }, - "@keystonehq/metamask-airgapped-keyring>rlp": { - "packages": { - "bn.js": true, - "browserify>buffer": true - } - }, "@lavamoat/lavadome-react": { "globals": { "Document.prototype": true, @@ -742,56 +789,8 @@ "@metamask-institutional/custody-controller": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask-institutional/custody-controller>@metamask/obs-store": true, - "@metamask-institutional/custody-keyring": true - } - }, - "@metamask-institutional/custody-controller>@metamask/obs-store": { - "packages": { - "@metamask-institutional/custody-controller>@metamask/obs-store>@metamask/safe-event-emitter": true, - "@metamask-institutional/custody-controller>@metamask/obs-store>through2": true, - "stream-browserify": true - } - }, - "@metamask-institutional/custody-controller>@metamask/obs-store>@metamask/safe-event-emitter": { - "globals": { - "setTimeout": true - }, - "packages": { - "webpack>events": true - } - }, - "@metamask-institutional/custody-controller>@metamask/obs-store>through2": { - "packages": { - "@metamask-institutional/custody-controller>@metamask/obs-store>through2>readable-stream": true, - "browserify>process": true, - "browserify>util": true, - "watchify>xtend": true - } - }, - "@metamask-institutional/custody-controller>@metamask/obs-store>through2>readable-stream": { - "packages": { - "@metamask-institutional/custody-controller>@metamask/obs-store>through2>readable-stream>isarray": true, - "@metamask-institutional/custody-controller>@metamask/obs-store>through2>readable-stream>safe-buffer": true, - "@metamask-institutional/custody-controller>@metamask/obs-store>through2>readable-stream>string_decoder": true, - "browserify>browser-resolve": true, - "browserify>process": true, - "browserify>timers-browserify": true, - "pumpify>inherits": true, - "readable-stream-2>core-util-is": true, - "readable-stream-2>process-nextick-args": true, - "readable-stream>util-deprecate": true, - "webpack>events": true - } - }, - "@metamask-institutional/custody-controller>@metamask/obs-store>through2>readable-stream>safe-buffer": { - "packages": { - "browserify>buffer": true - } - }, - "@metamask-institutional/custody-controller>@metamask/obs-store>through2>readable-stream>string_decoder": { - "packages": { - "@metamask-institutional/custody-controller>@metamask/obs-store>through2>readable-stream>safe-buffer": true + "@metamask-institutional/custody-keyring": true, + "@metamask/obs-store": true } }, "@metamask-institutional/custody-keyring": { @@ -803,9 +802,9 @@ "packages": { "@ethereumjs/tx>@ethereumjs/util": true, "@metamask-institutional/custody-keyring>@metamask-institutional/configuration-client": true, - "@metamask-institutional/custody-keyring>@metamask/obs-store": true, "@metamask-institutional/sdk": true, "@metamask-institutional/sdk>@metamask-institutional/types": true, + "@metamask/obs-store": true, "browserify>crypto-browserify": true, "gulp-sass>lodash.clonedeep": true, "webpack>events": true @@ -817,54 +816,6 @@ "fetch": true } }, - "@metamask-institutional/custody-keyring>@metamask/obs-store": { - "packages": { - "@metamask-institutional/custody-keyring>@metamask/obs-store>@metamask/safe-event-emitter": true, - "@metamask-institutional/custody-keyring>@metamask/obs-store>through2": true, - "stream-browserify": true - } - }, - "@metamask-institutional/custody-keyring>@metamask/obs-store>@metamask/safe-event-emitter": { - "globals": { - "setTimeout": true - }, - "packages": { - "webpack>events": true - } - }, - "@metamask-institutional/custody-keyring>@metamask/obs-store>through2": { - "packages": { - "@metamask-institutional/custody-keyring>@metamask/obs-store>through2>readable-stream": true, - "browserify>process": true, - "browserify>util": true, - "watchify>xtend": true - } - }, - "@metamask-institutional/custody-keyring>@metamask/obs-store>through2>readable-stream": { - "packages": { - "@metamask-institutional/custody-keyring>@metamask/obs-store>through2>readable-stream>isarray": true, - "@metamask-institutional/custody-keyring>@metamask/obs-store>through2>readable-stream>safe-buffer": true, - "@metamask-institutional/custody-keyring>@metamask/obs-store>through2>readable-stream>string_decoder": true, - "browserify>browser-resolve": true, - "browserify>process": true, - "browserify>timers-browserify": true, - "pumpify>inherits": true, - "readable-stream-2>core-util-is": true, - "readable-stream-2>process-nextick-args": true, - "readable-stream>util-deprecate": true, - "webpack>events": true - } - }, - "@metamask-institutional/custody-keyring>@metamask/obs-store>through2>readable-stream>safe-buffer": { - "packages": { - "browserify>buffer": true - } - }, - "@metamask-institutional/custody-keyring>@metamask/obs-store>through2>readable-stream>string_decoder": { - "packages": { - "@metamask-institutional/custody-keyring>@metamask/obs-store>through2>readable-stream>safe-buffer": true - } - }, "@metamask-institutional/extension": { "globals": { "console.log": true @@ -879,55 +830,7 @@ "@metamask-institutional/institutional-features": { "packages": { "@metamask-institutional/custody-keyring": true, - "@metamask-institutional/institutional-features>@metamask/obs-store": true - } - }, - "@metamask-institutional/institutional-features>@metamask/obs-store": { - "packages": { - "@metamask-institutional/institutional-features>@metamask/obs-store>@metamask/safe-event-emitter": true, - "@metamask-institutional/institutional-features>@metamask/obs-store>through2": true, - "stream-browserify": true - } - }, - "@metamask-institutional/institutional-features>@metamask/obs-store>@metamask/safe-event-emitter": { - "globals": { - "setTimeout": true - }, - "packages": { - "webpack>events": true - } - }, - "@metamask-institutional/institutional-features>@metamask/obs-store>through2": { - "packages": { - "@metamask-institutional/institutional-features>@metamask/obs-store>through2>readable-stream": true, - "browserify>process": true, - "browserify>util": true, - "watchify>xtend": true - } - }, - "@metamask-institutional/institutional-features>@metamask/obs-store>through2>readable-stream": { - "packages": { - "@metamask-institutional/institutional-features>@metamask/obs-store>through2>readable-stream>isarray": true, - "@metamask-institutional/institutional-features>@metamask/obs-store>through2>readable-stream>safe-buffer": true, - "@metamask-institutional/institutional-features>@metamask/obs-store>through2>readable-stream>string_decoder": true, - "browserify>browser-resolve": true, - "browserify>process": true, - "browserify>timers-browserify": true, - "pumpify>inherits": true, - "readable-stream-2>core-util-is": true, - "readable-stream-2>process-nextick-args": true, - "readable-stream>util-deprecate": true, - "webpack>events": true - } - }, - "@metamask-institutional/institutional-features>@metamask/obs-store>through2>readable-stream>safe-buffer": { - "packages": { - "browserify>buffer": true - } - }, - "@metamask-institutional/institutional-features>@metamask/obs-store>through2>readable-stream>string_decoder": { - "packages": { - "@metamask-institutional/institutional-features>@metamask/obs-store>through2>readable-stream>safe-buffer": true + "@metamask/obs-store": true } }, "@metamask-institutional/rpc-allowlist": { @@ -941,7 +844,6 @@ "console.debug": true, "console.error": true, "console.log": true, - "console.warn": true, "fetch": true }, "packages": { @@ -961,7 +863,7 @@ "@ethereumjs/tx>@ethereumjs/util": true, "@metamask-institutional/sdk": true, "@metamask-institutional/transaction-update>@metamask-institutional/websocket-client": true, - "@metamask-institutional/transaction-update>@metamask/obs-store": true, + "@metamask/obs-store": true, "webpack>events": true } }, @@ -976,54 +878,6 @@ "webpack>events": true } }, - "@metamask-institutional/transaction-update>@metamask/obs-store": { - "packages": { - "@metamask-institutional/transaction-update>@metamask/obs-store>@metamask/safe-event-emitter": true, - "@metamask-institutional/transaction-update>@metamask/obs-store>through2": true, - "stream-browserify": true - } - }, - "@metamask-institutional/transaction-update>@metamask/obs-store>@metamask/safe-event-emitter": { - "globals": { - "setTimeout": true - }, - "packages": { - "webpack>events": true - } - }, - "@metamask-institutional/transaction-update>@metamask/obs-store>through2": { - "packages": { - "@metamask-institutional/transaction-update>@metamask/obs-store>through2>readable-stream": true, - "browserify>process": true, - "browserify>util": true, - "watchify>xtend": true - } - }, - "@metamask-institutional/transaction-update>@metamask/obs-store>through2>readable-stream": { - "packages": { - "@metamask-institutional/transaction-update>@metamask/obs-store>through2>readable-stream>isarray": true, - "@metamask-institutional/transaction-update>@metamask/obs-store>through2>readable-stream>safe-buffer": true, - "@metamask-institutional/transaction-update>@metamask/obs-store>through2>readable-stream>string_decoder": true, - "browserify>browser-resolve": true, - "browserify>process": true, - "browserify>timers-browserify": true, - "pumpify>inherits": true, - "readable-stream-2>core-util-is": true, - "readable-stream-2>process-nextick-args": true, - "readable-stream>util-deprecate": true, - "webpack>events": true - } - }, - "@metamask-institutional/transaction-update>@metamask/obs-store>through2>readable-stream>safe-buffer": { - "packages": { - "browserify>buffer": true - } - }, - "@metamask-institutional/transaction-update>@metamask/obs-store>through2>readable-stream>string_decoder": { - "packages": { - "@metamask-institutional/transaction-update>@metamask/obs-store>through2>readable-stream>safe-buffer": true - } - }, "@metamask/abi-utils": { "packages": { "@metamask/utils": true, @@ -1035,8 +889,8 @@ "@ethereumjs/tx>@ethereumjs/util": true, "@ethereumjs/tx>ethereum-cryptography": true, "@metamask/accounts-controller>@metamask/base-controller": true, - "@metamask/accounts-controller>@metamask/keyring-api": true, "@metamask/eth-snap-keyring": true, + "@metamask/keyring-api": true, "@metamask/keyring-controller": true, "@metamask/snaps-utils": true, "@metamask/utils": true, @@ -1051,22 +905,6 @@ "immer": true } }, - "@metamask/accounts-controller>@metamask/keyring-api": { - "globals": { - "URL": true - }, - "packages": { - "@metamask/accounts-controller>@metamask/keyring-api>uuid": true, - "@metamask/keyring-api>bech32": true, - "@metamask/utils": true, - "superstruct": true - } - }, - "@metamask/accounts-controller>@metamask/keyring-api>uuid": { - "globals": { - "crypto": true - } - }, "@metamask/address-book-controller": { "packages": { "@metamask/address-book-controller>@metamask/controller-utils": true, @@ -1111,7 +949,7 @@ "packages": { "@metamask/approval-controller>@metamask/base-controller": true, "@metamask/approval-controller>nanoid": true, - "@metamask/providers>@metamask/rpc-errors": true + "@metamask/rpc-errors": true } }, "@metamask/approval-controller>@metamask/base-controller": { @@ -1155,7 +993,7 @@ "@metamask/controller-utils": true, "@metamask/eth-query": true, "@metamask/metamask-eth-abis": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/utils": true, "bn.js": true, "lodash": true, @@ -1313,9 +1151,9 @@ "console.error": true }, "packages": { - "@metamask/assets-controllers>async-mutex": true, "@metamask/eth-json-rpc-filters>@metamask/eth-query": true, "@metamask/eth-json-rpc-filters>@metamask/json-rpc-engine": true, + "@metamask/eth-json-rpc-filters>async-mutex": true, "@metamask/safe-event-emitter": true, "pify": true } @@ -1328,11 +1166,19 @@ }, "@metamask/eth-json-rpc-filters>@metamask/json-rpc-engine": { "packages": { - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, "@metamask/utils": true } }, + "@metamask/eth-json-rpc-filters>async-mutex": { + "globals": { + "setTimeout": true + }, + "packages": { + "@trezor/connect-web>tslib": true + } + }, "@metamask/eth-json-rpc-middleware": { "globals": { "URL": true, @@ -1343,7 +1189,7 @@ "@metamask/eth-json-rpc-middleware>@metamask/json-rpc-engine": true, "@metamask/eth-json-rpc-middleware>safe-stable-stringify": true, "@metamask/eth-sig-util": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/utils": true, "pify": true, "sass-loader>klona": true @@ -1357,14 +1203,14 @@ }, "@metamask/eth-json-rpc-middleware>@metamask/eth-json-rpc-provider>@metamask/json-rpc-engine": { "packages": { - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, "@metamask/utils": true } }, "@metamask/eth-json-rpc-middleware>@metamask/json-rpc-engine": { "packages": { - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, "@metamask/utils": true } @@ -1751,7 +1597,6 @@ "@metamask/eth-query": true, "@metamask/gas-fee-controller>@metamask/controller-utils": true, "bn.js": true, - "browserify>buffer": true, "uuid": true } }, @@ -1824,13 +1669,13 @@ "@metamask/keyring-controller": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask/assets-controllers>async-mutex": true, "@metamask/base-controller": true, "@metamask/browser-passworder": true, "@metamask/eth-sig-util": true, "@metamask/keyring-controller>@metamask/eth-hd-keyring": true, "@metamask/keyring-controller>@metamask/eth-simple-keyring": true, "@metamask/keyring-controller>ethereumjs-wallet": true, + "@metamask/name-controller>async-mutex": true, "@metamask/utils": true } }, @@ -1887,18 +1732,12 @@ "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util": { "packages": { "@metamask/keyring-controller>ethereumjs-wallet>ethereum-cryptography": true, - "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util>rlp": true, "bn.js": true, "browserify>assert": true, "browserify>buffer": true, "browserify>insert-module-globals>is-buffer": true, - "ethereumjs-util>create-hash": true - } - }, - "@metamask/keyring-controller>ethereumjs-wallet>ethereumjs-util>rlp": { - "packages": { - "bn.js": true, - "browserify>buffer": true + "ethereumjs-util>create-hash": true, + "ethereumjs-util>rlp": true } }, "@metamask/logging-controller": { @@ -2033,9 +1872,9 @@ "@metamask/network-controller>@metamask/controller-utils": true, "@metamask/network-controller>@metamask/eth-json-rpc-infura": true, "@metamask/network-controller>@metamask/eth-json-rpc-provider": true, - "@metamask/network-controller>@metamask/json-rpc-engine": true, "@metamask/network-controller>@metamask/swappable-obj-proxy": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, "@metamask/utils": true, "browserify>assert": true, "uuid": true @@ -2074,56 +1913,29 @@ "packages": { "@metamask/eth-json-rpc-middleware>@metamask/eth-json-rpc-provider": true, "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/json-rpc-engine": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/utils": true, "node-fetch": true } }, "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/json-rpc-engine": { "packages": { - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, "@metamask/utils": true } }, "@metamask/network-controller>@metamask/eth-json-rpc-provider": { "packages": { - "@metamask/network-controller>@metamask/json-rpc-engine": true, - "@metamask/safe-event-emitter": true - } - }, - "@metamask/network-controller>@metamask/json-rpc-engine": { - "packages": { - "@metamask/providers>@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, - "@metamask/utils": true + "@metamask/snaps-controllers>@metamask/json-rpc-engine": true } }, "@metamask/notification-controller": { "packages": { - "@metamask/notification-controller>@metamask/base-controller": true, - "@metamask/notification-controller>@metamask/utils": true, - "@metamask/notification-controller>nanoid": true - } - }, - "@metamask/notification-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, - "@metamask/notification-controller>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true + "@metamask/base-controller": true, + "@metamask/notification-controller>nanoid": true, + "@metamask/utils": true } }, "@metamask/notification-controller>nanoid": { @@ -2158,9 +1970,9 @@ "packages": { "@metamask/permission-controller>@metamask/base-controller": true, "@metamask/permission-controller>@metamask/controller-utils": true, - "@metamask/permission-controller>@metamask/json-rpc-engine": true, "@metamask/permission-controller>nanoid": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, "@metamask/utils": true, "deep-freeze-strict": true, "immer": true @@ -2192,13 +2004,6 @@ "eth-ens-namehash": true } }, - "@metamask/permission-controller>@metamask/json-rpc-engine": { - "packages": { - "@metamask/providers>@metamask/rpc-errors": true, - "@metamask/safe-event-emitter": true, - "@metamask/utils": true - } - }, "@metamask/permission-controller>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2216,29 +2021,11 @@ }, "packages": { "@metamask/base-controller": true, - "@metamask/phishing-controller>@metamask/controller-utils": true, + "@metamask/controller-utils": true, "@metamask/phishing-warning>eth-phishing-detect": true, "punycode": true } }, - "@metamask/phishing-controller>@metamask/controller-utils": { - "globals": { - "URL": true, - "console.error": true, - "fetch": true, - "setTimeout": true - }, - "packages": { - "@ethereumjs/tx>@ethereumjs/util": true, - "@metamask/controller-utils>@spruceid/siwe-parser": true, - "@metamask/ethjs>@metamask/ethjs-unit": true, - "@metamask/utils": true, - "bn.js": true, - "browserify>buffer": true, - "eslint>fast-deep-equal": true, - "eth-ens-namehash": true - } - }, "@metamask/phishing-warning>eth-phishing-detect": { "packages": { "eslint>optionator>fast-levenshtein": true @@ -2339,31 +2126,39 @@ "ethereumjs-util>ethereum-cryptography>hash.js": true } }, - "@metamask/providers>@metamask/rpc-errors": { - "packages": { - "@metamask/utils": true, - "eth-rpc-errors>fast-safe-stringify": true - } - }, "@metamask/queued-request-controller": { "packages": { - "@metamask/base-controller": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/queued-request-controller>@metamask/base-controller": true, + "@metamask/rpc-errors": true, "@metamask/selected-network-controller": true, "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, "@metamask/utils": true } }, + "@metamask/queued-request-controller>@metamask/base-controller": { + "globals": { + "setTimeout": true + }, + "packages": { + "immer": true + } + }, "@metamask/rate-limit-controller": { "globals": { "setTimeout": true }, "packages": { "@metamask/base-controller": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/utils": true } }, + "@metamask/rpc-errors": { + "packages": { + "@metamask/utils": true, + "eth-rpc-errors>fast-safe-stringify": true + } + }, "@metamask/rpc-methods-flask>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2418,7 +2213,7 @@ "packages": { "@metamask/base-controller": true, "@metamask/logging-controller": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/signature-controller>@metamask/controller-utils": true, "@metamask/signature-controller>@metamask/message-manager": true, "@metamask/utils": true, @@ -2571,14 +2366,14 @@ "@ethersproject/providers": true, "@metamask/eth-query": true, "@metamask/metamask-eth-abis": true, + "@metamask/name-controller>async-mutex": true, "@metamask/network-controller": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/tx": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@ethereumjs/util": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/base-controller": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/controller-utils": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>@metamask/gas-fee-controller": true, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>async-mutex": true, "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry": true, "@metamask/transaction-controller>@metamask/nonce-tracker": true, "@metamask/utils": true, @@ -2705,15 +2500,6 @@ "uuid": true } }, - "@metamask/smart-transactions-controller>@metamask/transaction-controller>async-mutex": { - "globals": { - "clearTimeout": true, - "setTimeout": true - }, - "packages": { - "@trezor/connect-web>tslib": true - } - }, "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry": { "packages": { "@metamask/smart-transactions-controller>@metamask/transaction-controller>eth-method-registry>@metamask/ethjs-contract": true, @@ -2776,21 +2562,19 @@ "globals": { "DecompressionStream": true, "URL": true, - "chrome.offscreen.createDocument": true, - "chrome.offscreen.hasDocument": true, "clearTimeout": true, "document.getElementById": true, "fetch.bind": true, "setTimeout": true }, "packages": { - "@metamask/base-controller": true, "@metamask/object-multiplex": true, + "@metamask/permission-controller": true, "@metamask/post-message-stream": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-controllers>@metamask/base-controller": true, "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, "@metamask/snaps-controllers>@metamask/json-rpc-middleware-stream": true, - "@metamask/snaps-controllers>@metamask/permission-controller": true, "@metamask/snaps-controllers>@xstate/fsm": true, "@metamask/snaps-controllers>concat-stream": true, "@metamask/snaps-controllers>get-npm-tarball-url": true, @@ -2812,9 +2596,17 @@ "crypto.getRandomValues": true } }, + "@metamask/snaps-controllers>@metamask/base-controller": { + "globals": { + "setTimeout": true + }, + "packages": { + "immer": true + } + }, "@metamask/snaps-controllers>@metamask/json-rpc-engine": { "packages": { - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/safe-event-emitter": true, "@metamask/utils": true } @@ -2829,21 +2621,6 @@ "readable-stream": true } }, - "@metamask/snaps-controllers>@metamask/permission-controller": { - "globals": { - "console.error": true - }, - "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/providers>@metamask/rpc-errors": true, - "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, - "@metamask/snaps-controllers>nanoid": true, - "@metamask/utils": true, - "deep-freeze-strict": true, - "immer": true - } - }, "@metamask/snaps-controllers>concat-stream": { "packages": { "browserify>buffer": true, @@ -2901,8 +2678,8 @@ }, "@metamask/snaps-rpc-methods": { "packages": { - "@metamask/providers>@metamask/rpc-errors": true, - "@metamask/snaps-rpc-methods>@metamask/permission-controller": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, "@metamask/snaps-sdk": true, "@metamask/snaps-sdk>@metamask/key-tree": true, "@metamask/snaps-utils": true, @@ -2911,33 +2688,12 @@ "superstruct": true } }, - "@metamask/snaps-rpc-methods>@metamask/permission-controller": { - "globals": { - "console.error": true - }, - "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/providers>@metamask/rpc-errors": true, - "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, - "@metamask/snaps-rpc-methods>@metamask/permission-controller>nanoid": true, - "@metamask/utils": true, - "deep-freeze-strict": true, - "immer": true - } - }, - "@metamask/snaps-rpc-methods>@metamask/permission-controller>nanoid": { - "globals": { - "crypto.getRandomValues": true - } - }, "@metamask/snaps-sdk": { "globals": { "fetch": true }, "packages": { - "@metamask/providers>@metamask/rpc-errors": true, - "@metamask/snaps-sdk>fast-xml-parser": true, + "@metamask/rpc-errors": true, "@metamask/utils": true, "superstruct": true } @@ -2951,15 +2707,6 @@ "@noble/hashes": true } }, - "@metamask/snaps-sdk>fast-xml-parser": { - "globals": { - "entityName": true, - "val": true - }, - "packages": { - "@metamask/snaps-sdk>fast-xml-parser>strnum": true - } - }, "@metamask/snaps-utils": { "globals": { "File": true, @@ -2976,13 +2723,14 @@ "fetch": true }, "packages": { - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, "@metamask/snaps-sdk": true, "@metamask/snaps-sdk>@metamask/key-tree": true, - "@metamask/snaps-utils>@metamask/permission-controller": true, "@metamask/snaps-utils>@metamask/slip44": true, "@metamask/snaps-utils>cron-parser": true, "@metamask/snaps-utils>fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, "@metamask/snaps-utils>marked": true, "@metamask/snaps-utils>rfdc": true, "@metamask/snaps-utils>validate-npm-package-name": true, @@ -2994,26 +2742,6 @@ "superstruct": true } }, - "@metamask/snaps-utils>@metamask/permission-controller": { - "globals": { - "console.error": true - }, - "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/providers>@metamask/rpc-errors": true, - "@metamask/snaps-controllers>@metamask/json-rpc-engine": true, - "@metamask/snaps-utils>@metamask/permission-controller>nanoid": true, - "@metamask/utils": true, - "deep-freeze-strict": true, - "immer": true - } - }, - "@metamask/snaps-utils>@metamask/permission-controller>nanoid": { - "globals": { - "crypto.getRandomValues": true - } - }, "@metamask/snaps-utils>@metamask/snaps-registry": { "packages": { "@metamask/message-signing-snap>@noble/curves": true, @@ -3028,6 +2756,15 @@ "luxon": true } }, + "@metamask/snaps-utils>fast-xml-parser": { + "globals": { + "entityName": true, + "val": true + }, + "packages": { + "@metamask/snaps-utils>fast-xml-parser>strnum": true + } + }, "@metamask/snaps-utils>marked": { "globals": { "console.error": true, @@ -3051,14 +2788,6 @@ "semver": true } }, - "@metamask/test-bundler>@ethersproject/abstract-provider": { - "packages": { - "@ethersproject/abi>@ethersproject/bytes": true, - "@ethersproject/abi>@ethersproject/logger": true, - "@ethersproject/abi>@ethersproject/properties": true, - "@ethersproject/bignumber": true - } - }, "@metamask/test-bundler>@ethersproject/networks": { "packages": { "@ethersproject/abi>@ethersproject/logger": true @@ -3081,12 +2810,12 @@ "@metamask/eth-query": true, "@metamask/gas-fee-controller": true, "@metamask/metamask-eth-abis": true, + "@metamask/name-controller>async-mutex": true, "@metamask/network-controller": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/transaction-controller>@metamask/base-controller": true, "@metamask/transaction-controller>@metamask/controller-utils": true, "@metamask/transaction-controller>@metamask/nonce-tracker": true, - "@metamask/transaction-controller>async-mutex": true, "@metamask/utils": true, "bn.js": true, "browserify>buffer": true, @@ -3139,15 +2868,6 @@ "@trezor/connect-web>tslib": true } }, - "@metamask/transaction-controller>async-mutex": { - "globals": { - "clearTimeout": true, - "setTimeout": true - }, - "packages": { - "@trezor/connect-web>tslib": true - } - }, "@metamask/user-operation-controller": { "globals": { "fetch": true @@ -3157,7 +2877,7 @@ "@metamask/base-controller": true, "@metamask/eth-query": true, "@metamask/gas-fee-controller": true, - "@metamask/providers>@metamask/rpc-errors": true, + "@metamask/rpc-errors": true, "@metamask/transaction-controller": true, "@metamask/user-operation-controller>@metamask/controller-utils": true, "@metamask/utils": true, @@ -4199,9 +3919,9 @@ "eth-lattice-keyring>gridplus-sdk>bitwise": true, "eth-lattice-keyring>gridplus-sdk>borc": true, "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser": true, + "eth-lattice-keyring>gridplus-sdk>rlp": true, "eth-lattice-keyring>gridplus-sdk>secp256k1": true, "eth-lattice-keyring>gridplus-sdk>uuid": true, - "eth-lattice-keyring>rlp": true, "ethereumjs-util>ethereum-cryptography>bs58check": true, "ethereumjs-util>ethereum-cryptography>hash.js": true, "lodash": true @@ -4300,6 +4020,11 @@ "ganache>abstract-level>buffer": true } }, + "eth-lattice-keyring>gridplus-sdk>rlp": { + "globals": { + "TextEncoder": true + } + }, "eth-lattice-keyring>gridplus-sdk>secp256k1": { "packages": { "@metamask/ppom-validator>elliptic": true diff --git a/lavamoat/build-system/policy.json b/lavamoat/build-system/policy.json index 635158958f06..02078b5d39fc 100644 --- a/lavamoat/build-system/policy.json +++ b/lavamoat/build-system/policy.json @@ -1161,7 +1161,7 @@ "@lavamoat/lavapack>combine-source-map>inline-source-map": true, "@lavamoat/lavapack>combine-source-map>lodash.memoize": true, "@lavamoat/lavapack>combine-source-map>source-map": true, - "nyc>convert-source-map": true + "gulp-sourcemaps>convert-source-map": true } }, "@lavamoat/lavapack>combine-source-map>inline-source-map": { @@ -1670,6 +1670,30 @@ "browserify>duplexer2>readable-stream>safe-buffer": true } }, + "browserify>glob": { + "builtin": { + "assert": true, + "events.EventEmitter": true, + "fs": true, + "path.join": true, + "path.resolve": true, + "util": true + }, + "globals": { + "console.error": true, + "process.cwd": true, + "process.nextTick": true, + "process.platform": true + }, + "packages": { + "@metamask/object-multiplex>once": true, + "eslint>minimatch": true, + "gulp-watch>path-is-absolute": true, + "mocha>glob>fs.realpath": true, + "mocha>glob>inflight": true, + "pumpify>inherits": true + } + }, "browserify>has": { "packages": { "browserify>has>function-bind": true @@ -2313,7 +2337,7 @@ "setTimeout": true }, "packages": { - "nyc>glob": true + "browserify>glob": true } }, "depcheck>@babel/traverse": { @@ -2552,11 +2576,11 @@ "process.cwd": true }, "packages": { + "browserify>glob": true, "del>is-glob": true, "depcheck>resolve": true, "eslint-plugin-import>tsconfig-paths": true, - "nock>debug": true, - "nyc>glob": true + "nock>debug": true } }, "eslint-plugin-import": { @@ -3910,13 +3934,13 @@ "gulp-sourcemaps>@gulp-sourcemaps/identity-map": true, "gulp-sourcemaps>@gulp-sourcemaps/map-sources": true, "gulp-sourcemaps>acorn": true, + "gulp-sourcemaps>convert-source-map": true, "gulp-sourcemaps>css": true, "gulp-sourcemaps>debug-fabulous": true, "gulp-sourcemaps>detect-newline": true, "gulp-sourcemaps>source-map": true, "gulp-sourcemaps>strip-bom-string": true, - "gulp-sourcemaps>through2": true, - "nyc>convert-source-map": true + "gulp-sourcemaps>through2": true } }, "gulp-sourcemaps>@gulp-sourcemaps/identity-map": { @@ -4033,6 +4057,15 @@ "define": true } }, + "gulp-sourcemaps>convert-source-map": { + "builtin": { + "fs.readFileSync": true, + "path.join": true + }, + "globals": { + "Buffer.from": true + } + }, "gulp-sourcemaps>css": { "builtin": { "fs.readFileSync": true, @@ -5719,7 +5752,7 @@ "gulp>gulp-cli>matchdep>micromatch>fragment-cache": true, "gulp>gulp-cli>matchdep>micromatch>nanomatch>define-property": true, "gulp>gulp-cli>matchdep>micromatch>regex-not": true, - "nyc>spawn-wrap>is-windows": true + "gulp>gulp-cli>replace-homedir>is-absolute>is-windows": true } }, "gulp>gulp-cli>matchdep>micromatch>nanomatch>define-property": { @@ -5737,7 +5770,7 @@ "gulp>gulp-cli>replace-homedir>is-absolute": { "packages": { "gulp>gulp-cli>replace-homedir>is-absolute>is-relative": true, - "nyc>spawn-wrap>is-windows": true + "gulp>gulp-cli>replace-homedir>is-absolute>is-windows": true } }, "gulp>gulp-cli>replace-homedir>is-absolute>is-relative": { @@ -5750,6 +5783,13 @@ "gulp>gulp-cli>replace-homedir>is-absolute>is-relative>is-unc-path>unc-path-regex": true } }, + "gulp>gulp-cli>replace-homedir>is-absolute>is-windows": { + "globals": { + "define": true, + "isWindows": "write", + "process": true + } + }, "gulp>undertaker": { "builtin": { "assert": true, @@ -5970,6 +6010,7 @@ "process.nextTick": true }, "packages": { + "browserify>glob": true, "eslint>glob-parent": true, "gulp>glob-watcher>is-negated-glob": true, "gulp>vinyl-fs>glob-stream>ordered-read-streams": true, @@ -5977,7 +6018,6 @@ "gulp>vinyl-fs>glob-stream>readable-stream": true, "gulp>vinyl-fs>glob-stream>to-absolute-glob": true, "gulp>vinyl-fs>glob-stream>unique-stream": true, - "nyc>glob": true, "react-markdown>unified>extend": true, "vinyl>remove-trailing-separator": true } @@ -6464,11 +6504,11 @@ }, "packages": { "del>graceful-fs": true, + "gulp-sourcemaps>convert-source-map": true, "gulp>vinyl-fs>remove-bom-buffer": true, "gulp>vinyl-fs>vinyl-sourcemap>append-buffer": true, "gulp>vinyl-fs>vinyl-sourcemap>normalize-path": true, "gulp>vinyl-fs>vinyl-sourcemap>now-and-later": true, - "nyc>convert-source-map": true, "vinyl": true } }, @@ -6769,7 +6809,7 @@ }, "packages": { "mocha>find-up>locate-path": true, - "nyc>find-up>path-exists": true + "mocha>find-up>path-exists": true } }, "mocha>find-up>locate-path": { @@ -6793,6 +6833,47 @@ "@storybook/test-runner>jest-circus>p-limit": true } }, + "mocha>find-up>path-exists": { + "builtin": { + "fs.access": true, + "fs.accessSync": true, + "util.promisify": true + } + }, + "mocha>glob>fs.realpath": { + "builtin": { + "fs.lstat": true, + "fs.lstatSync": true, + "fs.readlink": true, + "fs.readlinkSync": true, + "fs.realpath": true, + "fs.realpathSync": true, + "fs.stat": true, + "fs.statSync": true, + "path.normalize": true, + "path.resolve": true + }, + "globals": { + "console.error": true, + "console.trace": true, + "process.env.NODE_DEBUG": true, + "process.nextTick": true, + "process.noDeprecation": true, + "process.platform": true, + "process.throwDeprecation": true, + "process.traceDeprecation": true, + "process.version": true + } + }, + "mocha>glob>inflight": { + "globals": { + "process.nextTick": true + }, + "packages": { + "@metamask/object-multiplex>once": true, + "@metamask/object-multiplex>once>wrappy": true + } + }, "mocha>log-symbols": { "packages": { "chalk": true, @@ -6855,105 +6936,6 @@ "node-sass": { "native": true }, - "nyc>convert-source-map": { - "builtin": { - "fs.readFileSync": true, - "path.join": true - }, - "globals": { - "Buffer.from": true - } - }, - "nyc>find-up>path-exists": { - "builtin": { - "fs.access": true, - "fs.accessSync": true, - "util.promisify": true - } - }, - "nyc>glob": { - "builtin": { - "assert": true, - "events.EventEmitter": true, - "fs": true, - "path.join": true, - "path.resolve": true, - "util": true - }, - "globals": { - "console.error": true, - "process.cwd": true, - "process.nextTick": true, - "process.platform": true - }, - "packages": { - "@metamask/object-multiplex>once": true, - "eslint>minimatch": true, - "gulp-watch>path-is-absolute": true, - "nyc>glob>fs.realpath": true, - "nyc>glob>inflight": true, - "pumpify>inherits": true - } - }, - "nyc>glob>fs.realpath": { - "builtin": { - "fs.lstat": true, - "fs.lstatSync": true, - "fs.readlink": true, - "fs.readlinkSync": true, - "fs.realpath": true, - "fs.realpathSync": true, - "fs.stat": true, - "fs.statSync": true, - "path.normalize": true, - "path.resolve": true - }, - "globals": { - "console.error": true, - "console.trace": true, - "process.env.NODE_DEBUG": true, - "process.nextTick": true, - "process.noDeprecation": true, - "process.platform": true, - "process.throwDeprecation": true, - "process.traceDeprecation": true, - "process.version": true - } - }, - "nyc>glob>inflight": { - "globals": { - "process.nextTick": true - }, - "packages": { - "@metamask/object-multiplex>once": true, - "@metamask/object-multiplex>once>wrappy": true - } - }, - "nyc>resolve-from": { - "builtin": { - "fs.realpathSync": true, - "module._nodeModulePaths": true, - "module._resolveFilename": true, - "path.join": true, - "path.resolve": true - } - }, - "nyc>signal-exit": { - "builtin": { - "assert.equal": true, - "events": true - }, - "globals": { - "process": true - } - }, - "nyc>spawn-wrap>is-windows": { - "globals": { - "define": true, - "isWindows": "write", - "process": true - } - }, "nyc>yargs>set-blocking": { "globals": { "process.stderr": true, @@ -8291,7 +8273,6 @@ "lodash": true, "mocha>log-symbols": true, "nock>debug": true, - "nyc>resolve-from": true, "stylelint>@stylelint/postcss-css-in-js": true, "stylelint>@stylelint/postcss-markdown": true, "stylelint>autoprefixer": true, @@ -8319,6 +8300,7 @@ "stylelint>postcss-selector-parser": true, "stylelint>postcss-syntax": true, "stylelint>postcss-value-parser": true, + "stylelint>resolve-from": true, "stylelint>specificity": true, "stylelint>style-search": true, "stylelint>sugarss": true, @@ -8519,7 +8501,7 @@ "setTimeout": true }, "packages": { - "nyc>glob": true + "browserify>glob": true } }, "stylelint>file-entry-cache>flat-cache>write": { @@ -8821,6 +8803,15 @@ "process.platform": true } }, + "stylelint>resolve-from": { + "builtin": { + "fs.realpathSync": true, + "module._nodeModulePaths": true, + "module._resolveFilename": true, + "path.join": true, + "path.resolve": true + } + }, "stylelint>specificity": { "globals": { "define": true @@ -8932,11 +8923,20 @@ }, "packages": { "eslint>imurmurhash": true, - "nyc>signal-exit": true, "stylelint>write-file-atomic>is-typedarray": true, + "stylelint>write-file-atomic>signal-exit": true, "stylelint>write-file-atomic>typedarray-to-buffer": true } }, + "stylelint>write-file-atomic>signal-exit": { + "builtin": { + "assert.equal": true, + "events": true + }, + "globals": { + "process": true + } + }, "stylelint>write-file-atomic>typedarray-to-buffer": { "globals": { "Buffer.from": true diff --git a/nyc.config.js b/nyc.config.js deleted file mode 100644 index 408ea27b427b..000000000000 --- a/nyc.config.js +++ /dev/null @@ -1,17 +0,0 @@ -// nyc is our coverage reporter for mocha, and currently is collecting -// coverage for .yarn folder. all of these are default excludes except the -// .yarn/** entry. This entire file should be removable once we complete the -// migration from mocha to jest in the app folder. -module.exports = { - exclude: [ - 'coverage/**', - 'packages/*/test/**', - 'test/**', - 'test{,-*}.js', - '**/*{.,-}test.js', - '**/__tests__/**', - '**/node_modules/**', - '**/babel.config.js', - '.yarn/**', - ], -}; diff --git a/offscreen/scripts/offscreen.ts b/offscreen/scripts/offscreen.ts index 8c0598b4bd3f..654c99151c28 100644 --- a/offscreen/scripts/offscreen.ts +++ b/offscreen/scripts/offscreen.ts @@ -1,5 +1,6 @@ import { BrowserRuntimePostMessageStream } from '@metamask/post-message-stream'; import { ProxySnapExecutor } from '@metamask/snaps-execution-environments'; +import { OffscreenCommunicationTarget } from '../../shared/constants/offscreen-communication'; import initLedger from './ledger'; import initTrezor from './trezor'; import initLattice from './lattice'; @@ -19,4 +20,9 @@ const parentStream = new BrowserRuntimePostMessageStream({ target: 'parent', }); -ProxySnapExecutor.initialize(parentStream); +ProxySnapExecutor.initialize(parentStream, './snaps/index.html'); + +chrome.runtime.sendMessage({ + target: OffscreenCommunicationTarget.extensionMain, + isBooted: true, +}); diff --git a/package.json b/package.json index 003e22c504d3..1afbb70c1e20 100644 --- a/package.json +++ b/package.json @@ -38,14 +38,14 @@ "dapp-chain": "GANACHE_ARGS='-b 2' concurrently -k -n ganache,dapp -p '[{time}][{name}]' 'yarn ganache:start' 'sleep 5 && yarn dapp'", "forwarder": "node ./development/static-server.js ./node_modules/@metamask/forwarder/dist/ --port 9010", "dapp-forwarder": "concurrently -k -n forwarder,dapp -p '[{time}][{name}]' 'yarn forwarder' 'yarn dapp'", - "test:unit": "node ./test/run-unit-tests.js --mocha --jestGlobal --jestDev", + "test:unit": "node ./test/run-unit-tests.js --jestGlobal --jestDev", "test:unit:jest": "node ./test/run-unit-tests.js --jestGlobal --jestDev", "test:unit:jest:watch": "node --inspect ./node_modules/.bin/jest --watch", "test:unit:global": "mocha test/unit-global/*.test.js", - "test:unit:mocha": "node ./test/run-unit-tests.js --mocha", "test:e2e:chrome": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js", "test:e2e:chrome:mmi": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js --mmi", "test:e2e:chrome:flask": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js --build-type flask", + "test:api-specs": "SELENIUM_BROWSER=chrome ts-node test/e2e/run-openrpc-api-test-coverage.ts", "test:e2e:mmi:ci": "yarn playwright test", "test:e2e:mmi:all": "yarn playwright test --project=mmi && yarn test:e2e:mmi:visual", "test:e2e:mmi:regular": "yarn playwright test --project=mmi", @@ -58,11 +58,9 @@ "test:e2e:firefox": "SELENIUM_BROWSER=firefox node test/e2e/run-all.js", "test:e2e:firefox:flask": "SELENIUM_BROWSER=firefox node test/e2e/run-all.js --build-type flask", "test:e2e:single": "node test/e2e/run-e2e-test.js", - "test:coverage:mocha": "node ./test/run-unit-tests.js --mocha --coverage", "test:coverage:jest": "node ./test/run-unit-tests.js --jestGlobal --coverage", "test:coverage:jest:dev": "node ./test/run-unit-tests.js --jestDev --coverage", - "test:coverage:validate": "node ./test/merge-coverage.js", - "test:coverage": "node ./test/run-unit-tests.js --mocha --jestGlobal --jestDev --coverage && yarn test:coverage:validate", + "test:coverage": "node ./test/run-unit-tests.js --jestGlobal --jestDev --coverage", "test:coverage:html": "yarn test:coverage --html", "ganache:start": "./development/run-ganache.sh", "sentry:publish": "node ./development/sentry-publish.js", @@ -114,6 +112,7 @@ "generate-beta-commit": "node ./development/generate-beta-commit.js", "validate-branch-name": "validate-branch-name", "add-release-label-to-pr-and-linked-issues": "ts-node ./.github/scripts/add-release-label-to-pr-and-linked-issues.ts", + "add-team-label-to-pr": "ts-node ./.github/scripts/add-team-label-to-pr.ts", "check-pr-has-required-labels": "ts-node ./.github/scripts/check-pr-has-required-labels.ts", "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", @@ -190,6 +189,8 @@ "regenerator-runtime@^0.13.4": "patch:regenerator-runtime@npm%3A0.13.7#./.yarn/patches/regenerator-runtime-npm-0.13.7-41bcbe64ea.patch", "regenerator-runtime@^0.13.7": "patch:regenerator-runtime@npm%3A0.13.7#./.yarn/patches/regenerator-runtime-npm-0.13.7-41bcbe64ea.patch", "regenerator-runtime@^0.11.0": "patch:regenerator-runtime@npm%3A0.13.7#./.yarn/patches/regenerator-runtime-npm-0.13.7-41bcbe64ea.patch", + "ws@8.13.0": "^8.17.1", + "ws@7.4.6": "^7.5.10", "jsdom@^16.7.0": "patch:jsdom@npm%3A16.7.0#./.yarn/patches/jsdom-npm-16.7.0-216c5c4bf9.patch", "trim": "^0.0.3", "@eslint/eslintrc@npm:^2.1.4": "patch:@eslint/eslintrc@npm%3A2.1.4#~/.yarn/patches/@eslint-eslintrc-npm-2.1.4-1ff4b5f908.patch", @@ -220,7 +221,7 @@ "semver@7.3.8": "^7.5.4", "@trezor/schema-utils@npm:1.0.2": "patch:@trezor/schema-utils@npm%3A1.0.2#~/.yarn/patches/@trezor-schema-utils-npm-1.0.2-7dd48689b2.patch", "lavamoat-core@npm:^15.1.1": "patch:lavamoat-core@npm%3A15.1.1#~/.yarn/patches/lavamoat-core-npm-15.1.1-51fbe39988.patch", - "@metamask/snaps-sdk": "^4.4.2", + "@metamask/snaps-sdk": "^6.0.0", "@metamask/transaction-controller": "^32.0.0", "@babel/runtime@npm:^7.7.6": "patch:@babel/runtime@npm%3A7.24.0#~/.yarn/patches/@babel-runtime-npm-7.24.0-7eb1dd11a2.patch", "@babel/runtime@npm:^7.9.2": "patch:@babel/runtime@npm%3A7.24.0#~/.yarn/patches/@babel-runtime-npm-7.24.0-7eb1dd11a2.patch", @@ -242,9 +243,6 @@ "@babel/runtime@npm:^7.18.3": "patch:@babel/runtime@npm%3A7.24.0#~/.yarn/patches/@babel-runtime-npm-7.24.0-7eb1dd11a2.patch", "@babel/runtime@npm:^7.8.3": "patch:@babel/runtime@npm%3A7.24.0#~/.yarn/patches/@babel-runtime-npm-7.24.0-7eb1dd11a2.patch", "@babel/runtime@npm:^7.8.4": "patch:@babel/runtime@npm%3A7.24.0#~/.yarn/patches/@babel-runtime-npm-7.24.0-7eb1dd11a2.patch", - "@metamask/keyring-controller@npm:^13.0.0": "patch:@metamask/keyring-controller@npm%3A15.0.0#~/.yarn/patches/@metamask-keyring-controller-npm-15.0.0-fa070ce311.patch", - "@metamask/keyring-controller@npm:^12.2.0": "patch:@metamask/keyring-controller@npm%3A15.0.0#~/.yarn/patches/@metamask-keyring-controller-npm-15.0.0-fa070ce311.patch", - "@metamask/keyring-controller@npm:^14.0.1": "patch:@metamask/keyring-controller@npm%3A15.0.0#~/.yarn/patches/@metamask-keyring-controller-npm-15.0.0-fa070ce311.patch", "@spruceid/siwe-parser@npm:1.1.3": "patch:@spruceid/siwe-parser@npm%3A2.1.0#~/.yarn/patches/@spruceid-siwe-parser-npm-2.1.0-060b7ede7a.patch", "@spruceid/siwe-parser@npm:2.1.0": "patch:@spruceid/siwe-parser@npm%3A2.1.0#~/.yarn/patches/@spruceid-siwe-parser-npm-2.1.0-060b7ede7a.patch", "@trezor/connect-web@npm:^9.2.2": "patch:@trezor/connect-web@npm%3A9.2.2#~/.yarn/patches/@trezor-connect-web-npm-9.2.2-a4de8e45fc.patch", @@ -254,39 +252,43 @@ "@expo/config-plugins/glob": "^10.3.10", "@metamask/network-controller": "patch:@metamask/network-controller@npm%3A19.0.0#~/.yarn/patches/@metamask-network-controller-npm-19.0.0-a5e0d1fe14.patch", "@solana/web3.js/rpc-websockets": "^8.0.1", - "@metamask/network-controller@npm:^19.0.0": "patch:@metamask/network-controller@npm%3A19.0.0#~/.yarn/patches/@metamask-network-controller-npm-19.0.0-a5e0d1fe14.patch" + "@metamask/network-controller@npm:^19.0.0": "patch:@metamask/network-controller@npm%3A19.0.0#~/.yarn/patches/@metamask-network-controller-npm-19.0.0-a5e0d1fe14.patch", + "@metamask/gas-fee-controller@npm:^15.1.1": "patch:@metamask/gas-fee-controller@npm%3A15.1.2#~/.yarn/patches/@metamask-gas-fee-controller-npm-15.1.2-db4d2976aa.patch", + "@metamask/nonce-tracker@npm:^5.0.0": "patch:@metamask/nonce-tracker@npm%3A5.0.0#~/.yarn/patches/@metamask-nonce-tracker-npm-5.0.0-d81478218e.patch" }, "dependencies": { "@babel/runtime": "patch:@babel/runtime@npm%3A7.24.0#~/.yarn/patches/@babel-runtime-npm-7.24.0-7eb1dd11a2.patch", - "@blockaid/ppom_release": "^1.4.7", + "@blockaid/ppom_release": "^1.4.8", "@contentful/rich-text-html-renderer": "^16.3.5", "@ensdomains/content-hash": "^2.5.7", "@ethereumjs/tx": "^4.1.1", "@ethersproject/abi": "^5.6.4", "@ethersproject/bignumber": "^5.7.0", "@ethersproject/contracts": "^5.7.0", + "@ethersproject/hash": "^5.7.0", "@ethersproject/hdnode": "^5.6.2", "@ethersproject/providers": "^5.7.2", + "@ethersproject/wallet": "^5.7.0", "@fortawesome/fontawesome-free": "^5.13.0", "@keystonehq/bc-ur-registry-eth": "^0.19.1", "@keystonehq/metamask-airgapped-keyring": "^0.13.1", "@lavamoat/lavadome-react": "0.0.17", "@lavamoat/snow": "^2.0.1", "@material-ui/core": "^4.11.0", - "@metamask-institutional/custody-controller": "^0.2.27", - "@metamask-institutional/custody-keyring": "^2.0.0", - "@metamask-institutional/extension": "^0.3.24", - "@metamask-institutional/institutional-features": "^1.3.2", + "@metamask-institutional/custody-controller": "^0.2.30", + "@metamask-institutional/custody-keyring": "^2.0.3", + "@metamask-institutional/extension": "^0.3.27", + "@metamask-institutional/institutional-features": "^1.3.5", "@metamask-institutional/portfolio-dashboard": "^1.4.1", "@metamask-institutional/rpc-allowlist": "^1.0.3", - "@metamask-institutional/sdk": "^0.1.27", - "@metamask-institutional/transaction-update": "^0.2.2", + "@metamask-institutional/sdk": "^0.1.30", + "@metamask-institutional/transaction-update": "^0.2.5", "@metamask/abi-utils": "^2.0.2", - "@metamask/accounts-controller": "^16.0.0", + "@metamask/accounts-controller": "^17.0.0", "@metamask/address-book-controller": "^4.0.1", "@metamask/announcement-controller": "^6.1.0", "@metamask/approval-controller": "^7.0.0", - "@metamask/assets-controllers": "patch:@metamask/assets-controllers@patch%3A@metamask/assets-controllers@npm%253A30.0.0%23~/.yarn/patches/@metamask-assets-controllers-npm-30.0.0-8747c20871.patch%3A%3Aversion=30.0.0&hash=9269c8#~/.yarn/patches/@metamask-assets-controllers-patch-26d4328777.patch", + "@metamask/assets-controllers": "patch:@metamask/assets-controllers@patch%3A@metamask/assets-controllers@patch%253A@metamask/assets-controllers@npm%25253A30.0.0%2523~/.yarn/patches/@metamask-assets-controllers-npm-30.0.0-8747c20871.patch%253A%253Aversion=30.0.0&hash=9269c8%23~/.yarn/patches/@metamask-assets-controllers-patch-26d4328777.patch%3A%3Aversion=30.0.0&hash=1ba1a6#~/.yarn/patches/@metamask-assets-controllers-patch-a3b39b55a6.patch", "@metamask/base-controller": "^5.0.1", "@metamask/browser-passworder": "^4.3.0", "@metamask/contract-metadata": "^2.5.0", @@ -305,10 +307,10 @@ "@metamask/ethjs": "^0.6.0", "@metamask/ethjs-contract": "^0.4.1", "@metamask/ethjs-query": "^0.7.1", - "@metamask/gas-fee-controller": "^15.1.2", + "@metamask/gas-fee-controller": "patch:@metamask/gas-fee-controller@npm%3A15.1.2#~/.yarn/patches/@metamask-gas-fee-controller-npm-15.1.2-db4d2976aa.patch", "@metamask/jazzicon": "^2.0.0", "@metamask/keyring-api": "^8.0.0", - "@metamask/keyring-controller": "patch:@metamask/keyring-controller@npm%3A15.0.0#~/.yarn/patches/@metamask-keyring-controller-npm-15.0.0-fa070ce311.patch", + "@metamask/keyring-controller": "^16.1.0", "@metamask/logging-controller": "^3.0.1", "@metamask/logo": "^3.1.2", "@metamask/message-manager": "^7.3.0", @@ -316,7 +318,7 @@ "@metamask/metamask-eth-abis": "^3.1.1", "@metamask/name-controller": "^8.0.0", "@metamask/network-controller": "patch:@metamask/network-controller@npm%3A19.0.0#~/.yarn/patches/@metamask-network-controller-npm-19.0.0-a5e0d1fe14.patch", - "@metamask/notification-controller": "^3.0.0", + "@metamask/notification-controller": "^5.0.1", "@metamask/object-multiplex": "^2.0.0", "@metamask/obs-store": "^9.0.0", "@metamask/permission-controller": "^10.0.0", @@ -325,18 +327,19 @@ "@metamask/post-message-stream": "^8.0.0", "@metamask/ppom-validator": "^0.31.0", "@metamask/providers": "^14.0.2", - "@metamask/queued-request-controller": "^0.10.0", + "@metamask/queued-request-controller": "^2.0.0", "@metamask/rate-limit-controller": "^5.0.1", + "@metamask/rpc-errors": "^6.2.1", "@metamask/safe-event-emitter": "^3.1.1", "@metamask/scure-bip39": "^2.0.3", "@metamask/selected-network-controller": "^15.0.2", - "@metamask/signature-controller": "^14.0.1", + "@metamask/signature-controller": "^16.0.0", "@metamask/smart-transactions-controller": "^10.1.2", - "@metamask/snaps-controllers": "^8.4.0", - "@metamask/snaps-execution-environments": "^6.3.0", - "@metamask/snaps-rpc-methods": "^9.1.2", - "@metamask/snaps-sdk": "^4.4.2", - "@metamask/snaps-utils": "^7.5.0", + "@metamask/snaps-controllers": "^9.2.0", + "@metamask/snaps-execution-environments": "^6.5.0", + "@metamask/snaps-rpc-methods": "^9.1.4", + "@metamask/snaps-sdk": "^6.0.0", + "@metamask/snaps-utils": "^7.7.0", "@metamask/transaction-controller": "^32.0.0", "@metamask/user-operation-controller": "^10.0.0", "@metamask/utils": "^8.2.1", @@ -442,6 +445,7 @@ "@lavamoat/lavadome-core": "0.0.10", "@lavamoat/lavapack": "^6.1.0", "@lgbot/madge": "^6.2.0", + "@metamask/api-specs": "^0.9.3", "@metamask/auto-changelog": "^2.1.0", "@metamask/build-utils": "^1.0.0", "@metamask/eslint-config": "^9.0.0", @@ -455,6 +459,10 @@ "@metamask/test-bundler": "^1.0.0", "@metamask/test-dapp": "^8.4.0", "@octokit/core": "^3.6.0", + "@open-rpc/meta-schema": "^1.14.6", + "@open-rpc/mock-server": "^1.7.5", + "@open-rpc/schema-utils-js": "^1.16.2", + "@open-rpc/test-coverage": "^2.2.2", "@playwright/test": "^1.39.0", "@sentry/cli": "^2.19.4", "@storybook/addon-a11y": "^7.6.19", @@ -567,9 +575,6 @@ "history": "^5.0.0", "husky": "^8.0.3", "ini": "^3.0.0", - "istanbul-lib-coverage": "^3.2.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-reports": "^3.1.5", "jest": "^29.7.0", "jest-canvas-mock": "^2.3.1", "jest-environment-jsdom": "^29.7.0", @@ -585,13 +590,11 @@ "mockttp": "^3.10.1", "nock": "patch:nock@npm%3A13.5.4#~/.yarn/patches/nock-npm-13.5.4-2c4f77b249.patch", "node-fetch": "^2.6.1", - "nyc": "^15.1.0", "postcss": "^8.4.32", "postcss-rtlcss": "^4.0.9", "prettier": "^2.7.1", "prettier-eslint": "^16.3.0", "prettier-plugin-sort-json": "^1.0.0", - "proxyquire": "^2.1.3", "pumpify": "^2.0.1", "randomcolor": "^0.5.4", "react-devtools": "^4.11.0", diff --git a/privacy-snapshot.json b/privacy-snapshot.json index 499582424bd4..b45cf79a6e8f 100644 --- a/privacy-snapshot.json +++ b/privacy-snapshot.json @@ -18,7 +18,7 @@ "etherscan.io", "execution.metamask.io", "fonts.gstatic.com", - "gas.api.infura.io", + "gas.api.cx.metamask.io", "github.com", "goerli.infura.io", "localhost:8000", diff --git a/shared/constants/app.ts b/shared/constants/app.ts index db176b8bbe93..a38ac274d301 100644 --- a/shared/constants/app.ts +++ b/shared/constants/app.ts @@ -1,6 +1,4 @@ -///: BEGIN:ONLY_INCLUDE_IF(snaps) import { DialogType } from '@metamask/snaps-sdk'; -///: END:ONLY_INCLUDE_IF import { RestrictedMethods } from './permissions'; /** @@ -34,6 +32,8 @@ export const MESSAGE_TYPE = { ETH_GET_ENCRYPTION_PUBLIC_KEY: 'eth_getEncryptionPublicKey', ETH_GET_BLOCK_BY_NUMBER: 'eth_getBlockByNumber', ETH_REQUEST_ACCOUNTS: 'eth_requestAccounts', + ETH_SEND_TRANSACTION: 'eth_sendTransaction', + ETH_SEND_RAW_TRANSACTION: 'eth_sendRawTransaction', ETH_SIGN: 'eth_sign', ETH_SIGN_TRANSACTION: 'eth_signTransaction', ETH_SIGN_TYPED_DATA: 'eth_signTypedData', @@ -49,11 +49,9 @@ export const MESSAGE_TYPE = { WALLET_REQUEST_PERMISSIONS: 'wallet_requestPermissions', WATCH_ASSET: 'wallet_watchAsset', WATCH_ASSET_LEGACY: 'metamask_watchAsset', - ///: BEGIN:ONLY_INCLUDE_IF(snaps) SNAP_DIALOG_ALERT: `${RestrictedMethods.snap_dialog}:alert`, SNAP_DIALOG_CONFIRMATION: `${RestrictedMethods.snap_dialog}:confirmation`, SNAP_DIALOG_PROMPT: `${RestrictedMethods.snap_dialog}:prompt`, - ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) MMI_AUTHENTICATE: 'metamaskinstitutional_authenticate', MMI_REAUTHENTICATE: 'metamaskinstitutional_reauthenticate', @@ -67,13 +65,11 @@ export const MESSAGE_TYPE = { ///: END:ONLY_INCLUDE_IF } as const; -///: BEGIN:ONLY_INCLUDE_IF(snaps) export const SNAP_DIALOG_TYPES = { [DialogType.Alert]: MESSAGE_TYPE.SNAP_DIALOG_ALERT, [DialogType.Confirmation]: MESSAGE_TYPE.SNAP_DIALOG_CONFIRMATION, [DialogType.Prompt]: MESSAGE_TYPE.SNAP_DIALOG_PROMPT, }; -///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) export const SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES = { diff --git a/shared/constants/first-party-contracts.ts b/shared/constants/first-party-contracts.ts index 07c3860a5168..100ef1ec6c7c 100644 --- a/shared/constants/first-party-contracts.ts +++ b/shared/constants/first-party-contracts.ts @@ -1,23 +1,35 @@ import { Hex } from '@metamask/utils'; import { CHAIN_IDS } from './network'; +export enum EXPERIENCES_TYPE { + METAMASK_VALIDATOR_STAKING = 'MetaMask Validator Staking', + METAMASK_POOLED_STAKING = 'MetaMask Pooled Staking', + METAMASK_THIRD_PARTY_STAKING = 'MetaMask Third Party Staking', + METAMASK_POOLED_STAKING_V1 = 'MetaMask Pool Staking (v1)', + METAMASK_BRIDGE = 'MetaMask Bridge', + METAMASK_SWAPS = 'MetaMask Swaps', +} + /** * A map of first-party contract names to their addresses on various chains. */ -export const FIRST_PARTY_CONTRACT_NAMES: Record> = { - 'MetaMask Validator Staking': { +export const FIRST_PARTY_CONTRACT_NAMES: Record< + EXPERIENCES_TYPE, + Record +> = { + [EXPERIENCES_TYPE.METAMASK_VALIDATOR_STAKING]: { [CHAIN_IDS.MAINNET]: '0xDc71aFFC862fceB6aD32BE58E098423A7727bEbd', }, - 'MetaMask Pooled Staking': { + [EXPERIENCES_TYPE.METAMASK_POOLED_STAKING]: { [CHAIN_IDS.MAINNET]: '0x4FEF9D741011476750A243aC70b9789a63dd47Df', }, - 'MetaMask Third Party Staking': { + [EXPERIENCES_TYPE.METAMASK_THIRD_PARTY_STAKING]: { [CHAIN_IDS.MAINNET]: '0x1f6692E78dDE07FF8da75769B6d7c716215bC7D0', }, - 'MetaMask Pool Staking (v1)': { + [EXPERIENCES_TYPE.METAMASK_POOLED_STAKING_V1]: { [CHAIN_IDS.MAINNET]: '0xc7bE520a13dC023A1b34C03F4Abdab8A43653F7B', }, - 'MetaMask Bridge': { + [EXPERIENCES_TYPE.METAMASK_BRIDGE]: { [CHAIN_IDS.MAINNET]: '0x0439e60F02a8900a951603950d8D4527f400C3f1', [CHAIN_IDS.OPTIMISM]: '0xB90357f2b86dbfD59c3502215d4060f71DF8ca0e', [CHAIN_IDS.BSC]: '0xaEc23140408534b378bf5832defc426dF8604B59', @@ -28,7 +40,7 @@ export const FIRST_PARTY_CONTRACT_NAMES: Record> = { [CHAIN_IDS.AVALANCHE]: '0x29106d08382d3c73bF477A94333C61Db1142E1B6', [CHAIN_IDS.LINEA_MAINNET]: '0xE3d0d2607182Af5B24f5C3C2E4990A053aDd64e3', }, - 'MetaMask Swaps': { + [EXPERIENCES_TYPE.METAMASK_SWAPS]: { [CHAIN_IDS.MAINNET]: '0x881D40237659C251811CEC9c364ef91dC08D300C', [CHAIN_IDS.BSC]: '0x1a1ec25DC08e98e5E93F1104B5e5cdD298707d31', [CHAIN_IDS.POLYGON]: '0x1a1ec25DC08e98e5E93F1104B5e5cdD298707d31', diff --git a/shared/constants/metametrics.ts b/shared/constants/metametrics.ts index 6db66f796679..209cc3c7f414 100644 --- a/shared/constants/metametrics.ts +++ b/shared/constants/metametrics.ts @@ -580,6 +580,7 @@ export enum MetaMetricsEventName { OnboardingWalletAdvancedSettingsWithAuthenticating = 'Settings Updated with Authenticating', OnboardingWalletAdvancedSettingsWithoutAuthenticating = 'Settings Updated without Authenticating', OnboardingWalletAdvancedSettingsTurnOffProfileSyncing = 'Turn Off Profile Syncing', + OnboardingWalletAdvancedSettingsTurnOnProfileSyncing = 'Turn On Profile Syncing', OnboardingWalletImportAttempted = 'Wallet Import Attempted', OnboardingWalletVideoPlay = 'SRP Intro Video Played', OnboardingTwitterClick = 'External Link Clicked', @@ -681,7 +682,6 @@ export enum MetaMetricsEventName { TransactionFinalized = 'Transaction Finalized', ExitedSwaps = 'Exited Swaps', SwapError = 'Swap Error', - ///: BEGIN:ONLY_INCLUDE_IF(snaps) SnapInstallStarted = 'Snap Install Started', SnapInstallFailed = 'Snap Install Failed', SnapInstallRejected = 'Snap Install Rejected', @@ -693,7 +693,6 @@ export enum MetaMetricsEventName { SnapUpdated = 'Snap Updated', SnapExportUsed = 'Snap Export Used', InsightSnapViewed = 'Insight Snap Viewed', - ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) AddSnapAccountEnabled = 'Add Snap Account Enabled', AddSnapAccountViewed = 'Add Snap Account Viewed', @@ -721,6 +720,9 @@ export enum MetaMetricsEventName { // Notifications PushNotificationReceived = 'Push Notification Received', PushNotificationClicked = 'Push Notification Clicked', + + NftAutoDetectionEnableModal = 'Nft Autodetection Enabled from modal', + NftAutoDetectionDisableModal = 'Nft Autodetection Disabled from modal', // Send sendAssetSelected = 'Send Asset Selected', sendFlowExited = 'Send Flow Exited', @@ -775,6 +777,7 @@ export enum MetaMetricsEventCategory { Tokens = 'Tokens', Transactions = 'Transactions', Wallet = 'Wallet', + Confirmations = 'Confirmations', ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) MMI = 'Institutional', ///: END:ONLY_INCLUDE_IF @@ -827,6 +830,7 @@ export enum MetaMetricsEventLocation { TokenDetails = 'token_details', TokenDetection = 'token_detection', TokenMenu = 'token_menu', + Transaction = 'transaction', } export enum MetaMetricsEventUiCustomization { diff --git a/shared/constants/multichain/assets.ts b/shared/constants/multichain/assets.ts new file mode 100644 index 000000000000..b4b2a74f6c22 --- /dev/null +++ b/shared/constants/multichain/assets.ts @@ -0,0 +1,5 @@ +import { MultichainNetworks } from './networks'; + +export const MULTICHAIN_NATIVE_CURRENCY_TO_CAIP19 = { + BTC: `${MultichainNetworks.BITCOIN}/slip44:0`, +} as const; diff --git a/shared/constants/network.ts b/shared/constants/network.ts index 62fe072bf0c0..452f0584ffaa 100644 --- a/shared/constants/network.ts +++ b/shared/constants/network.ts @@ -1116,3 +1116,10 @@ export const TEST_NETWORKS = [ LINEA_GOERLI_DISPLAY_NAME, LINEA_SEPOLIA_DISPLAY_NAME, ]; + +export const TEST_NETWORK_IDS = [ + CHAIN_IDS.GOERLI, + CHAIN_IDS.SEPOLIA, + CHAIN_IDS.LINEA_GOERLI, + CHAIN_IDS.LINEA_SEPOLIA, +]; diff --git a/shared/constants/offscreen-communication.ts b/shared/constants/offscreen-communication.ts index cab4232c09dc..618239609956 100644 --- a/shared/constants/offscreen-communication.ts +++ b/shared/constants/offscreen-communication.ts @@ -7,6 +7,7 @@ export enum OffscreenCommunicationTarget { ledgerOffscreen = 'ledger-offscreen', latticeOffscreen = 'lattice-offscreen', extension = 'extension-offscreen', + extensionMain = 'extension', } /** diff --git a/shared/constants/permissions.ts b/shared/constants/permissions.ts index 0b3cebc377e5..57cccfc81efb 100644 --- a/shared/constants/permissions.ts +++ b/shared/constants/permissions.ts @@ -3,9 +3,12 @@ export const CaveatTypes = Object.freeze({ restrictNetworkSwitching: 'restrictNetworkSwitching' as const, }); +export const RestrictedEthMethods = Object.freeze({ + eth_accounts: 'eth_accounts', +}); + export const RestrictedMethods = Object.freeze({ eth_accounts: 'eth_accounts', - ///: BEGIN:ONLY_INCLUDE_IF(snaps) snap_dialog: 'snap_dialog', snap_notify: 'snap_notify', snap_manageState: 'snap_manageState', @@ -15,20 +18,15 @@ export const RestrictedMethods = Object.freeze({ snap_getEntropy: 'snap_getEntropy', snap_getLocale: 'snap_getLocale', wallet_snap: 'wallet_snap', - ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) snap_manageAccounts: 'snap_manageAccounts', ///: END:ONLY_INCLUDE_IF } as const); -///: BEGIN:ONLY_INCLUDE_IF(snaps) // ConnectionPermission is pseudo permission used to make possible // displaying pre-approved connections in the UI seamlessly, alongside other permissions. export const ConnectionPermission = Object.freeze({ connection_permission: 'connection_permission', }); -///: END:ONLY_INCLUDE_IF -///: BEGIN:ONLY_INCLUDE_IF(snaps) export * from './snaps/permissions'; -///: END:ONLY_INCLUDE_IF diff --git a/shared/constants/swaps.ts b/shared/constants/swaps.ts index 61f9b58b3ed5..bd3db9d94a2f 100644 --- a/shared/constants/swaps.ts +++ b/shared/constants/swaps.ts @@ -171,7 +171,7 @@ const SWAPS_TESTNET_CHAIN_ID = '0x539'; export const SWAPS_API_V2_BASE_URL = 'https://swap.api.cx.metamask.io'; export const SWAPS_DEV_API_V2_BASE_URL = 'https://swap.dev-api.cx.metamask.io'; export const TOKEN_API_BASE_URL = 'https://tokens.api.cx.metamask.io'; -export const GAS_API_BASE_URL = 'https://gas.api.infura.io'; +export const GAS_API_BASE_URL = 'https://gas.api.cx.metamask.io'; export const GAS_DEV_API_BASE_URL = 'https://gas.uat-api.cx.metamask.io'; const BSC_DEFAULT_BLOCK_EXPLORER_URL = 'https://bscscan.com/'; diff --git a/shared/constants/verification.ts b/shared/constants/verification.ts new file mode 100644 index 000000000000..ce6ddaed9ea9 --- /dev/null +++ b/shared/constants/verification.ts @@ -0,0 +1,28 @@ +import { Hex } from '@metamask/utils'; +import { + EXPERIENCES_TYPE, + FIRST_PARTY_CONTRACT_NAMES, +} from './first-party-contracts'; + +export const TX_SIG_LEN = 130; +export const EXPERIENCES_TO_VERIFY = [EXPERIENCES_TYPE.METAMASK_BRIDGE]; +export const TRUSTED_SIGNERS: Partial> = { + [EXPERIENCES_TYPE.METAMASK_BRIDGE]: + '0x533FbF047Ed13C20e263e2576e41c747206d1348', +}; + +// look up the corresponding experience provided an address on a chain id +export const getExperience = ( + address: Hex, + chainId: Hex, +): EXPERIENCES_TYPE | undefined => + ( + Object.entries(FIRST_PARTY_CONTRACT_NAMES) as [ + EXPERIENCES_TYPE, + Record, + ][] + ).find( + ([, chainMap]) => + (chainMap[chainId]?.toLowerCase() as Hex) === + (address.toLowerCase() as Hex), + )?.[0]; diff --git a/shared/lib/swaps-utils.js b/shared/lib/swaps-utils.js index 303e0f12f0e1..62c71ecbaadb 100644 --- a/shared/lib/swaps-utils.js +++ b/shared/lib/swaps-utils.js @@ -3,6 +3,7 @@ import log from 'loglevel'; import { CHAIN_IDS } from '../constants/network'; import { GAS_API_BASE_URL, + GAS_DEV_API_BASE_URL, SWAPS_API_V2_BASE_URL, SWAPS_CHAINID_DEFAULT_TOKEN_MAP, SWAPS_CLIENT_ID, @@ -17,6 +18,8 @@ import { addHexPrefix } from '../../app/scripts/lib/util'; import { decimalToHex } from '../modules/conversion.utils'; import fetchWithCache from './fetch-with-cache'; +const FALLBACK_GAS_MULTIPLIER = 1.5; + const TEST_CHAIN_IDS = [CHAIN_IDS.GOERLI, CHAIN_IDS.LOCALHOST]; const clientIdHeader = { 'X-Client-Id': SWAPS_CLIENT_ID }; @@ -130,7 +133,7 @@ const getBaseUrlForNewSwapsApi = (type, chainId) => { const v2ApiBaseUrl = useDevApis ? SWAPS_DEV_API_V2_BASE_URL : SWAPS_API_V2_BASE_URL; - const gasApiBaseUrl = GAS_API_BASE_URL; + const gasApiBaseUrl = useDevApis ? GAS_DEV_API_BASE_URL : GAS_API_BASE_URL; const tokenApiBaseUrl = TOKEN_API_BASE_URL; const noNetworkSpecificTypes = ['refreshTime']; // These types don't need network info in the URL. if (noNetworkSpecificTypes.includes(type)) { @@ -324,3 +327,37 @@ export async function fetchTradesInfo( return newQuotes; } + +/** + * Given a gas estimate, gas multiplier, max gas, and custom max gas, returns the max gas limit + * to use for a transaction. + * + * @param {string} gasEstimate - The gas estimate for the transaction. + * @param {number} gasMultiplier - The gas multiplier to use. + * @param {number} maxGas - The max gas limit to use. + * @param {string} customMaxGas - The custom max gas limit to use. + * @returns {string} The max gas limit to use for the transaction. + */ + +export function calculateMaxGasLimit( + gasEstimate, + gasMultiplier = FALLBACK_GAS_MULTIPLIER, + maxGas, + customMaxGas, +) { + const gasLimitForMax = new BigNumber(gasEstimate || 0, 16) + .round(0) + .toString(16); + + const usedGasLimitWithMultiplier = new BigNumber(gasLimitForMax, 16) + .times(gasMultiplier, 10) + .round(0) + .toString(16); + + const nonCustomMaxGasLimit = gasEstimate + ? usedGasLimitWithMultiplier + : `0x${decimalToHex(maxGas || 0)}`; + const maxGasLimit = customMaxGas || nonCustomMaxGasLimit; + + return maxGasLimit; +} diff --git a/shared/lib/swaps-utils.test.js b/shared/lib/swaps-utils.test.js index 5db5fb23085b..4cbc0927e1f3 100644 --- a/shared/lib/swaps-utils.test.js +++ b/shared/lib/swaps-utils.test.js @@ -10,7 +10,11 @@ import { TOKENS, MOCK_TRADE_RESPONSE_2, } from '../../ui/pages/swaps/swaps-util-test-constants'; -import { fetchTradesInfo, shouldEnableDirectWrapping } from './swaps-utils'; +import { + fetchTradesInfo, + shouldEnableDirectWrapping, + calculateMaxGasLimit, +} from './swaps-utils'; jest.mock('./storage-helpers', () => ({ getStorageItem: jest.fn(), @@ -224,4 +228,46 @@ describe('Swaps Utils', () => { expect(shouldEnableDirectWrapping(CHAIN_IDS.MAINNET)).toBe(false); }); }); + + describe('calculateMaxGasLimit', () => { + const gasEstimate = '0x37b15'; + const maxGas = 273740; + let expectedMaxGas = '42d4c'; + let gasMultiplier = 1.2; + let customMaxGas = ''; + + it('should return the max gas limit', () => { + const result = calculateMaxGasLimit( + gasEstimate, + gasMultiplier, + maxGas, + customMaxGas, + ); + expect(result).toStrictEqual(expectedMaxGas); + }); + + it('should return the custom max gas limit', () => { + customMaxGas = '46d4c'; + const result = calculateMaxGasLimit( + gasEstimate, + gasMultiplier, + maxGas, + customMaxGas, + ); + expect(result).toStrictEqual(customMaxGas); + }); + + it('should return the max gas limit with a gas multiplier of 4.5', () => { + gasMultiplier = 4.5; + expectedMaxGas = 'fa9df'; + customMaxGas = ''; + const result = calculateMaxGasLimit( + gasEstimate, + gasMultiplier, + maxGas, + customMaxGas, + ); + expect(result).toStrictEqual(expectedMaxGas); + }); + }); }); diff --git a/shared/lib/ui-utils.js b/shared/lib/ui-utils.js index cbb28bf0bd16..a1fcf17d159c 100644 --- a/shared/lib/ui-utils.js +++ b/shared/lib/ui-utils.js @@ -15,7 +15,7 @@ export const AUTO_DETECT_TOKEN_LEARN_MORE_LINK = export const CONSENSYS_TERMS_OF_USE = 'https://consensys.io/terms-of-use'; export const SECURITY_ALERTS_LEARN_MORE_LINK = - 'https://support.metamask.io/hc/en-us/articles/19878220833947'; + 'https://support.metamask.io/privacy-and-security/how-to-turn-on-security-alerts/'; export const TRANSACTION_SIMULATIONS_LEARN_MORE_LINK = 'https://support.metamask.io/transactions-and-gas/transactions/simulations/'; diff --git a/shared/modules/selectors/index.ts b/shared/modules/selectors/index.ts index 51c2d67f930c..2274b562629b 100644 --- a/shared/modules/selectors/index.ts +++ b/shared/modules/selectors/index.ts @@ -1,3 +1,4 @@ export * from './smart-transactions'; export * from './feature-flags'; export * from './token-auto-detect'; +export * from './nft-auto-detect'; diff --git a/shared/modules/selectors/nft-auto-detect.ts b/shared/modules/selectors/nft-auto-detect.ts new file mode 100644 index 000000000000..889a0348f179 --- /dev/null +++ b/shared/modules/selectors/nft-auto-detect.ts @@ -0,0 +1,29 @@ +import { + getIsMainnet, + getUseNftDetection, +} from '../../../ui/selectors/selectors'; + +type NftAutoDetectionMetaMaskState = { + metamask: { + preferences: { + showNftAutodetectModal: boolean | null; + }; + }; +}; + +export const getShowNftAutodetectModal = ( + state: NftAutoDetectionMetaMaskState, +): boolean | null => { + return state.metamask.preferences?.showNftAutodetectModal; +}; + +export const getIsShowNftAutodetectModal = ( + state: NftAutoDetectionMetaMaskState, +) => { + return ( + !getUseNftDetection(state) && + getIsMainnet(state) && + (getShowNftAutodetectModal(state) === null || + getShowNftAutodetectModal(state) === undefined) + ); +}; diff --git a/test/data/confirmations/contract-interaction.ts b/test/data/confirmations/contract-interaction.ts index dd0500bbaec2..55bcb4b26ad0 100644 --- a/test/data/confirmations/contract-interaction.ts +++ b/test/data/confirmations/contract-interaction.ts @@ -4,6 +4,9 @@ import { } from '@metamask/transaction-controller'; import { Confirmation } from '../../../ui/pages/confirmations/types/confirm'; +export const PAYMASTER_AND_DATA = + '0x9d6ac51b972544251fcc0f2902e633e3f9bd3f2900000000000000000000000000000000000000000000000000000000666bfd410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003498a76eb88b702e5e52b00fbc16a36baf89ebe3e0dd23170949cffc0a623011383cced660ff67930308c22e5aa746a2d586629ddbd87046a146225bf80e9d6f1b'; + export const CONTRACT_INTERACTION_SENDER_ADDRESS = '0x2e0d7e8c45221fca00d74a3609a0f7097035d09b'; diff --git a/test/data/confirmations/personal_sign.ts b/test/data/confirmations/personal_sign.ts index c6f7907eccc4..bac5b3f9838a 100644 --- a/test/data/confirmations/personal_sign.ts +++ b/test/data/confirmations/personal_sign.ts @@ -1,3 +1,5 @@ +import { SignatureRequestType } from '../../../ui/pages/confirmations/types/confirm'; + export const PERSONAL_SIGN_SENDER_ADDRESS = '0x8eeee1781fd885ff5ddef7789486676961873d12'; @@ -13,7 +15,7 @@ export const unapprovedPersonalSignMsg = { origin: 'https://metamask.github.io', siwe: { isSIWEMessage: false, parsedMessage: null }, }, -}; +} as SignatureRequestType; export const signatureRequestSIWE = { id: '210ca3b0-1ccb-11ef-b096-89c4d726ebb5', @@ -45,7 +47,7 @@ export const signatureRequestSIWE = { }, }, }, -}; +} as SignatureRequestType; export const SignatureRequestSIWEWithResources = { id: '210ca3b0-1ccb-11ef-b096-89c4d726ebb5', @@ -83,4 +85,4 @@ export const SignatureRequestSIWEWithResources = { }, }, }, -}; +} as SignatureRequestType; diff --git a/test/data/confirmations/typed_sign.ts b/test/data/confirmations/typed_sign.ts index e368d74234f1..61e6fa68713d 100644 --- a/test/data/confirmations/typed_sign.ts +++ b/test/data/confirmations/typed_sign.ts @@ -1,3 +1,5 @@ +import { SignatureRequestType } from '../../../ui/pages/confirmations/types/confirm'; + export const unapprovedTypedSignMsgV1 = { id: '82ab2400-e2c6-11ee-9627-73cc88f00492', securityAlertResponse: { @@ -19,7 +21,7 @@ export const unapprovedTypedSignMsgV1 = { version: 'V1', origin: 'https://metamask.github.io', }, -}; +} as SignatureRequestType; const rawMessageV3 = { types: { @@ -71,7 +73,7 @@ export const unapprovedTypedSignMsgV3 = { signatureMethod: 'eth_signTypedData_v3', origin: 'https://metamask.github.io', }, -}; +} as SignatureRequestType; export const rawMessageV4 = { domain: { @@ -132,7 +134,7 @@ export const unapprovedTypedSignMsgV4 = { data: JSON.stringify(rawMessageV4), origin: 'https://metamask.github.io', }, -}; +} as SignatureRequestType; export const permitSignatureMsg = { id: '0b1787a0-1c44-11ef-b70d-e7064bd7b659', @@ -151,4 +153,4 @@ export const permitSignatureMsg = { signatureMethod: 'eth_signTypedData_v4', origin: 'https://metamask.github.io', }, -}; +} as SignatureRequestType; diff --git a/test/data/mock-send-state.json b/test/data/mock-send-state.json index f1caf9b7eeee..c8604c96c055 100644 --- a/test/data/mock-send-state.json +++ b/test/data/mock-send-state.json @@ -1245,6 +1245,7 @@ "send": { "amountMode": "INPUT", "currentTransactionUUID": "1-tx", + "disabledSwapAndSendNetworks": [], "draftTransactions": { "1-tx": { "amount": { diff --git a/test/data/mock-state.json b/test/data/mock-state.json index 9c05c573584c..920533023a58 100644 --- a/test/data/mock-state.json +++ b/test/data/mock-state.json @@ -1946,6 +1946,7 @@ "send": { "amountMode": "INPUT", "currentTransactionUUID": null, + "disabledSwapAndSendNetworks": [], "draftTransactions": {}, "eip1559support": false, "gasEstimateIsLoading": true, diff --git a/test/data/transaction-data.json b/test/data/transaction-data.json index bdcbd7cd6abf..ad2dacfe082c 100644 --- a/test/data/transaction-data.json +++ b/test/data/transaction-data.json @@ -1252,5 +1252,1393 @@ }, "hasRetried": false, "hasCancelled": false + }, + { + "nonce": "0x9", + "transactions": [ + { + "actionId": 1718133500046.724, + "approvalTxId": "5ce4f880-2827-11ef-9c65-d1738f73c46f", + "baseFeePerGas": "0x0", + "blockTimestamp": "0x6668a2fd", + "chainId": "0x38", + "defaultGasEstimates": { + "estimateType": "medium", + "gas": "0x51fc6", + "maxFeePerGas": "0xb2d05e00", + "maxPriorityFeePerGas": "0xb2d05e00" + }, + "destinationTokenAddress": "0x1af3f329e8be154074d8769d1ffa4ee058b1dbc3", + "destinationTokenAmount": "4990565483715933000", + "destinationTokenDecimals": 18, + "destinationTokenSymbol": "DAI", + "hash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "history": [], + "id": "5cf9b900-2827-11ef-9c65-d1738f73c46f", + "networkClientId": "899c28d9-9f01-43c6-be76-b5e9f3cfe8bc", + "origin": "metamask", + "r": "0x443ec5a1269c18767b078894d177b4067831ea9ddcb33ebf92670d6667940985", + "rawTx": "0x02f903f1380984b2d05e0084b2d05e0083051fc6943cb693656622fc470f0bb07d3f5813f7889bf82e80b90384048226a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d0000000000000000000000000000000000000000000000004563918244f4000000000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c188380000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d0000000000000000000000001af3f329e8be154074d8769d1ffa4ee058b1dbc30000000000000000000000000000000000000000000000004563918244f4000000000000000000000000000000000000000000000000000043df73024b82680400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000148c43c9ef600000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000004563918244f4000000000000000000000000000000000000000000000000000043df73024b826804000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d000000000000000000000000e9e7cea3dedca5984780bafc599bd69add087d560000000000000000000000001af3f329e8be154074d8769d1ffa4ee058b1dbc3869584cd00000000000000000000000011ededebf63bef0ea2d2d071bdf88f71543ec6fb0000000000000000000000000000000000000000aca7f7533c2098f78f35db24000000000000000000000000000000000000000000000000c001a0443ec5a1269c18767b078894d177b4067831ea9ddcb33ebf92670d6667940985a0519c27715e9ce0cab31e7738ffad7d454088c3e3c855f54b8a0965a17d999c62", + "s": "0x519c27715e9ce0cab31e7738ffad7d454088c3e3c855f54b8a0965a17d999c62", + "sendFlowHistory": [], + "sourceTokenAddress": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + "sourceTokenAmount": "5000000000000000000", + "sourceTokenDecimals": 18, + "sourceTokenSymbol": "USDC", + "status": "confirmed", + "submittedTime": 1718133500335, + "swapAndSendRecipient": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838", + "swapTokenValue": "5", + "time": 1718133500048, + "txParams": { + "data": "0x048226a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d0000000000000000000000000000000000000000000000004563918244f4000000000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c188380000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d0000000000000000000000001af3f329e8be154074d8769d1ffa4ee058b1dbc30000000000000000000000000000000000000000000000004563918244f4000000000000000000000000000000000000000000000000000043df73024b82680400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000148c43c9ef600000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000004563918244f4000000000000000000000000000000000000000000000000000043df73024b826804000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d000000000000000000000000e9e7cea3dedca5984780bafc599bd69add087d560000000000000000000000001af3f329e8be154074d8769d1ffa4ee058b1dbc3869584cd00000000000000000000000011ededebf63bef0ea2d2d071bdf88f71543ec6fb0000000000000000000000000000000000000000aca7f7533c2098f78f35db24000000000000000000000000000000000000000000000000", + "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838", + "gas": "0x51fc6", + "gasLimit": "0x51fc6", + "maxFeePerGas": "0xb2d05e00", + "maxPriorityFeePerGas": "0xb2d05e00", + "nonce": "0x9", + "to": "0x3cb693656622fc470f0bb07d3f5813f7889bf82e", + "type": "0x2", + "value": "0x0" + }, + "txReceipt": { + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "contractAddress": null, + "cumulativeGasUsed": "0x30fb3e", + "effectiveGasPrice": "0xb2d05e00", + "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838", + "gasUsed": "0x401d6", + "logs": [ + { + "address": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x0000000000000000000000000000000000000000000000004563918244f40000", + "logIndex": "0x45", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c18838", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x0000000000000000000000000000000000000000000000000000000000000000", + "logIndex": "0x46", + "removed": false, + "topics": [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c18838", + "0x0000000000000000000000003cb693656622fc470f0bb07d3f5813f7889bf82e" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x0000000000000000000000000000000000000000000000004563918244f40000", + "logIndex": "0x47", + "removed": false, + "topics": [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889", + "0x000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x0000000000000000000000000000000000000000000000004563918244f40000", + "logIndex": "0x48", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889", + "0x0000000000000000000000002354ef4df11afacb85a5c7f98b624072eccddbb1" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x0000000000000000000000000000000000000000000000000000000000000000", + "logIndex": "0x49", + "removed": false, + "topics": [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889", + "0x000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0xe9e7cea3dedca5984780bafc599bd69add087d56", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x00000000000000000000000000000000000000000000000045351670c8c8eb16", + "logIndex": "0x4a", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000002354ef4df11afacb85a5c7f98b624072eccddbb1", + "0x00000000000000000000000066fdb2eccfb58cf098eaa419e5efde841368e489" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x2354ef4df11afacb85a5c7f98b624072eccddbb1", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x000000000000000000000000000000000000000000005cf210c436771e7d4016000000000000000000000000000000000000000000005cfaf19326e57b4c5707", + "logIndex": "0x4b", + "removed": false, + "topics": [ + "0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x2354ef4df11afacb85a5c7f98b624072eccddbb1", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x0000000000000000000000000000000000000000000000004563918244f400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045351670c8c8eb16", + "logIndex": "0x4c", + "removed": false, + "topics": [ + "0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822", + "0x000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff", + "0x00000000000000000000000066fdb2eccfb58cf098eaa419e5efde841368e489" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x1af3f329e8be154074d8769d1ffa4ee058b1dbc3", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x000000000000000000000000000000000000000000000000453047e918796cfd", + "logIndex": "0x4d", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x00000000000000000000000066fdb2eccfb58cf098eaa419e5efde841368e489", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x66fdb2eccfb58cf098eaa419e5efde841368e489", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x0000000000000000000000000000000000000000000020c638a3898eb7869dcf0000000000000000000000000000000000000000000020af974dbd9430b354c8", + "logIndex": "0x4e", + "removed": false, + "topics": [ + "0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x66fdb2eccfb58cf098eaa419e5efde841368e489", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045351670c8c8eb16000000000000000000000000000000000000000000000000453047e918796cfd0000000000000000000000000000000000000000000000000000000000000000", + "logIndex": "0x4f", + "removed": false, + "topics": [ + "0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822", + "0x000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x0000000000000000000000000000000000000000000000000000000000000000", + "logIndex": "0x50", + "removed": false, + "topics": [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889", + "0x000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x1af3f329e8be154074d8769d1ffa4ee058b1dbc3", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x000000000000000000000000000000000000000000000000453047e918796cfd", + "logIndex": "0x51", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889", + "0x000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c18838" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x3cb693656622fc470f0bb07d3f5813f7889bf82e", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000045357415000000000000000000000000000000000000000000000000000000000", + "logIndex": "0x52", + "removed": false, + "topics": [ + "0x0c18aae526accb31b01cf9a15bdf435e70632ee31efc4c5c0752c4262ea45d2f" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + } + ], + "logsBloom": "0x006000000040001000000000800004000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000002000c0000000010000012000040008000000240000000000000088000000000000000008000000000000000000000004000040000000000000002008000010000000020000000000000001000000000040008000000000000800084000004000000100020000000000000000004000000000000000000000000000004000000000000000000002000000020000000000001000000040100000001000000020000000000010000000004002000000100000000000800000008000000000000001000000", + "status": "0x1", + "to": "0x3cb693656622fc470f0bb07d3f5813f7889bf82e", + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21", + "type": "0x2" + }, + "type": "swapAndSend", + "userEditedGasLimit": false, + "userFeeLevel": "medium", + "v": "0x1", + "verifiedOnBlockchain": true + } + ], + "initialTransaction": { + "actionId": 1718133500046.724, + "approvalTxId": "5ce4f880-2827-11ef-9c65-d1738f73c46f", + "baseFeePerGas": "0x0", + "blockTimestamp": "0x6668a2fd", + "chainId": "0x38", + "defaultGasEstimates": { + "estimateType": "medium", + "gas": "0x51fc6", + "maxFeePerGas": "0xb2d05e00", + "maxPriorityFeePerGas": "0xb2d05e00" + }, + "destinationTokenAddress": "0x1af3f329e8be154074d8769d1ffa4ee058b1dbc3", + "destinationTokenAmount": "4990565483715933000", + "destinationTokenDecimals": 18, + "destinationTokenSymbol": "DAI", + "hash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "history": [], + "id": "5cf9b900-2827-11ef-9c65-d1738f73c46f", + "networkClientId": "899c28d9-9f01-43c6-be76-b5e9f3cfe8bc", + "origin": "metamask", + "r": "0x443ec5a1269c18767b078894d177b4067831ea9ddcb33ebf92670d6667940985", + "rawTx": "0x02f903f1380984b2d05e0084b2d05e0083051fc6943cb693656622fc470f0bb07d3f5813f7889bf82e80b90384048226a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d0000000000000000000000000000000000000000000000004563918244f4000000000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c188380000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d0000000000000000000000001af3f329e8be154074d8769d1ffa4ee058b1dbc30000000000000000000000000000000000000000000000004563918244f4000000000000000000000000000000000000000000000000000043df73024b82680400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000148c43c9ef600000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000004563918244f4000000000000000000000000000000000000000000000000000043df73024b826804000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d000000000000000000000000e9e7cea3dedca5984780bafc599bd69add087d560000000000000000000000001af3f329e8be154074d8769d1ffa4ee058b1dbc3869584cd00000000000000000000000011ededebf63bef0ea2d2d071bdf88f71543ec6fb0000000000000000000000000000000000000000aca7f7533c2098f78f35db24000000000000000000000000000000000000000000000000c001a0443ec5a1269c18767b078894d177b4067831ea9ddcb33ebf92670d6667940985a0519c27715e9ce0cab31e7738ffad7d454088c3e3c855f54b8a0965a17d999c62", + "s": "0x519c27715e9ce0cab31e7738ffad7d454088c3e3c855f54b8a0965a17d999c62", + "sendFlowHistory": [], + "sourceTokenAddress": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + "sourceTokenAmount": "5000000000000000000", + "sourceTokenDecimals": 18, + "sourceTokenSymbol": "USDC", + "status": "confirmed", + "submittedTime": 1718133500335, + "swapAndSendRecipient": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838", + "swapTokenValue": "5", + "time": 1718133500048, + "txParams": { + "data": "0x048226a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d0000000000000000000000000000000000000000000000004563918244f4000000000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c188380000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d0000000000000000000000001af3f329e8be154074d8769d1ffa4ee058b1dbc30000000000000000000000000000000000000000000000004563918244f4000000000000000000000000000000000000000000000000000043df73024b82680400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000148c43c9ef600000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000004563918244f4000000000000000000000000000000000000000000000000000043df73024b826804000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d000000000000000000000000e9e7cea3dedca5984780bafc599bd69add087d560000000000000000000000001af3f329e8be154074d8769d1ffa4ee058b1dbc3869584cd00000000000000000000000011ededebf63bef0ea2d2d071bdf88f71543ec6fb0000000000000000000000000000000000000000aca7f7533c2098f78f35db24000000000000000000000000000000000000000000000000", + "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838", + "gas": "0x51fc6", + "gasLimit": "0x51fc6", + "maxFeePerGas": "0xb2d05e00", + "maxPriorityFeePerGas": "0xb2d05e00", + "nonce": "0x9", + "to": "0x3cb693656622fc470f0bb07d3f5813f7889bf82e", + "type": "0x2", + "value": "0x0" + }, + "txReceipt": { + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "contractAddress": null, + "cumulativeGasUsed": "0x30fb3e", + "effectiveGasPrice": "0xb2d05e00", + "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838", + "gasUsed": "0x401d6", + "logs": [ + { + "address": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x0000000000000000000000000000000000000000000000004563918244f40000", + "logIndex": "0x45", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c18838", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x0000000000000000000000000000000000000000000000000000000000000000", + "logIndex": "0x46", + "removed": false, + "topics": [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c18838", + "0x0000000000000000000000003cb693656622fc470f0bb07d3f5813f7889bf82e" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x0000000000000000000000000000000000000000000000004563918244f40000", + "logIndex": "0x47", + "removed": false, + "topics": [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889", + "0x000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x0000000000000000000000000000000000000000000000004563918244f40000", + "logIndex": "0x48", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889", + "0x0000000000000000000000002354ef4df11afacb85a5c7f98b624072eccddbb1" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x0000000000000000000000000000000000000000000000000000000000000000", + "logIndex": "0x49", + "removed": false, + "topics": [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889", + "0x000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0xe9e7cea3dedca5984780bafc599bd69add087d56", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x00000000000000000000000000000000000000000000000045351670c8c8eb16", + "logIndex": "0x4a", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000002354ef4df11afacb85a5c7f98b624072eccddbb1", + "0x00000000000000000000000066fdb2eccfb58cf098eaa419e5efde841368e489" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x2354ef4df11afacb85a5c7f98b624072eccddbb1", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x000000000000000000000000000000000000000000005cf210c436771e7d4016000000000000000000000000000000000000000000005cfaf19326e57b4c5707", + "logIndex": "0x4b", + "removed": false, + "topics": [ + "0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x2354ef4df11afacb85a5c7f98b624072eccddbb1", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x0000000000000000000000000000000000000000000000004563918244f400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045351670c8c8eb16", + "logIndex": "0x4c", + "removed": false, + "topics": [ + "0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822", + "0x000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff", + "0x00000000000000000000000066fdb2eccfb58cf098eaa419e5efde841368e489" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x1af3f329e8be154074d8769d1ffa4ee058b1dbc3", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x000000000000000000000000000000000000000000000000453047e918796cfd", + "logIndex": "0x4d", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x00000000000000000000000066fdb2eccfb58cf098eaa419e5efde841368e489", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x66fdb2eccfb58cf098eaa419e5efde841368e489", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x0000000000000000000000000000000000000000000020c638a3898eb7869dcf0000000000000000000000000000000000000000000020af974dbd9430b354c8", + "logIndex": "0x4e", + "removed": false, + "topics": [ + "0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x66fdb2eccfb58cf098eaa419e5efde841368e489", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045351670c8c8eb16000000000000000000000000000000000000000000000000453047e918796cfd0000000000000000000000000000000000000000000000000000000000000000", + "logIndex": "0x4f", + "removed": false, + "topics": [ + "0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822", + "0x000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x0000000000000000000000000000000000000000000000000000000000000000", + "logIndex": "0x50", + "removed": false, + "topics": [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889", + "0x000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x1af3f329e8be154074d8769d1ffa4ee058b1dbc3", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x000000000000000000000000000000000000000000000000453047e918796cfd", + "logIndex": "0x51", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889", + "0x000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c18838" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x3cb693656622fc470f0bb07d3f5813f7889bf82e", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000045357415000000000000000000000000000000000000000000000000000000000", + "logIndex": "0x52", + "removed": false, + "topics": [ + "0x0c18aae526accb31b01cf9a15bdf435e70632ee31efc4c5c0752c4262ea45d2f" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + } + ], + "logsBloom": "0x006000000040001000000000800004000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000002000c0000000010000012000040008000000240000000000000088000000000000000008000000000000000000000004000040000000000000002008000010000000020000000000000001000000000040008000000000000800084000004000000100020000000000000000004000000000000000000000000000004000000000000000000002000000020000000000001000000040100000001000000020000000000010000000004002000000100000000000800000008000000000000001000000", + "status": "0x1", + "to": "0x3cb693656622fc470f0bb07d3f5813f7889bf82e", + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21", + "type": "0x2" + }, + "type": "swapAndSend", + "userEditedGasLimit": false, + "userFeeLevel": "medium", + "v": "0x1", + "verifiedOnBlockchain": true + }, + "primaryTransaction": { + "actionId": 1718133500046.724, + "approvalTxId": "5ce4f880-2827-11ef-9c65-d1738f73c46f", + "baseFeePerGas": "0x0", + "blockTimestamp": "0x6668a2fd", + "chainId": "0x38", + "defaultGasEstimates": { + "estimateType": "medium", + "gas": "0x51fc6", + "maxFeePerGas": "0xb2d05e00", + "maxPriorityFeePerGas": "0xb2d05e00" + }, + "destinationTokenAddress": "0x1af3f329e8be154074d8769d1ffa4ee058b1dbc3", + "destinationTokenAmount": "4990565483715933000", + "destinationTokenDecimals": 18, + "destinationTokenSymbol": "DAI", + "hash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "history": [], + "id": "5cf9b900-2827-11ef-9c65-d1738f73c46f", + "networkClientId": "899c28d9-9f01-43c6-be76-b5e9f3cfe8bc", + "origin": "metamask", + "r": "0x443ec5a1269c18767b078894d177b4067831ea9ddcb33ebf92670d6667940985", + "rawTx": "0x02f903f1380984b2d05e0084b2d05e0083051fc6943cb693656622fc470f0bb07d3f5813f7889bf82e80b90384048226a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d0000000000000000000000000000000000000000000000004563918244f4000000000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c188380000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d0000000000000000000000001af3f329e8be154074d8769d1ffa4ee058b1dbc30000000000000000000000000000000000000000000000004563918244f4000000000000000000000000000000000000000000000000000043df73024b82680400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000148c43c9ef600000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000004563918244f4000000000000000000000000000000000000000000000000000043df73024b826804000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d000000000000000000000000e9e7cea3dedca5984780bafc599bd69add087d560000000000000000000000001af3f329e8be154074d8769d1ffa4ee058b1dbc3869584cd00000000000000000000000011ededebf63bef0ea2d2d071bdf88f71543ec6fb0000000000000000000000000000000000000000aca7f7533c2098f78f35db24000000000000000000000000000000000000000000000000c001a0443ec5a1269c18767b078894d177b4067831ea9ddcb33ebf92670d6667940985a0519c27715e9ce0cab31e7738ffad7d454088c3e3c855f54b8a0965a17d999c62", + "s": "0x519c27715e9ce0cab31e7738ffad7d454088c3e3c855f54b8a0965a17d999c62", + "sendFlowHistory": [], + "sourceTokenAddress": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + "sourceTokenAmount": "5000000000000000000", + "sourceTokenDecimals": 18, + "sourceTokenSymbol": "USDC", + "status": "confirmed", + "submittedTime": 1718133500335, + "swapAndSendRecipient": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838", + "swapTokenValue": "5", + "time": 1718133500048, + "txParams": { + "data": "0x048226a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d0000000000000000000000000000000000000000000000004563918244f4000000000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c188380000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d0000000000000000000000001af3f329e8be154074d8769d1ffa4ee058b1dbc30000000000000000000000000000000000000000000000004563918244f4000000000000000000000000000000000000000000000000000043df73024b82680400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000148c43c9ef600000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000004563918244f4000000000000000000000000000000000000000000000000000043df73024b826804000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d000000000000000000000000e9e7cea3dedca5984780bafc599bd69add087d560000000000000000000000001af3f329e8be154074d8769d1ffa4ee058b1dbc3869584cd00000000000000000000000011ededebf63bef0ea2d2d071bdf88f71543ec6fb0000000000000000000000000000000000000000aca7f7533c2098f78f35db24000000000000000000000000000000000000000000000000", + "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838", + "gas": "0x51fc6", + "gasLimit": "0x51fc6", + "maxFeePerGas": "0xb2d05e00", + "maxPriorityFeePerGas": "0xb2d05e00", + "nonce": "0x9", + "to": "0x3cb693656622fc470f0bb07d3f5813f7889bf82e", + "type": "0x2", + "value": "0x0" + }, + "txReceipt": { + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "contractAddress": null, + "cumulativeGasUsed": "0x30fb3e", + "effectiveGasPrice": "0xb2d05e00", + "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838", + "gasUsed": "0x401d6", + "logs": [ + { + "address": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x0000000000000000000000000000000000000000000000004563918244f40000", + "logIndex": "0x45", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c18838", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x0000000000000000000000000000000000000000000000000000000000000000", + "logIndex": "0x46", + "removed": false, + "topics": [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c18838", + "0x0000000000000000000000003cb693656622fc470f0bb07d3f5813f7889bf82e" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x0000000000000000000000000000000000000000000000004563918244f40000", + "logIndex": "0x47", + "removed": false, + "topics": [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889", + "0x000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x0000000000000000000000000000000000000000000000004563918244f40000", + "logIndex": "0x48", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889", + "0x0000000000000000000000002354ef4df11afacb85a5c7f98b624072eccddbb1" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x0000000000000000000000000000000000000000000000000000000000000000", + "logIndex": "0x49", + "removed": false, + "topics": [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889", + "0x000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0xe9e7cea3dedca5984780bafc599bd69add087d56", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x00000000000000000000000000000000000000000000000045351670c8c8eb16", + "logIndex": "0x4a", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000002354ef4df11afacb85a5c7f98b624072eccddbb1", + "0x00000000000000000000000066fdb2eccfb58cf098eaa419e5efde841368e489" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x2354ef4df11afacb85a5c7f98b624072eccddbb1", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x000000000000000000000000000000000000000000005cf210c436771e7d4016000000000000000000000000000000000000000000005cfaf19326e57b4c5707", + "logIndex": "0x4b", + "removed": false, + "topics": [ + "0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x2354ef4df11afacb85a5c7f98b624072eccddbb1", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x0000000000000000000000000000000000000000000000004563918244f400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045351670c8c8eb16", + "logIndex": "0x4c", + "removed": false, + "topics": [ + "0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822", + "0x000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff", + "0x00000000000000000000000066fdb2eccfb58cf098eaa419e5efde841368e489" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x1af3f329e8be154074d8769d1ffa4ee058b1dbc3", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x000000000000000000000000000000000000000000000000453047e918796cfd", + "logIndex": "0x4d", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x00000000000000000000000066fdb2eccfb58cf098eaa419e5efde841368e489", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x66fdb2eccfb58cf098eaa419e5efde841368e489", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x0000000000000000000000000000000000000000000020c638a3898eb7869dcf0000000000000000000000000000000000000000000020af974dbd9430b354c8", + "logIndex": "0x4e", + "removed": false, + "topics": [ + "0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x66fdb2eccfb58cf098eaa419e5efde841368e489", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045351670c8c8eb16000000000000000000000000000000000000000000000000453047e918796cfd0000000000000000000000000000000000000000000000000000000000000000", + "logIndex": "0x4f", + "removed": false, + "topics": [ + "0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822", + "0x000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x0000000000000000000000000000000000000000000000000000000000000000", + "logIndex": "0x50", + "removed": false, + "topics": [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889", + "0x000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x1af3f329e8be154074d8769d1ffa4ee058b1dbc3", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x000000000000000000000000000000000000000000000000453047e918796cfd", + "logIndex": "0x51", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889", + "0x000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c18838" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + }, + { + "address": "0x3cb693656622fc470f0bb07d3f5813f7889bf82e", + "blockHash": "0x2ad0174dc123330852694ddeaa1924450b6bba340485f18abe3a6681bc8896f7", + "blockNumber": "0x25b2338", + "data": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000045357415000000000000000000000000000000000000000000000000000000000", + "logIndex": "0x52", + "removed": false, + "topics": [ + "0x0c18aae526accb31b01cf9a15bdf435e70632ee31efc4c5c0752c4262ea45d2f" + ], + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21" + } + ], + "logsBloom": "0x006000000040001000000000800004000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000002000c0000000010000012000040008000000240000000000000088000000000000000008000000000000000000000004000040000000000000002008000010000000020000000000000001000000000040008000000000000800084000004000000100020000000000000000004000000000000000000000000000004000000000000000000002000000020000000000001000000040100000001000000020000000000010000000004002000000100000000000800000008000000000000001000000", + "status": "0x1", + "to": "0x3cb693656622fc470f0bb07d3f5813f7889bf82e", + "transactionHash": "0x5be1ae5ae27c4e2a5542228b4f7967a660881dcbc71521a978ea6d9c3b3971fe", + "transactionIndex": "0x21", + "type": "0x2" + }, + "type": "swapAndSend", + "userEditedGasLimit": false, + "userFeeLevel": "medium", + "v": "0x1", + "verifiedOnBlockchain": true + }, + "hasRetried": false, + "hasCancelled": false + }, + { + "nonce": "0x0", + "transactions": [ + { + "actionId": 1717789986313.557, + "baseFeePerGas": "0x0", + "blockTimestamp": "0x66636524", + "chainId": "0x38", + "defaultGasEstimates": { + "estimateType": "medium", + "gas": "0x397cf", + "maxFeePerGas": "0xb2d05e00", + "maxPriorityFeePerGas": "0xb2d05e00" + }, + "destinationTokenAddress": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + "destinationTokenAmount": "33721280446418538542", + "destinationTokenDecimals": 18, + "destinationTokenSymbol": "USDC", + "firstRetryBlockNumber": "0x259650b", + "gasFeeEstimates": { + "high": { + "maxFeePerGas": "0xb2d05e00", + "maxPriorityFeePerGas": "0xb2d05e00" + }, + "low": { + "maxFeePerGas": "0xb2d05e00", + "maxPriorityFeePerGas": "0xb2d05e00" + }, + "medium": { + "maxFeePerGas": "0xb2d05e00", + "maxPriorityFeePerGas": "0xb2d05e00" + }, + "type": "fee-market" + }, + "gasFeeEstimatesLoaded": true, + "hash": "0x5b13c4573b31d93bfb564c4f8506448bba16908b6974a4c0494a40d9aa622de7", + "history": [], + "id": "8ed642e0-2507-11ef-99c4-53f3fc2768a2", + "networkClientId": "899c28d9-9f01-43c6-be76-b5e9f3cfe8bc", + "origin": "metamask", + "r": "0x7fbbe0d92a8da53a9bc98ef8144c6677ecb08050ea180548d93b7830cb407a2a", + "rawTx": "0x02f903d8388084b2d05e0084b2d05e00830397cf943cb693656622fc470f0bb07d3f5813f7889bf82e87b1a2bc2ec50000b90364048226a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b1a2bc2ec5000000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c1883800000000000000000000000000000000000000000000000000000000000000000000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d00000000000000000000000000000000000000000000000000b014d4c6ae2800000000000000000000000000000000000000000000000001ca9e03628c802fdb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000018de76816d80000000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000128c43c9ef6000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000b014d4c6ae2800000000000000000000000000000000000000000000000001ca9e03628c802fdb00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d869584cd00000000000000000000000011ededebf63bef0ea2d2d071bdf88f71543ec6fb000000000000000000000000000000003736a41146b8f4bb97dbe80a5771808c000000000000000000000000000000000000000000000000c001a07fbbe0d92a8da53a9bc98ef8144c6677ecb08050ea180548d93b7830cb407a2aa04d050489b179ec7832166566d3769408ef6c407d0032243cdabeb99bab87d634", + "s": "0x4d050489b179ec7832166566d3769408ef6c407d0032243cdabeb99bab87d634", + "sendFlowHistory": [], + "sourceTokenAddress": "0x0000000000000000000000000000000000000000", + "sourceTokenAmount": "50000000000000000", + "sourceTokenDecimals": 18, + "sourceTokenSymbol": "BNB", + "status": "confirmed", + "submittedTime": 1717789986704, + "swapAndSendRecipient": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838", + "swapTokenValue": "0.05", + "time": 1717789986319, + "txParams": { + "data": "0x048226a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b1a2bc2ec5000000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c1883800000000000000000000000000000000000000000000000000000000000000000000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d00000000000000000000000000000000000000000000000000b014d4c6ae2800000000000000000000000000000000000000000000000001ca9e03628c802fdb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000018de76816d80000000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000128c43c9ef6000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000b014d4c6ae2800000000000000000000000000000000000000000000000001ca9e03628c802fdb00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d869584cd00000000000000000000000011ededebf63bef0ea2d2d071bdf88f71543ec6fb000000000000000000000000000000003736a41146b8f4bb97dbe80a5771808c000000000000000000000000000000000000000000000000", + "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838", + "gas": "0x397cf", + "gasLimit": "0x397cf", + "maxFeePerGas": "0xb2d05e00", + "maxPriorityFeePerGas": "0xb2d05e00", + "nonce": "0x0", + "to": "0x3cb693656622fc470f0bb07d3f5813f7889bf82e", + "type": "0x2", + "value": "0xb1a2bc2ec50000" + }, + "txReceipt": { + "blockHash": "0xdf447bf7f6018f3b30ddc7004f45d8d55f68434865a05598ce4c1fd774642590", + "blockNumber": "0x259650c", + "contractAddress": null, + "cumulativeGasUsed": "0x2644bd", + "effectiveGasPrice": "0xb2d05e00", + "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838", + "gasUsed": "0x31a28", + "logs": [ + { + "address": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c", + "blockHash": "0xdf447bf7f6018f3b30ddc7004f45d8d55f68434865a05598ce4c1fd774642590", + "blockNumber": "0x259650c", + "data": "0x00000000000000000000000000000000000000000000000000b014d4c6ae2800", + "logIndex": "0x38", + "removed": false, + "topics": [ + "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c", + "0x000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff" + ], + "transactionHash": "0x5b13c4573b31d93bfb564c4f8506448bba16908b6974a4c0494a40d9aa622de7", + "transactionIndex": "0x2e" + }, + { + "address": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c", + "blockHash": "0xdf447bf7f6018f3b30ddc7004f45d8d55f68434865a05598ce4c1fd774642590", + "blockNumber": "0x259650c", + "data": "0x00000000000000000000000000000000000000000000000000b014d4c6ae2800", + "logIndex": "0x39", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff", + "0x000000000000000000000000d99c7f6c65857ac913a8f880a4cb84032ab2fc5b" + ], + "transactionHash": "0x5b13c4573b31d93bfb564c4f8506448bba16908b6974a4c0494a40d9aa622de7", + "transactionIndex": "0x2e" + }, + { + "address": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + "blockHash": "0xdf447bf7f6018f3b30ddc7004f45d8d55f68434865a05598ce4c1fd774642590", + "blockNumber": "0x259650c", + "data": "0x000000000000000000000000000000000000000000000001d3be028ce893dfbe", + "logIndex": "0x3a", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000d99c7f6c65857ac913a8f880a4cb84032ab2fc5b", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889" + ], + "transactionHash": "0x5b13c4573b31d93bfb564c4f8506448bba16908b6974a4c0494a40d9aa622de7", + "transactionIndex": "0x2e" + }, + { + "address": "0xd99c7f6c65857ac913a8f880a4cb84032ab2fc5b", + "blockHash": "0xdf447bf7f6018f3b30ddc7004f45d8d55f68434865a05598ce4c1fd774642590", + "blockNumber": "0x259650c", + "data": "0x000000000000000000000000000000000000000000003ef046f04c7cafc1e5e20000000000000000000000000000000000000000000000179ff39de2fc5b390a", + "logIndex": "0x3b", + "removed": false, + "topics": [ + "0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1" + ], + "transactionHash": "0x5b13c4573b31d93bfb564c4f8506448bba16908b6974a4c0494a40d9aa622de7", + "transactionIndex": "0x2e" + }, + { + "address": "0xd99c7f6c65857ac913a8f880a4cb84032ab2fc5b", + "blockHash": "0xdf447bf7f6018f3b30ddc7004f45d8d55f68434865a05598ce4c1fd774642590", + "blockNumber": "0x259650c", + "data": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b014d4c6ae2800000000000000000000000000000000000000000000000001d3be028ce893dfbe0000000000000000000000000000000000000000000000000000000000000000", + "logIndex": "0x3c", + "removed": false, + "topics": [ + "0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822", + "0x000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889" + ], + "transactionHash": "0x5b13c4573b31d93bfb564c4f8506448bba16908b6974a4c0494a40d9aa622de7", + "transactionIndex": "0x2e" + }, + { + "address": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + "blockHash": "0xdf447bf7f6018f3b30ddc7004f45d8d55f68434865a05598ce4c1fd774642590", + "blockNumber": "0x259650c", + "data": "0x000000000000000000000000000000000000000000000001d3be028ce893dfbe", + "logIndex": "0x3d", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889", + "0x000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c18838" + ], + "transactionHash": "0x5b13c4573b31d93bfb564c4f8506448bba16908b6974a4c0494a40d9aa622de7", + "transactionIndex": "0x2e" + }, + { + "address": "0x3cb693656622fc470f0bb07d3f5813f7889bf82e", + "blockHash": "0xdf447bf7f6018f3b30ddc7004f45d8d55f68434865a05598ce4c1fd774642590", + "blockNumber": "0x259650c", + "data": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000045357415000000000000000000000000000000000000000000000000000000000", + "logIndex": "0x3e", + "removed": false, + "topics": [ + "0x0c18aae526accb31b01cf9a15bdf435e70632ee31efc4c5c0752c4262ea45d2f" + ], + "transactionHash": "0x5b13c4573b31d93bfb564c4f8506448bba16908b6974a4c0494a40d9aa622de7", + "transactionIndex": "0x2e" + } + ], + "logsBloom": "0x00600000000000000000000080000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000010000012000000008000000200000000000000000000400008000000000400000000000000000000004000040000000000000000008000010000000000000000000000001000000000040008000040001000800080000004000000000000000000000000000004000000000000000000000000004024000000000000000000002000000000000040000000000000000000000001040000000000080000000000000005002000000000000000000000000000000400000000001000000", + "status": "0x1", + "to": "0x3cb693656622fc470f0bb07d3f5813f7889bf82e", + "transactionHash": "0x5b13c4573b31d93bfb564c4f8506448bba16908b6974a4c0494a40d9aa622de7", + "transactionIndex": "0x2e", + "type": "0x2" + }, + "type": "swapAndSend", + "userEditedGasLimit": false, + "userFeeLevel": "medium", + "v": "0x1", + "verifiedOnBlockchain": true + } + ], + "initialTransaction": { + "actionId": 1717789986313.557, + "baseFeePerGas": "0x0", + "blockTimestamp": "0x66636524", + "chainId": "0x38", + "defaultGasEstimates": { + "estimateType": "medium", + "gas": "0x397cf", + "maxFeePerGas": "0xb2d05e00", + "maxPriorityFeePerGas": "0xb2d05e00" + }, + "destinationTokenAddress": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + "destinationTokenAmount": "33721280446418538542", + "destinationTokenDecimals": 18, + "destinationTokenSymbol": "USDC", + "firstRetryBlockNumber": "0x259650b", + "gasFeeEstimates": { + "high": { + "maxFeePerGas": "0xb2d05e00", + "maxPriorityFeePerGas": "0xb2d05e00" + }, + "low": { + "maxFeePerGas": "0xb2d05e00", + "maxPriorityFeePerGas": "0xb2d05e00" + }, + "medium": { + "maxFeePerGas": "0xb2d05e00", + "maxPriorityFeePerGas": "0xb2d05e00" + }, + "type": "fee-market" + }, + "gasFeeEstimatesLoaded": true, + "hash": "0x5b13c4573b31d93bfb564c4f8506448bba16908b6974a4c0494a40d9aa622de7", + "history": [], + "id": "8ed642e0-2507-11ef-99c4-53f3fc2768a2", + "networkClientId": "899c28d9-9f01-43c6-be76-b5e9f3cfe8bc", + "origin": "metamask", + "r": "0x7fbbe0d92a8da53a9bc98ef8144c6677ecb08050ea180548d93b7830cb407a2a", + "rawTx": "0x02f903d8388084b2d05e0084b2d05e00830397cf943cb693656622fc470f0bb07d3f5813f7889bf82e87b1a2bc2ec50000b90364048226a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b1a2bc2ec5000000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c1883800000000000000000000000000000000000000000000000000000000000000000000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d00000000000000000000000000000000000000000000000000b014d4c6ae2800000000000000000000000000000000000000000000000001ca9e03628c802fdb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000018de76816d80000000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000128c43c9ef6000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000b014d4c6ae2800000000000000000000000000000000000000000000000001ca9e03628c802fdb00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d869584cd00000000000000000000000011ededebf63bef0ea2d2d071bdf88f71543ec6fb000000000000000000000000000000003736a41146b8f4bb97dbe80a5771808c000000000000000000000000000000000000000000000000c001a07fbbe0d92a8da53a9bc98ef8144c6677ecb08050ea180548d93b7830cb407a2aa04d050489b179ec7832166566d3769408ef6c407d0032243cdabeb99bab87d634", + "s": "0x4d050489b179ec7832166566d3769408ef6c407d0032243cdabeb99bab87d634", + "sendFlowHistory": [], + "sourceTokenAddress": "0x0000000000000000000000000000000000000000", + "sourceTokenAmount": "50000000000000000", + "sourceTokenDecimals": 18, + "sourceTokenSymbol": "BNB", + "status": "confirmed", + "submittedTime": 1717789986704, + "swapAndSendRecipient": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838", + "swapTokenValue": "0.05", + "time": 1717789986319, + "txParams": { + "data": "0x048226a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b1a2bc2ec5000000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c1883800000000000000000000000000000000000000000000000000000000000000000000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d00000000000000000000000000000000000000000000000000b014d4c6ae2800000000000000000000000000000000000000000000000001ca9e03628c802fdb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000018de76816d80000000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000128c43c9ef6000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000b014d4c6ae2800000000000000000000000000000000000000000000000001ca9e03628c802fdb00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d869584cd00000000000000000000000011ededebf63bef0ea2d2d071bdf88f71543ec6fb000000000000000000000000000000003736a41146b8f4bb97dbe80a5771808c000000000000000000000000000000000000000000000000", + "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838", + "gas": "0x397cf", + "gasLimit": "0x397cf", + "maxFeePerGas": "0xb2d05e00", + "maxPriorityFeePerGas": "0xb2d05e00", + "nonce": "0x0", + "to": "0x3cb693656622fc470f0bb07d3f5813f7889bf82e", + "type": "0x2", + "value": "0xb1a2bc2ec50000" + }, + "txReceipt": { + "blockHash": "0xdf447bf7f6018f3b30ddc7004f45d8d55f68434865a05598ce4c1fd774642590", + "blockNumber": "0x259650c", + "contractAddress": null, + "cumulativeGasUsed": "0x2644bd", + "effectiveGasPrice": "0xb2d05e00", + "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838", + "gasUsed": "0x31a28", + "logs": [ + { + "address": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c", + "blockHash": "0xdf447bf7f6018f3b30ddc7004f45d8d55f68434865a05598ce4c1fd774642590", + "blockNumber": "0x259650c", + "data": "0x00000000000000000000000000000000000000000000000000b014d4c6ae2800", + "logIndex": "0x38", + "removed": false, + "topics": [ + "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c", + "0x000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff" + ], + "transactionHash": "0x5b13c4573b31d93bfb564c4f8506448bba16908b6974a4c0494a40d9aa622de7", + "transactionIndex": "0x2e" + }, + { + "address": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c", + "blockHash": "0xdf447bf7f6018f3b30ddc7004f45d8d55f68434865a05598ce4c1fd774642590", + "blockNumber": "0x259650c", + "data": "0x00000000000000000000000000000000000000000000000000b014d4c6ae2800", + "logIndex": "0x39", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff", + "0x000000000000000000000000d99c7f6c65857ac913a8f880a4cb84032ab2fc5b" + ], + "transactionHash": "0x5b13c4573b31d93bfb564c4f8506448bba16908b6974a4c0494a40d9aa622de7", + "transactionIndex": "0x2e" + }, + { + "address": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + "blockHash": "0xdf447bf7f6018f3b30ddc7004f45d8d55f68434865a05598ce4c1fd774642590", + "blockNumber": "0x259650c", + "data": "0x000000000000000000000000000000000000000000000001d3be028ce893dfbe", + "logIndex": "0x3a", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000d99c7f6c65857ac913a8f880a4cb84032ab2fc5b", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889" + ], + "transactionHash": "0x5b13c4573b31d93bfb564c4f8506448bba16908b6974a4c0494a40d9aa622de7", + "transactionIndex": "0x2e" + }, + { + "address": "0xd99c7f6c65857ac913a8f880a4cb84032ab2fc5b", + "blockHash": "0xdf447bf7f6018f3b30ddc7004f45d8d55f68434865a05598ce4c1fd774642590", + "blockNumber": "0x259650c", + "data": "0x000000000000000000000000000000000000000000003ef046f04c7cafc1e5e20000000000000000000000000000000000000000000000179ff39de2fc5b390a", + "logIndex": "0x3b", + "removed": false, + "topics": [ + "0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1" + ], + "transactionHash": "0x5b13c4573b31d93bfb564c4f8506448bba16908b6974a4c0494a40d9aa622de7", + "transactionIndex": "0x2e" + }, + { + "address": "0xd99c7f6c65857ac913a8f880a4cb84032ab2fc5b", + "blockHash": "0xdf447bf7f6018f3b30ddc7004f45d8d55f68434865a05598ce4c1fd774642590", + "blockNumber": "0x259650c", + "data": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b014d4c6ae2800000000000000000000000000000000000000000000000001d3be028ce893dfbe0000000000000000000000000000000000000000000000000000000000000000", + "logIndex": "0x3c", + "removed": false, + "topics": [ + "0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822", + "0x000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889" + ], + "transactionHash": "0x5b13c4573b31d93bfb564c4f8506448bba16908b6974a4c0494a40d9aa622de7", + "transactionIndex": "0x2e" + }, + { + "address": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + "blockHash": "0xdf447bf7f6018f3b30ddc7004f45d8d55f68434865a05598ce4c1fd774642590", + "blockNumber": "0x259650c", + "data": "0x000000000000000000000000000000000000000000000001d3be028ce893dfbe", + "logIndex": "0x3d", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889", + "0x000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c18838" + ], + "transactionHash": "0x5b13c4573b31d93bfb564c4f8506448bba16908b6974a4c0494a40d9aa622de7", + "transactionIndex": "0x2e" + }, + { + "address": "0x3cb693656622fc470f0bb07d3f5813f7889bf82e", + "blockHash": "0xdf447bf7f6018f3b30ddc7004f45d8d55f68434865a05598ce4c1fd774642590", + "blockNumber": "0x259650c", + "data": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000045357415000000000000000000000000000000000000000000000000000000000", + "logIndex": "0x3e", + "removed": false, + "topics": [ + "0x0c18aae526accb31b01cf9a15bdf435e70632ee31efc4c5c0752c4262ea45d2f" + ], + "transactionHash": "0x5b13c4573b31d93bfb564c4f8506448bba16908b6974a4c0494a40d9aa622de7", + "transactionIndex": "0x2e" + } + ], + "logsBloom": "0x00600000000000000000000080000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000010000012000000008000000200000000000000000000400008000000000400000000000000000000004000040000000000000000008000010000000000000000000000001000000000040008000040001000800080000004000000000000000000000000000004000000000000000000000000004024000000000000000000002000000000000040000000000000000000000001040000000000080000000000000005002000000000000000000000000000000400000000001000000", + "status": "0x1", + "to": "0x3cb693656622fc470f0bb07d3f5813f7889bf82e", + "transactionHash": "0x5b13c4573b31d93bfb564c4f8506448bba16908b6974a4c0494a40d9aa622de7", + "transactionIndex": "0x2e", + "type": "0x2" + }, + "type": "swapAndSend", + "userEditedGasLimit": false, + "userFeeLevel": "medium", + "v": "0x1", + "verifiedOnBlockchain": true + }, + "primaryTransaction": { + "actionId": 1717789986313.557, + "baseFeePerGas": "0x0", + "blockTimestamp": "0x66636524", + "chainId": "0x38", + "defaultGasEstimates": { + "estimateType": "medium", + "gas": "0x397cf", + "maxFeePerGas": "0xb2d05e00", + "maxPriorityFeePerGas": "0xb2d05e00" + }, + "destinationTokenAddress": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + "destinationTokenAmount": "33721280446418538542", + "destinationTokenDecimals": 18, + "destinationTokenSymbol": "USDC", + "firstRetryBlockNumber": "0x259650b", + "gasFeeEstimates": { + "high": { + "maxFeePerGas": "0xb2d05e00", + "maxPriorityFeePerGas": "0xb2d05e00" + }, + "low": { + "maxFeePerGas": "0xb2d05e00", + "maxPriorityFeePerGas": "0xb2d05e00" + }, + "medium": { + "maxFeePerGas": "0xb2d05e00", + "maxPriorityFeePerGas": "0xb2d05e00" + }, + "type": "fee-market" + }, + "gasFeeEstimatesLoaded": true, + "hash": "0x5b13c4573b31d93bfb564c4f8506448bba16908b6974a4c0494a40d9aa622de7", + "history": [], + "id": "8ed642e0-2507-11ef-99c4-53f3fc2768a2", + "networkClientId": "899c28d9-9f01-43c6-be76-b5e9f3cfe8bc", + "origin": "metamask", + "r": "0x7fbbe0d92a8da53a9bc98ef8144c6677ecb08050ea180548d93b7830cb407a2a", + "rawTx": "0x02f903d8388084b2d05e0084b2d05e00830397cf943cb693656622fc470f0bb07d3f5813f7889bf82e87b1a2bc2ec50000b90364048226a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b1a2bc2ec5000000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c1883800000000000000000000000000000000000000000000000000000000000000000000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d00000000000000000000000000000000000000000000000000b014d4c6ae2800000000000000000000000000000000000000000000000001ca9e03628c802fdb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000018de76816d80000000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000128c43c9ef6000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000b014d4c6ae2800000000000000000000000000000000000000000000000001ca9e03628c802fdb00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d869584cd00000000000000000000000011ededebf63bef0ea2d2d071bdf88f71543ec6fb000000000000000000000000000000003736a41146b8f4bb97dbe80a5771808c000000000000000000000000000000000000000000000000c001a07fbbe0d92a8da53a9bc98ef8144c6677ecb08050ea180548d93b7830cb407a2aa04d050489b179ec7832166566d3769408ef6c407d0032243cdabeb99bab87d634", + "s": "0x4d050489b179ec7832166566d3769408ef6c407d0032243cdabeb99bab87d634", + "sendFlowHistory": [], + "sourceTokenAddress": "0x0000000000000000000000000000000000000000", + "sourceTokenAmount": "50000000000000000", + "sourceTokenDecimals": 18, + "sourceTokenSymbol": "BNB", + "status": "confirmed", + "submittedTime": 1717789986704, + "swapAndSendRecipient": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838", + "swapTokenValue": "0.05", + "time": 1717789986319, + "txParams": { + "data": "0x048226a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b1a2bc2ec5000000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c1883800000000000000000000000000000000000000000000000000000000000000000000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d00000000000000000000000000000000000000000000000000b014d4c6ae2800000000000000000000000000000000000000000000000001ca9e03628c802fdb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000018de76816d80000000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000128c43c9ef6000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000b014d4c6ae2800000000000000000000000000000000000000000000000001ca9e03628c802fdb00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d869584cd00000000000000000000000011ededebf63bef0ea2d2d071bdf88f71543ec6fb000000000000000000000000000000003736a41146b8f4bb97dbe80a5771808c000000000000000000000000000000000000000000000000", + "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838", + "gas": "0x397cf", + "gasLimit": "0x397cf", + "maxFeePerGas": "0xb2d05e00", + "maxPriorityFeePerGas": "0xb2d05e00", + "nonce": "0x0", + "to": "0x3cb693656622fc470f0bb07d3f5813f7889bf82e", + "type": "0x2", + "value": "0xb1a2bc2ec50000" + }, + "txReceipt": { + "blockHash": "0xdf447bf7f6018f3b30ddc7004f45d8d55f68434865a05598ce4c1fd774642590", + "blockNumber": "0x259650c", + "contractAddress": null, + "cumulativeGasUsed": "0x2644bd", + "effectiveGasPrice": "0xb2d05e00", + "from": "0x141d32a89a1e0a5ef360034a2f60a4b917c18838", + "gasUsed": "0x31a28", + "logs": [ + { + "address": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c", + "blockHash": "0xdf447bf7f6018f3b30ddc7004f45d8d55f68434865a05598ce4c1fd774642590", + "blockNumber": "0x259650c", + "data": "0x00000000000000000000000000000000000000000000000000b014d4c6ae2800", + "logIndex": "0x38", + "removed": false, + "topics": [ + "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c", + "0x000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff" + ], + "transactionHash": "0x5b13c4573b31d93bfb564c4f8506448bba16908b6974a4c0494a40d9aa622de7", + "transactionIndex": "0x2e" + }, + { + "address": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c", + "blockHash": "0xdf447bf7f6018f3b30ddc7004f45d8d55f68434865a05598ce4c1fd774642590", + "blockNumber": "0x259650c", + "data": "0x00000000000000000000000000000000000000000000000000b014d4c6ae2800", + "logIndex": "0x39", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff", + "0x000000000000000000000000d99c7f6c65857ac913a8f880a4cb84032ab2fc5b" + ], + "transactionHash": "0x5b13c4573b31d93bfb564c4f8506448bba16908b6974a4c0494a40d9aa622de7", + "transactionIndex": "0x2e" + }, + { + "address": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + "blockHash": "0xdf447bf7f6018f3b30ddc7004f45d8d55f68434865a05598ce4c1fd774642590", + "blockNumber": "0x259650c", + "data": "0x000000000000000000000000000000000000000000000001d3be028ce893dfbe", + "logIndex": "0x3a", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000d99c7f6c65857ac913a8f880a4cb84032ab2fc5b", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889" + ], + "transactionHash": "0x5b13c4573b31d93bfb564c4f8506448bba16908b6974a4c0494a40d9aa622de7", + "transactionIndex": "0x2e" + }, + { + "address": "0xd99c7f6c65857ac913a8f880a4cb84032ab2fc5b", + "blockHash": "0xdf447bf7f6018f3b30ddc7004f45d8d55f68434865a05598ce4c1fd774642590", + "blockNumber": "0x259650c", + "data": "0x000000000000000000000000000000000000000000003ef046f04c7cafc1e5e20000000000000000000000000000000000000000000000179ff39de2fc5b390a", + "logIndex": "0x3b", + "removed": false, + "topics": [ + "0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1" + ], + "transactionHash": "0x5b13c4573b31d93bfb564c4f8506448bba16908b6974a4c0494a40d9aa622de7", + "transactionIndex": "0x2e" + }, + { + "address": "0xd99c7f6c65857ac913a8f880a4cb84032ab2fc5b", + "blockHash": "0xdf447bf7f6018f3b30ddc7004f45d8d55f68434865a05598ce4c1fd774642590", + "blockNumber": "0x259650c", + "data": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b014d4c6ae2800000000000000000000000000000000000000000000000001d3be028ce893dfbe0000000000000000000000000000000000000000000000000000000000000000", + "logIndex": "0x3c", + "removed": false, + "topics": [ + "0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822", + "0x000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889" + ], + "transactionHash": "0x5b13c4573b31d93bfb564c4f8506448bba16908b6974a4c0494a40d9aa622de7", + "transactionIndex": "0x2e" + }, + { + "address": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + "blockHash": "0xdf447bf7f6018f3b30ddc7004f45d8d55f68434865a05598ce4c1fd774642590", + "blockNumber": "0x259650c", + "data": "0x000000000000000000000000000000000000000000000001d3be028ce893dfbe", + "logIndex": "0x3d", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000bd7607133b9620320e745b3e0b1af717f8ea5889", + "0x000000000000000000000000141d32a89a1e0a5ef360034a2f60a4b917c18838" + ], + "transactionHash": "0x5b13c4573b31d93bfb564c4f8506448bba16908b6974a4c0494a40d9aa622de7", + "transactionIndex": "0x2e" + }, + { + "address": "0x3cb693656622fc470f0bb07d3f5813f7889bf82e", + "blockHash": "0xdf447bf7f6018f3b30ddc7004f45d8d55f68434865a05598ce4c1fd774642590", + "blockNumber": "0x259650c", + "data": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000045357415000000000000000000000000000000000000000000000000000000000", + "logIndex": "0x3e", + "removed": false, + "topics": [ + "0x0c18aae526accb31b01cf9a15bdf435e70632ee31efc4c5c0752c4262ea45d2f" + ], + "transactionHash": "0x5b13c4573b31d93bfb564c4f8506448bba16908b6974a4c0494a40d9aa622de7", + "transactionIndex": "0x2e" + } + ], + "logsBloom": "0x00600000000000000000000080000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000010000012000000008000000200000000000000000000400008000000000400000000000000000000004000040000000000000000008000010000000000000000000000001000000000040008000040001000800080000004000000000000000000000000000004000000000000000000000000004024000000000000000000002000000000000040000000000000000000000001040000000000080000000000000005002000000000000000000000000000000400000000001000000", + "status": "0x1", + "to": "0x3cb693656622fc470f0bb07d3f5813f7889bf82e", + "transactionHash": "0x5b13c4573b31d93bfb564c4f8506448bba16908b6974a4c0494a40d9aa622de7", + "transactionIndex": "0x2e", + "type": "0x2" + }, + "type": "swapAndSend", + "userEditedGasLimit": false, + "userFeeLevel": "medium", + "v": "0x1", + "verifiedOnBlockchain": true + }, + "hasRetried": false, + "hasCancelled": false } ] diff --git a/test/e2e/.mocharc.js b/test/e2e/.mocharc.js index 3513eaa0efdd..77eccc7deb25 100644 --- a/test/e2e/.mocharc.js +++ b/test/e2e/.mocharc.js @@ -1,5 +1,8 @@ // This file exists to add mocha configuration options specific to our // E2E Test suite. module.exports = { - require: ['test/e2e/e2e-mocha-setup.js'], + // Registers tsx so that we may use the same TypeScript features in + // E2E tests that we use elsewhere in our code. + require: ['tsx/esm'], + 'node-option': ['import=tsx'], }; diff --git a/test/e2e/accounts/account-custom-name.spec.ts b/test/e2e/accounts/account-custom-name.spec.ts index d88da7155784..cdab01cfe826 100644 --- a/test/e2e/accounts/account-custom-name.spec.ts +++ b/test/e2e/accounts/account-custom-name.spec.ts @@ -2,7 +2,7 @@ import { Suite } from 'mocha'; import { unlockWallet, withFixtures, - waitForAccountRendered, + locateAccountBalanceDOM, findAnotherAccountFromAccountList, } from '../helpers'; import FixtureBuilder from '../fixture-builder'; @@ -20,7 +20,6 @@ describe('Account Custom Name Persistence', function (this: Suite) { }, async ({ driver }: { driver: Driver }) => { await unlockWallet(driver); - // Change account label for existing account await driver.clickElement( '[data-testid="account-options-menu-button"]', @@ -47,7 +46,7 @@ describe('Account Custom Name Persistence', function (this: Suite) { ); await driver.fill('[placeholder="Account 2"]', anotherAccountLabel); await driver.clickElement({ text: 'Create', tag: 'button' }); - await waitForAccountRendered(driver); + await locateAccountBalanceDOM(driver); // Verify initial custom account label after freshly added account was active const accountOneSelector = await findAnotherAccountFromAccountList( diff --git a/test/e2e/api-specs/ConfirmationRejectionRule.ts b/test/e2e/api-specs/ConfirmationRejectionRule.ts new file mode 100644 index 000000000000..9a8348357a69 --- /dev/null +++ b/test/e2e/api-specs/ConfirmationRejectionRule.ts @@ -0,0 +1,224 @@ +import Rule from '@open-rpc/test-coverage/build/rules/rule'; +import { Call } from '@open-rpc/test-coverage/build/coverage'; +import { + ContentDescriptorObject, + ExampleObject, + ExamplePairingObject, + MethodObject, +} from '@open-rpc/meta-schema'; +import paramsToObj from '@open-rpc/test-coverage/build/utils/params-to-obj'; +import { Driver } from '../webdriver/driver'; +import { WINDOW_TITLES, switchToOrOpenDapp } from '../helpers'; +import { addToQueue } from './helpers'; + +type ConfirmationsRejectRuleOptions = { + driver: Driver; + only: string[]; +}; +// this rule makes sure that all confirmation requests are rejected. +// it also validates that the JSON-RPC response is an error with +// error code 4001 (user rejected request) +export class ConfirmationsRejectRule implements Rule { + private driver: Driver; + + private only: string[]; + + private rejectButtonInsteadOfCancel: string[]; + + private requiresEthAccountsPermission: string[]; + + constructor(options: ConfirmationsRejectRuleOptions) { + this.driver = options.driver; + this.only = options.only; + this.rejectButtonInsteadOfCancel = [ + 'personal_sign', + 'eth_signTypedData_v4', + ]; + this.requiresEthAccountsPermission = [ + 'personal_sign', + 'eth_signTypedData_v4', + 'eth_getEncryptionPublicKey', + ]; + } + + getTitle() { + return 'Confirmations Rejection Rule'; + } + + async beforeRequest(_: unknown, call: Call) { + await new Promise((resolve, reject) => { + addToQueue({ + name: 'beforeRequest', + resolve, + reject, + task: async () => { + try { + if (this.requiresEthAccountsPermission.includes(call.methodName)) { + const requestPermissionsRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_requestPermissions', + params: [{ eth_accounts: {} }], + }); + + await this.driver.executeScript( + `window.ethereum.request(${requestPermissionsRequest})`, + ); + const screenshot = await this.driver.driver.takeScreenshot(); + call.attachments = call.attachments || []; + call.attachments.push({ + type: 'image', + data: `data:image/png;base64,${screenshot}`, + }); + + await this.driver.waitUntilXWindowHandles(3); + await this.driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await this.driver.findClickableElements({ + text: 'Next', + tag: 'button', + }); + + const screenshotTwo = await this.driver.driver.takeScreenshot(); + call.attachments.push({ + type: 'image', + data: `data:image/png;base64,${screenshotTwo}`, + }); + + await this.driver.clickElement({ + text: 'Next', + tag: 'button', + }); + + await this.driver.clickElement({ + text: 'Confirm', + tag: 'button', + }); + + await switchToOrOpenDapp(this.driver); + } + } catch (e) { + console.log(e); + } + }, + }); + }); + } + + async afterRequest(_: unknown, call: Call) { + await new Promise((resolve, reject) => { + addToQueue({ + name: 'afterRequest', + resolve, + reject, + task: async () => { + try { + await this.driver.waitUntilXWindowHandles(3); + await this.driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + let text = 'Cancel'; + if (this.rejectButtonInsteadOfCancel.includes(call.methodName)) { + await this.driver.findClickableElements({ + text: 'Reject', + tag: 'button', + }); + text = 'Reject'; + } else { + await this.driver.findClickableElements({ + text: 'Cancel', + tag: 'button', + }); + } + const screenshot = await this.driver.driver.takeScreenshot(); + call.attachments = call.attachments || []; + call.attachments.push({ + type: 'image', + data: `data:image/png;base64,${screenshot}`, + }); + await this.driver.clickElement({ text, tag: 'button' }); + // make sure to switch back to the dapp or else the next test will fail on the wrong window + await switchToOrOpenDapp(this.driver); + } catch (e) { + console.log(e); + } + }, + }); + }); + } + + // get all the confirmation calls to make and expect to pass + getCalls(_: unknown, method: MethodObject) { + const calls: Call[] = []; + const isMethodAllowed = this.only ? this.only.includes(method.name) : true; + if (isMethodAllowed) { + if (method.examples) { + // pull the first example + const e = method.examples[0]; + const ex = e as ExamplePairingObject; + + if (!ex.result) { + return calls; + } + const p = ex.params.map((_e) => (_e as ExampleObject).value); + const params = + method.paramStructure === 'by-name' + ? paramsToObj(p, method.params as ContentDescriptorObject[]) + : p; + calls.push({ + title: `${this.getTitle()} - with example ${ex.name}`, + methodName: method.name, + params, + url: '', + resultSchema: (method.result as ContentDescriptorObject).schema, + expectedResult: (ex.result as ExampleObject).value, + }); + } else { + // naively call the method with no params + calls.push({ + title: `${method.name} > confirmation rejection`, + methodName: method.name, + params: [], + url: '', + resultSchema: (method.result as ContentDescriptorObject).schema, + }); + } + } + return calls; + } + + async afterResponse(_: unknown, call: Call) { + await new Promise((resolve, reject) => { + addToQueue({ + name: 'afterResponse', + resolve, + reject, + task: async () => { + try { + if (this.requiresEthAccountsPermission.includes(call.methodName)) { + const revokePermissionsRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_revokePermissions', + params: [{ eth_accounts: {} }], + }); + + await this.driver.executeScript( + `window.ethereum.request(${revokePermissionsRequest})`, + ); + } + } catch (e) { + console.log(e); + } + }, + }); + }); + } + + validateCall(call: Call) { + if (call.error) { + call.valid = call.error.code === 4001; + if (!call.valid) { + call.reason = `Expected error code 4001, got ${call.error.code}`; + } + } + return call; + } +} diff --git a/test/e2e/api-specs/helpers.ts b/test/e2e/api-specs/helpers.ts new file mode 100644 index 000000000000..d4f4d3be22c5 --- /dev/null +++ b/test/e2e/api-specs/helpers.ts @@ -0,0 +1,135 @@ +import { v4 as uuid } from 'uuid'; +import { ErrorObject } from '@open-rpc/meta-schema'; +import { Driver } from '../webdriver/driver'; + +// eslint-disable-next-line @typescript-eslint/no-shadow, @typescript-eslint/no-explicit-any +declare let window: any; + +type QueueItem = { + task: () => Promise; + resolve: (value: unknown) => void; + reject: (reason?: unknown) => void; + name: string; +}; + +export const taskQueue: QueueItem[] = []; +let isProcessing = false; + +export const processQueue = async () => { + if (isProcessing || taskQueue.length === 0) { + return; + } + + isProcessing = true; + const item = taskQueue.shift(); + if (!item) { + return; + } + const { task, resolve, reject }: QueueItem | undefined = item; + try { + const result = await task(); + resolve(result); + } catch (error) { + reject(error); + } finally { + isProcessing = false; + await processQueue(); + } +}; + +export const addToQueue = ({ task, resolve, reject, name }: QueueItem) => { + taskQueue.push({ task, resolve, reject, name }); + return processQueue(); +}; + +export const pollForResult = async ( + driver: Driver, + generatedKey: string, +): Promise => { + let result; + // eslint-disable-next-line no-loop-func + await new Promise((resolve, reject) => { + addToQueue({ + name: 'pollResult', + resolve, + reject, + task: async () => { + result = await driver.executeScript( + `return window['${generatedKey}'];`, + ); + + while (result === null) { + // Continue polling if result is not set + await driver.delay(500); + result = await driver.executeScript( + `return window['${generatedKey}'];`, + ); + } + + // clear the result + await driver.executeScript(`delete window['${generatedKey}'];`); + + return result; + }, + }); + }); + if (result !== undefined) { + return result; + } + return pollForResult(driver, generatedKey); +}; + +export const createDriverTransport = (driver: Driver) => { + return async ( + _: string, + method: string, + params: unknown[] | Record, + ) => { + const generatedKey = uuid(); + return new Promise((resolve, reject) => { + const execute = async () => { + await addToQueue({ + name: 'transport', + resolve, + reject, + task: async () => { + // don't wait for executeScript to finish window.ethereum promise + // we need this because if we wait for the promise to resolve it + // will hang in selenium since it can only do one thing at a time. + // the workaround is to put the response on window.asyncResult and poll for it. + driver.executeScript( + ([m, p, g]: [ + string, + unknown[] | Record, + string, + ]) => { + window[g] = null; + window.ethereum + .request({ method: m, params: p }) + .then((r: unknown) => { + window[g] = { result: r }; + }) + .catch((e: ErrorObject) => { + window[g] = { + error: { + code: e.code, + message: e.message, + data: e.data, + }, + }; + }); + }, + method, + params, + generatedKey, + ); + }, + }); + }; + return execute(); + }).then(async () => { + const response = await pollForResult(driver, generatedKey); + return response; + }); + }; +}; diff --git a/test/e2e/default-fixture.js b/test/e2e/default-fixture.js index 98413671caca..1ffe75134970 100644 --- a/test/e2e/default-fixture.js +++ b/test/e2e/default-fixture.js @@ -107,6 +107,8 @@ function defaultFixture(inputChainId = CHAIN_IDS.LOCALHOST) { '__FIXTURE_SUBSTITUTION__currentDateInMilliseconds', showTestnetMessageInDropdown: true, trezorModel: null, + newPrivacyPolicyToastClickedOrClosed: true, + newPrivacyPolicyToastShownDate: Date.now(), usedNetworks: { [CHAIN_IDS.MAINNET]: true, [CHAIN_IDS.LINEA_MAINNET]: true, @@ -219,6 +221,9 @@ function defaultFixture(inputChainId = CHAIN_IDS.LOCALHOST) { useMultiAccountBalanceChecker: true, useRequestQueue: true, }, + QueuedRequestController: { + queuedRequestCount: 0, + }, SelectedNetworkController: { domains: {}, }, diff --git a/test/e2e/e2e-mocha-setup.js b/test/e2e/e2e-mocha-setup.js deleted file mode 100644 index 26965006ece5..000000000000 --- a/test/e2e/e2e-mocha-setup.js +++ /dev/null @@ -1,5 +0,0 @@ -// This file simply registers babel and ts-node so that we may use the same -// ECMAScript features in E2E tests that we use elsewhere in our code. It also -// allows values to be read from TypeScript files. -require('@babel/register'); -require('ts-node/register'); diff --git a/test/e2e/fixture-builder.js b/test/e2e/fixture-builder.js index 283ed7678edf..3560ad7f3ff7 100644 --- a/test/e2e/fixture-builder.js +++ b/test/e2e/fixture-builder.js @@ -100,6 +100,9 @@ function onboardingFixture() { useMultiAccountBalanceChecker: true, useRequestQueue: true, }, + QueuedRequestController: { + queuedRequestCount: 0, + }, SelectedNetworkController: { domains: {}, }, @@ -486,7 +489,7 @@ class FixtureBuilder { withPreferencesControllerAdditionalAccountIdentities() { return this.withPreferencesController({ - identites: { + identities: { '0x5cfe73b6021e818b776b421b1c4db2474086a7e1': { address: '0x5cfe73b6021e818b776b421b1c4db2474086a7e1', lastSelected: 1665507600000, @@ -494,7 +497,7 @@ class FixtureBuilder { }, '0x09781764c08de8ca82e156bbf156a3ca217c7950': { address: '0x09781764c08de8ca82e156bbf156a3ca217c7950', - lastSelected: 1665507500000, + lastSelected: 1665507800000, name: 'Account 2', }, }, @@ -530,6 +533,12 @@ class FixtureBuilder { }); } + withPreferencesControllerTxSimulationsDisabled() { + return this.withPreferencesController({ + useTransactionSimulations: false, + }); + } + withAccountsController(data) { merge(this.fixture.data.AccountsController, data); return this; @@ -609,10 +618,9 @@ class FixtureBuilder { withAccountsControllerAdditionalAccountIdentities() { return this.withAccountsController({ internalAccounts: { - selectedAccount: '2fdb2de6-80c7-4d2f-9f95-cb6895389843', accounts: { - '2fdb2de6-80c7-4d2f-9f95-cb6895389843': { - id: '2fdb2de6-80c7-4d2f-9f95-cb6895389843', + 'd5e45e4a-3b04-4a09-a5e1-39762e5c6be4': { + id: 'd5e45e4a-3b04-4a09-a5e1-39762e5c6be4', address: '0x5cfe73b6021e818b776b421b1c4db2474086a7e1', options: {}, methods: [ @@ -632,8 +640,8 @@ class FixtureBuilder { }, }, }, - 'dd658aab-abf2-4f53-b735-c8a57151d447': { - id: 'dd658aab-abf2-4f53-b735-c8a57151d447', + 'e9976a84-110e-46c3-9811-e2da7b5528d3': { + id: 'e9976a84-110e-46c3-9811-e2da7b5528d3', address: '0x09781764c08de8ca82e156bbf156a3ca217c7950', options: {}, methods: [ @@ -647,7 +655,7 @@ class FixtureBuilder { type: 'eip155:eoa', metadata: { name: 'Account 2', - lastSelected: 1665507500000, + lastSelected: 1665507800000, keyring: { type: 'HD Key Tree', }, @@ -655,6 +663,7 @@ class FixtureBuilder { }, }, }, + selectedAccount: 'd5e45e4a-3b04-4a09-a5e1-39762e5c6be4', }); } @@ -814,8 +823,28 @@ class FixtureBuilder { timestamp: 1631545992244, value: false, }, + { + op: 'add', + path: '/simulationData', + value: { + error: { + code: 'disabled', + message: 'Simulation disabled', + }, + tokenBalanceChanges: [], + }, + note: 'TransactionController#updateSimulationData - Update simulation data', + timestamp: 1631545992244, + }, ], ], + simulationData: { + error: { + code: 'disabled', + message: 'Simulation disabled', + }, + tokenBalanceChanges: [], + }, id: '7087d1d7-f0e8-4c0f-a903-6d9daa392baf', loadingDefaults: false, origin: 'https://metamask.github.io', @@ -869,8 +898,28 @@ class FixtureBuilder { timestamp: 1631545994695, value: false, }, + { + op: 'add', + path: '/simulationData', + value: { + error: { + code: 'disabled', + message: 'Simulation disabled', + }, + tokenBalanceChanges: [], + }, + note: 'TransactionController#updateSimulationData - Update simulation data', + timestamp: 1631545992244, + }, ], ], + simulationData: { + error: { + code: 'disabled', + message: 'Simulation disabled', + }, + tokenBalanceChanges: [], + }, id: '6eab4240-3762-4581-abc5-cd91eab6964e', loadingDefaults: false, origin: 'https://metamask.github.io', @@ -924,8 +973,28 @@ class FixtureBuilder { timestamp: 1631545996678, value: false, }, + { + op: 'add', + path: '/simulationData', + value: { + error: { + code: 'disabled', + message: 'Simulation disabled', + }, + tokenBalanceChanges: [], + }, + note: 'TransactionController#updateSimulationData - Update simulation data', + timestamp: 1631545992244, + }, ], ], + simulationData: { + error: { + code: 'disabled', + message: 'Simulation disabled', + }, + tokenBalanceChanges: [], + }, id: 'c15eee26-11d6-4914-a70e-36ef9a3bcacb', loadingDefaults: false, origin: 'https://metamask.github.io', @@ -979,8 +1048,28 @@ class FixtureBuilder { timestamp: 1631545998677, value: false, }, + { + op: 'add', + path: '/simulationData', + value: { + error: { + code: 'disabled', + message: 'Simulation disabled', + }, + tokenBalanceChanges: [], + }, + note: 'TransactionController#updateSimulationData - Update simulation data', + timestamp: 1631545992244, + }, ], ], + simulationData: { + error: { + code: 'disabled', + message: 'Simulation disabled', + }, + tokenBalanceChanges: [], + }, id: 'dfa9e5ad-d069-46b1-976e-a23734971d87', loadingDefaults: false, origin: 'https://metamask.github.io', diff --git a/test/e2e/helpers.js b/test/e2e/helpers.js index 3fb5f9f111ae..41951f973aa9 100644 --- a/test/e2e/helpers.js +++ b/test/e2e/helpers.js @@ -44,6 +44,7 @@ async function withFixtures(options, testSuite) { title, ignoredConsoleErrors = [], dappPath = undefined, + disableGanache, dappPaths, testSpecificMock = function () { // do nothing. @@ -54,7 +55,10 @@ async function withFixtures(options, testSuite) { } = options; const fixtureServer = new FixtureServer(); - const ganacheServer = new Ganache(); + let ganacheServer; + if (!disableGanache) { + ganacheServer = new Ganache(); + } const bundlerServer = new Bundler(); const https = await mockttp.generateCACertificate(); const mockServer = mockttp.getLocal({ https, cors: true }); @@ -67,10 +71,12 @@ async function withFixtures(options, testSuite) { let driver; let failed = false; try { - await ganacheServer.start(ganacheOptions); + if (!disableGanache) { + await ganacheServer.start(ganacheOptions); + } let contractRegistry; - if (smartContract) { + if (smartContract && !disableGanache) { const ganacheSeeder = new GanacheSeeder(ganacheServer.getProvider()); const contracts = smartContract instanceof Array ? smartContract : [smartContract]; @@ -97,7 +103,7 @@ async function withFixtures(options, testSuite) { }); } - if (useBundler) { + if (!disableGanache && useBundler) { await initBundler(bundlerServer, ganacheServer, usePaymaster); } @@ -263,7 +269,9 @@ async function withFixtures(options, testSuite) { } finally { if (!failed || process.env.E2E_LEAVE_RUNNING !== 'true') { await fixtureServer.stop(); - await ganacheServer.quit(); + if (ganacheServer) { + await ganacheServer.quit(); + } if (ganacheOptions?.concurrent) { secondaryGanacheServer.forEach(async (server) => { @@ -638,7 +646,7 @@ const tapAndHoldToRevealSRP = async (driver) => { text: tEn('holdToRevealSRP'), tag: 'span', }, - 2000, + 3000, ); }; @@ -861,20 +869,20 @@ const TEST_SEED_PHRASE_TWO = // Usually happens when onboarded to make sure the state is retrieved from metamaskState properly, or after txn is made const locateAccountBalanceDOM = async (driver, ganacheServer) => { - const balance = await ganacheServer.getBalance(); - - await driver.waitForSelector({ - css: '[data-testid="eth-overview__primary-currency"]', - text: `${balance} ETH`, - }); + const balanceSelector = '[data-testid="eth-overview__primary-currency"]'; + if (ganacheServer) { + const balance = await ganacheServer.getBalance(); + await driver.waitForSelector({ + css: balanceSelector, + text: `${balance} ETH`, + }); + } else { + await driver.findElement(balanceSelector); + } }; const WALLET_PASSWORD = 'correct horse battery staple'; -async function waitForAccountRendered(driver) { - await driver.findElement('[data-testid="eth-overview__primary-currency"]'); -} - /** * Unlock the wallet with the default password. * This method is intended to replace driver.navigate and should not be called after driver.navigate. @@ -1151,7 +1159,6 @@ module.exports = { unlockWallet, logInWithBalanceValidation, locateAccountBalanceDOM, - waitForAccountRendered, generateGanacheOptions, WALLET_PASSWORD, WINDOW_TITLES, diff --git a/test/e2e/mmi/Dockerfile b/test/e2e/mmi/Dockerfile index 200dbfb72df4..27bcf32d61fd 100644 --- a/test/e2e/mmi/Dockerfile +++ b/test/e2e/mmi/Dockerfile @@ -1,9 +1,10 @@ -FROM mcr.microsoft.com/playwright:v1.42.1-focal AS build +FROM mcr.microsoft.com/playwright:v1.44.1-focal AS build WORKDIR '/usr/src/app' # Copy test files COPY playwright.config.ts . +COPY env.ts test/helpers/env.ts COPY . ./test/e2e/mmi/ # Copy extension for test COPY ./dist/chrome ./dist/chrome diff --git a/test/e2e/mmi/README.md b/test/e2e/mmi/README.md index d57d1d0ddedb..7c4b37ff6ec0 100644 --- a/test/e2e/mmi/README.md +++ b/test/e2e/mmi/README.md @@ -94,4 +94,4 @@ When tests finish on the pipeline, you can find the same logs that you use local ![CircleCI Job Actifact detail](resources/circleci-artifact-screnshot.png) ## Contact MMI team -If you encounter any problems while working on these e2e tests, you can write into the Consensys Slack channel `metamask-mmi-collab`. \ No newline at end of file +If you encounter any problems while working on these e2e tests, you can write into the Consensys Slack channel `contact-mmi-team`. \ No newline at end of file diff --git a/test/e2e/mmi/env.ts b/test/e2e/mmi/env.ts new file mode 100644 index 000000000000..dc21b57b129a --- /dev/null +++ b/test/e2e/mmi/env.ts @@ -0,0 +1,36 @@ +import { env } from 'process'; + +type HeadlessCapableServiceName = 'SELENIUM' | 'PLAYWRIGHT'; + +export function isHeadless(serviceName: HeadlessCapableServiceName): boolean { + if (serviceName) { + const serviceKey = `${serviceName}_HEADLESS`; + if (env[serviceKey]) { + return parseBoolean(env[serviceKey]); + } + } + return Boolean(env.HEADLESS) && parseBoolean(env.HEADLESS); +} + +export function parseBoolean(value: undefined | string): boolean { + if (!value) { + return false; + } + if (typeof value === 'boolean') { + return value; + } + if (typeof value !== 'string') { + throw new Error(`Not-a-Boolean: '${value}'`); + } + switch (value.toLowerCase().trim()) { + case 'false': + case '0': + case '': + return false; + case 'true': + case '1': + return true; + default: + throw new Error(`Not-a-Boolean: '${value}'`); + } +} diff --git a/test/e2e/mmi/pageObjects/mmi-accountMenu-page.ts b/test/e2e/mmi/pageObjects/mmi-accountMenu-page.ts index 83a8f2a91e2d..7680769bbe6c 100644 --- a/test/e2e/mmi/pageObjects/mmi-accountMenu-page.ts +++ b/test/e2e/mmi/pageObjects/mmi-accountMenu-page.ts @@ -120,7 +120,7 @@ export class MMIAccountMenuPage { async selectCustodyAccount(account: string) { await this.accountsMenu(); - await this.dialog.getByText(`${account}`).first().click(); + await this.dialog.getByText(`${account}`).click(); } async accountMenuScreenshot(screenshotName: string) { @@ -159,7 +159,7 @@ export class MMIAccountMenuPage { .click(); await this.page.getByTestId('account-options-menu__remove-jwt').click(); await expect(this.page.getByText('Remove custodian token')).toBeVisible(); - await this.page.getByRole('button', { name: /remove/iu }).click(); + await this.page.getByTestId('remove-jwt-confirm-btn').click(); } async getAccountNames() { diff --git a/test/e2e/mmi/pageObjects/mmi-main-page.ts b/test/e2e/mmi/pageObjects/mmi-main-page.ts index 8d78ceafd39d..591f4aa3428f 100644 --- a/test/e2e/mmi/pageObjects/mmi-main-page.ts +++ b/test/e2e/mmi/pageObjects/mmi-main-page.ts @@ -62,6 +62,10 @@ export class MMIMainPage { .getAttribute('data-custodiantransactionid')) as string; } + async closeCustodyConfirmLink() { + return this.page.locator('button[aria-label="Close"]').click(); + } + async selectMainAction(action: string) { await this.page .locator(`.wallet-overview__buttons >> text=${action}`) @@ -69,7 +73,7 @@ export class MMIMainPage { } async sendFunds(account: string, amount: string) { - await this.page.locator(`text="${account}"`).click(); + await this.page.locator(`button >> text="${account}"`).click(); await expect( this.page.locator('.ens-input__selected-input__title'), ).toContainText(`${account}`); diff --git a/test/e2e/mmi/scripts/run-visual-test.sh b/test/e2e/mmi/scripts/run-visual-test.sh index 1c63fc598c02..d3249d7bc792 100755 --- a/test/e2e/mmi/scripts/run-visual-test.sh +++ b/test/e2e/mmi/scripts/run-visual-test.sh @@ -13,7 +13,7 @@ mkdir -p test/e2e/mmi/dist cp -r dist/chrome test/e2e/mmi/dist/chrome # copy playwright config to the docker context -cp playwright.config.ts test/e2e/mmi/ +cp playwright.config.ts test/helpers/env.ts test/e2e/mmi/ # Build the Docker image echo "Building the Docker image..." @@ -43,4 +43,4 @@ rm test/e2e/mmi/playwright.config.ts echo "Removing mmi dist/chrome from test dir..." rm -rf test/e2e/mmi/dist -echo "Script completed successfully." \ No newline at end of file +echo "Script completed successfully." diff --git a/test/e2e/mmi/specs/extension.visual.spec.ts-snapshots/token-replacement-notification-mmi-visual-linux.png b/test/e2e/mmi/specs/extension.visual.spec.ts-snapshots/token-replacement-notification-mmi-visual-linux.png index 37eced1cdcf2..36dd9b1cf85a 100644 Binary files a/test/e2e/mmi/specs/extension.visual.spec.ts-snapshots/token-replacement-notification-mmi-visual-linux.png and b/test/e2e/mmi/specs/extension.visual.spec.ts-snapshots/token-replacement-notification-mmi-visual-linux.png differ diff --git a/test/e2e/mmi/specs/navigation.spec.ts b/test/e2e/mmi/specs/navigation.spec.ts index ad6b011e3662..61d943258b28 100644 --- a/test/e2e/mmi/specs/navigation.spec.ts +++ b/test/e2e/mmi/specs/navigation.spec.ts @@ -16,12 +16,12 @@ const support = 'https://mmi-support.metamask.io/hc/en-us'; const supportContactUs = 'https://mmi-support.metamask.io/hc/en-us/requests/new'; const mmiHomePage = 'https://metamask.io/institutions/'; -const privacyAndPolicy = 'https://consensys.io/privacy-policy'; +const privacyAndNotice = 'https://consensys.io/privacy-notice'; const openSeaTermsOfUse = 'https://opensea.io/securityproviderterms'; const metamaskAttributions = 'https://raw.githubusercontent.com/MetaMask/metamask-extension/develop/attribution.txt'; const termsOfUse = 'https://consensys.io/terms-of-use'; -const learnMoreArticles = 'https://support.metamask.io/hc/en-us/articles'; +const learnMoreArticles = 'https://support.metamask.io/'; test.describe('MMI Navigation', () => { test('MMI full navigation links', async ({ context }) => { @@ -130,7 +130,7 @@ test.describe('MMI Navigation', () => { context, mainMenuPage.page, 'Privacy policy', - privacyAndPolicy, + privacyAndNotice, ); await checkLinkURL( context, @@ -152,7 +152,7 @@ test.describe('MMI Navigation', () => { context, mainMenuPage.page, 'Privacy policy', - privacyAndPolicy, + privacyAndNotice, ); await checkLinkURL(context, mainMenuPage.page, 'Terms of use', termsOfUse); await checkLinkURL( diff --git a/test/e2e/mmi/specs/qrCode.spec.ts b/test/e2e/mmi/specs/qrCode.spec.ts index 64d977459403..23ad4c88e1b4 100644 --- a/test/e2e/mmi/specs/qrCode.spec.ts +++ b/test/e2e/mmi/specs/qrCode.spec.ts @@ -9,7 +9,8 @@ import { CustodianTestClient } from '../custodian-hooks/hooks'; import { SEPOLIA_DISPLAY_NAME } from '../helpers/utils'; test.describe('QR Code Connection Request', () => { - test('run the extension and add custodian accounts using the QR Code feature', async ({ + // @TODO Follow up task to understand why this test fails more times than it passes + test.skip('run the extension and add custodian accounts using the QR Code feature', async ({ page, context, }) => { diff --git a/test/e2e/mmi/specs/transactions.spec.ts b/test/e2e/mmi/specs/transactions.spec.ts index 5eabb8298dc7..27583283417b 100644 --- a/test/e2e/mmi/specs/transactions.spec.ts +++ b/test/e2e/mmi/specs/transactions.spec.ts @@ -68,12 +68,14 @@ const sendTransaction = async ( if (repeatTx) { await mainPage.bringToFront(); + await mainPage.closeCustodyConfirmLink(); await mainPage.selectMainAction('Send'); await mainPage.sendFunds(accounTo, '0'); } // Check that action took place await mainPage.bringToFront(); + await mainPage.closeCustodyConfirmLink(); await mainPage.openActivityTab(); await mainPage.checkLastTransactionStatus(/created/iu); // Get custodianTxId to mine the transaction @@ -122,7 +124,7 @@ test.describe('MMI send', () => { const statusName = await client.submitTransactionById(custodianTxId); await mainPage.checkLastTransactionStatus(statusName); - if (secondCustodianTxId.length > 0) { + if (secondCustodianTxId && secondCustodianTxId.length > 0) { await client.submitTransactionById(secondCustodianTxId); } }); diff --git a/test/e2e/mmi/specs/visual.spec.ts b/test/e2e/mmi/specs/visual.spec.ts index 8c2f8553de42..9e7b2a424fc4 100644 --- a/test/e2e/mmi/specs/visual.spec.ts +++ b/test/e2e/mmi/specs/visual.spec.ts @@ -5,7 +5,6 @@ import { MMINetworkPage } from '../pageObjects/mmi-network-page'; import { MMISignUpPage } from '../pageObjects/mmi-signup-page'; import { CustodianTestClient } from '../custodian-hooks/hooks'; import { MMIAccountMenuPage } from '../pageObjects/mmi-accountMenu-page'; -import { MMIMainPage } from '../pageObjects/mmi-main-page'; import { SEPOLIA_DISPLAY_NAME } from '../helpers/utils'; test.describe('MMI visual', () => { @@ -46,14 +45,9 @@ test.describe('MMI visual', () => { const client = new CustodianTestClient(); await client.setup(); - // It will use account A by default - const accounts = await client.getSelectedAccounts(); - const accountA = accounts[0]; - const accountsPopup = new MMIAccountMenuPage(page); await accountsPopup.accountsMenu(); - // await accountsPopup.accountMenuScreenshot('connect_custodian.png'); await accountsPopup.connectCustodian( process.env.MMI_E2E_CUSTODIAN_NAME as string, true, @@ -61,20 +55,8 @@ test.describe('MMI visual', () => { // Check accounts added from Custodian await accountsPopup.accountsMenu(); - // await accountsPopup.accountMenuScreenshot('custody_accounts_selection.png'); // Check remove custodian token screen (aborted before removed) await accountsPopup.removeTokenScreenshot('Custody Account A'); - - // Select custodian accounts - await accountsPopup.selectCustodyAccount(accountA); - - // Check that custodian logo is loaded and account is selected - const mainPage = new MMIMainPage(page); - - await mainPage.mainPageScreenshot( - 'mainWindow_custodian_selected.png', - accountA, - ); }); }); diff --git a/test/e2e/mmi/specs/visual.spec.ts-snapshots/custodian-connection-info-mmi-visual-linux.png b/test/e2e/mmi/specs/visual.spec.ts-snapshots/custodian-connection-info-mmi-visual-linux.png index a722d3cc1bc7..8b628e6b61cc 100644 Binary files a/test/e2e/mmi/specs/visual.spec.ts-snapshots/custodian-connection-info-mmi-visual-linux.png and b/test/e2e/mmi/specs/visual.spec.ts-snapshots/custodian-connection-info-mmi-visual-linux.png differ diff --git a/test/e2e/mmi/specs/visual.spec.ts-snapshots/custodian-list-mmi-visual-linux.png b/test/e2e/mmi/specs/visual.spec.ts-snapshots/custodian-list-mmi-visual-linux.png index 3725a09c80e1..d4a4c717a303 100644 Binary files a/test/e2e/mmi/specs/visual.spec.ts-snapshots/custodian-list-mmi-visual-linux.png and b/test/e2e/mmi/specs/visual.spec.ts-snapshots/custodian-list-mmi-visual-linux.png differ diff --git a/test/e2e/mmi/specs/visual.spec.ts-snapshots/mainWindow-custodian-selected-mmi-visual-linux.png b/test/e2e/mmi/specs/visual.spec.ts-snapshots/mainWindow-custodian-selected-mmi-visual-linux.png index 507e6f1e8971..4254768bc46b 100644 Binary files a/test/e2e/mmi/specs/visual.spec.ts-snapshots/mainWindow-custodian-selected-mmi-visual-linux.png and b/test/e2e/mmi/specs/visual.spec.ts-snapshots/mainWindow-custodian-selected-mmi-visual-linux.png differ diff --git a/test/e2e/mock-e2e.js b/test/e2e/mock-e2e.js index aa4572d842e3..f3279377114d 100644 --- a/test/e2e/mock-e2e.js +++ b/test/e2e/mock-e2e.js @@ -191,60 +191,18 @@ async function setupMocking( }; }); - const gasPricesCallbackMock = () => ({ - statusCode: 200, - json: { - SafeGasPrice: '1', - ProposeGasPrice: '2', - FastGasPrice: '3', - }, - }); - const suggestedGasFeesCallbackMock = () => ({ - statusCode: 200, - json: { - low: { - suggestedMaxPriorityFeePerGas: '1', - suggestedMaxFeePerGas: '20.44436136', - minWaitTimeEstimate: 15000, - maxWaitTimeEstimate: 30000, - }, - medium: { - suggestedMaxPriorityFeePerGas: '1.5', - suggestedMaxFeePerGas: '25.80554517', - minWaitTimeEstimate: 15000, - maxWaitTimeEstimate: 45000, - }, - high: { - suggestedMaxPriorityFeePerGas: '2', - suggestedMaxFeePerGas: '27.277766977', - minWaitTimeEstimate: 15000, - maxWaitTimeEstimate: 60000, - }, - estimatedBaseFee: '19.444436136', - networkCongestion: 0.14685, - latestPriorityFeeRange: ['0.378818859', '6.555563864'], - historicalPriorityFeeRange: ['0.1', '248.262969261'], - historicalBaseFeeRange: ['14.146999781', '28.825256275'], - priorityFeeTrend: 'down', - baseFeeTrend: 'up', - }, - }); - await server .forGet(`${GAS_API_BASE_URL}/networks/${chainId}/gasPrices`) - .thenCallback(gasPricesCallbackMock); - - await server - .forGet(`${GAS_API_BASE_URL}/networks/1/gasPrices`) - .thenCallback(gasPricesCallbackMock); - - await server - .forGet(`${GAS_API_BASE_URL}/networks/1/suggestedGasFees`) - .thenCallback(suggestedGasFeesCallbackMock); - - await server - .forGet(`${GAS_API_BASE_URL}/networks/${chainId}/suggestedGasFees`) - .thenCallback(suggestedGasFeesCallbackMock); + .thenCallback(() => { + return { + statusCode: 200, + json: { + SafeGasPrice: '1', + ProposeGasPrice: '2', + FastGasPrice: '3', + }, + }; + }); await server .forGet(`${SWAPS_API_V2_BASE_URL}/networks/1/token`) @@ -263,6 +221,41 @@ async function setupMocking( }; }); + await server + .forGet(`${GAS_API_BASE_URL}/networks/${chainId}/suggestedGasFees`) + .thenCallback(() => { + return { + statusCode: 200, + json: { + low: { + suggestedMaxPriorityFeePerGas: '1', + suggestedMaxFeePerGas: '20.44436136', + minWaitTimeEstimate: 15000, + maxWaitTimeEstimate: 30000, + }, + medium: { + suggestedMaxPriorityFeePerGas: '1.5', + suggestedMaxFeePerGas: '25.80554517', + minWaitTimeEstimate: 15000, + maxWaitTimeEstimate: 45000, + }, + high: { + suggestedMaxPriorityFeePerGas: '2', + suggestedMaxFeePerGas: '27.277766977', + minWaitTimeEstimate: 15000, + maxWaitTimeEstimate: 60000, + }, + estimatedBaseFee: '19.444436136', + networkCongestion: 0.14685, + latestPriorityFeeRange: ['0.378818859', '6.555563864'], + historicalPriorityFeeRange: ['0.1', '248.262969261'], + historicalBaseFeeRange: ['14.146999781', '28.825256275'], + priorityFeeTrend: 'down', + baseFeeTrend: 'up', + }, + }; + }); + await server .forGet(`${SWAPS_API_V2_BASE_URL}/featureFlags`) .thenCallback(() => { diff --git a/test/e2e/run-openrpc-api-test-coverage.ts b/test/e2e/run-openrpc-api-test-coverage.ts new file mode 100644 index 000000000000..f192f6088954 --- /dev/null +++ b/test/e2e/run-openrpc-api-test-coverage.ts @@ -0,0 +1,410 @@ +import testCoverage from '@open-rpc/test-coverage'; +import { parseOpenRPCDocument } from '@open-rpc/schema-utils-js'; +import HtmlReporter from '@open-rpc/test-coverage/build/reporters/html-reporter'; +import ExamplesRule from '@open-rpc/test-coverage/build/rules/examples-rule'; +import JsonSchemaFakerRule from '@open-rpc/test-coverage/build/rules/json-schema-faker-rule'; + +import { + ExampleObject, + ExamplePairingObject, + MethodObject, +} from '@open-rpc/meta-schema'; +import openrpcDocument from '@metamask/api-specs'; +import { ConfirmationsRejectRule } from './api-specs/ConfirmationRejectionRule'; + +import { Driver, PAGES } from './webdriver/driver'; + +import { createDriverTransport } from './api-specs/helpers'; + +import FixtureBuilder from './fixture-builder'; +import { + withFixtures, + openDapp, + unlockWallet, + DAPP_URL, + ACCOUNT_1, +} from './helpers'; + +// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires +const mockServer = require('@open-rpc/mock-server/build/index').default; + +async function main() { + const port = 8545; + const chainId = 1337; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder().build(), + disableGanache: true, + title: 'api-specs coverage', + }, + async ({ driver }: { driver: Driver }) => { + await unlockWallet(driver); + + // Navigate to extension home screen + await driver.navigate(PAGES.HOME); + + // Open Dapp + await openDapp(driver, undefined, DAPP_URL); + + const transport = createDriverTransport(driver); + + const transaction = + openrpcDocument.components?.schemas?.TransactionInfo?.allOf?.[0]; + + if (transaction) { + delete transaction.unevaluatedProperties; + } + + const chainIdMethod = openrpcDocument.methods.find( + (m) => (m as MethodObject).name === 'eth_chainId', + ); + (chainIdMethod as MethodObject).examples = [ + { + name: 'chainIdExample', + description: 'Example of a chainId request', + params: [], + result: { + name: 'chainIdResult', + value: `0x${chainId.toString(16)}`, + }, + }, + ]; + + const getBalanceMethod = openrpcDocument.methods.find( + (m) => (m as MethodObject).name === 'eth_getBalance', + ); + + (getBalanceMethod as MethodObject).examples = [ + { + name: 'getBalanceExample', + description: 'Example of a getBalance request', + params: [ + { + name: 'address', + value: ACCOUNT_1, + }, + { + name: 'tag', + value: 'latest', + }, + ], + result: { + name: 'getBalanceResult', + value: '0x1a8819e0c9bab700', // can we get this from a variable too + }, + }, + ]; + + const blockNumber = openrpcDocument.methods.find( + (m) => (m as MethodObject).name === 'eth_blockNumber', + ); + + (blockNumber as MethodObject).examples = [ + { + name: 'blockNumberExample', + description: 'Example of a blockNumber request', + params: [], + result: { + name: 'blockNumberResult', + value: '0x1', + }, + }, + ]; + + const personalSign = openrpcDocument.methods.find( + (m) => (m as MethodObject).name === 'personal_sign', + ); + + (personalSign as MethodObject).examples = [ + { + name: 'personalSignExample', + description: 'Example of a personalSign request', + params: [ + { + name: 'data', + value: '0xdeadbeef', + }, + { + name: 'address', + value: ACCOUNT_1, + }, + ], + result: { + name: 'personalSignResult', + value: '0x1a8819e0c9bab700', + }, + }, + ]; + + const switchEthereumChain = openrpcDocument.methods.find( + (m) => (m as MethodObject).name === 'wallet_switchEthereumChain', + ); + (switchEthereumChain as MethodObject).examples = [ + { + name: 'wallet_switchEthereumChain', + description: + 'Example of a wallet_switchEthereumChain request to sepolia', + params: [ + { + name: 'SwitchEthereumChainParameter', + value: { + chainId: '0xaa36a7', + }, + }, + ], + result: { + name: 'wallet_switchEthereumChain', + value: null, + }, + }, + ]; + + const signTypedData4 = openrpcDocument.methods.find( + (m) => (m as MethodObject).name === 'eth_signTypedData_v4', + ); + + const signTypedData4Example = (signTypedData4 as MethodObject) + .examples?.[0] as ExamplePairingObject; + + // just update address for signTypedData + (signTypedData4Example.params[0] as ExampleObject).value = ACCOUNT_1; + + // update chainId for signTypedData + ( + signTypedData4Example.params[1] as ExampleObject + ).value.domain.chainId = 1337; + + // net_version missing from execution-apis. see here: https://github.com/ethereum/execution-apis/issues/540 + const netVersion: MethodObject = { + name: 'net_version', + summary: 'Returns the current network ID.', + params: [], + result: { + description: 'Returns the current network ID.', + name: 'net_version', + schema: { + type: 'string', + }, + }, + description: 'Returns the current network ID.', + examples: [ + { + name: 'net_version', + description: 'Example of a net_version request', + params: [], + result: { + name: 'net_version', + description: 'The current network ID', + value: '0x1', + }, + }, + ], + }; + // add net_version + (openrpcDocument.methods as MethodObject[]).push( + netVersion as unknown as MethodObject, + ); + + const getEncryptionPublicKey = openrpcDocument.methods.find( + (m) => (m as MethodObject).name === 'eth_getEncryptionPublicKey', + ); + + (getEncryptionPublicKey as MethodObject).examples = [ + { + name: 'getEncryptionPublicKeyExample', + description: 'Example of a getEncryptionPublicKey request', + params: [ + { + name: 'address', + value: ACCOUNT_1, + }, + ], + result: { + name: 'getEncryptionPublicKeyResult', + value: '0x1a8819e0c9bab700', + }, + }, + ]; + + const getTransactionCount = openrpcDocument.methods.find( + (m) => (m as MethodObject).name === 'eth_getTransactionCount', + ); + (getTransactionCount as MethodObject).examples = [ + { + name: 'getTransactionCountExampleEarliest', + description: 'Example of a pending getTransactionCount request', + params: [ + { + name: 'address', + value: ACCOUNT_1, + }, + { + name: 'tag', + value: 'earliest', + }, + ], + result: { + name: 'getTransactionCountResult', + value: '0x0', + }, + }, + { + name: 'getTransactionCountExampleFinalized', + description: 'Example of a pending getTransactionCount request', + params: [ + { + name: 'address', + value: ACCOUNT_1, + }, + { + name: 'tag', + value: 'finalized', + }, + ], + result: { + name: 'getTransactionCountResult', + value: '0x0', + }, + }, + { + name: 'getTransactionCountExampleSafe', + description: 'Example of a pending getTransactionCount request', + params: [ + { + name: 'address', + value: ACCOUNT_1, + }, + { + name: 'tag', + value: 'safe', + }, + ], + result: { + name: 'getTransactionCountResult', + value: '0x0', + }, + }, + { + name: 'getTransactionCountExample', + description: 'Example of a getTransactionCount request', + params: [ + { + name: 'address', + value: ACCOUNT_1, + }, + { + name: 'tag', + value: 'latest', + }, + ], + result: { + name: 'getTransactionCountResult', + value: '0x0', + }, + }, + // returns a number right now. see here: https://github.com/MetaMask/metamask-extension/pull/14822 + // { + // name: 'getTransactionCountExamplePending', + // description: 'Example of a pending getTransactionCount request', + // params: [ + // { + // name: 'address', + // value: ACCOUNT_1, + // }, + // { + // name: 'tag', + // value: 'pending', + // }, + // ], + // result: { + // name: 'getTransactionCountResult', + // value: '0x0', + // }, + // }, + ]; + + const server = mockServer(port, openrpcDocument); + server.start(); + + // TODO: move these to a "Confirmation" tag in api-specs + const methodsWithConfirmations = [ + 'wallet_requestPermissions', + 'eth_requestAccounts', + 'wallet_watchAsset', + 'personal_sign', // requires permissions for eth_accounts + 'wallet_addEthereumChain', + 'eth_signTypedData_v4', // requires permissions for eth_accounts + 'wallet_switchEthereumChain', + + // commented out because its not returning 4001 error. + // see here https://github.com/MetaMask/metamask-extension/issues/24227 + // 'eth_getEncryptionPublicKey', // requires permissions for eth_accounts + ]; + const filteredMethods = openrpcDocument.methods + .filter((_m: unknown) => { + const m = _m as MethodObject; + return ( + m.name.includes('snap') || + m.name.includes('Snap') || + m.name.toLowerCase().includes('account') || + m.name.includes('crypt') || + m.name.includes('blob') || + m.name.includes('sendTransaction') || + m.name.startsWith('wallet_scanQRCode') || + methodsWithConfirmations.includes(m.name) || + // filters are currently 0 prefixed for odd length on + // extension which doesn't pass spec + // see here: https://github.com/MetaMask/eth-json-rpc-filters/issues/152 + m.name.includes('filter') || + m.name.includes('Filter') + ); + }) + .map((m) => (m as MethodObject).name); + + const testCoverageResults = await testCoverage({ + openrpcDocument: (await parseOpenRPCDocument( + openrpcDocument as never, + )) as never, + transport, + reporters: [ + 'console-streaming', + new HtmlReporter({ autoOpen: !process.env.CI }), + ], + skip: [ + 'eth_coinbase', + // these 2 methods below are not supported by MetaMask extension yet and + // don't get passed through. See here: https://github.com/MetaMask/metamask-extension/issues/24225 + 'eth_getBlockReceipts', + 'eth_maxPriorityFeePerGas', + ], + rules: [ + new JsonSchemaFakerRule({ + only: [], + skip: filteredMethods, + numCalls: 2, + }), + new ExamplesRule({ + only: [], + skip: filteredMethods, + }), + new ConfirmationsRejectRule({ + driver, + only: methodsWithConfirmations, + }), + ], + }); + + await driver.quit(); + + // if any of the tests failed, exit with a non-zero code + if (testCoverageResults.every((r) => r.valid)) { + process.exit(0); + } else { + process.exit(1); + } + }, + ); +} + +main(); diff --git a/test/e2e/snaps/enums.js b/test/e2e/snaps/enums.js index 5732b1b7f335..98bbdfea099a 100644 --- a/test/e2e/snaps/enums.js +++ b/test/e2e/snaps/enums.js @@ -1,3 +1,3 @@ module.exports = { - TEST_SNAPS_WEBSITE_URL: 'https://metamask.github.io/snaps/test-snaps/2.6.1/', + TEST_SNAPS_WEBSITE_URL: 'https://metamask.github.io/snaps/test-snaps/2.11.0/', }; diff --git a/test/e2e/snaps/test-snap-interactive-ui.spec.js b/test/e2e/snaps/test-snap-interactive-ui.spec.js new file mode 100644 index 000000000000..ec4d47fee817 --- /dev/null +++ b/test/e2e/snaps/test-snap-interactive-ui.spec.js @@ -0,0 +1,99 @@ +const { + defaultGanacheOptions, + withFixtures, + unlockWallet, + switchToNotificationWindow, + WINDOW_TITLES, +} = require('../helpers'); +const FixtureBuilder = require('../fixture-builder'); +const { TEST_SNAPS_WEBSITE_URL } = require('./enums'); + +describe('Test Snap Interactive UI', function () { + it('test interactive ui elements', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions: defaultGanacheOptions, + failOnConsoleError: false, + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); + + // navigate to test snaps page and connect to interactive ui snap + await driver.openNewPage(TEST_SNAPS_WEBSITE_URL); + await driver.delay(1000); + const dialogButton = await driver.findElement('#connectinteractive-ui'); + await driver.scrollToElement(dialogButton); + await driver.delay(1000); + await driver.clickElement('#connectinteractive-ui'); + + // switch to metamask extension and click connect + await switchToNotificationWindow(driver); + await driver.clickElement({ + text: 'Connect', + tag: 'button', + }); + + await driver.clickElementSafe('[data-testid="snap-install-scroll"]'); + + await driver.waitForSelector({ text: 'Confirm' }); + + await driver.clickElement({ + text: 'Confirm', + tag: 'button', + }); + + await driver.waitForSelector({ text: 'OK' }); + + await driver.clickElement({ + text: 'OK', + tag: 'button', + }); + + // switch to test snaps tab + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); + + // wait for npm installation success + await driver.waitForSelector('#connectinteractive-ui'); + + // click create dialog button + await driver.clickElement('#createDialogButton'); + await driver.delay(500); + + // switch to dialog popup + await switchToNotificationWindow(driver); + await driver.delay(500); + + // fill in thr example input + await driver.fill('#example-input', 'foo bar'); + + // try to click on dropdown + await driver.waitForSelector('[data-testid="snaps-dropdown"]'); + await driver.clickElement('[data-testid="snaps-dropdown"]'); + + // try to select option 2 from the list + await driver.clickElement({ text: 'Option 2', tag: 'option' }); + + // try to click approve + await driver.clickElement('#submit'); + + // check for returned values + await driver.waitForSelector({ text: 'foo bar', tag: 'p' }); + await driver.waitForSelector({ text: 'option2', tag: 'p' }); + + // try to click on approve + await driver.clickElement('[data-testid="confirmation-submit-button"]'); + + // switch to test snaps tab + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestSnaps); + + // look for returned true + await driver.waitForSelector({ + text: 'true', + css: '#interactiveUIResult', + }); + }, + ); + }); +}); diff --git a/test/e2e/tests/account/add-account.spec.js b/test/e2e/tests/account/add-account.spec.js index 927164033a7d..1c66d7334d98 100644 --- a/test/e2e/tests/account/add-account.spec.js +++ b/test/e2e/tests/account/add-account.spec.js @@ -5,7 +5,8 @@ const { completeImportSRPOnboardingFlow, sendTransaction, findAnotherAccountFromAccountList, - waitForAccountRendered, + locateAccountBalanceDOM, + logInWithBalanceValidation, regularDelayMs, unlockWallet, WALLET_PASSWORD, @@ -57,7 +58,7 @@ describe('Add account', function () { ganacheOptions, title: this.test.fullTitle(), }, - async ({ driver }) => { + async ({ driver, ganacheServer }) => { await driver.navigate(); // On boarding with 1st account @@ -67,8 +68,8 @@ describe('Add account', function () { WALLET_PASSWORD, ); - // Check address of 1st account - await waitForAccountRendered(driver); + // Check address of 1st accoun + await locateAccountBalanceDOM(driver, ganacheServer); await driver.findElement('[data-testid="app-header-copy-button"]'); // Create 2nd account @@ -83,7 +84,7 @@ describe('Add account', function () { await driver.clickElement({ text: 'Create', tag: 'button' }); // Check address of 2nd account - await waitForAccountRendered(driver); + await locateAccountBalanceDOM(driver); await driver.findElement('[data-testid="app-header-copy-button"]'); // Log into the account with balance(account 1) @@ -94,7 +95,7 @@ describe('Add account', function () { 1, 'Account 1', ); - await waitForAccountRendered(driver); + await locateAccountBalanceDOM(driver); await driver.clickElement(accountOneSelector); await sendTransaction(driver, secondAccount, '2.8'); @@ -127,7 +128,7 @@ describe('Add account', function () { // Land in 1st account home page await driver.findElement('.home__main-view'); - await waitForAccountRendered(driver); + await locateAccountBalanceDOM(driver); // Check address of 1st account await driver.findElement('[data-testid="app-header-copy-button"]'); @@ -155,7 +156,7 @@ describe('Add account', function () { title: this.test.fullTitle(), }, async ({ driver }) => { - await unlockWallet(driver); + await logInWithBalanceValidation(driver); await driver.clickElement('[data-testid="account-menu-icon"]'); await driver.clickElement( @@ -168,7 +169,7 @@ describe('Add account', function () { await driver.clickElement({ text: 'Create', tag: 'button' }); // Wait for 2nd account to be created - await waitForAccountRendered(driver); + await locateAccountBalanceDOM(driver); await driver.findElement({ css: '[data-testid="account-menu-icon"]', text: '2nd account', @@ -201,7 +202,7 @@ describe('Add account', function () { ); // Wait for 3rd account to be created - await waitForAccountRendered(driver); + await locateAccountBalanceDOM(driver); await driver.findElement({ css: '[data-testid="account-menu-icon"]', text: 'Account 3', diff --git a/test/e2e/tests/account/import-flow.spec.js b/test/e2e/tests/account/import-flow.spec.js index dee7d13e19da..e25174cfa249 100644 --- a/test/e2e/tests/account/import-flow.spec.js +++ b/test/e2e/tests/account/import-flow.spec.js @@ -10,8 +10,9 @@ const { completeImportSRPOnboardingFlowWordByWord, openActionMenuAndStartSendFlow, unlockWallet, + logInWithBalanceValidation, + locateAccountBalanceDOM, WALLET_PASSWORD, - waitForAccountRendered, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); const { emptyHtmlPage } = require('../../mock-e2e'); @@ -287,9 +288,8 @@ describe('Import flow @no-mmi', function () { ganacheOptions, title: this.test.fullTitle(), }, - async ({ driver }) => { - await unlockWallet(driver); - await waitForAccountRendered(driver); + async ({ driver, ganacheServer }) => { + await logInWithBalanceValidation(driver, ganacheServer); // Imports an account with JSON file await driver.clickElement('[data-testid="account-menu-icon"]'); await driver.clickElement( @@ -316,7 +316,7 @@ describe('Import flow @no-mmi', function () { '[data-testid="import-account-confirm-button"]', ); - await waitForAccountRendered(driver); + await locateAccountBalanceDOM(driver, ganacheServer); // New imported account has correct name and label await driver.findClickableElement({ css: '[data-testid="account-menu-icon"]', diff --git a/test/e2e/tests/confirmations/contract-interaction-redesign.spec.js b/test/e2e/tests/confirmations/contract-interaction-redesign.spec.js new file mode 100644 index 000000000000..96b0c2602476 --- /dev/null +++ b/test/e2e/tests/confirmations/contract-interaction-redesign.spec.js @@ -0,0 +1,194 @@ +const { + defaultGanacheOptions, + openDapp, + unlockWallet, + WINDOW_TITLES, + withFixtures, +} = require('../../helpers'); +const FixtureBuilder = require('../../fixture-builder'); +const { scrollAndConfirmAndAssertConfirm } = require('./helpers'); + +describe('Confirmation Redesign Contract Interaction Component', function () { + if (!process.env.ENABLE_CONFIRMATION_REDESIGN) { + return; + } + + it('Sends a contract interaction type 0 transaction without custom nonce editing', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .withPreferencesController({ + preferences: { redesignedConfirmationsEnabled: true }, + }) + .build(), + ganacheOptions: defaultGanacheOptions, + title: this.test?.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); + await openDapp(driver); + + await createContractDeploymentTransaction(driver); + await confirmContractDeploymentTransaction(driver); + + await createDepositTransaction(driver); + await confirmDepositTransaction(driver); + }, + ); + }); + + it('Sends a contract interaction type 0 transaction with custom nonce editing', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .withPreferencesController({ + preferences: { redesignedConfirmationsEnabled: true }, + }) + .build(), + ganacheOptions: defaultGanacheOptions, + title: this.test?.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); + await openDapp(driver); + + await toggleOnCustomNonce(driver); + + await createContractDeploymentTransaction(driver); + await confirmContractDeploymentTransaction(driver); + + await createDepositTransaction(driver); + await confirmDepositTransactionWithCustomNonce(driver, '10'); + }, + ); + }); +}); + +async function toggleOnCustomNonce(driver) { + // switch to metamask page and open the three dots menu + await driver.switchToWindowWithTitle(WINDOW_TITLES.ExtensionInFullScreenView); + + // Open settings menu button + const accountOptionsMenuSelector = + '[data-testid="account-options-menu-button"]'; + await driver.waitForSelector(accountOptionsMenuSelector); + await driver.clickElement(accountOptionsMenuSelector); + + // Click settings from dropdown menu + const globalMenuSettingsSelector = '[data-testid="global-menu-settings"]'; + await driver.waitForSelector(globalMenuSettingsSelector); + await driver.clickElement(globalMenuSettingsSelector); + + // Click Advanced tab + const advancedTabRawLocator = { + text: 'Advanced', + tag: 'div', + }; + await driver.clickElement(advancedTabRawLocator); + + // Toggle on custom toggle setting (off by default) + await driver.clickElement('.custom-nonce-toggle'); + + // Close settings + await driver.clickElement( + '.settings-page__header__title-container__close-button', + ); +} + +async function createContractDeploymentTransaction(driver) { + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + await driver.clickElement(`#deployButton`); +} + +async function confirmContractDeploymentTransaction(driver) { + await driver.delay(2000); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.waitForSelector({ + css: '.confirm-page-container-summary__action__name', + text: 'Contract deployment', + }); + + await driver.clickElement({ text: 'Confirm', tag: 'button' }); + await driver.waitUntilXWindowHandles(2); + await driver.switchToWindowWithTitle(WINDOW_TITLES.ExtensionInFullScreenView); + await driver.clickElement({ text: 'Activity', tag: 'button' }); + await driver.waitForSelector( + '.transaction-list__completed-transactions .activity-list-item:nth-of-type(1)', + ); +} + +async function createDepositTransaction(driver) { + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + await driver.clickElement(`#depositButton`); +} + +async function confirmDepositTransaction(driver) { + await driver.delay(2000); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.waitForSelector({ + css: 'h2', + text: 'Transaction request', + }); + + await toggleAdvancedDetails(driver); + + await driver.waitForSelector({ + css: 'p', + text: 'Nonce', + }); + + await scrollAndConfirmAndAssertConfirm(driver); +} + +async function confirmDepositTransactionWithCustomNonce(driver, customNonce) { + await driver.delay(2000); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.waitForSelector({ + css: 'h2', + text: 'Transaction request', + }); + + await toggleAdvancedDetails(driver); + + await driver.waitForSelector({ + css: 'p', + text: 'Nonce', + }); + + await driver.clickElement('.edit-nonce-btn'); + await driver.fill('[data-testid="custom-nonce-input"]', customNonce); + await driver.clickElement({ + text: 'Save', + tag: 'button', + }); + await scrollAndConfirmAndAssertConfirm(driver); + + // Confirm tx was submitted with the higher nonce + await driver.switchToWindowWithTitle(WINDOW_TITLES.ExtensionInFullScreenView); + + await driver.delay(500); + + const sendTransactionListItem = await driver.findElement( + '.transaction-list__pending-transactions .activity-list-item', + ); + await sendTransactionListItem.click(); + + await driver.waitForSelector({ + css: '.transaction-breakdown__value', + text: customNonce, + }); +} + +async function toggleAdvancedDetails(driver) { + // TODO - Scroll button not shown in Firefox if advanced details enabled too fast. + await driver.delay(1000); + + await driver.clickElement(`[data-testid="header-advanced-details-button"]`); +} diff --git a/test/e2e/tests/confirmations/signatures/permit.spec.ts b/test/e2e/tests/confirmations/signatures/permit.spec.ts index 5afe288f869b..8320fd748f63 100644 --- a/test/e2e/tests/confirmations/signatures/permit.spec.ts +++ b/test/e2e/tests/confirmations/signatures/permit.spec.ts @@ -85,7 +85,7 @@ async function assertInfoValues(driver: Driver) { }); const value = driver.findElement({ text: '3000' }); const nonce = driver.findElement({ text: '0' }); - const deadline = driver.findElement({ text: '50000000000' }); + const deadline = driver.findElement({ text: '02 August 1971, 16:53' }); assert.ok(await origin, 'origin'); assert.ok(await contractPetName, 'contractPetName'); diff --git a/test/e2e/tests/metrics/dapp-viewed.spec.js b/test/e2e/tests/metrics/dapp-viewed.spec.js index 5ea408b68d57..f88d7b754bf5 100644 --- a/test/e2e/tests/metrics/dapp-viewed.spec.js +++ b/test/e2e/tests/metrics/dapp-viewed.spec.js @@ -5,8 +5,9 @@ const { unlockWallet, getEventPayloads, openDapp, - waitForAccountRendered, + logInWithBalanceValidation, WINDOW_TITLES, + defaultGanacheOptions, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); const { @@ -40,7 +41,6 @@ async function mockPermissionApprovedEndpoint(mockServer) { } async function createTwoAccounts(driver) { - await waitForAccountRendered(driver); await driver.clickElement('[data-testid="account-menu-icon"]'); await driver.clickElement( '[data-testid="multichain-account-menu-popover-action-button"]', @@ -107,10 +107,10 @@ describe('Dapp viewed Event @no-mmi', function () { .build(), title: this.test.fullTitle(), testSpecificMock: mockSegment, + ganacheOptions: defaultGanacheOptions, }, - async ({ driver, mockedEndpoint: mockedEndpoints }) => { - await unlockWallet(driver); - + async ({ driver, mockedEndpoint: mockedEndpoints, ganacheServer }) => { + await logInWithBalanceValidation(driver, ganacheServer); await connectToDapp(driver); await waitForDappConnected(driver); const events = await getEventPayloads(driver, mockedEndpoints); @@ -142,10 +142,10 @@ describe('Dapp viewed Event @no-mmi', function () { .build(), title: this.test.fullTitle(), testSpecificMock: mockSegment, + ganacheOptions: defaultGanacheOptions, }, - async ({ driver, mockedEndpoint: mockedEndpoints }) => { - await unlockWallet(driver); - await waitForAccountRendered(driver); + async ({ driver, mockedEndpoint: mockedEndpoints, ganacheServer }) => { + await logInWithBalanceValidation(driver, ganacheServer); await connectToDapp(driver); await waitForDappConnected(driver); // open dapp in a new page @@ -180,10 +180,10 @@ describe('Dapp viewed Event @no-mmi', function () { .build(), title: this.test.fullTitle(), testSpecificMock: mockSegment, + ganacheOptions: defaultGanacheOptions, }, - async ({ driver, mockedEndpoint: mockedEndpoints }) => { - await unlockWallet(driver); - await waitForAccountRendered(driver); + async ({ driver, mockedEndpoint: mockedEndpoints, ganacheServer }) => { + await logInWithBalanceValidation(driver, ganacheServer); await connectToDapp(driver); await waitForDappConnected(driver); // refresh dapp @@ -223,10 +223,10 @@ describe('Dapp viewed Event @no-mmi', function () { .build(), title: this.test.fullTitle(), testSpecificMock: mockSegment, + ganacheOptions: defaultGanacheOptions, }, - async ({ driver, mockedEndpoint: mockedEndpoints }) => { - await unlockWallet(driver); - await waitForAccountRendered(driver); + async ({ driver, mockedEndpoint: mockedEndpoints, ganacheServer }) => { + await logInWithBalanceValidation(driver, ganacheServer); await connectToDapp(driver); await waitForDappConnected(driver); // open dapp in a new page @@ -259,10 +259,11 @@ describe('Dapp viewed Event @no-mmi', function () { }) .build(), title: this.test.fullTitle(), + ganacheOptions: defaultGanacheOptions, testSpecificMock: mockSegment, }, - async ({ driver, mockedEndpoint: mockedEndpoints }) => { - await unlockWallet(driver); + async ({ driver, mockedEndpoint: mockedEndpoints, ganacheServer }) => { + await logInWithBalanceValidation(driver, ganacheServer); // create 2nd account await createTwoAccounts(driver); // Connect to dapp with two accounts @@ -314,10 +315,10 @@ describe('Dapp viewed Event @no-mmi', function () { .build(), title: this.test.fullTitle(), testSpecificMock: mockSegment, + ganacheOptions: defaultGanacheOptions, }, - async ({ driver, mockedEndpoint: mockedEndpoints }) => { - await unlockWallet(driver); - await waitForAccountRendered(driver); + async ({ driver, mockedEndpoint: mockedEndpoints, ganacheServer }) => { + await logInWithBalanceValidation(driver, ganacheServer); await connectToDapp(driver); await waitForDappConnected(driver); diff --git a/test/e2e/tests/metrics/signature-approved.spec.js b/test/e2e/tests/metrics/signature-approved.spec.js index 2c03605d49f1..c5b4a2ac1165 100644 --- a/test/e2e/tests/metrics/signature-approved.spec.js +++ b/test/e2e/tests/metrics/signature-approved.spec.js @@ -80,6 +80,7 @@ describe('Signature Approved Event @no-mmi', function () { category: 'inpage_provider', locale: 'en', chain_id: '0x539', + eip712_primary_type: 'Mail', environment_type: 'background', security_alert_reason: 'NotApplicable', security_alert_response: 'NotApplicable', @@ -91,6 +92,7 @@ describe('Signature Approved Event @no-mmi', function () { category: 'inpage_provider', locale: 'en', chain_id: '0x539', + eip712_primary_type: 'Mail', environment_type: 'background', security_alert_response: 'NotApplicable', }); diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json index 8ab0a3b93cd0..84fef8e26a90 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json @@ -30,7 +30,7 @@ "fullScreenGasPollTokens": "object", "recoveryPhraseReminderHasBeenShown": true, "recoveryPhraseReminderLastShown": "number", - "outdatedBrowserWarningLastShown": "number", + "outdatedBrowserWarningLastShown": "object", "nftsDetectionNoticeDismissed": false, "showTestnetMessageInDropdown": true, "showBetaHeader": false, @@ -39,8 +39,8 @@ "showAccountBanner": true, "trezorModel": null, "onboardingDate": "object", - "newPrivacyPolicyToastClickedOrClosed": "object", - "newPrivacyPolicyToastShownDate": "object", + "newPrivacyPolicyToastClickedOrClosed": "boolean", + "newPrivacyPolicyToastShownDate": "number", "hadAdvancedGasFeesSetPriorToMigration92_3": false, "nftsDropdownState": {}, "termsOfUseLastAgreed": "number", @@ -100,11 +100,11 @@ "MetaMetricsController": { "participateInMetaMetrics": true, "metaMetricsId": "fake-metrics-id", + "dataCollectionForMarketing": "boolean", "eventsBeforeMetricsOptIn": "object", "traits": "object", "previousUserTraits": "object", "fragments": "object", - "dataCollectionForMarketing": "boolean", "segmentApiCalls": "object" }, "MetamaskNotificationsController": { @@ -119,6 +119,12 @@ "isUpdatingMetamaskNotificationsAccount": "object", "isCheckingAccountsPresence": "boolean" }, + "MultichainBalancesController": { "balances": "object" }, + "MultichainRatesController": { + "fiatCurrency": "usd", + "rates": { "btc": { "conversionDate": 0, "conversionRate": "0" } }, + "cryptocurrencies": ["btc"] + }, "NameController": { "names": "object", "nameSources": "object" }, "NetworkController": { "selectedNetworkClientId": "string", @@ -209,6 +215,9 @@ "selectedAddress": "string" }, "PushPlatformNotificationsController": { "fcmToken": "string" }, + "QueuedRequestController": { + "queuedRequestCount": 0 + }, "SelectedNetworkController": { "domains": "object" }, "SignatureController": { "unapprovedMsgs": "object", diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json index 93650970b9a9..1dd7775ec70f 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -66,7 +66,7 @@ "fullScreenGasPollTokens": "object", "recoveryPhraseReminderHasBeenShown": true, "recoveryPhraseReminderLastShown": "number", - "outdatedBrowserWarningLastShown": "number", + "outdatedBrowserWarningLastShown": "object", "nftsDetectionNoticeDismissed": false, "showTestnetMessageInDropdown": true, "showBetaHeader": false, @@ -75,12 +75,13 @@ "showAccountBanner": true, "trezorModel": null, "onboardingDate": "object", - "newPrivacyPolicyToastClickedOrClosed": "object", - "newPrivacyPolicyToastShownDate": "object", + "newPrivacyPolicyToastClickedOrClosed": "boolean", + "newPrivacyPolicyToastShownDate": "number", "hadAdvancedGasFeesSetPriorToMigration92_3": false, "nftsDropdownState": {}, "termsOfUseLastAgreed": "number", "qrHardware": {}, + "queuedRequestCount": 0, "usedNetworks": { "0x1": true, "0x5": true, "0x539": true }, "snapsInstallPrivacyWarningShown": true, "surveyLinkLastClickedOrClosed": "object", @@ -93,6 +94,7 @@ "previousMigrationVersion": 0, "currentMigrationVersion": "number", "showTokenAutodetectModalOnUpgrade": "object", + "balances": "object", "selectedNetworkClientId": "string", "networksMetadata": { "networkConfigurationId": { @@ -178,6 +180,9 @@ "logs": "object", "methodData": "object", "lastFetchedBlockNumbers": "object", + "fiatCurrency": "usd", + "rates": { "btc": { "conversionDate": 0, "conversionRate": "0" } }, + "cryptocurrencies": ["btc"], "snaps": "object", "snapStates": "object", "unencryptedSnapStates": "object", diff --git a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json index 60379a1d0811..2909bf1d3597 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json @@ -46,6 +46,8 @@ "0x5": true, "0x539": true }, + "newPrivacyPolicyToastClickedOrClosed": "boolean", + "newPrivacyPolicyToastShownDate": "number", "snapsInstallPrivacyWarningShown": true }, "CurrencyController": { @@ -129,6 +131,9 @@ "useMultiAccountBalanceChecker": true, "useRequestQueue": true }, + "QueuedRequestController": { + "queuedRequestCount": 0 + }, "SelectedNetworkController": { "domains": "object" }, "SmartTransactionsController": { "smartTransactionsState": { diff --git a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json index 48392c6b4db3..b41e6d6333d1 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json @@ -46,6 +46,8 @@ "0x5": true, "0x539": true }, + "newPrivacyPolicyToastClickedOrClosed": "boolean", + "newPrivacyPolicyToastShownDate": "number", "snapsInstallPrivacyWarningShown": true }, "CurrencyController": { @@ -129,6 +131,9 @@ "useMultiAccountBalanceChecker": true, "useRequestQueue": true }, + "QueuedRequestController": { + "queuedRequestCount": 0 + }, "SelectedNetworkController": { "domains": "object" }, "SmartTransactionsController": { "smartTransactionsState": { diff --git a/test/e2e/tests/metrics/unlock-wallet.spec.js b/test/e2e/tests/metrics/unlock-wallet.spec.js index 3961bb3a94ce..24b69c710667 100644 --- a/test/e2e/tests/metrics/unlock-wallet.spec.js +++ b/test/e2e/tests/metrics/unlock-wallet.spec.js @@ -1,8 +1,7 @@ const { strict: assert } = require('assert'); const { withFixtures, - unlockWallet, - waitForAccountRendered, + logInWithBalanceValidation, defaultGanacheOptions, getEventPayloads, } = require('../../helpers'); @@ -36,19 +35,29 @@ describe('Unlock wallet', function () { title: this.test.fullTitle(), testSpecificMock: mockSegment, }, - async ({ driver, mockedEndpoint }) => { - await unlockWallet(driver); - await waitForAccountRendered(driver); + async ({ driver, mockedEndpoint, ganacheServer }) => { + await logInWithBalanceValidation(driver, ganacheServer); const events = await getEventPayloads(driver, mockedEndpoint); - assert.equal(events.length, 3); - assertBatchValue(events[0], 'Home', '/'); - assertBatchValue(events[1], 'Unlock Page', '/unlock'); - assertBatchValue(events[2], 'Home', '/'); + const sortedEvents = sortEventsByTime(events); + await assert.equal(sortedEvents.length, 3); + assertBatchValue(sortedEvents[0], 'Home', '/'); + assertBatchValue(sortedEvents[1], 'Unlock Page', '/unlock'); + assertBatchValue(sortedEvents[2], 'Home', '/'); }, ); }); }); +function sortEventsByTime(events) { + events.sort((event1, event2) => { + const timestamp1 = new Date(event1.timestamp); + const timestamp2 = new Date(event2.timestamp); + // Compare timestamps, return -1 for earlier, 1 for later, 0 for equal + return timestamp1 - timestamp2; + }); + return events; +} + function assertBatchValue(event, assertedTitle, assertedPath) { const { title, path } = event.context.page; assert.equal(title, assertedTitle); diff --git a/test/e2e/tests/multichain/connection-page.spec.js b/test/e2e/tests/multichain/connection-page.spec.js index 3836600e2619..e5594de82840 100644 --- a/test/e2e/tests/multichain/connection-page.spec.js +++ b/test/e2e/tests/multichain/connection-page.spec.js @@ -1,10 +1,11 @@ const { strict: assert } = require('assert'); const { withFixtures, - unlockWallet, WINDOW_TITLES, - waitForAccountRendered, connectToDapp, + logInWithBalanceValidation, + locateAccountBalanceDOM, + defaultGanacheOptions, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); @@ -18,10 +19,10 @@ describe('Connections page', function () { dapp: true, fixtures: new FixtureBuilder().build(), title: this.test.fullTitle(), + ganacheOptions: defaultGanacheOptions, }, - async ({ driver }) => { - await unlockWallet(driver); - await waitForAccountRendered(driver); + async ({ driver, ganacheServer }) => { + await logInWithBalanceValidation(driver, ganacheServer); await connectToDapp(driver); // It should render connected status for button if dapp is connected @@ -91,16 +92,17 @@ describe('Connections page', function () { }, ); }); + it('should connect more accounts when already connected to a dapp', async function () { await withFixtures( { dapp: true, fixtures: new FixtureBuilder().build(), title: this.test.fullTitle(), + ganacheOptions: defaultGanacheOptions, }, - async ({ driver }) => { - await unlockWallet(driver); - await waitForAccountRendered(driver); + async ({ driver, ganacheServer }) => { + await logInWithBalanceValidation(driver, ganacheServer); await connectToDapp(driver); const account = await driver.findElement('#accounts'); @@ -135,7 +137,7 @@ describe('Connections page', function () { ); await driver.fill('[placeholder="Account 3"]', accountLabel3); await driver.clickElement({ text: 'Create', tag: 'button' }); - await waitForAccountRendered(driver); + await locateAccountBalanceDOM(driver); await driver.clickElement( '[data-testid ="account-options-menu-button"]', ); diff --git a/test/e2e/tests/multichain/permission-page.spec.js b/test/e2e/tests/multichain/permission-page.spec.js index 9bc5a6949f0b..5ae3f71b6046 100644 --- a/test/e2e/tests/multichain/permission-page.spec.js +++ b/test/e2e/tests/multichain/permission-page.spec.js @@ -1,10 +1,10 @@ const { strict: assert } = require('assert'); const { withFixtures, - unlockWallet, WINDOW_TITLES, - waitForAccountRendered, connectToDapp, + logInWithBalanceValidation, + defaultGanacheOptions, } = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); @@ -15,10 +15,10 @@ describe('Permissions Page', function () { dapp: true, fixtures: new FixtureBuilder().build(), title: this.test.fullTitle(), + ganacheOptions: defaultGanacheOptions, }, - async ({ driver }) => { - await unlockWallet(driver); - await waitForAccountRendered(driver); + async ({ driver, ganacheServer }) => { + await logInWithBalanceValidation(driver, ganacheServer); await connectToDapp(driver); // close test dapp window to avoid future confusion @@ -51,10 +51,10 @@ describe('Permissions Page', function () { dapp: true, fixtures: new FixtureBuilder().build(), title: this.test.fullTitle(), + ganacheOptions: defaultGanacheOptions, }, - async ({ driver }) => { - await unlockWallet(driver); - await waitForAccountRendered(driver); + async ({ driver, ganacheServer }) => { + await logInWithBalanceValidation(driver, ganacheServer); await connectToDapp(driver); // close test dapp window to avoid future confusion diff --git a/test/e2e/tests/network/add-custom-network.spec.js b/test/e2e/tests/network/add-custom-network.spec.js index 5dc761710a33..0cd18c13a9bf 100644 --- a/test/e2e/tests/network/add-custom-network.spec.js +++ b/test/e2e/tests/network/add-custom-network.spec.js @@ -165,6 +165,22 @@ describe('Custom network', function () { windowHandles, ); + // To mitigate a race condition, we wait until the 3 callout warnings appear + await driver.waitForSelector({ + tag: 'span', + text: 'According to our record the network name may not correctly match this chain ID.', + }); + + await driver.waitForSelector({ + tag: 'span', + text: 'According to our records the submitted RPC URL value does not match a known provider for this chain ID.', + }); + + await driver.waitForSelector({ + tag: 'a', + text: 'verify the network details', + }); + await driver.clickElement({ tag: 'button', text: 'Approve', diff --git a/test/e2e/tests/onboarding/onboarding.spec.js b/test/e2e/tests/onboarding/onboarding.spec.js index de0939ac2282..c9cb594f403d 100644 --- a/test/e2e/tests/onboarding/onboarding.spec.js +++ b/test/e2e/tests/onboarding/onboarding.spec.js @@ -372,7 +372,7 @@ describe('MetaMask onboarding @no-mmi', function () { json: { jsonrpc: '2.0', id: '1111111111111111', - result: '0x1', + result: '0x0', }, }; }), @@ -508,7 +508,7 @@ describe('MetaMask onboarding @no-mmi', function () { json: { jsonrpc: '2.0', id: '1111111111111111', - result: '0x1', + result: '0x0', }, }; }), diff --git a/test/e2e/tests/ppom/mocks/json-rpc-result.ts b/test/e2e/tests/ppom/mocks/json-rpc-result.ts index 775e049e02b7..e42b35c0dfc8 100644 --- a/test/e2e/tests/ppom/mocks/json-rpc-result.ts +++ b/test/e2e/tests/ppom/mocks/json-rpc-result.ts @@ -9,6 +9,7 @@ export type mockJsonRpcResultType = { export const mockJsonRpcResult: mockJsonRpcResultType = { eth_blockNumber: { default: MOCK_BLOCK_NUMBER, + custom: '0x130E45F', }, eth_estimateGas: { @@ -387,4 +388,7 @@ export const mockJsonRpcResult: mockJsonRpcResultType = { BUSD_STORAGE_2: '0x000000000000000000000000137dcd97872de27a4d3bf36a4643c5e18fa40713', }, + net_version: { + default: '1', + }, }; diff --git a/test/e2e/tests/privacy/basic-functionality.spec.js b/test/e2e/tests/privacy/basic-functionality.spec.js index d20aa0542285..aef2f16728de 100644 --- a/test/e2e/tests/privacy/basic-functionality.spec.js +++ b/test/e2e/tests/privacy/basic-functionality.spec.js @@ -7,10 +7,17 @@ const { tinyDelayMs, defaultGanacheOptions, } = require('../../helpers'); +const { METAMASK_STALELIST_URL } = require('../phishing-controller/helpers'); const FixtureBuilder = require('../../fixture-builder'); async function mockApis(mockServer) { return [ + await mockServer.forGet(METAMASK_STALELIST_URL).thenCallback(() => { + return { + statusCode: 200, + body: [{ fakedata: true }], + }; + }), await mockServer .forGet('https://token.api.cx.metamask.io/tokens/1') .thenCallback(() => { @@ -66,6 +73,10 @@ describe('MetaMask onboarding @no-mmi', function () { await driver.clickElement({ text: 'Ethereum Mainnet', tag: 'p' }); await driver.delay(tinyDelayMs); + // Wait until network is fully switched and refresh tokens before asserting to mitigate flakiness + await driver.assertElementNotPresent('.loading-overlay'); + await driver.clickElement('[data-testid="refresh-list-button"]'); + for (let i = 0; i < mockedEndpoints.length; i += 1) { const requests = await mockedEndpoints[i].getSeenRequests(); @@ -104,9 +115,12 @@ describe('MetaMask onboarding @no-mmi', function () { await driver.clickElement({ text: 'Ethereum Mainnet', tag: 'p' }); await driver.delay(tinyDelayMs); + // Wait until network is fully switched and refresh tokens before asserting to mitigate flakiness + await driver.assertElementNotPresent('.loading-overlay'); + await driver.clickElement('[data-testid="refresh-list-button"]'); + for (let i = 0; i < mockedEndpoints.length; i += 1) { const requests = await mockedEndpoints[i].getSeenRequests(); - assert.equal( requests.length, 1, diff --git a/test/e2e/tests/request-queuing/chain-id-check.spec.js b/test/e2e/tests/request-queuing/chain-id-check.spec.js deleted file mode 100644 index 780254295796..000000000000 --- a/test/e2e/tests/request-queuing/chain-id-check.spec.js +++ /dev/null @@ -1,103 +0,0 @@ -const { strict: assert } = require('assert'); -const FixtureBuilder = require('../../fixture-builder'); -const { - withFixtures, - openDapp, - unlockWallet, - DAPP_URL, - regularDelayMs, - WINDOW_TITLES, - defaultGanacheOptions, - switchToNotificationWindow, -} = require('../../helpers'); -const { PAGES } = require('../../webdriver/driver'); - -describe('Request Queuing', function () { - it('should keep chain id the same with request queuing and switching mm network with a connected site.', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .withSelectedNetworkControllerPerDomain() - .build(), - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], - }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); - - // Navigate to extension home screen - await driver.navigate(PAGES.HOME); - - // Open Dapp One - await openDapp(driver, undefined, DAPP_URL); - - // Connect to dapp - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); - - await driver.delay(regularDelayMs); - - await switchToNotificationWindow(driver); - - await driver.clickElement({ - text: 'Next', - tag: 'button', - css: '[data-testid="page-container-footer-next"]', - }); - - await driver.clickElement({ - text: 'Confirm', - tag: 'button', - css: '[data-testid="page-container-footer-next"]', - }); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - - const request = JSON.stringify({ - method: 'eth_chainId', - }); - - const firstChainIdCall = await driver.executeScript( - `return window.ethereum.request(${request})`, - ); - - assert.equal(firstChainIdCall, '0x539'); // 1337 - - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - - // Network Selector - await driver.clickElement('[data-testid="network-display"]'); - - // Switch to second network - await driver.clickElement({ - text: 'Localhost 8546', - css: 'p', - }); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - - const secondChainIdCall = await driver.executeScript( - `return window.ethereum.request(${request})`, - ); - - assert.equal(secondChainIdCall, '0x539'); // 1337 - }, - ); - }); -}); diff --git a/test/e2e/tests/request-queuing/chainid-check.spec.js b/test/e2e/tests/request-queuing/chainid-check.spec.js index 664cb1117d3c..850051d39c6a 100644 --- a/test/e2e/tests/request-queuing/chainid-check.spec.js +++ b/test/e2e/tests/request-queuing/chainid-check.spec.js @@ -12,123 +12,315 @@ const { } = require('../../helpers'); const { PAGES } = require('../../webdriver/driver'); -describe('Request-queue chainId proxy sync', function () { - it('should preserve per dapp network selections after connecting without refresh calls @no-mmi', async function () { - const port = 8546; - const chainId = 1338; - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withNetworkControllerDoubleGanache() - .withPreferencesControllerUseRequestQueueEnabled() - .withSelectedNetworkControllerPerDomain() - .build(), - ganacheOptions: { - ...defaultGanacheOptions, - concurrent: [ - { - port, - chainId, - ganacheOptions2: defaultGanacheOptions, - }, - ], +describe('Request Queueing chainId proxy sync', function () { + describe('request queue is on', function () { + it('should preserve per dapp network selections after connecting and switching without refresh calls @no-mmi', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .withSelectedNetworkControllerPerDomain() + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + title: this.test.fullTitle(), }, - title: this.test.fullTitle(), - }, - async ({ driver }) => { - await unlockWallet(driver); + async ({ driver }) => { + await unlockWallet(driver); - // Navigate to extension home screen - await driver.navigate(PAGES.HOME); + // Navigate to extension home screen + await driver.navigate(PAGES.HOME); - // Open Dapp One - await openDapp(driver, undefined, DAPP_URL); + // Open Dapp One + await openDapp(driver, undefined, DAPP_URL); - await driver.delay(regularDelayMs); + await driver.delay(regularDelayMs); - const chainIdRequest = JSON.stringify({ - method: 'eth_chainId', - }); + const chainIdRequest = JSON.stringify({ + method: 'eth_chainId', + }); - const chainIdCheckBeforeConnect = await driver.executeScript( - `return window.ethereum.request(${chainIdRequest})`, - ); + const chainIdBeforeConnect = await driver.executeScript( + `return window.ethereum.request(${chainIdRequest})`, + ); - assert.equal(chainIdCheckBeforeConnect, '0x539'); // 1337 + assert.equal(chainIdBeforeConnect, '0x539'); // 1337 - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); - // Network Selector - await driver.clickElement('[data-testid="network-display"]'); + // Network Selector + await driver.clickElement('[data-testid="network-display"]'); - // Switch to second network - await driver.clickElement({ - text: 'Ethereum Mainnet', - css: 'p', - }); + // Switch to second network + await driver.clickElement({ + text: 'Ethereum Mainnet', + css: 'p', + }); - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - const secondChainIdCheckBeforeConnect = await driver.executeScript( - `return window.ethereum.request(${chainIdRequest})`, - ); + const chainIdBeforeConnectAfterManualSwitch = + await driver.executeScript( + `return window.ethereum.request(${chainIdRequest})`, + ); - // before connecting the chainId should change with the wallet - assert.equal(secondChainIdCheckBeforeConnect, '0x1'); + // before connecting the chainId should change with the wallet + assert.equal(chainIdBeforeConnectAfterManualSwitch, '0x1'); - // Connect to dapp - await driver.findClickableElement({ text: 'Connect', tag: 'button' }); - await driver.clickElement('#connectButton'); + // Connect to dapp + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); - await driver.delay(regularDelayMs); + await driver.delay(regularDelayMs); - await switchToNotificationWindow(driver); + await switchToNotificationWindow(driver); - await driver.clickElement({ - text: 'Next', - tag: 'button', - css: '[data-testid="page-container-footer-next"]', - }); + await driver.clickElement({ + text: 'Next', + tag: 'button', + css: '[data-testid="page-container-footer-next"]', + }); - await driver.clickElement({ - text: 'Confirm', - tag: 'button', - css: '[data-testid="page-container-footer-next"]', - }); + await driver.clickElement({ + text: 'Confirm', + tag: 'button', + css: '[data-testid="page-container-footer-next"]', + }); - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - const firstChainIdCallAfterConnect = await driver.executeScript( - `return window.ethereum.request(${chainIdRequest})`, - ); + const chainIdAfterConnect = await driver.executeScript( + `return window.ethereum.request(${chainIdRequest})`, + ); - assert.equal(firstChainIdCallAfterConnect, '0x1'); + // should still be on the same chainId as the wallet after connecting + assert.equal(chainIdAfterConnect, '0x1'); - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); + const switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x539' }], + }); - // Network Selector - await driver.clickElement('[data-testid="network-display"]'); + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); - // Switch to second network - await driver.clickElement({ - text: 'Localhost 8546', - css: 'p', - }); + await switchToNotificationWindow(driver); + await driver.findClickableElements({ + text: 'Switch network', + tag: 'button', + }); - await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + await driver.clickElement({ text: 'Switch network', tag: 'button' }); - const secondChainIdCall = await driver.executeScript( - `return window.ethereum.request(${chainIdRequest})`, - ); - // after connecting the dapp should now have its own selected network - // independent from the globally selected and therefore should not have changed - assert.equal(secondChainIdCall, '0x1'); - }, - ); + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + + const chainIdAfterDappSwitch = await driver.executeScript( + `return window.ethereum.request(${chainIdRequest})`, + ); + + // should be on the new chainId that was requested + assert.equal(chainIdAfterDappSwitch, '0x539'); // 1337 + + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + // Network Selector + await driver.clickElement('[data-testid="network-display"]'); + + // Switch network + await driver.clickElement({ + text: 'Localhost 8546', + css: 'p', + }); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + + const chainIdAfterManualSwitch = await driver.executeScript( + `return window.ethereum.request(${chainIdRequest})`, + ); + // after connecting the dapp should now have its own selected network + // independent from the globally selected and therefore should not have changed when + // the globally selected network was manually changed via the wallet UI + assert.equal(chainIdAfterManualSwitch, '0x539'); // 1337 + }, + ); + }); + }); + + describe('request queue is off', function () { + it('should always follow the globally selected network after connecting and switching without refresh calls @no-mmi', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .build(), + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); + + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); + await driver.clickElement({ text: 'Settings', tag: 'div' }); + await driver.clickElement({ text: 'Experimental', tag: 'div' }); + await driver.clickElement( + '[data-testid="experimental-setting-toggle-request-queue"]', + ); + + // Navigate to extension home screen + await driver.navigate(PAGES.HOME); + + // Open Dapp One + await openDapp(driver, undefined, DAPP_URL); + + await driver.delay(regularDelayMs); + + const chainIdRequest = JSON.stringify({ + method: 'eth_chainId', + }); + + const chainIdBeforeConnect = await driver.executeScript( + `return window.ethereum.request(${chainIdRequest})`, + ); + + assert.equal(chainIdBeforeConnect, '0x539'); // 1337 + + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + // Network Selector + await driver.clickElement('[data-testid="network-display"]'); + + // Switch to second network + await driver.clickElement({ + text: 'Ethereum Mainnet', + css: 'p', + }); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + + const chainIdBeforeConnectAfterManualSwitch = + await driver.executeScript( + `return window.ethereum.request(${chainIdRequest})`, + ); + + // before connecting the chainId should change with the wallet + assert.equal(chainIdBeforeConnectAfterManualSwitch, '0x1'); + + // Connect to dapp + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); + + await driver.delay(regularDelayMs); + + await switchToNotificationWindow(driver); + + await driver.clickElement({ + text: 'Next', + tag: 'button', + css: '[data-testid="page-container-footer-next"]', + }); + + await driver.clickElement({ + text: 'Confirm', + tag: 'button', + css: '[data-testid="page-container-footer-next"]', + }); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + + const chainIdAfterConnect = await driver.executeScript( + `return window.ethereum.request(${chainIdRequest})`, + ); + + // should still be on the same chainId as the wallet after connecting + assert.equal(chainIdAfterConnect, '0x1'); + + const switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x539' }], + }); + + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); + + await switchToNotificationWindow(driver); + await driver.findClickableElements({ + text: 'Switch network', + tag: 'button', + }); + + await driver.clickElement({ text: 'Switch network', tag: 'button' }); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + + const chainIdAfterDappSwitch = await driver.executeScript( + `return window.ethereum.request(${chainIdRequest})`, + ); + + // should be on the new chainId that was requested + assert.equal(chainIdAfterDappSwitch, '0x539'); // 1337 + + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + // TODO: check that the wallet network has changed too + + // Network Selector + await driver.clickElement('[data-testid="network-display"]'); + + // Switch network + await driver.clickElement({ + text: 'Ethereum Mainnet', + css: 'p', + }); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + + const chainIdAfterManualSwitch = await driver.executeScript( + `return window.ethereum.request(${chainIdRequest})`, + ); + // after connecting the dapp should still be following the + // globally selected network and therefore should have changed when + // the globally selected network was manually changed via the wallet UI + assert.equal(chainIdAfterManualSwitch, '0x1'); + }, + ); + }); }); }); diff --git a/test/e2e/tests/request-queuing/dapp1-switch-dapp2-eth-request-accounts.spec.js b/test/e2e/tests/request-queuing/dapp1-switch-dapp2-eth-request-accounts.spec.js new file mode 100644 index 000000000000..e448c995e077 --- /dev/null +++ b/test/e2e/tests/request-queuing/dapp1-switch-dapp2-eth-request-accounts.spec.js @@ -0,0 +1,151 @@ +const { strict: assert } = require('assert'); + +const FixtureBuilder = require('../../fixture-builder'); +const { + withFixtures, + openDapp, + unlockWallet, + DAPP_URL, + DAPP_ONE_URL, + regularDelayMs, + WINDOW_TITLES, + defaultGanacheOptions, + switchToNotificationWindow, +} = require('../../helpers'); + +describe('Request Queuing Dapp 1 Send Tx -> Dapp 2 Request Accounts Tx', function () { + it('should queue `eth_requestAccounts` requests when the requesting dapp does not already have connected accounts', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .withPermissionControllerConnectedToTestDapp() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); + + // Open Dapp One + await openDapp(driver, undefined, DAPP_URL); + + // Dapp Send Button + await driver.clickElement('#sendButton'); + + // Leave the confirmation pending + + await openDapp(driver, undefined, DAPP_ONE_URL); + + const accountsOnload = await ( + await driver.findElement('#accounts') + ).getText(); + assert.deepStrictEqual(accountsOnload, ''); + + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); + + await driver.delay(regularDelayMs); + + const accountsBeforeConnect = await ( + await driver.findElement('#accounts') + ).getText(); + assert.deepStrictEqual(accountsBeforeConnect, ''); + + // Reject the pending confirmation from the first dapp + await switchToNotificationWindow(driver, 4); + await driver.clickElement({ text: 'Reject', tag: 'button' }); + + // Wait for switch confirmation to close then request accounts confirmation to show for the second dapp + await driver.delay(regularDelayMs); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + await driver.clickElement({ + text: 'Next', + tag: 'button', + css: '[data-testid="page-container-footer-next"]', + }); + + await driver.clickElement({ + text: 'Confirm', + tag: 'button', + css: '[data-testid="page-container-footer-next"]', + }); + + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + + await driver.waitForSelector({ + text: '0x5cfe73b6021e818b776b421b1c4db2474086a7e1', + css: '#accounts', + }); + }, + ); + }); + + it('should not queue the `eth_requestAccounts` requests when the requesting dapp already has connected accounts', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerDoubleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .withPermissionControllerConnectedToTwoTestDapps() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); + + // Open Dapp One + await openDapp(driver, undefined, DAPP_URL); + + // Dapp Send Button + await driver.clickElement('#sendButton'); + + // Leave the confirmation pending + + await openDapp(driver, undefined, DAPP_ONE_URL); + + const ethRequestAccounts = JSON.stringify({ + jsonrpc: '2.0', + method: 'eth_requestAccounts', + }); + + const accounts = await driver.executeScript( + `return window.ethereum.request(${ethRequestAccounts})`, + ); + + assert.deepStrictEqual(accounts, [ + '0x5cfe73b6021e818b776b421b1c4db2474086a7e1', + ]); + }, + ); + }); +}); diff --git a/test/e2e/tests/request-queuing/dapp1-switch-dapp2-send.spec.js b/test/e2e/tests/request-queuing/dapp1-switch-dapp2-send.spec.js index 02f496f68235..6cab2f88d96b 100644 --- a/test/e2e/tests/request-queuing/dapp1-switch-dapp2-send.spec.js +++ b/test/e2e/tests/request-queuing/dapp1-switch-dapp2-send.spec.js @@ -12,7 +12,7 @@ const { } = require('../../helpers'); describe('Request Queuing Dapp 1, Switch Tx -> Dapp 2 Send Tx', function () { - it('should queue send tx after switch network confirmation.', async function () { + it('should queue send tx after switch network confirmation and transaction should target the correct network after switch is confirmed', async function () { const port = 8546; const chainId = 1338; await withFixtures( @@ -165,4 +165,158 @@ describe('Request Queuing Dapp 1, Switch Tx -> Dapp 2 Send Tx', function () { }, ); }); + + it('should queue send tx after switch network confirmation and transaction should target the correct network after switch is cancelled.', async function () { + const port = 8546; + const chainId = 1338; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withNetworkControllerTripleGanache() + .withPreferencesControllerUseRequestQueueEnabled() + .withSelectedNetworkControllerPerDomain() + .build(), + dappOptions: { numberOfDapps: 2 }, + ganacheOptions: { + ...defaultGanacheOptions, + concurrent: [ + { + port, + chainId, + ganacheOptions2: defaultGanacheOptions, + }, + { + port: 7777, + chainId: 1000, + ganacheOptions2: defaultGanacheOptions, + }, + ], + }, + title: this.test.fullTitle(), + }, + async ({ driver }) => { + await unlockWallet(driver); + + // Open Dapp One + await openDapp(driver, undefined, DAPP_URL); + + // Connect to dapp + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); + + await driver.delay(regularDelayMs); + + await switchToNotificationWindow(driver); + + await driver.clickElement({ + text: 'Next', + tag: 'button', + css: '[data-testid="page-container-footer-next"]', + }); + + await driver.clickElement({ + text: 'Confirm', + tag: 'button', + css: '[data-testid="page-container-footer-next"]', + }); + + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + // Network Selector + await driver.clickElement('[data-testid="network-display"]'); + + // Switch to second network + await driver.clickElement({ + text: 'Localhost 8546', + css: 'p', + }); + + // Wait for the first dapp's connect confirmation to disappear + await driver.waitUntilXWindowHandles(2); + + // TODO: Request Queuing bug when opening both dapps at the same time will have them stuck on the same network, with will be incorrect for one of them. + // Open Dapp Two + await openDapp(driver, undefined, DAPP_ONE_URL); + + // Connect to dapp 2 + await driver.findClickableElement({ text: 'Connect', tag: 'button' }); + await driver.clickElement('#connectButton'); + + await driver.delay(regularDelayMs); + + await switchToNotificationWindow(driver, 4); + + await driver.clickElement({ + text: 'Next', + tag: 'button', + css: '[data-testid="page-container-footer-next"]', + }); + + await driver.clickElement({ + text: 'Confirm', + tag: 'button', + css: '[data-testid="page-container-footer-next"]', + }); + + await driver.switchToWindowWithUrl(DAPP_URL); + + // switchEthereumChain request + const switchEthereumChainRequest = JSON.stringify({ + jsonrpc: '2.0', + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x3e8' }], + }); + + // Initiate switchEthereumChain on Dapp Two + await driver.executeScript( + `window.ethereum.request(${switchEthereumChainRequest})`, + ); + + await driver.switchToWindowWithUrl(DAPP_ONE_URL); + + await driver.clickElement('#sendButton'); + + await switchToNotificationWindow(driver, 4); + await driver.findClickableElements({ + text: 'Cancel', + tag: 'button', + }); + + await driver.clickElement({ text: 'Cancel', tag: 'button' }); + + // Wait for switch confirmation to close then tx confirmation to show. + await driver.waitUntilXWindowHandles(3); + + await switchToNotificationWindow(driver, 4); + + // Check correct network on the send confirmation. + await driver.findElement({ + css: '[data-testid="network-display"]', + text: 'Localhost 8546', + }); + + await driver.clickElement({ text: 'Confirm', tag: 'button' }); + + // Switch back to the extension + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + await driver.clickElement( + '[data-testid="account-overview__activity-tab"]', + ); + + // Check for transaction + await driver.wait(async () => { + const confirmedTxes = await driver.findElements( + '.transaction-list__completed-transactions .activity-list-item', + ); + return confirmedTxes.length === 1; + }, 10000); + }, + ); + }); }); diff --git a/test/e2e/tests/settings/ipfs-ens-resolution.spec.js b/test/e2e/tests/settings/ipfs-ens-resolution.spec.js index 02e65cd946b0..fe06449f1d47 100644 --- a/test/e2e/tests/settings/ipfs-ens-resolution.spec.js +++ b/test/e2e/tests/settings/ipfs-ens-resolution.spec.js @@ -1,6 +1,151 @@ -const { buildWebDriver } = require('../../webdriver'); -const { withFixtures, tinyDelayMs, unlockWallet } = require('../../helpers'); +const { + defaultGanacheOptions, + tinyDelayMs, + unlockWallet, + withFixtures, +} = require('../../helpers'); const FixtureBuilder = require('../../fixture-builder'); +const { mockServerJsonRpc } = require('../ppom/mocks/mock-server-json-rpc'); + +const BALANCE_CHECKER = '0xb1f8e55c7f64d203c1400b9d8555d050f94adf39'; +const ENS_PUBLIC_RESOLVER = '0x4976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41'; +const ENS_REGISTRY_WITH_FALLBACK = '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e'; + +async function mockInfura(mockServer) { + await mockServerJsonRpc(mockServer, [ + [ + 'eth_blockNumber', + { + methodResultVariant: 'custom', + }, + ], + ['eth_estimateGas'], + ['eth_feeHistory'], + ['eth_gasPrice'], + ['eth_getBalance'], + ['eth_getBlockByNumber'], + ['eth_getTransactionCount'], + ['net_version'], + ]); + + // Resolver Call + await mockServer + .forPost() + .withJsonBodyIncluding({ + method: 'eth_call', + params: [ + { + to: ENS_REGISTRY_WITH_FALLBACK, + }, + ], + }) + .thenCallback(async (req) => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: (await req.body.getJson()).id, + result: + '0x0000000000000000000000004976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41', + }, + }; + }); + + // Supports Interface Call + await mockServer + .forPost() + .withJsonBodyIncluding({ + method: 'eth_call', + params: [ + { + data: '0x01ffc9a7bc1c58d100000000000000000000000000000000000000000000000000000000', + to: ENS_PUBLIC_RESOLVER, + }, + ], + }) + .thenCallback(async (req) => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: (await req.body.getJson()).id, + result: + '0x0000000000000000000000000000000000000000000000000000000000000001', + }, + }; + }); + + // Supports Interface Call + await mockServer + .forPost() + .withJsonBodyIncluding({ + method: 'eth_call', + params: [ + { + data: '0x01ffc9a7d8389dc500000000000000000000000000000000000000000000000000000000', + to: ENS_PUBLIC_RESOLVER, + }, + ], + }) + .thenCallback(async (req) => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: (await req.body.getJson()).id, + result: + '0x0000000000000000000000000000000000000000000000000000000000000000', + }, + }; + }); + + // Content Hash Call + await mockServer + .forPost() + .withJsonBodyIncluding({ + method: 'eth_call', + params: [ + { + data: '0xbc1c58d1e650784434622eeb4ffbbb3220ebb371e26ad1a77f388680d42d8b1624baa6df', + to: ENS_PUBLIC_RESOLVER, + }, + ], + }) + .thenCallback(async (req) => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: (await req.body.getJson()).id, + result: + '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000', + }, + }; + }); + + // Token Balance Call + await mockServer + .forPost() + .withJsonBodyIncluding({ + method: 'eth_call', + params: [ + { + to: BALANCE_CHECKER, + }, + ], + }) + .thenCallback(async (req) => { + return { + statusCode: 200, + json: { + jsonrpc: '2.0', + id: (await req.body.getJson()).id, + result: + '0x0000000000000000000000000000000000000000000000000000000000000000', + }, + }; + }); +} describe('Settings', function () { const ENS_NAME = 'metamask.eth'; @@ -10,25 +155,38 @@ describe('Settings', function () { it('Redirects to ENS domains when user inputs ENS into address bar', async function () { // Using proxy port that doesn't resolve so that the browser can error out properly // on the ".eth" hostname. The proxy does too much interference with 8000. - const { driver } = await buildWebDriver({ proxyUrl: '127.0.0.1:8001' }); - await driver.navigate(); - - // The setting defaults to "on" so we can simply enter an ENS address - // into the address bar and listen for address change - try { - await driver.openNewPage(ENS_NAME_URL); - } catch (e) { - // Ignore ERR_PROXY_CONNECTION_FAILED error - // since all we care about is getting to the correct URL - } - - // Ensure that the redirect to ENS Domains has happened - await driver.wait(async () => { - const currentUrl = await driver.getCurrentUrl(); - return currentUrl === ENS_DESTINATION_URL; - }, tinyDelayMs); - - await driver.quit(); + + await withFixtures( + { + fixtures: new FixtureBuilder().withNetworkControllerOnMainnet().build(), + ganacheOptions: defaultGanacheOptions, + title: this.test.title, + testSpecificMock: mockInfura, + driverOptions: { + proxyPort: '8001', + }, + }, + async ({ driver }) => { + await driver.navigate(); + + // The setting defaults to "on" so we can simply enter an ENS address + // into the address bar and listen for address change + try { + await driver.openNewPage(ENS_NAME_URL); + } catch (e) { + // Ignore ERR_PROXY_CONNECTION_FAILED error + // since all we care about is getting to the correct URL + } + + // Ensure that the redirect to ENS Domains has happened + await driver.wait(async () => { + const currentUrl = await driver.getCurrentUrl(); + return currentUrl === ENS_DESTINATION_URL; + }, 10000); + // Setting a large delay has proven to stabilize the flakiness of the redirect + // and it's only a MAX value + }, + ); }); it('Does not fetch ENS data for ENS Domain when ENS and IPFS switched off', async function () { diff --git a/test/e2e/tests/swap-send/swap-send-erc20.spec.ts b/test/e2e/tests/swap-send/swap-send-erc20.spec.ts index eb20ea18f42d..e8b416b9604e 100644 --- a/test/e2e/tests/swap-send/swap-send-erc20.spec.ts +++ b/test/e2e/tests/swap-send/swap-send-erc20.spec.ts @@ -4,6 +4,7 @@ import { openActionMenuAndStartSendFlow, logInWithBalanceValidation, } from '../../helpers'; +import { Driver } from '../../webdriver/driver'; import type { Ganache } from '../../seeder/ganache'; import { NATIVE_TOKEN_SYMBOL, @@ -21,14 +22,13 @@ describe('Swap-Send ERC20', function () { getSwapSendFixtures( this.test?.fullTitle(), SWAP_SEND_QUOTES_RESPONSE_TST_ETH, + '?sourceAmount=100000&sourceToken=0x581c3C1A2A4EBDE2A0Df29B5cf4c116E42945947&destinationToken=0x0000000000000000000000000000000000000000&sender=0x5cfe73b6021e818b776b421b1c4db2474086a7e1&recipient=0xc427D562164062a23a5cFf596A4a3208e72Acd28&slippage=2', ), async ({ driver, ganacheServer, }: { - // TODO: Replace `any` with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - driver: any; + driver: Driver; ganacheServer: Ganache; }) => { const swapSendPage = new SwapSendPage(driver); @@ -104,7 +104,7 @@ describe('Swap-Send ERC20', function () { 'Send TST as ETH', 'Confirmed', '-10 TST', - '-$0.00', + '', ); driver.summarizeErrorsAndExceptions(); diff --git a/test/e2e/tests/swap-send/swap-send-eth.spec.ts b/test/e2e/tests/swap-send/swap-send-eth.spec.ts index 4622a2000e24..244693d513a2 100644 --- a/test/e2e/tests/swap-send/swap-send-eth.spec.ts +++ b/test/e2e/tests/swap-send/swap-send-eth.spec.ts @@ -71,18 +71,20 @@ describe('Swap-Send ETH', function () { '≈ $38.84', ); + // TODO assert swap api request payload + await swapSendPage.submitSwap(); await swapSendPage.verifyHistoryEntry( 'Send ETH as TST', 'Pending', '-1 ETH', - '-$3,010.00', + '', ); await swapSendPage.verifyHistoryEntry( 'Send ETH as TST', 'Confirmed', '-1 ETH', - '-$3,010.00', + '', ); driver.summarizeErrorsAndExceptions(); diff --git a/test/e2e/tests/swap-send/swap-send-test-utils.ts b/test/e2e/tests/swap-send/swap-send-test-utils.ts index 94e4a8f0a0c9..a78cd2a27057 100644 --- a/test/e2e/tests/swap-send/swap-send-test-utils.ts +++ b/test/e2e/tests/swap-send/swap-send-test-utils.ts @@ -245,10 +245,11 @@ export class SwapSendPage { } export const mockSwapsApi = - (quotes = SWAP_SEND_QUOTES_RESPONSE_ETH_TST) => + (quotes: typeof SWAP_SEND_QUOTES_RESPONSE_ETH_TST, query: string) => async (mockServer: Mockttp) => { - return await mockServer + await mockServer .forGet(`${SWAPS_API_V2_BASE_URL}/v2/networks/1337/quotes`) + .withExactQuery(query) .always() .thenCallback(() => { return { @@ -261,6 +262,7 @@ export const mockSwapsApi = export const getSwapSendFixtures = ( title?: string, swapsQuotes = SWAP_SEND_QUOTES_RESPONSE_ETH_TST, + swapsQuery = '?sourceAmount=1000000000000000000&sourceToken=0x0000000000000000000000000000000000000000&destinationToken=0x581c3C1A2A4EBDE2A0Df29B5cf4c116E42945947&sender=0x5cfe73b6021e818b776b421b1c4db2474086a7e1&recipient=0xc427D562164062a23a5cFf596A4a3208e72Acd28&slippage=2', ) => { const ETH_CONVERSION_RATE_USD = 3010; return { @@ -296,7 +298,7 @@ export const getSwapSendFixtures = ( .build(), smartContract: SMART_CONTRACTS.HST, ethConversionInUsd: ETH_CONVERSION_RATE_USD, - testSpecificMock: mockSwapsApi(swapsQuotes), + testSpecificMock: mockSwapsApi(swapsQuotes, swapsQuery), ganacheOptions: generateGanacheOptions({ hardfork: 'london' }), title, }; diff --git a/test/e2e/tests/tokens/increase-token-allowance.spec.js b/test/e2e/tests/tokens/increase-token-allowance.spec.js index 4fb092906558..52a7d9454e38 100644 --- a/test/e2e/tests/tokens/increase-token-allowance.spec.js +++ b/test/e2e/tests/tokens/increase-token-allowance.spec.js @@ -232,6 +232,8 @@ describe('Increase Token Allowance', function () { }); await driver.delay(2000); + // Windows: MetaMask, Test Dapp and Dialog + await driver.waitUntilXWindowHandles(3); await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); let spendingCapElement = await driver.findElement( '[data-testid="custom-spending-cap-input"]', @@ -297,6 +299,8 @@ describe('Increase Token Allowance', function () { } async function confirmTransferFromTokensSuccess(driver) { + // Windows: MetaMask, Test Dapp and Dialog + await driver.waitUntilXWindowHandles(3, 1000, 10000); await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); await driver.waitForSelector({ text: '1.5 TST', tag: 'h1' }); await driver.clickElement({ text: 'Confirm', tag: 'button' }); diff --git a/test/e2e/tests/tokens/nft/import-nft.spec.js b/test/e2e/tests/tokens/nft/import-nft.spec.js index 872e5a6c4dbf..ed3d9056e599 100644 --- a/test/e2e/tests/tokens/nft/import-nft.spec.js +++ b/test/e2e/tests/tokens/nft/import-nft.spec.js @@ -4,7 +4,7 @@ const { withFixtures, unlockWallet, findAnotherAccountFromAccountList, - waitForAccountRendered, + locateAccountBalanceDOM, } = require('../../../helpers'); const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts'); const FixtureBuilder = require('../../../fixture-builder'); @@ -70,7 +70,7 @@ describe('Import NFT', function () { smartContract, title: this.test.fullTitle(), }, - async ({ driver, _, contractRegistry }) => { + async ({ driver, ganacheServer, contractRegistry }) => { const contractAddress = contractRegistry.getContractAddress(smartContract); await unlockWallet(driver); @@ -109,24 +109,22 @@ describe('Import NFT', function () { ); await driver.clickElement({ text: 'Add a new account', tag: 'button' }); - // set account name - await driver.fill('[placeholder="Account 2"]', '2nd account'); - await driver.delay(400); + // By clicking creating button without filling in the account name + // the default name would be set as Account 2 await driver.clickElement({ text: 'Create', tag: 'button' }); await driver.isElementPresent({ tag: 'span', - text: '2nd account', + text: 'Account 2', }); - const accountOneSelector = await findAnotherAccountFromAccountList( driver, 1, 'Account 1', ); - await waitForAccountRendered(driver); - await driver.clickElement(accountOneSelector); + await driver.clickElement(accountOneSelector); + await locateAccountBalanceDOM(driver, ganacheServer); const nftIsStillDisplayed = await driver.isElementPresentAndVisible({ css: 'h5', text: 'TestDappNFTs', diff --git a/test/e2e/tests/tokens/token-list.spec.ts b/test/e2e/tests/tokens/token-list.spec.ts new file mode 100644 index 000000000000..23a477cc83b3 --- /dev/null +++ b/test/e2e/tests/tokens/token-list.spec.ts @@ -0,0 +1,195 @@ +import { strict as assert } from 'assert'; +import { Mockttp } from 'mockttp'; +import { Context } from 'mocha'; +import { zeroAddress } from 'ethereumjs-util'; +import { CHAIN_IDS } from '../../../../shared/constants/network'; +import { toChecksumHexAddress } from '../../../../shared/modules/hexstring-utils'; +import FixtureBuilder from '../../fixture-builder'; +import { + clickNestedButton, + defaultGanacheOptions, + unlockWallet, + withFixtures, +} from '../../helpers'; +import { Driver } from '../../webdriver/driver'; + +describe('Token List', function () { + const chainId = CHAIN_IDS.MAINNET; + const tokenAddress = '0x2EFA2Cb29C2341d8E5Ba7D3262C9e9d6f1Bf3711'; + const symbol = 'foo'; + + const fixtures = { + fixtures: new FixtureBuilder({ inputChainId: chainId }).build(), + ganacheOptions: { + ...defaultGanacheOptions, + chainId: parseInt(chainId, 16), + }, + }; + + const importToken = async (driver: Driver) => { + await driver.clickElement({ text: 'Import tokens', tag: 'button' }); + await clickNestedButton(driver, 'Custom token'); + await driver.fill( + '[data-testid="import-tokens-modal-custom-address"]', + tokenAddress, + ); + await driver.waitForSelector('p.mm-box--color-error-default'); + await driver.fill( + '[data-testid="import-tokens-modal-custom-symbol"]', + symbol, + ); + await driver.clickElement({ text: 'Next', tag: 'button' }); + await driver.clickElement( + '[data-testid="import-tokens-modal-import-button"]', + ); + }; + + it('should not shows percentage increase for an ERC20 token without prices available', async function () { + await withFixtures( + { + ...fixtures, + title: (this as Context).test?.fullTitle(), + testSpecificMock: async (mockServer: Mockttp) => [ + // Mock no current price + await mockServer + .forGet( + `https://price.api.cx.metamask.io/v2/chains/${parseInt( + chainId, + 16, + )}/spot-prices`, + ) + .thenCallback(() => ({ + statusCode: 200, + json: {}, + })), + // Mock no historical prices + await mockServer + .forGet( + `https://price.api.cx.metamask.io/v1/chains/${chainId}/historical-prices/${tokenAddress}`, + ) + .thenCallback(() => ({ + statusCode: 200, + json: {}, + })), + ], + }, + async ({ driver }: { driver: Driver }) => { + await unlockWallet(driver); + await importToken(driver); + + // Verify native token increase + const testIdNative = `token-increase-decrease-percentage-${zeroAddress()}`; + + // Verify native token increase + const testId = `token-increase-decrease-percentage-${tokenAddress}`; + + const percentageNative = await ( + await driver.findElement(`[data-testid="${testIdNative}"]`) + ).getText(); + assert.equal(percentageNative, ''); + + const percentage = await ( + await driver.findElement(`[data-testid="${testId}"]`) + ).getText(); + assert.equal(percentage, ''); + }, + ); + }); + + it('shows percentage increase for an ERC20 token with prices available', async function () { + const ethConversionInUsd = 10000; + + // Prices are in ETH + const marketData = { + price: 0.123, + marketCap: 12, + pricePercentChange1d: 0.05, + }; + + const marketDataNative = { + price: 0.123, + marketCap: 12, + pricePercentChange1d: 0.02, + }; + + await withFixtures( + { + ...fixtures, + title: (this as Context).test?.fullTitle(), + ethConversionInUsd, + testSpecificMock: async (mockServer: Mockttp) => [ + // Mock current price + await mockServer + .forGet( + `https://price.api.cx.metamask.io/v2/chains/${parseInt( + chainId, + 16, + )}/spot-prices`, + ) + .thenCallback(() => ({ + statusCode: 200, + json: { + [zeroAddress()]: marketDataNative, + [tokenAddress.toLowerCase()]: marketData, + }, + })), + // Mock historical prices + await mockServer + .forGet( + `https://price.api.cx.metamask.io/v1/chains/${chainId}/historical-prices/${toChecksumHexAddress( + tokenAddress, + )}`, + ) + .thenCallback(() => ({ + statusCode: 200, + json: { + prices: [ + [1717566000000, marketData.price * 0.9], + [1717566322300, marketData.price], + [1717566611338, marketData.price * 1.1], + ], + }, + })), + ], + }, + async ({ driver }: { driver: Driver }) => { + await unlockWallet(driver); + await importToken(driver); + await driver.delay(500); + + // Verify native token increase + const testIdNative = `token-increase-decrease-percentage-${zeroAddress()}`; + + // Verify native token increase + const testId = `token-increase-decrease-percentage-${tokenAddress}`; + + const percentageNative = await ( + await driver.findElement(`[data-testid="${testIdNative}"]`) + ).getText(); + assert.equal(percentageNative, '+0.02%'); + + const percentage = await ( + await driver.findElement(`[data-testid="${testId}"]`) + ).getText(); + assert.equal(percentage, '+0.05%'); + + // check increase balance for native token + const increaseValue = await ( + await driver.findElement( + `[data-testid="token-increase-decrease-value"]`, + ) + ).getText(); + assert.equal(increaseValue, '+$50.00 '); + + // check percentage increase balance for native token + const percentageIncreaseDecrease = await ( + await driver.findElement( + `[data-testid="token-increase-decrease-percentage"]`, + ) + ).getText(); + + assert.equal(percentageIncreaseDecrease, '(+0.02%)'); + }, + ); + }); +}); diff --git a/test/e2e/tests/transaction/navigate-transactions.spec.js b/test/e2e/tests/transaction/navigate-transactions.spec.js index 99807d54f461..12c1144d5472 100644 --- a/test/e2e/tests/transaction/navigate-transactions.spec.js +++ b/test/e2e/tests/transaction/navigate-transactions.spec.js @@ -13,6 +13,7 @@ describe('Navigate transactions', function () { await withFixtures( { fixtures: new FixtureBuilder() + .withPreferencesControllerTxSimulationsDisabled() .withTransactionControllerMultipleTransactions() .build(), ganacheOptions: generateGanacheOptions({ hardfork: 'london' }), @@ -21,6 +22,12 @@ describe('Navigate transactions', function () { async ({ driver }) => { await unlockWallet(driver); + // Wait until total amount is loaded to mitigate flakiness on reject + await driver.findElement({ + tag: 'span', + text: '3.0000315', + }); + // navigate transactions await driver.clickElement('[data-testid="next-page"]'); let navigationElement = await driver.findElement( @@ -102,6 +109,7 @@ describe('Navigate transactions', function () { dapp: true, fixtures: new FixtureBuilder() .withPermissionControllerConnectedToTestDapp() + .withPreferencesControllerTxSimulationsDisabled() .withTransactionControllerMultipleTransactions() .build(), ganacheOptions: generateGanacheOptions({ hardfork: 'london' }), @@ -110,6 +118,12 @@ describe('Navigate transactions', function () { async ({ driver }) => { await unlockWallet(driver); + // Wait until total amount is loaded to mitigate flakiness on reject + await driver.findElement({ + tag: 'span', + text: '3.0000315', + }); + await driver.clickElement('[data-testid="next-page"]'); let navigationElement = await driver.findElement( '.confirm-page-container-navigation', @@ -146,6 +160,7 @@ describe('Navigate transactions', function () { await withFixtures( { fixtures: new FixtureBuilder() + .withPreferencesControllerTxSimulationsDisabled() .withTransactionControllerMultipleTransactions() .build(), ganacheOptions: generateGanacheOptions({ hardfork: 'london' }), @@ -154,6 +169,12 @@ describe('Navigate transactions', function () { async ({ driver }) => { await unlockWallet(driver); + // Wait until total amount is loaded to mitigate flakiness on reject + await driver.findElement({ + tag: 'span', + text: '3.0000315', + }); + // reject transaction await driver.clickElement({ text: 'Reject', tag: 'button' }); const navigationElement = await driver.waitForSelector({ @@ -174,6 +195,7 @@ describe('Navigate transactions', function () { await withFixtures( { fixtures: new FixtureBuilder() + .withPreferencesControllerTxSimulationsDisabled() .withTransactionControllerMultipleTransactions() .build(), ganacheOptions: generateGanacheOptions({ hardfork: 'london' }), @@ -182,6 +204,12 @@ describe('Navigate transactions', function () { async ({ driver }) => { await unlockWallet(driver); + // Wait until total amount is loaded to mitigate flakiness on reject + await driver.findElement({ + tag: 'span', + text: '3.0000315', + }); + // confirm transaction await driver.clickElement({ text: 'Confirm', tag: 'button' }); const navigationElement = await driver.waitForSelector({ @@ -202,6 +230,7 @@ describe('Navigate transactions', function () { await withFixtures( { fixtures: new FixtureBuilder() + .withPreferencesControllerTxSimulationsDisabled() .withTransactionControllerMultipleTransactions() .build(), ganacheOptions: generateGanacheOptions({ hardfork: 'london' }), @@ -210,6 +239,12 @@ describe('Navigate transactions', function () { async ({ driver, ganacheServer }) => { await unlockWallet(driver); + // Wait until total amount is loaded to mitigate flakiness on reject + await driver.findElement({ + tag: 'span', + text: '3.0000315', + }); + // reject transactions await driver.clickElement({ text: 'Reject 4', tag: 'a' }); await driver.clickElement({ text: 'Reject all', tag: 'button' }); diff --git a/test/e2e/vault-decryption-chrome.spec.js b/test/e2e/vault-decryption-chrome.spec.js index 6db89a79dbd0..c7939599b1fa 100644 --- a/test/e2e/vault-decryption-chrome.spec.js +++ b/test/e2e/vault-decryption-chrome.spec.js @@ -58,10 +58,17 @@ async function closePopoverIfPresent(driver) { await driver.clickElementSafe(popoverButtonSelector); await driver.clickElementSafe(enableButtonSelector); + // NFT Autodetection Independent Announcement + const nftAutodetection = { + css: '[data-testid="auto-detect-nft-modal"] button', + text: 'Not right now', + }; + await driver.clickElementSafe(nftAutodetection); + // Token Autodetection Independent Announcement const tokenAutodetection = { + css: '[data-testid="auto-detect-token-modal"] button', text: 'Not right now', - tag: 'button', }; await driver.clickElementSafe(tokenAutodetection); } @@ -84,9 +91,9 @@ async function getSRP(driver) { describe('Vault Decryptor Page', function () { it('is able to decrypt the vault using the vault-decryptor webapp', async function () { await withFixtures({}, async ({ driver }) => { - await driver.navigate(); - // the first app launch opens a new tab, we need to switch the focus - // to the first one. + // we don't need to use navigate + // since MM will automatically open a new window in prod build + await driver.waitUntilXWindowHandles(2); await driver.switchToWindowWithTitle('MetaMask'); // create a new vault through onboarding flow await completeCreateNewWalletOnboardingFlowWithOptOut( diff --git a/test/e2e/webdriver/chrome.js b/test/e2e/webdriver/chrome.js index b5068759812a..edbf90179f5a 100644 --- a/test/e2e/webdriver/chrome.js +++ b/test/e2e/webdriver/chrome.js @@ -4,21 +4,27 @@ const { ThenableWebDriver } = require('selenium-webdriver'); // eslint-disable-l const { isHeadless } = require('../../helpers/env'); /** - * Proxy host to use for HTTPS requests + * Determine the appropriate proxy server value to use * - * @type {string} + * @param {string|number} [proxyPort] - The proxy port to use + * @returns {string} The proxy server address */ -const HTTPS_PROXY_HOST = `${ - process.env.SELENIUM_HTTPS_PROXY || '127.0.0.1:8000' -}`; +function getProxyServer(proxyPort) { + const DEFAULT_PROXY_HOST = '127.0.0.1:8000'; + const { SELENIUM_HTTPS_PROXY } = process.env; + if (proxyPort) { + return `127.0.0.1:${proxyPort}`; + } + return SELENIUM_HTTPS_PROXY || DEFAULT_PROXY_HOST; +} /** * A wrapper around a {@code WebDriver} instance exposing Chrome-specific functionality */ class ChromeDriver { - static async build({ openDevToolsForTabs, port }) { + static async build({ openDevToolsForTabs, port, proxyPort }) { const args = [ - `--proxy-server=${HTTPS_PROXY_HOST}`, // Set proxy in the way that doesn't interfere with Selenium Manager + `--proxy-server=${getProxyServer(proxyPort)}`, // Set proxy in the way that doesn't interfere with Selenium Manager '--disable-features=OptimizationGuideModelDownloading,OptimizationHintsFetching,OptimizationTargetPrediction,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-dev-shm-usage', diff --git a/test/e2e/webdriver/driver.js b/test/e2e/webdriver/driver.js index 6815460f3091..5b76eb0d1c91 100644 --- a/test/e2e/webdriver/driver.js +++ b/test/e2e/webdriver/driver.js @@ -861,11 +861,21 @@ class Driver { * Retrieves the title of the window tab with the given handle ID. * * @param {int} handlerId - unique ID for the tab whose title is needed. - * @returns {Promise} promise resolving to the tab title after command completion + * @param {number} retries - Number of times to retry fetching the title if not immediately available. + * @param {number} interval - Time in milliseconds to wait between retries. + * @returns {Promise} Promise resolving to the tab title after command completion. + * @throws {Error} Throws an error if the window title does not load within the specified retries. */ - async getWindowTitleByHandlerId(handlerId) { + async getWindowTitleByHandlerId(handlerId, retries = 5, interval = 1000) { await this.driver.switchTo().window(handlerId); - return await this.driver.getTitle(); + for (let attempt = 1; attempt <= retries; attempt++) { + const title = await this.driver.getTitle(); + if (title) { + return title; + } + await new Promise((resolve) => setTimeout(resolve, interval)); + } + throw new Error('Window title did not load within the specified retries'); } /** @@ -1036,10 +1046,19 @@ class Driver { // not render visibly to the user and therefore no screenshot can be // taken. In this case we skip the screenshot and log the error. try { - const screenshot = await this.driver.takeScreenshot(); - await fs.writeFile(`${filepathBase}-screenshot.png`, screenshot, { - encoding: 'base64', - }); + // If there's more than one tab open, we want to iterate through all of them and take a screenshot with a unique name + const windowHandles = await this.driver.getAllWindowHandles(); + for (const handle of windowHandles) { + await this.driver.switchTo().window(handle); + const screenshot = await this.driver.takeScreenshot(); + await fs.writeFile( + `${filepathBase}-screenshot-${windowHandles.indexOf(handle) + 1}.png`, + screenshot, + { + encoding: 'base64', + }, + ); + } } catch (e) { console.error('Failed to take screenshot', e); } diff --git a/test/e2e/webdriver/firefox.js b/test/e2e/webdriver/firefox.js index f56436dc3892..e202c36c4092 100644 --- a/test/e2e/webdriver/firefox.js +++ b/test/e2e/webdriver/firefox.js @@ -20,11 +20,20 @@ const { isHeadless } = require('../../helpers/env'); const TEMP_PROFILE_PATH_PREFIX = path.join(os.tmpdir(), 'MetaMask-Fx-Profile'); /** - * Proxy host to use for HTTPS requests + * Determine the appropriate proxy server value to use + * + * @param {string|number} [proxyPort] - The proxy port to use + * @returns {string} The proxy server URL */ -const HTTPS_PROXY_HOST = new URL( - process.env.SELENIUM_HTTPS_PROXY || 'http://127.0.0.1:8000', -); +function getProxyServerURL(proxyPort) { + const DEFAULT_PROXY_HOST = 'http://127.0.0.1:8000'; + const { SELENIUM_HTTPS_PROXY } = process.env; + + if (proxyPort) { + return new URL(`http://127.0.0.1:${proxyPort}`); + } + return new URL(SELENIUM_HTTPS_PROXY || DEFAULT_PROXY_HOST); +} /** * A wrapper around a {@code WebDriver} instance exposing Firefox-specific functionality @@ -36,18 +45,21 @@ class FirefoxDriver { * @param {object} options - the options for the build * @param options.responsive * @param options.port + * @param options.proxyPort * @returns {Promise<{driver: !ThenableWebDriver, extensionUrl: string, extensionId: string}>} */ - static async build({ responsive, port }) { + static async build({ responsive, port, proxyPort }) { const templateProfile = fs.mkdtempSync(TEMP_PROFILE_PATH_PREFIX); const options = new firefox.Options().setProfile(templateProfile); + const proxyServerURL = getProxyServerURL(proxyPort); + // Set proxy in the way that doesn't interfere with Selenium Manager options.setPreference('network.proxy.type', 1); - options.setPreference('network.proxy.ssl', HTTPS_PROXY_HOST.hostname); + options.setPreference('network.proxy.ssl', proxyServerURL.hostname); options.setPreference( 'network.proxy.ssl_port', - parseInt(HTTPS_PROXY_HOST.port, 10), + parseInt(proxyServerURL.port, 10), ); options.setAcceptInsecureCerts(true); diff --git a/test/e2e/webdriver/index.js b/test/e2e/webdriver/index.js index 900e2f599157..4d5197bb0dac 100644 --- a/test/e2e/webdriver/index.js +++ b/test/e2e/webdriver/index.js @@ -3,14 +3,23 @@ const { Driver } = require('./driver'); const ChromeDriver = require('./chrome'); const FirefoxDriver = require('./firefox'); -async function buildWebDriver({ openDevToolsForTabs, port, timeOut } = {}) { +async function buildWebDriver({ + openDevToolsForTabs, + port, + timeOut, + proxyPort, +} = {}) { const browser = process.env.SELENIUM_BROWSER; const { driver: seleniumDriver, extensionId, extensionUrl, - } = await buildBrowserWebDriver(browser, { openDevToolsForTabs, port }); + } = await buildBrowserWebDriver(browser, { + openDevToolsForTabs, + port, + proxyPort, + }); const driver = new Driver(seleniumDriver, browser, extensionUrl, timeOut); return { diff --git a/test/env.js b/test/env.js index 8dbdd0aaa6bc..2c8b6c1af6e5 100644 --- a/test/env.js +++ b/test/env.js @@ -1,6 +1,6 @@ process.env.METAMASK_ENVIRONMENT = 'test'; process.env.SUPPORT_LINK = 'https://support.metamask.io'; -process.env.SUPPORT_REQUEST_LINK = 'https://metamask.zendesk.com/hc/en-us'; +process.env.SUPPORT_REQUEST_LINK = 'https://support.metamask.io'; process.env.IFRAME_EXECUTION_ENVIRONMENT_URL = 'https://execution.metamask.io/0.36.1-flask.1/index.html'; process.env.AUTH_API = 'https://mock-test-auth-api.metamask.io'; @@ -13,3 +13,5 @@ process.env.NOTIFICATIONS_SERVICE_URL = 'https://mock-test-notifications-api.metamask.io'; process.env.PUSH_NOTIFICATIONS_SERVICE_URL = 'https://mock-test-push-notifications-api.metamask.io'; +process.env.ENABLE_CONFIRMATION_REDESIGN = 'true'; +process.env.PORTFOLIO_URL = 'https://portfolio.test'; diff --git a/test/jest/constants.js b/test/jest/constants.js index 37253f494720..5f63bda3eb9d 100644 --- a/test/jest/constants.js +++ b/test/jest/constants.js @@ -1,2 +1,2 @@ export const METASWAP_BASE_URL = 'https://swap.api.cx.metamask.io'; -export const GAS_API_URL = 'https://gas.api.infura.io'; +export const GAS_API_URL = 'https://gas.api.cx.metamask.io'; diff --git a/test/jest/mocks.js b/test/jest/mocks.js index 3170a8dbb8f9..b52a0d984df3 100644 --- a/test/jest/mocks.js +++ b/test/jest/mocks.js @@ -1,4 +1,9 @@ -import { EthAccountType, EthMethod } from '@metamask/keyring-api'; +import { + EthAccountType, + EthMethod, + BtcMethod, + BtcAccountType, +} from '@metamask/keyring-api'; import { KeyringTypes } from '@metamask/keyring-controller'; import { v4 as uuidv4 } from 'uuid'; import { keyringTypeToName } from '@metamask/accounts-controller'; @@ -168,10 +173,37 @@ export const getInitialSendStateWithExistingTxState = (draftTxState) => ({ export function createMockInternalAccount({ address = MOCK_DEFAULT_ADDRESS, name, - is4337 = false, + type = EthAccountType.Eoa, keyringType = KeyringTypes.hd, snapOptions, } = {}) { + let methods; + + switch (type) { + case EthAccountType.Eoa: + methods = [ + EthMethod.PersonalSign, + EthMethod.Sign, + EthMethod.SignTransaction, + EthMethod.SignTypedDataV1, + EthMethod.SignTypedDataV3, + EthMethod.SignTypedDataV4, + ]; + break; + case EthAccountType.Erc4337: + methods = [ + EthMethod.PatchUserOperation, + EthMethod.PrepareUserOperation, + EthMethod.SignUserOperation, + ]; + break; + case BtcAccountType.P2wpkh: + methods = [BtcMethod.SendMany]; + break; + default: + throw new Error(`Unknown account type: ${type}`); + } + return { address, id: uuidv4(), @@ -184,21 +216,8 @@ export function createMockInternalAccount({ snap: snapOptions, }, options: {}, - methods: is4337 - ? [ - EthMethod.PrepareUserOperation, - EthMethod.PatchUserOperation, - EthMethod.SignUserOperation, - ] - : [ - EthMethod.PersonalSign, - EthMethod.Sign, - EthMethod.SignTransaction, - EthMethod.SignTypedDataV1, - EthMethod.SignTypedDataV3, - EthMethod.SignTypedDataV4, - ], - type: is4337 ? EthAccountType.Erc4337 : EthAccountType.Eoa, + methods, + type, }; } diff --git a/test/lib/render-helpers.js b/test/lib/render-helpers.js index ff5e97006c4d..e77e8418c2e1 100644 --- a/test/lib/render-helpers.js +++ b/test/lib/render-helpers.js @@ -75,11 +75,24 @@ export function renderWithProvider(component, store, pathname = '/') { }; } -export function renderHookWithProvider(hook, state, pathname = '/') { +export function renderHookWithProvider(hook, state, pathname = '/', Container) { const store = state ? configureStore(state) : undefined; - const { history, Wrapper } = createProviderWrapper(store, pathname); + + const { history, Wrapper: ProviderWrapper } = createProviderWrapper( + store, + pathname, + ); + + const wrapper = Container + ? ({ children }) => ( + + {children} + + ) + : ProviderWrapper; + return { - ...renderHook(hook, { wrapper: Wrapper }), + ...renderHook(hook, { wrapper }), history, }; } diff --git a/test/lib/wait-until-called.js b/test/lib/wait-until-called.js deleted file mode 100644 index c59e51aa4c69..000000000000 --- a/test/lib/wait-until-called.js +++ /dev/null @@ -1,66 +0,0 @@ -const DEFAULT_TIMEOUT = 10000; - -/** - * A function that wraps a sinon stub and returns an asynchronous function - * that resolves if the stubbed function was called enough times, or throws - * if the timeout is exceeded. - * - * The stub that has been passed in will be setup to call the wrapped function - * directly. - * - * WARNING: Any existing `callsFake` behavior will be overwritten. - * - * @param {import('sinon').stub} stub - A sinon stub of a function - * @param {unknown} [wrappedThis] - The object the stubbed function was called - * on, if any (i.e. the `this` value) - * @param {object} [options] - Optional configuration - * @param {number} [options.callCount] - The number of calls to wait for. - * @param {number|null} [options.timeout] - The timeout, in milliseconds. Pass - * in `null` to disable the timeout. - * @returns {Function} An asynchronous function that resolves when the stub is - * called enough times, or throws if the timeout is reached. - */ -function waitUntilCalled( - stub, - wrappedThis = null, - { callCount = 1, timeout = DEFAULT_TIMEOUT } = {}, -) { - let numCalls = 0; - let resolve; - let timeoutHandle; - const stubHasBeenCalled = new Promise((_resolve) => { - resolve = _resolve; - if (timeout !== null) { - timeoutHandle = setTimeout( - () => resolve(new Error('Timeout exceeded')), - timeout, - ); - } - }); - stub.callsFake((...args) => { - try { - if (stub.wrappedMethod) { - stub.wrappedMethod.call(wrappedThis, ...args); - } - } finally { - if (numCalls < callCount) { - numCalls += 1; - if (numCalls === callCount) { - if (timeoutHandle) { - clearTimeout(timeoutHandle); - } - resolve(); - } - } - } - }); - - return async () => { - const error = await stubHasBeenCalled; - if (error) { - throw error; - } - }; -} - -module.exports = waitUntilCalled; diff --git a/test/manual-scenarios/settings/about-metamask/ui-validation.csv b/test/manual-scenarios/settings/about-metamask/ui-validation.csv index 05917f67437a..1b8e9d38a65d 100644 --- a/test/manual-scenarios/settings/about-metamask/ui-validation.csv +++ b/test/manual-scenarios/settings/about-metamask/ui-validation.csv @@ -10,7 +10,7 @@ Step,Test Steps,Test Data,Expected Result,Notes 8,Click on the close button,,Validate the wallet overview page appears., 9,Click on the link for the 'Privacy policy',,Privacy policy 'https://consensys.io/privacy-policy' page is loaded,Critical because it's the only way to access privacy policy 10,Click on the link for the 'Terms of use',,Terms of use page 'https://consensys.io/terms-of-use' is loaded, -11,Click on the link for the 'Visit our support center',,Support page 'https://support.metamask.io/hc/en-us' is loaded, +11,Click on the link for the 'Visit our support center',,Support page 'https://support.metamask.io' is loaded, 12,Click on the link for the 'Attributions',,Attributions 'https://raw.githubusercontent.com/MetaMask/metamask-extension/develop/attribution.txt' page is loaded, 13,Click on the link for the 'Visit our website',,Visit our website 'https://metamask.io/' page is loaded, -14,Click on the link for the 'Contact us',,Contact us 'https://support.metamask.io/hc/en-us' code page is loaded, +14,Click on the link for the 'Contact us',,Contact us 'https://support.metamask.io' code page is loaded, diff --git a/test/manual-scenarios/settings/advanced/show-test-networks.md b/test/manual-scenarios/settings/advanced/show-test-networks.md new file mode 100644 index 000000000000..bd505aae9b07 --- /dev/null +++ b/test/manual-scenarios/settings/advanced/show-test-networks.md @@ -0,0 +1,51 @@ + +# Manual test scenario for the 'Show test networks' toggle + +Below is a summary of the validations included:- + +* Validate that the 'Show test networks' toggle is present in the Advanced settings and is off by default. +* Validate that the toggle can be turned on and off. +* Validate that when the toggle is turned on, the Networks dropdown also shows the 'Show test networks' as on, and the test networks are visible. + + +```markdown + +# Advanced Settings: Verify "Show test networks" Toggle Functionality + +# Feature: Toggle "Show test networks" in Advanced Settings + +In order to enhance user experience +As a user of the wallet extension +I want to toggle the "Show test networks" option in the Advanced Settings + +# Scenario: Default state of "Show test networks" toggle is OFF + +Given I am in settings +When I click on the "Advanced" tab +Then the "Show test networks" toggle is set to off by default +And I click on the Network selection drop down on the left top +Then the "Select a network" dialog box appears +And the "Show test networks" toggle is selected OFF +And the test networks are not visible in the dropdown + +# Scenario: "Show test networks" toggle icon functionality + +Given I am on the Advanced settings page +And the "Show test networks" toggle is initially set to OFF +When I click on the "Show test networks" toggle icon +Then the toggle switch should visually indicate to ON +When I click on the "Show test networks" toggle icon again +Then the toggle switch should visually indicate to OFF + +# Scenario: Turning ON "Show test networks" and validate the 'Show test networks' in network selection dialog is turned ON and the test networks are visible + +Given I am on the Advanced settings page +When I toggle the "Show test networks" switch ON +Then the toggle switch should visually indicate to ON +When I click on the Network selection drop down on the left top +Then the "Select a network" dialog box appears +And the "Show test networks" toggle is selected ON automatically +And the test networks are shown as expected + + +``` diff --git a/test/merge-coverage.js b/test/merge-coverage.js deleted file mode 100644 index 1a6617a4e90b..000000000000 --- a/test/merge-coverage.js +++ /dev/null @@ -1,268 +0,0 @@ -const fs = require('fs'); -const path = require('path'); -const libCoverage = require('istanbul-lib-coverage'); -const libReport = require('istanbul-lib-report'); -const reports = require('istanbul-reports'); -const glob = require('fast-glob'); -const yargs = require('yargs/yargs'); -const { hideBin } = require('yargs/helpers'); -// Temporarily commented out as we can't rely on the commented yaml file -// Can be restored when the codecov checks are restored -// const yaml = require('yaml'); -const codecovTargets = require('../coverage-targets'); - -// Temporarily commented out as we can't rely on the commented yaml file -// Can be restored when the codecov checks are restored. In the meantime -// the important parts of the yaml file are copied below in normal js object -// format. -// const codecovConfig = yaml.parse(fs.readFileSync('codecov.yml', 'utf8')); - -const codecovConfig = { - coverage: { - status: { - global: {}, - project: { - transforms: { - paths: ['development/build/transforms/**/*.js'], - }, - }, - }, - }, -}; - -const COVERAGE_DIR = './coverage/'; - -const COVERAGE_THRESHOLD_FOR_BUMP = 1; - -/** - * Load .json file at path and parse it into a javascript object - * - * @param {string} filePath - path to the file to load - * @returns {object} the JavaScript object parsed from the file - */ -function loadData(filePath) { - const json = fs.readFileSync(filePath); - return JSON.parse(json); -} - -/** - * Loads an array of json coverage files and merges them into a final coverage - * report. - * - * @param {string[]} files - array of strings that are paths to files - * @returns {libCoverage.CoverageMap} CoverageMap - */ -function mergeCoverageMaps(files) { - const coverageMap = libCoverage.createCoverageMap({}); - - files.forEach((covergeFinalFile) => { - coverageMap.merge(loadData(covergeFinalFile)); - }); - - return coverageMap; -} - -/** - * Given a target directory and a coverageMap generates a finalized coverage - * summary report and saves it to the directory. - * - * @param {string} dir - target directory - * @param {libCoverage.CoverageMap} coverageMap - CoverageMap to report on - * @param reportType - * @param reportOptions - */ -function generateSummaryReport(dir, coverageMap, reportType, reportOptions) { - const context = libReport.createContext({ - dir, - coverageMap, - }); - - reports.create(reportType, reportOptions ?? {}).execute(context); -} - -/** - * Generates a multiline string with coverage data - * - * @param {CoverageTarget} target - Target coverage threshold - * @param {import('istanbul-lib-coverage').CoverageSummaryData} actual - - * istanbul coverage summary detailing actual summary - * @returns {string} multiline report of coverage - */ -function generateConsoleReport(target, actual) { - const { lines, branches, functions, statements } = actual.data; - const breakdown = - `Lines: ${lines.covered}/${lines.total} (${lines.pct}%). Target: ${target.lines}%\n` + - `Branches: ${branches.covered}/${branches.total} (${branches.pct}%). Target: ${target.branches}%\n` + - `Statements: ${statements.covered}/${statements.total} (${statements.pct}%). Target: ${target.statements}%\n` + - `Functions: ${functions.covered}/${functions.total} (${functions.pct}%). Target: ${target.functions}%`; - return breakdown; -} - -/** - * @typedef {object} CoverageTarget - * @property {number} lines - percentage of lines that must be covered - * @property {number} statements - percentage of statements that must be covered - * @property {number} branches - percentage of branches that must be covered - * @property {number} functions - percentage of functions that must be covered - */ - -/** - * Checks if the coverage meets target - * - * @param {CoverageTarget} target - * @param {import('istanbul-lib-coverage').CoverageSummaryData} actual - * @returns {boolean} - */ -function isCoverageInsufficient(target, actual) { - const lineCoverageNotMet = actual.lines.pct < target.lines; - const branchCoverageNotMet = actual.branches.pct < target.branches; - const functionCoverageNotMet = actual.functions.pct < target.functions; - const statementCoverageNotMet = actual.statements.pct < target.statements; - return ( - lineCoverageNotMet || - branchCoverageNotMet || - functionCoverageNotMet || - statementCoverageNotMet - ); -} - -/** - * Checks if the coverage should be bumped up - * - * @param {CoverageTarget} target - * @param {import('istanbul-lib-coverage').CoverageSummaryData} actual - * @returns {boolean} - */ -function shouldCoverageBeBumped(target, actual) { - const lineCoverageNeedsBumped = - actual.lines.pct > target.lines + COVERAGE_THRESHOLD_FOR_BUMP; - const branchCoverageNeedsBumped = - actual.branches.pct > target.branches + COVERAGE_THRESHOLD_FOR_BUMP; - const functionCoverageNeedsBumped = - actual.functions.pct > target.functions + COVERAGE_THRESHOLD_FOR_BUMP; - const statementCoverageNeedsBumped = - actual.statements.pct > target.statements + COVERAGE_THRESHOLD_FOR_BUMP; - return ( - lineCoverageNeedsBumped || - branchCoverageNeedsBumped || - functionCoverageNeedsBumped || - statementCoverageNeedsBumped - ); -} - -/** - * Creates and returns a combined coverage summary report of every file in the - * provided array. - * - * @param {string[]} files - array of files generated by fast-glob - * @param {libCoverage.CoverageMap} coverageMap - * @returns {import('istanbul-lib-coverage').CoverageSummaryData} - */ -function getFileCoverage(files, coverageMap) { - const subCoverageMap = libCoverage.createCoverageMap({}); - - files.forEach((file) => { - try { - subCoverageMap.merge( - coverageMap.fileCoverageFor(`${process.cwd()}/${file}`), - ); - } catch { - // If the coverage doesn't exist, it means that it was excluded from - // coverage or had no coverage to report, which is fine. Glob is a lot - // wider of a net then what the test file runners match against. - } - }); - - const summary = subCoverageMap.getCoverageSummary(); - return summary; -} - -/** - * Checks coverage and reports to console - * Throws an error if coverage isn't met - * - * @param {string} name - The target's name from coverageThresholds in jest - * config - * @param {CoverageTarget} target - the target coverage threshold - * @param {import('istanbul-lib-coverage').CoverageSummaryData} actual - - * istanbul coverage summary representing actual coverage - */ -function checkCoverage(name, target, actual) { - const breakdown = generateConsoleReport(target, actual); - if (isCoverageInsufficient(target, actual)) { - const errorMsg = `Coverage thresholds for ${name} NOT met\n${breakdown}`; - throw new Error(errorMsg); - } else if (shouldCoverageBeBumped(target, actual)) { - const errorMsg = `Coverage EXCEEDS threshold for ${name} and must be bumped\n${breakdown}`; - throw new Error(errorMsg); - } - console.log(`Coverage thresholds for ${name} met\n${breakdown}\n\n`); -} - -/** - * Primary script function - */ -async function start() { - const { - argv: { html }, - } = yargs(hideBin(process.argv)).usage( - '$0 [options]', - 'Run unit tests on the application code.', - (yargsInstance) => - yargsInstance - .option('html', { - alias: ['h'], - default: false, - description: 'Generate HTML report', - type: 'boolean', - }) - .strict(), - ); - // First get all of the files matching the pattern coverage-final-${n}.json - // from the coverage directory - const files = fs.readdirSync(COVERAGE_DIR); - const filePaths = files - .filter( - (file) => - path.basename(file).startsWith('coverage-final') && - path.extname(file) === '.json', - ) - .map((file) => path.join(COVERAGE_DIR, file)); - - // Next, generate a coverageMap - const coverageMap = mergeCoverageMaps(filePaths, true); - - // Persist this to file, which may eventually be used in more steps - generateSummaryReport(COVERAGE_DIR, coverageMap, 'json-summary'); - if (html) { - generateSummaryReport(COVERAGE_DIR, coverageMap, 'html'); - } - - // Use the keys in coverageThreshold in jest config to determine targets - const coverageTargets = Object.keys(codecovConfig.coverage.status.project); - - // Check coverage totals for each target - coverageTargets.forEach((target) => { - const summary = - target === 'global' - ? coverageMap.getCoverageSummary() - : getFileCoverage( - glob.sync([ - ...codecovConfig.coverage.status.project[target].paths, - // checking test file coverage is redundant. - '!**/*.test.js', - '!**/__mocks__/**/*.js', - '!**/*.stories.*', - ]), - coverageMap, - ); - // Check and validate the coverage - checkCoverage(target, codecovTargets[target], summary); - }); -} - -start().catch((error) => { - // Report the errored coverage check - console.error(error); - process.exit(1); -}); diff --git a/test/run-unit-tests.js b/test/run-unit-tests.js index a58a858c09c8..645bcfc02e1b 100644 --- a/test/run-unit-tests.js +++ b/test/run-unit-tests.js @@ -64,45 +64,14 @@ async function runJest( } } -/** - * Run mocha tests on the app directory. Mocha tests do not yet support - * parallelism / test-splitting. - * - * @param {boolean} coverage - Use nyc to collect coverage - */ -async function runMocha({ coverage }) { - const options = ['mocha', './app/**/*.test.js']; - // If coverage is true, then we need to run nyc as the first command - // and mocha after, so we use unshift to add three options to the beginning - // of the options array. - if (coverage) { - options.unshift('nyc', '--reporter=json', 'yarn'); - } - await runInShell('yarn', options); - if (coverage) { - // Once done we rename the coverage file so that it is unique among test - // runners - await runCommand('mv', [ - './coverage/coverage-final.json', - `./coverage/coverage-final-mocha.json`, - ]); - } -} - async function start() { const { - argv: { mocha, jestGlobal, jestDev, coverage, fakeParallelism, maxWorkers }, + argv: { jestGlobal, jestDev, coverage, fakeParallelism, maxWorkers }, } = yargs(hideBin(process.argv)).usage( '$0 [options]', 'Run unit tests on the application code.', (yargsInstance) => yargsInstance - .option('mocha', { - alias: ['m'], - default: false, - description: 'Run Mocha tests', - type: 'boolean', - }) .option('jestDev', { alias: ['d'], default: false, @@ -156,8 +125,6 @@ async function start() { throw new Error( 'Do not try to run both jest test configs with fakeParallelism, bad things could happen.', ); - } else if (mocha) { - throw new Error('Test splitting is not supported for mocha yet.'); } else { const processes = []; for (let x = 0; x < fakeParallelism; x++) { @@ -179,9 +146,6 @@ async function start() { totalShards: maxProcesses, maxWorkers, }; - if (mocha) { - await runMocha(options); - } if (jestDev) { await runJest({ target: 'dev', ...options }); } diff --git a/ui/components/app/alert-system/alert-modal/alert-modal.tsx b/ui/components/app/alert-system/alert-modal/alert-modal.tsx index b53a49585649..b428cf13c138 100644 --- a/ui/components/app/alert-system/alert-modal/alert-modal.tsx +++ b/ui/components/app/alert-system/alert-modal/alert-modal.tsx @@ -74,7 +74,7 @@ export type AlertModalProps = { /** * The function to be executed when the modal needs to be closed. */ - onClose: () => void; + onClose: (request?: { recursive?: boolean }) => void; /** * The owner ID of the relevant alert from the `confirmAlerts` reducer. */ @@ -254,9 +254,24 @@ function AcknowledgeButton({ ); } -function ActionButton({ action }: { action?: { key: string; label: string } }) { +function ActionButton({ + action, + onClose, +}: { + action?: { key: string; label: string }; + onClose: (request: { recursive?: boolean } | void) => void; +}) { const { processAction } = useAlertActionHandler(); + const handleClick = useCallback(() => { + if (!action) { + return; + } + + processAction(action.key); + onClose({ recursive: true }); + }, [action, onClose, processAction]); + if (!action) { return null; } @@ -269,7 +284,7 @@ function ActionButton({ action }: { action?: { key: string; label: string } }) { variant={ButtonVariant.Primary} width={BlockSize.Full} size={ButtonSize.Lg} - onClick={() => processAction(key)} + onClick={handleClick} > {label} @@ -290,9 +305,12 @@ export function AlertModal({ }: AlertModalProps) { const { isAlertConfirmed, setAlertConfirmed, alerts } = useAlerts(ownerId); - const handleClose = useCallback(() => { - onClose(); - }, [onClose]); + const handleClose = useCallback( + (...args) => { + onClose(...args); + }, + [onClose], + ); const selectedAlert = alerts.find((alert: Alert) => alert.key === alertKey); @@ -358,7 +376,11 @@ export function AlertModal({ /> {(selectedAlert.actions ?? []).map( (action: { key: string; label: string }) => ( - + ), )} diff --git a/ui/components/app/alert-system/confirm-alert-modal/confirm-alert-modal.test.tsx b/ui/components/app/alert-system/confirm-alert-modal/confirm-alert-modal.test.tsx index cd4891840b01..2f038c4ec99b 100644 --- a/ui/components/app/alert-system/confirm-alert-modal/confirm-alert-modal.test.tsx +++ b/ui/components/app/alert-system/confirm-alert-modal/confirm-alert-modal.test.tsx @@ -17,6 +17,12 @@ describe('ConfirmAlertModal', () => { const onCancelMock = jest.fn(); const onSubmitMock = jest.fn(); const alertsMock = [ + { + key: DATA_ALERT_KEY_MOCK, + field: DATA_ALERT_KEY_MOCK, + severity: Severity.Danger, + message: DATA_ALERT_MESSAGE_MOCK, + }, { key: FROM_ALERT_KEY_MOCK, field: FROM_ALERT_KEY_MOCK, @@ -25,12 +31,6 @@ describe('ConfirmAlertModal', () => { reason: 'Reason 1', alertDetails: ['Detail 1', 'Detail 2'], }, - { - key: DATA_ALERT_KEY_MOCK, - field: DATA_ALERT_KEY_MOCK, - severity: Severity.Danger, - message: DATA_ALERT_MESSAGE_MOCK, - }, ]; const STATE_MOCK = { @@ -44,11 +44,11 @@ describe('ConfirmAlertModal', () => { }, }, }; + const mockStore = configureMockStore([])(STATE_MOCK); const defaultProps: ConfirmAlertModalProps = { ownerId: OWNER_ID_MOCK, - alertKey: FROM_ALERT_KEY_MOCK, onClose: onCloseMock, onCancel: onCancelMock, onSubmit: onSubmitMock, @@ -65,7 +65,7 @@ describe('ConfirmAlertModal', () => { it('disables submit button when confirm modal is not acknowledged', () => { const { getByTestId } = renderWithProvider( - , + , mockStore, ); @@ -84,7 +84,7 @@ describe('ConfirmAlertModal', () => { it('calls onSubmit when the button is clicked', () => { const { getByTestId } = renderWithProvider( - , + , mockStore, ); diff --git a/ui/components/app/alert-system/confirm-alert-modal/confirm-alert-modal.tsx b/ui/components/app/alert-system/confirm-alert-modal/confirm-alert-modal.tsx index e31d9c853e2e..c9647cedf633 100644 --- a/ui/components/app/alert-system/confirm-alert-modal/confirm-alert-modal.tsx +++ b/ui/components/app/alert-system/confirm-alert-modal/confirm-alert-modal.tsx @@ -25,8 +25,6 @@ import { AcknowledgeCheckboxBase } from '../alert-modal/alert-modal'; import { MultipleAlertModal } from '../multiple-alert-modal'; export type ConfirmAlertModalProps = { - /** The unique key representing the specific alert field. */ - alertKey: string; /** Callback function that is called when the cancel button is clicked. */ onCancel: () => void; /** The function to be executed when the modal needs to be closed. */ @@ -112,7 +110,6 @@ function ConfirmDetails({ } export function ConfirmAlertModal({ - alertKey, onCancel, onClose, onSubmit, @@ -121,16 +118,22 @@ export function ConfirmAlertModal({ const t = useI18nContext(); const { alerts, unconfirmedDangerAlerts } = useAlerts(ownerId); - const selectedAlert = alerts.find((alert) => alert.key === alertKey); - const [confirmCheckbox, setConfirmCheckbox] = useState(false); + // if there are multiple alerts, show the multiple alert modal const [multipleAlertModalVisible, setMultipleAlertModalVisible] = useState(unconfirmedDangerAlerts.length > 1); - const handleCloseMultipleAlertModal = useCallback(() => { - setMultipleAlertModalVisible(false); - }, []); + const handleCloseMultipleAlertModal = useCallback( + (request?: { recursive?: boolean }) => { + setMultipleAlertModalVisible(false); + + if (request?.recursive) { + onClose(); + } + }, + [onClose], + ); const handleOpenMultipleAlertModal = useCallback(() => { setMultipleAlertModalVisible(true); @@ -138,16 +141,11 @@ export function ConfirmAlertModal({ const handleConfirmCheckbox = useCallback(() => { setConfirmCheckbox(!confirmCheckbox); - }, [confirmCheckbox, selectedAlert]); - - if (!selectedAlert) { - return null; - } + }, [confirmCheckbox]); if (multipleAlertModalVisible) { return ( diff --git a/ui/components/app/alert-system/multiple-alert-modal/multiple-alert-modal.tsx b/ui/components/app/alert-system/multiple-alert-modal/multiple-alert-modal.tsx index 4d966aee7ccf..e060279f4f85 100644 --- a/ui/components/app/alert-system/multiple-alert-modal/multiple-alert-modal.tsx +++ b/ui/components/app/alert-system/multiple-alert-modal/multiple-alert-modal.tsx @@ -23,11 +23,11 @@ import { Alert } from '../../../../ducks/confirm-alerts/confirm-alerts'; export type MultipleAlertModalProps = { /** The key of the initial alert to display. */ - alertKey: string; + alertKey?: string; /** The function to be executed when the button in the alert modal is clicked. */ onFinalAcknowledgeClick: () => void; /** The function to be executed when the modal needs to be closed. */ - onClose: () => void; + onClose: (request?: { recursive?: boolean }) => void; /** The unique identifier of the entity that owns the alert. */ ownerId: string; }; @@ -148,8 +148,12 @@ export function MultipleAlertModal({ }: MultipleAlertModalProps) { const { isAlertConfirmed, alerts } = useAlerts(ownerId); + const initialAlertIndex = alerts.findIndex( + (alert: Alert) => alert.key === alertKey, + ); + const [selectedIndex, setSelectedIndex] = useState( - alerts.findIndex((alert: Alert) => alert.key === alertKey), + initialAlertIndex === -1 ? 0 : initialAlertIndex, ); const selectedAlert = alerts[selectedIndex]; diff --git a/ui/components/app/app-components.scss b/ui/components/app/app-components.scss index e516ff1fd8f6..63c916a5b256 100644 --- a/ui/components/app/app-components.scss +++ b/ui/components/app/app-components.scss @@ -23,6 +23,7 @@ @import 'snaps/snap-ui-renderer/index'; @import 'snaps/snap-ui-markdown/index'; @import 'snaps/snap-ui-button/index'; +@import 'snaps/snap-ui-file-input/index'; @import 'snaps/snap-delineator/index'; @import 'snaps/snap-list-item/index'; @import 'snaps/copyable/index'; @@ -77,6 +78,4 @@ @import '../institutional/custody-confirm-link-modal/index'; @import '../institutional/transaction-failed-modal/index'; ///: END:ONLY_INCLUDE_IF -///: BEGIN:ONLY_INCLUDE_IF(snaps) @import 'snaps/snap-insight/index'; -///: END:ONLY_INCLUDE_IF diff --git a/ui/components/app/asset-list/asset-list.js b/ui/components/app/asset-list/asset-list.js index a4e30cad81fc..82c45a27134e 100644 --- a/ui/components/app/asset-list/asset-list.js +++ b/ui/components/app/asset-list/asset-list.js @@ -6,23 +6,23 @@ import { PRIMARY, SECONDARY } from '../../../helpers/constants/common'; import { useUserPreferencedCurrency } from '../../../hooks/useUserPreferencedCurrency'; import { getSelectedAccountCachedBalance, - getShouldShowFiat, - getNativeCurrencyImage, getDetectedTokensInCurrentNetwork, getIstokenDetectionInactiveOnNonMainnetSupportedNetwork, getShouldHideZeroBalanceTokens, ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) getIsBuyableChain, ///: END:ONLY_INCLUDE_IF - getCurrentNetwork, getSelectedAccount, getPreferences, - getIsMainnet, } from '../../../selectors'; import { - getNativeCurrency, - getProviderConfig, -} from '../../../ducks/metamask/metamask'; + getMultichainCurrentNetwork, + getMultichainNativeCurrency, + getMultichainIsEvm, + getMultichainShouldShowFiat, + getMultichainCurrencyImage, + getMultichainIsMainnet, +} from '../../../selectors/multichain'; import { useCurrencyDisplay } from '../../../hooks/useCurrencyDisplay'; import { MetaMetricsContext } from '../../../contexts/metametrics'; import { @@ -53,12 +53,13 @@ import { const AssetList = ({ onClickAsset }) => { const [showDetectedTokens, setShowDetectedTokens] = useState(false); const selectedAccountBalance = useSelector(getSelectedAccountCachedBalance); - const nativeCurrency = useSelector(getNativeCurrency); - const showFiat = useSelector(getShouldShowFiat); - const { chainId } = useSelector(getCurrentNetwork); - const isMainnet = useSelector(getIsMainnet); + const nativeCurrency = useSelector(getMultichainNativeCurrency); + const showFiat = useSelector(getMultichainShouldShowFiat); + const isMainnet = useSelector(getMultichainIsMainnet); const { useNativeCurrencyAsPrimaryCurrency } = useSelector(getPreferences); - const { ticker, type, rpcUrl } = useSelector(getProviderConfig); + const { chainId, ticker, type, rpcUrl } = useSelector( + getMultichainCurrentNetwork, + ); const isOriginalNativeSymbol = useIsOriginalNativeTokenSymbol( chainId, ticker, @@ -68,7 +69,7 @@ const AssetList = ({ onClickAsset }) => { const trackEvent = useContext(MetaMetricsContext); const balance = useSelector(getSelectedAccountCachedBalance); const balanceIsLoading = !balance; - const { address: selectedAddress } = useSelector(getSelectedAccount); + const selectedAccount = useSelector(getSelectedAccount); const shouldHideZeroBalanceTokens = useSelector( getShouldHideZeroBalanceTokens, ); @@ -94,14 +95,14 @@ const AssetList = ({ onClickAsset }) => { currency: secondaryCurrency, }); - const primaryTokenImage = useSelector(getNativeCurrencyImage); + const primaryTokenImage = useSelector(getMultichainCurrencyImage); const detectedTokens = useSelector(getDetectedTokensInCurrentNetwork) || []; const isTokenDetectionInactiveOnNonMainnetSupportedNetwork = useSelector( getIstokenDetectionInactiveOnNonMainnetSupportedNetwork, ); const { tokensWithBalances, totalFiatBalance, loading } = - useAccountTotalFiatBalance(selectedAddress, shouldHideZeroBalanceTokens); + useAccountTotalFiatBalance(selectedAccount, shouldHideZeroBalanceTokens); tokensWithBalances.forEach((token) => { // token.string is the balance displayed in the TokenList UI token.string = roundToDecimalPlacesRemovingExtraZeroes(token.string, 5); @@ -112,7 +113,9 @@ const AssetList = ({ onClickAsset }) => { const shouldShowBuy = isBuyableChain && balanceIsZero; ///: END:ONLY_INCLUDE_IF - let isStakeable = isMainnet; + const isEvm = useSelector(getMultichainIsEvm); + + let isStakeable = isMainnet && isEvm; ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) isStakeable = false; ///: END:ONLY_INCLUDE_IF diff --git a/ui/components/app/auto-detect-nft/auto-detect-nft-modal.test.stories.js b/ui/components/app/auto-detect-nft/auto-detect-nft-modal.test.stories.js new file mode 100644 index 000000000000..2d915d6a61f5 --- /dev/null +++ b/ui/components/app/auto-detect-nft/auto-detect-nft-modal.test.stories.js @@ -0,0 +1,48 @@ +import React from 'react'; +import { Provider } from 'react-redux'; +import testData from '../../../../.storybook/test-data'; +import configureStore from '../../../store/store'; +import AutoDetectNftModal from './auto-detect-nft-modal'; + +const customData = { + ...testData, + metamask: { + ...testData.metamask, + currentCurrency: 'USD', + intlLocale: 'en-US', + }, +}; +const customStore = configureStore(customData); + +export default { + title: 'Components/App/AutoDetectNftModal', + component: AutoDetectNftModal, + decorators: [ + (Story) => ( + + + + ), + ], + argTypes: { + isOpen: { + control: 'boolean', + }, + onClose: { action: 'onClose' }, + }, + args: { + isOpen: true, + }, +}; + +const Template = (args) => ; + +export const ModalOpen = Template.bind({}); +ModalOpen.args = { + isOpen: true, +}; + +export const ModalClosed = Template.bind({}); +ModalClosed.args = { + isOpen: false, +}; diff --git a/ui/components/app/auto-detect-nft/auto-detect-nft-modal.test.tsx b/ui/components/app/auto-detect-nft/auto-detect-nft-modal.test.tsx new file mode 100644 index 000000000000..e3fcc8b6e425 --- /dev/null +++ b/ui/components/app/auto-detect-nft/auto-detect-nft-modal.test.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { screen, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { useDispatch } from 'react-redux'; +import configureMockStore from 'redux-mock-store'; +import mockState from '../../../../test/data/mock-state.json'; +import { renderWithProvider } from '../../../../test/lib/render-helpers'; +import AutoDetectNftModal from './auto-detect-nft-modal'; + +// Mock store setup +const mockStore = configureMockStore([])(mockState); + +jest.mock('react-redux', () => ({ + ...jest.requireActual('react-redux'), + useDispatch: jest.fn(), +})); + +describe('AutoDetectNftModal', () => { + const useDispatchMock = jest.mocked(useDispatch); + + beforeEach(() => { + jest.resetAllMocks(); + useDispatchMock.mockReturnValue(jest.fn()); + }); + + it('renders the modal when isOpen is true', () => { + renderWithProvider( + ({})} />, + mockStore, + ); + + expect(screen.getByText('Enable NFT autodetection')).toBeInTheDocument(); + expect(screen.getByText('Allow')).toBeInTheDocument(); + expect(screen.getByText('Not right now')).toBeInTheDocument(); + }); + + it('calls onClose with true when Allow button is clicked', () => { + useDispatchMock.mockReturnValue(jest.fn().mockResolvedValue({})); + const handleClose = jest.fn(); + renderWithProvider( + , + mockStore, + ); + + fireEvent.click(screen.getByText('Allow')); + expect(handleClose).toHaveBeenCalledWith(true); + }); + + it('calls onClose with false when Not right now button is clicked', () => { + useDispatchMock.mockReturnValue(jest.fn().mockResolvedValue({})); + const handleClose = jest.fn(); + renderWithProvider( + , + mockStore, + ); + + fireEvent.click(screen.getByText('Not right now')); + expect(handleClose).toHaveBeenCalledWith(false); + }); +}); diff --git a/ui/components/app/auto-detect-nft/auto-detect-nft-modal.tsx b/ui/components/app/auto-detect-nft/auto-detect-nft-modal.tsx new file mode 100644 index 000000000000..3fa744a72e89 --- /dev/null +++ b/ui/components/app/auto-detect-nft/auto-detect-nft-modal.tsx @@ -0,0 +1,132 @@ +import React, { useCallback, useContext } from 'react'; + +import { useDispatch, useSelector } from 'react-redux'; +import { + Modal, + ModalContent, + ModalOverlay, + ModalHeader, + Box, + Text, + ModalBody, + ModalFooter, +} from '../../component-library'; +import { useI18nContext } from '../../../hooks/useI18nContext'; +import { + AlignItems, + BorderRadius, + Display, + FlexDirection, + JustifyContent, + TextAlign, + TextVariant, +} from '../../../helpers/constants/design-system'; +import { setOpenSeaEnabled, setUseNftDetection } from '../../../store/actions'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; +import { + MetaMetricsEventCategory, + MetaMetricsEventName, +} from '../../../../shared/constants/metametrics'; +import { getProviderConfig } from '../../../ducks/metamask/metamask'; +import { ORIGIN_METAMASK } from '../../../../shared/constants/app'; + +type AutoDetectNftModalProps = { + isOpen: boolean; + onClose: (arg: boolean) => void; +}; +function AutoDetectNftModal({ isOpen, onClose }: AutoDetectNftModalProps) { + const t = useI18nContext(); + const dispatch = useDispatch(); + const trackEvent = useContext(MetaMetricsContext); + const { chainId } = useSelector(getProviderConfig); + + const handleNftAutoDetection = useCallback( + (val) => { + trackEvent({ + event: val + ? MetaMetricsEventName.NftAutoDetectionEnableModal + : MetaMetricsEventName.NftAutoDetectionDisableModal, + category: MetaMetricsEventCategory.Navigation, + properties: { + chain_id: chainId, + referrer: ORIGIN_METAMASK, + }, + }); + if (val) { + dispatch(setOpenSeaEnabled(val)); + dispatch(setUseNftDetection(val)); + } + onClose(val); + }, + [dispatch], + ); + + return ( + onClose(true)} + isClosedOnOutsideClick={false} + isClosedOnEscapeKey={false} + className="mm-modal__custom-scrollbar auto-detect-in-modal" + data-testid="auto-detect-nft-modal" + autoFocus={false} + > + + + + {t('enableNftAutoDetection')} + + + + + + + {t('allowMetaMaskToDetectNFTs')} + + + {t('immediateAccessToYourNFTs')} + + + {t('effortlesslyNavigateYourDigitalAssets')} + + + {t('diveStraightIntoUsingYourNFTs')} + + + + + handleNftAutoDetection(true)} + submitButtonProps={{ + children: t('allow'), + block: true, + }} + onCancel={() => handleNftAutoDetection(false)} + cancelButtonProps={{ + children: t('notRightNow'), + block: true, + }} + /> + + + ); +} + +export default AutoDetectNftModal; diff --git a/ui/components/app/auto-detect-nft/index.scss b/ui/components/app/auto-detect-nft/index.scss new file mode 100644 index 000000000000..5714d957f0f9 --- /dev/null +++ b/ui/components/app/auto-detect-nft/index.scss @@ -0,0 +1,10 @@ +.auto-detect-in-modal { + &__benefit { + flex: 1; + } + + &__dialog { + background-position: -80px 16px; + background-repeat: no-repeat; + } +} diff --git a/ui/components/app/auto-detect-token/auto-detect-token-modal.tsx b/ui/components/app/auto-detect-token/auto-detect-token-modal.tsx index cf03216ec3ca..c050d53c64cd 100644 --- a/ui/components/app/auto-detect-token/auto-detect-token-modal.tsx +++ b/ui/components/app/auto-detect-token/auto-detect-token-modal.tsx @@ -74,6 +74,7 @@ function AutoDetectTokenModal({ isClosedOnOutsideClick={false} isClosedOnEscapeKey={false} className="mm-modal__custom-scrollbar auto-detect-in-modal" + data-testid="auto-detect-token-modal" autoFocus={false} > diff --git a/ui/components/app/beta-header/__snapshots__/beta-header.test.js.snap b/ui/components/app/beta-header/__snapshots__/beta-header.test.js.snap index 723696ccf89b..d29a64e13dd2 100644 --- a/ui/components/app/beta-header/__snapshots__/beta-header.test.js.snap +++ b/ui/components/app/beta-header/__snapshots__/beta-header.test.js.snap @@ -12,7 +12,7 @@ exports[`Beta Header should match snapshot 1`] = ` This is a beta version. Please report bugs diff --git a/ui/components/app/confirm/info/__snapshots__/info.test.tsx.snap b/ui/components/app/confirm/info/__snapshots__/info.test.tsx.snap index 1054ecabe66a..a257e3ee3fa8 100644 --- a/ui/components/app/confirm/info/__snapshots__/info.test.tsx.snap +++ b/ui/components/app/confirm/info/__snapshots__/info.test.tsx.snap @@ -77,7 +77,7 @@ exports[`ConfirmInfo should match snapshot 1`] = `
`; diff --git a/ui/components/app/confirm/info/row/__snapshots__/section.test.tsx.snap b/ui/components/app/confirm/info/row/__snapshots__/section.test.tsx.snap new file mode 100644 index 000000000000..8f48917bd4f3 --- /dev/null +++ b/ui/components/app/confirm/info/row/__snapshots__/section.test.tsx.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ConfirmInfoSection should match snapshot 1`] = ` +
+
+ Test Content +
+
+`; + +exports[`ConfirmInfoSection should match snapshot without padding 1`] = ` +
+
+ Test Content +
+
+`; diff --git a/ui/components/app/confirm/info/row/alert-row/alert-row.stories.tsx b/ui/components/app/confirm/info/row/alert-row/alert-row.stories.tsx index b3d64ac65651..c6b9a3664375 100644 --- a/ui/components/app/confirm/info/row/alert-row/alert-row.stories.tsx +++ b/ui/components/app/confirm/info/row/alert-row/alert-row.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { ConfirmInfoRowVariant } from '../row'; -import { AlertRow } from './alert-row'; +import { ConfirmInfoAlertRow } from './alert-row'; import configureStore from '../../../../../../store/store'; import { Provider } from 'react-redux'; import { Meta } from '@storybook/react'; @@ -41,7 +41,7 @@ const storeMock = configureStore({ const ConfirmInfoRowStory = { title: 'Components/App/Confirm/AlertRow', - component: AlertRow, + component: ConfirmInfoAlertRow, argTypes: { variant: { control: 'select', @@ -55,9 +55,9 @@ const ConfirmInfoRowStory = { }, }, decorators: [(story) => {story()}], -} as Meta; +} as Meta; -export const DefaultStory = (args) => ; +export const DefaultStory = (args) => ; DefaultStory.storyName = 'Default'; diff --git a/ui/components/app/confirm/info/row/alert-row/alert-row.test.tsx b/ui/components/app/confirm/info/row/alert-row/alert-row.test.tsx index 968c4238509a..f539326a9284 100644 --- a/ui/components/app/confirm/info/row/alert-row/alert-row.test.tsx +++ b/ui/components/app/confirm/info/row/alert-row/alert-row.test.tsx @@ -5,7 +5,7 @@ import { Text } from '../../../../../component-library'; import { renderWithProvider } from '../../../../../../../test/lib/render-helpers'; import { Severity } from '../../../../../../helpers/constants/design-system'; import mockState from '../../../../../../../test/data/mock-state.json'; -import { AlertRow, AlertRowProps } from './alert-row'; +import { ConfirmInfoAlertRow, ConfirmInfoAlertRowProps } from './alert-row'; const onProcessActionMock = jest.fn(); @@ -36,7 +36,7 @@ describe('AlertRow', () => { }, ]; const renderAlertRow = ( - props?: Partial, + props?: Partial, state?: Record, ) => { const STATE_MOCK = { @@ -60,7 +60,7 @@ describe('AlertRow', () => { const mockStore = configureMockStore([])(STATE_MOCK); return renderWithProvider( - value} ownerId={OWNER_ID_NO_ALERT_MOCK} diff --git a/ui/components/app/confirm/info/row/alert-row/alert-row.tsx b/ui/components/app/confirm/info/row/alert-row/alert-row.tsx index ef84818904ad..ec64e2b79636 100644 --- a/ui/components/app/confirm/info/row/alert-row/alert-row.tsx +++ b/ui/components/app/confirm/info/row/alert-row/alert-row.tsx @@ -13,7 +13,7 @@ import { import { Box } from '../../../../../component-library'; import { MultipleAlertModal } from '../../../../alert-system/multiple-alert-modal'; -export type AlertRowProps = ConfirmInfoRowProps & { +export type ConfirmInfoAlertRowProps = ConfirmInfoRowProps & { alertKey: string; ownerId: string; }; @@ -36,12 +36,12 @@ function getAlertTextColors( } } -export const AlertRow = ({ +export const ConfirmInfoAlertRow = ({ alertKey, ownerId, variant, ...rowProperties -}: AlertRowProps) => { +}: ConfirmInfoAlertRowProps) => { const { getFieldAlerts } = useAlerts(ownerId); const fieldAlerts = getFieldAlerts(alertKey); const hasFieldAlert = fieldAlerts.length > 0; @@ -49,11 +49,11 @@ export const AlertRow = ({ const [alertModalVisible, setAlertModalVisible] = useState(false); - const handleCloseModal = () => { + const handleModalClose = () => { setAlertModalVisible(false); }; - const handleOpenModal = () => { + const handleInlineAlertClick = () => { setAlertModalVisible(true); }; @@ -66,7 +66,10 @@ export const AlertRow = ({ const inlineAlert = hasFieldAlert ? ( - + ) : null; @@ -74,10 +77,10 @@ export const AlertRow = ({ <> {alertModalVisible && ( )} diff --git a/ui/components/app/confirm/info/row/constants.ts b/ui/components/app/confirm/info/row/constants.ts index 884ec88fefe9..6bdcd7bb7542 100644 --- a/ui/components/app/confirm/info/row/constants.ts +++ b/ui/components/app/confirm/info/row/constants.ts @@ -1 +1,12 @@ export const TEST_ADDRESS = '0x5CfE73b6021E818B776b421B1c4Db2474086a7e1'; + +export enum RowAlertKey { + EstimatedFee = 'estimatedFee', + Speed = 'speed', +} + +export enum AlertActionKey { + Buy = 'buy', + ShowAdvancedGasFeeModal = 'showAdvancedGasModal', + ShowGasFeeModal = 'showGasFeeModal', +} diff --git a/ui/components/app/confirm/info/row/date.stories.tsx b/ui/components/app/confirm/info/row/date.stories.tsx new file mode 100644 index 000000000000..971ba7624f3e --- /dev/null +++ b/ui/components/app/confirm/info/row/date.stories.tsx @@ -0,0 +1,26 @@ +import React from 'react'; + +import { ConfirmInfoRow } from './row'; +import { ConfirmInfoRowDate } from './date'; + +const ConfirmInfoRowDateStory = { + title: 'Components/App/Confirm/InfoRowDate', + component: ConfirmInfoRowDate, + + decorators: [ + (story) => {story()}, + ], + + argTypes: { + url: { + control: 'date', + }, + }, +}; + +export const DefaultStory = ({ date }) => ; +DefaultStory.args = { + date: 1633019124000, +}; + +export default ConfirmInfoRowDateStory; diff --git a/ui/components/app/confirm/info/row/date.test.tsx b/ui/components/app/confirm/info/row/date.test.tsx new file mode 100644 index 000000000000..eda8510e4e7c --- /dev/null +++ b/ui/components/app/confirm/info/row/date.test.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { render } from '@testing-library/react'; + +import { ConfirmInfoRowDate } from './date'; + +describe('ConfirmInfoRowDate', () => { + it('should match snapshot', () => { + const { getByText } = render(); + expect(getByText('30 September 2021, 16:25')).toBeInTheDocument(); + }); +}); diff --git a/ui/components/app/confirm/info/row/date.tsx b/ui/components/app/confirm/info/row/date.tsx new file mode 100644 index 000000000000..47fa8e4ea56a --- /dev/null +++ b/ui/components/app/confirm/info/row/date.tsx @@ -0,0 +1,27 @@ +import React from 'react'; + +import { + AlignItems, + Display, + FlexWrap, + TextColor, +} from '../../../../../helpers/constants/design-system'; +import { formatUTCDate } from '../../../../../helpers/utils/util'; +import { Box, Text } from '../../../../component-library'; + +export type ConfirmInfoRowDateProps = { + date: number; +}; + +export const ConfirmInfoRowDate = ({ date }: ConfirmInfoRowDateProps) => ( + + + {formatUTCDate(date)} + + +); diff --git a/ui/components/app/confirm/info/row/divider.tsx b/ui/components/app/confirm/info/row/divider.tsx index cc2c1ac70e2a..98c09840f4bb 100644 --- a/ui/components/app/confirm/info/row/divider.tsx +++ b/ui/components/app/confirm/info/row/divider.tsx @@ -6,6 +6,9 @@ export const ConfirmInfoRowDivider: React.FC = () => { style={{ height: '1px', backgroundColor: 'var(--color-border-muted)', + // Ignore the padding from the section. + marginLeft: '-8px', + marginRight: '-8px', }} >
); diff --git a/ui/components/app/confirm/info/row/index.ts b/ui/components/app/confirm/info/row/index.ts index ef2102b280a1..0fc08d5887bb 100644 --- a/ui/components/app/confirm/info/row/index.ts +++ b/ui/components/app/confirm/info/row/index.ts @@ -1,4 +1,5 @@ export * from './address'; +export * from './date'; export * from './divider'; export * from './row'; export * from './text'; diff --git a/ui/components/app/confirm/info/row/row.tsx b/ui/components/app/confirm/info/row/row.tsx index bb29832db7b2..1455c46c44ef 100644 --- a/ui/components/app/confirm/info/row/row.tsx +++ b/ui/components/app/confirm/info/row/row.tsx @@ -66,7 +66,7 @@ export const ConfirmInfoRowContext = createContext({ variant: ConfirmInfoRowVariant.Default, }); -export const ConfirmInfoRow = ({ +export const ConfirmInfoRow: React.FC = ({ label, children, variant = ConfirmInfoRowVariant.Default, @@ -74,7 +74,7 @@ export const ConfirmInfoRow = ({ style, labelChildren, color, -}: ConfirmInfoRowProps) => ( +}) => ( ( +
+ {story()} +
+ ), + ], + + argTypes: { + noPadding: { + control: 'boolean', + }, + }, +}; + +export const DefaultStory = (args) => ( + <> + + + + + + + + + + + + + + + + + +); + +DefaultStory.args = { + noPadding: false, +}; + +export default ConfirmInfoSectionStory; diff --git a/ui/components/app/confirm/info/row/section.test.tsx b/ui/components/app/confirm/info/row/section.test.tsx new file mode 100644 index 000000000000..e9c344caf0cb --- /dev/null +++ b/ui/components/app/confirm/info/row/section.test.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import { ConfirmInfoSection } from './section'; + +describe('ConfirmInfoSection', () => { + it('should match snapshot', () => { + const { container } = render( + Test Content, + ); + expect(container).toMatchSnapshot(); + }); + + it('should match snapshot without padding', () => { + const { container } = render( + Test Content, + ); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/ui/components/app/confirm/info/row/section.tsx b/ui/components/app/confirm/info/row/section.tsx new file mode 100644 index 000000000000..f49c8103fae4 --- /dev/null +++ b/ui/components/app/confirm/info/row/section.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { Box } from '../../../../component-library'; +import { + BackgroundColor, + BorderRadius, +} from '../../../../../helpers/constants/design-system'; + +export type ConfirmInfoSectionProps = { + children: React.ReactNode | string; + noPadding?: boolean; +}; + +export const ConfirmInfoSection = ({ + children, + noPadding, +}: ConfirmInfoSectionProps) => { + return ( + + {children} + + ); +}; diff --git a/ui/components/app/confirm/info/row/text.stories.tsx b/ui/components/app/confirm/info/row/text.stories.tsx index 81c1333c9c38..49ae403679df 100644 --- a/ui/components/app/confirm/info/row/text.stories.tsx +++ b/ui/components/app/confirm/info/row/text.stories.tsx @@ -1,3 +1,4 @@ +import { action } from '@storybook/addon-actions'; import React from 'react'; import { ConfirmInfoRow } from './row'; import { ConfirmInfoRowText } from './text'; @@ -5,15 +6,16 @@ import { ConfirmInfoRowText } from './text'; const ConfirmInfoRowTextStory = { title: 'Components/App/Confirm/InfoRowText', component: ConfirmInfoRowText, - decorators: [ (story) => {story()}, ], - argTypes: { url: { control: 'text', }, + onEditClick: { + action: 'onEditClick', + }, }, }; @@ -22,4 +24,9 @@ DefaultStory.args = { text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', }; +export const EditableStory = (props) => ; +EditableStory.args = { + text: 'Lorem ipsum dolor sit amet.', +}; + export default ConfirmInfoRowTextStory; diff --git a/ui/components/app/confirm/info/row/text.tsx b/ui/components/app/confirm/info/row/text.tsx index c9f046d34f28..dc1a35d2c14e 100644 --- a/ui/components/app/confirm/info/row/text.tsx +++ b/ui/components/app/confirm/info/row/text.tsx @@ -1,17 +1,29 @@ -import React from 'react'; -import { Box, Text } from '../../../../component-library'; +import React, { useContext } from 'react'; +import { I18nContext } from '../../../../../contexts/i18n'; import { AlignItems, Display, FlexWrap, + IconColor, TextColor, } from '../../../../../helpers/constants/design-system'; +import { Box, ButtonIcon, IconName, Text } from '../../../../component-library'; export type ConfirmInfoRowTextProps = { text: string; + onEditClick?: () => void; + editIconClassName?: string; }; -export const ConfirmInfoRowText = ({ text }: ConfirmInfoRowTextProps) => { +export const ConfirmInfoRowText: React.FC = ({ + text, + onEditClick, + editIconClassName, +}) => { + const t = useContext(I18nContext); + + const isEditable = Boolean(onEditClick); + return ( { {text} + {isEditable ? ( + + ) : null} ); }; diff --git a/ui/components/app/currency-input/currency-input.test.js b/ui/components/app/currency-input/currency-input.test.js index 799bc2c2e6ef..91ee730132c8 100644 --- a/ui/components/app/currency-input/currency-input.test.js +++ b/ui/components/app/currency-input/currency-input.test.js @@ -1,6 +1,7 @@ import React from 'react'; import configureMockStore from 'redux-mock-store'; import { fireEvent, waitFor } from '@testing-library/react'; +import mockState from '../../../../test/data/mock-state.json'; import { renderWithProvider } from '../../../../test/lib/render-helpers'; import { useIsOriginalNativeTokenSymbol } from '../../../hooks/useIsOriginalNativeTokenSymbol'; import CurrencyInput from '.'; @@ -16,6 +17,7 @@ describe('CurrencyInput Component', () => { const mockStore = { metamask: { + ...mockState.metamask, currentCurrency: 'usd', currencyRates: { ETH: { 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 faecfc0586af..5c7f9218283a 100644 --- a/ui/components/app/metamask-template-renderer/safe-component-list.js +++ b/ui/components/app/metamask-template-renderer/safe-component-list.js @@ -15,7 +15,6 @@ import Tooltip from '../../ui/tooltip/tooltip'; import { AvatarIcon, Text } from '../../component-library'; import ActionableMessage from '../../ui/actionable-message/actionable-message'; import { AccountListItem } from '../../multichain'; -///: BEGIN:ONLY_INCLUDE_IF(snaps) import { ConfirmInfoRow, ConfirmInfoRowAddress, @@ -28,11 +27,13 @@ import { SnapUIMarkdown } from '../snaps/snap-ui-markdown'; import { SnapUILink } from '../snaps/snap-ui-link'; import { SmartTransactionStatusPage } from '../../../pages/smart-transactions/smart-transaction-status-page'; import { SnapUIImage } from '../snaps/snap-ui-image'; +import { SnapUIFileInput } from '../snaps/snap-ui-file-input'; import { SnapUIInput } from '../snaps/snap-ui-input'; import { SnapUIForm } from '../snaps/snap-ui-form'; import { SnapUIButton } from '../snaps/snap-ui-button'; import { SnapUIDropdown } from '../snaps/snap-ui-dropdown'; -///: END:ONLY_INCLUDE_IF +import { SnapUICheckbox } from '../snaps/snap-ui-checkbox'; +import { SnapUITooltip } from '../snaps/snap-ui-tooltip'; ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) import { SnapAccountSuccessMessage } from '../../../pages/confirmations/components/snap-account-success-message'; import { SnapAccountErrorMessage } from '../../../pages/confirmations/components/snap-account-error-message'; @@ -71,7 +72,6 @@ export const safeComponentList = { Typography, SmartTransactionStatusPage, UrlIcon, - ///: BEGIN:ONLY_INCLUDE_IF(snaps) Copyable, SnapDelineator, SnapUIMarkdown, @@ -81,11 +81,13 @@ export const safeComponentList = { ConfirmInfoRow, ConfirmInfoRowAddress, ConfirmInfoRowValueDouble, + SnapUIFileInput, SnapUIInput, SnapUIButton, SnapUIForm, SnapUIDropdown, - ///: END:ONLY_INCLUDE_IF + SnapUICheckbox, + SnapUITooltip, ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) CreateSnapAccount, RemoveSnapAccount, diff --git a/ui/components/app/mmi-signature-mismatch-banner/mmi-signature-mismatch-banner.tsx b/ui/components/app/mmi-signature-mismatch-banner/mmi-signature-mismatch-banner.tsx index 3fd8d7449d53..4d1e2a583c61 100644 --- a/ui/components/app/mmi-signature-mismatch-banner/mmi-signature-mismatch-banner.tsx +++ b/ui/components/app/mmi-signature-mismatch-banner/mmi-signature-mismatch-banner.tsx @@ -48,8 +48,8 @@ const MMISignatureMismatchBanner: React.FC = memo(() => { }, [currentConfirmation, allAccounts]); if ( - selectedAccount && - fromAccount && + !selectedAccount || + !fromAccount || selectedAccount.address === fromAccount.address ) { return null; diff --git a/ui/components/app/modals/cancel-transaction/cancel-transaction-gas-fee/cancel-transaction-gas-fee.component.test.js b/ui/components/app/modals/cancel-transaction/cancel-transaction-gas-fee/cancel-transaction-gas-fee.component.test.js index 03798f9c8209..a2303eec8e3b 100644 --- a/ui/components/app/modals/cancel-transaction/cancel-transaction-gas-fee/cancel-transaction-gas-fee.component.test.js +++ b/ui/components/app/modals/cancel-transaction/cancel-transaction-gas-fee/cancel-transaction-gas-fee.component.test.js @@ -1,5 +1,6 @@ import React from 'react'; import configureMockStore from 'redux-mock-store'; +import mockState from '../../../../../../test/data/mock-state.json'; import { renderWithProvider } from '../../../../../../test/lib/render-helpers'; import { CHAIN_IDS, @@ -9,7 +10,7 @@ import { import CancelTransactionGasFee from './cancel-transaction-gas-fee.component'; describe('CancelTransactionGasFee Component', () => { - const mockState = { + const defaultState = { metamask: { providerConfig: { chainId: CHAIN_IDS.GOERLI, @@ -20,10 +21,12 @@ describe('CancelTransactionGasFee Component', () => { preferences: { useNativeCurrencyAsPrimaryCurrency: false, }, + completedOnboarding: true, + internalAccounts: mockState.metamask.internalAccounts, }, }; - const mockStore = configureMockStore()(mockState); + const mockStore = configureMockStore()(defaultState); it('should render', () => { const props = { diff --git a/ui/components/app/modals/confirm-remove-account/__snapshots__/confirm-remove-account.test.js.snap b/ui/components/app/modals/confirm-remove-account/__snapshots__/confirm-remove-account.test.js.snap index 41b9359dd8c4..e62c58582592 100644 --- a/ui/components/app/modals/confirm-remove-account/__snapshots__/confirm-remove-account.test.js.snap +++ b/ui/components/app/modals/confirm-remove-account/__snapshots__/confirm-remove-account.test.js.snap @@ -122,7 +122,7 @@ exports[`Confirm Remove Account should match snapshot 1`] = ` This account will be removed from your wallet. Please make sure you have the original Secret Recovery Phrase or private key for this imported account before continuing. You can import or create accounts again from the account drop-down.
diff --git a/ui/components/app/modals/eth-sign-modal/__snapshots__/eth-sign-modal.test.js.snap b/ui/components/app/modals/eth-sign-modal/__snapshots__/eth-sign-modal.test.js.snap index 87de53ae8c56..bcc48a9d8e1c 100644 --- a/ui/components/app/modals/eth-sign-modal/__snapshots__/eth-sign-modal.test.js.snap +++ b/ui/components/app/modals/eth-sign-modal/__snapshots__/eth-sign-modal.test.js.snap @@ -33,7 +33,7 @@ exports[`Eth Sign Modal should match snapshot 1`] = ` Allowing eth_sign requests can make you vulnerable to phishing attacks. Always review the URL and be careful when signing messages that contain code. diff --git a/ui/components/app/modals/eth-sign-modal/eth-sign-modal.js b/ui/components/app/modals/eth-sign-modal/eth-sign-modal.js index d5f9a7931300..a8bc47c9cd7d 100644 --- a/ui/components/app/modals/eth-sign-modal/eth-sign-modal.js +++ b/ui/components/app/modals/eth-sign-modal/eth-sign-modal.js @@ -99,7 +99,7 @@ const EthSignModal = ({ hideModal }) => { {t('toggleEthSignModalDescription')} {t('learnMoreUpperCase')} diff --git a/ui/components/app/network-display/index.scss b/ui/components/app/network-display/index.scss index 9d6fa922819e..1c4e3dd568cb 100644 --- a/ui/components/app/network-display/index.scss +++ b/ui/components/app/network-display/index.scss @@ -1,37 +1,5 @@ .network-display { - display: flex; - align-items: center; - justify-content: flex-start; - padding: 0 10px; - border-radius: 4px; - min-height: 25px; - user-select: none; - - &--disabled { - cursor: not-allowed; - } - - &.chip { - margin: 0; - max-width: 100%; - } - - & .chip__label { - padding-left: 2px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - & .chip__left-icon { - padding-left: 8px; - } - - & .chip__right-icon { - margin-right: 4px; - } - - &--clickable { - cursor: pointer; + &:not([disabled]):active { + background-color: transparent; } } diff --git a/ui/components/app/network-display/network-display.js b/ui/components/app/network-display/network-display.js index d4b81ec38d0a..11c56a60fcb7 100644 --- a/ui/components/app/network-display/network-display.js +++ b/ui/components/app/network-display/network-display.js @@ -1,129 +1,27 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import classnames from 'classnames'; import { useSelector } from 'react-redux'; -import { - NETWORK_TYPES, - BUILT_IN_NETWORKS, -} from '../../../../shared/constants/network'; -import LoadingIndicator from '../../ui/loading-indicator'; -import ColorIndicator from '../../ui/color-indicator'; import { BorderColor, - IconColor, - Size, - TypographyVariant, + BackgroundColor, } from '../../../helpers/constants/design-system'; -import Chip from '../../ui/chip/chip'; -import { useI18nContext } from '../../../hooks/useI18nContext'; -import { isNetworkLoading } from '../../../selectors'; -import { Icon, IconName, IconSize } from '../../component-library'; -import { getProviderConfig } from '../../../ducks/metamask/metamask'; -import { getNetworkLabelKey } from '../../../helpers/utils/i18n-helper'; - -/** - * @deprecated The `` component has been deprecated in favor of the new `` component from the component-library. - * Please update your code to use the new `` component instead, which can be found at ui/components/component-library/picker-network/picker-network.tsx. - * You can find documentation for the new `PickerNetwork` component in the MetaMask Storybook: - * {@link https://metamask.github.io/metamask-storybook/?path=/docs/components-componentlibrary-pickernetwork--docs} - * If you would like to help with the replacement of the old `NetworkDisplay` component, please submit a pull request against this GitHub issue: - * {@link https://github.com/MetaMask/metamask-extension/issues/20485} - */ - -export default function NetworkDisplay({ - indicatorSize, - disabled, - labelProps, - targetNetwork, - onClick, -}) { - const networkIsLoading = useSelector(isNetworkLoading); - const providerConfig = useSelector(getProviderConfig); - const t = useI18nContext(); - - const { nickname, type: networkType } = targetNetwork ?? providerConfig; +import { getCurrentNetwork } from '../../../selectors'; +import { PickerNetwork, AvatarNetworkSize } from '../../component-library'; +export default function NetworkDisplay() { + const currentNetwork = useSelector(getCurrentNetwork); return ( - - - - } - rightIcon={ - onClick ? : null - } - label={ - networkType === NETWORK_TYPES.RPC - ? nickname ?? t('privateNetwork') - : t(getNetworkLabelKey(networkType)) - } - className={classnames('network-display', { - 'network-display--disabled': disabled, - 'network-display--clickable': typeof onClick === 'function', - })} - labelProps={{ - variant: TypographyVariant.H7, - ...labelProps, - }} + ); } -NetworkDisplay.propTypes = { - /** - * The size of the indicator - */ - indicatorSize: PropTypes.oneOf(Object.values(Size)), - /** - * The label props of the label can use most of the Typography props - */ - labelProps: Chip.propTypes.labelProps, - /** - * The target network - */ - targetNetwork: PropTypes.shape({ - type: PropTypes.oneOf([ - ...Object.keys(BUILT_IN_NETWORKS), - NETWORK_TYPES.RPC, - ]), - nickname: PropTypes.string, - }), - /** - * Whether the NetworkDisplay is disabled - */ - disabled: PropTypes.bool, - /** - * The onClick event handler of the NetworkDisplay - * if it is not passed it is assumed that the NetworkDisplay - * should not be interactive and removes the caret and changes the border color - * of the NetworkDisplay - */ - onClick: PropTypes.func, -}; - -NetworkDisplay.defaultProps = { - indicatorSize: Size.LG, -}; diff --git a/ui/components/app/network-display/network-display.stories.js b/ui/components/app/network-display/network-display.stories.js index 5a67dc676ae4..2392a107b6e9 100644 --- a/ui/components/app/network-display/network-display.stories.js +++ b/ui/components/app/network-display/network-display.stories.js @@ -1,107 +1,10 @@ import React from 'react'; - -import { - BUILT_IN_NETWORKS, - NETWORK_TYPES, -} from '../../../../shared/constants/network'; -import { Severity, Size } from '../../../helpers/constants/design-system'; - -import { BannerAlert } from '../../component-library/banner-alert'; import NetworkDisplay from '.'; export default { title: 'Components/App/NetworkDisplay', - - argTypes: { - indicatorSize: { - control: 'select', - options: Object.values(Size), - }, - labelProps: { - control: 'object', - }, - targetNetwork: { - control: 'select', - options: [...Object.keys(BUILT_IN_NETWORKS), NETWORK_TYPES.RPC], - }, - disabled: { - control: 'boolean', - }, - onClick: { - action: 'onClick', - description: - 'The onClick event handler of the NetworkDisplay. If it is not passed it is assumed that the NetworkDisplay SHOULD NOT be interactive and removes the caret and changes the border color of the NetworkDisplay to border-muted', - }, - }, - args: { - targetNetwork: 'goerli', - }, }; -export const DefaultStory = (args) => ( - <> - - - -); +export const DefaultStory = () => ; DefaultStory.storyName = 'Default'; - -export const TargetNetwork = (args) => { - const targetNetworkArr = [ - ...Object.keys(BUILT_IN_NETWORKS), - NETWORK_TYPES.RPC, - ]; - return ( - <> - {Object.values(targetNetworkArr).map((variant) => ( - - ))} - - ); -}; - -export const DisplayOnly = (args) => { - const targetNetworkArr = [ - ...Object.keys(BUILT_IN_NETWORKS), - NETWORK_TYPES.RPC, - ]; - return ( - <> - {Object.values(targetNetworkArr).map((variant) => ( - - ))} - - ); -}; diff --git a/ui/components/app/nfts-detection-notice-nfts-tab/nfts-detection-notice-nfts-tab.js b/ui/components/app/nfts-detection-notice-nfts-tab/nfts-detection-notice-nfts-tab.js index c3a19fdf18aa..7ec66e532aec 100644 --- a/ui/components/app/nfts-detection-notice-nfts-tab/nfts-detection-notice-nfts-tab.js +++ b/ui/components/app/nfts-detection-notice-nfts-tab/nfts-detection-notice-nfts-tab.js @@ -1,21 +1,34 @@ import React from 'react'; -import { useHistory } from 'react-router-dom'; +import { useDispatch, useSelector } from 'react-redux'; import { BannerAlert } from '../../component-library'; import { useI18nContext } from '../../../hooks/useI18nContext'; -import { SECURITY_ROUTE } from '../../../helpers/constants/routes'; +import { + detectNfts, + setOpenSeaEnabled, + setShowNftDetectionEnablementToast, + setUseNftDetection, +} from '../../../store/actions'; +import { getOpenSeaEnabled } from '../../../selectors'; export default function NFTsDetectionNoticeNFTsTab() { const t = useI18nContext(); - const history = useHistory(); + const dispatch = useDispatch(); + const isDisplayNFTMediaToggleEnabled = useSelector(getOpenSeaEnabled); return ( { - e.preventDefault(); - history.push(`${SECURITY_ROUTE}#autodetect-nfts`); + actionButtonOnClick={() => { + if (!isDisplayNFTMediaToggleEnabled) { + dispatch(setOpenSeaEnabled(true)); + } + dispatch(setUseNftDetection(true)); + // Show toast + dispatch(setShowNftDetectionEnablementToast(true)); + // dispatch action to detect nfts + dispatch(detectNfts()); }} > { diff --git a/ui/components/app/nfts-tab/index.scss b/ui/components/app/nfts-tab/index.scss index 2f5e266c136a..44c20656d417 100644 --- a/ui/components/app/nfts-tab/index.scss +++ b/ui/components/app/nfts-tab/index.scss @@ -1,7 +1,15 @@ .nfts-tab { + &__fetching { + display: flex; + height: 100px; + align-items: center; + justify-content: center; + padding: 30px; + } + &__loading { display: flex; - height: 250px; + height: 200px; align-items: center; justify-content: center; padding: 30px; diff --git a/ui/components/app/nfts-tab/nfts-tab.js b/ui/components/app/nfts-tab/nfts-tab.js index f9f87ea0f8a1..bc54ca4c9766 100644 --- a/ui/components/app/nfts-tab/nfts-tab.js +++ b/ui/components/app/nfts-tab/nfts-tab.js @@ -23,6 +23,7 @@ import { ///: END:ONLY_INCLUDE_IF getIsMainnet, getUseNftDetection, + getNftIsStillFetchingIndication, } from '../../../selectors'; import { checkAndUpdateAllNftsOwnershipStatus, @@ -49,6 +50,7 @@ import { } from '../../multichain/ramps-card/ramps-card'; import { useAccountTotalFiatBalance } from '../../../hooks/useAccountTotalFiatBalance'; ///: END:ONLY_INCLUDE_IF +import Spinner from '../../ui/spinner'; export default function NftsTab() { const useNftDetection = useSelector(getUseNftDetection); @@ -57,14 +59,17 @@ export default function NftsTab() { const t = useI18nContext(); const dispatch = useDispatch(); const trackEvent = useContext(MetaMetricsContext); + const nftsStillFetchingIndication = useSelector( + getNftIsStillFetchingIndication, + ); ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) - const { address: selectedAddress } = useSelector(getSelectedAccount); + const selectedAccount = useSelector(getSelectedAccount); const shouldHideZeroBalanceTokens = useSelector( getShouldHideZeroBalanceTokens, ); const { totalFiatBalance } = useAccountTotalFiatBalance( - selectedAddress, + selectedAccount, shouldHideZeroBalanceTokens, ); const balanceIsZero = Number(totalFiatBalance) === 0; @@ -114,8 +119,15 @@ export default function NftsTab() { currentLocale, ]); - if (nftsLoading) { - return
{t('loadingNFTs')}
; + if (!hasAnyNfts && nftsStillFetchingIndication) { + return ( + + + + ); } return ( @@ -128,18 +140,29 @@ export default function NftsTab() { ///: END:ONLY_INCLUDE_IF } + {isMainnet && !useNftDetection ? ( + + + + ) : null} {hasAnyNfts > 0 || previouslyOwnedCollection.nfts.length > 0 ? ( - - ) : ( - <> - {isMainnet && !useNftDetection ? ( - - + + + + {nftsStillFetchingIndication ? ( + + ) : null} + + ) : ( + <> { checkAndUpdateAllNftsOwnershipStatus: checkAndUpdateAllNftsOwnershipStatusStub, updateNftDropDownState: updateNftDropDownStateStub, + setUseNftDetection: setUseNftDetectionStub, + setOpenSeaEnabled: setDisplayNftMediaStub, }); const historyPushMock = jest.fn(); @@ -234,31 +238,46 @@ describe('NFT Items', () => { jest.clearAllMocks(); }); + function delay(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + describe('NFTs Detection Notice', () => { - it('should render the NFTs Detection Notice when currently selected network is Mainnet and currently selected account has no nfts', () => { + it('should render the NFTs Detection Notice when currently selected network is Mainnet and nft detection is set to false and user has nfts', () => { + render({ + selectedAddress: ACCOUNT_1, + nfts: NFTS, + }); + expect(screen.queryByText('NFT autodetection')).toBeInTheDocument(); + }); + + it('should render the NFTs Detection Notice when currently selected network is Mainnet and nft detection is set to false and user has no nfts', async () => { render({ selectedAddress: ACCOUNT_2, nfts: NFTS, + useNftDetection: false, }); expect(screen.queryByText('NFT autodetection')).toBeInTheDocument(); }); - it('should not render the NFTs Detection Notice when currently selected network is Mainnet and currently selected account has NFTs', () => { + it('should not render the NFTs Detection Notice when currently selected network is Mainnet and nft detection is ON', () => { render({ selectedAddress: ACCOUNT_1, nfts: NFTS, + useNftDetection: true, }); expect(screen.queryByText('NFT autodetection')).not.toBeInTheDocument(); }); - it('should take user to the experimental settings tab in settings when user clicks "Turn on NFT detection in Settings"', () => { + it('should turn on nft detection without going to settings when user clicks "Enable NFT Autodetection" and nft detection is set to false', async () => { render({ selectedAddress: ACCOUNT_2, nfts: NFTS, + useNftDetection: false, }); - fireEvent.click(screen.queryByText('Turn on NFT detection in Settings')); - expect(historyPushMock).toHaveBeenCalledTimes(1); - expect(historyPushMock).toHaveBeenCalledWith( - `${SECURITY_ROUTE}#autodetect-nfts`, - ); + fireEvent.click(screen.queryByText('Enable NFT Autodetection')); + expect(setUseNftDetectionStub).toHaveBeenCalledTimes(1); + expect(setDisplayNftMediaStub).toHaveBeenCalledTimes(1); + expect(setUseNftDetectionStub.mock.calls[0][0]).toStrictEqual(true); + expect(setDisplayNftMediaStub.mock.calls[0][0]).toStrictEqual(true); }); it('should not render the NFTs Detection Notice when currently selected network is Mainnet and currently selected account has no NFTs but use NFT autodetection preference is set to true', () => { render({ @@ -268,10 +287,20 @@ describe('NFT Items', () => { }); expect(screen.queryByText('NFT autodetection')).not.toBeInTheDocument(); }); - it('should not render the NFTs Detection Notice when currently selected network is Mainnet and currently selected account has no NFTs but user has dismissed the notice before', () => { + it('should render the NFTs Detection Notice when currently selected network is Mainnet and currently selected account has no NFTs but user has dismissed the notice before', () => { + render({ + selectedAddress: ACCOUNT_1, + nfts: NFTS, + }); + expect(screen.queryByText('NFT autodetection')).toBeInTheDocument(); + }); + + it('should not render the NFTs Detection Notice when currently selected network is NOT Mainnet', () => { render({ selectedAddress: ACCOUNT_1, nfts: NFTS, + useNftDetection: false, + chainId: '0x4', }); expect(screen.queryByText('NFT autodetection')).not.toBeInTheDocument(); }); @@ -337,11 +366,13 @@ describe('NFT Items', () => { }); describe('NFT Tab Ramps Card', () => { - it('shows the ramp card when user balance is zero', () => { + it('shows the ramp card when user balance is zero', async () => { const { queryByText } = render({ selectedAddress: ACCOUNT_1, balance: '0x0', }); + // wait for spinner to be removed + await delay(3000); expect(queryByText('Get ETH to buy NFTs')).toBeInTheDocument(); }); diff --git a/ui/components/app/permission-page-container/permission-page-container.component.js b/ui/components/app/permission-page-container/permission-page-container.component.js index 50429fcd846b..f71fb089d3b3 100644 --- a/ui/components/app/permission-page-container/permission-page-container.component.js +++ b/ui/components/app/permission-page-container/permission-page-container.component.js @@ -1,22 +1,19 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; -///: BEGIN:ONLY_INCLUDE_IF(snaps) import { SnapCaveatType, WALLET_SNAP_PERMISSION_KEY, } from '@metamask/snaps-rpc-methods'; -///: END:ONLY_INCLUDE_IF import { SubjectType } from '@metamask/permission-controller'; import { MetaMetricsEventCategory } from '../../../../shared/constants/metametrics'; import { PageContainerFooter } from '../../ui/page-container'; import PermissionsConnectFooter from '../permissions-connect-footer'; -///: BEGIN:ONLY_INCLUDE_IF(snaps) import { RestrictedMethods } from '../../../../shared/constants/permissions'; import { PermissionNames } from '../../../../app/scripts/controllers/permissions'; import SnapPrivacyWarning from '../snaps/snap-privacy-warning'; import { getDedupedSnaps } from '../../../helpers/utils/util'; -///: END:ONLY_INCLUDE_IF +import { containsEthPermissionsAndNonEvmAccount } from '../../../helpers/utils/permissions'; import { BackgroundColor, Display, @@ -31,11 +28,9 @@ export default class PermissionPageContainer extends Component { rejectPermissionsRequest: PropTypes.func.isRequired, selectedAccounts: PropTypes.array, allAccountsSelected: PropTypes.bool, - ///: BEGIN:ONLY_INCLUDE_IF(snaps) currentPermissions: PropTypes.object, snapsInstallPrivacyWarningShown: PropTypes.bool.isRequired, setSnapsInstallPrivacyWarningShownStatus: PropTypes.func, - ///: END:ONLY_INCLUDE_IF request: PropTypes.object, requestMetadata: PropTypes.object, targetSubjectMetadata: PropTypes.shape({ @@ -54,9 +49,7 @@ export default class PermissionPageContainer extends Component { requestMetadata: {}, selectedAccounts: [], allAccountsSelected: false, - ///: BEGIN:ONLY_INCLUDE_IF(snaps) currentPermissions: {}, - ///: END:ONLY_INCLUDE_IF }; static contextTypes = { @@ -69,12 +62,10 @@ export default class PermissionPageContainer extends Component { getRequestedPermissions() { return Object.entries(this.props.request.permissions ?? {}).reduce( (acc, [permissionName, permissionValue]) => { - ///: BEGIN:ONLY_INCLUDE_IF(snaps) if (permissionName === RestrictedMethods.wallet_snap) { acc[permissionName] = this.getDedupedSnapPermissions(); return acc; } - ///: END:ONLY_INCLUDE_IF acc[permissionName] = permissionValue; return acc; }, @@ -82,7 +73,6 @@ export default class PermissionPageContainer extends Component { ); } - ///: BEGIN:ONLY_INCLUDE_IF(snaps) getDedupedSnapPermissions() { const { request, currentPermissions } = this.props; const snapKeys = getDedupedSnaps(request, currentPermissions); @@ -106,7 +96,6 @@ export default class PermissionPageContainer extends Component { isShowingSnapsPrivacyWarning: true, }); } - ///: END:ONLY_INCLUDE_IF componentDidMount() { this.context.trackEvent({ @@ -118,13 +107,11 @@ export default class PermissionPageContainer extends Component { }, }); - ///: BEGIN:ONLY_INCLUDE_IF(snaps) if (this.props.request.permissions[WALLET_SNAP_PERMISSION_KEY]) { if (this.props.snapsInstallPrivacyWarningShown === false) { this.showSnapsPrivacyWarning(); } } - ///: END:ONLY_INCLUDE_IF } goBack() { @@ -186,7 +173,6 @@ export default class PermissionPageContainer extends Component { const requestedPermissions = this.getRequestedPermissions(); - ///: BEGIN:ONLY_INCLUDE_IF(snaps) const setIsShowingSnapsPrivacyWarning = (value) => { this.setState({ isShowingSnapsPrivacyWarning: value, @@ -197,7 +183,6 @@ export default class PermissionPageContainer extends Component { setIsShowingSnapsPrivacyWarning(false); this.props.setSnapsInstallPrivacyWarningShownStatus(true); }; - ///: END:ONLY_INCLUDE_IF const footerLeftActionText = requestedPermissions[ PermissionNames.permittedChains @@ -207,18 +192,12 @@ export default class PermissionPageContainer extends Component { return ( <> - { - ///: BEGIN:ONLY_INCLUDE_IF(snaps) - <> - {this.state.isShowingSnapsPrivacyWarning && ( - confirmSnapsPrivacyWarning()} - onCanceled={() => this.onCancel()} - /> - )} - - ///: END:ONLY_INCLUDE_IF - } + {this.state.isShowingSnapsPrivacyWarning && ( + confirmSnapsPrivacyWarning()} + onCanceled={() => this.onCancel()} + /> + )} this.onSubmit()} submitText={this.context.t('confirm')} buttonSizeLarge={false} + disabled={containsEthPermissionsAndNonEvmAccount( + selectedAccounts, + requestedPermissions, + )} /> diff --git a/ui/components/app/permission-page-container/permission-page-container.container.js b/ui/components/app/permission-page-container/permission-page-container.container.js index db313ef363d0..de131fc13734 100644 --- a/ui/components/app/permission-page-container/permission-page-container.container.js +++ b/ui/components/app/permission-page-container/permission-page-container.container.js @@ -1,20 +1,14 @@ import { connect } from 'react-redux'; -import { - getInternalAccounts, - ///: BEGIN:ONLY_INCLUDE_IF(snaps) - getPermissions, - ///: END:ONLY_INCLUDE_IF -} from '../../../selectors'; +import { getInternalAccounts, getPermissions } from '../../../selectors'; import PermissionPageContainer from './permission-page-container.component'; const mapStateToProps = (state, ownProps) => { const { selectedAccounts } = ownProps; - ///: BEGIN:ONLY_INCLUDE_IF(snaps) const currentPermissions = getPermissions( state, ownProps.request.metadata?.origin, ); - ///: END:ONLY_INCLUDE_IF + const allInternalAccounts = getInternalAccounts(state); const allInternalAccountsSelected = Object.keys(selectedAccounts).length === @@ -22,9 +16,7 @@ const mapStateToProps = (state, ownProps) => { return { allInternalAccountsSelected, - ///: BEGIN:ONLY_INCLUDE_IF(snaps) currentPermissions, - ///: END:ONLY_INCLUDE_IF }; }; diff --git a/ui/components/app/permissions-connect-header/permissions-connect-header.component.js b/ui/components/app/permissions-connect-header/permissions-connect-header.component.js index c8f4b4b92638..5a333df926c3 100644 --- a/ui/components/app/permissions-connect-header/permissions-connect-header.component.js +++ b/ui/components/app/permissions-connect-header/permissions-connect-header.component.js @@ -1,9 +1,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import classnames from 'classnames'; -///: BEGIN:ONLY_INCLUDE_IF(snaps) import { SubjectType } from '@metamask/permission-controller'; -///: END:ONLY_INCLUDE_IF import SiteOrigin from '../../ui/site-origin'; import Box from '../../ui/box'; import { @@ -33,23 +31,12 @@ export default class PermissionsConnectHeader extends Component { }; renderHeaderIcon() { - const { - iconUrl, - iconName, - siteOrigin, - leftIcon, - rightIcon, - ///: BEGIN:ONLY_INCLUDE_IF(snaps) - subjectType, - ///: END:ONLY_INCLUDE_IF - } = this.props; - - ///: BEGIN:ONLY_INCLUDE_IF(snaps) + const { iconUrl, iconName, siteOrigin, leftIcon, rightIcon, subjectType } = + this.props; if (subjectType === SubjectType.Snap) { return null; } - ///: END:ONLY_INCLUDE_IF return (
diff --git a/ui/components/app/snaps/snap-ui-button/snap-ui-button.tsx b/ui/components/app/snaps/snap-ui-button/snap-ui-button.tsx index 5c6735d6d16a..998a7ca2d782 100644 --- a/ui/components/app/snaps/snap-ui-button/snap-ui-button.tsx +++ b/ui/components/app/snaps/snap-ui-button/snap-ui-button.tsx @@ -36,7 +36,10 @@ export const SnapUIButton: FunctionComponent< event.preventDefault(); } - handleEvent({ event: UserInputEventType.ButtonClickEvent, name }); + handleEvent({ + event: UserInputEventType.ButtonClickEvent, + name, + }); }; const overriddenVariant = disabled ? 'disabled' : variant; diff --git a/ui/components/app/snaps/snap-ui-checkbox/index.ts b/ui/components/app/snaps/snap-ui-checkbox/index.ts new file mode 100644 index 000000000000..e75fe2634f38 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-checkbox/index.ts @@ -0,0 +1 @@ +export * from './snap-ui-checkbox'; diff --git a/ui/components/app/snaps/snap-ui-checkbox/snap-ui-checkbox.tsx b/ui/components/app/snaps/snap-ui-checkbox/snap-ui-checkbox.tsx new file mode 100644 index 000000000000..39df1c0ff8f7 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-checkbox/snap-ui-checkbox.tsx @@ -0,0 +1,81 @@ +import React, { FunctionComponent, useEffect, useState } from 'react'; +import { useSnapInterfaceContext } from '../../../../contexts/snaps'; +import { + Display, + FlexDirection, +} from '../../../../helpers/constants/design-system'; +import { + Box, + Label, + HelpText, + HelpTextSeverity, + Checkbox, +} from '../../../component-library'; +import ToggleButton from '../../../ui/toggle-button'; + +export type SnapUICheckboxProps = { + name: string; + fieldLabel?: string; + variant?: 'default' | 'toggle'; + label?: string; + error?: string; + form?: string; +}; + +export const SnapUICheckbox: FunctionComponent = ({ + name, + variant, + fieldLabel, + label, + error, + form, + ...props +}) => { + const { handleInputChange, getValue } = useSnapInterfaceContext(); + + const initialValue = getValue(name, form); + + const [value, setValue] = useState(initialValue ?? false); + + useEffect(() => { + if (initialValue !== undefined && initialValue !== null) { + setValue(initialValue); + } + }, [initialValue]); + + const handleChange = () => { + setValue(!value); + handleInputChange(name, !value, form); + }; + + return ( + + {fieldLabel && } + {variant === 'toggle' ? ( + + ) : ( + + )} + {error && ( + + {error} + + )} + + ); +}; diff --git a/ui/components/app/snaps/snap-ui-dropdown/snap-ui-dropdown.tsx b/ui/components/app/snaps/snap-ui-dropdown/snap-ui-dropdown.tsx index b8e6187c7e83..2e70f2809399 100644 --- a/ui/components/app/snaps/snap-ui-dropdown/snap-ui-dropdown.tsx +++ b/ui/components/app/snaps/snap-ui-dropdown/snap-ui-dropdown.tsx @@ -29,7 +29,7 @@ export const SnapUIDropdown: FunctionComponent = ({ }) => { const { handleInputChange, getValue } = useSnapInterfaceContext(); - const initialValue = getValue(name, form); + const initialValue = getValue(name, form); const [value, setValue] = useState(initialValue ?? ''); @@ -51,7 +51,12 @@ export const SnapUIDropdown: FunctionComponent = ({ flexDirection={FlexDirection.Column} > {label && } - + {error && ( {error} diff --git a/ui/components/app/snaps/snap-ui-file-input/index.scss b/ui/components/app/snaps/snap-ui-file-input/index.scss new file mode 100644 index 000000000000..787eb6150728 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-file-input/index.scss @@ -0,0 +1,21 @@ +.snap-ui-renderer { + &__file-input { + &__drop-zone { + background-color: var(--color-background-alternative); + + .mm-icon, + .mm-text { + color: var(--color-icon-alternative); + } + + &:hover .mm-icon, + &:hover .mm-text { + color: var(--color-info-default); + } + + &:hover { + background-color: var(--color-background-alternative-hover); + } + } + } +} diff --git a/ui/components/app/snaps/snap-ui-file-input/index.ts b/ui/components/app/snaps/snap-ui-file-input/index.ts new file mode 100644 index 000000000000..fa2b7b5ad2a1 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-file-input/index.ts @@ -0,0 +1 @@ +export * from './snap-ui-file-input'; diff --git a/ui/components/app/snaps/snap-ui-file-input/snap-ui-file-input.tsx b/ui/components/app/snaps/snap-ui-file-input/snap-ui-file-input.tsx new file mode 100644 index 000000000000..9508dae3e0d3 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-file-input/snap-ui-file-input.tsx @@ -0,0 +1,212 @@ +import React, { + ChangeEvent, + // eslint-disable-next-line @typescript-eslint/no-shadow + DragEvent, + FunctionComponent, + useRef, + useState, +} from 'react'; +import classnames from 'classnames'; +import { useSnapInterfaceContext } from '../../../../contexts/snaps'; +import { + Box, + ButtonIcon, + ButtonIconSize, + HelpText, + HelpTextSeverity, + Icon, + IconName, + IconSize, + Label, + Text, +} from '../../../component-library'; +import { + AlignItems, + BackgroundColor, + BorderColor, + BorderRadius, + BorderStyle, + Display, + FlexDirection, + IconColor, + JustifyContent, + TextAlign, +} from '../../../../helpers/constants/design-system'; +import { useI18nContext } from '../../../../hooks/useI18nContext'; + +export type SnapUIFileInputProps = { + name: string; + label?: string; + form?: string; + accept?: string[]; + compact?: boolean; + error?: boolean; + helpText?: string; +}; + +/** + * A file input component, which is used to create a file input field for Snaps + * user interfaces. + * + * @param props - The props of the component. + * @param props.name - The name of the file input. This is used to identify the + * file input field in the form data. + * @param props.label - The label of the file input, which is displayed above + * the file input field. + * @param props.form - The name of the form that the file input belongs to. This + * is used to group the file input field with other form fields. + * @param props.accept - The types of files that the file input can accept. This + * is used to filter the files that the user can select when the input field is + * clicked. + * @param props.compact - Whether the file input should be displayed in a + * compact mode. In compact mode, the file input is displayed as a button with + * an icon. + * @param props.error - Whether the file input has an error. If the file input + * has an error, the help text is displayed in red. + * @param props.helpText - The help text of the file input, which is displayed + * below the file input field. + * @returns A file input element. + */ +export const SnapUIFileInput: FunctionComponent = ({ + name, + label, + form, + accept, + compact, + error, + helpText, +}) => { + const t = useI18nContext(); + const { handleFileChange } = useSnapInterfaceContext(); + const ref = useRef(null); + const [active, setActive] = useState(false); + + const handleClick = () => { + ref.current?.click(); + }; + + const handleChange = (event: ChangeEvent) => { + const file = event.target.files?.[0] ?? null; + handleFileChange(name, file, form); + }; + + const handleDragOver = (event: DragEvent) => { + event.preventDefault(); + setActive(true); + }; + + const handleDragLeave = (event: DragEvent) => { + event.preventDefault(); + setActive(false); + }; + + const handleDrop = (event: DragEvent) => { + event.preventDefault(); + setActive(false); + + const file = event.dataTransfer?.files?.[0] ?? null; + handleFileChange(name, file, form); + }; + + const header = ( + <> + {label && ( + + )} + + + ); + + const footer = ( + <> + {helpText && ( + + {helpText} + + )} + + ); + + if (compact) { + return ( + + {header} + + {footer} + + ); + } + + return ( + + {header} + + + + {t('uploadDropFile')} + + + {footer} + + ); +}; diff --git a/ui/components/app/snaps/snap-ui-form/snap-ui-form.tsx b/ui/components/app/snaps/snap-ui-form/snap-ui-form.tsx index 4dfe245b854f..6f265bc71710 100644 --- a/ui/components/app/snaps/snap-ui-form/snap-ui-form.tsx +++ b/ui/components/app/snaps/snap-ui-form/snap-ui-form.tsx @@ -19,7 +19,10 @@ export const SnapUIForm: FunctionComponent = ({ const handleSubmit = (event: FormEvent) => { event.preventDefault(); - handleEvent({ event: UserInputEventType.FormSubmitEvent, name }); + handleEvent({ + event: UserInputEventType.FormSubmitEvent, + name, + }); }; return ( diff --git a/ui/components/app/snaps/snap-ui-input/snap-ui-input.tsx b/ui/components/app/snaps/snap-ui-input/snap-ui-input.tsx index 2374e00f3927..43c612517139 100644 --- a/ui/components/app/snaps/snap-ui-input/snap-ui-input.tsx +++ b/ui/components/app/snaps/snap-ui-input/snap-ui-input.tsx @@ -17,7 +17,7 @@ export const SnapUIInput: FunctionComponent< > = ({ name, form, ...props }) => { const { handleInputChange, getValue } = useSnapInterfaceContext(); - const initialValue = getValue(name, form); + const initialValue = getValue(name, form); const [value, setValue] = useState(initialValue ?? ''); diff --git a/ui/components/app/snaps/snap-ui-renderer/components/checkbox.ts b/ui/components/app/snaps/snap-ui-renderer/components/checkbox.ts new file mode 100644 index 000000000000..862ca5203950 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-renderer/components/checkbox.ts @@ -0,0 +1,16 @@ +import { CheckboxElement } from '@metamask/snaps-sdk/jsx'; + +import { UIComponentFactory } from './types'; + +export const checkbox: UIComponentFactory = ({ + element, + form, +}) => ({ + element: 'SnapUICheckbox', + props: { + name: element.props.name, + label: element.props.label, + variant: element.props.variant, + form, + }, +}); diff --git a/ui/components/app/snaps/snap-ui-renderer/components/field.ts b/ui/components/app/snaps/snap-ui-renderer/components/field.ts index 193f618346ca..eb5b0b0e2256 100644 --- a/ui/components/app/snaps/snap-ui-renderer/components/field.ts +++ b/ui/components/app/snaps/snap-ui-renderer/components/field.ts @@ -4,10 +4,12 @@ import { ButtonElement, JSXElement, DropdownElement, + CheckboxElement, } from '@metamask/snaps-sdk/jsx'; import { getJsxChildren } from '@metamask/snaps-utils'; import { button as buttonFn } from './button'; import { dropdown as dropdownFn } from './dropdown'; +import { checkbox as checkboxFn } from './checkbox'; import { UIComponentFactory, UIComponentParams } from './types'; export const field: UIComponentFactory = ({ element, form }) => { @@ -16,12 +18,28 @@ export const field: UIComponentFactory = ({ element, form }) => { const child = children[0] as JSXElement; switch (child.type) { + case 'FileInput': { + return { + element: 'SnapUIFileInput', + props: { + name: child.props.name, + accept: child.props.accept, + compact: child.props.compact, + label: element.props.label, + form, + error: element.props.error !== undefined, + helpText: element.props.error, + }, + }; + } + case 'Input': { const input = child as InputElement; const button = children[1] as ButtonElement; const buttonMapped = button && buttonFn({ element: button } as UIComponentParams); + return { element: 'SnapUIInput', props: { @@ -66,6 +84,22 @@ export const field: UIComponentFactory = ({ element, form }) => { }; } + case 'Checkbox': { + const checkbox = child as CheckboxElement; + const checkboxMapped = checkboxFn({ + element: checkbox, + } as UIComponentParams); + return { + element: 'SnapUICheckbox', + props: { + ...checkboxMapped.props, + fieldLabel: element.props.label, + form, + error: element.props.error, + }, + }; + } + default: throw new Error(`Invalid Field child: ${child.type}`); } diff --git a/ui/components/app/snaps/snap-ui-renderer/components/file-input.ts b/ui/components/app/snaps/snap-ui-renderer/components/file-input.ts new file mode 100644 index 000000000000..edeba6d89551 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-renderer/components/file-input.ts @@ -0,0 +1,19 @@ +import { FileInputElement } from '@metamask/snaps-sdk/jsx'; + +import { UIComponentFactory } from './types'; + +export const fileInput: UIComponentFactory = ({ + element, + form, +}) => ({ + element: 'SnapUIInput', + props: { + element: 'SnapUIFileInput', + props: { + name: element.props.name, + accept: element.props.accept, + compact: element.props.compact, + form, + }, + }, +}); diff --git a/ui/components/app/snaps/snap-ui-renderer/components/index.ts b/ui/components/app/snaps/snap-ui-renderer/components/index.ts index 9aa6796de1a3..eed08c75fb91 100644 --- a/ui/components/app/snaps/snap-ui-renderer/components/index.ts +++ b/ui/components/app/snaps/snap-ui-renderer/components/index.ts @@ -8,6 +8,7 @@ import { row } from './row'; import { address } from './address'; import { copyable } from './copyable'; import { button } from './button'; +import { fileInput } from './file-input'; import { form } from './form'; import { input } from './input'; import { bold } from './bold'; @@ -16,6 +17,8 @@ import { link } from './link'; import { field } from './field'; import { dropdown } from './dropdown'; import { value } from './value'; +import { checkbox } from './checkbox'; +import { tooltip } from './tooltip'; export const COMPONENT_MAPPING = { Box: box, @@ -28,6 +31,7 @@ export const COMPONENT_MAPPING = { Row: row, Address: address, Button: button, + FileInput: fileInput, Form: form, Input: input, Bold: bold, @@ -36,4 +40,6 @@ export const COMPONENT_MAPPING = { Field: field, Dropdown: dropdown, Value: value, + Checkbox: checkbox, + Tooltip: tooltip, }; diff --git a/ui/components/app/snaps/snap-ui-renderer/components/row.ts b/ui/components/app/snaps/snap-ui-renderer/components/row.ts index 153da8fc8148..b57425250c7a 100644 --- a/ui/components/app/snaps/snap-ui-renderer/components/row.ts +++ b/ui/components/app/snaps/snap-ui-renderer/components/row.ts @@ -9,6 +9,7 @@ export const row: UIComponentFactory = ({ element, ...params }) => ({ props: { label: element.props.label, variant: element.props.variant, + tooltip: element.props.tooltip, style: { // We do this to cause an overhang with certain confirmation row variants marginLeft: '-8px', diff --git a/ui/components/app/snaps/snap-ui-renderer/components/text.ts b/ui/components/app/snaps/snap-ui-renderer/components/text.ts index 7ed7d9be1236..9502df1aabaa 100644 --- a/ui/components/app/snaps/snap-ui-renderer/components/text.ts +++ b/ui/components/app/snaps/snap-ui-renderer/components/text.ts @@ -23,5 +23,6 @@ export const text: UIComponentFactory = ({ overflowWrap: OverflowWrap.Anywhere, color: TextColor.inherit, className: 'snap-ui-renderer__text', + textAlign: element.props.alignment, }, }); diff --git a/ui/components/app/snaps/snap-ui-renderer/components/tooltip.ts b/ui/components/app/snaps/snap-ui-renderer/components/tooltip.ts new file mode 100644 index 000000000000..facf3dcd7168 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-renderer/components/tooltip.ts @@ -0,0 +1,23 @@ +import { JSXElement, Text, TooltipElement } from '@metamask/snaps-sdk/jsx'; +import { getJsxChildren } from '@metamask/snaps-utils'; +import { mapToTemplate } from '../utils'; +import { UIComponentFactory } from './types'; + +export const tooltip: UIComponentFactory = ({ + element, + ...params +}) => ({ + element: 'SnapUITooltip', + children: getJsxChildren(element).map((children) => + mapToTemplate({ element: children as JSXElement, ...params }), + ), + propComponents: { + content: mapToTemplate({ + element: + typeof element.props.content === 'string' + ? Text({ children: element.props.content }) + : element.props.content, + ...params, + }), + }, +}); diff --git a/ui/components/app/snaps/snap-ui-renderer/components/types.ts b/ui/components/app/snaps/snap-ui-renderer/components/types.ts index ab1048d0b3a7..4e4824f686e1 100644 --- a/ui/components/app/snaps/snap-ui-renderer/components/types.ts +++ b/ui/components/app/snaps/snap-ui-renderer/components/types.ts @@ -1,4 +1,4 @@ -import { JSXElement, MaybeArray } from '@metamask/snaps-sdk/jsx'; +import { JSXElement, SnapsChildren } from '@metamask/snaps-sdk/jsx'; export type UIComponentParams = { map: Record; @@ -9,7 +9,7 @@ export type UIComponentParams = { export type UIComponent = { element: string; props?: Record; - children?: MaybeArray; + children?: SnapsChildren; key?: string; }; diff --git a/ui/components/app/snaps/snap-ui-renderer/snap-ui-renderer.js b/ui/components/app/snaps/snap-ui-renderer/snap-ui-renderer.js index 4b5f19e0a014..6a812917306b 100644 --- a/ui/components/app/snaps/snap-ui-renderer/snap-ui-renderer.js +++ b/ui/components/app/snaps/snap-ui-renderer/snap-ui-renderer.js @@ -5,10 +5,7 @@ import { useSelector } from 'react-redux'; import { isEqual } from 'lodash'; import MetaMaskTemplateRenderer from '../../metamask-template-renderer/metamask-template-renderer'; import { SnapDelineator } from '../snap-delineator'; -import { - getSnapMetadata, - getMemoizedInterfaceContent, -} from '../../../../selectors'; +import { getSnapMetadata, getMemoizedInterface } from '../../../../selectors'; import { Box, FormTextField } from '../../../component-library'; import { DelineatorType } from '../../../../helpers/constants/snaps'; @@ -35,10 +32,15 @@ const SnapUIRendererComponent = ({ getSnapMetadata(state, snapId), ); - const content = useSelector((state) => - getMemoizedInterfaceContent(state, interfaceId), + const interfaceState = useSelector( + (state) => getMemoizedInterface(state, interfaceId), + // We only want to update the state if the content has changed. + // We do this to avoid useless re-renders. + (oldState, newState) => isEqual(oldState.content, newState.content), ); + const content = interfaceState?.content; + // sections are memoized to avoid useless re-renders if one of the parents element re-renders. const sections = useMemo( () => @@ -64,6 +66,8 @@ const SnapUIRendererComponent = ({ ); } + const { state: initialState, context } = interfaceState; + return ( - + {isPrompt && ( diff --git a/ui/components/app/snaps/snap-ui-tooltip/index.ts b/ui/components/app/snaps/snap-ui-tooltip/index.ts new file mode 100644 index 000000000000..8a97b4d74e26 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-tooltip/index.ts @@ -0,0 +1 @@ +export * from './snap-ui-tooltip'; diff --git a/ui/components/app/snaps/snap-ui-tooltip/snap-ui-tooltip.tsx b/ui/components/app/snaps/snap-ui-tooltip/snap-ui-tooltip.tsx new file mode 100644 index 000000000000..953ee50c87b5 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-tooltip/snap-ui-tooltip.tsx @@ -0,0 +1,22 @@ +import React, { FunctionComponent, ReactNode } from 'react'; +import Tooltip from '../../../ui/tooltip'; + +export type SnapUITooltipProps = { + content: ReactNode; +}; + +export const SnapUITooltip: FunctionComponent = ({ + content, + children, +}) => { + return ( + + {children} + + ); +}; diff --git a/ui/components/app/token-cell/__snapshots__/token-cell.test.js.snap b/ui/components/app/token-cell/__snapshots__/token-cell.test.js.snap index 2c3664ff5f4f..139f516bdc95 100644 --- a/ui/components/app/token-cell/__snapshots__/token-cell.test.js.snap +++ b/ui/components/app/token-cell/__snapshots__/token-cell.test.js.snap @@ -53,12 +53,14 @@ exports[`Token Cell should match snapshot 1`] = ` > TEST -

- TEST -

+

+

- 5.000 + 5 TEST

diff --git a/ui/components/app/token-cell/token-cell.js b/ui/components/app/token-cell/token-cell.js index 0052096254be..e58fda03918a 100644 --- a/ui/components/app/token-cell/token-cell.js +++ b/ui/components/app/token-cell/token-cell.js @@ -6,6 +6,7 @@ import { useTokenFiatAmount } from '../../../hooks/useTokenFiatAmount'; import { TokenListItem } from '../../multichain'; import { isEqualCaseInsensitive } from '../../../../shared/modules/string-utils'; import { useIsOriginalTokenSymbol } from '../../../hooks/useIsOriginalTokenSymbol'; +import { getIntlLocale } from '../../../ducks/locale/locale'; export default function TokenCell({ address, image, symbol, string, onClick }) { const tokenList = useSelector(getTokenList); @@ -17,6 +18,11 @@ export default function TokenCell({ address, image, symbol, string, onClick }) { const title = tokenData?.name || symbol; const tokenImage = tokenData?.iconUrl || image; const formattedFiat = useTokenFiatAmount(address, string, symbol); + const locale = useSelector(getIntlLocale); + const primary = new Intl.NumberFormat(locale, { + minimumSignificantDigits: 1, + }).format(string.toString()); + const isOriginalTokenSymbol = useIsOriginalTokenSymbol(address, symbol); return ( @@ -24,7 +30,7 @@ export default function TokenCell({ address, image, symbol, string, onClick }) { onClick={onClick ? () => onClick(address) : undefined} tokenSymbol={symbol} tokenImage={tokenImage} - primary={`${string || 0}`} + primary={`${primary || 0}`} secondary={isOriginalTokenSymbol ? formattedFiat : null} title={title} isOriginalTokenSymbol={isOriginalTokenSymbol} diff --git a/ui/components/app/token-cell/token-cell.test.js b/ui/components/app/token-cell/token-cell.test.js index 8312000d3ed2..731361de7df6 100644 --- a/ui/components/app/token-cell/token-cell.test.js +++ b/ui/components/app/token-cell/token-cell.test.js @@ -86,6 +86,13 @@ describe('Token Cell', () => { onClick: jest.fn(), }; + const propsLargeAmount = { + address: '0xAnotherToken', + symbol: 'TEST', + string: '5000000', + currentCurrency: 'usd', + onClick: jest.fn(), + }; useSelector.mockReturnValue(MOCK_GET_TOKEN_LIST); useTokenFiatAmount.mockReturnValue('5.00'); @@ -121,4 +128,16 @@ describe('Token Cell', () => { expect(image).toBeInTheDocument(); expect(image).toHaveAttribute('src', './images/test_image.svg'); }); + + it('should render amount with the correct format', () => { + const { getByTestId } = renderWithProvider( + , + mockStore, + ); + + const amountElement = getByTestId('multichain-token-list-item-value'); + + expect(amountElement).toBeInTheDocument(); + expect(amountElement.textContent).toBe('5,000,000 TEST'); + }); }); diff --git a/ui/components/app/transaction-breakdown/transaction-breakdown.test.js b/ui/components/app/transaction-breakdown/transaction-breakdown.test.js index 5b8c5dac80d0..fcc6ad6e05e6 100644 --- a/ui/components/app/transaction-breakdown/transaction-breakdown.test.js +++ b/ui/components/app/transaction-breakdown/transaction-breakdown.test.js @@ -2,6 +2,7 @@ import React from 'react'; import configureMockStore from 'redux-mock-store'; import { within } from '@testing-library/react'; import { renderWithProvider } from '../../../../test/jest/rendering'; +import mockState from '../../../../test/data/mock-state.json'; import { MAINNET_DISPLAY_NAME, NETWORK_TYPES, @@ -31,6 +32,8 @@ describe('TransactionBreakdown', () => { nickname: MAINNET_DISPLAY_NAME, type: NETWORK_TYPES.MAINNET, }, + internalAccounts: mockState.metamask.internalAccounts, + completedOnboarding: true, }, }); diff --git a/ui/components/app/transaction-list/transaction-list.component.js b/ui/components/app/transaction-list/transaction-list.component.js index 1173593eb4c3..b47130c5d739 100644 --- a/ui/components/app/transaction-list/transaction-list.component.js +++ b/ui/components/app/transaction-list/transaction-list.component.js @@ -130,14 +130,14 @@ export default function TransactionList({ nonceSortedCompletedTransactionsSelector, ); const chainId = useSelector(getCurrentChainId); - const { address: selectedAddress } = useSelector(getSelectedAccount); + const selectedAccount = useSelector(getSelectedAccount); ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) const shouldHideZeroBalanceTokens = useSelector( getShouldHideZeroBalanceTokens, ); const { totalFiatBalance } = useAccountTotalFiatBalance( - selectedAddress, + selectedAccount, shouldHideZeroBalanceTokens, ); const balanceIsZero = Number(totalFiatBalance) === 0; @@ -206,7 +206,8 @@ export default function TransactionList({ const removeIncomingTxsButToAnotherAddress = (dateGroup) => { const isIncomingTxsButToAnotherAddress = (transaction) => transaction.type === TransactionType.incoming && - transaction.txParams.to.toLowerCase() !== selectedAddress.toLowerCase(); + transaction.txParams.to.toLowerCase() !== + selectedAccount.address.toLowerCase(); dateGroup.transactionGroups = dateGroup.transactionGroups.map( (transactionGroup) => { diff --git a/ui/components/app/user-preferenced-currency-display/user-preferenced-currency-display.component.js b/ui/components/app/user-preferenced-currency-display/user-preferenced-currency-display.component.js index bfe1e8e09033..f8c63b6b043a 100644 --- a/ui/components/app/user-preferenced-currency-display/user-preferenced-currency-display.component.js +++ b/ui/components/app/user-preferenced-currency-display/user-preferenced-currency-display.component.js @@ -1,19 +1,22 @@ import React, { useMemo } from 'react'; import PropTypes from 'prop-types'; -import { useSelector } from 'react-redux'; import { EtherDenomination } from '../../../../shared/constants/common'; import { PRIMARY, SECONDARY } from '../../../helpers/constants/common'; import CurrencyDisplay from '../../ui/currency-display'; import { useUserPreferencedCurrency } from '../../../hooks/useUserPreferencedCurrency'; import { AvatarNetwork, AvatarNetworkSize } from '../../component-library'; -import { getCurrentNetwork } from '../../../selectors'; -import { getNativeCurrency } from '../../../ducks/metamask/metamask'; +import { + getMultichainNativeCurrency, + getMultichainCurrentNetwork, +} from '../../../selectors/multichain'; +import { useMultichainSelector } from '../../../hooks/useMultichainSelector'; /* eslint-disable jsdoc/require-param-name */ // eslint-disable-next-line jsdoc/require-param /** @param {PropTypes.InferProps>} */ export default function UserPreferencedCurrencyDisplay({ 'data-testid': dataTestId, + account, ethNumberOfDecimals, fiatNumberOfDecimals, numberOfDecimals: propsNumberOfDecimals, @@ -24,9 +27,16 @@ export default function UserPreferencedCurrencyDisplay({ showCurrencySuffix, ...restProps }) { - const currentNetwork = useSelector(getCurrentNetwork); - const nativeCurrency = useSelector(getNativeCurrency); + const currentNetwork = useMultichainSelector( + getMultichainCurrentNetwork, + account, + ); + const nativeCurrency = useMultichainSelector( + getMultichainNativeCurrency, + account, + ); const { currency, numberOfDecimals } = useUserPreferencedCurrency(type, { + account, ethNumberOfDecimals, fiatNumberOfDecimals, numberOfDecimals: propsNumberOfDecimals, @@ -54,6 +64,7 @@ export default function UserPreferencedCurrencyDisplay({ return ( { describe('rendering', () => { - const mockState = { + const defaultState = { metamask: { + ...mockState.metamask, providerConfig: { chainId: CHAIN_IDS.MAINNET, nickname: MAINNET_DISPLAY_NAME, @@ -23,7 +25,7 @@ describe('UserPreferencedCurrencyDisplay Component', () => { }, }, }; - const mockStore = configureMockStore()(mockState); + const mockStore = configureMockStore()(defaultState); it('should match snapshot', () => { const { container } = renderWithProvider( , diff --git a/ui/components/app/wallet-overview/coin-overview.tsx b/ui/components/app/wallet-overview/coin-overview.tsx index 2bba21cb06eb..abf1f6ca7158 100644 --- a/ui/components/app/wallet-overview/coin-overview.tsx +++ b/ui/components/app/wallet-overview/coin-overview.tsx @@ -1,6 +1,8 @@ import React, { useContext } from 'react'; import { useSelector } from 'react-redux'; import classnames from 'classnames'; +import { zeroAddress } from 'ethereumjs-util'; + ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) import { CaipChainId } from '@metamask/utils'; ///: END:ONLY_INCLUDE_IF @@ -11,6 +13,7 @@ import { PRIMARY, SECONDARY } from '../../../helpers/constants/common'; import { getShouldShowFiat, getPreferences, + getTokensMarketData, ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) SwapsEthToken, ///: END:ONLY_INCLUDE_IF @@ -19,6 +22,7 @@ import Spinner from '../../ui/spinner'; import { useIsOriginalNativeTokenSymbol } from '../../../hooks/useIsOriginalNativeTokenSymbol'; import { getProviderConfig } from '../../../ducks/metamask/metamask'; import { showPrimaryCurrency } from '../../../../shared/modules/currency-display.utils'; +import { PercentageAndAmountChange } from '../../multichain/token-list-item/price/percentage-and-amount-change/percentage-and-amount-change'; import WalletOverview from './wallet-overview'; import CoinButtons from './coin-buttons'; @@ -70,6 +74,7 @@ export const CoinOverview = ({ type, rpcUrl, ); + const tokensMarketData = useSelector(getTokensMarketData); return ( )} +
} diff --git a/ui/components/app/wallet-overview/eth-overview.test.js b/ui/components/app/wallet-overview/eth-overview.test.js index d5117cc845a6..0d13bff7e1ef 100644 --- a/ui/components/app/wallet-overview/eth-overview.test.js +++ b/ui/components/app/wallet-overview/eth-overview.test.js @@ -13,6 +13,7 @@ import { renderWithProvider } from '../../../../test/jest/rendering'; import { KeyringType } from '../../../../shared/constants/keyring'; import { useIsOriginalNativeTokenSymbol } from '../../../hooks/useIsOriginalNativeTokenSymbol'; import { ETH_EOA_METHODS } from '../../../../shared/constants/eth-methods'; +import { getIntlLocale } from '../../../ducks/locale/locale'; import EthOverview from './eth-overview'; // Mock BUYABLE_CHAINS_MAP @@ -38,10 +39,17 @@ jest.mock('../../../hooks/useIsOriginalNativeTokenSymbol', () => { }; }); +jest.mock('../../../ducks/locale/locale', () => ({ + getIntlLocale: jest.fn(), +})); + +const mockGetIntlLocale = getIntlLocale; + let openTabSpy; describe('EthOverview', () => { useIsOriginalNativeTokenSymbol.mockReturnValue(true); + mockGetIntlLocale.mockReturnValue('en-US'); const mockStore = { metamask: { diff --git a/ui/components/app/whats-new-popup/whats-new-popup.js b/ui/components/app/whats-new-popup/whats-new-popup.js index a56ad75fe7fd..9a316af62889 100644 --- a/ui/components/app/whats-new-popup/whats-new-popup.js +++ b/ui/components/app/whats-new-popup/whats-new-popup.js @@ -17,9 +17,7 @@ import { MetaMetricsContext } from '../../../contexts/metametrics'; import { getCurrentLocale } from '../../../ducks/locale/locale'; import { TextVariant } from '../../../helpers/constants/design-system'; import { useEqualityCheck } from '../../../hooks/useEqualityCheck'; -///: BEGIN:ONLY_INCLUDE_IF(blockaid) import { useTheme } from '../../../hooks/useTheme'; -///: END:ONLY_INCLUDE_IF import { getSortedAnnouncementsToShow } from '../../../selectors'; import { updateViewedNotifications } from '../../../store/actions'; import { ButtonPrimary, Text } from '../../component-library'; @@ -165,10 +163,7 @@ export default function WhatsNewPopup({ onClose }) { const notifications = useSelector(getSortedAnnouncementsToShow); const locale = useSelector(getCurrentLocale); - - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) const theme = useTheme(); - ///: END:ONLY_INCLUDE_IF const [seenNotifications, setSeenNotifications] = useState({}); const [shouldShowScrollButton, setShouldShowScrollButton] = useState(true); @@ -272,14 +267,9 @@ export default function WhatsNewPopup({ onClose }) { >
{notifications.map(({ id }, index) => { - const notification = getTranslatedUINotifications( - t, - locale, - - ///: BEGIN:ONLY_INCLUDE_IF(blockaid) - theme, - ///: END:ONLY_INCLUDE_IF - )[id]; + const notification = getTranslatedUINotifications(t, locale, theme)[ + id + ]; const isLast = index === notifications.length - 1; // Choose the appropriate rendering function based on the id const renderNotification = diff --git a/ui/components/component-library/avatar-network/avatar-network.tsx b/ui/components/component-library/avatar-network/avatar-network.tsx index 676b674aaad7..487f2ad7ee05 100644 --- a/ui/components/component-library/avatar-network/avatar-network.tsx +++ b/ui/components/component-library/avatar-network/avatar-network.tsx @@ -80,7 +80,7 @@ export const AvatarNetwork: AvatarNetworkComponent = React.forwardRef( } onError={handleOnError} src={src} - alt={`${name} logo` || 'network logo'} + alt={(name && `${name} logo`) || 'network logo'} /> )} diff --git a/ui/components/component-library/icon/README.mdx b/ui/components/component-library/icon/README.mdx index 624caebf8748..157db9f7ac04 100644 --- a/ui/components/component-library/icon/README.mdx +++ b/ui/components/component-library/icon/README.mdx @@ -196,7 +196,7 @@ If your svg **does not** contain a single path, you will need to get a designer #### Step 2. -Add your optimized svg file to to `app/images/icons` +Add your optimized svg file to `app/images/icons` #### Step 3. diff --git a/ui/components/component-library/icon/icon.types.ts b/ui/components/component-library/icon/icon.types.ts index 8e576423714e..9c87851d0b65 100644 --- a/ui/components/component-library/icon/icon.types.ts +++ b/ui/components/component-library/icon/icon.types.ts @@ -60,6 +60,7 @@ export enum IconName { Connect = 'connect', CopySuccess = 'copy-success', Copy = 'copy', + Customize = 'customize', Danger = 'danger', Dark = 'dark', Data = 'data', diff --git a/ui/components/institutional/confirm-remove-jwt-modal/confirm-remove-jwt-modal.js b/ui/components/institutional/confirm-remove-jwt-modal/confirm-remove-jwt-modal.js index 438c30545f0e..9ec21650c310 100644 --- a/ui/components/institutional/confirm-remove-jwt-modal/confirm-remove-jwt-modal.js +++ b/ui/components/institutional/confirm-remove-jwt-modal/confirm-remove-jwt-modal.js @@ -134,6 +134,7 @@ const ConfirmRemoveJWT = ({ variant={BUTTON_VARIANT.PRIMARY} size={BUTTON_SIZES.LG} onClick={handleRemove} + data-testid="remove-jwt-confirm-btn" > {t('remove')} diff --git a/ui/components/multichain/account-list-item/__snapshots__/account-list-item.test.js.snap b/ui/components/multichain/account-list-item/__snapshots__/account-list-item.test.js.snap index a30725e9989f..d97d0fd70fbe 100644 --- a/ui/components/multichain/account-list-item/__snapshots__/account-list-item.test.js.snap +++ b/ui/components/multichain/account-list-item/__snapshots__/account-list-item.test.js.snap @@ -1,6 +1,301 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`AccountListItem renders AccountListItem component and shows account name, address, and balance 1`] = ` +exports[`AccountListItem renders AccountListItem component and shows account name, address, and balance for non-EVM account: non-EVM-account-list-item 1`] = ` +
+ +
+`; + +exports[`AccountListItem renders AccountListItem component and shows account name, address, and balance: evm-account-list-item 1`] = `