diff --git a/.circleci/config.yml b/.circleci/config.yml index 01a6a5791bb4..250ca8560f9f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -95,6 +95,11 @@ aliases: workflows: test_and_release: + when: + not: + matches: + pattern: /^l10n_crowdin_action$/ + value: << pipeline.git.branch >> jobs: - create_release_pull_request: <<: *rc_branch_only @@ -154,6 +159,9 @@ workflows: - prep-build-test-mv2: requires: - prep-deps + - prep-build-test-webpack: + requires: + - prep-deps - prep-build-test-flask: requires: - prep-deps @@ -184,6 +192,10 @@ workflows: - test-lint-changelog: requires: - prep-deps + - test-e2e-chrome-webpack: + requires: + - prep-build-test-webpack + - get-changed-files-with-git-diff - test-e2e-chrome: requires: - prep-build-test @@ -230,9 +242,6 @@ workflows: - /^Version-v(\d+)[.](\d+)[.](\d+)/ requires: - prep-build - - test-unit-global: - requires: - - prep-deps - test-storybook: requires: - prep-deps @@ -273,7 +282,6 @@ workflows: - test-lint-shellcheck - test-lint-lockfile - test-lint-changelog - - test-unit-global - validate-source-maps - validate-source-maps-beta - validate-source-maps-flask @@ -288,6 +296,7 @@ workflows: - test-e2e-chrome-mmi - test-e2e-chrome-rpc-mmi - test-e2e-chrome-vault-decryption + - test-e2e-chrome-webpack - test-storybook - benchmark: requires: @@ -338,9 +347,30 @@ workflows: requires: - prep-build-ts-migration-dashboard + locales_only: + when: + matches: + pattern: /^l10n_crowdin_action$/ + value: << pipeline.git.branch >> + jobs: + - prep-deps + - get-changed-files-with-git-diff: + requires: + - prep-deps + - validate-locales-only: + requires: + - get-changed-files-with-git-diff + - test-lint: + requires: + - prep-deps + - all-tests-pass: + requires: + - test-lint + - validate-locales-only + jobs: trigger-beta-build: - executor: node-browsers-medium-plus + executor: node-browsers-small steps: - run: *shallow-git-clone - run: sudo corepack enable @@ -355,8 +385,7 @@ jobs: steps: - run: name: Build beta prod - command: | - .circleci/scripts/trigger-beta-build.sh + command: .circleci/scripts/trigger-beta-build.sh - run: name: Move beta build to 'dist-beta' to avoid conflict with production build command: mv ./dist ./dist-beta @@ -429,8 +458,7 @@ jobs: - run: sudo corepack enable - run: name: Save Yarn version - command: | - yarn --version > /tmp/YARN_VERSION + command: yarn --version > /tmp/YARN_VERSION - restore_cache: keys: # First try to get the specific cache for the checksum of the yarn.lock file. @@ -476,6 +504,15 @@ jobs: paths: - changed-files + validate-locales-only: + executor: node-browsers-small + steps: + - run: *shallow-git-clone + - run: sudo corepack enable + - attach_workspace: + at: . + - run: yarn tsx .circleci/scripts/validate-locales-only.ts + validate-lavamoat-allow-scripts: executor: node-browsers-small steps: @@ -870,6 +907,26 @@ jobs: - dist-test-mv2 - builds-test-mv2 + prep-build-test-webpack: + executor: node-linux-medium + steps: + - run: *shallow-git-clone + - attach_workspace: + at: . + - run: + name: Activate yarn + command: corepack enable + - run: + name: Build extension for testing + command: yarn build:test:webpack + - run: + name: Move test build to 'dist-test-webpack' to avoid conflict with production build + command: mv ./dist ./dist-test-webpack + - persist_to_workspace: + root: . + paths: + - dist-test-webpack + prep-build-storybook: executor: node-linux-medium steps: @@ -1011,6 +1068,27 @@ jobs: name: depcheck command: yarn depcheck + test-e2e-chrome-webpack: + executor: node-browsers-medium-plus + parallelism: 20 + steps: + - run: *shallow-git-clone + - run: sudo corepack enable + - attach_workspace: + at: . + - run: + name: Move test build to dist + command: mv ./dist-test-webpack ./dist + - run: + name: test:e2e:chrome:webpack + command: .circleci/scripts/test-run-e2e.sh yarn test:e2e:chrome:webpack + no_output_timeout: 5m + - store_artifacts: + path: test-artifacts + destination: test-artifacts + - store_test_results: + path: test/test-results/e2e + test-api-specs: executor: node-browsers-medium-plus steps: @@ -1027,8 +1105,7 @@ jobs: - gh/install - run: name: test:api-specs - command: | - timeout 20m yarn test:api-specs --retries 2 + command: .circleci/scripts/test-run-e2e.sh yarn test:api-specs no_output_timeout: 5m - run: name: Comment on PR @@ -1059,11 +1136,7 @@ jobs: command: mv ./builds-test ./builds - run: name: test:e2e:chrome - command: | - if .circleci/scripts/test-run-e2e.sh - then - timeout 20m yarn test:e2e:chrome --retries 1 - fi + command: .circleci/scripts/test-run-e2e.sh yarn test:e2e:chrome no_output_timeout: 5m - store_artifacts: path: test-artifacts @@ -1087,11 +1160,7 @@ jobs: command: mv ./builds-test ./builds - run: name: test:e2e:chrome:rpc - command: | - if .circleci/scripts/test-run-e2e.sh - then - timeout 20m yarn test:e2e:chrome:rpc --retries 1 - fi + command: .circleci/scripts/test-run-e2e.sh yarn test:e2e:chrome:rpc no_output_timeout: 5m - store_artifacts: path: test-artifacts @@ -1115,11 +1184,7 @@ jobs: command: mv ./builds-test ./builds - run: name: test:e2e:chrome:multi-provider - command: | - if .circleci/scripts/test-run-e2e.sh - then - yarn test:e2e:chrome:multi-provider --retries 1 - fi + command: .circleci/scripts/test-run-e2e.sh yarn test:e2e:chrome:multi-provider no_output_timeout: 5m - store_artifacts: path: test-artifacts @@ -1142,11 +1207,7 @@ jobs: command: mv ./builds-test-mmi ./builds - run: name: test:e2e:chrome:rpc - command: | - if .circleci/scripts/test-run-e2e.sh - then - timeout 20m yarn test:e2e:chrome:rpc --retries 1 --build-type=mmi - fi + command: .circleci/scripts/test-run-e2e.sh yarn test:e2e:chrome:rpc --build-type=mmi no_output_timeout: 5m - store_artifacts: path: test-artifacts @@ -1162,12 +1223,8 @@ jobs: - attach_workspace: at: . - run: - name: test:e2e:chrome:vault - command: | - if .circleci/scripts/test-run-e2e.sh - then - yarn test:e2e:single test/e2e/vault-decryption-chrome.spec.js --browser chrome --retries 1 - fi + name: test:e2e:single + command: .circleci/scripts/test-run-e2e.sh yarn test:e2e:single test/e2e/vault-decryption-chrome.spec.js --browser chrome no_output_timeout: 5m - store_artifacts: path: test-artifacts @@ -1191,12 +1248,7 @@ jobs: command: mv ./builds-test-flask-mv2 ./builds - run: name: test:e2e:firefox:flask - command: | - export ENABLE_MV3=false - if .circleci/scripts/test-run-e2e.sh - then - timeout 20m yarn test:e2e:firefox:flask --retries 1 - fi + command: ENABLE_MV3=false .circleci/scripts/test-run-e2e.sh yarn test:e2e:firefox:flask no_output_timeout: 5m - store_artifacts: path: test-artifacts @@ -1220,11 +1272,7 @@ jobs: command: mv ./builds-test-flask ./builds - run: name: test:e2e:chrome:flask - command: | - if .circleci/scripts/test-run-e2e.sh - then - timeout 20m yarn test:e2e:chrome:flask --retries 1 - fi + command: .circleci/scripts/test-run-e2e.sh yarn test:e2e:chrome:flask no_output_timeout: 5m - store_artifacts: path: test-artifacts @@ -1248,11 +1296,7 @@ jobs: command: mv ./builds-test-mmi ./builds - run: name: test:e2e:chrome:mmi - command: | - if .circleci/scripts/test-run-e2e.sh - then - timeout 20m yarn test:e2e:chrome:mmi --retries 1 --build-type=mmi - fi + command: .circleci/scripts/test-run-e2e.sh yarn test:e2e:chrome:mmi --build-type=mmi no_output_timeout: 5m - store_artifacts: path: test-artifacts @@ -1274,8 +1318,7 @@ jobs: command: mv ./dist-test-mmi-playwright ./dist - run: name: Install chromium - command: | - yarn playwright install chromium + command: yarn playwright install chromium - run: name: test:e2e:chrome:mmi command: | @@ -1312,8 +1355,7 @@ jobs: at: . - run: name: Install chromium - command: | - yarn playwright install chromium + command: yarn playwright install chromium - run: name: test:e2e:chrome:swap command: | @@ -1349,12 +1391,7 @@ jobs: command: mv ./builds-test-mv2 ./builds - run: name: test:e2e:firefox - command: | - export ENABLE_MV3=false - if .circleci/scripts/test-run-e2e.sh - then - timeout 20m yarn test:e2e:firefox --retries 1 - fi + command: ENABLE_MV3=false .circleci/scripts/test-run-e2e.sh yarn test:e2e:firefox no_output_timeout: 5m - store_artifacts: path: test-artifacts @@ -1510,12 +1547,6 @@ jobs: command: | echo "export PARENT_COMMIT=$(git merge-base origin/HEAD HEAD)" >> $BASH_ENV source $BASH_ENV - - run: - name: Set commit message env var - command: | - commit_title=$(git show -s --format='%s' HEAD) - echo "export SHA1_COMMIT_TITLE=\"$commit_title\"" >> $BASH_ENV - source $BASH_ENV - run: name: build:announce command: ./development/metamaskbot-build-announce.js @@ -1544,8 +1575,7 @@ jobs: command: yarn sentry:publish --build-type mmi - run: name: Create GitHub release - command: | - .circleci/scripts/release-create-gh-release.sh + command: .circleci/scripts/release-create-gh-release.sh job-publish-storybook: executor: node-browsers-small @@ -1581,17 +1611,6 @@ jobs: git config user.email metamaskbot@users.noreply.github.com yarn ts-migration:dashboard:deploy - test-unit-global: - executor: node-browsers-small - steps: - - run: *shallow-git-clone - - run: sudo corepack enable - - attach_workspace: - at: . - - run: - name: test:unit:global - command: yarn test:unit:global - validate-source-maps: executor: node-browsers-small steps: @@ -1612,8 +1631,7 @@ jobs: at: . - run: name: Validate source maps - command: | - .circleci/scripts/validate-source-maps-beta.sh + command: .circleci/scripts/validate-source-maps-beta.sh validate-source-maps-mmi: executor: node-browsers-small diff --git a/.circleci/scripts/git-diff-develop.ts b/.circleci/scripts/git-diff-develop.ts index 1ad337bc4283..3cf5022d4e12 100644 --- a/.circleci/scripts/git-diff-develop.ts +++ b/.circleci/scripts/git-diff-develop.ts @@ -123,7 +123,7 @@ async function storeGitDiffOutput() { // Store the output of git diff const outputPath = path.resolve(outputDir, 'changed-files.txt'); - fs.writeFileSync(outputPath, diffOutput); + fs.writeFileSync(outputPath, diffOutput.trim()); console.log(`Git diff results saved to ${outputPath}`); process.exit(0); diff --git a/.circleci/scripts/test-run-e2e.sh b/.circleci/scripts/test-run-e2e.sh index ff23712349ba..25ae1eb7b64c 100755 --- a/.circleci/scripts/test-run-e2e.sh +++ b/.circleci/scripts/test-run-e2e.sh @@ -11,4 +11,16 @@ then exit 1 fi +# Run the actual test command from the parameters +timeout 20m "$@" --retries 1 + +# Error code 124 means the command timed out +if [ $? -eq 124 ]; then + # Once deleted, if someone tries to "Rerun failed tests" the result will be + # "Error: can not rerun failed tests: no failed tests could be found" + echo 'Timeout error, deleting the test results' + rm -rf test/test-results/e2e + exit 124 +fi + exit 0 diff --git a/.circleci/scripts/validate-locales-only.ts b/.circleci/scripts/validate-locales-only.ts new file mode 100644 index 000000000000..d34be572c0bb --- /dev/null +++ b/.circleci/scripts/validate-locales-only.ts @@ -0,0 +1,31 @@ +const { readChangedFiles } = require('../../test/e2e/changedFilesUtil.js'); + +/** + * Verifies that all changed files are in the /_locales/ directory. + * Fails the build if any changed files are outside of the /_locales/ directory. + * Fails if no changed files are detected. + */ +async function validateLocalesOnlyChangedFiles() { + const changedFiles = await readChangedFiles(); + if (!changedFiles || changedFiles.length === 0) { + console.error('Failure: No changed files detected.'); + process.exit(1); + } + const invalidFiles = changedFiles.filter( + (file) => !file.startsWith('app/_locales/'), + ); + if (invalidFiles.length > 0) { + console.error( + 'Failure: Changed files must be in the /_locales/ directory.\n Changed Files:', + changedFiles, + '\n Invalid Files:', + invalidFiles, + ); + process.exit(1); + } else { + console.log('Passed validation'); + process.exit(0); + } +} + +validateLocalesOnlyChangedFiles(); diff --git a/.depcheckrc.yml b/.depcheckrc.yml index e4169c5436f0..7ce26732fcf7 100644 --- a/.depcheckrc.yml +++ b/.depcheckrc.yml @@ -58,6 +58,23 @@ ignores: - 'resolve-url-loader' # jest environments - 'jest-environment-jsdom' + # webpack + - '@pmmmwh/react-refresh-webpack-plugin' # dev tool + - 'webpack-dev-server' # dev tool + - 'html-bundler-webpack-plugin' # build tool + - 'postcss-loader' # build tool + - '@swc/helpers' # build tool + - browserslist # build tool + - 'buffer' # polyfill + - 'crypto-browserify' # polyfill + - 'process' # polyfill + - 'stream-http' # polyfill + - 'rimraf' # misc: install helper + - 'json-schema-to-ts' # misc: typescript helper + - 'https-browserify' # polyfill + - 'path-browserify' # polyfill + - 'nyc' # coverage + - 'core-js-pure' # polyfills # babel - '@babel/plugin-transform-logical-assignment-operators' # trezor diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 80e6d89d346a..25d48bbc1b41 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -13,7 +13,6 @@ ], "settings": { "editor.formatOnSave": true, - "git.autofetch": true, "git.ignoreRebaseWarning": true, "git.rebaseWhenSync": true, "gitlens.showWelcomeOnInstall": false @@ -48,7 +47,7 @@ } }, - "postAttachCommand": "/usr/local/share/desktop-init.sh && git pull --rebase; yarn download-builds", + "postAttachCommand": "/usr/local/share/desktop-init.sh && git pull; yarn download-builds", // This is a working Infura key, but it's on the Free Plan and has very limited requests per second // If you want to use your own INFURA_PROJECT_ID, follow the instructions in README.md @@ -56,5 +55,5 @@ "runArgs": ["--shm-size=1g"], - "updateContentCommand": "sudo .devcontainer/install.sh && yarn --immutable && yarn tsx .devcontainer/setup-browsers.ts && echo 'export DISPLAY=:1' >> ~/.bashrc" + "updateContentCommand": "sudo .devcontainer/install.sh && corepack enable && COREPACK_ENABLE_DOWNLOAD_PROMPT=0 yarn --immutable && yarn tsx .devcontainer/setup-browsers.ts && echo 'export DISPLAY=:1' >> ~/.bashrc" } diff --git a/.devcontainer/download-builds.ts b/.devcontainer/download-builds.ts index 1253c8a81599..83e1822379a1 100644 --- a/.devcontainer/download-builds.ts +++ b/.devcontainer/download-builds.ts @@ -52,7 +52,11 @@ async function getBuilds(branch: string, jobNames: string[]) { const artifacts = await response.json(); - if (!artifacts || artifacts.length === 0) { + if ( + !artifacts || + artifacts.length === 0 || + artifacts.message === 'Not Found' + ) { return []; } diff --git a/.eslintrc.js b/.eslintrc.js index 0aea1e739e3f..c4c4cc41927e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -257,7 +257,7 @@ module.exports = { * Mocha library. */ { - files: ['test/e2e/**/*.spec.js', 'test/unit-global/*.test.js'], + files: ['test/e2e/**/*.spec.js'], extends: ['@metamask/eslint-config-mocha'], rules: { // In Mocha tests, it is common to use `this` to store values or do @@ -282,7 +282,8 @@ module.exports = { 'app/scripts/metamask-controller.actions.test.js', 'app/scripts/detect-multiple-instances.test.js', 'app/scripts/controllers/bridge.test.ts', - 'app/scripts/controllers/swaps.test.js', + 'app/scripts/controllers/swaps/**/*.test.js', + 'app/scripts/controllers/swaps/**/*.test.ts', 'app/scripts/controllers/metametrics.test.js', 'app/scripts/controllers/permissions/**/*.test.js', 'app/scripts/controllers/preferences.test.js', @@ -298,6 +299,7 @@ module.exports = { 'test/jest/*.js', 'test/lib/timer-helpers.js', 'test/e2e/helpers.test.js', + 'test/unit-global/*.test.js', 'ui/**/*.test.js', 'ui/__mocks__/*.js', 'shared/lib/error-utils.test.js', @@ -366,7 +368,6 @@ module.exports = { 'development/**/*.js', 'test/e2e/benchmark.js', 'test/helpers/setup-helper.js', - 'test/run-unit-tests.js', ], rules: { 'node/no-process-exit': 'off', diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0d88dcbb2613..51e5a51129b8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -84,6 +84,10 @@ ui/components/component-library @MetaMask/design-system-engineers app/scripts/lib/snap-keyring @MetaMask/accounts-engineers +# Swaps team to own code for the swaps folder +ui/pages/swaps @MetaMask/swaps-engineers +app/scripts/controllers/swaps @MetaMask/swaps-engineers + # Snaps **/snaps/** @MetaMask/snaps-devs shared/constants/permissions.ts @MetaMask/snaps-devs diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index cec32fc26969..2b72c66524bd 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -48,6 +48,18 @@ body: label: Error messages or log output description: Please copy and paste any relevant error messages or log output. This will be automatically formatted, so there is no need for backticks. render: shell + - type: dropdown + id: stage + attributes: + label: Detection stage + description: At what stage was the bug detected? + options: + - In production (default) + - In beta + - During release testing + - On the development branch + validations: + required: true - type: input id: version attributes: diff --git a/.github/actions/setup-environment/action.yml b/.github/actions/setup-environment/action.yml deleted file mode 100644 index 8bdd0ba24c64..000000000000 --- a/.github/actions/setup-environment/action.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: 'Setup environment' -description: 'Setup environment' -runs: - using: 'composite' - steps: - - run: corepack enable - shell: bash - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - cache: yarn - - - name: Install dependencies - run: yarn --immutable - shell: bash diff --git a/.github/scripts/add-team-label-to-pr.ts b/.github/scripts/add-team-label.ts similarity index 93% rename from .github/scripts/add-team-label-to-pr.ts rename to .github/scripts/add-team-label.ts index fbbbe30cac37..40461c598bd6 100644 --- a/.github/scripts/add-team-label-to-pr.ts +++ b/.github/scripts/add-team-label.ts @@ -16,11 +16,11 @@ async function main(): Promise { // 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. + // we need to create our own "PERSONAL_ACCESS_TOKEN" with "repo" permissions. // Such a token allows to access other repositories of the MetaMask organisation. - const personalAccessToken = process.env.RELEASE_LABEL_TOKEN; + const personalAccessToken = process.env.PERSONAL_ACCESS_TOKEN; if (!personalAccessToken) { - core.setFailed('RELEASE_LABEL_TOKEN not found'); + core.setFailed('PERSONAL_ACCESS_TOKEN not found'); process.exit(1); } diff --git a/.github/scripts/check-template-and-add-labels.ts b/.github/scripts/check-template-and-add-labels.ts index a526ccc33b6e..ee9e4b4d163d 100644 --- a/.github/scripts/check-template-and-add-labels.ts +++ b/.github/scripts/check-template-and-add-labels.ts @@ -21,6 +21,13 @@ import { import { TemplateType, templates } from './shared/template'; import { retrievePullRequest } from './shared/pull-request'; +enum RegressionStage { + Development, + Testing, + Beta, + Production +} + const knownBots = ["metamaskbot", "dependabot", "github-actions", "sentry-io"]; main().catch((error: Error): void => { @@ -120,23 +127,9 @@ async function main(): Promise { invalidIssueTemplateLabel, ); - // Extract release version from bug report issue body (if existing) - const releaseVersion = extractReleaseVersionFromBugReportIssueBody( - labelable.body, - ); + // Add regression label to the bug report issue + addRegressionLabelToIssue(octokit, labelable); - // Add regression prod label to the bug report issue if release version was found in issue body - if(isReleaseCandidateIssue(labelable)) { - console.log( - `Issue ${labelable?.number} is not a production issue. Regression prod label is not needed.`, - ); - } else if (releaseVersion) { - await addRegressionProdLabelToIssue(octokit, releaseVersion, labelable); - } else { - console.log( - `No release version was found in body of bug report issue ${labelable?.number}.`, - ); - } } else { const errorMessage = "Issue body does not match any of expected templates ('general-issue.yml' or 'bug-report.yml').\n\nMake sure issue's body includes all section titles.\n\nSections titles are listed here: https://github.com/MetaMask/metamask-extension/blob/develop/.github/scripts/shared/template.ts#L14-L37"; @@ -215,6 +208,28 @@ function extractTemplateTypeFromBody(body: string): TemplateType { return TemplateType.None; } +// This helper function extracts regression stage (Development, Testing, Production) from bug report issue's body. +function extractRegressionStageFromBugReportIssueBody( + body: string, +): RegressionStage | undefined { + const detectionStageRegex = /### Detection stage\s*\n\s*(.*)/i; + const match = body.match(detectionStageRegex); + const extractedAnswer = match ? match[1].trim() : undefined; + + switch (extractedAnswer) { + case 'On the development branch': + return RegressionStage.Development; + case 'During release testing': + return RegressionStage.Testing; + case 'In beta': + return RegressionStage.Beta; + case 'In production (default)': + return RegressionStage.Production; + default: + return undefined; + } +} + // This helper function extracts release version from bug report issue's body. function extractReleaseVersionFromBugReportIssueBody( body: string, @@ -235,49 +250,54 @@ function extractReleaseVersionFromBugReportIssueBody( return version; } -// This function adds the correct "regression-prod-x.y.z" label to the issue, and removes other ones -async function addRegressionProdLabelToIssue( +// This function adds the correct regression label to the issue, and removes other ones +async function addRegressionLabelToIssue( octokit: InstanceType, - releaseVersion: string, issue: Labelable, ): Promise { - // Craft regression prod label to add - const regressionProdLabel: Label = { - name: `regression-prod-${releaseVersion}`, - color: '5319E7', // violet - description: `Regression bug that was found in production in release ${releaseVersion}`, - }; - - let regressionProdLabelFound: boolean = false; - const regressionProdLabelsToBeRemoved: { + // Extract regression stage from bug report issue body (if existing) + const regressionStage = extractRegressionStageFromBugReportIssueBody( + issue.body, + ); + + // Extract release version from bug report issue body (if existing) + const releaseVersion = extractReleaseVersionFromBugReportIssueBody( + issue.body, + ); + + // Craft regression label to add + const regressionLabel: Label = craftRegressionLabel(regressionStage, releaseVersion); + + let regressionLabelFound: boolean = false; + const regressionLabelsToBeRemoved: { id: string; name: string; }[] = []; // Loop over issue's labels, to see if regression labels are either missing, or to be removed issue?.labels?.forEach((label) => { - if (label?.name === regressionProdLabel.name) { - regressionProdLabelFound = true; - } else if (label?.name?.startsWith('regression-prod-')) { - regressionProdLabelsToBeRemoved.push(label); + if (label?.name === regressionLabel.name) { + regressionLabelFound = true; + } else if (label?.name?.startsWith('regression-')) { + regressionLabelsToBeRemoved.push(label); } }); // Add regression prod label to the issue if missing - if (regressionProdLabelFound) { + if (regressionLabelFound) { console.log( - `Issue ${issue?.number} already has ${regressionProdLabel.name} label.`, + `Issue ${issue?.number} already has ${regressionLabel.name} label.`, ); } else { console.log( - `Add ${regressionProdLabel.name} label to issue ${issue?.number}.`, + `Add ${regressionLabel.name} label to issue ${issue?.number}.`, ); - await addLabelToLabelable(octokit, issue, regressionProdLabel); + await addLabelToLabelable(octokit, issue, regressionLabel); } // Remove other regression prod label from the issue await Promise.all( - regressionProdLabelsToBeRemoved.map((label) => { + regressionLabelsToBeRemoved.map((label) => { removeLabelFromLabelable(octokit, issue, label?.id); }), ); @@ -309,9 +329,42 @@ async function userBelongsToMetaMaskOrg( return Boolean(userBelongsToMetaMaskOrgResult?.user?.organization?.id); } -// This function checks if issue is a release candidate (RC) issue, discovered during release regression testing phase. If so, it means it is not a production issue. -function isReleaseCandidateIssue( - issue: Labelable, -): boolean { - return Boolean(issue.labels.find(label => label.name.startsWith('regression-RC'))); +// This function crafts appropriate label, corresponding to regression stage and release version. +function craftRegressionLabel(regressionStage: RegressionStage | undefined, releaseVersion: string | undefined): Label { + switch (regressionStage) { + case RegressionStage.Development: + return { + name: `regression-develop`, + color: '5319E7', // violet + description: `Regression bug that was found on development branch, but not yet present in production`, + }; + + case RegressionStage.Testing: + return { + name: `regression-RC-${releaseVersion || '*'}`, + color: '744C11', // orange + description: releaseVersion ? `Regression bug that was found in release candidate (RC) for release ${releaseVersion}` : `TODO: Unknown release version. Please replace with correct 'regression-RC-x.y.z' label, where 'x.y.z' is the number of the release where bug was found.`, + }; + + case RegressionStage.Beta: + return { + name: `regression-beta-${releaseVersion || '*'}`, + color: 'D94A83', // pink + description: releaseVersion ? `Regression bug that was found in beta in release ${releaseVersion}` : `TODO: Unknown release version. Please replace with correct 'regression-beta-x.y.z' label, where 'x.y.z' is the number of the release where bug was found.`, + }; + + case RegressionStage.Production: + return { + name: `regression-prod-${releaseVersion || '*'}`, + color: '5319E7', // violet + description: releaseVersion ? `Regression bug that was found in production in release ${releaseVersion}` : `TODO: Unknown release version. Please replace with correct 'regression-prod-x.y.z' label, where 'x.y.z' is the number of the release where bug was found.`, + }; + + default: + return { + name: `regression-*`, + color: 'EDEDED', // grey + description: `TODO: Unknown regression stage. Please replace with correct regression label: 'regression-develop', 'regression-RC-x.y.z', or 'regression-prod-x.y.z' label, where 'x.y.z' is the number of the release where bug was found.`, + }; + } } diff --git a/.github/workflows/add-release-label.yml b/.github/workflows/add-release-label.yml index f92f3f5fdc95..94ba76fcefa0 100644 --- a/.github/workflows/add-release-label.yml +++ b/.github/workflows/add-release-label.yml @@ -12,25 +12,13 @@ jobs: runs-on: ubuntu-latest if: github.event.pull_request.merged == true 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 + fetch-depth: 0 # This is needed to checkout all branches - - name: Install dependencies - run: yarn --immutable + - name: Setup environment + uses: metamask/github-tools/.github/actions/setup-environment@main - name: Get the next semver version id: get-next-semver-version diff --git a/.github/workflows/add-team-label.yml b/.github/workflows/add-team-label.yml index eba9e48c1f15..02a75aefd4a2 100644 --- a/.github/workflows/add-team-label.yml +++ b/.github/workflows/add-team-label.yml @@ -1,4 +1,4 @@ -name: Add team label to PR when it is opened +name: Add team label on: pull_request: @@ -7,31 +7,6 @@ on: 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 + uses: metamask/github-tools/.github/workflows/add-team-label.yml@main + secrets: + PERSONAL_ACCESS_TOKEN: ${{ secrets.RELEASE_LABEL_TOKEN }} diff --git a/.github/workflows/check-attributions.yml b/.github/workflows/check-attributions.yml index f1c531e34e9d..4880cc65ebe8 100644 --- a/.github/workflows/check-attributions.yml +++ b/.github/workflows/check-attributions.yml @@ -17,13 +17,9 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 - - run: corepack enable - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - cache: 'yarn' - - name: Install dependencies from cache - run: yarn --immutable + + - name: Setup environment + uses: metamask/github-tools/.github/actions/setup-environment@main + - name: Check attributions changes run: yarn attributions:check diff --git a/.github/workflows/check-pr-labels.yml b/.github/workflows/check-pr-labels.yml index eebd284a1561..19c0576feaae 100644 --- a/.github/workflows/check-pr-labels.yml +++ b/.github/workflows/check-pr-labels.yml @@ -1,4 +1,4 @@ -name: 'Check PR has required labels' +name: Check PR has required labels on: pull_request: branches: @@ -17,28 +17,13 @@ jobs: pull-requests: read 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: 1 # This retrieves only the latest commit. - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - cache: yarn - - name: Install dependencies - run: yarn --immutable + - name: Setup environment + uses: metamask/github-tools/.github/actions/setup-environment@main - name: Check PR has required labels - id: check-pr-has-required-labels env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: yarn run check-pr-has-required-labels diff --git a/.github/workflows/check-template-and-add-labels.yml b/.github/workflows/check-template-and-add-labels.yml index 42fd2f236c72..e9c1c2844ca5 100644 --- a/.github/workflows/check-template-and-add-labels.yml +++ b/.github/workflows/check-template-and-add-labels.yml @@ -10,25 +10,11 @@ jobs: check-template-and-add-labels: 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: 1 # This retrieves only the latest commit. - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - cache: yarn - - name: Install dependencies - run: yarn --immutable + - name: Setup environment + uses: metamask/github-tools/.github/actions/setup-environment@main - name: Check template and add labels id: check-template-and-add-labels diff --git a/.github/workflows/close-bug-report.yml b/.github/workflows/close-bug-report.yml index dbe8f17acc6f..46dcd0a9e9ff 100644 --- a/.github/workflows/close-bug-report.yml +++ b/.github/workflows/close-bug-report.yml @@ -12,28 +12,13 @@ jobs: runs-on: ubuntu-latest if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'Version-v') 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: 1 # This retrieves only the latest commit. - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - cache: yarn - - name: Install dependencies - run: yarn --immutable + - name: Setup environment + uses: metamask/github-tools/.github/actions/setup-environment@main - name: Close release bug report issue - id: close-release-bug-report-issue env: BUG_REPORT_REPO: MetaMask-planning BUG_REPORT_TOKEN: ${{ secrets.BUG_REPORT_TOKEN }} diff --git a/.github/workflows/codespaces.yml b/.github/workflows/codespaces.yml index d424a6a3e336..3455e2db54d4 100644 --- a/.github/workflows/codespaces.yml +++ b/.github/workflows/codespaces.yml @@ -13,21 +13,8 @@ jobs: name: Generate cache image 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: 1 # This retrieves only the latest commit. - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - cache: 'yarn' - - run: yarn --immutable + - name: Setup environment + uses: metamask/github-tools/.github/actions/setup-environment@main diff --git a/.github/workflows/fitness-functions.yml b/.github/workflows/fitness-functions.yml index 001db44cde2f..b4979c8f3e7b 100644 --- a/.github/workflows/fitness-functions.yml +++ b/.github/workflows/fitness-functions.yml @@ -9,25 +9,13 @@ jobs: runs-on: ubuntu-latest steps: - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 'lts/*' - - run: corepack enable - - name: Checkout code uses: actions/checkout@v4 with: - fetch-depth: 0 - - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - cache: yarn + fetch-depth: 0 # This is needed to checkout all branches - - name: Install dependencies - run: yarn --immutable + - name: Setup environment + uses: metamask/github-tools/.github/actions/setup-environment@main - name: Run fitness functions env: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a1c5cc29a575..d14fefe82717 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,10 +11,12 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: Download actionlint id: download-actionlint run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/7fdc9630cc360ea1a469eed64ac6d78caeda1234/scripts/download-actionlint.bash) 1.6.23 shell: bash + - name: Check workflow files run: ${{ steps.download-actionlint.outputs.executable }} -color shell: bash diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml index a45566089901..8de1899e0dfa 100644 --- a/.github/workflows/run-integration-tests.yml +++ b/.github/workflows/run-integration-tests.yml @@ -14,7 +14,7 @@ jobs: uses: actions/checkout@v4 - name: Setup environment - uses: ./.github/actions/setup-environment + uses: metamask/github-tools/.github/actions/setup-environment@main - name: test:integration:coverage run: yarn test:integration:coverage diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index 6b0d0958471b..6765352f5d7d 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -1,32 +1,72 @@ -# WARNING! It is currently being investigated how to make this faster -# DO NOT blindly copy this workflow, not noticing the slow down, -# because suddenly our tests will take hours to pass CI. -# Hopefully this comment here will help prevent that. -# https://github.com/MetaMask/metamask-extension/issues/25680 - name: Run unit tests on: + push: + branches: [develop, master] pull_request: types: [opened,reopened,synchronize] jobs: - test-unit-jest: + test-unit: runs-on: ubuntu-latest + strategy: + matrix: + shard: [1, 2, 3, 4, 5, 6] steps: - name: Checkout repository uses: actions/checkout@v4 - name: Setup environment - uses: ./.github/actions/setup-environment + uses: metamask/github-tools/.github/actions/setup-environment@main + + - name: test:unit:coverage + run: yarn test:unit:coverage --shard=${{ matrix.shard }}/${{ strategy.job-total }} + + - name: Rename coverage to shard coverage + run: mv coverage/coverage-final.json coverage/coverage-${{matrix.shard}}.json - - name: test:coverage:jest:dev - run: yarn test:coverage:jest:dev + - uses: actions/upload-artifact@v4 + with: + name: coverage-${{matrix.shard}} + path: coverage/coverage-${{matrix.shard}}.json + + test-webpack: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup environment + uses: metamask/github-tools/.github/actions/setup-environment@main - - name: test:coverage:jest - run: yarn test:coverage:jest + - name: test:unit:webpack:coverage + run: yarn test:unit:webpack:coverage + + - name: Rename coverage + run: mv coverage/coverage-final.json coverage/coverage-webpack.json + + - uses: actions/upload-artifact@v4 + with: + name: coverage-webpack + path: coverage/coverage-webpack.json + + report-coverage: + runs-on: ubuntu-latest + needs: + - test-unit + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download coverage from shards + uses: actions/download-artifact@v4 + with: + path: coverage + pattern: coverage-* + merge-multiple: true - - uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 + - name: Upload coverage to Codecov + uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true diff --git a/.github/workflows/update-attributions.yml b/.github/workflows/update-attributions.yml index 9e2625e3657d..dba8f6b8b72e 100644 --- a/.github/workflows/update-attributions.yml +++ b/.github/workflows/update-attributions.yml @@ -58,14 +58,8 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_NUMBER: ${{ github.event.issue.number }} - - run: corepack enable - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - cache: 'yarn' - - name: Install Yarn dependencies - run: yarn --immutable + - name: Setup environment + uses: metamask/github-tools/.github/actions/setup-environment@main - name: Get commit SHA id: commit-sha run: echo "COMMIT_SHA=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" @@ -86,18 +80,12 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_NUMBER: ${{ github.event.issue.number }} - - run: corepack enable - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - cache: 'yarn' - - name: Install dependencies from cache - run: yarn --immutable --immutable-cache + - name: Setup environment + uses: metamask/github-tools/.github/actions/setup-environment@main - name: Generate Attributions run: yarn attributions:generate - name: Cache attributions file - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 with: path: attribution.txt key: cache-build-${{ needs.prepare.outputs.COMMIT_SHA }} @@ -125,7 +113,7 @@ jobs: id: commit-sha run: echo "COMMIT_SHA=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" - name: Restore attributions file - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: attribution.txt key: cache-build-${{ needs.prepare.outputs.COMMIT_SHA }} diff --git a/.github/workflows/update-lavamoat-policies.yml b/.github/workflows/update-lavamoat-policies.yml index 41d75af08ae0..1baef7fb4460 100644 --- a/.github/workflows/update-lavamoat-policies.yml +++ b/.github/workflows/update-lavamoat-policies.yml @@ -51,11 +51,6 @@ jobs: outputs: COMMIT_SHA: ${{ steps.commit-sha.outputs.COMMIT_SHA }} steps: - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 'lts/*' - - run: corepack enable - name: Checkout repository uses: actions/checkout@v4 - name: Checkout pull request @@ -63,13 +58,8 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.LAVAMOAT_UPDATE_TOKEN }} PR_NUMBER: ${{ github.event.issue.number }} - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - cache: 'yarn' - - name: Install Yarn dependencies - run: yarn --immutable + - name: Setup environment + uses: metamask/github-tools/.github/actions/setup-environment@main - name: Get commit SHA id: commit-sha run: echo "COMMIT_SHA=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" @@ -80,11 +70,6 @@ jobs: needs: - prepare steps: - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 'lts/*' - - run: corepack enable - name: Checkout repository uses: actions/checkout@v4 - name: Checkout pull request @@ -92,17 +77,12 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.LAVAMOAT_UPDATE_TOKEN }} PR_NUMBER: ${{ github.event.issue.number }} - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - cache: 'yarn' - - name: Install dependencies from cache - run: yarn --immutable --immutable-cache + - name: Setup environment + uses: metamask/github-tools/.github/actions/setup-environment@main - name: Update LavaMoat build policy run: yarn lavamoat:build:auto - name: Cache build policy - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 with: path: lavamoat/build-system key: cache-build-${{ needs.prepare.outputs.COMMIT_SHA }} @@ -119,11 +99,6 @@ jobs: - prepare - update-lavamoat-build-policy steps: - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 'lts/*' - - run: corepack enable - name: Checkout repository uses: actions/checkout@v4 - name: Checkout pull request @@ -131,15 +106,10 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.LAVAMOAT_UPDATE_TOKEN }} PR_NUMBER: ${{ github.event.issue.number }} - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - cache: 'yarn' - - name: Install dependencies from cache - run: yarn --immutable --immutable-cache + - name: Setup environment + uses: metamask/github-tools/.github/actions/setup-environment@main - name: Restore build policy - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: lavamoat/build-system key: cache-build-${{ needs.prepare.outputs.COMMIT_SHA }} @@ -149,7 +119,7 @@ jobs: env: INFURA_PROJECT_ID: 00000000000 - name: Cache ${{ matrix.build-type }} application policy - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 with: path: lavamoat/browserify/${{ matrix.build-type }} key: cache-${{ matrix.build-type }}-${{ needs.prepare.outputs.COMMIT_SHA }} @@ -179,7 +149,7 @@ jobs: id: commit-sha run: echo "COMMIT_SHA=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" - name: Restore build policy - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: lavamoat/build-system key: cache-build-${{ needs.prepare.outputs.COMMIT_SHA }} @@ -188,25 +158,25 @@ jobs: # Ensure this is synchronized with the list above in the "update-lavamoat-webapp-policy" job # and with the build type list in `builds.yml` - name: Restore main application policy - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: lavamoat/browserify/main key: cache-main-${{ needs.prepare.outputs.COMMIT_SHA }} fail-on-cache-miss: true - name: Restore beta application policy - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: lavamoat/browserify/beta key: cache-beta-${{ needs.prepare.outputs.COMMIT_SHA }} fail-on-cache-miss: true - name: Restore flask application policy - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: lavamoat/browserify/flask key: cache-flask-${{ needs.prepare.outputs.COMMIT_SHA }} fail-on-cache-miss: true - name: Restore mmi application policy - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: lavamoat/browserify/mmi key: cache-mmi-${{ needs.prepare.outputs.COMMIT_SHA }} diff --git a/.metamaskrc.dist b/.metamaskrc.dist index 1a49ba3c3bd7..745c51528c3e 100644 --- a/.metamaskrc.dist +++ b/.metamaskrc.dist @@ -39,3 +39,7 @@ BLOCKAID_PUBLIC_KEY= ; The endpoint used to submit errors and tracing data to Sentry. ; The below is the `test-metamask` project. ; SENTRY_DSN_DEV=https://f59f3dd640d2429d9d0e2445a87ea8e1@sentry.io/273496 + +; Enable/disable why did you render debug tool: https://github.com/welldone-software/why-did-you-render +; This should NEVER be enabled in production since it slows down react +; ENABLE_WHY_DID_YOU_RENDER=false diff --git a/.prettierignore b/.prettierignore index e70feef786f9..9c4b3868464b 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,18 +1,16 @@ -node_modules/**/* -lavamoat/**/policy.json -dist/**/* -builds/**/* -test-*/**/* -app/vendor/** -.nyc_output/**/* -test/e2e/send-eth-with-private-key-test/**/* *.scss -development/chromereload.js -development/ts-migration-dashboard/filesToConvert.json -development/ts-migration-dashboard/build/** -public/**/* -development/charts/** +.nyc_output/**/* node_modules/**/* -storybook-build/**/* -jest-coverage/**/* -coverage/**/* +/app/vendor/** +/builds/**/* +/coverage/**/* +/development/charts/** +/development/chromereload.js +/development/ts-migration-dashboard/filesToConvert.json +/development/ts-migration-dashboard/build/** +/dist/**/* +/lavamoat/**/policy.json +/storybook-build/**/* +/test-artifacts/**/* +/test/test-results/**/* +/test/e2e/send-eth-with-private-key-test/**/* diff --git a/.storybook/initial-states/transactions.js b/.storybook/initial-states/transactions.js index 6dd76b2be7e8..f7d81759770e 100644 --- a/.storybook/initial-states/transactions.js +++ b/.storybook/initial-states/transactions.js @@ -7,7 +7,6 @@ const MOCK_TX_TYPE = { INCOMING: 'incoming', PERSONAL_SIGN: 'personal_sign', RETRY: 'retry', - SIGN: 'eth_sign', SIGN_TYPED_DATA: 'eth_signTypedData', SIMPLE_SEND: 'simpleSend', SMART: 'smart', @@ -420,27 +419,6 @@ export const MOCK_TRANSACTION_BY_TYPE = { 'Error: [ethjs-query] while formatting outputs from RPC \'{"value":{"code":-32000,"message":"replacement transaction underpriced"}}\'\n at chrome-extension://hbljfohiafgaaaabejngpgolnboohpaf/common-5.js:14346:29', }, }, - [MOCK_TX_TYPE.SIGN]: { - id: 5177046356058675, - msgParams: { - from: '0xabce7847fd3661a9b7c86aaf1daea08d9da5750e', - data: - '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0', - origin: 'https://metamask.github.io', - }, - txParams: { - from: '0xabc14609ef9e09776ac5fe00bdbfef57bcdefebb', - gas: '0x5208', - gasPrice: '0x77359400', - nonce: '0x3', - value: '0x00', - data: - '0x608060405234801561001057600080fd5b5033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000808190555061023b806100686000396000f300608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632e1a7d4d1461005c5780638da5cb5b1461009d578063d0e30db0146100f4575b600080fd5b34801561006857600080fd5b5061008760048036038101908080359060200190929190505050610112565b6040518082815260200191505060405180910390f35b3480156100a957600080fd5b506100b26101d0565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100fc6101f6565b6040518082815260200191505060405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561017057600080fd5b8160008082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f193505050501580156101c5573d6000803e3d6000fd5b506000549050919050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60003460008082825401925050819055506000549050905600a165627a7a72305820f237db3ec816a52589d82512117bc85bc08d3537683ffeff9059108caf3e5d400029', - }, - time: 1653451051909, - status: 'unapproved', - type: 'eth_sign', - }, [MOCK_TX_TYPE.SIGN_TYPED_DATA]: { id: 5177046356058598, msgParams: { diff --git a/.storybook/preview.js b/.storybook/preview.js index b0486d83aeeb..fc077a637f79 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -20,6 +20,7 @@ import { metamaskStorybookTheme } from './metamask-storybook-theme'; import { DocsContainer } from '@storybook/addon-docs'; import { useDarkMode } from 'storybook-dark-mode'; import { themes } from '@storybook/theming'; +import { AlertMetricsProvider } from '../ui/components/app/alert-system/contexts/alertMetricsContext'; // eslint-disable-next-line /* @ts-expect-error: Avoids error from window property not existing */ @@ -124,13 +125,15 @@ const metamaskDecorator = (story, context) => { - - {story()} - + + + {story()} + + diff --git a/.storybook/test-data.js b/.storybook/test-data.js index fab382d4b6b8..d251f3194877 100644 --- a/.storybook/test-data.js +++ b/.storybook/test-data.js @@ -1204,8 +1204,6 @@ const state = { v: '0x93', }, ], - unapprovedMsgs: {}, - unapprovedMsgCount: 0, unapprovedPersonalMsgs: {}, unapprovedPersonalMsgCount: 0, unapprovedDecryptMsgs: {}, @@ -1484,7 +1482,19 @@ const state = { }, }, ensResolutionsByAddress: {}, - pendingApprovals: {}, + pendingApprovals: { + '741bad30-45b6-11ef-b6ec-870d18dd6c01': { + id: '741bad30-45b6-11ef-b6ec-870d18dd6c01', + origin: 'http://127.0.0.1:8080', + type: 'transaction', + time: 1721383540624, + requestData: { + txId: '741bad30-45b6-11ef-b6ec-870d18dd6c01', + }, + requestState: null, + expectsResult: true, + }, + }, pendingApprovalCount: 0, subjectMetadata: { 'http://localhost:8080': { diff --git a/.vscode/cspell.json b/.vscode/cspell.json index 24e3747bfa9b..51c7db9f6211 100644 --- a/.vscode/cspell.json +++ b/.vscode/cspell.json @@ -70,6 +70,7 @@ "quickstart", "recompiles", "shellcheck", + "SIWE", "sourcemaps", "sprintf", "testcase", diff --git a/.vscode/launch.json b/.vscode/launch.json index cd250eabebf7..bb8b8618b403 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -25,7 +25,9 @@ "args": [ "${fileBasenameNoExtension}", "--config", - "jest.integration.config.js" + "jest.integration.config.js", + "--testTimeout", + "30000" ], "console": "integratedTerminal", "internalConsoleOptions": "neverOpen", diff --git a/.vscode/package.json-schema.json b/.vscode/package.json-schema.json index c377e274b3ce..458329d0741c 100644 --- a/.vscode/package.json-schema.json +++ b/.vscode/package.json-schema.json @@ -5,9 +5,22 @@ "required": ["lavamoat"], "properties": { "scripts": { + "required": ["webpack", "webpack:clearcache", "postinstall"], "properties": { "start:dev": { "description": "Runs `yarn start` with the addition of react/redux backend devtool servers enabled." + }, + "webpack": { + "type": "string", + "description": "Builds the extension in \"dev\" mode. Run `yarn webpack --help` for usage information." + }, + "webpack:clearcache": { + "type": "string", + "description": "Deletes webpack's build cache. Useful to force a rebuild (webpack not detecting changes, node_modules have changed, etc)." + }, + "postinstall": { + "type": "string", + "description": "Runs automatically after running `yarn` (`yarn install`) in order to prime the webpack dev build." } } }, @@ -16,6 +29,62 @@ "properties": { "autoprefixer": { "description": "Used by our build systems to automatically add prefixes to CSS rules based on our browserslist." + }, + "@types/chrome": { + "type": "string", + "description": "Provides type definitions for the Chrome extension manifest.json files." + }, + "buffer": { + "type": "string", + "description": "Provides a global Buffer object for use in the browser (webpack)" + }, + "crypto-browserify": { + "type": "string", + "description": "Polyfill's node's crypto API for use in the browser (webpack)" + }, + "dotenv": { + "type": "string", + "description": "Loads environment variables from a .metamaskrc file (webpack)" + }, + "fflate": { + "type": "string", + "description": "Provides zip capabilities for bundling (webpack)" + }, + "postcss-loader": { + "type": "string", + "description": "Loads postcss plugins (webpack)" + }, + "process": { + "type": "string", + "description": "Provides a global process object for use in the browser (webpack)" + }, + "schema-utils": { + "type": "string", + "description": "Provides utilities for validating options objects (webpack)" + }, + "stream-http": { + "type": "string", + "description": "Polyfill's node's stream API for use in the browser (webpack)" + }, + "@swc/core": { + "type": "string", + "description": "Transpiles javascript and typescript (webpack)" + }, + "tsx": { + "type": "string", + "description": "Provides blazing fast typescript compilation (no type checking) (webpack)" + }, + "rimraf": { + "type": "string", + "description": "Provides a cross-platform way of deleting files from the command line (webpack)" + }, + "json-schema-to-ts": { + "type": "string", + "description": "Generates typescript types from json schemas (webpack)" + }, + "@pmmmwh/react-refresh-webpack-plugin": { + "type": "string", + "description": "Provides hot reloading for react components (webpack)" } } }, diff --git a/.yarn/patches/@metamask-keyring-controller-npm-17.1.1-098cb41930.patch b/.yarn/patches/@metamask-keyring-controller-npm-17.1.1-098cb41930.patch new file mode 100644 index 000000000000..439c1ddf49ae --- /dev/null +++ b/.yarn/patches/@metamask-keyring-controller-npm-17.1.1-098cb41930.patch @@ -0,0 +1,13 @@ +diff --git a/package.json b/package.json +index 5a6217eaed16fdfe7f1ad693871f85320bd6b421..69bdf1a9155497e37fc58db7bbc74597fd543535 100644 +--- a/package.json ++++ b/package.json +@@ -18,7 +18,7 @@ + "sideEffects": false, + "exports": { + ".": { +- "import": "./dist/index.mjs", ++ "import": "./dist/index.js", + "require": "./dist/index.js", + "types": "./dist/types/index.d.ts" + }, diff --git a/.yarn/patches/@metamask-profile-sync-controller-npm-0.2.0-4d922a9e03.patch b/.yarn/patches/@metamask-profile-sync-controller-npm-0.2.0-4d922a9e03.patch new file mode 100644 index 000000000000..540b61f79c57 --- /dev/null +++ b/.yarn/patches/@metamask-profile-sync-controller-npm-0.2.0-4d922a9e03.patch @@ -0,0 +1,323 @@ +diff --git a/dist/chunk-77LSWSGB.js b/dist/chunk-77LSWSGB.js +index 7f29fc213985d9817d7d279ae494f6a27527ecf8..fbfb937f3579c610ea3d800e8a3953f995462595 100644 +--- a/dist/chunk-77LSWSGB.js ++++ b/dist/chunk-77LSWSGB.js +@@ -1,23 +1,17 @@ +-"use strict";Object.defineProperty(exports, "__esModule", {value: true}); ++"use strict"; ++Object.defineProperty(exports, "__esModule", { value: true }); + ++var _chunkR2S3HIDNjs = require("./chunk-R2S3HIDN.js"); + +-var _chunkR2S3HIDNjs = require('./chunk-R2S3HIDN.js'); ++var _chunk3TE6M5TVjs = require("./chunk-3TE6M5TV.js"); + ++var _chunkSLHAVOTEjs = require("./chunk-SLHAVOTE.js"); + +-var _chunk3TE6M5TVjs = require('./chunk-3TE6M5TV.js'); ++var _chunkAK7OTIWAjs = require("./chunk-AK7OTIWA.js"); + ++var _chunkT3FNDVE3js = require("./chunk-T3FNDVE3.js"); + +-var _chunkSLHAVOTEjs = require('./chunk-SLHAVOTE.js'); +- +- +-var _chunkAK7OTIWAjs = require('./chunk-AK7OTIWA.js'); +- +- +- +-var _chunkT3FNDVE3js = require('./chunk-T3FNDVE3.js'); +- +- +-var _chunkIGY2S5BCjs = require('./chunk-IGY2S5BC.js'); ++var _chunkIGY2S5BCjs = require("./chunk-IGY2S5BC.js"); + + // src/controllers/user-storage/index.ts + var user_storage_exports = {}; +@@ -26,7 +20,7 @@ _chunkIGY2S5BCjs.__export.call(void 0, user_storage_exports, { + Encryption: () => _chunkT3FNDVE3js.encryption_default, + Mocks: () => fixtures_exports, + createSHA256Hash: () => _chunkT3FNDVE3js.createSHA256Hash, +- defaultState: () => _chunkR2S3HIDNjs.defaultState ++ defaultState: () => _chunkR2S3HIDNjs.defaultState, + }); + + // src/controllers/user-storage/__fixtures__/index.ts +@@ -36,46 +30,52 @@ _chunkIGY2S5BCjs.__export.call(void 0, fixtures_exports, { + MOCK_STORAGE_DATA: () => MOCK_STORAGE_DATA, + MOCK_STORAGE_KEY: () => MOCK_STORAGE_KEY, + MOCK_STORAGE_KEY_SIGNATURE: () => MOCK_STORAGE_KEY_SIGNATURE, +- MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT: () => MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT, ++ MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT: () => ++ MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT, + getMockUserStorageGetResponse: () => getMockUserStorageGetResponse, +- getMockUserStoragePutResponse: () => getMockUserStoragePutResponse ++ getMockUserStoragePutResponse: () => getMockUserStoragePutResponse, + }); + + // src/controllers/user-storage/__fixtures__/mockStorage.ts + var MOCK_STORAGE_KEY_SIGNATURE = "mockStorageKey"; +-var MOCK_STORAGE_KEY = _chunkT3FNDVE3js.createSHA256Hash.call(void 0, MOCK_STORAGE_KEY_SIGNATURE); +-var MOCK_STORAGE_DATA = JSON.stringify({ hello: "world" }); +-var MOCK_ENCRYPTED_STORAGE_DATA = _chunkAK7OTIWAjs.encryption_default.encryptString( +- MOCK_STORAGE_DATA, +- MOCK_STORAGE_KEY ++var MOCK_STORAGE_KEY = _chunkT3FNDVE3js.createSHA256Hash.call( ++ void 0, ++ MOCK_STORAGE_KEY_SIGNATURE + ); ++var MOCK_STORAGE_DATA = JSON.stringify({ hello: "world" }); ++var MOCK_ENCRYPTED_STORAGE_DATA = ++ _chunkAK7OTIWAjs.encryption_default.encryptString( ++ MOCK_STORAGE_DATA, ++ MOCK_STORAGE_KEY ++ ); + + // src/controllers/user-storage/__fixtures__/mockResponses.ts +-var MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT = `${_chunk3TE6M5TVjs.USER_STORAGE_ENDPOINT}${_chunkSLHAVOTEjs.createEntryPath.call(void 0, +- "notifications.notificationSettings", ++var MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT = `${ ++ _chunk3TE6M5TVjs.USER_STORAGE_ENDPOINT ++}${_chunkSLHAVOTEjs.createEntryPath.call( ++ void 0, ++ "notifications.notification_settings", + MOCK_STORAGE_KEY + )}`; + var MOCK_GET_USER_STORAGE_RESPONSE = { + HashedKey: "HASHED_KEY", +- Data: MOCK_ENCRYPTED_STORAGE_DATA ++ Data: MOCK_ENCRYPTED_STORAGE_DATA, + }; + var getMockUserStorageGetResponse = () => { + return { + url: MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT, + requestMethod: "GET", +- response: MOCK_GET_USER_STORAGE_RESPONSE ++ response: MOCK_GET_USER_STORAGE_RESPONSE, + }; + }; + var getMockUserStoragePutResponse = () => { + return { + url: MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT, + requestMethod: "PUT", +- response: null ++ response: null, + }; + }; + +- +- +- +-exports.fixtures_exports = fixtures_exports; exports.user_storage_exports = user_storage_exports; ++exports.fixtures_exports = fixtures_exports; ++exports.user_storage_exports = user_storage_exports; + //# sourceMappingURL=chunk-77LSWSGB.js.map +diff --git a/dist/chunk-77LSWSGB.js.map b/dist/chunk-77LSWSGB.js.map +index dae9a343058b5d0c21707b57044e7329082cc969..c7d6a2d8c599ca4e090cb928af1b8394634e04d3 100644 +--- a/dist/chunk-77LSWSGB.js.map ++++ b/dist/chunk-77LSWSGB.js.map +@@ -1 +1 @@ +-{"version":3,"sources":["../src/controllers/user-storage/index.ts","../src/controllers/user-storage/__fixtures__/index.ts","../src/controllers/user-storage/__fixtures__/mockStorage.ts","../src/controllers/user-storage/__fixtures__/mockResponses.ts"],"names":["encryption_default"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEO,IAAM,6BAA6B;AACnC,IAAM,mBAAmB,iBAAiB,0BAA0B;AACpE,IAAM,oBAAoB,KAAK,UAAU,EAAE,OAAO,QAAQ,CAAC;AAC3D,IAAM,8BAA8BA,oBAAW;AAAA,EACpD;AAAA,EACA;AACF;;;ACGO,IAAM,2CAA2C,GAAG,qBAAqB,GAAG;AAAA,EACjF;AAAA,EACA;AACF,CAAC;AAED,IAAM,iCAAyD;AAAA,EAC7D,WAAW;AAAA,EACX,MAAM;AACR;AAEO,IAAM,gCAAgC,MAAM;AACjD,SAAO;AAAA,IACL,KAAK;AAAA,IACL,eAAe;AAAA,IACf,UAAU;AAAA,EACZ;AACF;AAEO,IAAM,gCAAgC,MAAM;AACjD,SAAO;AAAA,IACL,KAAK;AAAA,IACL,eAAe;AAAA,IACf,UAAU;AAAA,EACZ;AACF","sourcesContent":["import Controller from './UserStorageController';\n\nexport { Controller };\nexport * from './UserStorageController';\nexport * from './encryption';\nexport * as Mocks from './__fixtures__';\n","export * from './mockResponses';\nexport * from './mockStorage';\n","import encryption, { createSHA256Hash } from '../encryption';\n\nexport const MOCK_STORAGE_KEY_SIGNATURE = 'mockStorageKey';\nexport const MOCK_STORAGE_KEY = createSHA256Hash(MOCK_STORAGE_KEY_SIGNATURE);\nexport const MOCK_STORAGE_DATA = JSON.stringify({ hello: 'world' });\nexport const MOCK_ENCRYPTED_STORAGE_DATA = encryption.encryptString(\n MOCK_STORAGE_DATA,\n MOCK_STORAGE_KEY,\n);\n","import { createEntryPath } from '../schema';\nimport type { GetUserStorageResponse } from '../services';\nimport { USER_STORAGE_ENDPOINT } from '../services';\nimport { MOCK_ENCRYPTED_STORAGE_DATA, MOCK_STORAGE_KEY } from './mockStorage';\n\ntype MockResponse = {\n url: string;\n requestMethod: 'GET' | 'POST' | 'PUT';\n response: unknown;\n};\n\nexport const MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT = `${USER_STORAGE_ENDPOINT}${createEntryPath(\n 'notifications.notificationSettings',\n MOCK_STORAGE_KEY,\n)}`;\n\nconst MOCK_GET_USER_STORAGE_RESPONSE: GetUserStorageResponse = {\n HashedKey: 'HASHED_KEY',\n Data: MOCK_ENCRYPTED_STORAGE_DATA,\n};\n\nexport const getMockUserStorageGetResponse = () => {\n return {\n url: MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT,\n requestMethod: 'GET',\n response: MOCK_GET_USER_STORAGE_RESPONSE,\n } satisfies MockResponse;\n};\n\nexport const getMockUserStoragePutResponse = () => {\n return {\n url: MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT,\n requestMethod: 'PUT',\n response: null,\n } satisfies MockResponse;\n};\n"]} +\ No newline at end of file ++{"version":3,"sources":["../src/controllers/user-storage/index.ts","../src/controllers/user-storage/__fixtures__/index.ts","../src/controllers/user-storage/__fixtures__/mockStorage.ts","../src/controllers/user-storage/__fixtures__/mockResponses.ts"],"names":["encryption_default"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEO,IAAM,6BAA6B;AACnC,IAAM,mBAAmB,iBAAiB,0BAA0B;AACpE,IAAM,oBAAoB,KAAK,UAAU,EAAE,OAAO,QAAQ,CAAC;AAC3D,IAAM,8BAA8BA,oBAAW;AAAA,EACpD;AAAA,EACA;AACF;;;ACGO,IAAM,2CAA2C,GAAG,qBAAqB,GAAG;AAAA,EACjF;AAAA,EACA;AACF,CAAC;AAED,IAAM,iCAAyD;AAAA,EAC7D,WAAW;AAAA,EACX,MAAM;AACR;AAEO,IAAM,gCAAgC,MAAM;AACjD,SAAO;AAAA,IACL,KAAK;AAAA,IACL,eAAe;AAAA,IACf,UAAU;AAAA,EACZ;AACF;AAEO,IAAM,gCAAgC,MAAM;AACjD,SAAO;AAAA,IACL,KAAK;AAAA,IACL,eAAe;AAAA,IACf,UAAU;AAAA,EACZ;AACF","sourcesContent":["import Controller from './UserStorageController';\n\nexport { Controller };\nexport * from './UserStorageController';\nexport * from './encryption';\nexport * as Mocks from './__fixtures__';\n","export * from './mockResponses';\nexport * from './mockStorage';\n","import encryption, { createSHA256Hash } from '../encryption';\n\nexport const MOCK_STORAGE_KEY_SIGNATURE = 'mockStorageKey';\nexport const MOCK_STORAGE_KEY = createSHA256Hash(MOCK_STORAGE_KEY_SIGNATURE);\nexport const MOCK_STORAGE_DATA = JSON.stringify({ hello: 'world' });\nexport const MOCK_ENCRYPTED_STORAGE_DATA = encryption.encryptString(\n MOCK_STORAGE_DATA,\n MOCK_STORAGE_KEY,\n);\n","import { createEntryPath } from '../schema';\nimport type { GetUserStorageResponse } from '../services';\nimport { USER_STORAGE_ENDPOINT } from '../services';\nimport { MOCK_ENCRYPTED_STORAGE_DATA, MOCK_STORAGE_KEY } from './mockStorage';\n\ntype MockResponse = {\n url: string;\n requestMethod: 'GET' | 'POST' | 'PUT';\n response: unknown;\n};\n\nexport const MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT = `${USER_STORAGE_ENDPOINT}${createEntryPath(\n 'notifications.notification_settings',\n MOCK_STORAGE_KEY,\n)}`;\n\nconst MOCK_GET_USER_STORAGE_RESPONSE: GetUserStorageResponse = {\n HashedKey: 'HASHED_KEY',\n Data: MOCK_ENCRYPTED_STORAGE_DATA,\n};\n\nexport const getMockUserStorageGetResponse = () => {\n return {\n url: MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT,\n requestMethod: 'GET',\n response: MOCK_GET_USER_STORAGE_RESPONSE,\n } satisfies MockResponse;\n};\n\nexport const getMockUserStoragePutResponse = () => {\n return {\n url: MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT,\n requestMethod: 'PUT',\n response: null,\n } satisfies MockResponse;\n};\n"]} +\ No newline at end of file +diff --git a/dist/chunk-ILIZJQ6X.mjs b/dist/chunk-ILIZJQ6X.mjs +index 75b92fcd4de183213c06ef83b119fcd085087064..9732ca708bd2939972cf39f03a18878254354aa2 100644 +--- a/dist/chunk-ILIZJQ6X.mjs ++++ b/dist/chunk-ILIZJQ6X.mjs +@@ -1,10 +1,8 @@ +-import { +- createSHA256Hash +-} from "./chunk-K5UKU454.mjs"; ++import { createSHA256Hash } from "./chunk-K5UKU454.mjs"; + + // src/controllers/user-storage/schema.ts + var USER_STORAGE_SCHEMA = { +- notifications: ["notificationSettings"] ++ notifications: ["notification_settings"], + }; + var getFeatureAndKeyFromPath = (path) => { + const pathRegex = /^\w+\.\w+$/u; +@@ -32,9 +30,5 @@ function createEntryPath(path, storageKey) { + return `/${feature}/${hashedKey}`; + } + +-export { +- USER_STORAGE_SCHEMA, +- getFeatureAndKeyFromPath, +- createEntryPath +-}; ++export { USER_STORAGE_SCHEMA, getFeatureAndKeyFromPath, createEntryPath }; + //# sourceMappingURL=chunk-ILIZJQ6X.mjs.map +diff --git a/dist/chunk-ILIZJQ6X.mjs.map b/dist/chunk-ILIZJQ6X.mjs.map +index 5cfdb8f82d2f3c75b64e692bcff173a81d75af8f..2caaf734426228ae7e8e08b24ac8fe7c763a2edd 100644 +--- a/dist/chunk-ILIZJQ6X.mjs.map ++++ b/dist/chunk-ILIZJQ6X.mjs.map +@@ -1 +1 @@ +-{"version":3,"sources":["../src/controllers/user-storage/schema.ts"],"sourcesContent":["import { createSHA256Hash } from './encryption';\n\n/**\n * The User Storage Endpoint requires a feature name and a namespace key.\n * Developers can provide additional features and keys by extending these types below.\n */\n\nexport const USER_STORAGE_SCHEMA = {\n notifications: ['notificationSettings'],\n} as const;\n\ntype UserStorageSchema = typeof USER_STORAGE_SCHEMA;\ntype UserStorageFeatures = keyof UserStorageSchema;\ntype UserStorageFeatureKeys =\n UserStorageSchema[Feature][number];\n\ntype UserStorageFeatureAndKey = {\n feature: UserStorageFeatures;\n key: UserStorageFeatureKeys;\n};\n\nexport type UserStoragePath = {\n [K in keyof UserStorageSchema]: `${K}.${UserStorageSchema[K][number]}`;\n}[keyof UserStorageSchema];\n\nexport const getFeatureAndKeyFromPath = (\n path: UserStoragePath,\n): UserStorageFeatureAndKey => {\n const pathRegex = /^\\w+\\.\\w+$/u;\n\n if (!pathRegex.test(path)) {\n throw new Error(\n `user-storage - path is not in the correct format. Correct format: 'feature.key'`,\n );\n }\n\n const [feature, key] = path.split('.') as [\n UserStorageFeatures,\n UserStorageFeatureKeys,\n ];\n\n if (!(feature in USER_STORAGE_SCHEMA)) {\n throw new Error(`user-storage - invalid feature provided: ${feature}`);\n }\n\n const validFeature = USER_STORAGE_SCHEMA[feature] as readonly string[];\n\n if (!validFeature.includes(key)) {\n const validKeys = USER_STORAGE_SCHEMA[feature].join(', ');\n\n throw new Error(\n `user-storage - invalid key provided for this feature: ${key}. Valid keys: ${validKeys}`,\n );\n }\n\n return { feature, key };\n};\n\n/**\n * Constructs a unique entry path for a user.\n * This can be done due to the uniqueness of the storage key (no users will share the same storage key).\n * The users entry is a unique hash that cannot be reversed.\n *\n * @param path - string in the form of `${feature}.${key}` that matches schema\n * @param storageKey - users storage key\n * @returns path to store entry\n */\nexport function createEntryPath(\n path: UserStoragePath,\n storageKey: string,\n): string {\n const { feature, key } = getFeatureAndKeyFromPath(path);\n const hashedKey = createSHA256Hash(key + storageKey);\n\n return `/${feature}/${hashedKey}`;\n}\n"],"mappings":";;;;;AAOO,IAAM,sBAAsB;AAAA,EACjC,eAAe,CAAC,sBAAsB;AACxC;AAgBO,IAAM,2BAA2B,CACtC,SAC6B;AAC7B,QAAM,YAAY;AAElB,MAAI,CAAC,UAAU,KAAK,IAAI,GAAG;AACzB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM,CAAC,SAAS,GAAG,IAAI,KAAK,MAAM,GAAG;AAKrC,MAAI,EAAE,WAAW,sBAAsB;AACrC,UAAM,IAAI,MAAM,4CAA4C,OAAO,EAAE;AAAA,EACvE;AAEA,QAAM,eAAe,oBAAoB,OAAO;AAEhD,MAAI,CAAC,aAAa,SAAS,GAAG,GAAG;AAC/B,UAAM,YAAY,oBAAoB,OAAO,EAAE,KAAK,IAAI;AAExD,UAAM,IAAI;AAAA,MACR,yDAAyD,GAAG,iBAAiB,SAAS;AAAA,IACxF;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,IAAI;AACxB;AAWO,SAAS,gBACd,MACA,YACQ;AACR,QAAM,EAAE,SAAS,IAAI,IAAI,yBAAyB,IAAI;AACtD,QAAM,YAAY,iBAAiB,MAAM,UAAU;AAEnD,SAAO,IAAI,OAAO,IAAI,SAAS;AACjC;","names":[]} +\ No newline at end of file ++{"version":3,"sources":["../src/controllers/user-storage/schema.ts"],"sourcesContent":["import { createSHA256Hash } from './encryption';\n\n/**\n * The User Storage Endpoint requires a feature name and a namespace key.\n * Developers can provide additional features and keys by extending these types below.\n */\n\nexport const USER_STORAGE_SCHEMA = {\n notifications: ['notification_settings'],\n} as const;\n\ntype UserStorageSchema = typeof USER_STORAGE_SCHEMA;\ntype UserStorageFeatures = keyof UserStorageSchema;\ntype UserStorageFeatureKeys =\n UserStorageSchema[Feature][number];\n\ntype UserStorageFeatureAndKey = {\n feature: UserStorageFeatures;\n key: UserStorageFeatureKeys;\n};\n\nexport type UserStoragePath = {\n [K in keyof UserStorageSchema]: `${K}.${UserStorageSchema[K][number]}`;\n}[keyof UserStorageSchema];\n\nexport const getFeatureAndKeyFromPath = (\n path: UserStoragePath,\n): UserStorageFeatureAndKey => {\n const pathRegex = /^\\w+\\.\\w+$/u;\n\n if (!pathRegex.test(path)) {\n throw new Error(\n `user-storage - path is not in the correct format. Correct format: 'feature.key'`,\n );\n }\n\n const [feature, key] = path.split('.') as [\n UserStorageFeatures,\n UserStorageFeatureKeys,\n ];\n\n if (!(feature in USER_STORAGE_SCHEMA)) {\n throw new Error(`user-storage - invalid feature provided: ${feature}`);\n }\n\n const validFeature = USER_STORAGE_SCHEMA[feature] as readonly string[];\n\n if (!validFeature.includes(key)) {\n const validKeys = USER_STORAGE_SCHEMA[feature].join(', ');\n\n throw new Error(\n `user-storage - invalid key provided for this feature: ${key}. Valid keys: ${validKeys}`,\n );\n }\n\n return { feature, key };\n};\n\n/**\n * Constructs a unique entry path for a user.\n * This can be done due to the uniqueness of the storage key (no users will share the same storage key).\n * The users entry is a unique hash that cannot be reversed.\n *\n * @param path - string in the form of `${feature}.${key}` that matches schema\n * @param storageKey - users storage key\n * @returns path to store entry\n */\nexport function createEntryPath(\n path: UserStoragePath,\n storageKey: string,\n): string {\n const { feature, key } = getFeatureAndKeyFromPath(path);\n const hashedKey = createSHA256Hash(key + storageKey);\n\n return `/${feature}/${hashedKey}`;\n}\n"],"mappings":";;;;;AAOO,IAAM,sBAAsB;AAAA,EACjC,eAAe,CAAC,sBAAsB;AACxC;AAgBO,IAAM,2BAA2B,CACtC,SAC6B;AAC7B,QAAM,YAAY;AAElB,MAAI,CAAC,UAAU,KAAK,IAAI,GAAG;AACzB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM,CAAC,SAAS,GAAG,IAAI,KAAK,MAAM,GAAG;AAKrC,MAAI,EAAE,WAAW,sBAAsB;AACrC,UAAM,IAAI,MAAM,4CAA4C,OAAO,EAAE;AAAA,EACvE;AAEA,QAAM,eAAe,oBAAoB,OAAO;AAEhD,MAAI,CAAC,aAAa,SAAS,GAAG,GAAG;AAC/B,UAAM,YAAY,oBAAoB,OAAO,EAAE,KAAK,IAAI;AAExD,UAAM,IAAI;AAAA,MACR,yDAAyD,GAAG,iBAAiB,SAAS;AAAA,IACxF;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,IAAI;AACxB;AAWO,SAAS,gBACd,MACA,YACQ;AACR,QAAM,EAAE,SAAS,IAAI,IAAI,yBAAyB,IAAI;AACtD,QAAM,YAAY,iBAAiB,MAAM,UAAU;AAEnD,SAAO,IAAI,OAAO,IAAI,SAAS;AACjC;","names":[]} +\ No newline at end of file +diff --git a/dist/chunk-IQECLCH4.mjs b/dist/chunk-IQECLCH4.mjs +index fbfb0670fd8dd1c15cf1d70809a59ac10435af23..e4a5b1a4728a4d2dad7c1d64048d2a869b84874c 100644 +--- a/dist/chunk-IQECLCH4.mjs ++++ b/dist/chunk-IQECLCH4.mjs +@@ -1,23 +1,9 @@ +-import { +- UserStorageController, +- defaultState +-} from "./chunk-LNSB4L7K.mjs"; +-import { +- USER_STORAGE_ENDPOINT +-} from "./chunk-FU7PSGFP.mjs"; +-import { +- createEntryPath +-} from "./chunk-ILIZJQ6X.mjs"; +-import { +- encryption_default as encryption_default2 +-} from "./chunk-5TOWF6UW.mjs"; +-import { +- createSHA256Hash, +- encryption_default +-} from "./chunk-K5UKU454.mjs"; +-import { +- __export +-} from "./chunk-U5UIDVOO.mjs"; ++import { UserStorageController, defaultState } from "./chunk-LNSB4L7K.mjs"; ++import { USER_STORAGE_ENDPOINT } from "./chunk-FU7PSGFP.mjs"; ++import { createEntryPath } from "./chunk-ILIZJQ6X.mjs"; ++import { encryption_default as encryption_default2 } from "./chunk-5TOWF6UW.mjs"; ++import { createSHA256Hash, encryption_default } from "./chunk-K5UKU454.mjs"; ++import { __export } from "./chunk-U5UIDVOO.mjs"; + + // src/controllers/user-storage/index.ts + var user_storage_exports = {}; +@@ -26,7 +12,7 @@ __export(user_storage_exports, { + Encryption: () => encryption_default, + Mocks: () => fixtures_exports, + createSHA256Hash: () => createSHA256Hash, +- defaultState: () => defaultState ++ defaultState: () => defaultState, + }); + + // src/controllers/user-storage/__fixtures__/index.ts +@@ -36,9 +22,10 @@ __export(fixtures_exports, { + MOCK_STORAGE_DATA: () => MOCK_STORAGE_DATA, + MOCK_STORAGE_KEY: () => MOCK_STORAGE_KEY, + MOCK_STORAGE_KEY_SIGNATURE: () => MOCK_STORAGE_KEY_SIGNATURE, +- MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT: () => MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT, ++ MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT: () => ++ MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT, + getMockUserStorageGetResponse: () => getMockUserStorageGetResponse, +- getMockUserStoragePutResponse: () => getMockUserStoragePutResponse ++ getMockUserStoragePutResponse: () => getMockUserStoragePutResponse, + }); + + // src/controllers/user-storage/__fixtures__/mockStorage.ts +@@ -52,30 +39,27 @@ var MOCK_ENCRYPTED_STORAGE_DATA = encryption_default2.encryptString( + + // src/controllers/user-storage/__fixtures__/mockResponses.ts + var MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT = `${USER_STORAGE_ENDPOINT}${createEntryPath( +- "notifications.notificationSettings", ++ "notifications.notification_settings", + MOCK_STORAGE_KEY + )}`; + var MOCK_GET_USER_STORAGE_RESPONSE = { + HashedKey: "HASHED_KEY", +- Data: MOCK_ENCRYPTED_STORAGE_DATA ++ Data: MOCK_ENCRYPTED_STORAGE_DATA, + }; + var getMockUserStorageGetResponse = () => { + return { + url: MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT, + requestMethod: "GET", +- response: MOCK_GET_USER_STORAGE_RESPONSE ++ response: MOCK_GET_USER_STORAGE_RESPONSE, + }; + }; + var getMockUserStoragePutResponse = () => { + return { + url: MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT, + requestMethod: "PUT", +- response: null ++ response: null, + }; + }; + +-export { +- fixtures_exports, +- user_storage_exports +-}; ++export { fixtures_exports, user_storage_exports }; + //# sourceMappingURL=chunk-IQECLCH4.mjs.map +diff --git a/dist/chunk-IQECLCH4.mjs.map b/dist/chunk-IQECLCH4.mjs.map +index 431f888e4c0a529b7213d83381f50ad5657899f9..78bc07ced21dbad3da0a955cb0d0d9a49ac53cb6 100644 +--- a/dist/chunk-IQECLCH4.mjs.map ++++ b/dist/chunk-IQECLCH4.mjs.map +@@ -1 +1 @@ +-{"version":3,"sources":["../src/controllers/user-storage/index.ts","../src/controllers/user-storage/__fixtures__/index.ts","../src/controllers/user-storage/__fixtures__/mockStorage.ts","../src/controllers/user-storage/__fixtures__/mockResponses.ts"],"sourcesContent":["import Controller from './UserStorageController';\n\nexport { Controller };\nexport * from './UserStorageController';\nexport * from './encryption';\nexport * as Mocks from './__fixtures__';\n","export * from './mockResponses';\nexport * from './mockStorage';\n","import encryption, { createSHA256Hash } from '../encryption';\n\nexport const MOCK_STORAGE_KEY_SIGNATURE = 'mockStorageKey';\nexport const MOCK_STORAGE_KEY = createSHA256Hash(MOCK_STORAGE_KEY_SIGNATURE);\nexport const MOCK_STORAGE_DATA = JSON.stringify({ hello: 'world' });\nexport const MOCK_ENCRYPTED_STORAGE_DATA = encryption.encryptString(\n MOCK_STORAGE_DATA,\n MOCK_STORAGE_KEY,\n);\n","import { createEntryPath } from '../schema';\nimport type { GetUserStorageResponse } from '../services';\nimport { USER_STORAGE_ENDPOINT } from '../services';\nimport { MOCK_ENCRYPTED_STORAGE_DATA, MOCK_STORAGE_KEY } from './mockStorage';\n\ntype MockResponse = {\n url: string;\n requestMethod: 'GET' | 'POST' | 'PUT';\n response: unknown;\n};\n\nexport const MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT = `${USER_STORAGE_ENDPOINT}${createEntryPath(\n 'notifications.notificationSettings',\n MOCK_STORAGE_KEY,\n)}`;\n\nconst MOCK_GET_USER_STORAGE_RESPONSE: GetUserStorageResponse = {\n HashedKey: 'HASHED_KEY',\n Data: MOCK_ENCRYPTED_STORAGE_DATA,\n};\n\nexport const getMockUserStorageGetResponse = () => {\n return {\n url: MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT,\n requestMethod: 'GET',\n response: MOCK_GET_USER_STORAGE_RESPONSE,\n } satisfies MockResponse;\n};\n\nexport const getMockUserStoragePutResponse = () => {\n return {\n url: MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT,\n requestMethod: 'PUT',\n response: null,\n } satisfies MockResponse;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEO,IAAM,6BAA6B;AACnC,IAAM,mBAAmB,iBAAiB,0BAA0B;AACpE,IAAM,oBAAoB,KAAK,UAAU,EAAE,OAAO,QAAQ,CAAC;AAC3D,IAAM,8BAA8BA,oBAAW;AAAA,EACpD;AAAA,EACA;AACF;;;ACGO,IAAM,2CAA2C,GAAG,qBAAqB,GAAG;AAAA,EACjF;AAAA,EACA;AACF,CAAC;AAED,IAAM,iCAAyD;AAAA,EAC7D,WAAW;AAAA,EACX,MAAM;AACR;AAEO,IAAM,gCAAgC,MAAM;AACjD,SAAO;AAAA,IACL,KAAK;AAAA,IACL,eAAe;AAAA,IACf,UAAU;AAAA,EACZ;AACF;AAEO,IAAM,gCAAgC,MAAM;AACjD,SAAO;AAAA,IACL,KAAK;AAAA,IACL,eAAe;AAAA,IACf,UAAU;AAAA,EACZ;AACF;","names":["encryption_default"]} +\ No newline at end of file ++{"version":3,"sources":["../src/controllers/user-storage/index.ts","../src/controllers/user-storage/__fixtures__/index.ts","../src/controllers/user-storage/__fixtures__/mockStorage.ts","../src/controllers/user-storage/__fixtures__/mockResponses.ts"],"sourcesContent":["import Controller from './UserStorageController';\n\nexport { Controller };\nexport * from './UserStorageController';\nexport * from './encryption';\nexport * as Mocks from './__fixtures__';\n","export * from './mockResponses';\nexport * from './mockStorage';\n","import encryption, { createSHA256Hash } from '../encryption';\n\nexport const MOCK_STORAGE_KEY_SIGNATURE = 'mockStorageKey';\nexport const MOCK_STORAGE_KEY = createSHA256Hash(MOCK_STORAGE_KEY_SIGNATURE);\nexport const MOCK_STORAGE_DATA = JSON.stringify({ hello: 'world' });\nexport const MOCK_ENCRYPTED_STORAGE_DATA = encryption.encryptString(\n MOCK_STORAGE_DATA,\n MOCK_STORAGE_KEY,\n);\n","import { createEntryPath } from '../schema';\nimport type { GetUserStorageResponse } from '../services';\nimport { USER_STORAGE_ENDPOINT } from '../services';\nimport { MOCK_ENCRYPTED_STORAGE_DATA, MOCK_STORAGE_KEY } from './mockStorage';\n\ntype MockResponse = {\n url: string;\n requestMethod: 'GET' | 'POST' | 'PUT';\n response: unknown;\n};\n\nexport const MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT = `${USER_STORAGE_ENDPOINT}${createEntryPath(\n 'notifications.notification_settings',\n MOCK_STORAGE_KEY,\n)}`;\n\nconst MOCK_GET_USER_STORAGE_RESPONSE: GetUserStorageResponse = {\n HashedKey: 'HASHED_KEY',\n Data: MOCK_ENCRYPTED_STORAGE_DATA,\n};\n\nexport const getMockUserStorageGetResponse = () => {\n return {\n url: MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT,\n requestMethod: 'GET',\n response: MOCK_GET_USER_STORAGE_RESPONSE,\n } satisfies MockResponse;\n};\n\nexport const getMockUserStoragePutResponse = () => {\n return {\n url: MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT,\n requestMethod: 'PUT',\n response: null,\n } satisfies MockResponse;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEO,IAAM,6BAA6B;AACnC,IAAM,mBAAmB,iBAAiB,0BAA0B;AACpE,IAAM,oBAAoB,KAAK,UAAU,EAAE,OAAO,QAAQ,CAAC;AAC3D,IAAM,8BAA8BA,oBAAW;AAAA,EACpD;AAAA,EACA;AACF;;;ACGO,IAAM,2CAA2C,GAAG,qBAAqB,GAAG;AAAA,EACjF;AAAA,EACA;AACF,CAAC;AAED,IAAM,iCAAyD;AAAA,EAC7D,WAAW;AAAA,EACX,MAAM;AACR;AAEO,IAAM,gCAAgC,MAAM;AACjD,SAAO;AAAA,IACL,KAAK;AAAA,IACL,eAAe;AAAA,IACf,UAAU;AAAA,EACZ;AACF;AAEO,IAAM,gCAAgC,MAAM;AACjD,SAAO;AAAA,IACL,KAAK;AAAA,IACL,eAAe;AAAA,IACf,UAAU;AAAA,EACZ;AACF;","names":["encryption_default"]} +\ No newline at end of file +diff --git a/dist/chunk-SLHAVOTE.js b/dist/chunk-SLHAVOTE.js +index 74a4398618d0d0f6a9a6b57b299722df40ced840..f4765cb6110283c23a87685665a980faaaa84152 100644 +--- a/dist/chunk-SLHAVOTE.js ++++ b/dist/chunk-SLHAVOTE.js +@@ -1,10 +1,11 @@ +-"use strict";Object.defineProperty(exports, "__esModule", {value: true}); ++"use strict"; ++Object.defineProperty(exports, "__esModule", { value: true }); + +-var _chunkT3FNDVE3js = require('./chunk-T3FNDVE3.js'); ++var _chunkT3FNDVE3js = require("./chunk-T3FNDVE3.js"); + + // src/controllers/user-storage/schema.ts + var USER_STORAGE_SCHEMA = { +- notifications: ["notificationSettings"] ++ notifications: ["notification_settings"], + }; + var getFeatureAndKeyFromPath = (path) => { + const pathRegex = /^\w+\.\w+$/u; +@@ -28,13 +29,14 @@ var getFeatureAndKeyFromPath = (path) => { + }; + function createEntryPath(path, storageKey) { + const { feature, key } = getFeatureAndKeyFromPath(path); +- const hashedKey = _chunkT3FNDVE3js.createSHA256Hash.call(void 0, key + storageKey); ++ const hashedKey = _chunkT3FNDVE3js.createSHA256Hash.call( ++ void 0, ++ key + storageKey ++ ); + return `/${feature}/${hashedKey}`; + } + +- +- +- +- +-exports.USER_STORAGE_SCHEMA = USER_STORAGE_SCHEMA; exports.getFeatureAndKeyFromPath = getFeatureAndKeyFromPath; exports.createEntryPath = createEntryPath; ++exports.USER_STORAGE_SCHEMA = USER_STORAGE_SCHEMA; ++exports.getFeatureAndKeyFromPath = getFeatureAndKeyFromPath; ++exports.createEntryPath = createEntryPath; + //# sourceMappingURL=chunk-SLHAVOTE.js.map +diff --git a/dist/chunk-SLHAVOTE.js.map b/dist/chunk-SLHAVOTE.js.map +index 9c00f76f521bd1783bf4dc24300cc51b412761c8..ed723ce11446984f3db8cb9c41b42a592c57398e 100644 +--- a/dist/chunk-SLHAVOTE.js.map ++++ b/dist/chunk-SLHAVOTE.js.map +@@ -1 +1 @@ +-{"version":3,"sources":["../src/controllers/user-storage/schema.ts"],"names":[],"mappings":";;;;;AAOO,IAAM,sBAAsB;AAAA,EACjC,eAAe,CAAC,sBAAsB;AACxC;AAgBO,IAAM,2BAA2B,CACtC,SAC6B;AAC7B,QAAM,YAAY;AAElB,MAAI,CAAC,UAAU,KAAK,IAAI,GAAG;AACzB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM,CAAC,SAAS,GAAG,IAAI,KAAK,MAAM,GAAG;AAKrC,MAAI,EAAE,WAAW,sBAAsB;AACrC,UAAM,IAAI,MAAM,4CAA4C,OAAO,EAAE;AAAA,EACvE;AAEA,QAAM,eAAe,oBAAoB,OAAO;AAEhD,MAAI,CAAC,aAAa,SAAS,GAAG,GAAG;AAC/B,UAAM,YAAY,oBAAoB,OAAO,EAAE,KAAK,IAAI;AAExD,UAAM,IAAI;AAAA,MACR,yDAAyD,GAAG,iBAAiB,SAAS;AAAA,IACxF;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,IAAI;AACxB;AAWO,SAAS,gBACd,MACA,YACQ;AACR,QAAM,EAAE,SAAS,IAAI,IAAI,yBAAyB,IAAI;AACtD,QAAM,YAAY,iBAAiB,MAAM,UAAU;AAEnD,SAAO,IAAI,OAAO,IAAI,SAAS;AACjC","sourcesContent":["import { createSHA256Hash } from './encryption';\n\n/**\n * The User Storage Endpoint requires a feature name and a namespace key.\n * Developers can provide additional features and keys by extending these types below.\n */\n\nexport const USER_STORAGE_SCHEMA = {\n notifications: ['notificationSettings'],\n} as const;\n\ntype UserStorageSchema = typeof USER_STORAGE_SCHEMA;\ntype UserStorageFeatures = keyof UserStorageSchema;\ntype UserStorageFeatureKeys =\n UserStorageSchema[Feature][number];\n\ntype UserStorageFeatureAndKey = {\n feature: UserStorageFeatures;\n key: UserStorageFeatureKeys;\n};\n\nexport type UserStoragePath = {\n [K in keyof UserStorageSchema]: `${K}.${UserStorageSchema[K][number]}`;\n}[keyof UserStorageSchema];\n\nexport const getFeatureAndKeyFromPath = (\n path: UserStoragePath,\n): UserStorageFeatureAndKey => {\n const pathRegex = /^\\w+\\.\\w+$/u;\n\n if (!pathRegex.test(path)) {\n throw new Error(\n `user-storage - path is not in the correct format. Correct format: 'feature.key'`,\n );\n }\n\n const [feature, key] = path.split('.') as [\n UserStorageFeatures,\n UserStorageFeatureKeys,\n ];\n\n if (!(feature in USER_STORAGE_SCHEMA)) {\n throw new Error(`user-storage - invalid feature provided: ${feature}`);\n }\n\n const validFeature = USER_STORAGE_SCHEMA[feature] as readonly string[];\n\n if (!validFeature.includes(key)) {\n const validKeys = USER_STORAGE_SCHEMA[feature].join(', ');\n\n throw new Error(\n `user-storage - invalid key provided for this feature: ${key}. Valid keys: ${validKeys}`,\n );\n }\n\n return { feature, key };\n};\n\n/**\n * Constructs a unique entry path for a user.\n * This can be done due to the uniqueness of the storage key (no users will share the same storage key).\n * The users entry is a unique hash that cannot be reversed.\n *\n * @param path - string in the form of `${feature}.${key}` that matches schema\n * @param storageKey - users storage key\n * @returns path to store entry\n */\nexport function createEntryPath(\n path: UserStoragePath,\n storageKey: string,\n): string {\n const { feature, key } = getFeatureAndKeyFromPath(path);\n const hashedKey = createSHA256Hash(key + storageKey);\n\n return `/${feature}/${hashedKey}`;\n}\n"]} +\ No newline at end of file ++{"version":3,"sources":["../src/controllers/user-storage/schema.ts"],"names":[],"mappings":";;;;;AAOO,IAAM,sBAAsB;AAAA,EACjC,eAAe,CAAC,sBAAsB;AACxC;AAgBO,IAAM,2BAA2B,CACtC,SAC6B;AAC7B,QAAM,YAAY;AAElB,MAAI,CAAC,UAAU,KAAK,IAAI,GAAG;AACzB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM,CAAC,SAAS,GAAG,IAAI,KAAK,MAAM,GAAG;AAKrC,MAAI,EAAE,WAAW,sBAAsB;AACrC,UAAM,IAAI,MAAM,4CAA4C,OAAO,EAAE;AAAA,EACvE;AAEA,QAAM,eAAe,oBAAoB,OAAO;AAEhD,MAAI,CAAC,aAAa,SAAS,GAAG,GAAG;AAC/B,UAAM,YAAY,oBAAoB,OAAO,EAAE,KAAK,IAAI;AAExD,UAAM,IAAI;AAAA,MACR,yDAAyD,GAAG,iBAAiB,SAAS;AAAA,IACxF;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,IAAI;AACxB;AAWO,SAAS,gBACd,MACA,YACQ;AACR,QAAM,EAAE,SAAS,IAAI,IAAI,yBAAyB,IAAI;AACtD,QAAM,YAAY,iBAAiB,MAAM,UAAU;AAEnD,SAAO,IAAI,OAAO,IAAI,SAAS;AACjC","sourcesContent":["import { createSHA256Hash } from './encryption';\n\n/**\n * The User Storage Endpoint requires a feature name and a namespace key.\n * Developers can provide additional features and keys by extending these types below.\n */\n\nexport const USER_STORAGE_SCHEMA = {\n notifications: ['notification_settings'],\n} as const;\n\ntype UserStorageSchema = typeof USER_STORAGE_SCHEMA;\ntype UserStorageFeatures = keyof UserStorageSchema;\ntype UserStorageFeatureKeys =\n UserStorageSchema[Feature][number];\n\ntype UserStorageFeatureAndKey = {\n feature: UserStorageFeatures;\n key: UserStorageFeatureKeys;\n};\n\nexport type UserStoragePath = {\n [K in keyof UserStorageSchema]: `${K}.${UserStorageSchema[K][number]}`;\n}[keyof UserStorageSchema];\n\nexport const getFeatureAndKeyFromPath = (\n path: UserStoragePath,\n): UserStorageFeatureAndKey => {\n const pathRegex = /^\\w+\\.\\w+$/u;\n\n if (!pathRegex.test(path)) {\n throw new Error(\n `user-storage - path is not in the correct format. Correct format: 'feature.key'`,\n );\n }\n\n const [feature, key] = path.split('.') as [\n UserStorageFeatures,\n UserStorageFeatureKeys,\n ];\n\n if (!(feature in USER_STORAGE_SCHEMA)) {\n throw new Error(`user-storage - invalid feature provided: ${feature}`);\n }\n\n const validFeature = USER_STORAGE_SCHEMA[feature] as readonly string[];\n\n if (!validFeature.includes(key)) {\n const validKeys = USER_STORAGE_SCHEMA[feature].join(', ');\n\n throw new Error(\n `user-storage - invalid key provided for this feature: ${key}. Valid keys: ${validKeys}`,\n );\n }\n\n return { feature, key };\n};\n\n/**\n * Constructs a unique entry path for a user.\n * This can be done due to the uniqueness of the storage key (no users will share the same storage key).\n * The users entry is a unique hash that cannot be reversed.\n *\n * @param path - string in the form of `${feature}.${key}` that matches schema\n * @param storageKey - users storage key\n * @returns path to store entry\n */\nexport function createEntryPath(\n path: UserStoragePath,\n storageKey: string,\n): string {\n const { feature, key } = getFeatureAndKeyFromPath(path);\n const hashedKey = createSHA256Hash(key + storageKey);\n\n return `/${feature}/${hashedKey}`;\n}\n"]} +\ No newline at end of file +diff --git a/dist/types/controllers/user-storage/schema.d.ts b/dist/types/controllers/user-storage/schema.d.ts +index 0f7fa2b3224fcafc1e01869338a9b978a50a2b4e..311b0309b076dd1d0d05e59ff20845952bc917d9 100644 +--- a/dist/types/controllers/user-storage/schema.d.ts ++++ b/dist/types/controllers/user-storage/schema.d.ts +@@ -3,7 +3,7 @@ + * Developers can provide additional features and keys by extending these types below. + */ + export declare const USER_STORAGE_SCHEMA: { +- readonly notifications: readonly ["notificationSettings"]; ++ readonly notifications: readonly ["notification_settings"]; + }; + type UserStorageSchema = typeof USER_STORAGE_SCHEMA; + type UserStorageFeatures = keyof UserStorageSchema; diff --git a/.yarn/patches/@metamask-selected-network-controller-npm-13.0.0-b0f6db473a.patch b/.yarn/patches/@metamask-selected-network-controller-npm-13.0.0-b0f6db473a.patch deleted file mode 100644 index 4fddeda9cc57..000000000000 --- a/.yarn/patches/@metamask-selected-network-controller-npm-13.0.0-b0f6db473a.patch +++ /dev/null @@ -1,1469 +0,0 @@ -diff --git a/dist/SelectedNetworkController.js b/dist/SelectedNetworkController.js -index ca967cd382ba810dadd7ffa914d5a8aceb6f156a..4b4103f6dd6c13e72956daa9557d623ec2c832b6 100644 ---- a/dist/SelectedNetworkController.js -+++ b/dist/SelectedNetworkController.js -@@ -4,12 +4,12 @@ - - - --var _chunkOGUVGN6Rjs = require('./chunk-OGUVGN6R.js'); -+var _chunkCECZWJ42js = require('./chunk-CECZWJ42.js'); - - - - - - --exports.METAMASK_DOMAIN = _chunkOGUVGN6Rjs.METAMASK_DOMAIN; exports.SelectedNetworkController = _chunkOGUVGN6Rjs.SelectedNetworkController; exports.SelectedNetworkControllerActionTypes = _chunkOGUVGN6Rjs.SelectedNetworkControllerActionTypes; exports.SelectedNetworkControllerEventTypes = _chunkOGUVGN6Rjs.SelectedNetworkControllerEventTypes; exports.controllerName = _chunkOGUVGN6Rjs.controllerName; -+exports.METAMASK_DOMAIN = _chunkCECZWJ42js.METAMASK_DOMAIN; exports.SelectedNetworkController = _chunkCECZWJ42js.SelectedNetworkController; exports.SelectedNetworkControllerActionTypes = _chunkCECZWJ42js.SelectedNetworkControllerActionTypes; exports.SelectedNetworkControllerEventTypes = _chunkCECZWJ42js.SelectedNetworkControllerEventTypes; exports.controllerName = _chunkCECZWJ42js.controllerName; - //# sourceMappingURL=SelectedNetworkController.js.map -\ No newline at end of file -diff --git a/dist/SelectedNetworkController.mjs b/dist/SelectedNetworkController.mjs -index 5228bbe7acb3da6abb826f00670a1034c51a0514..d4b1f0cf8d8e6123df56be0b766e285968f8587c 100644 ---- a/dist/SelectedNetworkController.mjs -+++ b/dist/SelectedNetworkController.mjs -@@ -4,7 +4,7 @@ import { - SelectedNetworkControllerActionTypes, - SelectedNetworkControllerEventTypes, - controllerName --} from "./chunk-S4D42VCM.mjs"; -+} from "./chunk-7DSTEJNI.mjs"; - export { - METAMASK_DOMAIN, - SelectedNetworkController, -diff --git a/dist/SelectedNetworkMiddleware.js b/dist/SelectedNetworkMiddleware.js -index 919f26ef57e8c6af5c63d1e0c8e85c43ec241e4a..0b9e434874a1ead6b596fa933597145ee40362d2 100644 ---- a/dist/SelectedNetworkMiddleware.js -+++ b/dist/SelectedNetworkMiddleware.js -@@ -1,8 +1,8 @@ - "use strict";Object.defineProperty(exports, "__esModule", {value: true}); - --var _chunk6W2ETVOHjs = require('./chunk-6W2ETVOH.js'); --require('./chunk-OGUVGN6R.js'); -+var _chunkANSSZMDIjs = require('./chunk-ANSSZMDI.js'); -+require('./chunk-CECZWJ42.js'); - - --exports.createSelectedNetworkMiddleware = _chunk6W2ETVOHjs.createSelectedNetworkMiddleware; -+exports.createSelectedNetworkMiddleware = _chunkANSSZMDIjs.createSelectedNetworkMiddleware; - //# sourceMappingURL=SelectedNetworkMiddleware.js.map -\ No newline at end of file -diff --git a/dist/SelectedNetworkMiddleware.mjs b/dist/SelectedNetworkMiddleware.mjs -index a9031b4aa2589009c2f23d03f1d23a01044c4178..c3302c19f429473963d663e87340b33d9b2caf98 100644 ---- a/dist/SelectedNetworkMiddleware.mjs -+++ b/dist/SelectedNetworkMiddleware.mjs -@@ -1,7 +1,7 @@ - import { - createSelectedNetworkMiddleware --} from "./chunk-ZY7ETPVE.mjs"; --import "./chunk-S4D42VCM.mjs"; -+} from "./chunk-HFN7TKJS.mjs"; -+import "./chunk-7DSTEJNI.mjs"; - export { - createSelectedNetworkMiddleware - }; -diff --git a/dist/chunk-6W2ETVOH.js b/dist/chunk-6W2ETVOH.js -deleted file mode 100644 -index 9714addd232767e296dd378a5cc0d57cafc9b882..0000000000000000000000000000000000000000 ---- a/dist/chunk-6W2ETVOH.js -+++ /dev/null -@@ -1,23 +0,0 @@ --"use strict";Object.defineProperty(exports, "__esModule", {value: true}); -- --var _chunkOGUVGN6Rjs = require('./chunk-OGUVGN6R.js'); -- --// src/SelectedNetworkMiddleware.ts --var createSelectedNetworkMiddleware = (messenger) => { -- const getNetworkClientIdForDomain = (origin) => messenger.call( -- _chunkOGUVGN6Rjs.SelectedNetworkControllerActionTypes.getNetworkClientIdForDomain, -- origin -- ); -- return (req, _, next) => { -- if (!req.origin) { -- throw new Error("Request object is lacking an 'origin'"); -- } -- req.networkClientId = getNetworkClientIdForDomain(req.origin); -- return next(); -- }; --}; -- -- -- --exports.createSelectedNetworkMiddleware = createSelectedNetworkMiddleware; --//# sourceMappingURL=chunk-6W2ETVOH.js.map -\ No newline at end of file -diff --git a/dist/chunk-6W2ETVOH.js.map b/dist/chunk-6W2ETVOH.js.map -deleted file mode 100644 -index 9fe4c1fd6e9b12bfd9091833618334f2c931098f..0000000000000000000000000000000000000000 ---- a/dist/chunk-6W2ETVOH.js.map -+++ /dev/null -@@ -1 +0,0 @@ --{"version":3,"sources":["../src/SelectedNetworkMiddleware.ts"],"names":[],"mappings":";;;;;AAYO,IAAM,kCAAkC,CAC7C,cAC2C;AAC3C,QAAM,8BAA8B,CAAC,WACnC,UAAU;AAAA,IACR,qCAAqC;AAAA,IACrC;AAAA,EACF;AAEF,SAAO,CAAC,KAA8C,GAAG,SAAS;AAChE,QAAI,CAAC,IAAI,QAAQ;AACf,YAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AAEA,QAAI,kBAAkB,4BAA4B,IAAI,MAAM;AAC5D,WAAO,KAAK;AAAA,EACd;AACF","sourcesContent":["import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine';\nimport type { NetworkClientId } from '@metamask/network-controller';\nimport type { Json, JsonRpcParams, JsonRpcRequest } from '@metamask/utils';\n\nimport type { SelectedNetworkControllerMessenger } from './SelectedNetworkController';\nimport { SelectedNetworkControllerActionTypes } from './SelectedNetworkController';\n\nexport type SelectedNetworkMiddlewareJsonRpcRequest = JsonRpcRequest & {\n networkClientId?: NetworkClientId;\n origin?: string;\n};\n\nexport const createSelectedNetworkMiddleware = (\n messenger: SelectedNetworkControllerMessenger,\n): JsonRpcMiddleware => {\n const getNetworkClientIdForDomain = (origin: string) =>\n messenger.call(\n SelectedNetworkControllerActionTypes.getNetworkClientIdForDomain,\n origin,\n );\n\n return (req: SelectedNetworkMiddlewareJsonRpcRequest, _, next) => {\n if (!req.origin) {\n throw new Error(\"Request object is lacking an 'origin'\");\n }\n\n req.networkClientId = getNetworkClientIdForDomain(req.origin);\n return next();\n };\n};\n"]} -\ No newline at end of file -diff --git a/dist/chunk-7DSTEJNI.mjs b/dist/chunk-7DSTEJNI.mjs -new file mode 100644 -index 0000000000000000000000000000000000000000..1902ab28e3c37d563704840e31fdbc885db5354c ---- /dev/null -+++ b/dist/chunk-7DSTEJNI.mjs -@@ -0,0 +1,284 @@ -+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/SelectedNetworkController.ts -+import { BaseController } from "@metamask/base-controller"; -+import { createEventEmitterProxy } from "@metamask/swappable-obj-proxy"; -+var controllerName = "SelectedNetworkController"; -+var stateMetadata = { -+ domains: { persist: true, anonymous: false } -+}; -+var getDefaultState = () => ({ domains: {} }); -+var snapsPrefixes = ["npm:", "local:"]; -+var METAMASK_DOMAIN = "metamask"; -+var SelectedNetworkControllerActionTypes = { -+ getState: `${controllerName}:getState`, -+ getNetworkClientIdForDomain: `${controllerName}:getNetworkClientIdForDomain`, -+ setNetworkClientIdForDomain: `${controllerName}:setNetworkClientIdForDomain` -+}; -+var SelectedNetworkControllerEventTypes = { -+ stateChange: `${controllerName}:stateChange` -+}; -+var _domainProxyMap, _useRequestQueuePreference, _registerMessageHandlers, registerMessageHandlers_fn, _setNetworkClientIdForDomain, setNetworkClientIdForDomain_fn, _unsetNetworkClientIdForDomain, unsetNetworkClientIdForDomain_fn, _domainHasPermissions, domainHasPermissions_fn, _resetAllPermissionedDomains, resetAllPermissionedDomains_fn; -+var SelectedNetworkController = class extends BaseController { -+ /** -+ * Construct a SelectedNetworkController controller. -+ * -+ * @param options - The controller options. -+ * @param options.messenger - The restricted controller messenger for the EncryptionPublicKey controller. -+ * @param options.state - The controllers initial state. -+ * @param options.useRequestQueuePreference - A boolean indicating whether to use the request queue preference. -+ * @param options.onPreferencesStateChange - A callback that is called when the preference state changes. -+ * @param options.domainProxyMap - A map for storing domain-specific proxies that are held in memory only during use. -+ */ -+ constructor({ -+ messenger, -+ state = getDefaultState(), -+ useRequestQueuePreference, -+ onPreferencesStateChange, -+ domainProxyMap -+ }) { -+ super({ -+ name: controllerName, -+ metadata: stateMetadata, -+ messenger, -+ state -+ }); -+ __privateAdd(this, _registerMessageHandlers); -+ __privateAdd(this, _setNetworkClientIdForDomain); -+ /** -+ * This method is used when a domain is removed from the PermissionsController. -+ * It will remove re-point the network proxy to the globally selected network in the domainProxyMap or, if no globally selected network client is available, delete the proxy. -+ * -+ * @param domain - The domain for which to unset the network client ID. -+ */ -+ __privateAdd(this, _unsetNetworkClientIdForDomain); -+ __privateAdd(this, _domainHasPermissions); -+ // Loop through all domains and for those with permissions it points that domain's proxy -+ // to an unproxied instance of the globally selected network client. -+ // NOT the NetworkController's proxy of the globally selected networkClient -+ __privateAdd(this, _resetAllPermissionedDomains); -+ __privateAdd(this, _domainProxyMap, void 0); -+ __privateAdd(this, _useRequestQueuePreference, void 0); -+ __privateSet(this, _useRequestQueuePreference, useRequestQueuePreference); -+ __privateSet(this, _domainProxyMap, domainProxyMap); -+ __privateMethod(this, _registerMessageHandlers, registerMessageHandlers_fn).call(this); -+ this.messagingSystem.call("PermissionController:getSubjectNames").filter((domain) => this.state.domains[domain] === void 0).forEach( -+ (domain) => this.setNetworkClientIdForDomain( -+ domain, -+ this.messagingSystem.call("NetworkController:getState").selectedNetworkClientId -+ ) -+ ); -+ this.messagingSystem.subscribe( -+ "PermissionController:stateChange", -+ (_, patches) => { -+ patches.forEach(({ op, path }) => { -+ const isChangingSubject = path[0] === "subjects" && path[1] !== void 0; -+ if (isChangingSubject && typeof path[1] === "string") { -+ const domain = path[1]; -+ if (op === "add" && this.state.domains[domain] === void 0) { -+ this.setNetworkClientIdForDomain( -+ domain, -+ this.messagingSystem.call("NetworkController:getState").selectedNetworkClientId -+ ); -+ } else if (op === "remove" && this.state.domains[domain] !== void 0) { -+ __privateMethod(this, _unsetNetworkClientIdForDomain, unsetNetworkClientIdForDomain_fn).call(this, domain); -+ } -+ } -+ }); -+ } -+ ); -+ this.messagingSystem.subscribe( -+ "NetworkController:stateChange", -+ ({ selectedNetworkClientId }, patches) => { -+ patches.forEach(({ op, path }) => { -+ if (op === "remove" && path[0] === "networkConfigurations") { -+ const removedNetworkClientId = path[1]; -+ Object.entries(this.state.domains).forEach( -+ ([domain, networkClientIdForDomain]) => { -+ if (networkClientIdForDomain === removedNetworkClientId) { -+ this.setNetworkClientIdForDomain( -+ domain, -+ selectedNetworkClientId -+ ); -+ } -+ } -+ ); -+ } -+ }); -+ } -+ ); -+ onPreferencesStateChange(({ useRequestQueue }) => { -+ if (__privateGet(this, _useRequestQueuePreference) !== useRequestQueue) { -+ if (!useRequestQueue) { -+ Object.keys(this.state.domains).forEach((domain) => { -+ __privateMethod(this, _unsetNetworkClientIdForDomain, unsetNetworkClientIdForDomain_fn).call(this, domain); -+ }); -+ } else { -+ __privateMethod(this, _resetAllPermissionedDomains, resetAllPermissionedDomains_fn).call(this); -+ } -+ __privateSet(this, _useRequestQueuePreference, useRequestQueue); -+ } -+ }); -+ } -+ setNetworkClientIdForDomain(domain, networkClientId) { -+ if (!__privateGet(this, _useRequestQueuePreference)) { -+ return; -+ } -+ if (domain === METAMASK_DOMAIN) { -+ throw new Error( -+ `NetworkClientId for domain "${METAMASK_DOMAIN}" cannot be set on the SelectedNetworkController` -+ ); -+ } -+ if (snapsPrefixes.some((prefix) => domain.startsWith(prefix))) { -+ return; -+ } -+ if (!__privateMethod(this, _domainHasPermissions, domainHasPermissions_fn).call(this, domain)) { -+ throw new Error( -+ "NetworkClientId for domain cannot be called with a domain that has not yet been granted permissions" -+ ); -+ } -+ __privateMethod(this, _setNetworkClientIdForDomain, setNetworkClientIdForDomain_fn).call(this, domain, networkClientId); -+ } -+ getNetworkClientIdForDomain(domain) { -+ const { selectedNetworkClientId: metamaskSelectedNetworkClientId } = this.messagingSystem.call("NetworkController:getState"); -+ if (!__privateGet(this, _useRequestQueuePreference)) { -+ return metamaskSelectedNetworkClientId; -+ } -+ return this.state.domains[domain] ?? metamaskSelectedNetworkClientId; -+ } -+ /** -+ * Accesses the provider and block tracker for the currently selected network. -+ * -+ * @param domain - the domain for the provider -+ * @returns The proxy and block tracker proxies. -+ */ -+ getProviderAndBlockTracker(domain) { -+ if (domain === METAMASK_DOMAIN || snapsPrefixes.some((prefix) => domain.startsWith(prefix))) { -+ const networkClient = this.messagingSystem.call( -+ "NetworkController:getSelectedNetworkClient" -+ ); -+ if (networkClient === void 0) { -+ throw new Error("Selected network not initialized"); -+ } -+ return networkClient; -+ } -+ let networkProxy = __privateGet(this, _domainProxyMap).get(domain); -+ if (networkProxy === void 0) { -+ let networkClient; -+ if (__privateGet(this, _useRequestQueuePreference) && __privateMethod(this, _domainHasPermissions, domainHasPermissions_fn).call(this, domain)) { -+ const networkClientId = this.getNetworkClientIdForDomain(domain); -+ networkClient = this.messagingSystem.call( -+ "NetworkController:getNetworkClientById", -+ networkClientId -+ ); -+ } else { -+ networkClient = this.messagingSystem.call( -+ "NetworkController:getSelectedNetworkClient" -+ ); -+ if (networkClient === void 0) { -+ throw new Error("Selected network not initialized"); -+ } -+ } -+ networkProxy = { -+ provider: createEventEmitterProxy(networkClient.provider), -+ blockTracker: createEventEmitterProxy(networkClient.blockTracker, { -+ eventFilter: "skipInternal" -+ }) -+ }; -+ __privateGet(this, _domainProxyMap).set(domain, networkProxy); -+ } -+ return networkProxy; -+ } -+}; -+_domainProxyMap = new WeakMap(); -+_useRequestQueuePreference = new WeakMap(); -+_registerMessageHandlers = new WeakSet(); -+registerMessageHandlers_fn = function() { -+ this.messagingSystem.registerActionHandler( -+ SelectedNetworkControllerActionTypes.getNetworkClientIdForDomain, -+ this.getNetworkClientIdForDomain.bind(this) -+ ); -+ this.messagingSystem.registerActionHandler( -+ SelectedNetworkControllerActionTypes.setNetworkClientIdForDomain, -+ this.setNetworkClientIdForDomain.bind(this) -+ ); -+}; -+_setNetworkClientIdForDomain = new WeakSet(); -+setNetworkClientIdForDomain_fn = function(domain, networkClientId) { -+ const networkClient = this.messagingSystem.call( -+ "NetworkController:getNetworkClientById", -+ networkClientId -+ ); -+ const networkProxy = this.getProviderAndBlockTracker(domain); -+ networkProxy.provider.setTarget(networkClient.provider); -+ networkProxy.blockTracker.setTarget(networkClient.blockTracker); -+ this.update((state) => { -+ state.domains[domain] = networkClientId; -+ }); -+}; -+_unsetNetworkClientIdForDomain = new WeakSet(); -+unsetNetworkClientIdForDomain_fn = function(domain) { -+ const globallySelectedNetworkClient = this.messagingSystem.call( -+ "NetworkController:getSelectedNetworkClient" -+ ); -+ const networkProxy = __privateGet(this, _domainProxyMap).get(domain); -+ if (networkProxy && globallySelectedNetworkClient) { -+ networkProxy.provider.setTarget(globallySelectedNetworkClient.provider); -+ networkProxy.blockTracker.setTarget( -+ globallySelectedNetworkClient.blockTracker -+ ); -+ } else if (networkProxy) { -+ __privateGet(this, _domainProxyMap).delete(domain); -+ } -+ this.update((state) => { -+ delete state.domains[domain]; -+ }); -+}; -+_domainHasPermissions = new WeakSet(); -+domainHasPermissions_fn = function(domain) { -+ return this.messagingSystem.call( -+ "PermissionController:hasPermissions", -+ domain -+ ); -+}; -+_resetAllPermissionedDomains = new WeakSet(); -+resetAllPermissionedDomains_fn = function() { -+ __privateGet(this, _domainProxyMap).forEach((_, domain) => { -+ const { selectedNetworkClientId } = this.messagingSystem.call( -+ "NetworkController:getState" -+ ); -+ if (__privateMethod(this, _domainHasPermissions, domainHasPermissions_fn).call(this, domain)) { -+ __privateMethod(this, _setNetworkClientIdForDomain, setNetworkClientIdForDomain_fn).call(this, domain, selectedNetworkClientId); -+ } -+ }); -+}; -+ -+export { -+ controllerName, -+ METAMASK_DOMAIN, -+ SelectedNetworkControllerActionTypes, -+ SelectedNetworkControllerEventTypes, -+ SelectedNetworkController -+}; -+//# sourceMappingURL=chunk-7DSTEJNI.mjs.map -\ No newline at end of file -diff --git a/dist/chunk-7DSTEJNI.mjs.map b/dist/chunk-7DSTEJNI.mjs.map -new file mode 100644 -index 0000000000000000000000000000000000000000..33fde9b65d7c89592b3754333e6c1f62a0d90f63 ---- /dev/null -+++ b/dist/chunk-7DSTEJNI.mjs.map -@@ -0,0 +1 @@ -+{"version":3,"sources":["../src/SelectedNetworkController.ts"],"sourcesContent":["import type { RestrictedControllerMessenger } from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type {\n BlockTrackerProxy,\n NetworkClientId,\n NetworkControllerGetNetworkClientByIdAction,\n NetworkControllerGetSelectedNetworkClientAction,\n NetworkControllerGetStateAction,\n NetworkControllerStateChangeEvent,\n ProviderProxy,\n} from '@metamask/network-controller';\nimport type {\n PermissionControllerStateChange,\n GetSubjects as PermissionControllerGetSubjectsAction,\n HasPermissions as PermissionControllerHasPermissions,\n} from '@metamask/permission-controller';\nimport { createEventEmitterProxy } from '@metamask/swappable-obj-proxy';\nimport type { Patch } from 'immer';\n\nexport const controllerName = 'SelectedNetworkController';\n\nconst stateMetadata = {\n domains: { persist: true, anonymous: false },\n};\n\nconst getDefaultState = () => ({ domains: {} });\n\n// npm and local are currently the only valid prefixes for snap domains\n// TODO: eventually we maybe want to pull this in from snaps-utils to ensure it stays in sync\n// For now it seems like overkill to add a dependency for this one constant\n// https://github.com/MetaMask/snaps/blob/2beee7803bfe9e540788a3558b546b9f55dc3cb4/packages/snaps-utils/src/types.ts#L120\nconst snapsPrefixes = ['npm:', 'local:'] as const;\n\nexport type Domain = string;\n\nexport const METAMASK_DOMAIN = 'metamask' as const;\n\nexport const SelectedNetworkControllerActionTypes = {\n getState: `${controllerName}:getState` as const,\n getNetworkClientIdForDomain:\n `${controllerName}:getNetworkClientIdForDomain` as const,\n setNetworkClientIdForDomain:\n `${controllerName}:setNetworkClientIdForDomain` as const,\n};\n\nexport const SelectedNetworkControllerEventTypes = {\n stateChange: `${controllerName}:stateChange` as const,\n};\n\nexport type SelectedNetworkControllerState = {\n domains: Record;\n};\n\nexport type SelectedNetworkControllerStateChangeEvent = {\n type: typeof SelectedNetworkControllerEventTypes.stateChange;\n payload: [SelectedNetworkControllerState, Patch[]];\n};\n\nexport type SelectedNetworkControllerGetSelectedNetworkStateAction = {\n type: typeof SelectedNetworkControllerActionTypes.getState;\n handler: () => SelectedNetworkControllerState;\n};\n\nexport type SelectedNetworkControllerGetNetworkClientIdForDomainAction = {\n type: typeof SelectedNetworkControllerActionTypes.getNetworkClientIdForDomain;\n handler: SelectedNetworkController['getNetworkClientIdForDomain'];\n};\n\nexport type SelectedNetworkControllerSetNetworkClientIdForDomainAction = {\n type: typeof SelectedNetworkControllerActionTypes.setNetworkClientIdForDomain;\n handler: SelectedNetworkController['setNetworkClientIdForDomain'];\n};\n\nexport type SelectedNetworkControllerActions =\n | SelectedNetworkControllerGetSelectedNetworkStateAction\n | SelectedNetworkControllerGetNetworkClientIdForDomainAction\n | SelectedNetworkControllerSetNetworkClientIdForDomainAction;\n\nexport type AllowedActions =\n | NetworkControllerGetNetworkClientByIdAction\n | NetworkControllerGetSelectedNetworkClientAction\n | NetworkControllerGetStateAction\n | PermissionControllerHasPermissions\n | PermissionControllerGetSubjectsAction;\n\nexport type SelectedNetworkControllerEvents =\n SelectedNetworkControllerStateChangeEvent;\n\nexport type AllowedEvents =\n | NetworkControllerStateChangeEvent\n | PermissionControllerStateChange;\n\nexport type SelectedNetworkControllerMessenger = RestrictedControllerMessenger<\n typeof controllerName,\n SelectedNetworkControllerActions | AllowedActions,\n SelectedNetworkControllerEvents | AllowedEvents,\n AllowedActions['type'],\n AllowedEvents['type']\n>;\n\nexport type SelectedNetworkControllerOptions = {\n state?: SelectedNetworkControllerState;\n messenger: SelectedNetworkControllerMessenger;\n useRequestQueuePreference: boolean;\n onPreferencesStateChange: (\n listener: (preferencesState: { useRequestQueue: boolean }) => void,\n ) => void;\n domainProxyMap: Map;\n};\n\nexport type NetworkProxy = {\n provider: ProviderProxy;\n blockTracker: BlockTrackerProxy;\n};\n\n/**\n * Controller for getting and setting the network for a particular domain.\n */\nexport class SelectedNetworkController extends BaseController<\n typeof controllerName,\n SelectedNetworkControllerState,\n SelectedNetworkControllerMessenger\n> {\n #domainProxyMap: Map;\n\n #useRequestQueuePreference: boolean;\n\n /**\n * Construct a SelectedNetworkController controller.\n *\n * @param options - The controller options.\n * @param options.messenger - The restricted controller messenger for the EncryptionPublicKey controller.\n * @param options.state - The controllers initial state.\n * @param options.useRequestQueuePreference - A boolean indicating whether to use the request queue preference.\n * @param options.onPreferencesStateChange - A callback that is called when the preference state changes.\n * @param options.domainProxyMap - A map for storing domain-specific proxies that are held in memory only during use.\n */\n constructor({\n messenger,\n state = getDefaultState(),\n useRequestQueuePreference,\n onPreferencesStateChange,\n domainProxyMap,\n }: SelectedNetworkControllerOptions) {\n super({\n name: controllerName,\n metadata: stateMetadata,\n messenger,\n state,\n });\n this.#useRequestQueuePreference = useRequestQueuePreference;\n this.#domainProxyMap = domainProxyMap;\n this.#registerMessageHandlers();\n\n // this is fetching all the dapp permissions from the PermissionsController and looking for any domains that are not in domains state in this controller. Then we take any missing domains and add them to state here, setting it with the globally selected networkClientId (fetched from the NetworkController)\n this.messagingSystem\n .call('PermissionController:getSubjectNames')\n .filter((domain) => this.state.domains[domain] === undefined)\n .forEach((domain) =>\n this.setNetworkClientIdForDomain(\n domain,\n this.messagingSystem.call('NetworkController:getState')\n .selectedNetworkClientId,\n ),\n );\n\n this.messagingSystem.subscribe(\n 'PermissionController:stateChange',\n (_, patches) => {\n patches.forEach(({ op, path }) => {\n const isChangingSubject =\n path[0] === 'subjects' && path[1] !== undefined;\n if (isChangingSubject && typeof path[1] === 'string') {\n const domain = path[1];\n if (op === 'add' && this.state.domains[domain] === undefined) {\n this.setNetworkClientIdForDomain(\n domain,\n this.messagingSystem.call('NetworkController:getState')\n .selectedNetworkClientId,\n );\n } else if (\n op === 'remove' &&\n this.state.domains[domain] !== undefined\n ) {\n this.#unsetNetworkClientIdForDomain(domain);\n }\n }\n });\n },\n );\n\n this.messagingSystem.subscribe(\n 'NetworkController:stateChange',\n ({ selectedNetworkClientId }, patches) => {\n patches.forEach(({ op, path }) => {\n // if a network is removed, update the networkClientId for all domains that were using it to the selected network\n if (op === 'remove' && path[0] === 'networkConfigurations') {\n const removedNetworkClientId = path[1] as NetworkClientId;\n Object.entries(this.state.domains).forEach(\n ([domain, networkClientIdForDomain]) => {\n if (networkClientIdForDomain === removedNetworkClientId) {\n this.setNetworkClientIdForDomain(\n domain,\n selectedNetworkClientId,\n );\n }\n },\n );\n }\n });\n },\n );\n\n onPreferencesStateChange(({ useRequestQueue }) => {\n if (this.#useRequestQueuePreference !== useRequestQueue) {\n if (!useRequestQueue) {\n // Loop through all domains and points each domain's proxy\n // to the NetworkController's own proxy of the globally selected networkClient\n Object.keys(this.state.domains).forEach((domain) => {\n this.#unsetNetworkClientIdForDomain(domain);\n });\n } else {\n this.#resetAllPermissionedDomains();\n }\n this.#useRequestQueuePreference = useRequestQueue;\n }\n });\n }\n\n #registerMessageHandlers(): void {\n this.messagingSystem.registerActionHandler(\n SelectedNetworkControllerActionTypes.getNetworkClientIdForDomain,\n this.getNetworkClientIdForDomain.bind(this),\n );\n this.messagingSystem.registerActionHandler(\n SelectedNetworkControllerActionTypes.setNetworkClientIdForDomain,\n this.setNetworkClientIdForDomain.bind(this),\n );\n }\n\n #setNetworkClientIdForDomain(\n domain: Domain,\n networkClientId: NetworkClientId,\n ) {\n const networkClient = this.messagingSystem.call(\n 'NetworkController:getNetworkClientById',\n networkClientId,\n );\n const networkProxy = this.getProviderAndBlockTracker(domain);\n networkProxy.provider.setTarget(networkClient.provider);\n networkProxy.blockTracker.setTarget(networkClient.blockTracker);\n\n this.update((state) => {\n state.domains[domain] = networkClientId;\n });\n }\n\n /**\n * This method is used when a domain is removed from the PermissionsController.\n * It will remove re-point the network proxy to the globally selected network in the domainProxyMap or, if no globally selected network client is available, delete the proxy.\n *\n * @param domain - The domain for which to unset the network client ID.\n */\n #unsetNetworkClientIdForDomain(domain: Domain) {\n const globallySelectedNetworkClient = this.messagingSystem.call(\n 'NetworkController:getSelectedNetworkClient',\n );\n const networkProxy = this.#domainProxyMap.get(domain);\n if (networkProxy && globallySelectedNetworkClient) {\n networkProxy.provider.setTarget(globallySelectedNetworkClient.provider);\n networkProxy.blockTracker.setTarget(\n globallySelectedNetworkClient.blockTracker,\n );\n } else if (networkProxy) {\n this.#domainProxyMap.delete(domain);\n }\n this.update((state) => {\n delete state.domains[domain];\n });\n }\n\n #domainHasPermissions(domain: Domain): boolean {\n return this.messagingSystem.call(\n 'PermissionController:hasPermissions',\n domain,\n );\n }\n\n // Loop through all domains and for those with permissions it points that domain's proxy\n // to an unproxied instance of the globally selected network client.\n // NOT the NetworkController's proxy of the globally selected networkClient\n #resetAllPermissionedDomains() {\n this.#domainProxyMap.forEach((_: NetworkProxy, domain: string) => {\n const { selectedNetworkClientId } = this.messagingSystem.call(\n 'NetworkController:getState',\n );\n // can't use public setNetworkClientIdForDomain because it will throw an error\n // rather than simply skip if the domain doesn't have permissions which can happen\n // in this case since proxies are added for each site the user visits\n if (this.#domainHasPermissions(domain)) {\n this.#setNetworkClientIdForDomain(domain, selectedNetworkClientId);\n }\n });\n }\n\n setNetworkClientIdForDomain(\n domain: Domain,\n networkClientId: NetworkClientId,\n ) {\n // Core PR: https://github.com/MetaMask/core/pull/4388\n // Patch Branch: patch-selected-network-controller-13.0.0-setNetworkClient-guard\n if (!this.#useRequestQueuePreference) {\n return;\n }\n if (domain === METAMASK_DOMAIN) {\n throw new Error(\n `NetworkClientId for domain \"${METAMASK_DOMAIN}\" cannot be set on the SelectedNetworkController`,\n );\n }\n\n if (snapsPrefixes.some((prefix) => domain.startsWith(prefix))) {\n return;\n }\n\n if (!this.#domainHasPermissions(domain)) {\n throw new Error(\n 'NetworkClientId for domain cannot be called with a domain that has not yet been granted permissions',\n );\n }\n\n this.#setNetworkClientIdForDomain(domain, networkClientId);\n }\n\n getNetworkClientIdForDomain(domain: Domain): NetworkClientId {\n const { selectedNetworkClientId: metamaskSelectedNetworkClientId } =\n this.messagingSystem.call('NetworkController:getState');\n if (!this.#useRequestQueuePreference) {\n return metamaskSelectedNetworkClientId;\n }\n return this.state.domains[domain] ?? metamaskSelectedNetworkClientId;\n }\n\n /**\n * Accesses the provider and block tracker for the currently selected network.\n *\n * @param domain - the domain for the provider\n * @returns The proxy and block tracker proxies.\n */\n getProviderAndBlockTracker(domain: Domain): NetworkProxy {\n // If the domain is 'metamask' or a snap, return the NetworkController's globally selected network client proxy\n if (\n domain === METAMASK_DOMAIN ||\n snapsPrefixes.some((prefix) => domain.startsWith(prefix))\n ) {\n const networkClient = this.messagingSystem.call(\n 'NetworkController:getSelectedNetworkClient',\n );\n if (networkClient === undefined) {\n throw new Error('Selected network not initialized');\n }\n return networkClient;\n }\n\n let networkProxy = this.#domainProxyMap.get(domain);\n if (networkProxy === undefined) {\n let networkClient;\n if (\n this.#useRequestQueuePreference &&\n this.#domainHasPermissions(domain)\n ) {\n const networkClientId = this.getNetworkClientIdForDomain(domain);\n networkClient = this.messagingSystem.call(\n 'NetworkController:getNetworkClientById',\n networkClientId,\n );\n } else {\n networkClient = this.messagingSystem.call(\n 'NetworkController:getSelectedNetworkClient',\n );\n if (networkClient === undefined) {\n throw new Error('Selected network not initialized');\n }\n }\n networkProxy = {\n provider: createEventEmitterProxy(networkClient.provider),\n blockTracker: createEventEmitterProxy(networkClient.blockTracker, {\n eventFilter: 'skipInternal',\n }),\n };\n this.#domainProxyMap.set(domain, networkProxy);\n }\n return networkProxy;\n }\n}\n"],"mappings":"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","names":[]} -\ No newline at end of file -diff --git a/dist/chunk-ANSSZMDI.js b/dist/chunk-ANSSZMDI.js -new file mode 100644 -index 0000000000000000000000000000000000000000..46fa6599a0d673a526cc2d054a47733fc0570e64 ---- /dev/null -+++ b/dist/chunk-ANSSZMDI.js -@@ -0,0 +1,23 @@ -+"use strict";Object.defineProperty(exports, "__esModule", {value: true}); -+ -+var _chunkCECZWJ42js = require('./chunk-CECZWJ42.js'); -+ -+// src/SelectedNetworkMiddleware.ts -+var createSelectedNetworkMiddleware = (messenger) => { -+ const getNetworkClientIdForDomain = (origin) => messenger.call( -+ _chunkCECZWJ42js.SelectedNetworkControllerActionTypes.getNetworkClientIdForDomain, -+ origin -+ ); -+ return (req, _, next) => { -+ if (!req.origin) { -+ throw new Error("Request object is lacking an 'origin'"); -+ } -+ req.networkClientId = getNetworkClientIdForDomain(req.origin); -+ return next(); -+ }; -+}; -+ -+ -+ -+exports.createSelectedNetworkMiddleware = createSelectedNetworkMiddleware; -+//# sourceMappingURL=chunk-ANSSZMDI.js.map -\ No newline at end of file -diff --git a/dist/chunk-ANSSZMDI.js.map b/dist/chunk-ANSSZMDI.js.map -new file mode 100644 -index 0000000000000000000000000000000000000000..9fe4c1fd6e9b12bfd9091833618334f2c931098f ---- /dev/null -+++ b/dist/chunk-ANSSZMDI.js.map -@@ -0,0 +1 @@ -+{"version":3,"sources":["../src/SelectedNetworkMiddleware.ts"],"names":[],"mappings":";;;;;AAYO,IAAM,kCAAkC,CAC7C,cAC2C;AAC3C,QAAM,8BAA8B,CAAC,WACnC,UAAU;AAAA,IACR,qCAAqC;AAAA,IACrC;AAAA,EACF;AAEF,SAAO,CAAC,KAA8C,GAAG,SAAS;AAChE,QAAI,CAAC,IAAI,QAAQ;AACf,YAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AAEA,QAAI,kBAAkB,4BAA4B,IAAI,MAAM;AAC5D,WAAO,KAAK;AAAA,EACd;AACF","sourcesContent":["import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine';\nimport type { NetworkClientId } from '@metamask/network-controller';\nimport type { Json, JsonRpcParams, JsonRpcRequest } from '@metamask/utils';\n\nimport type { SelectedNetworkControllerMessenger } from './SelectedNetworkController';\nimport { SelectedNetworkControllerActionTypes } from './SelectedNetworkController';\n\nexport type SelectedNetworkMiddlewareJsonRpcRequest = JsonRpcRequest & {\n networkClientId?: NetworkClientId;\n origin?: string;\n};\n\nexport const createSelectedNetworkMiddleware = (\n messenger: SelectedNetworkControllerMessenger,\n): JsonRpcMiddleware => {\n const getNetworkClientIdForDomain = (origin: string) =>\n messenger.call(\n SelectedNetworkControllerActionTypes.getNetworkClientIdForDomain,\n origin,\n );\n\n return (req: SelectedNetworkMiddlewareJsonRpcRequest, _, next) => {\n if (!req.origin) {\n throw new Error(\"Request object is lacking an 'origin'\");\n }\n\n req.networkClientId = getNetworkClientIdForDomain(req.origin);\n return next();\n };\n};\n"]} -\ No newline at end of file -diff --git a/dist/chunk-CECZWJ42.js b/dist/chunk-CECZWJ42.js -new file mode 100644 -index 0000000000000000000000000000000000000000..3471ce2b3b927414a25b81b864ddad0cdaeeb26a ---- /dev/null -+++ b/dist/chunk-CECZWJ42.js -@@ -0,0 +1,284 @@ -+"use strict";Object.defineProperty(exports, "__esModule", {value: true});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/SelectedNetworkController.ts -+var _basecontroller = require('@metamask/base-controller'); -+var _swappableobjproxy = require('@metamask/swappable-obj-proxy'); -+var controllerName = "SelectedNetworkController"; -+var stateMetadata = { -+ domains: { persist: true, anonymous: false } -+}; -+var getDefaultState = () => ({ domains: {} }); -+var snapsPrefixes = ["npm:", "local:"]; -+var METAMASK_DOMAIN = "metamask"; -+var SelectedNetworkControllerActionTypes = { -+ getState: `${controllerName}:getState`, -+ getNetworkClientIdForDomain: `${controllerName}:getNetworkClientIdForDomain`, -+ setNetworkClientIdForDomain: `${controllerName}:setNetworkClientIdForDomain` -+}; -+var SelectedNetworkControllerEventTypes = { -+ stateChange: `${controllerName}:stateChange` -+}; -+var _domainProxyMap, _useRequestQueuePreference, _registerMessageHandlers, registerMessageHandlers_fn, _setNetworkClientIdForDomain, setNetworkClientIdForDomain_fn, _unsetNetworkClientIdForDomain, unsetNetworkClientIdForDomain_fn, _domainHasPermissions, domainHasPermissions_fn, _resetAllPermissionedDomains, resetAllPermissionedDomains_fn; -+var SelectedNetworkController = class extends _basecontroller.BaseController { -+ /** -+ * Construct a SelectedNetworkController controller. -+ * -+ * @param options - The controller options. -+ * @param options.messenger - The restricted controller messenger for the EncryptionPublicKey controller. -+ * @param options.state - The controllers initial state. -+ * @param options.useRequestQueuePreference - A boolean indicating whether to use the request queue preference. -+ * @param options.onPreferencesStateChange - A callback that is called when the preference state changes. -+ * @param options.domainProxyMap - A map for storing domain-specific proxies that are held in memory only during use. -+ */ -+ constructor({ -+ messenger, -+ state = getDefaultState(), -+ useRequestQueuePreference, -+ onPreferencesStateChange, -+ domainProxyMap -+ }) { -+ super({ -+ name: controllerName, -+ metadata: stateMetadata, -+ messenger, -+ state -+ }); -+ __privateAdd(this, _registerMessageHandlers); -+ __privateAdd(this, _setNetworkClientIdForDomain); -+ /** -+ * This method is used when a domain is removed from the PermissionsController. -+ * It will remove re-point the network proxy to the globally selected network in the domainProxyMap or, if no globally selected network client is available, delete the proxy. -+ * -+ * @param domain - The domain for which to unset the network client ID. -+ */ -+ __privateAdd(this, _unsetNetworkClientIdForDomain); -+ __privateAdd(this, _domainHasPermissions); -+ // Loop through all domains and for those with permissions it points that domain's proxy -+ // to an unproxied instance of the globally selected network client. -+ // NOT the NetworkController's proxy of the globally selected networkClient -+ __privateAdd(this, _resetAllPermissionedDomains); -+ __privateAdd(this, _domainProxyMap, void 0); -+ __privateAdd(this, _useRequestQueuePreference, void 0); -+ __privateSet(this, _useRequestQueuePreference, useRequestQueuePreference); -+ __privateSet(this, _domainProxyMap, domainProxyMap); -+ __privateMethod(this, _registerMessageHandlers, registerMessageHandlers_fn).call(this); -+ this.messagingSystem.call("PermissionController:getSubjectNames").filter((domain) => this.state.domains[domain] === void 0).forEach( -+ (domain) => this.setNetworkClientIdForDomain( -+ domain, -+ this.messagingSystem.call("NetworkController:getState").selectedNetworkClientId -+ ) -+ ); -+ this.messagingSystem.subscribe( -+ "PermissionController:stateChange", -+ (_, patches) => { -+ patches.forEach(({ op, path }) => { -+ const isChangingSubject = path[0] === "subjects" && path[1] !== void 0; -+ if (isChangingSubject && typeof path[1] === "string") { -+ const domain = path[1]; -+ if (op === "add" && this.state.domains[domain] === void 0) { -+ this.setNetworkClientIdForDomain( -+ domain, -+ this.messagingSystem.call("NetworkController:getState").selectedNetworkClientId -+ ); -+ } else if (op === "remove" && this.state.domains[domain] !== void 0) { -+ __privateMethod(this, _unsetNetworkClientIdForDomain, unsetNetworkClientIdForDomain_fn).call(this, domain); -+ } -+ } -+ }); -+ } -+ ); -+ this.messagingSystem.subscribe( -+ "NetworkController:stateChange", -+ ({ selectedNetworkClientId }, patches) => { -+ patches.forEach(({ op, path }) => { -+ if (op === "remove" && path[0] === "networkConfigurations") { -+ const removedNetworkClientId = path[1]; -+ Object.entries(this.state.domains).forEach( -+ ([domain, networkClientIdForDomain]) => { -+ if (networkClientIdForDomain === removedNetworkClientId) { -+ this.setNetworkClientIdForDomain( -+ domain, -+ selectedNetworkClientId -+ ); -+ } -+ } -+ ); -+ } -+ }); -+ } -+ ); -+ onPreferencesStateChange(({ useRequestQueue }) => { -+ if (__privateGet(this, _useRequestQueuePreference) !== useRequestQueue) { -+ if (!useRequestQueue) { -+ Object.keys(this.state.domains).forEach((domain) => { -+ __privateMethod(this, _unsetNetworkClientIdForDomain, unsetNetworkClientIdForDomain_fn).call(this, domain); -+ }); -+ } else { -+ __privateMethod(this, _resetAllPermissionedDomains, resetAllPermissionedDomains_fn).call(this); -+ } -+ __privateSet(this, _useRequestQueuePreference, useRequestQueue); -+ } -+ }); -+ } -+ setNetworkClientIdForDomain(domain, networkClientId) { -+ if (!__privateGet(this, _useRequestQueuePreference)) { -+ return; -+ } -+ if (domain === METAMASK_DOMAIN) { -+ throw new Error( -+ `NetworkClientId for domain "${METAMASK_DOMAIN}" cannot be set on the SelectedNetworkController` -+ ); -+ } -+ if (snapsPrefixes.some((prefix) => domain.startsWith(prefix))) { -+ return; -+ } -+ if (!__privateMethod(this, _domainHasPermissions, domainHasPermissions_fn).call(this, domain)) { -+ throw new Error( -+ "NetworkClientId for domain cannot be called with a domain that has not yet been granted permissions" -+ ); -+ } -+ __privateMethod(this, _setNetworkClientIdForDomain, setNetworkClientIdForDomain_fn).call(this, domain, networkClientId); -+ } -+ getNetworkClientIdForDomain(domain) { -+ const { selectedNetworkClientId: metamaskSelectedNetworkClientId } = this.messagingSystem.call("NetworkController:getState"); -+ if (!__privateGet(this, _useRequestQueuePreference)) { -+ return metamaskSelectedNetworkClientId; -+ } -+ return this.state.domains[domain] ?? metamaskSelectedNetworkClientId; -+ } -+ /** -+ * Accesses the provider and block tracker for the currently selected network. -+ * -+ * @param domain - the domain for the provider -+ * @returns The proxy and block tracker proxies. -+ */ -+ getProviderAndBlockTracker(domain) { -+ if (domain === METAMASK_DOMAIN || snapsPrefixes.some((prefix) => domain.startsWith(prefix))) { -+ const networkClient = this.messagingSystem.call( -+ "NetworkController:getSelectedNetworkClient" -+ ); -+ if (networkClient === void 0) { -+ throw new Error("Selected network not initialized"); -+ } -+ return networkClient; -+ } -+ let networkProxy = __privateGet(this, _domainProxyMap).get(domain); -+ if (networkProxy === void 0) { -+ let networkClient; -+ if (__privateGet(this, _useRequestQueuePreference) && __privateMethod(this, _domainHasPermissions, domainHasPermissions_fn).call(this, domain)) { -+ const networkClientId = this.getNetworkClientIdForDomain(domain); -+ networkClient = this.messagingSystem.call( -+ "NetworkController:getNetworkClientById", -+ networkClientId -+ ); -+ } else { -+ networkClient = this.messagingSystem.call( -+ "NetworkController:getSelectedNetworkClient" -+ ); -+ if (networkClient === void 0) { -+ throw new Error("Selected network not initialized"); -+ } -+ } -+ networkProxy = { -+ provider: _swappableobjproxy.createEventEmitterProxy.call(void 0, networkClient.provider), -+ blockTracker: _swappableobjproxy.createEventEmitterProxy.call(void 0, networkClient.blockTracker, { -+ eventFilter: "skipInternal" -+ }) -+ }; -+ __privateGet(this, _domainProxyMap).set(domain, networkProxy); -+ } -+ return networkProxy; -+ } -+}; -+_domainProxyMap = new WeakMap(); -+_useRequestQueuePreference = new WeakMap(); -+_registerMessageHandlers = new WeakSet(); -+registerMessageHandlers_fn = function() { -+ this.messagingSystem.registerActionHandler( -+ SelectedNetworkControllerActionTypes.getNetworkClientIdForDomain, -+ this.getNetworkClientIdForDomain.bind(this) -+ ); -+ this.messagingSystem.registerActionHandler( -+ SelectedNetworkControllerActionTypes.setNetworkClientIdForDomain, -+ this.setNetworkClientIdForDomain.bind(this) -+ ); -+}; -+_setNetworkClientIdForDomain = new WeakSet(); -+setNetworkClientIdForDomain_fn = function(domain, networkClientId) { -+ const networkClient = this.messagingSystem.call( -+ "NetworkController:getNetworkClientById", -+ networkClientId -+ ); -+ const networkProxy = this.getProviderAndBlockTracker(domain); -+ networkProxy.provider.setTarget(networkClient.provider); -+ networkProxy.blockTracker.setTarget(networkClient.blockTracker); -+ this.update((state) => { -+ state.domains[domain] = networkClientId; -+ }); -+}; -+_unsetNetworkClientIdForDomain = new WeakSet(); -+unsetNetworkClientIdForDomain_fn = function(domain) { -+ const globallySelectedNetworkClient = this.messagingSystem.call( -+ "NetworkController:getSelectedNetworkClient" -+ ); -+ const networkProxy = __privateGet(this, _domainProxyMap).get(domain); -+ if (networkProxy && globallySelectedNetworkClient) { -+ networkProxy.provider.setTarget(globallySelectedNetworkClient.provider); -+ networkProxy.blockTracker.setTarget( -+ globallySelectedNetworkClient.blockTracker -+ ); -+ } else if (networkProxy) { -+ __privateGet(this, _domainProxyMap).delete(domain); -+ } -+ this.update((state) => { -+ delete state.domains[domain]; -+ }); -+}; -+_domainHasPermissions = new WeakSet(); -+domainHasPermissions_fn = function(domain) { -+ return this.messagingSystem.call( -+ "PermissionController:hasPermissions", -+ domain -+ ); -+}; -+_resetAllPermissionedDomains = new WeakSet(); -+resetAllPermissionedDomains_fn = function() { -+ __privateGet(this, _domainProxyMap).forEach((_, domain) => { -+ const { selectedNetworkClientId } = this.messagingSystem.call( -+ "NetworkController:getState" -+ ); -+ if (__privateMethod(this, _domainHasPermissions, domainHasPermissions_fn).call(this, domain)) { -+ __privateMethod(this, _setNetworkClientIdForDomain, setNetworkClientIdForDomain_fn).call(this, domain, selectedNetworkClientId); -+ } -+ }); -+}; -+ -+ -+ -+ -+ -+ -+ -+exports.controllerName = controllerName; exports.METAMASK_DOMAIN = METAMASK_DOMAIN; exports.SelectedNetworkControllerActionTypes = SelectedNetworkControllerActionTypes; exports.SelectedNetworkControllerEventTypes = SelectedNetworkControllerEventTypes; exports.SelectedNetworkController = SelectedNetworkController; -+//# sourceMappingURL=chunk-CECZWJ42.js.map -\ No newline at end of file -diff --git a/dist/chunk-CECZWJ42.js.map b/dist/chunk-CECZWJ42.js.map -new file mode 100644 -index 0000000000000000000000000000000000000000..a2eab942638757819a9cb850057450500d22f9ed ---- /dev/null -+++ b/dist/chunk-CECZWJ42.js.map -@@ -0,0 +1 @@ -+{"version":3,"sources":["../src/SelectedNetworkController.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AACA,SAAS,sBAAsB;AAe/B,SAAS,+BAA+B;AAGjC,IAAM,iBAAiB;AAE9B,IAAM,gBAAgB;AAAA,EACpB,SAAS,EAAE,SAAS,MAAM,WAAW,MAAM;AAC7C;AAEA,IAAM,kBAAkB,OAAO,EAAE,SAAS,CAAC,EAAE;AAM7C,IAAM,gBAAgB,CAAC,QAAQ,QAAQ;AAIhC,IAAM,kBAAkB;AAExB,IAAM,uCAAuC;AAAA,EAClD,UAAU,GAAG,cAAc;AAAA,EAC3B,6BACE,GAAG,cAAc;AAAA,EACnB,6BACE,GAAG,cAAc;AACrB;AAEO,IAAM,sCAAsC;AAAA,EACjD,aAAa,GAAG,cAAc;AAChC;AA/CA;AAsHO,IAAM,4BAAN,cAAwC,eAI7C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,YAAY;AAAA,IACV;AAAA,IACA,QAAQ,gBAAgB;AAAA,IACxB;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAAqC;AACnC,UAAM;AAAA,MACJ,MAAM;AAAA,MACN,UAAU;AAAA,MACV;AAAA,MACA;AAAA,IACF,CAAC;AAgFH;AAWA;AAuBA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkBA;AAUA;AAAA;AAAA;AAAA;AAxKA;AAEA;AAyBE,uBAAK,4BAA6B;AAClC,uBAAK,iBAAkB;AACvB,0BAAK,sDAAL;AAGA,SAAK,gBACF,KAAK,sCAAsC,EAC3C,OAAO,CAAC,WAAW,KAAK,MAAM,QAAQ,MAAM,MAAM,MAAS,EAC3D;AAAA,MAAQ,CAAC,WACR,KAAK;AAAA,QACH;AAAA,QACA,KAAK,gBAAgB,KAAK,4BAA4B,EACnD;AAAA,MACL;AAAA,IACF;AAEF,SAAK,gBAAgB;AAAA,MACnB;AAAA,MACA,CAAC,GAAG,YAAY;AACd,gBAAQ,QAAQ,CAAC,EAAE,IAAI,KAAK,MAAM;AAChC,gBAAM,oBACJ,KAAK,CAAC,MAAM,cAAc,KAAK,CAAC,MAAM;AACxC,cAAI,qBAAqB,OAAO,KAAK,CAAC,MAAM,UAAU;AACpD,kBAAM,SAAS,KAAK,CAAC;AACrB,gBAAI,OAAO,SAAS,KAAK,MAAM,QAAQ,MAAM,MAAM,QAAW;AAC5D,mBAAK;AAAA,gBACH;AAAA,gBACA,KAAK,gBAAgB,KAAK,4BAA4B,EACnD;AAAA,cACL;AAAA,YACF,WACE,OAAO,YACP,KAAK,MAAM,QAAQ,MAAM,MAAM,QAC/B;AACA,oCAAK,kEAAL,WAAoC;AAAA,YACtC;AAAA,UACF;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AAEA,SAAK,gBAAgB;AAAA,MACnB;AAAA,MACA,CAAC,EAAE,wBAAwB,GAAG,YAAY;AACxC,gBAAQ,QAAQ,CAAC,EAAE,IAAI,KAAK,MAAM;AAEhC,cAAI,OAAO,YAAY,KAAK,CAAC,MAAM,yBAAyB;AAC1D,kBAAM,yBAAyB,KAAK,CAAC;AACrC,mBAAO,QAAQ,KAAK,MAAM,OAAO,EAAE;AAAA,cACjC,CAAC,CAAC,QAAQ,wBAAwB,MAAM;AACtC,oBAAI,6BAA6B,wBAAwB;AACvD,uBAAK;AAAA,oBACH;AAAA,oBACA;AAAA,kBACF;AAAA,gBACF;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AAEA,6BAAyB,CAAC,EAAE,gBAAgB,MAAM;AAChD,UAAI,mBAAK,gCAA+B,iBAAiB;AACvD,YAAI,CAAC,iBAAiB;AAGpB,iBAAO,KAAK,KAAK,MAAM,OAAO,EAAE,QAAQ,CAAC,WAAW;AAClD,kCAAK,kEAAL,WAAoC;AAAA,UACtC,CAAC;AAAA,QACH,OAAO;AACL,gCAAK,8DAAL;AAAA,QACF;AACA,2BAAK,4BAA6B;AAAA,MACpC;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EA8EA,4BACE,QACA,iBACA;AAGA,QAAI,CAAC,mBAAK,6BAA4B;AACpC;AAAA,IACF;AACA,QAAI,WAAW,iBAAiB;AAC9B,YAAM,IAAI;AAAA,QACR,+BAA+B,eAAe;AAAA,MAChD;AAAA,IACF;AAEA,QAAI,cAAc,KAAK,CAAC,WAAW,OAAO,WAAW,MAAM,CAAC,GAAG;AAC7D;AAAA,IACF;AAEA,QAAI,CAAC,sBAAK,gDAAL,WAA2B,SAAS;AACvC,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,0BAAK,8DAAL,WAAkC,QAAQ;AAAA,EAC5C;AAAA,EAEA,4BAA4B,QAAiC;AAC3D,UAAM,EAAE,yBAAyB,gCAAgC,IAC/D,KAAK,gBAAgB,KAAK,4BAA4B;AACxD,QAAI,CAAC,mBAAK,6BAA4B;AACpC,aAAO;AAAA,IACT;AACA,WAAO,KAAK,MAAM,QAAQ,MAAM,KAAK;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,2BAA2B,QAA8B;AAEvD,QACE,WAAW,mBACX,cAAc,KAAK,CAAC,WAAW,OAAO,WAAW,MAAM,CAAC,GACxD;AACA,YAAM,gBAAgB,KAAK,gBAAgB;AAAA,QACzC;AAAA,MACF;AACA,UAAI,kBAAkB,QAAW;AAC/B,cAAM,IAAI,MAAM,kCAAkC;AAAA,MACpD;AACA,aAAO;AAAA,IACT;AAEA,QAAI,eAAe,mBAAK,iBAAgB,IAAI,MAAM;AAClD,QAAI,iBAAiB,QAAW;AAC9B,UAAI;AACJ,UACE,mBAAK,+BACL,sBAAK,gDAAL,WAA2B,SAC3B;AACA,cAAM,kBAAkB,KAAK,4BAA4B,MAAM;AAC/D,wBAAgB,KAAK,gBAAgB;AAAA,UACnC;AAAA,UACA;AAAA,QACF;AAAA,MACF,OAAO;AACL,wBAAgB,KAAK,gBAAgB;AAAA,UACnC;AAAA,QACF;AACA,YAAI,kBAAkB,QAAW;AAC/B,gBAAM,IAAI,MAAM,kCAAkC;AAAA,QACpD;AAAA,MACF;AACA,qBAAe;AAAA,QACb,UAAU,wBAAwB,cAAc,QAAQ;AAAA,QACxD,cAAc,wBAAwB,cAAc,cAAc;AAAA,UAChE,aAAa;AAAA,QACf,CAAC;AAAA,MACH;AACA,yBAAK,iBAAgB,IAAI,QAAQ,YAAY;AAAA,IAC/C;AACA,WAAO;AAAA,EACT;AACF;AA9QE;AAEA;AAwGA;AAAA,6BAAwB,WAAS;AAC/B,OAAK,gBAAgB;AAAA,IACnB,qCAAqC;AAAA,IACrC,KAAK,4BAA4B,KAAK,IAAI;AAAA,EAC5C;AACA,OAAK,gBAAgB;AAAA,IACnB,qCAAqC;AAAA,IACrC,KAAK,4BAA4B,KAAK,IAAI;AAAA,EAC5C;AACF;AAEA;AAAA,iCAA4B,SAC1B,QACA,iBACA;AACA,QAAM,gBAAgB,KAAK,gBAAgB;AAAA,IACzC;AAAA,IACA;AAAA,EACF;AACA,QAAM,eAAe,KAAK,2BAA2B,MAAM;AAC3D,eAAa,SAAS,UAAU,cAAc,QAAQ;AACtD,eAAa,aAAa,UAAU,cAAc,YAAY;AAE9D,OAAK,OAAO,CAAC,UAAU;AACrB,UAAM,QAAQ,MAAM,IAAI;AAAA,EAC1B,CAAC;AACH;AAQA;AAAA,mCAA8B,SAAC,QAAgB;AAC7C,QAAM,gCAAgC,KAAK,gBAAgB;AAAA,IACzD;AAAA,EACF;AACA,QAAM,eAAe,mBAAK,iBAAgB,IAAI,MAAM;AACpD,MAAI,gBAAgB,+BAA+B;AACjD,iBAAa,SAAS,UAAU,8BAA8B,QAAQ;AACtE,iBAAa,aAAa;AAAA,MACxB,8BAA8B;AAAA,IAChC;AAAA,EACF,WAAW,cAAc;AACvB,uBAAK,iBAAgB,OAAO,MAAM;AAAA,EACpC;AACA,OAAK,OAAO,CAAC,UAAU;AACrB,WAAO,MAAM,QAAQ,MAAM;AAAA,EAC7B,CAAC;AACH;AAEA;AAAA,0BAAqB,SAAC,QAAyB;AAC7C,SAAO,KAAK,gBAAgB;AAAA,IAC1B;AAAA,IACA;AAAA,EACF;AACF;AAKA;AAAA,iCAA4B,WAAG;AAC7B,qBAAK,iBAAgB,QAAQ,CAAC,GAAiB,WAAmB;AAChE,UAAM,EAAE,wBAAwB,IAAI,KAAK,gBAAgB;AAAA,MACvD;AAAA,IACF;AAIA,QAAI,sBAAK,gDAAL,WAA2B,SAAS;AACtC,4BAAK,8DAAL,WAAkC,QAAQ;AAAA,IAC5C;AAAA,EACF,CAAC;AACH","sourcesContent":["import type { RestrictedControllerMessenger } from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type {\n BlockTrackerProxy,\n NetworkClientId,\n NetworkControllerGetNetworkClientByIdAction,\n NetworkControllerGetSelectedNetworkClientAction,\n NetworkControllerGetStateAction,\n NetworkControllerStateChangeEvent,\n ProviderProxy,\n} from '@metamask/network-controller';\nimport type {\n PermissionControllerStateChange,\n GetSubjects as PermissionControllerGetSubjectsAction,\n HasPermissions as PermissionControllerHasPermissions,\n} from '@metamask/permission-controller';\nimport { createEventEmitterProxy } from '@metamask/swappable-obj-proxy';\nimport type { Patch } from 'immer';\n\nexport const controllerName = 'SelectedNetworkController';\n\nconst stateMetadata = {\n domains: { persist: true, anonymous: false },\n};\n\nconst getDefaultState = () => ({ domains: {} });\n\n// npm and local are currently the only valid prefixes for snap domains\n// TODO: eventually we maybe want to pull this in from snaps-utils to ensure it stays in sync\n// For now it seems like overkill to add a dependency for this one constant\n// https://github.com/MetaMask/snaps/blob/2beee7803bfe9e540788a3558b546b9f55dc3cb4/packages/snaps-utils/src/types.ts#L120\nconst snapsPrefixes = ['npm:', 'local:'] as const;\n\nexport type Domain = string;\n\nexport const METAMASK_DOMAIN = 'metamask' as const;\n\nexport const SelectedNetworkControllerActionTypes = {\n getState: `${controllerName}:getState` as const,\n getNetworkClientIdForDomain:\n `${controllerName}:getNetworkClientIdForDomain` as const,\n setNetworkClientIdForDomain:\n `${controllerName}:setNetworkClientIdForDomain` as const,\n};\n\nexport const SelectedNetworkControllerEventTypes = {\n stateChange: `${controllerName}:stateChange` as const,\n};\n\nexport type SelectedNetworkControllerState = {\n domains: Record;\n};\n\nexport type SelectedNetworkControllerStateChangeEvent = {\n type: typeof SelectedNetworkControllerEventTypes.stateChange;\n payload: [SelectedNetworkControllerState, Patch[]];\n};\n\nexport type SelectedNetworkControllerGetSelectedNetworkStateAction = {\n type: typeof SelectedNetworkControllerActionTypes.getState;\n handler: () => SelectedNetworkControllerState;\n};\n\nexport type SelectedNetworkControllerGetNetworkClientIdForDomainAction = {\n type: typeof SelectedNetworkControllerActionTypes.getNetworkClientIdForDomain;\n handler: SelectedNetworkController['getNetworkClientIdForDomain'];\n};\n\nexport type SelectedNetworkControllerSetNetworkClientIdForDomainAction = {\n type: typeof SelectedNetworkControllerActionTypes.setNetworkClientIdForDomain;\n handler: SelectedNetworkController['setNetworkClientIdForDomain'];\n};\n\nexport type SelectedNetworkControllerActions =\n | SelectedNetworkControllerGetSelectedNetworkStateAction\n | SelectedNetworkControllerGetNetworkClientIdForDomainAction\n | SelectedNetworkControllerSetNetworkClientIdForDomainAction;\n\nexport type AllowedActions =\n | NetworkControllerGetNetworkClientByIdAction\n | NetworkControllerGetSelectedNetworkClientAction\n | NetworkControllerGetStateAction\n | PermissionControllerHasPermissions\n | PermissionControllerGetSubjectsAction;\n\nexport type SelectedNetworkControllerEvents =\n SelectedNetworkControllerStateChangeEvent;\n\nexport type AllowedEvents =\n | NetworkControllerStateChangeEvent\n | PermissionControllerStateChange;\n\nexport type SelectedNetworkControllerMessenger = RestrictedControllerMessenger<\n typeof controllerName,\n SelectedNetworkControllerActions | AllowedActions,\n SelectedNetworkControllerEvents | AllowedEvents,\n AllowedActions['type'],\n AllowedEvents['type']\n>;\n\nexport type SelectedNetworkControllerOptions = {\n state?: SelectedNetworkControllerState;\n messenger: SelectedNetworkControllerMessenger;\n useRequestQueuePreference: boolean;\n onPreferencesStateChange: (\n listener: (preferencesState: { useRequestQueue: boolean }) => void,\n ) => void;\n domainProxyMap: Map;\n};\n\nexport type NetworkProxy = {\n provider: ProviderProxy;\n blockTracker: BlockTrackerProxy;\n};\n\n/**\n * Controller for getting and setting the network for a particular domain.\n */\nexport class SelectedNetworkController extends BaseController<\n typeof controllerName,\n SelectedNetworkControllerState,\n SelectedNetworkControllerMessenger\n> {\n #domainProxyMap: Map;\n\n #useRequestQueuePreference: boolean;\n\n /**\n * Construct a SelectedNetworkController controller.\n *\n * @param options - The controller options.\n * @param options.messenger - The restricted controller messenger for the EncryptionPublicKey controller.\n * @param options.state - The controllers initial state.\n * @param options.useRequestQueuePreference - A boolean indicating whether to use the request queue preference.\n * @param options.onPreferencesStateChange - A callback that is called when the preference state changes.\n * @param options.domainProxyMap - A map for storing domain-specific proxies that are held in memory only during use.\n */\n constructor({\n messenger,\n state = getDefaultState(),\n useRequestQueuePreference,\n onPreferencesStateChange,\n domainProxyMap,\n }: SelectedNetworkControllerOptions) {\n super({\n name: controllerName,\n metadata: stateMetadata,\n messenger,\n state,\n });\n this.#useRequestQueuePreference = useRequestQueuePreference;\n this.#domainProxyMap = domainProxyMap;\n this.#registerMessageHandlers();\n\n // this is fetching all the dapp permissions from the PermissionsController and looking for any domains that are not in domains state in this controller. Then we take any missing domains and add them to state here, setting it with the globally selected networkClientId (fetched from the NetworkController)\n this.messagingSystem\n .call('PermissionController:getSubjectNames')\n .filter((domain) => this.state.domains[domain] === undefined)\n .forEach((domain) =>\n this.setNetworkClientIdForDomain(\n domain,\n this.messagingSystem.call('NetworkController:getState')\n .selectedNetworkClientId,\n ),\n );\n\n this.messagingSystem.subscribe(\n 'PermissionController:stateChange',\n (_, patches) => {\n patches.forEach(({ op, path }) => {\n const isChangingSubject =\n path[0] === 'subjects' && path[1] !== undefined;\n if (isChangingSubject && typeof path[1] === 'string') {\n const domain = path[1];\n if (op === 'add' && this.state.domains[domain] === undefined) {\n this.setNetworkClientIdForDomain(\n domain,\n this.messagingSystem.call('NetworkController:getState')\n .selectedNetworkClientId,\n );\n } else if (\n op === 'remove' &&\n this.state.domains[domain] !== undefined\n ) {\n this.#unsetNetworkClientIdForDomain(domain);\n }\n }\n });\n },\n );\n\n this.messagingSystem.subscribe(\n 'NetworkController:stateChange',\n ({ selectedNetworkClientId }, patches) => {\n patches.forEach(({ op, path }) => {\n // if a network is removed, update the networkClientId for all domains that were using it to the selected network\n if (op === 'remove' && path[0] === 'networkConfigurations') {\n const removedNetworkClientId = path[1] as NetworkClientId;\n Object.entries(this.state.domains).forEach(\n ([domain, networkClientIdForDomain]) => {\n if (networkClientIdForDomain === removedNetworkClientId) {\n this.setNetworkClientIdForDomain(\n domain,\n selectedNetworkClientId,\n );\n }\n },\n );\n }\n });\n },\n );\n\n onPreferencesStateChange(({ useRequestQueue }) => {\n if (this.#useRequestQueuePreference !== useRequestQueue) {\n if (!useRequestQueue) {\n // Loop through all domains and points each domain's proxy\n // to the NetworkController's own proxy of the globally selected networkClient\n Object.keys(this.state.domains).forEach((domain) => {\n this.#unsetNetworkClientIdForDomain(domain);\n });\n } else {\n this.#resetAllPermissionedDomains();\n }\n this.#useRequestQueuePreference = useRequestQueue;\n }\n });\n }\n\n #registerMessageHandlers(): void {\n this.messagingSystem.registerActionHandler(\n SelectedNetworkControllerActionTypes.getNetworkClientIdForDomain,\n this.getNetworkClientIdForDomain.bind(this),\n );\n this.messagingSystem.registerActionHandler(\n SelectedNetworkControllerActionTypes.setNetworkClientIdForDomain,\n this.setNetworkClientIdForDomain.bind(this),\n );\n }\n\n #setNetworkClientIdForDomain(\n domain: Domain,\n networkClientId: NetworkClientId,\n ) {\n const networkClient = this.messagingSystem.call(\n 'NetworkController:getNetworkClientById',\n networkClientId,\n );\n const networkProxy = this.getProviderAndBlockTracker(domain);\n networkProxy.provider.setTarget(networkClient.provider);\n networkProxy.blockTracker.setTarget(networkClient.blockTracker);\n\n this.update((state) => {\n state.domains[domain] = networkClientId;\n });\n }\n\n /**\n * This method is used when a domain is removed from the PermissionsController.\n * It will remove re-point the network proxy to the globally selected network in the domainProxyMap or, if no globally selected network client is available, delete the proxy.\n *\n * @param domain - The domain for which to unset the network client ID.\n */\n #unsetNetworkClientIdForDomain(domain: Domain) {\n const globallySelectedNetworkClient = this.messagingSystem.call(\n 'NetworkController:getSelectedNetworkClient',\n );\n const networkProxy = this.#domainProxyMap.get(domain);\n if (networkProxy && globallySelectedNetworkClient) {\n networkProxy.provider.setTarget(globallySelectedNetworkClient.provider);\n networkProxy.blockTracker.setTarget(\n globallySelectedNetworkClient.blockTracker,\n );\n } else if (networkProxy) {\n this.#domainProxyMap.delete(domain);\n }\n this.update((state) => {\n delete state.domains[domain];\n });\n }\n\n #domainHasPermissions(domain: Domain): boolean {\n return this.messagingSystem.call(\n 'PermissionController:hasPermissions',\n domain,\n );\n }\n\n // Loop through all domains and for those with permissions it points that domain's proxy\n // to an unproxied instance of the globally selected network client.\n // NOT the NetworkController's proxy of the globally selected networkClient\n #resetAllPermissionedDomains() {\n this.#domainProxyMap.forEach((_: NetworkProxy, domain: string) => {\n const { selectedNetworkClientId } = this.messagingSystem.call(\n 'NetworkController:getState',\n );\n // can't use public setNetworkClientIdForDomain because it will throw an error\n // rather than simply skip if the domain doesn't have permissions which can happen\n // in this case since proxies are added for each site the user visits\n if (this.#domainHasPermissions(domain)) {\n this.#setNetworkClientIdForDomain(domain, selectedNetworkClientId);\n }\n });\n }\n\n setNetworkClientIdForDomain(\n domain: Domain,\n networkClientId: NetworkClientId,\n ) {\n // Core PR: https://github.com/MetaMask/core/pull/4388\n // Patch Branch: patch-selected-network-controller-13.0.0-setNetworkClient-guard\n if (!this.#useRequestQueuePreference) {\n return;\n }\n if (domain === METAMASK_DOMAIN) {\n throw new Error(\n `NetworkClientId for domain \"${METAMASK_DOMAIN}\" cannot be set on the SelectedNetworkController`,\n );\n }\n\n if (snapsPrefixes.some((prefix) => domain.startsWith(prefix))) {\n return;\n }\n\n if (!this.#domainHasPermissions(domain)) {\n throw new Error(\n 'NetworkClientId for domain cannot be called with a domain that has not yet been granted permissions',\n );\n }\n\n this.#setNetworkClientIdForDomain(domain, networkClientId);\n }\n\n getNetworkClientIdForDomain(domain: Domain): NetworkClientId {\n const { selectedNetworkClientId: metamaskSelectedNetworkClientId } =\n this.messagingSystem.call('NetworkController:getState');\n if (!this.#useRequestQueuePreference) {\n return metamaskSelectedNetworkClientId;\n }\n return this.state.domains[domain] ?? metamaskSelectedNetworkClientId;\n }\n\n /**\n * Accesses the provider and block tracker for the currently selected network.\n *\n * @param domain - the domain for the provider\n * @returns The proxy and block tracker proxies.\n */\n getProviderAndBlockTracker(domain: Domain): NetworkProxy {\n // If the domain is 'metamask' or a snap, return the NetworkController's globally selected network client proxy\n if (\n domain === METAMASK_DOMAIN ||\n snapsPrefixes.some((prefix) => domain.startsWith(prefix))\n ) {\n const networkClient = this.messagingSystem.call(\n 'NetworkController:getSelectedNetworkClient',\n );\n if (networkClient === undefined) {\n throw new Error('Selected network not initialized');\n }\n return networkClient;\n }\n\n let networkProxy = this.#domainProxyMap.get(domain);\n if (networkProxy === undefined) {\n let networkClient;\n if (\n this.#useRequestQueuePreference &&\n this.#domainHasPermissions(domain)\n ) {\n const networkClientId = this.getNetworkClientIdForDomain(domain);\n networkClient = this.messagingSystem.call(\n 'NetworkController:getNetworkClientById',\n networkClientId,\n );\n } else {\n networkClient = this.messagingSystem.call(\n 'NetworkController:getSelectedNetworkClient',\n );\n if (networkClient === undefined) {\n throw new Error('Selected network not initialized');\n }\n }\n networkProxy = {\n provider: createEventEmitterProxy(networkClient.provider),\n blockTracker: createEventEmitterProxy(networkClient.blockTracker, {\n eventFilter: 'skipInternal',\n }),\n };\n this.#domainProxyMap.set(domain, networkProxy);\n }\n return networkProxy;\n }\n}\n"]} -\ No newline at end of file -diff --git a/dist/chunk-HFN7TKJS.mjs b/dist/chunk-HFN7TKJS.mjs -new file mode 100644 -index 0000000000000000000000000000000000000000..6f4a4ec6b18571939d2b6c10ddb8ab4e5345389c ---- /dev/null -+++ b/dist/chunk-HFN7TKJS.mjs -@@ -0,0 +1,23 @@ -+import { -+ SelectedNetworkControllerActionTypes -+} from "./chunk-7DSTEJNI.mjs"; -+ -+// src/SelectedNetworkMiddleware.ts -+var createSelectedNetworkMiddleware = (messenger) => { -+ const getNetworkClientIdForDomain = (origin) => messenger.call( -+ SelectedNetworkControllerActionTypes.getNetworkClientIdForDomain, -+ origin -+ ); -+ return (req, _, next) => { -+ if (!req.origin) { -+ throw new Error("Request object is lacking an 'origin'"); -+ } -+ req.networkClientId = getNetworkClientIdForDomain(req.origin); -+ return next(); -+ }; -+}; -+ -+export { -+ createSelectedNetworkMiddleware -+}; -+//# sourceMappingURL=chunk-HFN7TKJS.mjs.map -\ No newline at end of file -diff --git a/dist/chunk-HFN7TKJS.mjs.map b/dist/chunk-HFN7TKJS.mjs.map -new file mode 100644 -index 0000000000000000000000000000000000000000..fbcfd73d57f291b4f129caa47ef0c9614987cc30 ---- /dev/null -+++ b/dist/chunk-HFN7TKJS.mjs.map -@@ -0,0 +1 @@ -+{"version":3,"sources":["../src/SelectedNetworkMiddleware.ts"],"sourcesContent":["import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine';\nimport type { NetworkClientId } from '@metamask/network-controller';\nimport type { Json, JsonRpcParams, JsonRpcRequest } from '@metamask/utils';\n\nimport type { SelectedNetworkControllerMessenger } from './SelectedNetworkController';\nimport { SelectedNetworkControllerActionTypes } from './SelectedNetworkController';\n\nexport type SelectedNetworkMiddlewareJsonRpcRequest = JsonRpcRequest & {\n networkClientId?: NetworkClientId;\n origin?: string;\n};\n\nexport const createSelectedNetworkMiddleware = (\n messenger: SelectedNetworkControllerMessenger,\n): JsonRpcMiddleware => {\n const getNetworkClientIdForDomain = (origin: string) =>\n messenger.call(\n SelectedNetworkControllerActionTypes.getNetworkClientIdForDomain,\n origin,\n );\n\n return (req: SelectedNetworkMiddlewareJsonRpcRequest, _, next) => {\n if (!req.origin) {\n throw new Error(\"Request object is lacking an 'origin'\");\n }\n\n req.networkClientId = getNetworkClientIdForDomain(req.origin);\n return next();\n };\n};\n"],"mappings":";;;;;AAYO,IAAM,kCAAkC,CAC7C,cAC2C;AAC3C,QAAM,8BAA8B,CAAC,WACnC,UAAU;AAAA,IACR,qCAAqC;AAAA,IACrC;AAAA,EACF;AAEF,SAAO,CAAC,KAA8C,GAAG,SAAS;AAChE,QAAI,CAAC,IAAI,QAAQ;AACf,YAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AAEA,QAAI,kBAAkB,4BAA4B,IAAI,MAAM;AAC5D,WAAO,KAAK;AAAA,EACd;AACF;","names":[]} -\ No newline at end of file -diff --git a/dist/chunk-OGUVGN6R.js b/dist/chunk-OGUVGN6R.js -deleted file mode 100644 -index 1fb2670b25d9230aa3b0d1d496223ccee8f48263..0000000000000000000000000000000000000000 ---- a/dist/chunk-OGUVGN6R.js -+++ /dev/null -@@ -1,281 +0,0 @@ --"use strict";Object.defineProperty(exports, "__esModule", {value: true});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/SelectedNetworkController.ts --var _basecontroller = require('@metamask/base-controller'); --var _swappableobjproxy = require('@metamask/swappable-obj-proxy'); --var controllerName = "SelectedNetworkController"; --var stateMetadata = { -- domains: { persist: true, anonymous: false } --}; --var getDefaultState = () => ({ domains: {} }); --var snapsPrefixes = ["npm:", "local:"]; --var METAMASK_DOMAIN = "metamask"; --var SelectedNetworkControllerActionTypes = { -- getState: `${controllerName}:getState`, -- getNetworkClientIdForDomain: `${controllerName}:getNetworkClientIdForDomain`, -- setNetworkClientIdForDomain: `${controllerName}:setNetworkClientIdForDomain` --}; --var SelectedNetworkControllerEventTypes = { -- stateChange: `${controllerName}:stateChange` --}; --var _domainProxyMap, _useRequestQueuePreference, _registerMessageHandlers, registerMessageHandlers_fn, _setNetworkClientIdForDomain, setNetworkClientIdForDomain_fn, _unsetNetworkClientIdForDomain, unsetNetworkClientIdForDomain_fn, _domainHasPermissions, domainHasPermissions_fn, _resetAllPermissionedDomains, resetAllPermissionedDomains_fn; --var SelectedNetworkController = class extends _basecontroller.BaseController { -- /** -- * Construct a SelectedNetworkController controller. -- * -- * @param options - The controller options. -- * @param options.messenger - The restricted controller messenger for the EncryptionPublicKey controller. -- * @param options.state - The controllers initial state. -- * @param options.useRequestQueuePreference - A boolean indicating whether to use the request queue preference. -- * @param options.onPreferencesStateChange - A callback that is called when the preference state changes. -- * @param options.domainProxyMap - A map for storing domain-specific proxies that are held in memory only during use. -- */ -- constructor({ -- messenger, -- state = getDefaultState(), -- useRequestQueuePreference, -- onPreferencesStateChange, -- domainProxyMap -- }) { -- super({ -- name: controllerName, -- metadata: stateMetadata, -- messenger, -- state -- }); -- __privateAdd(this, _registerMessageHandlers); -- __privateAdd(this, _setNetworkClientIdForDomain); -- /** -- * This method is used when a domain is removed from the PermissionsController. -- * It will remove re-point the network proxy to the globally selected network in the domainProxyMap or, if no globally selected network client is available, delete the proxy. -- * -- * @param domain - The domain for which to unset the network client ID. -- */ -- __privateAdd(this, _unsetNetworkClientIdForDomain); -- __privateAdd(this, _domainHasPermissions); -- // Loop through all domains and for those with permissions it points that domain's proxy -- // to an unproxied instance of the globally selected network client. -- // NOT the NetworkController's proxy of the globally selected networkClient -- __privateAdd(this, _resetAllPermissionedDomains); -- __privateAdd(this, _domainProxyMap, void 0); -- __privateAdd(this, _useRequestQueuePreference, void 0); -- __privateSet(this, _useRequestQueuePreference, useRequestQueuePreference); -- __privateSet(this, _domainProxyMap, domainProxyMap); -- __privateMethod(this, _registerMessageHandlers, registerMessageHandlers_fn).call(this); -- this.messagingSystem.call("PermissionController:getSubjectNames").filter((domain) => this.state.domains[domain] === void 0).forEach( -- (domain) => this.setNetworkClientIdForDomain( -- domain, -- this.messagingSystem.call("NetworkController:getState").selectedNetworkClientId -- ) -- ); -- this.messagingSystem.subscribe( -- "PermissionController:stateChange", -- (_, patches) => { -- patches.forEach(({ op, path }) => { -- const isChangingSubject = path[0] === "subjects" && path[1] !== void 0; -- if (isChangingSubject && typeof path[1] === "string") { -- const domain = path[1]; -- if (op === "add" && this.state.domains[domain] === void 0) { -- this.setNetworkClientIdForDomain( -- domain, -- this.messagingSystem.call("NetworkController:getState").selectedNetworkClientId -- ); -- } else if (op === "remove" && this.state.domains[domain] !== void 0) { -- __privateMethod(this, _unsetNetworkClientIdForDomain, unsetNetworkClientIdForDomain_fn).call(this, domain); -- } -- } -- }); -- } -- ); -- this.messagingSystem.subscribe( -- "NetworkController:stateChange", -- ({ selectedNetworkClientId }, patches) => { -- patches.forEach(({ op, path }) => { -- if (op === "remove" && path[0] === "networkConfigurations") { -- const removedNetworkClientId = path[1]; -- Object.entries(this.state.domains).forEach( -- ([domain, networkClientIdForDomain]) => { -- if (networkClientIdForDomain === removedNetworkClientId) { -- this.setNetworkClientIdForDomain( -- domain, -- selectedNetworkClientId -- ); -- } -- } -- ); -- } -- }); -- } -- ); -- onPreferencesStateChange(({ useRequestQueue }) => { -- if (__privateGet(this, _useRequestQueuePreference) !== useRequestQueue) { -- if (!useRequestQueue) { -- Object.keys(this.state.domains).forEach((domain) => { -- __privateMethod(this, _unsetNetworkClientIdForDomain, unsetNetworkClientIdForDomain_fn).call(this, domain); -- }); -- } else { -- __privateMethod(this, _resetAllPermissionedDomains, resetAllPermissionedDomains_fn).call(this); -- } -- __privateSet(this, _useRequestQueuePreference, useRequestQueue); -- } -- }); -- } -- setNetworkClientIdForDomain(domain, networkClientId) { -- if (domain === METAMASK_DOMAIN) { -- throw new Error( -- `NetworkClientId for domain "${METAMASK_DOMAIN}" cannot be set on the SelectedNetworkController` -- ); -- } -- if (snapsPrefixes.some((prefix) => domain.startsWith(prefix))) { -- return; -- } -- if (!__privateMethod(this, _domainHasPermissions, domainHasPermissions_fn).call(this, domain)) { -- throw new Error( -- "NetworkClientId for domain cannot be called with a domain that has not yet been granted permissions" -- ); -- } -- __privateMethod(this, _setNetworkClientIdForDomain, setNetworkClientIdForDomain_fn).call(this, domain, networkClientId); -- } -- getNetworkClientIdForDomain(domain) { -- const { selectedNetworkClientId: metamaskSelectedNetworkClientId } = this.messagingSystem.call("NetworkController:getState"); -- if (!__privateGet(this, _useRequestQueuePreference)) { -- return metamaskSelectedNetworkClientId; -- } -- return this.state.domains[domain] ?? metamaskSelectedNetworkClientId; -- } -- /** -- * Accesses the provider and block tracker for the currently selected network. -- * -- * @param domain - the domain for the provider -- * @returns The proxy and block tracker proxies. -- */ -- getProviderAndBlockTracker(domain) { -- if (domain === METAMASK_DOMAIN || snapsPrefixes.some((prefix) => domain.startsWith(prefix))) { -- const networkClient = this.messagingSystem.call( -- "NetworkController:getSelectedNetworkClient" -- ); -- if (networkClient === void 0) { -- throw new Error("Selected network not initialized"); -- } -- return networkClient; -- } -- let networkProxy = __privateGet(this, _domainProxyMap).get(domain); -- if (networkProxy === void 0) { -- let networkClient; -- if (__privateGet(this, _useRequestQueuePreference) && __privateMethod(this, _domainHasPermissions, domainHasPermissions_fn).call(this, domain)) { -- const networkClientId = this.getNetworkClientIdForDomain(domain); -- networkClient = this.messagingSystem.call( -- "NetworkController:getNetworkClientById", -- networkClientId -- ); -- } else { -- networkClient = this.messagingSystem.call( -- "NetworkController:getSelectedNetworkClient" -- ); -- if (networkClient === void 0) { -- throw new Error("Selected network not initialized"); -- } -- } -- networkProxy = { -- provider: _swappableobjproxy.createEventEmitterProxy.call(void 0, networkClient.provider), -- blockTracker: _swappableobjproxy.createEventEmitterProxy.call(void 0, networkClient.blockTracker, { -- eventFilter: "skipInternal" -- }) -- }; -- __privateGet(this, _domainProxyMap).set(domain, networkProxy); -- } -- return networkProxy; -- } --}; --_domainProxyMap = new WeakMap(); --_useRequestQueuePreference = new WeakMap(); --_registerMessageHandlers = new WeakSet(); --registerMessageHandlers_fn = function() { -- this.messagingSystem.registerActionHandler( -- SelectedNetworkControllerActionTypes.getNetworkClientIdForDomain, -- this.getNetworkClientIdForDomain.bind(this) -- ); -- this.messagingSystem.registerActionHandler( -- SelectedNetworkControllerActionTypes.setNetworkClientIdForDomain, -- this.setNetworkClientIdForDomain.bind(this) -- ); --}; --_setNetworkClientIdForDomain = new WeakSet(); --setNetworkClientIdForDomain_fn = function(domain, networkClientId) { -- const networkClient = this.messagingSystem.call( -- "NetworkController:getNetworkClientById", -- networkClientId -- ); -- const networkProxy = this.getProviderAndBlockTracker(domain); -- networkProxy.provider.setTarget(networkClient.provider); -- networkProxy.blockTracker.setTarget(networkClient.blockTracker); -- this.update((state) => { -- state.domains[domain] = networkClientId; -- }); --}; --_unsetNetworkClientIdForDomain = new WeakSet(); --unsetNetworkClientIdForDomain_fn = function(domain) { -- const globallySelectedNetworkClient = this.messagingSystem.call( -- "NetworkController:getSelectedNetworkClient" -- ); -- const networkProxy = __privateGet(this, _domainProxyMap).get(domain); -- if (networkProxy && globallySelectedNetworkClient) { -- networkProxy.provider.setTarget(globallySelectedNetworkClient.provider); -- networkProxy.blockTracker.setTarget( -- globallySelectedNetworkClient.blockTracker -- ); -- } else if (networkProxy) { -- __privateGet(this, _domainProxyMap).delete(domain); -- } -- this.update((state) => { -- delete state.domains[domain]; -- }); --}; --_domainHasPermissions = new WeakSet(); --domainHasPermissions_fn = function(domain) { -- return this.messagingSystem.call( -- "PermissionController:hasPermissions", -- domain -- ); --}; --_resetAllPermissionedDomains = new WeakSet(); --resetAllPermissionedDomains_fn = function() { -- __privateGet(this, _domainProxyMap).forEach((_, domain) => { -- const { selectedNetworkClientId } = this.messagingSystem.call( -- "NetworkController:getState" -- ); -- if (__privateMethod(this, _domainHasPermissions, domainHasPermissions_fn).call(this, domain)) { -- __privateMethod(this, _setNetworkClientIdForDomain, setNetworkClientIdForDomain_fn).call(this, domain, selectedNetworkClientId); -- } -- }); --}; -- -- -- -- -- -- -- --exports.controllerName = controllerName; exports.METAMASK_DOMAIN = METAMASK_DOMAIN; exports.SelectedNetworkControllerActionTypes = SelectedNetworkControllerActionTypes; exports.SelectedNetworkControllerEventTypes = SelectedNetworkControllerEventTypes; exports.SelectedNetworkController = SelectedNetworkController; --//# sourceMappingURL=chunk-OGUVGN6R.js.map -\ No newline at end of file -diff --git a/dist/chunk-OGUVGN6R.js.map b/dist/chunk-OGUVGN6R.js.map -deleted file mode 100644 -index 4e38e02bd55874905ac6ea9c5874f01f102a7ff7..0000000000000000000000000000000000000000 ---- a/dist/chunk-OGUVGN6R.js.map -+++ /dev/null -@@ -1 +0,0 @@ --{"version":3,"sources":["../src/SelectedNetworkController.ts"],"names":[],"mappings":"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","sourcesContent":["import type { RestrictedControllerMessenger } from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type {\n BlockTrackerProxy,\n NetworkClientId,\n NetworkControllerGetNetworkClientByIdAction,\n NetworkControllerGetSelectedNetworkClientAction,\n NetworkControllerGetStateAction,\n NetworkControllerStateChangeEvent,\n ProviderProxy,\n} from '@metamask/network-controller';\nimport type {\n PermissionControllerStateChange,\n GetSubjects as PermissionControllerGetSubjectsAction,\n HasPermissions as PermissionControllerHasPermissions,\n} from '@metamask/permission-controller';\nimport { createEventEmitterProxy } from '@metamask/swappable-obj-proxy';\nimport type { Patch } from 'immer';\n\nexport const controllerName = 'SelectedNetworkController';\n\nconst stateMetadata = {\n domains: { persist: true, anonymous: false },\n};\n\nconst getDefaultState = () => ({ domains: {} });\n\n// npm and local are currently the only valid prefixes for snap domains\n// TODO: eventually we maybe want to pull this in from snaps-utils to ensure it stays in sync\n// For now it seems like overkill to add a dependency for this one constant\n// https://github.com/MetaMask/snaps/blob/2beee7803bfe9e540788a3558b546b9f55dc3cb4/packages/snaps-utils/src/types.ts#L120\nconst snapsPrefixes = ['npm:', 'local:'] as const;\n\nexport type Domain = string;\n\nexport const METAMASK_DOMAIN = 'metamask' as const;\n\nexport const SelectedNetworkControllerActionTypes = {\n getState: `${controllerName}:getState` as const,\n getNetworkClientIdForDomain:\n `${controllerName}:getNetworkClientIdForDomain` as const,\n setNetworkClientIdForDomain:\n `${controllerName}:setNetworkClientIdForDomain` as const,\n};\n\nexport const SelectedNetworkControllerEventTypes = {\n stateChange: `${controllerName}:stateChange` as const,\n};\n\nexport type SelectedNetworkControllerState = {\n domains: Record;\n};\n\nexport type SelectedNetworkControllerStateChangeEvent = {\n type: typeof SelectedNetworkControllerEventTypes.stateChange;\n payload: [SelectedNetworkControllerState, Patch[]];\n};\n\nexport type SelectedNetworkControllerGetSelectedNetworkStateAction = {\n type: typeof SelectedNetworkControllerActionTypes.getState;\n handler: () => SelectedNetworkControllerState;\n};\n\nexport type SelectedNetworkControllerGetNetworkClientIdForDomainAction = {\n type: typeof SelectedNetworkControllerActionTypes.getNetworkClientIdForDomain;\n handler: SelectedNetworkController['getNetworkClientIdForDomain'];\n};\n\nexport type SelectedNetworkControllerSetNetworkClientIdForDomainAction = {\n type: typeof SelectedNetworkControllerActionTypes.setNetworkClientIdForDomain;\n handler: SelectedNetworkController['setNetworkClientIdForDomain'];\n};\n\nexport type SelectedNetworkControllerActions =\n | SelectedNetworkControllerGetSelectedNetworkStateAction\n | SelectedNetworkControllerGetNetworkClientIdForDomainAction\n | SelectedNetworkControllerSetNetworkClientIdForDomainAction;\n\nexport type AllowedActions =\n | NetworkControllerGetNetworkClientByIdAction\n | NetworkControllerGetSelectedNetworkClientAction\n | NetworkControllerGetStateAction\n | PermissionControllerHasPermissions\n | PermissionControllerGetSubjectsAction;\n\nexport type SelectedNetworkControllerEvents =\n SelectedNetworkControllerStateChangeEvent;\n\nexport type AllowedEvents =\n | NetworkControllerStateChangeEvent\n | PermissionControllerStateChange;\n\nexport type SelectedNetworkControllerMessenger = RestrictedControllerMessenger<\n typeof controllerName,\n SelectedNetworkControllerActions | AllowedActions,\n SelectedNetworkControllerEvents | AllowedEvents,\n AllowedActions['type'],\n AllowedEvents['type']\n>;\n\nexport type SelectedNetworkControllerOptions = {\n state?: SelectedNetworkControllerState;\n messenger: SelectedNetworkControllerMessenger;\n useRequestQueuePreference: boolean;\n onPreferencesStateChange: (\n listener: (preferencesState: { useRequestQueue: boolean }) => void,\n ) => void;\n domainProxyMap: Map;\n};\n\nexport type NetworkProxy = {\n provider: ProviderProxy;\n blockTracker: BlockTrackerProxy;\n};\n\n/**\n * Controller for getting and setting the network for a particular domain.\n */\nexport class SelectedNetworkController extends BaseController<\n typeof controllerName,\n SelectedNetworkControllerState,\n SelectedNetworkControllerMessenger\n> {\n #domainProxyMap: Map;\n\n #useRequestQueuePreference: boolean;\n\n /**\n * Construct a SelectedNetworkController controller.\n *\n * @param options - The controller options.\n * @param options.messenger - The restricted controller messenger for the EncryptionPublicKey controller.\n * @param options.state - The controllers initial state.\n * @param options.useRequestQueuePreference - A boolean indicating whether to use the request queue preference.\n * @param options.onPreferencesStateChange - A callback that is called when the preference state changes.\n * @param options.domainProxyMap - A map for storing domain-specific proxies that are held in memory only during use.\n */\n constructor({\n messenger,\n state = getDefaultState(),\n useRequestQueuePreference,\n onPreferencesStateChange,\n domainProxyMap,\n }: SelectedNetworkControllerOptions) {\n super({\n name: controllerName,\n metadata: stateMetadata,\n messenger,\n state,\n });\n this.#useRequestQueuePreference = useRequestQueuePreference;\n this.#domainProxyMap = domainProxyMap;\n this.#registerMessageHandlers();\n\n // this is fetching all the dapp permissions from the PermissionsController and looking for any domains that are not in domains state in this controller. Then we take any missing domains and add them to state here, setting it with the globally selected networkClientId (fetched from the NetworkController)\n this.messagingSystem\n .call('PermissionController:getSubjectNames')\n .filter((domain) => this.state.domains[domain] === undefined)\n .forEach((domain) =>\n this.setNetworkClientIdForDomain(\n domain,\n this.messagingSystem.call('NetworkController:getState')\n .selectedNetworkClientId,\n ),\n );\n\n this.messagingSystem.subscribe(\n 'PermissionController:stateChange',\n (_, patches) => {\n patches.forEach(({ op, path }) => {\n const isChangingSubject =\n path[0] === 'subjects' && path[1] !== undefined;\n if (isChangingSubject && typeof path[1] === 'string') {\n const domain = path[1];\n if (op === 'add' && this.state.domains[domain] === undefined) {\n this.setNetworkClientIdForDomain(\n domain,\n this.messagingSystem.call('NetworkController:getState')\n .selectedNetworkClientId,\n );\n } else if (\n op === 'remove' &&\n this.state.domains[domain] !== undefined\n ) {\n this.#unsetNetworkClientIdForDomain(domain);\n }\n }\n });\n },\n );\n\n this.messagingSystem.subscribe(\n 'NetworkController:stateChange',\n ({ selectedNetworkClientId }, patches) => {\n patches.forEach(({ op, path }) => {\n // if a network is removed, update the networkClientId for all domains that were using it to the selected network\n if (op === 'remove' && path[0] === 'networkConfigurations') {\n const removedNetworkClientId = path[1] as NetworkClientId;\n Object.entries(this.state.domains).forEach(\n ([domain, networkClientIdForDomain]) => {\n if (networkClientIdForDomain === removedNetworkClientId) {\n this.setNetworkClientIdForDomain(\n domain,\n selectedNetworkClientId,\n );\n }\n },\n );\n }\n });\n },\n );\n\n onPreferencesStateChange(({ useRequestQueue }) => {\n if (this.#useRequestQueuePreference !== useRequestQueue) {\n if (!useRequestQueue) {\n // Loop through all domains and points each domain's proxy\n // to the NetworkController's own proxy of the globally selected networkClient\n Object.keys(this.state.domains).forEach((domain) => {\n this.#unsetNetworkClientIdForDomain(domain);\n });\n } else {\n this.#resetAllPermissionedDomains();\n }\n this.#useRequestQueuePreference = useRequestQueue;\n }\n });\n }\n\n #registerMessageHandlers(): void {\n this.messagingSystem.registerActionHandler(\n SelectedNetworkControllerActionTypes.getNetworkClientIdForDomain,\n this.getNetworkClientIdForDomain.bind(this),\n );\n this.messagingSystem.registerActionHandler(\n SelectedNetworkControllerActionTypes.setNetworkClientIdForDomain,\n this.setNetworkClientIdForDomain.bind(this),\n );\n }\n\n #setNetworkClientIdForDomain(\n domain: Domain,\n networkClientId: NetworkClientId,\n ) {\n const networkClient = this.messagingSystem.call(\n 'NetworkController:getNetworkClientById',\n networkClientId,\n );\n const networkProxy = this.getProviderAndBlockTracker(domain);\n networkProxy.provider.setTarget(networkClient.provider);\n networkProxy.blockTracker.setTarget(networkClient.blockTracker);\n\n this.update((state) => {\n state.domains[domain] = networkClientId;\n });\n }\n\n /**\n * This method is used when a domain is removed from the PermissionsController.\n * It will remove re-point the network proxy to the globally selected network in the domainProxyMap or, if no globally selected network client is available, delete the proxy.\n *\n * @param domain - The domain for which to unset the network client ID.\n */\n #unsetNetworkClientIdForDomain(domain: Domain) {\n const globallySelectedNetworkClient = this.messagingSystem.call(\n 'NetworkController:getSelectedNetworkClient',\n );\n const networkProxy = this.#domainProxyMap.get(domain);\n if (networkProxy && globallySelectedNetworkClient) {\n networkProxy.provider.setTarget(globallySelectedNetworkClient.provider);\n networkProxy.blockTracker.setTarget(\n globallySelectedNetworkClient.blockTracker,\n );\n } else if (networkProxy) {\n this.#domainProxyMap.delete(domain);\n }\n this.update((state) => {\n delete state.domains[domain];\n });\n }\n\n #domainHasPermissions(domain: Domain): boolean {\n return this.messagingSystem.call(\n 'PermissionController:hasPermissions',\n domain,\n );\n }\n\n // Loop through all domains and for those with permissions it points that domain's proxy\n // to an unproxied instance of the globally selected network client.\n // NOT the NetworkController's proxy of the globally selected networkClient\n #resetAllPermissionedDomains() {\n this.#domainProxyMap.forEach((_: NetworkProxy, domain: string) => {\n const { selectedNetworkClientId } = this.messagingSystem.call(\n 'NetworkController:getState',\n );\n // can't use public setNetworkClientIdForDomain because it will throw an error\n // rather than simply skip if the domain doesn't have permissions which can happen\n // in this case since proxies are added for each site the user visits\n if (this.#domainHasPermissions(domain)) {\n this.#setNetworkClientIdForDomain(domain, selectedNetworkClientId);\n }\n });\n }\n\n setNetworkClientIdForDomain(\n domain: Domain,\n networkClientId: NetworkClientId,\n ) {\n if (domain === METAMASK_DOMAIN) {\n throw new Error(\n `NetworkClientId for domain \"${METAMASK_DOMAIN}\" cannot be set on the SelectedNetworkController`,\n );\n }\n\n if (snapsPrefixes.some((prefix) => domain.startsWith(prefix))) {\n return;\n }\n\n if (!this.#domainHasPermissions(domain)) {\n throw new Error(\n 'NetworkClientId for domain cannot be called with a domain that has not yet been granted permissions',\n );\n }\n\n this.#setNetworkClientIdForDomain(domain, networkClientId);\n }\n\n getNetworkClientIdForDomain(domain: Domain): NetworkClientId {\n const { selectedNetworkClientId: metamaskSelectedNetworkClientId } =\n this.messagingSystem.call('NetworkController:getState');\n if (!this.#useRequestQueuePreference) {\n return metamaskSelectedNetworkClientId;\n }\n return this.state.domains[domain] ?? metamaskSelectedNetworkClientId;\n }\n\n /**\n * Accesses the provider and block tracker for the currently selected network.\n *\n * @param domain - the domain for the provider\n * @returns The proxy and block tracker proxies.\n */\n getProviderAndBlockTracker(domain: Domain): NetworkProxy {\n // If the domain is 'metamask' or a snap, return the NetworkController's globally selected network client proxy\n if (\n domain === METAMASK_DOMAIN ||\n snapsPrefixes.some((prefix) => domain.startsWith(prefix))\n ) {\n const networkClient = this.messagingSystem.call(\n 'NetworkController:getSelectedNetworkClient',\n );\n if (networkClient === undefined) {\n throw new Error('Selected network not initialized');\n }\n return networkClient;\n }\n\n let networkProxy = this.#domainProxyMap.get(domain);\n if (networkProxy === undefined) {\n let networkClient;\n if (\n this.#useRequestQueuePreference &&\n this.#domainHasPermissions(domain)\n ) {\n const networkClientId = this.getNetworkClientIdForDomain(domain);\n networkClient = this.messagingSystem.call(\n 'NetworkController:getNetworkClientById',\n networkClientId,\n );\n } else {\n networkClient = this.messagingSystem.call(\n 'NetworkController:getSelectedNetworkClient',\n );\n if (networkClient === undefined) {\n throw new Error('Selected network not initialized');\n }\n }\n networkProxy = {\n provider: createEventEmitterProxy(networkClient.provider),\n blockTracker: createEventEmitterProxy(networkClient.blockTracker, {\n eventFilter: 'skipInternal',\n }),\n };\n this.#domainProxyMap.set(domain, networkProxy);\n }\n return networkProxy;\n }\n}\n"]} -\ No newline at end of file -diff --git a/dist/chunk-S4D42VCM.mjs b/dist/chunk-S4D42VCM.mjs -deleted file mode 100644 -index a2a93432c8799383bef3bbe2cf3adf1ac3bda043..0000000000000000000000000000000000000000 ---- a/dist/chunk-S4D42VCM.mjs -+++ /dev/null -@@ -1,281 +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/SelectedNetworkController.ts --import { BaseController } from "@metamask/base-controller"; --import { createEventEmitterProxy } from "@metamask/swappable-obj-proxy"; --var controllerName = "SelectedNetworkController"; --var stateMetadata = { -- domains: { persist: true, anonymous: false } --}; --var getDefaultState = () => ({ domains: {} }); --var snapsPrefixes = ["npm:", "local:"]; --var METAMASK_DOMAIN = "metamask"; --var SelectedNetworkControllerActionTypes = { -- getState: `${controllerName}:getState`, -- getNetworkClientIdForDomain: `${controllerName}:getNetworkClientIdForDomain`, -- setNetworkClientIdForDomain: `${controllerName}:setNetworkClientIdForDomain` --}; --var SelectedNetworkControllerEventTypes = { -- stateChange: `${controllerName}:stateChange` --}; --var _domainProxyMap, _useRequestQueuePreference, _registerMessageHandlers, registerMessageHandlers_fn, _setNetworkClientIdForDomain, setNetworkClientIdForDomain_fn, _unsetNetworkClientIdForDomain, unsetNetworkClientIdForDomain_fn, _domainHasPermissions, domainHasPermissions_fn, _resetAllPermissionedDomains, resetAllPermissionedDomains_fn; --var SelectedNetworkController = class extends BaseController { -- /** -- * Construct a SelectedNetworkController controller. -- * -- * @param options - The controller options. -- * @param options.messenger - The restricted controller messenger for the EncryptionPublicKey controller. -- * @param options.state - The controllers initial state. -- * @param options.useRequestQueuePreference - A boolean indicating whether to use the request queue preference. -- * @param options.onPreferencesStateChange - A callback that is called when the preference state changes. -- * @param options.domainProxyMap - A map for storing domain-specific proxies that are held in memory only during use. -- */ -- constructor({ -- messenger, -- state = getDefaultState(), -- useRequestQueuePreference, -- onPreferencesStateChange, -- domainProxyMap -- }) { -- super({ -- name: controllerName, -- metadata: stateMetadata, -- messenger, -- state -- }); -- __privateAdd(this, _registerMessageHandlers); -- __privateAdd(this, _setNetworkClientIdForDomain); -- /** -- * This method is used when a domain is removed from the PermissionsController. -- * It will remove re-point the network proxy to the globally selected network in the domainProxyMap or, if no globally selected network client is available, delete the proxy. -- * -- * @param domain - The domain for which to unset the network client ID. -- */ -- __privateAdd(this, _unsetNetworkClientIdForDomain); -- __privateAdd(this, _domainHasPermissions); -- // Loop through all domains and for those with permissions it points that domain's proxy -- // to an unproxied instance of the globally selected network client. -- // NOT the NetworkController's proxy of the globally selected networkClient -- __privateAdd(this, _resetAllPermissionedDomains); -- __privateAdd(this, _domainProxyMap, void 0); -- __privateAdd(this, _useRequestQueuePreference, void 0); -- __privateSet(this, _useRequestQueuePreference, useRequestQueuePreference); -- __privateSet(this, _domainProxyMap, domainProxyMap); -- __privateMethod(this, _registerMessageHandlers, registerMessageHandlers_fn).call(this); -- this.messagingSystem.call("PermissionController:getSubjectNames").filter((domain) => this.state.domains[domain] === void 0).forEach( -- (domain) => this.setNetworkClientIdForDomain( -- domain, -- this.messagingSystem.call("NetworkController:getState").selectedNetworkClientId -- ) -- ); -- this.messagingSystem.subscribe( -- "PermissionController:stateChange", -- (_, patches) => { -- patches.forEach(({ op, path }) => { -- const isChangingSubject = path[0] === "subjects" && path[1] !== void 0; -- if (isChangingSubject && typeof path[1] === "string") { -- const domain = path[1]; -- if (op === "add" && this.state.domains[domain] === void 0) { -- this.setNetworkClientIdForDomain( -- domain, -- this.messagingSystem.call("NetworkController:getState").selectedNetworkClientId -- ); -- } else if (op === "remove" && this.state.domains[domain] !== void 0) { -- __privateMethod(this, _unsetNetworkClientIdForDomain, unsetNetworkClientIdForDomain_fn).call(this, domain); -- } -- } -- }); -- } -- ); -- this.messagingSystem.subscribe( -- "NetworkController:stateChange", -- ({ selectedNetworkClientId }, patches) => { -- patches.forEach(({ op, path }) => { -- if (op === "remove" && path[0] === "networkConfigurations") { -- const removedNetworkClientId = path[1]; -- Object.entries(this.state.domains).forEach( -- ([domain, networkClientIdForDomain]) => { -- if (networkClientIdForDomain === removedNetworkClientId) { -- this.setNetworkClientIdForDomain( -- domain, -- selectedNetworkClientId -- ); -- } -- } -- ); -- } -- }); -- } -- ); -- onPreferencesStateChange(({ useRequestQueue }) => { -- if (__privateGet(this, _useRequestQueuePreference) !== useRequestQueue) { -- if (!useRequestQueue) { -- Object.keys(this.state.domains).forEach((domain) => { -- __privateMethod(this, _unsetNetworkClientIdForDomain, unsetNetworkClientIdForDomain_fn).call(this, domain); -- }); -- } else { -- __privateMethod(this, _resetAllPermissionedDomains, resetAllPermissionedDomains_fn).call(this); -- } -- __privateSet(this, _useRequestQueuePreference, useRequestQueue); -- } -- }); -- } -- setNetworkClientIdForDomain(domain, networkClientId) { -- if (domain === METAMASK_DOMAIN) { -- throw new Error( -- `NetworkClientId for domain "${METAMASK_DOMAIN}" cannot be set on the SelectedNetworkController` -- ); -- } -- if (snapsPrefixes.some((prefix) => domain.startsWith(prefix))) { -- return; -- } -- if (!__privateMethod(this, _domainHasPermissions, domainHasPermissions_fn).call(this, domain)) { -- throw new Error( -- "NetworkClientId for domain cannot be called with a domain that has not yet been granted permissions" -- ); -- } -- __privateMethod(this, _setNetworkClientIdForDomain, setNetworkClientIdForDomain_fn).call(this, domain, networkClientId); -- } -- getNetworkClientIdForDomain(domain) { -- const { selectedNetworkClientId: metamaskSelectedNetworkClientId } = this.messagingSystem.call("NetworkController:getState"); -- if (!__privateGet(this, _useRequestQueuePreference)) { -- return metamaskSelectedNetworkClientId; -- } -- return this.state.domains[domain] ?? metamaskSelectedNetworkClientId; -- } -- /** -- * Accesses the provider and block tracker for the currently selected network. -- * -- * @param domain - the domain for the provider -- * @returns The proxy and block tracker proxies. -- */ -- getProviderAndBlockTracker(domain) { -- if (domain === METAMASK_DOMAIN || snapsPrefixes.some((prefix) => domain.startsWith(prefix))) { -- const networkClient = this.messagingSystem.call( -- "NetworkController:getSelectedNetworkClient" -- ); -- if (networkClient === void 0) { -- throw new Error("Selected network not initialized"); -- } -- return networkClient; -- } -- let networkProxy = __privateGet(this, _domainProxyMap).get(domain); -- if (networkProxy === void 0) { -- let networkClient; -- if (__privateGet(this, _useRequestQueuePreference) && __privateMethod(this, _domainHasPermissions, domainHasPermissions_fn).call(this, domain)) { -- const networkClientId = this.getNetworkClientIdForDomain(domain); -- networkClient = this.messagingSystem.call( -- "NetworkController:getNetworkClientById", -- networkClientId -- ); -- } else { -- networkClient = this.messagingSystem.call( -- "NetworkController:getSelectedNetworkClient" -- ); -- if (networkClient === void 0) { -- throw new Error("Selected network not initialized"); -- } -- } -- networkProxy = { -- provider: createEventEmitterProxy(networkClient.provider), -- blockTracker: createEventEmitterProxy(networkClient.blockTracker, { -- eventFilter: "skipInternal" -- }) -- }; -- __privateGet(this, _domainProxyMap).set(domain, networkProxy); -- } -- return networkProxy; -- } --}; --_domainProxyMap = new WeakMap(); --_useRequestQueuePreference = new WeakMap(); --_registerMessageHandlers = new WeakSet(); --registerMessageHandlers_fn = function() { -- this.messagingSystem.registerActionHandler( -- SelectedNetworkControllerActionTypes.getNetworkClientIdForDomain, -- this.getNetworkClientIdForDomain.bind(this) -- ); -- this.messagingSystem.registerActionHandler( -- SelectedNetworkControllerActionTypes.setNetworkClientIdForDomain, -- this.setNetworkClientIdForDomain.bind(this) -- ); --}; --_setNetworkClientIdForDomain = new WeakSet(); --setNetworkClientIdForDomain_fn = function(domain, networkClientId) { -- const networkClient = this.messagingSystem.call( -- "NetworkController:getNetworkClientById", -- networkClientId -- ); -- const networkProxy = this.getProviderAndBlockTracker(domain); -- networkProxy.provider.setTarget(networkClient.provider); -- networkProxy.blockTracker.setTarget(networkClient.blockTracker); -- this.update((state) => { -- state.domains[domain] = networkClientId; -- }); --}; --_unsetNetworkClientIdForDomain = new WeakSet(); --unsetNetworkClientIdForDomain_fn = function(domain) { -- const globallySelectedNetworkClient = this.messagingSystem.call( -- "NetworkController:getSelectedNetworkClient" -- ); -- const networkProxy = __privateGet(this, _domainProxyMap).get(domain); -- if (networkProxy && globallySelectedNetworkClient) { -- networkProxy.provider.setTarget(globallySelectedNetworkClient.provider); -- networkProxy.blockTracker.setTarget( -- globallySelectedNetworkClient.blockTracker -- ); -- } else if (networkProxy) { -- __privateGet(this, _domainProxyMap).delete(domain); -- } -- this.update((state) => { -- delete state.domains[domain]; -- }); --}; --_domainHasPermissions = new WeakSet(); --domainHasPermissions_fn = function(domain) { -- return this.messagingSystem.call( -- "PermissionController:hasPermissions", -- domain -- ); --}; --_resetAllPermissionedDomains = new WeakSet(); --resetAllPermissionedDomains_fn = function() { -- __privateGet(this, _domainProxyMap).forEach((_, domain) => { -- const { selectedNetworkClientId } = this.messagingSystem.call( -- "NetworkController:getState" -- ); -- if (__privateMethod(this, _domainHasPermissions, domainHasPermissions_fn).call(this, domain)) { -- __privateMethod(this, _setNetworkClientIdForDomain, setNetworkClientIdForDomain_fn).call(this, domain, selectedNetworkClientId); -- } -- }); --}; -- --export { -- controllerName, -- METAMASK_DOMAIN, -- SelectedNetworkControllerActionTypes, -- SelectedNetworkControllerEventTypes, -- SelectedNetworkController --}; --//# sourceMappingURL=chunk-S4D42VCM.mjs.map -\ No newline at end of file -diff --git a/dist/chunk-S4D42VCM.mjs.map b/dist/chunk-S4D42VCM.mjs.map -deleted file mode 100644 -index b05eba0cbdeb5af4729d92e7b8de695c2d2a75e3..0000000000000000000000000000000000000000 ---- a/dist/chunk-S4D42VCM.mjs.map -+++ /dev/null -@@ -1 +0,0 @@ --{"version":3,"sources":["../src/SelectedNetworkController.ts"],"sourcesContent":["import type { RestrictedControllerMessenger } from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type {\n BlockTrackerProxy,\n NetworkClientId,\n NetworkControllerGetNetworkClientByIdAction,\n NetworkControllerGetSelectedNetworkClientAction,\n NetworkControllerGetStateAction,\n NetworkControllerStateChangeEvent,\n ProviderProxy,\n} from '@metamask/network-controller';\nimport type {\n PermissionControllerStateChange,\n GetSubjects as PermissionControllerGetSubjectsAction,\n HasPermissions as PermissionControllerHasPermissions,\n} from '@metamask/permission-controller';\nimport { createEventEmitterProxy } from '@metamask/swappable-obj-proxy';\nimport type { Patch } from 'immer';\n\nexport const controllerName = 'SelectedNetworkController';\n\nconst stateMetadata = {\n domains: { persist: true, anonymous: false },\n};\n\nconst getDefaultState = () => ({ domains: {} });\n\n// npm and local are currently the only valid prefixes for snap domains\n// TODO: eventually we maybe want to pull this in from snaps-utils to ensure it stays in sync\n// For now it seems like overkill to add a dependency for this one constant\n// https://github.com/MetaMask/snaps/blob/2beee7803bfe9e540788a3558b546b9f55dc3cb4/packages/snaps-utils/src/types.ts#L120\nconst snapsPrefixes = ['npm:', 'local:'] as const;\n\nexport type Domain = string;\n\nexport const METAMASK_DOMAIN = 'metamask' as const;\n\nexport const SelectedNetworkControllerActionTypes = {\n getState: `${controllerName}:getState` as const,\n getNetworkClientIdForDomain:\n `${controllerName}:getNetworkClientIdForDomain` as const,\n setNetworkClientIdForDomain:\n `${controllerName}:setNetworkClientIdForDomain` as const,\n};\n\nexport const SelectedNetworkControllerEventTypes = {\n stateChange: `${controllerName}:stateChange` as const,\n};\n\nexport type SelectedNetworkControllerState = {\n domains: Record;\n};\n\nexport type SelectedNetworkControllerStateChangeEvent = {\n type: typeof SelectedNetworkControllerEventTypes.stateChange;\n payload: [SelectedNetworkControllerState, Patch[]];\n};\n\nexport type SelectedNetworkControllerGetSelectedNetworkStateAction = {\n type: typeof SelectedNetworkControllerActionTypes.getState;\n handler: () => SelectedNetworkControllerState;\n};\n\nexport type SelectedNetworkControllerGetNetworkClientIdForDomainAction = {\n type: typeof SelectedNetworkControllerActionTypes.getNetworkClientIdForDomain;\n handler: SelectedNetworkController['getNetworkClientIdForDomain'];\n};\n\nexport type SelectedNetworkControllerSetNetworkClientIdForDomainAction = {\n type: typeof SelectedNetworkControllerActionTypes.setNetworkClientIdForDomain;\n handler: SelectedNetworkController['setNetworkClientIdForDomain'];\n};\n\nexport type SelectedNetworkControllerActions =\n | SelectedNetworkControllerGetSelectedNetworkStateAction\n | SelectedNetworkControllerGetNetworkClientIdForDomainAction\n | SelectedNetworkControllerSetNetworkClientIdForDomainAction;\n\nexport type AllowedActions =\n | NetworkControllerGetNetworkClientByIdAction\n | NetworkControllerGetSelectedNetworkClientAction\n | NetworkControllerGetStateAction\n | PermissionControllerHasPermissions\n | PermissionControllerGetSubjectsAction;\n\nexport type SelectedNetworkControllerEvents =\n SelectedNetworkControllerStateChangeEvent;\n\nexport type AllowedEvents =\n | NetworkControllerStateChangeEvent\n | PermissionControllerStateChange;\n\nexport type SelectedNetworkControllerMessenger = RestrictedControllerMessenger<\n typeof controllerName,\n SelectedNetworkControllerActions | AllowedActions,\n SelectedNetworkControllerEvents | AllowedEvents,\n AllowedActions['type'],\n AllowedEvents['type']\n>;\n\nexport type SelectedNetworkControllerOptions = {\n state?: SelectedNetworkControllerState;\n messenger: SelectedNetworkControllerMessenger;\n useRequestQueuePreference: boolean;\n onPreferencesStateChange: (\n listener: (preferencesState: { useRequestQueue: boolean }) => void,\n ) => void;\n domainProxyMap: Map;\n};\n\nexport type NetworkProxy = {\n provider: ProviderProxy;\n blockTracker: BlockTrackerProxy;\n};\n\n/**\n * Controller for getting and setting the network for a particular domain.\n */\nexport class SelectedNetworkController extends BaseController<\n typeof controllerName,\n SelectedNetworkControllerState,\n SelectedNetworkControllerMessenger\n> {\n #domainProxyMap: Map;\n\n #useRequestQueuePreference: boolean;\n\n /**\n * Construct a SelectedNetworkController controller.\n *\n * @param options - The controller options.\n * @param options.messenger - The restricted controller messenger for the EncryptionPublicKey controller.\n * @param options.state - The controllers initial state.\n * @param options.useRequestQueuePreference - A boolean indicating whether to use the request queue preference.\n * @param options.onPreferencesStateChange - A callback that is called when the preference state changes.\n * @param options.domainProxyMap - A map for storing domain-specific proxies that are held in memory only during use.\n */\n constructor({\n messenger,\n state = getDefaultState(),\n useRequestQueuePreference,\n onPreferencesStateChange,\n domainProxyMap,\n }: SelectedNetworkControllerOptions) {\n super({\n name: controllerName,\n metadata: stateMetadata,\n messenger,\n state,\n });\n this.#useRequestQueuePreference = useRequestQueuePreference;\n this.#domainProxyMap = domainProxyMap;\n this.#registerMessageHandlers();\n\n // this is fetching all the dapp permissions from the PermissionsController and looking for any domains that are not in domains state in this controller. Then we take any missing domains and add them to state here, setting it with the globally selected networkClientId (fetched from the NetworkController)\n this.messagingSystem\n .call('PermissionController:getSubjectNames')\n .filter((domain) => this.state.domains[domain] === undefined)\n .forEach((domain) =>\n this.setNetworkClientIdForDomain(\n domain,\n this.messagingSystem.call('NetworkController:getState')\n .selectedNetworkClientId,\n ),\n );\n\n this.messagingSystem.subscribe(\n 'PermissionController:stateChange',\n (_, patches) => {\n patches.forEach(({ op, path }) => {\n const isChangingSubject =\n path[0] === 'subjects' && path[1] !== undefined;\n if (isChangingSubject && typeof path[1] === 'string') {\n const domain = path[1];\n if (op === 'add' && this.state.domains[domain] === undefined) {\n this.setNetworkClientIdForDomain(\n domain,\n this.messagingSystem.call('NetworkController:getState')\n .selectedNetworkClientId,\n );\n } else if (\n op === 'remove' &&\n this.state.domains[domain] !== undefined\n ) {\n this.#unsetNetworkClientIdForDomain(domain);\n }\n }\n });\n },\n );\n\n this.messagingSystem.subscribe(\n 'NetworkController:stateChange',\n ({ selectedNetworkClientId }, patches) => {\n patches.forEach(({ op, path }) => {\n // if a network is removed, update the networkClientId for all domains that were using it to the selected network\n if (op === 'remove' && path[0] === 'networkConfigurations') {\n const removedNetworkClientId = path[1] as NetworkClientId;\n Object.entries(this.state.domains).forEach(\n ([domain, networkClientIdForDomain]) => {\n if (networkClientIdForDomain === removedNetworkClientId) {\n this.setNetworkClientIdForDomain(\n domain,\n selectedNetworkClientId,\n );\n }\n },\n );\n }\n });\n },\n );\n\n onPreferencesStateChange(({ useRequestQueue }) => {\n if (this.#useRequestQueuePreference !== useRequestQueue) {\n if (!useRequestQueue) {\n // Loop through all domains and points each domain's proxy\n // to the NetworkController's own proxy of the globally selected networkClient\n Object.keys(this.state.domains).forEach((domain) => {\n this.#unsetNetworkClientIdForDomain(domain);\n });\n } else {\n this.#resetAllPermissionedDomains();\n }\n this.#useRequestQueuePreference = useRequestQueue;\n }\n });\n }\n\n #registerMessageHandlers(): void {\n this.messagingSystem.registerActionHandler(\n SelectedNetworkControllerActionTypes.getNetworkClientIdForDomain,\n this.getNetworkClientIdForDomain.bind(this),\n );\n this.messagingSystem.registerActionHandler(\n SelectedNetworkControllerActionTypes.setNetworkClientIdForDomain,\n this.setNetworkClientIdForDomain.bind(this),\n );\n }\n\n #setNetworkClientIdForDomain(\n domain: Domain,\n networkClientId: NetworkClientId,\n ) {\n const networkClient = this.messagingSystem.call(\n 'NetworkController:getNetworkClientById',\n networkClientId,\n );\n const networkProxy = this.getProviderAndBlockTracker(domain);\n networkProxy.provider.setTarget(networkClient.provider);\n networkProxy.blockTracker.setTarget(networkClient.blockTracker);\n\n this.update((state) => {\n state.domains[domain] = networkClientId;\n });\n }\n\n /**\n * This method is used when a domain is removed from the PermissionsController.\n * It will remove re-point the network proxy to the globally selected network in the domainProxyMap or, if no globally selected network client is available, delete the proxy.\n *\n * @param domain - The domain for which to unset the network client ID.\n */\n #unsetNetworkClientIdForDomain(domain: Domain) {\n const globallySelectedNetworkClient = this.messagingSystem.call(\n 'NetworkController:getSelectedNetworkClient',\n );\n const networkProxy = this.#domainProxyMap.get(domain);\n if (networkProxy && globallySelectedNetworkClient) {\n networkProxy.provider.setTarget(globallySelectedNetworkClient.provider);\n networkProxy.blockTracker.setTarget(\n globallySelectedNetworkClient.blockTracker,\n );\n } else if (networkProxy) {\n this.#domainProxyMap.delete(domain);\n }\n this.update((state) => {\n delete state.domains[domain];\n });\n }\n\n #domainHasPermissions(domain: Domain): boolean {\n return this.messagingSystem.call(\n 'PermissionController:hasPermissions',\n domain,\n );\n }\n\n // Loop through all domains and for those with permissions it points that domain's proxy\n // to an unproxied instance of the globally selected network client.\n // NOT the NetworkController's proxy of the globally selected networkClient\n #resetAllPermissionedDomains() {\n this.#domainProxyMap.forEach((_: NetworkProxy, domain: string) => {\n const { selectedNetworkClientId } = this.messagingSystem.call(\n 'NetworkController:getState',\n );\n // can't use public setNetworkClientIdForDomain because it will throw an error\n // rather than simply skip if the domain doesn't have permissions which can happen\n // in this case since proxies are added for each site the user visits\n if (this.#domainHasPermissions(domain)) {\n this.#setNetworkClientIdForDomain(domain, selectedNetworkClientId);\n }\n });\n }\n\n setNetworkClientIdForDomain(\n domain: Domain,\n networkClientId: NetworkClientId,\n ) {\n if (domain === METAMASK_DOMAIN) {\n throw new Error(\n `NetworkClientId for domain \"${METAMASK_DOMAIN}\" cannot be set on the SelectedNetworkController`,\n );\n }\n\n if (snapsPrefixes.some((prefix) => domain.startsWith(prefix))) {\n return;\n }\n\n if (!this.#domainHasPermissions(domain)) {\n throw new Error(\n 'NetworkClientId for domain cannot be called with a domain that has not yet been granted permissions',\n );\n }\n\n this.#setNetworkClientIdForDomain(domain, networkClientId);\n }\n\n getNetworkClientIdForDomain(domain: Domain): NetworkClientId {\n const { selectedNetworkClientId: metamaskSelectedNetworkClientId } =\n this.messagingSystem.call('NetworkController:getState');\n if (!this.#useRequestQueuePreference) {\n return metamaskSelectedNetworkClientId;\n }\n return this.state.domains[domain] ?? metamaskSelectedNetworkClientId;\n }\n\n /**\n * Accesses the provider and block tracker for the currently selected network.\n *\n * @param domain - the domain for the provider\n * @returns The proxy and block tracker proxies.\n */\n getProviderAndBlockTracker(domain: Domain): NetworkProxy {\n // If the domain is 'metamask' or a snap, return the NetworkController's globally selected network client proxy\n if (\n domain === METAMASK_DOMAIN ||\n snapsPrefixes.some((prefix) => domain.startsWith(prefix))\n ) {\n const networkClient = this.messagingSystem.call(\n 'NetworkController:getSelectedNetworkClient',\n );\n if (networkClient === undefined) {\n throw new Error('Selected network not initialized');\n }\n return networkClient;\n }\n\n let networkProxy = this.#domainProxyMap.get(domain);\n if (networkProxy === undefined) {\n let networkClient;\n if (\n this.#useRequestQueuePreference &&\n this.#domainHasPermissions(domain)\n ) {\n const networkClientId = this.getNetworkClientIdForDomain(domain);\n networkClient = this.messagingSystem.call(\n 'NetworkController:getNetworkClientById',\n networkClientId,\n );\n } else {\n networkClient = this.messagingSystem.call(\n 'NetworkController:getSelectedNetworkClient',\n );\n if (networkClient === undefined) {\n throw new Error('Selected network not initialized');\n }\n }\n networkProxy = {\n provider: createEventEmitterProxy(networkClient.provider),\n blockTracker: createEventEmitterProxy(networkClient.blockTracker, {\n eventFilter: 'skipInternal',\n }),\n };\n this.#domainProxyMap.set(domain, networkProxy);\n }\n return networkProxy;\n }\n}\n"],"mappings":"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","names":[]} -\ No newline at end of file -diff --git a/dist/chunk-ZY7ETPVE.mjs b/dist/chunk-ZY7ETPVE.mjs -deleted file mode 100644 -index 50a21cfa6988a3e32a4c1a20f9113eaec7bf99ac..0000000000000000000000000000000000000000 ---- a/dist/chunk-ZY7ETPVE.mjs -+++ /dev/null -@@ -1,23 +0,0 @@ --import { -- SelectedNetworkControllerActionTypes --} from "./chunk-S4D42VCM.mjs"; -- --// src/SelectedNetworkMiddleware.ts --var createSelectedNetworkMiddleware = (messenger) => { -- const getNetworkClientIdForDomain = (origin) => messenger.call( -- SelectedNetworkControllerActionTypes.getNetworkClientIdForDomain, -- origin -- ); -- return (req, _, next) => { -- if (!req.origin) { -- throw new Error("Request object is lacking an 'origin'"); -- } -- req.networkClientId = getNetworkClientIdForDomain(req.origin); -- return next(); -- }; --}; -- --export { -- createSelectedNetworkMiddleware --}; --//# sourceMappingURL=chunk-ZY7ETPVE.mjs.map -\ No newline at end of file -diff --git a/dist/chunk-ZY7ETPVE.mjs.map b/dist/chunk-ZY7ETPVE.mjs.map -deleted file mode 100644 -index fbcfd73d57f291b4f129caa47ef0c9614987cc30..0000000000000000000000000000000000000000 ---- a/dist/chunk-ZY7ETPVE.mjs.map -+++ /dev/null -@@ -1 +0,0 @@ --{"version":3,"sources":["../src/SelectedNetworkMiddleware.ts"],"sourcesContent":["import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine';\nimport type { NetworkClientId } from '@metamask/network-controller';\nimport type { Json, JsonRpcParams, JsonRpcRequest } from '@metamask/utils';\n\nimport type { SelectedNetworkControllerMessenger } from './SelectedNetworkController';\nimport { SelectedNetworkControllerActionTypes } from './SelectedNetworkController';\n\nexport type SelectedNetworkMiddlewareJsonRpcRequest = JsonRpcRequest & {\n networkClientId?: NetworkClientId;\n origin?: string;\n};\n\nexport const createSelectedNetworkMiddleware = (\n messenger: SelectedNetworkControllerMessenger,\n): JsonRpcMiddleware => {\n const getNetworkClientIdForDomain = (origin: string) =>\n messenger.call(\n SelectedNetworkControllerActionTypes.getNetworkClientIdForDomain,\n origin,\n );\n\n return (req: SelectedNetworkMiddlewareJsonRpcRequest, _, next) => {\n if (!req.origin) {\n throw new Error(\"Request object is lacking an 'origin'\");\n }\n\n req.networkClientId = getNetworkClientIdForDomain(req.origin);\n return next();\n };\n};\n"],"mappings":";;;;;AAYO,IAAM,kCAAkC,CAC7C,cAC2C;AAC3C,QAAM,8BAA8B,CAAC,WACnC,UAAU;AAAA,IACR,qCAAqC;AAAA,IACrC;AAAA,EACF;AAEF,SAAO,CAAC,KAA8C,GAAG,SAAS;AAChE,QAAI,CAAC,IAAI,QAAQ;AACf,YAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AAEA,QAAI,kBAAkB,4BAA4B,IAAI,MAAM;AAC5D,WAAO,KAAK;AAAA,EACd;AACF;","names":[]} -\ No newline at end of file -diff --git a/dist/index.js b/dist/index.js -index 4c9b1798d88466e11abbcf1c0616f2617d73ab5a..cd71df24b3f6424f640c62ce8a7938fcf15ae480 100644 ---- a/dist/index.js -+++ b/dist/index.js -@@ -1,17 +1,17 @@ - "use strict";Object.defineProperty(exports, "__esModule", {value: true}); - --var _chunk6W2ETVOHjs = require('./chunk-6W2ETVOH.js'); -+var _chunkANSSZMDIjs = require('./chunk-ANSSZMDI.js'); - - - - - --var _chunkOGUVGN6Rjs = require('./chunk-OGUVGN6R.js'); -+var _chunkCECZWJ42js = require('./chunk-CECZWJ42.js'); - - - - - - --exports.METAMASK_DOMAIN = _chunkOGUVGN6Rjs.METAMASK_DOMAIN; exports.SelectedNetworkController = _chunkOGUVGN6Rjs.SelectedNetworkController; exports.SelectedNetworkControllerActionTypes = _chunkOGUVGN6Rjs.SelectedNetworkControllerActionTypes; exports.SelectedNetworkControllerEventTypes = _chunkOGUVGN6Rjs.SelectedNetworkControllerEventTypes; exports.createSelectedNetworkMiddleware = _chunk6W2ETVOHjs.createSelectedNetworkMiddleware; -+exports.METAMASK_DOMAIN = _chunkCECZWJ42js.METAMASK_DOMAIN; exports.SelectedNetworkController = _chunkCECZWJ42js.SelectedNetworkController; exports.SelectedNetworkControllerActionTypes = _chunkCECZWJ42js.SelectedNetworkControllerActionTypes; exports.SelectedNetworkControllerEventTypes = _chunkCECZWJ42js.SelectedNetworkControllerEventTypes; exports.createSelectedNetworkMiddleware = _chunkANSSZMDIjs.createSelectedNetworkMiddleware; - //# sourceMappingURL=index.js.map -\ No newline at end of file -diff --git a/dist/index.mjs b/dist/index.mjs -index 8780cadd8a535b745960ebde8d7aa49989451ef6..0c7b103ca9cb6a604bf5f831bccef3002abd7c5f 100644 ---- a/dist/index.mjs -+++ b/dist/index.mjs -@@ -1,12 +1,12 @@ - import { - createSelectedNetworkMiddleware --} from "./chunk-ZY7ETPVE.mjs"; -+} from "./chunk-HFN7TKJS.mjs"; - import { - METAMASK_DOMAIN, - SelectedNetworkController, - SelectedNetworkControllerActionTypes, - SelectedNetworkControllerEventTypes --} from "./chunk-S4D42VCM.mjs"; -+} from "./chunk-7DSTEJNI.mjs"; - export { - METAMASK_DOMAIN, - SelectedNetworkController, -diff --git a/dist/tsconfig.build.tsbuildinfo b/dist/tsconfig.build.tsbuildinfo -index 0f3a7a7e18a90ebf82da9962818a7e552c892d9c..8a2c9ef72e1d3a4302d4bda2c3e634414225fd17 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/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/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","../../../node_modules/@metamask/rpc-errors/dist/types/utils.d.ts","../../../node_modules/@metamask/rpc-errors/dist/types/classes.d.ts","../../../node_modules/@metamask/rpc-errors/dist/types/errors.d.ts","../../../node_modules/@metamask/rpc-errors/dist/types/error-constants.d.ts","../../../node_modules/@metamask/rpc-errors/dist/types/index.d.ts","../../approval-controller/dist/types/approvalcontroller.d.ts","../../approval-controller/dist/types/errors.d.ts","../../approval-controller/dist/types/index.d.ts","../../permission-controller/dist/types/permission-middleware.d.ts","../../permission-controller/dist/types/subjectmetadatacontroller.d.ts","../../permission-controller/dist/types/permissioncontroller.d.ts","../../permission-controller/dist/types/permission.d.ts","../../permission-controller/dist/types/caveat.d.ts","../../permission-controller/dist/types/errors.d.ts","../../permission-controller/dist/types/utils.d.ts","../../permission-controller/dist/types/rpc-methods/getpermissions.d.ts","../../permission-controller/dist/types/rpc-methods/requestpermissions.d.ts","../../permission-controller/dist/types/rpc-methods/revokepermissions.d.ts","../../permission-controller/dist/types/rpc-methods/index.d.ts","../../permission-controller/dist/types/index.d.ts","../src/selectednetworkcontroller.ts","../src/selectednetworkmiddleware.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/uuid/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","e475453e7140e95542332943d3052fe4c7430ad1efce42b3e9157f1fee8cbc5f","ebfdf904255ce746c9d30117c2edef355fb19bf7650478d2405f39f0e4f302e6","f3f63b48addb8e2ea9d20bb671c3c306413b3daa39996d0ae52f63d8e32158e1","a50599c08934a62f11657bdbe0dc929ab66da1b1f09974408fd9a33ec1bb8060","5a20e7d6c630b91be15e9b837853173829d00273197481dc8d3e94df61105a71","8d478048d71cc16f806d4b71b252ecb67c7444ccf4f4b09b29a312712184f859","b4000a0a525fa921e896cbdb32ae802c9684f0fd371b5fc69e7310f7918cc2c3","9df4662ca3dbc2522bc115833ee04faa1afbb4e249a85ef4a0a09c621346bd08","b25d9065cf1c1f537a140bbc508e953ed2262f77134574c432d206ff36f4bdbf","1b103313097041aa9cd705a682c652f08613cb5cf8663321061c0902f845e81c","68ccec8662818911d8a12b8ed028bc5729fb4f1d34793c4701265ba60bc73cf4","5f85b8b79dc4d36af672c035b2beb71545de63a5d60bccbeee64c260941672ab","b3d48529ae61dc27d0bfbfa2cb3e0dff8189644bd155bdf5df1e8e14669f7043","40fe4b689225816b31fe5794c0fbf3534568819709e40295ead998a2bc1ab237","f65b5e33b9ad545a1eebbd6afe857314725ad42aaf069913e33f928ab3e4990a","fb6f2a87beb7fb1f4c2b762d0c76a9459fc91f557231569b0ee21399e22aa13d","31c858dc85996fac4b7fa944e1016d5c72f514930a72357ab5001097bf6511c7","3de30a871b3340be8b679c52aa12f90dd1c8c60874517be58968fdbcc4d79445","6fd985bd31eaf77542625306fb0404d32bff978990f0a06428e5f0b9a3b58109","34693fb4a5e771e11668219221344dd1bd7d8b77ed005a1c1d965fb559be8406","7394959e5a741b185456e1ef5d64599c36c60a323207450991e7a42e08911419",{"version":"e44ea2d6b7b853f6c81482416db43dafc11944561b810e469ae423085511ce7e","affectsGlobalScope":true},"f51b4042a3ac86f1f707500a9768f88d0b0c1fc3f3e45a73333283dea720cdc6",{"version":"a7289d79eb84a59d2475b4d0136b4404be3cfdd17c3ea46b9194add1d645df01","affectsGlobalScope":true},"0bb26fa2a90ee890eed57ee812c71fa84d3d07850163ec4a204de86412cc57c1","132ca47da601c60141dd6f10bd08c70d0620177e5638439df2464ec3945b6d98",{"version":"55d2bbae076fed7269c3e16faeb32f988f558427b7a1c3bf04aa7551ab86ae90","affectsGlobalScope":true},"a40826e8476694e90da94aa008283a7de50d1dafd37beada623863f1901cb7fb","cf83847c9264dcd592b6c89c1542925b899b277228687f3638614e3fa784cf76","3a41ebe7f089d50f447466b35b6cabb8b584c0994fc9809d0cd0a4ebc41e1239","7693b90b3075deaccafd5efb467bf9f2b747a3075be888652ef73e64396d8628","0c42d6cba77d9ad1cf45256ccb8489aa502fe2dbee1ec9048a29d49f5d532e73","2cf89c17245db65d175d4ef699cd68187516f9b3ae5c572fc0b9ad60f35dc223","5f20d20b7607174caf1a6da9141aeb9f2142159ae2410ca30c7a0fccd1d19c99",{"version":"a34d65f61ec5aac5b53502c8b0bd4e00d217bccb95bf94d449e2571baa11fb8c","affectsGlobalScope":true},"8d42e5af5fb0a96a77e135ce84cc60636c9bad39d9dba043a4efe9d1bdeb3cc3","56fcc451e9065eb121c9cc4c1b9994a816306f3b0b3b1fce7ad59f0ac97a9999","8a6f12b74d3e6c4f5e1b918cb8e64ae16bc6756cf0d48bcc28a28e1bf26ca0cd","c3759b5bc5cc40f5988d86a497741a80fa91258629ae50a2b3735e774cd377cc","bf268a0aea37ad4ae3b7a9b58559190b6fc01ea16a31e35cd05817a0a60f895a","45dd82fb5aea9b12b2a90b427b28f3a014e8b2ee9b74087a5ab882841cb5fbc5",{"version":"d7dad6db394a3d9f7b49755e4b610fbf8ed6eb0c9810ae5f1a119f6b5d76de45","affectsGlobalScope":true},"48b2f9302651eb31acd5be69bb4e6b35797a7fcd6b77391d10a4ccadf7dc3609","0c8c917ef15498c827bd494a0ef365e9f76deb211f8acbb86932e20489310788","dd67d2b5e4e8a182a38de8e69fb736945eaa4588e0909c14e01a14bd3cc1fd1e",{"version":"9cdc2c6144b03822c9842505d09945bcf813b86827fdb260dd7586b63abc19bf","affectsGlobalScope":true},{"version":"2923dee3c897f03e91b54a210cdbefea7290562f0ac4b948667d4c9ee844b79e","affectsGlobalScope":true},"79169698d09a2be54b14f3bcad2575b414bf3525063fde0a1e4fcd5d6efd380e","051d939bcf77caa3cef3282708ab3a6fdfb741a7366e1d74a9e7603b67417ec3","0be79b3ff0f16b6c2f9bc8c4cc7097ea417d8d67f8267f7e1eec8e32b548c2ff","1c61ffa3a71b77363b30d19832c269ef62fba787f5610cac7254728d3b69ab2e","a234d62ae81d012ebf23898a45672edf3e5c93ecf5a438a42b96c08dd68cde43","269929a24b2816343a178008ac9ae9248304d92a8ba8e233055e0ed6dbe6ef71","09ed02a725db002693236b6dfc49b2c6eb5557be1421d7fbe4f07cfe38211d92","09d801ff4a303d4976d4b9cb94af3a9097c4a70345e662d176975872d2998e51","c8558b01389b5f7610ac293aa612ccea2ae64d83af43b49f8142f190be1f414c","c40fdf7b2e18df49ce0568e37f0292c12807a0748be79e272745e7216bed2606",{"version":"b10b426c56e220b5093bf8a2446ee47af47263b7b1a03f4b18e42326b231b111","affectsGlobalScope":true},"4e228e78c1e9b0a75c70588d59288f63a6258e8b1fe4a67b0c53fe03461421d9","b4635ef36bee17e1304337d591c3b6b461ecdbc1876d0effbe6a581e62201fe5","205d50c24359ead003dc537b9b65d2a64208dfdffe368f403cf9e0357831db9e","1265fddcd0c68be9d2a3b29805d0280484c961264dd95e0b675f7bd91f777e78",{"version":"e4507242542bd499238f693d88b2d32e22177cc508854625f87bcc9bc3fa1256","affectsGlobalScope":true},{"version":"d942354e4966a98d3a92d1b1af0b4ac06f33af3f88116743e2c304c027ca26ef","affectsGlobalScope":true},"39f0808e5be3cb38674726c21fe2eb453c55e48a901679b4ce30fef85549b892","6afd66a7432ef100027ea110449e874196381e019e30eda7e7d8ca390366b7a8","befb8a9a78ac99d8fbc3ed392810489a7b90760c7a58934e8f1c8538f581cff3","e670bdf01540d35c170fae68edfd2f288eff909936780c379d6a9103b787b22c","867f95abf1df444aab146b19847391fc2f922a55f6a970a27ed8226766cee29f",{"version":"ab9b9a36e5284fd8d3bf2f7d5fcbc60052f25f27e4d20954782099282c60d23e","affectsGlobalScope":true},"88003d9ab15507806f41b120be6d407c1afe566c2f6689ebe3a034dd5ec0c8dc","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","6a9c5127096b35264eb7cd21b2417bfc1d42cceca9ba4ce2bb0c3410b7816042","93b7325b49dfbf613d940ed0e471216657b2d77459dac34f1b5b1678f08f884c","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","a12eaa942232703a8a8477a2f240ad5a2c26c595012ea8f128224e77984099c4","4070c2f1c3434fcf84886e04d30d82cd650ee443e53b82b404b144175cf8741e","2cea9689efa8591732096235abe7f084fc29c92badd5b0897a5e876b77e71887","4ed4e504126014fee13aaef5e3fc140f2ff7031ff3a8b5386717905820ea2d09","8129a34006218a6f3cdc81bbd438d5429eb18b08b4338a26977ac3b4df129d75","30d2170e1a718b5035611af55e3618b4ba8f42f0749bb52ee593da6082c4e2ce","98ef38666d88ec9699a722053e07ede65d3042f693fe7ff8c786e53dbb6fd43b","a3b8b6be7620897d1e481e8650c980a210a138fceb6e710eaf95fd9dd0dfe94a","12c89d0e32758c120a569045f21cf5b77244f86792611ced8de7f86b37e77781","9f9e5bae412fa5909fae636d6733aee27a108cc2ed5b13980611016336774d3c","662fe197bba64bd3f17ee118058cd2d0d2dbe33d7c0c865fd6365d90bfc44e1e","030519c351f800551cac2658038804969ca4584d2c0175a710602ac234ca1340","0278a6939ca83cd040b08ff8c5fc7838b6693ddc52f22526bf158e6b10e0246c","c2d6206e5ba4fd3063b01218c2b3b997afc1cfbeb49fcee991fa8595842ce53d","29c188a2c660f99f1b4835022e011c4268d7af989d4b7dda33c0a69ca1a777f8","1ed0bf138e87912d741e28333b58cbf814ae863783b3b404d2454cbabb9c5fc0","3452ee7d8ef0b1bbd47b2a56924a1dc3c79dc84a19d212e9dc496f92e4943aa0","99510e20e3d4816e283e59e8f0f31f603b2f026648240ffdb1ca9f24be678419","037d1fbeb96dc35600814be14d0fbf31acf35f1d7b443ea33514937de69c2bf2","64d1859b7dd9f419ba08e064c2b16b1a5edde0316d6c2bb1833c9381d4dffc3d","69b0f96ca137c0dba05f321a159141ad36f79cfba2fffcc29d131e280275e6f2","7bb64cf513a2b1cf7a94f81ca201f3d76a9b17af556f0cfc4e2707443e6caa66","a9add2a29da4cd0b617ae89f196b3f2172a031aeb086922cdf097236eef8b008","99afac3e6e683ee3111e499f9919953e9489cb39cad74363717aa3805e91db51","c49f92b83968f4ee0b6026396a9b6e2d6fee8b660d08a90efb03355ce3433a7a","4fdc6afe4d7ef6aeb32ac0818d47e99f98a31d0696abc4cb2af489c78ac1ba1d","d73d5a0e854037d43781b2d5d33f4b95ee509e0ddede677aade79fbee6a97cdc","35d14e1ae04be300828b1a1614316b9312a009cfd5e29fa56f94c2a9f60b12df","d160fe745f9c3b72d7b9036fdb2b6b500a520d43e36bb842c927b6fe59ea2c23",{"version":"4a415bf08be658b3f7ab2c2754a077e36a32e08aabb73aad26de47a45c0fa81e","signature":"3a4f9e7087c566703447928d15c234bd0bcc63a2a50b6ec39ed37c9bc0342310"},{"version":"506bcad28e45e13c23c2c25c9691e0dc42d8755a0e24b9a48586f51b5bebae8f","signature":"3a12771c76c5ed979438dc0e390224bd3d8661bcbea18114f77c4c6d9de5b8b2"},{"version":"6fbdb35bc3b9cfb225c48108b5bacb5c0d67bbcb677cf13b912e15a852551023","signature":"ee06131adf64c6cd201de36563174cff0e160b81438be186a7bd85e6c2b54fa7"},"a5aaeca001d2f69093d04aac4db321e4c338fd9b20cbc4f0b0af3dc6ae0f235b","cc957354aa3c94c9961ebf46282cfde1e81d107fc5785a61f62c67f1dd3ac2eb","8041cfce439ff29d339742389de04c136e3029d6b1817f07b2d7fcbfb7534990","93de1c6dab503f053efe8d304cb522bb3a89feab8c98f307a674a4fae04773e9","29a46d003ca3c721e6405f00dee7e3de91b14e09701eba5d887bf76fb2d47d38","069bebfee29864e3955378107e243508b163e77ab10de6a5ee03ae06939f0bb9","9990f9e566bc3c2c6e38df81294fb756e7f5b7b0e5bb17ab75384e190548b4b6",{"version":"64d4b35c5456adf258d2cf56c341e203a073253f229ef3208fc0d5020253b241","affectsGlobalScope":true},"ee7d8894904b465b072be0d2e4b45cf6b887cdba16a467645c4e200982ece7ea","f3d8c757e148ad968f0d98697987db363070abada5f503da3c06aefd9d4248c1","df95e00612c1faa5e0e7ef0dba589b18665bbeb3221db2b6cee1fe4d0e61921f","afe73051ff6a03a9565cbd8ebb0e956ee3df5e913ad5c1ded64218aabfa3dcb5","8b06ac3faeacb8484d84ddb44571d8f410697f98d7bfa86c0fda60373a9f5215","7eb06594824ada538b1d8b48c3925a83e7db792f47a081a62cf3e5c4e23cf0ee","f5638f7c2f12a9a1a57b5c41b3c1ea7db3876c003bab68e6a57afd6bcc169af0","0d14fa22c41fdc7277e6f71473b20ebc07f40f00e38875142335d5b63cdfc9d2","d8aab31ba8e618cc3eea10b0945de81cb93b7e8150a013a482332263b9305322","462bccdf75fcafc1ae8c30400c9425e1a4681db5d605d1a0edb4f990a54d8094","5923d8facbac6ecf7c84739a5c701a57af94a6f6648d6229a6c768cf28f0f8cb","7adecb2c3238794c378d336a8182d4c3dd2c4fa6fa1785e2797a3db550edea62","dc12dc0e5aa06f4e1a7692149b78f89116af823b9e1f1e4eae140cd3e0e674e6","1bfc6565b90c8771615cd8cfcf9b36efc0275e5e83ac7d9181307e96eb495161","8a8a96898906f065f296665e411f51010b51372fa260d5373bf9f64356703190","7f82ef88bdb67d9a850dd1c7cd2d690f33e0f0acd208e3c9eba086f3670d4f73",{"version":"ccfd8774cd9b929f63ff7dcf657977eb0652e3547f1fcac1b3a1dc5db22d4d58","affectsGlobalScope":true},"d92dc90fecd2552db74d8dc3c6fb4db9145b2aa0efe2c127236ba035969068d4","96d14f21b7652903852eef49379d04dbda28c16ed36468f8c9fa08f7c14c9538","675e702f2032766a91eeadee64f51014c64688525da99dccd8178f0c599f13a8","458111fc89d11d2151277c822dfdc1a28fa5b6b2493cf942e37d4cd0a6ee5f22","19c816167e076e7c24f074389c6cf3ed87bdbb917d1ea439ca281f9d26db2439","187119ff4f9553676a884e296089e131e8cc01691c546273b1d0089c3533ce42","febf0b2de54781102b00f61653b21377390a048fbf5262718c91860d11ff34a6","98f9d826db9cd99d27a01a59ee5f22863df00ccf1aaf43e1d7db80ebf716f7c3","0aaef8cded245bf5036a7a40b65622dd6c4da71f7a35343112edbe112b348a1e","00baffbe8a2f2e4875367479489b5d43b5fc1429ecb4a4cc98cfc3009095f52a","dcd91d3b697cb650b95db5471189b99815af5db2a1cd28760f91e0b12ede8ed5","3c92b6dfd43cc1c2485d9eba5ff0b74a19bb8725b692773ef1d66dac48cda4bd","3cf0d343c2276842a5b617f22ba82af6322c7cfe8bb52238ffc0c491a3c21019","df996e25faa505f85aeb294d15ebe61b399cf1d1e49959cdfaf2cc0815c203f9",{"version":"f2eff8704452659641164876c1ef0df4174659ce7311b0665798ea3f556fa9ad","affectsGlobalScope":true},"8841e2aa774b89bd23302dede20663306dc1b9902431ac64b24be8b8d0e3f649","2b8264b2fefd7367e0f20e2c04eed5d3038831fe00f5efbc110ff0131aab899b","a73a445c1e0a6d0f8b48e8eb22dc9d647896783a7f8991cbbc31c0d94bf1f5a2","d88a5e779faf033be3d52142a04fbe1cb96009868e3bbdd296b2bc6c59e06c0e","cd1d2f103b79002cd94b85a640a103f094227a2c4c53bc8af1fdbf4e13d9729e","5e379df3d61561c2ed7789b5995b9ba2143bbba21a905e2381e16efe7d1fa424","f07a137bbe2de7a122c37bfea00e761975fb264c49f18003d398d71b3fb35a5f","3dce33e7eb25594863b8e615f14a45ab98190d85953436750644212d8a18c066","2b93035328f7778d200252681c1d86285d501ed424825a18f81e4c3028aa51d9","2ac9c8332c5f8510b8bdd571f8271e0f39b0577714d5e95c1e79a12b2616f069","42c21aa963e7b86fa00801d96e88b36803188018d5ad91db2a9101bccd40b3ff","d31eb848cdebb4c55b4893b335a7c0cca95ad66dee13cbb7d0893810c0a9c301","b9f96255e1048ed2ea33ec553122716f0e57fc1c3ad778e9aa15f5b46547bd23","7a9e0a564fee396cacf706523b5aeed96e04c6b871a8bebefad78499fbffc5bc","906c751ef5822ec0dadcea2f0e9db64a33fb4ee926cc9f7efa38afe5d5371b2a","5387c049e9702f2d2d7ece1a74836a14b47fbebe9bbeb19f94c580a37c855351","c68391fb9efad5d99ff332c65b1606248c4e4a9f1dd9a087204242b56c7126d6","e9cf02252d3a0ced987d24845dcb1f11c1be5541f17e5daa44c6de2d18138d0c","e8b02b879754d85f48489294f99147aeccc352c760d95a6fe2b6e49cd400b2fe","9f6908ab3d8a86c68b86e38578afc7095114e66b2fc36a2a96e9252aac3998e0","0eedb2344442b143ddcd788f87096961cd8572b64f10b4afc3356aa0460171c6","71405cc70f183d029cc5018375f6c35117ffdaf11846c35ebf85ee3956b1b2a6","c68baff4d8ba346130e9753cefe2e487a16731bf17e05fdacc81e8c9a26aae9d","2cd15528d8bb5d0453aa339b4b52e0696e8b07e790c153831c642c3dea5ac8af","479d622e66283ffa9883fbc33e441f7fc928b2277ff30aacbec7b7761b4e9579","ade307876dc5ca267ca308d09e737b611505e015c535863f22420a11fffc1c54","f8cdefa3e0dee639eccbe9794b46f90291e5fd3989fcba60d2f08fde56179fb9","86c5a62f99aac7053976e317dbe9acb2eaf903aaf3d2e5bb1cafe5c2df7b37a8","2b300954ce01a8343866f737656e13243e86e5baef51bd0631b21dcef1f6e954","a2d409a9ffd872d6b9d78ead00baa116bbc73cfa959fce9a2f29d3227876b2a1","b288936f560cd71f4a6002953290de9ff8dfbfbf37f5a9391be5c83322324898","61178a781ef82e0ff54f9430397e71e8f365fc1e3725e0e5346f2de7b0d50dfa","6a6ccb37feb3aad32d9be026a3337db195979cd5727a616fc0f557e974101a54","c649ea79205c029a02272ef55b7ab14ada0903db26144d2205021f24727ac7a3","38e2b02897c6357bbcff729ef84c736727b45cc152abe95a7567caccdfad2a1d","d6610ea7e0b1a7686dba062a1e5544dd7d34140f4545305b7c6afaebfb348341","3dee35db743bdba2c8d19aece7ac049bde6fa587e195d86547c882784e6ba34c","b15e55c5fa977c2f25ca0b1db52cfa2d1fd4bf0baf90a8b90d4a7678ca462ff1","f41d30972724714763a2698ae949fbc463afb203b5fa7c4ad7e4de0871129a17","843dd7b6a7c6269fd43827303f5cbe65c1fecabc30b4670a50d5a15d57daeeb9","f06d8b8567ee9fd799bf7f806efe93b67683ef24f4dea5b23ef12edff4434d9d","6017384f697ff38bc3ef6a546df5b230c3c31329db84cbfe686c83bec011e2b2","e1a5b30d9248549ca0c0bb1d653bafae20c64c4aa5928cc4cd3017b55c2177b0","a593632d5878f17295bd53e1c77f27bf4c15212822f764a2bfc1702f4b413fa0","a868a534ba1c2ca9060b8a13b0ffbbbf78b4be7b0ff80d8c75b02773f7192c29","da7545aba8f54a50fde23e2ede00158dc8112560d934cee58098dfb03aae9b9d","34baf65cfee92f110d6653322e2120c2d368ee64b3c7981dff08ed105c4f19b0","a1a261624efb3a00ff346b13580f70f3463b8cdcc58b60f5793ff11785d52cab","f83b320cceccfc48457a818d18fc9a006ab18d0bdd727aa2c2e73dc1b4a45e98","9d92b037978bb9525bc4b673ebddd443277542e010c0aef019c03a170ccdaa73","b0d10e46cfe3f6c476b69af02eaa38e4ccc7430221ce3109ae84bb9fb8282298","fab58e600970e66547644a44bc9918e3223aa2cbd9e8763cec004b2cfb48827e","70e9a18da08294f75bf23e46c7d69e67634c0765d355887b9b41f0d959e1426e","ed44ba6b95f08b758748be7902e0cc54178b1337c56d0e2469c77b03f63ac73b"],"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":[[120,247],[120],[91,120,127,128,129,144],[120,128,129,145,146],[120,127,128],[120,127,144,147,150],[120,127,147,150,151],[120,148,149,150,152,153],[120,127,150],[120,127,144,147,148,149,152],[120,127,135],[120,127],[91,120,127],[80,120,127],[120,131,132,133,134,135,136,137,138,139,140,141,142,143],[120,127,133,134],[120,127,133,135],[120,166,224],[120,224,225],[120,224,225,226,227],[120,166],[120,198],[120,198,199,200],[64,120],[67,120],[64,67,120],[65,66,67,68,69,70,71,72,73,74,75,120,155,158,159,160,161,162,163,164,165],[58,64,65,120],[67,73,75,120,154],[120,157],[67,68,120],[64,120,161],[120,193,194],[120,247,248,249,250,251],[120,247,249],[120,156],[120,254,255,256],[92,120,127],[120,259],[120,260],[120,271],[120,265,270],[120,274,276,277,278,279,280,281,282,283,284,285,286],[120,274,275,277,278,279,280,281,282,283,284,285,286],[120,275,276,277,278,279,280,281,282,283,284,285,286],[120,274,275,276,278,279,280,281,282,283,284,285,286],[120,274,275,276,277,279,280,281,282,283,284,285,286],[120,274,275,276,277,278,280,281,282,283,284,285,286],[120,274,275,276,277,278,279,281,282,283,284,285,286],[120,274,275,276,277,278,279,280,282,283,284,285,286],[120,274,275,276,277,278,279,280,281,283,284,285,286],[120,274,275,276,277,278,279,280,281,282,284,285,286],[120,274,275,276,277,278,279,280,281,282,283,285,286],[120,274,275,276,277,278,279,280,281,282,283,284,286],[120,274,275,276,277,278,279,280,281,282,283,284,285],[76,120],[79,120],[80,85,111,120],[81,91,92,99,108,119,120],[81,82,91,99,120],[83,120],[84,85,92,100,120],[85,108,116,120],[86,88,91,99,120],[87,120],[88,89,120],[90,91,120],[91,120],[91,92,93,108,119,120],[91,92,93,108,120],[91,94,99,108,119,120],[91,92,94,95,99,108,116,119,120],[94,96,108,116,119,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],[91,97,120],[98,119,120,124],[88,91,99,108,120],[100,120],[101,120],[79,102,120],[103,118,120,124],[104,120],[105,120],[91,106,120],[106,107,120,122],[80,91,108,109,110,120],[80,108,110,120],[108,109,120],[111,120],[112,120],[91,114,115,120],[114,115,120],[85,99,116,120],[117,120],[99,118,120],[80,94,105,119,120],[85,120],[108,120,121],[120,122],[120,123],[80,85,91,93,102,108,119,120,122,124],[108,120,125],[120,127,292],[120,295,334],[120,295,319,334],[120,334],[120,295],[120,295,320,334],[120,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,321,322,323,324,325,326,327,328,329,330,331,332,333],[120,320,334],[120,335],[120,339],[120,203],[120,215,216,217],[120,203,214,215],[120,178],[120,178,179,180,181,182],[120,167,168,169,170,171,172,173,174,175,176,177],[120,263,266],[120,263,266,267,268],[120,265],[120,262,269],[120,264],[57,59,60,61,62,63,120],[57,58,120],[59,120],[58,59,120],[57,59,120],[120,166,187,228],[120,229,230],[120,166,183,184,185],[120,184],[56,120,184,185,186],[120,185],[120,188],[120,188,189,192,196],[120,195],[120,166,190,191],[120,211,212,213],[120,210,211],[120,166,210,211],[120,166,203,210],[120,166,204],[120,204,205,206,207,208,209],[120,166,203],[120,219],[120,202,219,221,222],[120,166,187,190,197,201,202,219,220],[120,166,197,214,218],[120,166,235],[120,166,228,235],[120,233,234,235,236,237,238,242],[120,166,210,243],[120,166,187,197,233,234,236],[120,166,187,197,231,232,233,235,236],[120,234,235,238],[120,239,240,241,243],[120,235,238],[120,166,235,238],[120,166,187,234],[120,166,210,235,236],[120,244,245],[120,183,187,201,223,243],[120,166,210,223,244],[244,245],[183,187,223,243],[166,210,223,244]],"referencedMap":[[249,1],[247,2],[145,3],[128,2],[147,4],[129,5],[146,2],[151,6],[152,7],[148,7],[154,8],[149,7],[153,9],[150,10],[136,11],[133,12],[140,13],[134,11],[131,14],[139,2],[144,15],[141,2],[142,2],[143,2],[138,12],[135,16],[132,2],[137,17],[190,2],[225,18],[227,2],[226,19],[228,20],[224,21],[203,13],[199,22],[200,22],[201,23],[198,2],[65,24],[66,24],[68,25],[69,24],[70,24],[71,26],[72,2],[73,2],[74,2],[67,24],[166,27],[75,28],[155,29],[158,30],[159,2],[160,2],[161,2],[162,2],[163,2],[164,31],[165,32],[193,2],[195,33],[194,2],[252,34],[248,1],[250,35],[251,1],[191,12],[157,36],[253,2],[254,2],[257,37],[255,2],[258,38],[259,2],[260,39],[261,40],[272,41],[271,42],[256,2],[273,2],[275,43],[276,44],[274,45],[277,46],[278,47],[279,48],[280,49],[281,50],[282,51],[283,52],[284,53],[285,54],[286,55],[287,2],[156,2],[76,56],[77,56],[79,57],[80,58],[81,59],[82,60],[83,61],[84,62],[85,63],[86,64],[87,65],[88,66],[89,66],[90,67],[91,68],[92,69],[93,70],[78,2],[126,2],[94,71],[95,72],[96,73],[127,74],[97,75],[98,76],[99,77],[100,78],[101,79],[102,80],[103,81],[104,82],[105,83],[106,84],[107,85],[108,86],[110,87],[109,88],[111,89],[112,90],[113,2],[114,91],[115,92],[116,93],[117,94],[118,95],[119,96],[120,97],[121,98],[122,99],[123,100],[124,101],[125,102],[288,2],[289,12],[290,2],[291,2],[293,103],[292,2],[294,12],[319,104],[320,105],[295,106],[298,106],[317,104],[318,104],[308,104],[307,107],[305,104],[300,104],[313,104],[311,104],[315,104],[299,104],[312,104],[316,104],[301,104],[302,104],[314,104],[296,104],[303,104],[304,104],[306,104],[310,104],[321,108],[309,104],[297,104],[334,109],[333,2],[328,108],[330,110],[329,108],[322,108],[323,108],[325,108],[327,108],[331,110],[332,110],[324,110],[326,110],[336,111],[335,2],[337,2],[338,2],[339,2],[340,112],[130,2],[262,2],[215,113],[218,114],[216,115],[217,115],[177,2],[174,116],[176,116],[175,116],[173,116],[183,117],[178,118],[182,2],[179,2],[181,2],[180,2],[169,116],[170,116],[171,116],[167,2],[168,2],[172,116],[263,2],[267,119],[269,120],[268,119],[266,121],[270,122],[265,123],[264,2],[57,2],[64,124],[59,125],[60,126],[61,126],[62,127],[63,127],[58,128],[8,2],[10,2],[9,2],[2,2],[11,2],[12,2],[13,2],[14,2],[15,2],[16,2],[17,2],[18,2],[3,2],[4,2],[22,2],[19,2],[20,2],[21,2],[23,2],[24,2],[25,2],[5,2],[26,2],[27,2],[28,2],[29,2],[6,2],[33,2],[30,2],[31,2],[32,2],[34,2],[7,2],[35,2],[40,2],[41,2],[36,2],[37,2],[38,2],[39,2],[1,2],[42,2],[229,129],[230,2],[231,130],[56,2],[186,131],[185,132],[187,133],[184,134],[189,135],[197,136],[196,137],[188,2],[192,138],[214,139],[212,140],[213,141],[211,142],[205,143],[206,143],[207,2],[208,143],[210,144],[204,145],[209,143],[202,2],[220,146],[222,146],[223,147],[221,148],[219,149],[236,150],[237,151],[243,152],[232,153],[235,154],[234,155],[239,156],[242,157],[240,158],[241,159],[233,160],[238,161],[246,162],[244,163],[245,164],[47,2],[48,2],[49,2],[50,2],[51,2],[52,2],[43,2],[53,2],[54,2],[55,2],[44,2],[45,2],[46,2]],"exportedModulesMap":[[249,1],[247,2],[145,3],[128,2],[147,4],[129,5],[146,2],[151,6],[152,7],[148,7],[154,8],[149,7],[153,9],[150,10],[136,11],[133,12],[140,13],[134,11],[131,14],[139,2],[144,15],[141,2],[142,2],[143,2],[138,12],[135,16],[132,2],[137,17],[190,2],[225,18],[227,2],[226,19],[228,20],[224,21],[203,13],[199,22],[200,22],[201,23],[198,2],[65,24],[66,24],[68,25],[69,24],[70,24],[71,26],[72,2],[73,2],[74,2],[67,24],[166,27],[75,28],[155,29],[158,30],[159,2],[160,2],[161,2],[162,2],[163,2],[164,31],[165,32],[193,2],[195,33],[194,2],[252,34],[248,1],[250,35],[251,1],[191,12],[157,36],[253,2],[254,2],[257,37],[255,2],[258,38],[259,2],[260,39],[261,40],[272,41],[271,42],[256,2],[273,2],[275,43],[276,44],[274,45],[277,46],[278,47],[279,48],[280,49],[281,50],[282,51],[283,52],[284,53],[285,54],[286,55],[287,2],[156,2],[76,56],[77,56],[79,57],[80,58],[81,59],[82,60],[83,61],[84,62],[85,63],[86,64],[87,65],[88,66],[89,66],[90,67],[91,68],[92,69],[93,70],[78,2],[126,2],[94,71],[95,72],[96,73],[127,74],[97,75],[98,76],[99,77],[100,78],[101,79],[102,80],[103,81],[104,82],[105,83],[106,84],[107,85],[108,86],[110,87],[109,88],[111,89],[112,90],[113,2],[114,91],[115,92],[116,93],[117,94],[118,95],[119,96],[120,97],[121,98],[122,99],[123,100],[124,101],[125,102],[288,2],[289,12],[290,2],[291,2],[293,103],[292,2],[294,12],[319,104],[320,105],[295,106],[298,106],[317,104],[318,104],[308,104],[307,107],[305,104],[300,104],[313,104],[311,104],[315,104],[299,104],[312,104],[316,104],[301,104],[302,104],[314,104],[296,104],[303,104],[304,104],[306,104],[310,104],[321,108],[309,104],[297,104],[334,109],[333,2],[328,108],[330,110],[329,108],[322,108],[323,108],[325,108],[327,108],[331,110],[332,110],[324,110],[326,110],[336,111],[335,2],[337,2],[338,2],[339,2],[340,112],[130,2],[262,2],[215,113],[218,114],[216,115],[217,115],[177,2],[174,116],[176,116],[175,116],[173,116],[183,117],[178,118],[182,2],[179,2],[181,2],[180,2],[169,116],[170,116],[171,116],[167,2],[168,2],[172,116],[263,2],[267,119],[269,120],[268,119],[266,121],[270,122],[265,123],[264,2],[57,2],[64,124],[59,125],[60,126],[61,126],[62,127],[63,127],[58,128],[8,2],[10,2],[9,2],[2,2],[11,2],[12,2],[13,2],[14,2],[15,2],[16,2],[17,2],[18,2],[3,2],[4,2],[22,2],[19,2],[20,2],[21,2],[23,2],[24,2],[25,2],[5,2],[26,2],[27,2],[28,2],[29,2],[6,2],[33,2],[30,2],[31,2],[32,2],[34,2],[7,2],[35,2],[40,2],[41,2],[36,2],[37,2],[38,2],[39,2],[1,2],[42,2],[229,129],[230,2],[231,130],[56,2],[186,131],[185,132],[187,133],[184,134],[189,135],[197,136],[196,137],[188,2],[192,138],[214,139],[212,140],[213,141],[211,142],[205,143],[206,143],[207,2],[208,143],[210,144],[204,145],[209,143],[202,2],[220,146],[222,146],[223,147],[221,148],[219,149],[236,150],[237,151],[243,152],[232,153],[235,154],[234,155],[239,156],[242,157],[240,158],[241,159],[233,160],[238,161],[246,165],[244,166],[245,167],[47,2],[48,2],[49,2],[50,2],[51,2],[52,2],[43,2],[53,2],[54,2],[55,2],[44,2],[45,2],[46,2]],"semanticDiagnosticsPerFile":[249,247,145,128,147,129,146,151,152,148,154,149,153,150,136,133,140,134,131,139,144,141,142,143,138,135,132,137,190,225,227,226,228,224,203,199,200,201,198,65,66,68,69,70,71,72,73,74,67,166,75,155,158,159,160,161,162,163,164,165,193,195,194,252,248,250,251,191,157,253,254,257,255,258,259,260,261,272,271,256,273,275,276,274,277,278,279,280,281,282,283,284,285,286,287,156,76,77,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,78,126,94,95,96,127,97,98,99,100,101,102,103,104,105,106,107,108,110,109,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,288,289,290,291,293,292,294,319,320,295,298,317,318,308,307,305,300,313,311,315,299,312,316,301,302,314,296,303,304,306,310,321,309,297,334,333,328,330,329,322,323,325,327,331,332,324,326,336,335,337,338,339,340,130,262,215,218,216,217,177,174,176,175,173,183,178,182,179,181,180,169,170,171,167,168,172,263,267,269,268,266,270,265,264,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,229,230,231,56,186,185,187,184,189,197,196,188,192,214,212,213,211,205,206,207,208,210,204,209,202,220,222,223,221,219,236,237,243,232,235,234,239,242,240,241,233,238,246,244,245,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/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/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","../../../node_modules/@metamask/rpc-errors/dist/types/utils.d.ts","../../../node_modules/@metamask/rpc-errors/dist/types/classes.d.ts","../../../node_modules/@metamask/rpc-errors/dist/types/errors.d.ts","../../../node_modules/@metamask/rpc-errors/dist/types/error-constants.d.ts","../../../node_modules/@metamask/rpc-errors/dist/types/index.d.ts","../../approval-controller/dist/types/ApprovalController.d.ts","../../approval-controller/dist/types/errors.d.ts","../../approval-controller/dist/types/index.d.ts","../../permission-controller/dist/types/permission-middleware.d.ts","../../permission-controller/dist/types/SubjectMetadataController.d.ts","../../permission-controller/dist/types/PermissionController.d.ts","../../permission-controller/dist/types/Permission.d.ts","../../permission-controller/dist/types/Caveat.d.ts","../../permission-controller/dist/types/errors.d.ts","../../permission-controller/dist/types/utils.d.ts","../../permission-controller/dist/types/rpc-methods/getPermissions.d.ts","../../permission-controller/dist/types/rpc-methods/requestPermissions.d.ts","../../permission-controller/dist/types/rpc-methods/revokePermissions.d.ts","../../permission-controller/dist/types/rpc-methods/index.d.ts","../../permission-controller/dist/types/index.d.ts","../src/SelectedNetworkController.ts","../src/SelectedNetworkMiddleware.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/uuid/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","e475453e7140e95542332943d3052fe4c7430ad1efce42b3e9157f1fee8cbc5f","ebfdf904255ce746c9d30117c2edef355fb19bf7650478d2405f39f0e4f302e6","f3f63b48addb8e2ea9d20bb671c3c306413b3daa39996d0ae52f63d8e32158e1","a50599c08934a62f11657bdbe0dc929ab66da1b1f09974408fd9a33ec1bb8060","5a20e7d6c630b91be15e9b837853173829d00273197481dc8d3e94df61105a71","8d478048d71cc16f806d4b71b252ecb67c7444ccf4f4b09b29a312712184f859","b4000a0a525fa921e896cbdb32ae802c9684f0fd371b5fc69e7310f7918cc2c3","9df4662ca3dbc2522bc115833ee04faa1afbb4e249a85ef4a0a09c621346bd08","b25d9065cf1c1f537a140bbc508e953ed2262f77134574c432d206ff36f4bdbf","1b103313097041aa9cd705a682c652f08613cb5cf8663321061c0902f845e81c","68ccec8662818911d8a12b8ed028bc5729fb4f1d34793c4701265ba60bc73cf4","5f85b8b79dc4d36af672c035b2beb71545de63a5d60bccbeee64c260941672ab","b3d48529ae61dc27d0bfbfa2cb3e0dff8189644bd155bdf5df1e8e14669f7043","40fe4b689225816b31fe5794c0fbf3534568819709e40295ead998a2bc1ab237","f65b5e33b9ad545a1eebbd6afe857314725ad42aaf069913e33f928ab3e4990a","fb6f2a87beb7fb1f4c2b762d0c76a9459fc91f557231569b0ee21399e22aa13d","31c858dc85996fac4b7fa944e1016d5c72f514930a72357ab5001097bf6511c7","3de30a871b3340be8b679c52aa12f90dd1c8c60874517be58968fdbcc4d79445","6fd985bd31eaf77542625306fb0404d32bff978990f0a06428e5f0b9a3b58109","34693fb4a5e771e11668219221344dd1bd7d8b77ed005a1c1d965fb559be8406","7394959e5a741b185456e1ef5d64599c36c60a323207450991e7a42e08911419",{"version":"e44ea2d6b7b853f6c81482416db43dafc11944561b810e469ae423085511ce7e","affectsGlobalScope":true},"f51b4042a3ac86f1f707500a9768f88d0b0c1fc3f3e45a73333283dea720cdc6",{"version":"a7289d79eb84a59d2475b4d0136b4404be3cfdd17c3ea46b9194add1d645df01","affectsGlobalScope":true},"0bb26fa2a90ee890eed57ee812c71fa84d3d07850163ec4a204de86412cc57c1","132ca47da601c60141dd6f10bd08c70d0620177e5638439df2464ec3945b6d98",{"version":"55d2bbae076fed7269c3e16faeb32f988f558427b7a1c3bf04aa7551ab86ae90","affectsGlobalScope":true},"a40826e8476694e90da94aa008283a7de50d1dafd37beada623863f1901cb7fb","cf83847c9264dcd592b6c89c1542925b899b277228687f3638614e3fa784cf76","3a41ebe7f089d50f447466b35b6cabb8b584c0994fc9809d0cd0a4ebc41e1239","7693b90b3075deaccafd5efb467bf9f2b747a3075be888652ef73e64396d8628","0c42d6cba77d9ad1cf45256ccb8489aa502fe2dbee1ec9048a29d49f5d532e73","2cf89c17245db65d175d4ef699cd68187516f9b3ae5c572fc0b9ad60f35dc223","5f20d20b7607174caf1a6da9141aeb9f2142159ae2410ca30c7a0fccd1d19c99",{"version":"a34d65f61ec5aac5b53502c8b0bd4e00d217bccb95bf94d449e2571baa11fb8c","affectsGlobalScope":true},"8d42e5af5fb0a96a77e135ce84cc60636c9bad39d9dba043a4efe9d1bdeb3cc3","56fcc451e9065eb121c9cc4c1b9994a816306f3b0b3b1fce7ad59f0ac97a9999","8a6f12b74d3e6c4f5e1b918cb8e64ae16bc6756cf0d48bcc28a28e1bf26ca0cd","c3759b5bc5cc40f5988d86a497741a80fa91258629ae50a2b3735e774cd377cc","bf268a0aea37ad4ae3b7a9b58559190b6fc01ea16a31e35cd05817a0a60f895a","45dd82fb5aea9b12b2a90b427b28f3a014e8b2ee9b74087a5ab882841cb5fbc5",{"version":"d7dad6db394a3d9f7b49755e4b610fbf8ed6eb0c9810ae5f1a119f6b5d76de45","affectsGlobalScope":true},"48b2f9302651eb31acd5be69bb4e6b35797a7fcd6b77391d10a4ccadf7dc3609","0c8c917ef15498c827bd494a0ef365e9f76deb211f8acbb86932e20489310788","dd67d2b5e4e8a182a38de8e69fb736945eaa4588e0909c14e01a14bd3cc1fd1e",{"version":"9cdc2c6144b03822c9842505d09945bcf813b86827fdb260dd7586b63abc19bf","affectsGlobalScope":true},{"version":"2923dee3c897f03e91b54a210cdbefea7290562f0ac4b948667d4c9ee844b79e","affectsGlobalScope":true},"79169698d09a2be54b14f3bcad2575b414bf3525063fde0a1e4fcd5d6efd380e","051d939bcf77caa3cef3282708ab3a6fdfb741a7366e1d74a9e7603b67417ec3","0be79b3ff0f16b6c2f9bc8c4cc7097ea417d8d67f8267f7e1eec8e32b548c2ff","1c61ffa3a71b77363b30d19832c269ef62fba787f5610cac7254728d3b69ab2e","a234d62ae81d012ebf23898a45672edf3e5c93ecf5a438a42b96c08dd68cde43","269929a24b2816343a178008ac9ae9248304d92a8ba8e233055e0ed6dbe6ef71","09ed02a725db002693236b6dfc49b2c6eb5557be1421d7fbe4f07cfe38211d92","09d801ff4a303d4976d4b9cb94af3a9097c4a70345e662d176975872d2998e51","c8558b01389b5f7610ac293aa612ccea2ae64d83af43b49f8142f190be1f414c","c40fdf7b2e18df49ce0568e37f0292c12807a0748be79e272745e7216bed2606",{"version":"b10b426c56e220b5093bf8a2446ee47af47263b7b1a03f4b18e42326b231b111","affectsGlobalScope":true},"4e228e78c1e9b0a75c70588d59288f63a6258e8b1fe4a67b0c53fe03461421d9","b4635ef36bee17e1304337d591c3b6b461ecdbc1876d0effbe6a581e62201fe5","205d50c24359ead003dc537b9b65d2a64208dfdffe368f403cf9e0357831db9e","1265fddcd0c68be9d2a3b29805d0280484c961264dd95e0b675f7bd91f777e78",{"version":"e4507242542bd499238f693d88b2d32e22177cc508854625f87bcc9bc3fa1256","affectsGlobalScope":true},{"version":"d942354e4966a98d3a92d1b1af0b4ac06f33af3f88116743e2c304c027ca26ef","affectsGlobalScope":true},"39f0808e5be3cb38674726c21fe2eb453c55e48a901679b4ce30fef85549b892","6afd66a7432ef100027ea110449e874196381e019e30eda7e7d8ca390366b7a8","befb8a9a78ac99d8fbc3ed392810489a7b90760c7a58934e8f1c8538f581cff3","e670bdf01540d35c170fae68edfd2f288eff909936780c379d6a9103b787b22c","867f95abf1df444aab146b19847391fc2f922a55f6a970a27ed8226766cee29f",{"version":"ab9b9a36e5284fd8d3bf2f7d5fcbc60052f25f27e4d20954782099282c60d23e","affectsGlobalScope":true},"88003d9ab15507806f41b120be6d407c1afe566c2f6689ebe3a034dd5ec0c8dc","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","6a9c5127096b35264eb7cd21b2417bfc1d42cceca9ba4ce2bb0c3410b7816042","93b7325b49dfbf613d940ed0e471216657b2d77459dac34f1b5b1678f08f884c","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","a12eaa942232703a8a8477a2f240ad5a2c26c595012ea8f128224e77984099c4","4070c2f1c3434fcf84886e04d30d82cd650ee443e53b82b404b144175cf8741e","2cea9689efa8591732096235abe7f084fc29c92badd5b0897a5e876b77e71887","4ed4e504126014fee13aaef5e3fc140f2ff7031ff3a8b5386717905820ea2d09","8129a34006218a6f3cdc81bbd438d5429eb18b08b4338a26977ac3b4df129d75","30d2170e1a718b5035611af55e3618b4ba8f42f0749bb52ee593da6082c4e2ce","98ef38666d88ec9699a722053e07ede65d3042f693fe7ff8c786e53dbb6fd43b","a3b8b6be7620897d1e481e8650c980a210a138fceb6e710eaf95fd9dd0dfe94a","12c89d0e32758c120a569045f21cf5b77244f86792611ced8de7f86b37e77781","9f9e5bae412fa5909fae636d6733aee27a108cc2ed5b13980611016336774d3c","662fe197bba64bd3f17ee118058cd2d0d2dbe33d7c0c865fd6365d90bfc44e1e","030519c351f800551cac2658038804969ca4584d2c0175a710602ac234ca1340","0278a6939ca83cd040b08ff8c5fc7838b6693ddc52f22526bf158e6b10e0246c","c2d6206e5ba4fd3063b01218c2b3b997afc1cfbeb49fcee991fa8595842ce53d","29c188a2c660f99f1b4835022e011c4268d7af989d4b7dda33c0a69ca1a777f8","1ed0bf138e87912d741e28333b58cbf814ae863783b3b404d2454cbabb9c5fc0","3452ee7d8ef0b1bbd47b2a56924a1dc3c79dc84a19d212e9dc496f92e4943aa0","99510e20e3d4816e283e59e8f0f31f603b2f026648240ffdb1ca9f24be678419","037d1fbeb96dc35600814be14d0fbf31acf35f1d7b443ea33514937de69c2bf2","64d1859b7dd9f419ba08e064c2b16b1a5edde0316d6c2bb1833c9381d4dffc3d","69b0f96ca137c0dba05f321a159141ad36f79cfba2fffcc29d131e280275e6f2","7bb64cf513a2b1cf7a94f81ca201f3d76a9b17af556f0cfc4e2707443e6caa66","a9add2a29da4cd0b617ae89f196b3f2172a031aeb086922cdf097236eef8b008","99afac3e6e683ee3111e499f9919953e9489cb39cad74363717aa3805e91db51","c49f92b83968f4ee0b6026396a9b6e2d6fee8b660d08a90efb03355ce3433a7a","4fdc6afe4d7ef6aeb32ac0818d47e99f98a31d0696abc4cb2af489c78ac1ba1d","d73d5a0e854037d43781b2d5d33f4b95ee509e0ddede677aade79fbee6a97cdc","35d14e1ae04be300828b1a1614316b9312a009cfd5e29fa56f94c2a9f60b12df","d160fe745f9c3b72d7b9036fdb2b6b500a520d43e36bb842c927b6fe59ea2c23",{"version":"4a6571cadd05e8087a34f7ca44f63a8f61aa5fcca650f0a577febc4877b3c34f","signature":"3a4f9e7087c566703447928d15c234bd0bcc63a2a50b6ec39ed37c9bc0342310"},{"version":"506bcad28e45e13c23c2c25c9691e0dc42d8755a0e24b9a48586f51b5bebae8f","signature":"3a12771c76c5ed979438dc0e390224bd3d8661bcbea18114f77c4c6d9de5b8b2"},{"version":"6fbdb35bc3b9cfb225c48108b5bacb5c0d67bbcb677cf13b912e15a852551023","signature":"ee06131adf64c6cd201de36563174cff0e160b81438be186a7bd85e6c2b54fa7"},"a5aaeca001d2f69093d04aac4db321e4c338fd9b20cbc4f0b0af3dc6ae0f235b","cc957354aa3c94c9961ebf46282cfde1e81d107fc5785a61f62c67f1dd3ac2eb","8041cfce439ff29d339742389de04c136e3029d6b1817f07b2d7fcbfb7534990","93de1c6dab503f053efe8d304cb522bb3a89feab8c98f307a674a4fae04773e9","29a46d003ca3c721e6405f00dee7e3de91b14e09701eba5d887bf76fb2d47d38","069bebfee29864e3955378107e243508b163e77ab10de6a5ee03ae06939f0bb9","9990f9e566bc3c2c6e38df81294fb756e7f5b7b0e5bb17ab75384e190548b4b6",{"version":"64d4b35c5456adf258d2cf56c341e203a073253f229ef3208fc0d5020253b241","affectsGlobalScope":true},"ee7d8894904b465b072be0d2e4b45cf6b887cdba16a467645c4e200982ece7ea","f3d8c757e148ad968f0d98697987db363070abada5f503da3c06aefd9d4248c1","df95e00612c1faa5e0e7ef0dba589b18665bbeb3221db2b6cee1fe4d0e61921f","afe73051ff6a03a9565cbd8ebb0e956ee3df5e913ad5c1ded64218aabfa3dcb5","8b06ac3faeacb8484d84ddb44571d8f410697f98d7bfa86c0fda60373a9f5215","7eb06594824ada538b1d8b48c3925a83e7db792f47a081a62cf3e5c4e23cf0ee","f5638f7c2f12a9a1a57b5c41b3c1ea7db3876c003bab68e6a57afd6bcc169af0","0d14fa22c41fdc7277e6f71473b20ebc07f40f00e38875142335d5b63cdfc9d2","d8aab31ba8e618cc3eea10b0945de81cb93b7e8150a013a482332263b9305322","462bccdf75fcafc1ae8c30400c9425e1a4681db5d605d1a0edb4f990a54d8094","5923d8facbac6ecf7c84739a5c701a57af94a6f6648d6229a6c768cf28f0f8cb","7adecb2c3238794c378d336a8182d4c3dd2c4fa6fa1785e2797a3db550edea62","dc12dc0e5aa06f4e1a7692149b78f89116af823b9e1f1e4eae140cd3e0e674e6","1bfc6565b90c8771615cd8cfcf9b36efc0275e5e83ac7d9181307e96eb495161","8a8a96898906f065f296665e411f51010b51372fa260d5373bf9f64356703190","7f82ef88bdb67d9a850dd1c7cd2d690f33e0f0acd208e3c9eba086f3670d4f73",{"version":"ccfd8774cd9b929f63ff7dcf657977eb0652e3547f1fcac1b3a1dc5db22d4d58","affectsGlobalScope":true},"d92dc90fecd2552db74d8dc3c6fb4db9145b2aa0efe2c127236ba035969068d4","96d14f21b7652903852eef49379d04dbda28c16ed36468f8c9fa08f7c14c9538","675e702f2032766a91eeadee64f51014c64688525da99dccd8178f0c599f13a8","458111fc89d11d2151277c822dfdc1a28fa5b6b2493cf942e37d4cd0a6ee5f22","19c816167e076e7c24f074389c6cf3ed87bdbb917d1ea439ca281f9d26db2439","187119ff4f9553676a884e296089e131e8cc01691c546273b1d0089c3533ce42","febf0b2de54781102b00f61653b21377390a048fbf5262718c91860d11ff34a6","98f9d826db9cd99d27a01a59ee5f22863df00ccf1aaf43e1d7db80ebf716f7c3","0aaef8cded245bf5036a7a40b65622dd6c4da71f7a35343112edbe112b348a1e","00baffbe8a2f2e4875367479489b5d43b5fc1429ecb4a4cc98cfc3009095f52a","dcd91d3b697cb650b95db5471189b99815af5db2a1cd28760f91e0b12ede8ed5","3c92b6dfd43cc1c2485d9eba5ff0b74a19bb8725b692773ef1d66dac48cda4bd","3cf0d343c2276842a5b617f22ba82af6322c7cfe8bb52238ffc0c491a3c21019","df996e25faa505f85aeb294d15ebe61b399cf1d1e49959cdfaf2cc0815c203f9",{"version":"f2eff8704452659641164876c1ef0df4174659ce7311b0665798ea3f556fa9ad","affectsGlobalScope":true},"8841e2aa774b89bd23302dede20663306dc1b9902431ac64b24be8b8d0e3f649","2b8264b2fefd7367e0f20e2c04eed5d3038831fe00f5efbc110ff0131aab899b","a73a445c1e0a6d0f8b48e8eb22dc9d647896783a7f8991cbbc31c0d94bf1f5a2","d88a5e779faf033be3d52142a04fbe1cb96009868e3bbdd296b2bc6c59e06c0e","cd1d2f103b79002cd94b85a640a103f094227a2c4c53bc8af1fdbf4e13d9729e","5e379df3d61561c2ed7789b5995b9ba2143bbba21a905e2381e16efe7d1fa424","f07a137bbe2de7a122c37bfea00e761975fb264c49f18003d398d71b3fb35a5f","3dce33e7eb25594863b8e615f14a45ab98190d85953436750644212d8a18c066","2b93035328f7778d200252681c1d86285d501ed424825a18f81e4c3028aa51d9","2ac9c8332c5f8510b8bdd571f8271e0f39b0577714d5e95c1e79a12b2616f069","42c21aa963e7b86fa00801d96e88b36803188018d5ad91db2a9101bccd40b3ff","d31eb848cdebb4c55b4893b335a7c0cca95ad66dee13cbb7d0893810c0a9c301","b9f96255e1048ed2ea33ec553122716f0e57fc1c3ad778e9aa15f5b46547bd23","7a9e0a564fee396cacf706523b5aeed96e04c6b871a8bebefad78499fbffc5bc","906c751ef5822ec0dadcea2f0e9db64a33fb4ee926cc9f7efa38afe5d5371b2a","5387c049e9702f2d2d7ece1a74836a14b47fbebe9bbeb19f94c580a37c855351","c68391fb9efad5d99ff332c65b1606248c4e4a9f1dd9a087204242b56c7126d6","e9cf02252d3a0ced987d24845dcb1f11c1be5541f17e5daa44c6de2d18138d0c","e8b02b879754d85f48489294f99147aeccc352c760d95a6fe2b6e49cd400b2fe","9f6908ab3d8a86c68b86e38578afc7095114e66b2fc36a2a96e9252aac3998e0","0eedb2344442b143ddcd788f87096961cd8572b64f10b4afc3356aa0460171c6","71405cc70f183d029cc5018375f6c35117ffdaf11846c35ebf85ee3956b1b2a6","c68baff4d8ba346130e9753cefe2e487a16731bf17e05fdacc81e8c9a26aae9d","2cd15528d8bb5d0453aa339b4b52e0696e8b07e790c153831c642c3dea5ac8af","479d622e66283ffa9883fbc33e441f7fc928b2277ff30aacbec7b7761b4e9579","ade307876dc5ca267ca308d09e737b611505e015c535863f22420a11fffc1c54","f8cdefa3e0dee639eccbe9794b46f90291e5fd3989fcba60d2f08fde56179fb9","86c5a62f99aac7053976e317dbe9acb2eaf903aaf3d2e5bb1cafe5c2df7b37a8","2b300954ce01a8343866f737656e13243e86e5baef51bd0631b21dcef1f6e954","a2d409a9ffd872d6b9d78ead00baa116bbc73cfa959fce9a2f29d3227876b2a1","b288936f560cd71f4a6002953290de9ff8dfbfbf37f5a9391be5c83322324898","61178a781ef82e0ff54f9430397e71e8f365fc1e3725e0e5346f2de7b0d50dfa","6a6ccb37feb3aad32d9be026a3337db195979cd5727a616fc0f557e974101a54","c649ea79205c029a02272ef55b7ab14ada0903db26144d2205021f24727ac7a3","38e2b02897c6357bbcff729ef84c736727b45cc152abe95a7567caccdfad2a1d","d6610ea7e0b1a7686dba062a1e5544dd7d34140f4545305b7c6afaebfb348341","3dee35db743bdba2c8d19aece7ac049bde6fa587e195d86547c882784e6ba34c","b15e55c5fa977c2f25ca0b1db52cfa2d1fd4bf0baf90a8b90d4a7678ca462ff1","f41d30972724714763a2698ae949fbc463afb203b5fa7c4ad7e4de0871129a17","843dd7b6a7c6269fd43827303f5cbe65c1fecabc30b4670a50d5a15d57daeeb9","f06d8b8567ee9fd799bf7f806efe93b67683ef24f4dea5b23ef12edff4434d9d","6017384f697ff38bc3ef6a546df5b230c3c31329db84cbfe686c83bec011e2b2","e1a5b30d9248549ca0c0bb1d653bafae20c64c4aa5928cc4cd3017b55c2177b0","a593632d5878f17295bd53e1c77f27bf4c15212822f764a2bfc1702f4b413fa0","a868a534ba1c2ca9060b8a13b0ffbbbf78b4be7b0ff80d8c75b02773f7192c29","da7545aba8f54a50fde23e2ede00158dc8112560d934cee58098dfb03aae9b9d","34baf65cfee92f110d6653322e2120c2d368ee64b3c7981dff08ed105c4f19b0","a1a261624efb3a00ff346b13580f70f3463b8cdcc58b60f5793ff11785d52cab","f83b320cceccfc48457a818d18fc9a006ab18d0bdd727aa2c2e73dc1b4a45e98","9d92b037978bb9525bc4b673ebddd443277542e010c0aef019c03a170ccdaa73","b0d10e46cfe3f6c476b69af02eaa38e4ccc7430221ce3109ae84bb9fb8282298","fab58e600970e66547644a44bc9918e3223aa2cbd9e8763cec004b2cfb48827e","70e9a18da08294f75bf23e46c7d69e67634c0765d355887b9b41f0d959e1426e","ed44ba6b95f08b758748be7902e0cc54178b1337c56d0e2469c77b03f63ac73b"],"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":[[120,247],[120],[91,120,127,128,129,144],[120,128,129,145,146],[120,127,128],[120,127,144,147,150],[120,127,147,150,151],[120,148,149,150,152,153],[120,127,150],[120,127,144,147,148,149,152],[120,127,135],[120,127],[91,120,127],[80,120,127],[120,131,132,133,134,135,136,137,138,139,140,141,142,143],[120,127,133,134],[120,127,133,135],[120,166,224],[120,224,225],[120,224,225,226,227],[120,166],[120,198],[120,198,199,200],[64,120],[67,120],[64,67,120],[65,66,67,68,69,70,71,72,73,74,75,120,155,158,159,160,161,162,163,164,165],[58,64,65,120],[67,73,75,120,154],[120,157],[67,68,120],[64,120,161],[120,193,194],[120,247,248,249,250,251],[120,247,249],[120,156],[120,254,255,256],[92,120,127],[120,259],[120,260],[120,271],[120,265,270],[120,274,276,277,278,279,280,281,282,283,284,285,286],[120,274,275,277,278,279,280,281,282,283,284,285,286],[120,275,276,277,278,279,280,281,282,283,284,285,286],[120,274,275,276,278,279,280,281,282,283,284,285,286],[120,274,275,276,277,279,280,281,282,283,284,285,286],[120,274,275,276,277,278,280,281,282,283,284,285,286],[120,274,275,276,277,278,279,281,282,283,284,285,286],[120,274,275,276,277,278,279,280,282,283,284,285,286],[120,274,275,276,277,278,279,280,281,283,284,285,286],[120,274,275,276,277,278,279,280,281,282,284,285,286],[120,274,275,276,277,278,279,280,281,282,283,285,286],[120,274,275,276,277,278,279,280,281,282,283,284,286],[120,274,275,276,277,278,279,280,281,282,283,284,285],[76,120],[79,120],[80,85,111,120],[81,91,92,99,108,119,120],[81,82,91,99,120],[83,120],[84,85,92,100,120],[85,108,116,120],[86,88,91,99,120],[87,120],[88,89,120],[90,91,120],[91,120],[91,92,93,108,119,120],[91,92,93,108,120],[91,94,99,108,119,120],[91,92,94,95,99,108,116,119,120],[94,96,108,116,119,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],[91,97,120],[98,119,120,124],[88,91,99,108,120],[100,120],[101,120],[79,102,120],[103,118,120,124],[104,120],[105,120],[91,106,120],[106,107,120,122],[80,91,108,109,110,120],[80,108,110,120],[108,109,120],[111,120],[112,120],[91,114,115,120],[114,115,120],[85,99,116,120],[117,120],[99,118,120],[80,94,105,119,120],[85,120],[108,120,121],[120,122],[120,123],[80,85,91,93,102,108,119,120,122,124],[108,120,125],[120,127,292],[120,295,334],[120,295,319,334],[120,334],[120,295],[120,295,320,334],[120,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,321,322,323,324,325,326,327,328,329,330,331,332,333],[120,320,334],[120,335],[120,339],[120,203],[120,203,214,215],[120,215,216,217],[120,178],[120,178,179,180,181,182],[120,167,168,169,170,171,172,173,174,175,176,177],[120,263,266],[120,263,266,267,268],[120,265],[120,262,269],[120,264],[57,59,60,61,62,63,120],[57,58,120],[59,120],[58,59,120],[57,59,120],[120,166,187,228],[120,229,230],[120,166,183,184,185],[120,184],[120,185],[56,120,184,185,186],[120,188],[120,188,189,192,196],[120,195],[120,166,190,191],[120,211,212,213],[120,210,211],[120,166,210,211],[120,166,203,210],[120,166,203],[120,166,204],[120,204,205,206,207,208,209],[120,166,187,190,197,201,202,219,220],[120,219],[120,202,219,221,222],[120,166,197,214,218],[120,166,235],[120,166,187,197,233,234,236],[120,166,187,197,231,232,233,235,236],[120,166,187,234],[120,166,228,235],[120,233,234,235,236,237,238,242],[120,166,210,243],[120,234,235,238],[120,239,240,241,243],[120,235,238],[120,166,235,238],[120,166,210,235,236],[120,183,187,201,223,243],[120,166,210,223,244],[120,244,245],[183,187,223,243],[166,210,223,244],[244,245]],"referencedMap":[[249,1],[247,2],[145,3],[128,2],[147,4],[129,5],[146,2],[151,6],[152,7],[148,7],[154,8],[149,7],[153,9],[150,10],[136,11],[133,12],[140,13],[134,11],[131,14],[139,2],[144,15],[141,2],[142,2],[143,2],[138,12],[135,16],[132,2],[137,17],[190,2],[225,18],[227,2],[226,19],[228,20],[224,21],[203,13],[199,22],[200,22],[201,23],[198,2],[65,24],[66,24],[68,25],[69,24],[70,24],[71,26],[72,2],[73,2],[74,2],[67,24],[166,27],[75,28],[155,29],[158,30],[159,2],[160,2],[161,2],[162,2],[163,2],[164,31],[165,32],[193,2],[195,33],[194,2],[252,34],[248,1],[250,35],[251,1],[191,12],[157,36],[253,2],[254,2],[257,37],[255,2],[258,38],[259,2],[260,39],[261,40],[272,41],[271,42],[256,2],[273,2],[275,43],[276,44],[274,45],[277,46],[278,47],[279,48],[280,49],[281,50],[282,51],[283,52],[284,53],[285,54],[286,55],[287,2],[156,2],[76,56],[77,56],[79,57],[80,58],[81,59],[82,60],[83,61],[84,62],[85,63],[86,64],[87,65],[88,66],[89,66],[90,67],[91,68],[92,69],[93,70],[78,2],[126,2],[94,71],[95,72],[96,73],[127,74],[97,75],[98,76],[99,77],[100,78],[101,79],[102,80],[103,81],[104,82],[105,83],[106,84],[107,85],[108,86],[110,87],[109,88],[111,89],[112,90],[113,2],[114,91],[115,92],[116,93],[117,94],[118,95],[119,96],[120,97],[121,98],[122,99],[123,100],[124,101],[125,102],[288,2],[289,12],[290,2],[291,2],[293,103],[292,2],[294,12],[319,104],[320,105],[295,106],[298,106],[317,104],[318,104],[308,104],[307,107],[305,104],[300,104],[313,104],[311,104],[315,104],[299,104],[312,104],[316,104],[301,104],[302,104],[314,104],[296,104],[303,104],[304,104],[306,104],[310,104],[321,108],[309,104],[297,104],[334,109],[333,2],[328,108],[330,110],[329,108],[322,108],[323,108],[325,108],[327,108],[331,110],[332,110],[324,110],[326,110],[336,111],[335,2],[337,2],[338,2],[339,2],[340,112],[130,2],[262,2],[215,113],[216,114],[217,114],[218,115],[177,2],[174,116],[176,116],[175,116],[173,116],[183,117],[178,118],[182,2],[179,2],[181,2],[180,2],[169,116],[170,116],[171,116],[167,2],[168,2],[172,116],[263,2],[267,119],[269,120],[268,119],[266,121],[270,122],[265,123],[264,2],[57,2],[64,124],[59,125],[60,126],[61,126],[62,127],[63,127],[58,128],[8,2],[10,2],[9,2],[2,2],[11,2],[12,2],[13,2],[14,2],[15,2],[16,2],[17,2],[18,2],[3,2],[4,2],[22,2],[19,2],[20,2],[21,2],[23,2],[24,2],[25,2],[5,2],[26,2],[27,2],[28,2],[29,2],[6,2],[33,2],[30,2],[31,2],[32,2],[34,2],[7,2],[35,2],[40,2],[41,2],[36,2],[37,2],[38,2],[39,2],[1,2],[42,2],[229,129],[230,2],[231,130],[56,2],[186,131],[185,132],[184,133],[187,134],[189,135],[197,136],[196,137],[188,2],[192,138],[214,139],[212,140],[213,141],[211,142],[204,143],[205,144],[206,144],[207,2],[208,144],[210,145],[209,144],[221,146],[202,2],[220,147],[222,147],[223,148],[219,149],[236,150],[235,151],[234,152],[233,153],[237,154],[243,155],[232,156],[239,157],[242,158],[240,159],[241,160],[238,161],[244,162],[245,163],[246,164],[47,2],[48,2],[49,2],[50,2],[51,2],[52,2],[43,2],[53,2],[54,2],[55,2],[44,2],[45,2],[46,2]],"exportedModulesMap":[[249,1],[247,2],[145,3],[128,2],[147,4],[129,5],[146,2],[151,6],[152,7],[148,7],[154,8],[149,7],[153,9],[150,10],[136,11],[133,12],[140,13],[134,11],[131,14],[139,2],[144,15],[141,2],[142,2],[143,2],[138,12],[135,16],[132,2],[137,17],[190,2],[225,18],[227,2],[226,19],[228,20],[224,21],[203,13],[199,22],[200,22],[201,23],[198,2],[65,24],[66,24],[68,25],[69,24],[70,24],[71,26],[72,2],[73,2],[74,2],[67,24],[166,27],[75,28],[155,29],[158,30],[159,2],[160,2],[161,2],[162,2],[163,2],[164,31],[165,32],[193,2],[195,33],[194,2],[252,34],[248,1],[250,35],[251,1],[191,12],[157,36],[253,2],[254,2],[257,37],[255,2],[258,38],[259,2],[260,39],[261,40],[272,41],[271,42],[256,2],[273,2],[275,43],[276,44],[274,45],[277,46],[278,47],[279,48],[280,49],[281,50],[282,51],[283,52],[284,53],[285,54],[286,55],[287,2],[156,2],[76,56],[77,56],[79,57],[80,58],[81,59],[82,60],[83,61],[84,62],[85,63],[86,64],[87,65],[88,66],[89,66],[90,67],[91,68],[92,69],[93,70],[78,2],[126,2],[94,71],[95,72],[96,73],[127,74],[97,75],[98,76],[99,77],[100,78],[101,79],[102,80],[103,81],[104,82],[105,83],[106,84],[107,85],[108,86],[110,87],[109,88],[111,89],[112,90],[113,2],[114,91],[115,92],[116,93],[117,94],[118,95],[119,96],[120,97],[121,98],[122,99],[123,100],[124,101],[125,102],[288,2],[289,12],[290,2],[291,2],[293,103],[292,2],[294,12],[319,104],[320,105],[295,106],[298,106],[317,104],[318,104],[308,104],[307,107],[305,104],[300,104],[313,104],[311,104],[315,104],[299,104],[312,104],[316,104],[301,104],[302,104],[314,104],[296,104],[303,104],[304,104],[306,104],[310,104],[321,108],[309,104],[297,104],[334,109],[333,2],[328,108],[330,110],[329,108],[322,108],[323,108],[325,108],[327,108],[331,110],[332,110],[324,110],[326,110],[336,111],[335,2],[337,2],[338,2],[339,2],[340,112],[130,2],[262,2],[215,113],[216,114],[217,114],[218,115],[177,2],[174,116],[176,116],[175,116],[173,116],[183,117],[178,118],[182,2],[179,2],[181,2],[180,2],[169,116],[170,116],[171,116],[167,2],[168,2],[172,116],[263,2],[267,119],[269,120],[268,119],[266,121],[270,122],[265,123],[264,2],[57,2],[64,124],[59,125],[60,126],[61,126],[62,127],[63,127],[58,128],[8,2],[10,2],[9,2],[2,2],[11,2],[12,2],[13,2],[14,2],[15,2],[16,2],[17,2],[18,2],[3,2],[4,2],[22,2],[19,2],[20,2],[21,2],[23,2],[24,2],[25,2],[5,2],[26,2],[27,2],[28,2],[29,2],[6,2],[33,2],[30,2],[31,2],[32,2],[34,2],[7,2],[35,2],[40,2],[41,2],[36,2],[37,2],[38,2],[39,2],[1,2],[42,2],[229,129],[230,2],[231,130],[56,2],[186,131],[185,132],[184,133],[187,134],[189,135],[197,136],[196,137],[188,2],[192,138],[214,139],[212,140],[213,141],[211,142],[204,143],[205,144],[206,144],[207,2],[208,144],[210,145],[209,144],[221,146],[202,2],[220,147],[222,147],[223,148],[219,149],[236,150],[235,151],[234,152],[233,153],[237,154],[243,155],[232,156],[239,157],[242,158],[240,159],[241,160],[238,161],[244,165],[245,166],[246,167],[47,2],[48,2],[49,2],[50,2],[51,2],[52,2],[43,2],[53,2],[54,2],[55,2],[44,2],[45,2],[46,2]],"semanticDiagnosticsPerFile":[249,247,145,128,147,129,146,151,152,148,154,149,153,150,136,133,140,134,131,139,144,141,142,143,138,135,132,137,190,225,227,226,228,224,203,199,200,201,198,65,66,68,69,70,71,72,73,74,67,166,75,155,158,159,160,161,162,163,164,165,193,195,194,252,248,250,251,191,157,253,254,257,255,258,259,260,261,272,271,256,273,275,276,274,277,278,279,280,281,282,283,284,285,286,287,156,76,77,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,78,126,94,95,96,127,97,98,99,100,101,102,103,104,105,106,107,108,110,109,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,288,289,290,291,293,292,294,319,320,295,298,317,318,308,307,305,300,313,311,315,299,312,316,301,302,314,296,303,304,306,310,321,309,297,334,333,328,330,329,322,323,325,327,331,332,324,326,336,335,337,338,339,340,130,262,215,216,217,218,177,174,176,175,173,183,178,182,179,181,180,169,170,171,167,168,172,263,267,269,268,266,270,265,264,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,229,230,231,56,186,185,184,187,189,197,196,188,192,214,212,213,211,204,205,206,207,208,210,209,221,202,220,222,223,219,236,235,234,233,237,243,232,239,242,240,241,238,244,245,246,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/SelectedNetworkController.d.ts.map b/dist/types/SelectedNetworkController.d.ts.map -index f9de27de6d312eeb12e583354bd0df73b08598a4..eb0dcc3cd8563769606ae8c6748d2ad1bd5665c1 100644 ---- a/dist/types/SelectedNetworkController.d.ts.map -+++ b/dist/types/SelectedNetworkController.d.ts.map -@@ -1 +1 @@ --{"version":3,"file":"SelectedNetworkController.d.ts","sourceRoot":"","sources":["../../src/SelectedNetworkController.ts"],"names":[],"mappings":"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"} -\ No newline at end of file -+{"version":3,"file":"SelectedNetworkController.d.ts","sourceRoot":"","sources":["../../src/SelectedNetworkController.ts"],"names":[],"mappings":"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"} -\ No newline at end of file diff --git a/.yarn/patches/@metamask-snaps-controllers-npm-9.4.0-7c3abbbea6.patch b/.yarn/patches/@metamask-snaps-controllers-npm-9.4.0-7c3abbbea6.patch new file mode 100644 index 000000000000..ec4dfb871c9a --- /dev/null +++ b/.yarn/patches/@metamask-snaps-controllers-npm-9.4.0-7c3abbbea6.patch @@ -0,0 +1,13 @@ +diff --git a/package.json b/package.json +index 9464ffc2614c6c648051df5cb5c9c9b9e651a831..5b1bcb34b8167cf6bbc5b2854b1f196e0fa0204f 100644 +--- a/package.json ++++ b/package.json +@@ -9,7 +9,7 @@ + "sideEffects": false, + "exports": { + ".": { +- "import": "./dist/index.mjs", ++ "import": "./dist/index.js", + "require": "./dist/index.js", + "types": "./dist/types/index.d.ts" + }, diff --git a/.yarn/patches/@metamask-snaps-utils-npm-7.7.0-2cc1f044af.patch b/.yarn/patches/@metamask-snaps-utils-npm-7.7.0-2cc1f044af.patch deleted file mode 100644 index 82ddce260b99..000000000000 --- a/.yarn/patches/@metamask-snaps-utils-npm-7.7.0-2cc1f044af.patch +++ /dev/null @@ -1,30 +0,0 @@ -diff --git a/dist/chunk-37VHIRUJ.js b/dist/chunk-37VHIRUJ.js -index a909a4ef20305665a07db5c25b4a9ff7eb0a447e..98dd75bf33a9716dc6cca96a38d184645f6ec033 100644 ---- a/dist/chunk-37VHIRUJ.js -+++ b/dist/chunk-37VHIRUJ.js -@@ -53,8 +53,8 @@ function assertIsKeyringOrigins(value, ErrorWrapper) { - } - function createOriginRegExp(matcher) { - const escaped = matcher.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&"); -- const regex = escaped.replace(/\*/gu, ".*"); -- return RegExp(regex, "u"); -+ const regex = escaped.replace(/\\\*/gu, '.*'); -+ return RegExp(`${regex}$`, 'u'); - } - function checkAllowedOrigin(matcher, origin) { - if (matcher === "*" || matcher === origin) { -diff --git a/dist/chunk-K2OTEZZZ.mjs b/dist/chunk-K2OTEZZZ.mjs -index 15be5da7563a5bdf464d7e9c28ed6f04863e378a..7f38bf328e71c1feb2b8850ba050ce9e55801668 100644 ---- a/dist/chunk-K2OTEZZZ.mjs -+++ b/dist/chunk-K2OTEZZZ.mjs -@@ -53,8 +53,8 @@ function assertIsKeyringOrigins(value, ErrorWrapper) { - } - function createOriginRegExp(matcher) { - const escaped = matcher.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&"); -- const regex = escaped.replace(/\*/gu, ".*"); -- return RegExp(regex, "u"); -+ const regex = escaped.replace(/\\\*/gu, '.*'); -+ return RegExp(`${regex}$`, 'u'); - } - function checkAllowedOrigin(matcher, origin) { - if (matcher === "*" || matcher === origin) { diff --git a/.yarn/patches/@metamask-transaction-controller-npm-32.0.0-e23c2c3443.patch b/.yarn/patches/@metamask-transaction-controller-npm-32.0.0-e23c2c3443.patch deleted file mode 100644 index eb2684b678b7..000000000000 Binary files a/.yarn/patches/@metamask-transaction-controller-npm-32.0.0-e23c2c3443.patch and /dev/null differ diff --git a/.yarn/patches/@metamask-transaction-controller-npm-34.0.0-8bdfa87aaf.patch b/.yarn/patches/@metamask-transaction-controller-npm-34.0.0-8bdfa87aaf.patch new file mode 100644 index 000000000000..4ba6272f67bf --- /dev/null +++ b/.yarn/patches/@metamask-transaction-controller-npm-34.0.0-8bdfa87aaf.patch @@ -0,0 +1,6174 @@ +diff --git a/dist/TransactionController.js b/dist/TransactionController.js +index bdb836a895f9bfdc9225c354b59c0aaab20f80c1..f03dc65ff723256e1d4ed2588199166f0ee6e3cc 100644 +--- a/dist/TransactionController.js ++++ b/dist/TransactionController.js +@@ -4,13 +4,13 @@ + + + +-var _chunkS7Q622ISjs = require('./chunk-S7Q622IS.js'); ++var _chunkIVR4NMOFjs = require('./chunk-IVR4NMOF.js'); + require('./chunk-PRUNMTRD.js'); + require('./chunk-74W7X6BE.js'); + require('./chunk-KT6UAKBB.js'); + require('./chunk-SD6CWFDF.js'); + require('./chunk-RXIUMVA5.js'); +-require('./chunk-ULD4JC3Q.js'); ++require('./chunk-6DODV6OV.js'); + require('./chunk-7LXE4KHV.js'); + require('./chunk-V72C4MCR.js'); + require('./chunk-QP75SWIQ.js'); +@@ -41,5 +41,5 @@ require('./chunk-Z4BLTVTB.js'); + + + +-exports.ApprovalState = _chunkS7Q622ISjs.ApprovalState; exports.CANCEL_RATE = _chunkS7Q622ISjs.CANCEL_RATE; exports.HARDFORK = _chunkS7Q622ISjs.HARDFORK; exports.SPEED_UP_RATE = _chunkS7Q622ISjs.SPEED_UP_RATE; exports.TransactionController = _chunkS7Q622ISjs.TransactionController; ++exports.ApprovalState = _chunkIVR4NMOFjs.ApprovalState; exports.CANCEL_RATE = _chunkIVR4NMOFjs.CANCEL_RATE; exports.HARDFORK = _chunkIVR4NMOFjs.HARDFORK; exports.SPEED_UP_RATE = _chunkIVR4NMOFjs.SPEED_UP_RATE; exports.TransactionController = _chunkIVR4NMOFjs.TransactionController; + //# sourceMappingURL=TransactionController.js.map +\ No newline at end of file +diff --git a/dist/TransactionController.mjs b/dist/TransactionController.mjs +index ac7d4c27a4bca05f03cb791441e9e5d87765bbf2..88ecaa74d9e133c02fe09341460f4dca2dd83865 100644 +--- a/dist/TransactionController.mjs ++++ b/dist/TransactionController.mjs +@@ -4,13 +4,13 @@ import { + HARDFORK, + SPEED_UP_RATE, + TransactionController +-} from "./chunk-UKV5HIMT.mjs"; ++} from "./chunk-YQYO6EGF.mjs"; + import "./chunk-6DDVVUJC.mjs"; + import "./chunk-EVL6KODQ.mjs"; + import "./chunk-K4KOSAGM.mjs"; + import "./chunk-KG4UW4K4.mjs"; + import "./chunk-5ZEJT5SN.mjs"; +-import "./chunk-6B5BEO3R.mjs"; ++import "./chunk-7M2R5AHC.mjs"; + import "./chunk-FRKQ3Z2L.mjs"; + import "./chunk-5G6OHAXI.mjs"; + import "./chunk-XGRAHX6T.mjs"; +diff --git a/dist/chunk-6DODV6OV.js b/dist/chunk-6DODV6OV.js +new file mode 100644 +index 0000000000000000000000000000000000000000..92a813c417d809304e216783e76b6a67651d4661 +--- /dev/null ++++ b/dist/chunk-6DODV6OV.js +@@ -0,0 +1,390 @@ ++"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } ++ ++ ++var _chunkS6VGOPUYjs = require('./chunk-S6VGOPUY.js'); ++ ++ ++ ++ ++ ++var _chunkZ4BLTVTBjs = require('./chunk-Z4BLTVTB.js'); ++ ++// src/helpers/PendingTransactionTracker.ts ++var _controllerutils = require('@metamask/controller-utils'); ++var _events = require('events'); var _events2 = _interopRequireDefault(_events); ++var _lodash = require('lodash'); ++var DROPPED_BLOCK_COUNT = 3; ++var RECEIPT_STATUS_SUCCESS = "0x1"; ++var RECEIPT_STATUS_FAILURE = "0x0"; ++var MAX_RETRY_BLOCK_DISTANCE = 50; ++var KNOWN_TRANSACTION_ERRORS = [ ++ "replacement transaction underpriced", ++ "known transaction", ++ "gas price too low to replace", ++ "transaction with the same hash was already imported", ++ "gateway timeout", ++ "nonce too low" ++]; ++var log = _chunkS6VGOPUYjs.createModuleLogger.call(void 0, _chunkS6VGOPUYjs.projectLogger, "pending-transactions"); ++var _blockTracker, _droppedBlockCountByHash, _getChainId, _getEthQuery, _getTransactions, _isResubmitEnabled, _listener, _getGlobalLock, _publishTransaction, _running, _beforeCheckPendingTransaction, _beforePublish, _start, start_fn, _onLatestBlock, onLatestBlock_fn, _checkTransactions, checkTransactions_fn, _resubmitTransactions, resubmitTransactions_fn, _isKnownTransactionError, isKnownTransactionError_fn, _resubmitTransaction, resubmitTransaction_fn, _isResubmitDue, isResubmitDue_fn, _checkTransaction, checkTransaction_fn, _onTransactionConfirmed, onTransactionConfirmed_fn, _isTransactionDropped, isTransactionDropped_fn, _isNonceTaken, isNonceTaken_fn, _getPendingTransactions, getPendingTransactions_fn, _warnTransaction, warnTransaction_fn, _failTransaction, failTransaction_fn, _dropTransaction, dropTransaction_fn, _updateTransaction, updateTransaction_fn, _getTransactionReceipt, getTransactionReceipt_fn, _getBlockByHash, getBlockByHash_fn, _getNetworkTransactionCount, getNetworkTransactionCount_fn, _getCurrentChainTransactions, getCurrentChainTransactions_fn; ++var PendingTransactionTracker = class { ++ constructor({ ++ blockTracker, ++ getChainId, ++ getEthQuery, ++ getTransactions, ++ isResubmitEnabled, ++ getGlobalLock, ++ publishTransaction, ++ hooks ++ }) { ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _start); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _onLatestBlock); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _checkTransactions); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _resubmitTransactions); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _isKnownTransactionError); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _resubmitTransaction); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _isResubmitDue); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _checkTransaction); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _onTransactionConfirmed); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _isTransactionDropped); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _isNonceTaken); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _getPendingTransactions); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _warnTransaction); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _failTransaction); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _dropTransaction); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _updateTransaction); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _getTransactionReceipt); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _getBlockByHash); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _getNetworkTransactionCount); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _getCurrentChainTransactions); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _blockTracker, void 0); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _droppedBlockCountByHash, void 0); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _getChainId, void 0); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _getEthQuery, void 0); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _getTransactions, void 0); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _isResubmitEnabled, void 0); ++ // TODO: Replace `any` with type ++ // eslint-disable-next-line @typescript-eslint/no-explicit-any ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _listener, void 0); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _getGlobalLock, void 0); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _publishTransaction, void 0); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _running, void 0); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _beforeCheckPendingTransaction, void 0); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _beforePublish, void 0); ++ this.startIfPendingTransactions = () => { ++ const pendingTransactions = _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getPendingTransactions, getPendingTransactions_fn).call(this); ++ if (pendingTransactions.length) { ++ _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _start, start_fn).call(this); ++ } else { ++ this.stop(); ++ } ++ }; ++ this.hub = new (0, _events2.default)(); ++ _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _blockTracker, blockTracker); ++ _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _droppedBlockCountByHash, /* @__PURE__ */ new Map()); ++ _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _getChainId, getChainId); ++ _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _getEthQuery, getEthQuery); ++ _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _getTransactions, getTransactions); ++ _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _isResubmitEnabled, isResubmitEnabled ?? (() => true)); ++ _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _listener, _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _onLatestBlock, onLatestBlock_fn).bind(this)); ++ _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _getGlobalLock, getGlobalLock); ++ _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _publishTransaction, publishTransaction); ++ _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _running, false); ++ _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _beforePublish, hooks?.beforePublish ?? (() => true)); ++ _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _beforeCheckPendingTransaction, hooks?.beforeCheckPendingTransaction ?? (() => true)); ++ } ++ /** ++ * Force checks the network if the given transaction is confirmed and updates it's status. ++ * ++ * @param txMeta - The transaction to check ++ */ ++ async forceCheckTransaction(txMeta) { ++ const releaseLock = await _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _getGlobalLock).call(this); ++ try { ++ await _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _checkTransaction, checkTransaction_fn).call(this, txMeta); ++ } catch (error) { ++ log("Failed to check transaction", error); ++ } finally { ++ releaseLock(); ++ } ++ } ++ stop() { ++ if (!_chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _running)) { ++ return; ++ } ++ _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _blockTracker).removeListener("latest", _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _listener)); ++ _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _running, false); ++ log("Stopped polling"); ++ } ++}; ++_blockTracker = new WeakMap(); ++_droppedBlockCountByHash = new WeakMap(); ++_getChainId = new WeakMap(); ++_getEthQuery = new WeakMap(); ++_getTransactions = new WeakMap(); ++_isResubmitEnabled = new WeakMap(); ++_listener = new WeakMap(); ++_getGlobalLock = new WeakMap(); ++_publishTransaction = new WeakMap(); ++_running = new WeakMap(); ++_beforeCheckPendingTransaction = new WeakMap(); ++_beforePublish = new WeakMap(); ++_start = new WeakSet(); ++start_fn = function() { ++ if (_chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _running)) { ++ return; ++ } ++ _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _blockTracker).on("latest", _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _listener)); ++ _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _running, true); ++ log("Started polling"); ++}; ++_onLatestBlock = new WeakSet(); ++onLatestBlock_fn = async function(latestBlockNumber) { ++ const releaseLock = await _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _getGlobalLock).call(this); ++ try { ++ await _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _checkTransactions, checkTransactions_fn).call(this); ++ } catch (error) { ++ log("Failed to check transactions", error); ++ } finally { ++ releaseLock(); ++ } ++ try { ++ await _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _resubmitTransactions, resubmitTransactions_fn).call(this, latestBlockNumber); ++ } catch (error) { ++ log("Failed to resubmit transactions", error); ++ } ++}; ++_checkTransactions = new WeakSet(); ++checkTransactions_fn = async function() { ++ log("Checking transactions"); ++ const pendingTransactions = _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getPendingTransactions, getPendingTransactions_fn).call(this); ++ if (!pendingTransactions.length) { ++ log("No pending transactions to check"); ++ return; ++ } ++ log("Found pending transactions to check", { ++ count: pendingTransactions.length, ++ ids: pendingTransactions.map((tx) => tx.id) ++ }); ++ await Promise.all( ++ pendingTransactions.map((tx) => _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _checkTransaction, checkTransaction_fn).call(this, tx)) ++ ); ++}; ++_resubmitTransactions = new WeakSet(); ++resubmitTransactions_fn = async function(latestBlockNumber) { ++ if (!_chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _isResubmitEnabled).call(this) || !_chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _running)) { ++ return; ++ } ++ log("Resubmitting transactions"); ++ const pendingTransactions = _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getPendingTransactions, getPendingTransactions_fn).call(this); ++ if (!pendingTransactions.length) { ++ log("No pending transactions to resubmit"); ++ return; ++ } ++ log("Found pending transactions to resubmit", { ++ count: pendingTransactions.length, ++ ids: pendingTransactions.map((tx) => tx.id) ++ }); ++ for (const txMeta of pendingTransactions) { ++ try { ++ await _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _resubmitTransaction, resubmitTransaction_fn).call(this, txMeta, latestBlockNumber); ++ } catch (error) { ++ const errorMessage = error.value?.message?.toLowerCase() || error.message?.toLowerCase() || String(error); ++ if (_chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _isKnownTransactionError, isKnownTransactionError_fn).call(this, errorMessage)) { ++ log("Ignoring known transaction error", errorMessage); ++ continue; ++ } ++ _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _warnTransaction, warnTransaction_fn).call(this, txMeta, error.message, "There was an error when resubmitting this transaction."); ++ } ++ } ++}; ++_isKnownTransactionError = new WeakSet(); ++isKnownTransactionError_fn = function(errorMessage) { ++ return KNOWN_TRANSACTION_ERRORS.some( ++ (knownError) => errorMessage.includes(knownError) ++ ); ++}; ++_resubmitTransaction = new WeakSet(); ++resubmitTransaction_fn = async function(txMeta, latestBlockNumber) { ++ if (!_chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _isResubmitDue, isResubmitDue_fn).call(this, txMeta, latestBlockNumber)) { ++ return; ++ } ++ const { rawTx } = txMeta; ++ if (!_chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _beforePublish).call(this, txMeta)) { ++ return; ++ } ++ const ethQuery = _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _getEthQuery).call(this, txMeta.networkClientId); ++ await _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _publishTransaction).call(this, ethQuery, rawTx); ++ const retryCount = (txMeta.retryCount ?? 0) + 1; ++ _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _updateTransaction, updateTransaction_fn).call(this, _lodash.merge.call(void 0, {}, txMeta, { retryCount }), "PendingTransactionTracker:transaction-retry - Retry count increased"); ++}; ++_isResubmitDue = new WeakSet(); ++isResubmitDue_fn = function(txMeta, latestBlockNumber) { ++ const txMetaWithFirstRetryBlockNumber = _lodash.cloneDeep.call(void 0, txMeta); ++ if (!txMetaWithFirstRetryBlockNumber.firstRetryBlockNumber) { ++ txMetaWithFirstRetryBlockNumber.firstRetryBlockNumber = latestBlockNumber; ++ _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _updateTransaction, updateTransaction_fn).call(this, txMetaWithFirstRetryBlockNumber, "PendingTransactionTracker:#isResubmitDue - First retry block number set"); ++ } ++ const { firstRetryBlockNumber } = txMetaWithFirstRetryBlockNumber; ++ const blocksSinceFirstRetry = Number.parseInt(latestBlockNumber, 16) - Number.parseInt(firstRetryBlockNumber, 16); ++ const retryCount = txMeta.retryCount || 0; ++ const requiredBlocksSinceFirstRetry = Math.min( ++ MAX_RETRY_BLOCK_DISTANCE, ++ Math.pow(2, retryCount) ++ ); ++ return blocksSinceFirstRetry >= requiredBlocksSinceFirstRetry; ++}; ++_checkTransaction = new WeakSet(); ++checkTransaction_fn = async function(txMeta) { ++ const { hash, id } = txMeta; ++ if (!hash && _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _beforeCheckPendingTransaction).call(this, txMeta)) { ++ const error = new Error( ++ "We had an error while submitting this transaction, please try again." ++ ); ++ error.name = "NoTxHashError"; ++ _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _failTransaction, failTransaction_fn).call(this, txMeta, error); ++ return; ++ } ++ if (_chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _isNonceTaken, isNonceTaken_fn).call(this, txMeta)) { ++ log("Nonce already taken", id); ++ _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _dropTransaction, dropTransaction_fn).call(this, txMeta); ++ return; ++ } ++ try { ++ const receipt = await _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getTransactionReceipt, getTransactionReceipt_fn).call(this, hash); ++ const isSuccess = receipt?.status === RECEIPT_STATUS_SUCCESS; ++ const isFailure = receipt?.status === RECEIPT_STATUS_FAILURE; ++ if (isFailure) { ++ log("Transaction receipt has failed status"); ++ _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _failTransaction, failTransaction_fn).call(this, txMeta, new Error("Transaction dropped or replaced")); ++ return; ++ } ++ const { blockNumber, blockHash } = receipt || {}; ++ if (isSuccess && blockNumber && blockHash) { ++ await _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _onTransactionConfirmed, onTransactionConfirmed_fn).call(this, txMeta, { ++ ...receipt, ++ blockNumber, ++ blockHash ++ }); ++ return; ++ } ++ } catch (error) { ++ log("Failed to check transaction", id, error); ++ _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _warnTransaction, warnTransaction_fn).call(this, txMeta, error.message, "There was a problem loading this transaction."); ++ return; ++ } ++ if (await _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _isTransactionDropped, isTransactionDropped_fn).call(this, txMeta)) { ++ _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _dropTransaction, dropTransaction_fn).call(this, txMeta); ++ } ++}; ++_onTransactionConfirmed = new WeakSet(); ++onTransactionConfirmed_fn = async function(txMeta, receipt) { ++ const { id } = txMeta; ++ const { blockHash } = receipt; ++ log("Transaction confirmed", id); ++ const { baseFeePerGas, timestamp: blockTimestamp } = await _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getBlockByHash, getBlockByHash_fn).call(this, blockHash, false); ++ const updatedTxMeta = _lodash.cloneDeep.call(void 0, txMeta); ++ updatedTxMeta.baseFeePerGas = baseFeePerGas; ++ updatedTxMeta.blockTimestamp = blockTimestamp; ++ updatedTxMeta.status = "confirmed" /* confirmed */; ++ updatedTxMeta.txParams = { ++ ...updatedTxMeta.txParams, ++ gasUsed: receipt.gasUsed ++ }; ++ updatedTxMeta.txReceipt = receipt; ++ updatedTxMeta.verifiedOnBlockchain = true; ++ _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _updateTransaction, updateTransaction_fn).call(this, updatedTxMeta, "PendingTransactionTracker:#onTransactionConfirmed - Transaction confirmed"); ++ this.hub.emit("transaction-confirmed", updatedTxMeta); ++}; ++_isTransactionDropped = new WeakSet(); ++isTransactionDropped_fn = async function(txMeta) { ++ const { ++ hash, ++ id, ++ txParams: { nonce, from } ++ } = txMeta; ++ if (!nonce || !hash) { ++ return false; ++ } ++ const networkNextNonceHex = await _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getNetworkTransactionCount, getNetworkTransactionCount_fn).call(this, from); ++ const networkNextNonceNumber = parseInt(networkNextNonceHex, 16); ++ const nonceNumber = parseInt(nonce, 16); ++ if (nonceNumber >= networkNextNonceNumber) { ++ return false; ++ } ++ let droppedBlockCount = _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _droppedBlockCountByHash).get(hash); ++ if (droppedBlockCount === void 0) { ++ droppedBlockCount = 0; ++ _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _droppedBlockCountByHash).set(hash, droppedBlockCount); ++ } ++ if (droppedBlockCount < DROPPED_BLOCK_COUNT) { ++ log("Incrementing dropped block count", { id, droppedBlockCount }); ++ _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _droppedBlockCountByHash).set(hash, droppedBlockCount + 1); ++ return false; ++ } ++ log("Hit dropped block count", id); ++ _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _droppedBlockCountByHash).delete(hash); ++ return true; ++}; ++_isNonceTaken = new WeakSet(); ++isNonceTaken_fn = function(txMeta) { ++ const { id, txParams } = txMeta; ++ return _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getCurrentChainTransactions, getCurrentChainTransactions_fn).call(this).some( ++ (tx) => tx.id !== id && tx.txParams.from === txParams.from && tx.status === "confirmed" /* confirmed */ && tx.txParams.nonce === txParams.nonce && tx.type !== "incoming" /* incoming */ ++ ); ++}; ++_getPendingTransactions = new WeakSet(); ++getPendingTransactions_fn = function() { ++ return _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getCurrentChainTransactions, getCurrentChainTransactions_fn).call(this).filter( ++ (tx) => tx.status === "submitted" /* submitted */ && !tx.verifiedOnBlockchain && !tx.isUserOperation ++ ); ++}; ++_warnTransaction = new WeakSet(); ++warnTransaction_fn = function(txMeta, error, message) { ++ _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _updateTransaction, updateTransaction_fn).call(this, { ++ ...txMeta, ++ warning: { error, message } ++ }, "PendingTransactionTracker:#warnTransaction - Warning added"); ++}; ++_failTransaction = new WeakSet(); ++failTransaction_fn = function(txMeta, error) { ++ log("Transaction failed", txMeta.id, error); ++ this.hub.emit("transaction-failed", txMeta, error); ++}; ++_dropTransaction = new WeakSet(); ++dropTransaction_fn = function(txMeta) { ++ log("Transaction dropped", txMeta.id); ++ this.hub.emit("transaction-dropped", txMeta); ++}; ++_updateTransaction = new WeakSet(); ++updateTransaction_fn = function(txMeta, note) { ++ this.hub.emit("transaction-updated", txMeta, note); ++}; ++_getTransactionReceipt = new WeakSet(); ++getTransactionReceipt_fn = async function(txHash) { ++ return await _controllerutils.query.call(void 0, _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _getEthQuery).call(this), "getTransactionReceipt", [txHash]); ++}; ++_getBlockByHash = new WeakSet(); ++getBlockByHash_fn = async function(blockHash, includeTransactionDetails) { ++ return await _controllerutils.query.call(void 0, _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _getEthQuery).call(this), "getBlockByHash", [ ++ blockHash, ++ includeTransactionDetails ++ ]); ++}; ++_getNetworkTransactionCount = new WeakSet(); ++getNetworkTransactionCount_fn = async function(address) { ++ return await _controllerutils.query.call(void 0, _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _getEthQuery).call(this), "getTransactionCount", [address]); ++}; ++_getCurrentChainTransactions = new WeakSet(); ++getCurrentChainTransactions_fn = function() { ++ const currentChainId = _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _getChainId).call(this); ++ return _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _getTransactions).call(this).filter( ++ (tx) => tx.chainId === currentChainId ++ ); ++}; ++ ++ ++ ++exports.PendingTransactionTracker = PendingTransactionTracker; ++//# sourceMappingURL=chunk-6DODV6OV.js.map +\ No newline at end of file +diff --git a/dist/chunk-6DODV6OV.js.map b/dist/chunk-6DODV6OV.js.map +new file mode 100644 +index 0000000000000000000000000000000000000000..c982d3394489271e672f68247654349d554c702a +--- /dev/null ++++ b/dist/chunk-6DODV6OV.js.map +@@ -0,0 +1 @@ ++{"version":3,"sources":["../src/helpers/PendingTransactionTracker.ts"],"names":[],"mappings":"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","sourcesContent":["import { query } from '@metamask/controller-utils';\nimport type EthQuery from '@metamask/eth-query';\nimport type {\n BlockTracker,\n NetworkClientId,\n} from '@metamask/network-controller';\nimport EventEmitter from 'events';\nimport { cloneDeep, merge } from 'lodash';\n\nimport { createModuleLogger, projectLogger } from '../logger';\nimport type { TransactionMeta, TransactionReceipt } from '../types';\nimport { TransactionStatus, TransactionType } from '../types';\n\n/**\n * We wait this many blocks before emitting a 'transaction-dropped' event\n * This is because we could be talking to a node that is out of sync\n */\nconst DROPPED_BLOCK_COUNT = 3;\n\nconst RECEIPT_STATUS_SUCCESS = '0x1';\nconst RECEIPT_STATUS_FAILURE = '0x0';\nconst MAX_RETRY_BLOCK_DISTANCE = 50;\n\nconst KNOWN_TRANSACTION_ERRORS = [\n 'replacement transaction underpriced',\n 'known transaction',\n 'gas price too low to replace',\n 'transaction with the same hash was already imported',\n 'gateway timeout',\n 'nonce too low',\n];\n\nconst log = createModuleLogger(projectLogger, 'pending-transactions');\n\ntype SuccessfulTransactionReceipt = TransactionReceipt & {\n blockNumber: string;\n blockHash: string;\n};\n\ntype Events = {\n 'transaction-confirmed': [txMeta: TransactionMeta];\n 'transaction-dropped': [txMeta: TransactionMeta];\n 'transaction-failed': [txMeta: TransactionMeta, error: Error];\n 'transaction-updated': [txMeta: TransactionMeta, note: string];\n};\n\n// This interface was created before this ESLint rule was added.\n// Convert to a `type` in a future major version.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\nexport interface PendingTransactionTrackerEventEmitter extends EventEmitter {\n // TODO: Either fix this lint violation or explain why it's necessary to ignore.\n // eslint-disable-next-line @typescript-eslint/naming-convention\n on(\n eventName: T,\n listener: (...args: Events[T]) => void,\n ): this;\n\n // TODO: Either fix this lint violation or explain why it's necessary to ignore.\n // eslint-disable-next-line @typescript-eslint/naming-convention\n emit(eventName: T, ...args: Events[T]): boolean;\n}\n\nexport class PendingTransactionTracker {\n hub: PendingTransactionTrackerEventEmitter;\n\n #blockTracker: BlockTracker;\n\n #droppedBlockCountByHash: Map;\n\n #getChainId: () => string;\n\n #getEthQuery: (networkClientId?: NetworkClientId) => EthQuery;\n\n #getTransactions: () => TransactionMeta[];\n\n #isResubmitEnabled: () => boolean;\n\n // TODO: Replace `any` with type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n #listener: any;\n\n #getGlobalLock: () => Promise<() => void>;\n\n #publishTransaction: (ethQuery: EthQuery, rawTx: string) => Promise;\n\n #running: boolean;\n\n #beforeCheckPendingTransaction: (transactionMeta: TransactionMeta) => boolean;\n\n #beforePublish: (transactionMeta: TransactionMeta) => boolean;\n\n constructor({\n blockTracker,\n getChainId,\n getEthQuery,\n getTransactions,\n isResubmitEnabled,\n getGlobalLock,\n publishTransaction,\n hooks,\n }: {\n blockTracker: BlockTracker;\n getChainId: () => string;\n getEthQuery: (networkClientId?: NetworkClientId) => EthQuery;\n getTransactions: () => TransactionMeta[];\n isResubmitEnabled?: () => boolean;\n getGlobalLock: () => Promise<() => void>;\n publishTransaction: (ethQuery: EthQuery, rawTx: string) => Promise;\n hooks?: {\n beforeCheckPendingTransaction?: (\n transactionMeta: TransactionMeta,\n ) => boolean;\n beforePublish?: (transactionMeta: TransactionMeta) => boolean;\n };\n }) {\n this.hub = new EventEmitter() as PendingTransactionTrackerEventEmitter;\n\n this.#blockTracker = blockTracker;\n this.#droppedBlockCountByHash = new Map();\n this.#getChainId = getChainId;\n this.#getEthQuery = getEthQuery;\n this.#getTransactions = getTransactions;\n this.#isResubmitEnabled = isResubmitEnabled ?? (() => true);\n this.#listener = this.#onLatestBlock.bind(this);\n this.#getGlobalLock = getGlobalLock;\n this.#publishTransaction = publishTransaction;\n this.#running = false;\n this.#beforePublish = hooks?.beforePublish ?? (() => true);\n this.#beforeCheckPendingTransaction =\n hooks?.beforeCheckPendingTransaction ?? (() => true);\n }\n\n startIfPendingTransactions = () => {\n const pendingTransactions = this.#getPendingTransactions();\n\n if (pendingTransactions.length) {\n this.#start();\n } else {\n this.stop();\n }\n };\n\n /**\n * Force checks the network if the given transaction is confirmed and updates it's status.\n *\n * @param txMeta - The transaction to check\n */\n async forceCheckTransaction(txMeta: TransactionMeta) {\n const releaseLock = await this.#getGlobalLock();\n\n try {\n await this.#checkTransaction(txMeta);\n } catch (error) {\n /* istanbul ignore next */\n log('Failed to check transaction', error);\n } finally {\n releaseLock();\n }\n }\n\n #start() {\n if (this.#running) {\n return;\n }\n\n this.#blockTracker.on('latest', this.#listener);\n this.#running = true;\n\n log('Started polling');\n }\n\n stop() {\n if (!this.#running) {\n return;\n }\n\n this.#blockTracker.removeListener('latest', this.#listener);\n this.#running = false;\n\n log('Stopped polling');\n }\n\n async #onLatestBlock(latestBlockNumber: string) {\n const releaseLock = await this.#getGlobalLock();\n\n try {\n await this.#checkTransactions();\n } catch (error) {\n /* istanbul ignore next */\n log('Failed to check transactions', error);\n } finally {\n releaseLock();\n }\n\n try {\n await this.#resubmitTransactions(latestBlockNumber);\n } catch (error) {\n /* istanbul ignore next */\n log('Failed to resubmit transactions', error);\n }\n }\n\n async #checkTransactions() {\n log('Checking transactions');\n\n const pendingTransactions = this.#getPendingTransactions();\n\n if (!pendingTransactions.length) {\n log('No pending transactions to check');\n return;\n }\n\n log('Found pending transactions to check', {\n count: pendingTransactions.length,\n ids: pendingTransactions.map((tx) => tx.id),\n });\n\n await Promise.all(\n pendingTransactions.map((tx) => this.#checkTransaction(tx)),\n );\n }\n\n async #resubmitTransactions(latestBlockNumber: string) {\n if (!this.#isResubmitEnabled() || !this.#running) {\n return;\n }\n\n log('Resubmitting transactions');\n\n const pendingTransactions = this.#getPendingTransactions();\n\n if (!pendingTransactions.length) {\n log('No pending transactions to resubmit');\n return;\n }\n\n log('Found pending transactions to resubmit', {\n count: pendingTransactions.length,\n ids: pendingTransactions.map((tx) => tx.id),\n });\n\n for (const txMeta of pendingTransactions) {\n try {\n await this.#resubmitTransaction(txMeta, latestBlockNumber);\n // TODO: Replace `any` with type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (error: any) {\n /* istanbul ignore next */\n const errorMessage =\n error.value?.message?.toLowerCase() ||\n error.message?.toLowerCase() ||\n String(error);\n\n if (this.#isKnownTransactionError(errorMessage)) {\n log('Ignoring known transaction error', errorMessage);\n continue;\n }\n\n this.#warnTransaction(\n txMeta,\n error.message,\n 'There was an error when resubmitting this transaction.',\n );\n }\n }\n }\n\n #isKnownTransactionError(errorMessage: string) {\n return KNOWN_TRANSACTION_ERRORS.some((knownError) =>\n errorMessage.includes(knownError),\n );\n }\n\n async #resubmitTransaction(\n txMeta: TransactionMeta,\n latestBlockNumber: string,\n ) {\n if (!this.#isResubmitDue(txMeta, latestBlockNumber)) {\n return;\n }\n\n const { rawTx } = txMeta;\n\n if (!this.#beforePublish(txMeta)) {\n return;\n }\n\n const ethQuery = this.#getEthQuery(txMeta.networkClientId);\n await this.#publishTransaction(ethQuery, rawTx as string);\n\n const retryCount = (txMeta.retryCount ?? 0) + 1;\n\n this.#updateTransaction(\n merge({}, txMeta, { retryCount }),\n 'PendingTransactionTracker:transaction-retry - Retry count increased',\n );\n }\n\n #isResubmitDue(txMeta: TransactionMeta, latestBlockNumber: string): boolean {\n const txMetaWithFirstRetryBlockNumber = cloneDeep(txMeta);\n\n if (!txMetaWithFirstRetryBlockNumber.firstRetryBlockNumber) {\n txMetaWithFirstRetryBlockNumber.firstRetryBlockNumber = latestBlockNumber;\n\n this.#updateTransaction(\n txMetaWithFirstRetryBlockNumber,\n 'PendingTransactionTracker:#isResubmitDue - First retry block number set',\n );\n }\n\n const { firstRetryBlockNumber } = txMetaWithFirstRetryBlockNumber;\n\n const blocksSinceFirstRetry =\n Number.parseInt(latestBlockNumber, 16) -\n Number.parseInt(firstRetryBlockNumber, 16);\n\n const retryCount = txMeta.retryCount || 0;\n\n // Exponential backoff to limit retries at publishing\n // Capped at ~15 minutes between retries\n const requiredBlocksSinceFirstRetry = Math.min(\n MAX_RETRY_BLOCK_DISTANCE,\n Math.pow(2, retryCount),\n );\n\n return blocksSinceFirstRetry >= requiredBlocksSinceFirstRetry;\n }\n\n async #checkTransaction(txMeta: TransactionMeta) {\n const { hash, id } = txMeta;\n\n if (!hash && this.#beforeCheckPendingTransaction(txMeta)) {\n const error = new Error(\n 'We had an error while submitting this transaction, please try again.',\n );\n\n error.name = 'NoTxHashError';\n\n this.#failTransaction(txMeta, error);\n\n return;\n }\n\n if (this.#isNonceTaken(txMeta)) {\n log('Nonce already taken', id);\n this.#dropTransaction(txMeta);\n return;\n }\n\n try {\n const receipt = await this.#getTransactionReceipt(hash);\n const isSuccess = receipt?.status === RECEIPT_STATUS_SUCCESS;\n const isFailure = receipt?.status === RECEIPT_STATUS_FAILURE;\n\n if (isFailure) {\n log('Transaction receipt has failed status');\n\n this.#failTransaction(\n txMeta,\n new Error('Transaction dropped or replaced'),\n );\n\n return;\n }\n\n const { blockNumber, blockHash } = receipt || {};\n\n if (isSuccess && blockNumber && blockHash) {\n await this.#onTransactionConfirmed(txMeta, {\n ...receipt,\n blockNumber,\n blockHash,\n });\n\n return;\n }\n // TODO: Replace `any` with type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (error: any) {\n log('Failed to check transaction', id, error);\n\n this.#warnTransaction(\n txMeta,\n error.message,\n 'There was a problem loading this transaction.',\n );\n\n return;\n }\n\n if (await this.#isTransactionDropped(txMeta)) {\n this.#dropTransaction(txMeta);\n }\n }\n\n async #onTransactionConfirmed(\n txMeta: TransactionMeta,\n receipt: SuccessfulTransactionReceipt,\n ) {\n const { id } = txMeta;\n const { blockHash } = receipt;\n\n log('Transaction confirmed', id);\n\n const { baseFeePerGas, timestamp: blockTimestamp } =\n await this.#getBlockByHash(blockHash, false);\n\n const updatedTxMeta = cloneDeep(txMeta);\n updatedTxMeta.baseFeePerGas = baseFeePerGas;\n updatedTxMeta.blockTimestamp = blockTimestamp;\n updatedTxMeta.status = TransactionStatus.confirmed;\n updatedTxMeta.txParams = {\n ...updatedTxMeta.txParams,\n gasUsed: receipt.gasUsed,\n };\n updatedTxMeta.txReceipt = receipt;\n updatedTxMeta.verifiedOnBlockchain = true;\n\n this.#updateTransaction(\n updatedTxMeta,\n 'PendingTransactionTracker:#onTransactionConfirmed - Transaction confirmed',\n );\n\n this.hub.emit('transaction-confirmed', updatedTxMeta);\n }\n\n async #isTransactionDropped(txMeta: TransactionMeta) {\n const {\n hash,\n id,\n txParams: { nonce, from },\n } = txMeta;\n\n /* istanbul ignore next */\n if (!nonce || !hash) {\n return false;\n }\n\n const networkNextNonceHex = await this.#getNetworkTransactionCount(from);\n const networkNextNonceNumber = parseInt(networkNextNonceHex, 16);\n const nonceNumber = parseInt(nonce, 16);\n\n if (nonceNumber >= networkNextNonceNumber) {\n return false;\n }\n\n let droppedBlockCount = this.#droppedBlockCountByHash.get(hash);\n\n if (droppedBlockCount === undefined) {\n droppedBlockCount = 0;\n this.#droppedBlockCountByHash.set(hash, droppedBlockCount);\n }\n\n if (droppedBlockCount < DROPPED_BLOCK_COUNT) {\n log('Incrementing dropped block count', { id, droppedBlockCount });\n this.#droppedBlockCountByHash.set(hash, droppedBlockCount + 1);\n return false;\n }\n\n log('Hit dropped block count', id);\n\n this.#droppedBlockCountByHash.delete(hash);\n return true;\n }\n\n #isNonceTaken(txMeta: TransactionMeta): boolean {\n const { id, txParams } = txMeta;\n\n return this.#getCurrentChainTransactions().some(\n (tx) =>\n tx.id !== id &&\n tx.txParams.from === txParams.from &&\n tx.status === TransactionStatus.confirmed &&\n tx.txParams.nonce === txParams.nonce &&\n tx.type !== TransactionType.incoming,\n );\n }\n\n #getPendingTransactions(): TransactionMeta[] {\n return this.#getCurrentChainTransactions().filter(\n (tx) =>\n tx.status === TransactionStatus.submitted &&\n !tx.verifiedOnBlockchain &&\n !tx.isUserOperation,\n );\n }\n\n #warnTransaction(txMeta: TransactionMeta, error: string, message: string) {\n this.#updateTransaction(\n {\n ...txMeta,\n warning: { error, message },\n },\n 'PendingTransactionTracker:#warnTransaction - Warning added',\n );\n }\n\n #failTransaction(txMeta: TransactionMeta, error: Error) {\n log('Transaction failed', txMeta.id, error);\n this.hub.emit('transaction-failed', txMeta, error);\n }\n\n #dropTransaction(txMeta: TransactionMeta) {\n log('Transaction dropped', txMeta.id);\n this.hub.emit('transaction-dropped', txMeta);\n }\n\n #updateTransaction(txMeta: TransactionMeta, note: string) {\n this.hub.emit('transaction-updated', txMeta, note);\n }\n\n async #getTransactionReceipt(\n txHash?: string,\n ): Promise {\n return await query(this.#getEthQuery(), 'getTransactionReceipt', [txHash]);\n }\n\n async #getBlockByHash(\n blockHash: string,\n includeTransactionDetails: boolean,\n // TODO: Replace `any` with type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ): Promise {\n return await query(this.#getEthQuery(), 'getBlockByHash', [\n blockHash,\n includeTransactionDetails,\n ]);\n }\n\n async #getNetworkTransactionCount(address: string): Promise {\n return await query(this.#getEthQuery(), 'getTransactionCount', [address]);\n }\n\n #getCurrentChainTransactions(): TransactionMeta[] {\n const currentChainId = this.#getChainId();\n\n return this.#getTransactions().filter(\n (tx) => tx.chainId === currentChainId,\n );\n }\n}\n"]} +\ No newline at end of file +diff --git a/dist/chunk-7M2R5AHC.mjs b/dist/chunk-7M2R5AHC.mjs +new file mode 100644 +index 0000000000000000000000000000000000000000..3b137fe1e719807e859ca036c76ffff8c234f964 +--- /dev/null ++++ b/dist/chunk-7M2R5AHC.mjs +@@ -0,0 +1,390 @@ ++import { ++ createModuleLogger, ++ projectLogger ++} from "./chunk-UQQWZT6C.mjs"; ++import { ++ __privateAdd, ++ __privateGet, ++ __privateMethod, ++ __privateSet ++} from "./chunk-XUI43LEZ.mjs"; ++ ++// src/helpers/PendingTransactionTracker.ts ++import { query } from "@metamask/controller-utils"; ++import EventEmitter from "events"; ++import { cloneDeep, merge } from "lodash"; ++var DROPPED_BLOCK_COUNT = 3; ++var RECEIPT_STATUS_SUCCESS = "0x1"; ++var RECEIPT_STATUS_FAILURE = "0x0"; ++var MAX_RETRY_BLOCK_DISTANCE = 50; ++var KNOWN_TRANSACTION_ERRORS = [ ++ "replacement transaction underpriced", ++ "known transaction", ++ "gas price too low to replace", ++ "transaction with the same hash was already imported", ++ "gateway timeout", ++ "nonce too low" ++]; ++var log = createModuleLogger(projectLogger, "pending-transactions"); ++var _blockTracker, _droppedBlockCountByHash, _getChainId, _getEthQuery, _getTransactions, _isResubmitEnabled, _listener, _getGlobalLock, _publishTransaction, _running, _beforeCheckPendingTransaction, _beforePublish, _start, start_fn, _onLatestBlock, onLatestBlock_fn, _checkTransactions, checkTransactions_fn, _resubmitTransactions, resubmitTransactions_fn, _isKnownTransactionError, isKnownTransactionError_fn, _resubmitTransaction, resubmitTransaction_fn, _isResubmitDue, isResubmitDue_fn, _checkTransaction, checkTransaction_fn, _onTransactionConfirmed, onTransactionConfirmed_fn, _isTransactionDropped, isTransactionDropped_fn, _isNonceTaken, isNonceTaken_fn, _getPendingTransactions, getPendingTransactions_fn, _warnTransaction, warnTransaction_fn, _failTransaction, failTransaction_fn, _dropTransaction, dropTransaction_fn, _updateTransaction, updateTransaction_fn, _getTransactionReceipt, getTransactionReceipt_fn, _getBlockByHash, getBlockByHash_fn, _getNetworkTransactionCount, getNetworkTransactionCount_fn, _getCurrentChainTransactions, getCurrentChainTransactions_fn; ++var PendingTransactionTracker = class { ++ constructor({ ++ blockTracker, ++ getChainId, ++ getEthQuery, ++ getTransactions, ++ isResubmitEnabled, ++ getGlobalLock, ++ publishTransaction, ++ hooks ++ }) { ++ __privateAdd(this, _start); ++ __privateAdd(this, _onLatestBlock); ++ __privateAdd(this, _checkTransactions); ++ __privateAdd(this, _resubmitTransactions); ++ __privateAdd(this, _isKnownTransactionError); ++ __privateAdd(this, _resubmitTransaction); ++ __privateAdd(this, _isResubmitDue); ++ __privateAdd(this, _checkTransaction); ++ __privateAdd(this, _onTransactionConfirmed); ++ __privateAdd(this, _isTransactionDropped); ++ __privateAdd(this, _isNonceTaken); ++ __privateAdd(this, _getPendingTransactions); ++ __privateAdd(this, _warnTransaction); ++ __privateAdd(this, _failTransaction); ++ __privateAdd(this, _dropTransaction); ++ __privateAdd(this, _updateTransaction); ++ __privateAdd(this, _getTransactionReceipt); ++ __privateAdd(this, _getBlockByHash); ++ __privateAdd(this, _getNetworkTransactionCount); ++ __privateAdd(this, _getCurrentChainTransactions); ++ __privateAdd(this, _blockTracker, void 0); ++ __privateAdd(this, _droppedBlockCountByHash, void 0); ++ __privateAdd(this, _getChainId, void 0); ++ __privateAdd(this, _getEthQuery, void 0); ++ __privateAdd(this, _getTransactions, void 0); ++ __privateAdd(this, _isResubmitEnabled, void 0); ++ // TODO: Replace `any` with type ++ // eslint-disable-next-line @typescript-eslint/no-explicit-any ++ __privateAdd(this, _listener, void 0); ++ __privateAdd(this, _getGlobalLock, void 0); ++ __privateAdd(this, _publishTransaction, void 0); ++ __privateAdd(this, _running, void 0); ++ __privateAdd(this, _beforeCheckPendingTransaction, void 0); ++ __privateAdd(this, _beforePublish, void 0); ++ this.startIfPendingTransactions = () => { ++ const pendingTransactions = __privateMethod(this, _getPendingTransactions, getPendingTransactions_fn).call(this); ++ if (pendingTransactions.length) { ++ __privateMethod(this, _start, start_fn).call(this); ++ } else { ++ this.stop(); ++ } ++ }; ++ this.hub = new EventEmitter(); ++ __privateSet(this, _blockTracker, blockTracker); ++ __privateSet(this, _droppedBlockCountByHash, /* @__PURE__ */ new Map()); ++ __privateSet(this, _getChainId, getChainId); ++ __privateSet(this, _getEthQuery, getEthQuery); ++ __privateSet(this, _getTransactions, getTransactions); ++ __privateSet(this, _isResubmitEnabled, isResubmitEnabled ?? (() => true)); ++ __privateSet(this, _listener, __privateMethod(this, _onLatestBlock, onLatestBlock_fn).bind(this)); ++ __privateSet(this, _getGlobalLock, getGlobalLock); ++ __privateSet(this, _publishTransaction, publishTransaction); ++ __privateSet(this, _running, false); ++ __privateSet(this, _beforePublish, hooks?.beforePublish ?? (() => true)); ++ __privateSet(this, _beforeCheckPendingTransaction, hooks?.beforeCheckPendingTransaction ?? (() => true)); ++ } ++ /** ++ * Force checks the network if the given transaction is confirmed and updates it's status. ++ * ++ * @param txMeta - The transaction to check ++ */ ++ async forceCheckTransaction(txMeta) { ++ const releaseLock = await __privateGet(this, _getGlobalLock).call(this); ++ try { ++ await __privateMethod(this, _checkTransaction, checkTransaction_fn).call(this, txMeta); ++ } catch (error) { ++ log("Failed to check transaction", error); ++ } finally { ++ releaseLock(); ++ } ++ } ++ stop() { ++ if (!__privateGet(this, _running)) { ++ return; ++ } ++ __privateGet(this, _blockTracker).removeListener("latest", __privateGet(this, _listener)); ++ __privateSet(this, _running, false); ++ log("Stopped polling"); ++ } ++}; ++_blockTracker = new WeakMap(); ++_droppedBlockCountByHash = new WeakMap(); ++_getChainId = new WeakMap(); ++_getEthQuery = new WeakMap(); ++_getTransactions = new WeakMap(); ++_isResubmitEnabled = new WeakMap(); ++_listener = new WeakMap(); ++_getGlobalLock = new WeakMap(); ++_publishTransaction = new WeakMap(); ++_running = new WeakMap(); ++_beforeCheckPendingTransaction = new WeakMap(); ++_beforePublish = new WeakMap(); ++_start = new WeakSet(); ++start_fn = function() { ++ if (__privateGet(this, _running)) { ++ return; ++ } ++ __privateGet(this, _blockTracker).on("latest", __privateGet(this, _listener)); ++ __privateSet(this, _running, true); ++ log("Started polling"); ++}; ++_onLatestBlock = new WeakSet(); ++onLatestBlock_fn = async function(latestBlockNumber) { ++ const releaseLock = await __privateGet(this, _getGlobalLock).call(this); ++ try { ++ await __privateMethod(this, _checkTransactions, checkTransactions_fn).call(this); ++ } catch (error) { ++ log("Failed to check transactions", error); ++ } finally { ++ releaseLock(); ++ } ++ try { ++ await __privateMethod(this, _resubmitTransactions, resubmitTransactions_fn).call(this, latestBlockNumber); ++ } catch (error) { ++ log("Failed to resubmit transactions", error); ++ } ++}; ++_checkTransactions = new WeakSet(); ++checkTransactions_fn = async function() { ++ log("Checking transactions"); ++ const pendingTransactions = __privateMethod(this, _getPendingTransactions, getPendingTransactions_fn).call(this); ++ if (!pendingTransactions.length) { ++ log("No pending transactions to check"); ++ return; ++ } ++ log("Found pending transactions to check", { ++ count: pendingTransactions.length, ++ ids: pendingTransactions.map((tx) => tx.id) ++ }); ++ await Promise.all( ++ pendingTransactions.map((tx) => __privateMethod(this, _checkTransaction, checkTransaction_fn).call(this, tx)) ++ ); ++}; ++_resubmitTransactions = new WeakSet(); ++resubmitTransactions_fn = async function(latestBlockNumber) { ++ if (!__privateGet(this, _isResubmitEnabled).call(this) || !__privateGet(this, _running)) { ++ return; ++ } ++ log("Resubmitting transactions"); ++ const pendingTransactions = __privateMethod(this, _getPendingTransactions, getPendingTransactions_fn).call(this); ++ if (!pendingTransactions.length) { ++ log("No pending transactions to resubmit"); ++ return; ++ } ++ log("Found pending transactions to resubmit", { ++ count: pendingTransactions.length, ++ ids: pendingTransactions.map((tx) => tx.id) ++ }); ++ for (const txMeta of pendingTransactions) { ++ try { ++ await __privateMethod(this, _resubmitTransaction, resubmitTransaction_fn).call(this, txMeta, latestBlockNumber); ++ } catch (error) { ++ const errorMessage = error.value?.message?.toLowerCase() || error.message?.toLowerCase() || String(error); ++ if (__privateMethod(this, _isKnownTransactionError, isKnownTransactionError_fn).call(this, errorMessage)) { ++ log("Ignoring known transaction error", errorMessage); ++ continue; ++ } ++ __privateMethod(this, _warnTransaction, warnTransaction_fn).call(this, txMeta, error.message, "There was an error when resubmitting this transaction."); ++ } ++ } ++}; ++_isKnownTransactionError = new WeakSet(); ++isKnownTransactionError_fn = function(errorMessage) { ++ return KNOWN_TRANSACTION_ERRORS.some( ++ (knownError) => errorMessage.includes(knownError) ++ ); ++}; ++_resubmitTransaction = new WeakSet(); ++resubmitTransaction_fn = async function(txMeta, latestBlockNumber) { ++ if (!__privateMethod(this, _isResubmitDue, isResubmitDue_fn).call(this, txMeta, latestBlockNumber)) { ++ return; ++ } ++ const { rawTx } = txMeta; ++ if (!__privateGet(this, _beforePublish).call(this, txMeta)) { ++ return; ++ } ++ const ethQuery = __privateGet(this, _getEthQuery).call(this, txMeta.networkClientId); ++ await __privateGet(this, _publishTransaction).call(this, ethQuery, rawTx); ++ const retryCount = (txMeta.retryCount ?? 0) + 1; ++ __privateMethod(this, _updateTransaction, updateTransaction_fn).call(this, merge({}, txMeta, { retryCount }), "PendingTransactionTracker:transaction-retry - Retry count increased"); ++}; ++_isResubmitDue = new WeakSet(); ++isResubmitDue_fn = function(txMeta, latestBlockNumber) { ++ const txMetaWithFirstRetryBlockNumber = cloneDeep(txMeta); ++ if (!txMetaWithFirstRetryBlockNumber.firstRetryBlockNumber) { ++ txMetaWithFirstRetryBlockNumber.firstRetryBlockNumber = latestBlockNumber; ++ __privateMethod(this, _updateTransaction, updateTransaction_fn).call(this, txMetaWithFirstRetryBlockNumber, "PendingTransactionTracker:#isResubmitDue - First retry block number set"); ++ } ++ const { firstRetryBlockNumber } = txMetaWithFirstRetryBlockNumber; ++ const blocksSinceFirstRetry = Number.parseInt(latestBlockNumber, 16) - Number.parseInt(firstRetryBlockNumber, 16); ++ const retryCount = txMeta.retryCount || 0; ++ const requiredBlocksSinceFirstRetry = Math.min( ++ MAX_RETRY_BLOCK_DISTANCE, ++ Math.pow(2, retryCount) ++ ); ++ return blocksSinceFirstRetry >= requiredBlocksSinceFirstRetry; ++}; ++_checkTransaction = new WeakSet(); ++checkTransaction_fn = async function(txMeta) { ++ const { hash, id } = txMeta; ++ if (!hash && __privateGet(this, _beforeCheckPendingTransaction).call(this, txMeta)) { ++ const error = new Error( ++ "We had an error while submitting this transaction, please try again." ++ ); ++ error.name = "NoTxHashError"; ++ __privateMethod(this, _failTransaction, failTransaction_fn).call(this, txMeta, error); ++ return; ++ } ++ if (__privateMethod(this, _isNonceTaken, isNonceTaken_fn).call(this, txMeta)) { ++ log("Nonce already taken", id); ++ __privateMethod(this, _dropTransaction, dropTransaction_fn).call(this, txMeta); ++ return; ++ } ++ try { ++ const receipt = await __privateMethod(this, _getTransactionReceipt, getTransactionReceipt_fn).call(this, hash); ++ const isSuccess = receipt?.status === RECEIPT_STATUS_SUCCESS; ++ const isFailure = receipt?.status === RECEIPT_STATUS_FAILURE; ++ if (isFailure) { ++ log("Transaction receipt has failed status"); ++ __privateMethod(this, _failTransaction, failTransaction_fn).call(this, txMeta, new Error("Transaction dropped or replaced")); ++ return; ++ } ++ const { blockNumber, blockHash } = receipt || {}; ++ if (isSuccess && blockNumber && blockHash) { ++ await __privateMethod(this, _onTransactionConfirmed, onTransactionConfirmed_fn).call(this, txMeta, { ++ ...receipt, ++ blockNumber, ++ blockHash ++ }); ++ return; ++ } ++ } catch (error) { ++ log("Failed to check transaction", id, error); ++ __privateMethod(this, _warnTransaction, warnTransaction_fn).call(this, txMeta, error.message, "There was a problem loading this transaction."); ++ return; ++ } ++ if (await __privateMethod(this, _isTransactionDropped, isTransactionDropped_fn).call(this, txMeta)) { ++ __privateMethod(this, _dropTransaction, dropTransaction_fn).call(this, txMeta); ++ } ++}; ++_onTransactionConfirmed = new WeakSet(); ++onTransactionConfirmed_fn = async function(txMeta, receipt) { ++ const { id } = txMeta; ++ const { blockHash } = receipt; ++ log("Transaction confirmed", id); ++ const { baseFeePerGas, timestamp: blockTimestamp } = await __privateMethod(this, _getBlockByHash, getBlockByHash_fn).call(this, blockHash, false); ++ const updatedTxMeta = cloneDeep(txMeta); ++ updatedTxMeta.baseFeePerGas = baseFeePerGas; ++ updatedTxMeta.blockTimestamp = blockTimestamp; ++ updatedTxMeta.status = "confirmed" /* confirmed */; ++ updatedTxMeta.txParams = { ++ ...updatedTxMeta.txParams, ++ gasUsed: receipt.gasUsed ++ }; ++ updatedTxMeta.txReceipt = receipt; ++ updatedTxMeta.verifiedOnBlockchain = true; ++ __privateMethod(this, _updateTransaction, updateTransaction_fn).call(this, updatedTxMeta, "PendingTransactionTracker:#onTransactionConfirmed - Transaction confirmed"); ++ this.hub.emit("transaction-confirmed", updatedTxMeta); ++}; ++_isTransactionDropped = new WeakSet(); ++isTransactionDropped_fn = async function(txMeta) { ++ const { ++ hash, ++ id, ++ txParams: { nonce, from } ++ } = txMeta; ++ if (!nonce || !hash) { ++ return false; ++ } ++ const networkNextNonceHex = await __privateMethod(this, _getNetworkTransactionCount, getNetworkTransactionCount_fn).call(this, from); ++ const networkNextNonceNumber = parseInt(networkNextNonceHex, 16); ++ const nonceNumber = parseInt(nonce, 16); ++ if (nonceNumber >= networkNextNonceNumber) { ++ return false; ++ } ++ let droppedBlockCount = __privateGet(this, _droppedBlockCountByHash).get(hash); ++ if (droppedBlockCount === void 0) { ++ droppedBlockCount = 0; ++ __privateGet(this, _droppedBlockCountByHash).set(hash, droppedBlockCount); ++ } ++ if (droppedBlockCount < DROPPED_BLOCK_COUNT) { ++ log("Incrementing dropped block count", { id, droppedBlockCount }); ++ __privateGet(this, _droppedBlockCountByHash).set(hash, droppedBlockCount + 1); ++ return false; ++ } ++ log("Hit dropped block count", id); ++ __privateGet(this, _droppedBlockCountByHash).delete(hash); ++ return true; ++}; ++_isNonceTaken = new WeakSet(); ++isNonceTaken_fn = function(txMeta) { ++ const { id, txParams } = txMeta; ++ return __privateMethod(this, _getCurrentChainTransactions, getCurrentChainTransactions_fn).call(this).some( ++ (tx) => tx.id !== id && tx.txParams.from === txParams.from && tx.status === "confirmed" /* confirmed */ && tx.txParams.nonce === txParams.nonce && tx.type !== "incoming" /* incoming */ ++ ); ++}; ++_getPendingTransactions = new WeakSet(); ++getPendingTransactions_fn = function() { ++ return __privateMethod(this, _getCurrentChainTransactions, getCurrentChainTransactions_fn).call(this).filter( ++ (tx) => tx.status === "submitted" /* submitted */ && !tx.verifiedOnBlockchain && !tx.isUserOperation ++ ); ++}; ++_warnTransaction = new WeakSet(); ++warnTransaction_fn = function(txMeta, error, message) { ++ __privateMethod(this, _updateTransaction, updateTransaction_fn).call(this, { ++ ...txMeta, ++ warning: { error, message } ++ }, "PendingTransactionTracker:#warnTransaction - Warning added"); ++}; ++_failTransaction = new WeakSet(); ++failTransaction_fn = function(txMeta, error) { ++ log("Transaction failed", txMeta.id, error); ++ this.hub.emit("transaction-failed", txMeta, error); ++}; ++_dropTransaction = new WeakSet(); ++dropTransaction_fn = function(txMeta) { ++ log("Transaction dropped", txMeta.id); ++ this.hub.emit("transaction-dropped", txMeta); ++}; ++_updateTransaction = new WeakSet(); ++updateTransaction_fn = function(txMeta, note) { ++ this.hub.emit("transaction-updated", txMeta, note); ++}; ++_getTransactionReceipt = new WeakSet(); ++getTransactionReceipt_fn = async function(txHash) { ++ return await query(__privateGet(this, _getEthQuery).call(this), "getTransactionReceipt", [txHash]); ++}; ++_getBlockByHash = new WeakSet(); ++getBlockByHash_fn = async function(blockHash, includeTransactionDetails) { ++ return await query(__privateGet(this, _getEthQuery).call(this), "getBlockByHash", [ ++ blockHash, ++ includeTransactionDetails ++ ]); ++}; ++_getNetworkTransactionCount = new WeakSet(); ++getNetworkTransactionCount_fn = async function(address) { ++ return await query(__privateGet(this, _getEthQuery).call(this), "getTransactionCount", [address]); ++}; ++_getCurrentChainTransactions = new WeakSet(); ++getCurrentChainTransactions_fn = function() { ++ const currentChainId = __privateGet(this, _getChainId).call(this); ++ return __privateGet(this, _getTransactions).call(this).filter( ++ (tx) => tx.chainId === currentChainId ++ ); ++}; ++ ++export { ++ PendingTransactionTracker ++}; ++//# sourceMappingURL=chunk-7M2R5AHC.mjs.map +\ No newline at end of file +diff --git a/dist/chunk-7M2R5AHC.mjs.map b/dist/chunk-7M2R5AHC.mjs.map +new file mode 100644 +index 0000000000000000000000000000000000000000..bbde7a01b69392362c7c39c97ef83bd8ebefd4a1 +--- /dev/null ++++ b/dist/chunk-7M2R5AHC.mjs.map +@@ -0,0 +1 @@ ++{"version":3,"sources":["../src/helpers/PendingTransactionTracker.ts"],"sourcesContent":["import { query } from '@metamask/controller-utils';\nimport type EthQuery from '@metamask/eth-query';\nimport type {\n BlockTracker,\n NetworkClientId,\n} from '@metamask/network-controller';\nimport EventEmitter from 'events';\nimport { cloneDeep, merge } from 'lodash';\n\nimport { createModuleLogger, projectLogger } from '../logger';\nimport type { TransactionMeta, TransactionReceipt } from '../types';\nimport { TransactionStatus, TransactionType } from '../types';\n\n/**\n * We wait this many blocks before emitting a 'transaction-dropped' event\n * This is because we could be talking to a node that is out of sync\n */\nconst DROPPED_BLOCK_COUNT = 3;\n\nconst RECEIPT_STATUS_SUCCESS = '0x1';\nconst RECEIPT_STATUS_FAILURE = '0x0';\nconst MAX_RETRY_BLOCK_DISTANCE = 50;\n\nconst KNOWN_TRANSACTION_ERRORS = [\n 'replacement transaction underpriced',\n 'known transaction',\n 'gas price too low to replace',\n 'transaction with the same hash was already imported',\n 'gateway timeout',\n 'nonce too low',\n];\n\nconst log = createModuleLogger(projectLogger, 'pending-transactions');\n\ntype SuccessfulTransactionReceipt = TransactionReceipt & {\n blockNumber: string;\n blockHash: string;\n};\n\ntype Events = {\n 'transaction-confirmed': [txMeta: TransactionMeta];\n 'transaction-dropped': [txMeta: TransactionMeta];\n 'transaction-failed': [txMeta: TransactionMeta, error: Error];\n 'transaction-updated': [txMeta: TransactionMeta, note: string];\n};\n\n// This interface was created before this ESLint rule was added.\n// Convert to a `type` in a future major version.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\nexport interface PendingTransactionTrackerEventEmitter extends EventEmitter {\n // TODO: Either fix this lint violation or explain why it's necessary to ignore.\n // eslint-disable-next-line @typescript-eslint/naming-convention\n on(\n eventName: T,\n listener: (...args: Events[T]) => void,\n ): this;\n\n // TODO: Either fix this lint violation or explain why it's necessary to ignore.\n // eslint-disable-next-line @typescript-eslint/naming-convention\n emit(eventName: T, ...args: Events[T]): boolean;\n}\n\nexport class PendingTransactionTracker {\n hub: PendingTransactionTrackerEventEmitter;\n\n #blockTracker: BlockTracker;\n\n #droppedBlockCountByHash: Map;\n\n #getChainId: () => string;\n\n #getEthQuery: (networkClientId?: NetworkClientId) => EthQuery;\n\n #getTransactions: () => TransactionMeta[];\n\n #isResubmitEnabled: () => boolean;\n\n // TODO: Replace `any` with type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n #listener: any;\n\n #getGlobalLock: () => Promise<() => void>;\n\n #publishTransaction: (ethQuery: EthQuery, rawTx: string) => Promise;\n\n #running: boolean;\n\n #beforeCheckPendingTransaction: (transactionMeta: TransactionMeta) => boolean;\n\n #beforePublish: (transactionMeta: TransactionMeta) => boolean;\n\n constructor({\n blockTracker,\n getChainId,\n getEthQuery,\n getTransactions,\n isResubmitEnabled,\n getGlobalLock,\n publishTransaction,\n hooks,\n }: {\n blockTracker: BlockTracker;\n getChainId: () => string;\n getEthQuery: (networkClientId?: NetworkClientId) => EthQuery;\n getTransactions: () => TransactionMeta[];\n isResubmitEnabled?: () => boolean;\n getGlobalLock: () => Promise<() => void>;\n publishTransaction: (ethQuery: EthQuery, rawTx: string) => Promise;\n hooks?: {\n beforeCheckPendingTransaction?: (\n transactionMeta: TransactionMeta,\n ) => boolean;\n beforePublish?: (transactionMeta: TransactionMeta) => boolean;\n };\n }) {\n this.hub = new EventEmitter() as PendingTransactionTrackerEventEmitter;\n\n this.#blockTracker = blockTracker;\n this.#droppedBlockCountByHash = new Map();\n this.#getChainId = getChainId;\n this.#getEthQuery = getEthQuery;\n this.#getTransactions = getTransactions;\n this.#isResubmitEnabled = isResubmitEnabled ?? (() => true);\n this.#listener = this.#onLatestBlock.bind(this);\n this.#getGlobalLock = getGlobalLock;\n this.#publishTransaction = publishTransaction;\n this.#running = false;\n this.#beforePublish = hooks?.beforePublish ?? (() => true);\n this.#beforeCheckPendingTransaction =\n hooks?.beforeCheckPendingTransaction ?? (() => true);\n }\n\n startIfPendingTransactions = () => {\n const pendingTransactions = this.#getPendingTransactions();\n\n if (pendingTransactions.length) {\n this.#start();\n } else {\n this.stop();\n }\n };\n\n /**\n * Force checks the network if the given transaction is confirmed and updates it's status.\n *\n * @param txMeta - The transaction to check\n */\n async forceCheckTransaction(txMeta: TransactionMeta) {\n const releaseLock = await this.#getGlobalLock();\n\n try {\n await this.#checkTransaction(txMeta);\n } catch (error) {\n /* istanbul ignore next */\n log('Failed to check transaction', error);\n } finally {\n releaseLock();\n }\n }\n\n #start() {\n if (this.#running) {\n return;\n }\n\n this.#blockTracker.on('latest', this.#listener);\n this.#running = true;\n\n log('Started polling');\n }\n\n stop() {\n if (!this.#running) {\n return;\n }\n\n this.#blockTracker.removeListener('latest', this.#listener);\n this.#running = false;\n\n log('Stopped polling');\n }\n\n async #onLatestBlock(latestBlockNumber: string) {\n const releaseLock = await this.#getGlobalLock();\n\n try {\n await this.#checkTransactions();\n } catch (error) {\n /* istanbul ignore next */\n log('Failed to check transactions', error);\n } finally {\n releaseLock();\n }\n\n try {\n await this.#resubmitTransactions(latestBlockNumber);\n } catch (error) {\n /* istanbul ignore next */\n log('Failed to resubmit transactions', error);\n }\n }\n\n async #checkTransactions() {\n log('Checking transactions');\n\n const pendingTransactions = this.#getPendingTransactions();\n\n if (!pendingTransactions.length) {\n log('No pending transactions to check');\n return;\n }\n\n log('Found pending transactions to check', {\n count: pendingTransactions.length,\n ids: pendingTransactions.map((tx) => tx.id),\n });\n\n await Promise.all(\n pendingTransactions.map((tx) => this.#checkTransaction(tx)),\n );\n }\n\n async #resubmitTransactions(latestBlockNumber: string) {\n if (!this.#isResubmitEnabled() || !this.#running) {\n return;\n }\n\n log('Resubmitting transactions');\n\n const pendingTransactions = this.#getPendingTransactions();\n\n if (!pendingTransactions.length) {\n log('No pending transactions to resubmit');\n return;\n }\n\n log('Found pending transactions to resubmit', {\n count: pendingTransactions.length,\n ids: pendingTransactions.map((tx) => tx.id),\n });\n\n for (const txMeta of pendingTransactions) {\n try {\n await this.#resubmitTransaction(txMeta, latestBlockNumber);\n // TODO: Replace `any` with type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (error: any) {\n /* istanbul ignore next */\n const errorMessage =\n error.value?.message?.toLowerCase() ||\n error.message?.toLowerCase() ||\n String(error);\n\n if (this.#isKnownTransactionError(errorMessage)) {\n log('Ignoring known transaction error', errorMessage);\n continue;\n }\n\n this.#warnTransaction(\n txMeta,\n error.message,\n 'There was an error when resubmitting this transaction.',\n );\n }\n }\n }\n\n #isKnownTransactionError(errorMessage: string) {\n return KNOWN_TRANSACTION_ERRORS.some((knownError) =>\n errorMessage.includes(knownError),\n );\n }\n\n async #resubmitTransaction(\n txMeta: TransactionMeta,\n latestBlockNumber: string,\n ) {\n if (!this.#isResubmitDue(txMeta, latestBlockNumber)) {\n return;\n }\n\n const { rawTx } = txMeta;\n\n if (!this.#beforePublish(txMeta)) {\n return;\n }\n\n const ethQuery = this.#getEthQuery(txMeta.networkClientId);\n await this.#publishTransaction(ethQuery, rawTx as string);\n\n const retryCount = (txMeta.retryCount ?? 0) + 1;\n\n this.#updateTransaction(\n merge({}, txMeta, { retryCount }),\n 'PendingTransactionTracker:transaction-retry - Retry count increased',\n );\n }\n\n #isResubmitDue(txMeta: TransactionMeta, latestBlockNumber: string): boolean {\n const txMetaWithFirstRetryBlockNumber = cloneDeep(txMeta);\n\n if (!txMetaWithFirstRetryBlockNumber.firstRetryBlockNumber) {\n txMetaWithFirstRetryBlockNumber.firstRetryBlockNumber = latestBlockNumber;\n\n this.#updateTransaction(\n txMetaWithFirstRetryBlockNumber,\n 'PendingTransactionTracker:#isResubmitDue - First retry block number set',\n );\n }\n\n const { firstRetryBlockNumber } = txMetaWithFirstRetryBlockNumber;\n\n const blocksSinceFirstRetry =\n Number.parseInt(latestBlockNumber, 16) -\n Number.parseInt(firstRetryBlockNumber, 16);\n\n const retryCount = txMeta.retryCount || 0;\n\n // Exponential backoff to limit retries at publishing\n // Capped at ~15 minutes between retries\n const requiredBlocksSinceFirstRetry = Math.min(\n MAX_RETRY_BLOCK_DISTANCE,\n Math.pow(2, retryCount),\n );\n\n return blocksSinceFirstRetry >= requiredBlocksSinceFirstRetry;\n }\n\n async #checkTransaction(txMeta: TransactionMeta) {\n const { hash, id } = txMeta;\n\n if (!hash && this.#beforeCheckPendingTransaction(txMeta)) {\n const error = new Error(\n 'We had an error while submitting this transaction, please try again.',\n );\n\n error.name = 'NoTxHashError';\n\n this.#failTransaction(txMeta, error);\n\n return;\n }\n\n if (this.#isNonceTaken(txMeta)) {\n log('Nonce already taken', id);\n this.#dropTransaction(txMeta);\n return;\n }\n\n try {\n const receipt = await this.#getTransactionReceipt(hash);\n const isSuccess = receipt?.status === RECEIPT_STATUS_SUCCESS;\n const isFailure = receipt?.status === RECEIPT_STATUS_FAILURE;\n\n if (isFailure) {\n log('Transaction receipt has failed status');\n\n this.#failTransaction(\n txMeta,\n new Error('Transaction dropped or replaced'),\n );\n\n return;\n }\n\n const { blockNumber, blockHash } = receipt || {};\n\n if (isSuccess && blockNumber && blockHash) {\n await this.#onTransactionConfirmed(txMeta, {\n ...receipt,\n blockNumber,\n blockHash,\n });\n\n return;\n }\n // TODO: Replace `any` with type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (error: any) {\n log('Failed to check transaction', id, error);\n\n this.#warnTransaction(\n txMeta,\n error.message,\n 'There was a problem loading this transaction.',\n );\n\n return;\n }\n\n if (await this.#isTransactionDropped(txMeta)) {\n this.#dropTransaction(txMeta);\n }\n }\n\n async #onTransactionConfirmed(\n txMeta: TransactionMeta,\n receipt: SuccessfulTransactionReceipt,\n ) {\n const { id } = txMeta;\n const { blockHash } = receipt;\n\n log('Transaction confirmed', id);\n\n const { baseFeePerGas, timestamp: blockTimestamp } =\n await this.#getBlockByHash(blockHash, false);\n\n const updatedTxMeta = cloneDeep(txMeta);\n updatedTxMeta.baseFeePerGas = baseFeePerGas;\n updatedTxMeta.blockTimestamp = blockTimestamp;\n updatedTxMeta.status = TransactionStatus.confirmed;\n updatedTxMeta.txParams = {\n ...updatedTxMeta.txParams,\n gasUsed: receipt.gasUsed,\n };\n updatedTxMeta.txReceipt = receipt;\n updatedTxMeta.verifiedOnBlockchain = true;\n\n this.#updateTransaction(\n updatedTxMeta,\n 'PendingTransactionTracker:#onTransactionConfirmed - Transaction confirmed',\n );\n\n this.hub.emit('transaction-confirmed', updatedTxMeta);\n }\n\n async #isTransactionDropped(txMeta: TransactionMeta) {\n const {\n hash,\n id,\n txParams: { nonce, from },\n } = txMeta;\n\n /* istanbul ignore next */\n if (!nonce || !hash) {\n return false;\n }\n\n const networkNextNonceHex = await this.#getNetworkTransactionCount(from);\n const networkNextNonceNumber = parseInt(networkNextNonceHex, 16);\n const nonceNumber = parseInt(nonce, 16);\n\n if (nonceNumber >= networkNextNonceNumber) {\n return false;\n }\n\n let droppedBlockCount = this.#droppedBlockCountByHash.get(hash);\n\n if (droppedBlockCount === undefined) {\n droppedBlockCount = 0;\n this.#droppedBlockCountByHash.set(hash, droppedBlockCount);\n }\n\n if (droppedBlockCount < DROPPED_BLOCK_COUNT) {\n log('Incrementing dropped block count', { id, droppedBlockCount });\n this.#droppedBlockCountByHash.set(hash, droppedBlockCount + 1);\n return false;\n }\n\n log('Hit dropped block count', id);\n\n this.#droppedBlockCountByHash.delete(hash);\n return true;\n }\n\n #isNonceTaken(txMeta: TransactionMeta): boolean {\n const { id, txParams } = txMeta;\n\n return this.#getCurrentChainTransactions().some(\n (tx) =>\n tx.id !== id &&\n tx.txParams.from === txParams.from &&\n tx.status === TransactionStatus.confirmed &&\n tx.txParams.nonce === txParams.nonce &&\n tx.type !== TransactionType.incoming,\n );\n }\n\n #getPendingTransactions(): TransactionMeta[] {\n return this.#getCurrentChainTransactions().filter(\n (tx) =>\n tx.status === TransactionStatus.submitted &&\n !tx.verifiedOnBlockchain &&\n !tx.isUserOperation,\n );\n }\n\n #warnTransaction(txMeta: TransactionMeta, error: string, message: string) {\n this.#updateTransaction(\n {\n ...txMeta,\n warning: { error, message },\n },\n 'PendingTransactionTracker:#warnTransaction - Warning added',\n );\n }\n\n #failTransaction(txMeta: TransactionMeta, error: Error) {\n log('Transaction failed', txMeta.id, error);\n this.hub.emit('transaction-failed', txMeta, error);\n }\n\n #dropTransaction(txMeta: TransactionMeta) {\n log('Transaction dropped', txMeta.id);\n this.hub.emit('transaction-dropped', txMeta);\n }\n\n #updateTransaction(txMeta: TransactionMeta, note: string) {\n this.hub.emit('transaction-updated', txMeta, note);\n }\n\n async #getTransactionReceipt(\n txHash?: string,\n ): Promise {\n return await query(this.#getEthQuery(), 'getTransactionReceipt', [txHash]);\n }\n\n async #getBlockByHash(\n blockHash: string,\n includeTransactionDetails: boolean,\n // TODO: Replace `any` with type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ): Promise {\n return await query(this.#getEthQuery(), 'getBlockByHash', [\n blockHash,\n includeTransactionDetails,\n ]);\n }\n\n async #getNetworkTransactionCount(address: string): Promise {\n return await query(this.#getEthQuery(), 'getTransactionCount', [address]);\n }\n\n #getCurrentChainTransactions(): TransactionMeta[] {\n const currentChainId = this.#getChainId();\n\n return this.#getTransactions().filter(\n (tx) => tx.chainId === currentChainId,\n );\n }\n}\n"],"mappings":"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","names":[]} +\ No newline at end of file +diff --git a/dist/chunk-IVR4NMOF.js b/dist/chunk-IVR4NMOF.js +new file mode 100644 +index 0000000000000000000000000000000000000000..2266127ef5aead422d1ccf8f1a53734f81499d96 +--- /dev/null ++++ b/dist/chunk-IVR4NMOF.js +@@ -0,0 +1,2556 @@ ++"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } ++ ++ ++var _chunkPRUNMTRDjs = require('./chunk-PRUNMTRD.js'); ++ ++ ++var _chunk74W7X6BEjs = require('./chunk-74W7X6BE.js'); ++ ++ ++var _chunkSD6CWFDFjs = require('./chunk-SD6CWFDF.js'); ++ ++ ++ ++var _chunkRXIUMVA5js = require('./chunk-RXIUMVA5.js'); ++ ++ ++var _chunk6DODV6OVjs = require('./chunk-6DODV6OV.js'); ++ ++ ++var _chunk7LXE4KHVjs = require('./chunk-7LXE4KHV.js'); ++ ++ ++ ++ ++var _chunkV72C4MCRjs = require('./chunk-V72C4MCR.js'); ++ ++ ++ ++var _chunkQP75SWIQjs = require('./chunk-QP75SWIQ.js'); ++ ++ ++var _chunkNYKRCWBGjs = require('./chunk-NYKRCWBG.js'); ++ ++ ++var _chunkWR5F34OWjs = require('./chunk-WR5F34OW.js'); ++ ++ ++var _chunkTJMQEH57js = require('./chunk-TJMQEH57.js'); ++ ++ ++var _chunk2EU6346Vjs = require('./chunk-2EU6346V.js'); ++ ++ ++ ++var _chunk2XKEAKQGjs = require('./chunk-2XKEAKQG.js'); ++ ++ ++var _chunkRHDPOIS4js = require('./chunk-RHDPOIS4.js'); ++ ++ ++var _chunk6OLJWLKKjs = require('./chunk-6OLJWLKK.js'); ++ ++ ++var _chunk7NMV2NPMjs = require('./chunk-7NMV2NPM.js'); ++ ++ ++var _chunkARZHJFVGjs = require('./chunk-ARZHJFVG.js'); ++ ++ ++var _chunkQTKXIDGEjs = require('./chunk-QTKXIDGE.js'); ++ ++ ++var _chunkC3WC4OJ3js = require('./chunk-C3WC4OJ3.js'); ++ ++ ++ ++var _chunkQH2H4W3Njs = require('./chunk-QH2H4W3N.js'); ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++var _chunkOZ6UB42Cjs = require('./chunk-OZ6UB42C.js'); ++ ++ ++var _chunk76FONEDAjs = require('./chunk-76FONEDA.js'); ++ ++ ++var _chunkS6VGOPUYjs = require('./chunk-S6VGOPUY.js'); ++ ++ ++ ++ ++ ++var _chunkZ4BLTVTBjs = require('./chunk-Z4BLTVTB.js'); ++ ++// src/TransactionController.ts ++var _common = require('@ethereumjs/common'); ++var _tx = require('@ethereumjs/tx'); ++var _util = require('@ethereumjs/util'); ++var _basecontroller = require('@metamask/base-controller'); ++ ++ ++ ++ ++ ++ ++var _controllerutils = require('@metamask/controller-utils'); ++var _ethquery = require('@metamask/eth-query'); var _ethquery2 = _interopRequireDefault(_ethquery); ++var _networkcontroller = require('@metamask/network-controller'); ++var _noncetracker = require('@metamask/nonce-tracker'); ++var _rpcerrors = require('@metamask/rpc-errors'); ++var _utils = require('@metamask/utils'); ++var _asyncmutex = require('async-mutex'); ++var _ethmethodregistry = require('eth-method-registry'); ++var _events = require('events'); ++var _lodash = require('lodash'); ++var _uuid = require('uuid'); ++var metadata = { ++ transactions: { ++ persist: true, ++ anonymous: false ++ }, ++ methodData: { ++ persist: true, ++ anonymous: false ++ }, ++ lastFetchedBlockNumbers: { ++ persist: true, ++ anonymous: false ++ } ++}; ++var HARDFORK = _common.Hardfork.London; ++var CANCEL_RATE = 1.1; ++var SPEED_UP_RATE = 1.1; ++var controllerName = "TransactionController"; ++var ApprovalState = /* @__PURE__ */ ((ApprovalState2) => { ++ ApprovalState2["Approved"] = "approved"; ++ ApprovalState2["NotApproved"] = "not-approved"; ++ ApprovalState2["SkippedViaBeforePublishHook"] = "skipped-via-before-publish-hook"; ++ return ApprovalState2; ++})(ApprovalState || {}); ++function getDefaultTransactionControllerState() { ++ return { ++ methodData: {}, ++ transactions: [], ++ lastFetchedBlockNumbers: {} ++ }; ++} ++var _internalEvents, _incomingTransactionOptions, _pendingTransactionOptions, _transactionHistoryLimit, _isSimulationEnabled, _testGasFeeFlows, _multichainTrackingHelper, _createNonceTracker, createNonceTracker_fn, _createIncomingTransactionHelper, createIncomingTransactionHelper_fn, _createPendingTransactionTracker, createPendingTransactionTracker_fn, _checkForPendingTransactionAndStartPolling, _stopAllTracking, stopAllTracking_fn, _removeIncomingTransactionHelperListeners, removeIncomingTransactionHelperListeners_fn, _addIncomingTransactionHelperListeners, addIncomingTransactionHelperListeners_fn, _removePendingTransactionTrackerListeners, removePendingTransactionTrackerListeners_fn, _addPendingTransactionTrackerListeners, addPendingTransactionTrackerListeners_fn, _getNonceTrackerPendingTransactions, getNonceTrackerPendingTransactions_fn, _getGasFeeFlows, getGasFeeFlows_fn, _getLayer1GasFeeFlows, getLayer1GasFeeFlows_fn, _updateTransactionInternal, updateTransactionInternal_fn, _checkIfTransactionParamsUpdated, checkIfTransactionParamsUpdated_fn, _onTransactionParamsUpdated, onTransactionParamsUpdated_fn, _updateSimulationData, updateSimulationData_fn, _onGasFeePollerTransactionUpdate, onGasFeePollerTransactionUpdate_fn, _getNetworkClientId, getNetworkClientId_fn, _getGlobalNetworkClientId, getGlobalNetworkClientId_fn, _getGlobalChainId, getGlobalChainId_fn, _isCustomNetwork, isCustomNetwork_fn, _getSelectedAccount, getSelectedAccount_fn; ++var TransactionController = class extends _basecontroller.BaseController { ++ /** ++ * Constructs a TransactionController. ++ * ++ * @param options - The controller options. ++ * @param options.blockTracker - The block tracker used to poll for new blocks data. ++ * @param options.disableHistory - Whether to disable storing history in transaction metadata. ++ * @param options.disableSendFlowHistory - Explicitly disable transaction metadata history. ++ * @param options.disableSwaps - Whether to disable additional processing on swaps transactions. ++ * @param options.getCurrentAccountEIP1559Compatibility - Whether or not the account supports EIP-1559. ++ * @param options.getCurrentNetworkEIP1559Compatibility - Whether or not the network supports EIP-1559. ++ * @param options.getExternalPendingTransactions - Callback to retrieve pending transactions from external sources. ++ * @param options.getGasFeeEstimates - Callback to retrieve gas fee estimates. ++ * @param options.getNetworkClientRegistry - Gets the network client registry. ++ * @param options.getNetworkState - Gets the state of the network controller. ++ * @param options.getPermittedAccounts - Get accounts that a given origin has permissions for. ++ * @param options.getSavedGasFees - Gets the saved gas fee config. ++ * @param options.incomingTransactions - Configuration options for incoming transaction support. ++ * @param options.isMultichainEnabled - Enable multichain support. ++ * @param options.isSimulationEnabled - Whether new transactions will be automatically simulated. ++ * @param options.messenger - The controller messenger. ++ * @param options.onNetworkStateChange - Allows subscribing to network controller state changes. ++ * @param options.pendingTransactions - Configuration options for pending transaction support. ++ * @param options.provider - The provider used to create the underlying EthQuery instance. ++ * @param options.securityProviderRequest - A function for verifying a transaction, whether it is malicious or not. ++ * @param options.sign - Function used to sign transactions. ++ * @param options.state - Initial state to set on this controller. ++ * @param options.testGasFeeFlows - Whether to use the test gas fee flow. ++ * @param options.transactionHistoryLimit - Transaction history limit. ++ * @param options.hooks - The controller hooks. ++ */ ++ constructor({ ++ blockTracker, ++ disableHistory, ++ disableSendFlowHistory, ++ disableSwaps, ++ getCurrentAccountEIP1559Compatibility, ++ getCurrentNetworkEIP1559Compatibility, ++ getExternalPendingTransactions, ++ getGasFeeEstimates, ++ getNetworkClientRegistry, ++ getNetworkState, ++ getPermittedAccounts, ++ getSavedGasFees, ++ incomingTransactions = {}, ++ isMultichainEnabled = false, ++ isSimulationEnabled, ++ messenger, ++ onNetworkStateChange, ++ pendingTransactions = {}, ++ provider, ++ securityProviderRequest, ++ sign, ++ state, ++ testGasFeeFlows, ++ transactionHistoryLimit = 40, ++ hooks ++ }) { ++ super({ ++ name: controllerName, ++ metadata, ++ messenger, ++ state: { ++ ...getDefaultTransactionControllerState(), ++ ...state ++ } ++ }); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _createNonceTracker); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _createIncomingTransactionHelper); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _createPendingTransactionTracker); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _stopAllTracking); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _removeIncomingTransactionHelperListeners); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _addIncomingTransactionHelperListeners); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _removePendingTransactionTrackerListeners); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _addPendingTransactionTrackerListeners); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _getNonceTrackerPendingTransactions); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _getGasFeeFlows); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _getLayer1GasFeeFlows); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _updateTransactionInternal); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _checkIfTransactionParamsUpdated); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _onTransactionParamsUpdated); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _updateSimulationData); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _onGasFeePollerTransactionUpdate); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _getNetworkClientId); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _getGlobalNetworkClientId); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _getGlobalChainId); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _isCustomNetwork); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _getSelectedAccount); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _internalEvents, new (0, _events.EventEmitter)()); ++ this.approvingTransactionIds = /* @__PURE__ */ new Set(); ++ this.mutex = new (0, _asyncmutex.Mutex)(); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _incomingTransactionOptions, void 0); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _pendingTransactionOptions, void 0); ++ this.signAbortCallbacks = /* @__PURE__ */ new Map(); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _transactionHistoryLimit, void 0); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _isSimulationEnabled, void 0); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _testGasFeeFlows, void 0); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _multichainTrackingHelper, void 0); ++ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _checkForPendingTransactionAndStartPolling, () => { ++ this.pendingTransactionTracker.startIfPendingTransactions(); ++ _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _multichainTrackingHelper).checkForPendingTransactionAndStartPolling(); ++ }); ++ this.messagingSystem = messenger; ++ this.getNetworkState = getNetworkState; ++ this.isSendFlowHistoryDisabled = disableSendFlowHistory ?? false; ++ this.isHistoryDisabled = disableHistory ?? false; ++ this.isSwapsDisabled = disableSwaps ?? false; ++ _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _isSimulationEnabled, isSimulationEnabled ?? (() => true)); ++ this.registry = new (0, _ethmethodregistry.MethodRegistry)({ provider }); ++ this.getSavedGasFees = getSavedGasFees ?? ((_chainId) => void 0); ++ this.getCurrentAccountEIP1559Compatibility = getCurrentAccountEIP1559Compatibility ?? (() => Promise.resolve(true)); ++ this.getCurrentNetworkEIP1559Compatibility = getCurrentNetworkEIP1559Compatibility; ++ this.getGasFeeEstimates = getGasFeeEstimates || (() => Promise.resolve({})); ++ this.getPermittedAccounts = getPermittedAccounts; ++ this.getExternalPendingTransactions = getExternalPendingTransactions ?? (() => []); ++ this.securityProviderRequest = securityProviderRequest; ++ _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _incomingTransactionOptions, incomingTransactions); ++ _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _pendingTransactionOptions, pendingTransactions); ++ _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _transactionHistoryLimit, transactionHistoryLimit); ++ this.sign = sign; ++ _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _testGasFeeFlows, testGasFeeFlows === true); ++ this.afterSign = hooks?.afterSign ?? (() => true); ++ this.beforeCheckPendingTransaction = hooks?.beforeCheckPendingTransaction ?? /* istanbul ignore next */ ++ (() => true); ++ this.beforePublish = hooks?.beforePublish ?? (() => true); ++ this.getAdditionalSignArguments = hooks?.getAdditionalSignArguments ?? (() => []); ++ this.publish = hooks?.publish ?? (() => Promise.resolve({ transactionHash: void 0 })); ++ this.nonceTracker = _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _createNonceTracker, createNonceTracker_fn).call(this, { ++ provider, ++ blockTracker ++ }); ++ const findNetworkClientIdByChainId = (chainId) => { ++ return this.messagingSystem.call( ++ `NetworkController:findNetworkClientIdByChainId`, ++ chainId ++ ); ++ }; ++ _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _multichainTrackingHelper, new (0, _chunk6OLJWLKKjs.MultichainTrackingHelper)({ ++ isMultichainEnabled, ++ provider, ++ nonceTracker: this.nonceTracker, ++ incomingTransactionOptions: incomingTransactions, ++ findNetworkClientIdByChainId, ++ getNetworkClientById: (networkClientId) => { ++ return this.messagingSystem.call( ++ `NetworkController:getNetworkClientById`, ++ networkClientId ++ ); ++ }, ++ getNetworkClientRegistry, ++ removeIncomingTransactionHelperListeners: _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _removeIncomingTransactionHelperListeners, removeIncomingTransactionHelperListeners_fn).bind(this), ++ removePendingTransactionTrackerListeners: _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _removePendingTransactionTrackerListeners, removePendingTransactionTrackerListeners_fn).bind(this), ++ createNonceTracker: _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _createNonceTracker, createNonceTracker_fn).bind(this), ++ createIncomingTransactionHelper: _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _createIncomingTransactionHelper, createIncomingTransactionHelper_fn).bind(this), ++ createPendingTransactionTracker: _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _createPendingTransactionTracker, createPendingTransactionTracker_fn).bind(this), ++ onNetworkStateChange: (listener) => { ++ this.messagingSystem.subscribe( ++ "NetworkController:stateChange", ++ listener ++ ); ++ } ++ })); ++ _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _multichainTrackingHelper).initialize(); ++ const etherscanRemoteTransactionSource = new (0, _chunk7NMV2NPMjs.EtherscanRemoteTransactionSource)({ ++ includeTokenTransfers: incomingTransactions.includeTokenTransfers ++ }); ++ this.incomingTransactionHelper = _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _createIncomingTransactionHelper, createIncomingTransactionHelper_fn).call(this, { ++ blockTracker, ++ etherscanRemoteTransactionSource ++ }); ++ this.pendingTransactionTracker = _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _createPendingTransactionTracker, createPendingTransactionTracker_fn).call(this, { ++ provider, ++ blockTracker ++ }); ++ this.gasFeeFlows = _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getGasFeeFlows, getGasFeeFlows_fn).call(this); ++ this.layer1GasFeeFlows = _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getLayer1GasFeeFlows, getLayer1GasFeeFlows_fn).call(this); ++ const gasFeePoller = new (0, _chunk2EU6346Vjs.GasFeePoller)({ ++ findNetworkClientIdByChainId, ++ gasFeeFlows: this.gasFeeFlows, ++ getGasFeeControllerEstimates: this.getGasFeeEstimates, ++ getProvider: (chainId, networkClientId) => _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _multichainTrackingHelper).getProvider({ ++ networkClientId, ++ chainId ++ }), ++ getTransactions: () => this.state.transactions, ++ layer1GasFeeFlows: this.layer1GasFeeFlows, ++ onStateChange: (listener) => { ++ this.messagingSystem.subscribe( ++ "TransactionController:stateChange", ++ listener ++ ); ++ } ++ }); ++ gasFeePoller.hub.on( ++ "transaction-updated", ++ _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _onGasFeePollerTransactionUpdate, onGasFeePollerTransactionUpdate_fn).bind(this) ++ ); ++ this.messagingSystem.subscribe( ++ "TransactionController:stateChange", ++ _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _checkForPendingTransactionAndStartPolling) ++ ); ++ onNetworkStateChange(() => { ++ _chunkS6VGOPUYjs.projectLogger.call(void 0, "Detected network change", this.getChainId()); ++ this.pendingTransactionTracker.startIfPendingTransactions(); ++ this.onBootCleanup(); ++ }); ++ this.onBootCleanup(); ++ _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _checkForPendingTransactionAndStartPolling).call(this); ++ } ++ failTransaction(transactionMeta, error, actionId) { ++ let newTransactionMeta; ++ try { ++ newTransactionMeta = _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _updateTransactionInternal, updateTransactionInternal_fn).call(this, { ++ transactionId: transactionMeta.id, ++ note: "TransactionController#failTransaction - Add error message and set status to failed", ++ skipValidation: true ++ }, (draftTransactionMeta) => { ++ draftTransactionMeta.status = "failed" /* failed */; ++ draftTransactionMeta.error = _chunkOZ6UB42Cjs.normalizeTxError.call(void 0, error); ++ }); ++ } catch (err) { ++ _chunkS6VGOPUYjs.projectLogger.call(void 0, "Failed to mark transaction as failed", err); ++ newTransactionMeta = { ++ ...transactionMeta, ++ status: "failed" /* failed */, ++ error: _chunkOZ6UB42Cjs.normalizeTxError.call(void 0, error) ++ }; ++ } ++ this.messagingSystem.publish(`${controllerName}:transactionFailed`, { ++ actionId, ++ error: error.message, ++ transactionMeta: newTransactionMeta ++ }); ++ this.onTransactionStatusChange(newTransactionMeta); ++ this.messagingSystem.publish( ++ `${controllerName}:transactionFinished`, ++ newTransactionMeta ++ ); ++ _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _internalEvents).emit( ++ `${transactionMeta.id}:finished`, ++ newTransactionMeta ++ ); ++ } ++ async registryLookup(fourBytePrefix) { ++ const registryMethod = await this.registry.lookup(fourBytePrefix); ++ if (!registryMethod) { ++ return { ++ registryMethod: "", ++ parsedRegistryMethod: { name: void 0, args: void 0 } ++ }; ++ } ++ const parsedRegistryMethod = this.registry.parse(registryMethod); ++ return { registryMethod, parsedRegistryMethod }; ++ } ++ /** ++ * Stops polling and removes listeners to prepare the controller for garbage collection. ++ */ ++ destroy() { ++ _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _stopAllTracking, stopAllTracking_fn).call(this); ++ } ++ /** ++ * Handle new method data request. ++ * ++ * @param fourBytePrefix - The method prefix. ++ * @returns The method data object corresponding to the given signature prefix. ++ */ ++ async handleMethodData(fourBytePrefix) { ++ const releaseLock = await this.mutex.acquire(); ++ try { ++ const { methodData } = this.state; ++ const knownMethod = Object.keys(methodData).find( ++ (knownFourBytePrefix) => fourBytePrefix === knownFourBytePrefix ++ ); ++ if (knownMethod) { ++ return methodData[fourBytePrefix]; ++ } ++ const registry = await this.registryLookup(fourBytePrefix); ++ this.update((state) => { ++ state.methodData[fourBytePrefix] = registry; ++ }); ++ return registry; ++ } finally { ++ releaseLock(); ++ } ++ } ++ /** ++ * Add a new unapproved transaction to state. Parameters will be validated, a ++ * unique transaction id will be generated, and gas and gasPrice will be calculated ++ * if not provided. If A `:unapproved` hub event will be emitted once added. ++ * ++ * @param txParams - Standard parameters for an Ethereum transaction. ++ * @param opts - Additional options to control how the transaction is added. ++ * @param opts.actionId - Unique ID to prevent duplicate requests. ++ * @param opts.deviceConfirmedOn - An enum to indicate what device confirmed the transaction. ++ * @param opts.method - RPC method that requested the transaction. ++ * @param opts.origin - The origin of the transaction request, such as a dApp hostname. ++ * @param opts.requireApproval - Whether the transaction requires approval by the user, defaults to true unless explicitly disabled. ++ * @param opts.securityAlertResponse - Response from security validator. ++ * @param opts.sendFlowHistory - The sendFlowHistory entries to add. ++ * @param opts.type - Type of transaction to add, such as 'cancel' or 'swap'. ++ * @param opts.swaps - Options for swaps transactions. ++ * @param opts.swaps.hasApproveTx - Whether the transaction has an approval transaction. ++ * @param opts.swaps.meta - Metadata for swap transaction. ++ * @param opts.networkClientId - The id of the network client for this transaction. ++ * @returns Object containing a promise resolving to the transaction hash if approved. ++ */ ++ async addTransaction(txParams, { ++ actionId, ++ deviceConfirmedOn, ++ method, ++ origin, ++ requireApproval, ++ securityAlertResponse, ++ sendFlowHistory, ++ swaps = {}, ++ type, ++ networkClientId: requestNetworkClientId ++ } = {}) { ++ _chunkS6VGOPUYjs.projectLogger.call(void 0, "Adding transaction", txParams); ++ txParams = _chunkOZ6UB42Cjs.normalizeTransactionParams.call(void 0, txParams); ++ if (requestNetworkClientId && !_chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _multichainTrackingHelper).has(requestNetworkClientId)) { ++ throw new Error( ++ "The networkClientId for this transaction could not be found" ++ ); ++ } ++ const networkClientId = requestNetworkClientId ?? _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getGlobalNetworkClientId, getGlobalNetworkClientId_fn).call(this); ++ const isEIP1559Compatible = await this.getEIP1559Compatibility( ++ networkClientId ++ ); ++ _chunkRXIUMVA5js.validateTxParams.call(void 0, txParams, isEIP1559Compatible); ++ if (origin) { ++ await _chunkRXIUMVA5js.validateTransactionOrigin.call(void 0, ++ await this.getPermittedAccounts(origin), ++ _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getSelectedAccount, getSelectedAccount_fn).call(this).address, ++ txParams.from, ++ origin ++ ); ++ } ++ const dappSuggestedGasFees = this.generateDappSuggestedGasFees( ++ txParams, ++ origin ++ ); ++ const chainId = this.getChainId(networkClientId); ++ const ethQuery = _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _multichainTrackingHelper).getEthQuery({ ++ networkClientId, ++ chainId ++ }); ++ const transactionType = type ?? (await _chunkSD6CWFDFjs.determineTransactionType.call(void 0, txParams, ethQuery)).type; ++ const existingTransactionMeta = this.getTransactionWithActionId(actionId); ++ let addedTransactionMeta = existingTransactionMeta ? _lodash.cloneDeep.call(void 0, existingTransactionMeta) : { ++ // Add actionId to txMeta to check if same actionId is seen again ++ actionId, ++ chainId, ++ dappSuggestedGasFees, ++ deviceConfirmedOn, ++ id: _uuid.v1.call(void 0, ), ++ origin, ++ securityAlertResponse, ++ status: "unapproved" /* unapproved */, ++ time: Date.now(), ++ txParams, ++ userEditedGasLimit: false, ++ verifiedOnBlockchain: false, ++ type: transactionType, ++ networkClientId ++ }; ++ await this.updateGasProperties(addedTransactionMeta); ++ if (!existingTransactionMeta) { ++ if (method && this.securityProviderRequest) { ++ const securityProviderResponse = await this.securityProviderRequest( ++ addedTransactionMeta, ++ method ++ ); ++ addedTransactionMeta.securityProviderResponse = securityProviderResponse; ++ } ++ if (!this.isSendFlowHistoryDisabled) { ++ addedTransactionMeta.sendFlowHistory = sendFlowHistory ?? []; ++ } ++ if (!this.isHistoryDisabled) { ++ addedTransactionMeta = _chunkQP75SWIQjs.addInitialHistorySnapshot.call(void 0, addedTransactionMeta); ++ } ++ addedTransactionMeta = _chunkQH2H4W3Njs.updateSwapsTransaction.call(void 0, ++ addedTransactionMeta, ++ transactionType, ++ swaps, ++ { ++ isSwapsDisabled: this.isSwapsDisabled, ++ cancelTransaction: this.cancelTransaction.bind(this), ++ messenger: this.messagingSystem ++ } ++ ); ++ this.addMetadata(addedTransactionMeta); ++ if (requireApproval !== false) { ++ _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _updateSimulationData, updateSimulationData_fn).call(this, addedTransactionMeta); ++ } else { ++ _chunkS6VGOPUYjs.projectLogger.call(void 0, "Skipping simulation as approval not required"); ++ } ++ this.messagingSystem.publish( ++ `${controllerName}:unapprovedTransactionAdded`, ++ addedTransactionMeta ++ ); ++ } ++ return { ++ result: this.processApproval(addedTransactionMeta, { ++ isExisting: Boolean(existingTransactionMeta), ++ requireApproval, ++ actionId ++ }), ++ transactionMeta: addedTransactionMeta ++ }; ++ } ++ startIncomingTransactionPolling(networkClientIds = []) { ++ if (networkClientIds.length === 0) { ++ this.incomingTransactionHelper.start(); ++ return; ++ } ++ _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _multichainTrackingHelper).startIncomingTransactionPolling( ++ networkClientIds ++ ); ++ } ++ stopIncomingTransactionPolling(networkClientIds = []) { ++ if (networkClientIds.length === 0) { ++ this.incomingTransactionHelper.stop(); ++ return; ++ } ++ _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _multichainTrackingHelper).stopIncomingTransactionPolling( ++ networkClientIds ++ ); ++ } ++ stopAllIncomingTransactionPolling() { ++ this.incomingTransactionHelper.stop(); ++ _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _multichainTrackingHelper).stopAllIncomingTransactionPolling(); ++ } ++ async updateIncomingTransactions(networkClientIds = []) { ++ if (networkClientIds.length === 0) { ++ await this.incomingTransactionHelper.update(); ++ return; ++ } ++ await _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _multichainTrackingHelper).updateIncomingTransactions( ++ networkClientIds ++ ); ++ } ++ /** ++ * Attempts to cancel a transaction based on its ID by setting its status to "rejected" ++ * and emitting a `:finished` hub event. ++ * ++ * @param transactionId - The ID of the transaction to cancel. ++ * @param gasValues - The gas values to use for the cancellation transaction. ++ * @param options - The options for the cancellation transaction. ++ * @param options.actionId - Unique ID to prevent duplicate requests. ++ * @param options.estimatedBaseFee - The estimated base fee of the transaction. ++ */ ++ async stopTransaction(transactionId, gasValues, { ++ estimatedBaseFee, ++ actionId ++ } = {}) { ++ if (this.getTransactionWithActionId(actionId)) { ++ return; ++ } ++ if (gasValues) { ++ gasValues = _chunkOZ6UB42Cjs.normalizeGasFeeValues.call(void 0, gasValues); ++ _chunkOZ6UB42Cjs.validateGasValues.call(void 0, gasValues); ++ } ++ _chunkS6VGOPUYjs.projectLogger.call(void 0, "Creating cancel transaction", transactionId, gasValues); ++ const transactionMeta = this.getTransaction(transactionId); ++ if (!transactionMeta) { ++ return; ++ } ++ if (!this.sign) { ++ throw new Error("No sign method defined."); ++ } ++ const minGasPrice = _chunkOZ6UB42Cjs.getIncreasedPriceFromExisting.call(void 0, ++ transactionMeta.txParams.gasPrice, ++ CANCEL_RATE ++ ); ++ const gasPriceFromValues = _chunkOZ6UB42Cjs.isGasPriceValue.call(void 0, gasValues) && gasValues.gasPrice; ++ const newGasPrice = gasPriceFromValues && _chunkOZ6UB42Cjs.validateMinimumIncrease.call(void 0, gasPriceFromValues, minGasPrice) || minGasPrice; ++ const existingMaxFeePerGas = transactionMeta.txParams?.maxFeePerGas; ++ const minMaxFeePerGas = _chunkOZ6UB42Cjs.getIncreasedPriceFromExisting.call(void 0, ++ existingMaxFeePerGas, ++ CANCEL_RATE ++ ); ++ const maxFeePerGasValues = _chunkOZ6UB42Cjs.isFeeMarketEIP1559Values.call(void 0, gasValues) && gasValues.maxFeePerGas; ++ const newMaxFeePerGas = maxFeePerGasValues && _chunkOZ6UB42Cjs.validateMinimumIncrease.call(void 0, maxFeePerGasValues, minMaxFeePerGas) || existingMaxFeePerGas && minMaxFeePerGas; ++ const existingMaxPriorityFeePerGas = transactionMeta.txParams?.maxPriorityFeePerGas; ++ const minMaxPriorityFeePerGas = _chunkOZ6UB42Cjs.getIncreasedPriceFromExisting.call(void 0, ++ existingMaxPriorityFeePerGas, ++ CANCEL_RATE ++ ); ++ const maxPriorityFeePerGasValues = _chunkOZ6UB42Cjs.isFeeMarketEIP1559Values.call(void 0, gasValues) && gasValues.maxPriorityFeePerGas; ++ const newMaxPriorityFeePerGas = maxPriorityFeePerGasValues && _chunkOZ6UB42Cjs.validateMinimumIncrease.call(void 0, ++ maxPriorityFeePerGasValues, ++ minMaxPriorityFeePerGas ++ ) || existingMaxPriorityFeePerGas && minMaxPriorityFeePerGas; ++ const newTxParams = newMaxFeePerGas && newMaxPriorityFeePerGas ? { ++ from: transactionMeta.txParams.from, ++ gasLimit: transactionMeta.txParams.gas, ++ maxFeePerGas: newMaxFeePerGas, ++ maxPriorityFeePerGas: newMaxPriorityFeePerGas, ++ type: "0x2" /* feeMarket */, ++ nonce: transactionMeta.txParams.nonce, ++ to: transactionMeta.txParams.from, ++ value: "0x0" ++ } : { ++ from: transactionMeta.txParams.from, ++ gasLimit: transactionMeta.txParams.gas, ++ gasPrice: newGasPrice, ++ nonce: transactionMeta.txParams.nonce, ++ to: transactionMeta.txParams.from, ++ value: "0x0" ++ }; ++ const unsignedEthTx = this.prepareUnsignedEthTx( ++ transactionMeta.chainId, ++ newTxParams ++ ); ++ const signedTx = await this.sign( ++ unsignedEthTx, ++ transactionMeta.txParams.from ++ ); ++ const rawTx = _util.bufferToHex.call(void 0, signedTx.serialize()); ++ const newFee = newTxParams.maxFeePerGas ?? newTxParams.gasPrice; ++ const oldFee = newTxParams.maxFeePerGas ? transactionMeta.txParams.maxFeePerGas : transactionMeta.txParams.gasPrice; ++ _chunkS6VGOPUYjs.projectLogger.call(void 0, "Submitting cancel transaction", { ++ oldFee, ++ newFee, ++ txParams: newTxParams ++ }); ++ const ethQuery = _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _multichainTrackingHelper).getEthQuery({ ++ networkClientId: transactionMeta.networkClientId, ++ chainId: transactionMeta.chainId ++ }); ++ const hash = await this.publishTransactionForRetry( ++ ethQuery, ++ rawTx, ++ transactionMeta ++ ); ++ const cancelTransactionMeta = { ++ actionId, ++ chainId: transactionMeta.chainId, ++ networkClientId: transactionMeta.networkClientId, ++ estimatedBaseFee, ++ hash, ++ id: _uuid.v1.call(void 0, ), ++ originalGasEstimate: transactionMeta.txParams.gas, ++ rawTx, ++ status: "submitted" /* submitted */, ++ time: Date.now(), ++ type: "cancel" /* cancel */, ++ txParams: newTxParams ++ }; ++ this.addMetadata(cancelTransactionMeta); ++ this.messagingSystem.publish(`${controllerName}:transactionApproved`, { ++ transactionMeta: cancelTransactionMeta, ++ actionId ++ }); ++ this.messagingSystem.publish(`${controllerName}:transactionSubmitted`, { ++ transactionMeta: cancelTransactionMeta, ++ actionId ++ }); ++ this.messagingSystem.publish( ++ `${controllerName}:transactionFinished`, ++ cancelTransactionMeta ++ ); ++ _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _internalEvents).emit( ++ `${transactionMeta.id}:finished`, ++ cancelTransactionMeta ++ ); ++ } ++ /** ++ * Attempts to speed up a transaction increasing transaction gasPrice by ten percent. ++ * ++ * @param transactionId - The ID of the transaction to speed up. ++ * @param gasValues - The gas values to use for the speed up transaction. ++ * @param options - The options for the speed up transaction. ++ * @param options.actionId - Unique ID to prevent duplicate requests ++ * @param options.estimatedBaseFee - The estimated base fee of the transaction. ++ */ ++ async speedUpTransaction(transactionId, gasValues, { ++ actionId, ++ estimatedBaseFee ++ } = {}) { ++ if (this.getTransactionWithActionId(actionId)) { ++ return; ++ } ++ if (gasValues) { ++ gasValues = _chunkOZ6UB42Cjs.normalizeGasFeeValues.call(void 0, gasValues); ++ _chunkOZ6UB42Cjs.validateGasValues.call(void 0, gasValues); ++ } ++ _chunkS6VGOPUYjs.projectLogger.call(void 0, "Creating speed up transaction", transactionId, gasValues); ++ const transactionMeta = this.getTransaction(transactionId); ++ if (!transactionMeta) { ++ return; ++ } ++ if (!this.sign) { ++ throw new Error("No sign method defined."); ++ } ++ const minGasPrice = _chunkOZ6UB42Cjs.getIncreasedPriceFromExisting.call(void 0, ++ transactionMeta.txParams.gasPrice, ++ SPEED_UP_RATE ++ ); ++ const gasPriceFromValues = _chunkOZ6UB42Cjs.isGasPriceValue.call(void 0, gasValues) && gasValues.gasPrice; ++ const newGasPrice = gasPriceFromValues && _chunkOZ6UB42Cjs.validateMinimumIncrease.call(void 0, gasPriceFromValues, minGasPrice) || minGasPrice; ++ const existingMaxFeePerGas = transactionMeta.txParams?.maxFeePerGas; ++ const minMaxFeePerGas = _chunkOZ6UB42Cjs.getIncreasedPriceFromExisting.call(void 0, ++ existingMaxFeePerGas, ++ SPEED_UP_RATE ++ ); ++ const maxFeePerGasValues = _chunkOZ6UB42Cjs.isFeeMarketEIP1559Values.call(void 0, gasValues) && gasValues.maxFeePerGas; ++ const newMaxFeePerGas = maxFeePerGasValues && _chunkOZ6UB42Cjs.validateMinimumIncrease.call(void 0, maxFeePerGasValues, minMaxFeePerGas) || existingMaxFeePerGas && minMaxFeePerGas; ++ const existingMaxPriorityFeePerGas = transactionMeta.txParams?.maxPriorityFeePerGas; ++ const minMaxPriorityFeePerGas = _chunkOZ6UB42Cjs.getIncreasedPriceFromExisting.call(void 0, ++ existingMaxPriorityFeePerGas, ++ SPEED_UP_RATE ++ ); ++ const maxPriorityFeePerGasValues = _chunkOZ6UB42Cjs.isFeeMarketEIP1559Values.call(void 0, gasValues) && gasValues.maxPriorityFeePerGas; ++ const newMaxPriorityFeePerGas = maxPriorityFeePerGasValues && _chunkOZ6UB42Cjs.validateMinimumIncrease.call(void 0, ++ maxPriorityFeePerGasValues, ++ minMaxPriorityFeePerGas ++ ) || existingMaxPriorityFeePerGas && minMaxPriorityFeePerGas; ++ const txParams = newMaxFeePerGas && newMaxPriorityFeePerGas ? { ++ ...transactionMeta.txParams, ++ gasLimit: transactionMeta.txParams.gas, ++ maxFeePerGas: newMaxFeePerGas, ++ maxPriorityFeePerGas: newMaxPriorityFeePerGas, ++ type: "0x2" /* feeMarket */ ++ } : { ++ ...transactionMeta.txParams, ++ gasLimit: transactionMeta.txParams.gas, ++ gasPrice: newGasPrice ++ }; ++ const unsignedEthTx = this.prepareUnsignedEthTx( ++ transactionMeta.chainId, ++ txParams ++ ); ++ const signedTx = await this.sign( ++ unsignedEthTx, ++ transactionMeta.txParams.from ++ ); ++ const transactionMetaWithRsv = this.updateTransactionMetaRSV( ++ transactionMeta, ++ signedTx ++ ); ++ const rawTx = _util.bufferToHex.call(void 0, signedTx.serialize()); ++ const newFee = txParams.maxFeePerGas ?? txParams.gasPrice; ++ const oldFee = txParams.maxFeePerGas ? transactionMetaWithRsv.txParams.maxFeePerGas : transactionMetaWithRsv.txParams.gasPrice; ++ _chunkS6VGOPUYjs.projectLogger.call(void 0, "Submitting speed up transaction", { oldFee, newFee, txParams }); ++ const ethQuery = _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _multichainTrackingHelper).getEthQuery({ ++ networkClientId: transactionMeta.networkClientId, ++ chainId: transactionMeta.chainId ++ }); ++ const hash = await this.publishTransactionForRetry( ++ ethQuery, ++ rawTx, ++ transactionMeta ++ ); ++ const baseTransactionMeta = { ++ ...transactionMetaWithRsv, ++ estimatedBaseFee, ++ id: _uuid.v1.call(void 0, ), ++ time: Date.now(), ++ hash, ++ actionId, ++ originalGasEstimate: transactionMeta.txParams.gas, ++ type: "retry" /* retry */, ++ originalType: transactionMeta.type ++ }; ++ const newTransactionMeta = newMaxFeePerGas && newMaxPriorityFeePerGas ? { ++ ...baseTransactionMeta, ++ txParams: { ++ ...transactionMeta.txParams, ++ maxFeePerGas: newMaxFeePerGas, ++ maxPriorityFeePerGas: newMaxPriorityFeePerGas ++ } ++ } : { ++ ...baseTransactionMeta, ++ txParams: { ++ ...transactionMeta.txParams, ++ gasPrice: newGasPrice ++ } ++ }; ++ this.addMetadata(newTransactionMeta); ++ this.messagingSystem.publish(`${controllerName}:transactionApproved`, { ++ transactionMeta: newTransactionMeta, ++ actionId ++ }); ++ this.messagingSystem.publish(`${controllerName}:transactionSubmitted`, { ++ transactionMeta: newTransactionMeta, ++ actionId ++ }); ++ this.messagingSystem.publish( ++ `${controllerName}:speedupTransactionAdded`, ++ newTransactionMeta ++ ); ++ } ++ /** ++ * Estimates required gas for a given transaction. ++ * ++ * @param transaction - The transaction to estimate gas for. ++ * @param networkClientId - The network client id to use for the estimate. ++ * @returns The gas and gas price. ++ */ ++ async estimateGas(transaction, networkClientId) { ++ const ethQuery = _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _multichainTrackingHelper).getEthQuery({ ++ networkClientId ++ }); ++ const { estimatedGas, simulationFails } = await _chunkV72C4MCRjs.estimateGas.call(void 0, ++ transaction, ++ ethQuery ++ ); ++ return { gas: estimatedGas, simulationFails }; ++ } ++ /** ++ * Estimates required gas for a given transaction and add additional gas buffer with the given multiplier. ++ * ++ * @param transaction - The transaction params to estimate gas for. ++ * @param multiplier - The multiplier to use for the gas buffer. ++ * @param networkClientId - The network client id to use for the estimate. ++ */ ++ async estimateGasBuffered(transaction, multiplier, networkClientId) { ++ const ethQuery = _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _multichainTrackingHelper).getEthQuery({ ++ networkClientId ++ }); ++ const { blockGasLimit, estimatedGas, simulationFails } = await _chunkV72C4MCRjs.estimateGas.call(void 0, ++ transaction, ++ ethQuery ++ ); ++ const gas = _chunkV72C4MCRjs.addGasBuffer.call(void 0, estimatedGas, blockGasLimit, multiplier); ++ return { ++ gas, ++ simulationFails ++ }; ++ } ++ /** ++ * Updates an existing transaction in state. ++ * ++ * @param transactionMeta - The new transaction to store in state. ++ * @param note - A note or update reason to include in the transaction history. ++ */ ++ updateTransaction(transactionMeta, note) { ++ const { id: transactionId } = transactionMeta; ++ _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _updateTransactionInternal, updateTransactionInternal_fn).call(this, { transactionId, note }, () => ({ ++ ...transactionMeta ++ })); ++ } ++ /** ++ * Update the security alert response for a transaction. ++ * ++ * @param transactionId - ID of the transaction. ++ * @param securityAlertResponse - The new security alert response for the transaction. ++ */ ++ updateSecurityAlertResponse(transactionId, securityAlertResponse) { ++ if (!securityAlertResponse) { ++ throw new Error( ++ "updateSecurityAlertResponse: securityAlertResponse should not be null" ++ ); ++ } ++ const transactionMeta = this.getTransaction(transactionId); ++ if (!transactionMeta) { ++ throw new Error( ++ `Cannot update security alert response as no transaction metadata found` ++ ); ++ } ++ const updatedTransactionMeta = { ++ ...transactionMeta, ++ securityAlertResponse ++ }; ++ this.updateTransaction( ++ updatedTransactionMeta, ++ `${controllerName}:updatesecurityAlertResponse - securityAlertResponse updated` ++ ); ++ } ++ /** ++ * Removes all transactions from state, optionally based on the current network. ++ * ++ * @param ignoreNetwork - Determines whether to wipe all transactions, or just those on the ++ * current network. If `true`, all transactions are wiped. ++ * @param address - If specified, only transactions originating from this address will be ++ * wiped on current network. ++ */ ++ wipeTransactions(ignoreNetwork, address) { ++ if (ignoreNetwork && !address) { ++ this.update((state) => { ++ state.transactions = []; ++ }); ++ return; ++ } ++ const currentChainId = this.getChainId(); ++ const newTransactions = this.state.transactions.filter( ++ ({ chainId, txParams }) => { ++ const isMatchingNetwork = ignoreNetwork || chainId === currentChainId; ++ if (!isMatchingNetwork) { ++ return true; ++ } ++ const isMatchingAddress = !address || txParams.from?.toLowerCase() === address.toLowerCase(); ++ return !isMatchingAddress; ++ } ++ ); ++ this.update((state) => { ++ state.transactions = this.trimTransactionsForState(newTransactions); ++ }); ++ } ++ /** ++ * Adds external provided transaction to state as confirmed transaction. ++ * ++ * @param transactionMeta - TransactionMeta to add transactions. ++ * @param transactionReceipt - TransactionReceipt of the external transaction. ++ * @param baseFeePerGas - Base fee per gas of the external transaction. ++ */ ++ async confirmExternalTransaction(transactionMeta, transactionReceipt, baseFeePerGas) { ++ const newTransactionMeta = this.addExternalTransaction(transactionMeta); ++ try { ++ const transactionId = newTransactionMeta.id; ++ const updatedTransactionMeta = { ++ ...newTransactionMeta, ++ status: "confirmed" /* confirmed */, ++ txReceipt: transactionReceipt ++ }; ++ if (baseFeePerGas) { ++ updatedTransactionMeta.baseFeePerGas = baseFeePerGas; ++ } ++ this.markNonceDuplicatesDropped(transactionId); ++ this.updateTransaction( ++ updatedTransactionMeta, ++ `${controllerName}:confirmExternalTransaction - Add external transaction` ++ ); ++ this.onTransactionStatusChange(updatedTransactionMeta); ++ this.updatePostBalance(updatedTransactionMeta); ++ this.messagingSystem.publish( ++ `${controllerName}:transactionConfirmed`, ++ updatedTransactionMeta ++ ); ++ } catch (error) { ++ console.error("Failed to confirm external transaction", error); ++ } ++ } ++ /** ++ * Append new send flow history to a transaction. ++ * ++ * @param transactionID - The ID of the transaction to update. ++ * @param currentSendFlowHistoryLength - The length of the current sendFlowHistory array. ++ * @param sendFlowHistoryToAdd - The sendFlowHistory entries to add. ++ * @returns The updated transactionMeta. ++ */ ++ updateTransactionSendFlowHistory(transactionID, currentSendFlowHistoryLength, sendFlowHistoryToAdd) { ++ if (this.isSendFlowHistoryDisabled) { ++ throw new Error( ++ "Send flow history is disabled for the current transaction controller" ++ ); ++ } ++ const transactionMeta = this.getTransaction(transactionID); ++ if (!transactionMeta) { ++ throw new Error( ++ `Cannot update send flow history as no transaction metadata found` ++ ); ++ } ++ _chunkOZ6UB42Cjs.validateIfTransactionUnapproved.call(void 0, ++ transactionMeta, ++ "updateTransactionSendFlowHistory" ++ ); ++ const sendFlowHistory = transactionMeta.sendFlowHistory ?? []; ++ if (currentSendFlowHistoryLength === sendFlowHistory.length) { ++ const updatedTransactionMeta = { ++ ...transactionMeta, ++ sendFlowHistory: [...sendFlowHistory, ...sendFlowHistoryToAdd] ++ }; ++ this.updateTransaction( ++ updatedTransactionMeta, ++ `${controllerName}:updateTransactionSendFlowHistory - sendFlowHistory updated` ++ ); ++ } ++ return this.getTransaction(transactionID); ++ } ++ /** ++ * Update the gas values of a transaction. ++ * ++ * @param transactionId - The ID of the transaction to update. ++ * @param gasValues - Gas values to update. ++ * @param gasValues.gas - Same as transaction.gasLimit. ++ * @param gasValues.gasLimit - Maxmimum number of units of gas to use for this transaction. ++ * @param gasValues.gasPrice - Price per gas for legacy transactions. ++ * @param gasValues.maxPriorityFeePerGas - Maximum amount per gas to give to validator as incentive. ++ * @param gasValues.maxFeePerGas - Maximum amount per gas to pay for the transaction, including the priority fee. ++ * @param gasValues.estimateUsed - Which estimate level was used. ++ * @param gasValues.estimateSuggested - Which estimate level that the API suggested. ++ * @param gasValues.defaultGasEstimates - The default estimate for gas. ++ * @param gasValues.originalGasEstimate - Original estimate for gas. ++ * @param gasValues.userEditedGasLimit - The gas limit supplied by user. ++ * @param gasValues.userFeeLevel - Estimate level user selected. ++ * @returns The updated transactionMeta. ++ */ ++ updateTransactionGasFees(transactionId, { ++ defaultGasEstimates, ++ estimateUsed, ++ estimateSuggested, ++ gas, ++ gasLimit, ++ gasPrice, ++ maxPriorityFeePerGas, ++ maxFeePerGas, ++ originalGasEstimate, ++ userEditedGasLimit, ++ userFeeLevel ++ }) { ++ const transactionMeta = this.getTransaction(transactionId); ++ if (!transactionMeta) { ++ throw new Error( ++ `Cannot update transaction as no transaction metadata found` ++ ); ++ } ++ _chunkOZ6UB42Cjs.validateIfTransactionUnapproved.call(void 0, ++ transactionMeta, ++ "updateTransactionGasFees" ++ ); ++ let transactionGasFees = { ++ txParams: { ++ gas, ++ gasLimit, ++ gasPrice, ++ maxPriorityFeePerGas, ++ maxFeePerGas ++ }, ++ defaultGasEstimates, ++ estimateUsed, ++ estimateSuggested, ++ originalGasEstimate, ++ userEditedGasLimit, ++ userFeeLevel ++ // TODO: Replace `any` with type ++ // eslint-disable-next-line @typescript-eslint/no-explicit-any ++ }; ++ transactionGasFees.txParams = _lodash.pickBy.call(void 0, transactionGasFees.txParams); ++ transactionGasFees = _lodash.pickBy.call(void 0, transactionGasFees); ++ const updatedMeta = _lodash.merge.call(void 0, {}, transactionMeta, transactionGasFees); ++ this.updateTransaction( ++ updatedMeta, ++ `${controllerName}:updateTransactionGasFees - gas values updated` ++ ); ++ return this.getTransaction(transactionId); ++ } ++ /** ++ * Update the previous gas values of a transaction. ++ * ++ * @param transactionId - The ID of the transaction to update. ++ * @param previousGas - Previous gas values to update. ++ * @param previousGas.gasLimit - Maxmimum number of units of gas to use for this transaction. ++ * @param previousGas.maxFeePerGas - Maximum amount per gas to pay for the transaction, including the priority fee. ++ * @param previousGas.maxPriorityFeePerGas - Maximum amount per gas to give to validator as incentive. ++ * @returns The updated transactionMeta. ++ */ ++ updatePreviousGasParams(transactionId, { ++ gasLimit, ++ maxFeePerGas, ++ maxPriorityFeePerGas ++ }) { ++ const transactionMeta = this.getTransaction(transactionId); ++ if (!transactionMeta) { ++ throw new Error( ++ `Cannot update transaction as no transaction metadata found` ++ ); ++ } ++ _chunkOZ6UB42Cjs.validateIfTransactionUnapproved.call(void 0, transactionMeta, "updatePreviousGasParams"); ++ const transactionPreviousGas = { ++ previousGas: { ++ gasLimit, ++ maxFeePerGas, ++ maxPriorityFeePerGas ++ } ++ // TODO: Replace `any` with type ++ // eslint-disable-next-line @typescript-eslint/no-explicit-any ++ }; ++ transactionPreviousGas.previousGas = _lodash.pickBy.call(void 0, ++ transactionPreviousGas.previousGas ++ ); ++ const updatedMeta = _lodash.merge.call(void 0, {}, transactionMeta, transactionPreviousGas); ++ this.updateTransaction( ++ updatedMeta, ++ `${controllerName}:updatePreviousGasParams - Previous gas values updated` ++ ); ++ return this.getTransaction(transactionId); ++ } ++ async getNonceLock(address, networkClientId) { ++ return _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _multichainTrackingHelper).getNonceLock( ++ address, ++ networkClientId ++ ); ++ } ++ /** ++ * Updates the editable parameters of a transaction. ++ * ++ * @param txId - The ID of the transaction to update. ++ * @param params - The editable parameters to update. ++ * @param params.data - Data to pass with the transaction. ++ * @param params.gas - Maximum number of units of gas to use for the transaction. ++ * @param params.gasPrice - Price per gas for legacy transactions. ++ * @param params.from - Address to send the transaction from. ++ * @param params.to - Address to send the transaction to. ++ * @param params.value - Value associated with the transaction. ++ * @returns The updated transaction metadata. ++ */ ++ async updateEditableParams(txId, { ++ data, ++ gas, ++ gasPrice, ++ from, ++ to, ++ value ++ }) { ++ const transactionMeta = this.getTransaction(txId); ++ if (!transactionMeta) { ++ throw new Error( ++ `Cannot update editable params as no transaction metadata found` ++ ); ++ } ++ _chunkOZ6UB42Cjs.validateIfTransactionUnapproved.call(void 0, transactionMeta, "updateEditableParams"); ++ const editableParams = { ++ txParams: { ++ data, ++ from, ++ to, ++ value, ++ gas, ++ gasPrice ++ } ++ }; ++ editableParams.txParams = _lodash.pickBy.call(void 0, ++ editableParams.txParams ++ ); ++ const updatedTransaction = _lodash.merge.call(void 0, {}, transactionMeta, editableParams); ++ const provider = _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _multichainTrackingHelper).getProvider({ ++ chainId: transactionMeta.chainId, ++ networkClientId: transactionMeta.networkClientId ++ }); ++ const ethQuery = new (0, _ethquery2.default)(provider); ++ const { type } = await _chunkSD6CWFDFjs.determineTransactionType.call(void 0, ++ updatedTransaction.txParams, ++ ethQuery ++ ); ++ updatedTransaction.type = type; ++ await _chunk2XKEAKQGjs.updateTransactionLayer1GasFee.call(void 0, { ++ layer1GasFeeFlows: this.layer1GasFeeFlows, ++ provider, ++ transactionMeta: updatedTransaction ++ }); ++ this.updateTransaction( ++ updatedTransaction, ++ `Update Editable Params for ${txId}` ++ ); ++ return this.getTransaction(txId); ++ } ++ /** ++ * Signs and returns the raw transaction data for provided transaction params list. ++ * ++ * @param listOfTxParams - The list of transaction params to approve. ++ * @param opts - Options bag. ++ * @param opts.hasNonce - Whether the transactions already have a nonce. ++ * @returns The raw transactions. ++ */ ++ async approveTransactionsWithSameNonce(listOfTxParams = [], { hasNonce } = {}) { ++ _chunkS6VGOPUYjs.projectLogger.call(void 0, "Approving transactions with same nonce", { ++ transactions: listOfTxParams ++ }); ++ if (listOfTxParams.length === 0) { ++ return ""; ++ } ++ const initialTx = listOfTxParams[0]; ++ const common = this.getCommonConfiguration(initialTx.chainId); ++ let networkClientId; ++ try { ++ networkClientId = this.messagingSystem.call( ++ `NetworkController:findNetworkClientIdByChainId`, ++ initialTx.chainId ++ ); ++ } catch (err) { ++ _chunkS6VGOPUYjs.projectLogger.call(void 0, "failed to find networkClientId from chainId", err); ++ } ++ const initialTxAsEthTx = _tx.TransactionFactory.fromTxData(initialTx, { ++ common ++ }); ++ const initialTxAsSerializedHex = _util.bufferToHex.call(void 0, initialTxAsEthTx.serialize()); ++ if (this.approvingTransactionIds.has(initialTxAsSerializedHex)) { ++ return ""; ++ } ++ this.approvingTransactionIds.add(initialTxAsSerializedHex); ++ let rawTransactions, nonceLock; ++ try { ++ const fromAddress = initialTx.from; ++ const requiresNonce = hasNonce !== true; ++ nonceLock = requiresNonce ? await this.getNonceLock(fromAddress, networkClientId) : void 0; ++ const nonce = nonceLock ? _utils.add0x.call(void 0, nonceLock.nextNonce.toString(16)) : initialTx.nonce; ++ if (nonceLock) { ++ _chunkS6VGOPUYjs.projectLogger.call(void 0, "Using nonce from nonce tracker", nonce, nonceLock.nonceDetails); ++ } ++ rawTransactions = await Promise.all( ++ listOfTxParams.map((txParams) => { ++ txParams.nonce = nonce; ++ return this.signExternalTransaction(txParams.chainId, txParams); ++ }) ++ ); ++ } catch (err) { ++ _chunkS6VGOPUYjs.projectLogger.call(void 0, "Error while signing transactions with same nonce", err); ++ throw err; ++ } finally { ++ nonceLock?.releaseLock(); ++ this.approvingTransactionIds.delete(initialTxAsSerializedHex); ++ } ++ return rawTransactions; ++ } ++ /** ++ * Update a custodial transaction. ++ * ++ * @param transactionId - The ID of the transaction to update. ++ * @param options - The custodial transaction options to update. ++ * @param options.errorMessage - The error message to be assigned in case transaction status update to failed. ++ * @param options.hash - The new hash value to be assigned. ++ * @param options.status - The new status value to be assigned. ++ */ ++ updateCustodialTransaction(transactionId, { ++ errorMessage, ++ hash, ++ status ++ }) { ++ const transactionMeta = this.getTransaction(transactionId); ++ if (!transactionMeta) { ++ throw new Error( ++ `Cannot update custodial transaction as no transaction metadata found` ++ ); ++ } ++ if (!transactionMeta.custodyId) { ++ throw new Error("Transaction must be a custodian transaction"); ++ } ++ if (status && ![ ++ "submitted" /* submitted */, ++ "signed" /* signed */, ++ "failed" /* failed */ ++ ].includes(status)) { ++ throw new Error( ++ `Cannot update custodial transaction with status: ${status}` ++ ); ++ } ++ const updatedTransactionMeta = _lodash.merge.call(void 0, ++ {}, ++ transactionMeta, ++ _lodash.pickBy.call(void 0, { hash, status }) ++ ); ++ if (updatedTransactionMeta.status === "submitted" /* submitted */) { ++ updatedTransactionMeta.submittedTime = (/* @__PURE__ */ new Date()).getTime(); ++ } ++ if (updatedTransactionMeta.status === "failed" /* failed */) { ++ updatedTransactionMeta.error = _chunkOZ6UB42Cjs.normalizeTxError.call(void 0, new Error(errorMessage)); ++ } ++ this.updateTransaction( ++ updatedTransactionMeta, ++ `${controllerName}:updateCustodialTransaction - Custodial transaction updated` ++ ); ++ if (["submitted" /* submitted */, "failed" /* failed */].includes( ++ status ++ )) { ++ this.messagingSystem.publish( ++ `${controllerName}:transactionFinished`, ++ updatedTransactionMeta ++ ); ++ _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _internalEvents).emit( ++ `${updatedTransactionMeta.id}:finished`, ++ updatedTransactionMeta ++ ); ++ } ++ } ++ /** ++ * Search transaction metadata for matching entries. ++ * ++ * @param opts - Options bag. ++ * @param opts.searchCriteria - An object containing values or functions for transaction properties to filter transactions with. ++ * @param opts.initialList - The transactions to search. Defaults to the current state. ++ * @param opts.filterToCurrentNetwork - Whether to filter the results to the current network. Defaults to true. ++ * @param opts.limit - The maximum number of transactions to return. No limit by default. ++ * @returns An array of transactions matching the provided options. ++ */ ++ getTransactions({ ++ searchCriteria = {}, ++ initialList, ++ filterToCurrentNetwork = true, ++ limit ++ } = {}) { ++ const chainId = this.getChainId(); ++ const predicateMethods = _lodash.mapValues.call(void 0, searchCriteria, (predicate) => { ++ return typeof predicate === "function" ? predicate : ( ++ // TODO: Replace `any` with type ++ // eslint-disable-next-line @typescript-eslint/no-explicit-any ++ (v) => v === predicate ++ ); ++ }); ++ const transactionsToFilter = initialList ?? this.state.transactions; ++ const filteredTransactions = _lodash.sortBy.call(void 0, ++ _lodash.pickBy.call(void 0, transactionsToFilter, (transaction) => { ++ if (filterToCurrentNetwork && transaction.chainId !== chainId) { ++ return false; ++ } ++ for (const [key, predicate] of Object.entries(predicateMethods)) { ++ if (key in transaction.txParams) { ++ if (predicate(transaction.txParams[key]) === false) { ++ return false; ++ } ++ } else if (predicate(transaction[key]) === false) { ++ return false; ++ } ++ } ++ return true; ++ }), ++ "time" ++ ); ++ if (limit !== void 0) { ++ const nonces = /* @__PURE__ */ new Set(); ++ const txs = []; ++ for (let i = filteredTransactions.length - 1; i > -1; i--) { ++ const txMeta = filteredTransactions[i]; ++ const { nonce } = txMeta.txParams; ++ if (!nonces.has(nonce)) { ++ if (nonces.size < limit) { ++ nonces.add(nonce); ++ } else { ++ continue; ++ } ++ } ++ txs.unshift(txMeta); ++ } ++ return txs; ++ } ++ return filteredTransactions; ++ } ++ async estimateGasFee({ ++ transactionParams, ++ chainId, ++ networkClientId: requestNetworkClientId ++ }) { ++ const networkClientId = _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getNetworkClientId, getNetworkClientId_fn).call(this, { ++ networkClientId: requestNetworkClientId, ++ chainId ++ }); ++ const transactionMeta = { ++ txParams: transactionParams, ++ chainId, ++ networkClientId ++ }; ++ const gasFeeFlow = _chunk76FONEDAjs.getGasFeeFlow.call(void 0, ++ transactionMeta, ++ this.gasFeeFlows ++ ); ++ const ethQuery = _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _multichainTrackingHelper).getEthQuery({ ++ networkClientId, ++ chainId ++ }); ++ const gasFeeControllerData = await this.getGasFeeEstimates({ ++ networkClientId ++ }); ++ return gasFeeFlow.getGasFees({ ++ ethQuery, ++ gasFeeControllerData, ++ transactionMeta ++ }); ++ } ++ /** ++ * Determine the layer 1 gas fee for the given transaction parameters. ++ * ++ * @param request - The request object. ++ * @param request.transactionParams - The transaction parameters to estimate the layer 1 gas fee for. ++ * @param request.chainId - The ID of the chain where the transaction will be executed. ++ * @param request.networkClientId - The ID of a specific network client to process the transaction. ++ */ ++ async getLayer1GasFee({ ++ transactionParams, ++ chainId, ++ networkClientId ++ }) { ++ const provider = _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _multichainTrackingHelper).getProvider({ ++ networkClientId, ++ chainId ++ }); ++ return await _chunk2XKEAKQGjs.getTransactionLayer1GasFee.call(void 0, { ++ layer1GasFeeFlows: this.layer1GasFeeFlows, ++ provider, ++ transactionMeta: { ++ txParams: transactionParams, ++ chainId ++ } ++ }); ++ } ++ async signExternalTransaction(chainId, transactionParams) { ++ if (!this.sign) { ++ throw new Error("No sign method defined."); ++ } ++ const normalizedTransactionParams = _chunkOZ6UB42Cjs.normalizeTransactionParams.call(void 0, transactionParams); ++ const type = _chunkOZ6UB42Cjs.isEIP1559Transaction.call(void 0, normalizedTransactionParams) ? "0x2" /* feeMarket */ : "0x0" /* legacy */; ++ const updatedTransactionParams = { ++ ...normalizedTransactionParams, ++ type, ++ gasLimit: normalizedTransactionParams.gas, ++ chainId ++ }; ++ const { from } = updatedTransactionParams; ++ const common = this.getCommonConfiguration(chainId); ++ const unsignedTransaction = _tx.TransactionFactory.fromTxData( ++ updatedTransactionParams, ++ { common } ++ ); ++ const signedTransaction = await this.sign(unsignedTransaction, from); ++ const rawTransaction = _util.bufferToHex.call(void 0, signedTransaction.serialize()); ++ return rawTransaction; ++ } ++ /** ++ * Removes unapproved transactions from state. ++ */ ++ clearUnapprovedTransactions() { ++ const transactions = this.state.transactions.filter( ++ ({ status }) => status !== "unapproved" /* unapproved */ ++ ); ++ this.update((state) => { ++ state.transactions = this.trimTransactionsForState(transactions); ++ }); ++ } ++ /** ++ * Stop the signing process for a specific transaction. ++ * Throws an error causing the transaction status to be set to failed. ++ * @param transactionId - The ID of the transaction to stop signing. ++ */ ++ abortTransactionSigning(transactionId) { ++ const transactionMeta = this.getTransaction(transactionId); ++ if (!transactionMeta) { ++ throw new Error(`Cannot abort signing as no transaction metadata found`); ++ } ++ const abortCallback = this.signAbortCallbacks.get(transactionId); ++ if (!abortCallback) { ++ throw new Error( ++ `Cannot abort signing as transaction is not waiting for signing` ++ ); ++ } ++ abortCallback(); ++ this.signAbortCallbacks.delete(transactionId); ++ } ++ addMetadata(transactionMeta) { ++ this.update((state) => { ++ state.transactions = this.trimTransactionsForState([ ++ ...state.transactions, ++ transactionMeta ++ ]); ++ }); ++ } ++ async updateGasProperties(transactionMeta) { ++ const isEIP1559Compatible = await this.getEIP1559Compatibility(transactionMeta.networkClientId) && transactionMeta.txParams.type !== "0x0" /* legacy */; ++ const { networkClientId, chainId } = transactionMeta; ++ const isCustomNetwork = _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _isCustomNetwork, isCustomNetwork_fn).call(this, networkClientId); ++ const ethQuery = _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _multichainTrackingHelper).getEthQuery({ ++ networkClientId, ++ chainId ++ }); ++ const provider = _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _multichainTrackingHelper).getProvider({ ++ networkClientId, ++ chainId ++ }); ++ await _chunkV72C4MCRjs.updateGas.call(void 0, { ++ ethQuery, ++ chainId, ++ isCustomNetwork, ++ txMeta: transactionMeta ++ }); ++ await _chunkC3WC4OJ3js.updateGasFees.call(void 0, { ++ eip1559: isEIP1559Compatible, ++ ethQuery, ++ gasFeeFlows: this.gasFeeFlows, ++ getGasFeeEstimates: this.getGasFeeEstimates, ++ getSavedGasFees: this.getSavedGasFees.bind(this), ++ txMeta: transactionMeta ++ }); ++ await _chunk2XKEAKQGjs.updateTransactionLayer1GasFee.call(void 0, { ++ layer1GasFeeFlows: this.layer1GasFeeFlows, ++ provider, ++ transactionMeta ++ }); ++ } ++ onBootCleanup() { ++ this.clearUnapprovedTransactions(); ++ this.failIncompleteTransactions(); ++ } ++ failIncompleteTransactions() { ++ const incompleteTransactions = this.state.transactions.filter( ++ (transaction) => ["approved" /* approved */, "signed" /* signed */].includes( ++ transaction.status ++ ) ++ ); ++ for (const transactionMeta of incompleteTransactions) { ++ this.failTransaction( ++ transactionMeta, ++ new Error("Transaction incomplete at startup") ++ ); ++ } ++ } ++ async processApproval(transactionMeta, { ++ isExisting = false, ++ requireApproval, ++ shouldShowRequest = true, ++ actionId ++ }) { ++ const transactionId = transactionMeta.id; ++ let resultCallbacks; ++ const { meta, isCompleted } = this.isTransactionCompleted(transactionId); ++ const finishedPromise = isCompleted ? Promise.resolve(meta) : this.waitForTransactionFinished(transactionId); ++ if (meta && !isExisting && !isCompleted) { ++ try { ++ if (requireApproval !== false) { ++ const acceptResult = await this.requestApproval(transactionMeta, { ++ shouldShowRequest ++ }); ++ resultCallbacks = acceptResult.resultCallbacks; ++ const approvalValue = acceptResult.value; ++ const updatedTransaction = approvalValue?.txMeta; ++ if (updatedTransaction) { ++ _chunkS6VGOPUYjs.projectLogger.call(void 0, "Updating transaction with approval data", { ++ customNonce: updatedTransaction.customNonceValue, ++ params: updatedTransaction.txParams ++ }); ++ this.updateTransaction( ++ updatedTransaction, ++ "TransactionController#processApproval - Updated with approval data" ++ ); ++ } ++ } ++ const { isCompleted: isTxCompleted } = this.isTransactionCompleted(transactionId); ++ if (!isTxCompleted) { ++ const approvalResult = await this.approveTransaction(transactionId); ++ if (approvalResult === "skipped-via-before-publish-hook" /* SkippedViaBeforePublishHook */ && resultCallbacks) { ++ resultCallbacks.success(); ++ } ++ const updatedTransactionMeta = this.getTransaction( ++ transactionId ++ ); ++ this.messagingSystem.publish( ++ `${controllerName}:transactionApproved`, ++ { ++ transactionMeta: updatedTransactionMeta, ++ actionId ++ } ++ ); ++ } ++ } catch (error) { ++ const { isCompleted: isTxCompleted } = this.isTransactionCompleted(transactionId); ++ if (!isTxCompleted) { ++ if (error?.code === _rpcerrors.errorCodes.provider.userRejectedRequest) { ++ this.cancelTransaction(transactionId, actionId); ++ throw _rpcerrors.providerErrors.userRejectedRequest( ++ "MetaMask Tx Signature: User denied transaction signature." ++ ); ++ } else { ++ this.failTransaction(meta, error, actionId); ++ } ++ } ++ } ++ } ++ const finalMeta = await finishedPromise; ++ switch (finalMeta?.status) { ++ case "failed" /* failed */: ++ resultCallbacks?.error(finalMeta.error); ++ throw _rpcerrors.rpcErrors.internal(finalMeta.error.message); ++ case "submitted" /* submitted */: ++ resultCallbacks?.success(); ++ return finalMeta.hash; ++ default: ++ const internalError = _rpcerrors.rpcErrors.internal( ++ `MetaMask Tx Signature: Unknown problem: ${JSON.stringify( ++ finalMeta || transactionId ++ )}` ++ ); ++ resultCallbacks?.error(internalError); ++ throw internalError; ++ } ++ } ++ /** ++ * Approves a transaction and updates it's status in state. If this is not a ++ * retry transaction, a nonce will be generated. The transaction is signed ++ * using the sign configuration property, then published to the blockchain. ++ * A `:finished` hub event is fired after success or failure. ++ * ++ * @param transactionId - The ID of the transaction to approve. ++ */ ++ async approveTransaction(transactionId) { ++ const cleanupTasks = new Array(); ++ cleanupTasks.push(await this.mutex.acquire()); ++ let transactionMeta = this.getTransactionOrThrow(transactionId); ++ try { ++ if (!this.sign) { ++ this.failTransaction( ++ transactionMeta, ++ new Error("No sign method defined.") ++ ); ++ return "not-approved" /* NotApproved */; ++ } else if (!transactionMeta.chainId) { ++ this.failTransaction(transactionMeta, new Error("No chainId defined.")); ++ return "not-approved" /* NotApproved */; ++ } ++ if (this.approvingTransactionIds.has(transactionId)) { ++ _chunkS6VGOPUYjs.projectLogger.call(void 0, "Skipping approval as signing in progress", transactionId); ++ return "not-approved" /* NotApproved */; ++ } ++ this.approvingTransactionIds.add(transactionId); ++ cleanupTasks.push( ++ () => this.approvingTransactionIds.delete(transactionId) ++ ); ++ const [nonce, releaseNonce] = await _chunkPRUNMTRDjs.getNextNonce.call(void 0, ++ transactionMeta, ++ (address) => _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _multichainTrackingHelper).getNonceLock( ++ address, ++ transactionMeta.networkClientId ++ ) ++ ); ++ releaseNonce && cleanupTasks.push(releaseNonce); ++ transactionMeta = _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _updateTransactionInternal, updateTransactionInternal_fn).call(this, { ++ transactionId, ++ note: "TransactionController#approveTransaction - Transaction approved" ++ }, (draftTxMeta) => { ++ const { txParams, chainId } = draftTxMeta; ++ draftTxMeta.status = "approved" /* approved */; ++ draftTxMeta.txParams = { ++ ...txParams, ++ nonce, ++ chainId, ++ gasLimit: txParams.gas, ++ ..._chunkOZ6UB42Cjs.isEIP1559Transaction.call(void 0, txParams) && { ++ type: "0x2" /* feeMarket */ ++ } ++ }; ++ }); ++ this.onTransactionStatusChange(transactionMeta); ++ const rawTx = await this.signTransaction( ++ transactionMeta, ++ transactionMeta.txParams ++ ); ++ if (!this.beforePublish(transactionMeta)) { ++ _chunkS6VGOPUYjs.projectLogger.call(void 0, "Skipping publishing transaction based on hook"); ++ this.messagingSystem.publish( ++ `${controllerName}:transactionPublishingSkipped`, ++ transactionMeta ++ ); ++ return "skipped-via-before-publish-hook" /* SkippedViaBeforePublishHook */; ++ } ++ if (!rawTx) { ++ return "not-approved" /* NotApproved */; ++ } ++ const ethQuery = _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _multichainTrackingHelper).getEthQuery({ ++ networkClientId: transactionMeta.networkClientId, ++ chainId: transactionMeta.chainId ++ }); ++ let preTxBalance; ++ const shouldUpdatePreTxBalance = transactionMeta.type === "swap" /* swap */; ++ if (shouldUpdatePreTxBalance) { ++ _chunkS6VGOPUYjs.projectLogger.call(void 0, "Determining pre-transaction balance"); ++ preTxBalance = await _controllerutils.query.call(void 0, ethQuery, "getBalance", [ ++ transactionMeta.txParams.from ++ ]); ++ } ++ _chunkS6VGOPUYjs.projectLogger.call(void 0, "Publishing transaction", transactionMeta.txParams); ++ let { transactionHash: hash } = await this.publish( ++ transactionMeta, ++ rawTx ++ ); ++ if (hash === void 0) { ++ hash = await this.publishTransaction(ethQuery, rawTx); ++ } ++ _chunkS6VGOPUYjs.projectLogger.call(void 0, "Publish successful", hash); ++ transactionMeta = _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _updateTransactionInternal, updateTransactionInternal_fn).call(this, { ++ transactionId, ++ note: "TransactionController#approveTransaction - Transaction submitted" ++ }, (draftTxMeta) => { ++ draftTxMeta.hash = hash; ++ draftTxMeta.status = "submitted" /* submitted */; ++ draftTxMeta.submittedTime = (/* @__PURE__ */ new Date()).getTime(); ++ if (shouldUpdatePreTxBalance) { ++ draftTxMeta.preTxBalance = preTxBalance; ++ _chunkS6VGOPUYjs.projectLogger.call(void 0, "Updated pre-transaction balance", preTxBalance); ++ } ++ }); ++ this.messagingSystem.publish(`${controllerName}:transactionSubmitted`, { ++ transactionMeta ++ }); ++ this.messagingSystem.publish( ++ `${controllerName}:transactionFinished`, ++ transactionMeta ++ ); ++ _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _internalEvents).emit(`${transactionId}:finished`, transactionMeta); ++ this.onTransactionStatusChange(transactionMeta); ++ return "approved" /* Approved */; ++ } catch (error) { ++ this.failTransaction(transactionMeta, error); ++ return "not-approved" /* NotApproved */; ++ } finally { ++ cleanupTasks.forEach((task) => task()); ++ } ++ } ++ async publishTransaction(ethQuery, rawTransaction) { ++ return await _controllerutils.query.call(void 0, ethQuery, "sendRawTransaction", [rawTransaction]); ++ } ++ /** ++ * Cancels a transaction based on its ID by setting its status to "rejected" ++ * and emitting a `:finished` hub event. ++ * ++ * @param transactionId - The ID of the transaction to cancel. ++ * @param actionId - The actionId passed from UI ++ */ ++ cancelTransaction(transactionId, actionId) { ++ const transactionMeta = this.state.transactions.find( ++ ({ id }) => id === transactionId ++ ); ++ if (!transactionMeta) { ++ return; ++ } ++ this.update((state) => { ++ const transactions = state.transactions.filter( ++ ({ id }) => id !== transactionId ++ ); ++ state.transactions = this.trimTransactionsForState(transactions); ++ }); ++ const updatedTransactionMeta = { ++ ...transactionMeta, ++ status: "rejected" /* rejected */ ++ }; ++ this.messagingSystem.publish( ++ `${controllerName}:transactionFinished`, ++ updatedTransactionMeta ++ ); ++ _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _internalEvents).emit( ++ // TODO: Either fix this lint violation or explain why it's necessary to ignore. ++ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions ++ `${transactionMeta.id}:finished`, ++ updatedTransactionMeta ++ ); ++ this.messagingSystem.publish(`${controllerName}:transactionRejected`, { ++ transactionMeta: updatedTransactionMeta, ++ actionId ++ }); ++ this.onTransactionStatusChange(updatedTransactionMeta); ++ } ++ /** ++ * Trim the amount of transactions that are set on the state. Checks ++ * if the length of the tx history is longer then desired persistence ++ * limit and then if it is removes the oldest confirmed or rejected tx. ++ * Pending or unapproved transactions will not be removed by this ++ * operation. For safety of presenting a fully functional transaction UI ++ * representation, this function will not break apart transactions with the ++ * same nonce, created on the same day, per network. Not accounting for ++ * transactions of the same nonce, same day and network combo can result in ++ * confusing or broken experiences in the UI. ++ * ++ * @param transactions - The transactions to be applied to the state. ++ * @returns The trimmed list of transactions. ++ */ ++ trimTransactionsForState(transactions) { ++ const nonceNetworkSet = /* @__PURE__ */ new Set(); ++ const txsToKeep = [...transactions].sort((a, b) => a.time > b.time ? -1 : 1).filter((tx) => { ++ const { chainId, status, txParams, time } = tx; ++ if (txParams) { ++ const key = `${String(txParams.nonce)}-${_controllerutils.convertHexToDecimal.call(void 0, ++ chainId ++ )}-${new Date(time).toDateString()}`; ++ if (nonceNetworkSet.has(key)) { ++ return true; ++ } else if (nonceNetworkSet.size < _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _transactionHistoryLimit) || !this.isFinalState(status)) { ++ nonceNetworkSet.add(key); ++ return true; ++ } ++ } ++ return false; ++ }); ++ txsToKeep.reverse(); ++ return txsToKeep; ++ } ++ /** ++ * Determines if the transaction is in a final state. ++ * ++ * @param status - The transaction status. ++ * @returns Whether the transaction is in a final state. ++ */ ++ isFinalState(status) { ++ return status === "rejected" /* rejected */ || status === "confirmed" /* confirmed */ || status === "failed" /* failed */; ++ } ++ /** ++ * Whether the transaction has at least completed all local processing. ++ * ++ * @param status - The transaction status. ++ * @returns Whether the transaction is in a final state. ++ */ ++ isLocalFinalState(status) { ++ return [ ++ "confirmed" /* confirmed */, ++ "failed" /* failed */, ++ "rejected" /* rejected */, ++ "submitted" /* submitted */ ++ ].includes(status); ++ } ++ async requestApproval(txMeta, { shouldShowRequest }) { ++ const id = this.getApprovalId(txMeta); ++ const { origin } = txMeta; ++ const type = _controllerutils.ApprovalType.Transaction; ++ const requestData = { txId: txMeta.id }; ++ return await this.messagingSystem.call( ++ "ApprovalController:addRequest", ++ { ++ id, ++ origin: origin || _controllerutils.ORIGIN_METAMASK, ++ type, ++ requestData, ++ expectsResult: true ++ }, ++ shouldShowRequest ++ ); ++ } ++ getTransaction(transactionId) { ++ const { transactions } = this.state; ++ return transactions.find(({ id }) => id === transactionId); ++ } ++ getTransactionOrThrow(transactionId, errorMessagePrefix = "TransactionController") { ++ const txMeta = this.getTransaction(transactionId); ++ if (!txMeta) { ++ throw new Error( ++ `${errorMessagePrefix}: No transaction found with id ${transactionId}` ++ ); ++ } ++ return txMeta; ++ } ++ getApprovalId(txMeta) { ++ return String(txMeta.id); ++ } ++ isTransactionCompleted(transactionId) { ++ const transaction = this.getTransaction(transactionId); ++ if (!transaction) { ++ return { meta: void 0, isCompleted: false }; ++ } ++ const isCompleted = this.isLocalFinalState(transaction.status); ++ return { meta: transaction, isCompleted }; ++ } ++ getChainId(networkClientId) { ++ const globalChainId = _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getGlobalChainId, getGlobalChainId_fn).call(this); ++ const globalNetworkClientId = _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getGlobalNetworkClientId, getGlobalNetworkClientId_fn).call(this); ++ if (!networkClientId || networkClientId === globalNetworkClientId) { ++ return globalChainId; ++ } ++ return this.messagingSystem.call( ++ `NetworkController:getNetworkClientById`, ++ networkClientId ++ ).configuration.chainId; ++ } ++ prepareUnsignedEthTx(chainId, txParams) { ++ return _tx.TransactionFactory.fromTxData(txParams, { ++ freeze: false, ++ common: this.getCommonConfiguration(chainId) ++ }); ++ } ++ /** ++ * `@ethereumjs/tx` uses `@ethereumjs/common` as a configuration tool for ++ * specifying which chain, network, hardfork and EIPs to support for ++ * a transaction. By referencing this configuration, and analyzing the fields ++ * specified in txParams, @ethereumjs/tx is able to determine which EIP-2718 ++ * transaction type to use. ++ * ++ * @param chainId - The chainId to use for the configuration. ++ * @returns common configuration object ++ */ ++ getCommonConfiguration(chainId) { ++ const customChainParams = { ++ chainId: parseInt(chainId, 16), ++ defaultHardfork: HARDFORK ++ }; ++ return _common.Common.custom(customChainParams); ++ } ++ onIncomingTransactions({ ++ added, ++ updated ++ }) { ++ this.update((state) => { ++ const { transactions: currentTransactions } = state; ++ const updatedTransactions = [ ++ ...added, ++ ...currentTransactions.map((originalTransaction) => { ++ const updatedTransaction = updated.find( ++ ({ hash }) => hash === originalTransaction.hash ++ ); ++ return updatedTransaction ?? originalTransaction; ++ }) ++ ]; ++ state.transactions = this.trimTransactionsForState(updatedTransactions); ++ }); ++ } ++ onUpdatedLastFetchedBlockNumbers({ ++ lastFetchedBlockNumbers, ++ blockNumber ++ }) { ++ this.update((state) => { ++ state.lastFetchedBlockNumbers = lastFetchedBlockNumbers; ++ }); ++ this.messagingSystem.publish( ++ `${controllerName}:incomingTransactionBlockReceived`, ++ blockNumber ++ ); ++ } ++ generateDappSuggestedGasFees(txParams, origin) { ++ if (!origin || origin === _controllerutils.ORIGIN_METAMASK) { ++ return void 0; ++ } ++ const { gasPrice, maxFeePerGas, maxPriorityFeePerGas, gas } = txParams; ++ if (gasPrice === void 0 && maxFeePerGas === void 0 && maxPriorityFeePerGas === void 0 && gas === void 0) { ++ return void 0; ++ } ++ const dappSuggestedGasFees = {}; ++ if (gasPrice !== void 0) { ++ dappSuggestedGasFees.gasPrice = gasPrice; ++ } else if (maxFeePerGas !== void 0 || maxPriorityFeePerGas !== void 0) { ++ dappSuggestedGasFees.maxFeePerGas = maxFeePerGas; ++ dappSuggestedGasFees.maxPriorityFeePerGas = maxPriorityFeePerGas; ++ } ++ if (gas !== void 0) { ++ dappSuggestedGasFees.gas = gas; ++ } ++ return dappSuggestedGasFees; ++ } ++ /** ++ * Validates and adds external provided transaction to state. ++ * ++ * @param transactionMeta - Nominated external transaction to be added to state. ++ * @returns The new transaction. ++ */ ++ addExternalTransaction(transactionMeta) { ++ const { chainId } = transactionMeta; ++ const { transactions } = this.state; ++ const fromAddress = transactionMeta?.txParams?.from; ++ const sameFromAndNetworkTransactions = transactions.filter( ++ (transaction) => transaction.txParams.from === fromAddress && transaction.chainId === chainId ++ ); ++ const confirmedTxs = sameFromAndNetworkTransactions.filter( ++ (transaction) => transaction.status === "confirmed" /* confirmed */ ++ ); ++ const pendingTxs = sameFromAndNetworkTransactions.filter( ++ (transaction) => transaction.status === "submitted" /* submitted */ ++ ); ++ _chunk7LXE4KHVjs.validateConfirmedExternalTransaction.call(void 0, ++ transactionMeta, ++ confirmedTxs, ++ pendingTxs ++ ); ++ const newTransactionMeta = (transactionMeta.history ?? []).length === 0 && !this.isHistoryDisabled ? _chunkQP75SWIQjs.addInitialHistorySnapshot.call(void 0, transactionMeta) : transactionMeta; ++ this.update((state) => { ++ state.transactions = this.trimTransactionsForState([ ++ ...state.transactions, ++ newTransactionMeta ++ ]); ++ }); ++ return newTransactionMeta; ++ } ++ /** ++ * Sets other txMeta statuses to dropped if the txMeta that has been confirmed has other transactions ++ * in the transactions have the same nonce. ++ * ++ * @param transactionId - Used to identify original transaction. ++ */ ++ markNonceDuplicatesDropped(transactionId) { ++ const transactionMeta = this.getTransaction(transactionId); ++ if (!transactionMeta) { ++ return; ++ } ++ const nonce = transactionMeta.txParams?.nonce; ++ const from = transactionMeta.txParams?.from; ++ const { chainId } = transactionMeta; ++ const sameNonceTransactions = this.state.transactions.filter( ++ (transaction) => transaction.id !== transactionId && transaction.txParams.from === from && transaction.txParams.nonce === nonce && transaction.chainId === chainId && transaction.type !== "incoming" /* incoming */ ++ ); ++ const sameNonceTransactionIds = sameNonceTransactions.map( ++ (transaction) => transaction.id ++ ); ++ if (sameNonceTransactions.length === 0) { ++ return; ++ } ++ this.update((state) => { ++ for (const transaction of state.transactions) { ++ if (sameNonceTransactionIds.includes(transaction.id)) { ++ transaction.replacedBy = transactionMeta?.hash; ++ transaction.replacedById = transactionMeta?.id; ++ } ++ } ++ }); ++ for (const transaction of this.state.transactions) { ++ if (sameNonceTransactionIds.includes(transaction.id) && transaction.status !== "failed" /* failed */) { ++ this.setTransactionStatusDropped(transaction); ++ } ++ } ++ } ++ /** ++ * Method to set transaction status to dropped. ++ * ++ * @param transactionMeta - TransactionMeta of transaction to be marked as dropped. ++ */ ++ setTransactionStatusDropped(transactionMeta) { ++ const updatedTransactionMeta = { ++ ...transactionMeta, ++ status: "dropped" /* dropped */ ++ }; ++ this.messagingSystem.publish(`${controllerName}:transactionDropped`, { ++ transactionMeta: updatedTransactionMeta ++ }); ++ this.updateTransaction( ++ updatedTransactionMeta, ++ "TransactionController#setTransactionStatusDropped - Transaction dropped" ++ ); ++ this.onTransactionStatusChange(updatedTransactionMeta); ++ } ++ /** ++ * Get transaction with provided actionId. ++ * ++ * @param actionId - Unique ID to prevent duplicate requests ++ * @returns the filtered transaction ++ */ ++ getTransactionWithActionId(actionId) { ++ return this.state.transactions.find( ++ (transaction) => actionId && transaction.actionId === actionId ++ ); ++ } ++ async waitForTransactionFinished(transactionId) { ++ return new Promise((resolve) => { ++ _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _internalEvents).once(`${transactionId}:finished`, (txMeta) => { ++ resolve(txMeta); ++ }); ++ }); ++ } ++ /** ++ * Updates the r, s, and v properties of a TransactionMeta object ++ * with values from a signed transaction. ++ * ++ * @param transactionMeta - The TransactionMeta object to update. ++ * @param signedTx - The encompassing type for all transaction types containing r, s, and v values. ++ * @returns The updated TransactionMeta object. ++ */ ++ updateTransactionMetaRSV(transactionMeta, signedTx) { ++ const transactionMetaWithRsv = _lodash.cloneDeep.call(void 0, transactionMeta); ++ for (const key of ["r", "s", "v"]) { ++ const value = signedTx[key]; ++ if (value === void 0 || value === null) { ++ continue; ++ } ++ transactionMetaWithRsv[key] = _utils.add0x.call(void 0, value.toString(16)); ++ } ++ return transactionMetaWithRsv; ++ } ++ async getEIP1559Compatibility(networkClientId) { ++ const currentNetworkIsEIP1559Compatible = await this.getCurrentNetworkEIP1559Compatibility(networkClientId); ++ const currentAccountIsEIP1559Compatible = await this.getCurrentAccountEIP1559Compatibility(); ++ return currentNetworkIsEIP1559Compatible && currentAccountIsEIP1559Compatible; ++ } ++ async signTransaction(transactionMeta, txParams) { ++ _chunkS6VGOPUYjs.projectLogger.call(void 0, "Signing transaction", txParams); ++ const unsignedEthTx = this.prepareUnsignedEthTx( ++ transactionMeta.chainId, ++ txParams ++ ); ++ this.approvingTransactionIds.add(transactionMeta.id); ++ const signedTx = await new Promise((resolve, reject) => { ++ this.sign?.( ++ unsignedEthTx, ++ txParams.from, ++ ...this.getAdditionalSignArguments(transactionMeta) ++ ).then(resolve, reject); ++ this.signAbortCallbacks.set( ++ transactionMeta.id, ++ () => reject(new Error("Signing aborted by user")) ++ ); ++ }); ++ this.signAbortCallbacks.delete(transactionMeta.id); ++ if (!signedTx) { ++ _chunkS6VGOPUYjs.projectLogger.call(void 0, "Skipping signed status as no signed transaction"); ++ return void 0; ++ } ++ const transactionMetaFromHook = _lodash.cloneDeep.call(void 0, transactionMeta); ++ if (!this.afterSign(transactionMetaFromHook, signedTx)) { ++ this.updateTransaction( ++ transactionMetaFromHook, ++ "TransactionController#signTransaction - Update after sign" ++ ); ++ _chunkS6VGOPUYjs.projectLogger.call(void 0, "Skipping signed status based on hook"); ++ return void 0; ++ } ++ const transactionMetaWithRsv = { ++ ...this.updateTransactionMetaRSV(transactionMetaFromHook, signedTx), ++ status: "signed" /* signed */ ++ }; ++ this.updateTransaction( ++ transactionMetaWithRsv, ++ "TransactionController#approveTransaction - Transaction signed" ++ ); ++ this.onTransactionStatusChange(transactionMetaWithRsv); ++ const rawTx = _util.bufferToHex.call(void 0, signedTx.serialize()); ++ const transactionMetaWithRawTx = _lodash.merge.call(void 0, {}, transactionMetaWithRsv, { ++ rawTx ++ }); ++ this.updateTransaction( ++ transactionMetaWithRawTx, ++ "TransactionController#approveTransaction - RawTransaction added" ++ ); ++ return rawTx; ++ } ++ onTransactionStatusChange(transactionMeta) { ++ this.messagingSystem.publish(`${controllerName}:transactionStatusUpdated`, { ++ transactionMeta ++ }); ++ } ++ getNonceTrackerTransactions(status, address, chainId = this.getChainId()) { ++ return _chunkPRUNMTRDjs.getAndFormatTransactionsForNonceTracker.call(void 0, ++ chainId, ++ address, ++ status, ++ this.state.transactions ++ ); ++ } ++ onConfirmedTransaction(transactionMeta) { ++ _chunkS6VGOPUYjs.projectLogger.call(void 0, "Processing confirmed transaction", transactionMeta.id); ++ this.markNonceDuplicatesDropped(transactionMeta.id); ++ this.messagingSystem.publish( ++ `${controllerName}:transactionConfirmed`, ++ transactionMeta ++ ); ++ this.onTransactionStatusChange(transactionMeta); ++ this.updatePostBalance(transactionMeta); ++ } ++ async updatePostBalance(transactionMeta) { ++ try { ++ if (transactionMeta.type !== "swap" /* swap */) { ++ return; ++ } ++ const ethQuery = _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _multichainTrackingHelper).getEthQuery({ ++ networkClientId: transactionMeta.networkClientId, ++ chainId: transactionMeta.chainId ++ }); ++ const { updatedTransactionMeta, approvalTransactionMeta } = await _chunkQH2H4W3Njs.updatePostTransactionBalance.call(void 0, transactionMeta, { ++ ethQuery, ++ getTransaction: this.getTransaction.bind(this), ++ updateTransaction: this.updateTransaction.bind(this) ++ }); ++ this.messagingSystem.publish( ++ `${controllerName}:postTransactionBalanceUpdated`, ++ { ++ transactionMeta: updatedTransactionMeta, ++ approvalTransactionMeta ++ } ++ ); ++ } catch (error) { ++ _chunkS6VGOPUYjs.projectLogger.call(void 0, "Error while updating post transaction balance", error); ++ } ++ } ++ async publishTransactionForRetry(ethQuery, rawTx, transactionMeta) { ++ try { ++ const hash = await this.publishTransaction(ethQuery, rawTx); ++ return hash; ++ } catch (error) { ++ if (this.isTransactionAlreadyConfirmedError(error)) { ++ await this.pendingTransactionTracker.forceCheckTransaction( ++ transactionMeta ++ ); ++ throw new Error("Previous transaction is already confirmed"); ++ } ++ throw error; ++ } ++ } ++ /** ++ * Ensures that error is a nonce issue ++ * ++ * @param error - The error to check ++ * @returns Whether or not the error is a nonce issue ++ */ ++ // TODO: Replace `any` with type ++ // Some networks are returning original error in the data field ++ // eslint-disable-next-line @typescript-eslint/no-explicit-any ++ isTransactionAlreadyConfirmedError(error) { ++ return error?.message?.includes("nonce too low") || error?.data?.message?.includes("nonce too low"); ++ } ++}; ++_internalEvents = new WeakMap(); ++_incomingTransactionOptions = new WeakMap(); ++_pendingTransactionOptions = new WeakMap(); ++_transactionHistoryLimit = new WeakMap(); ++_isSimulationEnabled = new WeakMap(); ++_testGasFeeFlows = new WeakMap(); ++_multichainTrackingHelper = new WeakMap(); ++_createNonceTracker = new WeakSet(); ++createNonceTracker_fn = function({ ++ provider, ++ blockTracker, ++ chainId ++}) { ++ return new (0, _noncetracker.NonceTracker)({ ++ // TODO: Fix types ++ // eslint-disable-next-line @typescript-eslint/no-explicit-any ++ provider, ++ // @ts-expect-error TODO: Fix types ++ blockTracker, ++ getPendingTransactions: _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getNonceTrackerPendingTransactions, getNonceTrackerPendingTransactions_fn).bind( ++ this, ++ chainId ++ ), ++ getConfirmedTransactions: this.getNonceTrackerTransactions.bind( ++ this, ++ "confirmed" /* confirmed */ ++ ) ++ }); ++}; ++_createIncomingTransactionHelper = new WeakSet(); ++createIncomingTransactionHelper_fn = function({ ++ blockTracker, ++ etherscanRemoteTransactionSource, ++ chainId ++}) { ++ const incomingTransactionHelper = new (0, _chunkRHDPOIS4js.IncomingTransactionHelper)({ ++ blockTracker, ++ getCurrentAccount: () => _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getSelectedAccount, getSelectedAccount_fn).call(this), ++ getLastFetchedBlockNumbers: () => this.state.lastFetchedBlockNumbers, ++ getChainId: chainId ? () => chainId : this.getChainId.bind(this), ++ isEnabled: _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _incomingTransactionOptions).isEnabled, ++ queryEntireHistory: _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _incomingTransactionOptions).queryEntireHistory, ++ remoteTransactionSource: etherscanRemoteTransactionSource, ++ transactionLimit: _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _transactionHistoryLimit), ++ updateTransactions: _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _incomingTransactionOptions).updateTransactions ++ }); ++ _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _addIncomingTransactionHelperListeners, addIncomingTransactionHelperListeners_fn).call(this, incomingTransactionHelper); ++ return incomingTransactionHelper; ++}; ++_createPendingTransactionTracker = new WeakSet(); ++createPendingTransactionTracker_fn = function({ ++ provider, ++ blockTracker, ++ chainId ++}) { ++ const ethQuery = new (0, _ethquery2.default)(provider); ++ const getChainId = chainId ? () => chainId : this.getChainId.bind(this); ++ const pendingTransactionTracker = new (0, _chunk6DODV6OVjs.PendingTransactionTracker)({ ++ blockTracker, ++ getChainId, ++ getEthQuery: () => ethQuery, ++ getTransactions: () => this.state.transactions, ++ isResubmitEnabled: _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _pendingTransactionOptions).isResubmitEnabled, ++ getGlobalLock: () => _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _multichainTrackingHelper).acquireNonceLockForChainIdKey({ ++ chainId: getChainId() ++ }), ++ publishTransaction: this.publishTransaction.bind(this), ++ hooks: { ++ beforeCheckPendingTransaction: this.beforeCheckPendingTransaction.bind(this), ++ beforePublish: this.beforePublish.bind(this) ++ } ++ }); ++ _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _addPendingTransactionTrackerListeners, addPendingTransactionTrackerListeners_fn).call(this, pendingTransactionTracker); ++ return pendingTransactionTracker; ++}; ++_checkForPendingTransactionAndStartPolling = new WeakMap(); ++_stopAllTracking = new WeakSet(); ++stopAllTracking_fn = function() { ++ this.pendingTransactionTracker.stop(); ++ _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _removePendingTransactionTrackerListeners, removePendingTransactionTrackerListeners_fn).call(this, this.pendingTransactionTracker); ++ this.incomingTransactionHelper.stop(); ++ _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _removeIncomingTransactionHelperListeners, removeIncomingTransactionHelperListeners_fn).call(this, this.incomingTransactionHelper); ++ _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _multichainTrackingHelper).stopAllTracking(); ++}; ++_removeIncomingTransactionHelperListeners = new WeakSet(); ++removeIncomingTransactionHelperListeners_fn = function(incomingTransactionHelper) { ++ incomingTransactionHelper.hub.removeAllListeners("transactions"); ++ incomingTransactionHelper.hub.removeAllListeners( ++ "updatedLastFetchedBlockNumbers" ++ ); ++}; ++_addIncomingTransactionHelperListeners = new WeakSet(); ++addIncomingTransactionHelperListeners_fn = function(incomingTransactionHelper) { ++ incomingTransactionHelper.hub.on( ++ "transactions", ++ this.onIncomingTransactions.bind(this) ++ ); ++ incomingTransactionHelper.hub.on( ++ "updatedLastFetchedBlockNumbers", ++ this.onUpdatedLastFetchedBlockNumbers.bind(this) ++ ); ++}; ++_removePendingTransactionTrackerListeners = new WeakSet(); ++removePendingTransactionTrackerListeners_fn = function(pendingTransactionTracker) { ++ pendingTransactionTracker.hub.removeAllListeners("transaction-confirmed"); ++ pendingTransactionTracker.hub.removeAllListeners("transaction-dropped"); ++ pendingTransactionTracker.hub.removeAllListeners("transaction-failed"); ++ pendingTransactionTracker.hub.removeAllListeners("transaction-updated"); ++}; ++_addPendingTransactionTrackerListeners = new WeakSet(); ++addPendingTransactionTrackerListeners_fn = function(pendingTransactionTracker) { ++ pendingTransactionTracker.hub.on( ++ "transaction-confirmed", ++ this.onConfirmedTransaction.bind(this) ++ ); ++ pendingTransactionTracker.hub.on( ++ "transaction-dropped", ++ this.setTransactionStatusDropped.bind(this) ++ ); ++ pendingTransactionTracker.hub.on( ++ "transaction-failed", ++ this.failTransaction.bind(this) ++ ); ++ pendingTransactionTracker.hub.on( ++ "transaction-updated", ++ this.updateTransaction.bind(this) ++ ); ++}; ++_getNonceTrackerPendingTransactions = new WeakSet(); ++getNonceTrackerPendingTransactions_fn = function(chainId, address) { ++ const standardPendingTransactions = this.getNonceTrackerTransactions( ++ "submitted" /* submitted */, ++ address, ++ chainId ++ ); ++ const externalPendingTransactions = this.getExternalPendingTransactions( ++ address, ++ chainId ++ ); ++ return [...standardPendingTransactions, ...externalPendingTransactions]; ++}; ++_getGasFeeFlows = new WeakSet(); ++getGasFeeFlows_fn = function() { ++ if (_chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _testGasFeeFlows)) { ++ return [new (0, _chunkTJMQEH57js.TestGasFeeFlow)()]; ++ } ++ return [new (0, _chunkARZHJFVGjs.LineaGasFeeFlow)(), new (0, _chunkQTKXIDGEjs.DefaultGasFeeFlow)()]; ++}; ++_getLayer1GasFeeFlows = new WeakSet(); ++getLayer1GasFeeFlows_fn = function() { ++ return [new (0, _chunkNYKRCWBGjs.OptimismLayer1GasFeeFlow)(), new (0, _chunkWR5F34OWjs.ScrollLayer1GasFeeFlow)()]; ++}; ++_updateTransactionInternal = new WeakSet(); ++updateTransactionInternal_fn = function({ ++ transactionId, ++ note, ++ skipHistory, ++ skipValidation ++}, callback) { ++ let updatedTransactionParams = []; ++ this.update((state) => { ++ const index = state.transactions.findIndex( ++ ({ id }) => id === transactionId ++ ); ++ let transactionMeta2 = state.transactions[index]; ++ transactionMeta2 = callback(transactionMeta2) ?? transactionMeta2; ++ if (skipValidation !== true) { ++ transactionMeta2.txParams = _chunkOZ6UB42Cjs.normalizeTransactionParams.call(void 0, ++ transactionMeta2.txParams ++ ); ++ _chunkRXIUMVA5js.validateTxParams.call(void 0, transactionMeta2.txParams); ++ } ++ updatedTransactionParams = _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _checkIfTransactionParamsUpdated, checkIfTransactionParamsUpdated_fn).call(this, transactionMeta2); ++ const shouldSkipHistory = this.isHistoryDisabled || skipHistory; ++ if (!shouldSkipHistory) { ++ transactionMeta2 = _chunkQP75SWIQjs.updateTransactionHistory.call(void 0, ++ transactionMeta2, ++ note ?? "Transaction updated" ++ ); ++ } ++ state.transactions[index] = transactionMeta2; ++ }); ++ const transactionMeta = this.getTransaction( ++ transactionId ++ ); ++ if (updatedTransactionParams.length > 0) { ++ _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _onTransactionParamsUpdated, onTransactionParamsUpdated_fn).call(this, transactionMeta, updatedTransactionParams); ++ } ++ return transactionMeta; ++}; ++_checkIfTransactionParamsUpdated = new WeakSet(); ++checkIfTransactionParamsUpdated_fn = function(newTransactionMeta) { ++ const { id: transactionId, txParams: newParams } = newTransactionMeta; ++ const originalParams = this.getTransaction(transactionId)?.txParams; ++ if (!originalParams || _lodash.isEqual.call(void 0, originalParams, newParams)) { ++ return []; ++ } ++ const params = Object.keys(newParams); ++ const updatedProperties = params.filter( ++ (param) => newParams[param] !== originalParams[param] ++ ); ++ _chunkS6VGOPUYjs.projectLogger.call(void 0, ++ "Transaction parameters have been updated", ++ transactionId, ++ updatedProperties, ++ originalParams, ++ newParams ++ ); ++ return updatedProperties; ++}; ++_onTransactionParamsUpdated = new WeakSet(); ++onTransactionParamsUpdated_fn = function(transactionMeta, updatedParams) { ++ if (["to", "value", "data"].some( ++ (param) => updatedParams.includes(param) ++ )) { ++ _chunkS6VGOPUYjs.projectLogger.call(void 0, "Updating simulation data due to transaction parameter update"); ++ _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _updateSimulationData, updateSimulationData_fn).call(this, transactionMeta); ++ } ++}; ++_updateSimulationData = new WeakSet(); ++updateSimulationData_fn = async function(transactionMeta) { ++ const { id: transactionId, chainId, txParams } = transactionMeta; ++ const { from, to, value, data } = txParams; ++ let simulationData = { ++ error: { ++ code: "disabled" /* Disabled */, ++ message: "Simulation disabled" ++ }, ++ tokenBalanceChanges: [] ++ }; ++ if (_chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _isSimulationEnabled).call(this)) { ++ _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _updateTransactionInternal, updateTransactionInternal_fn).call(this, { transactionId, skipHistory: true }, (txMeta) => { ++ txMeta.simulationData = void 0; ++ }); ++ simulationData = await _chunk74W7X6BEjs.getSimulationData.call(void 0, { ++ chainId, ++ from, ++ to, ++ value, ++ data ++ }); ++ } ++ const finalTransactionMeta = this.getTransaction(transactionId); ++ if (!finalTransactionMeta) { ++ _chunkS6VGOPUYjs.projectLogger.call(void 0, ++ "Cannot update simulation data as transaction not found", ++ transactionId, ++ simulationData ++ ); ++ return; ++ } ++ _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _updateTransactionInternal, updateTransactionInternal_fn).call(this, { ++ transactionId, ++ note: "TransactionController#updateSimulationData - Update simulation data" ++ }, (txMeta) => { ++ txMeta.simulationData = simulationData; ++ }); ++ _chunkS6VGOPUYjs.projectLogger.call(void 0, "Updated simulation data", transactionId, simulationData); ++}; ++_onGasFeePollerTransactionUpdate = new WeakSet(); ++onGasFeePollerTransactionUpdate_fn = function({ ++ transactionId, ++ gasFeeEstimates, ++ gasFeeEstimatesLoaded, ++ layer1GasFee ++}) { ++ _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _updateTransactionInternal, updateTransactionInternal_fn).call(this, { transactionId, skipHistory: true }, (txMeta) => { ++ if (gasFeeEstimates) { ++ txMeta.gasFeeEstimates = gasFeeEstimates; ++ } ++ if (gasFeeEstimatesLoaded !== void 0) { ++ txMeta.gasFeeEstimatesLoaded = gasFeeEstimatesLoaded; ++ } ++ if (layer1GasFee) { ++ txMeta.layer1GasFee = layer1GasFee; ++ } ++ }); ++}; ++_getNetworkClientId = new WeakSet(); ++getNetworkClientId_fn = function({ ++ networkClientId: requestNetworkClientId, ++ chainId ++}) { ++ const globalChainId = _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getGlobalChainId, getGlobalChainId_fn).call(this); ++ const globalNetworkClientId = _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getGlobalNetworkClientId, getGlobalNetworkClientId_fn).call(this); ++ if (requestNetworkClientId) { ++ return requestNetworkClientId; ++ } ++ if (!chainId || chainId === globalChainId) { ++ return globalNetworkClientId; ++ } ++ return this.messagingSystem.call( ++ `NetworkController:findNetworkClientIdByChainId`, ++ chainId ++ ); ++}; ++_getGlobalNetworkClientId = new WeakSet(); ++getGlobalNetworkClientId_fn = function() { ++ return this.getNetworkState().selectedNetworkClientId; ++}; ++_getGlobalChainId = new WeakSet(); ++getGlobalChainId_fn = function() { ++ return this.messagingSystem.call( ++ `NetworkController:getNetworkClientById`, ++ this.getNetworkState().selectedNetworkClientId ++ ).configuration.chainId; ++}; ++_isCustomNetwork = new WeakSet(); ++isCustomNetwork_fn = function(networkClientId) { ++ const globalNetworkClientId = _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getGlobalNetworkClientId, getGlobalNetworkClientId_fn).call(this); ++ if (!networkClientId || networkClientId === globalNetworkClientId) { ++ return !_controllerutils.isInfuraNetworkType.call(void 0, ++ this.getNetworkState().selectedNetworkClientId ++ ); ++ } ++ return this.messagingSystem.call( ++ `NetworkController:getNetworkClientById`, ++ networkClientId ++ ).configuration.type === _networkcontroller.NetworkClientType.Custom; ++}; ++_getSelectedAccount = new WeakSet(); ++getSelectedAccount_fn = function() { ++ return this.messagingSystem.call("AccountsController:getSelectedAccount"); ++}; ++ ++ ++ ++ ++ ++ ++ ++exports.HARDFORK = HARDFORK; exports.CANCEL_RATE = CANCEL_RATE; exports.SPEED_UP_RATE = SPEED_UP_RATE; exports.ApprovalState = ApprovalState; exports.TransactionController = TransactionController; ++//# sourceMappingURL=chunk-IVR4NMOF.js.map +\ No newline at end of file +diff --git a/dist/chunk-IVR4NMOF.js.map b/dist/chunk-IVR4NMOF.js.map +new file mode 100644 +index 0000000000000000000000000000000000000000..e06df9082a1bf650bafaf9b2a3602a8c597cf6ed +--- /dev/null ++++ b/dist/chunk-IVR4NMOF.js.map +@@ -0,0 +1 @@ ++{"version":3,"sources":["../src/TransactionController.ts"],"names":["ApprovalState","transactionMeta"],"mappings":"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","sourcesContent":["import { Hardfork, Common, type ChainConfig } from '@ethereumjs/common';\nimport type { TypedTransaction } from '@ethereumjs/tx';\nimport { TransactionFactory } from '@ethereumjs/tx';\nimport { bufferToHex } from '@ethereumjs/util';\nimport type { AccountsControllerGetSelectedAccountAction } from '@metamask/accounts-controller';\nimport type {\n AcceptResultCallbacks,\n AddApprovalRequest,\n AddResult,\n} from '@metamask/approval-controller';\nimport type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n RestrictedControllerMessenger,\n} from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport {\n query,\n ApprovalType,\n ORIGIN_METAMASK,\n convertHexToDecimal,\n isInfuraNetworkType,\n} from '@metamask/controller-utils';\nimport EthQuery from '@metamask/eth-query';\nimport type {\n FetchGasFeeEstimateOptions,\n GasFeeState,\n} from '@metamask/gas-fee-controller';\nimport type {\n BlockTracker,\n NetworkClientId,\n NetworkController,\n NetworkControllerStateChangeEvent,\n NetworkState,\n Provider,\n NetworkControllerFindNetworkClientIdByChainIdAction,\n NetworkControllerGetNetworkClientByIdAction,\n} from '@metamask/network-controller';\nimport { NetworkClientType } from '@metamask/network-controller';\nimport type {\n NonceLock,\n Transaction as NonceTrackerTransaction,\n} from '@metamask/nonce-tracker';\nimport { NonceTracker } from '@metamask/nonce-tracker';\nimport { errorCodes, rpcErrors, providerErrors } from '@metamask/rpc-errors';\nimport type { Hex } from '@metamask/utils';\nimport { add0x } from '@metamask/utils';\nimport { Mutex } from 'async-mutex';\nimport { MethodRegistry } from 'eth-method-registry';\nimport { EventEmitter } from 'events';\nimport { cloneDeep, mapValues, merge, pickBy, sortBy, isEqual } from 'lodash';\nimport { v1 as random } from 'uuid';\n\nimport { DefaultGasFeeFlow } from './gas-flows/DefaultGasFeeFlow';\nimport { LineaGasFeeFlow } from './gas-flows/LineaGasFeeFlow';\nimport { OptimismLayer1GasFeeFlow } from './gas-flows/OptimismLayer1GasFeeFlow';\nimport { ScrollLayer1GasFeeFlow } from './gas-flows/ScrollLayer1GasFeeFlow';\nimport { TestGasFeeFlow } from './gas-flows/TestGasFeeFlow';\nimport { EtherscanRemoteTransactionSource } from './helpers/EtherscanRemoteTransactionSource';\nimport { GasFeePoller } from './helpers/GasFeePoller';\nimport type { IncomingTransactionOptions } from './helpers/IncomingTransactionHelper';\nimport { IncomingTransactionHelper } from './helpers/IncomingTransactionHelper';\nimport { MultichainTrackingHelper } from './helpers/MultichainTrackingHelper';\nimport { PendingTransactionTracker } from './helpers/PendingTransactionTracker';\nimport { projectLogger as log } from './logger';\nimport type {\n DappSuggestedGasFees,\n Layer1GasFeeFlow,\n SavedGasFees,\n SecurityProviderRequest,\n SendFlowHistoryEntry,\n TransactionParams,\n TransactionMeta,\n TransactionReceipt,\n WalletDevice,\n SecurityAlertResponse,\n GasFeeFlow,\n SimulationData,\n GasFeeEstimates,\n GasFeeFlowResponse,\n} from './types';\nimport {\n TransactionEnvelopeType,\n TransactionType,\n TransactionStatus,\n SimulationErrorCode,\n} from './types';\nimport { validateConfirmedExternalTransaction } from './utils/external-transactions';\nimport { addGasBuffer, estimateGas, updateGas } from './utils/gas';\nimport { updateGasFees } from './utils/gas-fees';\nimport { getGasFeeFlow } from './utils/gas-flow';\nimport {\n addInitialHistorySnapshot,\n updateTransactionHistory,\n} from './utils/history';\nimport {\n getTransactionLayer1GasFee,\n updateTransactionLayer1GasFee,\n} from './utils/layer1-gas-fee-flow';\nimport {\n getAndFormatTransactionsForNonceTracker,\n getNextNonce,\n} from './utils/nonce';\nimport { getSimulationData } from './utils/simulation';\nimport {\n updatePostTransactionBalance,\n updateSwapsTransaction,\n} from './utils/swaps';\nimport { determineTransactionType } from './utils/transaction-type';\nimport {\n getIncreasedPriceFromExisting,\n normalizeTransactionParams,\n isEIP1559Transaction,\n isFeeMarketEIP1559Values,\n isGasPriceValue,\n validateGasValues,\n validateIfTransactionUnapproved,\n validateMinimumIncrease,\n normalizeTxError,\n normalizeGasFeeValues,\n} from './utils/utils';\nimport {\n validateTransactionOrigin,\n validateTxParams,\n} from './utils/validation';\n\n/**\n * Metadata for the TransactionController state, describing how to \"anonymize\"\n * the state and which parts should be persisted.\n */\nconst metadata = {\n transactions: {\n persist: true,\n anonymous: false,\n },\n methodData: {\n persist: true,\n anonymous: false,\n },\n lastFetchedBlockNumbers: {\n persist: true,\n anonymous: false,\n },\n};\n\nexport const HARDFORK = Hardfork.London;\n\n/**\n * Object with new transaction's meta and a promise resolving to the\n * transaction hash if successful.\n *\n * @property result - Promise resolving to a new transaction hash\n * @property transactionMeta - Meta information about this new transaction\n */\n// This interface was created before this ESLint rule was added.\n// Convert to a `type` in a future major version.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\nexport interface Result {\n result: Promise;\n transactionMeta: TransactionMeta;\n}\n\n// This interface was created before this ESLint rule was added.\n// Convert to a `type` in a future major version.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\nexport interface GasPriceValue {\n gasPrice: string;\n}\n\n// This interface was created before this ESLint rule was added.\n// Convert to a `type` in a future major version.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\nexport interface FeeMarketEIP1559Values {\n maxFeePerGas: string;\n maxPriorityFeePerGas: string;\n}\n\n/**\n * Method data registry object\n *\n * @property registryMethod - Registry method raw string\n * @property parsedRegistryMethod - Registry method object, containing name and method arguments\n */\nexport type MethodData = {\n registryMethod: string;\n parsedRegistryMethod:\n | {\n name: string;\n args: { type: string }[];\n }\n | {\n // We're using `any` instead of `undefined` for compatibility with `Json`\n // TODO: Correct this type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n name?: any;\n // We're using `any` instead of `undefined` for compatibility with `Json`\n // TODO: Correct this type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n args?: any;\n };\n};\n\n/**\n * Transaction controller state\n *\n * @property transactions - A list of TransactionMeta objects\n * @property methodData - Object containing all known method data information\n * @property lastFetchedBlockNumbers - Last fetched block numbers.\n */\nexport type TransactionControllerState = {\n transactions: TransactionMeta[];\n methodData: Record;\n lastFetchedBlockNumbers: { [key: string]: number };\n};\n\n/**\n * Multiplier used to determine a transaction's increased gas fee during cancellation\n */\nexport const CANCEL_RATE = 1.1;\n\n/**\n * Multiplier used to determine a transaction's increased gas fee during speed up\n */\nexport const SPEED_UP_RATE = 1.1;\n\n/**\n * Represents the `TransactionController:getState` action.\n */\nexport type TransactionControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n TransactionControllerState\n>;\n\n/**\n * The internal actions available to the TransactionController.\n */\nexport type TransactionControllerActions = TransactionControllerGetStateAction;\n\n/**\n * Configuration options for the PendingTransactionTracker\n *\n * @property isResubmitEnabled - Whether transaction publishing is automatically retried.\n */\nexport type PendingTransactionOptions = {\n isResubmitEnabled?: () => boolean;\n};\n\n/**\n * TransactionController constructor options.\n *\n * @property blockTracker - The block tracker used to poll for new blocks data.\n * @property disableHistory - Whether to disable storing history in transaction metadata.\n * @property disableSendFlowHistory - Explicitly disable transaction metadata history.\n * @property disableSwaps - Whether to disable additional processing on swaps transactions.\n * @property getCurrentAccountEIP1559Compatibility - Whether or not the account supports EIP-1559.\n * @property getCurrentNetworkEIP1559Compatibility - Whether or not the network supports EIP-1559.\n * @property getExternalPendingTransactions - Callback to retrieve pending transactions from external sources.\n * @property getGasFeeEstimates - Callback to retrieve gas fee estimates.\n * @property getNetworkClientRegistry - Gets the network client registry.\n * @property getNetworkState - Gets the state of the network controller.\n * @property getPermittedAccounts - Get accounts that a given origin has permissions for.\n * @property getSavedGasFees - Gets the saved gas fee config.\n * @property getSelectedAddress - Gets the address of the currently selected account.\n * @property incomingTransactions - Configuration options for incoming transaction support.\n * @property isMultichainEnabled - Enable multichain support.\n * @property isSimulationEnabled - Whether new transactions will be automatically simulated.\n * @property messenger - The controller messenger.\n * @property onNetworkStateChange - Allows subscribing to network controller state changes.\n * @property pendingTransactions - Configuration options for pending transaction support.\n * @property provider - The provider used to create the underlying EthQuery instance.\n * @property securityProviderRequest - A function for verifying a transaction, whether it is malicious or not.\n * @property sign - Function used to sign transactions.\n * @property state - Initial state to set on this controller.\n * @property transactionHistoryLimit - Transaction history limit.\n * @property hooks - The controller hooks.\n * @property hooks.afterSign - Additional logic to execute after signing a transaction. Return false to not change the status to signed.\n * @property hooks.beforeCheckPendingTransaction - Additional logic to execute before checking pending transactions. Return false to prevent the broadcast of the transaction.\n * @property hooks.beforePublish - Additional logic to execute before publishing a transaction. Return false to prevent the broadcast of the transaction.\n * @property hooks.getAdditionalSignArguments - Returns additional arguments required to sign a transaction.\n * @property hooks.publish - Alternate logic to publish a transaction.\n */\nexport type TransactionControllerOptions = {\n blockTracker: BlockTracker;\n disableHistory: boolean;\n disableSendFlowHistory: boolean;\n disableSwaps: boolean;\n getCurrentAccountEIP1559Compatibility?: () => Promise;\n getCurrentNetworkEIP1559Compatibility: () => Promise;\n getExternalPendingTransactions?: (\n address: string,\n chainId?: string,\n ) => NonceTrackerTransaction[];\n getGasFeeEstimates?: (\n options: FetchGasFeeEstimateOptions,\n ) => Promise;\n getNetworkClientRegistry: NetworkController['getNetworkClientRegistry'];\n getNetworkState: () => NetworkState;\n getPermittedAccounts: (origin?: string) => Promise;\n getSavedGasFees?: (chainId: Hex) => SavedGasFees | undefined;\n incomingTransactions?: IncomingTransactionOptions;\n isMultichainEnabled: boolean;\n isSimulationEnabled?: () => boolean;\n messenger: TransactionControllerMessenger;\n onNetworkStateChange: (listener: (state: NetworkState) => void) => void;\n pendingTransactions?: PendingTransactionOptions;\n provider: Provider;\n securityProviderRequest?: SecurityProviderRequest;\n sign?: (\n transaction: TypedTransaction,\n from: string,\n transactionMeta?: TransactionMeta,\n ) => Promise;\n state?: Partial;\n testGasFeeFlows?: boolean;\n transactionHistoryLimit: number;\n hooks: {\n afterSign?: (\n transactionMeta: TransactionMeta,\n signedTx: TypedTransaction,\n ) => boolean;\n beforeCheckPendingTransaction?: (\n transactionMeta: TransactionMeta,\n ) => boolean;\n beforePublish?: (transactionMeta: TransactionMeta) => boolean;\n getAdditionalSignArguments?: (\n transactionMeta: TransactionMeta,\n ) => (TransactionMeta | undefined)[];\n publish?: (\n transactionMeta: TransactionMeta,\n ) => Promise<{ transactionHash: string }>;\n };\n};\n\n/**\n * The name of the {@link TransactionController}.\n */\nconst controllerName = 'TransactionController';\n\n/**\n * The external actions available to the {@link TransactionController}.\n */\nexport type AllowedActions =\n | AddApprovalRequest\n | NetworkControllerFindNetworkClientIdByChainIdAction\n | NetworkControllerGetNetworkClientByIdAction\n | AccountsControllerGetSelectedAccountAction;\n\n/**\n * The external events available to the {@link TransactionController}.\n */\nexport type AllowedEvents = NetworkControllerStateChangeEvent;\n\n/**\n * Represents the `TransactionController:stateChange` event.\n */\nexport type TransactionControllerStateChangeEvent = ControllerStateChangeEvent<\n typeof controllerName,\n TransactionControllerState\n>;\n\n/**\n * Represents the `TransactionController:incomingTransactionBlockReceived` event.\n */\nexport type TransactionControllerIncomingTransactionBlockReceivedEvent = {\n type: `${typeof controllerName}:incomingTransactionBlockReceived`;\n payload: [blockNumber: number];\n};\n\n/**\n * Represents the `TransactionController:postTransactionBalanceUpdated` event.\n */\nexport type TransactionControllerPostTransactionBalanceUpdatedEvent = {\n type: `${typeof controllerName}:postTransactionBalanceUpdated`;\n payload: [\n {\n transactionMeta: TransactionMeta;\n approvalTransactionMeta?: TransactionMeta;\n },\n ];\n};\n\n/**\n * Represents the `TransactionController:speedUpTransactionAdded` event.\n */\nexport type TransactionControllerSpeedupTransactionAddedEvent = {\n type: `${typeof controllerName}:speedupTransactionAdded`;\n payload: [transactionMeta: TransactionMeta];\n};\n\n/**\n * Represents the `TransactionController:transactionApproved` event.\n */\nexport type TransactionControllerTransactionApprovedEvent = {\n type: `${typeof controllerName}:transactionApproved`;\n payload: [\n {\n transactionMeta: TransactionMeta;\n actionId?: string;\n },\n ];\n};\n\n/**\n * Represents the `TransactionController:transactionConfirmed` event.\n */\nexport type TransactionControllerTransactionConfirmedEvent = {\n type: `${typeof controllerName}:transactionConfirmed`;\n payload: [transactionMeta: TransactionMeta];\n};\n\n/**\n * Represents the `TransactionController:transactionDropped` event.\n */\nexport type TransactionControllerTransactionDroppedEvent = {\n type: `${typeof controllerName}:transactionDropped`;\n payload: [{ transactionMeta: TransactionMeta }];\n};\n\n/**\n * Represents the `TransactionController:transactionFailed` event.\n */\nexport type TransactionControllerTransactionFailedEvent = {\n type: `${typeof controllerName}:transactionFailed`;\n payload: [\n {\n actionId?: string;\n error: string;\n transactionMeta: TransactionMeta;\n },\n ];\n};\n\n/**\n * Represents the `TransactionController:transactionFinished` event.\n */\nexport type TransactionControllerTransactionFinishedEvent = {\n type: `${typeof controllerName}:transactionFinished`;\n payload: [transactionMeta: TransactionMeta];\n};\n\n/**\n * Represents the `TransactionController:transactionNewSwapApproval` event.\n */\nexport type TransactionControllerTransactionNewSwapApprovalEvent = {\n type: `${typeof controllerName}:transactionNewSwapApproval`;\n payload: [{ transactionMeta: TransactionMeta }];\n};\n\n/**\n * Represents the `TransactionController:transactionNewSwap` event.\n */\nexport type TransactionControllerTransactionNewSwapEvent = {\n type: `${typeof controllerName}:transactionNewSwap`;\n payload: [{ transactionMeta: TransactionMeta }];\n};\n\n/**\n * Represents the `TransactionController:transactionNewSwapApproval` event.\n */\nexport type TransactionControllerTransactionNewSwapAndSendEvent = {\n type: `${typeof controllerName}:transactionNewSwapAndSend`;\n payload: [{ transactionMeta: TransactionMeta }];\n};\n\n/**\n * Represents the `TransactionController:transactionPublishingSkipped` event.\n */\nexport type TransactionControllerTransactionPublishingSkipped = {\n type: `${typeof controllerName}:transactionPublishingSkipped`;\n payload: [transactionMeta: TransactionMeta];\n};\n\n/**\n * Represents the `TransactionController:transactionRejected` event.\n */\nexport type TransactionControllerTransactionRejectedEvent = {\n type: `${typeof controllerName}:transactionRejected`;\n payload: [\n {\n transactionMeta: TransactionMeta;\n actionId?: string;\n },\n ];\n};\n\n/**\n * Represents the `TransactionController:transactionStatusUpdated` event.\n */\nexport type TransactionControllerTransactionStatusUpdatedEvent = {\n type: `${typeof controllerName}:transactionStatusUpdated`;\n payload: [\n {\n transactionMeta: TransactionMeta;\n },\n ];\n};\n\n/**\n * Represents the `TransactionController:transactionSubmitted` event.\n */\nexport type TransactionControllerTransactionSubmittedEvent = {\n type: `${typeof controllerName}:transactionSubmitted`;\n payload: [\n {\n transactionMeta: TransactionMeta;\n actionId?: string;\n },\n ];\n};\n\n/**\n * Represents the `TransactionController:unapprovedTransactionAdded` event.\n */\nexport type TransactionControllerUnapprovedTransactionAddedEvent = {\n type: `${typeof controllerName}:unapprovedTransactionAdded`;\n payload: [transactionMeta: TransactionMeta];\n};\n\n/**\n * The internal events available to the {@link TransactionController}.\n */\nexport type TransactionControllerEvents =\n | TransactionControllerIncomingTransactionBlockReceivedEvent\n | TransactionControllerPostTransactionBalanceUpdatedEvent\n | TransactionControllerSpeedupTransactionAddedEvent\n | TransactionControllerStateChangeEvent\n | TransactionControllerTransactionApprovedEvent\n | TransactionControllerTransactionConfirmedEvent\n | TransactionControllerTransactionDroppedEvent\n | TransactionControllerTransactionFailedEvent\n | TransactionControllerTransactionFinishedEvent\n | TransactionControllerTransactionNewSwapApprovalEvent\n | TransactionControllerTransactionNewSwapEvent\n | TransactionControllerTransactionNewSwapAndSendEvent\n | TransactionControllerTransactionPublishingSkipped\n | TransactionControllerTransactionRejectedEvent\n | TransactionControllerTransactionStatusUpdatedEvent\n | TransactionControllerTransactionSubmittedEvent\n | TransactionControllerUnapprovedTransactionAddedEvent;\n\n/**\n * The messenger of the {@link TransactionController}.\n */\nexport type TransactionControllerMessenger = RestrictedControllerMessenger<\n typeof controllerName,\n TransactionControllerActions | AllowedActions,\n TransactionControllerEvents | AllowedEvents,\n AllowedActions['type'],\n AllowedEvents['type']\n>;\n\n/**\n * Possible states of the approve transaction step.\n */\nexport enum ApprovalState {\n Approved = 'approved',\n NotApproved = 'not-approved',\n SkippedViaBeforePublishHook = 'skipped-via-before-publish-hook',\n}\n\n/**\n * Get the default TransactionsController state.\n *\n * @returns The default TransactionsController state.\n */\nfunction getDefaultTransactionControllerState(): TransactionControllerState {\n return {\n methodData: {},\n transactions: [],\n lastFetchedBlockNumbers: {},\n };\n}\n\n/**\n * Controller responsible for submitting and managing transactions.\n */\nexport class TransactionController extends BaseController<\n typeof controllerName,\n TransactionControllerState,\n TransactionControllerMessenger\n> {\n #internalEvents = new EventEmitter();\n\n private readonly isHistoryDisabled: boolean;\n\n private readonly isSwapsDisabled: boolean;\n\n private readonly isSendFlowHistoryDisabled: boolean;\n\n private readonly approvingTransactionIds: Set = new Set();\n\n private readonly nonceTracker: NonceTracker;\n\n private readonly registry: MethodRegistry;\n\n private readonly mutex = new Mutex();\n\n private readonly gasFeeFlows: GasFeeFlow[];\n\n private readonly getSavedGasFees: (chainId: Hex) => SavedGasFees | undefined;\n\n private readonly getNetworkState: () => NetworkState;\n\n private readonly getCurrentAccountEIP1559Compatibility: () => Promise;\n\n private readonly getCurrentNetworkEIP1559Compatibility: (\n networkClientId?: NetworkClientId,\n ) => Promise;\n\n private readonly getGasFeeEstimates: (\n options: FetchGasFeeEstimateOptions,\n ) => Promise;\n\n private readonly getPermittedAccounts: (origin?: string) => Promise;\n\n private readonly getExternalPendingTransactions: (\n address: string,\n chainId?: string,\n ) => NonceTrackerTransaction[];\n\n private readonly layer1GasFeeFlows: Layer1GasFeeFlow[];\n\n readonly #incomingTransactionOptions: IncomingTransactionOptions;\n\n private readonly incomingTransactionHelper: IncomingTransactionHelper;\n\n private readonly securityProviderRequest?: SecurityProviderRequest;\n\n readonly #pendingTransactionOptions: PendingTransactionOptions;\n\n private readonly pendingTransactionTracker: PendingTransactionTracker;\n\n private readonly signAbortCallbacks: Map void> = new Map();\n\n #transactionHistoryLimit: number;\n\n #isSimulationEnabled: () => boolean;\n\n #testGasFeeFlows: boolean;\n\n private readonly afterSign: (\n transactionMeta: TransactionMeta,\n signedTx: TypedTransaction,\n ) => boolean;\n\n private readonly beforeCheckPendingTransaction: (\n transactionMeta: TransactionMeta,\n ) => boolean;\n\n private readonly beforePublish: (transactionMeta: TransactionMeta) => boolean;\n\n private readonly publish: (\n transactionMeta: TransactionMeta,\n rawTx: string,\n ) => Promise<{ transactionHash?: string }>;\n\n private readonly getAdditionalSignArguments: (\n transactionMeta: TransactionMeta,\n ) => (TransactionMeta | undefined)[];\n\n private failTransaction(\n transactionMeta: TransactionMeta,\n error: Error,\n actionId?: string,\n ) {\n let newTransactionMeta: TransactionMeta;\n\n try {\n newTransactionMeta = this.#updateTransactionInternal(\n {\n transactionId: transactionMeta.id,\n note: 'TransactionController#failTransaction - Add error message and set status to failed',\n skipValidation: true,\n },\n (draftTransactionMeta) => {\n draftTransactionMeta.status = TransactionStatus.failed;\n\n (\n draftTransactionMeta as TransactionMeta & {\n status: TransactionStatus.failed;\n }\n ).error = normalizeTxError(error);\n },\n );\n } catch (err: unknown) {\n log('Failed to mark transaction as failed', err);\n\n newTransactionMeta = {\n ...transactionMeta,\n status: TransactionStatus.failed,\n error: normalizeTxError(error),\n };\n }\n\n this.messagingSystem.publish(`${controllerName}:transactionFailed`, {\n actionId,\n error: error.message,\n transactionMeta: newTransactionMeta,\n });\n\n this.onTransactionStatusChange(newTransactionMeta);\n\n this.messagingSystem.publish(\n `${controllerName}:transactionFinished`,\n newTransactionMeta,\n );\n\n this.#internalEvents.emit(\n `${transactionMeta.id}:finished`,\n newTransactionMeta,\n );\n }\n\n private async registryLookup(fourBytePrefix: string): Promise {\n const registryMethod = await this.registry.lookup(fourBytePrefix);\n if (!registryMethod) {\n return {\n registryMethod: '',\n parsedRegistryMethod: { name: undefined, args: undefined },\n };\n }\n const parsedRegistryMethod = this.registry.parse(registryMethod);\n return { registryMethod, parsedRegistryMethod };\n }\n\n #multichainTrackingHelper: MultichainTrackingHelper;\n\n /**\n * Method used to sign transactions\n */\n sign?: (\n transaction: TypedTransaction,\n from: string,\n transactionMeta?: TransactionMeta,\n ) => Promise;\n\n /**\n * Constructs a TransactionController.\n *\n * @param options - The controller options.\n * @param options.blockTracker - The block tracker used to poll for new blocks data.\n * @param options.disableHistory - Whether to disable storing history in transaction metadata.\n * @param options.disableSendFlowHistory - Explicitly disable transaction metadata history.\n * @param options.disableSwaps - Whether to disable additional processing on swaps transactions.\n * @param options.getCurrentAccountEIP1559Compatibility - Whether or not the account supports EIP-1559.\n * @param options.getCurrentNetworkEIP1559Compatibility - Whether or not the network supports EIP-1559.\n * @param options.getExternalPendingTransactions - Callback to retrieve pending transactions from external sources.\n * @param options.getGasFeeEstimates - Callback to retrieve gas fee estimates.\n * @param options.getNetworkClientRegistry - Gets the network client registry.\n * @param options.getNetworkState - Gets the state of the network controller.\n * @param options.getPermittedAccounts - Get accounts that a given origin has permissions for.\n * @param options.getSavedGasFees - Gets the saved gas fee config.\n * @param options.incomingTransactions - Configuration options for incoming transaction support.\n * @param options.isMultichainEnabled - Enable multichain support.\n * @param options.isSimulationEnabled - Whether new transactions will be automatically simulated.\n * @param options.messenger - The controller messenger.\n * @param options.onNetworkStateChange - Allows subscribing to network controller state changes.\n * @param options.pendingTransactions - Configuration options for pending transaction support.\n * @param options.provider - The provider used to create the underlying EthQuery instance.\n * @param options.securityProviderRequest - A function for verifying a transaction, whether it is malicious or not.\n * @param options.sign - Function used to sign transactions.\n * @param options.state - Initial state to set on this controller.\n * @param options.testGasFeeFlows - Whether to use the test gas fee flow.\n * @param options.transactionHistoryLimit - Transaction history limit.\n * @param options.hooks - The controller hooks.\n */\n constructor({\n blockTracker,\n disableHistory,\n disableSendFlowHistory,\n disableSwaps,\n getCurrentAccountEIP1559Compatibility,\n getCurrentNetworkEIP1559Compatibility,\n getExternalPendingTransactions,\n getGasFeeEstimates,\n getNetworkClientRegistry,\n getNetworkState,\n getPermittedAccounts,\n getSavedGasFees,\n incomingTransactions = {},\n isMultichainEnabled = false,\n isSimulationEnabled,\n messenger,\n onNetworkStateChange,\n pendingTransactions = {},\n provider,\n securityProviderRequest,\n sign,\n state,\n testGasFeeFlows,\n transactionHistoryLimit = 40,\n hooks,\n }: TransactionControllerOptions) {\n super({\n name: controllerName,\n metadata,\n messenger,\n state: {\n ...getDefaultTransactionControllerState(),\n ...state,\n },\n });\n\n this.messagingSystem = messenger;\n this.getNetworkState = getNetworkState;\n this.isSendFlowHistoryDisabled = disableSendFlowHistory ?? false;\n this.isHistoryDisabled = disableHistory ?? false;\n this.isSwapsDisabled = disableSwaps ?? false;\n this.#isSimulationEnabled = isSimulationEnabled ?? (() => true);\n // @ts-expect-error the type in eth-method-registry is inappropriate and should be changed\n this.registry = new MethodRegistry({ provider });\n this.getSavedGasFees = getSavedGasFees ?? ((_chainId) => undefined);\n this.getCurrentAccountEIP1559Compatibility =\n getCurrentAccountEIP1559Compatibility ?? (() => Promise.resolve(true));\n this.getCurrentNetworkEIP1559Compatibility =\n getCurrentNetworkEIP1559Compatibility;\n this.getGasFeeEstimates =\n getGasFeeEstimates || (() => Promise.resolve({} as GasFeeState));\n this.getPermittedAccounts = getPermittedAccounts;\n this.getExternalPendingTransactions =\n getExternalPendingTransactions ?? (() => []);\n this.securityProviderRequest = securityProviderRequest;\n this.#incomingTransactionOptions = incomingTransactions;\n this.#pendingTransactionOptions = pendingTransactions;\n this.#transactionHistoryLimit = transactionHistoryLimit;\n this.sign = sign;\n this.#testGasFeeFlows = testGasFeeFlows === true;\n\n this.afterSign = hooks?.afterSign ?? (() => true);\n this.beforeCheckPendingTransaction =\n hooks?.beforeCheckPendingTransaction ??\n /* istanbul ignore next */\n (() => true);\n this.beforePublish = hooks?.beforePublish ?? (() => true);\n this.getAdditionalSignArguments =\n hooks?.getAdditionalSignArguments ?? (() => []);\n this.publish =\n hooks?.publish ?? (() => Promise.resolve({ transactionHash: undefined }));\n\n this.nonceTracker = this.#createNonceTracker({\n provider,\n blockTracker,\n });\n\n const findNetworkClientIdByChainId = (chainId: Hex) => {\n return this.messagingSystem.call(\n `NetworkController:findNetworkClientIdByChainId`,\n chainId,\n );\n };\n\n this.#multichainTrackingHelper = new MultichainTrackingHelper({\n isMultichainEnabled,\n provider,\n nonceTracker: this.nonceTracker,\n incomingTransactionOptions: incomingTransactions,\n findNetworkClientIdByChainId,\n getNetworkClientById: ((networkClientId: NetworkClientId) => {\n return this.messagingSystem.call(\n `NetworkController:getNetworkClientById`,\n networkClientId,\n );\n }) as NetworkController['getNetworkClientById'],\n getNetworkClientRegistry,\n removeIncomingTransactionHelperListeners:\n this.#removeIncomingTransactionHelperListeners.bind(this),\n removePendingTransactionTrackerListeners:\n this.#removePendingTransactionTrackerListeners.bind(this),\n createNonceTracker: this.#createNonceTracker.bind(this),\n createIncomingTransactionHelper:\n this.#createIncomingTransactionHelper.bind(this),\n createPendingTransactionTracker:\n this.#createPendingTransactionTracker.bind(this),\n onNetworkStateChange: (listener) => {\n this.messagingSystem.subscribe(\n 'NetworkController:stateChange',\n listener,\n );\n },\n });\n this.#multichainTrackingHelper.initialize();\n\n const etherscanRemoteTransactionSource =\n new EtherscanRemoteTransactionSource({\n includeTokenTransfers: incomingTransactions.includeTokenTransfers,\n });\n\n this.incomingTransactionHelper = this.#createIncomingTransactionHelper({\n blockTracker,\n etherscanRemoteTransactionSource,\n });\n\n this.pendingTransactionTracker = this.#createPendingTransactionTracker({\n provider,\n blockTracker,\n });\n\n this.gasFeeFlows = this.#getGasFeeFlows();\n this.layer1GasFeeFlows = this.#getLayer1GasFeeFlows();\n\n const gasFeePoller = new GasFeePoller({\n findNetworkClientIdByChainId,\n gasFeeFlows: this.gasFeeFlows,\n getGasFeeControllerEstimates: this.getGasFeeEstimates,\n getProvider: (chainId, networkClientId) =>\n this.#multichainTrackingHelper.getProvider({\n networkClientId,\n chainId,\n }),\n getTransactions: () => this.state.transactions,\n layer1GasFeeFlows: this.layer1GasFeeFlows,\n onStateChange: (listener) => {\n this.messagingSystem.subscribe(\n 'TransactionController:stateChange',\n listener,\n );\n },\n });\n\n gasFeePoller.hub.on(\n 'transaction-updated',\n this.#onGasFeePollerTransactionUpdate.bind(this),\n );\n\n // when transactionsController state changes\n // check for pending transactions and start polling if there are any\n this.messagingSystem.subscribe(\n 'TransactionController:stateChange',\n this.#checkForPendingTransactionAndStartPolling,\n );\n\n // TODO once v2 is merged make sure this only runs when\n // selectedNetworkClientId changes\n onNetworkStateChange(() => {\n log('Detected network change', this.getChainId());\n this.pendingTransactionTracker.startIfPendingTransactions();\n this.onBootCleanup();\n });\n\n this.onBootCleanup();\n this.#checkForPendingTransactionAndStartPolling();\n }\n\n /**\n * Stops polling and removes listeners to prepare the controller for garbage collection.\n */\n destroy() {\n this.#stopAllTracking();\n }\n\n /**\n * Handle new method data request.\n *\n * @param fourBytePrefix - The method prefix.\n * @returns The method data object corresponding to the given signature prefix.\n */\n async handleMethodData(fourBytePrefix: string): Promise {\n const releaseLock = await this.mutex.acquire();\n try {\n const { methodData } = this.state;\n const knownMethod = Object.keys(methodData).find(\n (knownFourBytePrefix) => fourBytePrefix === knownFourBytePrefix,\n );\n if (knownMethod) {\n return methodData[fourBytePrefix];\n }\n const registry = await this.registryLookup(fourBytePrefix);\n this.update((state) => {\n state.methodData[fourBytePrefix] = registry;\n });\n return registry;\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Add a new unapproved transaction to state. Parameters will be validated, a\n * unique transaction id will be generated, and gas and gasPrice will be calculated\n * if not provided. If A `:unapproved` hub event will be emitted once added.\n *\n * @param txParams - Standard parameters for an Ethereum transaction.\n * @param opts - Additional options to control how the transaction is added.\n * @param opts.actionId - Unique ID to prevent duplicate requests.\n * @param opts.deviceConfirmedOn - An enum to indicate what device confirmed the transaction.\n * @param opts.method - RPC method that requested the transaction.\n * @param opts.origin - The origin of the transaction request, such as a dApp hostname.\n * @param opts.requireApproval - Whether the transaction requires approval by the user, defaults to true unless explicitly disabled.\n * @param opts.securityAlertResponse - Response from security validator.\n * @param opts.sendFlowHistory - The sendFlowHistory entries to add.\n * @param opts.type - Type of transaction to add, such as 'cancel' or 'swap'.\n * @param opts.swaps - Options for swaps transactions.\n * @param opts.swaps.hasApproveTx - Whether the transaction has an approval transaction.\n * @param opts.swaps.meta - Metadata for swap transaction.\n * @param opts.networkClientId - The id of the network client for this transaction.\n * @returns Object containing a promise resolving to the transaction hash if approved.\n */\n async addTransaction(\n txParams: TransactionParams,\n {\n actionId,\n deviceConfirmedOn,\n method,\n origin,\n requireApproval,\n securityAlertResponse,\n sendFlowHistory,\n swaps = {},\n type,\n networkClientId: requestNetworkClientId,\n }: {\n actionId?: string;\n deviceConfirmedOn?: WalletDevice;\n method?: string;\n origin?: string;\n requireApproval?: boolean | undefined;\n securityAlertResponse?: SecurityAlertResponse;\n sendFlowHistory?: SendFlowHistoryEntry[];\n swaps?: {\n hasApproveTx?: boolean;\n meta?: Partial;\n };\n type?: TransactionType;\n networkClientId?: NetworkClientId;\n } = {},\n ): Promise {\n log('Adding transaction', txParams);\n\n txParams = normalizeTransactionParams(txParams);\n if (\n requestNetworkClientId &&\n !this.#multichainTrackingHelper.has(requestNetworkClientId)\n ) {\n throw new Error(\n 'The networkClientId for this transaction could not be found',\n );\n }\n\n const networkClientId =\n requestNetworkClientId ?? this.#getGlobalNetworkClientId();\n\n const isEIP1559Compatible = await this.getEIP1559Compatibility(\n networkClientId,\n );\n\n validateTxParams(txParams, isEIP1559Compatible);\n\n if (origin) {\n await validateTransactionOrigin(\n await this.getPermittedAccounts(origin),\n this.#getSelectedAccount().address,\n txParams.from,\n origin,\n );\n }\n\n const dappSuggestedGasFees = this.generateDappSuggestedGasFees(\n txParams,\n origin,\n );\n\n const chainId = this.getChainId(networkClientId);\n const ethQuery = this.#multichainTrackingHelper.getEthQuery({\n networkClientId,\n chainId,\n });\n\n const transactionType =\n type ?? (await determineTransactionType(txParams, ethQuery)).type;\n\n const existingTransactionMeta = this.getTransactionWithActionId(actionId);\n\n // If a request to add a transaction with the same actionId is submitted again, a new transaction will not be created for it.\n let addedTransactionMeta = existingTransactionMeta\n ? cloneDeep(existingTransactionMeta)\n : {\n // Add actionId to txMeta to check if same actionId is seen again\n actionId,\n chainId,\n dappSuggestedGasFees,\n deviceConfirmedOn,\n id: random(),\n origin,\n securityAlertResponse,\n status: TransactionStatus.unapproved as const,\n time: Date.now(),\n txParams,\n userEditedGasLimit: false,\n verifiedOnBlockchain: false,\n type: transactionType,\n networkClientId,\n };\n\n await this.updateGasProperties(addedTransactionMeta);\n\n // Checks if a transaction already exists with a given actionId\n if (!existingTransactionMeta) {\n // Set security provider response\n if (method && this.securityProviderRequest) {\n const securityProviderResponse = await this.securityProviderRequest(\n addedTransactionMeta,\n method,\n );\n addedTransactionMeta.securityProviderResponse =\n securityProviderResponse;\n }\n\n if (!this.isSendFlowHistoryDisabled) {\n addedTransactionMeta.sendFlowHistory = sendFlowHistory ?? [];\n }\n // Initial history push\n if (!this.isHistoryDisabled) {\n addedTransactionMeta = addInitialHistorySnapshot(addedTransactionMeta);\n }\n\n addedTransactionMeta = updateSwapsTransaction(\n addedTransactionMeta,\n transactionType,\n swaps,\n {\n isSwapsDisabled: this.isSwapsDisabled,\n cancelTransaction: this.cancelTransaction.bind(this),\n messenger: this.messagingSystem,\n },\n );\n\n this.addMetadata(addedTransactionMeta);\n\n if (requireApproval !== false) {\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.#updateSimulationData(addedTransactionMeta);\n } else {\n log('Skipping simulation as approval not required');\n }\n\n this.messagingSystem.publish(\n `${controllerName}:unapprovedTransactionAdded`,\n addedTransactionMeta,\n );\n }\n\n return {\n result: this.processApproval(addedTransactionMeta, {\n isExisting: Boolean(existingTransactionMeta),\n requireApproval,\n actionId,\n }),\n transactionMeta: addedTransactionMeta,\n };\n }\n\n startIncomingTransactionPolling(networkClientIds: NetworkClientId[] = []) {\n if (networkClientIds.length === 0) {\n this.incomingTransactionHelper.start();\n return;\n }\n this.#multichainTrackingHelper.startIncomingTransactionPolling(\n networkClientIds,\n );\n }\n\n stopIncomingTransactionPolling(networkClientIds: NetworkClientId[] = []) {\n if (networkClientIds.length === 0) {\n this.incomingTransactionHelper.stop();\n return;\n }\n this.#multichainTrackingHelper.stopIncomingTransactionPolling(\n networkClientIds,\n );\n }\n\n stopAllIncomingTransactionPolling() {\n this.incomingTransactionHelper.stop();\n this.#multichainTrackingHelper.stopAllIncomingTransactionPolling();\n }\n\n async updateIncomingTransactions(networkClientIds: NetworkClientId[] = []) {\n if (networkClientIds.length === 0) {\n await this.incomingTransactionHelper.update();\n return;\n }\n await this.#multichainTrackingHelper.updateIncomingTransactions(\n networkClientIds,\n );\n }\n\n /**\n * Attempts to cancel a transaction based on its ID by setting its status to \"rejected\"\n * and emitting a `:finished` hub event.\n *\n * @param transactionId - The ID of the transaction to cancel.\n * @param gasValues - The gas values to use for the cancellation transaction.\n * @param options - The options for the cancellation transaction.\n * @param options.actionId - Unique ID to prevent duplicate requests.\n * @param options.estimatedBaseFee - The estimated base fee of the transaction.\n */\n async stopTransaction(\n transactionId: string,\n gasValues?: GasPriceValue | FeeMarketEIP1559Values,\n {\n estimatedBaseFee,\n actionId,\n }: { estimatedBaseFee?: string; actionId?: string } = {},\n ) {\n // If transaction is found for same action id, do not create a cancel transaction.\n if (this.getTransactionWithActionId(actionId)) {\n return;\n }\n\n if (gasValues) {\n // Not good practice to reassign a parameter but temporarily avoiding a larger refactor.\n gasValues = normalizeGasFeeValues(gasValues);\n validateGasValues(gasValues);\n }\n\n log('Creating cancel transaction', transactionId, gasValues);\n\n const transactionMeta = this.getTransaction(transactionId);\n if (!transactionMeta) {\n return;\n }\n\n if (!this.sign) {\n throw new Error('No sign method defined.');\n }\n\n // gasPrice (legacy non EIP1559)\n const minGasPrice = getIncreasedPriceFromExisting(\n transactionMeta.txParams.gasPrice,\n CANCEL_RATE,\n );\n\n const gasPriceFromValues = isGasPriceValue(gasValues) && gasValues.gasPrice;\n\n const newGasPrice =\n (gasPriceFromValues &&\n validateMinimumIncrease(gasPriceFromValues, minGasPrice)) ||\n minGasPrice;\n\n // maxFeePerGas (EIP1559)\n const existingMaxFeePerGas = transactionMeta.txParams?.maxFeePerGas;\n const minMaxFeePerGas = getIncreasedPriceFromExisting(\n existingMaxFeePerGas,\n CANCEL_RATE,\n );\n const maxFeePerGasValues =\n isFeeMarketEIP1559Values(gasValues) && gasValues.maxFeePerGas;\n const newMaxFeePerGas =\n (maxFeePerGasValues &&\n validateMinimumIncrease(maxFeePerGasValues, minMaxFeePerGas)) ||\n (existingMaxFeePerGas && minMaxFeePerGas);\n\n // maxPriorityFeePerGas (EIP1559)\n const existingMaxPriorityFeePerGas =\n transactionMeta.txParams?.maxPriorityFeePerGas;\n const minMaxPriorityFeePerGas = getIncreasedPriceFromExisting(\n existingMaxPriorityFeePerGas,\n CANCEL_RATE,\n );\n const maxPriorityFeePerGasValues =\n isFeeMarketEIP1559Values(gasValues) && gasValues.maxPriorityFeePerGas;\n const newMaxPriorityFeePerGas =\n (maxPriorityFeePerGasValues &&\n validateMinimumIncrease(\n maxPriorityFeePerGasValues,\n minMaxPriorityFeePerGas,\n )) ||\n (existingMaxPriorityFeePerGas && minMaxPriorityFeePerGas);\n\n const newTxParams: TransactionParams =\n newMaxFeePerGas && newMaxPriorityFeePerGas\n ? {\n from: transactionMeta.txParams.from,\n gasLimit: transactionMeta.txParams.gas,\n maxFeePerGas: newMaxFeePerGas,\n maxPriorityFeePerGas: newMaxPriorityFeePerGas,\n type: TransactionEnvelopeType.feeMarket,\n nonce: transactionMeta.txParams.nonce,\n to: transactionMeta.txParams.from,\n value: '0x0',\n }\n : {\n from: transactionMeta.txParams.from,\n gasLimit: transactionMeta.txParams.gas,\n gasPrice: newGasPrice,\n nonce: transactionMeta.txParams.nonce,\n to: transactionMeta.txParams.from,\n value: '0x0',\n };\n\n const unsignedEthTx = this.prepareUnsignedEthTx(\n transactionMeta.chainId,\n newTxParams,\n );\n\n const signedTx = await this.sign(\n unsignedEthTx,\n transactionMeta.txParams.from,\n );\n\n const rawTx = bufferToHex(signedTx.serialize());\n\n const newFee = newTxParams.maxFeePerGas ?? newTxParams.gasPrice;\n\n const oldFee = newTxParams.maxFeePerGas\n ? transactionMeta.txParams.maxFeePerGas\n : transactionMeta.txParams.gasPrice;\n\n log('Submitting cancel transaction', {\n oldFee,\n newFee,\n txParams: newTxParams,\n });\n\n const ethQuery = this.#multichainTrackingHelper.getEthQuery({\n networkClientId: transactionMeta.networkClientId,\n chainId: transactionMeta.chainId,\n });\n const hash = await this.publishTransactionForRetry(\n ethQuery,\n rawTx,\n transactionMeta,\n );\n\n const cancelTransactionMeta = {\n actionId,\n chainId: transactionMeta.chainId,\n networkClientId: transactionMeta.networkClientId,\n estimatedBaseFee,\n hash,\n id: random(),\n originalGasEstimate: transactionMeta.txParams.gas,\n rawTx,\n status: TransactionStatus.submitted as const,\n time: Date.now(),\n type: TransactionType.cancel as const,\n txParams: newTxParams,\n };\n\n this.addMetadata(cancelTransactionMeta);\n\n // stopTransaction has no approval request, so we assume the user has already approved the transaction\n this.messagingSystem.publish(`${controllerName}:transactionApproved`, {\n transactionMeta: cancelTransactionMeta,\n actionId,\n });\n this.messagingSystem.publish(`${controllerName}:transactionSubmitted`, {\n transactionMeta: cancelTransactionMeta,\n actionId,\n });\n\n this.messagingSystem.publish(\n `${controllerName}:transactionFinished`,\n cancelTransactionMeta,\n );\n this.#internalEvents.emit(\n `${transactionMeta.id}:finished`,\n cancelTransactionMeta,\n );\n }\n\n /**\n * Attempts to speed up a transaction increasing transaction gasPrice by ten percent.\n *\n * @param transactionId - The ID of the transaction to speed up.\n * @param gasValues - The gas values to use for the speed up transaction.\n * @param options - The options for the speed up transaction.\n * @param options.actionId - Unique ID to prevent duplicate requests\n * @param options.estimatedBaseFee - The estimated base fee of the transaction.\n */\n async speedUpTransaction(\n transactionId: string,\n gasValues?: GasPriceValue | FeeMarketEIP1559Values,\n {\n actionId,\n estimatedBaseFee,\n }: { actionId?: string; estimatedBaseFee?: string } = {},\n ) {\n // If transaction is found for same action id, do not create a new speed up transaction.\n if (this.getTransactionWithActionId(actionId)) {\n return;\n }\n\n if (gasValues) {\n // Not good practice to reassign a parameter but temporarily avoiding a larger refactor.\n gasValues = normalizeGasFeeValues(gasValues);\n validateGasValues(gasValues);\n }\n\n log('Creating speed up transaction', transactionId, gasValues);\n\n const transactionMeta = this.getTransaction(transactionId);\n /* istanbul ignore next */\n if (!transactionMeta) {\n return;\n }\n\n /* istanbul ignore next */\n if (!this.sign) {\n throw new Error('No sign method defined.');\n }\n\n // gasPrice (legacy non EIP1559)\n const minGasPrice = getIncreasedPriceFromExisting(\n transactionMeta.txParams.gasPrice,\n SPEED_UP_RATE,\n );\n\n const gasPriceFromValues = isGasPriceValue(gasValues) && gasValues.gasPrice;\n\n const newGasPrice =\n (gasPriceFromValues &&\n validateMinimumIncrease(gasPriceFromValues, minGasPrice)) ||\n minGasPrice;\n\n // maxFeePerGas (EIP1559)\n const existingMaxFeePerGas = transactionMeta.txParams?.maxFeePerGas;\n const minMaxFeePerGas = getIncreasedPriceFromExisting(\n existingMaxFeePerGas,\n SPEED_UP_RATE,\n );\n const maxFeePerGasValues =\n isFeeMarketEIP1559Values(gasValues) && gasValues.maxFeePerGas;\n const newMaxFeePerGas =\n (maxFeePerGasValues &&\n validateMinimumIncrease(maxFeePerGasValues, minMaxFeePerGas)) ||\n (existingMaxFeePerGas && minMaxFeePerGas);\n\n // maxPriorityFeePerGas (EIP1559)\n const existingMaxPriorityFeePerGas =\n transactionMeta.txParams?.maxPriorityFeePerGas;\n const minMaxPriorityFeePerGas = getIncreasedPriceFromExisting(\n existingMaxPriorityFeePerGas,\n SPEED_UP_RATE,\n );\n const maxPriorityFeePerGasValues =\n isFeeMarketEIP1559Values(gasValues) && gasValues.maxPriorityFeePerGas;\n const newMaxPriorityFeePerGas =\n (maxPriorityFeePerGasValues &&\n validateMinimumIncrease(\n maxPriorityFeePerGasValues,\n minMaxPriorityFeePerGas,\n )) ||\n (existingMaxPriorityFeePerGas && minMaxPriorityFeePerGas);\n\n const txParams: TransactionParams =\n newMaxFeePerGas && newMaxPriorityFeePerGas\n ? {\n ...transactionMeta.txParams,\n gasLimit: transactionMeta.txParams.gas,\n maxFeePerGas: newMaxFeePerGas,\n maxPriorityFeePerGas: newMaxPriorityFeePerGas,\n type: TransactionEnvelopeType.feeMarket,\n }\n : {\n ...transactionMeta.txParams,\n gasLimit: transactionMeta.txParams.gas,\n gasPrice: newGasPrice,\n };\n\n const unsignedEthTx = this.prepareUnsignedEthTx(\n transactionMeta.chainId,\n txParams,\n );\n\n const signedTx = await this.sign(\n unsignedEthTx,\n transactionMeta.txParams.from,\n );\n\n const transactionMetaWithRsv = this.updateTransactionMetaRSV(\n transactionMeta,\n signedTx,\n );\n const rawTx = bufferToHex(signedTx.serialize());\n\n const newFee = txParams.maxFeePerGas ?? txParams.gasPrice;\n\n const oldFee = txParams.maxFeePerGas\n ? transactionMetaWithRsv.txParams.maxFeePerGas\n : transactionMetaWithRsv.txParams.gasPrice;\n\n log('Submitting speed up transaction', { oldFee, newFee, txParams });\n\n const ethQuery = this.#multichainTrackingHelper.getEthQuery({\n networkClientId: transactionMeta.networkClientId,\n chainId: transactionMeta.chainId,\n });\n const hash = await this.publishTransactionForRetry(\n ethQuery,\n rawTx,\n transactionMeta,\n );\n\n const baseTransactionMeta = {\n ...transactionMetaWithRsv,\n estimatedBaseFee,\n id: random(),\n time: Date.now(),\n hash,\n actionId,\n originalGasEstimate: transactionMeta.txParams.gas,\n type: TransactionType.retry as const,\n originalType: transactionMeta.type,\n };\n\n const newTransactionMeta =\n newMaxFeePerGas && newMaxPriorityFeePerGas\n ? {\n ...baseTransactionMeta,\n txParams: {\n ...transactionMeta.txParams,\n maxFeePerGas: newMaxFeePerGas,\n maxPriorityFeePerGas: newMaxPriorityFeePerGas,\n },\n }\n : {\n ...baseTransactionMeta,\n txParams: {\n ...transactionMeta.txParams,\n gasPrice: newGasPrice,\n },\n };\n\n this.addMetadata(newTransactionMeta);\n\n // speedUpTransaction has no approval request, so we assume the user has already approved the transaction\n this.messagingSystem.publish(`${controllerName}:transactionApproved`, {\n transactionMeta: newTransactionMeta,\n actionId,\n });\n\n this.messagingSystem.publish(`${controllerName}:transactionSubmitted`, {\n transactionMeta: newTransactionMeta,\n actionId,\n });\n\n this.messagingSystem.publish(\n `${controllerName}:speedupTransactionAdded`,\n newTransactionMeta,\n );\n }\n\n /**\n * Estimates required gas for a given transaction.\n *\n * @param transaction - The transaction to estimate gas for.\n * @param networkClientId - The network client id to use for the estimate.\n * @returns The gas and gas price.\n */\n async estimateGas(\n transaction: TransactionParams,\n networkClientId?: NetworkClientId,\n ) {\n const ethQuery = this.#multichainTrackingHelper.getEthQuery({\n networkClientId,\n });\n const { estimatedGas, simulationFails } = await estimateGas(\n transaction,\n ethQuery,\n );\n\n return { gas: estimatedGas, simulationFails };\n }\n\n /**\n * Estimates required gas for a given transaction and add additional gas buffer with the given multiplier.\n *\n * @param transaction - The transaction params to estimate gas for.\n * @param multiplier - The multiplier to use for the gas buffer.\n * @param networkClientId - The network client id to use for the estimate.\n */\n async estimateGasBuffered(\n transaction: TransactionParams,\n multiplier: number,\n networkClientId?: NetworkClientId,\n ) {\n const ethQuery = this.#multichainTrackingHelper.getEthQuery({\n networkClientId,\n });\n const { blockGasLimit, estimatedGas, simulationFails } = await estimateGas(\n transaction,\n ethQuery,\n );\n\n const gas = addGasBuffer(estimatedGas, blockGasLimit, multiplier);\n\n return {\n gas,\n simulationFails,\n };\n }\n\n /**\n * Updates an existing transaction in state.\n *\n * @param transactionMeta - The new transaction to store in state.\n * @param note - A note or update reason to include in the transaction history.\n */\n updateTransaction(transactionMeta: TransactionMeta, note: string) {\n const { id: transactionId } = transactionMeta;\n\n this.#updateTransactionInternal({ transactionId, note }, () => ({\n ...transactionMeta,\n }));\n }\n\n /**\n * Update the security alert response for a transaction.\n *\n * @param transactionId - ID of the transaction.\n * @param securityAlertResponse - The new security alert response for the transaction.\n */\n updateSecurityAlertResponse(\n transactionId: string,\n securityAlertResponse: SecurityAlertResponse,\n ) {\n if (!securityAlertResponse) {\n throw new Error(\n 'updateSecurityAlertResponse: securityAlertResponse should not be null',\n );\n }\n const transactionMeta = this.getTransaction(transactionId);\n if (!transactionMeta) {\n throw new Error(\n `Cannot update security alert response as no transaction metadata found`,\n );\n }\n const updatedTransactionMeta = {\n ...transactionMeta,\n securityAlertResponse,\n };\n this.updateTransaction(\n updatedTransactionMeta,\n `${controllerName}:updatesecurityAlertResponse - securityAlertResponse updated`,\n );\n }\n\n /**\n * Removes all transactions from state, optionally based on the current network.\n *\n * @param ignoreNetwork - Determines whether to wipe all transactions, or just those on the\n * current network. If `true`, all transactions are wiped.\n * @param address - If specified, only transactions originating from this address will be\n * wiped on current network.\n */\n wipeTransactions(ignoreNetwork?: boolean, address?: string) {\n /* istanbul ignore next */\n if (ignoreNetwork && !address) {\n this.update((state) => {\n state.transactions = [];\n });\n return;\n }\n const currentChainId = this.getChainId();\n const newTransactions = this.state.transactions.filter(\n ({ chainId, txParams }) => {\n const isMatchingNetwork = ignoreNetwork || chainId === currentChainId;\n\n if (!isMatchingNetwork) {\n return true;\n }\n\n const isMatchingAddress =\n !address || txParams.from?.toLowerCase() === address.toLowerCase();\n\n return !isMatchingAddress;\n },\n );\n\n this.update((state) => {\n state.transactions = this.trimTransactionsForState(newTransactions);\n });\n }\n\n /**\n * Adds external provided transaction to state as confirmed transaction.\n *\n * @param transactionMeta - TransactionMeta to add transactions.\n * @param transactionReceipt - TransactionReceipt of the external transaction.\n * @param baseFeePerGas - Base fee per gas of the external transaction.\n */\n async confirmExternalTransaction(\n transactionMeta: TransactionMeta,\n transactionReceipt: TransactionReceipt,\n baseFeePerGas: Hex,\n ) {\n // Run validation and add external transaction to state.\n const newTransactionMeta = this.addExternalTransaction(transactionMeta);\n\n try {\n const transactionId = newTransactionMeta.id;\n\n // Make sure status is confirmed and define gasUsed as in receipt.\n const updatedTransactionMeta = {\n ...newTransactionMeta,\n status: TransactionStatus.confirmed as const,\n txReceipt: transactionReceipt,\n };\n if (baseFeePerGas) {\n updatedTransactionMeta.baseFeePerGas = baseFeePerGas;\n }\n\n // Update same nonce local transactions as dropped and define replacedBy properties.\n this.markNonceDuplicatesDropped(transactionId);\n\n // Update external provided transaction with updated gas values and confirmed status.\n this.updateTransaction(\n updatedTransactionMeta,\n `${controllerName}:confirmExternalTransaction - Add external transaction`,\n );\n this.onTransactionStatusChange(updatedTransactionMeta);\n\n // Intentional given potential duration of process.\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.updatePostBalance(updatedTransactionMeta);\n\n this.messagingSystem.publish(\n `${controllerName}:transactionConfirmed`,\n updatedTransactionMeta,\n );\n } catch (error) {\n console.error('Failed to confirm external transaction', error);\n }\n }\n\n /**\n * Append new send flow history to a transaction.\n *\n * @param transactionID - The ID of the transaction to update.\n * @param currentSendFlowHistoryLength - The length of the current sendFlowHistory array.\n * @param sendFlowHistoryToAdd - The sendFlowHistory entries to add.\n * @returns The updated transactionMeta.\n */\n updateTransactionSendFlowHistory(\n transactionID: string,\n currentSendFlowHistoryLength: number,\n sendFlowHistoryToAdd: SendFlowHistoryEntry[],\n ): TransactionMeta {\n if (this.isSendFlowHistoryDisabled) {\n throw new Error(\n 'Send flow history is disabled for the current transaction controller',\n );\n }\n\n const transactionMeta = this.getTransaction(transactionID);\n\n if (!transactionMeta) {\n throw new Error(\n `Cannot update send flow history as no transaction metadata found`,\n );\n }\n\n validateIfTransactionUnapproved(\n transactionMeta,\n 'updateTransactionSendFlowHistory',\n );\n\n const sendFlowHistory = transactionMeta.sendFlowHistory ?? [];\n if (currentSendFlowHistoryLength === sendFlowHistory.length) {\n const updatedTransactionMeta = {\n ...transactionMeta,\n sendFlowHistory: [...sendFlowHistory, ...sendFlowHistoryToAdd],\n };\n this.updateTransaction(\n updatedTransactionMeta,\n `${controllerName}:updateTransactionSendFlowHistory - sendFlowHistory updated`,\n );\n }\n\n return this.getTransaction(transactionID) as TransactionMeta;\n }\n\n /**\n * Update the gas values of a transaction.\n *\n * @param transactionId - The ID of the transaction to update.\n * @param gasValues - Gas values to update.\n * @param gasValues.gas - Same as transaction.gasLimit.\n * @param gasValues.gasLimit - Maxmimum number of units of gas to use for this transaction.\n * @param gasValues.gasPrice - Price per gas for legacy transactions.\n * @param gasValues.maxPriorityFeePerGas - Maximum amount per gas to give to validator as incentive.\n * @param gasValues.maxFeePerGas - Maximum amount per gas to pay for the transaction, including the priority fee.\n * @param gasValues.estimateUsed - Which estimate level was used.\n * @param gasValues.estimateSuggested - Which estimate level that the API suggested.\n * @param gasValues.defaultGasEstimates - The default estimate for gas.\n * @param gasValues.originalGasEstimate - Original estimate for gas.\n * @param gasValues.userEditedGasLimit - The gas limit supplied by user.\n * @param gasValues.userFeeLevel - Estimate level user selected.\n * @returns The updated transactionMeta.\n */\n updateTransactionGasFees(\n transactionId: string,\n {\n defaultGasEstimates,\n estimateUsed,\n estimateSuggested,\n gas,\n gasLimit,\n gasPrice,\n maxPriorityFeePerGas,\n maxFeePerGas,\n originalGasEstimate,\n userEditedGasLimit,\n userFeeLevel,\n }: {\n defaultGasEstimates?: string;\n estimateUsed?: string;\n estimateSuggested?: string;\n gas?: string;\n gasLimit?: string;\n gasPrice?: string;\n maxPriorityFeePerGas?: string;\n maxFeePerGas?: string;\n originalGasEstimate?: string;\n userEditedGasLimit?: boolean;\n userFeeLevel?: string;\n },\n ): TransactionMeta {\n const transactionMeta = this.getTransaction(transactionId);\n\n if (!transactionMeta) {\n throw new Error(\n `Cannot update transaction as no transaction metadata found`,\n );\n }\n\n validateIfTransactionUnapproved(\n transactionMeta,\n 'updateTransactionGasFees',\n );\n\n let transactionGasFees = {\n txParams: {\n gas,\n gasLimit,\n gasPrice,\n maxPriorityFeePerGas,\n maxFeePerGas,\n },\n defaultGasEstimates,\n estimateUsed,\n estimateSuggested,\n originalGasEstimate,\n userEditedGasLimit,\n userFeeLevel,\n // TODO: Replace `any` with type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } as any;\n\n // only update what is defined\n transactionGasFees.txParams = pickBy(transactionGasFees.txParams);\n transactionGasFees = pickBy(transactionGasFees);\n\n // merge updated gas values with existing transaction meta\n const updatedMeta = merge({}, transactionMeta, transactionGasFees);\n\n this.updateTransaction(\n updatedMeta,\n `${controllerName}:updateTransactionGasFees - gas values updated`,\n );\n\n return this.getTransaction(transactionId) as TransactionMeta;\n }\n\n /**\n * Update the previous gas values of a transaction.\n *\n * @param transactionId - The ID of the transaction to update.\n * @param previousGas - Previous gas values to update.\n * @param previousGas.gasLimit - Maxmimum number of units of gas to use for this transaction.\n * @param previousGas.maxFeePerGas - Maximum amount per gas to pay for the transaction, including the priority fee.\n * @param previousGas.maxPriorityFeePerGas - Maximum amount per gas to give to validator as incentive.\n * @returns The updated transactionMeta.\n */\n updatePreviousGasParams(\n transactionId: string,\n {\n gasLimit,\n maxFeePerGas,\n maxPriorityFeePerGas,\n }: {\n gasLimit?: string;\n maxFeePerGas?: string;\n maxPriorityFeePerGas?: string;\n },\n ): TransactionMeta {\n const transactionMeta = this.getTransaction(transactionId);\n\n if (!transactionMeta) {\n throw new Error(\n `Cannot update transaction as no transaction metadata found`,\n );\n }\n\n validateIfTransactionUnapproved(transactionMeta, 'updatePreviousGasParams');\n\n const transactionPreviousGas = {\n previousGas: {\n gasLimit,\n maxFeePerGas,\n maxPriorityFeePerGas,\n },\n // TODO: Replace `any` with type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } as any;\n\n // only update what is defined\n transactionPreviousGas.previousGas = pickBy(\n transactionPreviousGas.previousGas,\n );\n\n // merge updated previous gas values with existing transaction meta\n const updatedMeta = merge({}, transactionMeta, transactionPreviousGas);\n\n this.updateTransaction(\n updatedMeta,\n `${controllerName}:updatePreviousGasParams - Previous gas values updated`,\n );\n\n return this.getTransaction(transactionId) as TransactionMeta;\n }\n\n async getNonceLock(\n address: string,\n networkClientId?: NetworkClientId,\n ): Promise {\n return this.#multichainTrackingHelper.getNonceLock(\n address,\n networkClientId,\n );\n }\n\n /**\n * Updates the editable parameters of a transaction.\n *\n * @param txId - The ID of the transaction to update.\n * @param params - The editable parameters to update.\n * @param params.data - Data to pass with the transaction.\n * @param params.gas - Maximum number of units of gas to use for the transaction.\n * @param params.gasPrice - Price per gas for legacy transactions.\n * @param params.from - Address to send the transaction from.\n * @param params.to - Address to send the transaction to.\n * @param params.value - Value associated with the transaction.\n * @returns The updated transaction metadata.\n */\n async updateEditableParams(\n txId: string,\n {\n data,\n gas,\n gasPrice,\n from,\n to,\n value,\n }: {\n data?: string;\n gas?: string;\n gasPrice?: string;\n from?: string;\n to?: string;\n value?: string;\n },\n ) {\n const transactionMeta = this.getTransaction(txId);\n if (!transactionMeta) {\n throw new Error(\n `Cannot update editable params as no transaction metadata found`,\n );\n }\n\n validateIfTransactionUnapproved(transactionMeta, 'updateEditableParams');\n\n const editableParams = {\n txParams: {\n data,\n from,\n to,\n value,\n gas,\n gasPrice,\n },\n } as Partial;\n\n editableParams.txParams = pickBy(\n editableParams.txParams,\n ) as TransactionParams;\n\n const updatedTransaction = merge({}, transactionMeta, editableParams);\n const provider = this.#multichainTrackingHelper.getProvider({\n chainId: transactionMeta.chainId,\n networkClientId: transactionMeta.networkClientId,\n });\n const ethQuery = new EthQuery(provider);\n const { type } = await determineTransactionType(\n updatedTransaction.txParams,\n ethQuery,\n );\n updatedTransaction.type = type;\n\n await updateTransactionLayer1GasFee({\n layer1GasFeeFlows: this.layer1GasFeeFlows,\n provider,\n transactionMeta: updatedTransaction,\n });\n\n this.updateTransaction(\n updatedTransaction,\n `Update Editable Params for ${txId}`,\n );\n return this.getTransaction(txId);\n }\n\n /**\n * Signs and returns the raw transaction data for provided transaction params list.\n *\n * @param listOfTxParams - The list of transaction params to approve.\n * @param opts - Options bag.\n * @param opts.hasNonce - Whether the transactions already have a nonce.\n * @returns The raw transactions.\n */\n async approveTransactionsWithSameNonce(\n listOfTxParams: (TransactionParams & { chainId: Hex })[] = [],\n { hasNonce }: { hasNonce?: boolean } = {},\n ): Promise {\n log('Approving transactions with same nonce', {\n transactions: listOfTxParams,\n });\n\n if (listOfTxParams.length === 0) {\n return '';\n }\n\n const initialTx = listOfTxParams[0];\n const common = this.getCommonConfiguration(initialTx.chainId);\n\n // We need to ensure we get the nonce using the the NonceTracker on the chain matching\n // the txParams. In this context we only have chainId available to us, but the\n // NonceTrackers are keyed by networkClientId. To workaround this, we attempt to find\n // a networkClientId that matches the chainId. As a fallback, the globally selected\n // network's NonceTracker will be used instead.\n let networkClientId: NetworkClientId | undefined;\n try {\n networkClientId = this.messagingSystem.call(\n `NetworkController:findNetworkClientIdByChainId`,\n initialTx.chainId,\n );\n } catch (err) {\n log('failed to find networkClientId from chainId', err);\n }\n\n const initialTxAsEthTx = TransactionFactory.fromTxData(initialTx, {\n common,\n });\n const initialTxAsSerializedHex = bufferToHex(initialTxAsEthTx.serialize());\n\n if (this.approvingTransactionIds.has(initialTxAsSerializedHex)) {\n return '';\n }\n this.approvingTransactionIds.add(initialTxAsSerializedHex);\n\n let rawTransactions, nonceLock;\n try {\n // TODO: we should add a check to verify that all transactions have the same from address\n const fromAddress = initialTx.from;\n const requiresNonce = hasNonce !== true;\n\n nonceLock = requiresNonce\n ? await this.getNonceLock(fromAddress, networkClientId)\n : undefined;\n\n const nonce = nonceLock\n ? add0x(nonceLock.nextNonce.toString(16))\n : initialTx.nonce;\n\n if (nonceLock) {\n log('Using nonce from nonce tracker', nonce, nonceLock.nonceDetails);\n }\n\n rawTransactions = await Promise.all(\n listOfTxParams.map((txParams) => {\n txParams.nonce = nonce;\n return this.signExternalTransaction(txParams.chainId, txParams);\n }),\n );\n } catch (err) {\n log('Error while signing transactions with same nonce', err);\n // Must set transaction to submitted/failed before releasing lock\n // continue with error chain\n throw err;\n } finally {\n nonceLock?.releaseLock();\n this.approvingTransactionIds.delete(initialTxAsSerializedHex);\n }\n return rawTransactions;\n }\n\n /**\n * Update a custodial transaction.\n *\n * @param transactionId - The ID of the transaction to update.\n * @param options - The custodial transaction options to update.\n * @param options.errorMessage - The error message to be assigned in case transaction status update to failed.\n * @param options.hash - The new hash value to be assigned.\n * @param options.status - The new status value to be assigned.\n */\n updateCustodialTransaction(\n transactionId: string,\n {\n errorMessage,\n hash,\n status,\n }: {\n errorMessage?: string;\n hash?: string;\n status?: TransactionStatus;\n },\n ) {\n const transactionMeta = this.getTransaction(transactionId);\n\n if (!transactionMeta) {\n throw new Error(\n `Cannot update custodial transaction as no transaction metadata found`,\n );\n }\n\n if (!transactionMeta.custodyId) {\n throw new Error('Transaction must be a custodian transaction');\n }\n\n if (\n status &&\n ![\n TransactionStatus.submitted,\n TransactionStatus.signed,\n TransactionStatus.failed,\n ].includes(status)\n ) {\n throw new Error(\n `Cannot update custodial transaction with status: ${status}`,\n );\n }\n\n const updatedTransactionMeta = merge(\n {},\n transactionMeta,\n pickBy({ hash, status }),\n ) as TransactionMeta;\n\n if (updatedTransactionMeta.status === TransactionStatus.submitted) {\n updatedTransactionMeta.submittedTime = new Date().getTime();\n }\n\n if (updatedTransactionMeta.status === TransactionStatus.failed) {\n updatedTransactionMeta.error = normalizeTxError(new Error(errorMessage));\n }\n\n this.updateTransaction(\n updatedTransactionMeta,\n `${controllerName}:updateCustodialTransaction - Custodial transaction updated`,\n );\n\n if (\n [TransactionStatus.submitted, TransactionStatus.failed].includes(\n status as TransactionStatus,\n )\n ) {\n this.messagingSystem.publish(\n `${controllerName}:transactionFinished`,\n updatedTransactionMeta,\n );\n this.#internalEvents.emit(\n `${updatedTransactionMeta.id}:finished`,\n updatedTransactionMeta,\n );\n }\n }\n\n /**\n * Search transaction metadata for matching entries.\n *\n * @param opts - Options bag.\n * @param opts.searchCriteria - An object containing values or functions for transaction properties to filter transactions with.\n * @param opts.initialList - The transactions to search. Defaults to the current state.\n * @param opts.filterToCurrentNetwork - Whether to filter the results to the current network. Defaults to true.\n * @param opts.limit - The maximum number of transactions to return. No limit by default.\n * @returns An array of transactions matching the provided options.\n */\n getTransactions({\n searchCriteria = {},\n initialList,\n filterToCurrentNetwork = true,\n limit,\n }: {\n // TODO: Replace `any` with type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n searchCriteria?: any;\n initialList?: TransactionMeta[];\n filterToCurrentNetwork?: boolean;\n limit?: number;\n } = {}): TransactionMeta[] {\n const chainId = this.getChainId();\n // searchCriteria is an object that might have values that aren't predicate\n // methods. When providing any other value type (string, number, etc), we\n // consider this shorthand for \"check the value at key for strict equality\n // with the provided value\". To conform this object to be only methods, we\n // mapValues (lodash) such that every value on the object is a method that\n // returns a boolean.\n const predicateMethods = mapValues(searchCriteria, (predicate) => {\n return typeof predicate === 'function'\n ? predicate\n : // TODO: Replace `any` with type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (v: any) => v === predicate;\n });\n\n const transactionsToFilter = initialList ?? this.state.transactions;\n\n // Combine sortBy and pickBy to transform our state object into an array of\n // matching transactions that are sorted by time.\n const filteredTransactions = sortBy(\n pickBy(transactionsToFilter, (transaction) => {\n if (filterToCurrentNetwork && transaction.chainId !== chainId) {\n return false;\n }\n // iterate over the predicateMethods keys to check if the transaction\n // matches the searchCriteria\n for (const [key, predicate] of Object.entries(predicateMethods)) {\n // We return false early as soon as we know that one of the specified\n // search criteria do not match the transaction. This prevents\n // needlessly checking all criteria when we already know the criteria\n // are not fully satisfied. We check both txParams and the base\n // object as predicate keys can be either.\n if (key in transaction.txParams) {\n // TODO: Replace `any` with type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n if (predicate((transaction.txParams as any)[key]) === false) {\n return false;\n }\n // TODO: Replace `any` with type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } else if (predicate((transaction as any)[key]) === false) {\n return false;\n }\n }\n\n return true;\n }),\n 'time',\n );\n if (limit !== undefined) {\n // We need to have all transactions of a given nonce in order to display\n // necessary details in the UI. We use the size of this set to determine\n // whether we have reached the limit provided, thus ensuring that all\n // transactions of nonces we include will be sent to the UI.\n const nonces = new Set();\n const txs = [];\n // By default, the transaction list we filter from is sorted by time ASC.\n // To ensure that filtered results prefers the newest transactions we\n // iterate from right to left, inserting transactions into front of a new\n // array. The original order is preserved, but we ensure that newest txs\n // are preferred.\n for (let i = filteredTransactions.length - 1; i > -1; i--) {\n const txMeta = filteredTransactions[i];\n const { nonce } = txMeta.txParams;\n if (!nonces.has(nonce)) {\n if (nonces.size < limit) {\n nonces.add(nonce);\n } else {\n continue;\n }\n }\n // Push transaction into the beginning of our array to ensure the\n // original order is preserved.\n txs.unshift(txMeta);\n }\n return txs;\n }\n return filteredTransactions;\n }\n\n async estimateGasFee({\n transactionParams,\n chainId,\n networkClientId: requestNetworkClientId,\n }: {\n transactionParams: TransactionParams;\n chainId?: Hex;\n networkClientId?: NetworkClientId;\n }): Promise {\n const networkClientId = this.#getNetworkClientId({\n networkClientId: requestNetworkClientId,\n chainId,\n });\n\n const transactionMeta = {\n txParams: transactionParams,\n chainId,\n networkClientId,\n } as TransactionMeta;\n\n // Guaranteed as the default gas fee flow matches all transactions.\n const gasFeeFlow = getGasFeeFlow(\n transactionMeta,\n this.gasFeeFlows,\n ) as GasFeeFlow;\n\n const ethQuery = this.#multichainTrackingHelper.getEthQuery({\n networkClientId,\n chainId,\n });\n\n const gasFeeControllerData = await this.getGasFeeEstimates({\n networkClientId,\n });\n\n return gasFeeFlow.getGasFees({\n ethQuery,\n gasFeeControllerData,\n transactionMeta,\n });\n }\n\n /**\n * Determine the layer 1 gas fee for the given transaction parameters.\n *\n * @param request - The request object.\n * @param request.transactionParams - The transaction parameters to estimate the layer 1 gas fee for.\n * @param request.chainId - The ID of the chain where the transaction will be executed.\n * @param request.networkClientId - The ID of a specific network client to process the transaction.\n */\n async getLayer1GasFee({\n transactionParams,\n chainId,\n networkClientId,\n }: {\n transactionParams: TransactionParams;\n chainId?: Hex;\n networkClientId?: NetworkClientId;\n }): Promise {\n const provider = this.#multichainTrackingHelper.getProvider({\n networkClientId,\n chainId,\n });\n\n return await getTransactionLayer1GasFee({\n layer1GasFeeFlows: this.layer1GasFeeFlows,\n provider,\n transactionMeta: {\n txParams: transactionParams,\n chainId,\n } as TransactionMeta,\n });\n }\n\n private async signExternalTransaction(\n chainId: Hex,\n transactionParams: TransactionParams,\n ): Promise {\n if (!this.sign) {\n throw new Error('No sign method defined.');\n }\n\n const normalizedTransactionParams =\n normalizeTransactionParams(transactionParams);\n const type = isEIP1559Transaction(normalizedTransactionParams)\n ? TransactionEnvelopeType.feeMarket\n : TransactionEnvelopeType.legacy;\n const updatedTransactionParams = {\n ...normalizedTransactionParams,\n type,\n gasLimit: normalizedTransactionParams.gas,\n chainId,\n };\n\n const { from } = updatedTransactionParams;\n const common = this.getCommonConfiguration(chainId);\n const unsignedTransaction = TransactionFactory.fromTxData(\n updatedTransactionParams,\n { common },\n );\n const signedTransaction = await this.sign(unsignedTransaction, from);\n\n const rawTransaction = bufferToHex(signedTransaction.serialize());\n return rawTransaction;\n }\n\n /**\n * Removes unapproved transactions from state.\n */\n clearUnapprovedTransactions() {\n const transactions = this.state.transactions.filter(\n ({ status }) => status !== TransactionStatus.unapproved,\n );\n this.update((state) => {\n state.transactions = this.trimTransactionsForState(transactions);\n });\n }\n\n /**\n * Stop the signing process for a specific transaction.\n * Throws an error causing the transaction status to be set to failed.\n * @param transactionId - The ID of the transaction to stop signing.\n */\n abortTransactionSigning(transactionId: string) {\n const transactionMeta = this.getTransaction(transactionId);\n\n if (!transactionMeta) {\n throw new Error(`Cannot abort signing as no transaction metadata found`);\n }\n\n const abortCallback = this.signAbortCallbacks.get(transactionId);\n\n if (!abortCallback) {\n throw new Error(\n `Cannot abort signing as transaction is not waiting for signing`,\n );\n }\n\n abortCallback();\n\n this.signAbortCallbacks.delete(transactionId);\n }\n\n private addMetadata(transactionMeta: TransactionMeta) {\n this.update((state) => {\n state.transactions = this.trimTransactionsForState([\n ...state.transactions,\n transactionMeta,\n ]);\n });\n }\n\n private async updateGasProperties(transactionMeta: TransactionMeta) {\n const isEIP1559Compatible =\n (await this.getEIP1559Compatibility(transactionMeta.networkClientId)) &&\n transactionMeta.txParams.type !== TransactionEnvelopeType.legacy;\n\n const { networkClientId, chainId } = transactionMeta;\n\n const isCustomNetwork = this.#isCustomNetwork(networkClientId);\n\n const ethQuery = this.#multichainTrackingHelper.getEthQuery({\n networkClientId,\n chainId,\n });\n\n const provider = this.#multichainTrackingHelper.getProvider({\n networkClientId,\n chainId,\n });\n\n await updateGas({\n ethQuery,\n chainId,\n isCustomNetwork,\n txMeta: transactionMeta,\n });\n\n await updateGasFees({\n eip1559: isEIP1559Compatible,\n ethQuery,\n gasFeeFlows: this.gasFeeFlows,\n getGasFeeEstimates: this.getGasFeeEstimates,\n getSavedGasFees: this.getSavedGasFees.bind(this),\n txMeta: transactionMeta,\n });\n\n await updateTransactionLayer1GasFee({\n layer1GasFeeFlows: this.layer1GasFeeFlows,\n provider,\n transactionMeta,\n });\n }\n\n private onBootCleanup() {\n this.clearUnapprovedTransactions();\n this.failIncompleteTransactions();\n }\n\n private failIncompleteTransactions() {\n const incompleteTransactions = this.state.transactions.filter(\n (transaction) =>\n [TransactionStatus.approved, TransactionStatus.signed].includes(\n transaction.status,\n ),\n );\n\n for (const transactionMeta of incompleteTransactions) {\n this.failTransaction(\n transactionMeta,\n new Error('Transaction incomplete at startup'),\n );\n }\n }\n\n private async processApproval(\n transactionMeta: TransactionMeta,\n {\n isExisting = false,\n requireApproval,\n shouldShowRequest = true,\n actionId,\n }: {\n isExisting?: boolean;\n requireApproval?: boolean | undefined;\n shouldShowRequest?: boolean;\n actionId?: string;\n },\n ): Promise {\n const transactionId = transactionMeta.id;\n let resultCallbacks: AcceptResultCallbacks | undefined;\n const { meta, isCompleted } = this.isTransactionCompleted(transactionId);\n const finishedPromise = isCompleted\n ? Promise.resolve(meta)\n : this.waitForTransactionFinished(transactionId);\n\n if (meta && !isExisting && !isCompleted) {\n try {\n if (requireApproval !== false) {\n const acceptResult = await this.requestApproval(transactionMeta, {\n shouldShowRequest,\n });\n resultCallbacks = acceptResult.resultCallbacks;\n\n const approvalValue = acceptResult.value as\n | {\n txMeta?: TransactionMeta;\n }\n | undefined;\n\n const updatedTransaction = approvalValue?.txMeta;\n\n if (updatedTransaction) {\n log('Updating transaction with approval data', {\n customNonce: updatedTransaction.customNonceValue,\n params: updatedTransaction.txParams,\n });\n\n this.updateTransaction(\n updatedTransaction,\n 'TransactionController#processApproval - Updated with approval data',\n );\n }\n }\n\n const { isCompleted: isTxCompleted } =\n this.isTransactionCompleted(transactionId);\n\n if (!isTxCompleted) {\n const approvalResult = await this.approveTransaction(transactionId);\n if (\n approvalResult === ApprovalState.SkippedViaBeforePublishHook &&\n resultCallbacks\n ) {\n resultCallbacks.success();\n }\n const updatedTransactionMeta = this.getTransaction(\n transactionId,\n ) as TransactionMeta;\n this.messagingSystem.publish(\n `${controllerName}:transactionApproved`,\n {\n transactionMeta: updatedTransactionMeta,\n actionId,\n },\n );\n }\n // TODO: Replace `any` with type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (error: any) {\n const { isCompleted: isTxCompleted } =\n this.isTransactionCompleted(transactionId);\n if (!isTxCompleted) {\n if (error?.code === errorCodes.provider.userRejectedRequest) {\n this.cancelTransaction(transactionId, actionId);\n\n throw providerErrors.userRejectedRequest(\n 'MetaMask Tx Signature: User denied transaction signature.',\n );\n } else {\n this.failTransaction(meta, error, actionId);\n }\n }\n }\n }\n\n const finalMeta = await finishedPromise;\n\n switch (finalMeta?.status) {\n case TransactionStatus.failed:\n resultCallbacks?.error(finalMeta.error);\n throw rpcErrors.internal(finalMeta.error.message);\n\n case TransactionStatus.submitted:\n resultCallbacks?.success();\n return finalMeta.hash as string;\n\n default:\n const internalError = rpcErrors.internal(\n `MetaMask Tx Signature: Unknown problem: ${JSON.stringify(\n finalMeta || transactionId,\n )}`,\n );\n\n resultCallbacks?.error(internalError);\n throw internalError;\n }\n }\n\n /**\n * Approves a transaction and updates it's status in state. If this is not a\n * retry transaction, a nonce will be generated. The transaction is signed\n * using the sign configuration property, then published to the blockchain.\n * A `:finished` hub event is fired after success or failure.\n *\n * @param transactionId - The ID of the transaction to approve.\n */\n private async approveTransaction(transactionId: string) {\n const cleanupTasks = new Array<() => void>();\n cleanupTasks.push(await this.mutex.acquire());\n\n let transactionMeta = this.getTransactionOrThrow(transactionId);\n\n try {\n if (!this.sign) {\n this.failTransaction(\n transactionMeta,\n new Error('No sign method defined.'),\n );\n return ApprovalState.NotApproved;\n } else if (!transactionMeta.chainId) {\n this.failTransaction(transactionMeta, new Error('No chainId defined.'));\n return ApprovalState.NotApproved;\n }\n\n if (this.approvingTransactionIds.has(transactionId)) {\n log('Skipping approval as signing in progress', transactionId);\n return ApprovalState.NotApproved;\n }\n this.approvingTransactionIds.add(transactionId);\n cleanupTasks.push(() =>\n this.approvingTransactionIds.delete(transactionId),\n );\n\n const [nonce, releaseNonce] = await getNextNonce(\n transactionMeta,\n (address: string) =>\n this.#multichainTrackingHelper.getNonceLock(\n address,\n transactionMeta.networkClientId,\n ),\n );\n\n // must set transaction to submitted/failed before releasing lock\n releaseNonce && cleanupTasks.push(releaseNonce);\n\n transactionMeta = this.#updateTransactionInternal(\n {\n transactionId,\n note: 'TransactionController#approveTransaction - Transaction approved',\n },\n (draftTxMeta) => {\n const { txParams, chainId } = draftTxMeta;\n\n draftTxMeta.status = TransactionStatus.approved;\n draftTxMeta.txParams = {\n ...txParams,\n nonce,\n chainId,\n gasLimit: txParams.gas,\n ...(isEIP1559Transaction(txParams) && {\n type: TransactionEnvelopeType.feeMarket,\n }),\n };\n },\n );\n\n this.onTransactionStatusChange(transactionMeta);\n\n const rawTx = await this.signTransaction(\n transactionMeta,\n transactionMeta.txParams,\n );\n\n if (!this.beforePublish(transactionMeta)) {\n log('Skipping publishing transaction based on hook');\n this.messagingSystem.publish(\n `${controllerName}:transactionPublishingSkipped`,\n transactionMeta,\n );\n return ApprovalState.SkippedViaBeforePublishHook;\n }\n\n if (!rawTx) {\n return ApprovalState.NotApproved;\n }\n\n const ethQuery = this.#multichainTrackingHelper.getEthQuery({\n networkClientId: transactionMeta.networkClientId,\n chainId: transactionMeta.chainId,\n });\n\n let preTxBalance: string | undefined;\n const shouldUpdatePreTxBalance =\n transactionMeta.type === TransactionType.swap;\n\n if (shouldUpdatePreTxBalance) {\n log('Determining pre-transaction balance');\n\n preTxBalance = await query(ethQuery, 'getBalance', [\n transactionMeta.txParams.from,\n ]);\n }\n\n log('Publishing transaction', transactionMeta.txParams);\n\n let { transactionHash: hash } = await this.publish(\n transactionMeta,\n rawTx,\n );\n\n if (hash === undefined) {\n hash = await this.publishTransaction(ethQuery, rawTx);\n }\n\n log('Publish successful', hash);\n\n transactionMeta = this.#updateTransactionInternal(\n {\n transactionId,\n note: 'TransactionController#approveTransaction - Transaction submitted',\n },\n (draftTxMeta) => {\n draftTxMeta.hash = hash;\n draftTxMeta.status = TransactionStatus.submitted;\n draftTxMeta.submittedTime = new Date().getTime();\n if (shouldUpdatePreTxBalance) {\n draftTxMeta.preTxBalance = preTxBalance;\n log('Updated pre-transaction balance', preTxBalance);\n }\n },\n );\n\n this.messagingSystem.publish(`${controllerName}:transactionSubmitted`, {\n transactionMeta,\n });\n\n this.messagingSystem.publish(\n `${controllerName}:transactionFinished`,\n transactionMeta,\n );\n this.#internalEvents.emit(`${transactionId}:finished`, transactionMeta);\n\n this.onTransactionStatusChange(transactionMeta);\n return ApprovalState.Approved;\n // TODO: Replace `any` with type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (error: any) {\n this.failTransaction(transactionMeta, error);\n return ApprovalState.NotApproved;\n } finally {\n cleanupTasks.forEach((task) => task());\n }\n }\n\n private async publishTransaction(\n ethQuery: EthQuery,\n rawTransaction: string,\n ): Promise {\n return await query(ethQuery, 'sendRawTransaction', [rawTransaction]);\n }\n\n /**\n * Cancels a transaction based on its ID by setting its status to \"rejected\"\n * and emitting a `:finished` hub event.\n *\n * @param transactionId - The ID of the transaction to cancel.\n * @param actionId - The actionId passed from UI\n */\n private cancelTransaction(transactionId: string, actionId?: string) {\n const transactionMeta = this.state.transactions.find(\n ({ id }) => id === transactionId,\n );\n if (!transactionMeta) {\n return;\n }\n this.update((state) => {\n const transactions = state.transactions.filter(\n ({ id }) => id !== transactionId,\n );\n state.transactions = this.trimTransactionsForState(transactions);\n });\n const updatedTransactionMeta = {\n ...transactionMeta,\n status: TransactionStatus.rejected as const,\n };\n this.messagingSystem.publish(\n `${controllerName}:transactionFinished`,\n updatedTransactionMeta,\n );\n this.#internalEvents.emit(\n // TODO: Either fix this lint violation or explain why it's necessary to ignore.\n // eslint-disable-next-line @typescript-eslint/restrict-template-expressions\n `${transactionMeta.id}:finished`,\n updatedTransactionMeta,\n );\n this.messagingSystem.publish(`${controllerName}:transactionRejected`, {\n transactionMeta: updatedTransactionMeta,\n actionId,\n });\n this.onTransactionStatusChange(updatedTransactionMeta);\n }\n\n /**\n * Trim the amount of transactions that are set on the state. Checks\n * if the length of the tx history is longer then desired persistence\n * limit and then if it is removes the oldest confirmed or rejected tx.\n * Pending or unapproved transactions will not be removed by this\n * operation. For safety of presenting a fully functional transaction UI\n * representation, this function will not break apart transactions with the\n * same nonce, created on the same day, per network. Not accounting for\n * transactions of the same nonce, same day and network combo can result in\n * confusing or broken experiences in the UI.\n *\n * @param transactions - The transactions to be applied to the state.\n * @returns The trimmed list of transactions.\n */\n private trimTransactionsForState(\n transactions: TransactionMeta[],\n ): TransactionMeta[] {\n const nonceNetworkSet = new Set();\n\n const txsToKeep = [...transactions]\n .sort((a, b) => (a.time > b.time ? -1 : 1)) // Descending time order\n .filter((tx) => {\n const { chainId, status, txParams, time } = tx;\n\n if (txParams) {\n // TODO: Either fix this lint violation or explain why it's necessary to ignore.\n // eslint-disable-next-line @typescript-eslint/restrict-template-expressions\n const key = `${String(txParams.nonce)}-${convertHexToDecimal(\n chainId,\n )}-${new Date(time).toDateString()}`;\n\n if (nonceNetworkSet.has(key)) {\n return true;\n } else if (\n nonceNetworkSet.size < this.#transactionHistoryLimit ||\n !this.isFinalState(status)\n ) {\n nonceNetworkSet.add(key);\n return true;\n }\n }\n\n return false;\n });\n\n txsToKeep.reverse(); // Ascending time order\n return txsToKeep;\n }\n\n /**\n * Determines if the transaction is in a final state.\n *\n * @param status - The transaction status.\n * @returns Whether the transaction is in a final state.\n */\n private isFinalState(status: TransactionStatus): boolean {\n return (\n status === TransactionStatus.rejected ||\n status === TransactionStatus.confirmed ||\n status === TransactionStatus.failed\n );\n }\n\n /**\n * Whether the transaction has at least completed all local processing.\n *\n * @param status - The transaction status.\n * @returns Whether the transaction is in a final state.\n */\n private isLocalFinalState(status: TransactionStatus): boolean {\n return [\n TransactionStatus.confirmed,\n TransactionStatus.failed,\n TransactionStatus.rejected,\n TransactionStatus.submitted,\n ].includes(status);\n }\n\n private async requestApproval(\n txMeta: TransactionMeta,\n { shouldShowRequest }: { shouldShowRequest: boolean },\n ): Promise {\n const id = this.getApprovalId(txMeta);\n const { origin } = txMeta;\n const type = ApprovalType.Transaction;\n const requestData = { txId: txMeta.id };\n\n return (await this.messagingSystem.call(\n 'ApprovalController:addRequest',\n {\n id,\n origin: origin || ORIGIN_METAMASK,\n type,\n requestData,\n expectsResult: true,\n },\n shouldShowRequest,\n )) as Promise;\n }\n\n private getTransaction(\n transactionId: string,\n ): Readonly | undefined {\n const { transactions } = this.state;\n return transactions.find(({ id }) => id === transactionId);\n }\n\n private getTransactionOrThrow(\n transactionId: string,\n errorMessagePrefix = 'TransactionController',\n ): Readonly {\n const txMeta = this.getTransaction(transactionId);\n if (!txMeta) {\n throw new Error(\n `${errorMessagePrefix}: No transaction found with id ${transactionId}`,\n );\n }\n return txMeta;\n }\n\n private getApprovalId(txMeta: TransactionMeta) {\n return String(txMeta.id);\n }\n\n private isTransactionCompleted(transactionId: string): {\n meta?: TransactionMeta;\n isCompleted: boolean;\n } {\n const transaction = this.getTransaction(transactionId);\n\n if (!transaction) {\n return { meta: undefined, isCompleted: false };\n }\n\n const isCompleted = this.isLocalFinalState(transaction.status);\n\n return { meta: transaction, isCompleted };\n }\n\n private getChainId(networkClientId?: NetworkClientId): Hex {\n const globalChainId = this.#getGlobalChainId();\n const globalNetworkClientId = this.#getGlobalNetworkClientId();\n\n if (!networkClientId || networkClientId === globalNetworkClientId) {\n return globalChainId;\n }\n\n return this.messagingSystem.call(\n `NetworkController:getNetworkClientById`,\n networkClientId,\n ).configuration.chainId;\n }\n\n private prepareUnsignedEthTx(\n chainId: Hex,\n txParams: TransactionParams,\n ): TypedTransaction {\n return TransactionFactory.fromTxData(txParams, {\n freeze: false,\n common: this.getCommonConfiguration(chainId),\n });\n }\n\n /**\n * `@ethereumjs/tx` uses `@ethereumjs/common` as a configuration tool for\n * specifying which chain, network, hardfork and EIPs to support for\n * a transaction. By referencing this configuration, and analyzing the fields\n * specified in txParams, @ethereumjs/tx is able to determine which EIP-2718\n * transaction type to use.\n *\n * @param chainId - The chainId to use for the configuration.\n * @returns common configuration object\n */\n private getCommonConfiguration(chainId: Hex): Common {\n const customChainParams: Partial = {\n chainId: parseInt(chainId, 16),\n defaultHardfork: HARDFORK,\n };\n\n return Common.custom(customChainParams);\n }\n\n private onIncomingTransactions({\n added,\n updated,\n }: {\n added: TransactionMeta[];\n updated: TransactionMeta[];\n }) {\n this.update((state) => {\n const { transactions: currentTransactions } = state;\n const updatedTransactions = [\n ...added,\n ...currentTransactions.map((originalTransaction) => {\n const updatedTransaction = updated.find(\n ({ hash }) => hash === originalTransaction.hash,\n );\n\n return updatedTransaction ?? originalTransaction;\n }),\n ];\n\n state.transactions = this.trimTransactionsForState(updatedTransactions);\n });\n }\n\n private onUpdatedLastFetchedBlockNumbers({\n lastFetchedBlockNumbers,\n blockNumber,\n }: {\n lastFetchedBlockNumbers: {\n [key: string]: number;\n };\n blockNumber: number;\n }) {\n this.update((state) => {\n state.lastFetchedBlockNumbers = lastFetchedBlockNumbers;\n });\n this.messagingSystem.publish(\n `${controllerName}:incomingTransactionBlockReceived`,\n blockNumber,\n );\n }\n\n private generateDappSuggestedGasFees(\n txParams: TransactionParams,\n origin?: string,\n ): DappSuggestedGasFees | undefined {\n if (!origin || origin === ORIGIN_METAMASK) {\n return undefined;\n }\n\n const { gasPrice, maxFeePerGas, maxPriorityFeePerGas, gas } = txParams;\n\n if (\n gasPrice === undefined &&\n maxFeePerGas === undefined &&\n maxPriorityFeePerGas === undefined &&\n gas === undefined\n ) {\n return undefined;\n }\n\n const dappSuggestedGasFees: DappSuggestedGasFees = {};\n\n if (gasPrice !== undefined) {\n dappSuggestedGasFees.gasPrice = gasPrice;\n } else if (\n maxFeePerGas !== undefined ||\n maxPriorityFeePerGas !== undefined\n ) {\n dappSuggestedGasFees.maxFeePerGas = maxFeePerGas;\n dappSuggestedGasFees.maxPriorityFeePerGas = maxPriorityFeePerGas;\n }\n\n if (gas !== undefined) {\n dappSuggestedGasFees.gas = gas;\n }\n\n return dappSuggestedGasFees;\n }\n\n /**\n * Validates and adds external provided transaction to state.\n *\n * @param transactionMeta - Nominated external transaction to be added to state.\n * @returns The new transaction.\n */\n private addExternalTransaction(transactionMeta: TransactionMeta) {\n const { chainId } = transactionMeta;\n const { transactions } = this.state;\n const fromAddress = transactionMeta?.txParams?.from;\n const sameFromAndNetworkTransactions = transactions.filter(\n (transaction) =>\n transaction.txParams.from === fromAddress &&\n transaction.chainId === chainId,\n );\n const confirmedTxs = sameFromAndNetworkTransactions.filter(\n (transaction) => transaction.status === TransactionStatus.confirmed,\n );\n const pendingTxs = sameFromAndNetworkTransactions.filter(\n (transaction) => transaction.status === TransactionStatus.submitted,\n );\n\n validateConfirmedExternalTransaction(\n transactionMeta,\n confirmedTxs,\n pendingTxs,\n );\n\n // Make sure provided external transaction has non empty history array\n const newTransactionMeta =\n (transactionMeta.history ?? []).length === 0 && !this.isHistoryDisabled\n ? addInitialHistorySnapshot(transactionMeta)\n : transactionMeta;\n\n this.update((state) => {\n state.transactions = this.trimTransactionsForState([\n ...state.transactions,\n newTransactionMeta,\n ]);\n });\n\n return newTransactionMeta;\n }\n\n /**\n * Sets other txMeta statuses to dropped if the txMeta that has been confirmed has other transactions\n * in the transactions have the same nonce.\n *\n * @param transactionId - Used to identify original transaction.\n */\n private markNonceDuplicatesDropped(transactionId: string) {\n const transactionMeta = this.getTransaction(transactionId);\n if (!transactionMeta) {\n return;\n }\n const nonce = transactionMeta.txParams?.nonce;\n const from = transactionMeta.txParams?.from;\n const { chainId } = transactionMeta;\n\n const sameNonceTransactions = this.state.transactions.filter(\n (transaction) =>\n transaction.id !== transactionId &&\n transaction.txParams.from === from &&\n transaction.txParams.nonce === nonce &&\n transaction.chainId === chainId &&\n transaction.type !== TransactionType.incoming,\n );\n const sameNonceTransactionIds = sameNonceTransactions.map(\n (transaction) => transaction.id,\n );\n\n if (sameNonceTransactions.length === 0) {\n return;\n }\n\n this.update((state) => {\n for (const transaction of state.transactions) {\n if (sameNonceTransactionIds.includes(transaction.id)) {\n transaction.replacedBy = transactionMeta?.hash;\n transaction.replacedById = transactionMeta?.id;\n }\n }\n });\n\n for (const transaction of this.state.transactions) {\n if (\n sameNonceTransactionIds.includes(transaction.id) &&\n transaction.status !== TransactionStatus.failed\n ) {\n this.setTransactionStatusDropped(transaction);\n }\n }\n }\n\n /**\n * Method to set transaction status to dropped.\n *\n * @param transactionMeta - TransactionMeta of transaction to be marked as dropped.\n */\n private setTransactionStatusDropped(transactionMeta: TransactionMeta) {\n const updatedTransactionMeta = {\n ...transactionMeta,\n status: TransactionStatus.dropped as const,\n };\n this.messagingSystem.publish(`${controllerName}:transactionDropped`, {\n transactionMeta: updatedTransactionMeta,\n });\n this.updateTransaction(\n updatedTransactionMeta,\n 'TransactionController#setTransactionStatusDropped - Transaction dropped',\n );\n this.onTransactionStatusChange(updatedTransactionMeta);\n }\n\n /**\n * Get transaction with provided actionId.\n *\n * @param actionId - Unique ID to prevent duplicate requests\n * @returns the filtered transaction\n */\n private getTransactionWithActionId(actionId?: string) {\n return this.state.transactions.find(\n (transaction) => actionId && transaction.actionId === actionId,\n );\n }\n\n private async waitForTransactionFinished(\n transactionId: string,\n ): Promise {\n return new Promise((resolve) => {\n this.#internalEvents.once(`${transactionId}:finished`, (txMeta) => {\n resolve(txMeta);\n });\n });\n }\n\n /**\n * Updates the r, s, and v properties of a TransactionMeta object\n * with values from a signed transaction.\n *\n * @param transactionMeta - The TransactionMeta object to update.\n * @param signedTx - The encompassing type for all transaction types containing r, s, and v values.\n * @returns The updated TransactionMeta object.\n */\n private updateTransactionMetaRSV(\n transactionMeta: TransactionMeta,\n signedTx: TypedTransaction,\n ): TransactionMeta {\n const transactionMetaWithRsv = cloneDeep(transactionMeta);\n\n for (const key of ['r', 's', 'v'] as const) {\n const value = signedTx[key];\n\n if (value === undefined || value === null) {\n continue;\n }\n\n transactionMetaWithRsv[key] = add0x(value.toString(16));\n }\n\n return transactionMetaWithRsv;\n }\n\n private async getEIP1559Compatibility(networkClientId?: NetworkClientId) {\n const currentNetworkIsEIP1559Compatible =\n await this.getCurrentNetworkEIP1559Compatibility(networkClientId);\n\n const currentAccountIsEIP1559Compatible =\n await this.getCurrentAccountEIP1559Compatibility();\n\n return (\n currentNetworkIsEIP1559Compatible && currentAccountIsEIP1559Compatible\n );\n }\n\n private async signTransaction(\n transactionMeta: TransactionMeta,\n txParams: TransactionParams,\n ): Promise {\n log('Signing transaction', txParams);\n\n const unsignedEthTx = this.prepareUnsignedEthTx(\n transactionMeta.chainId,\n txParams,\n );\n\n this.approvingTransactionIds.add(transactionMeta.id);\n\n const signedTx = await new Promise((resolve, reject) => {\n this.sign?.(\n unsignedEthTx,\n txParams.from,\n ...this.getAdditionalSignArguments(transactionMeta),\n ).then(resolve, reject);\n\n this.signAbortCallbacks.set(transactionMeta.id, () =>\n reject(new Error('Signing aborted by user')),\n );\n });\n\n this.signAbortCallbacks.delete(transactionMeta.id);\n\n if (!signedTx) {\n log('Skipping signed status as no signed transaction');\n return undefined;\n }\n\n const transactionMetaFromHook = cloneDeep(transactionMeta);\n if (!this.afterSign(transactionMetaFromHook, signedTx)) {\n this.updateTransaction(\n transactionMetaFromHook,\n 'TransactionController#signTransaction - Update after sign',\n );\n\n log('Skipping signed status based on hook');\n\n return undefined;\n }\n\n const transactionMetaWithRsv = {\n ...this.updateTransactionMetaRSV(transactionMetaFromHook, signedTx),\n status: TransactionStatus.signed as const,\n };\n\n this.updateTransaction(\n transactionMetaWithRsv,\n 'TransactionController#approveTransaction - Transaction signed',\n );\n\n this.onTransactionStatusChange(transactionMetaWithRsv);\n\n const rawTx = bufferToHex(signedTx.serialize());\n\n const transactionMetaWithRawTx = merge({}, transactionMetaWithRsv, {\n rawTx,\n });\n\n this.updateTransaction(\n transactionMetaWithRawTx,\n 'TransactionController#approveTransaction - RawTransaction added',\n );\n\n return rawTx;\n }\n\n private onTransactionStatusChange(transactionMeta: TransactionMeta) {\n this.messagingSystem.publish(`${controllerName}:transactionStatusUpdated`, {\n transactionMeta,\n });\n }\n\n private getNonceTrackerTransactions(\n status: TransactionStatus,\n address: string,\n chainId: string = this.getChainId(),\n ) {\n return getAndFormatTransactionsForNonceTracker(\n chainId,\n address,\n status,\n this.state.transactions,\n );\n }\n\n private onConfirmedTransaction(transactionMeta: TransactionMeta) {\n log('Processing confirmed transaction', transactionMeta.id);\n\n this.markNonceDuplicatesDropped(transactionMeta.id);\n\n this.messagingSystem.publish(\n `${controllerName}:transactionConfirmed`,\n transactionMeta,\n );\n\n this.onTransactionStatusChange(transactionMeta);\n\n // Intentional given potential duration of process.\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.updatePostBalance(transactionMeta);\n }\n\n private async updatePostBalance(transactionMeta: TransactionMeta) {\n try {\n if (transactionMeta.type !== TransactionType.swap) {\n return;\n }\n\n const ethQuery = this.#multichainTrackingHelper.getEthQuery({\n networkClientId: transactionMeta.networkClientId,\n chainId: transactionMeta.chainId,\n });\n const { updatedTransactionMeta, approvalTransactionMeta } =\n await updatePostTransactionBalance(transactionMeta, {\n ethQuery,\n getTransaction: this.getTransaction.bind(this),\n updateTransaction: this.updateTransaction.bind(this),\n });\n\n this.messagingSystem.publish(\n `${controllerName}:postTransactionBalanceUpdated`,\n {\n transactionMeta: updatedTransactionMeta,\n approvalTransactionMeta,\n },\n );\n } catch (error) {\n /* istanbul ignore next */\n log('Error while updating post transaction balance', error);\n }\n }\n\n #createNonceTracker({\n provider,\n blockTracker,\n chainId,\n }: {\n provider: Provider;\n blockTracker: BlockTracker;\n chainId?: Hex;\n }): NonceTracker {\n return new NonceTracker({\n // TODO: Fix types\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n provider: provider as any,\n // @ts-expect-error TODO: Fix types\n blockTracker,\n getPendingTransactions: this.#getNonceTrackerPendingTransactions.bind(\n this,\n chainId,\n ),\n getConfirmedTransactions: this.getNonceTrackerTransactions.bind(\n this,\n TransactionStatus.confirmed,\n ),\n });\n }\n\n #createIncomingTransactionHelper({\n blockTracker,\n etherscanRemoteTransactionSource,\n chainId,\n }: {\n blockTracker: BlockTracker;\n etherscanRemoteTransactionSource: EtherscanRemoteTransactionSource;\n chainId?: Hex;\n }): IncomingTransactionHelper {\n const incomingTransactionHelper = new IncomingTransactionHelper({\n blockTracker,\n getCurrentAccount: () => this.#getSelectedAccount(),\n getLastFetchedBlockNumbers: () => this.state.lastFetchedBlockNumbers,\n getChainId: chainId ? () => chainId : this.getChainId.bind(this),\n isEnabled: this.#incomingTransactionOptions.isEnabled,\n queryEntireHistory: this.#incomingTransactionOptions.queryEntireHistory,\n remoteTransactionSource: etherscanRemoteTransactionSource,\n transactionLimit: this.#transactionHistoryLimit,\n updateTransactions: this.#incomingTransactionOptions.updateTransactions,\n });\n\n this.#addIncomingTransactionHelperListeners(incomingTransactionHelper);\n\n return incomingTransactionHelper;\n }\n\n #createPendingTransactionTracker({\n provider,\n blockTracker,\n chainId,\n }: {\n provider: Provider;\n blockTracker: BlockTracker;\n chainId?: Hex;\n }): PendingTransactionTracker {\n const ethQuery = new EthQuery(provider);\n const getChainId = chainId ? () => chainId : this.getChainId.bind(this);\n\n const pendingTransactionTracker = new PendingTransactionTracker({\n blockTracker,\n getChainId,\n getEthQuery: () => ethQuery,\n getTransactions: () => this.state.transactions,\n isResubmitEnabled: this.#pendingTransactionOptions.isResubmitEnabled,\n getGlobalLock: () =>\n this.#multichainTrackingHelper.acquireNonceLockForChainIdKey({\n chainId: getChainId(),\n }),\n publishTransaction: this.publishTransaction.bind(this),\n hooks: {\n beforeCheckPendingTransaction:\n this.beforeCheckPendingTransaction.bind(this),\n beforePublish: this.beforePublish.bind(this),\n },\n });\n\n this.#addPendingTransactionTrackerListeners(pendingTransactionTracker);\n\n return pendingTransactionTracker;\n }\n\n #checkForPendingTransactionAndStartPolling = () => {\n // PendingTransactionTracker reads state through its getTransactions hook\n this.pendingTransactionTracker.startIfPendingTransactions();\n this.#multichainTrackingHelper.checkForPendingTransactionAndStartPolling();\n };\n\n #stopAllTracking() {\n this.pendingTransactionTracker.stop();\n this.#removePendingTransactionTrackerListeners(\n this.pendingTransactionTracker,\n );\n this.incomingTransactionHelper.stop();\n this.#removeIncomingTransactionHelperListeners(\n this.incomingTransactionHelper,\n );\n\n this.#multichainTrackingHelper.stopAllTracking();\n }\n\n #removeIncomingTransactionHelperListeners(\n incomingTransactionHelper: IncomingTransactionHelper,\n ) {\n incomingTransactionHelper.hub.removeAllListeners('transactions');\n incomingTransactionHelper.hub.removeAllListeners(\n 'updatedLastFetchedBlockNumbers',\n );\n }\n\n #addIncomingTransactionHelperListeners(\n incomingTransactionHelper: IncomingTransactionHelper,\n ) {\n incomingTransactionHelper.hub.on(\n 'transactions',\n this.onIncomingTransactions.bind(this),\n );\n incomingTransactionHelper.hub.on(\n 'updatedLastFetchedBlockNumbers',\n this.onUpdatedLastFetchedBlockNumbers.bind(this),\n );\n }\n\n #removePendingTransactionTrackerListeners(\n pendingTransactionTracker: PendingTransactionTracker,\n ) {\n pendingTransactionTracker.hub.removeAllListeners('transaction-confirmed');\n pendingTransactionTracker.hub.removeAllListeners('transaction-dropped');\n pendingTransactionTracker.hub.removeAllListeners('transaction-failed');\n pendingTransactionTracker.hub.removeAllListeners('transaction-updated');\n }\n\n #addPendingTransactionTrackerListeners(\n pendingTransactionTracker: PendingTransactionTracker,\n ) {\n pendingTransactionTracker.hub.on(\n 'transaction-confirmed',\n this.onConfirmedTransaction.bind(this),\n );\n\n pendingTransactionTracker.hub.on(\n 'transaction-dropped',\n this.setTransactionStatusDropped.bind(this),\n );\n\n pendingTransactionTracker.hub.on(\n 'transaction-failed',\n this.failTransaction.bind(this),\n );\n\n pendingTransactionTracker.hub.on(\n 'transaction-updated',\n this.updateTransaction.bind(this),\n );\n }\n\n #getNonceTrackerPendingTransactions(\n chainId: string | undefined,\n address: string,\n ) {\n const standardPendingTransactions = this.getNonceTrackerTransactions(\n TransactionStatus.submitted,\n address,\n chainId,\n );\n\n const externalPendingTransactions = this.getExternalPendingTransactions(\n address,\n chainId,\n );\n return [...standardPendingTransactions, ...externalPendingTransactions];\n }\n\n private async publishTransactionForRetry(\n ethQuery: EthQuery,\n rawTx: string,\n transactionMeta: TransactionMeta,\n ): Promise {\n try {\n const hash = await this.publishTransaction(ethQuery, rawTx);\n return hash;\n } catch (error: unknown) {\n if (this.isTransactionAlreadyConfirmedError(error as Error)) {\n await this.pendingTransactionTracker.forceCheckTransaction(\n transactionMeta,\n );\n throw new Error('Previous transaction is already confirmed');\n }\n throw error;\n }\n }\n\n /**\n * Ensures that error is a nonce issue\n *\n * @param error - The error to check\n * @returns Whether or not the error is a nonce issue\n */\n // TODO: Replace `any` with type\n // Some networks are returning original error in the data field\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private isTransactionAlreadyConfirmedError(error: any): boolean {\n return (\n error?.message?.includes('nonce too low') ||\n error?.data?.message?.includes('nonce too low')\n );\n }\n\n #getGasFeeFlows(): GasFeeFlow[] {\n if (this.#testGasFeeFlows) {\n return [new TestGasFeeFlow()];\n }\n\n return [new LineaGasFeeFlow(), new DefaultGasFeeFlow()];\n }\n\n #getLayer1GasFeeFlows(): Layer1GasFeeFlow[] {\n return [new OptimismLayer1GasFeeFlow(), new ScrollLayer1GasFeeFlow()];\n }\n\n #updateTransactionInternal(\n {\n transactionId,\n note,\n skipHistory,\n skipValidation,\n }: {\n transactionId: string;\n note?: string;\n skipHistory?: boolean;\n skipValidation?: boolean;\n },\n callback: (transactionMeta: TransactionMeta) => TransactionMeta | void,\n ): Readonly {\n let updatedTransactionParams: (keyof TransactionParams)[] = [];\n\n this.update((state) => {\n const index = state.transactions.findIndex(\n ({ id }) => id === transactionId,\n );\n\n let transactionMeta = state.transactions[index];\n\n // eslint-disable-next-line n/callback-return\n transactionMeta = callback(transactionMeta) ?? transactionMeta;\n\n if (skipValidation !== true) {\n transactionMeta.txParams = normalizeTransactionParams(\n transactionMeta.txParams,\n );\n\n validateTxParams(transactionMeta.txParams);\n }\n\n updatedTransactionParams =\n this.#checkIfTransactionParamsUpdated(transactionMeta);\n\n const shouldSkipHistory = this.isHistoryDisabled || skipHistory;\n\n if (!shouldSkipHistory) {\n transactionMeta = updateTransactionHistory(\n transactionMeta,\n note ?? 'Transaction updated',\n );\n }\n state.transactions[index] = transactionMeta;\n });\n\n const transactionMeta = this.getTransaction(\n transactionId,\n ) as TransactionMeta;\n\n if (updatedTransactionParams.length > 0) {\n this.#onTransactionParamsUpdated(\n transactionMeta,\n updatedTransactionParams,\n );\n }\n\n return transactionMeta;\n }\n\n #checkIfTransactionParamsUpdated(newTransactionMeta: TransactionMeta) {\n const { id: transactionId, txParams: newParams } = newTransactionMeta;\n\n const originalParams = this.getTransaction(transactionId)?.txParams;\n\n if (!originalParams || isEqual(originalParams, newParams)) {\n return [];\n }\n\n const params = Object.keys(newParams) as (keyof TransactionParams)[];\n\n const updatedProperties = params.filter(\n (param) => newParams[param] !== originalParams[param],\n );\n\n log(\n 'Transaction parameters have been updated',\n transactionId,\n updatedProperties,\n originalParams,\n newParams,\n );\n\n return updatedProperties;\n }\n\n #onTransactionParamsUpdated(\n transactionMeta: TransactionMeta,\n updatedParams: (keyof TransactionParams)[],\n ) {\n if (\n (['to', 'value', 'data'] as const).some((param) =>\n updatedParams.includes(param),\n )\n ) {\n log('Updating simulation data due to transaction parameter update');\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.#updateSimulationData(transactionMeta);\n }\n }\n\n async #updateSimulationData(transactionMeta: TransactionMeta) {\n const { id: transactionId, chainId, txParams } = transactionMeta;\n const { from, to, value, data } = txParams;\n\n let simulationData: SimulationData = {\n error: {\n code: SimulationErrorCode.Disabled,\n message: 'Simulation disabled',\n },\n tokenBalanceChanges: [],\n };\n\n if (this.#isSimulationEnabled()) {\n this.#updateTransactionInternal(\n { transactionId, skipHistory: true },\n (txMeta) => {\n txMeta.simulationData = undefined;\n },\n );\n\n simulationData = await getSimulationData({\n chainId,\n from: from as Hex,\n to: to as Hex,\n value: value as Hex,\n data: data as Hex,\n });\n }\n\n const finalTransactionMeta = this.getTransaction(transactionId);\n\n /* istanbul ignore if */\n if (!finalTransactionMeta) {\n log(\n 'Cannot update simulation data as transaction not found',\n transactionId,\n simulationData,\n );\n\n return;\n }\n\n this.#updateTransactionInternal(\n {\n transactionId,\n note: 'TransactionController#updateSimulationData - Update simulation data',\n },\n (txMeta) => {\n txMeta.simulationData = simulationData;\n },\n );\n\n log('Updated simulation data', transactionId, simulationData);\n }\n\n #onGasFeePollerTransactionUpdate({\n transactionId,\n gasFeeEstimates,\n gasFeeEstimatesLoaded,\n layer1GasFee,\n }: {\n transactionId: string;\n gasFeeEstimates?: GasFeeEstimates;\n gasFeeEstimatesLoaded?: boolean;\n layer1GasFee?: Hex;\n }) {\n this.#updateTransactionInternal(\n { transactionId, skipHistory: true },\n (txMeta) => {\n if (gasFeeEstimates) {\n txMeta.gasFeeEstimates = gasFeeEstimates;\n }\n\n if (gasFeeEstimatesLoaded !== undefined) {\n txMeta.gasFeeEstimatesLoaded = gasFeeEstimatesLoaded;\n }\n\n if (layer1GasFee) {\n txMeta.layer1GasFee = layer1GasFee;\n }\n },\n );\n }\n\n #getNetworkClientId({\n networkClientId: requestNetworkClientId,\n chainId,\n }: {\n networkClientId?: NetworkClientId;\n chainId?: Hex;\n }) {\n const globalChainId = this.#getGlobalChainId();\n const globalNetworkClientId = this.#getGlobalNetworkClientId();\n\n if (requestNetworkClientId) {\n return requestNetworkClientId;\n }\n\n if (!chainId || chainId === globalChainId) {\n return globalNetworkClientId;\n }\n\n return this.messagingSystem.call(\n `NetworkController:findNetworkClientIdByChainId`,\n chainId,\n );\n }\n\n #getGlobalNetworkClientId() {\n return this.getNetworkState().selectedNetworkClientId;\n }\n\n #getGlobalChainId() {\n return this.messagingSystem.call(\n `NetworkController:getNetworkClientById`,\n this.getNetworkState().selectedNetworkClientId,\n ).configuration.chainId;\n }\n\n #isCustomNetwork(networkClientId?: NetworkClientId) {\n const globalNetworkClientId = this.#getGlobalNetworkClientId();\n\n if (!networkClientId || networkClientId === globalNetworkClientId) {\n return !isInfuraNetworkType(\n this.getNetworkState().selectedNetworkClientId,\n );\n }\n\n return (\n this.messagingSystem.call(\n `NetworkController:getNetworkClientById`,\n networkClientId,\n ).configuration.type === NetworkClientType.Custom\n );\n }\n\n #getSelectedAccount() {\n return this.messagingSystem.call('AccountsController:getSelectedAccount');\n }\n}\n"]} +\ No newline at end of file +diff --git a/dist/chunk-YQYO6EGF.mjs b/dist/chunk-YQYO6EGF.mjs +new file mode 100644 +index 0000000000000000000000000000000000000000..a055cb8a748cd205a7dab47019d1e1a747c20c05 +--- /dev/null ++++ b/dist/chunk-YQYO6EGF.mjs +@@ -0,0 +1,2556 @@ ++import { ++ getAndFormatTransactionsForNonceTracker, ++ getNextNonce ++} from "./chunk-6DDVVUJC.mjs"; ++import { ++ getSimulationData ++} from "./chunk-EVL6KODQ.mjs"; ++import { ++ determineTransactionType ++} from "./chunk-KG4UW4K4.mjs"; ++import { ++ validateTransactionOrigin, ++ validateTxParams ++} from "./chunk-5ZEJT5SN.mjs"; ++import { ++ PendingTransactionTracker ++} from "./chunk-7M2R5AHC.mjs"; ++import { ++ validateConfirmedExternalTransaction ++} from "./chunk-FRKQ3Z2L.mjs"; ++import { ++ addGasBuffer, ++ estimateGas, ++ updateGas ++} from "./chunk-5G6OHAXI.mjs"; ++import { ++ addInitialHistorySnapshot, ++ updateTransactionHistory ++} from "./chunk-XGRAHX6T.mjs"; ++import { ++ OptimismLayer1GasFeeFlow ++} from "./chunk-VEVVBHP3.mjs"; ++import { ++ ScrollLayer1GasFeeFlow ++} from "./chunk-Z4GV3YQQ.mjs"; ++import { ++ TestGasFeeFlow ++} from "./chunk-FMRLPVFZ.mjs"; ++import { ++ GasFeePoller ++} from "./chunk-SFFTNB2X.mjs"; ++import { ++ getTransactionLayer1GasFee, ++ updateTransactionLayer1GasFee ++} from "./chunk-NOHEXQ7Y.mjs"; ++import { ++ IncomingTransactionHelper ++} from "./chunk-3ZV5YEUV.mjs"; ++import { ++ MultichainTrackingHelper ++} from "./chunk-4V4XIPCI.mjs"; ++import { ++ EtherscanRemoteTransactionSource ++} from "./chunk-EKJXGERC.mjs"; ++import { ++ LineaGasFeeFlow ++} from "./chunk-UHG2LLVV.mjs"; ++import { ++ DefaultGasFeeFlow ++} from "./chunk-H2KZOK3J.mjs"; ++import { ++ updateGasFees ++} from "./chunk-VXNPVIYL.mjs"; ++import { ++ updatePostTransactionBalance, ++ updateSwapsTransaction ++} from "./chunk-GNAL5HC2.mjs"; ++import { ++ getIncreasedPriceFromExisting, ++ isEIP1559Transaction, ++ isFeeMarketEIP1559Values, ++ isGasPriceValue, ++ normalizeGasFeeValues, ++ normalizeTransactionParams, ++ normalizeTxError, ++ validateGasValues, ++ validateIfTransactionUnapproved, ++ validateMinimumIncrease ++} from "./chunk-Q56I5ONX.mjs"; ++import { ++ getGasFeeFlow ++} from "./chunk-JXXTNVU4.mjs"; ++import { ++ projectLogger ++} from "./chunk-UQQWZT6C.mjs"; ++import { ++ __privateAdd, ++ __privateGet, ++ __privateMethod, ++ __privateSet ++} from "./chunk-XUI43LEZ.mjs"; ++ ++// src/TransactionController.ts ++import { Hardfork, Common } from "@ethereumjs/common"; ++import { TransactionFactory } from "@ethereumjs/tx"; ++import { bufferToHex } from "@ethereumjs/util"; ++import { BaseController } from "@metamask/base-controller"; ++import { ++ query, ++ ApprovalType, ++ ORIGIN_METAMASK, ++ convertHexToDecimal, ++ isInfuraNetworkType ++} from "@metamask/controller-utils"; ++import EthQuery from "@metamask/eth-query"; ++import { NetworkClientType } from "@metamask/network-controller"; ++import { NonceTracker } from "@metamask/nonce-tracker"; ++import { errorCodes, rpcErrors, providerErrors } from "@metamask/rpc-errors"; ++import { add0x } from "@metamask/utils"; ++import { Mutex } from "async-mutex"; ++import { MethodRegistry } from "eth-method-registry"; ++import { EventEmitter } from "events"; ++import { cloneDeep, mapValues, merge, pickBy, sortBy, isEqual } from "lodash"; ++import { v1 as random } from "uuid"; ++var metadata = { ++ transactions: { ++ persist: true, ++ anonymous: false ++ }, ++ methodData: { ++ persist: true, ++ anonymous: false ++ }, ++ lastFetchedBlockNumbers: { ++ persist: true, ++ anonymous: false ++ } ++}; ++var HARDFORK = Hardfork.London; ++var CANCEL_RATE = 1.1; ++var SPEED_UP_RATE = 1.1; ++var controllerName = "TransactionController"; ++var ApprovalState = /* @__PURE__ */ ((ApprovalState2) => { ++ ApprovalState2["Approved"] = "approved"; ++ ApprovalState2["NotApproved"] = "not-approved"; ++ ApprovalState2["SkippedViaBeforePublishHook"] = "skipped-via-before-publish-hook"; ++ return ApprovalState2; ++})(ApprovalState || {}); ++function getDefaultTransactionControllerState() { ++ return { ++ methodData: {}, ++ transactions: [], ++ lastFetchedBlockNumbers: {} ++ }; ++} ++var _internalEvents, _incomingTransactionOptions, _pendingTransactionOptions, _transactionHistoryLimit, _isSimulationEnabled, _testGasFeeFlows, _multichainTrackingHelper, _createNonceTracker, createNonceTracker_fn, _createIncomingTransactionHelper, createIncomingTransactionHelper_fn, _createPendingTransactionTracker, createPendingTransactionTracker_fn, _checkForPendingTransactionAndStartPolling, _stopAllTracking, stopAllTracking_fn, _removeIncomingTransactionHelperListeners, removeIncomingTransactionHelperListeners_fn, _addIncomingTransactionHelperListeners, addIncomingTransactionHelperListeners_fn, _removePendingTransactionTrackerListeners, removePendingTransactionTrackerListeners_fn, _addPendingTransactionTrackerListeners, addPendingTransactionTrackerListeners_fn, _getNonceTrackerPendingTransactions, getNonceTrackerPendingTransactions_fn, _getGasFeeFlows, getGasFeeFlows_fn, _getLayer1GasFeeFlows, getLayer1GasFeeFlows_fn, _updateTransactionInternal, updateTransactionInternal_fn, _checkIfTransactionParamsUpdated, checkIfTransactionParamsUpdated_fn, _onTransactionParamsUpdated, onTransactionParamsUpdated_fn, _updateSimulationData, updateSimulationData_fn, _onGasFeePollerTransactionUpdate, onGasFeePollerTransactionUpdate_fn, _getNetworkClientId, getNetworkClientId_fn, _getGlobalNetworkClientId, getGlobalNetworkClientId_fn, _getGlobalChainId, getGlobalChainId_fn, _isCustomNetwork, isCustomNetwork_fn, _getSelectedAccount, getSelectedAccount_fn; ++var TransactionController = class extends BaseController { ++ /** ++ * Constructs a TransactionController. ++ * ++ * @param options - The controller options. ++ * @param options.blockTracker - The block tracker used to poll for new blocks data. ++ * @param options.disableHistory - Whether to disable storing history in transaction metadata. ++ * @param options.disableSendFlowHistory - Explicitly disable transaction metadata history. ++ * @param options.disableSwaps - Whether to disable additional processing on swaps transactions. ++ * @param options.getCurrentAccountEIP1559Compatibility - Whether or not the account supports EIP-1559. ++ * @param options.getCurrentNetworkEIP1559Compatibility - Whether or not the network supports EIP-1559. ++ * @param options.getExternalPendingTransactions - Callback to retrieve pending transactions from external sources. ++ * @param options.getGasFeeEstimates - Callback to retrieve gas fee estimates. ++ * @param options.getNetworkClientRegistry - Gets the network client registry. ++ * @param options.getNetworkState - Gets the state of the network controller. ++ * @param options.getPermittedAccounts - Get accounts that a given origin has permissions for. ++ * @param options.getSavedGasFees - Gets the saved gas fee config. ++ * @param options.incomingTransactions - Configuration options for incoming transaction support. ++ * @param options.isMultichainEnabled - Enable multichain support. ++ * @param options.isSimulationEnabled - Whether new transactions will be automatically simulated. ++ * @param options.messenger - The controller messenger. ++ * @param options.onNetworkStateChange - Allows subscribing to network controller state changes. ++ * @param options.pendingTransactions - Configuration options for pending transaction support. ++ * @param options.provider - The provider used to create the underlying EthQuery instance. ++ * @param options.securityProviderRequest - A function for verifying a transaction, whether it is malicious or not. ++ * @param options.sign - Function used to sign transactions. ++ * @param options.state - Initial state to set on this controller. ++ * @param options.testGasFeeFlows - Whether to use the test gas fee flow. ++ * @param options.transactionHistoryLimit - Transaction history limit. ++ * @param options.hooks - The controller hooks. ++ */ ++ constructor({ ++ blockTracker, ++ disableHistory, ++ disableSendFlowHistory, ++ disableSwaps, ++ getCurrentAccountEIP1559Compatibility, ++ getCurrentNetworkEIP1559Compatibility, ++ getExternalPendingTransactions, ++ getGasFeeEstimates, ++ getNetworkClientRegistry, ++ getNetworkState, ++ getPermittedAccounts, ++ getSavedGasFees, ++ incomingTransactions = {}, ++ isMultichainEnabled = false, ++ isSimulationEnabled, ++ messenger, ++ onNetworkStateChange, ++ pendingTransactions = {}, ++ provider, ++ securityProviderRequest, ++ sign, ++ state, ++ testGasFeeFlows, ++ transactionHistoryLimit = 40, ++ hooks ++ }) { ++ super({ ++ name: controllerName, ++ metadata, ++ messenger, ++ state: { ++ ...getDefaultTransactionControllerState(), ++ ...state ++ } ++ }); ++ __privateAdd(this, _createNonceTracker); ++ __privateAdd(this, _createIncomingTransactionHelper); ++ __privateAdd(this, _createPendingTransactionTracker); ++ __privateAdd(this, _stopAllTracking); ++ __privateAdd(this, _removeIncomingTransactionHelperListeners); ++ __privateAdd(this, _addIncomingTransactionHelperListeners); ++ __privateAdd(this, _removePendingTransactionTrackerListeners); ++ __privateAdd(this, _addPendingTransactionTrackerListeners); ++ __privateAdd(this, _getNonceTrackerPendingTransactions); ++ __privateAdd(this, _getGasFeeFlows); ++ __privateAdd(this, _getLayer1GasFeeFlows); ++ __privateAdd(this, _updateTransactionInternal); ++ __privateAdd(this, _checkIfTransactionParamsUpdated); ++ __privateAdd(this, _onTransactionParamsUpdated); ++ __privateAdd(this, _updateSimulationData); ++ __privateAdd(this, _onGasFeePollerTransactionUpdate); ++ __privateAdd(this, _getNetworkClientId); ++ __privateAdd(this, _getGlobalNetworkClientId); ++ __privateAdd(this, _getGlobalChainId); ++ __privateAdd(this, _isCustomNetwork); ++ __privateAdd(this, _getSelectedAccount); ++ __privateAdd(this, _internalEvents, new EventEmitter()); ++ this.approvingTransactionIds = /* @__PURE__ */ new Set(); ++ this.mutex = new Mutex(); ++ __privateAdd(this, _incomingTransactionOptions, void 0); ++ __privateAdd(this, _pendingTransactionOptions, void 0); ++ this.signAbortCallbacks = /* @__PURE__ */ new Map(); ++ __privateAdd(this, _transactionHistoryLimit, void 0); ++ __privateAdd(this, _isSimulationEnabled, void 0); ++ __privateAdd(this, _testGasFeeFlows, void 0); ++ __privateAdd(this, _multichainTrackingHelper, void 0); ++ __privateAdd(this, _checkForPendingTransactionAndStartPolling, () => { ++ this.pendingTransactionTracker.startIfPendingTransactions(); ++ __privateGet(this, _multichainTrackingHelper).checkForPendingTransactionAndStartPolling(); ++ }); ++ this.messagingSystem = messenger; ++ this.getNetworkState = getNetworkState; ++ this.isSendFlowHistoryDisabled = disableSendFlowHistory ?? false; ++ this.isHistoryDisabled = disableHistory ?? false; ++ this.isSwapsDisabled = disableSwaps ?? false; ++ __privateSet(this, _isSimulationEnabled, isSimulationEnabled ?? (() => true)); ++ this.registry = new MethodRegistry({ provider }); ++ this.getSavedGasFees = getSavedGasFees ?? ((_chainId) => void 0); ++ this.getCurrentAccountEIP1559Compatibility = getCurrentAccountEIP1559Compatibility ?? (() => Promise.resolve(true)); ++ this.getCurrentNetworkEIP1559Compatibility = getCurrentNetworkEIP1559Compatibility; ++ this.getGasFeeEstimates = getGasFeeEstimates || (() => Promise.resolve({})); ++ this.getPermittedAccounts = getPermittedAccounts; ++ this.getExternalPendingTransactions = getExternalPendingTransactions ?? (() => []); ++ this.securityProviderRequest = securityProviderRequest; ++ __privateSet(this, _incomingTransactionOptions, incomingTransactions); ++ __privateSet(this, _pendingTransactionOptions, pendingTransactions); ++ __privateSet(this, _transactionHistoryLimit, transactionHistoryLimit); ++ this.sign = sign; ++ __privateSet(this, _testGasFeeFlows, testGasFeeFlows === true); ++ this.afterSign = hooks?.afterSign ?? (() => true); ++ this.beforeCheckPendingTransaction = hooks?.beforeCheckPendingTransaction ?? /* istanbul ignore next */ ++ (() => true); ++ this.beforePublish = hooks?.beforePublish ?? (() => true); ++ this.getAdditionalSignArguments = hooks?.getAdditionalSignArguments ?? (() => []); ++ this.publish = hooks?.publish ?? (() => Promise.resolve({ transactionHash: void 0 })); ++ this.nonceTracker = __privateMethod(this, _createNonceTracker, createNonceTracker_fn).call(this, { ++ provider, ++ blockTracker ++ }); ++ const findNetworkClientIdByChainId = (chainId) => { ++ return this.messagingSystem.call( ++ `NetworkController:findNetworkClientIdByChainId`, ++ chainId ++ ); ++ }; ++ __privateSet(this, _multichainTrackingHelper, new MultichainTrackingHelper({ ++ isMultichainEnabled, ++ provider, ++ nonceTracker: this.nonceTracker, ++ incomingTransactionOptions: incomingTransactions, ++ findNetworkClientIdByChainId, ++ getNetworkClientById: (networkClientId) => { ++ return this.messagingSystem.call( ++ `NetworkController:getNetworkClientById`, ++ networkClientId ++ ); ++ }, ++ getNetworkClientRegistry, ++ removeIncomingTransactionHelperListeners: __privateMethod(this, _removeIncomingTransactionHelperListeners, removeIncomingTransactionHelperListeners_fn).bind(this), ++ removePendingTransactionTrackerListeners: __privateMethod(this, _removePendingTransactionTrackerListeners, removePendingTransactionTrackerListeners_fn).bind(this), ++ createNonceTracker: __privateMethod(this, _createNonceTracker, createNonceTracker_fn).bind(this), ++ createIncomingTransactionHelper: __privateMethod(this, _createIncomingTransactionHelper, createIncomingTransactionHelper_fn).bind(this), ++ createPendingTransactionTracker: __privateMethod(this, _createPendingTransactionTracker, createPendingTransactionTracker_fn).bind(this), ++ onNetworkStateChange: (listener) => { ++ this.messagingSystem.subscribe( ++ "NetworkController:stateChange", ++ listener ++ ); ++ } ++ })); ++ __privateGet(this, _multichainTrackingHelper).initialize(); ++ const etherscanRemoteTransactionSource = new EtherscanRemoteTransactionSource({ ++ includeTokenTransfers: incomingTransactions.includeTokenTransfers ++ }); ++ this.incomingTransactionHelper = __privateMethod(this, _createIncomingTransactionHelper, createIncomingTransactionHelper_fn).call(this, { ++ blockTracker, ++ etherscanRemoteTransactionSource ++ }); ++ this.pendingTransactionTracker = __privateMethod(this, _createPendingTransactionTracker, createPendingTransactionTracker_fn).call(this, { ++ provider, ++ blockTracker ++ }); ++ this.gasFeeFlows = __privateMethod(this, _getGasFeeFlows, getGasFeeFlows_fn).call(this); ++ this.layer1GasFeeFlows = __privateMethod(this, _getLayer1GasFeeFlows, getLayer1GasFeeFlows_fn).call(this); ++ const gasFeePoller = new GasFeePoller({ ++ findNetworkClientIdByChainId, ++ gasFeeFlows: this.gasFeeFlows, ++ getGasFeeControllerEstimates: this.getGasFeeEstimates, ++ getProvider: (chainId, networkClientId) => __privateGet(this, _multichainTrackingHelper).getProvider({ ++ networkClientId, ++ chainId ++ }), ++ getTransactions: () => this.state.transactions, ++ layer1GasFeeFlows: this.layer1GasFeeFlows, ++ onStateChange: (listener) => { ++ this.messagingSystem.subscribe( ++ "TransactionController:stateChange", ++ listener ++ ); ++ } ++ }); ++ gasFeePoller.hub.on( ++ "transaction-updated", ++ __privateMethod(this, _onGasFeePollerTransactionUpdate, onGasFeePollerTransactionUpdate_fn).bind(this) ++ ); ++ this.messagingSystem.subscribe( ++ "TransactionController:stateChange", ++ __privateGet(this, _checkForPendingTransactionAndStartPolling) ++ ); ++ onNetworkStateChange(() => { ++ projectLogger("Detected network change", this.getChainId()); ++ this.pendingTransactionTracker.startIfPendingTransactions(); ++ this.onBootCleanup(); ++ }); ++ this.onBootCleanup(); ++ __privateGet(this, _checkForPendingTransactionAndStartPolling).call(this); ++ } ++ failTransaction(transactionMeta, error, actionId) { ++ let newTransactionMeta; ++ try { ++ newTransactionMeta = __privateMethod(this, _updateTransactionInternal, updateTransactionInternal_fn).call(this, { ++ transactionId: transactionMeta.id, ++ note: "TransactionController#failTransaction - Add error message and set status to failed", ++ skipValidation: true ++ }, (draftTransactionMeta) => { ++ draftTransactionMeta.status = "failed" /* failed */; ++ draftTransactionMeta.error = normalizeTxError(error); ++ }); ++ } catch (err) { ++ projectLogger("Failed to mark transaction as failed", err); ++ newTransactionMeta = { ++ ...transactionMeta, ++ status: "failed" /* failed */, ++ error: normalizeTxError(error) ++ }; ++ } ++ this.messagingSystem.publish(`${controllerName}:transactionFailed`, { ++ actionId, ++ error: error.message, ++ transactionMeta: newTransactionMeta ++ }); ++ this.onTransactionStatusChange(newTransactionMeta); ++ this.messagingSystem.publish( ++ `${controllerName}:transactionFinished`, ++ newTransactionMeta ++ ); ++ __privateGet(this, _internalEvents).emit( ++ `${transactionMeta.id}:finished`, ++ newTransactionMeta ++ ); ++ } ++ async registryLookup(fourBytePrefix) { ++ const registryMethod = await this.registry.lookup(fourBytePrefix); ++ if (!registryMethod) { ++ return { ++ registryMethod: "", ++ parsedRegistryMethod: { name: void 0, args: void 0 } ++ }; ++ } ++ const parsedRegistryMethod = this.registry.parse(registryMethod); ++ return { registryMethod, parsedRegistryMethod }; ++ } ++ /** ++ * Stops polling and removes listeners to prepare the controller for garbage collection. ++ */ ++ destroy() { ++ __privateMethod(this, _stopAllTracking, stopAllTracking_fn).call(this); ++ } ++ /** ++ * Handle new method data request. ++ * ++ * @param fourBytePrefix - The method prefix. ++ * @returns The method data object corresponding to the given signature prefix. ++ */ ++ async handleMethodData(fourBytePrefix) { ++ const releaseLock = await this.mutex.acquire(); ++ try { ++ const { methodData } = this.state; ++ const knownMethod = Object.keys(methodData).find( ++ (knownFourBytePrefix) => fourBytePrefix === knownFourBytePrefix ++ ); ++ if (knownMethod) { ++ return methodData[fourBytePrefix]; ++ } ++ const registry = await this.registryLookup(fourBytePrefix); ++ this.update((state) => { ++ state.methodData[fourBytePrefix] = registry; ++ }); ++ return registry; ++ } finally { ++ releaseLock(); ++ } ++ } ++ /** ++ * Add a new unapproved transaction to state. Parameters will be validated, a ++ * unique transaction id will be generated, and gas and gasPrice will be calculated ++ * if not provided. If A `:unapproved` hub event will be emitted once added. ++ * ++ * @param txParams - Standard parameters for an Ethereum transaction. ++ * @param opts - Additional options to control how the transaction is added. ++ * @param opts.actionId - Unique ID to prevent duplicate requests. ++ * @param opts.deviceConfirmedOn - An enum to indicate what device confirmed the transaction. ++ * @param opts.method - RPC method that requested the transaction. ++ * @param opts.origin - The origin of the transaction request, such as a dApp hostname. ++ * @param opts.requireApproval - Whether the transaction requires approval by the user, defaults to true unless explicitly disabled. ++ * @param opts.securityAlertResponse - Response from security validator. ++ * @param opts.sendFlowHistory - The sendFlowHistory entries to add. ++ * @param opts.type - Type of transaction to add, such as 'cancel' or 'swap'. ++ * @param opts.swaps - Options for swaps transactions. ++ * @param opts.swaps.hasApproveTx - Whether the transaction has an approval transaction. ++ * @param opts.swaps.meta - Metadata for swap transaction. ++ * @param opts.networkClientId - The id of the network client for this transaction. ++ * @returns Object containing a promise resolving to the transaction hash if approved. ++ */ ++ async addTransaction(txParams, { ++ actionId, ++ deviceConfirmedOn, ++ method, ++ origin, ++ requireApproval, ++ securityAlertResponse, ++ sendFlowHistory, ++ swaps = {}, ++ type, ++ networkClientId: requestNetworkClientId ++ } = {}) { ++ projectLogger("Adding transaction", txParams); ++ txParams = normalizeTransactionParams(txParams); ++ if (requestNetworkClientId && !__privateGet(this, _multichainTrackingHelper).has(requestNetworkClientId)) { ++ throw new Error( ++ "The networkClientId for this transaction could not be found" ++ ); ++ } ++ const networkClientId = requestNetworkClientId ?? __privateMethod(this, _getGlobalNetworkClientId, getGlobalNetworkClientId_fn).call(this); ++ const isEIP1559Compatible = await this.getEIP1559Compatibility( ++ networkClientId ++ ); ++ validateTxParams(txParams, isEIP1559Compatible); ++ if (origin) { ++ await validateTransactionOrigin( ++ await this.getPermittedAccounts(origin), ++ __privateMethod(this, _getSelectedAccount, getSelectedAccount_fn).call(this).address, ++ txParams.from, ++ origin ++ ); ++ } ++ const dappSuggestedGasFees = this.generateDappSuggestedGasFees( ++ txParams, ++ origin ++ ); ++ const chainId = this.getChainId(networkClientId); ++ const ethQuery = __privateGet(this, _multichainTrackingHelper).getEthQuery({ ++ networkClientId, ++ chainId ++ }); ++ const transactionType = type ?? (await determineTransactionType(txParams, ethQuery)).type; ++ const existingTransactionMeta = this.getTransactionWithActionId(actionId); ++ let addedTransactionMeta = existingTransactionMeta ? cloneDeep(existingTransactionMeta) : { ++ // Add actionId to txMeta to check if same actionId is seen again ++ actionId, ++ chainId, ++ dappSuggestedGasFees, ++ deviceConfirmedOn, ++ id: random(), ++ origin, ++ securityAlertResponse, ++ status: "unapproved" /* unapproved */, ++ time: Date.now(), ++ txParams, ++ userEditedGasLimit: false, ++ verifiedOnBlockchain: false, ++ type: transactionType, ++ networkClientId ++ }; ++ await this.updateGasProperties(addedTransactionMeta); ++ if (!existingTransactionMeta) { ++ if (method && this.securityProviderRequest) { ++ const securityProviderResponse = await this.securityProviderRequest( ++ addedTransactionMeta, ++ method ++ ); ++ addedTransactionMeta.securityProviderResponse = securityProviderResponse; ++ } ++ if (!this.isSendFlowHistoryDisabled) { ++ addedTransactionMeta.sendFlowHistory = sendFlowHistory ?? []; ++ } ++ if (!this.isHistoryDisabled) { ++ addedTransactionMeta = addInitialHistorySnapshot(addedTransactionMeta); ++ } ++ addedTransactionMeta = updateSwapsTransaction( ++ addedTransactionMeta, ++ transactionType, ++ swaps, ++ { ++ isSwapsDisabled: this.isSwapsDisabled, ++ cancelTransaction: this.cancelTransaction.bind(this), ++ messenger: this.messagingSystem ++ } ++ ); ++ this.addMetadata(addedTransactionMeta); ++ if (requireApproval !== false) { ++ __privateMethod(this, _updateSimulationData, updateSimulationData_fn).call(this, addedTransactionMeta); ++ } else { ++ projectLogger("Skipping simulation as approval not required"); ++ } ++ this.messagingSystem.publish( ++ `${controllerName}:unapprovedTransactionAdded`, ++ addedTransactionMeta ++ ); ++ } ++ return { ++ result: this.processApproval(addedTransactionMeta, { ++ isExisting: Boolean(existingTransactionMeta), ++ requireApproval, ++ actionId ++ }), ++ transactionMeta: addedTransactionMeta ++ }; ++ } ++ startIncomingTransactionPolling(networkClientIds = []) { ++ if (networkClientIds.length === 0) { ++ this.incomingTransactionHelper.start(); ++ return; ++ } ++ __privateGet(this, _multichainTrackingHelper).startIncomingTransactionPolling( ++ networkClientIds ++ ); ++ } ++ stopIncomingTransactionPolling(networkClientIds = []) { ++ if (networkClientIds.length === 0) { ++ this.incomingTransactionHelper.stop(); ++ return; ++ } ++ __privateGet(this, _multichainTrackingHelper).stopIncomingTransactionPolling( ++ networkClientIds ++ ); ++ } ++ stopAllIncomingTransactionPolling() { ++ this.incomingTransactionHelper.stop(); ++ __privateGet(this, _multichainTrackingHelper).stopAllIncomingTransactionPolling(); ++ } ++ async updateIncomingTransactions(networkClientIds = []) { ++ if (networkClientIds.length === 0) { ++ await this.incomingTransactionHelper.update(); ++ return; ++ } ++ await __privateGet(this, _multichainTrackingHelper).updateIncomingTransactions( ++ networkClientIds ++ ); ++ } ++ /** ++ * Attempts to cancel a transaction based on its ID by setting its status to "rejected" ++ * and emitting a `:finished` hub event. ++ * ++ * @param transactionId - The ID of the transaction to cancel. ++ * @param gasValues - The gas values to use for the cancellation transaction. ++ * @param options - The options for the cancellation transaction. ++ * @param options.actionId - Unique ID to prevent duplicate requests. ++ * @param options.estimatedBaseFee - The estimated base fee of the transaction. ++ */ ++ async stopTransaction(transactionId, gasValues, { ++ estimatedBaseFee, ++ actionId ++ } = {}) { ++ if (this.getTransactionWithActionId(actionId)) { ++ return; ++ } ++ if (gasValues) { ++ gasValues = normalizeGasFeeValues(gasValues); ++ validateGasValues(gasValues); ++ } ++ projectLogger("Creating cancel transaction", transactionId, gasValues); ++ const transactionMeta = this.getTransaction(transactionId); ++ if (!transactionMeta) { ++ return; ++ } ++ if (!this.sign) { ++ throw new Error("No sign method defined."); ++ } ++ const minGasPrice = getIncreasedPriceFromExisting( ++ transactionMeta.txParams.gasPrice, ++ CANCEL_RATE ++ ); ++ const gasPriceFromValues = isGasPriceValue(gasValues) && gasValues.gasPrice; ++ const newGasPrice = gasPriceFromValues && validateMinimumIncrease(gasPriceFromValues, minGasPrice) || minGasPrice; ++ const existingMaxFeePerGas = transactionMeta.txParams?.maxFeePerGas; ++ const minMaxFeePerGas = getIncreasedPriceFromExisting( ++ existingMaxFeePerGas, ++ CANCEL_RATE ++ ); ++ const maxFeePerGasValues = isFeeMarketEIP1559Values(gasValues) && gasValues.maxFeePerGas; ++ const newMaxFeePerGas = maxFeePerGasValues && validateMinimumIncrease(maxFeePerGasValues, minMaxFeePerGas) || existingMaxFeePerGas && minMaxFeePerGas; ++ const existingMaxPriorityFeePerGas = transactionMeta.txParams?.maxPriorityFeePerGas; ++ const minMaxPriorityFeePerGas = getIncreasedPriceFromExisting( ++ existingMaxPriorityFeePerGas, ++ CANCEL_RATE ++ ); ++ const maxPriorityFeePerGasValues = isFeeMarketEIP1559Values(gasValues) && gasValues.maxPriorityFeePerGas; ++ const newMaxPriorityFeePerGas = maxPriorityFeePerGasValues && validateMinimumIncrease( ++ maxPriorityFeePerGasValues, ++ minMaxPriorityFeePerGas ++ ) || existingMaxPriorityFeePerGas && minMaxPriorityFeePerGas; ++ const newTxParams = newMaxFeePerGas && newMaxPriorityFeePerGas ? { ++ from: transactionMeta.txParams.from, ++ gasLimit: transactionMeta.txParams.gas, ++ maxFeePerGas: newMaxFeePerGas, ++ maxPriorityFeePerGas: newMaxPriorityFeePerGas, ++ type: "0x2" /* feeMarket */, ++ nonce: transactionMeta.txParams.nonce, ++ to: transactionMeta.txParams.from, ++ value: "0x0" ++ } : { ++ from: transactionMeta.txParams.from, ++ gasLimit: transactionMeta.txParams.gas, ++ gasPrice: newGasPrice, ++ nonce: transactionMeta.txParams.nonce, ++ to: transactionMeta.txParams.from, ++ value: "0x0" ++ }; ++ const unsignedEthTx = this.prepareUnsignedEthTx( ++ transactionMeta.chainId, ++ newTxParams ++ ); ++ const signedTx = await this.sign( ++ unsignedEthTx, ++ transactionMeta.txParams.from ++ ); ++ const rawTx = bufferToHex(signedTx.serialize()); ++ const newFee = newTxParams.maxFeePerGas ?? newTxParams.gasPrice; ++ const oldFee = newTxParams.maxFeePerGas ? transactionMeta.txParams.maxFeePerGas : transactionMeta.txParams.gasPrice; ++ projectLogger("Submitting cancel transaction", { ++ oldFee, ++ newFee, ++ txParams: newTxParams ++ }); ++ const ethQuery = __privateGet(this, _multichainTrackingHelper).getEthQuery({ ++ networkClientId: transactionMeta.networkClientId, ++ chainId: transactionMeta.chainId ++ }); ++ const hash = await this.publishTransactionForRetry( ++ ethQuery, ++ rawTx, ++ transactionMeta ++ ); ++ const cancelTransactionMeta = { ++ actionId, ++ chainId: transactionMeta.chainId, ++ networkClientId: transactionMeta.networkClientId, ++ estimatedBaseFee, ++ hash, ++ id: random(), ++ originalGasEstimate: transactionMeta.txParams.gas, ++ rawTx, ++ status: "submitted" /* submitted */, ++ time: Date.now(), ++ type: "cancel" /* cancel */, ++ txParams: newTxParams ++ }; ++ this.addMetadata(cancelTransactionMeta); ++ this.messagingSystem.publish(`${controllerName}:transactionApproved`, { ++ transactionMeta: cancelTransactionMeta, ++ actionId ++ }); ++ this.messagingSystem.publish(`${controllerName}:transactionSubmitted`, { ++ transactionMeta: cancelTransactionMeta, ++ actionId ++ }); ++ this.messagingSystem.publish( ++ `${controllerName}:transactionFinished`, ++ cancelTransactionMeta ++ ); ++ __privateGet(this, _internalEvents).emit( ++ `${transactionMeta.id}:finished`, ++ cancelTransactionMeta ++ ); ++ } ++ /** ++ * Attempts to speed up a transaction increasing transaction gasPrice by ten percent. ++ * ++ * @param transactionId - The ID of the transaction to speed up. ++ * @param gasValues - The gas values to use for the speed up transaction. ++ * @param options - The options for the speed up transaction. ++ * @param options.actionId - Unique ID to prevent duplicate requests ++ * @param options.estimatedBaseFee - The estimated base fee of the transaction. ++ */ ++ async speedUpTransaction(transactionId, gasValues, { ++ actionId, ++ estimatedBaseFee ++ } = {}) { ++ if (this.getTransactionWithActionId(actionId)) { ++ return; ++ } ++ if (gasValues) { ++ gasValues = normalizeGasFeeValues(gasValues); ++ validateGasValues(gasValues); ++ } ++ projectLogger("Creating speed up transaction", transactionId, gasValues); ++ const transactionMeta = this.getTransaction(transactionId); ++ if (!transactionMeta) { ++ return; ++ } ++ if (!this.sign) { ++ throw new Error("No sign method defined."); ++ } ++ const minGasPrice = getIncreasedPriceFromExisting( ++ transactionMeta.txParams.gasPrice, ++ SPEED_UP_RATE ++ ); ++ const gasPriceFromValues = isGasPriceValue(gasValues) && gasValues.gasPrice; ++ const newGasPrice = gasPriceFromValues && validateMinimumIncrease(gasPriceFromValues, minGasPrice) || minGasPrice; ++ const existingMaxFeePerGas = transactionMeta.txParams?.maxFeePerGas; ++ const minMaxFeePerGas = getIncreasedPriceFromExisting( ++ existingMaxFeePerGas, ++ SPEED_UP_RATE ++ ); ++ const maxFeePerGasValues = isFeeMarketEIP1559Values(gasValues) && gasValues.maxFeePerGas; ++ const newMaxFeePerGas = maxFeePerGasValues && validateMinimumIncrease(maxFeePerGasValues, minMaxFeePerGas) || existingMaxFeePerGas && minMaxFeePerGas; ++ const existingMaxPriorityFeePerGas = transactionMeta.txParams?.maxPriorityFeePerGas; ++ const minMaxPriorityFeePerGas = getIncreasedPriceFromExisting( ++ existingMaxPriorityFeePerGas, ++ SPEED_UP_RATE ++ ); ++ const maxPriorityFeePerGasValues = isFeeMarketEIP1559Values(gasValues) && gasValues.maxPriorityFeePerGas; ++ const newMaxPriorityFeePerGas = maxPriorityFeePerGasValues && validateMinimumIncrease( ++ maxPriorityFeePerGasValues, ++ minMaxPriorityFeePerGas ++ ) || existingMaxPriorityFeePerGas && minMaxPriorityFeePerGas; ++ const txParams = newMaxFeePerGas && newMaxPriorityFeePerGas ? { ++ ...transactionMeta.txParams, ++ gasLimit: transactionMeta.txParams.gas, ++ maxFeePerGas: newMaxFeePerGas, ++ maxPriorityFeePerGas: newMaxPriorityFeePerGas, ++ type: "0x2" /* feeMarket */ ++ } : { ++ ...transactionMeta.txParams, ++ gasLimit: transactionMeta.txParams.gas, ++ gasPrice: newGasPrice ++ }; ++ const unsignedEthTx = this.prepareUnsignedEthTx( ++ transactionMeta.chainId, ++ txParams ++ ); ++ const signedTx = await this.sign( ++ unsignedEthTx, ++ transactionMeta.txParams.from ++ ); ++ const transactionMetaWithRsv = this.updateTransactionMetaRSV( ++ transactionMeta, ++ signedTx ++ ); ++ const rawTx = bufferToHex(signedTx.serialize()); ++ const newFee = txParams.maxFeePerGas ?? txParams.gasPrice; ++ const oldFee = txParams.maxFeePerGas ? transactionMetaWithRsv.txParams.maxFeePerGas : transactionMetaWithRsv.txParams.gasPrice; ++ projectLogger("Submitting speed up transaction", { oldFee, newFee, txParams }); ++ const ethQuery = __privateGet(this, _multichainTrackingHelper).getEthQuery({ ++ networkClientId: transactionMeta.networkClientId, ++ chainId: transactionMeta.chainId ++ }); ++ const hash = await this.publishTransactionForRetry( ++ ethQuery, ++ rawTx, ++ transactionMeta ++ ); ++ const baseTransactionMeta = { ++ ...transactionMetaWithRsv, ++ estimatedBaseFee, ++ id: random(), ++ time: Date.now(), ++ hash, ++ actionId, ++ originalGasEstimate: transactionMeta.txParams.gas, ++ type: "retry" /* retry */, ++ originalType: transactionMeta.type ++ }; ++ const newTransactionMeta = newMaxFeePerGas && newMaxPriorityFeePerGas ? { ++ ...baseTransactionMeta, ++ txParams: { ++ ...transactionMeta.txParams, ++ maxFeePerGas: newMaxFeePerGas, ++ maxPriorityFeePerGas: newMaxPriorityFeePerGas ++ } ++ } : { ++ ...baseTransactionMeta, ++ txParams: { ++ ...transactionMeta.txParams, ++ gasPrice: newGasPrice ++ } ++ }; ++ this.addMetadata(newTransactionMeta); ++ this.messagingSystem.publish(`${controllerName}:transactionApproved`, { ++ transactionMeta: newTransactionMeta, ++ actionId ++ }); ++ this.messagingSystem.publish(`${controllerName}:transactionSubmitted`, { ++ transactionMeta: newTransactionMeta, ++ actionId ++ }); ++ this.messagingSystem.publish( ++ `${controllerName}:speedupTransactionAdded`, ++ newTransactionMeta ++ ); ++ } ++ /** ++ * Estimates required gas for a given transaction. ++ * ++ * @param transaction - The transaction to estimate gas for. ++ * @param networkClientId - The network client id to use for the estimate. ++ * @returns The gas and gas price. ++ */ ++ async estimateGas(transaction, networkClientId) { ++ const ethQuery = __privateGet(this, _multichainTrackingHelper).getEthQuery({ ++ networkClientId ++ }); ++ const { estimatedGas, simulationFails } = await estimateGas( ++ transaction, ++ ethQuery ++ ); ++ return { gas: estimatedGas, simulationFails }; ++ } ++ /** ++ * Estimates required gas for a given transaction and add additional gas buffer with the given multiplier. ++ * ++ * @param transaction - The transaction params to estimate gas for. ++ * @param multiplier - The multiplier to use for the gas buffer. ++ * @param networkClientId - The network client id to use for the estimate. ++ */ ++ async estimateGasBuffered(transaction, multiplier, networkClientId) { ++ const ethQuery = __privateGet(this, _multichainTrackingHelper).getEthQuery({ ++ networkClientId ++ }); ++ const { blockGasLimit, estimatedGas, simulationFails } = await estimateGas( ++ transaction, ++ ethQuery ++ ); ++ const gas = addGasBuffer(estimatedGas, blockGasLimit, multiplier); ++ return { ++ gas, ++ simulationFails ++ }; ++ } ++ /** ++ * Updates an existing transaction in state. ++ * ++ * @param transactionMeta - The new transaction to store in state. ++ * @param note - A note or update reason to include in the transaction history. ++ */ ++ updateTransaction(transactionMeta, note) { ++ const { id: transactionId } = transactionMeta; ++ __privateMethod(this, _updateTransactionInternal, updateTransactionInternal_fn).call(this, { transactionId, note }, () => ({ ++ ...transactionMeta ++ })); ++ } ++ /** ++ * Update the security alert response for a transaction. ++ * ++ * @param transactionId - ID of the transaction. ++ * @param securityAlertResponse - The new security alert response for the transaction. ++ */ ++ updateSecurityAlertResponse(transactionId, securityAlertResponse) { ++ if (!securityAlertResponse) { ++ throw new Error( ++ "updateSecurityAlertResponse: securityAlertResponse should not be null" ++ ); ++ } ++ const transactionMeta = this.getTransaction(transactionId); ++ if (!transactionMeta) { ++ throw new Error( ++ `Cannot update security alert response as no transaction metadata found` ++ ); ++ } ++ const updatedTransactionMeta = { ++ ...transactionMeta, ++ securityAlertResponse ++ }; ++ this.updateTransaction( ++ updatedTransactionMeta, ++ `${controllerName}:updatesecurityAlertResponse - securityAlertResponse updated` ++ ); ++ } ++ /** ++ * Removes all transactions from state, optionally based on the current network. ++ * ++ * @param ignoreNetwork - Determines whether to wipe all transactions, or just those on the ++ * current network. If `true`, all transactions are wiped. ++ * @param address - If specified, only transactions originating from this address will be ++ * wiped on current network. ++ */ ++ wipeTransactions(ignoreNetwork, address) { ++ if (ignoreNetwork && !address) { ++ this.update((state) => { ++ state.transactions = []; ++ }); ++ return; ++ } ++ const currentChainId = this.getChainId(); ++ const newTransactions = this.state.transactions.filter( ++ ({ chainId, txParams }) => { ++ const isMatchingNetwork = ignoreNetwork || chainId === currentChainId; ++ if (!isMatchingNetwork) { ++ return true; ++ } ++ const isMatchingAddress = !address || txParams.from?.toLowerCase() === address.toLowerCase(); ++ return !isMatchingAddress; ++ } ++ ); ++ this.update((state) => { ++ state.transactions = this.trimTransactionsForState(newTransactions); ++ }); ++ } ++ /** ++ * Adds external provided transaction to state as confirmed transaction. ++ * ++ * @param transactionMeta - TransactionMeta to add transactions. ++ * @param transactionReceipt - TransactionReceipt of the external transaction. ++ * @param baseFeePerGas - Base fee per gas of the external transaction. ++ */ ++ async confirmExternalTransaction(transactionMeta, transactionReceipt, baseFeePerGas) { ++ const newTransactionMeta = this.addExternalTransaction(transactionMeta); ++ try { ++ const transactionId = newTransactionMeta.id; ++ const updatedTransactionMeta = { ++ ...newTransactionMeta, ++ status: "confirmed" /* confirmed */, ++ txReceipt: transactionReceipt ++ }; ++ if (baseFeePerGas) { ++ updatedTransactionMeta.baseFeePerGas = baseFeePerGas; ++ } ++ this.markNonceDuplicatesDropped(transactionId); ++ this.updateTransaction( ++ updatedTransactionMeta, ++ `${controllerName}:confirmExternalTransaction - Add external transaction` ++ ); ++ this.onTransactionStatusChange(updatedTransactionMeta); ++ this.updatePostBalance(updatedTransactionMeta); ++ this.messagingSystem.publish( ++ `${controllerName}:transactionConfirmed`, ++ updatedTransactionMeta ++ ); ++ } catch (error) { ++ console.error("Failed to confirm external transaction", error); ++ } ++ } ++ /** ++ * Append new send flow history to a transaction. ++ * ++ * @param transactionID - The ID of the transaction to update. ++ * @param currentSendFlowHistoryLength - The length of the current sendFlowHistory array. ++ * @param sendFlowHistoryToAdd - The sendFlowHistory entries to add. ++ * @returns The updated transactionMeta. ++ */ ++ updateTransactionSendFlowHistory(transactionID, currentSendFlowHistoryLength, sendFlowHistoryToAdd) { ++ if (this.isSendFlowHistoryDisabled) { ++ throw new Error( ++ "Send flow history is disabled for the current transaction controller" ++ ); ++ } ++ const transactionMeta = this.getTransaction(transactionID); ++ if (!transactionMeta) { ++ throw new Error( ++ `Cannot update send flow history as no transaction metadata found` ++ ); ++ } ++ validateIfTransactionUnapproved( ++ transactionMeta, ++ "updateTransactionSendFlowHistory" ++ ); ++ const sendFlowHistory = transactionMeta.sendFlowHistory ?? []; ++ if (currentSendFlowHistoryLength === sendFlowHistory.length) { ++ const updatedTransactionMeta = { ++ ...transactionMeta, ++ sendFlowHistory: [...sendFlowHistory, ...sendFlowHistoryToAdd] ++ }; ++ this.updateTransaction( ++ updatedTransactionMeta, ++ `${controllerName}:updateTransactionSendFlowHistory - sendFlowHistory updated` ++ ); ++ } ++ return this.getTransaction(transactionID); ++ } ++ /** ++ * Update the gas values of a transaction. ++ * ++ * @param transactionId - The ID of the transaction to update. ++ * @param gasValues - Gas values to update. ++ * @param gasValues.gas - Same as transaction.gasLimit. ++ * @param gasValues.gasLimit - Maxmimum number of units of gas to use for this transaction. ++ * @param gasValues.gasPrice - Price per gas for legacy transactions. ++ * @param gasValues.maxPriorityFeePerGas - Maximum amount per gas to give to validator as incentive. ++ * @param gasValues.maxFeePerGas - Maximum amount per gas to pay for the transaction, including the priority fee. ++ * @param gasValues.estimateUsed - Which estimate level was used. ++ * @param gasValues.estimateSuggested - Which estimate level that the API suggested. ++ * @param gasValues.defaultGasEstimates - The default estimate for gas. ++ * @param gasValues.originalGasEstimate - Original estimate for gas. ++ * @param gasValues.userEditedGasLimit - The gas limit supplied by user. ++ * @param gasValues.userFeeLevel - Estimate level user selected. ++ * @returns The updated transactionMeta. ++ */ ++ updateTransactionGasFees(transactionId, { ++ defaultGasEstimates, ++ estimateUsed, ++ estimateSuggested, ++ gas, ++ gasLimit, ++ gasPrice, ++ maxPriorityFeePerGas, ++ maxFeePerGas, ++ originalGasEstimate, ++ userEditedGasLimit, ++ userFeeLevel ++ }) { ++ const transactionMeta = this.getTransaction(transactionId); ++ if (!transactionMeta) { ++ throw new Error( ++ `Cannot update transaction as no transaction metadata found` ++ ); ++ } ++ validateIfTransactionUnapproved( ++ transactionMeta, ++ "updateTransactionGasFees" ++ ); ++ let transactionGasFees = { ++ txParams: { ++ gas, ++ gasLimit, ++ gasPrice, ++ maxPriorityFeePerGas, ++ maxFeePerGas ++ }, ++ defaultGasEstimates, ++ estimateUsed, ++ estimateSuggested, ++ originalGasEstimate, ++ userEditedGasLimit, ++ userFeeLevel ++ // TODO: Replace `any` with type ++ // eslint-disable-next-line @typescript-eslint/no-explicit-any ++ }; ++ transactionGasFees.txParams = pickBy(transactionGasFees.txParams); ++ transactionGasFees = pickBy(transactionGasFees); ++ const updatedMeta = merge({}, transactionMeta, transactionGasFees); ++ this.updateTransaction( ++ updatedMeta, ++ `${controllerName}:updateTransactionGasFees - gas values updated` ++ ); ++ return this.getTransaction(transactionId); ++ } ++ /** ++ * Update the previous gas values of a transaction. ++ * ++ * @param transactionId - The ID of the transaction to update. ++ * @param previousGas - Previous gas values to update. ++ * @param previousGas.gasLimit - Maxmimum number of units of gas to use for this transaction. ++ * @param previousGas.maxFeePerGas - Maximum amount per gas to pay for the transaction, including the priority fee. ++ * @param previousGas.maxPriorityFeePerGas - Maximum amount per gas to give to validator as incentive. ++ * @returns The updated transactionMeta. ++ */ ++ updatePreviousGasParams(transactionId, { ++ gasLimit, ++ maxFeePerGas, ++ maxPriorityFeePerGas ++ }) { ++ const transactionMeta = this.getTransaction(transactionId); ++ if (!transactionMeta) { ++ throw new Error( ++ `Cannot update transaction as no transaction metadata found` ++ ); ++ } ++ validateIfTransactionUnapproved(transactionMeta, "updatePreviousGasParams"); ++ const transactionPreviousGas = { ++ previousGas: { ++ gasLimit, ++ maxFeePerGas, ++ maxPriorityFeePerGas ++ } ++ // TODO: Replace `any` with type ++ // eslint-disable-next-line @typescript-eslint/no-explicit-any ++ }; ++ transactionPreviousGas.previousGas = pickBy( ++ transactionPreviousGas.previousGas ++ ); ++ const updatedMeta = merge({}, transactionMeta, transactionPreviousGas); ++ this.updateTransaction( ++ updatedMeta, ++ `${controllerName}:updatePreviousGasParams - Previous gas values updated` ++ ); ++ return this.getTransaction(transactionId); ++ } ++ async getNonceLock(address, networkClientId) { ++ return __privateGet(this, _multichainTrackingHelper).getNonceLock( ++ address, ++ networkClientId ++ ); ++ } ++ /** ++ * Updates the editable parameters of a transaction. ++ * ++ * @param txId - The ID of the transaction to update. ++ * @param params - The editable parameters to update. ++ * @param params.data - Data to pass with the transaction. ++ * @param params.gas - Maximum number of units of gas to use for the transaction. ++ * @param params.gasPrice - Price per gas for legacy transactions. ++ * @param params.from - Address to send the transaction from. ++ * @param params.to - Address to send the transaction to. ++ * @param params.value - Value associated with the transaction. ++ * @returns The updated transaction metadata. ++ */ ++ async updateEditableParams(txId, { ++ data, ++ gas, ++ gasPrice, ++ from, ++ to, ++ value ++ }) { ++ const transactionMeta = this.getTransaction(txId); ++ if (!transactionMeta) { ++ throw new Error( ++ `Cannot update editable params as no transaction metadata found` ++ ); ++ } ++ validateIfTransactionUnapproved(transactionMeta, "updateEditableParams"); ++ const editableParams = { ++ txParams: { ++ data, ++ from, ++ to, ++ value, ++ gas, ++ gasPrice ++ } ++ }; ++ editableParams.txParams = pickBy( ++ editableParams.txParams ++ ); ++ const updatedTransaction = merge({}, transactionMeta, editableParams); ++ const provider = __privateGet(this, _multichainTrackingHelper).getProvider({ ++ chainId: transactionMeta.chainId, ++ networkClientId: transactionMeta.networkClientId ++ }); ++ const ethQuery = new EthQuery(provider); ++ const { type } = await determineTransactionType( ++ updatedTransaction.txParams, ++ ethQuery ++ ); ++ updatedTransaction.type = type; ++ await updateTransactionLayer1GasFee({ ++ layer1GasFeeFlows: this.layer1GasFeeFlows, ++ provider, ++ transactionMeta: updatedTransaction ++ }); ++ this.updateTransaction( ++ updatedTransaction, ++ `Update Editable Params for ${txId}` ++ ); ++ return this.getTransaction(txId); ++ } ++ /** ++ * Signs and returns the raw transaction data for provided transaction params list. ++ * ++ * @param listOfTxParams - The list of transaction params to approve. ++ * @param opts - Options bag. ++ * @param opts.hasNonce - Whether the transactions already have a nonce. ++ * @returns The raw transactions. ++ */ ++ async approveTransactionsWithSameNonce(listOfTxParams = [], { hasNonce } = {}) { ++ projectLogger("Approving transactions with same nonce", { ++ transactions: listOfTxParams ++ }); ++ if (listOfTxParams.length === 0) { ++ return ""; ++ } ++ const initialTx = listOfTxParams[0]; ++ const common = this.getCommonConfiguration(initialTx.chainId); ++ let networkClientId; ++ try { ++ networkClientId = this.messagingSystem.call( ++ `NetworkController:findNetworkClientIdByChainId`, ++ initialTx.chainId ++ ); ++ } catch (err) { ++ projectLogger("failed to find networkClientId from chainId", err); ++ } ++ const initialTxAsEthTx = TransactionFactory.fromTxData(initialTx, { ++ common ++ }); ++ const initialTxAsSerializedHex = bufferToHex(initialTxAsEthTx.serialize()); ++ if (this.approvingTransactionIds.has(initialTxAsSerializedHex)) { ++ return ""; ++ } ++ this.approvingTransactionIds.add(initialTxAsSerializedHex); ++ let rawTransactions, nonceLock; ++ try { ++ const fromAddress = initialTx.from; ++ const requiresNonce = hasNonce !== true; ++ nonceLock = requiresNonce ? await this.getNonceLock(fromAddress, networkClientId) : void 0; ++ const nonce = nonceLock ? add0x(nonceLock.nextNonce.toString(16)) : initialTx.nonce; ++ if (nonceLock) { ++ projectLogger("Using nonce from nonce tracker", nonce, nonceLock.nonceDetails); ++ } ++ rawTransactions = await Promise.all( ++ listOfTxParams.map((txParams) => { ++ txParams.nonce = nonce; ++ return this.signExternalTransaction(txParams.chainId, txParams); ++ }) ++ ); ++ } catch (err) { ++ projectLogger("Error while signing transactions with same nonce", err); ++ throw err; ++ } finally { ++ nonceLock?.releaseLock(); ++ this.approvingTransactionIds.delete(initialTxAsSerializedHex); ++ } ++ return rawTransactions; ++ } ++ /** ++ * Update a custodial transaction. ++ * ++ * @param transactionId - The ID of the transaction to update. ++ * @param options - The custodial transaction options to update. ++ * @param options.errorMessage - The error message to be assigned in case transaction status update to failed. ++ * @param options.hash - The new hash value to be assigned. ++ * @param options.status - The new status value to be assigned. ++ */ ++ updateCustodialTransaction(transactionId, { ++ errorMessage, ++ hash, ++ status ++ }) { ++ const transactionMeta = this.getTransaction(transactionId); ++ if (!transactionMeta) { ++ throw new Error( ++ `Cannot update custodial transaction as no transaction metadata found` ++ ); ++ } ++ if (!transactionMeta.custodyId) { ++ throw new Error("Transaction must be a custodian transaction"); ++ } ++ if (status && ![ ++ "submitted" /* submitted */, ++ "signed" /* signed */, ++ "failed" /* failed */ ++ ].includes(status)) { ++ throw new Error( ++ `Cannot update custodial transaction with status: ${status}` ++ ); ++ } ++ const updatedTransactionMeta = merge( ++ {}, ++ transactionMeta, ++ pickBy({ hash, status }) ++ ); ++ if (updatedTransactionMeta.status === "submitted" /* submitted */) { ++ updatedTransactionMeta.submittedTime = (/* @__PURE__ */ new Date()).getTime(); ++ } ++ if (updatedTransactionMeta.status === "failed" /* failed */) { ++ updatedTransactionMeta.error = normalizeTxError(new Error(errorMessage)); ++ } ++ this.updateTransaction( ++ updatedTransactionMeta, ++ `${controllerName}:updateCustodialTransaction - Custodial transaction updated` ++ ); ++ if (["submitted" /* submitted */, "failed" /* failed */].includes( ++ status ++ )) { ++ this.messagingSystem.publish( ++ `${controllerName}:transactionFinished`, ++ updatedTransactionMeta ++ ); ++ __privateGet(this, _internalEvents).emit( ++ `${updatedTransactionMeta.id}:finished`, ++ updatedTransactionMeta ++ ); ++ } ++ } ++ /** ++ * Search transaction metadata for matching entries. ++ * ++ * @param opts - Options bag. ++ * @param opts.searchCriteria - An object containing values or functions for transaction properties to filter transactions with. ++ * @param opts.initialList - The transactions to search. Defaults to the current state. ++ * @param opts.filterToCurrentNetwork - Whether to filter the results to the current network. Defaults to true. ++ * @param opts.limit - The maximum number of transactions to return. No limit by default. ++ * @returns An array of transactions matching the provided options. ++ */ ++ getTransactions({ ++ searchCriteria = {}, ++ initialList, ++ filterToCurrentNetwork = true, ++ limit ++ } = {}) { ++ const chainId = this.getChainId(); ++ const predicateMethods = mapValues(searchCriteria, (predicate) => { ++ return typeof predicate === "function" ? predicate : ( ++ // TODO: Replace `any` with type ++ // eslint-disable-next-line @typescript-eslint/no-explicit-any ++ (v) => v === predicate ++ ); ++ }); ++ const transactionsToFilter = initialList ?? this.state.transactions; ++ const filteredTransactions = sortBy( ++ pickBy(transactionsToFilter, (transaction) => { ++ if (filterToCurrentNetwork && transaction.chainId !== chainId) { ++ return false; ++ } ++ for (const [key, predicate] of Object.entries(predicateMethods)) { ++ if (key in transaction.txParams) { ++ if (predicate(transaction.txParams[key]) === false) { ++ return false; ++ } ++ } else if (predicate(transaction[key]) === false) { ++ return false; ++ } ++ } ++ return true; ++ }), ++ "time" ++ ); ++ if (limit !== void 0) { ++ const nonces = /* @__PURE__ */ new Set(); ++ const txs = []; ++ for (let i = filteredTransactions.length - 1; i > -1; i--) { ++ const txMeta = filteredTransactions[i]; ++ const { nonce } = txMeta.txParams; ++ if (!nonces.has(nonce)) { ++ if (nonces.size < limit) { ++ nonces.add(nonce); ++ } else { ++ continue; ++ } ++ } ++ txs.unshift(txMeta); ++ } ++ return txs; ++ } ++ return filteredTransactions; ++ } ++ async estimateGasFee({ ++ transactionParams, ++ chainId, ++ networkClientId: requestNetworkClientId ++ }) { ++ const networkClientId = __privateMethod(this, _getNetworkClientId, getNetworkClientId_fn).call(this, { ++ networkClientId: requestNetworkClientId, ++ chainId ++ }); ++ const transactionMeta = { ++ txParams: transactionParams, ++ chainId, ++ networkClientId ++ }; ++ const gasFeeFlow = getGasFeeFlow( ++ transactionMeta, ++ this.gasFeeFlows ++ ); ++ const ethQuery = __privateGet(this, _multichainTrackingHelper).getEthQuery({ ++ networkClientId, ++ chainId ++ }); ++ const gasFeeControllerData = await this.getGasFeeEstimates({ ++ networkClientId ++ }); ++ return gasFeeFlow.getGasFees({ ++ ethQuery, ++ gasFeeControllerData, ++ transactionMeta ++ }); ++ } ++ /** ++ * Determine the layer 1 gas fee for the given transaction parameters. ++ * ++ * @param request - The request object. ++ * @param request.transactionParams - The transaction parameters to estimate the layer 1 gas fee for. ++ * @param request.chainId - The ID of the chain where the transaction will be executed. ++ * @param request.networkClientId - The ID of a specific network client to process the transaction. ++ */ ++ async getLayer1GasFee({ ++ transactionParams, ++ chainId, ++ networkClientId ++ }) { ++ const provider = __privateGet(this, _multichainTrackingHelper).getProvider({ ++ networkClientId, ++ chainId ++ }); ++ return await getTransactionLayer1GasFee({ ++ layer1GasFeeFlows: this.layer1GasFeeFlows, ++ provider, ++ transactionMeta: { ++ txParams: transactionParams, ++ chainId ++ } ++ }); ++ } ++ async signExternalTransaction(chainId, transactionParams) { ++ if (!this.sign) { ++ throw new Error("No sign method defined."); ++ } ++ const normalizedTransactionParams = normalizeTransactionParams(transactionParams); ++ const type = isEIP1559Transaction(normalizedTransactionParams) ? "0x2" /* feeMarket */ : "0x0" /* legacy */; ++ const updatedTransactionParams = { ++ ...normalizedTransactionParams, ++ type, ++ gasLimit: normalizedTransactionParams.gas, ++ chainId ++ }; ++ const { from } = updatedTransactionParams; ++ const common = this.getCommonConfiguration(chainId); ++ const unsignedTransaction = TransactionFactory.fromTxData( ++ updatedTransactionParams, ++ { common } ++ ); ++ const signedTransaction = await this.sign(unsignedTransaction, from); ++ const rawTransaction = bufferToHex(signedTransaction.serialize()); ++ return rawTransaction; ++ } ++ /** ++ * Removes unapproved transactions from state. ++ */ ++ clearUnapprovedTransactions() { ++ const transactions = this.state.transactions.filter( ++ ({ status }) => status !== "unapproved" /* unapproved */ ++ ); ++ this.update((state) => { ++ state.transactions = this.trimTransactionsForState(transactions); ++ }); ++ } ++ /** ++ * Stop the signing process for a specific transaction. ++ * Throws an error causing the transaction status to be set to failed. ++ * @param transactionId - The ID of the transaction to stop signing. ++ */ ++ abortTransactionSigning(transactionId) { ++ const transactionMeta = this.getTransaction(transactionId); ++ if (!transactionMeta) { ++ throw new Error(`Cannot abort signing as no transaction metadata found`); ++ } ++ const abortCallback = this.signAbortCallbacks.get(transactionId); ++ if (!abortCallback) { ++ throw new Error( ++ `Cannot abort signing as transaction is not waiting for signing` ++ ); ++ } ++ abortCallback(); ++ this.signAbortCallbacks.delete(transactionId); ++ } ++ addMetadata(transactionMeta) { ++ this.update((state) => { ++ state.transactions = this.trimTransactionsForState([ ++ ...state.transactions, ++ transactionMeta ++ ]); ++ }); ++ } ++ async updateGasProperties(transactionMeta) { ++ const isEIP1559Compatible = await this.getEIP1559Compatibility(transactionMeta.networkClientId) && transactionMeta.txParams.type !== "0x0" /* legacy */; ++ const { networkClientId, chainId } = transactionMeta; ++ const isCustomNetwork = __privateMethod(this, _isCustomNetwork, isCustomNetwork_fn).call(this, networkClientId); ++ const ethQuery = __privateGet(this, _multichainTrackingHelper).getEthQuery({ ++ networkClientId, ++ chainId ++ }); ++ const provider = __privateGet(this, _multichainTrackingHelper).getProvider({ ++ networkClientId, ++ chainId ++ }); ++ await updateGas({ ++ ethQuery, ++ chainId, ++ isCustomNetwork, ++ txMeta: transactionMeta ++ }); ++ await updateGasFees({ ++ eip1559: isEIP1559Compatible, ++ ethQuery, ++ gasFeeFlows: this.gasFeeFlows, ++ getGasFeeEstimates: this.getGasFeeEstimates, ++ getSavedGasFees: this.getSavedGasFees.bind(this), ++ txMeta: transactionMeta ++ }); ++ await updateTransactionLayer1GasFee({ ++ layer1GasFeeFlows: this.layer1GasFeeFlows, ++ provider, ++ transactionMeta ++ }); ++ } ++ onBootCleanup() { ++ this.clearUnapprovedTransactions(); ++ this.failIncompleteTransactions(); ++ } ++ failIncompleteTransactions() { ++ const incompleteTransactions = this.state.transactions.filter( ++ (transaction) => ["approved" /* approved */, "signed" /* signed */].includes( ++ transaction.status ++ ) ++ ); ++ for (const transactionMeta of incompleteTransactions) { ++ this.failTransaction( ++ transactionMeta, ++ new Error("Transaction incomplete at startup") ++ ); ++ } ++ } ++ async processApproval(transactionMeta, { ++ isExisting = false, ++ requireApproval, ++ shouldShowRequest = true, ++ actionId ++ }) { ++ const transactionId = transactionMeta.id; ++ let resultCallbacks; ++ const { meta, isCompleted } = this.isTransactionCompleted(transactionId); ++ const finishedPromise = isCompleted ? Promise.resolve(meta) : this.waitForTransactionFinished(transactionId); ++ if (meta && !isExisting && !isCompleted) { ++ try { ++ if (requireApproval !== false) { ++ const acceptResult = await this.requestApproval(transactionMeta, { ++ shouldShowRequest ++ }); ++ resultCallbacks = acceptResult.resultCallbacks; ++ const approvalValue = acceptResult.value; ++ const updatedTransaction = approvalValue?.txMeta; ++ if (updatedTransaction) { ++ projectLogger("Updating transaction with approval data", { ++ customNonce: updatedTransaction.customNonceValue, ++ params: updatedTransaction.txParams ++ }); ++ this.updateTransaction( ++ updatedTransaction, ++ "TransactionController#processApproval - Updated with approval data" ++ ); ++ } ++ } ++ const { isCompleted: isTxCompleted } = this.isTransactionCompleted(transactionId); ++ if (!isTxCompleted) { ++ const approvalResult = await this.approveTransaction(transactionId); ++ if (approvalResult === "skipped-via-before-publish-hook" /* SkippedViaBeforePublishHook */ && resultCallbacks) { ++ resultCallbacks.success(); ++ } ++ const updatedTransactionMeta = this.getTransaction( ++ transactionId ++ ); ++ this.messagingSystem.publish( ++ `${controllerName}:transactionApproved`, ++ { ++ transactionMeta: updatedTransactionMeta, ++ actionId ++ } ++ ); ++ } ++ } catch (error) { ++ const { isCompleted: isTxCompleted } = this.isTransactionCompleted(transactionId); ++ if (!isTxCompleted) { ++ if (error?.code === errorCodes.provider.userRejectedRequest) { ++ this.cancelTransaction(transactionId, actionId); ++ throw providerErrors.userRejectedRequest( ++ "MetaMask Tx Signature: User denied transaction signature." ++ ); ++ } else { ++ this.failTransaction(meta, error, actionId); ++ } ++ } ++ } ++ } ++ const finalMeta = await finishedPromise; ++ switch (finalMeta?.status) { ++ case "failed" /* failed */: ++ resultCallbacks?.error(finalMeta.error); ++ throw rpcErrors.internal(finalMeta.error.message); ++ case "submitted" /* submitted */: ++ resultCallbacks?.success(); ++ return finalMeta.hash; ++ default: ++ const internalError = rpcErrors.internal( ++ `MetaMask Tx Signature: Unknown problem: ${JSON.stringify( ++ finalMeta || transactionId ++ )}` ++ ); ++ resultCallbacks?.error(internalError); ++ throw internalError; ++ } ++ } ++ /** ++ * Approves a transaction and updates it's status in state. If this is not a ++ * retry transaction, a nonce will be generated. The transaction is signed ++ * using the sign configuration property, then published to the blockchain. ++ * A `:finished` hub event is fired after success or failure. ++ * ++ * @param transactionId - The ID of the transaction to approve. ++ */ ++ async approveTransaction(transactionId) { ++ const cleanupTasks = new Array(); ++ cleanupTasks.push(await this.mutex.acquire()); ++ let transactionMeta = this.getTransactionOrThrow(transactionId); ++ try { ++ if (!this.sign) { ++ this.failTransaction( ++ transactionMeta, ++ new Error("No sign method defined.") ++ ); ++ return "not-approved" /* NotApproved */; ++ } else if (!transactionMeta.chainId) { ++ this.failTransaction(transactionMeta, new Error("No chainId defined.")); ++ return "not-approved" /* NotApproved */; ++ } ++ if (this.approvingTransactionIds.has(transactionId)) { ++ projectLogger("Skipping approval as signing in progress", transactionId); ++ return "not-approved" /* NotApproved */; ++ } ++ this.approvingTransactionIds.add(transactionId); ++ cleanupTasks.push( ++ () => this.approvingTransactionIds.delete(transactionId) ++ ); ++ const [nonce, releaseNonce] = await getNextNonce( ++ transactionMeta, ++ (address) => __privateGet(this, _multichainTrackingHelper).getNonceLock( ++ address, ++ transactionMeta.networkClientId ++ ) ++ ); ++ releaseNonce && cleanupTasks.push(releaseNonce); ++ transactionMeta = __privateMethod(this, _updateTransactionInternal, updateTransactionInternal_fn).call(this, { ++ transactionId, ++ note: "TransactionController#approveTransaction - Transaction approved" ++ }, (draftTxMeta) => { ++ const { txParams, chainId } = draftTxMeta; ++ draftTxMeta.status = "approved" /* approved */; ++ draftTxMeta.txParams = { ++ ...txParams, ++ nonce, ++ chainId, ++ gasLimit: txParams.gas, ++ ...isEIP1559Transaction(txParams) && { ++ type: "0x2" /* feeMarket */ ++ } ++ }; ++ }); ++ this.onTransactionStatusChange(transactionMeta); ++ const rawTx = await this.signTransaction( ++ transactionMeta, ++ transactionMeta.txParams ++ ); ++ if (!this.beforePublish(transactionMeta)) { ++ projectLogger("Skipping publishing transaction based on hook"); ++ this.messagingSystem.publish( ++ `${controllerName}:transactionPublishingSkipped`, ++ transactionMeta ++ ); ++ return "skipped-via-before-publish-hook" /* SkippedViaBeforePublishHook */; ++ } ++ if (!rawTx) { ++ return "not-approved" /* NotApproved */; ++ } ++ const ethQuery = __privateGet(this, _multichainTrackingHelper).getEthQuery({ ++ networkClientId: transactionMeta.networkClientId, ++ chainId: transactionMeta.chainId ++ }); ++ let preTxBalance; ++ const shouldUpdatePreTxBalance = transactionMeta.type === "swap" /* swap */; ++ if (shouldUpdatePreTxBalance) { ++ projectLogger("Determining pre-transaction balance"); ++ preTxBalance = await query(ethQuery, "getBalance", [ ++ transactionMeta.txParams.from ++ ]); ++ } ++ projectLogger("Publishing transaction", transactionMeta.txParams); ++ let { transactionHash: hash } = await this.publish( ++ transactionMeta, ++ rawTx ++ ); ++ if (hash === void 0) { ++ hash = await this.publishTransaction(ethQuery, rawTx); ++ } ++ projectLogger("Publish successful", hash); ++ transactionMeta = __privateMethod(this, _updateTransactionInternal, updateTransactionInternal_fn).call(this, { ++ transactionId, ++ note: "TransactionController#approveTransaction - Transaction submitted" ++ }, (draftTxMeta) => { ++ draftTxMeta.hash = hash; ++ draftTxMeta.status = "submitted" /* submitted */; ++ draftTxMeta.submittedTime = (/* @__PURE__ */ new Date()).getTime(); ++ if (shouldUpdatePreTxBalance) { ++ draftTxMeta.preTxBalance = preTxBalance; ++ projectLogger("Updated pre-transaction balance", preTxBalance); ++ } ++ }); ++ this.messagingSystem.publish(`${controllerName}:transactionSubmitted`, { ++ transactionMeta ++ }); ++ this.messagingSystem.publish( ++ `${controllerName}:transactionFinished`, ++ transactionMeta ++ ); ++ __privateGet(this, _internalEvents).emit(`${transactionId}:finished`, transactionMeta); ++ this.onTransactionStatusChange(transactionMeta); ++ return "approved" /* Approved */; ++ } catch (error) { ++ this.failTransaction(transactionMeta, error); ++ return "not-approved" /* NotApproved */; ++ } finally { ++ cleanupTasks.forEach((task) => task()); ++ } ++ } ++ async publishTransaction(ethQuery, rawTransaction) { ++ return await query(ethQuery, "sendRawTransaction", [rawTransaction]); ++ } ++ /** ++ * Cancels a transaction based on its ID by setting its status to "rejected" ++ * and emitting a `:finished` hub event. ++ * ++ * @param transactionId - The ID of the transaction to cancel. ++ * @param actionId - The actionId passed from UI ++ */ ++ cancelTransaction(transactionId, actionId) { ++ const transactionMeta = this.state.transactions.find( ++ ({ id }) => id === transactionId ++ ); ++ if (!transactionMeta) { ++ return; ++ } ++ this.update((state) => { ++ const transactions = state.transactions.filter( ++ ({ id }) => id !== transactionId ++ ); ++ state.transactions = this.trimTransactionsForState(transactions); ++ }); ++ const updatedTransactionMeta = { ++ ...transactionMeta, ++ status: "rejected" /* rejected */ ++ }; ++ this.messagingSystem.publish( ++ `${controllerName}:transactionFinished`, ++ updatedTransactionMeta ++ ); ++ __privateGet(this, _internalEvents).emit( ++ // TODO: Either fix this lint violation or explain why it's necessary to ignore. ++ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions ++ `${transactionMeta.id}:finished`, ++ updatedTransactionMeta ++ ); ++ this.messagingSystem.publish(`${controllerName}:transactionRejected`, { ++ transactionMeta: updatedTransactionMeta, ++ actionId ++ }); ++ this.onTransactionStatusChange(updatedTransactionMeta); ++ } ++ /** ++ * Trim the amount of transactions that are set on the state. Checks ++ * if the length of the tx history is longer then desired persistence ++ * limit and then if it is removes the oldest confirmed or rejected tx. ++ * Pending or unapproved transactions will not be removed by this ++ * operation. For safety of presenting a fully functional transaction UI ++ * representation, this function will not break apart transactions with the ++ * same nonce, created on the same day, per network. Not accounting for ++ * transactions of the same nonce, same day and network combo can result in ++ * confusing or broken experiences in the UI. ++ * ++ * @param transactions - The transactions to be applied to the state. ++ * @returns The trimmed list of transactions. ++ */ ++ trimTransactionsForState(transactions) { ++ const nonceNetworkSet = /* @__PURE__ */ new Set(); ++ const txsToKeep = [...transactions].sort((a, b) => a.time > b.time ? -1 : 1).filter((tx) => { ++ const { chainId, status, txParams, time } = tx; ++ if (txParams) { ++ const key = `${String(txParams.nonce)}-${convertHexToDecimal( ++ chainId ++ )}-${new Date(time).toDateString()}`; ++ if (nonceNetworkSet.has(key)) { ++ return true; ++ } else if (nonceNetworkSet.size < __privateGet(this, _transactionHistoryLimit) || !this.isFinalState(status)) { ++ nonceNetworkSet.add(key); ++ return true; ++ } ++ } ++ return false; ++ }); ++ txsToKeep.reverse(); ++ return txsToKeep; ++ } ++ /** ++ * Determines if the transaction is in a final state. ++ * ++ * @param status - The transaction status. ++ * @returns Whether the transaction is in a final state. ++ */ ++ isFinalState(status) { ++ return status === "rejected" /* rejected */ || status === "confirmed" /* confirmed */ || status === "failed" /* failed */; ++ } ++ /** ++ * Whether the transaction has at least completed all local processing. ++ * ++ * @param status - The transaction status. ++ * @returns Whether the transaction is in a final state. ++ */ ++ isLocalFinalState(status) { ++ return [ ++ "confirmed" /* confirmed */, ++ "failed" /* failed */, ++ "rejected" /* rejected */, ++ "submitted" /* submitted */ ++ ].includes(status); ++ } ++ async requestApproval(txMeta, { shouldShowRequest }) { ++ const id = this.getApprovalId(txMeta); ++ const { origin } = txMeta; ++ const type = ApprovalType.Transaction; ++ const requestData = { txId: txMeta.id }; ++ return await this.messagingSystem.call( ++ "ApprovalController:addRequest", ++ { ++ id, ++ origin: origin || ORIGIN_METAMASK, ++ type, ++ requestData, ++ expectsResult: true ++ }, ++ shouldShowRequest ++ ); ++ } ++ getTransaction(transactionId) { ++ const { transactions } = this.state; ++ return transactions.find(({ id }) => id === transactionId); ++ } ++ getTransactionOrThrow(transactionId, errorMessagePrefix = "TransactionController") { ++ const txMeta = this.getTransaction(transactionId); ++ if (!txMeta) { ++ throw new Error( ++ `${errorMessagePrefix}: No transaction found with id ${transactionId}` ++ ); ++ } ++ return txMeta; ++ } ++ getApprovalId(txMeta) { ++ return String(txMeta.id); ++ } ++ isTransactionCompleted(transactionId) { ++ const transaction = this.getTransaction(transactionId); ++ if (!transaction) { ++ return { meta: void 0, isCompleted: false }; ++ } ++ const isCompleted = this.isLocalFinalState(transaction.status); ++ return { meta: transaction, isCompleted }; ++ } ++ getChainId(networkClientId) { ++ const globalChainId = __privateMethod(this, _getGlobalChainId, getGlobalChainId_fn).call(this); ++ const globalNetworkClientId = __privateMethod(this, _getGlobalNetworkClientId, getGlobalNetworkClientId_fn).call(this); ++ if (!networkClientId || networkClientId === globalNetworkClientId) { ++ return globalChainId; ++ } ++ return this.messagingSystem.call( ++ `NetworkController:getNetworkClientById`, ++ networkClientId ++ ).configuration.chainId; ++ } ++ prepareUnsignedEthTx(chainId, txParams) { ++ return TransactionFactory.fromTxData(txParams, { ++ freeze: false, ++ common: this.getCommonConfiguration(chainId) ++ }); ++ } ++ /** ++ * `@ethereumjs/tx` uses `@ethereumjs/common` as a configuration tool for ++ * specifying which chain, network, hardfork and EIPs to support for ++ * a transaction. By referencing this configuration, and analyzing the fields ++ * specified in txParams, @ethereumjs/tx is able to determine which EIP-2718 ++ * transaction type to use. ++ * ++ * @param chainId - The chainId to use for the configuration. ++ * @returns common configuration object ++ */ ++ getCommonConfiguration(chainId) { ++ const customChainParams = { ++ chainId: parseInt(chainId, 16), ++ defaultHardfork: HARDFORK ++ }; ++ return Common.custom(customChainParams); ++ } ++ onIncomingTransactions({ ++ added, ++ updated ++ }) { ++ this.update((state) => { ++ const { transactions: currentTransactions } = state; ++ const updatedTransactions = [ ++ ...added, ++ ...currentTransactions.map((originalTransaction) => { ++ const updatedTransaction = updated.find( ++ ({ hash }) => hash === originalTransaction.hash ++ ); ++ return updatedTransaction ?? originalTransaction; ++ }) ++ ]; ++ state.transactions = this.trimTransactionsForState(updatedTransactions); ++ }); ++ } ++ onUpdatedLastFetchedBlockNumbers({ ++ lastFetchedBlockNumbers, ++ blockNumber ++ }) { ++ this.update((state) => { ++ state.lastFetchedBlockNumbers = lastFetchedBlockNumbers; ++ }); ++ this.messagingSystem.publish( ++ `${controllerName}:incomingTransactionBlockReceived`, ++ blockNumber ++ ); ++ } ++ generateDappSuggestedGasFees(txParams, origin) { ++ if (!origin || origin === ORIGIN_METAMASK) { ++ return void 0; ++ } ++ const { gasPrice, maxFeePerGas, maxPriorityFeePerGas, gas } = txParams; ++ if (gasPrice === void 0 && maxFeePerGas === void 0 && maxPriorityFeePerGas === void 0 && gas === void 0) { ++ return void 0; ++ } ++ const dappSuggestedGasFees = {}; ++ if (gasPrice !== void 0) { ++ dappSuggestedGasFees.gasPrice = gasPrice; ++ } else if (maxFeePerGas !== void 0 || maxPriorityFeePerGas !== void 0) { ++ dappSuggestedGasFees.maxFeePerGas = maxFeePerGas; ++ dappSuggestedGasFees.maxPriorityFeePerGas = maxPriorityFeePerGas; ++ } ++ if (gas !== void 0) { ++ dappSuggestedGasFees.gas = gas; ++ } ++ return dappSuggestedGasFees; ++ } ++ /** ++ * Validates and adds external provided transaction to state. ++ * ++ * @param transactionMeta - Nominated external transaction to be added to state. ++ * @returns The new transaction. ++ */ ++ addExternalTransaction(transactionMeta) { ++ const { chainId } = transactionMeta; ++ const { transactions } = this.state; ++ const fromAddress = transactionMeta?.txParams?.from; ++ const sameFromAndNetworkTransactions = transactions.filter( ++ (transaction) => transaction.txParams.from === fromAddress && transaction.chainId === chainId ++ ); ++ const confirmedTxs = sameFromAndNetworkTransactions.filter( ++ (transaction) => transaction.status === "confirmed" /* confirmed */ ++ ); ++ const pendingTxs = sameFromAndNetworkTransactions.filter( ++ (transaction) => transaction.status === "submitted" /* submitted */ ++ ); ++ validateConfirmedExternalTransaction( ++ transactionMeta, ++ confirmedTxs, ++ pendingTxs ++ ); ++ const newTransactionMeta = (transactionMeta.history ?? []).length === 0 && !this.isHistoryDisabled ? addInitialHistorySnapshot(transactionMeta) : transactionMeta; ++ this.update((state) => { ++ state.transactions = this.trimTransactionsForState([ ++ ...state.transactions, ++ newTransactionMeta ++ ]); ++ }); ++ return newTransactionMeta; ++ } ++ /** ++ * Sets other txMeta statuses to dropped if the txMeta that has been confirmed has other transactions ++ * in the transactions have the same nonce. ++ * ++ * @param transactionId - Used to identify original transaction. ++ */ ++ markNonceDuplicatesDropped(transactionId) { ++ const transactionMeta = this.getTransaction(transactionId); ++ if (!transactionMeta) { ++ return; ++ } ++ const nonce = transactionMeta.txParams?.nonce; ++ const from = transactionMeta.txParams?.from; ++ const { chainId } = transactionMeta; ++ const sameNonceTransactions = this.state.transactions.filter( ++ (transaction) => transaction.id !== transactionId && transaction.txParams.from === from && transaction.txParams.nonce === nonce && transaction.chainId === chainId && transaction.type !== "incoming" /* incoming */ ++ ); ++ const sameNonceTransactionIds = sameNonceTransactions.map( ++ (transaction) => transaction.id ++ ); ++ if (sameNonceTransactions.length === 0) { ++ return; ++ } ++ this.update((state) => { ++ for (const transaction of state.transactions) { ++ if (sameNonceTransactionIds.includes(transaction.id)) { ++ transaction.replacedBy = transactionMeta?.hash; ++ transaction.replacedById = transactionMeta?.id; ++ } ++ } ++ }); ++ for (const transaction of this.state.transactions) { ++ if (sameNonceTransactionIds.includes(transaction.id) && transaction.status !== "failed" /* failed */) { ++ this.setTransactionStatusDropped(transaction); ++ } ++ } ++ } ++ /** ++ * Method to set transaction status to dropped. ++ * ++ * @param transactionMeta - TransactionMeta of transaction to be marked as dropped. ++ */ ++ setTransactionStatusDropped(transactionMeta) { ++ const updatedTransactionMeta = { ++ ...transactionMeta, ++ status: "dropped" /* dropped */ ++ }; ++ this.messagingSystem.publish(`${controllerName}:transactionDropped`, { ++ transactionMeta: updatedTransactionMeta ++ }); ++ this.updateTransaction( ++ updatedTransactionMeta, ++ "TransactionController#setTransactionStatusDropped - Transaction dropped" ++ ); ++ this.onTransactionStatusChange(updatedTransactionMeta); ++ } ++ /** ++ * Get transaction with provided actionId. ++ * ++ * @param actionId - Unique ID to prevent duplicate requests ++ * @returns the filtered transaction ++ */ ++ getTransactionWithActionId(actionId) { ++ return this.state.transactions.find( ++ (transaction) => actionId && transaction.actionId === actionId ++ ); ++ } ++ async waitForTransactionFinished(transactionId) { ++ return new Promise((resolve) => { ++ __privateGet(this, _internalEvents).once(`${transactionId}:finished`, (txMeta) => { ++ resolve(txMeta); ++ }); ++ }); ++ } ++ /** ++ * Updates the r, s, and v properties of a TransactionMeta object ++ * with values from a signed transaction. ++ * ++ * @param transactionMeta - The TransactionMeta object to update. ++ * @param signedTx - The encompassing type for all transaction types containing r, s, and v values. ++ * @returns The updated TransactionMeta object. ++ */ ++ updateTransactionMetaRSV(transactionMeta, signedTx) { ++ const transactionMetaWithRsv = cloneDeep(transactionMeta); ++ for (const key of ["r", "s", "v"]) { ++ const value = signedTx[key]; ++ if (value === void 0 || value === null) { ++ continue; ++ } ++ transactionMetaWithRsv[key] = add0x(value.toString(16)); ++ } ++ return transactionMetaWithRsv; ++ } ++ async getEIP1559Compatibility(networkClientId) { ++ const currentNetworkIsEIP1559Compatible = await this.getCurrentNetworkEIP1559Compatibility(networkClientId); ++ const currentAccountIsEIP1559Compatible = await this.getCurrentAccountEIP1559Compatibility(); ++ return currentNetworkIsEIP1559Compatible && currentAccountIsEIP1559Compatible; ++ } ++ async signTransaction(transactionMeta, txParams) { ++ projectLogger("Signing transaction", txParams); ++ const unsignedEthTx = this.prepareUnsignedEthTx( ++ transactionMeta.chainId, ++ txParams ++ ); ++ this.approvingTransactionIds.add(transactionMeta.id); ++ const signedTx = await new Promise((resolve, reject) => { ++ this.sign?.( ++ unsignedEthTx, ++ txParams.from, ++ ...this.getAdditionalSignArguments(transactionMeta) ++ ).then(resolve, reject); ++ this.signAbortCallbacks.set( ++ transactionMeta.id, ++ () => reject(new Error("Signing aborted by user")) ++ ); ++ }); ++ this.signAbortCallbacks.delete(transactionMeta.id); ++ if (!signedTx) { ++ projectLogger("Skipping signed status as no signed transaction"); ++ return void 0; ++ } ++ const transactionMetaFromHook = cloneDeep(transactionMeta); ++ if (!this.afterSign(transactionMetaFromHook, signedTx)) { ++ this.updateTransaction( ++ transactionMetaFromHook, ++ "TransactionController#signTransaction - Update after sign" ++ ); ++ projectLogger("Skipping signed status based on hook"); ++ return void 0; ++ } ++ const transactionMetaWithRsv = { ++ ...this.updateTransactionMetaRSV(transactionMetaFromHook, signedTx), ++ status: "signed" /* signed */ ++ }; ++ this.updateTransaction( ++ transactionMetaWithRsv, ++ "TransactionController#approveTransaction - Transaction signed" ++ ); ++ this.onTransactionStatusChange(transactionMetaWithRsv); ++ const rawTx = bufferToHex(signedTx.serialize()); ++ const transactionMetaWithRawTx = merge({}, transactionMetaWithRsv, { ++ rawTx ++ }); ++ this.updateTransaction( ++ transactionMetaWithRawTx, ++ "TransactionController#approveTransaction - RawTransaction added" ++ ); ++ return rawTx; ++ } ++ onTransactionStatusChange(transactionMeta) { ++ this.messagingSystem.publish(`${controllerName}:transactionStatusUpdated`, { ++ transactionMeta ++ }); ++ } ++ getNonceTrackerTransactions(status, address, chainId = this.getChainId()) { ++ return getAndFormatTransactionsForNonceTracker( ++ chainId, ++ address, ++ status, ++ this.state.transactions ++ ); ++ } ++ onConfirmedTransaction(transactionMeta) { ++ projectLogger("Processing confirmed transaction", transactionMeta.id); ++ this.markNonceDuplicatesDropped(transactionMeta.id); ++ this.messagingSystem.publish( ++ `${controllerName}:transactionConfirmed`, ++ transactionMeta ++ ); ++ this.onTransactionStatusChange(transactionMeta); ++ this.updatePostBalance(transactionMeta); ++ } ++ async updatePostBalance(transactionMeta) { ++ try { ++ if (transactionMeta.type !== "swap" /* swap */) { ++ return; ++ } ++ const ethQuery = __privateGet(this, _multichainTrackingHelper).getEthQuery({ ++ networkClientId: transactionMeta.networkClientId, ++ chainId: transactionMeta.chainId ++ }); ++ const { updatedTransactionMeta, approvalTransactionMeta } = await updatePostTransactionBalance(transactionMeta, { ++ ethQuery, ++ getTransaction: this.getTransaction.bind(this), ++ updateTransaction: this.updateTransaction.bind(this) ++ }); ++ this.messagingSystem.publish( ++ `${controllerName}:postTransactionBalanceUpdated`, ++ { ++ transactionMeta: updatedTransactionMeta, ++ approvalTransactionMeta ++ } ++ ); ++ } catch (error) { ++ projectLogger("Error while updating post transaction balance", error); ++ } ++ } ++ async publishTransactionForRetry(ethQuery, rawTx, transactionMeta) { ++ try { ++ const hash = await this.publishTransaction(ethQuery, rawTx); ++ return hash; ++ } catch (error) { ++ if (this.isTransactionAlreadyConfirmedError(error)) { ++ await this.pendingTransactionTracker.forceCheckTransaction( ++ transactionMeta ++ ); ++ throw new Error("Previous transaction is already confirmed"); ++ } ++ throw error; ++ } ++ } ++ /** ++ * Ensures that error is a nonce issue ++ * ++ * @param error - The error to check ++ * @returns Whether or not the error is a nonce issue ++ */ ++ // TODO: Replace `any` with type ++ // Some networks are returning original error in the data field ++ // eslint-disable-next-line @typescript-eslint/no-explicit-any ++ isTransactionAlreadyConfirmedError(error) { ++ return error?.message?.includes("nonce too low") || error?.data?.message?.includes("nonce too low"); ++ } ++}; ++_internalEvents = new WeakMap(); ++_incomingTransactionOptions = new WeakMap(); ++_pendingTransactionOptions = new WeakMap(); ++_transactionHistoryLimit = new WeakMap(); ++_isSimulationEnabled = new WeakMap(); ++_testGasFeeFlows = new WeakMap(); ++_multichainTrackingHelper = new WeakMap(); ++_createNonceTracker = new WeakSet(); ++createNonceTracker_fn = function({ ++ provider, ++ blockTracker, ++ chainId ++}) { ++ return new NonceTracker({ ++ // TODO: Fix types ++ // eslint-disable-next-line @typescript-eslint/no-explicit-any ++ provider, ++ // @ts-expect-error TODO: Fix types ++ blockTracker, ++ getPendingTransactions: __privateMethod(this, _getNonceTrackerPendingTransactions, getNonceTrackerPendingTransactions_fn).bind( ++ this, ++ chainId ++ ), ++ getConfirmedTransactions: this.getNonceTrackerTransactions.bind( ++ this, ++ "confirmed" /* confirmed */ ++ ) ++ }); ++}; ++_createIncomingTransactionHelper = new WeakSet(); ++createIncomingTransactionHelper_fn = function({ ++ blockTracker, ++ etherscanRemoteTransactionSource, ++ chainId ++}) { ++ const incomingTransactionHelper = new IncomingTransactionHelper({ ++ blockTracker, ++ getCurrentAccount: () => __privateMethod(this, _getSelectedAccount, getSelectedAccount_fn).call(this), ++ getLastFetchedBlockNumbers: () => this.state.lastFetchedBlockNumbers, ++ getChainId: chainId ? () => chainId : this.getChainId.bind(this), ++ isEnabled: __privateGet(this, _incomingTransactionOptions).isEnabled, ++ queryEntireHistory: __privateGet(this, _incomingTransactionOptions).queryEntireHistory, ++ remoteTransactionSource: etherscanRemoteTransactionSource, ++ transactionLimit: __privateGet(this, _transactionHistoryLimit), ++ updateTransactions: __privateGet(this, _incomingTransactionOptions).updateTransactions ++ }); ++ __privateMethod(this, _addIncomingTransactionHelperListeners, addIncomingTransactionHelperListeners_fn).call(this, incomingTransactionHelper); ++ return incomingTransactionHelper; ++}; ++_createPendingTransactionTracker = new WeakSet(); ++createPendingTransactionTracker_fn = function({ ++ provider, ++ blockTracker, ++ chainId ++}) { ++ const ethQuery = new EthQuery(provider); ++ const getChainId = chainId ? () => chainId : this.getChainId.bind(this); ++ const pendingTransactionTracker = new PendingTransactionTracker({ ++ blockTracker, ++ getChainId, ++ getEthQuery: () => ethQuery, ++ getTransactions: () => this.state.transactions, ++ isResubmitEnabled: __privateGet(this, _pendingTransactionOptions).isResubmitEnabled, ++ getGlobalLock: () => __privateGet(this, _multichainTrackingHelper).acquireNonceLockForChainIdKey({ ++ chainId: getChainId() ++ }), ++ publishTransaction: this.publishTransaction.bind(this), ++ hooks: { ++ beforeCheckPendingTransaction: this.beforeCheckPendingTransaction.bind(this), ++ beforePublish: this.beforePublish.bind(this) ++ } ++ }); ++ __privateMethod(this, _addPendingTransactionTrackerListeners, addPendingTransactionTrackerListeners_fn).call(this, pendingTransactionTracker); ++ return pendingTransactionTracker; ++}; ++_checkForPendingTransactionAndStartPolling = new WeakMap(); ++_stopAllTracking = new WeakSet(); ++stopAllTracking_fn = function() { ++ this.pendingTransactionTracker.stop(); ++ __privateMethod(this, _removePendingTransactionTrackerListeners, removePendingTransactionTrackerListeners_fn).call(this, this.pendingTransactionTracker); ++ this.incomingTransactionHelper.stop(); ++ __privateMethod(this, _removeIncomingTransactionHelperListeners, removeIncomingTransactionHelperListeners_fn).call(this, this.incomingTransactionHelper); ++ __privateGet(this, _multichainTrackingHelper).stopAllTracking(); ++}; ++_removeIncomingTransactionHelperListeners = new WeakSet(); ++removeIncomingTransactionHelperListeners_fn = function(incomingTransactionHelper) { ++ incomingTransactionHelper.hub.removeAllListeners("transactions"); ++ incomingTransactionHelper.hub.removeAllListeners( ++ "updatedLastFetchedBlockNumbers" ++ ); ++}; ++_addIncomingTransactionHelperListeners = new WeakSet(); ++addIncomingTransactionHelperListeners_fn = function(incomingTransactionHelper) { ++ incomingTransactionHelper.hub.on( ++ "transactions", ++ this.onIncomingTransactions.bind(this) ++ ); ++ incomingTransactionHelper.hub.on( ++ "updatedLastFetchedBlockNumbers", ++ this.onUpdatedLastFetchedBlockNumbers.bind(this) ++ ); ++}; ++_removePendingTransactionTrackerListeners = new WeakSet(); ++removePendingTransactionTrackerListeners_fn = function(pendingTransactionTracker) { ++ pendingTransactionTracker.hub.removeAllListeners("transaction-confirmed"); ++ pendingTransactionTracker.hub.removeAllListeners("transaction-dropped"); ++ pendingTransactionTracker.hub.removeAllListeners("transaction-failed"); ++ pendingTransactionTracker.hub.removeAllListeners("transaction-updated"); ++}; ++_addPendingTransactionTrackerListeners = new WeakSet(); ++addPendingTransactionTrackerListeners_fn = function(pendingTransactionTracker) { ++ pendingTransactionTracker.hub.on( ++ "transaction-confirmed", ++ this.onConfirmedTransaction.bind(this) ++ ); ++ pendingTransactionTracker.hub.on( ++ "transaction-dropped", ++ this.setTransactionStatusDropped.bind(this) ++ ); ++ pendingTransactionTracker.hub.on( ++ "transaction-failed", ++ this.failTransaction.bind(this) ++ ); ++ pendingTransactionTracker.hub.on( ++ "transaction-updated", ++ this.updateTransaction.bind(this) ++ ); ++}; ++_getNonceTrackerPendingTransactions = new WeakSet(); ++getNonceTrackerPendingTransactions_fn = function(chainId, address) { ++ const standardPendingTransactions = this.getNonceTrackerTransactions( ++ "submitted" /* submitted */, ++ address, ++ chainId ++ ); ++ const externalPendingTransactions = this.getExternalPendingTransactions( ++ address, ++ chainId ++ ); ++ return [...standardPendingTransactions, ...externalPendingTransactions]; ++}; ++_getGasFeeFlows = new WeakSet(); ++getGasFeeFlows_fn = function() { ++ if (__privateGet(this, _testGasFeeFlows)) { ++ return [new TestGasFeeFlow()]; ++ } ++ return [new LineaGasFeeFlow(), new DefaultGasFeeFlow()]; ++}; ++_getLayer1GasFeeFlows = new WeakSet(); ++getLayer1GasFeeFlows_fn = function() { ++ return [new OptimismLayer1GasFeeFlow(), new ScrollLayer1GasFeeFlow()]; ++}; ++_updateTransactionInternal = new WeakSet(); ++updateTransactionInternal_fn = function({ ++ transactionId, ++ note, ++ skipHistory, ++ skipValidation ++}, callback) { ++ let updatedTransactionParams = []; ++ this.update((state) => { ++ const index = state.transactions.findIndex( ++ ({ id }) => id === transactionId ++ ); ++ let transactionMeta2 = state.transactions[index]; ++ transactionMeta2 = callback(transactionMeta2) ?? transactionMeta2; ++ if (skipValidation !== true) { ++ transactionMeta2.txParams = normalizeTransactionParams( ++ transactionMeta2.txParams ++ ); ++ validateTxParams(transactionMeta2.txParams); ++ } ++ updatedTransactionParams = __privateMethod(this, _checkIfTransactionParamsUpdated, checkIfTransactionParamsUpdated_fn).call(this, transactionMeta2); ++ const shouldSkipHistory = this.isHistoryDisabled || skipHistory; ++ if (!shouldSkipHistory) { ++ transactionMeta2 = updateTransactionHistory( ++ transactionMeta2, ++ note ?? "Transaction updated" ++ ); ++ } ++ state.transactions[index] = transactionMeta2; ++ }); ++ const transactionMeta = this.getTransaction( ++ transactionId ++ ); ++ if (updatedTransactionParams.length > 0) { ++ __privateMethod(this, _onTransactionParamsUpdated, onTransactionParamsUpdated_fn).call(this, transactionMeta, updatedTransactionParams); ++ } ++ return transactionMeta; ++}; ++_checkIfTransactionParamsUpdated = new WeakSet(); ++checkIfTransactionParamsUpdated_fn = function(newTransactionMeta) { ++ const { id: transactionId, txParams: newParams } = newTransactionMeta; ++ const originalParams = this.getTransaction(transactionId)?.txParams; ++ if (!originalParams || isEqual(originalParams, newParams)) { ++ return []; ++ } ++ const params = Object.keys(newParams); ++ const updatedProperties = params.filter( ++ (param) => newParams[param] !== originalParams[param] ++ ); ++ projectLogger( ++ "Transaction parameters have been updated", ++ transactionId, ++ updatedProperties, ++ originalParams, ++ newParams ++ ); ++ return updatedProperties; ++}; ++_onTransactionParamsUpdated = new WeakSet(); ++onTransactionParamsUpdated_fn = function(transactionMeta, updatedParams) { ++ if (["to", "value", "data"].some( ++ (param) => updatedParams.includes(param) ++ )) { ++ projectLogger("Updating simulation data due to transaction parameter update"); ++ __privateMethod(this, _updateSimulationData, updateSimulationData_fn).call(this, transactionMeta); ++ } ++}; ++_updateSimulationData = new WeakSet(); ++updateSimulationData_fn = async function(transactionMeta) { ++ const { id: transactionId, chainId, txParams } = transactionMeta; ++ const { from, to, value, data } = txParams; ++ let simulationData = { ++ error: { ++ code: "disabled" /* Disabled */, ++ message: "Simulation disabled" ++ }, ++ tokenBalanceChanges: [] ++ }; ++ if (__privateGet(this, _isSimulationEnabled).call(this)) { ++ __privateMethod(this, _updateTransactionInternal, updateTransactionInternal_fn).call(this, { transactionId, skipHistory: true }, (txMeta) => { ++ txMeta.simulationData = void 0; ++ }); ++ simulationData = await getSimulationData({ ++ chainId, ++ from, ++ to, ++ value, ++ data ++ }); ++ } ++ const finalTransactionMeta = this.getTransaction(transactionId); ++ if (!finalTransactionMeta) { ++ projectLogger( ++ "Cannot update simulation data as transaction not found", ++ transactionId, ++ simulationData ++ ); ++ return; ++ } ++ __privateMethod(this, _updateTransactionInternal, updateTransactionInternal_fn).call(this, { ++ transactionId, ++ note: "TransactionController#updateSimulationData - Update simulation data" ++ }, (txMeta) => { ++ txMeta.simulationData = simulationData; ++ }); ++ projectLogger("Updated simulation data", transactionId, simulationData); ++}; ++_onGasFeePollerTransactionUpdate = new WeakSet(); ++onGasFeePollerTransactionUpdate_fn = function({ ++ transactionId, ++ gasFeeEstimates, ++ gasFeeEstimatesLoaded, ++ layer1GasFee ++}) { ++ __privateMethod(this, _updateTransactionInternal, updateTransactionInternal_fn).call(this, { transactionId, skipHistory: true }, (txMeta) => { ++ if (gasFeeEstimates) { ++ txMeta.gasFeeEstimates = gasFeeEstimates; ++ } ++ if (gasFeeEstimatesLoaded !== void 0) { ++ txMeta.gasFeeEstimatesLoaded = gasFeeEstimatesLoaded; ++ } ++ if (layer1GasFee) { ++ txMeta.layer1GasFee = layer1GasFee; ++ } ++ }); ++}; ++_getNetworkClientId = new WeakSet(); ++getNetworkClientId_fn = function({ ++ networkClientId: requestNetworkClientId, ++ chainId ++}) { ++ const globalChainId = __privateMethod(this, _getGlobalChainId, getGlobalChainId_fn).call(this); ++ const globalNetworkClientId = __privateMethod(this, _getGlobalNetworkClientId, getGlobalNetworkClientId_fn).call(this); ++ if (requestNetworkClientId) { ++ return requestNetworkClientId; ++ } ++ if (!chainId || chainId === globalChainId) { ++ return globalNetworkClientId; ++ } ++ return this.messagingSystem.call( ++ `NetworkController:findNetworkClientIdByChainId`, ++ chainId ++ ); ++}; ++_getGlobalNetworkClientId = new WeakSet(); ++getGlobalNetworkClientId_fn = function() { ++ return this.getNetworkState().selectedNetworkClientId; ++}; ++_getGlobalChainId = new WeakSet(); ++getGlobalChainId_fn = function() { ++ return this.messagingSystem.call( ++ `NetworkController:getNetworkClientById`, ++ this.getNetworkState().selectedNetworkClientId ++ ).configuration.chainId; ++}; ++_isCustomNetwork = new WeakSet(); ++isCustomNetwork_fn = function(networkClientId) { ++ const globalNetworkClientId = __privateMethod(this, _getGlobalNetworkClientId, getGlobalNetworkClientId_fn).call(this); ++ if (!networkClientId || networkClientId === globalNetworkClientId) { ++ return !isInfuraNetworkType( ++ this.getNetworkState().selectedNetworkClientId ++ ); ++ } ++ return this.messagingSystem.call( ++ `NetworkController:getNetworkClientById`, ++ networkClientId ++ ).configuration.type === NetworkClientType.Custom; ++}; ++_getSelectedAccount = new WeakSet(); ++getSelectedAccount_fn = function() { ++ return this.messagingSystem.call("AccountsController:getSelectedAccount"); ++}; ++ ++export { ++ HARDFORK, ++ CANCEL_RATE, ++ SPEED_UP_RATE, ++ ApprovalState, ++ TransactionController ++}; ++//# sourceMappingURL=chunk-YQYO6EGF.mjs.map +\ No newline at end of file +diff --git a/dist/chunk-YQYO6EGF.mjs.map b/dist/chunk-YQYO6EGF.mjs.map +new file mode 100644 +index 0000000000000000000000000000000000000000..d0f3b3d31c6e0ad96b3d873dd82bdbdc0cdf7abe +--- /dev/null ++++ b/dist/chunk-YQYO6EGF.mjs.map +@@ -0,0 +1 @@ ++{"version":3,"sources":["../src/TransactionController.ts"],"sourcesContent":["import { Hardfork, Common, type ChainConfig } from '@ethereumjs/common';\nimport type { TypedTransaction } from '@ethereumjs/tx';\nimport { TransactionFactory } from '@ethereumjs/tx';\nimport { bufferToHex } from '@ethereumjs/util';\nimport type { AccountsControllerGetSelectedAccountAction } from '@metamask/accounts-controller';\nimport type {\n AcceptResultCallbacks,\n AddApprovalRequest,\n AddResult,\n} from '@metamask/approval-controller';\nimport type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n RestrictedControllerMessenger,\n} from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport {\n query,\n ApprovalType,\n ORIGIN_METAMASK,\n convertHexToDecimal,\n isInfuraNetworkType,\n} from '@metamask/controller-utils';\nimport EthQuery from '@metamask/eth-query';\nimport type {\n FetchGasFeeEstimateOptions,\n GasFeeState,\n} from '@metamask/gas-fee-controller';\nimport type {\n BlockTracker,\n NetworkClientId,\n NetworkController,\n NetworkControllerStateChangeEvent,\n NetworkState,\n Provider,\n NetworkControllerFindNetworkClientIdByChainIdAction,\n NetworkControllerGetNetworkClientByIdAction,\n} from '@metamask/network-controller';\nimport { NetworkClientType } from '@metamask/network-controller';\nimport type {\n NonceLock,\n Transaction as NonceTrackerTransaction,\n} from '@metamask/nonce-tracker';\nimport { NonceTracker } from '@metamask/nonce-tracker';\nimport { errorCodes, rpcErrors, providerErrors } from '@metamask/rpc-errors';\nimport type { Hex } from '@metamask/utils';\nimport { add0x } from '@metamask/utils';\nimport { Mutex } from 'async-mutex';\nimport { MethodRegistry } from 'eth-method-registry';\nimport { EventEmitter } from 'events';\nimport { cloneDeep, mapValues, merge, pickBy, sortBy, isEqual } from 'lodash';\nimport { v1 as random } from 'uuid';\n\nimport { DefaultGasFeeFlow } from './gas-flows/DefaultGasFeeFlow';\nimport { LineaGasFeeFlow } from './gas-flows/LineaGasFeeFlow';\nimport { OptimismLayer1GasFeeFlow } from './gas-flows/OptimismLayer1GasFeeFlow';\nimport { ScrollLayer1GasFeeFlow } from './gas-flows/ScrollLayer1GasFeeFlow';\nimport { TestGasFeeFlow } from './gas-flows/TestGasFeeFlow';\nimport { EtherscanRemoteTransactionSource } from './helpers/EtherscanRemoteTransactionSource';\nimport { GasFeePoller } from './helpers/GasFeePoller';\nimport type { IncomingTransactionOptions } from './helpers/IncomingTransactionHelper';\nimport { IncomingTransactionHelper } from './helpers/IncomingTransactionHelper';\nimport { MultichainTrackingHelper } from './helpers/MultichainTrackingHelper';\nimport { PendingTransactionTracker } from './helpers/PendingTransactionTracker';\nimport { projectLogger as log } from './logger';\nimport type {\n DappSuggestedGasFees,\n Layer1GasFeeFlow,\n SavedGasFees,\n SecurityProviderRequest,\n SendFlowHistoryEntry,\n TransactionParams,\n TransactionMeta,\n TransactionReceipt,\n WalletDevice,\n SecurityAlertResponse,\n GasFeeFlow,\n SimulationData,\n GasFeeEstimates,\n GasFeeFlowResponse,\n} from './types';\nimport {\n TransactionEnvelopeType,\n TransactionType,\n TransactionStatus,\n SimulationErrorCode,\n} from './types';\nimport { validateConfirmedExternalTransaction } from './utils/external-transactions';\nimport { addGasBuffer, estimateGas, updateGas } from './utils/gas';\nimport { updateGasFees } from './utils/gas-fees';\nimport { getGasFeeFlow } from './utils/gas-flow';\nimport {\n addInitialHistorySnapshot,\n updateTransactionHistory,\n} from './utils/history';\nimport {\n getTransactionLayer1GasFee,\n updateTransactionLayer1GasFee,\n} from './utils/layer1-gas-fee-flow';\nimport {\n getAndFormatTransactionsForNonceTracker,\n getNextNonce,\n} from './utils/nonce';\nimport { getSimulationData } from './utils/simulation';\nimport {\n updatePostTransactionBalance,\n updateSwapsTransaction,\n} from './utils/swaps';\nimport { determineTransactionType } from './utils/transaction-type';\nimport {\n getIncreasedPriceFromExisting,\n normalizeTransactionParams,\n isEIP1559Transaction,\n isFeeMarketEIP1559Values,\n isGasPriceValue,\n validateGasValues,\n validateIfTransactionUnapproved,\n validateMinimumIncrease,\n normalizeTxError,\n normalizeGasFeeValues,\n} from './utils/utils';\nimport {\n validateTransactionOrigin,\n validateTxParams,\n} from './utils/validation';\n\n/**\n * Metadata for the TransactionController state, describing how to \"anonymize\"\n * the state and which parts should be persisted.\n */\nconst metadata = {\n transactions: {\n persist: true,\n anonymous: false,\n },\n methodData: {\n persist: true,\n anonymous: false,\n },\n lastFetchedBlockNumbers: {\n persist: true,\n anonymous: false,\n },\n};\n\nexport const HARDFORK = Hardfork.London;\n\n/**\n * Object with new transaction's meta and a promise resolving to the\n * transaction hash if successful.\n *\n * @property result - Promise resolving to a new transaction hash\n * @property transactionMeta - Meta information about this new transaction\n */\n// This interface was created before this ESLint rule was added.\n// Convert to a `type` in a future major version.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\nexport interface Result {\n result: Promise;\n transactionMeta: TransactionMeta;\n}\n\n// This interface was created before this ESLint rule was added.\n// Convert to a `type` in a future major version.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\nexport interface GasPriceValue {\n gasPrice: string;\n}\n\n// This interface was created before this ESLint rule was added.\n// Convert to a `type` in a future major version.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\nexport interface FeeMarketEIP1559Values {\n maxFeePerGas: string;\n maxPriorityFeePerGas: string;\n}\n\n/**\n * Method data registry object\n *\n * @property registryMethod - Registry method raw string\n * @property parsedRegistryMethod - Registry method object, containing name and method arguments\n */\nexport type MethodData = {\n registryMethod: string;\n parsedRegistryMethod:\n | {\n name: string;\n args: { type: string }[];\n }\n | {\n // We're using `any` instead of `undefined` for compatibility with `Json`\n // TODO: Correct this type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n name?: any;\n // We're using `any` instead of `undefined` for compatibility with `Json`\n // TODO: Correct this type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n args?: any;\n };\n};\n\n/**\n * Transaction controller state\n *\n * @property transactions - A list of TransactionMeta objects\n * @property methodData - Object containing all known method data information\n * @property lastFetchedBlockNumbers - Last fetched block numbers.\n */\nexport type TransactionControllerState = {\n transactions: TransactionMeta[];\n methodData: Record;\n lastFetchedBlockNumbers: { [key: string]: number };\n};\n\n/**\n * Multiplier used to determine a transaction's increased gas fee during cancellation\n */\nexport const CANCEL_RATE = 1.1;\n\n/**\n * Multiplier used to determine a transaction's increased gas fee during speed up\n */\nexport const SPEED_UP_RATE = 1.1;\n\n/**\n * Represents the `TransactionController:getState` action.\n */\nexport type TransactionControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n TransactionControllerState\n>;\n\n/**\n * The internal actions available to the TransactionController.\n */\nexport type TransactionControllerActions = TransactionControllerGetStateAction;\n\n/**\n * Configuration options for the PendingTransactionTracker\n *\n * @property isResubmitEnabled - Whether transaction publishing is automatically retried.\n */\nexport type PendingTransactionOptions = {\n isResubmitEnabled?: () => boolean;\n};\n\n/**\n * TransactionController constructor options.\n *\n * @property blockTracker - The block tracker used to poll for new blocks data.\n * @property disableHistory - Whether to disable storing history in transaction metadata.\n * @property disableSendFlowHistory - Explicitly disable transaction metadata history.\n * @property disableSwaps - Whether to disable additional processing on swaps transactions.\n * @property getCurrentAccountEIP1559Compatibility - Whether or not the account supports EIP-1559.\n * @property getCurrentNetworkEIP1559Compatibility - Whether or not the network supports EIP-1559.\n * @property getExternalPendingTransactions - Callback to retrieve pending transactions from external sources.\n * @property getGasFeeEstimates - Callback to retrieve gas fee estimates.\n * @property getNetworkClientRegistry - Gets the network client registry.\n * @property getNetworkState - Gets the state of the network controller.\n * @property getPermittedAccounts - Get accounts that a given origin has permissions for.\n * @property getSavedGasFees - Gets the saved gas fee config.\n * @property getSelectedAddress - Gets the address of the currently selected account.\n * @property incomingTransactions - Configuration options for incoming transaction support.\n * @property isMultichainEnabled - Enable multichain support.\n * @property isSimulationEnabled - Whether new transactions will be automatically simulated.\n * @property messenger - The controller messenger.\n * @property onNetworkStateChange - Allows subscribing to network controller state changes.\n * @property pendingTransactions - Configuration options for pending transaction support.\n * @property provider - The provider used to create the underlying EthQuery instance.\n * @property securityProviderRequest - A function for verifying a transaction, whether it is malicious or not.\n * @property sign - Function used to sign transactions.\n * @property state - Initial state to set on this controller.\n * @property transactionHistoryLimit - Transaction history limit.\n * @property hooks - The controller hooks.\n * @property hooks.afterSign - Additional logic to execute after signing a transaction. Return false to not change the status to signed.\n * @property hooks.beforeCheckPendingTransaction - Additional logic to execute before checking pending transactions. Return false to prevent the broadcast of the transaction.\n * @property hooks.beforePublish - Additional logic to execute before publishing a transaction. Return false to prevent the broadcast of the transaction.\n * @property hooks.getAdditionalSignArguments - Returns additional arguments required to sign a transaction.\n * @property hooks.publish - Alternate logic to publish a transaction.\n */\nexport type TransactionControllerOptions = {\n blockTracker: BlockTracker;\n disableHistory: boolean;\n disableSendFlowHistory: boolean;\n disableSwaps: boolean;\n getCurrentAccountEIP1559Compatibility?: () => Promise;\n getCurrentNetworkEIP1559Compatibility: () => Promise;\n getExternalPendingTransactions?: (\n address: string,\n chainId?: string,\n ) => NonceTrackerTransaction[];\n getGasFeeEstimates?: (\n options: FetchGasFeeEstimateOptions,\n ) => Promise;\n getNetworkClientRegistry: NetworkController['getNetworkClientRegistry'];\n getNetworkState: () => NetworkState;\n getPermittedAccounts: (origin?: string) => Promise;\n getSavedGasFees?: (chainId: Hex) => SavedGasFees | undefined;\n incomingTransactions?: IncomingTransactionOptions;\n isMultichainEnabled: boolean;\n isSimulationEnabled?: () => boolean;\n messenger: TransactionControllerMessenger;\n onNetworkStateChange: (listener: (state: NetworkState) => void) => void;\n pendingTransactions?: PendingTransactionOptions;\n provider: Provider;\n securityProviderRequest?: SecurityProviderRequest;\n sign?: (\n transaction: TypedTransaction,\n from: string,\n transactionMeta?: TransactionMeta,\n ) => Promise;\n state?: Partial;\n testGasFeeFlows?: boolean;\n transactionHistoryLimit: number;\n hooks: {\n afterSign?: (\n transactionMeta: TransactionMeta,\n signedTx: TypedTransaction,\n ) => boolean;\n beforeCheckPendingTransaction?: (\n transactionMeta: TransactionMeta,\n ) => boolean;\n beforePublish?: (transactionMeta: TransactionMeta) => boolean;\n getAdditionalSignArguments?: (\n transactionMeta: TransactionMeta,\n ) => (TransactionMeta | undefined)[];\n publish?: (\n transactionMeta: TransactionMeta,\n ) => Promise<{ transactionHash: string }>;\n };\n};\n\n/**\n * The name of the {@link TransactionController}.\n */\nconst controllerName = 'TransactionController';\n\n/**\n * The external actions available to the {@link TransactionController}.\n */\nexport type AllowedActions =\n | AddApprovalRequest\n | NetworkControllerFindNetworkClientIdByChainIdAction\n | NetworkControllerGetNetworkClientByIdAction\n | AccountsControllerGetSelectedAccountAction;\n\n/**\n * The external events available to the {@link TransactionController}.\n */\nexport type AllowedEvents = NetworkControllerStateChangeEvent;\n\n/**\n * Represents the `TransactionController:stateChange` event.\n */\nexport type TransactionControllerStateChangeEvent = ControllerStateChangeEvent<\n typeof controllerName,\n TransactionControllerState\n>;\n\n/**\n * Represents the `TransactionController:incomingTransactionBlockReceived` event.\n */\nexport type TransactionControllerIncomingTransactionBlockReceivedEvent = {\n type: `${typeof controllerName}:incomingTransactionBlockReceived`;\n payload: [blockNumber: number];\n};\n\n/**\n * Represents the `TransactionController:postTransactionBalanceUpdated` event.\n */\nexport type TransactionControllerPostTransactionBalanceUpdatedEvent = {\n type: `${typeof controllerName}:postTransactionBalanceUpdated`;\n payload: [\n {\n transactionMeta: TransactionMeta;\n approvalTransactionMeta?: TransactionMeta;\n },\n ];\n};\n\n/**\n * Represents the `TransactionController:speedUpTransactionAdded` event.\n */\nexport type TransactionControllerSpeedupTransactionAddedEvent = {\n type: `${typeof controllerName}:speedupTransactionAdded`;\n payload: [transactionMeta: TransactionMeta];\n};\n\n/**\n * Represents the `TransactionController:transactionApproved` event.\n */\nexport type TransactionControllerTransactionApprovedEvent = {\n type: `${typeof controllerName}:transactionApproved`;\n payload: [\n {\n transactionMeta: TransactionMeta;\n actionId?: string;\n },\n ];\n};\n\n/**\n * Represents the `TransactionController:transactionConfirmed` event.\n */\nexport type TransactionControllerTransactionConfirmedEvent = {\n type: `${typeof controllerName}:transactionConfirmed`;\n payload: [transactionMeta: TransactionMeta];\n};\n\n/**\n * Represents the `TransactionController:transactionDropped` event.\n */\nexport type TransactionControllerTransactionDroppedEvent = {\n type: `${typeof controllerName}:transactionDropped`;\n payload: [{ transactionMeta: TransactionMeta }];\n};\n\n/**\n * Represents the `TransactionController:transactionFailed` event.\n */\nexport type TransactionControllerTransactionFailedEvent = {\n type: `${typeof controllerName}:transactionFailed`;\n payload: [\n {\n actionId?: string;\n error: string;\n transactionMeta: TransactionMeta;\n },\n ];\n};\n\n/**\n * Represents the `TransactionController:transactionFinished` event.\n */\nexport type TransactionControllerTransactionFinishedEvent = {\n type: `${typeof controllerName}:transactionFinished`;\n payload: [transactionMeta: TransactionMeta];\n};\n\n/**\n * Represents the `TransactionController:transactionNewSwapApproval` event.\n */\nexport type TransactionControllerTransactionNewSwapApprovalEvent = {\n type: `${typeof controllerName}:transactionNewSwapApproval`;\n payload: [{ transactionMeta: TransactionMeta }];\n};\n\n/**\n * Represents the `TransactionController:transactionNewSwap` event.\n */\nexport type TransactionControllerTransactionNewSwapEvent = {\n type: `${typeof controllerName}:transactionNewSwap`;\n payload: [{ transactionMeta: TransactionMeta }];\n};\n\n/**\n * Represents the `TransactionController:transactionNewSwapApproval` event.\n */\nexport type TransactionControllerTransactionNewSwapAndSendEvent = {\n type: `${typeof controllerName}:transactionNewSwapAndSend`;\n payload: [{ transactionMeta: TransactionMeta }];\n};\n\n/**\n * Represents the `TransactionController:transactionPublishingSkipped` event.\n */\nexport type TransactionControllerTransactionPublishingSkipped = {\n type: `${typeof controllerName}:transactionPublishingSkipped`;\n payload: [transactionMeta: TransactionMeta];\n};\n\n/**\n * Represents the `TransactionController:transactionRejected` event.\n */\nexport type TransactionControllerTransactionRejectedEvent = {\n type: `${typeof controllerName}:transactionRejected`;\n payload: [\n {\n transactionMeta: TransactionMeta;\n actionId?: string;\n },\n ];\n};\n\n/**\n * Represents the `TransactionController:transactionStatusUpdated` event.\n */\nexport type TransactionControllerTransactionStatusUpdatedEvent = {\n type: `${typeof controllerName}:transactionStatusUpdated`;\n payload: [\n {\n transactionMeta: TransactionMeta;\n },\n ];\n};\n\n/**\n * Represents the `TransactionController:transactionSubmitted` event.\n */\nexport type TransactionControllerTransactionSubmittedEvent = {\n type: `${typeof controllerName}:transactionSubmitted`;\n payload: [\n {\n transactionMeta: TransactionMeta;\n actionId?: string;\n },\n ];\n};\n\n/**\n * Represents the `TransactionController:unapprovedTransactionAdded` event.\n */\nexport type TransactionControllerUnapprovedTransactionAddedEvent = {\n type: `${typeof controllerName}:unapprovedTransactionAdded`;\n payload: [transactionMeta: TransactionMeta];\n};\n\n/**\n * The internal events available to the {@link TransactionController}.\n */\nexport type TransactionControllerEvents =\n | TransactionControllerIncomingTransactionBlockReceivedEvent\n | TransactionControllerPostTransactionBalanceUpdatedEvent\n | TransactionControllerSpeedupTransactionAddedEvent\n | TransactionControllerStateChangeEvent\n | TransactionControllerTransactionApprovedEvent\n | TransactionControllerTransactionConfirmedEvent\n | TransactionControllerTransactionDroppedEvent\n | TransactionControllerTransactionFailedEvent\n | TransactionControllerTransactionFinishedEvent\n | TransactionControllerTransactionNewSwapApprovalEvent\n | TransactionControllerTransactionNewSwapEvent\n | TransactionControllerTransactionNewSwapAndSendEvent\n | TransactionControllerTransactionPublishingSkipped\n | TransactionControllerTransactionRejectedEvent\n | TransactionControllerTransactionStatusUpdatedEvent\n | TransactionControllerTransactionSubmittedEvent\n | TransactionControllerUnapprovedTransactionAddedEvent;\n\n/**\n * The messenger of the {@link TransactionController}.\n */\nexport type TransactionControllerMessenger = RestrictedControllerMessenger<\n typeof controllerName,\n TransactionControllerActions | AllowedActions,\n TransactionControllerEvents | AllowedEvents,\n AllowedActions['type'],\n AllowedEvents['type']\n>;\n\n/**\n * Possible states of the approve transaction step.\n */\nexport enum ApprovalState {\n Approved = 'approved',\n NotApproved = 'not-approved',\n SkippedViaBeforePublishHook = 'skipped-via-before-publish-hook',\n}\n\n/**\n * Get the default TransactionsController state.\n *\n * @returns The default TransactionsController state.\n */\nfunction getDefaultTransactionControllerState(): TransactionControllerState {\n return {\n methodData: {},\n transactions: [],\n lastFetchedBlockNumbers: {},\n };\n}\n\n/**\n * Controller responsible for submitting and managing transactions.\n */\nexport class TransactionController extends BaseController<\n typeof controllerName,\n TransactionControllerState,\n TransactionControllerMessenger\n> {\n #internalEvents = new EventEmitter();\n\n private readonly isHistoryDisabled: boolean;\n\n private readonly isSwapsDisabled: boolean;\n\n private readonly isSendFlowHistoryDisabled: boolean;\n\n private readonly approvingTransactionIds: Set = new Set();\n\n private readonly nonceTracker: NonceTracker;\n\n private readonly registry: MethodRegistry;\n\n private readonly mutex = new Mutex();\n\n private readonly gasFeeFlows: GasFeeFlow[];\n\n private readonly getSavedGasFees: (chainId: Hex) => SavedGasFees | undefined;\n\n private readonly getNetworkState: () => NetworkState;\n\n private readonly getCurrentAccountEIP1559Compatibility: () => Promise;\n\n private readonly getCurrentNetworkEIP1559Compatibility: (\n networkClientId?: NetworkClientId,\n ) => Promise;\n\n private readonly getGasFeeEstimates: (\n options: FetchGasFeeEstimateOptions,\n ) => Promise;\n\n private readonly getPermittedAccounts: (origin?: string) => Promise;\n\n private readonly getExternalPendingTransactions: (\n address: string,\n chainId?: string,\n ) => NonceTrackerTransaction[];\n\n private readonly layer1GasFeeFlows: Layer1GasFeeFlow[];\n\n readonly #incomingTransactionOptions: IncomingTransactionOptions;\n\n private readonly incomingTransactionHelper: IncomingTransactionHelper;\n\n private readonly securityProviderRequest?: SecurityProviderRequest;\n\n readonly #pendingTransactionOptions: PendingTransactionOptions;\n\n private readonly pendingTransactionTracker: PendingTransactionTracker;\n\n private readonly signAbortCallbacks: Map void> = new Map();\n\n #transactionHistoryLimit: number;\n\n #isSimulationEnabled: () => boolean;\n\n #testGasFeeFlows: boolean;\n\n private readonly afterSign: (\n transactionMeta: TransactionMeta,\n signedTx: TypedTransaction,\n ) => boolean;\n\n private readonly beforeCheckPendingTransaction: (\n transactionMeta: TransactionMeta,\n ) => boolean;\n\n private readonly beforePublish: (transactionMeta: TransactionMeta) => boolean;\n\n private readonly publish: (\n transactionMeta: TransactionMeta,\n rawTx: string,\n ) => Promise<{ transactionHash?: string }>;\n\n private readonly getAdditionalSignArguments: (\n transactionMeta: TransactionMeta,\n ) => (TransactionMeta | undefined)[];\n\n private failTransaction(\n transactionMeta: TransactionMeta,\n error: Error,\n actionId?: string,\n ) {\n let newTransactionMeta: TransactionMeta;\n\n try {\n newTransactionMeta = this.#updateTransactionInternal(\n {\n transactionId: transactionMeta.id,\n note: 'TransactionController#failTransaction - Add error message and set status to failed',\n skipValidation: true,\n },\n (draftTransactionMeta) => {\n draftTransactionMeta.status = TransactionStatus.failed;\n\n (\n draftTransactionMeta as TransactionMeta & {\n status: TransactionStatus.failed;\n }\n ).error = normalizeTxError(error);\n },\n );\n } catch (err: unknown) {\n log('Failed to mark transaction as failed', err);\n\n newTransactionMeta = {\n ...transactionMeta,\n status: TransactionStatus.failed,\n error: normalizeTxError(error),\n };\n }\n\n this.messagingSystem.publish(`${controllerName}:transactionFailed`, {\n actionId,\n error: error.message,\n transactionMeta: newTransactionMeta,\n });\n\n this.onTransactionStatusChange(newTransactionMeta);\n\n this.messagingSystem.publish(\n `${controllerName}:transactionFinished`,\n newTransactionMeta,\n );\n\n this.#internalEvents.emit(\n `${transactionMeta.id}:finished`,\n newTransactionMeta,\n );\n }\n\n private async registryLookup(fourBytePrefix: string): Promise {\n const registryMethod = await this.registry.lookup(fourBytePrefix);\n if (!registryMethod) {\n return {\n registryMethod: '',\n parsedRegistryMethod: { name: undefined, args: undefined },\n };\n }\n const parsedRegistryMethod = this.registry.parse(registryMethod);\n return { registryMethod, parsedRegistryMethod };\n }\n\n #multichainTrackingHelper: MultichainTrackingHelper;\n\n /**\n * Method used to sign transactions\n */\n sign?: (\n transaction: TypedTransaction,\n from: string,\n transactionMeta?: TransactionMeta,\n ) => Promise;\n\n /**\n * Constructs a TransactionController.\n *\n * @param options - The controller options.\n * @param options.blockTracker - The block tracker used to poll for new blocks data.\n * @param options.disableHistory - Whether to disable storing history in transaction metadata.\n * @param options.disableSendFlowHistory - Explicitly disable transaction metadata history.\n * @param options.disableSwaps - Whether to disable additional processing on swaps transactions.\n * @param options.getCurrentAccountEIP1559Compatibility - Whether or not the account supports EIP-1559.\n * @param options.getCurrentNetworkEIP1559Compatibility - Whether or not the network supports EIP-1559.\n * @param options.getExternalPendingTransactions - Callback to retrieve pending transactions from external sources.\n * @param options.getGasFeeEstimates - Callback to retrieve gas fee estimates.\n * @param options.getNetworkClientRegistry - Gets the network client registry.\n * @param options.getNetworkState - Gets the state of the network controller.\n * @param options.getPermittedAccounts - Get accounts that a given origin has permissions for.\n * @param options.getSavedGasFees - Gets the saved gas fee config.\n * @param options.incomingTransactions - Configuration options for incoming transaction support.\n * @param options.isMultichainEnabled - Enable multichain support.\n * @param options.isSimulationEnabled - Whether new transactions will be automatically simulated.\n * @param options.messenger - The controller messenger.\n * @param options.onNetworkStateChange - Allows subscribing to network controller state changes.\n * @param options.pendingTransactions - Configuration options for pending transaction support.\n * @param options.provider - The provider used to create the underlying EthQuery instance.\n * @param options.securityProviderRequest - A function for verifying a transaction, whether it is malicious or not.\n * @param options.sign - Function used to sign transactions.\n * @param options.state - Initial state to set on this controller.\n * @param options.testGasFeeFlows - Whether to use the test gas fee flow.\n * @param options.transactionHistoryLimit - Transaction history limit.\n * @param options.hooks - The controller hooks.\n */\n constructor({\n blockTracker,\n disableHistory,\n disableSendFlowHistory,\n disableSwaps,\n getCurrentAccountEIP1559Compatibility,\n getCurrentNetworkEIP1559Compatibility,\n getExternalPendingTransactions,\n getGasFeeEstimates,\n getNetworkClientRegistry,\n getNetworkState,\n getPermittedAccounts,\n getSavedGasFees,\n incomingTransactions = {},\n isMultichainEnabled = false,\n isSimulationEnabled,\n messenger,\n onNetworkStateChange,\n pendingTransactions = {},\n provider,\n securityProviderRequest,\n sign,\n state,\n testGasFeeFlows,\n transactionHistoryLimit = 40,\n hooks,\n }: TransactionControllerOptions) {\n super({\n name: controllerName,\n metadata,\n messenger,\n state: {\n ...getDefaultTransactionControllerState(),\n ...state,\n },\n });\n\n this.messagingSystem = messenger;\n this.getNetworkState = getNetworkState;\n this.isSendFlowHistoryDisabled = disableSendFlowHistory ?? false;\n this.isHistoryDisabled = disableHistory ?? false;\n this.isSwapsDisabled = disableSwaps ?? false;\n this.#isSimulationEnabled = isSimulationEnabled ?? (() => true);\n // @ts-expect-error the type in eth-method-registry is inappropriate and should be changed\n this.registry = new MethodRegistry({ provider });\n this.getSavedGasFees = getSavedGasFees ?? ((_chainId) => undefined);\n this.getCurrentAccountEIP1559Compatibility =\n getCurrentAccountEIP1559Compatibility ?? (() => Promise.resolve(true));\n this.getCurrentNetworkEIP1559Compatibility =\n getCurrentNetworkEIP1559Compatibility;\n this.getGasFeeEstimates =\n getGasFeeEstimates || (() => Promise.resolve({} as GasFeeState));\n this.getPermittedAccounts = getPermittedAccounts;\n this.getExternalPendingTransactions =\n getExternalPendingTransactions ?? (() => []);\n this.securityProviderRequest = securityProviderRequest;\n this.#incomingTransactionOptions = incomingTransactions;\n this.#pendingTransactionOptions = pendingTransactions;\n this.#transactionHistoryLimit = transactionHistoryLimit;\n this.sign = sign;\n this.#testGasFeeFlows = testGasFeeFlows === true;\n\n this.afterSign = hooks?.afterSign ?? (() => true);\n this.beforeCheckPendingTransaction =\n hooks?.beforeCheckPendingTransaction ??\n /* istanbul ignore next */\n (() => true);\n this.beforePublish = hooks?.beforePublish ?? (() => true);\n this.getAdditionalSignArguments =\n hooks?.getAdditionalSignArguments ?? (() => []);\n this.publish =\n hooks?.publish ?? (() => Promise.resolve({ transactionHash: undefined }));\n\n this.nonceTracker = this.#createNonceTracker({\n provider,\n blockTracker,\n });\n\n const findNetworkClientIdByChainId = (chainId: Hex) => {\n return this.messagingSystem.call(\n `NetworkController:findNetworkClientIdByChainId`,\n chainId,\n );\n };\n\n this.#multichainTrackingHelper = new MultichainTrackingHelper({\n isMultichainEnabled,\n provider,\n nonceTracker: this.nonceTracker,\n incomingTransactionOptions: incomingTransactions,\n findNetworkClientIdByChainId,\n getNetworkClientById: ((networkClientId: NetworkClientId) => {\n return this.messagingSystem.call(\n `NetworkController:getNetworkClientById`,\n networkClientId,\n );\n }) as NetworkController['getNetworkClientById'],\n getNetworkClientRegistry,\n removeIncomingTransactionHelperListeners:\n this.#removeIncomingTransactionHelperListeners.bind(this),\n removePendingTransactionTrackerListeners:\n this.#removePendingTransactionTrackerListeners.bind(this),\n createNonceTracker: this.#createNonceTracker.bind(this),\n createIncomingTransactionHelper:\n this.#createIncomingTransactionHelper.bind(this),\n createPendingTransactionTracker:\n this.#createPendingTransactionTracker.bind(this),\n onNetworkStateChange: (listener) => {\n this.messagingSystem.subscribe(\n 'NetworkController:stateChange',\n listener,\n );\n },\n });\n this.#multichainTrackingHelper.initialize();\n\n const etherscanRemoteTransactionSource =\n new EtherscanRemoteTransactionSource({\n includeTokenTransfers: incomingTransactions.includeTokenTransfers,\n });\n\n this.incomingTransactionHelper = this.#createIncomingTransactionHelper({\n blockTracker,\n etherscanRemoteTransactionSource,\n });\n\n this.pendingTransactionTracker = this.#createPendingTransactionTracker({\n provider,\n blockTracker,\n });\n\n this.gasFeeFlows = this.#getGasFeeFlows();\n this.layer1GasFeeFlows = this.#getLayer1GasFeeFlows();\n\n const gasFeePoller = new GasFeePoller({\n findNetworkClientIdByChainId,\n gasFeeFlows: this.gasFeeFlows,\n getGasFeeControllerEstimates: this.getGasFeeEstimates,\n getProvider: (chainId, networkClientId) =>\n this.#multichainTrackingHelper.getProvider({\n networkClientId,\n chainId,\n }),\n getTransactions: () => this.state.transactions,\n layer1GasFeeFlows: this.layer1GasFeeFlows,\n onStateChange: (listener) => {\n this.messagingSystem.subscribe(\n 'TransactionController:stateChange',\n listener,\n );\n },\n });\n\n gasFeePoller.hub.on(\n 'transaction-updated',\n this.#onGasFeePollerTransactionUpdate.bind(this),\n );\n\n // when transactionsController state changes\n // check for pending transactions and start polling if there are any\n this.messagingSystem.subscribe(\n 'TransactionController:stateChange',\n this.#checkForPendingTransactionAndStartPolling,\n );\n\n // TODO once v2 is merged make sure this only runs when\n // selectedNetworkClientId changes\n onNetworkStateChange(() => {\n log('Detected network change', this.getChainId());\n this.pendingTransactionTracker.startIfPendingTransactions();\n this.onBootCleanup();\n });\n\n this.onBootCleanup();\n this.#checkForPendingTransactionAndStartPolling();\n }\n\n /**\n * Stops polling and removes listeners to prepare the controller for garbage collection.\n */\n destroy() {\n this.#stopAllTracking();\n }\n\n /**\n * Handle new method data request.\n *\n * @param fourBytePrefix - The method prefix.\n * @returns The method data object corresponding to the given signature prefix.\n */\n async handleMethodData(fourBytePrefix: string): Promise {\n const releaseLock = await this.mutex.acquire();\n try {\n const { methodData } = this.state;\n const knownMethod = Object.keys(methodData).find(\n (knownFourBytePrefix) => fourBytePrefix === knownFourBytePrefix,\n );\n if (knownMethod) {\n return methodData[fourBytePrefix];\n }\n const registry = await this.registryLookup(fourBytePrefix);\n this.update((state) => {\n state.methodData[fourBytePrefix] = registry;\n });\n return registry;\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Add a new unapproved transaction to state. Parameters will be validated, a\n * unique transaction id will be generated, and gas and gasPrice will be calculated\n * if not provided. If A `:unapproved` hub event will be emitted once added.\n *\n * @param txParams - Standard parameters for an Ethereum transaction.\n * @param opts - Additional options to control how the transaction is added.\n * @param opts.actionId - Unique ID to prevent duplicate requests.\n * @param opts.deviceConfirmedOn - An enum to indicate what device confirmed the transaction.\n * @param opts.method - RPC method that requested the transaction.\n * @param opts.origin - The origin of the transaction request, such as a dApp hostname.\n * @param opts.requireApproval - Whether the transaction requires approval by the user, defaults to true unless explicitly disabled.\n * @param opts.securityAlertResponse - Response from security validator.\n * @param opts.sendFlowHistory - The sendFlowHistory entries to add.\n * @param opts.type - Type of transaction to add, such as 'cancel' or 'swap'.\n * @param opts.swaps - Options for swaps transactions.\n * @param opts.swaps.hasApproveTx - Whether the transaction has an approval transaction.\n * @param opts.swaps.meta - Metadata for swap transaction.\n * @param opts.networkClientId - The id of the network client for this transaction.\n * @returns Object containing a promise resolving to the transaction hash if approved.\n */\n async addTransaction(\n txParams: TransactionParams,\n {\n actionId,\n deviceConfirmedOn,\n method,\n origin,\n requireApproval,\n securityAlertResponse,\n sendFlowHistory,\n swaps = {},\n type,\n networkClientId: requestNetworkClientId,\n }: {\n actionId?: string;\n deviceConfirmedOn?: WalletDevice;\n method?: string;\n origin?: string;\n requireApproval?: boolean | undefined;\n securityAlertResponse?: SecurityAlertResponse;\n sendFlowHistory?: SendFlowHistoryEntry[];\n swaps?: {\n hasApproveTx?: boolean;\n meta?: Partial;\n };\n type?: TransactionType;\n networkClientId?: NetworkClientId;\n } = {},\n ): Promise {\n log('Adding transaction', txParams);\n\n txParams = normalizeTransactionParams(txParams);\n if (\n requestNetworkClientId &&\n !this.#multichainTrackingHelper.has(requestNetworkClientId)\n ) {\n throw new Error(\n 'The networkClientId for this transaction could not be found',\n );\n }\n\n const networkClientId =\n requestNetworkClientId ?? this.#getGlobalNetworkClientId();\n\n const isEIP1559Compatible = await this.getEIP1559Compatibility(\n networkClientId,\n );\n\n validateTxParams(txParams, isEIP1559Compatible);\n\n if (origin) {\n await validateTransactionOrigin(\n await this.getPermittedAccounts(origin),\n this.#getSelectedAccount().address,\n txParams.from,\n origin,\n );\n }\n\n const dappSuggestedGasFees = this.generateDappSuggestedGasFees(\n txParams,\n origin,\n );\n\n const chainId = this.getChainId(networkClientId);\n const ethQuery = this.#multichainTrackingHelper.getEthQuery({\n networkClientId,\n chainId,\n });\n\n const transactionType =\n type ?? (await determineTransactionType(txParams, ethQuery)).type;\n\n const existingTransactionMeta = this.getTransactionWithActionId(actionId);\n\n // If a request to add a transaction with the same actionId is submitted again, a new transaction will not be created for it.\n let addedTransactionMeta = existingTransactionMeta\n ? cloneDeep(existingTransactionMeta)\n : {\n // Add actionId to txMeta to check if same actionId is seen again\n actionId,\n chainId,\n dappSuggestedGasFees,\n deviceConfirmedOn,\n id: random(),\n origin,\n securityAlertResponse,\n status: TransactionStatus.unapproved as const,\n time: Date.now(),\n txParams,\n userEditedGasLimit: false,\n verifiedOnBlockchain: false,\n type: transactionType,\n networkClientId,\n };\n\n await this.updateGasProperties(addedTransactionMeta);\n\n // Checks if a transaction already exists with a given actionId\n if (!existingTransactionMeta) {\n // Set security provider response\n if (method && this.securityProviderRequest) {\n const securityProviderResponse = await this.securityProviderRequest(\n addedTransactionMeta,\n method,\n );\n addedTransactionMeta.securityProviderResponse =\n securityProviderResponse;\n }\n\n if (!this.isSendFlowHistoryDisabled) {\n addedTransactionMeta.sendFlowHistory = sendFlowHistory ?? [];\n }\n // Initial history push\n if (!this.isHistoryDisabled) {\n addedTransactionMeta = addInitialHistorySnapshot(addedTransactionMeta);\n }\n\n addedTransactionMeta = updateSwapsTransaction(\n addedTransactionMeta,\n transactionType,\n swaps,\n {\n isSwapsDisabled: this.isSwapsDisabled,\n cancelTransaction: this.cancelTransaction.bind(this),\n messenger: this.messagingSystem,\n },\n );\n\n this.addMetadata(addedTransactionMeta);\n\n if (requireApproval !== false) {\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.#updateSimulationData(addedTransactionMeta);\n } else {\n log('Skipping simulation as approval not required');\n }\n\n this.messagingSystem.publish(\n `${controllerName}:unapprovedTransactionAdded`,\n addedTransactionMeta,\n );\n }\n\n return {\n result: this.processApproval(addedTransactionMeta, {\n isExisting: Boolean(existingTransactionMeta),\n requireApproval,\n actionId,\n }),\n transactionMeta: addedTransactionMeta,\n };\n }\n\n startIncomingTransactionPolling(networkClientIds: NetworkClientId[] = []) {\n if (networkClientIds.length === 0) {\n this.incomingTransactionHelper.start();\n return;\n }\n this.#multichainTrackingHelper.startIncomingTransactionPolling(\n networkClientIds,\n );\n }\n\n stopIncomingTransactionPolling(networkClientIds: NetworkClientId[] = []) {\n if (networkClientIds.length === 0) {\n this.incomingTransactionHelper.stop();\n return;\n }\n this.#multichainTrackingHelper.stopIncomingTransactionPolling(\n networkClientIds,\n );\n }\n\n stopAllIncomingTransactionPolling() {\n this.incomingTransactionHelper.stop();\n this.#multichainTrackingHelper.stopAllIncomingTransactionPolling();\n }\n\n async updateIncomingTransactions(networkClientIds: NetworkClientId[] = []) {\n if (networkClientIds.length === 0) {\n await this.incomingTransactionHelper.update();\n return;\n }\n await this.#multichainTrackingHelper.updateIncomingTransactions(\n networkClientIds,\n );\n }\n\n /**\n * Attempts to cancel a transaction based on its ID by setting its status to \"rejected\"\n * and emitting a `:finished` hub event.\n *\n * @param transactionId - The ID of the transaction to cancel.\n * @param gasValues - The gas values to use for the cancellation transaction.\n * @param options - The options for the cancellation transaction.\n * @param options.actionId - Unique ID to prevent duplicate requests.\n * @param options.estimatedBaseFee - The estimated base fee of the transaction.\n */\n async stopTransaction(\n transactionId: string,\n gasValues?: GasPriceValue | FeeMarketEIP1559Values,\n {\n estimatedBaseFee,\n actionId,\n }: { estimatedBaseFee?: string; actionId?: string } = {},\n ) {\n // If transaction is found for same action id, do not create a cancel transaction.\n if (this.getTransactionWithActionId(actionId)) {\n return;\n }\n\n if (gasValues) {\n // Not good practice to reassign a parameter but temporarily avoiding a larger refactor.\n gasValues = normalizeGasFeeValues(gasValues);\n validateGasValues(gasValues);\n }\n\n log('Creating cancel transaction', transactionId, gasValues);\n\n const transactionMeta = this.getTransaction(transactionId);\n if (!transactionMeta) {\n return;\n }\n\n if (!this.sign) {\n throw new Error('No sign method defined.');\n }\n\n // gasPrice (legacy non EIP1559)\n const minGasPrice = getIncreasedPriceFromExisting(\n transactionMeta.txParams.gasPrice,\n CANCEL_RATE,\n );\n\n const gasPriceFromValues = isGasPriceValue(gasValues) && gasValues.gasPrice;\n\n const newGasPrice =\n (gasPriceFromValues &&\n validateMinimumIncrease(gasPriceFromValues, minGasPrice)) ||\n minGasPrice;\n\n // maxFeePerGas (EIP1559)\n const existingMaxFeePerGas = transactionMeta.txParams?.maxFeePerGas;\n const minMaxFeePerGas = getIncreasedPriceFromExisting(\n existingMaxFeePerGas,\n CANCEL_RATE,\n );\n const maxFeePerGasValues =\n isFeeMarketEIP1559Values(gasValues) && gasValues.maxFeePerGas;\n const newMaxFeePerGas =\n (maxFeePerGasValues &&\n validateMinimumIncrease(maxFeePerGasValues, minMaxFeePerGas)) ||\n (existingMaxFeePerGas && minMaxFeePerGas);\n\n // maxPriorityFeePerGas (EIP1559)\n const existingMaxPriorityFeePerGas =\n transactionMeta.txParams?.maxPriorityFeePerGas;\n const minMaxPriorityFeePerGas = getIncreasedPriceFromExisting(\n existingMaxPriorityFeePerGas,\n CANCEL_RATE,\n );\n const maxPriorityFeePerGasValues =\n isFeeMarketEIP1559Values(gasValues) && gasValues.maxPriorityFeePerGas;\n const newMaxPriorityFeePerGas =\n (maxPriorityFeePerGasValues &&\n validateMinimumIncrease(\n maxPriorityFeePerGasValues,\n minMaxPriorityFeePerGas,\n )) ||\n (existingMaxPriorityFeePerGas && minMaxPriorityFeePerGas);\n\n const newTxParams: TransactionParams =\n newMaxFeePerGas && newMaxPriorityFeePerGas\n ? {\n from: transactionMeta.txParams.from,\n gasLimit: transactionMeta.txParams.gas,\n maxFeePerGas: newMaxFeePerGas,\n maxPriorityFeePerGas: newMaxPriorityFeePerGas,\n type: TransactionEnvelopeType.feeMarket,\n nonce: transactionMeta.txParams.nonce,\n to: transactionMeta.txParams.from,\n value: '0x0',\n }\n : {\n from: transactionMeta.txParams.from,\n gasLimit: transactionMeta.txParams.gas,\n gasPrice: newGasPrice,\n nonce: transactionMeta.txParams.nonce,\n to: transactionMeta.txParams.from,\n value: '0x0',\n };\n\n const unsignedEthTx = this.prepareUnsignedEthTx(\n transactionMeta.chainId,\n newTxParams,\n );\n\n const signedTx = await this.sign(\n unsignedEthTx,\n transactionMeta.txParams.from,\n );\n\n const rawTx = bufferToHex(signedTx.serialize());\n\n const newFee = newTxParams.maxFeePerGas ?? newTxParams.gasPrice;\n\n const oldFee = newTxParams.maxFeePerGas\n ? transactionMeta.txParams.maxFeePerGas\n : transactionMeta.txParams.gasPrice;\n\n log('Submitting cancel transaction', {\n oldFee,\n newFee,\n txParams: newTxParams,\n });\n\n const ethQuery = this.#multichainTrackingHelper.getEthQuery({\n networkClientId: transactionMeta.networkClientId,\n chainId: transactionMeta.chainId,\n });\n const hash = await this.publishTransactionForRetry(\n ethQuery,\n rawTx,\n transactionMeta,\n );\n\n const cancelTransactionMeta = {\n actionId,\n chainId: transactionMeta.chainId,\n networkClientId: transactionMeta.networkClientId,\n estimatedBaseFee,\n hash,\n id: random(),\n originalGasEstimate: transactionMeta.txParams.gas,\n rawTx,\n status: TransactionStatus.submitted as const,\n time: Date.now(),\n type: TransactionType.cancel as const,\n txParams: newTxParams,\n };\n\n this.addMetadata(cancelTransactionMeta);\n\n // stopTransaction has no approval request, so we assume the user has already approved the transaction\n this.messagingSystem.publish(`${controllerName}:transactionApproved`, {\n transactionMeta: cancelTransactionMeta,\n actionId,\n });\n this.messagingSystem.publish(`${controllerName}:transactionSubmitted`, {\n transactionMeta: cancelTransactionMeta,\n actionId,\n });\n\n this.messagingSystem.publish(\n `${controllerName}:transactionFinished`,\n cancelTransactionMeta,\n );\n this.#internalEvents.emit(\n `${transactionMeta.id}:finished`,\n cancelTransactionMeta,\n );\n }\n\n /**\n * Attempts to speed up a transaction increasing transaction gasPrice by ten percent.\n *\n * @param transactionId - The ID of the transaction to speed up.\n * @param gasValues - The gas values to use for the speed up transaction.\n * @param options - The options for the speed up transaction.\n * @param options.actionId - Unique ID to prevent duplicate requests\n * @param options.estimatedBaseFee - The estimated base fee of the transaction.\n */\n async speedUpTransaction(\n transactionId: string,\n gasValues?: GasPriceValue | FeeMarketEIP1559Values,\n {\n actionId,\n estimatedBaseFee,\n }: { actionId?: string; estimatedBaseFee?: string } = {},\n ) {\n // If transaction is found for same action id, do not create a new speed up transaction.\n if (this.getTransactionWithActionId(actionId)) {\n return;\n }\n\n if (gasValues) {\n // Not good practice to reassign a parameter but temporarily avoiding a larger refactor.\n gasValues = normalizeGasFeeValues(gasValues);\n validateGasValues(gasValues);\n }\n\n log('Creating speed up transaction', transactionId, gasValues);\n\n const transactionMeta = this.getTransaction(transactionId);\n /* istanbul ignore next */\n if (!transactionMeta) {\n return;\n }\n\n /* istanbul ignore next */\n if (!this.sign) {\n throw new Error('No sign method defined.');\n }\n\n // gasPrice (legacy non EIP1559)\n const minGasPrice = getIncreasedPriceFromExisting(\n transactionMeta.txParams.gasPrice,\n SPEED_UP_RATE,\n );\n\n const gasPriceFromValues = isGasPriceValue(gasValues) && gasValues.gasPrice;\n\n const newGasPrice =\n (gasPriceFromValues &&\n validateMinimumIncrease(gasPriceFromValues, minGasPrice)) ||\n minGasPrice;\n\n // maxFeePerGas (EIP1559)\n const existingMaxFeePerGas = transactionMeta.txParams?.maxFeePerGas;\n const minMaxFeePerGas = getIncreasedPriceFromExisting(\n existingMaxFeePerGas,\n SPEED_UP_RATE,\n );\n const maxFeePerGasValues =\n isFeeMarketEIP1559Values(gasValues) && gasValues.maxFeePerGas;\n const newMaxFeePerGas =\n (maxFeePerGasValues &&\n validateMinimumIncrease(maxFeePerGasValues, minMaxFeePerGas)) ||\n (existingMaxFeePerGas && minMaxFeePerGas);\n\n // maxPriorityFeePerGas (EIP1559)\n const existingMaxPriorityFeePerGas =\n transactionMeta.txParams?.maxPriorityFeePerGas;\n const minMaxPriorityFeePerGas = getIncreasedPriceFromExisting(\n existingMaxPriorityFeePerGas,\n SPEED_UP_RATE,\n );\n const maxPriorityFeePerGasValues =\n isFeeMarketEIP1559Values(gasValues) && gasValues.maxPriorityFeePerGas;\n const newMaxPriorityFeePerGas =\n (maxPriorityFeePerGasValues &&\n validateMinimumIncrease(\n maxPriorityFeePerGasValues,\n minMaxPriorityFeePerGas,\n )) ||\n (existingMaxPriorityFeePerGas && minMaxPriorityFeePerGas);\n\n const txParams: TransactionParams =\n newMaxFeePerGas && newMaxPriorityFeePerGas\n ? {\n ...transactionMeta.txParams,\n gasLimit: transactionMeta.txParams.gas,\n maxFeePerGas: newMaxFeePerGas,\n maxPriorityFeePerGas: newMaxPriorityFeePerGas,\n type: TransactionEnvelopeType.feeMarket,\n }\n : {\n ...transactionMeta.txParams,\n gasLimit: transactionMeta.txParams.gas,\n gasPrice: newGasPrice,\n };\n\n const unsignedEthTx = this.prepareUnsignedEthTx(\n transactionMeta.chainId,\n txParams,\n );\n\n const signedTx = await this.sign(\n unsignedEthTx,\n transactionMeta.txParams.from,\n );\n\n const transactionMetaWithRsv = this.updateTransactionMetaRSV(\n transactionMeta,\n signedTx,\n );\n const rawTx = bufferToHex(signedTx.serialize());\n\n const newFee = txParams.maxFeePerGas ?? txParams.gasPrice;\n\n const oldFee = txParams.maxFeePerGas\n ? transactionMetaWithRsv.txParams.maxFeePerGas\n : transactionMetaWithRsv.txParams.gasPrice;\n\n log('Submitting speed up transaction', { oldFee, newFee, txParams });\n\n const ethQuery = this.#multichainTrackingHelper.getEthQuery({\n networkClientId: transactionMeta.networkClientId,\n chainId: transactionMeta.chainId,\n });\n const hash = await this.publishTransactionForRetry(\n ethQuery,\n rawTx,\n transactionMeta,\n );\n\n const baseTransactionMeta = {\n ...transactionMetaWithRsv,\n estimatedBaseFee,\n id: random(),\n time: Date.now(),\n hash,\n actionId,\n originalGasEstimate: transactionMeta.txParams.gas,\n type: TransactionType.retry as const,\n originalType: transactionMeta.type,\n };\n\n const newTransactionMeta =\n newMaxFeePerGas && newMaxPriorityFeePerGas\n ? {\n ...baseTransactionMeta,\n txParams: {\n ...transactionMeta.txParams,\n maxFeePerGas: newMaxFeePerGas,\n maxPriorityFeePerGas: newMaxPriorityFeePerGas,\n },\n }\n : {\n ...baseTransactionMeta,\n txParams: {\n ...transactionMeta.txParams,\n gasPrice: newGasPrice,\n },\n };\n\n this.addMetadata(newTransactionMeta);\n\n // speedUpTransaction has no approval request, so we assume the user has already approved the transaction\n this.messagingSystem.publish(`${controllerName}:transactionApproved`, {\n transactionMeta: newTransactionMeta,\n actionId,\n });\n\n this.messagingSystem.publish(`${controllerName}:transactionSubmitted`, {\n transactionMeta: newTransactionMeta,\n actionId,\n });\n\n this.messagingSystem.publish(\n `${controllerName}:speedupTransactionAdded`,\n newTransactionMeta,\n );\n }\n\n /**\n * Estimates required gas for a given transaction.\n *\n * @param transaction - The transaction to estimate gas for.\n * @param networkClientId - The network client id to use for the estimate.\n * @returns The gas and gas price.\n */\n async estimateGas(\n transaction: TransactionParams,\n networkClientId?: NetworkClientId,\n ) {\n const ethQuery = this.#multichainTrackingHelper.getEthQuery({\n networkClientId,\n });\n const { estimatedGas, simulationFails } = await estimateGas(\n transaction,\n ethQuery,\n );\n\n return { gas: estimatedGas, simulationFails };\n }\n\n /**\n * Estimates required gas for a given transaction and add additional gas buffer with the given multiplier.\n *\n * @param transaction - The transaction params to estimate gas for.\n * @param multiplier - The multiplier to use for the gas buffer.\n * @param networkClientId - The network client id to use for the estimate.\n */\n async estimateGasBuffered(\n transaction: TransactionParams,\n multiplier: number,\n networkClientId?: NetworkClientId,\n ) {\n const ethQuery = this.#multichainTrackingHelper.getEthQuery({\n networkClientId,\n });\n const { blockGasLimit, estimatedGas, simulationFails } = await estimateGas(\n transaction,\n ethQuery,\n );\n\n const gas = addGasBuffer(estimatedGas, blockGasLimit, multiplier);\n\n return {\n gas,\n simulationFails,\n };\n }\n\n /**\n * Updates an existing transaction in state.\n *\n * @param transactionMeta - The new transaction to store in state.\n * @param note - A note or update reason to include in the transaction history.\n */\n updateTransaction(transactionMeta: TransactionMeta, note: string) {\n const { id: transactionId } = transactionMeta;\n\n this.#updateTransactionInternal({ transactionId, note }, () => ({\n ...transactionMeta,\n }));\n }\n\n /**\n * Update the security alert response for a transaction.\n *\n * @param transactionId - ID of the transaction.\n * @param securityAlertResponse - The new security alert response for the transaction.\n */\n updateSecurityAlertResponse(\n transactionId: string,\n securityAlertResponse: SecurityAlertResponse,\n ) {\n if (!securityAlertResponse) {\n throw new Error(\n 'updateSecurityAlertResponse: securityAlertResponse should not be null',\n );\n }\n const transactionMeta = this.getTransaction(transactionId);\n if (!transactionMeta) {\n throw new Error(\n `Cannot update security alert response as no transaction metadata found`,\n );\n }\n const updatedTransactionMeta = {\n ...transactionMeta,\n securityAlertResponse,\n };\n this.updateTransaction(\n updatedTransactionMeta,\n `${controllerName}:updatesecurityAlertResponse - securityAlertResponse updated`,\n );\n }\n\n /**\n * Removes all transactions from state, optionally based on the current network.\n *\n * @param ignoreNetwork - Determines whether to wipe all transactions, or just those on the\n * current network. If `true`, all transactions are wiped.\n * @param address - If specified, only transactions originating from this address will be\n * wiped on current network.\n */\n wipeTransactions(ignoreNetwork?: boolean, address?: string) {\n /* istanbul ignore next */\n if (ignoreNetwork && !address) {\n this.update((state) => {\n state.transactions = [];\n });\n return;\n }\n const currentChainId = this.getChainId();\n const newTransactions = this.state.transactions.filter(\n ({ chainId, txParams }) => {\n const isMatchingNetwork = ignoreNetwork || chainId === currentChainId;\n\n if (!isMatchingNetwork) {\n return true;\n }\n\n const isMatchingAddress =\n !address || txParams.from?.toLowerCase() === address.toLowerCase();\n\n return !isMatchingAddress;\n },\n );\n\n this.update((state) => {\n state.transactions = this.trimTransactionsForState(newTransactions);\n });\n }\n\n /**\n * Adds external provided transaction to state as confirmed transaction.\n *\n * @param transactionMeta - TransactionMeta to add transactions.\n * @param transactionReceipt - TransactionReceipt of the external transaction.\n * @param baseFeePerGas - Base fee per gas of the external transaction.\n */\n async confirmExternalTransaction(\n transactionMeta: TransactionMeta,\n transactionReceipt: TransactionReceipt,\n baseFeePerGas: Hex,\n ) {\n // Run validation and add external transaction to state.\n const newTransactionMeta = this.addExternalTransaction(transactionMeta);\n\n try {\n const transactionId = newTransactionMeta.id;\n\n // Make sure status is confirmed and define gasUsed as in receipt.\n const updatedTransactionMeta = {\n ...newTransactionMeta,\n status: TransactionStatus.confirmed as const,\n txReceipt: transactionReceipt,\n };\n if (baseFeePerGas) {\n updatedTransactionMeta.baseFeePerGas = baseFeePerGas;\n }\n\n // Update same nonce local transactions as dropped and define replacedBy properties.\n this.markNonceDuplicatesDropped(transactionId);\n\n // Update external provided transaction with updated gas values and confirmed status.\n this.updateTransaction(\n updatedTransactionMeta,\n `${controllerName}:confirmExternalTransaction - Add external transaction`,\n );\n this.onTransactionStatusChange(updatedTransactionMeta);\n\n // Intentional given potential duration of process.\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.updatePostBalance(updatedTransactionMeta);\n\n this.messagingSystem.publish(\n `${controllerName}:transactionConfirmed`,\n updatedTransactionMeta,\n );\n } catch (error) {\n console.error('Failed to confirm external transaction', error);\n }\n }\n\n /**\n * Append new send flow history to a transaction.\n *\n * @param transactionID - The ID of the transaction to update.\n * @param currentSendFlowHistoryLength - The length of the current sendFlowHistory array.\n * @param sendFlowHistoryToAdd - The sendFlowHistory entries to add.\n * @returns The updated transactionMeta.\n */\n updateTransactionSendFlowHistory(\n transactionID: string,\n currentSendFlowHistoryLength: number,\n sendFlowHistoryToAdd: SendFlowHistoryEntry[],\n ): TransactionMeta {\n if (this.isSendFlowHistoryDisabled) {\n throw new Error(\n 'Send flow history is disabled for the current transaction controller',\n );\n }\n\n const transactionMeta = this.getTransaction(transactionID);\n\n if (!transactionMeta) {\n throw new Error(\n `Cannot update send flow history as no transaction metadata found`,\n );\n }\n\n validateIfTransactionUnapproved(\n transactionMeta,\n 'updateTransactionSendFlowHistory',\n );\n\n const sendFlowHistory = transactionMeta.sendFlowHistory ?? [];\n if (currentSendFlowHistoryLength === sendFlowHistory.length) {\n const updatedTransactionMeta = {\n ...transactionMeta,\n sendFlowHistory: [...sendFlowHistory, ...sendFlowHistoryToAdd],\n };\n this.updateTransaction(\n updatedTransactionMeta,\n `${controllerName}:updateTransactionSendFlowHistory - sendFlowHistory updated`,\n );\n }\n\n return this.getTransaction(transactionID) as TransactionMeta;\n }\n\n /**\n * Update the gas values of a transaction.\n *\n * @param transactionId - The ID of the transaction to update.\n * @param gasValues - Gas values to update.\n * @param gasValues.gas - Same as transaction.gasLimit.\n * @param gasValues.gasLimit - Maxmimum number of units of gas to use for this transaction.\n * @param gasValues.gasPrice - Price per gas for legacy transactions.\n * @param gasValues.maxPriorityFeePerGas - Maximum amount per gas to give to validator as incentive.\n * @param gasValues.maxFeePerGas - Maximum amount per gas to pay for the transaction, including the priority fee.\n * @param gasValues.estimateUsed - Which estimate level was used.\n * @param gasValues.estimateSuggested - Which estimate level that the API suggested.\n * @param gasValues.defaultGasEstimates - The default estimate for gas.\n * @param gasValues.originalGasEstimate - Original estimate for gas.\n * @param gasValues.userEditedGasLimit - The gas limit supplied by user.\n * @param gasValues.userFeeLevel - Estimate level user selected.\n * @returns The updated transactionMeta.\n */\n updateTransactionGasFees(\n transactionId: string,\n {\n defaultGasEstimates,\n estimateUsed,\n estimateSuggested,\n gas,\n gasLimit,\n gasPrice,\n maxPriorityFeePerGas,\n maxFeePerGas,\n originalGasEstimate,\n userEditedGasLimit,\n userFeeLevel,\n }: {\n defaultGasEstimates?: string;\n estimateUsed?: string;\n estimateSuggested?: string;\n gas?: string;\n gasLimit?: string;\n gasPrice?: string;\n maxPriorityFeePerGas?: string;\n maxFeePerGas?: string;\n originalGasEstimate?: string;\n userEditedGasLimit?: boolean;\n userFeeLevel?: string;\n },\n ): TransactionMeta {\n const transactionMeta = this.getTransaction(transactionId);\n\n if (!transactionMeta) {\n throw new Error(\n `Cannot update transaction as no transaction metadata found`,\n );\n }\n\n validateIfTransactionUnapproved(\n transactionMeta,\n 'updateTransactionGasFees',\n );\n\n let transactionGasFees = {\n txParams: {\n gas,\n gasLimit,\n gasPrice,\n maxPriorityFeePerGas,\n maxFeePerGas,\n },\n defaultGasEstimates,\n estimateUsed,\n estimateSuggested,\n originalGasEstimate,\n userEditedGasLimit,\n userFeeLevel,\n // TODO: Replace `any` with type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } as any;\n\n // only update what is defined\n transactionGasFees.txParams = pickBy(transactionGasFees.txParams);\n transactionGasFees = pickBy(transactionGasFees);\n\n // merge updated gas values with existing transaction meta\n const updatedMeta = merge({}, transactionMeta, transactionGasFees);\n\n this.updateTransaction(\n updatedMeta,\n `${controllerName}:updateTransactionGasFees - gas values updated`,\n );\n\n return this.getTransaction(transactionId) as TransactionMeta;\n }\n\n /**\n * Update the previous gas values of a transaction.\n *\n * @param transactionId - The ID of the transaction to update.\n * @param previousGas - Previous gas values to update.\n * @param previousGas.gasLimit - Maxmimum number of units of gas to use for this transaction.\n * @param previousGas.maxFeePerGas - Maximum amount per gas to pay for the transaction, including the priority fee.\n * @param previousGas.maxPriorityFeePerGas - Maximum amount per gas to give to validator as incentive.\n * @returns The updated transactionMeta.\n */\n updatePreviousGasParams(\n transactionId: string,\n {\n gasLimit,\n maxFeePerGas,\n maxPriorityFeePerGas,\n }: {\n gasLimit?: string;\n maxFeePerGas?: string;\n maxPriorityFeePerGas?: string;\n },\n ): TransactionMeta {\n const transactionMeta = this.getTransaction(transactionId);\n\n if (!transactionMeta) {\n throw new Error(\n `Cannot update transaction as no transaction metadata found`,\n );\n }\n\n validateIfTransactionUnapproved(transactionMeta, 'updatePreviousGasParams');\n\n const transactionPreviousGas = {\n previousGas: {\n gasLimit,\n maxFeePerGas,\n maxPriorityFeePerGas,\n },\n // TODO: Replace `any` with type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } as any;\n\n // only update what is defined\n transactionPreviousGas.previousGas = pickBy(\n transactionPreviousGas.previousGas,\n );\n\n // merge updated previous gas values with existing transaction meta\n const updatedMeta = merge({}, transactionMeta, transactionPreviousGas);\n\n this.updateTransaction(\n updatedMeta,\n `${controllerName}:updatePreviousGasParams - Previous gas values updated`,\n );\n\n return this.getTransaction(transactionId) as TransactionMeta;\n }\n\n async getNonceLock(\n address: string,\n networkClientId?: NetworkClientId,\n ): Promise {\n return this.#multichainTrackingHelper.getNonceLock(\n address,\n networkClientId,\n );\n }\n\n /**\n * Updates the editable parameters of a transaction.\n *\n * @param txId - The ID of the transaction to update.\n * @param params - The editable parameters to update.\n * @param params.data - Data to pass with the transaction.\n * @param params.gas - Maximum number of units of gas to use for the transaction.\n * @param params.gasPrice - Price per gas for legacy transactions.\n * @param params.from - Address to send the transaction from.\n * @param params.to - Address to send the transaction to.\n * @param params.value - Value associated with the transaction.\n * @returns The updated transaction metadata.\n */\n async updateEditableParams(\n txId: string,\n {\n data,\n gas,\n gasPrice,\n from,\n to,\n value,\n }: {\n data?: string;\n gas?: string;\n gasPrice?: string;\n from?: string;\n to?: string;\n value?: string;\n },\n ) {\n const transactionMeta = this.getTransaction(txId);\n if (!transactionMeta) {\n throw new Error(\n `Cannot update editable params as no transaction metadata found`,\n );\n }\n\n validateIfTransactionUnapproved(transactionMeta, 'updateEditableParams');\n\n const editableParams = {\n txParams: {\n data,\n from,\n to,\n value,\n gas,\n gasPrice,\n },\n } as Partial;\n\n editableParams.txParams = pickBy(\n editableParams.txParams,\n ) as TransactionParams;\n\n const updatedTransaction = merge({}, transactionMeta, editableParams);\n const provider = this.#multichainTrackingHelper.getProvider({\n chainId: transactionMeta.chainId,\n networkClientId: transactionMeta.networkClientId,\n });\n const ethQuery = new EthQuery(provider);\n const { type } = await determineTransactionType(\n updatedTransaction.txParams,\n ethQuery,\n );\n updatedTransaction.type = type;\n\n await updateTransactionLayer1GasFee({\n layer1GasFeeFlows: this.layer1GasFeeFlows,\n provider,\n transactionMeta: updatedTransaction,\n });\n\n this.updateTransaction(\n updatedTransaction,\n `Update Editable Params for ${txId}`,\n );\n return this.getTransaction(txId);\n }\n\n /**\n * Signs and returns the raw transaction data for provided transaction params list.\n *\n * @param listOfTxParams - The list of transaction params to approve.\n * @param opts - Options bag.\n * @param opts.hasNonce - Whether the transactions already have a nonce.\n * @returns The raw transactions.\n */\n async approveTransactionsWithSameNonce(\n listOfTxParams: (TransactionParams & { chainId: Hex })[] = [],\n { hasNonce }: { hasNonce?: boolean } = {},\n ): Promise {\n log('Approving transactions with same nonce', {\n transactions: listOfTxParams,\n });\n\n if (listOfTxParams.length === 0) {\n return '';\n }\n\n const initialTx = listOfTxParams[0];\n const common = this.getCommonConfiguration(initialTx.chainId);\n\n // We need to ensure we get the nonce using the the NonceTracker on the chain matching\n // the txParams. In this context we only have chainId available to us, but the\n // NonceTrackers are keyed by networkClientId. To workaround this, we attempt to find\n // a networkClientId that matches the chainId. As a fallback, the globally selected\n // network's NonceTracker will be used instead.\n let networkClientId: NetworkClientId | undefined;\n try {\n networkClientId = this.messagingSystem.call(\n `NetworkController:findNetworkClientIdByChainId`,\n initialTx.chainId,\n );\n } catch (err) {\n log('failed to find networkClientId from chainId', err);\n }\n\n const initialTxAsEthTx = TransactionFactory.fromTxData(initialTx, {\n common,\n });\n const initialTxAsSerializedHex = bufferToHex(initialTxAsEthTx.serialize());\n\n if (this.approvingTransactionIds.has(initialTxAsSerializedHex)) {\n return '';\n }\n this.approvingTransactionIds.add(initialTxAsSerializedHex);\n\n let rawTransactions, nonceLock;\n try {\n // TODO: we should add a check to verify that all transactions have the same from address\n const fromAddress = initialTx.from;\n const requiresNonce = hasNonce !== true;\n\n nonceLock = requiresNonce\n ? await this.getNonceLock(fromAddress, networkClientId)\n : undefined;\n\n const nonce = nonceLock\n ? add0x(nonceLock.nextNonce.toString(16))\n : initialTx.nonce;\n\n if (nonceLock) {\n log('Using nonce from nonce tracker', nonce, nonceLock.nonceDetails);\n }\n\n rawTransactions = await Promise.all(\n listOfTxParams.map((txParams) => {\n txParams.nonce = nonce;\n return this.signExternalTransaction(txParams.chainId, txParams);\n }),\n );\n } catch (err) {\n log('Error while signing transactions with same nonce', err);\n // Must set transaction to submitted/failed before releasing lock\n // continue with error chain\n throw err;\n } finally {\n nonceLock?.releaseLock();\n this.approvingTransactionIds.delete(initialTxAsSerializedHex);\n }\n return rawTransactions;\n }\n\n /**\n * Update a custodial transaction.\n *\n * @param transactionId - The ID of the transaction to update.\n * @param options - The custodial transaction options to update.\n * @param options.errorMessage - The error message to be assigned in case transaction status update to failed.\n * @param options.hash - The new hash value to be assigned.\n * @param options.status - The new status value to be assigned.\n */\n updateCustodialTransaction(\n transactionId: string,\n {\n errorMessage,\n hash,\n status,\n }: {\n errorMessage?: string;\n hash?: string;\n status?: TransactionStatus;\n },\n ) {\n const transactionMeta = this.getTransaction(transactionId);\n\n if (!transactionMeta) {\n throw new Error(\n `Cannot update custodial transaction as no transaction metadata found`,\n );\n }\n\n if (!transactionMeta.custodyId) {\n throw new Error('Transaction must be a custodian transaction');\n }\n\n if (\n status &&\n ![\n TransactionStatus.submitted,\n TransactionStatus.signed,\n TransactionStatus.failed,\n ].includes(status)\n ) {\n throw new Error(\n `Cannot update custodial transaction with status: ${status}`,\n );\n }\n\n const updatedTransactionMeta = merge(\n {},\n transactionMeta,\n pickBy({ hash, status }),\n ) as TransactionMeta;\n\n if (updatedTransactionMeta.status === TransactionStatus.submitted) {\n updatedTransactionMeta.submittedTime = new Date().getTime();\n }\n\n if (updatedTransactionMeta.status === TransactionStatus.failed) {\n updatedTransactionMeta.error = normalizeTxError(new Error(errorMessage));\n }\n\n this.updateTransaction(\n updatedTransactionMeta,\n `${controllerName}:updateCustodialTransaction - Custodial transaction updated`,\n );\n\n if (\n [TransactionStatus.submitted, TransactionStatus.failed].includes(\n status as TransactionStatus,\n )\n ) {\n this.messagingSystem.publish(\n `${controllerName}:transactionFinished`,\n updatedTransactionMeta,\n );\n this.#internalEvents.emit(\n `${updatedTransactionMeta.id}:finished`,\n updatedTransactionMeta,\n );\n }\n }\n\n /**\n * Search transaction metadata for matching entries.\n *\n * @param opts - Options bag.\n * @param opts.searchCriteria - An object containing values or functions for transaction properties to filter transactions with.\n * @param opts.initialList - The transactions to search. Defaults to the current state.\n * @param opts.filterToCurrentNetwork - Whether to filter the results to the current network. Defaults to true.\n * @param opts.limit - The maximum number of transactions to return. No limit by default.\n * @returns An array of transactions matching the provided options.\n */\n getTransactions({\n searchCriteria = {},\n initialList,\n filterToCurrentNetwork = true,\n limit,\n }: {\n // TODO: Replace `any` with type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n searchCriteria?: any;\n initialList?: TransactionMeta[];\n filterToCurrentNetwork?: boolean;\n limit?: number;\n } = {}): TransactionMeta[] {\n const chainId = this.getChainId();\n // searchCriteria is an object that might have values that aren't predicate\n // methods. When providing any other value type (string, number, etc), we\n // consider this shorthand for \"check the value at key for strict equality\n // with the provided value\". To conform this object to be only methods, we\n // mapValues (lodash) such that every value on the object is a method that\n // returns a boolean.\n const predicateMethods = mapValues(searchCriteria, (predicate) => {\n return typeof predicate === 'function'\n ? predicate\n : // TODO: Replace `any` with type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (v: any) => v === predicate;\n });\n\n const transactionsToFilter = initialList ?? this.state.transactions;\n\n // Combine sortBy and pickBy to transform our state object into an array of\n // matching transactions that are sorted by time.\n const filteredTransactions = sortBy(\n pickBy(transactionsToFilter, (transaction) => {\n if (filterToCurrentNetwork && transaction.chainId !== chainId) {\n return false;\n }\n // iterate over the predicateMethods keys to check if the transaction\n // matches the searchCriteria\n for (const [key, predicate] of Object.entries(predicateMethods)) {\n // We return false early as soon as we know that one of the specified\n // search criteria do not match the transaction. This prevents\n // needlessly checking all criteria when we already know the criteria\n // are not fully satisfied. We check both txParams and the base\n // object as predicate keys can be either.\n if (key in transaction.txParams) {\n // TODO: Replace `any` with type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n if (predicate((transaction.txParams as any)[key]) === false) {\n return false;\n }\n // TODO: Replace `any` with type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } else if (predicate((transaction as any)[key]) === false) {\n return false;\n }\n }\n\n return true;\n }),\n 'time',\n );\n if (limit !== undefined) {\n // We need to have all transactions of a given nonce in order to display\n // necessary details in the UI. We use the size of this set to determine\n // whether we have reached the limit provided, thus ensuring that all\n // transactions of nonces we include will be sent to the UI.\n const nonces = new Set();\n const txs = [];\n // By default, the transaction list we filter from is sorted by time ASC.\n // To ensure that filtered results prefers the newest transactions we\n // iterate from right to left, inserting transactions into front of a new\n // array. The original order is preserved, but we ensure that newest txs\n // are preferred.\n for (let i = filteredTransactions.length - 1; i > -1; i--) {\n const txMeta = filteredTransactions[i];\n const { nonce } = txMeta.txParams;\n if (!nonces.has(nonce)) {\n if (nonces.size < limit) {\n nonces.add(nonce);\n } else {\n continue;\n }\n }\n // Push transaction into the beginning of our array to ensure the\n // original order is preserved.\n txs.unshift(txMeta);\n }\n return txs;\n }\n return filteredTransactions;\n }\n\n async estimateGasFee({\n transactionParams,\n chainId,\n networkClientId: requestNetworkClientId,\n }: {\n transactionParams: TransactionParams;\n chainId?: Hex;\n networkClientId?: NetworkClientId;\n }): Promise {\n const networkClientId = this.#getNetworkClientId({\n networkClientId: requestNetworkClientId,\n chainId,\n });\n\n const transactionMeta = {\n txParams: transactionParams,\n chainId,\n networkClientId,\n } as TransactionMeta;\n\n // Guaranteed as the default gas fee flow matches all transactions.\n const gasFeeFlow = getGasFeeFlow(\n transactionMeta,\n this.gasFeeFlows,\n ) as GasFeeFlow;\n\n const ethQuery = this.#multichainTrackingHelper.getEthQuery({\n networkClientId,\n chainId,\n });\n\n const gasFeeControllerData = await this.getGasFeeEstimates({\n networkClientId,\n });\n\n return gasFeeFlow.getGasFees({\n ethQuery,\n gasFeeControllerData,\n transactionMeta,\n });\n }\n\n /**\n * Determine the layer 1 gas fee for the given transaction parameters.\n *\n * @param request - The request object.\n * @param request.transactionParams - The transaction parameters to estimate the layer 1 gas fee for.\n * @param request.chainId - The ID of the chain where the transaction will be executed.\n * @param request.networkClientId - The ID of a specific network client to process the transaction.\n */\n async getLayer1GasFee({\n transactionParams,\n chainId,\n networkClientId,\n }: {\n transactionParams: TransactionParams;\n chainId?: Hex;\n networkClientId?: NetworkClientId;\n }): Promise {\n const provider = this.#multichainTrackingHelper.getProvider({\n networkClientId,\n chainId,\n });\n\n return await getTransactionLayer1GasFee({\n layer1GasFeeFlows: this.layer1GasFeeFlows,\n provider,\n transactionMeta: {\n txParams: transactionParams,\n chainId,\n } as TransactionMeta,\n });\n }\n\n private async signExternalTransaction(\n chainId: Hex,\n transactionParams: TransactionParams,\n ): Promise {\n if (!this.sign) {\n throw new Error('No sign method defined.');\n }\n\n const normalizedTransactionParams =\n normalizeTransactionParams(transactionParams);\n const type = isEIP1559Transaction(normalizedTransactionParams)\n ? TransactionEnvelopeType.feeMarket\n : TransactionEnvelopeType.legacy;\n const updatedTransactionParams = {\n ...normalizedTransactionParams,\n type,\n gasLimit: normalizedTransactionParams.gas,\n chainId,\n };\n\n const { from } = updatedTransactionParams;\n const common = this.getCommonConfiguration(chainId);\n const unsignedTransaction = TransactionFactory.fromTxData(\n updatedTransactionParams,\n { common },\n );\n const signedTransaction = await this.sign(unsignedTransaction, from);\n\n const rawTransaction = bufferToHex(signedTransaction.serialize());\n return rawTransaction;\n }\n\n /**\n * Removes unapproved transactions from state.\n */\n clearUnapprovedTransactions() {\n const transactions = this.state.transactions.filter(\n ({ status }) => status !== TransactionStatus.unapproved,\n );\n this.update((state) => {\n state.transactions = this.trimTransactionsForState(transactions);\n });\n }\n\n /**\n * Stop the signing process for a specific transaction.\n * Throws an error causing the transaction status to be set to failed.\n * @param transactionId - The ID of the transaction to stop signing.\n */\n abortTransactionSigning(transactionId: string) {\n const transactionMeta = this.getTransaction(transactionId);\n\n if (!transactionMeta) {\n throw new Error(`Cannot abort signing as no transaction metadata found`);\n }\n\n const abortCallback = this.signAbortCallbacks.get(transactionId);\n\n if (!abortCallback) {\n throw new Error(\n `Cannot abort signing as transaction is not waiting for signing`,\n );\n }\n\n abortCallback();\n\n this.signAbortCallbacks.delete(transactionId);\n }\n\n private addMetadata(transactionMeta: TransactionMeta) {\n this.update((state) => {\n state.transactions = this.trimTransactionsForState([\n ...state.transactions,\n transactionMeta,\n ]);\n });\n }\n\n private async updateGasProperties(transactionMeta: TransactionMeta) {\n const isEIP1559Compatible =\n (await this.getEIP1559Compatibility(transactionMeta.networkClientId)) &&\n transactionMeta.txParams.type !== TransactionEnvelopeType.legacy;\n\n const { networkClientId, chainId } = transactionMeta;\n\n const isCustomNetwork = this.#isCustomNetwork(networkClientId);\n\n const ethQuery = this.#multichainTrackingHelper.getEthQuery({\n networkClientId,\n chainId,\n });\n\n const provider = this.#multichainTrackingHelper.getProvider({\n networkClientId,\n chainId,\n });\n\n await updateGas({\n ethQuery,\n chainId,\n isCustomNetwork,\n txMeta: transactionMeta,\n });\n\n await updateGasFees({\n eip1559: isEIP1559Compatible,\n ethQuery,\n gasFeeFlows: this.gasFeeFlows,\n getGasFeeEstimates: this.getGasFeeEstimates,\n getSavedGasFees: this.getSavedGasFees.bind(this),\n txMeta: transactionMeta,\n });\n\n await updateTransactionLayer1GasFee({\n layer1GasFeeFlows: this.layer1GasFeeFlows,\n provider,\n transactionMeta,\n });\n }\n\n private onBootCleanup() {\n this.clearUnapprovedTransactions();\n this.failIncompleteTransactions();\n }\n\n private failIncompleteTransactions() {\n const incompleteTransactions = this.state.transactions.filter(\n (transaction) =>\n [TransactionStatus.approved, TransactionStatus.signed].includes(\n transaction.status,\n ),\n );\n\n for (const transactionMeta of incompleteTransactions) {\n this.failTransaction(\n transactionMeta,\n new Error('Transaction incomplete at startup'),\n );\n }\n }\n\n private async processApproval(\n transactionMeta: TransactionMeta,\n {\n isExisting = false,\n requireApproval,\n shouldShowRequest = true,\n actionId,\n }: {\n isExisting?: boolean;\n requireApproval?: boolean | undefined;\n shouldShowRequest?: boolean;\n actionId?: string;\n },\n ): Promise {\n const transactionId = transactionMeta.id;\n let resultCallbacks: AcceptResultCallbacks | undefined;\n const { meta, isCompleted } = this.isTransactionCompleted(transactionId);\n const finishedPromise = isCompleted\n ? Promise.resolve(meta)\n : this.waitForTransactionFinished(transactionId);\n\n if (meta && !isExisting && !isCompleted) {\n try {\n if (requireApproval !== false) {\n const acceptResult = await this.requestApproval(transactionMeta, {\n shouldShowRequest,\n });\n resultCallbacks = acceptResult.resultCallbacks;\n\n const approvalValue = acceptResult.value as\n | {\n txMeta?: TransactionMeta;\n }\n | undefined;\n\n const updatedTransaction = approvalValue?.txMeta;\n\n if (updatedTransaction) {\n log('Updating transaction with approval data', {\n customNonce: updatedTransaction.customNonceValue,\n params: updatedTransaction.txParams,\n });\n\n this.updateTransaction(\n updatedTransaction,\n 'TransactionController#processApproval - Updated with approval data',\n );\n }\n }\n\n const { isCompleted: isTxCompleted } =\n this.isTransactionCompleted(transactionId);\n\n if (!isTxCompleted) {\n const approvalResult = await this.approveTransaction(transactionId);\n if (\n approvalResult === ApprovalState.SkippedViaBeforePublishHook &&\n resultCallbacks\n ) {\n resultCallbacks.success();\n }\n const updatedTransactionMeta = this.getTransaction(\n transactionId,\n ) as TransactionMeta;\n this.messagingSystem.publish(\n `${controllerName}:transactionApproved`,\n {\n transactionMeta: updatedTransactionMeta,\n actionId,\n },\n );\n }\n // TODO: Replace `any` with type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (error: any) {\n const { isCompleted: isTxCompleted } =\n this.isTransactionCompleted(transactionId);\n if (!isTxCompleted) {\n if (error?.code === errorCodes.provider.userRejectedRequest) {\n this.cancelTransaction(transactionId, actionId);\n\n throw providerErrors.userRejectedRequest(\n 'MetaMask Tx Signature: User denied transaction signature.',\n );\n } else {\n this.failTransaction(meta, error, actionId);\n }\n }\n }\n }\n\n const finalMeta = await finishedPromise;\n\n switch (finalMeta?.status) {\n case TransactionStatus.failed:\n resultCallbacks?.error(finalMeta.error);\n throw rpcErrors.internal(finalMeta.error.message);\n\n case TransactionStatus.submitted:\n resultCallbacks?.success();\n return finalMeta.hash as string;\n\n default:\n const internalError = rpcErrors.internal(\n `MetaMask Tx Signature: Unknown problem: ${JSON.stringify(\n finalMeta || transactionId,\n )}`,\n );\n\n resultCallbacks?.error(internalError);\n throw internalError;\n }\n }\n\n /**\n * Approves a transaction and updates it's status in state. If this is not a\n * retry transaction, a nonce will be generated. The transaction is signed\n * using the sign configuration property, then published to the blockchain.\n * A `:finished` hub event is fired after success or failure.\n *\n * @param transactionId - The ID of the transaction to approve.\n */\n private async approveTransaction(transactionId: string) {\n const cleanupTasks = new Array<() => void>();\n cleanupTasks.push(await this.mutex.acquire());\n\n let transactionMeta = this.getTransactionOrThrow(transactionId);\n\n try {\n if (!this.sign) {\n this.failTransaction(\n transactionMeta,\n new Error('No sign method defined.'),\n );\n return ApprovalState.NotApproved;\n } else if (!transactionMeta.chainId) {\n this.failTransaction(transactionMeta, new Error('No chainId defined.'));\n return ApprovalState.NotApproved;\n }\n\n if (this.approvingTransactionIds.has(transactionId)) {\n log('Skipping approval as signing in progress', transactionId);\n return ApprovalState.NotApproved;\n }\n this.approvingTransactionIds.add(transactionId);\n cleanupTasks.push(() =>\n this.approvingTransactionIds.delete(transactionId),\n );\n\n const [nonce, releaseNonce] = await getNextNonce(\n transactionMeta,\n (address: string) =>\n this.#multichainTrackingHelper.getNonceLock(\n address,\n transactionMeta.networkClientId,\n ),\n );\n\n // must set transaction to submitted/failed before releasing lock\n releaseNonce && cleanupTasks.push(releaseNonce);\n\n transactionMeta = this.#updateTransactionInternal(\n {\n transactionId,\n note: 'TransactionController#approveTransaction - Transaction approved',\n },\n (draftTxMeta) => {\n const { txParams, chainId } = draftTxMeta;\n\n draftTxMeta.status = TransactionStatus.approved;\n draftTxMeta.txParams = {\n ...txParams,\n nonce,\n chainId,\n gasLimit: txParams.gas,\n ...(isEIP1559Transaction(txParams) && {\n type: TransactionEnvelopeType.feeMarket,\n }),\n };\n },\n );\n\n this.onTransactionStatusChange(transactionMeta);\n\n const rawTx = await this.signTransaction(\n transactionMeta,\n transactionMeta.txParams,\n );\n\n if (!this.beforePublish(transactionMeta)) {\n log('Skipping publishing transaction based on hook');\n this.messagingSystem.publish(\n `${controllerName}:transactionPublishingSkipped`,\n transactionMeta,\n );\n return ApprovalState.SkippedViaBeforePublishHook;\n }\n\n if (!rawTx) {\n return ApprovalState.NotApproved;\n }\n\n const ethQuery = this.#multichainTrackingHelper.getEthQuery({\n networkClientId: transactionMeta.networkClientId,\n chainId: transactionMeta.chainId,\n });\n\n let preTxBalance: string | undefined;\n const shouldUpdatePreTxBalance =\n transactionMeta.type === TransactionType.swap;\n\n if (shouldUpdatePreTxBalance) {\n log('Determining pre-transaction balance');\n\n preTxBalance = await query(ethQuery, 'getBalance', [\n transactionMeta.txParams.from,\n ]);\n }\n\n log('Publishing transaction', transactionMeta.txParams);\n\n let { transactionHash: hash } = await this.publish(\n transactionMeta,\n rawTx,\n );\n\n if (hash === undefined) {\n hash = await this.publishTransaction(ethQuery, rawTx);\n }\n\n log('Publish successful', hash);\n\n transactionMeta = this.#updateTransactionInternal(\n {\n transactionId,\n note: 'TransactionController#approveTransaction - Transaction submitted',\n },\n (draftTxMeta) => {\n draftTxMeta.hash = hash;\n draftTxMeta.status = TransactionStatus.submitted;\n draftTxMeta.submittedTime = new Date().getTime();\n if (shouldUpdatePreTxBalance) {\n draftTxMeta.preTxBalance = preTxBalance;\n log('Updated pre-transaction balance', preTxBalance);\n }\n },\n );\n\n this.messagingSystem.publish(`${controllerName}:transactionSubmitted`, {\n transactionMeta,\n });\n\n this.messagingSystem.publish(\n `${controllerName}:transactionFinished`,\n transactionMeta,\n );\n this.#internalEvents.emit(`${transactionId}:finished`, transactionMeta);\n\n this.onTransactionStatusChange(transactionMeta);\n return ApprovalState.Approved;\n // TODO: Replace `any` with type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (error: any) {\n this.failTransaction(transactionMeta, error);\n return ApprovalState.NotApproved;\n } finally {\n cleanupTasks.forEach((task) => task());\n }\n }\n\n private async publishTransaction(\n ethQuery: EthQuery,\n rawTransaction: string,\n ): Promise {\n return await query(ethQuery, 'sendRawTransaction', [rawTransaction]);\n }\n\n /**\n * Cancels a transaction based on its ID by setting its status to \"rejected\"\n * and emitting a `:finished` hub event.\n *\n * @param transactionId - The ID of the transaction to cancel.\n * @param actionId - The actionId passed from UI\n */\n private cancelTransaction(transactionId: string, actionId?: string) {\n const transactionMeta = this.state.transactions.find(\n ({ id }) => id === transactionId,\n );\n if (!transactionMeta) {\n return;\n }\n this.update((state) => {\n const transactions = state.transactions.filter(\n ({ id }) => id !== transactionId,\n );\n state.transactions = this.trimTransactionsForState(transactions);\n });\n const updatedTransactionMeta = {\n ...transactionMeta,\n status: TransactionStatus.rejected as const,\n };\n this.messagingSystem.publish(\n `${controllerName}:transactionFinished`,\n updatedTransactionMeta,\n );\n this.#internalEvents.emit(\n // TODO: Either fix this lint violation or explain why it's necessary to ignore.\n // eslint-disable-next-line @typescript-eslint/restrict-template-expressions\n `${transactionMeta.id}:finished`,\n updatedTransactionMeta,\n );\n this.messagingSystem.publish(`${controllerName}:transactionRejected`, {\n transactionMeta: updatedTransactionMeta,\n actionId,\n });\n this.onTransactionStatusChange(updatedTransactionMeta);\n }\n\n /**\n * Trim the amount of transactions that are set on the state. Checks\n * if the length of the tx history is longer then desired persistence\n * limit and then if it is removes the oldest confirmed or rejected tx.\n * Pending or unapproved transactions will not be removed by this\n * operation. For safety of presenting a fully functional transaction UI\n * representation, this function will not break apart transactions with the\n * same nonce, created on the same day, per network. Not accounting for\n * transactions of the same nonce, same day and network combo can result in\n * confusing or broken experiences in the UI.\n *\n * @param transactions - The transactions to be applied to the state.\n * @returns The trimmed list of transactions.\n */\n private trimTransactionsForState(\n transactions: TransactionMeta[],\n ): TransactionMeta[] {\n const nonceNetworkSet = new Set();\n\n const txsToKeep = [...transactions]\n .sort((a, b) => (a.time > b.time ? -1 : 1)) // Descending time order\n .filter((tx) => {\n const { chainId, status, txParams, time } = tx;\n\n if (txParams) {\n // TODO: Either fix this lint violation or explain why it's necessary to ignore.\n // eslint-disable-next-line @typescript-eslint/restrict-template-expressions\n const key = `${String(txParams.nonce)}-${convertHexToDecimal(\n chainId,\n )}-${new Date(time).toDateString()}`;\n\n if (nonceNetworkSet.has(key)) {\n return true;\n } else if (\n nonceNetworkSet.size < this.#transactionHistoryLimit ||\n !this.isFinalState(status)\n ) {\n nonceNetworkSet.add(key);\n return true;\n }\n }\n\n return false;\n });\n\n txsToKeep.reverse(); // Ascending time order\n return txsToKeep;\n }\n\n /**\n * Determines if the transaction is in a final state.\n *\n * @param status - The transaction status.\n * @returns Whether the transaction is in a final state.\n */\n private isFinalState(status: TransactionStatus): boolean {\n return (\n status === TransactionStatus.rejected ||\n status === TransactionStatus.confirmed ||\n status === TransactionStatus.failed\n );\n }\n\n /**\n * Whether the transaction has at least completed all local processing.\n *\n * @param status - The transaction status.\n * @returns Whether the transaction is in a final state.\n */\n private isLocalFinalState(status: TransactionStatus): boolean {\n return [\n TransactionStatus.confirmed,\n TransactionStatus.failed,\n TransactionStatus.rejected,\n TransactionStatus.submitted,\n ].includes(status);\n }\n\n private async requestApproval(\n txMeta: TransactionMeta,\n { shouldShowRequest }: { shouldShowRequest: boolean },\n ): Promise {\n const id = this.getApprovalId(txMeta);\n const { origin } = txMeta;\n const type = ApprovalType.Transaction;\n const requestData = { txId: txMeta.id };\n\n return (await this.messagingSystem.call(\n 'ApprovalController:addRequest',\n {\n id,\n origin: origin || ORIGIN_METAMASK,\n type,\n requestData,\n expectsResult: true,\n },\n shouldShowRequest,\n )) as Promise;\n }\n\n private getTransaction(\n transactionId: string,\n ): Readonly | undefined {\n const { transactions } = this.state;\n return transactions.find(({ id }) => id === transactionId);\n }\n\n private getTransactionOrThrow(\n transactionId: string,\n errorMessagePrefix = 'TransactionController',\n ): Readonly {\n const txMeta = this.getTransaction(transactionId);\n if (!txMeta) {\n throw new Error(\n `${errorMessagePrefix}: No transaction found with id ${transactionId}`,\n );\n }\n return txMeta;\n }\n\n private getApprovalId(txMeta: TransactionMeta) {\n return String(txMeta.id);\n }\n\n private isTransactionCompleted(transactionId: string): {\n meta?: TransactionMeta;\n isCompleted: boolean;\n } {\n const transaction = this.getTransaction(transactionId);\n\n if (!transaction) {\n return { meta: undefined, isCompleted: false };\n }\n\n const isCompleted = this.isLocalFinalState(transaction.status);\n\n return { meta: transaction, isCompleted };\n }\n\n private getChainId(networkClientId?: NetworkClientId): Hex {\n const globalChainId = this.#getGlobalChainId();\n const globalNetworkClientId = this.#getGlobalNetworkClientId();\n\n if (!networkClientId || networkClientId === globalNetworkClientId) {\n return globalChainId;\n }\n\n return this.messagingSystem.call(\n `NetworkController:getNetworkClientById`,\n networkClientId,\n ).configuration.chainId;\n }\n\n private prepareUnsignedEthTx(\n chainId: Hex,\n txParams: TransactionParams,\n ): TypedTransaction {\n return TransactionFactory.fromTxData(txParams, {\n freeze: false,\n common: this.getCommonConfiguration(chainId),\n });\n }\n\n /**\n * `@ethereumjs/tx` uses `@ethereumjs/common` as a configuration tool for\n * specifying which chain, network, hardfork and EIPs to support for\n * a transaction. By referencing this configuration, and analyzing the fields\n * specified in txParams, @ethereumjs/tx is able to determine which EIP-2718\n * transaction type to use.\n *\n * @param chainId - The chainId to use for the configuration.\n * @returns common configuration object\n */\n private getCommonConfiguration(chainId: Hex): Common {\n const customChainParams: Partial = {\n chainId: parseInt(chainId, 16),\n defaultHardfork: HARDFORK,\n };\n\n return Common.custom(customChainParams);\n }\n\n private onIncomingTransactions({\n added,\n updated,\n }: {\n added: TransactionMeta[];\n updated: TransactionMeta[];\n }) {\n this.update((state) => {\n const { transactions: currentTransactions } = state;\n const updatedTransactions = [\n ...added,\n ...currentTransactions.map((originalTransaction) => {\n const updatedTransaction = updated.find(\n ({ hash }) => hash === originalTransaction.hash,\n );\n\n return updatedTransaction ?? originalTransaction;\n }),\n ];\n\n state.transactions = this.trimTransactionsForState(updatedTransactions);\n });\n }\n\n private onUpdatedLastFetchedBlockNumbers({\n lastFetchedBlockNumbers,\n blockNumber,\n }: {\n lastFetchedBlockNumbers: {\n [key: string]: number;\n };\n blockNumber: number;\n }) {\n this.update((state) => {\n state.lastFetchedBlockNumbers = lastFetchedBlockNumbers;\n });\n this.messagingSystem.publish(\n `${controllerName}:incomingTransactionBlockReceived`,\n blockNumber,\n );\n }\n\n private generateDappSuggestedGasFees(\n txParams: TransactionParams,\n origin?: string,\n ): DappSuggestedGasFees | undefined {\n if (!origin || origin === ORIGIN_METAMASK) {\n return undefined;\n }\n\n const { gasPrice, maxFeePerGas, maxPriorityFeePerGas, gas } = txParams;\n\n if (\n gasPrice === undefined &&\n maxFeePerGas === undefined &&\n maxPriorityFeePerGas === undefined &&\n gas === undefined\n ) {\n return undefined;\n }\n\n const dappSuggestedGasFees: DappSuggestedGasFees = {};\n\n if (gasPrice !== undefined) {\n dappSuggestedGasFees.gasPrice = gasPrice;\n } else if (\n maxFeePerGas !== undefined ||\n maxPriorityFeePerGas !== undefined\n ) {\n dappSuggestedGasFees.maxFeePerGas = maxFeePerGas;\n dappSuggestedGasFees.maxPriorityFeePerGas = maxPriorityFeePerGas;\n }\n\n if (gas !== undefined) {\n dappSuggestedGasFees.gas = gas;\n }\n\n return dappSuggestedGasFees;\n }\n\n /**\n * Validates and adds external provided transaction to state.\n *\n * @param transactionMeta - Nominated external transaction to be added to state.\n * @returns The new transaction.\n */\n private addExternalTransaction(transactionMeta: TransactionMeta) {\n const { chainId } = transactionMeta;\n const { transactions } = this.state;\n const fromAddress = transactionMeta?.txParams?.from;\n const sameFromAndNetworkTransactions = transactions.filter(\n (transaction) =>\n transaction.txParams.from === fromAddress &&\n transaction.chainId === chainId,\n );\n const confirmedTxs = sameFromAndNetworkTransactions.filter(\n (transaction) => transaction.status === TransactionStatus.confirmed,\n );\n const pendingTxs = sameFromAndNetworkTransactions.filter(\n (transaction) => transaction.status === TransactionStatus.submitted,\n );\n\n validateConfirmedExternalTransaction(\n transactionMeta,\n confirmedTxs,\n pendingTxs,\n );\n\n // Make sure provided external transaction has non empty history array\n const newTransactionMeta =\n (transactionMeta.history ?? []).length === 0 && !this.isHistoryDisabled\n ? addInitialHistorySnapshot(transactionMeta)\n : transactionMeta;\n\n this.update((state) => {\n state.transactions = this.trimTransactionsForState([\n ...state.transactions,\n newTransactionMeta,\n ]);\n });\n\n return newTransactionMeta;\n }\n\n /**\n * Sets other txMeta statuses to dropped if the txMeta that has been confirmed has other transactions\n * in the transactions have the same nonce.\n *\n * @param transactionId - Used to identify original transaction.\n */\n private markNonceDuplicatesDropped(transactionId: string) {\n const transactionMeta = this.getTransaction(transactionId);\n if (!transactionMeta) {\n return;\n }\n const nonce = transactionMeta.txParams?.nonce;\n const from = transactionMeta.txParams?.from;\n const { chainId } = transactionMeta;\n\n const sameNonceTransactions = this.state.transactions.filter(\n (transaction) =>\n transaction.id !== transactionId &&\n transaction.txParams.from === from &&\n transaction.txParams.nonce === nonce &&\n transaction.chainId === chainId &&\n transaction.type !== TransactionType.incoming,\n );\n const sameNonceTransactionIds = sameNonceTransactions.map(\n (transaction) => transaction.id,\n );\n\n if (sameNonceTransactions.length === 0) {\n return;\n }\n\n this.update((state) => {\n for (const transaction of state.transactions) {\n if (sameNonceTransactionIds.includes(transaction.id)) {\n transaction.replacedBy = transactionMeta?.hash;\n transaction.replacedById = transactionMeta?.id;\n }\n }\n });\n\n for (const transaction of this.state.transactions) {\n if (\n sameNonceTransactionIds.includes(transaction.id) &&\n transaction.status !== TransactionStatus.failed\n ) {\n this.setTransactionStatusDropped(transaction);\n }\n }\n }\n\n /**\n * Method to set transaction status to dropped.\n *\n * @param transactionMeta - TransactionMeta of transaction to be marked as dropped.\n */\n private setTransactionStatusDropped(transactionMeta: TransactionMeta) {\n const updatedTransactionMeta = {\n ...transactionMeta,\n status: TransactionStatus.dropped as const,\n };\n this.messagingSystem.publish(`${controllerName}:transactionDropped`, {\n transactionMeta: updatedTransactionMeta,\n });\n this.updateTransaction(\n updatedTransactionMeta,\n 'TransactionController#setTransactionStatusDropped - Transaction dropped',\n );\n this.onTransactionStatusChange(updatedTransactionMeta);\n }\n\n /**\n * Get transaction with provided actionId.\n *\n * @param actionId - Unique ID to prevent duplicate requests\n * @returns the filtered transaction\n */\n private getTransactionWithActionId(actionId?: string) {\n return this.state.transactions.find(\n (transaction) => actionId && transaction.actionId === actionId,\n );\n }\n\n private async waitForTransactionFinished(\n transactionId: string,\n ): Promise {\n return new Promise((resolve) => {\n this.#internalEvents.once(`${transactionId}:finished`, (txMeta) => {\n resolve(txMeta);\n });\n });\n }\n\n /**\n * Updates the r, s, and v properties of a TransactionMeta object\n * with values from a signed transaction.\n *\n * @param transactionMeta - The TransactionMeta object to update.\n * @param signedTx - The encompassing type for all transaction types containing r, s, and v values.\n * @returns The updated TransactionMeta object.\n */\n private updateTransactionMetaRSV(\n transactionMeta: TransactionMeta,\n signedTx: TypedTransaction,\n ): TransactionMeta {\n const transactionMetaWithRsv = cloneDeep(transactionMeta);\n\n for (const key of ['r', 's', 'v'] as const) {\n const value = signedTx[key];\n\n if (value === undefined || value === null) {\n continue;\n }\n\n transactionMetaWithRsv[key] = add0x(value.toString(16));\n }\n\n return transactionMetaWithRsv;\n }\n\n private async getEIP1559Compatibility(networkClientId?: NetworkClientId) {\n const currentNetworkIsEIP1559Compatible =\n await this.getCurrentNetworkEIP1559Compatibility(networkClientId);\n\n const currentAccountIsEIP1559Compatible =\n await this.getCurrentAccountEIP1559Compatibility();\n\n return (\n currentNetworkIsEIP1559Compatible && currentAccountIsEIP1559Compatible\n );\n }\n\n private async signTransaction(\n transactionMeta: TransactionMeta,\n txParams: TransactionParams,\n ): Promise {\n log('Signing transaction', txParams);\n\n const unsignedEthTx = this.prepareUnsignedEthTx(\n transactionMeta.chainId,\n txParams,\n );\n\n this.approvingTransactionIds.add(transactionMeta.id);\n\n const signedTx = await new Promise((resolve, reject) => {\n this.sign?.(\n unsignedEthTx,\n txParams.from,\n ...this.getAdditionalSignArguments(transactionMeta),\n ).then(resolve, reject);\n\n this.signAbortCallbacks.set(transactionMeta.id, () =>\n reject(new Error('Signing aborted by user')),\n );\n });\n\n this.signAbortCallbacks.delete(transactionMeta.id);\n\n if (!signedTx) {\n log('Skipping signed status as no signed transaction');\n return undefined;\n }\n\n const transactionMetaFromHook = cloneDeep(transactionMeta);\n if (!this.afterSign(transactionMetaFromHook, signedTx)) {\n this.updateTransaction(\n transactionMetaFromHook,\n 'TransactionController#signTransaction - Update after sign',\n );\n\n log('Skipping signed status based on hook');\n\n return undefined;\n }\n\n const transactionMetaWithRsv = {\n ...this.updateTransactionMetaRSV(transactionMetaFromHook, signedTx),\n status: TransactionStatus.signed as const,\n };\n\n this.updateTransaction(\n transactionMetaWithRsv,\n 'TransactionController#approveTransaction - Transaction signed',\n );\n\n this.onTransactionStatusChange(transactionMetaWithRsv);\n\n const rawTx = bufferToHex(signedTx.serialize());\n\n const transactionMetaWithRawTx = merge({}, transactionMetaWithRsv, {\n rawTx,\n });\n\n this.updateTransaction(\n transactionMetaWithRawTx,\n 'TransactionController#approveTransaction - RawTransaction added',\n );\n\n return rawTx;\n }\n\n private onTransactionStatusChange(transactionMeta: TransactionMeta) {\n this.messagingSystem.publish(`${controllerName}:transactionStatusUpdated`, {\n transactionMeta,\n });\n }\n\n private getNonceTrackerTransactions(\n status: TransactionStatus,\n address: string,\n chainId: string = this.getChainId(),\n ) {\n return getAndFormatTransactionsForNonceTracker(\n chainId,\n address,\n status,\n this.state.transactions,\n );\n }\n\n private onConfirmedTransaction(transactionMeta: TransactionMeta) {\n log('Processing confirmed transaction', transactionMeta.id);\n\n this.markNonceDuplicatesDropped(transactionMeta.id);\n\n this.messagingSystem.publish(\n `${controllerName}:transactionConfirmed`,\n transactionMeta,\n );\n\n this.onTransactionStatusChange(transactionMeta);\n\n // Intentional given potential duration of process.\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.updatePostBalance(transactionMeta);\n }\n\n private async updatePostBalance(transactionMeta: TransactionMeta) {\n try {\n if (transactionMeta.type !== TransactionType.swap) {\n return;\n }\n\n const ethQuery = this.#multichainTrackingHelper.getEthQuery({\n networkClientId: transactionMeta.networkClientId,\n chainId: transactionMeta.chainId,\n });\n const { updatedTransactionMeta, approvalTransactionMeta } =\n await updatePostTransactionBalance(transactionMeta, {\n ethQuery,\n getTransaction: this.getTransaction.bind(this),\n updateTransaction: this.updateTransaction.bind(this),\n });\n\n this.messagingSystem.publish(\n `${controllerName}:postTransactionBalanceUpdated`,\n {\n transactionMeta: updatedTransactionMeta,\n approvalTransactionMeta,\n },\n );\n } catch (error) {\n /* istanbul ignore next */\n log('Error while updating post transaction balance', error);\n }\n }\n\n #createNonceTracker({\n provider,\n blockTracker,\n chainId,\n }: {\n provider: Provider;\n blockTracker: BlockTracker;\n chainId?: Hex;\n }): NonceTracker {\n return new NonceTracker({\n // TODO: Fix types\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n provider: provider as any,\n // @ts-expect-error TODO: Fix types\n blockTracker,\n getPendingTransactions: this.#getNonceTrackerPendingTransactions.bind(\n this,\n chainId,\n ),\n getConfirmedTransactions: this.getNonceTrackerTransactions.bind(\n this,\n TransactionStatus.confirmed,\n ),\n });\n }\n\n #createIncomingTransactionHelper({\n blockTracker,\n etherscanRemoteTransactionSource,\n chainId,\n }: {\n blockTracker: BlockTracker;\n etherscanRemoteTransactionSource: EtherscanRemoteTransactionSource;\n chainId?: Hex;\n }): IncomingTransactionHelper {\n const incomingTransactionHelper = new IncomingTransactionHelper({\n blockTracker,\n getCurrentAccount: () => this.#getSelectedAccount(),\n getLastFetchedBlockNumbers: () => this.state.lastFetchedBlockNumbers,\n getChainId: chainId ? () => chainId : this.getChainId.bind(this),\n isEnabled: this.#incomingTransactionOptions.isEnabled,\n queryEntireHistory: this.#incomingTransactionOptions.queryEntireHistory,\n remoteTransactionSource: etherscanRemoteTransactionSource,\n transactionLimit: this.#transactionHistoryLimit,\n updateTransactions: this.#incomingTransactionOptions.updateTransactions,\n });\n\n this.#addIncomingTransactionHelperListeners(incomingTransactionHelper);\n\n return incomingTransactionHelper;\n }\n\n #createPendingTransactionTracker({\n provider,\n blockTracker,\n chainId,\n }: {\n provider: Provider;\n blockTracker: BlockTracker;\n chainId?: Hex;\n }): PendingTransactionTracker {\n const ethQuery = new EthQuery(provider);\n const getChainId = chainId ? () => chainId : this.getChainId.bind(this);\n\n const pendingTransactionTracker = new PendingTransactionTracker({\n blockTracker,\n getChainId,\n getEthQuery: () => ethQuery,\n getTransactions: () => this.state.transactions,\n isResubmitEnabled: this.#pendingTransactionOptions.isResubmitEnabled,\n getGlobalLock: () =>\n this.#multichainTrackingHelper.acquireNonceLockForChainIdKey({\n chainId: getChainId(),\n }),\n publishTransaction: this.publishTransaction.bind(this),\n hooks: {\n beforeCheckPendingTransaction:\n this.beforeCheckPendingTransaction.bind(this),\n beforePublish: this.beforePublish.bind(this),\n },\n });\n\n this.#addPendingTransactionTrackerListeners(pendingTransactionTracker);\n\n return pendingTransactionTracker;\n }\n\n #checkForPendingTransactionAndStartPolling = () => {\n // PendingTransactionTracker reads state through its getTransactions hook\n this.pendingTransactionTracker.startIfPendingTransactions();\n this.#multichainTrackingHelper.checkForPendingTransactionAndStartPolling();\n };\n\n #stopAllTracking() {\n this.pendingTransactionTracker.stop();\n this.#removePendingTransactionTrackerListeners(\n this.pendingTransactionTracker,\n );\n this.incomingTransactionHelper.stop();\n this.#removeIncomingTransactionHelperListeners(\n this.incomingTransactionHelper,\n );\n\n this.#multichainTrackingHelper.stopAllTracking();\n }\n\n #removeIncomingTransactionHelperListeners(\n incomingTransactionHelper: IncomingTransactionHelper,\n ) {\n incomingTransactionHelper.hub.removeAllListeners('transactions');\n incomingTransactionHelper.hub.removeAllListeners(\n 'updatedLastFetchedBlockNumbers',\n );\n }\n\n #addIncomingTransactionHelperListeners(\n incomingTransactionHelper: IncomingTransactionHelper,\n ) {\n incomingTransactionHelper.hub.on(\n 'transactions',\n this.onIncomingTransactions.bind(this),\n );\n incomingTransactionHelper.hub.on(\n 'updatedLastFetchedBlockNumbers',\n this.onUpdatedLastFetchedBlockNumbers.bind(this),\n );\n }\n\n #removePendingTransactionTrackerListeners(\n pendingTransactionTracker: PendingTransactionTracker,\n ) {\n pendingTransactionTracker.hub.removeAllListeners('transaction-confirmed');\n pendingTransactionTracker.hub.removeAllListeners('transaction-dropped');\n pendingTransactionTracker.hub.removeAllListeners('transaction-failed');\n pendingTransactionTracker.hub.removeAllListeners('transaction-updated');\n }\n\n #addPendingTransactionTrackerListeners(\n pendingTransactionTracker: PendingTransactionTracker,\n ) {\n pendingTransactionTracker.hub.on(\n 'transaction-confirmed',\n this.onConfirmedTransaction.bind(this),\n );\n\n pendingTransactionTracker.hub.on(\n 'transaction-dropped',\n this.setTransactionStatusDropped.bind(this),\n );\n\n pendingTransactionTracker.hub.on(\n 'transaction-failed',\n this.failTransaction.bind(this),\n );\n\n pendingTransactionTracker.hub.on(\n 'transaction-updated',\n this.updateTransaction.bind(this),\n );\n }\n\n #getNonceTrackerPendingTransactions(\n chainId: string | undefined,\n address: string,\n ) {\n const standardPendingTransactions = this.getNonceTrackerTransactions(\n TransactionStatus.submitted,\n address,\n chainId,\n );\n\n const externalPendingTransactions = this.getExternalPendingTransactions(\n address,\n chainId,\n );\n return [...standardPendingTransactions, ...externalPendingTransactions];\n }\n\n private async publishTransactionForRetry(\n ethQuery: EthQuery,\n rawTx: string,\n transactionMeta: TransactionMeta,\n ): Promise {\n try {\n const hash = await this.publishTransaction(ethQuery, rawTx);\n return hash;\n } catch (error: unknown) {\n if (this.isTransactionAlreadyConfirmedError(error as Error)) {\n await this.pendingTransactionTracker.forceCheckTransaction(\n transactionMeta,\n );\n throw new Error('Previous transaction is already confirmed');\n }\n throw error;\n }\n }\n\n /**\n * Ensures that error is a nonce issue\n *\n * @param error - The error to check\n * @returns Whether or not the error is a nonce issue\n */\n // TODO: Replace `any` with type\n // Some networks are returning original error in the data field\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private isTransactionAlreadyConfirmedError(error: any): boolean {\n return (\n error?.message?.includes('nonce too low') ||\n error?.data?.message?.includes('nonce too low')\n );\n }\n\n #getGasFeeFlows(): GasFeeFlow[] {\n if (this.#testGasFeeFlows) {\n return [new TestGasFeeFlow()];\n }\n\n return [new LineaGasFeeFlow(), new DefaultGasFeeFlow()];\n }\n\n #getLayer1GasFeeFlows(): Layer1GasFeeFlow[] {\n return [new OptimismLayer1GasFeeFlow(), new ScrollLayer1GasFeeFlow()];\n }\n\n #updateTransactionInternal(\n {\n transactionId,\n note,\n skipHistory,\n skipValidation,\n }: {\n transactionId: string;\n note?: string;\n skipHistory?: boolean;\n skipValidation?: boolean;\n },\n callback: (transactionMeta: TransactionMeta) => TransactionMeta | void,\n ): Readonly {\n let updatedTransactionParams: (keyof TransactionParams)[] = [];\n\n this.update((state) => {\n const index = state.transactions.findIndex(\n ({ id }) => id === transactionId,\n );\n\n let transactionMeta = state.transactions[index];\n\n // eslint-disable-next-line n/callback-return\n transactionMeta = callback(transactionMeta) ?? transactionMeta;\n\n if (skipValidation !== true) {\n transactionMeta.txParams = normalizeTransactionParams(\n transactionMeta.txParams,\n );\n\n validateTxParams(transactionMeta.txParams);\n }\n\n updatedTransactionParams =\n this.#checkIfTransactionParamsUpdated(transactionMeta);\n\n const shouldSkipHistory = this.isHistoryDisabled || skipHistory;\n\n if (!shouldSkipHistory) {\n transactionMeta = updateTransactionHistory(\n transactionMeta,\n note ?? 'Transaction updated',\n );\n }\n state.transactions[index] = transactionMeta;\n });\n\n const transactionMeta = this.getTransaction(\n transactionId,\n ) as TransactionMeta;\n\n if (updatedTransactionParams.length > 0) {\n this.#onTransactionParamsUpdated(\n transactionMeta,\n updatedTransactionParams,\n );\n }\n\n return transactionMeta;\n }\n\n #checkIfTransactionParamsUpdated(newTransactionMeta: TransactionMeta) {\n const { id: transactionId, txParams: newParams } = newTransactionMeta;\n\n const originalParams = this.getTransaction(transactionId)?.txParams;\n\n if (!originalParams || isEqual(originalParams, newParams)) {\n return [];\n }\n\n const params = Object.keys(newParams) as (keyof TransactionParams)[];\n\n const updatedProperties = params.filter(\n (param) => newParams[param] !== originalParams[param],\n );\n\n log(\n 'Transaction parameters have been updated',\n transactionId,\n updatedProperties,\n originalParams,\n newParams,\n );\n\n return updatedProperties;\n }\n\n #onTransactionParamsUpdated(\n transactionMeta: TransactionMeta,\n updatedParams: (keyof TransactionParams)[],\n ) {\n if (\n (['to', 'value', 'data'] as const).some((param) =>\n updatedParams.includes(param),\n )\n ) {\n log('Updating simulation data due to transaction parameter update');\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.#updateSimulationData(transactionMeta);\n }\n }\n\n async #updateSimulationData(transactionMeta: TransactionMeta) {\n const { id: transactionId, chainId, txParams } = transactionMeta;\n const { from, to, value, data } = txParams;\n\n let simulationData: SimulationData = {\n error: {\n code: SimulationErrorCode.Disabled,\n message: 'Simulation disabled',\n },\n tokenBalanceChanges: [],\n };\n\n if (this.#isSimulationEnabled()) {\n this.#updateTransactionInternal(\n { transactionId, skipHistory: true },\n (txMeta) => {\n txMeta.simulationData = undefined;\n },\n );\n\n simulationData = await getSimulationData({\n chainId,\n from: from as Hex,\n to: to as Hex,\n value: value as Hex,\n data: data as Hex,\n });\n }\n\n const finalTransactionMeta = this.getTransaction(transactionId);\n\n /* istanbul ignore if */\n if (!finalTransactionMeta) {\n log(\n 'Cannot update simulation data as transaction not found',\n transactionId,\n simulationData,\n );\n\n return;\n }\n\n this.#updateTransactionInternal(\n {\n transactionId,\n note: 'TransactionController#updateSimulationData - Update simulation data',\n },\n (txMeta) => {\n txMeta.simulationData = simulationData;\n },\n );\n\n log('Updated simulation data', transactionId, simulationData);\n }\n\n #onGasFeePollerTransactionUpdate({\n transactionId,\n gasFeeEstimates,\n gasFeeEstimatesLoaded,\n layer1GasFee,\n }: {\n transactionId: string;\n gasFeeEstimates?: GasFeeEstimates;\n gasFeeEstimatesLoaded?: boolean;\n layer1GasFee?: Hex;\n }) {\n this.#updateTransactionInternal(\n { transactionId, skipHistory: true },\n (txMeta) => {\n if (gasFeeEstimates) {\n txMeta.gasFeeEstimates = gasFeeEstimates;\n }\n\n if (gasFeeEstimatesLoaded !== undefined) {\n txMeta.gasFeeEstimatesLoaded = gasFeeEstimatesLoaded;\n }\n\n if (layer1GasFee) {\n txMeta.layer1GasFee = layer1GasFee;\n }\n },\n );\n }\n\n #getNetworkClientId({\n networkClientId: requestNetworkClientId,\n chainId,\n }: {\n networkClientId?: NetworkClientId;\n chainId?: Hex;\n }) {\n const globalChainId = this.#getGlobalChainId();\n const globalNetworkClientId = this.#getGlobalNetworkClientId();\n\n if (requestNetworkClientId) {\n return requestNetworkClientId;\n }\n\n if (!chainId || chainId === globalChainId) {\n return globalNetworkClientId;\n }\n\n return this.messagingSystem.call(\n `NetworkController:findNetworkClientIdByChainId`,\n chainId,\n );\n }\n\n #getGlobalNetworkClientId() {\n return this.getNetworkState().selectedNetworkClientId;\n }\n\n #getGlobalChainId() {\n return this.messagingSystem.call(\n `NetworkController:getNetworkClientById`,\n this.getNetworkState().selectedNetworkClientId,\n ).configuration.chainId;\n }\n\n #isCustomNetwork(networkClientId?: NetworkClientId) {\n const globalNetworkClientId = this.#getGlobalNetworkClientId();\n\n if (!networkClientId || networkClientId === globalNetworkClientId) {\n return !isInfuraNetworkType(\n this.getNetworkState().selectedNetworkClientId,\n );\n }\n\n return (\n this.messagingSystem.call(\n `NetworkController:getNetworkClientById`,\n networkClientId,\n ).configuration.type === NetworkClientType.Custom\n );\n }\n\n #getSelectedAccount() {\n return this.messagingSystem.call('AccountsController:getSelectedAccount');\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,UAAU,cAAgC;AAEnD,SAAS,0BAA0B;AACnC,SAAS,mBAAmB;AAY5B,SAAS,sBAAsB;AAC/B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,OAAO,cAAc;AAerB,SAAS,yBAAyB;AAKlC,SAAS,oBAAoB;AAC7B,SAAS,YAAY,WAAW,sBAAsB;AAEtD,SAAS,aAAa;AACtB,SAAS,aAAa;AACtB,SAAS,sBAAsB;AAC/B,SAAS,oBAAoB;AAC7B,SAAS,WAAW,WAAW,OAAO,QAAQ,QAAQ,eAAe;AACrE,SAAS,MAAM,cAAc;AA+E7B,IAAM,WAAW;AAAA,EACf,cAAc;AAAA,IACZ,SAAS;AAAA,IACT,WAAW;AAAA,EACb;AAAA,EACA,YAAY;AAAA,IACV,SAAS;AAAA,IACT,WAAW;AAAA,EACb;AAAA,EACA,yBAAyB;AAAA,IACvB,SAAS;AAAA,IACT,WAAW;AAAA,EACb;AACF;AAEO,IAAM,WAAW,SAAS;AAyE1B,IAAM,cAAc;AAKpB,IAAM,gBAAgB;AAiH7B,IAAM,iBAAiB;AA0NhB,IAAK,gBAAL,kBAAKA,mBAAL;AACL,EAAAA,eAAA,cAAW;AACX,EAAAA,eAAA,iBAAc;AACd,EAAAA,eAAA,iCAA8B;AAHpB,SAAAA;AAAA,GAAA;AAWZ,SAAS,uCAAmE;AAC1E,SAAO;AAAA,IACL,YAAY,CAAC;AAAA,IACb,cAAc,CAAC;AAAA,IACf,yBAAyB,CAAC;AAAA,EAC5B;AACF;AA3jBA;AAgkBO,IAAM,wBAAN,cAAoC,eAIzC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA0LA,YAAY;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,uBAAuB,CAAC;AAAA,IACxB,sBAAsB;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,IACA,sBAAsB,CAAC;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,0BAA0B;AAAA,IAC1B;AAAA,EACF,GAAiC;AAC/B,UAAM;AAAA,MACJ,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA,OAAO;AAAA,QACL,GAAG,qCAAqC;AAAA,QACxC,GAAG;AAAA,MACL;AAAA,IACF,CAAC;AA0hFH;AA0BA;AA0BA;AAyCA;AAaA;AASA;AAaA;AASA;AAwBA;AAoDA;AAQA;AAIA;AA8DA;AA0BA;AAeA,uBAAM;AAuDN;AA6BA;AAwBA;AAIA;AAOA;AAiBA;AAtsGA,wCAAkB,IAAI,aAAa;AAQnC,SAAiB,0BAAuC,oBAAI,IAAI;AAMhE,SAAiB,QAAQ,IAAI,MAAM;AA2BnC,uBAAS,6BAAT;AAMA,uBAAS,4BAAT;AAIA,SAAiB,qBAA8C,oBAAI,IAAI;AAEvE;AAEA;AAEA;AAuFA;AA6rFA,mEAA6C,MAAM;AAEjD,WAAK,0BAA0B,2BAA2B;AAC1D,yBAAK,2BAA0B,0CAA0C;AAAA,IAC3E;AAnnFE,SAAK,kBAAkB;AACvB,SAAK,kBAAkB;AACvB,SAAK,4BAA4B,0BAA0B;AAC3D,SAAK,oBAAoB,kBAAkB;AAC3C,SAAK,kBAAkB,gBAAgB;AACvC,uBAAK,sBAAuB,wBAAwB,MAAM;AAE1D,SAAK,WAAW,IAAI,eAAe,EAAE,SAAS,CAAC;AAC/C,SAAK,kBAAkB,oBAAoB,CAAC,aAAa;AACzD,SAAK,wCACH,0CAA0C,MAAM,QAAQ,QAAQ,IAAI;AACtE,SAAK,wCACH;AACF,SAAK,qBACH,uBAAuB,MAAM,QAAQ,QAAQ,CAAC,CAAgB;AAChE,SAAK,uBAAuB;AAC5B,SAAK,iCACH,mCAAmC,MAAM,CAAC;AAC5C,SAAK,0BAA0B;AAC/B,uBAAK,6BAA8B;AACnC,uBAAK,4BAA6B;AAClC,uBAAK,0BAA2B;AAChC,SAAK,OAAO;AACZ,uBAAK,kBAAmB,oBAAoB;AAE5C,SAAK,YAAY,OAAO,cAAc,MAAM;AAC5C,SAAK,gCACH,OAAO;AAAA,KAEN,MAAM;AACT,SAAK,gBAAgB,OAAO,kBAAkB,MAAM;AACpD,SAAK,6BACH,OAAO,+BAA+B,MAAM,CAAC;AAC/C,SAAK,UACH,OAAO,YAAY,MAAM,QAAQ,QAAQ,EAAE,iBAAiB,OAAU,CAAC;AAEzE,SAAK,eAAe,sBAAK,4CAAL,WAAyB;AAAA,MAC3C;AAAA,MACA;AAAA,IACF;AAEA,UAAM,+BAA+B,CAAC,YAAiB;AACrD,aAAO,KAAK,gBAAgB;AAAA,QAC1B;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,uBAAK,2BAA4B,IAAI,yBAAyB;AAAA,MAC5D;AAAA,MACA;AAAA,MACA,cAAc,KAAK;AAAA,MACnB,4BAA4B;AAAA,MAC5B;AAAA,MACA,sBAAuB,CAAC,oBAAqC;AAC3D,eAAO,KAAK,gBAAgB;AAAA,UAC1B;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,MACA;AAAA,MACA,0CACE,sBAAK,wFAA0C,KAAK,IAAI;AAAA,MAC1D,0CACE,sBAAK,wFAA0C,KAAK,IAAI;AAAA,MAC1D,oBAAoB,sBAAK,4CAAoB,KAAK,IAAI;AAAA,MACtD,iCACE,sBAAK,sEAAiC,KAAK,IAAI;AAAA,MACjD,iCACE,sBAAK,sEAAiC,KAAK,IAAI;AAAA,MACjD,sBAAsB,CAAC,aAAa;AAClC,aAAK,gBAAgB;AAAA,UACnB;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AACD,uBAAK,2BAA0B,WAAW;AAE1C,UAAM,mCACJ,IAAI,iCAAiC;AAAA,MACnC,uBAAuB,qBAAqB;AAAA,IAC9C,CAAC;AAEH,SAAK,4BAA4B,sBAAK,sEAAL,WAAsC;AAAA,MACrE;AAAA,MACA;AAAA,IACF;AAEA,SAAK,4BAA4B,sBAAK,sEAAL,WAAsC;AAAA,MACrE;AAAA,MACA;AAAA,IACF;AAEA,SAAK,cAAc,sBAAK,oCAAL;AACnB,SAAK,oBAAoB,sBAAK,gDAAL;AAEzB,UAAM,eAAe,IAAI,aAAa;AAAA,MACpC;AAAA,MACA,aAAa,KAAK;AAAA,MAClB,8BAA8B,KAAK;AAAA,MACnC,aAAa,CAAC,SAAS,oBACrB,mBAAK,2BAA0B,YAAY;AAAA,QACzC;AAAA,QACA;AAAA,MACF,CAAC;AAAA,MACH,iBAAiB,MAAM,KAAK,MAAM;AAAA,MAClC,mBAAmB,KAAK;AAAA,MACxB,eAAe,CAAC,aAAa;AAC3B,aAAK,gBAAgB;AAAA,UACnB;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAED,iBAAa,IAAI;AAAA,MACf;AAAA,MACA,sBAAK,sEAAiC,KAAK,IAAI;AAAA,IACjD;AAIA,SAAK,gBAAgB;AAAA,MACnB;AAAA,MACA,mBAAK;AAAA,IACP;AAIA,yBAAqB,MAAM;AACzB,oBAAI,2BAA2B,KAAK,WAAW,CAAC;AAChD,WAAK,0BAA0B,2BAA2B;AAC1D,WAAK,cAAc;AAAA,IACrB,CAAC;AAED,SAAK,cAAc;AACnB,uBAAK,4CAAL;AAAA,EACF;AAAA,EAzRQ,gBACN,iBACA,OACA,UACA;AACA,QAAI;AAEJ,QAAI;AACF,2BAAqB,sBAAK,0DAAL,WACnB;AAAA,QACE,eAAe,gBAAgB;AAAA,QAC/B,MAAM;AAAA,QACN,gBAAgB;AAAA,MAClB,GACA,CAAC,yBAAyB;AACxB,6BAAqB;AAErB,QACE,qBAGA,QAAQ,iBAAiB,KAAK;AAAA,MAClC;AAAA,IAEJ,SAAS,KAAc;AACrB,oBAAI,wCAAwC,GAAG;AAE/C,2BAAqB;AAAA,QACnB,GAAG;AAAA,QACH;AAAA,QACA,OAAO,iBAAiB,KAAK;AAAA,MAC/B;AAAA,IACF;AAEA,SAAK,gBAAgB,QAAQ,GAAG,cAAc,sBAAsB;AAAA,MAClE;AAAA,MACA,OAAO,MAAM;AAAA,MACb,iBAAiB;AAAA,IACnB,CAAC;AAED,SAAK,0BAA0B,kBAAkB;AAEjD,SAAK,gBAAgB;AAAA,MACnB,GAAG,cAAc;AAAA,MACjB;AAAA,IACF;AAEA,uBAAK,iBAAgB;AAAA,MACnB,GAAG,gBAAgB,EAAE;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,eAAe,gBAA6C;AACxE,UAAM,iBAAiB,MAAM,KAAK,SAAS,OAAO,cAAc;AAChE,QAAI,CAAC,gBAAgB;AACnB,aAAO;AAAA,QACL,gBAAgB;AAAA,QAChB,sBAAsB,EAAE,MAAM,QAAW,MAAM,OAAU;AAAA,MAC3D;AAAA,IACF;AACA,UAAM,uBAAuB,KAAK,SAAS,MAAM,cAAc;AAC/D,WAAO,EAAE,gBAAgB,qBAAqB;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA,EA+NA,UAAU;AACR,0BAAK,sCAAL;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,iBAAiB,gBAA6C;AAClE,UAAM,cAAc,MAAM,KAAK,MAAM,QAAQ;AAC7C,QAAI;AACF,YAAM,EAAE,WAAW,IAAI,KAAK;AAC5B,YAAM,cAAc,OAAO,KAAK,UAAU,EAAE;AAAA,QAC1C,CAAC,wBAAwB,mBAAmB;AAAA,MAC9C;AACA,UAAI,aAAa;AACf,eAAO,WAAW,cAAc;AAAA,MAClC;AACA,YAAM,WAAW,MAAM,KAAK,eAAe,cAAc;AACzD,WAAK,OAAO,CAAC,UAAU;AACrB,cAAM,WAAW,cAAc,IAAI;AAAA,MACrC,CAAC;AACD,aAAO;AAAA,IACT,UAAE;AACA,kBAAY;AAAA,IACd;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuBA,MAAM,eACJ,UACA;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ,CAAC;AAAA,IACT;AAAA,IACA,iBAAiB;AAAA,EACnB,IAcI,CAAC,GACY;AACjB,kBAAI,sBAAsB,QAAQ;AAElC,eAAW,2BAA2B,QAAQ;AAC9C,QACE,0BACA,CAAC,mBAAK,2BAA0B,IAAI,sBAAsB,GAC1D;AACA,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,UAAM,kBACJ,0BAA0B,sBAAK,wDAAL;AAE5B,UAAM,sBAAsB,MAAM,KAAK;AAAA,MACrC;AAAA,IACF;AAEA,qBAAiB,UAAU,mBAAmB;AAE9C,QAAI,QAAQ;AACV,YAAM;AAAA,QACJ,MAAM,KAAK,qBAAqB,MAAM;AAAA,QACtC,sBAAK,4CAAL,WAA2B;AAAA,QAC3B,SAAS;AAAA,QACT;AAAA,MACF;AAAA,IACF;AAEA,UAAM,uBAAuB,KAAK;AAAA,MAChC;AAAA,MACA;AAAA,IACF;AAEA,UAAM,UAAU,KAAK,WAAW,eAAe;AAC/C,UAAM,WAAW,mBAAK,2BAA0B,YAAY;AAAA,MAC1D;AAAA,MACA;AAAA,IACF,CAAC;AAED,UAAM,kBACJ,SAAS,MAAM,yBAAyB,UAAU,QAAQ,GAAG;AAE/D,UAAM,0BAA0B,KAAK,2BAA2B,QAAQ;AAGxE,QAAI,uBAAuB,0BACvB,UAAU,uBAAuB,IACjC;AAAA;AAAA,MAEE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,IAAI,OAAO;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,MACA,MAAM,KAAK,IAAI;AAAA,MACf;AAAA,MACA,oBAAoB;AAAA,MACpB,sBAAsB;AAAA,MACtB,MAAM;AAAA,MACN;AAAA,IACF;AAEJ,UAAM,KAAK,oBAAoB,oBAAoB;AAGnD,QAAI,CAAC,yBAAyB;AAE5B,UAAI,UAAU,KAAK,yBAAyB;AAC1C,cAAM,2BAA2B,MAAM,KAAK;AAAA,UAC1C;AAAA,UACA;AAAA,QACF;AACA,6BAAqB,2BACnB;AAAA,MACJ;AAEA,UAAI,CAAC,KAAK,2BAA2B;AACnC,6BAAqB,kBAAkB,mBAAmB,CAAC;AAAA,MAC7D;AAEA,UAAI,CAAC,KAAK,mBAAmB;AAC3B,+BAAuB,0BAA0B,oBAAoB;AAAA,MACvE;AAEA,6BAAuB;AAAA,QACrB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,UACE,iBAAiB,KAAK;AAAA,UACtB,mBAAmB,KAAK,kBAAkB,KAAK,IAAI;AAAA,UACnD,WAAW,KAAK;AAAA,QAClB;AAAA,MACF;AAEA,WAAK,YAAY,oBAAoB;AAErC,UAAI,oBAAoB,OAAO;AAE7B,8BAAK,gDAAL,WAA2B;AAAA,MAC7B,OAAO;AACL,sBAAI,8CAA8C;AAAA,MACpD;AAEA,WAAK,gBAAgB;AAAA,QACnB,GAAG,cAAc;AAAA,QACjB;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,MACL,QAAQ,KAAK,gBAAgB,sBAAsB;AAAA,QACjD,YAAY,QAAQ,uBAAuB;AAAA,QAC3C;AAAA,QACA;AAAA,MACF,CAAC;AAAA,MACD,iBAAiB;AAAA,IACnB;AAAA,EACF;AAAA,EAEA,gCAAgC,mBAAsC,CAAC,GAAG;AACxE,QAAI,iBAAiB,WAAW,GAAG;AACjC,WAAK,0BAA0B,MAAM;AACrC;AAAA,IACF;AACA,uBAAK,2BAA0B;AAAA,MAC7B;AAAA,IACF;AAAA,EACF;AAAA,EAEA,+BAA+B,mBAAsC,CAAC,GAAG;AACvE,QAAI,iBAAiB,WAAW,GAAG;AACjC,WAAK,0BAA0B,KAAK;AACpC;AAAA,IACF;AACA,uBAAK,2BAA0B;AAAA,MAC7B;AAAA,IACF;AAAA,EACF;AAAA,EAEA,oCAAoC;AAClC,SAAK,0BAA0B,KAAK;AACpC,uBAAK,2BAA0B,kCAAkC;AAAA,EACnE;AAAA,EAEA,MAAM,2BAA2B,mBAAsC,CAAC,GAAG;AACzE,QAAI,iBAAiB,WAAW,GAAG;AACjC,YAAM,KAAK,0BAA0B,OAAO;AAC5C;AAAA,IACF;AACA,UAAM,mBAAK,2BAA0B;AAAA,MACnC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,gBACJ,eACA,WACA;AAAA,IACE;AAAA,IACA;AAAA,EACF,IAAsD,CAAC,GACvD;AAEA,QAAI,KAAK,2BAA2B,QAAQ,GAAG;AAC7C;AAAA,IACF;AAEA,QAAI,WAAW;AAEb,kBAAY,sBAAsB,SAAS;AAC3C,wBAAkB,SAAS;AAAA,IAC7B;AAEA,kBAAI,+BAA+B,eAAe,SAAS;AAE3D,UAAM,kBAAkB,KAAK,eAAe,aAAa;AACzD,QAAI,CAAC,iBAAiB;AACpB;AAAA,IACF;AAEA,QAAI,CAAC,KAAK,MAAM;AACd,YAAM,IAAI,MAAM,yBAAyB;AAAA,IAC3C;AAGA,UAAM,cAAc;AAAA,MAClB,gBAAgB,SAAS;AAAA,MACzB;AAAA,IACF;AAEA,UAAM,qBAAqB,gBAAgB,SAAS,KAAK,UAAU;AAEnE,UAAM,cACH,sBACC,wBAAwB,oBAAoB,WAAW,KACzD;AAGF,UAAM,uBAAuB,gBAAgB,UAAU;AACvD,UAAM,kBAAkB;AAAA,MACtB;AAAA,MACA;AAAA,IACF;AACA,UAAM,qBACJ,yBAAyB,SAAS,KAAK,UAAU;AACnD,UAAM,kBACH,sBACC,wBAAwB,oBAAoB,eAAe,KAC5D,wBAAwB;AAG3B,UAAM,+BACJ,gBAAgB,UAAU;AAC5B,UAAM,0BAA0B;AAAA,MAC9B;AAAA,MACA;AAAA,IACF;AACA,UAAM,6BACJ,yBAAyB,SAAS,KAAK,UAAU;AACnD,UAAM,0BACH,8BACC;AAAA,MACE;AAAA,MACA;AAAA,IACF,KACD,gCAAgC;AAEnC,UAAM,cACJ,mBAAmB,0BACf;AAAA,MACE,MAAM,gBAAgB,SAAS;AAAA,MAC/B,UAAU,gBAAgB,SAAS;AAAA,MACnC,cAAc;AAAA,MACd,sBAAsB;AAAA,MACtB;AAAA,MACA,OAAO,gBAAgB,SAAS;AAAA,MAChC,IAAI,gBAAgB,SAAS;AAAA,MAC7B,OAAO;AAAA,IACT,IACA;AAAA,MACE,MAAM,gBAAgB,SAAS;AAAA,MAC/B,UAAU,gBAAgB,SAAS;AAAA,MACnC,UAAU;AAAA,MACV,OAAO,gBAAgB,SAAS;AAAA,MAChC,IAAI,gBAAgB,SAAS;AAAA,MAC7B,OAAO;AAAA,IACT;AAEN,UAAM,gBAAgB,KAAK;AAAA,MACzB,gBAAgB;AAAA,MAChB;AAAA,IACF;AAEA,UAAM,WAAW,MAAM,KAAK;AAAA,MAC1B;AAAA,MACA,gBAAgB,SAAS;AAAA,IAC3B;AAEA,UAAM,QAAQ,YAAY,SAAS,UAAU,CAAC;AAE9C,UAAM,SAAS,YAAY,gBAAgB,YAAY;AAEvD,UAAM,SAAS,YAAY,eACvB,gBAAgB,SAAS,eACzB,gBAAgB,SAAS;AAE7B,kBAAI,iCAAiC;AAAA,MACnC;AAAA,MACA;AAAA,MACA,UAAU;AAAA,IACZ,CAAC;AAED,UAAM,WAAW,mBAAK,2BAA0B,YAAY;AAAA,MAC1D,iBAAiB,gBAAgB;AAAA,MACjC,SAAS,gBAAgB;AAAA,IAC3B,CAAC;AACD,UAAM,OAAO,MAAM,KAAK;AAAA,MACtB;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,UAAM,wBAAwB;AAAA,MAC5B;AAAA,MACA,SAAS,gBAAgB;AAAA,MACzB,iBAAiB,gBAAgB;AAAA,MACjC;AAAA,MACA;AAAA,MACA,IAAI,OAAO;AAAA,MACX,qBAAqB,gBAAgB,SAAS;AAAA,MAC9C;AAAA,MACA;AAAA,MACA,MAAM,KAAK,IAAI;AAAA,MACf;AAAA,MACA,UAAU;AAAA,IACZ;AAEA,SAAK,YAAY,qBAAqB;AAGtC,SAAK,gBAAgB,QAAQ,GAAG,cAAc,wBAAwB;AAAA,MACpE,iBAAiB;AAAA,MACjB;AAAA,IACF,CAAC;AACD,SAAK,gBAAgB,QAAQ,GAAG,cAAc,yBAAyB;AAAA,MACrE,iBAAiB;AAAA,MACjB;AAAA,IACF,CAAC;AAED,SAAK,gBAAgB;AAAA,MACnB,GAAG,cAAc;AAAA,MACjB;AAAA,IACF;AACA,uBAAK,iBAAgB;AAAA,MACnB,GAAG,gBAAgB,EAAE;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,mBACJ,eACA,WACA;AAAA,IACE;AAAA,IACA;AAAA,EACF,IAAsD,CAAC,GACvD;AAEA,QAAI,KAAK,2BAA2B,QAAQ,GAAG;AAC7C;AAAA,IACF;AAEA,QAAI,WAAW;AAEb,kBAAY,sBAAsB,SAAS;AAC3C,wBAAkB,SAAS;AAAA,IAC7B;AAEA,kBAAI,iCAAiC,eAAe,SAAS;AAE7D,UAAM,kBAAkB,KAAK,eAAe,aAAa;AAEzD,QAAI,CAAC,iBAAiB;AACpB;AAAA,IACF;AAGA,QAAI,CAAC,KAAK,MAAM;AACd,YAAM,IAAI,MAAM,yBAAyB;AAAA,IAC3C;AAGA,UAAM,cAAc;AAAA,MAClB,gBAAgB,SAAS;AAAA,MACzB;AAAA,IACF;AAEA,UAAM,qBAAqB,gBAAgB,SAAS,KAAK,UAAU;AAEnE,UAAM,cACH,sBACC,wBAAwB,oBAAoB,WAAW,KACzD;AAGF,UAAM,uBAAuB,gBAAgB,UAAU;AACvD,UAAM,kBAAkB;AAAA,MACtB;AAAA,MACA;AAAA,IACF;AACA,UAAM,qBACJ,yBAAyB,SAAS,KAAK,UAAU;AACnD,UAAM,kBACH,sBACC,wBAAwB,oBAAoB,eAAe,KAC5D,wBAAwB;AAG3B,UAAM,+BACJ,gBAAgB,UAAU;AAC5B,UAAM,0BAA0B;AAAA,MAC9B;AAAA,MACA;AAAA,IACF;AACA,UAAM,6BACJ,yBAAyB,SAAS,KAAK,UAAU;AACnD,UAAM,0BACH,8BACC;AAAA,MACE;AAAA,MACA;AAAA,IACF,KACD,gCAAgC;AAEnC,UAAM,WACJ,mBAAmB,0BACf;AAAA,MACE,GAAG,gBAAgB;AAAA,MACnB,UAAU,gBAAgB,SAAS;AAAA,MACnC,cAAc;AAAA,MACd,sBAAsB;AAAA,MACtB;AAAA,IACF,IACA;AAAA,MACE,GAAG,gBAAgB;AAAA,MACnB,UAAU,gBAAgB,SAAS;AAAA,MACnC,UAAU;AAAA,IACZ;AAEN,UAAM,gBAAgB,KAAK;AAAA,MACzB,gBAAgB;AAAA,MAChB;AAAA,IACF;AAEA,UAAM,WAAW,MAAM,KAAK;AAAA,MAC1B;AAAA,MACA,gBAAgB,SAAS;AAAA,IAC3B;AAEA,UAAM,yBAAyB,KAAK;AAAA,MAClC;AAAA,MACA;AAAA,IACF;AACA,UAAM,QAAQ,YAAY,SAAS,UAAU,CAAC;AAE9C,UAAM,SAAS,SAAS,gBAAgB,SAAS;AAEjD,UAAM,SAAS,SAAS,eACpB,uBAAuB,SAAS,eAChC,uBAAuB,SAAS;AAEpC,kBAAI,mCAAmC,EAAE,QAAQ,QAAQ,SAAS,CAAC;AAEnE,UAAM,WAAW,mBAAK,2BAA0B,YAAY;AAAA,MAC1D,iBAAiB,gBAAgB;AAAA,MACjC,SAAS,gBAAgB;AAAA,IAC3B,CAAC;AACD,UAAM,OAAO,MAAM,KAAK;AAAA,MACtB;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,UAAM,sBAAsB;AAAA,MAC1B,GAAG;AAAA,MACH;AAAA,MACA,IAAI,OAAO;AAAA,MACX,MAAM,KAAK,IAAI;AAAA,MACf;AAAA,MACA;AAAA,MACA,qBAAqB,gBAAgB,SAAS;AAAA,MAC9C;AAAA,MACA,cAAc,gBAAgB;AAAA,IAChC;AAEA,UAAM,qBACJ,mBAAmB,0BACf;AAAA,MACE,GAAG;AAAA,MACH,UAAU;AAAA,QACR,GAAG,gBAAgB;AAAA,QACnB,cAAc;AAAA,QACd,sBAAsB;AAAA,MACxB;AAAA,IACF,IACA;AAAA,MACE,GAAG;AAAA,MACH,UAAU;AAAA,QACR,GAAG,gBAAgB;AAAA,QACnB,UAAU;AAAA,MACZ;AAAA,IACF;AAEN,SAAK,YAAY,kBAAkB;AAGnC,SAAK,gBAAgB,QAAQ,GAAG,cAAc,wBAAwB;AAAA,MACpE,iBAAiB;AAAA,MACjB;AAAA,IACF,CAAC;AAED,SAAK,gBAAgB,QAAQ,GAAG,cAAc,yBAAyB;AAAA,MACrE,iBAAiB;AAAA,MACjB;AAAA,IACF,CAAC;AAED,SAAK,gBAAgB;AAAA,MACnB,GAAG,cAAc;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,YACJ,aACA,iBACA;AACA,UAAM,WAAW,mBAAK,2BAA0B,YAAY;AAAA,MAC1D;AAAA,IACF,CAAC;AACD,UAAM,EAAE,cAAc,gBAAgB,IAAI,MAAM;AAAA,MAC9C;AAAA,MACA;AAAA,IACF;AAEA,WAAO,EAAE,KAAK,cAAc,gBAAgB;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,oBACJ,aACA,YACA,iBACA;AACA,UAAM,WAAW,mBAAK,2BAA0B,YAAY;AAAA,MAC1D;AAAA,IACF,CAAC;AACD,UAAM,EAAE,eAAe,cAAc,gBAAgB,IAAI,MAAM;AAAA,MAC7D;AAAA,MACA;AAAA,IACF;AAEA,UAAM,MAAM,aAAa,cAAc,eAAe,UAAU;AAEhE,WAAO;AAAA,MACL;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,kBAAkB,iBAAkC,MAAc;AAChE,UAAM,EAAE,IAAI,cAAc,IAAI;AAE9B,0BAAK,0DAAL,WAAgC,EAAE,eAAe,KAAK,GAAG,OAAO;AAAA,MAC9D,GAAG;AAAA,IACL;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,4BACE,eACA,uBACA;AACA,QAAI,CAAC,uBAAuB;AAC1B,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,UAAM,kBAAkB,KAAK,eAAe,aAAa;AACzD,QAAI,CAAC,iBAAiB;AACpB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,UAAM,yBAAyB;AAAA,MAC7B,GAAG;AAAA,MACH;AAAA,IACF;AACA,SAAK;AAAA,MACH;AAAA,MACA,GAAG,cAAc;AAAA,IACnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,iBAAiB,eAAyB,SAAkB;AAE1D,QAAI,iBAAiB,CAAC,SAAS;AAC7B,WAAK,OAAO,CAAC,UAAU;AACrB,cAAM,eAAe,CAAC;AAAA,MACxB,CAAC;AACD;AAAA,IACF;AACA,UAAM,iBAAiB,KAAK,WAAW;AACvC,UAAM,kBAAkB,KAAK,MAAM,aAAa;AAAA,MAC9C,CAAC,EAAE,SAAS,SAAS,MAAM;AACzB,cAAM,oBAAoB,iBAAiB,YAAY;AAEvD,YAAI,CAAC,mBAAmB;AACtB,iBAAO;AAAA,QACT;AAEA,cAAM,oBACJ,CAAC,WAAW,SAAS,MAAM,YAAY,MAAM,QAAQ,YAAY;AAEnE,eAAO,CAAC;AAAA,MACV;AAAA,IACF;AAEA,SAAK,OAAO,CAAC,UAAU;AACrB,YAAM,eAAe,KAAK,yBAAyB,eAAe;AAAA,IACpE,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,2BACJ,iBACA,oBACA,eACA;AAEA,UAAM,qBAAqB,KAAK,uBAAuB,eAAe;AAEtE,QAAI;AACF,YAAM,gBAAgB,mBAAmB;AAGzC,YAAM,yBAAyB;AAAA,QAC7B,GAAG;AAAA,QACH;AAAA,QACA,WAAW;AAAA,MACb;AACA,UAAI,eAAe;AACjB,+BAAuB,gBAAgB;AAAA,MACzC;AAGA,WAAK,2BAA2B,aAAa;AAG7C,WAAK;AAAA,QACH;AAAA,QACA,GAAG,cAAc;AAAA,MACnB;AACA,WAAK,0BAA0B,sBAAsB;AAIrD,WAAK,kBAAkB,sBAAsB;AAE7C,WAAK,gBAAgB;AAAA,QACnB,GAAG,cAAc;AAAA,QACjB;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,MAAM,0CAA0C,KAAK;AAAA,IAC/D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,iCACE,eACA,8BACA,sBACiB;AACjB,QAAI,KAAK,2BAA2B;AAClC,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,UAAM,kBAAkB,KAAK,eAAe,aAAa;AAEzD,QAAI,CAAC,iBAAiB;AACpB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA;AAAA,MACE;AAAA,MACA;AAAA,IACF;AAEA,UAAM,kBAAkB,gBAAgB,mBAAmB,CAAC;AAC5D,QAAI,iCAAiC,gBAAgB,QAAQ;AAC3D,YAAM,yBAAyB;AAAA,QAC7B,GAAG;AAAA,QACH,iBAAiB,CAAC,GAAG,iBAAiB,GAAG,oBAAoB;AAAA,MAC/D;AACA,WAAK;AAAA,QACH;AAAA,QACA,GAAG,cAAc;AAAA,MACnB;AAAA,IACF;AAEA,WAAO,KAAK,eAAe,aAAa;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBA,yBACE,eACA;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAaiB;AACjB,UAAM,kBAAkB,KAAK,eAAe,aAAa;AAEzD,QAAI,CAAC,iBAAiB;AACpB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA;AAAA,MACE;AAAA,MACA;AAAA,IACF;AAEA,QAAI,qBAAqB;AAAA,MACvB,UAAU;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA;AAAA,IAGF;AAGA,uBAAmB,WAAW,OAAO,mBAAmB,QAAQ;AAChE,yBAAqB,OAAO,kBAAkB;AAG9C,UAAM,cAAc,MAAM,CAAC,GAAG,iBAAiB,kBAAkB;AAEjE,SAAK;AAAA,MACH;AAAA,MACA,GAAG,cAAc;AAAA,IACnB;AAEA,WAAO,KAAK,eAAe,aAAa;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,wBACE,eACA;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAKiB;AACjB,UAAM,kBAAkB,KAAK,eAAe,aAAa;AAEzD,QAAI,CAAC,iBAAiB;AACpB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,oCAAgC,iBAAiB,yBAAyB;AAE1E,UAAM,yBAAyB;AAAA,MAC7B,aAAa;AAAA,QACX;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA;AAAA;AAAA,IAGF;AAGA,2BAAuB,cAAc;AAAA,MACnC,uBAAuB;AAAA,IACzB;AAGA,UAAM,cAAc,MAAM,CAAC,GAAG,iBAAiB,sBAAsB;AAErE,SAAK;AAAA,MACH;AAAA,MACA,GAAG,cAAc;AAAA,IACnB;AAEA,WAAO,KAAK,eAAe,aAAa;AAAA,EAC1C;AAAA,EAEA,MAAM,aACJ,SACA,iBACoB;AACpB,WAAO,mBAAK,2BAA0B;AAAA,MACpC;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,MAAM,qBACJ,MACA;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAQA;AACA,UAAM,kBAAkB,KAAK,eAAe,IAAI;AAChD,QAAI,CAAC,iBAAiB;AACpB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,oCAAgC,iBAAiB,sBAAsB;AAEvE,UAAM,iBAAiB;AAAA,MACrB,UAAU;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,mBAAe,WAAW;AAAA,MACxB,eAAe;AAAA,IACjB;AAEA,UAAM,qBAAqB,MAAM,CAAC,GAAG,iBAAiB,cAAc;AACpE,UAAM,WAAW,mBAAK,2BAA0B,YAAY;AAAA,MAC1D,SAAS,gBAAgB;AAAA,MACzB,iBAAiB,gBAAgB;AAAA,IACnC,CAAC;AACD,UAAM,WAAW,IAAI,SAAS,QAAQ;AACtC,UAAM,EAAE,KAAK,IAAI,MAAM;AAAA,MACrB,mBAAmB;AAAA,MACnB;AAAA,IACF;AACA,uBAAmB,OAAO;AAE1B,UAAM,8BAA8B;AAAA,MAClC,mBAAmB,KAAK;AAAA,MACxB;AAAA,MACA,iBAAiB;AAAA,IACnB,CAAC;AAED,SAAK;AAAA,MACH;AAAA,MACA,8BAA8B,IAAI;AAAA,IACpC;AACA,WAAO,KAAK,eAAe,IAAI;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,iCACJ,iBAA2D,CAAC,GAC5D,EAAE,SAAS,IAA4B,CAAC,GACZ;AAC5B,kBAAI,0CAA0C;AAAA,MAC5C,cAAc;AAAA,IAChB,CAAC;AAED,QAAI,eAAe,WAAW,GAAG;AAC/B,aAAO;AAAA,IACT;AAEA,UAAM,YAAY,eAAe,CAAC;AAClC,UAAM,SAAS,KAAK,uBAAuB,UAAU,OAAO;AAO5D,QAAI;AACJ,QAAI;AACF,wBAAkB,KAAK,gBAAgB;AAAA,QACrC;AAAA,QACA,UAAU;AAAA,MACZ;AAAA,IACF,SAAS,KAAK;AACZ,oBAAI,+CAA+C,GAAG;AAAA,IACxD;AAEA,UAAM,mBAAmB,mBAAmB,WAAW,WAAW;AAAA,MAChE;AAAA,IACF,CAAC;AACD,UAAM,2BAA2B,YAAY,iBAAiB,UAAU,CAAC;AAEzE,QAAI,KAAK,wBAAwB,IAAI,wBAAwB,GAAG;AAC9D,aAAO;AAAA,IACT;AACA,SAAK,wBAAwB,IAAI,wBAAwB;AAEzD,QAAI,iBAAiB;AACrB,QAAI;AAEF,YAAM,cAAc,UAAU;AAC9B,YAAM,gBAAgB,aAAa;AAEnC,kBAAY,gBACR,MAAM,KAAK,aAAa,aAAa,eAAe,IACpD;AAEJ,YAAM,QAAQ,YACV,MAAM,UAAU,UAAU,SAAS,EAAE,CAAC,IACtC,UAAU;AAEd,UAAI,WAAW;AACb,sBAAI,kCAAkC,OAAO,UAAU,YAAY;AAAA,MACrE;AAEA,wBAAkB,MAAM,QAAQ;AAAA,QAC9B,eAAe,IAAI,CAAC,aAAa;AAC/B,mBAAS,QAAQ;AACjB,iBAAO,KAAK,wBAAwB,SAAS,SAAS,QAAQ;AAAA,QAChE,CAAC;AAAA,MACH;AAAA,IACF,SAAS,KAAK;AACZ,oBAAI,oDAAoD,GAAG;AAG3D,YAAM;AAAA,IACR,UAAE;AACA,iBAAW,YAAY;AACvB,WAAK,wBAAwB,OAAO,wBAAwB;AAAA,IAC9D;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,2BACE,eACA;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAKA;AACA,UAAM,kBAAkB,KAAK,eAAe,aAAa;AAEzD,QAAI,CAAC,iBAAiB;AACpB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,QAAI,CAAC,gBAAgB,WAAW;AAC9B,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAC/D;AAEA,QACE,UACA,CAAC;AAAA;AAAA;AAAA;AAAA,IAID,EAAE,SAAS,MAAM,GACjB;AACA,YAAM,IAAI;AAAA,QACR,oDAAoD,MAAM;AAAA,MAC5D;AAAA,IACF;AAEA,UAAM,yBAAyB;AAAA,MAC7B,CAAC;AAAA,MACD;AAAA,MACA,OAAO,EAAE,MAAM,OAAO,CAAC;AAAA,IACzB;AAEA,QAAI,uBAAuB,wCAAwC;AACjE,6BAAuB,iBAAgB,oBAAI,KAAK,GAAE,QAAQ;AAAA,IAC5D;AAEA,QAAI,uBAAuB,kCAAqC;AAC9D,6BAAuB,QAAQ,iBAAiB,IAAI,MAAM,YAAY,CAAC;AAAA,IACzE;AAEA,SAAK;AAAA,MACH;AAAA,MACA,GAAG,cAAc;AAAA,IACnB;AAEA,QACE,mDAAsD,EAAE;AAAA,MACtD;AAAA,IACF,GACA;AACA,WAAK,gBAAgB;AAAA,QACnB,GAAG,cAAc;AAAA,QACjB;AAAA,MACF;AACA,yBAAK,iBAAgB;AAAA,QACnB,GAAG,uBAAuB,EAAE;AAAA,QAC5B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,gBAAgB;AAAA,IACd,iBAAiB,CAAC;AAAA,IAClB;AAAA,IACA,yBAAyB;AAAA,IACzB;AAAA,EACF,IAOI,CAAC,GAAsB;AACzB,UAAM,UAAU,KAAK,WAAW;AAOhC,UAAM,mBAAmB,UAAU,gBAAgB,CAAC,cAAc;AAChE,aAAO,OAAO,cAAc,aACxB;AAAA;AAAA;AAAA,QAGA,CAAC,MAAW,MAAM;AAAA;AAAA,IACxB,CAAC;AAED,UAAM,uBAAuB,eAAe,KAAK,MAAM;AAIvD,UAAM,uBAAuB;AAAA,MAC3B,OAAO,sBAAsB,CAAC,gBAAgB;AAC5C,YAAI,0BAA0B,YAAY,YAAY,SAAS;AAC7D,iBAAO;AAAA,QACT;AAGA,mBAAW,CAAC,KAAK,SAAS,KAAK,OAAO,QAAQ,gBAAgB,GAAG;AAM/D,cAAI,OAAO,YAAY,UAAU;AAG/B,gBAAI,UAAW,YAAY,SAAiB,GAAG,CAAC,MAAM,OAAO;AAC3D,qBAAO;AAAA,YACT;AAAA,UAGF,WAAW,UAAW,YAAoB,GAAG,CAAC,MAAM,OAAO;AACzD,mBAAO;AAAA,UACT;AAAA,QACF;AAEA,eAAO;AAAA,MACT,CAAC;AAAA,MACD;AAAA,IACF;AACA,QAAI,UAAU,QAAW;AAKvB,YAAM,SAAS,oBAAI,IAAI;AACvB,YAAM,MAAM,CAAC;AAMb,eAAS,IAAI,qBAAqB,SAAS,GAAG,IAAI,IAAI,KAAK;AACzD,cAAM,SAAS,qBAAqB,CAAC;AACrC,cAAM,EAAE,MAAM,IAAI,OAAO;AACzB,YAAI,CAAC,OAAO,IAAI,KAAK,GAAG;AACtB,cAAI,OAAO,OAAO,OAAO;AACvB,mBAAO,IAAI,KAAK;AAAA,UAClB,OAAO;AACL;AAAA,UACF;AAAA,QACF;AAGA,YAAI,QAAQ,MAAM;AAAA,MACpB;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,eAAe;AAAA,IACnB;AAAA,IACA;AAAA,IACA,iBAAiB;AAAA,EACnB,GAIgC;AAC9B,UAAM,kBAAkB,sBAAK,4CAAL,WAAyB;AAAA,MAC/C,iBAAiB;AAAA,MACjB;AAAA,IACF;AAEA,UAAM,kBAAkB;AAAA,MACtB,UAAU;AAAA,MACV;AAAA,MACA;AAAA,IACF;AAGA,UAAM,aAAa;AAAA,MACjB;AAAA,MACA,KAAK;AAAA,IACP;AAEA,UAAM,WAAW,mBAAK,2BAA0B,YAAY;AAAA,MAC1D;AAAA,MACA;AAAA,IACF,CAAC;AAED,UAAM,uBAAuB,MAAM,KAAK,mBAAmB;AAAA,MACzD;AAAA,IACF,CAAC;AAED,WAAO,WAAW,WAAW;AAAA,MAC3B;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,gBAAgB;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAI6B;AAC3B,UAAM,WAAW,mBAAK,2BAA0B,YAAY;AAAA,MAC1D;AAAA,MACA;AAAA,IACF,CAAC;AAED,WAAO,MAAM,2BAA2B;AAAA,MACtC,mBAAmB,KAAK;AAAA,MACxB;AAAA,MACA,iBAAiB;AAAA,QACf,UAAU;AAAA,QACV;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,wBACZ,SACA,mBACiB;AACjB,QAAI,CAAC,KAAK,MAAM;AACd,YAAM,IAAI,MAAM,yBAAyB;AAAA,IAC3C;AAEA,UAAM,8BACJ,2BAA2B,iBAAiB;AAC9C,UAAM,OAAO,qBAAqB,2BAA2B;AAG7D,UAAM,2BAA2B;AAAA,MAC/B,GAAG;AAAA,MACH;AAAA,MACA,UAAU,4BAA4B;AAAA,MACtC;AAAA,IACF;AAEA,UAAM,EAAE,KAAK,IAAI;AACjB,UAAM,SAAS,KAAK,uBAAuB,OAAO;AAClD,UAAM,sBAAsB,mBAAmB;AAAA,MAC7C;AAAA,MACA,EAAE,OAAO;AAAA,IACX;AACA,UAAM,oBAAoB,MAAM,KAAK,KAAK,qBAAqB,IAAI;AAEnE,UAAM,iBAAiB,YAAY,kBAAkB,UAAU,CAAC;AAChE,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,8BAA8B;AAC5B,UAAM,eAAe,KAAK,MAAM,aAAa;AAAA,MAC3C,CAAC,EAAE,OAAO,MAAM;AAAA,IAClB;AACA,SAAK,OAAO,CAAC,UAAU;AACrB,YAAM,eAAe,KAAK,yBAAyB,YAAY;AAAA,IACjE,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,wBAAwB,eAAuB;AAC7C,UAAM,kBAAkB,KAAK,eAAe,aAAa;AAEzD,QAAI,CAAC,iBAAiB;AACpB,YAAM,IAAI,MAAM,uDAAuD;AAAA,IACzE;AAEA,UAAM,gBAAgB,KAAK,mBAAmB,IAAI,aAAa;AAE/D,QAAI,CAAC,eAAe;AAClB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,kBAAc;AAEd,SAAK,mBAAmB,OAAO,aAAa;AAAA,EAC9C;AAAA,EAEQ,YAAY,iBAAkC;AACpD,SAAK,OAAO,CAAC,UAAU;AACrB,YAAM,eAAe,KAAK,yBAAyB;AAAA,QACjD,GAAG,MAAM;AAAA,QACT;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,oBAAoB,iBAAkC;AAClE,UAAM,sBACH,MAAM,KAAK,wBAAwB,gBAAgB,eAAe,KACnE,gBAAgB,SAAS;AAE3B,UAAM,EAAE,iBAAiB,QAAQ,IAAI;AAErC,UAAM,kBAAkB,sBAAK,sCAAL,WAAsB;AAE9C,UAAM,WAAW,mBAAK,2BAA0B,YAAY;AAAA,MAC1D;AAAA,MACA;AAAA,IACF,CAAC;AAED,UAAM,WAAW,mBAAK,2BAA0B,YAAY;AAAA,MAC1D;AAAA,MACA;AAAA,IACF,CAAC;AAED,UAAM,UAAU;AAAA,MACd;AAAA,MACA;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,IACV,CAAC;AAED,UAAM,cAAc;AAAA,MAClB,SAAS;AAAA,MACT;AAAA,MACA,aAAa,KAAK;AAAA,MAClB,oBAAoB,KAAK;AAAA,MACzB,iBAAiB,KAAK,gBAAgB,KAAK,IAAI;AAAA,MAC/C,QAAQ;AAAA,IACV,CAAC;AAED,UAAM,8BAA8B;AAAA,MAClC,mBAAmB,KAAK;AAAA,MACxB;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEQ,gBAAgB;AACtB,SAAK,4BAA4B;AACjC,SAAK,2BAA2B;AAAA,EAClC;AAAA,EAEQ,6BAA6B;AACnC,UAAM,yBAAyB,KAAK,MAAM,aAAa;AAAA,MACrD,CAAC,gBACC,iDAAqD,EAAE;AAAA,QACrD,YAAY;AAAA,MACd;AAAA,IACJ;AAEA,eAAW,mBAAmB,wBAAwB;AACpD,WAAK;AAAA,QACH;AAAA,QACA,IAAI,MAAM,mCAAmC;AAAA,MAC/C;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,gBACZ,iBACA;AAAA,IACE,aAAa;AAAA,IACb;AAAA,IACA,oBAAoB;AAAA,IACpB;AAAA,EACF,GAMiB;AACjB,UAAM,gBAAgB,gBAAgB;AACtC,QAAI;AACJ,UAAM,EAAE,MAAM,YAAY,IAAI,KAAK,uBAAuB,aAAa;AACvE,UAAM,kBAAkB,cACpB,QAAQ,QAAQ,IAAI,IACpB,KAAK,2BAA2B,aAAa;AAEjD,QAAI,QAAQ,CAAC,cAAc,CAAC,aAAa;AACvC,UAAI;AACF,YAAI,oBAAoB,OAAO;AAC7B,gBAAM,eAAe,MAAM,KAAK,gBAAgB,iBAAiB;AAAA,YAC/D;AAAA,UACF,CAAC;AACD,4BAAkB,aAAa;AAE/B,gBAAM,gBAAgB,aAAa;AAMnC,gBAAM,qBAAqB,eAAe;AAE1C,cAAI,oBAAoB;AACtB,0BAAI,2CAA2C;AAAA,cAC7C,aAAa,mBAAmB;AAAA,cAChC,QAAQ,mBAAmB;AAAA,YAC7B,CAAC;AAED,iBAAK;AAAA,cACH;AAAA,cACA;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAEA,cAAM,EAAE,aAAa,cAAc,IACjC,KAAK,uBAAuB,aAAa;AAE3C,YAAI,CAAC,eAAe;AAClB,gBAAM,iBAAiB,MAAM,KAAK,mBAAmB,aAAa;AAClE,cACE,mBAAmB,uEACnB,iBACA;AACA,4BAAgB,QAAQ;AAAA,UAC1B;AACA,gBAAM,yBAAyB,KAAK;AAAA,YAClC;AAAA,UACF;AACA,eAAK,gBAAgB;AAAA,YACnB,GAAG,cAAc;AAAA,YACjB;AAAA,cACE,iBAAiB;AAAA,cACjB;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MAGF,SAAS,OAAY;AACnB,cAAM,EAAE,aAAa,cAAc,IACjC,KAAK,uBAAuB,aAAa;AAC3C,YAAI,CAAC,eAAe;AAClB,cAAI,OAAO,SAAS,WAAW,SAAS,qBAAqB;AAC3D,iBAAK,kBAAkB,eAAe,QAAQ;AAE9C,kBAAM,eAAe;AAAA,cACnB;AAAA,YACF;AAAA,UACF,OAAO;AACL,iBAAK,gBAAgB,MAAM,OAAO,QAAQ;AAAA,UAC5C;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,UAAM,YAAY,MAAM;AAExB,YAAQ,WAAW,QAAQ;AAAA,MACzB;AACE,yBAAiB,MAAM,UAAU,KAAK;AACtC,cAAM,UAAU,SAAS,UAAU,MAAM,OAAO;AAAA,MAElD;AACE,yBAAiB,QAAQ;AACzB,eAAO,UAAU;AAAA,MAEnB;AACE,cAAM,gBAAgB,UAAU;AAAA,UAC9B,2CAA2C,KAAK;AAAA,YAC9C,aAAa;AAAA,UACf,CAAC;AAAA,QACH;AAEA,yBAAiB,MAAM,aAAa;AACpC,cAAM;AAAA,IACV;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAc,mBAAmB,eAAuB;AACtD,UAAM,eAAe,IAAI,MAAkB;AAC3C,iBAAa,KAAK,MAAM,KAAK,MAAM,QAAQ,CAAC;AAE5C,QAAI,kBAAkB,KAAK,sBAAsB,aAAa;AAE9D,QAAI;AACF,UAAI,CAAC,KAAK,MAAM;AACd,aAAK;AAAA,UACH;AAAA,UACA,IAAI,MAAM,yBAAyB;AAAA,QACrC;AACA,eAAO;AAAA,MACT,WAAW,CAAC,gBAAgB,SAAS;AACnC,aAAK,gBAAgB,iBAAiB,IAAI,MAAM,qBAAqB,CAAC;AACtE,eAAO;AAAA,MACT;AAEA,UAAI,KAAK,wBAAwB,IAAI,aAAa,GAAG;AACnD,sBAAI,4CAA4C,aAAa;AAC7D,eAAO;AAAA,MACT;AACA,WAAK,wBAAwB,IAAI,aAAa;AAC9C,mBAAa;AAAA,QAAK,MAChB,KAAK,wBAAwB,OAAO,aAAa;AAAA,MACnD;AAEA,YAAM,CAAC,OAAO,YAAY,IAAI,MAAM;AAAA,QAClC;AAAA,QACA,CAAC,YACC,mBAAK,2BAA0B;AAAA,UAC7B;AAAA,UACA,gBAAgB;AAAA,QAClB;AAAA,MACJ;AAGA,sBAAgB,aAAa,KAAK,YAAY;AAE9C,wBAAkB,sBAAK,0DAAL,WAChB;AAAA,QACE;AAAA,QACA,MAAM;AAAA,MACR,GACA,CAAC,gBAAgB;AACf,cAAM,EAAE,UAAU,QAAQ,IAAI;AAE9B,oBAAY;AACZ,oBAAY,WAAW;AAAA,UACrB,GAAG;AAAA,UACH;AAAA,UACA;AAAA,UACA,UAAU,SAAS;AAAA,UACnB,GAAI,qBAAqB,QAAQ,KAAK;AAAA,YACpC;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAGF,WAAK,0BAA0B,eAAe;AAE9C,YAAM,QAAQ,MAAM,KAAK;AAAA,QACvB;AAAA,QACA,gBAAgB;AAAA,MAClB;AAEA,UAAI,CAAC,KAAK,cAAc,eAAe,GAAG;AACxC,sBAAI,+CAA+C;AACnD,aAAK,gBAAgB;AAAA,UACnB,GAAG,cAAc;AAAA,UACjB;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAEA,UAAI,CAAC,OAAO;AACV,eAAO;AAAA,MACT;AAEA,YAAM,WAAW,mBAAK,2BAA0B,YAAY;AAAA,QAC1D,iBAAiB,gBAAgB;AAAA,QACjC,SAAS,gBAAgB;AAAA,MAC3B,CAAC;AAED,UAAI;AACJ,YAAM,2BACJ,gBAAgB;AAElB,UAAI,0BAA0B;AAC5B,sBAAI,qCAAqC;AAEzC,uBAAe,MAAM,MAAM,UAAU,cAAc;AAAA,UACjD,gBAAgB,SAAS;AAAA,QAC3B,CAAC;AAAA,MACH;AAEA,oBAAI,0BAA0B,gBAAgB,QAAQ;AAEtD,UAAI,EAAE,iBAAiB,KAAK,IAAI,MAAM,KAAK;AAAA,QACzC;AAAA,QACA;AAAA,MACF;AAEA,UAAI,SAAS,QAAW;AACtB,eAAO,MAAM,KAAK,mBAAmB,UAAU,KAAK;AAAA,MACtD;AAEA,oBAAI,sBAAsB,IAAI;AAE9B,wBAAkB,sBAAK,0DAAL,WAChB;AAAA,QACE;AAAA,QACA,MAAM;AAAA,MACR,GACA,CAAC,gBAAgB;AACf,oBAAY,OAAO;AACnB,oBAAY;AACZ,oBAAY,iBAAgB,oBAAI,KAAK,GAAE,QAAQ;AAC/C,YAAI,0BAA0B;AAC5B,sBAAY,eAAe;AAC3B,wBAAI,mCAAmC,YAAY;AAAA,QACrD;AAAA,MACF;AAGF,WAAK,gBAAgB,QAAQ,GAAG,cAAc,yBAAyB;AAAA,QACrE;AAAA,MACF,CAAC;AAED,WAAK,gBAAgB;AAAA,QACnB,GAAG,cAAc;AAAA,QACjB;AAAA,MACF;AACA,yBAAK,iBAAgB,KAAK,GAAG,aAAa,aAAa,eAAe;AAEtE,WAAK,0BAA0B,eAAe;AAC9C,aAAO;AAAA,IAGT,SAAS,OAAY;AACnB,WAAK,gBAAgB,iBAAiB,KAAK;AAC3C,aAAO;AAAA,IACT,UAAE;AACA,mBAAa,QAAQ,CAAC,SAAS,KAAK,CAAC;AAAA,IACvC;AAAA,EACF;AAAA,EAEA,MAAc,mBACZ,UACA,gBACiB;AACjB,WAAO,MAAM,MAAM,UAAU,sBAAsB,CAAC,cAAc,CAAC;AAAA,EACrE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,kBAAkB,eAAuB,UAAmB;AAClE,UAAM,kBAAkB,KAAK,MAAM,aAAa;AAAA,MAC9C,CAAC,EAAE,GAAG,MAAM,OAAO;AAAA,IACrB;AACA,QAAI,CAAC,iBAAiB;AACpB;AAAA,IACF;AACA,SAAK,OAAO,CAAC,UAAU;AACrB,YAAM,eAAe,MAAM,aAAa;AAAA,QACtC,CAAC,EAAE,GAAG,MAAM,OAAO;AAAA,MACrB;AACA,YAAM,eAAe,KAAK,yBAAyB,YAAY;AAAA,IACjE,CAAC;AACD,UAAM,yBAAyB;AAAA,MAC7B,GAAG;AAAA,MACH;AAAA,IACF;AACA,SAAK,gBAAgB;AAAA,MACnB,GAAG,cAAc;AAAA,MACjB;AAAA,IACF;AACA,uBAAK,iBAAgB;AAAA;AAAA;AAAA,MAGnB,GAAG,gBAAgB,EAAE;AAAA,MACrB;AAAA,IACF;AACA,SAAK,gBAAgB,QAAQ,GAAG,cAAc,wBAAwB;AAAA,MACpE,iBAAiB;AAAA,MACjB;AAAA,IACF,CAAC;AACD,SAAK,0BAA0B,sBAAsB;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBQ,yBACN,cACmB;AACnB,UAAM,kBAAkB,oBAAI,IAAI;AAEhC,UAAM,YAAY,CAAC,GAAG,YAAY,EAC/B,KAAK,CAAC,GAAG,MAAO,EAAE,OAAO,EAAE,OAAO,KAAK,CAAE,EACzC,OAAO,CAAC,OAAO;AACd,YAAM,EAAE,SAAS,QAAQ,UAAU,KAAK,IAAI;AAE5C,UAAI,UAAU;AAGZ,cAAM,MAAM,GAAG,OAAO,SAAS,KAAK,CAAC,IAAI;AAAA,UACvC;AAAA,QACF,CAAC,IAAI,IAAI,KAAK,IAAI,EAAE,aAAa,CAAC;AAElC,YAAI,gBAAgB,IAAI,GAAG,GAAG;AAC5B,iBAAO;AAAA,QACT,WACE,gBAAgB,OAAO,mBAAK,6BAC5B,CAAC,KAAK,aAAa,MAAM,GACzB;AACA,0BAAgB,IAAI,GAAG;AACvB,iBAAO;AAAA,QACT;AAAA,MACF;AAEA,aAAO;AAAA,IACT,CAAC;AAEH,cAAU,QAAQ;AAClB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,aAAa,QAAoC;AACvD,WACE,wCACA,0CACA;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,kBAAkB,QAAoC;AAC5D,WAAO;AAAA;AAAA;AAAA;AAAA;AAAA,IAKP,EAAE,SAAS,MAAM;AAAA,EACnB;AAAA,EAEA,MAAc,gBACZ,QACA,EAAE,kBAAkB,GACA;AACpB,UAAM,KAAK,KAAK,cAAc,MAAM;AACpC,UAAM,EAAE,OAAO,IAAI;AACnB,UAAM,OAAO,aAAa;AAC1B,UAAM,cAAc,EAAE,MAAM,OAAO,GAAG;AAEtC,WAAQ,MAAM,KAAK,gBAAgB;AAAA,MACjC;AAAA,MACA;AAAA,QACE;AAAA,QACA,QAAQ,UAAU;AAAA,QAClB;AAAA,QACA;AAAA,QACA,eAAe;AAAA,MACjB;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,eACN,eACuC;AACvC,UAAM,EAAE,aAAa,IAAI,KAAK;AAC9B,WAAO,aAAa,KAAK,CAAC,EAAE,GAAG,MAAM,OAAO,aAAa;AAAA,EAC3D;AAAA,EAEQ,sBACN,eACA,qBAAqB,yBACM;AAC3B,UAAM,SAAS,KAAK,eAAe,aAAa;AAChD,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI;AAAA,QACR,GAAG,kBAAkB,kCAAkC,aAAa;AAAA,MACtE;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,cAAc,QAAyB;AAC7C,WAAO,OAAO,OAAO,EAAE;AAAA,EACzB;AAAA,EAEQ,uBAAuB,eAG7B;AACA,UAAM,cAAc,KAAK,eAAe,aAAa;AAErD,QAAI,CAAC,aAAa;AAChB,aAAO,EAAE,MAAM,QAAW,aAAa,MAAM;AAAA,IAC/C;AAEA,UAAM,cAAc,KAAK,kBAAkB,YAAY,MAAM;AAE7D,WAAO,EAAE,MAAM,aAAa,YAAY;AAAA,EAC1C;AAAA,EAEQ,WAAW,iBAAwC;AACzD,UAAM,gBAAgB,sBAAK,wCAAL;AACtB,UAAM,wBAAwB,sBAAK,wDAAL;AAE9B,QAAI,CAAC,mBAAmB,oBAAoB,uBAAuB;AACjE,aAAO;AAAA,IACT;AAEA,WAAO,KAAK,gBAAgB;AAAA,MAC1B;AAAA,MACA;AAAA,IACF,EAAE,cAAc;AAAA,EAClB;AAAA,EAEQ,qBACN,SACA,UACkB;AAClB,WAAO,mBAAmB,WAAW,UAAU;AAAA,MAC7C,QAAQ;AAAA,MACR,QAAQ,KAAK,uBAAuB,OAAO;AAAA,IAC7C,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYQ,uBAAuB,SAAsB;AACnD,UAAM,oBAA0C;AAAA,MAC9C,SAAS,SAAS,SAAS,EAAE;AAAA,MAC7B,iBAAiB;AAAA,IACnB;AAEA,WAAO,OAAO,OAAO,iBAAiB;AAAA,EACxC;AAAA,EAEQ,uBAAuB;AAAA,IAC7B;AAAA,IACA;AAAA,EACF,GAGG;AACD,SAAK,OAAO,CAAC,UAAU;AACrB,YAAM,EAAE,cAAc,oBAAoB,IAAI;AAC9C,YAAM,sBAAsB;AAAA,QAC1B,GAAG;AAAA,QACH,GAAG,oBAAoB,IAAI,CAAC,wBAAwB;AAClD,gBAAM,qBAAqB,QAAQ;AAAA,YACjC,CAAC,EAAE,KAAK,MAAM,SAAS,oBAAoB;AAAA,UAC7C;AAEA,iBAAO,sBAAsB;AAAA,QAC/B,CAAC;AAAA,MACH;AAEA,YAAM,eAAe,KAAK,yBAAyB,mBAAmB;AAAA,IACxE,CAAC;AAAA,EACH;AAAA,EAEQ,iCAAiC;AAAA,IACvC;AAAA,IACA;AAAA,EACF,GAKG;AACD,SAAK,OAAO,CAAC,UAAU;AACrB,YAAM,0BAA0B;AAAA,IAClC,CAAC;AACD,SAAK,gBAAgB;AAAA,MACnB,GAAG,cAAc;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,6BACN,UACA,QACkC;AAClC,QAAI,CAAC,UAAU,WAAW,iBAAiB;AACzC,aAAO;AAAA,IACT;AAEA,UAAM,EAAE,UAAU,cAAc,sBAAsB,IAAI,IAAI;AAE9D,QACE,aAAa,UACb,iBAAiB,UACjB,yBAAyB,UACzB,QAAQ,QACR;AACA,aAAO;AAAA,IACT;AAEA,UAAM,uBAA6C,CAAC;AAEpD,QAAI,aAAa,QAAW;AAC1B,2BAAqB,WAAW;AAAA,IAClC,WACE,iBAAiB,UACjB,yBAAyB,QACzB;AACA,2BAAqB,eAAe;AACpC,2BAAqB,uBAAuB;AAAA,IAC9C;AAEA,QAAI,QAAQ,QAAW;AACrB,2BAAqB,MAAM;AAAA,IAC7B;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,uBAAuB,iBAAkC;AAC/D,UAAM,EAAE,QAAQ,IAAI;AACpB,UAAM,EAAE,aAAa,IAAI,KAAK;AAC9B,UAAM,cAAc,iBAAiB,UAAU;AAC/C,UAAM,iCAAiC,aAAa;AAAA,MAClD,CAAC,gBACC,YAAY,SAAS,SAAS,eAC9B,YAAY,YAAY;AAAA,IAC5B;AACA,UAAM,eAAe,+BAA+B;AAAA,MAClD,CAAC,gBAAgB,YAAY;AAAA,IAC/B;AACA,UAAM,aAAa,+BAA+B;AAAA,MAChD,CAAC,gBAAgB,YAAY;AAAA,IAC/B;AAEA;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAGA,UAAM,sBACH,gBAAgB,WAAW,CAAC,GAAG,WAAW,KAAK,CAAC,KAAK,oBAClD,0BAA0B,eAAe,IACzC;AAEN,SAAK,OAAO,CAAC,UAAU;AACrB,YAAM,eAAe,KAAK,yBAAyB;AAAA,QACjD,GAAG,MAAM;AAAA,QACT;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAED,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,2BAA2B,eAAuB;AACxD,UAAM,kBAAkB,KAAK,eAAe,aAAa;AACzD,QAAI,CAAC,iBAAiB;AACpB;AAAA,IACF;AACA,UAAM,QAAQ,gBAAgB,UAAU;AACxC,UAAM,OAAO,gBAAgB,UAAU;AACvC,UAAM,EAAE,QAAQ,IAAI;AAEpB,UAAM,wBAAwB,KAAK,MAAM,aAAa;AAAA,MACpD,CAAC,gBACC,YAAY,OAAO,iBACnB,YAAY,SAAS,SAAS,QAC9B,YAAY,SAAS,UAAU,SAC/B,YAAY,YAAY,WACxB,YAAY;AAAA,IAChB;AACA,UAAM,0BAA0B,sBAAsB;AAAA,MACpD,CAAC,gBAAgB,YAAY;AAAA,IAC/B;AAEA,QAAI,sBAAsB,WAAW,GAAG;AACtC;AAAA,IACF;AAEA,SAAK,OAAO,CAAC,UAAU;AACrB,iBAAW,eAAe,MAAM,cAAc;AAC5C,YAAI,wBAAwB,SAAS,YAAY,EAAE,GAAG;AACpD,sBAAY,aAAa,iBAAiB;AAC1C,sBAAY,eAAe,iBAAiB;AAAA,QAC9C;AAAA,MACF;AAAA,IACF,CAAC;AAED,eAAW,eAAe,KAAK,MAAM,cAAc;AACjD,UACE,wBAAwB,SAAS,YAAY,EAAE,KAC/C,YAAY,kCACZ;AACA,aAAK,4BAA4B,WAAW;AAAA,MAC9C;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,4BAA4B,iBAAkC;AACpE,UAAM,yBAAyB;AAAA,MAC7B,GAAG;AAAA,MACH;AAAA,IACF;AACA,SAAK,gBAAgB,QAAQ,GAAG,cAAc,uBAAuB;AAAA,MACnE,iBAAiB;AAAA,IACnB,CAAC;AACD,SAAK;AAAA,MACH;AAAA,MACA;AAAA,IACF;AACA,SAAK,0BAA0B,sBAAsB;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,2BAA2B,UAAmB;AACpD,WAAO,KAAK,MAAM,aAAa;AAAA,MAC7B,CAAC,gBAAgB,YAAY,YAAY,aAAa;AAAA,IACxD;AAAA,EACF;AAAA,EAEA,MAAc,2BACZ,eAC0B;AAC1B,WAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,yBAAK,iBAAgB,KAAK,GAAG,aAAa,aAAa,CAAC,WAAW;AACjE,gBAAQ,MAAM;AAAA,MAChB,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,yBACN,iBACA,UACiB;AACjB,UAAM,yBAAyB,UAAU,eAAe;AAExD,eAAW,OAAO,CAAC,KAAK,KAAK,GAAG,GAAY;AAC1C,YAAM,QAAQ,SAAS,GAAG;AAE1B,UAAI,UAAU,UAAa,UAAU,MAAM;AACzC;AAAA,MACF;AAEA,6BAAuB,GAAG,IAAI,MAAM,MAAM,SAAS,EAAE,CAAC;AAAA,IACxD;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,wBAAwB,iBAAmC;AACvE,UAAM,oCACJ,MAAM,KAAK,sCAAsC,eAAe;AAElE,UAAM,oCACJ,MAAM,KAAK,sCAAsC;AAEnD,WACE,qCAAqC;AAAA,EAEzC;AAAA,EAEA,MAAc,gBACZ,iBACA,UAC6B;AAC7B,kBAAI,uBAAuB,QAAQ;AAEnC,UAAM,gBAAgB,KAAK;AAAA,MACzB,gBAAgB;AAAA,MAChB;AAAA,IACF;AAEA,SAAK,wBAAwB,IAAI,gBAAgB,EAAE;AAEnD,UAAM,WAAW,MAAM,IAAI,QAA0B,CAAC,SAAS,WAAW;AACxE,WAAK;AAAA,QACH;AAAA,QACA,SAAS;AAAA,QACT,GAAG,KAAK,2BAA2B,eAAe;AAAA,MACpD,EAAE,KAAK,SAAS,MAAM;AAEtB,WAAK,mBAAmB;AAAA,QAAI,gBAAgB;AAAA,QAAI,MAC9C,OAAO,IAAI,MAAM,yBAAyB,CAAC;AAAA,MAC7C;AAAA,IACF,CAAC;AAED,SAAK,mBAAmB,OAAO,gBAAgB,EAAE;AAEjD,QAAI,CAAC,UAAU;AACb,oBAAI,iDAAiD;AACrD,aAAO;AAAA,IACT;AAEA,UAAM,0BAA0B,UAAU,eAAe;AACzD,QAAI,CAAC,KAAK,UAAU,yBAAyB,QAAQ,GAAG;AACtD,WAAK;AAAA,QACH;AAAA,QACA;AAAA,MACF;AAEA,oBAAI,sCAAsC;AAE1C,aAAO;AAAA,IACT;AAEA,UAAM,yBAAyB;AAAA,MAC7B,GAAG,KAAK,yBAAyB,yBAAyB,QAAQ;AAAA,MAClE;AAAA,IACF;AAEA,SAAK;AAAA,MACH;AAAA,MACA;AAAA,IACF;AAEA,SAAK,0BAA0B,sBAAsB;AAErD,UAAM,QAAQ,YAAY,SAAS,UAAU,CAAC;AAE9C,UAAM,2BAA2B,MAAM,CAAC,GAAG,wBAAwB;AAAA,MACjE;AAAA,IACF,CAAC;AAED,SAAK;AAAA,MACH;AAAA,MACA;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA,EAEQ,0BAA0B,iBAAkC;AAClE,SAAK,gBAAgB,QAAQ,GAAG,cAAc,6BAA6B;AAAA,MACzE;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEQ,4BACN,QACA,SACA,UAAkB,KAAK,WAAW,GAClC;AACA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA,KAAK,MAAM;AAAA,IACb;AAAA,EACF;AAAA,EAEQ,uBAAuB,iBAAkC;AAC/D,kBAAI,oCAAoC,gBAAgB,EAAE;AAE1D,SAAK,2BAA2B,gBAAgB,EAAE;AAElD,SAAK,gBAAgB;AAAA,MACnB,GAAG,cAAc;AAAA,MACjB;AAAA,IACF;AAEA,SAAK,0BAA0B,eAAe;AAI9C,SAAK,kBAAkB,eAAe;AAAA,EACxC;AAAA,EAEA,MAAc,kBAAkB,iBAAkC;AAChE,QAAI;AACF,UAAI,gBAAgB,4BAA+B;AACjD;AAAA,MACF;AAEA,YAAM,WAAW,mBAAK,2BAA0B,YAAY;AAAA,QAC1D,iBAAiB,gBAAgB;AAAA,QACjC,SAAS,gBAAgB;AAAA,MAC3B,CAAC;AACD,YAAM,EAAE,wBAAwB,wBAAwB,IACtD,MAAM,6BAA6B,iBAAiB;AAAA,QAClD;AAAA,QACA,gBAAgB,KAAK,eAAe,KAAK,IAAI;AAAA,QAC7C,mBAAmB,KAAK,kBAAkB,KAAK,IAAI;AAAA,MACrD,CAAC;AAEH,WAAK,gBAAgB;AAAA,QACnB,GAAG,cAAc;AAAA,QACjB;AAAA,UACE,iBAAiB;AAAA,UACjB;AAAA,QACF;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AAEd,oBAAI,iDAAiD,KAAK;AAAA,IAC5D;AAAA,EACF;AAAA,EAoLA,MAAc,2BACZ,UACA,OACA,iBACiB;AACjB,QAAI;AACF,YAAM,OAAO,MAAM,KAAK,mBAAmB,UAAU,KAAK;AAC1D,aAAO;AAAA,IACT,SAAS,OAAgB;AACvB,UAAI,KAAK,mCAAmC,KAAc,GAAG;AAC3D,cAAM,KAAK,0BAA0B;AAAA,UACnC;AAAA,QACF;AACA,cAAM,IAAI,MAAM,2CAA2C;AAAA,MAC7D;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWQ,mCAAmC,OAAqB;AAC9D,WACE,OAAO,SAAS,SAAS,eAAe,KACxC,OAAO,MAAM,SAAS,SAAS,eAAe;AAAA,EAElD;AAgQF;AAzsGE;AAyCS;AAMA;AAMT;AAEA;AAEA;AAuFA;AAsmFA;AAAA,wBAAmB,SAAC;AAAA,EAClB;AAAA,EACA;AAAA,EACA;AACF,GAIiB;AACf,SAAO,IAAI,aAAa;AAAA;AAAA;AAAA,IAGtB;AAAA;AAAA,IAEA;AAAA,IACA,wBAAwB,sBAAK,4EAAoC;AAAA,MAC/D;AAAA,MACA;AAAA,IACF;AAAA,IACA,0BAA0B,KAAK,4BAA4B;AAAA,MACzD;AAAA;AAAA,IAEF;AAAA,EACF,CAAC;AACH;AAEA;AAAA,qCAAgC,SAAC;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AACF,GAI8B;AAC5B,QAAM,4BAA4B,IAAI,0BAA0B;AAAA,IAC9D;AAAA,IACA,mBAAmB,MAAM,sBAAK,4CAAL;AAAA,IACzB,4BAA4B,MAAM,KAAK,MAAM;AAAA,IAC7C,YAAY,UAAU,MAAM,UAAU,KAAK,WAAW,KAAK,IAAI;AAAA,IAC/D,WAAW,mBAAK,6BAA4B;AAAA,IAC5C,oBAAoB,mBAAK,6BAA4B;AAAA,IACrD,yBAAyB;AAAA,IACzB,kBAAkB,mBAAK;AAAA,IACvB,oBAAoB,mBAAK,6BAA4B;AAAA,EACvD,CAAC;AAED,wBAAK,kFAAL,WAA4C;AAE5C,SAAO;AACT;AAEA;AAAA,qCAAgC,SAAC;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AACF,GAI8B;AAC5B,QAAM,WAAW,IAAI,SAAS,QAAQ;AACtC,QAAM,aAAa,UAAU,MAAM,UAAU,KAAK,WAAW,KAAK,IAAI;AAEtE,QAAM,4BAA4B,IAAI,0BAA0B;AAAA,IAC9D;AAAA,IACA;AAAA,IACA,aAAa,MAAM;AAAA,IACnB,iBAAiB,MAAM,KAAK,MAAM;AAAA,IAClC,mBAAmB,mBAAK,4BAA2B;AAAA,IACnD,eAAe,MACb,mBAAK,2BAA0B,8BAA8B;AAAA,MAC3D,SAAS,WAAW;AAAA,IACtB,CAAC;AAAA,IACH,oBAAoB,KAAK,mBAAmB,KAAK,IAAI;AAAA,IACrD,OAAO;AAAA,MACL,+BACE,KAAK,8BAA8B,KAAK,IAAI;AAAA,MAC9C,eAAe,KAAK,cAAc,KAAK,IAAI;AAAA,IAC7C;AAAA,EACF,CAAC;AAED,wBAAK,kFAAL,WAA4C;AAE5C,SAAO;AACT;AAEA;AAMA;AAAA,qBAAgB,WAAG;AACjB,OAAK,0BAA0B,KAAK;AACpC,wBAAK,wFAAL,WACE,KAAK;AAEP,OAAK,0BAA0B,KAAK;AACpC,wBAAK,wFAAL,WACE,KAAK;AAGP,qBAAK,2BAA0B,gBAAgB;AACjD;AAEA;AAAA,8CAAyC,SACvC,2BACA;AACA,4BAA0B,IAAI,mBAAmB,cAAc;AAC/D,4BAA0B,IAAI;AAAA,IAC5B;AAAA,EACF;AACF;AAEA;AAAA,2CAAsC,SACpC,2BACA;AACA,4BAA0B,IAAI;AAAA,IAC5B;AAAA,IACA,KAAK,uBAAuB,KAAK,IAAI;AAAA,EACvC;AACA,4BAA0B,IAAI;AAAA,IAC5B;AAAA,IACA,KAAK,iCAAiC,KAAK,IAAI;AAAA,EACjD;AACF;AAEA;AAAA,8CAAyC,SACvC,2BACA;AACA,4BAA0B,IAAI,mBAAmB,uBAAuB;AACxE,4BAA0B,IAAI,mBAAmB,qBAAqB;AACtE,4BAA0B,IAAI,mBAAmB,oBAAoB;AACrE,4BAA0B,IAAI,mBAAmB,qBAAqB;AACxE;AAEA;AAAA,2CAAsC,SACpC,2BACA;AACA,4BAA0B,IAAI;AAAA,IAC5B;AAAA,IACA,KAAK,uBAAuB,KAAK,IAAI;AAAA,EACvC;AAEA,4BAA0B,IAAI;AAAA,IAC5B;AAAA,IACA,KAAK,4BAA4B,KAAK,IAAI;AAAA,EAC5C;AAEA,4BAA0B,IAAI;AAAA,IAC5B;AAAA,IACA,KAAK,gBAAgB,KAAK,IAAI;AAAA,EAChC;AAEA,4BAA0B,IAAI;AAAA,IAC5B;AAAA,IACA,KAAK,kBAAkB,KAAK,IAAI;AAAA,EAClC;AACF;AAEA;AAAA,wCAAmC,SACjC,SACA,SACA;AACA,QAAM,8BAA8B,KAAK;AAAA;AAAA,IAEvC;AAAA,IACA;AAAA,EACF;AAEA,QAAM,8BAA8B,KAAK;AAAA,IACvC;AAAA,IACA;AAAA,EACF;AACA,SAAO,CAAC,GAAG,6BAA6B,GAAG,2BAA2B;AACxE;AAqCA;AAAA,oBAAe,WAAiB;AAC9B,MAAI,mBAAK,mBAAkB;AACzB,WAAO,CAAC,IAAI,eAAe,CAAC;AAAA,EAC9B;AAEA,SAAO,CAAC,IAAI,gBAAgB,GAAG,IAAI,kBAAkB,CAAC;AACxD;AAEA;AAAA,0BAAqB,WAAuB;AAC1C,SAAO,CAAC,IAAI,yBAAyB,GAAG,IAAI,uBAAuB,CAAC;AACtE;AAEA;AAAA,+BAA0B,SACxB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMA,UAC2B;AAC3B,MAAI,2BAAwD,CAAC;AAE7D,OAAK,OAAO,CAAC,UAAU;AACrB,UAAM,QAAQ,MAAM,aAAa;AAAA,MAC/B,CAAC,EAAE,GAAG,MAAM,OAAO;AAAA,IACrB;AAEA,QAAIC,mBAAkB,MAAM,aAAa,KAAK;AAG9C,IAAAA,mBAAkB,SAASA,gBAAe,KAAKA;AAE/C,QAAI,mBAAmB,MAAM;AAC3B,MAAAA,iBAAgB,WAAW;AAAA,QACzBA,iBAAgB;AAAA,MAClB;AAEA,uBAAiBA,iBAAgB,QAAQ;AAAA,IAC3C;AAEA,+BACE,sBAAK,sEAAL,WAAsCA;AAExC,UAAM,oBAAoB,KAAK,qBAAqB;AAEpD,QAAI,CAAC,mBAAmB;AACtB,MAAAA,mBAAkB;AAAA,QAChBA;AAAA,QACA,QAAQ;AAAA,MACV;AAAA,IACF;AACA,UAAM,aAAa,KAAK,IAAIA;AAAA,EAC9B,CAAC;AAED,QAAM,kBAAkB,KAAK;AAAA,IAC3B;AAAA,EACF;AAEA,MAAI,yBAAyB,SAAS,GAAG;AACvC,0BAAK,4DAAL,WACE,iBACA;AAAA,EAEJ;AAEA,SAAO;AACT;AAEA;AAAA,qCAAgC,SAAC,oBAAqC;AACpE,QAAM,EAAE,IAAI,eAAe,UAAU,UAAU,IAAI;AAEnD,QAAM,iBAAiB,KAAK,eAAe,aAAa,GAAG;AAE3D,MAAI,CAAC,kBAAkB,QAAQ,gBAAgB,SAAS,GAAG;AACzD,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,SAAS,OAAO,KAAK,SAAS;AAEpC,QAAM,oBAAoB,OAAO;AAAA,IAC/B,CAAC,UAAU,UAAU,KAAK,MAAM,eAAe,KAAK;AAAA,EACtD;AAEA;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,SAAO;AACT;AAEA;AAAA,gCAA2B,SACzB,iBACA,eACA;AACA,MACG,CAAC,MAAM,SAAS,MAAM,EAAY;AAAA,IAAK,CAAC,UACvC,cAAc,SAAS,KAAK;AAAA,EAC9B,GACA;AACA,kBAAI,8DAA8D;AAElE,0BAAK,gDAAL,WAA2B;AAAA,EAC7B;AACF;AAEM;AAAA,0BAAqB,eAAC,iBAAkC;AAC5D,QAAM,EAAE,IAAI,eAAe,SAAS,SAAS,IAAI;AACjD,QAAM,EAAE,MAAM,IAAI,OAAO,KAAK,IAAI;AAElC,MAAI,iBAAiC;AAAA,IACnC,OAAO;AAAA,MACL;AAAA,MACA,SAAS;AAAA,IACX;AAAA,IACA,qBAAqB,CAAC;AAAA,EACxB;AAEA,MAAI,mBAAK,sBAAL,YAA6B;AAC/B,0BAAK,0DAAL,WACE,EAAE,eAAe,aAAa,KAAK,GACnC,CAAC,WAAW;AACV,aAAO,iBAAiB;AAAA,IAC1B;AAGF,qBAAiB,MAAM,kBAAkB;AAAA,MACvC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAEA,QAAM,uBAAuB,KAAK,eAAe,aAAa;AAG9D,MAAI,CAAC,sBAAsB;AACzB;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA;AAAA,EACF;AAEA,wBAAK,0DAAL,WACE;AAAA,IACE;AAAA,IACA,MAAM;AAAA,EACR,GACA,CAAC,WAAW;AACV,WAAO,iBAAiB;AAAA,EAC1B;AAGF,gBAAI,2BAA2B,eAAe,cAAc;AAC9D;AAEA;AAAA,qCAAgC,SAAC;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,wBAAK,0DAAL,WACE,EAAE,eAAe,aAAa,KAAK,GACnC,CAAC,WAAW;AACV,QAAI,iBAAiB;AACnB,aAAO,kBAAkB;AAAA,IAC3B;AAEA,QAAI,0BAA0B,QAAW;AACvC,aAAO,wBAAwB;AAAA,IACjC;AAEA,QAAI,cAAc;AAChB,aAAO,eAAe;AAAA,IACxB;AAAA,EACF;AAEJ;AAEA;AAAA,wBAAmB,SAAC;AAAA,EAClB,iBAAiB;AAAA,EACjB;AACF,GAGG;AACD,QAAM,gBAAgB,sBAAK,wCAAL;AACtB,QAAM,wBAAwB,sBAAK,wDAAL;AAE9B,MAAI,wBAAwB;AAC1B,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,WAAW,YAAY,eAAe;AACzC,WAAO;AAAA,EACT;AAEA,SAAO,KAAK,gBAAgB;AAAA,IAC1B;AAAA,IACA;AAAA,EACF;AACF;AAEA;AAAA,8BAAyB,WAAG;AAC1B,SAAO,KAAK,gBAAgB,EAAE;AAChC;AAEA;AAAA,sBAAiB,WAAG;AAClB,SAAO,KAAK,gBAAgB;AAAA,IAC1B;AAAA,IACA,KAAK,gBAAgB,EAAE;AAAA,EACzB,EAAE,cAAc;AAClB;AAEA;AAAA,qBAAgB,SAAC,iBAAmC;AAClD,QAAM,wBAAwB,sBAAK,wDAAL;AAE9B,MAAI,CAAC,mBAAmB,oBAAoB,uBAAuB;AACjE,WAAO,CAAC;AAAA,MACN,KAAK,gBAAgB,EAAE;AAAA,IACzB;AAAA,EACF;AAEA,SACE,KAAK,gBAAgB;AAAA,IACnB;AAAA,IACA;AAAA,EACF,EAAE,cAAc,SAAS,kBAAkB;AAE/C;AAEA;AAAA,wBAAmB,WAAG;AACpB,SAAO,KAAK,gBAAgB,KAAK,uCAAuC;AAC1E;","names":["ApprovalState","transactionMeta"]} +\ No newline at end of file +diff --git a/dist/helpers/PendingTransactionTracker.js b/dist/helpers/PendingTransactionTracker.js +index 339a7782f047161c1909f8a91bb4299fad55a5f0..fb40ba14596eb229b004073776cfe1ddb171094a 100644 +--- a/dist/helpers/PendingTransactionTracker.js ++++ b/dist/helpers/PendingTransactionTracker.js +@@ -1,10 +1,10 @@ + "use strict";Object.defineProperty(exports, "__esModule", {value: true}); + +-var _chunkULD4JC3Qjs = require('../chunk-ULD4JC3Q.js'); ++var _chunk6DODV6OVjs = require('../chunk-6DODV6OV.js'); + require('../chunk-S6VGOPUY.js'); + require('../chunk-AYTU4HU5.js'); + require('../chunk-Z4BLTVTB.js'); + + +-exports.PendingTransactionTracker = _chunkULD4JC3Qjs.PendingTransactionTracker; ++exports.PendingTransactionTracker = _chunk6DODV6OVjs.PendingTransactionTracker; + //# sourceMappingURL=PendingTransactionTracker.js.map +\ No newline at end of file +diff --git a/dist/helpers/PendingTransactionTracker.mjs b/dist/helpers/PendingTransactionTracker.mjs +index e942405e82ab16407d8cbf318d84c1991f4e20d4..546ccbc8aede4f95bccc47d7c495000a8128dd82 100644 +--- a/dist/helpers/PendingTransactionTracker.mjs ++++ b/dist/helpers/PendingTransactionTracker.mjs +@@ -1,6 +1,6 @@ + import { + PendingTransactionTracker +-} from "../chunk-6B5BEO3R.mjs"; ++} from "../chunk-7M2R5AHC.mjs"; + import "../chunk-UQQWZT6C.mjs"; + import "../chunk-6SJYXSF3.mjs"; + import "../chunk-XUI43LEZ.mjs"; +diff --git a/dist/index.js b/dist/index.js +index 16f7e96ba4efe3f7d7aec6cecbb1fce6f991dd0b..77e7c5cc78efe603bcd6afbe95a5a6dee9cae719 100644 +--- a/dist/index.js ++++ b/dist/index.js +@@ -2,7 +2,7 @@ + + + +-var _chunkS7Q622ISjs = require('./chunk-S7Q622IS.js'); ++var _chunkIVR4NMOFjs = require('./chunk-IVR4NMOF.js'); + require('./chunk-PRUNMTRD.js'); + require('./chunk-74W7X6BE.js'); + require('./chunk-KT6UAKBB.js'); +@@ -10,7 +10,7 @@ require('./chunk-KT6UAKBB.js'); + + var _chunkSD6CWFDFjs = require('./chunk-SD6CWFDF.js'); + require('./chunk-RXIUMVA5.js'); +-require('./chunk-ULD4JC3Q.js'); ++require('./chunk-6DODV6OV.js'); + require('./chunk-7LXE4KHV.js'); + require('./chunk-V72C4MCR.js'); + require('./chunk-QP75SWIQ.js'); +@@ -67,5 +67,5 @@ require('./chunk-Z4BLTVTB.js'); + + + +-exports.CANCEL_RATE = _chunkS7Q622ISjs.CANCEL_RATE; exports.GasFeeEstimateLevel = _chunkAYTU4HU5js.GasFeeEstimateLevel; exports.GasFeeEstimateType = _chunkAYTU4HU5js.GasFeeEstimateType; exports.HARDFORK = _chunkS7Q622ISjs.HARDFORK; exports.SimulationErrorCode = _chunkAYTU4HU5js.SimulationErrorCode; exports.SimulationTokenStandard = _chunkAYTU4HU5js.SimulationTokenStandard; exports.TransactionController = _chunkS7Q622ISjs.TransactionController; exports.TransactionEnvelopeType = _chunkAYTU4HU5js.TransactionEnvelopeType; exports.TransactionStatus = _chunkAYTU4HU5js.TransactionStatus; exports.TransactionType = _chunkAYTU4HU5js.TransactionType; exports.UserFeeLevel = _chunkAYTU4HU5js.UserFeeLevel; exports.WalletDevice = _chunkAYTU4HU5js.WalletDevice; exports.determineTransactionType = _chunkSD6CWFDFjs.determineTransactionType; exports.isEIP1559Transaction = _chunkOZ6UB42Cjs.isEIP1559Transaction; exports.mergeGasFeeEstimates = _chunk76FONEDAjs.mergeGasFeeEstimates; exports.normalizeTransactionParams = _chunkOZ6UB42Cjs.normalizeTransactionParams; ++exports.CANCEL_RATE = _chunkIVR4NMOFjs.CANCEL_RATE; exports.GasFeeEstimateLevel = _chunkAYTU4HU5js.GasFeeEstimateLevel; exports.GasFeeEstimateType = _chunkAYTU4HU5js.GasFeeEstimateType; exports.HARDFORK = _chunkIVR4NMOFjs.HARDFORK; exports.SimulationErrorCode = _chunkAYTU4HU5js.SimulationErrorCode; exports.SimulationTokenStandard = _chunkAYTU4HU5js.SimulationTokenStandard; exports.TransactionController = _chunkIVR4NMOFjs.TransactionController; exports.TransactionEnvelopeType = _chunkAYTU4HU5js.TransactionEnvelopeType; exports.TransactionStatus = _chunkAYTU4HU5js.TransactionStatus; exports.TransactionType = _chunkAYTU4HU5js.TransactionType; exports.UserFeeLevel = _chunkAYTU4HU5js.UserFeeLevel; exports.WalletDevice = _chunkAYTU4HU5js.WalletDevice; exports.determineTransactionType = _chunkSD6CWFDFjs.determineTransactionType; exports.isEIP1559Transaction = _chunkOZ6UB42Cjs.isEIP1559Transaction; exports.mergeGasFeeEstimates = _chunk76FONEDAjs.mergeGasFeeEstimates; exports.normalizeTransactionParams = _chunkOZ6UB42Cjs.normalizeTransactionParams; + //# sourceMappingURL=index.js.map +\ No newline at end of file +diff --git a/dist/index.mjs b/dist/index.mjs +index edca04b36c84ba0245f49301275dbc5d49edeb18..a3506a7fa4f3050ff689540eefcddadd5d934b6b 100644 +--- a/dist/index.mjs ++++ b/dist/index.mjs +@@ -2,7 +2,7 @@ import { + CANCEL_RATE, + HARDFORK, + TransactionController +-} from "./chunk-UKV5HIMT.mjs"; ++} from "./chunk-YQYO6EGF.mjs"; + import "./chunk-6DDVVUJC.mjs"; + import "./chunk-EVL6KODQ.mjs"; + import "./chunk-K4KOSAGM.mjs"; +@@ -10,7 +10,7 @@ import { + determineTransactionType + } from "./chunk-KG4UW4K4.mjs"; + import "./chunk-5ZEJT5SN.mjs"; +-import "./chunk-6B5BEO3R.mjs"; ++import "./chunk-7M2R5AHC.mjs"; + import "./chunk-FRKQ3Z2L.mjs"; + import "./chunk-5G6OHAXI.mjs"; + import "./chunk-XGRAHX6T.mjs"; +diff --git a/dist/tsconfig.build.tsbuildinfo b/dist/tsconfig.build.tsbuildinfo +index 32138a8b41ae5a233d27a25ee160c68c6a7b5a73..4304f0751c5d4f5457696f8ac9e1b4ab38f843b7 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","../../../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","../../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/@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","../../../node_modules/@metamask/keyring-api/dist/api/account.d.ts","../../../node_modules/@metamask/keyring-api/dist/api/balance.d.ts","../../../node_modules/@metamask/keyring-api/dist/api/caip.d.ts","../../../node_modules/@metamask/keyring-api/dist/api/export.d.ts","../../../node_modules/@metamask/keyring-api/dist/superstruct.d.ts","../../../node_modules/@metamask/keyring-api/dist/api/request.d.ts","../../../node_modules/@metamask/keyring-api/dist/api/response.d.ts","../../../node_modules/@metamask/keyring-api/dist/api/keyring.d.ts","../../../node_modules/@metamask/keyring-api/dist/api/index.d.ts","../../../node_modules/@metamask/keyring-api/dist/btc/types.d.ts","../../../node_modules/@metamask/keyring-api/dist/btc/index.d.ts","../../../node_modules/@metamask/keyring-api/dist/contexts.d.ts","../../../node_modules/@metamask/keyring-api/dist/eth/erc4337/types.d.ts","../../../node_modules/@metamask/keyring-api/dist/eth/erc4337/index.d.ts","../../../node_modules/@metamask/keyring-api/dist/eth/types.d.ts","../../../node_modules/@metamask/keyring-api/dist/eth/utils.d.ts","../../../node_modules/@metamask/keyring-api/dist/eth/index.d.ts","../../../node_modules/@metamask/keyring-api/dist/events.d.ts","../../../node_modules/@metamask/keyring-api/dist/internal/api.d.ts","../../../node_modules/@metamask/keyring-api/dist/internal/eth/EthKeyring.d.ts","../../../node_modules/@metamask/keyring-api/dist/internal/eth/index.d.ts","../../../node_modules/@metamask/keyring-api/dist/internal/events.d.ts","../../../node_modules/@metamask/keyring-api/dist/internal/rpc.d.ts","../../../node_modules/@metamask/keyring-api/dist/internal/types.d.ts","../../../node_modules/@metamask/keyring-api/dist/internal/index.d.ts","../../../node_modules/@metamask/keyring-api/dist/JsonRpcRequest.d.ts","../../../node_modules/@metamask/keyring-api/dist/KeyringClient.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","../../../node_modules/@metamask/providers/dist/types/utils.d.ts","../../../node_modules/@metamask/providers/dist/types/BaseProvider.d.ts","../../../node_modules/@metamask/providers/dist/types/EIP6963.d.ts","../../../node_modules/@types/readable-stream/node_modules/safe-buffer/index.d.ts","../../../node_modules/@types/readable-stream/index.d.ts","../../../node_modules/@metamask/providers/dist/types/StreamProvider.d.ts","../../../node_modules/@metamask/providers/dist/types/extension-provider/createExternalExtensionProvider.d.ts","../../../node_modules/@metamask/providers/dist/types/MetaMaskInpageProvider.d.ts","../../../node_modules/@metamask/providers/dist/types/initializeInpageProvider.d.ts","../../../node_modules/@metamask/providers/dist/types/shimWeb3.d.ts","../../../node_modules/@metamask/providers/dist/types/index.d.ts","../../../node_modules/@metamask/keyring-api/dist/KeyringSnapRpcClient.d.ts","../../../node_modules/@metamask/keyring-api/dist/rpc-handler.d.ts","../../../node_modules/@metamask/rpc-errors/dist/types/utils.d.ts","../../../node_modules/@metamask/rpc-errors/dist/types/classes.d.ts","../../../node_modules/@metamask/rpc-errors/dist/types/errors.d.ts","../../../node_modules/@metamask/rpc-errors/dist/types/error-constants.d.ts","../../../node_modules/@metamask/rpc-errors/dist/types/index.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/errors.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/internals/error-wrappers.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/internals/errors.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/internals/helpers.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/internals/structs.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/create-interface.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/dialog.d.ts","../../../node_modules/@metamask/key-tree/dist/constants.d.cts","../../../node_modules/@metamask/key-tree/node_modules/@noble/curves/abstract/modular.d.ts","../../../node_modules/@metamask/key-tree/node_modules/@noble/curves/abstract/utils.d.ts","../../../node_modules/@metamask/key-tree/node_modules/@noble/curves/abstract/curve.d.ts","../../../node_modules/@metamask/key-tree/dist/curves/ed25519.d.cts","../../../node_modules/@metamask/key-tree/dist/curves/ed25519Bip32.d.cts","../../../node_modules/@metamask/key-tree/node_modules/@noble/curves/abstract/weierstrass.d.ts","../../../node_modules/@metamask/key-tree/dist/curves/secp256k1.d.cts","../../../node_modules/@metamask/key-tree/dist/curves/curve.d.cts","../../../node_modules/@metamask/key-tree/dist/curves/index.d.cts","../../../node_modules/@metamask/key-tree/dist/utils.d.cts","../../../node_modules/@metamask/key-tree/dist/BIP44CoinTypeNode.d.cts","../../../node_modules/@metamask/key-tree/dist/SLIP10Node.d.cts","../../../node_modules/@metamask/key-tree/dist/BIP44Node.d.cts","../../../node_modules/@metamask/key-tree/dist/derivers/bip32.d.cts","../../../node_modules/@metamask/key-tree/dist/derivers/bip39.d.cts","../../../node_modules/@metamask/key-tree/dist/derivers/cip3.d.cts","../../../node_modules/@metamask/key-tree/dist/derivers/slip10.d.cts","../../../node_modules/@metamask/key-tree/dist/derivers/index.d.cts","../../../node_modules/@metamask/key-tree/dist/index.d.cts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/caip.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/permissions.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/get-bip32-entropy.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/get-bip32-public-key.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/get-bip44-entropy.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/get-client-status.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/get-entropy.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/get-file.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/component.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/Address.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/Box.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/Copyable.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/Divider.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/form/Button.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/form/Option.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/form/Dropdown.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/form/Input.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/form/Field.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/form/Form.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/form/index.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/formatting/Bold.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/formatting/Italic.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/formatting/index.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/Heading.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/Image.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/Link.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/Text.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/Value.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/Row.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/Spinner.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/index.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/jsx-runtime.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/jsx-dev-runtime.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/validation.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/index.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/ui/nodes.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/ui/components/address.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/ui/components/copyable.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/ui/components/divider.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/ui/components/heading.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/ui/components/image.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/ui/components/panel.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/ui/components/spinner.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/ui/components/text.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/ui/components/row.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/ui/components/button.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/ui/components/input.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/ui/components/form.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/ui/components/index.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/ui/component.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/ui/index.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/interface.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/get-interface-state.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/get-locale.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/snap.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/get-snaps.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/invoke-snap.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/invoke-keyring.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/manage-accounts.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/manage-state.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/notify.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/request-snaps.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/update-interface.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/methods.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/index.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/provider.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/global.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/images.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/handlers/cronjob.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/handlers/home-page.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/handlers/keyring.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/handlers/lifecycle.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/handlers/name-lookup.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/handlers/rpc-request.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/handlers/transaction.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/handlers/signature.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/handlers/user-input.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/handlers/index.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/index.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/internals/jsx.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/internals/svg.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/internals/index.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/error-wrappers.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/images.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/index.d.ts","../../../node_modules/@metamask/keyring-api/dist/snap-utils.d.ts","../../../node_modules/@metamask/keyring-api/dist/index.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/patchCBOR.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/lib/DataItem.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/lib/cbor-sync.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/lib/index.d.ts","../../../node_modules/@ngraveio/bc-ur/dist/ur.d.ts","../../../node_modules/@ngraveio/bc-ur/dist/urEncoder.d.ts","../../../node_modules/@ngraveio/bc-ur/dist/fountainEncoder.d.ts","../../../node_modules/@ngraveio/bc-ur/dist/fountainDecoder.d.ts","../../../node_modules/@ngraveio/bc-ur/dist/urDecoder.d.ts","../../../node_modules/@ngraveio/bc-ur/dist/index.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/RegistryType.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/RegistryItem.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/CryptoCoinInfo.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/PathComponent.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/CryptoKeypath.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/types.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/CryptoHDKey.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/CryptoECKey.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/Bytes.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/MultiKey.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/ScriptExpression.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/CryptoOutput.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/CryptoPSBT.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/CryptoAccount.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/Decoder/index.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/extended/CryptoMultiAccounts.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/errors/index.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/extended/DerivationSchema.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/extended/KeyDerivation.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/extended/QRHardwareCall.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/utils.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/index.d.ts","../../../node_modules/@keystonehq/bc-ur-registry-eth/dist/EthSignRequest.d.ts","../../../node_modules/@keystonehq/bc-ur-registry-eth/dist/EthSignature.d.ts","../../../node_modules/@keystonehq/bc-ur-registry-eth/dist/ETHNFTItem.d.ts","../../../node_modules/@keystonehq/bc-ur-registry-eth/dist/utlis.d.ts","../../../node_modules/@keystonehq/bc-ur-registry-eth/dist/index.d.ts","../../../node_modules/@keystonehq/base-eth-keyring/dist/InteractionProvider.d.ts","../../../node_modules/@keystonehq/base-eth-keyring/dist/BaseKeyring.d.ts","../../../node_modules/@keystonehq/base-eth-keyring/dist/index.d.ts","../../../node_modules/@metamask/obs-store/dist/ObservableStore.d.ts","../../../node_modules/@metamask/obs-store/dist/asStream.d.ts","../../../node_modules/@metamask/obs-store/dist/ComposedStore.d.ts","../../../node_modules/@metamask/obs-store/dist/MergedStore.d.ts","../../../node_modules/@metamask/obs-store/dist/transform.d.ts","../../../node_modules/@metamask/obs-store/dist/index.d.ts","../../../node_modules/@keystonehq/metamask-airgapped-keyring/dist/MetaMaskInteractionProvider.d.ts","../../../node_modules/@keystonehq/metamask-airgapped-keyring/dist/MetaMaskKeyring.d.ts","../../../node_modules/@keystonehq/metamask-airgapped-keyring/dist/index.d.ts","../../../node_modules/@metamask/browser-passworder/dist/index.d.ts","../../message-manager/dist/types/AbstractMessageManager.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","../../message-manager/dist/types/PersonalMessageManager.d.ts","../../message-manager/dist/types/TypedMessageManager.d.ts","../../message-manager/dist/types/EncryptionPublicKeyManager.d.ts","../../message-manager/dist/types/DecryptMessageManager.d.ts","../../message-manager/dist/types/index.d.ts","../../keyring-controller/dist/types/KeyringController.d.ts","../../keyring-controller/dist/types/index.d.ts","../../../node_modules/@metamask/object-multiplex/dist/Substream.d.ts","../../../node_modules/@metamask/object-multiplex/dist/ObjectMultiplex.d.ts","../../../node_modules/@metamask/object-multiplex/dist/index.d.ts","../../../node_modules/@metamask/post-message-stream/dist/utils.d.ts","../../../node_modules/@metamask/post-message-stream/dist/BasePostMessageStream.d.ts","../../../node_modules/@metamask/post-message-stream/dist/window/WindowPostMessageStream.d.ts","../../../node_modules/@metamask/post-message-stream/dist/WebWorker/WebWorkerPostMessageStream.d.ts","../../../node_modules/@metamask/post-message-stream/dist/WebWorker/WebWorkerParentPostMessageStream.d.ts","../../../node_modules/@metamask/post-message-stream/dist/node-process/ProcessParentMessageStream.d.ts","../../../node_modules/@metamask/post-message-stream/dist/node-process/ProcessMessageStream.d.ts","../../../node_modules/@metamask/post-message-stream/dist/node-thread/ThreadParentMessageStream.d.ts","../../../node_modules/@metamask/post-message-stream/dist/node-thread/ThreadMessageStream.d.ts","../../../node_modules/@metamask/post-message-stream/dist/runtime/BrowserRuntimePostMessageStream.d.ts","../../../node_modules/@metamask/post-message-stream/dist/index.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/array.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/auxiliary-files.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/virtual-file/VirtualFile.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/virtual-file/index.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/base64.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/bytes.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/caveats.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/checksum.d.ts","../../../node_modules/cron-parser/types/common.d.ts","../../../node_modules/cron-parser/types/index.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/cronjob.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/deep-clone.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/default-endowments.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/derivation-paths.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/entropy.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/errors.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/handler-types.d.ts","../../../node_modules/@metamask/snaps-sdk/jsx.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/handlers.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/iframe.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/json.d.ts","../../../node_modules/nanoid/index.d.ts","../../approval-controller/dist/types/ApprovalController.d.ts","../../approval-controller/dist/types/errors.d.ts","../../approval-controller/dist/types/index.d.ts","../../../node_modules/@types/deep-freeze-strict/index.d.ts","../../permission-controller/src/permission-middleware.ts","../../permission-controller/src/SubjectMetadataController.ts","../../permission-controller/src/utils.ts","../../permission-controller/src/PermissionController.ts","../../permission-controller/src/Permission.ts","../../permission-controller/src/errors.ts","../../permission-controller/src/Caveat.ts","../../permission-controller/src/rpc-methods/getPermissions.ts","../../permission-controller/src/rpc-methods/requestPermissions.ts","../../permission-controller/src/rpc-methods/revokePermissions.ts","../../permission-controller/src/rpc-methods/index.ts","../../permission-controller/src/index.ts","../../../node_modules/@metamask/snaps-utils/dist/types/json-rpc.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/structs.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/manifest/validation.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/manifest/index.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/localization.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/logging.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/namespace.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/path.d.ts","../../../node_modules/@metamask/snaps-registry/dist/verify.d.ts","../../../node_modules/@metamask/snaps-registry/dist/index.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/types.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/snaps.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/strings.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/ui.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/validation.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/versions.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/index.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/snaps/Timer.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/services/ExecutionService.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/services/AbstractExecutionService.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/services/ProxyPostMessageStream.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/services/iframe/IframeExecutionService.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/services/iframe/index.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/services/proxy/ProxyExecutionService.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/services/offscreen/OffscreenExecutionService.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/services/offscreen/index.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/services/webworker/WebWorkerExecutionService.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/services/webworker/index.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/services/index.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/snaps/location/npm.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/snaps/location/location.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/snaps/location/http.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/snaps/location/local.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/snaps/location/index.d.ts","../../../node_modules/@xstate/fsm/lib/types.d.ts","../../../node_modules/@xstate/fsm/lib/index.d.ts","../../../node_modules/@types/punycode/index.d.ts","../../../node_modules/fastest-levenshtein/mod.d.ts","../../phishing-controller/src/utils.ts","../../phishing-controller/src/PhishingDetector.ts","../../phishing-controller/src/PhishingController.ts","../../phishing-controller/src/index.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/interface/SnapInterfaceController.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/interface/index.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/types/encryptor.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/types/index.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/snaps/registry/registry.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/snaps/registry/json.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/snaps/registry/index.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/snaps/SnapController.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/snaps/selectors.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/snaps/index.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/utils.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/cronjob/CronjobController.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/cronjob/index.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/index.d.ts","../../accounts-controller/dist/types/AccountsController.d.ts","../../../node_modules/@types/uuid/index.d.ts","../../accounts-controller/dist/types/utils.d.ts","../../accounts-controller/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","../../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/@metamask/eth-block-tracker/dist/BlockTracker.d.ts","../../../node_modules/@metamask/eth-block-tracker/dist/PollingBlockTracker.d.ts","../../../node_modules/@metamask/eth-block-tracker/dist/SubscribeBlockTracker.d.ts","../../../node_modules/@metamask/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","../../gas-fee-controller/dist/types/GasFeeController.d.ts","../../gas-fee-controller/dist/types/index.d.ts","../../../node_modules/@metamask/nonce-tracker/node_modules/async-mutex/lib/MutexInterface.d.ts","../../../node_modules/@metamask/nonce-tracker/node_modules/async-mutex/lib/Mutex.d.ts","../../../node_modules/@metamask/nonce-tracker/node_modules/async-mutex/lib/SemaphoreInterface.d.ts","../../../node_modules/@metamask/nonce-tracker/node_modules/async-mutex/lib/Semaphore.d.ts","../../../node_modules/@metamask/nonce-tracker/node_modules/async-mutex/lib/withTimeout.d.ts","../../../node_modules/@metamask/nonce-tracker/node_modules/async-mutex/lib/tryAcquire.d.ts","../../../node_modules/@metamask/nonce-tracker/node_modules/async-mutex/lib/errors.d.ts","../../../node_modules/@metamask/nonce-tracker/node_modules/async-mutex/lib/index.d.ts","../../../node_modules/@metamask/nonce-tracker/dist/NonceTracker.d.ts","../../../node_modules/@metamask/nonce-tracker/dist/index.d.ts","../../../node_modules/async-mutex/lib/MutexInterface.d.ts","../../../node_modules/async-mutex/lib/Mutex.d.ts","../../../node_modules/async-mutex/lib/SemaphoreInterface.d.ts","../../../node_modules/async-mutex/lib/Semaphore.d.ts","../../../node_modules/async-mutex/lib/withTimeout.d.ts","../../../node_modules/async-mutex/lib/tryAcquire.d.ts","../../../node_modules/async-mutex/lib/errors.d.ts","../../../node_modules/async-mutex/lib/index.d.ts","../../../node_modules/eth-method-registry/dist/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","../src/logger.ts","../../../node_modules/fast-json-patch/module/helpers.d.ts","../../../node_modules/fast-json-patch/module/core.d.ts","../../../node_modules/fast-json-patch/module/duplex.d.ts","../../../node_modules/fast-json-patch/index.d.ts","../src/types.ts","../src/utils/gas-flow.ts","../src/constants.ts","../src/utils/utils.ts","../src/utils/swaps.ts","../src/utils/gas-fees.ts","../src/gas-flows/DefaultGasFeeFlow.ts","../src/gas-flows/LineaGasFeeFlow.ts","../../../node_modules/@ethersproject/bytes/lib/index.d.ts","../../../node_modules/@ethersproject/bignumber/lib/bignumber.d.ts","../../../node_modules/@ethersproject/bignumber/lib/fixednumber.d.ts","../../../node_modules/@ethersproject/bignumber/lib/index.d.ts","../../../node_modules/@ethersproject/abi/lib/fragments.d.ts","../../../node_modules/@ethersproject/abi/lib/coders/abstract-coder.d.ts","../../../node_modules/@ethersproject/abi/lib/abi-coder.d.ts","../../../node_modules/@ethersproject/properties/lib/index.d.ts","../../../node_modules/@ethersproject/abi/lib/interface.d.ts","../../../node_modules/@ethersproject/abi/lib/index.d.ts","../../../node_modules/@ethersproject/networks/lib/types.d.ts","../../../node_modules/@ethersproject/networks/lib/index.d.ts","../../../node_modules/@ethersproject/transactions/lib/index.d.ts","../../../node_modules/@ethersproject/web/lib/index.d.ts","../../../node_modules/@ethersproject/abstract-provider/lib/index.d.ts","../../../node_modules/@ethersproject/abstract-signer/lib/index.d.ts","../../../node_modules/@ethersproject/contracts/lib/index.d.ts","../../../node_modules/@ethersproject/providers/lib/formatter.d.ts","../../../node_modules/@ethersproject/providers/lib/base-provider.d.ts","../../../node_modules/@ethersproject/providers/lib/json-rpc-provider.d.ts","../../../node_modules/@ethersproject/providers/lib/websocket-provider.d.ts","../../../node_modules/@ethersproject/providers/lib/url-json-rpc-provider.d.ts","../../../node_modules/@ethersproject/providers/lib/alchemy-provider.d.ts","../../../node_modules/@ethersproject/providers/lib/ankr-provider.d.ts","../../../node_modules/@ethersproject/providers/lib/cloudflare-provider.d.ts","../../../node_modules/@ethersproject/providers/lib/etherscan-provider.d.ts","../../../node_modules/@ethersproject/providers/lib/fallback-provider.d.ts","../../../node_modules/@ethersproject/providers/lib/ipc-provider.d.ts","../../../node_modules/@ethersproject/providers/lib/infura-provider.d.ts","../../../node_modules/@ethersproject/providers/lib/json-rpc-batch-provider.d.ts","../../../node_modules/@ethersproject/providers/lib/nodesmith-provider.d.ts","../../../node_modules/@ethersproject/providers/lib/pocket-provider.d.ts","../../../node_modules/@ethersproject/providers/lib/web3-provider.d.ts","../../../node_modules/@ethersproject/providers/lib/index.d.ts","../src/gas-flows/OracleLayer1GasFeeFlow.ts","../src/gas-flows/OptimismLayer1GasFeeFlow.ts","../src/gas-flows/ScrollLayer1GasFeeFlow.ts","../src/gas-flows/TestGasFeeFlow.ts","../src/utils/etherscan.ts","../src/helpers/EtherscanRemoteTransactionSource.ts","../src/utils/layer1-gas-fee-flow.ts","../src/helpers/GasFeePoller.ts","../src/helpers/IncomingTransactionHelper.ts","../src/helpers/PendingTransactionTracker.ts","../src/helpers/MultichainTrackingHelper.ts","../src/utils/external-transactions.ts","../src/utils/gas.ts","../src/utils/history.ts","../src/utils/nonce.ts","../../../node_modules/@metamask/metamask-eth-abis/dist/abis/abiERC20.d.ts","../../../node_modules/@metamask/metamask-eth-abis/dist/abis/abiERC721.d.ts","../../../node_modules/@metamask/metamask-eth-abis/dist/abis/abiERC1155.d.ts","../../../node_modules/@metamask/metamask-eth-abis/dist/abis/fiatTokenV2.d.ts","../../../node_modules/@metamask/metamask-eth-abis/dist/index.d.ts","../src/errors.ts","../src/utils/simulation-api.ts","../src/utils/simulation.ts","../src/utils/transaction-type.ts","../src/utils/validation.ts","../src/TransactionController.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/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/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/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":"f31113ac9492fdd6e78bf6151b338c92e5b1837be426ef4aa0648ce82d13b518","affectsGlobalScope":true},"62a0875a0397b35a2364f1d401c0ce17975dfa4d47bf6844de858ae04da349f9","ee7491d0318d1fafcba97d5b72b450eb52671570f7a4ecd9e8898d40eaae9472","e3e7d217d89b380c1f34395eadc9289542851b0f0a64007dfe1fb7cf7423d24e","fd79909e93b4d50fd0ed9f3d39ddf8ba0653290bac25c295aac49f6befbd081b","345a9cc2945406f53051cd0e9b51f82e1e53929848eab046fdda91ee8aa7da31","9debe2de883da37a914e5e784a7be54c201b8f1d783822ad6f443ff409a5ea21","dee5d5c5440cda1f3668f11809a5503c30db0476ad117dd450f7ba5a45300e8f","f5e396c1424c391078c866d6f84afe0b4d2f7f85a160b9c756cd63b5b1775d93","5caa6f4fff16066d377d4e254f6c34c16540da3809cd66cd626a303bc33c419f","730d055528bdf12c8524870bb33d237991be9084c57634e56e5d8075f6605e02","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","75b22c74010ba649de1a1676a4c4b8b5bb4294fecd05089e2094429b16d7840c","5615ccf831db2ffc82145243081ebdb60ea8e1005ee8f975d1c0c1401a9c894e","38682ed3630bb6ecdace80d5a9adc811fc20a419f1940446e306c3a020d083b9","cc182e6e4f691cd6f7bf7cb491247a4c7818f9f1cb2db1d45c65ff906e3f741b","a50599c08934a62f11657bdbe0dc929ab66da1b1f09974408fd9a33ec1bb8060","5a20e7d6c630b91be15e9b837853173829d00273197481dc8d3e94df61105a71","8d478048d71cc16f806d4b71b252ecb67c7444ccf4f4b09b29a312712184f859","e0eda929c6b9b628cdeb0e54cd3582cb97e64f28aab34612fc1431c545899584","9df4662ca3dbc2522bc115833ee04faa1afbb4e249a85ef4a0a09c621346bd08","b25d9065cf1c1f537a140bbc508e953ed2262f77134574c432d206ff36f4bdbf","1b103313097041aa9cd705a682c652f08613cb5cf8663321061c0902f845e81c","68ccec8662818911d8a12b8ed028bc5729fb4f1d34793c4701265ba60bc73cf4","5f85b8b79dc4d36af672c035b2beb71545de63a5d60bccbeee64c260941672ab","b3d48529ae61dc27d0bfbfa2cb3e0dff8189644bd155bdf5df1e8e14669f7043","40fe4b689225816b31fe5794c0fbf3534568819709e40295ead998a2bc1ab237","f65b5e33b9ad545a1eebbd6afe857314725ad42aaf069913e33f928ab3e4990a","fb6f2a87beb7fb1f4c2b762d0c76a9459fc91f557231569b0ee21399e22aa13d","31c858dc85996fac4b7fa944e1016d5c72f514930a72357ab5001097bf6511c7","3de30a871b3340be8b679c52aa12f90dd1c8c60874517be58968fdbcc4d79445","6fd985bd31eaf77542625306fb0404d32bff978990f0a06428e5f0b9a3b58109","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","e2d6963e7bf7186e30b7a4c9859aba4e96eda6d1be537e5b1a43bdddc7e9dc8f","10afdd7bba6ec9b7f95a4b419b2dbb64245fea4a61bbe7d68e2f841b414f7312","413121b26b3bd9f7fea237f15f564ee2b95bcd0cceec1b1621075375ccc0c0e0","d2af215963d01cef397ce8fa2f7ad08ee8beffdd39fe14b96021ddf26554b59f","2fc9848431d0f5e2b49bb312aaf07dd2d5a34300a2ced60200a2da49e6a82b43","c5ee2b685431ea2b9aacd9bb9e15cac1ecfa5c448972b188432313354d47c848","3e69be1137d88eb0730332aed359caedea4a27903da15dbe6a1615fa11206807","2283d079c3945b6e5ca8b9355311a213e03b74bffc65a3234c3c141a0a795700","f47272f05bd57f4356abc81232bded724d13e54f0fd801e0fb93a58237db1829","07ae8e9890f49ef6ebe629e339ac590025606a1e96754965bbb2bf889199ced2","d5c19655468e29f60c871b21e73af8ebc653f736e7123ade916f22c4a5f80ce5","6a8649609161e2794b383ba275b0a6cb4a072dde7b954648f83dc6cdf1bfe4a8","601d4a40a69c782addaf84185d4547568ec072095ab9976610d89922d1291f8b","d5c19655468e29f60c871b21e73af8ebc653f736e7123ade916f22c4a5f80ce5","b5c9c8c4a2cd1cb9f76d849fb472d475c3cebdd48306414a4a19bd11d82c4055","b61e6a808f5f50873ac03f35d5a38fa8d4dd23a24f80ab69df1a032e8c71562d","c8be9283a381044a392a0687af5d98d3f51cbada2320b1801a82c948b6e39499","85052c71d72b9b017c88179f57a464d66e22619c7acd7d83b117a79cf1608979","502cd7c30fe21e2c36b62913d0cb5d20affc8779b3ad40881b26d90a22de3aaa","6d3101b183ea67ef606b93fe42127f30b2db5ac3b72c34ca9d6d8b00eb85d0f6","f5d7a36ff056cc314b0f61c89a03c4c36a24183b246e61d958e75e86521304cd","ff30e8237e23dde68041b5f09526ee86835b12c5d8b6421c1153093fdbeb9438","f516fc1e77e5ffd71fbe705b679797c3c5eb76bf76a88549e6316a29f3e197f7","b5b1110565ac688b660a893654a6c1bce6747f3aa6f847001a8a5ff4412394ba","3a971ea3e36685b96f24fbd53a94ad8dc061711b84e51fde4cf201f7041e618d","9b6c162d20e2ad4abdcff61a24082564ac59e63092220618162aef6e440c9228","7804ff981554ba1a0592481072806fc39dc1484791beda43eb7a60e16e70a360","fcc8beef29f39f09b1d9c9f99c42f9fed605ab1c28d2a630185f732b9ba53763","d6e6620a30d582182acc3f0a992a0c311adc589f111096aea11ab83fc09a5ccc","6213b8f686f56beab22b59a0f468590fd3a4c5fa931236a017efeca91d7c9584","c451cec9a588b1f105a5ea2c6063d4fca112b9d70105cacdadda0e1ef67e9379","cb047832dc68f5a2c41c62c5e95ddcacbae3a8b034d40cd15319a8cb7f25104a","980336ccdfc3c08f3c3b201aa6662e6016e20f15847f8465b68f3e8e67b4665c","5a3493939995f46ff3d9073cd534fb8961c3bf4e08c71db27066ff03d906dea8","8f333214062532989f190aed5f99c62eb820722e41956e8229e17cd246fbdd10","d1f010c19eb9c8190bd0859fa3b6f4975543b912b8b85e20bbb0b5bfbdf4d2b3","de4ccc96cef3f97fab148640799abb32a24b567a902a8233913f98481e3131bf",{"version":"801934aa449fe6df584bccdcc5d5b9280295cb7ac84918b6014fc5086e6f9ff6","affectsGlobalScope":true},"5e379df3d61561c2ed7789b5995b9ba2143bbba21a905e2381e16efe7d1fa424","f07a137bbe2de7a122c37bfea00e761975fb264c49f18003d398d71b3fb35a5f","6af760fb9ea02dc807c5053d8aee86389c4fce72fbb26af7b9568cac6c4710d5","c62c4ba5e910b4523f7e7adf4a55ec45c2bac99d9d8e9b0fe0c2a800a6f641b9","92131434f876fdd6fcbc40bd54a9d7500c66974362b16bd42641f990468587f4","8cf023c0bd57992fdd2ce6a7030a1874f49c8edc62eaffa9bfffcf18d2a2a1a2","8ea8f3040e38fb50d7dc3653f3b8a0dbb5244e82111576f99ce096bdc0fbf94c","48ed788ad126545a6156fcc37cd3bcf17de18a3e3fe6b6ef62cfb8140d1a45a2","63c271a745f628ffd4bd7ad0a63b021c362c9bd6bf8b18441a7162892395a214","8d3457e6c7c5cb890729fb60cb8db18f261226a3ea3ff6a4db4b84ea78313ace","9f9e5bae412fa5909fae636d6733aee27a108cc2ed5b13980611016336774d3c","662fe197bba64bd3f17ee118058cd2d0d2dbe33d7c0c865fd6365d90bfc44e1e","030519c351f800551cac2658038804969ca4584d2c0175a710602ac234ca1340","0278a6939ca83cd040b08ff8c5fc7838b6693ddc52f22526bf158e6b10e0246c","c2d6206e5ba4fd3063b01218c2b3b997afc1cfbeb49fcee991fa8595842ce53d","6a8096993458a3d71229031aa7415974eb5b47b320213e29660adfb519d6a3f4","cb7996a1af5b1d276483cd0c9b9de6540eff021abc90a720511ff4464519a2ff","9df6ec68878d65bc690ea3a33ce3ef5aa8254c36bc5f8346c0c2fd1f3b88a35c","a4fad04c4acc8a4b195cbbccef4c55019104753d547d5c94441643ccc89108a0","0244c23ea642361f7c192c1f0cfff9c12cfa5f51f9b155edd5c0a89fef308d34","c7298e68632ab03155f6de963d3d09cc4a5874c28a81524f56c667d8a052e538","3c69a83bde847af6fc3a53e1bb6b13cd06d38a27a142814b8dacc374f3b93284","5b46f7113f54565e7ffc83f2b474f557a1f54c7e5946769d5be220454656be73","fb58035d39c5759283cb73cfb3548aefe370aa3ad4e81fdb4e46f0979eb7669f","1311c325948b2d5576cebc70b1bf968d3446b4630802bef54120daf04ce1f625","d0b3609e8e7afed0fd0570152255458407e67249b94f6603afdfd68599423f21","17f4c5a1d6eaa87ea27eadcdff9085af3190533d98f799dda79a3af6f9a630ea","3e6f734ddf40e2e99ff7fff9568b7d9720663af9a0632c26a352c8d3270a3f0e","ec13f78303abcf550c5569dfae1446b8ceb89050f68ce04491481e72e8122ae2","a3fc57dbaa7f1efb010399ad4ef4fd9b462aa4e93bf74a9a34b099b97ffcc9cb","ffddd7ec6a450b0cb6f2f73f80de1df963ead312d7c81a8440268f34146ecb87","5d6a36ca0087fd6876df654d1b4192f0e402adfde994ad47e5c065da33692f9c","eb157a09c5f543d98644e2a99a785f9e0e91f054f9fecbf1c3e15831ff5d63a7","edd5530e2b1ccdf65093296e40a8634fcb11ecda3c164c31383a8c34cb04bc9d","9dfaf96d090fe8d96143465d85b4837661ae535143eea9ef99cd20df2e66338e","209d45c27e03c1417c42985252de6c25a2ec23abdc199d88e6139c88b93abd11","0ee5cdba58cfde3012bb9ff2e9edcc4e35a651373a2aa2c83ff9eb7df635419a","540f4dca27ea5a232828b6d91e1b2fce2720bdabaa4c1f3fbf59b672cc58bd8a","ba086b99d545ec6c9ff356989f076b5652ea1b09bcc65b87dfc43a5195a2efcc","c85d9776b36166b928ab1488d9224ebf970d41b0a35f09a3ee0b9bee3e698061","683196f606c5dab1c8c4a24a66d26e00f16f2d4b2a5abe25ebedd37d2954f930","9c3a1b01cba1238fb723ce06b6c163ef6c53be755394406782564d5c42c636b2","6e795e6270d39e918c7a0e62ac73793cda06fcf4b3692ee46583e15f5bf57ab8","0e821ef1eb67fa6144ea4de4277d913f5b1982d7407afd5f93754a8239d41554","5c09195ef359ffa9c6bbdb4fefb101d87ede4b9e9c28213faf5b45d102e4c609","80b4d93a4dcc90a12f6f4bb7c6851a8182ae29e556716d0d80b5c012a5ef554a","2556ef9d1820e0b6bbca6dd65a50ea64f525c4d8247ab50dff44c3f0d14a5643","cbd1c836db190d6e3add07165afc228f04e1f6170e1fe3aa5e6fc24a7e9573a3","9b13881feb958237232586d888a10a39d47cdffe3ee34688ed41888fa7baad94","122fe82cf5af80f0b26832b258b537b7dfe3ec28449c301b259ab10204b50d45","c467dada8fea6d60dff8a8be2675f737cacc76e14e50b72daa0f0710376df84b","9cb80bba611c2dd155a446ce424fe4bb1df2129751bc9416b7e42c055d1ddbff","6ee568039016b81ed70292a595ab781ab978cba4243a5fe49507040ee4f7ac8a","043783bebe87efb440183c9ebc8c4fdc1bb92060a5a0f7ce847e30dee7013ac3","e3dc0a97a59dea936b4fb7b1f6f4117b4aac9c86d0cd08b69bab2d0532a8a5e3","5d897601f8a4fe913057019d8211b99b06e3138f625a0cfb601d074f4278271d","a68bb369c4ba8ab43a78f3fad2d3ec130e1418bc946521b9c84e9b336d6e88f1","65f219e6e1f9d27c677a49d41ae7989b83bf6baa56debbeb50d95c3ab21632e2","cfde5d194dd858ad68f910defaed5b0d28730f8bf38359a9265a93ab29bc7bef","c89354ae268153d965011e484150f0c92faa87f3f66507c25b496973178e0400","f20aae41b169cddcbf3fde8ac380443182c8d7225194e788c404d9e11e6dc75d","a6f4816a634bb1ceb513634c1ef7c0535f461ed2565336eed69f6ac2babbe15b","c48566cb13403fca44192b4528e3f2ac993869d39526bd42cd2f2167c0285add","efae20e0c581240c7522e04829da4f0453ca263068596554d4b0e27878c7dfac","3af68ef927788cda7daab34be513fa4508229fdc6e5130d564a0a1ccb3fefafe","bbbd2cbb15a37d5f4dd54ad8c7c537d3df8352117523030fcec7dcbe62a05a58","b50d24ebc117f8805332e7e260e9587f572bb7b2ff0ca1ff6cfafb38015781f3","5cc8b8e18fe7fefab4b3c53a39467b5a0deb4200abae7f063ff0624b9e856c51","8e990781eb0107c25429b1274a31a4f3866a9a46290cce40f354b2a6e71c6c21","608c45069e89c4c8f0ab29f896cc93c6553808072d6304b23611b6c6de3c24bb","22cbabe752781b5f35482af9d1fcf1455cb1ece74e8b84700d4abcb44abe3776","b9ce4613536386a98897f1e3d8f61a851ce6cb34dc3c9db4f2ef5f55f007e9e1","a5d1209c7bf277af86281392d46e12ce3dd6052586053f757fb2e606cc75c0f3","31b5f53e3d57470830e87f9e03c02d4569ac81d4a758fdda75092f9a3f58beba","d765fbab22fd7003a65ed670100362ec1c90d55a772e6773a774135594e7ea41","c1f11d9b42bfb0823d34d93c58df91ffb6690b5a717b7d310d83f258f1784e58","775b207f00d4df5b3b0b536aa696d572cdd2cabe8ea18dd28e8b52f691fa2a55","f75cd30f162c2af5e5aca39c01c1a521bfa034fae523793de872815a3468bc08","0cf1123db73dabd86466a462375a6addae52f58d23030c6033f8aadc23539a36","e29cef4158591ed213b1c2cba8988237b1ff369f7a6ecd8cb8ac0302bad1fba8","5307876e4d0021ea01235eb2f7c24671f3d8b37590f4b446cd132a4e1dc9a335","92550acd737790dc60c4c130e6aac78656dd48a8334a4882f40e7f86bdf7a590","3df821880914f8bb3c8107b1107be75c8ddbe2120a2cefabbaf9b65936b5f4dd","f46ba7c6fa7fcc8b3d57c4618c18db3f4d8bfe1fcab5551d7f6d9a82cf4d6078","078b7043bea0968860374bf4671ed74dd9f6be4e28ab659517d81f74be463c51","68b139ebb9a7f3ee4ded6286d74f978a47968727665120f3bfc560476ce33c4d","56d02c29b2fd39b1b1a1265df291f3f98e6ec3e6119aff9f4cfa44fe888efaa7","2d01884891da6495cb4a2f060e4898209a507e711464c4c1480df85264e863ed","c485c6497f7587314c4c4a59b74850cbca4c0c4bc08146a918cfd237ef821dbb","e9eec004735b1bf7015edf5400aeb914a53132134d230e93786590d904d094cc","080b1aa93227952b4dd74b9d2c6e4f6002eb8403533749116a1c53bb9961c02d","874087eec1d457f6e3baf5ac46c42ea200e55040b394fac667aa3a64c49f5f6c","6e8a5b04a18abb192abc89d7219b9c6f633cb3136777ec808673a65f111ca749","4e7ac7e5dd58a6c29c724728b031669e3068b194b62c2b83f92e76a36cb34dbb","d74d2a92b54f95e47d2b76bd5ee516aab7ae93afb79cd34c6681dd29eb09e72a","747e6326a724bc54f799a466a5b5c4978a601a04a063a5bdabe150af2f25b9e2","b57e22e53b56cca7a57bfcfb234aa6a66f9b9e4c07159d7388f94f17a3eaee2c","e47709ec4d1618ef429648cd8ef967aef2005526b34fcbfac33037add347dc71","b81abb3e47fbbb3af41fa75bada89bbcfa4b0feed9a0d6d4b19ed1ce1033b53c","15b330546e9784461058e5fd6e2346bf272140fa6f0cda34e193ae501d8b17b1","4d8ce72fd080bf9a46bdcc274bcbacccedd66d84e203966b197ac25a96932183","73327e6ae34e3f6591877fb75b451cf620cbbd76ee2b678213a9f793633cd0d3","3f1ba2f69944fa346789db7f60d53c9bec00032de0d797967978dea42e77b941","3f5df31539fee4816b97d4e45b4344fbdaf3ca59f6df941f8d780ee441e92cc1","50aaf44eb4d0e086af13729b3471a0a7dce95ea35ebd21c762ba26e203134b2e","3857c1773b8503c3ca45b7bc09ac89c3930c85ce93021054503f73d5d9101b5c","72702bd07fd6fb3ef64aadbcb909103aadfe71ee76e9fdeb11e0c92693cff6cb","f0dd6f7c9783637655478db7d7caf6becd41a79d54482aa59578ce88ab38e9bf",{"version":"cd756ccdabf433dd02b84d755383e489f14b3c1aede0477783aa04830fd5d695","affectsGlobalScope":true},"a4c88dbecdf8ee0c79f5b7c2bf31cd77e593f5d78384e2b674f67d754a549a9e","9cbdff04326da794ba008c0fc977ab062d1fe3fa2e9759654c72ffbe54b64a7c","aa60f8d20d36116fe05edaab24adee3c275209f71b65e272692cf99daf9489e1","150855f967a6490161d5aeed4cc4adf31fcb8f5dbe54b75799c12b8687fc9cc2","79576487ac18e047e8192fc582ff488ce375fe4df0cb028a17f831cf42b976f2","47ddb601df40bfa01cebdd06ee8b87d0b72aa1259a4ceba3ad3b5cf68130112a","6b6392704ddb3f50e647dbbb716782bdd0cf8ea9cc134aae256a26223e632b47","afc3ad2a50f7f4de908e26fcf467e09ab8528c0e90f91e602b4865d953839228","df90b0c6b1d81851364c4d97fa23b91a993482bcf4a7bed7c7a24aa41632d494","db34610570eed46b8b72bc662a91261200b8578af0ac02781ce7d9aca99bc683","11ee9ab699b4619d217c640d917ca198f58066a86bd58c2917197d62aa6601e0","cf9d589d9e73bf32c8e7a6cae6b4a1cf9bef39e5594072533fdce985581a6ddc","959544feb1ca2df29eec6c500f27ea10f4885df245ebd8418fb4b87914614383","6548ab4b57eb9d092471a04513091673345f2fd95d5b876f600402ea8d603ee0","2793e8c6a023d26f78d6777a6d7f20fae3a9a8169863d46d8d54c73071851232","d0f11e830aa1350a31d9c00a0197243e9711e4882947aef53a96c629f405cb10","6610b9f45f1f71d2b1fb67df49cbcabe3f9e668a1ccb7d8328a51407b259ffb3","abbcc437e0792ab2fe08797ceca1ec85a95ec413c51612313b18ab8e75f690f6","e29d76ef1183ac0edf94b4712b6e51730c447c7e773e75ceb44a720b0c9a9fd9","4ee6dc3424998eede9a2a9b114acaaf7969cdda67baf82ba2c9cf88a8eec0ab1","8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881","25139d6a726e0e19d9fc4fa3197367b4a82ec34a08a5ecf23963e142c202c0f3","e3328bffc8eab74665a4fe9c59d6f12f4c8570c3d858497e241eb37efe17dfcf","29389551e426a46421134b55182d6fcf5b143670998bf81db2619c1228235392","c18f7e16982695bdd04e3e183a327d116185f77f1a37b9b2e849d7d93269cd74","2cfb37011989c21dc70b91d521a2d5a4e0f18507f5f536b5dfe504edb15916e8","bb5e02df7aaec7a4ea642639a9963b24b8d9fd6798351f07d8c58616942fbcbf","299a899cb4d061f5d83843ec453e936e9659b2c435746823f90c40eddaef4745","d5610c0fd12870f644b0f42c1bcc4fa2295ac3e3ca01916bdb42c3bdc4c80c36","2c56a8e249b1f45dbdf973100cd37fe2ea68709573cf1fdf2e3052c593be68d8","3553da417ee7b07e388b13bd12a70a1c03e65a6132ba5427fe68f5b362373e6f","612358502042d351c227ba779fdcf6d875d827e424930e60297c533524e50668","d2b5be376ef162aa0c24a826e7dd2d77671a045c085e16d1c1276db4bdccbac7","c4138d8dcccedaff6621e009cf0a54a7bed2a5ad4c509a3513bccc4f417ef939","ad8747fe978dff3e80f4b12b48d37cc8dff11b61d04c035aefbc982ce21201ce","b154f789fd65298e1ba6cbba6944ea892d564c95f3d3700ed85baf8f80748473","c660265aedd7c5b236e2017e53095cb98da66200eb0e8d023b5bf713c36494e8","0efc36bf5c0daca6217fec7063359ccdab8c3a23bb405d25340fae22cf72d74f","5abff0c87d4f9c89715107042d4c73b68ef7a128759f451c8a0fc450cbaaf660","5a03308fbd1af441065149a84c692931bebc7e7735afc23be8684f4e10d3aa06","c787bf4f8f0abbf815cfbd348be41046f2b8f270be24fe7aa8a8fcdd2b7df8c2","e7a5191c663a3228f30104961d548b372e51c5936c01ffc8eddd262bb98d7d7c","43fdc9abe6f8640fda4cdc55a1ee5f666d3fce554277043df925c383137ddf69","f0b09665c9d52de465687fbd3cfb65111d3ffc59ae00c6f42654150f3db05518","72f8c078d06cff690e24ff2b0e118a9de2833dcebf7c53e762dcb505ddf36a68","9705efb0fd901180de84ca4dd11d86f87fd73f99d6a5660a664c048a7487e385","f9b9d0950fdfb90f57e3f045fe73dce7fa6e7921b37622fc12e64fcd90afbd0f","e61b36e7fde608f8bb4b9c973d81556553a715eaef42a181a16ddd7a28da4ac7","03b8389b222af729eae0fb3c33366dcbb1f5a0102ce319bf1d7d5ee987e59fd0","2bf6be7c04db280fdd9b786764f8650c23f9f4d533791cb25a11b25314b76a55","dbb5fc7edd36bfba95cc4dd564e4458276ced30eed18bc05fbda948b3fda8686","c2b556c7cff0dabce2e31cb373ac61c14d8ebc35f1086dff30b39e9ec5357d0d","f958af01131076e8af55d28c4835a51063226ab488ca8738fdee38aeef7d0d33","9f3797b01e3d83d4e4b875699ae984f380ca86aa0a0c9df43ac5bba1cb1f8b7b","752b15ad1b34887adeaa838fc55f5d4ca399026afd266d4ed4db0e3db02eae4e","778331eaea1093451e50be9844bd2b6937c3bb81b0b1ee700624c9774ecfcf2b","0ca0dfc9f657d0822eca9530c7870b22a1d2a5fc48182bdd4d0e6e88e4ad9c35","5c746f034288e6842dd1589b169dcfcc16c5ce5abbd928889ab67aea4fe0b501","92ce6dbbcc135cffd09a58e19fef34bf351391bec92c40d849e4e9997d475769","99e77d092fed72b6b8578d00c7af004f76e98b30ba99f1947535eb4c04a51676","b5ef52a9f9083724decc5d060f0b34e3a480deed71c32d55ca16c214eb4cc928","5d3d7938b2d7d0a9f851276741327c2ae4c013e7eb420fc3f7caed3b79c8f37f","14df6b81e50a035e9e391558cf30a0420d03e2eb42c7db9c57f44b818e5d5179","f100912a3785eed4a3d29c12f5910b101af9353454de5ddba9b4d43456c56dd1","446439eacf81a163fd7dfc53b28f80deca3d13b250d67639739aa25aa4491090","98034cd285344125f7165a3bb68246d38ab35fabe7f6d6a7c8f80407d31f548d","06b4a23064991251512df4edc12341d5bc69a17b942da18372312d383c90eee7","0f898802705f9a534b537f1be6c57265080e0abd6993d935554c255e6d56cc1a","745efa7b6e27b7216cccede166a822b56acc41b10a8090966c8cf2c96239cb83","6ab2a6257ae7bb05559841100c786c845fe465a90be7b904db9096c2fb14696b","26958d6f77e6db2425ca65df0fbfaba439396ef7f4457f5643fc32e4b62568a6","5d697a4b315cc5bb3042ae869abffd10c3b0d7b182cda0e4c45d8819937e5796","89b040dec8fcfc1de98827e1f4d4977e6ff5d3302c6790e9f10b54b916e1c742","6ee58aa536dabb19b09bc036f1abe83feb51e13d63b23d30b2d0631a2de99b8f","8aceb205dcc6f814ad99635baf1e40b6e01d06d3fe27b72fd766c6d0b8c0c600","299567f84bfedd1468dca2755a829cb19e607a6811673788807dc8921e211bc9","795d9fb85aad92221504db74dd179b506bd189bba0c104426f7e7bb8a66ffee5","1311bc194e0a69fe61031e852c1c0b439e2a2a3d1d5e2d8ff795499b9f283459","4b7ce19369d7e7fae76720c2c6c7f671bf3fa0f7093edb864f1ac358ca7c456c","c972ef44deca1fa8fab465915ffa00f82e126aacf3dfc8979c03b1b066ce5bb6","30285a1011c6d6b52f3ba3abb0a984be8148c05cdefb8eb6eb562335a3991f35","e0de9f50e80fed1cc161b50e8e68dc056e38df75a4ef667a06b1922e372de169","6a8b31be08b212d1fc96de0ddd1ea49f32382ba712fea24c70bb56447f643f82","19ac6d624e4c18de4584db4bbdbc55387dbe3d19b3c134e50346bdf165658a17","54e3798c2801e8f3bc7a825d3d26c6a80ce763e19e6cb0b714594c430ef72332","70b8333214aadaccda8d38435911d3e3a686e503837dfda6b8c3f8c83e05729b","fe849e916564b8172f31a547395516668f3c122bfe017f82e7140d8dac402208","d42c6e985bdb10a2aaa3dae14d9b0d8589e74a7c2f9475bf543b855bb3c010ba","56c48fb5bb6316dfc27fbad065966b4ddbc38e9a0a1a5060d19b5da405ae7d6e","7091568b9a1b74b699ad09df6c130db712ed089d173a235e301a7a7ee0a4ca44","de33aa2a38affd9e71297ef7ec001a4525502878b09744308fb6518159f77d2d","57476e482c9b4e152bd23d0dc3c29375e48afee0de775674a3c1ea63cb4cf843","3ec4ecf6502ebdb1f3e24c712eb70160c606214ba2e71b4304b5a50fc2e4f293","83f7b6c1dc91deece32c3bee746a43f3616b7cc9f6510764bd53451f6712ff25","c23f2e8772304163fa7e4335be11f3dbdfd720d2209057566b7dfef746ef1862","2a26cb78d3de9708cd656787a663902270c9421ef89188286c3b7ec89b63bb15","e61fda2800677c210116c397dd85079a0956c87fd714826c08b25b10fdd56546","ef7bdfb4f157f9c9b9bd7f5766f0f8e07fac8e7482eec071673f3c9d08082982","d2f2ac1436cbb7c8d122cc7de96521345254e5b36591d9d064d9763de2a7b254","3cd2ba07285d01224f9595924dc7f760c7babb386a6eb825cb551f8d829fe6fa","3ae9770861c2ece5849778e9f15567d95b87df0165c0a5b1312181df19458a56","37b51656ff8302a4556e29c775f311eb9ad813948d2c527405cea041dba3baf3","00abf32ca3af92f8be9ecbc9b63090b4909a756317d791927a83cffd24e9c8ac","cd28efe88fac7a92f3f5cfc7dd3c764f0b31bdaaa061ff044de1639810d6a7da","8b2100d3ba68063b7baf5038f26eefe46543dcebf1e7dbaf46277f24307cefcb","131b7f32092aa78e12fcb2a6db7c71e17f85e076c0635ad8d991b80d10130c19","d1c84af1e6d5fa4a5f4badd45b03b67c9255a675df235a3ec25307a0f5524278","aa4d6dc9282133162a76109d99c5795583276c4fd27284f128d484acf12b0841","3355c4c572f076ad963d95f0e28075b8558e2ab492c84eb94f9e2c48f1c2368b","5638cfd48b0c56bc9ed0c779d53a40b92c9cd9c9d6312e3a21c52542d38094f3","827eb54656695635a6e25543f711f0fe86d1083e5e1c0e84f394ffc122bd3ad7","2309cee540edc190aa607149b673b437cb8807f4e8d921bf7f5a50e6aa8d609c","703509e96cc30dce834ef8188c958c69306473b8a7e5cb3a6f324cee05a1f7bb","900daf04dc607dc3858c0f976d6f9e17b829a07de58d62dc6f730eaf06986075","08e0ac95e650bd4c87145b6ab2257b70c06254bf76a0b9f8a7d60c51fb8ed6b8","4b57ec505a035491c692b89af2c6902c312ec22f8fa9b6dae3e93686659fb7e0","7d796672940d3b2d37f2edea4d7bcf4c7993966286006228cbc8fa35ac92871d","132fd53917ed7f55275faa52c35e4d4d41e9576fea231d12740b723df2bade93","de2ecf9b1d6f60338f7b59b6f593ef77af9abd0e70ba8f2942953d0c6e1850af","cf18e9d003f1d3d1d61a04eb2d1cff3e8a8cf9cd306d0532ea82700069f2fc42","393192a39f26f9247a74ecbaea6668972af8e9125c955d1798234dceca6010f7","27ca878cf70b3030e8403f51ce65949d364fa776d6dae3527f91635a40836672","178e2de7a8702742957ad24deaeddec84a48cd913b5d932b16afd2a707b3e416","a45ee7555d019a67fbe092898d1aef0b1d02a9f6679ab84461ff515b4460d706","29c188a2c660f99f1b4835022e011c4268d7af989d4b7dda33c0a69ca1a777f8","1ed0bf138e87912d741e28333b58cbf814ae863783b3b404d2454cbabb9c5fc0","3452ee7d8ef0b1bbd47b2a56924a1dc3c79dc84a19d212e9dc496f92e4943aa0","8c95f96ccd4be0674944077aec1e4f2cccd515ca06d4327562dd017250e7d3fc","6fe7571c8a80808224648046008d1366ba4e29206ac79ce4c56d6fab3350492b","a98be76d8c257aa9e316bdb305b8c4228f0cf904d4b70547fc2999f3f99b5a01","7419d99dfe020d543c8ee736ab7ec17127d6a2c61c40e5f245c6dbd3fa6eaea4","2495815b16258136f98d91e441f4462f9b694520af86bb8c8373724cdc410096","a64568c16a5821575de4f6280ba1ea4686a1ceecd649fa90ba957c8b1b007013","ac46f284c80582f7c1284eef93f2d1c80add2d3b0e8a4076d6ca3db58d3af747","dee4dbaef83bb1061a44f39a91a59300d3dc02528eb57f748222235dd8e02159","a39c32b055d2e6103e5c49b9aed2d7bb5b06571c98fc31105264d280431bdbd7","618ebb93311695a13844118cdc9a7314dd3a2c6f35092d87f76828cac555ddc9","d36c3d116ce59a3f072c0014f0c020c76e916ba906066ddc4f193f546a43bceb","9bed8447acaa89be63540ec500b165442fcb0de020015175b5a5c66d42a61c4a","f128a2d1209d243ba2f7755c2fc313be2c7569fa0d9b4dc5cc60714fb0cc6634","a17e6861b709149f29a2bd896cee94526df2f06b24a2b60614b56649b5e9aabe","9c79ace686f720f4dd833740f7190e12cdce363362c982c164745527a412ef40","439850ca5075c6db55487b2c7fb27a6051fecbf180eee0809b67bb2783a89813","75d48857bc4216880443a24d985071262bb8b89a9952c77fd430cb0caa21f9bf","33e40cf77499b3d9712db82e15683373925e85817dbe82a24ee0ee6e44bffb70","d5bbd453310990e851908183fbbef9e6e2db8e0c86d97b42b723fd5238f71c81","95e76bed30f6e993e1fcc1b90a4675682e4800ae43403547a775d6e3c7ab2b0f","8b206b995edc6dd849b85c1c56531b9780e3ba75302fd02a2d173f008028707e","97040b190f0daa10cf9a15e51a2fac66b26ddefd7b65998bd6027d1dd67647b7","877c25dfae100e555014e45d1d80364496a0c876201e5dea91a0fd0a6a4ff765","d53f9f96afd41359edeb2d5ad98559f3bfad261391d5aef95320fefb0c6a8742","23d98226adf3be74e1f0470f85e7fd154cd7aa979d60b43190a7437f0d0426eb","639f9321a98b734242a3573764d7f1de5369b0b0b10c768ae37639e8bda5dd03","a42c39d8b7d1b1eccb69c7919ea60dcc2670ea672a0af90b70a730974ec0e9fb","dc5fe5f6b39c3fdfaeba333bcd5f0cc98bb3068797a4d7010f585366f549ddf7","4a3ab8cb278bfd1f18f24cc45a02188b63afa6aef50035df6d79c4638f24059a","e724c9ce92f2a8a31ed260764c5455852a13d292e2a31d26acc6840ec0e83208","40220ba1b091aff0cb20df5467202b62af561b09fcf3b24c22a60066d46f9e62","30abf588759f9e828a94f0c7f031eae094bb668c6dd4d902fa296779267c05c5","bd875d031474860131eadb42300aa57a71527bbb2b239d5b31ab6a9e352c84f0","773bf9af93b5027de9b5b4c779d5cda35f0eb92c7f43a97f2ef3ca081495a191","617f2b4f5115969c7b0f225d4962e6bec1cec7e5c687d84370eba4931b7dd047","59625b1fcc91f2686751fd5b623126f434d7b405bd8d866a555963ce2ac69846","5e0dc1bd24b45c46f2188d2f7f4b67f311610c72b706f963c5bf04c2e1fcc35d","fc69ffd599d3e525aba38f80c7cc2ecd187dbf148287364c75f199c8294a00e6","2ad138be6972de52ed966e6402aa6403af79e9f183486e0a725ffa075a0555fb","480274a4f75a7b3bd5c697a55e1905887b62a928592c0db3c282525fb235ba70","967fb6e86b55db228ab50c81f85f39d6a23a0c15bcfa6e19d255e0952d33a65a","c39e7d32dddfcdaa93b18b99fa430ebb1d6ba366459563d400add22f92e3644b","e3932de252bbe43132ad3226865b2a376ad945dbc1d767540c01b7bddc6477c2","b2f52f3cbd863dc4e690614b5cddbf412dea435d0de099db6d8adfd3cbefcd65","557c93b35f3b58e6844a9b8817559da1e0641f7f08f918e3cd1a8efee126746f","80ad2ae93d57dadac5e377ec6743df5e0211ea30bafd4b648c52366af057bb2b","07f90213b5800a0b43a6d6f309517dcca5afc6ffeb4bed396878a29fc5d6ceb0","bb0e637020f81cb40d16f202c3a783f0e269e29547fb84ca9f187a5ea8556965","462da802b50ac0d94a3c8f7f58a6a0aa08108bfc1394449ea56f1e0f63f5132e","2ccea88888048bbfcacbc9531a5596ea48a3e7dcd0a25f531a81bb717903ba4f","b7eece07762a9a944f097ee05d6f6b642b1e2dd87e6fc1b09600d881e5475377","1916218868966583a3c9f18501ee78929cab8450ebb8076ebd609873c258154d","98ca5ae10ab02fe747a7a53138f43525e0129aa1107892ea4e1fe9c99575809c","9760678d20c9faa0d0e1269806bce578bb76598a4a188a4d3987171263be20c5","21f706150e32f03ecd1714d7a7ac55ce3caadc7c7a2a960ba57cc5d39ad84c9d","6954ec87361b77bb8895426909fecfd154e3fd72a2b82f681c8bb15bc46f2389","da1963f37d566ff9f71bf8ca5c628656bae02fc9509050041547e9c7063cc58f","57e4bed825036f7f1328505bc512af492f28b1b57a48f1ff9b6d90b930041a52","3ef0957915b7719ac58153eaea6ce810ff8688276e570f8938455f3ec7930df7","05e0ad043fdd4e2d4874a97bd716174af64d63e43851c09830c00e819a80d395","2dff0ed1eb2046fbdbc2c13914117e1ff1112e217f90542ea5e7f41e39f0393e","a0ba1e2711c2520189ed980225e7a429b0706a1eabf9113e53f0e72550a1b23d","169b66aee819a4b165c397b832b31691f0be8d35cf8f2ec6364c23ee727b20b8","badb4cfbfc6eca3a038be22c76297bec0b5c1478d8b73d60e8b50725b7dcc15c","21e7e0eddddc112f2b891d1066eac74680291db768d3ef9b908965935380ab98","489e195150979dc291520e6f3289f055516cf342f314931c6b4553aebf2859bb","516efe800aaa0b7504b71f2d7e7e9bed5f28eb6c9c739bfdf237f09c7addea46","10ae729013e6620dc937df5dd7077c34e29ad313a28aa75cec39957640cdc8b0","ac5f95dee5e4787fa7c68a81a052cdfa0e03adec8331d3276389384df36cb849","0aaa321f1f662ec931e55c85867d316d8af16b59337111e22901516a0e1caacb","fab58e600970e66547644a44bc9918e3223aa2cbd9e8763cec004b2cfb48827e","a120dfb4736e6ec4c78f1bff5ff7f977d346152e6b7020659ee1ce4717f6f66f","9eda7b58498bed72dd98ebf1d6f8dd3bf5df5004b2f91c610093bf48f970c615","8e7adb22c0adecf7464861fc58ae3fc617b41ffbd70c97aa8493dc0966a82273","755f3cd1d9c1b564cff090e3b0e29200ae55690a91b87cb9e7a64c2dbeb314d3","d6bb7e0a6877b7856c183bff13d09dd9ae599ea43c6f6b33d3d5f72a830ed460","f1b51ae93c762d7c43f559933cd4842dd870367e8d92e90704ffa685dd5b29a3","3f450762fd7c34ed545e738abccb0af6a703572a10521643cf8fc88e3724c99c","bf5d041f2440b4a9391e2b5eb3b8d94cbf1e3b8ff4703b6539d4e65e758c8f37","8516469eb90e723b0eb03df1be098f7e6a4709f6f48fd4532868d20a0a934f6e","d60e9ab369a72d234aac49adbe2900d8ef1408a6ea4db552cf2a48c9d8d6a1bc","0ebb4698803f01e2e7df6acce572fff068f4a20c47221721dafd70a27e372831","03460a54d0e0481d1e11097f66ad43f054bc95efdafe5f81bbc7a82be181af75","ded24ddc7157689a5aa14bd651272ab8cd6e7812da2196a65d8c5e63131cfb23","2cea9689efa8591732096235abe7f084fc29c92badd5b0897a5e876b77e71887","4ed4e504126014fee13aaef5e3fc140f2ff7031ff3a8b5386717905820ea2d09","00e77e0bf106bc1e4f303ab02724df5173506d44acb6c22b4f582a88f22c3250","30d2170e1a718b5035611af55e3618b4ba8f42f0749bb52ee593da6082c4e2ce","568c86c9edf716ae08cd3c8ca61647a3eb43ff52a9aeb7c972f7be62cd35b797","a3b8b6be7620897d1e481e8650c980a210a138fceb6e710eaf95fd9dd0dfe94a","12c89d0e32758c120a569045f21cf5b77244f86792611ced8de7f86b37e77781","14bd47270e654c8eb3b1489fa8c095912ee62a0a29bb92743393203722347c53","cc8b411aec64e03abfc3936a397025c781adb89942ec2fcc66e2a353f93ce677","db5624ecf400ed47648e72353a0ebefd3293d2e6295834a3f013c548ecbd0edf","92cb686a9ca5eb5dd7d5d8d43a3707194c1e91ea07a027b3bcb60b6011b24632","da3ab7396ab4fe390f01091bd0d4c4a4e1e2a15a46d47446d6fb7194897d0f6c","66bbae6120d307ec2021ebd2241b8ad23f832b663e13363ca8b4c8dbc131a4e6","fb14266ae4070bd16db6b071e98887452bc359695c40742e38515a89dbc80a63","4a24d83c0d8f489465c4d38ed9fd87121c8a2cf50c47efe09c2eca93d39fa908","c052e32b9de53cd2596f196a0901801961bd7a31be9fac4ac2f117e4103e3a07","b15cdbb45919bc3b8e6b6f962d65382e85061d70bc26a968604f3dce4ad3a891","d6b58d955981bc1742501b792f1ab9f4cba0c4611f28dcf1c99376c1c33c9f9c","f0b9f6d5db82c3d1679f71b187c4451dbc2875ba734ce416a4804ad47390970a","a5c38939c3e22954a7166d80ab931ac6757283737b000f1e6dc924c6f4402b88","31a863da9da2a3edec16665695bdbc3134e853195f82dafec58e98c8e1bb3119","a00417f73bbba413d1345dd77252ede0bd0c957e37a9cadc9abb4c34cbd0eac1","90d1ad8d2983cb003d6f237b41c56a8f252f72071bbc53576e02b3c96d7ea47a","f3815045e126ec1b9d224782805a915ae01876a1c7d1eb9b3e320ffadbd63535","d07557f21b2ad690bfe37864aa28090bd7d01c7152b77938d92d97c8419c7144","b843ea5227a9873512aa1226b546a7e52ea5e922b89461f8b202a2f2a3f0b013","64b4d440f905da272e0568224ef8d62c5cd730755c6d453043f2e606e060ec5a","d6b58d955981bc1742501b792f1ab9f4cba0c4611f28dcf1c99376c1c33c9f9c","f0b9f6d5db82c3d1679f71b187c4451dbc2875ba734ce416a4804ad47390970a","a5c38939c3e22954a7166d80ab931ac6757283737b000f1e6dc924c6f4402b88","31a863da9da2a3edec16665695bdbc3134e853195f82dafec58e98c8e1bb3119","c0e03327bc548757709a7e2ca3063ca8b46227b5e13cd981ca3483035ef5ac44","b8442e9db28157344d1bc5d8a5a256f1692de213f0c0ddeb84359834015a008c","458111fc89d11d2151277c822dfdc1a28fa5b6b2493cf942e37d4cd0a6ee5f22","da2b6356b84a40111aaecb18304ea4e4fcb43d70efb1c13ca7d7a906445ee0d3","187119ff4f9553676a884e296089e131e8cc01691c546273b1d0089c3533ce42","febf0b2de54781102b00f61653b21377390a048fbf5262718c91860d11ff34a6","6f294731b495c65ecf46a5694f0082954b961cf05463bea823f8014098eaffa0","0aaef8cded245bf5036a7a40b65622dd6c4da71f7a35343112edbe112b348a1e","00baffbe8a2f2e4875367479489b5d43b5fc1429ecb4a4cc98cfc3009095f52a","68a0d0c508e1b6d8d23a519a8a0a3303dc5baa4849ca049f21e5bad41945e3fc","3c92b6dfd43cc1c2485d9eba5ff0b74a19bb8725b692773ef1d66dac48cda4bd","b03afe4bec768ae333582915146f48b161e567a81b5ebc31c4d78af089770ac9","df996e25faa505f85aeb294d15ebe61b399cf1d1e49959cdfaf2cc0815c203f9","4f6a12044ee6f458db11964153830abbc499e73d065c51c329ec97407f4b13dd",{"version":"99e8bb8d262bece129ac203f0c7436a07771e9cf5ba06a308d1b16993606eaf2","signature":"8705a9680ed4afb15edbd7bb9ee24af33060d1165117f293559f3073bf8d0101"},"ebf6e19cb84d78da20d022a95f05e8aef12e56f816a1ee12835a4da40d7b14cf","589357c2f88f1188a0dfc48c4c4cf4d22fac9f654805df5f2789a01b5616b74f","6abe62ec5b9b6a747c1a7687d58ff179cdfb61adee717b6e4882120f7da4399f","5c1301f550be26133f4fd34eadf38815db096ecaf9b75948b444a063097f496d",{"version":"26e64fa5fc9c7fce9daf4131f396fb5012dbdd92fb2e2bcda5aa083a76d18888","signature":"cdca22d444beb7cbe168d11a666b994be4b19c5ed7df1856612ac4dd7c2242fe"},{"version":"34ef3dd636b7074beec81346987a81ac245e1cfd75adf0babc68e6cb6c572ca3","signature":"82065c62b6a8089452cb40191a55299b2d0718ddce833446faa6c01f48f05b29"},{"version":"c1eb1aa5e32fd31d4564bffb458942d8caf500d86388c811cbb853c274e4773a","signature":"a7fe41f597b2631d3fb439d9b3ee32d1606c651a45ce2fa0d170a2614e68d280"},{"version":"327fd9ca522780f73a64e32e400a6c5bcdd89a5e706314d57ff1611bf1a99a0d","signature":"70b3082385b926b4bb0dbcef0b2f444c4f807d312546f27ee248d50b0dfa5877"},{"version":"3b1ea19c2b95501c5d8e87fe4c8044d204c4402a8b48f282bd348f973355f3c5","signature":"01b86f9481ddaf74b65f12e90ae2d5bedbc0e67e64e8cb273c7a1907cc66dbec"},{"version":"897a42f20db3ee955b1cc64506c040b0b1dcebe45d9ba3147e133d110f487f6e","signature":"3089238aed154b07430dd80de65df3115d268f21f1afcd8568a58d65c7827c5f"},{"version":"2d41bac312ef892971b2344a102feb99985e87f79edc18ed2c43ece97703fb91","signature":"2642375958909546f682d51f9c3682f553ae5f919f7b4a77d49262c200bca248"},{"version":"db3db9885deb334e6606785a0bfe7aecdcae172d36a6b4bd55958c756b92ac6c","signature":"79cdba32abf1fd279e588363d3048cb4b3d537a81530d32079cea1df22d66f93"},"1fcb8b15db812281d69a3090d488903f9e93033004aef9d8889ca3ad0753a96f","bdf5a95eb0a2dd1d39805bdf51b46ba012bb9b92b2ddaae16219595bba7678a5","9f794a0e8550a03baff865a3961cc22afbd85bc4ba9672bdda036971928f85f4","66a697d1e4cdbf25cdce4644a8085a8563041fa8c7731d4d9f5e8f22e66ba72c","a0c8e17f4d1ea2704c62d7349bc3b8d9a12e3761b5960cb44144d3f0333b3fcb","3471c0df3d0391e1dffe6e8bf150294531b2b71a2afa5f2b86e52bf84a5db60a","5d4df4de055eddf3187094f938a640f8d96e4c551a47d6686596fdb6ba4c3014","8bc2cad630da1033c1fd8d7df2bffb18af0da6113bd086a8bbec04a2471a1e00","a1059d1bbc8ad46bfe668b8450e7e8002887c4ab987bdb96d6108d8023f8bb8f","5134885e9648e2c6745f8aa1c3e7f5ab3b3617258b3d81ca02de6655ede3d74e","4f1ae3f24125216cf07c5211a3f00d2bb4782d7cc76c0681603f8249f9232ff0","d3fb92a5640f83f7844d60b35317a0f95c27e3658a749d76d218c461ad091668","d1f8bfcd91b284657ef8187c55ace7db91a3c43e642c3f14e54364154932f7e4","f54c92bfcae54f360fe79514746efce4870e4ddabc064e95d406bba291e9f672","175fd7186fa6a70f9db9b270a04a503cae23cf01cb77e3905bac115c38424cf7","c993f7ed1b8e1023c1f2ee5b262dbc3b70b27475674e40a53a58591f9972dacc","c914014ab7c7001178663f29d31a495398234a41219af61f26d7e8e91b46af96","277afd6ab6ec72889e2988e0ddd7d138c1f512e68a1fa4e90eedfd71e2097a51","c0908f85f2b645d375127a3b53a17a65f782e17962d5c1eb68f08b1188acbf15","3fadac5d409cc2f27b1d2f4e7568600f02840205f301c9ae7a3068b46476438b","da6aae64ad559286830fd44c81e3d33303348f184af7db4fde8dd99ae9749407","3633f87c97d359cb55fa7bf0668fb2be8a23342951af6ec2d06e6d0cf7409371","cc3a5427d44fc77ff25e80b3edee4650a51f83de761faf5e633994ecf1ab1b44","b350eda75c6e47299b36002b31d5b220c405c21c365e708989829db013fadbb4","f421882756b6714834ae4687ab1aeadf344a1cc45437d2edffbac020ff3801c1","1d61d6ad832dabafbf63b86c5a79d704f2c8763ada9318e135b17a3cb2d09b32","e5cef5de3e5ad3436d414d20743231e284733b9cf4375dc79eff4fcca4282f99","e624419ba84e33e661e89a28083119ca41f6953dba09a4f82b660684087afe6d","942be430bd0feaced2e3e598273b17e50ea565ec9dac840b580b0b99e1a3cd5c","73350006cec5a0c6b71d53b0b0ddbfb82be96752a9c4e3c904c59e633bc9485e","a7df5c2e9594966c7e0d4a763b13ed5727506d892669df5f7bc9826f539c1d35","258cc5cd6891f6bcbaccefd953997038844e7f65d582cac987ffabf7181bcd4c","00a6db28fc4df6ddf10adbe630d9df620ec13af19039c1869653e60dafa739d2","649324d5abb5464aabe35d86cd0eef16562df811f0971481cee664afa5acbc88",{"version":"628749b6edfc907c32583a77f7dde111642dbfc13265fa566e9a8fa47f224b51","signature":"495944b274165419ec08446dbd612d6276e2c12b92caa1f1e6c645cbc044ef25"},{"version":"e2f7d4348da1a42007547574ec71504de5e9df04d270bcc4c672bec1068257e4","signature":"0d7e153773886e59a74ffe1fac08bef805541411de160b9f3af36f8a6a3c6022"},{"version":"70fa251413c8e1926804d27e8aa01f96bf56141270e8adaeedfeaf0cc7147cef","signature":"2e85c128d27849ff4bd436f75d32d8a64d9013d420f09c82c6eae63cb7131020"},{"version":"334a6eff67fdb6feabbe5a612552a0714c424ccd07abbb096672085e7d43fb4a","signature":"19756a360a54eda2a10138b94b37a87519fd1a27c678a1b82187295e40bbfacd"},{"version":"722e48bdd1c494feabfb081d7d582d4554276abacce92f69128511918c125273","signature":"b195f1ad5886c1600c53bc7296210f9ded9a9a673e01988eecf9f20f48a4d9d5"},{"version":"1b5f109f8e1b74f648bf19b878188928678f443c2b2a21db0861f57d0715ef69","signature":"55310e6719d6bd9462e76cbba6a582712b30a85ee4949b8d98e14e0f46738e78"},{"version":"d184310a8c121c1ed754995dc55f8ca212bb1ef94979f99423dcdc48569b3c51","signature":"99ec28bacd04a3185d90660fe18bae48f33cbb1d50c73c64cae98e67f7c0ca01"},{"version":"48d475a0c6f91f62a89b128923cdec08f1f30a12df0068493f0d9b2774125b01","signature":"6a90b1b75bb0eb776ae223adc1f3f1cc343abf3e68df619933a3248910061290"},{"version":"e581d928f182594fe6aa7c0dd2e0ce02fe65fb53b7d40a59af9c2f171eee6428","signature":"1ac721bca31657133deb33e2ae005d557e8e6e0aa9a466142a2b0388e2e2638c"},{"version":"f18e14371215da28d2375c023017adcbd420314020f2a4ca4e9d9369ca80c1f4","signature":"0aeb9a7ce850134709dee9ecb63c1883e387eb70f960e0510100f4b2bb70caa4"},{"version":"bcc7e1fd0b70240f11846f0c5a284be69834446899b64477371cee7aaca38965","signature":"bab97a4f0736f1c1cd0546f79f993ecf30b34404cf4479a4f39068915880cb1c"},{"version":"6720778d4192df7ececcfd9dfebed8a006c9c44f88fe8b74880ab3ba7e14cce4","signature":"121c82998e23aa414d41a2f08e108074760318a1c11a2a5183b88b0d9be4ef60"},{"version":"e3fe28954899e21bf8a7db496cb4b90313e826bb5ae938d84bc73c3bdaa31cc9","signature":"4e1f22dbfc0754b698f1e291c7c92bf1220834bd5620207084236399cfd02e2d"},{"version":"7f2a2cdf8eecbe353d449055d91c6ee619f90ca3b3a49ba5a44563c44aab5d1a","signature":"8e6165fa13e0d2f40e2403ab20b72804d02c663709a3f7383a320050e893fe8b"},{"version":"a02cb9d0a7363cbbce45fa86bbd7d64615811e30f2a7c47a2718fcf53f20eae7","signature":"9af2721670eb3402e476cd827256d4af7ab1d6db57f1615cbef18c75164df9e5"},"14c94f7888c75007a94132f03caef0f6b58bcb136c2994213fd2d3b99f3d7f85","4695042a55a75a6c62dc57f2efe60ef3c7bbe19adedb5081f6e51dd664bbc3f7","b006ad8d854471e7a270bd8918508090961bdc1cfe77ed51f13f471fe682acac","310901df1081433ff7c3b7918496cabb92ded208b04294d3d2bd35acae2de836","c8646410cd2a6bf05eb7e7a51c881776410d07fd5d8f75092a2c815c9c6fda52",{"version":"127604bb56d364ecc35cbb4927ba7c53f552353fc7913b07a4f5cc957210aabd","signature":"973a1e0a155ab26d66226ff9d64a36cf61227e9240b21cabdc67df29847a6599"},{"version":"dcea5769c8b69d7b7a5ee6ffd4d22260e47d53d22990e91d504cbdc0c0120c14","signature":"16c51743932253da5b661b0a5068eb1423a6f020f62e6783ce8ac5259cff10f2"},{"version":"67be5e00299e02d108b294758dcc0218da9f2a2823dea61d708ddbe705771ae5","signature":"05fe3dec4dc02961a8959758da54c6ff9d32a232183041163d4d52cc6bf39015"},{"version":"e2e5ebf01c7004f157b8c750fdddb9f227fbf3119a87297e3a014db04c3f0887","signature":"fc6fe9c667e291d0bbdc904c921d2c1d385175f8c135d9e549298c96265acaec"},{"version":"6b0da45d7a1027dd4a9b14ac009b018761e8851c84a9e54ecc1be9086f0516c6","signature":"38fd30580198d072da98f6dbcb7535f47359ca91ffe57e3b9bfd1961a3b209ab"},{"version":"b75a82fbc994a947805538c6652361c50c70f121e173c9ae62b190b8936790e0","signature":"0e92b9a9c01325fa2e1bc7574a0ff68328f69f8db609be2156a4c3da5ce493df"},{"version":"4c408d170f00539f8957a9cfce1d7f3e4a2d36651dbf4b16337a7af5c568d7e5","signature":"a0c04e7f7ac63b60e113ff0a7ad4fbf9214babb1db0572641709a5859710e27c"},"4489c6a9fde8934733aa7df6f7911461ee6e9e4ad092736bd416f6b2cc20b2c6","2c8e55457aaf4902941dfdba4061935922e8ee6e120539c9801cd7b400fae050","8041cfce439ff29d339742389de04c136e3029d6b1817f07b2d7fcbfb7534990","670a76db379b27c8ff42f1ba927828a22862e2ab0b0908e38b671f0e912cc5ed","9d38964b57191567a14b396422c87488cecd48f405c642daa734159875ee81d9","069bebfee29864e3955378107e243508b163e77ab10de6a5ee03ae06939f0bb9",{"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","8841e2aa774b89bd23302dede20663306dc1b9902431ac64b24be8b8d0e3f649","916be7d770b0ae0406be9486ac12eb9825f21514961dd050594c4b250617d5a8","254d9fb8c872d73d34594be8a200fd7311dbfa10a4116bfc465fba408052f2b3","d88a5e779faf033be3d52142a04fbe1cb96009868e3bbdd296b2bc6c59e06c0e","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":[[666],[72,108,109,110,125],[109,110,126,127],[108,109],[108,125,128,131],[108,128,131,132],[129,130,131,133,134],[108,131],[108,125,128,129,130,133],[108,116],[108],[72,108],[60,108],[112,113,114,115,116,117,118,119,120,121,122,123,124],[108,114,115],[108,114,116],[605,609,610],[605,608],[608],[609,611,613],[605,608,609,610,611,612],[605,608,612,616,617,618],[605,608,612,619],[605],[605,606],[606,607],[605,608,614,617,619,620],[615],[616,618,622,625,626],[616,618,626],[108,608,612,616,617,619,622],[616,626],[616,619,623],[608,617,619],[616,619,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637],[616,624],[108,624],[605,612,616,617,618,619,620,623],[616,618,622,624],[616,623,624],[108,135,392,393],[392],[393,394],[108,387],[387,388,389,390,391],[108,359,366,367],[108,359,366,367,387],[108,359,366,367,371],[108,359,366,367,368,370,371],[108,359,366,367,369],[108,359,366,367,372,373,375,376],[365,387],[357,366,367,372,373],[359,365,366],[108,359,366,367,372],[108,359,366,367,370],[108,359,366,367,383],[108,359,366,367,384],[111,356,359,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386],[357],[357,358],[72,108,392,395,401],[395,402],[402,403],[216],[216,544,545],[545,546,547],[249,258,259,262],[249,258,261],[249,258,260,262],[250,253,254,256],[250,251,252],[253,254,256,257],[250,251,255],[261,267],[249,258,261,267],[258,261,267],[258,261,263,264,265,266],[249,258,259,260,261,262,267],[249,258],[250],[144,167,193],[167,197,214],[167,214,215,234],[144,167],[144],[189,190,191,192,194,195,196],[167,189,190,191,192,194,195],[198],[201],[144,193],[202,203,204],[197],[193,197,199,200,205,206,213,215,235,236,354],[167,200,205],[208],[207,209,210,211,212],[144,167,193,197],[167,206,353],[138,144],[654,655,656,657],[548,567],[568],[560],[562],[560,561,562,563,564,565,566],[560,562],[108,228,424],[108,228,425],[425],[396],[108,228,396],[396,397,398,399,400],[108,228],[228,427],[228,428],[228,427,428],[427,428,429,430,431,432,433,434,435,436],[61,108,228,427,428],[105,108,228,427,428],[167,216,223,224],[225],[108,167,225,228,229],[108,167,216,223,225,228],[229],[224,225,226,229,230,231,232,233],[108,226,228,231],[224,231],[167,223],[167,237],[237,238],[237,238,239,240],[167],[188,353,475,492,493,506,531],[529],[167,504,519,527,528,530],[188,353,455,517],[518],[108,223,228,426,437,492,493,494,506],[167,188,492,506],[167,437],[437,495],[497],[494,495,496,498,501,503],[495,499,504],[500],[437,495,496],[495,496],[502],[167,184,188,353,462,475,485,492,493,504,506,509,511,519,521,524],[509,524,525,526],[492,506],[505,506,507,508],[492,506,507],[167,492,505,506],[167,492,506],[522,523],[167,188,485,522],[167,353,485],[520],[167,353,475,492,493,506,527],[144,167,484],[108,167,242],[167,350],[319],[167,242,319,347,350,351,352],[108,167,241,242],[243,244,245,246,348,349],[138,144,347],[138,144,245],[277],[277,291],[277,278,293,295,296],[277,291,294],[277,283],[277,282,284,285],[277,282,286],[282,283,284,285,286,287],[289,290],[278,279,280,281,288,291,292,293,294,295,296,297,298],[277,299,300,301,302],[144,277,299,350],[334],[347],[337,338,339,340,341,342,343,344,345],[167,269],[319,343,350],[269,347,350],[144,320],[269,270,320,323,333,334,335,346],[144,167,303,319],[347,350],[268,270],[270],[350],[320],[167,323],[247,248,271,272,273,274,275,276,321,322,324,325,326,327,328,329,330,331,332],[167,325],[247,248,271,272,273,274,275,276,321,322,324,325,326,327,328,329,330,331,350],[167,268,269],[234,333],[167,270],[317],[144,304],[305,306,307,308,309,310,311,312,313,314,315,316],[304,317,318],[303],[353],[441],[440],[144,167,447],[268],[167,241,353],[144,353,454,455],[438,439,441,442,443,444,445,448,449,450,451,452,453,454,456,457,458,476,477,479,480,481,482,483,486,487,488,489,490,491],[144,167,475],[144,167,353,441,479],[158],[478],[144,167,268,353,477],[144,353,477],[144,353,475,478,480,485,486],[144,167,441,444,456,479,480],[353,455],[486],[536],[536,537,538],[147],[144,147],[145,146,147,148,149,150,151,152,153,154,155,156,159,160,161,162,163,164,165,166],[138,144,145],[135,147,153,155],[147,148],[144,162],[108,362],[360,361,364],[360,363],[108,360],[412,413],[666,667,668,669,670],[666,668],[157],[672,673,674],[73,108],[677],[678],[689],[683,688],[579,581,582,583,584,585,586,587,588,589,590,591],[579,580,582,583,584,585,586,587,588,589,590,591],[580,581,582,583,584,585,586,587,588,589,590,591],[579,580,581,583,584,585,586,587,588,589,590,591],[579,580,581,582,584,585,586,587,588,589,590,591],[579,580,581,582,583,585,586,587,588,589,590,591],[579,580,581,582,583,584,586,587,588,589,590,591],[579,580,581,582,583,584,585,587,588,589,590,591],[579,580,581,582,583,584,585,586,588,589,590,591],[579,580,581,582,583,584,585,586,587,589,590,591],[579,580,581,582,583,584,585,586,587,588,590,591],[579,580,581,582,583,584,585,586,587,588,589,591],[579,580,581,582,583,584,585,586,587,588,589,590],[56],[59],[60,65,92],[61,72,73,80,89,100],[61,62,72,80],[63,101],[64,65,73,81],[65,89,97],[66,68,72,80],[67],[68,69],[72],[71,72],[59,72],[72,73,74,89,100],[72,73,74,89],[72,75,80,89,100],[72,73,75,76,80,89,97,100],[75,77,89,97,100],[56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,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],[72,78],[79,100,105],[68,72,80,89],[81],[82],[59,83],[84,99,105],[85],[86],[72,87],[87,88,101,103],[60,72,89,90,91],[60,89,91],[89,90],[92],[93],[72,95,96],[95,96],[65,80,89,97],[98],[80,99],[60,75,86,100],[65,101],[89,102],[103],[104],[60,65,72,74,83,89,100,103,105],[89,106],[108,227],[697,736],[697,721,736],[736],[697],[697,722,736],[697,698,699,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,717,718,719,720,721,722,723,724,725,726,727,728,729,730,731,732,733,734,735],[722,736],[737],[740],[510],[570],[572],[570,571,572,573,574,575,576],[570,572],[446],[593,594,595],[593],[594],[179],[179,180,181,182,183],[168,169,170,171,172,173,174,175,176,177,178],[681,684],[681,684,685,686],[683],[680,687],[682],[137,139,140,141,142,143],[137,138],[139],[138,139],[137,139],[167,188,355,423,531],[532,534],[423,533],[167,188,241],[460,461],[167,184,185,186],[185],[186],[136,185,186,187],[407],[407,408,411,415],[414],[167,409,410],[541,542,543],[223,541],[167,223,541],[167,216,223],[167,188,553,557],[558],[167,216],[167,217],[217,218,219,220,221,222],[135,167,184,188,355,404,405,421],[422],[72,108,167,188],[406],[406,416],[406,417,418,419,420],[167,188,409,416,539,540,549,550],[549],[540,549,551,552],[167,416,544,548],[167,467,468,469],[167,188,416,459,465,467,470],[167,184,188,241,416,459,462,463,464,465,466,468,469,470],[167,188,467],[241,467,468,470],[465,466,467,468,469,470,474],[167,223,467,469,475],[167,223,466,467,468],[471,472,473],[167,223,416,466,468,469],[167,223,466,468,469],[167,223,468,470],[188,416,512,514,515],[513,514],[516],[515,516],[167,188,553,554],[108,167,188,553,554],[554,555,556],[167,553],[72,125,128,135,167,188,241,409,416,462,533,535,553,559,569,577,578,591,592,597,598,600,601,602,603,604,640,641,642,644,645,646,647,648,649,650,651,652,653,661,662,663],[167,597],[167,559,592,597,602],[167,409,410,416,592,597,603],[167,597,599,639],[128,135,167,591,592,597,621,638],[167,416,597],[167,410,416,533,577,592,597,599,643],[72,167,409,553,559,592,597,598,645],[72,167,535,553,577,592,597],[167,409,553,569,577,592,644,647,648],[72,409,416,553,591,592,597],[597,598,600,643,662,664],[135,167,409,553,559,596],[167,416,592,599],[241,597],[167,409,416,559,592,597,598,601],[416,559,597],[167,409,416,592,597,599],[591,596,597],[167,553,592,597],[416,569,592,597],[167,416,592,659],[52,167,416,592,597,599,614,659,660],[409,416,591,592,597,599,600,664],[52,409,416,597,614],[167,416,597,664],[52,241,416,597,600,614],[128,135,167,188,462,535,553,559,569,597,647],[597],[597,639],[72,167,553,559,597],[72,167,535,553,597],[167,409,553,569,644,647,648],[72,409,553,597],[158,167],[167,409,559,597],[559,597],[158,167,409,597],[167,553,597],[569,597],[167,597,614,660],[409,597,664],[409,597],[597,664]],"referencedMap":[[668,1],[126,2],[128,3],[110,4],[132,5],[133,6],[129,6],[135,7],[130,6],[134,8],[131,9],[117,10],[114,11],[121,12],[115,10],[112,13],[125,14],[119,11],[116,15],[118,16],[611,17],[610,18],[609,19],[614,20],[613,21],[619,22],[620,23],[606,24],[607,25],[608,26],[621,27],[616,28],[627,29],[628,30],[623,31],[629,32],[630,33],[631,33],[622,34],[638,35],[633,29],[632,36],[634,37],[624,38],[635,32],[636,30],[626,39],[637,36],[625,40],[617,18],[394,41],[393,42],[395,43],[390,44],[388,44],[389,44],[392,45],[374,46],[379,47],[368,46],[373,48],[372,49],[370,50],[377,51],[378,46],[380,52],[375,53],[367,54],[381,55],[383,56],[384,57],[385,58],[387,59],[358,60],[359,61],[402,62],[403,63],[404,64],[545,65],[546,66],[547,66],[548,67],[260,68],[262,69],[261,70],[257,71],[253,72],[254,72],[258,73],[256,74],[263,75],[264,76],[265,77],[267,78],[266,75],[268,79],[259,80],[252,81],[255,72],[214,82],[215,83],[235,84],[189,85],[190,86],[191,86],[192,85],[197,87],[196,88],[194,82],[195,85],[199,89],[198,85],[202,90],[201,91],[205,92],[203,85],[204,93],[355,94],[207,82],[208,95],[209,96],[210,82],[213,97],[212,98],[236,83],[354,99],[193,100],[658,101],[568,102],[569,103],[561,104],[563,105],[567,106],[565,107],[564,107],[425,108],[424,109],[426,110],[398,111],[399,111],[396,65],[397,112],[401,113],[400,114],[428,115],[431,116],[430,117],[437,118],[433,117],[432,119],[435,117],[434,120],[436,116],[429,116],[225,121],[226,122],[231,123],[229,124],[230,125],[234,126],[232,127],[233,128],[224,129],[238,130],[239,131],[241,132],[237,133],[216,12],[529,134],[530,135],[531,136],[518,137],[519,138],[495,139],[494,140],[496,141],[497,142],[498,143],[504,144],[500,145],[501,146],[499,147],[502,148],[503,149],[525,150],[527,151],[507,152],[509,153],[508,154],[506,155],[505,156],[524,157],[523,158],[522,159],[526,152],[520,133],[521,160],[528,161],[485,162],[484,85],[351,163],[242,164],[352,165],[353,166],[243,167],[244,133],[245,133],[350,168],[348,169],[246,170],[277,133],[278,171],[279,171],[280,171],[281,171],[292,171],[293,171],[294,172],[297,173],[298,171],[295,174],[296,171],[282,171],[284,175],[286,176],[287,177],[285,171],[283,171],[288,178],[289,171],[290,171],[291,179],[299,180],[303,181],[301,171],[300,171],[302,182],[335,183],[337,133],[338,184],[346,185],[339,133],[341,186],[342,133],[344,187],[343,188],[345,189],[347,190],[320,191],[247,184],[248,192],[271,193],[272,194],[273,193],[275,133],[276,195],[321,196],[324,197],[333,198],[326,199],[325,133],[327,133],[328,164],[332,200],[329,195],[330,197],[331,184],[270,201],[334,202],[323,203],[318,204],[305,205],[314,205],[306,205],[307,205],[316,205],[308,205],[309,205],[317,206],[315,205],[310,205],[313,205],[311,205],[312,205],[319,207],[304,86],[455,208],[439,209],[442,210],[443,211],[445,210],[448,212],[451,213],[453,214],[456,215],[492,216],[476,217],[458,133],[480,218],[481,219],[479,220],[478,221],[482,222],[487,223],[477,100],[486,224],[489,225],[490,226],[491,133],[441,211],[537,227],[538,227],[539,228],[145,86],[146,86],[148,229],[149,86],[150,86],[151,230],[147,86],[167,231],[155,232],[156,233],[159,219],[165,234],[166,235],[363,236],[362,11],[365,237],[360,11],[364,238],[361,239],[414,240],[671,241],[667,1],[669,242],[670,1],[410,11],[158,243],[675,244],[676,245],[678,246],[679,247],[690,248],[689,249],[580,250],[581,251],[579,252],[582,253],[583,254],[584,255],[585,256],[586,257],[587,258],[588,259],[589,260],[590,261],[591,262],[56,263],[57,263],[59,264],[60,265],[61,266],[62,267],[63,268],[64,269],[65,270],[66,271],[67,272],[68,273],[69,273],[70,274],[71,275],[72,276],[73,277],[74,278],[75,279],[76,280],[77,281],[108,282],[78,283],[79,284],[80,285],[81,286],[82,287],[83,288],[84,289],[85,290],[86,291],[87,292],[88,293],[89,294],[91,295],[90,296],[92,297],[93,298],[95,299],[96,300],[97,301],[98,302],[99,303],[100,304],[101,305],[102,306],[103,307],[104,308],[105,309],[106,310],[694,11],[228,311],[696,11],[721,312],[722,313],[697,314],[700,314],[719,312],[720,312],[710,312],[709,315],[707,312],[702,312],[715,312],[713,312],[717,312],[701,312],[714,312],[718,312],[703,312],[704,312],[716,312],[698,312],[705,312],[706,312],[708,312],[712,312],[723,316],[711,312],[699,312],[736,317],[730,316],[732,318],[731,316],[724,316],[725,316],[727,316],[729,316],[733,318],[734,318],[726,318],[728,318],[738,319],[741,320],[511,321],[571,322],[573,323],[577,324],[575,325],[574,325],[447,326],[596,327],[594,328],[595,329],[175,330],[177,330],[176,330],[174,330],[184,331],[179,332],[170,330],[171,330],[172,330],[173,330],[685,333],[687,334],[686,333],[684,335],[688,336],[683,337],[144,338],[139,339],[140,340],[141,340],[142,341],[143,341],[138,342],[532,343],[535,344],[534,345],[460,346],[462,347],[187,348],[186,349],[185,350],[188,351],[408,352],[416,353],[415,354],[411,355],[544,356],[542,357],[543,358],[541,359],[558,360],[559,361],[217,362],[218,363],[219,363],[221,363],[223,364],[222,363],[422,365],[423,366],[406,367],[420,368],[419,368],[417,369],[418,368],[421,370],[551,371],[550,372],[552,372],[553,373],[549,374],[470,375],[468,376],[467,377],[465,378],[469,379],[475,380],[464,381],[471,382],[474,383],[472,384],[473,385],[466,386],[516,387],[515,388],[517,389],[514,390],[555,391],[556,392],[557,393],[554,394],[664,395],[659,396],[603,397],[604,398],[640,399],[639,400],[641,399],[642,401],[644,402],[646,403],[647,404],[649,405],[648,406],[665,407],[592,133],[597,408],[643,409],[650,410],[602,411],[598,412],[651,413],[652,414],[645,415],[653,416],[660,417],[661,418],[601,419],[662,420],[600,421],[663,422]],"exportedModulesMap":[[668,1],[126,2],[128,3],[110,4],[132,5],[133,6],[129,6],[135,7],[130,6],[134,8],[131,9],[117,10],[114,11],[121,12],[115,10],[112,13],[125,14],[119,11],[116,15],[118,16],[611,17],[610,18],[609,19],[614,20],[613,21],[619,22],[620,23],[606,24],[607,25],[608,26],[621,27],[616,28],[627,29],[628,30],[623,31],[629,32],[630,33],[631,33],[622,34],[638,35],[633,29],[632,36],[634,37],[624,38],[635,32],[636,30],[626,39],[637,36],[625,40],[617,18],[394,41],[393,42],[395,43],[390,44],[388,44],[389,44],[392,45],[374,46],[379,47],[368,46],[373,48],[372,49],[370,50],[377,51],[378,46],[380,52],[375,53],[367,54],[381,55],[383,56],[384,57],[385,58],[387,59],[358,60],[359,61],[402,62],[403,63],[404,64],[545,65],[546,66],[547,66],[548,67],[260,68],[262,69],[261,70],[257,71],[253,72],[254,72],[258,73],[256,74],[263,75],[264,76],[265,77],[267,78],[266,75],[268,79],[259,80],[252,81],[255,72],[214,82],[215,83],[235,84],[189,85],[190,86],[191,86],[192,85],[197,87],[196,88],[194,82],[195,85],[199,89],[198,85],[202,90],[201,91],[205,92],[203,85],[204,93],[355,94],[207,82],[208,95],[209,96],[210,82],[213,97],[212,98],[236,83],[354,99],[193,100],[658,101],[568,102],[569,103],[561,104],[563,105],[567,106],[565,107],[564,107],[425,108],[424,109],[426,110],[398,111],[399,111],[396,65],[397,112],[401,113],[400,114],[428,115],[431,116],[430,117],[437,118],[433,117],[432,119],[435,117],[434,120],[436,116],[429,116],[225,121],[226,122],[231,123],[229,124],[230,125],[234,126],[232,127],[233,128],[224,129],[238,130],[239,131],[241,132],[237,133],[216,12],[529,134],[530,135],[531,136],[518,137],[519,138],[495,139],[494,140],[496,141],[497,142],[498,143],[504,144],[500,145],[501,146],[499,147],[502,148],[503,149],[525,150],[527,151],[507,152],[509,153],[508,154],[506,155],[505,156],[524,157],[523,158],[522,159],[526,152],[520,133],[521,160],[528,161],[485,162],[484,85],[351,163],[242,164],[352,165],[353,166],[243,167],[244,133],[245,133],[350,168],[348,169],[246,170],[277,133],[278,171],[279,171],[280,171],[281,171],[292,171],[293,171],[294,172],[297,173],[298,171],[295,174],[296,171],[282,171],[284,175],[286,176],[287,177],[285,171],[283,171],[288,178],[289,171],[290,171],[291,179],[299,180],[303,181],[301,171],[300,171],[302,182],[335,183],[337,133],[338,184],[346,185],[339,133],[341,186],[342,133],[344,187],[343,188],[345,189],[347,190],[320,191],[247,184],[248,192],[271,193],[272,194],[273,193],[275,133],[276,195],[321,196],[324,197],[333,198],[326,199],[325,133],[327,133],[328,164],[332,200],[329,195],[330,197],[331,184],[270,201],[334,202],[323,203],[318,204],[305,205],[314,205],[306,205],[307,205],[316,205],[308,205],[309,205],[317,206],[315,205],[310,205],[313,205],[311,205],[312,205],[319,207],[304,86],[455,208],[439,209],[442,210],[443,211],[445,210],[448,212],[451,213],[453,214],[456,215],[492,216],[476,217],[458,133],[480,218],[481,219],[479,220],[478,221],[482,222],[487,223],[477,100],[486,224],[489,225],[490,226],[491,133],[441,211],[537,227],[538,227],[539,228],[145,86],[146,86],[148,229],[149,86],[150,86],[151,230],[147,86],[167,231],[155,232],[156,233],[159,219],[165,234],[166,235],[363,236],[362,11],[365,237],[360,11],[364,238],[361,239],[414,240],[671,241],[667,1],[669,242],[670,1],[410,11],[158,243],[675,244],[676,245],[678,246],[679,247],[690,248],[689,249],[580,250],[581,251],[579,252],[582,253],[583,254],[584,255],[585,256],[586,257],[587,258],[588,259],[589,260],[590,261],[591,262],[56,263],[57,263],[59,264],[60,265],[61,266],[62,267],[63,268],[64,269],[65,270],[66,271],[67,272],[68,273],[69,273],[70,274],[71,275],[72,276],[73,277],[74,278],[75,279],[76,280],[77,281],[108,282],[78,283],[79,284],[80,285],[81,286],[82,287],[83,288],[84,289],[85,290],[86,291],[87,292],[88,293],[89,294],[91,295],[90,296],[92,297],[93,298],[95,299],[96,300],[97,301],[98,302],[99,303],[100,304],[101,305],[102,306],[103,307],[104,308],[105,309],[106,310],[694,11],[228,311],[696,11],[721,312],[722,313],[697,314],[700,314],[719,312],[720,312],[710,312],[709,315],[707,312],[702,312],[715,312],[713,312],[717,312],[701,312],[714,312],[718,312],[703,312],[704,312],[716,312],[698,312],[705,312],[706,312],[708,312],[712,312],[723,316],[711,312],[699,312],[736,317],[730,316],[732,318],[731,316],[724,316],[725,316],[727,316],[729,316],[733,318],[734,318],[726,318],[728,318],[738,319],[741,320],[511,321],[571,322],[573,323],[577,324],[575,325],[574,325],[447,326],[596,327],[594,328],[595,329],[175,330],[177,330],[176,330],[174,330],[184,331],[179,332],[170,330],[171,330],[172,330],[173,330],[685,333],[687,334],[686,333],[684,335],[688,336],[683,337],[144,338],[139,339],[140,340],[141,340],[142,341],[143,341],[138,342],[532,343],[535,344],[534,345],[460,346],[462,347],[187,348],[186,349],[185,350],[188,351],[408,352],[416,353],[415,354],[411,355],[544,356],[542,357],[543,358],[541,359],[558,360],[559,361],[217,362],[218,363],[219,363],[221,363],[223,364],[222,363],[422,365],[423,366],[406,367],[420,368],[419,368],[417,369],[418,368],[421,370],[551,371],[550,372],[552,372],[553,373],[549,374],[470,375],[468,376],[467,377],[465,378],[469,379],[475,380],[464,381],[471,382],[474,383],[472,384],[473,385],[466,386],[516,387],[515,388],[517,389],[514,390],[555,391],[556,392],[557,393],[554,394],[664,423],[659,133],[603,424],[604,424],[640,425],[639,396],[641,425],[642,424],[644,396],[646,426],[647,427],[649,428],[648,429],[665,407],[592,430],[597,408],[643,133],[650,424],[602,431],[598,432],[651,433],[652,424],[645,434],[653,435],[660,133],[661,436],[601,437],[662,438],[600,439],[663,424]],"semanticDiagnosticsPerFile":[668,666,126,109,128,110,127,132,133,129,135,130,134,131,117,114,121,115,112,120,125,122,123,124,119,116,113,118,611,610,609,614,613,619,620,606,607,608,605,621,616,615,612,627,628,623,629,630,631,622,638,633,632,634,624,635,636,626,637,625,617,618,394,393,395,390,388,389,392,391,374,379,368,373,372,370,377,378,380,375,369,367,366,376,382,381,383,384,385,387,357,358,359,356,371,386,402,403,404,405,545,546,547,548,409,260,262,261,249,257,253,254,258,256,263,264,265,267,266,268,259,252,250,251,255,214,215,235,189,190,191,192,197,196,194,195,199,198,200,202,201,205,203,204,206,355,207,208,209,210,213,211,212,236,354,193,656,654,655,657,658,568,569,561,560,563,562,566,567,565,564,425,424,426,398,399,396,397,401,400,428,431,430,437,433,432,435,434,436,427,429,225,226,231,229,230,234,232,233,224,238,240,239,241,237,216,529,530,531,518,519,495,494,496,497,498,504,500,501,499,502,503,525,493,527,507,509,508,506,505,524,523,522,526,520,521,528,485,484,351,242,352,353,243,244,245,350,348,246,349,277,278,279,280,281,292,293,294,297,298,295,296,282,284,286,287,285,283,288,289,290,291,299,303,301,300,302,269,335,337,338,346,339,340,341,342,344,343,345,336,347,320,247,248,271,272,273,274,275,276,321,322,324,333,326,325,327,328,332,329,330,331,270,334,323,318,305,314,306,307,316,308,309,317,315,310,313,311,312,319,304,455,438,439,442,443,444,445,448,449,450,451,452,453,454,456,457,492,476,458,480,481,479,478,482,483,487,488,477,486,489,490,491,440,441,537,538,539,536,145,146,148,149,150,151,152,153,154,147,167,155,156,159,160,161,162,163,164,165,166,363,362,365,360,364,361,412,414,413,671,667,669,670,410,158,463,672,675,673,676,677,678,679,690,689,674,691,580,581,579,582,583,584,585,586,587,588,589,590,591,692,157,56,57,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,58,107,75,76,77,108,78,79,80,81,82,83,84,85,86,87,88,89,91,90,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,693,694,695,512,228,227,696,721,722,697,700,719,720,710,709,707,702,715,713,717,701,714,718,703,704,716,698,705,706,708,712,723,711,699,736,735,730,732,731,724,725,727,729,733,734,726,728,738,737,739,533,740,741,511,510,571,570,573,572,576,577,575,574,111,680,446,447,578,596,594,595,593,513,178,175,177,176,174,184,179,183,180,182,181,170,171,172,168,169,173,681,685,687,686,684,688,459,683,682,137,144,139,140,141,142,143,138,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,532,535,534,460,461,462,136,187,186,185,188,408,416,415,407,411,544,542,543,541,558,559,217,218,219,220,221,223,222,422,423,406,420,419,417,418,421,551,540,550,552,553,549,470,468,467,465,469,475,464,471,474,472,473,466,516,515,517,514,555,556,557,554,664,599,659,603,604,640,639,641,642,644,646,647,649,648,665,592,597,643,650,602,598,651,652,645,653,660,661,601,662,600,663,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","../../../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","../../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/@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","../../../node_modules/@metamask/keyring-api/dist/api/account.d.ts","../../../node_modules/@metamask/keyring-api/dist/api/balance.d.ts","../../../node_modules/@metamask/keyring-api/dist/api/caip.d.ts","../../../node_modules/@metamask/keyring-api/dist/api/export.d.ts","../../../node_modules/@metamask/keyring-api/dist/superstruct.d.ts","../../../node_modules/@metamask/keyring-api/dist/api/request.d.ts","../../../node_modules/@metamask/keyring-api/dist/api/response.d.ts","../../../node_modules/@metamask/keyring-api/dist/api/keyring.d.ts","../../../node_modules/@metamask/keyring-api/dist/api/index.d.ts","../../../node_modules/@metamask/keyring-api/dist/btc/types.d.ts","../../../node_modules/@metamask/keyring-api/dist/btc/index.d.ts","../../../node_modules/@metamask/keyring-api/dist/contexts.d.ts","../../../node_modules/@metamask/keyring-api/dist/eth/erc4337/types.d.ts","../../../node_modules/@metamask/keyring-api/dist/eth/erc4337/index.d.ts","../../../node_modules/@metamask/keyring-api/dist/eth/types.d.ts","../../../node_modules/@metamask/keyring-api/dist/eth/utils.d.ts","../../../node_modules/@metamask/keyring-api/dist/eth/index.d.ts","../../../node_modules/@metamask/keyring-api/dist/events.d.ts","../../../node_modules/@metamask/keyring-api/dist/internal/api.d.ts","../../../node_modules/@metamask/keyring-api/dist/internal/eth/ethkeyring.d.ts","../../../node_modules/@metamask/keyring-api/dist/internal/eth/index.d.ts","../../../node_modules/@metamask/keyring-api/dist/internal/events.d.ts","../../../node_modules/@metamask/keyring-api/dist/internal/rpc.d.ts","../../../node_modules/@metamask/keyring-api/dist/internal/types.d.ts","../../../node_modules/@metamask/keyring-api/dist/internal/index.d.ts","../../../node_modules/@metamask/keyring-api/dist/jsonrpcrequest.d.ts","../../../node_modules/@metamask/keyring-api/dist/keyringclient.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","../../../node_modules/@metamask/providers/dist/types/utils.d.ts","../../../node_modules/@metamask/providers/dist/types/baseprovider.d.ts","../../../node_modules/@metamask/providers/dist/types/eip6963.d.ts","../../../node_modules/@types/readable-stream/node_modules/safe-buffer/index.d.ts","../../../node_modules/@types/readable-stream/index.d.ts","../../../node_modules/@metamask/providers/dist/types/streamprovider.d.ts","../../../node_modules/@metamask/providers/dist/types/extension-provider/createexternalextensionprovider.d.ts","../../../node_modules/@metamask/providers/dist/types/metamaskinpageprovider.d.ts","../../../node_modules/@metamask/providers/dist/types/initializeinpageprovider.d.ts","../../../node_modules/@metamask/providers/dist/types/shimweb3.d.ts","../../../node_modules/@metamask/providers/dist/types/index.d.ts","../../../node_modules/@metamask/keyring-api/dist/keyringsnaprpcclient.d.ts","../../../node_modules/@metamask/keyring-api/dist/rpc-handler.d.ts","../../../node_modules/@metamask/rpc-errors/dist/types/utils.d.ts","../../../node_modules/@metamask/rpc-errors/dist/types/classes.d.ts","../../../node_modules/@metamask/rpc-errors/dist/types/errors.d.ts","../../../node_modules/@metamask/rpc-errors/dist/types/error-constants.d.ts","../../../node_modules/@metamask/rpc-errors/dist/types/index.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/errors.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/internals/error-wrappers.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/internals/errors.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/internals/helpers.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/internals/structs.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/create-interface.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/dialog.d.ts","../../../node_modules/@metamask/key-tree/dist/constants.d.cts","../../../node_modules/@metamask/key-tree/node_modules/@noble/curves/abstract/modular.d.ts","../../../node_modules/@metamask/key-tree/node_modules/@noble/curves/abstract/utils.d.ts","../../../node_modules/@metamask/key-tree/node_modules/@noble/curves/abstract/curve.d.ts","../../../node_modules/@metamask/key-tree/dist/curves/ed25519.d.cts","../../../node_modules/@metamask/key-tree/dist/curves/ed25519bip32.d.cts","../../../node_modules/@metamask/key-tree/node_modules/@noble/curves/abstract/weierstrass.d.ts","../../../node_modules/@metamask/key-tree/dist/curves/secp256k1.d.cts","../../../node_modules/@metamask/key-tree/dist/curves/curve.d.cts","../../../node_modules/@metamask/key-tree/dist/curves/index.d.cts","../../../node_modules/@metamask/key-tree/dist/utils.d.cts","../../../node_modules/@metamask/key-tree/dist/bip44cointypenode.d.cts","../../../node_modules/@metamask/key-tree/dist/slip10node.d.cts","../../../node_modules/@metamask/key-tree/dist/bip44node.d.cts","../../../node_modules/@metamask/key-tree/dist/derivers/bip32.d.cts","../../../node_modules/@metamask/key-tree/dist/derivers/bip39.d.cts","../../../node_modules/@metamask/key-tree/dist/derivers/cip3.d.cts","../../../node_modules/@metamask/key-tree/dist/derivers/slip10.d.cts","../../../node_modules/@metamask/key-tree/dist/derivers/index.d.cts","../../../node_modules/@metamask/key-tree/dist/index.d.cts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/caip.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/permissions.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/get-bip32-entropy.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/get-bip32-public-key.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/get-bip44-entropy.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/get-client-status.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/get-entropy.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/get-file.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/component.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/address.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/box.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/copyable.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/divider.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/form/button.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/form/option.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/form/dropdown.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/form/input.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/form/field.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/form/form.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/form/index.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/formatting/bold.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/formatting/italic.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/formatting/index.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/heading.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/image.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/link.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/text.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/value.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/row.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/spinner.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/components/index.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/jsx-runtime.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/jsx-dev-runtime.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/validation.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/jsx/index.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/ui/nodes.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/ui/components/address.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/ui/components/copyable.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/ui/components/divider.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/ui/components/heading.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/ui/components/image.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/ui/components/panel.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/ui/components/spinner.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/ui/components/text.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/ui/components/row.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/ui/components/button.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/ui/components/input.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/ui/components/form.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/ui/components/index.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/ui/component.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/ui/index.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/interface.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/get-interface-state.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/get-locale.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/snap.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/get-snaps.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/invoke-snap.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/invoke-keyring.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/manage-accounts.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/manage-state.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/notify.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/request-snaps.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/update-interface.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/methods.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/methods/index.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/provider.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/global.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/images.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/handlers/cronjob.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/handlers/home-page.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/handlers/keyring.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/handlers/lifecycle.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/handlers/name-lookup.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/handlers/rpc-request.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/handlers/transaction.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/handlers/signature.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/handlers/user-input.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/handlers/index.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/types/index.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/internals/jsx.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/internals/svg.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/internals/index.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/error-wrappers.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/images.d.ts","../../../node_modules/@metamask/snaps-sdk/dist/types/index.d.ts","../../../node_modules/@metamask/keyring-api/dist/snap-utils.d.ts","../../../node_modules/@metamask/keyring-api/dist/index.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/patchcbor.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/lib/dataitem.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/lib/cbor-sync.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/lib/index.d.ts","../../../node_modules/@ngraveio/bc-ur/dist/ur.d.ts","../../../node_modules/@ngraveio/bc-ur/dist/urencoder.d.ts","../../../node_modules/@ngraveio/bc-ur/dist/fountainencoder.d.ts","../../../node_modules/@ngraveio/bc-ur/dist/fountaindecoder.d.ts","../../../node_modules/@ngraveio/bc-ur/dist/urdecoder.d.ts","../../../node_modules/@ngraveio/bc-ur/dist/index.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/registrytype.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/registryitem.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/cryptocoininfo.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/pathcomponent.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/cryptokeypath.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/types.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/cryptohdkey.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/cryptoeckey.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/bytes.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/multikey.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/scriptexpression.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/cryptooutput.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/cryptopsbt.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/cryptoaccount.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/decoder/index.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/extended/cryptomultiaccounts.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/errors/index.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/extended/derivationschema.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/extended/keyderivation.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/extended/qrhardwarecall.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/utils.d.ts","../../../node_modules/@keystonehq/bc-ur-registry/dist/index.d.ts","../../../node_modules/@keystonehq/bc-ur-registry-eth/dist/ethsignrequest.d.ts","../../../node_modules/@keystonehq/bc-ur-registry-eth/dist/ethsignature.d.ts","../../../node_modules/@keystonehq/bc-ur-registry-eth/dist/ethnftitem.d.ts","../../../node_modules/@keystonehq/bc-ur-registry-eth/dist/utlis.d.ts","../../../node_modules/@keystonehq/bc-ur-registry-eth/dist/index.d.ts","../../../node_modules/@keystonehq/base-eth-keyring/dist/interactionprovider.d.ts","../../../node_modules/@keystonehq/base-eth-keyring/dist/basekeyring.d.ts","../../../node_modules/@keystonehq/base-eth-keyring/dist/index.d.ts","../../../node_modules/@metamask/obs-store/dist/observablestore.d.ts","../../../node_modules/@metamask/obs-store/dist/asstream.d.ts","../../../node_modules/@metamask/obs-store/dist/composedstore.d.ts","../../../node_modules/@metamask/obs-store/dist/mergedstore.d.ts","../../../node_modules/@metamask/obs-store/dist/transform.d.ts","../../../node_modules/@metamask/obs-store/dist/index.d.ts","../../../node_modules/@keystonehq/metamask-airgapped-keyring/dist/metamaskinteractionprovider.d.ts","../../../node_modules/@keystonehq/metamask-airgapped-keyring/dist/metamaskkeyring.d.ts","../../../node_modules/@keystonehq/metamask-airgapped-keyring/dist/index.d.ts","../../../node_modules/@metamask/browser-passworder/dist/index.d.ts","../../message-manager/dist/types/abstractmessagemanager.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","../../message-manager/dist/types/personalmessagemanager.d.ts","../../message-manager/dist/types/typedmessagemanager.d.ts","../../message-manager/dist/types/encryptionpublickeymanager.d.ts","../../message-manager/dist/types/decryptmessagemanager.d.ts","../../message-manager/dist/types/index.d.ts","../../keyring-controller/dist/types/keyringcontroller.d.ts","../../keyring-controller/dist/types/index.d.ts","../../../node_modules/@metamask/object-multiplex/dist/substream.d.ts","../../../node_modules/@metamask/object-multiplex/dist/objectmultiplex.d.ts","../../../node_modules/@metamask/object-multiplex/dist/index.d.ts","../../../node_modules/@metamask/post-message-stream/dist/utils.d.ts","../../../node_modules/@metamask/post-message-stream/dist/basepostmessagestream.d.ts","../../../node_modules/@metamask/post-message-stream/dist/window/windowpostmessagestream.d.ts","../../../node_modules/@metamask/post-message-stream/dist/webworker/webworkerpostmessagestream.d.ts","../../../node_modules/@metamask/post-message-stream/dist/webworker/webworkerparentpostmessagestream.d.ts","../../../node_modules/@metamask/post-message-stream/dist/node-process/processparentmessagestream.d.ts","../../../node_modules/@metamask/post-message-stream/dist/node-process/processmessagestream.d.ts","../../../node_modules/@metamask/post-message-stream/dist/node-thread/threadparentmessagestream.d.ts","../../../node_modules/@metamask/post-message-stream/dist/node-thread/threadmessagestream.d.ts","../../../node_modules/@metamask/post-message-stream/dist/runtime/browserruntimepostmessagestream.d.ts","../../../node_modules/@metamask/post-message-stream/dist/index.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/array.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/auxiliary-files.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/virtual-file/virtualfile.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/virtual-file/index.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/base64.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/bytes.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/caveats.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/checksum.d.ts","../../../node_modules/cron-parser/types/common.d.ts","../../../node_modules/cron-parser/types/index.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/cronjob.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/deep-clone.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/default-endowments.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/derivation-paths.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/entropy.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/errors.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/handler-types.d.ts","../../../node_modules/@metamask/snaps-sdk/jsx.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/handlers.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/iframe.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/json.d.ts","../../../node_modules/nanoid/index.d.ts","../../approval-controller/dist/types/approvalcontroller.d.ts","../../approval-controller/dist/types/errors.d.ts","../../approval-controller/dist/types/index.d.ts","../../../node_modules/@types/deep-freeze-strict/index.d.ts","../../permission-controller/src/permission-middleware.ts","../../permission-controller/src/subjectmetadatacontroller.ts","../../permission-controller/src/utils.ts","../../permission-controller/src/permissioncontroller.ts","../../permission-controller/src/permission.ts","../../permission-controller/src/errors.ts","../../permission-controller/src/caveat.ts","../../permission-controller/src/rpc-methods/getpermissions.ts","../../permission-controller/src/rpc-methods/requestpermissions.ts","../../permission-controller/src/rpc-methods/revokepermissions.ts","../../permission-controller/src/rpc-methods/index.ts","../../permission-controller/src/index.ts","../../../node_modules/@metamask/snaps-utils/dist/types/json-rpc.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/structs.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/manifest/validation.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/manifest/index.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/localization.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/logging.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/namespace.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/path.d.ts","../../../node_modules/@metamask/snaps-registry/dist/verify.d.ts","../../../node_modules/@metamask/snaps-registry/dist/index.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/types.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/snaps.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/strings.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/ui.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/validation.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/versions.d.ts","../../../node_modules/@metamask/snaps-utils/dist/types/index.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/snaps/timer.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/services/executionservice.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/services/abstractexecutionservice.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/services/proxypostmessagestream.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/services/iframe/iframeexecutionservice.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/services/iframe/index.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/services/proxy/proxyexecutionservice.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/services/offscreen/offscreenexecutionservice.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/services/offscreen/index.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/services/webworker/webworkerexecutionservice.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/services/webworker/index.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/services/index.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/snaps/location/npm.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/snaps/location/location.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/snaps/location/http.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/snaps/location/local.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/snaps/location/index.d.ts","../../../node_modules/@xstate/fsm/lib/types.d.ts","../../../node_modules/@xstate/fsm/lib/index.d.ts","../../../node_modules/@types/punycode/index.d.ts","../../../node_modules/fastest-levenshtein/mod.d.ts","../../phishing-controller/src/utils.ts","../../phishing-controller/src/phishingdetector.ts","../../phishing-controller/src/phishingcontroller.ts","../../phishing-controller/src/index.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/interface/snapinterfacecontroller.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/interface/index.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/types/encryptor.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/types/index.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/snaps/registry/registry.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/snaps/registry/json.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/snaps/registry/index.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/snaps/snapcontroller.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/snaps/selectors.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/snaps/index.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/utils.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/cronjob/cronjobcontroller.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/cronjob/index.d.ts","../../../node_modules/@metamask/snaps-controllers/dist/types/index.d.ts","../../accounts-controller/dist/types/accountscontroller.d.ts","../../../node_modules/@types/uuid/index.d.ts","../../accounts-controller/dist/types/utils.d.ts","../../accounts-controller/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","../../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/@metamask/eth-block-tracker/dist/blocktracker.d.ts","../../../node_modules/@metamask/eth-block-tracker/dist/pollingblocktracker.d.ts","../../../node_modules/@metamask/eth-block-tracker/dist/subscribeblocktracker.d.ts","../../../node_modules/@metamask/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","../../gas-fee-controller/dist/types/gasfeecontroller.d.ts","../../gas-fee-controller/dist/types/index.d.ts","../../../node_modules/@metamask/nonce-tracker/node_modules/async-mutex/lib/mutexinterface.d.ts","../../../node_modules/@metamask/nonce-tracker/node_modules/async-mutex/lib/mutex.d.ts","../../../node_modules/@metamask/nonce-tracker/node_modules/async-mutex/lib/semaphoreinterface.d.ts","../../../node_modules/@metamask/nonce-tracker/node_modules/async-mutex/lib/semaphore.d.ts","../../../node_modules/@metamask/nonce-tracker/node_modules/async-mutex/lib/withtimeout.d.ts","../../../node_modules/@metamask/nonce-tracker/node_modules/async-mutex/lib/tryacquire.d.ts","../../../node_modules/@metamask/nonce-tracker/node_modules/async-mutex/lib/errors.d.ts","../../../node_modules/@metamask/nonce-tracker/node_modules/async-mutex/lib/index.d.ts","../../../node_modules/@metamask/nonce-tracker/dist/noncetracker.d.ts","../../../node_modules/@metamask/nonce-tracker/dist/index.d.ts","../../../node_modules/async-mutex/lib/mutexinterface.d.ts","../../../node_modules/async-mutex/lib/mutex.d.ts","../../../node_modules/async-mutex/lib/semaphoreinterface.d.ts","../../../node_modules/async-mutex/lib/semaphore.d.ts","../../../node_modules/async-mutex/lib/withtimeout.d.ts","../../../node_modules/async-mutex/lib/tryacquire.d.ts","../../../node_modules/async-mutex/lib/errors.d.ts","../../../node_modules/async-mutex/lib/index.d.ts","../../../node_modules/eth-method-registry/dist/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","../src/logger.ts","../../../node_modules/fast-json-patch/module/helpers.d.ts","../../../node_modules/fast-json-patch/module/core.d.ts","../../../node_modules/fast-json-patch/module/duplex.d.ts","../../../node_modules/fast-json-patch/index.d.ts","../src/types.ts","../src/utils/gas-flow.ts","../src/constants.ts","../src/utils/utils.ts","../src/utils/swaps.ts","../src/utils/gas-fees.ts","../src/gas-flows/defaultgasfeeflow.ts","../src/gas-flows/lineagasfeeflow.ts","../../../node_modules/@ethersproject/bytes/lib/index.d.ts","../../../node_modules/@ethersproject/bignumber/lib/bignumber.d.ts","../../../node_modules/@ethersproject/bignumber/lib/fixednumber.d.ts","../../../node_modules/@ethersproject/bignumber/lib/index.d.ts","../../../node_modules/@ethersproject/abi/lib/fragments.d.ts","../../../node_modules/@ethersproject/abi/lib/coders/abstract-coder.d.ts","../../../node_modules/@ethersproject/abi/lib/abi-coder.d.ts","../../../node_modules/@ethersproject/properties/lib/index.d.ts","../../../node_modules/@ethersproject/abi/lib/interface.d.ts","../../../node_modules/@ethersproject/abi/lib/index.d.ts","../../../node_modules/@ethersproject/networks/lib/types.d.ts","../../../node_modules/@ethersproject/networks/lib/index.d.ts","../../../node_modules/@ethersproject/transactions/lib/index.d.ts","../../../node_modules/@ethersproject/web/lib/index.d.ts","../../../node_modules/@ethersproject/abstract-provider/lib/index.d.ts","../../../node_modules/@ethersproject/abstract-signer/lib/index.d.ts","../../../node_modules/@ethersproject/contracts/lib/index.d.ts","../../../node_modules/@ethersproject/providers/lib/formatter.d.ts","../../../node_modules/@ethersproject/providers/lib/base-provider.d.ts","../../../node_modules/@ethersproject/providers/lib/json-rpc-provider.d.ts","../../../node_modules/@ethersproject/providers/lib/websocket-provider.d.ts","../../../node_modules/@ethersproject/providers/lib/url-json-rpc-provider.d.ts","../../../node_modules/@ethersproject/providers/lib/alchemy-provider.d.ts","../../../node_modules/@ethersproject/providers/lib/ankr-provider.d.ts","../../../node_modules/@ethersproject/providers/lib/cloudflare-provider.d.ts","../../../node_modules/@ethersproject/providers/lib/etherscan-provider.d.ts","../../../node_modules/@ethersproject/providers/lib/fallback-provider.d.ts","../../../node_modules/@ethersproject/providers/lib/ipc-provider.d.ts","../../../node_modules/@ethersproject/providers/lib/infura-provider.d.ts","../../../node_modules/@ethersproject/providers/lib/json-rpc-batch-provider.d.ts","../../../node_modules/@ethersproject/providers/lib/nodesmith-provider.d.ts","../../../node_modules/@ethersproject/providers/lib/pocket-provider.d.ts","../../../node_modules/@ethersproject/providers/lib/web3-provider.d.ts","../../../node_modules/@ethersproject/providers/lib/index.d.ts","../src/gas-flows/oraclelayer1gasfeeflow.ts","../src/gas-flows/optimismlayer1gasfeeflow.ts","../src/gas-flows/scrolllayer1gasfeeflow.ts","../src/gas-flows/testgasfeeflow.ts","../src/utils/etherscan.ts","../src/helpers/etherscanremotetransactionsource.ts","../src/utils/layer1-gas-fee-flow.ts","../src/helpers/gasfeepoller.ts","../src/helpers/incomingtransactionhelper.ts","../src/helpers/pendingtransactiontracker.ts","../src/helpers/multichaintrackinghelper.ts","../src/utils/external-transactions.ts","../src/utils/gas.ts","../src/utils/history.ts","../src/utils/nonce.ts","../../../node_modules/@metamask/metamask-eth-abis/dist/abis/abierc20.d.ts","../../../node_modules/@metamask/metamask-eth-abis/dist/abis/abierc721.d.ts","../../../node_modules/@metamask/metamask-eth-abis/dist/abis/abierc1155.d.ts","../../../node_modules/@metamask/metamask-eth-abis/dist/abis/fiattokenv2.d.ts","../../../node_modules/@metamask/metamask-eth-abis/dist/index.d.ts","../src/errors.ts","../src/utils/simulation-api.ts","../src/utils/simulation.ts","../src/utils/transaction-type.ts","../src/utils/validation.ts","../src/transactioncontroller.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/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/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/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":"f31113ac9492fdd6e78bf6151b338c92e5b1837be426ef4aa0648ce82d13b518","affectsGlobalScope":true},"62a0875a0397b35a2364f1d401c0ce17975dfa4d47bf6844de858ae04da349f9","ee7491d0318d1fafcba97d5b72b450eb52671570f7a4ecd9e8898d40eaae9472","e3e7d217d89b380c1f34395eadc9289542851b0f0a64007dfe1fb7cf7423d24e","fd79909e93b4d50fd0ed9f3d39ddf8ba0653290bac25c295aac49f6befbd081b","345a9cc2945406f53051cd0e9b51f82e1e53929848eab046fdda91ee8aa7da31","9debe2de883da37a914e5e784a7be54c201b8f1d783822ad6f443ff409a5ea21","dee5d5c5440cda1f3668f11809a5503c30db0476ad117dd450f7ba5a45300e8f","f5e396c1424c391078c866d6f84afe0b4d2f7f85a160b9c756cd63b5b1775d93","5caa6f4fff16066d377d4e254f6c34c16540da3809cd66cd626a303bc33c419f","730d055528bdf12c8524870bb33d237991be9084c57634e56e5d8075f6605e02","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","75b22c74010ba649de1a1676a4c4b8b5bb4294fecd05089e2094429b16d7840c","5615ccf831db2ffc82145243081ebdb60ea8e1005ee8f975d1c0c1401a9c894e","38682ed3630bb6ecdace80d5a9adc811fc20a419f1940446e306c3a020d083b9","cc182e6e4f691cd6f7bf7cb491247a4c7818f9f1cb2db1d45c65ff906e3f741b","a50599c08934a62f11657bdbe0dc929ab66da1b1f09974408fd9a33ec1bb8060","5a20e7d6c630b91be15e9b837853173829d00273197481dc8d3e94df61105a71","8d478048d71cc16f806d4b71b252ecb67c7444ccf4f4b09b29a312712184f859","e0eda929c6b9b628cdeb0e54cd3582cb97e64f28aab34612fc1431c545899584","9df4662ca3dbc2522bc115833ee04faa1afbb4e249a85ef4a0a09c621346bd08","b25d9065cf1c1f537a140bbc508e953ed2262f77134574c432d206ff36f4bdbf","1b103313097041aa9cd705a682c652f08613cb5cf8663321061c0902f845e81c","68ccec8662818911d8a12b8ed028bc5729fb4f1d34793c4701265ba60bc73cf4","5f85b8b79dc4d36af672c035b2beb71545de63a5d60bccbeee64c260941672ab","b3d48529ae61dc27d0bfbfa2cb3e0dff8189644bd155bdf5df1e8e14669f7043","40fe4b689225816b31fe5794c0fbf3534568819709e40295ead998a2bc1ab237","f65b5e33b9ad545a1eebbd6afe857314725ad42aaf069913e33f928ab3e4990a","fb6f2a87beb7fb1f4c2b762d0c76a9459fc91f557231569b0ee21399e22aa13d","31c858dc85996fac4b7fa944e1016d5c72f514930a72357ab5001097bf6511c7","3de30a871b3340be8b679c52aa12f90dd1c8c60874517be58968fdbcc4d79445","6fd985bd31eaf77542625306fb0404d32bff978990f0a06428e5f0b9a3b58109","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","e2d6963e7bf7186e30b7a4c9859aba4e96eda6d1be537e5b1a43bdddc7e9dc8f","10afdd7bba6ec9b7f95a4b419b2dbb64245fea4a61bbe7d68e2f841b414f7312","413121b26b3bd9f7fea237f15f564ee2b95bcd0cceec1b1621075375ccc0c0e0","d2af215963d01cef397ce8fa2f7ad08ee8beffdd39fe14b96021ddf26554b59f","2fc9848431d0f5e2b49bb312aaf07dd2d5a34300a2ced60200a2da49e6a82b43","c5ee2b685431ea2b9aacd9bb9e15cac1ecfa5c448972b188432313354d47c848","3e69be1137d88eb0730332aed359caedea4a27903da15dbe6a1615fa11206807","2283d079c3945b6e5ca8b9355311a213e03b74bffc65a3234c3c141a0a795700","f47272f05bd57f4356abc81232bded724d13e54f0fd801e0fb93a58237db1829","07ae8e9890f49ef6ebe629e339ac590025606a1e96754965bbb2bf889199ced2","d5c19655468e29f60c871b21e73af8ebc653f736e7123ade916f22c4a5f80ce5","6a8649609161e2794b383ba275b0a6cb4a072dde7b954648f83dc6cdf1bfe4a8","601d4a40a69c782addaf84185d4547568ec072095ab9976610d89922d1291f8b","d5c19655468e29f60c871b21e73af8ebc653f736e7123ade916f22c4a5f80ce5","b5c9c8c4a2cd1cb9f76d849fb472d475c3cebdd48306414a4a19bd11d82c4055","b61e6a808f5f50873ac03f35d5a38fa8d4dd23a24f80ab69df1a032e8c71562d","c8be9283a381044a392a0687af5d98d3f51cbada2320b1801a82c948b6e39499","85052c71d72b9b017c88179f57a464d66e22619c7acd7d83b117a79cf1608979","502cd7c30fe21e2c36b62913d0cb5d20affc8779b3ad40881b26d90a22de3aaa","6d3101b183ea67ef606b93fe42127f30b2db5ac3b72c34ca9d6d8b00eb85d0f6","f5d7a36ff056cc314b0f61c89a03c4c36a24183b246e61d958e75e86521304cd","ff30e8237e23dde68041b5f09526ee86835b12c5d8b6421c1153093fdbeb9438","f516fc1e77e5ffd71fbe705b679797c3c5eb76bf76a88549e6316a29f3e197f7","b5b1110565ac688b660a893654a6c1bce6747f3aa6f847001a8a5ff4412394ba","3a971ea3e36685b96f24fbd53a94ad8dc061711b84e51fde4cf201f7041e618d","9b6c162d20e2ad4abdcff61a24082564ac59e63092220618162aef6e440c9228","7804ff981554ba1a0592481072806fc39dc1484791beda43eb7a60e16e70a360","fcc8beef29f39f09b1d9c9f99c42f9fed605ab1c28d2a630185f732b9ba53763","d6e6620a30d582182acc3f0a992a0c311adc589f111096aea11ab83fc09a5ccc","6213b8f686f56beab22b59a0f468590fd3a4c5fa931236a017efeca91d7c9584","c451cec9a588b1f105a5ea2c6063d4fca112b9d70105cacdadda0e1ef67e9379","cb047832dc68f5a2c41c62c5e95ddcacbae3a8b034d40cd15319a8cb7f25104a","980336ccdfc3c08f3c3b201aa6662e6016e20f15847f8465b68f3e8e67b4665c","5a3493939995f46ff3d9073cd534fb8961c3bf4e08c71db27066ff03d906dea8","8f333214062532989f190aed5f99c62eb820722e41956e8229e17cd246fbdd10","d1f010c19eb9c8190bd0859fa3b6f4975543b912b8b85e20bbb0b5bfbdf4d2b3","de4ccc96cef3f97fab148640799abb32a24b567a902a8233913f98481e3131bf",{"version":"801934aa449fe6df584bccdcc5d5b9280295cb7ac84918b6014fc5086e6f9ff6","affectsGlobalScope":true},"5e379df3d61561c2ed7789b5995b9ba2143bbba21a905e2381e16efe7d1fa424","f07a137bbe2de7a122c37bfea00e761975fb264c49f18003d398d71b3fb35a5f","6af760fb9ea02dc807c5053d8aee86389c4fce72fbb26af7b9568cac6c4710d5","c62c4ba5e910b4523f7e7adf4a55ec45c2bac99d9d8e9b0fe0c2a800a6f641b9","92131434f876fdd6fcbc40bd54a9d7500c66974362b16bd42641f990468587f4","8cf023c0bd57992fdd2ce6a7030a1874f49c8edc62eaffa9bfffcf18d2a2a1a2","8ea8f3040e38fb50d7dc3653f3b8a0dbb5244e82111576f99ce096bdc0fbf94c","48ed788ad126545a6156fcc37cd3bcf17de18a3e3fe6b6ef62cfb8140d1a45a2","63c271a745f628ffd4bd7ad0a63b021c362c9bd6bf8b18441a7162892395a214","8d3457e6c7c5cb890729fb60cb8db18f261226a3ea3ff6a4db4b84ea78313ace","9f9e5bae412fa5909fae636d6733aee27a108cc2ed5b13980611016336774d3c","662fe197bba64bd3f17ee118058cd2d0d2dbe33d7c0c865fd6365d90bfc44e1e","030519c351f800551cac2658038804969ca4584d2c0175a710602ac234ca1340","0278a6939ca83cd040b08ff8c5fc7838b6693ddc52f22526bf158e6b10e0246c","c2d6206e5ba4fd3063b01218c2b3b997afc1cfbeb49fcee991fa8595842ce53d","6a8096993458a3d71229031aa7415974eb5b47b320213e29660adfb519d6a3f4","cb7996a1af5b1d276483cd0c9b9de6540eff021abc90a720511ff4464519a2ff","9df6ec68878d65bc690ea3a33ce3ef5aa8254c36bc5f8346c0c2fd1f3b88a35c","a4fad04c4acc8a4b195cbbccef4c55019104753d547d5c94441643ccc89108a0","0244c23ea642361f7c192c1f0cfff9c12cfa5f51f9b155edd5c0a89fef308d34","c7298e68632ab03155f6de963d3d09cc4a5874c28a81524f56c667d8a052e538","3c69a83bde847af6fc3a53e1bb6b13cd06d38a27a142814b8dacc374f3b93284","5b46f7113f54565e7ffc83f2b474f557a1f54c7e5946769d5be220454656be73","fb58035d39c5759283cb73cfb3548aefe370aa3ad4e81fdb4e46f0979eb7669f","1311c325948b2d5576cebc70b1bf968d3446b4630802bef54120daf04ce1f625","d0b3609e8e7afed0fd0570152255458407e67249b94f6603afdfd68599423f21","17f4c5a1d6eaa87ea27eadcdff9085af3190533d98f799dda79a3af6f9a630ea","3e6f734ddf40e2e99ff7fff9568b7d9720663af9a0632c26a352c8d3270a3f0e","ec13f78303abcf550c5569dfae1446b8ceb89050f68ce04491481e72e8122ae2","a3fc57dbaa7f1efb010399ad4ef4fd9b462aa4e93bf74a9a34b099b97ffcc9cb","ffddd7ec6a450b0cb6f2f73f80de1df963ead312d7c81a8440268f34146ecb87","5d6a36ca0087fd6876df654d1b4192f0e402adfde994ad47e5c065da33692f9c","eb157a09c5f543d98644e2a99a785f9e0e91f054f9fecbf1c3e15831ff5d63a7","edd5530e2b1ccdf65093296e40a8634fcb11ecda3c164c31383a8c34cb04bc9d","9dfaf96d090fe8d96143465d85b4837661ae535143eea9ef99cd20df2e66338e","209d45c27e03c1417c42985252de6c25a2ec23abdc199d88e6139c88b93abd11","0ee5cdba58cfde3012bb9ff2e9edcc4e35a651373a2aa2c83ff9eb7df635419a","540f4dca27ea5a232828b6d91e1b2fce2720bdabaa4c1f3fbf59b672cc58bd8a","ba086b99d545ec6c9ff356989f076b5652ea1b09bcc65b87dfc43a5195a2efcc","c85d9776b36166b928ab1488d9224ebf970d41b0a35f09a3ee0b9bee3e698061","683196f606c5dab1c8c4a24a66d26e00f16f2d4b2a5abe25ebedd37d2954f930","9c3a1b01cba1238fb723ce06b6c163ef6c53be755394406782564d5c42c636b2","6e795e6270d39e918c7a0e62ac73793cda06fcf4b3692ee46583e15f5bf57ab8","0e821ef1eb67fa6144ea4de4277d913f5b1982d7407afd5f93754a8239d41554","5c09195ef359ffa9c6bbdb4fefb101d87ede4b9e9c28213faf5b45d102e4c609","80b4d93a4dcc90a12f6f4bb7c6851a8182ae29e556716d0d80b5c012a5ef554a","2556ef9d1820e0b6bbca6dd65a50ea64f525c4d8247ab50dff44c3f0d14a5643","cbd1c836db190d6e3add07165afc228f04e1f6170e1fe3aa5e6fc24a7e9573a3","9b13881feb958237232586d888a10a39d47cdffe3ee34688ed41888fa7baad94","122fe82cf5af80f0b26832b258b537b7dfe3ec28449c301b259ab10204b50d45","c467dada8fea6d60dff8a8be2675f737cacc76e14e50b72daa0f0710376df84b","9cb80bba611c2dd155a446ce424fe4bb1df2129751bc9416b7e42c055d1ddbff","6ee568039016b81ed70292a595ab781ab978cba4243a5fe49507040ee4f7ac8a","043783bebe87efb440183c9ebc8c4fdc1bb92060a5a0f7ce847e30dee7013ac3","e3dc0a97a59dea936b4fb7b1f6f4117b4aac9c86d0cd08b69bab2d0532a8a5e3","5d897601f8a4fe913057019d8211b99b06e3138f625a0cfb601d074f4278271d","a68bb369c4ba8ab43a78f3fad2d3ec130e1418bc946521b9c84e9b336d6e88f1","65f219e6e1f9d27c677a49d41ae7989b83bf6baa56debbeb50d95c3ab21632e2","cfde5d194dd858ad68f910defaed5b0d28730f8bf38359a9265a93ab29bc7bef","c89354ae268153d965011e484150f0c92faa87f3f66507c25b496973178e0400","f20aae41b169cddcbf3fde8ac380443182c8d7225194e788c404d9e11e6dc75d","a6f4816a634bb1ceb513634c1ef7c0535f461ed2565336eed69f6ac2babbe15b","c48566cb13403fca44192b4528e3f2ac993869d39526bd42cd2f2167c0285add","efae20e0c581240c7522e04829da4f0453ca263068596554d4b0e27878c7dfac","3af68ef927788cda7daab34be513fa4508229fdc6e5130d564a0a1ccb3fefafe","bbbd2cbb15a37d5f4dd54ad8c7c537d3df8352117523030fcec7dcbe62a05a58","b50d24ebc117f8805332e7e260e9587f572bb7b2ff0ca1ff6cfafb38015781f3","5cc8b8e18fe7fefab4b3c53a39467b5a0deb4200abae7f063ff0624b9e856c51","8e990781eb0107c25429b1274a31a4f3866a9a46290cce40f354b2a6e71c6c21","608c45069e89c4c8f0ab29f896cc93c6553808072d6304b23611b6c6de3c24bb","22cbabe752781b5f35482af9d1fcf1455cb1ece74e8b84700d4abcb44abe3776","b9ce4613536386a98897f1e3d8f61a851ce6cb34dc3c9db4f2ef5f55f007e9e1","a5d1209c7bf277af86281392d46e12ce3dd6052586053f757fb2e606cc75c0f3","31b5f53e3d57470830e87f9e03c02d4569ac81d4a758fdda75092f9a3f58beba","d765fbab22fd7003a65ed670100362ec1c90d55a772e6773a774135594e7ea41","c1f11d9b42bfb0823d34d93c58df91ffb6690b5a717b7d310d83f258f1784e58","775b207f00d4df5b3b0b536aa696d572cdd2cabe8ea18dd28e8b52f691fa2a55","f75cd30f162c2af5e5aca39c01c1a521bfa034fae523793de872815a3468bc08","0cf1123db73dabd86466a462375a6addae52f58d23030c6033f8aadc23539a36","e29cef4158591ed213b1c2cba8988237b1ff369f7a6ecd8cb8ac0302bad1fba8","5307876e4d0021ea01235eb2f7c24671f3d8b37590f4b446cd132a4e1dc9a335","92550acd737790dc60c4c130e6aac78656dd48a8334a4882f40e7f86bdf7a590","3df821880914f8bb3c8107b1107be75c8ddbe2120a2cefabbaf9b65936b5f4dd","f46ba7c6fa7fcc8b3d57c4618c18db3f4d8bfe1fcab5551d7f6d9a82cf4d6078","078b7043bea0968860374bf4671ed74dd9f6be4e28ab659517d81f74be463c51","68b139ebb9a7f3ee4ded6286d74f978a47968727665120f3bfc560476ce33c4d","56d02c29b2fd39b1b1a1265df291f3f98e6ec3e6119aff9f4cfa44fe888efaa7","2d01884891da6495cb4a2f060e4898209a507e711464c4c1480df85264e863ed","c485c6497f7587314c4c4a59b74850cbca4c0c4bc08146a918cfd237ef821dbb","e9eec004735b1bf7015edf5400aeb914a53132134d230e93786590d904d094cc","080b1aa93227952b4dd74b9d2c6e4f6002eb8403533749116a1c53bb9961c02d","874087eec1d457f6e3baf5ac46c42ea200e55040b394fac667aa3a64c49f5f6c","6e8a5b04a18abb192abc89d7219b9c6f633cb3136777ec808673a65f111ca749","4e7ac7e5dd58a6c29c724728b031669e3068b194b62c2b83f92e76a36cb34dbb","d74d2a92b54f95e47d2b76bd5ee516aab7ae93afb79cd34c6681dd29eb09e72a","747e6326a724bc54f799a466a5b5c4978a601a04a063a5bdabe150af2f25b9e2","b57e22e53b56cca7a57bfcfb234aa6a66f9b9e4c07159d7388f94f17a3eaee2c","e47709ec4d1618ef429648cd8ef967aef2005526b34fcbfac33037add347dc71","b81abb3e47fbbb3af41fa75bada89bbcfa4b0feed9a0d6d4b19ed1ce1033b53c","15b330546e9784461058e5fd6e2346bf272140fa6f0cda34e193ae501d8b17b1","4d8ce72fd080bf9a46bdcc274bcbacccedd66d84e203966b197ac25a96932183","73327e6ae34e3f6591877fb75b451cf620cbbd76ee2b678213a9f793633cd0d3","3f1ba2f69944fa346789db7f60d53c9bec00032de0d797967978dea42e77b941","3f5df31539fee4816b97d4e45b4344fbdaf3ca59f6df941f8d780ee441e92cc1","50aaf44eb4d0e086af13729b3471a0a7dce95ea35ebd21c762ba26e203134b2e","3857c1773b8503c3ca45b7bc09ac89c3930c85ce93021054503f73d5d9101b5c","72702bd07fd6fb3ef64aadbcb909103aadfe71ee76e9fdeb11e0c92693cff6cb","f0dd6f7c9783637655478db7d7caf6becd41a79d54482aa59578ce88ab38e9bf",{"version":"cd756ccdabf433dd02b84d755383e489f14b3c1aede0477783aa04830fd5d695","affectsGlobalScope":true},"a4c88dbecdf8ee0c79f5b7c2bf31cd77e593f5d78384e2b674f67d754a549a9e","9cbdff04326da794ba008c0fc977ab062d1fe3fa2e9759654c72ffbe54b64a7c","aa60f8d20d36116fe05edaab24adee3c275209f71b65e272692cf99daf9489e1","150855f967a6490161d5aeed4cc4adf31fcb8f5dbe54b75799c12b8687fc9cc2","79576487ac18e047e8192fc582ff488ce375fe4df0cb028a17f831cf42b976f2","47ddb601df40bfa01cebdd06ee8b87d0b72aa1259a4ceba3ad3b5cf68130112a","6b6392704ddb3f50e647dbbb716782bdd0cf8ea9cc134aae256a26223e632b47","afc3ad2a50f7f4de908e26fcf467e09ab8528c0e90f91e602b4865d953839228","df90b0c6b1d81851364c4d97fa23b91a993482bcf4a7bed7c7a24aa41632d494","db34610570eed46b8b72bc662a91261200b8578af0ac02781ce7d9aca99bc683","11ee9ab699b4619d217c640d917ca198f58066a86bd58c2917197d62aa6601e0","cf9d589d9e73bf32c8e7a6cae6b4a1cf9bef39e5594072533fdce985581a6ddc","959544feb1ca2df29eec6c500f27ea10f4885df245ebd8418fb4b87914614383","6548ab4b57eb9d092471a04513091673345f2fd95d5b876f600402ea8d603ee0","2793e8c6a023d26f78d6777a6d7f20fae3a9a8169863d46d8d54c73071851232","d0f11e830aa1350a31d9c00a0197243e9711e4882947aef53a96c629f405cb10","6610b9f45f1f71d2b1fb67df49cbcabe3f9e668a1ccb7d8328a51407b259ffb3","abbcc437e0792ab2fe08797ceca1ec85a95ec413c51612313b18ab8e75f690f6","e29d76ef1183ac0edf94b4712b6e51730c447c7e773e75ceb44a720b0c9a9fd9","4ee6dc3424998eede9a2a9b114acaaf7969cdda67baf82ba2c9cf88a8eec0ab1","8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881","25139d6a726e0e19d9fc4fa3197367b4a82ec34a08a5ecf23963e142c202c0f3","e3328bffc8eab74665a4fe9c59d6f12f4c8570c3d858497e241eb37efe17dfcf","29389551e426a46421134b55182d6fcf5b143670998bf81db2619c1228235392","c18f7e16982695bdd04e3e183a327d116185f77f1a37b9b2e849d7d93269cd74","2cfb37011989c21dc70b91d521a2d5a4e0f18507f5f536b5dfe504edb15916e8","bb5e02df7aaec7a4ea642639a9963b24b8d9fd6798351f07d8c58616942fbcbf","299a899cb4d061f5d83843ec453e936e9659b2c435746823f90c40eddaef4745","d5610c0fd12870f644b0f42c1bcc4fa2295ac3e3ca01916bdb42c3bdc4c80c36","2c56a8e249b1f45dbdf973100cd37fe2ea68709573cf1fdf2e3052c593be68d8","3553da417ee7b07e388b13bd12a70a1c03e65a6132ba5427fe68f5b362373e6f","612358502042d351c227ba779fdcf6d875d827e424930e60297c533524e50668","d2b5be376ef162aa0c24a826e7dd2d77671a045c085e16d1c1276db4bdccbac7","c4138d8dcccedaff6621e009cf0a54a7bed2a5ad4c509a3513bccc4f417ef939","ad8747fe978dff3e80f4b12b48d37cc8dff11b61d04c035aefbc982ce21201ce","b154f789fd65298e1ba6cbba6944ea892d564c95f3d3700ed85baf8f80748473","c660265aedd7c5b236e2017e53095cb98da66200eb0e8d023b5bf713c36494e8","0efc36bf5c0daca6217fec7063359ccdab8c3a23bb405d25340fae22cf72d74f","5abff0c87d4f9c89715107042d4c73b68ef7a128759f451c8a0fc450cbaaf660","5a03308fbd1af441065149a84c692931bebc7e7735afc23be8684f4e10d3aa06","c787bf4f8f0abbf815cfbd348be41046f2b8f270be24fe7aa8a8fcdd2b7df8c2","e7a5191c663a3228f30104961d548b372e51c5936c01ffc8eddd262bb98d7d7c","43fdc9abe6f8640fda4cdc55a1ee5f666d3fce554277043df925c383137ddf69","f0b09665c9d52de465687fbd3cfb65111d3ffc59ae00c6f42654150f3db05518","72f8c078d06cff690e24ff2b0e118a9de2833dcebf7c53e762dcb505ddf36a68","9705efb0fd901180de84ca4dd11d86f87fd73f99d6a5660a664c048a7487e385","f9b9d0950fdfb90f57e3f045fe73dce7fa6e7921b37622fc12e64fcd90afbd0f","e61b36e7fde608f8bb4b9c973d81556553a715eaef42a181a16ddd7a28da4ac7","03b8389b222af729eae0fb3c33366dcbb1f5a0102ce319bf1d7d5ee987e59fd0","2bf6be7c04db280fdd9b786764f8650c23f9f4d533791cb25a11b25314b76a55","dbb5fc7edd36bfba95cc4dd564e4458276ced30eed18bc05fbda948b3fda8686","c2b556c7cff0dabce2e31cb373ac61c14d8ebc35f1086dff30b39e9ec5357d0d","f958af01131076e8af55d28c4835a51063226ab488ca8738fdee38aeef7d0d33","9f3797b01e3d83d4e4b875699ae984f380ca86aa0a0c9df43ac5bba1cb1f8b7b","752b15ad1b34887adeaa838fc55f5d4ca399026afd266d4ed4db0e3db02eae4e","778331eaea1093451e50be9844bd2b6937c3bb81b0b1ee700624c9774ecfcf2b","0ca0dfc9f657d0822eca9530c7870b22a1d2a5fc48182bdd4d0e6e88e4ad9c35","5c746f034288e6842dd1589b169dcfcc16c5ce5abbd928889ab67aea4fe0b501","92ce6dbbcc135cffd09a58e19fef34bf351391bec92c40d849e4e9997d475769","99e77d092fed72b6b8578d00c7af004f76e98b30ba99f1947535eb4c04a51676","b5ef52a9f9083724decc5d060f0b34e3a480deed71c32d55ca16c214eb4cc928","5d3d7938b2d7d0a9f851276741327c2ae4c013e7eb420fc3f7caed3b79c8f37f","14df6b81e50a035e9e391558cf30a0420d03e2eb42c7db9c57f44b818e5d5179","f100912a3785eed4a3d29c12f5910b101af9353454de5ddba9b4d43456c56dd1","446439eacf81a163fd7dfc53b28f80deca3d13b250d67639739aa25aa4491090","98034cd285344125f7165a3bb68246d38ab35fabe7f6d6a7c8f80407d31f548d","06b4a23064991251512df4edc12341d5bc69a17b942da18372312d383c90eee7","0f898802705f9a534b537f1be6c57265080e0abd6993d935554c255e6d56cc1a","745efa7b6e27b7216cccede166a822b56acc41b10a8090966c8cf2c96239cb83","6ab2a6257ae7bb05559841100c786c845fe465a90be7b904db9096c2fb14696b","26958d6f77e6db2425ca65df0fbfaba439396ef7f4457f5643fc32e4b62568a6","5d697a4b315cc5bb3042ae869abffd10c3b0d7b182cda0e4c45d8819937e5796","89b040dec8fcfc1de98827e1f4d4977e6ff5d3302c6790e9f10b54b916e1c742","6ee58aa536dabb19b09bc036f1abe83feb51e13d63b23d30b2d0631a2de99b8f","8aceb205dcc6f814ad99635baf1e40b6e01d06d3fe27b72fd766c6d0b8c0c600","299567f84bfedd1468dca2755a829cb19e607a6811673788807dc8921e211bc9","795d9fb85aad92221504db74dd179b506bd189bba0c104426f7e7bb8a66ffee5","1311bc194e0a69fe61031e852c1c0b439e2a2a3d1d5e2d8ff795499b9f283459","4b7ce19369d7e7fae76720c2c6c7f671bf3fa0f7093edb864f1ac358ca7c456c","c972ef44deca1fa8fab465915ffa00f82e126aacf3dfc8979c03b1b066ce5bb6","30285a1011c6d6b52f3ba3abb0a984be8148c05cdefb8eb6eb562335a3991f35","e0de9f50e80fed1cc161b50e8e68dc056e38df75a4ef667a06b1922e372de169","6a8b31be08b212d1fc96de0ddd1ea49f32382ba712fea24c70bb56447f643f82","19ac6d624e4c18de4584db4bbdbc55387dbe3d19b3c134e50346bdf165658a17","54e3798c2801e8f3bc7a825d3d26c6a80ce763e19e6cb0b714594c430ef72332","70b8333214aadaccda8d38435911d3e3a686e503837dfda6b8c3f8c83e05729b","fe849e916564b8172f31a547395516668f3c122bfe017f82e7140d8dac402208","d42c6e985bdb10a2aaa3dae14d9b0d8589e74a7c2f9475bf543b855bb3c010ba","56c48fb5bb6316dfc27fbad065966b4ddbc38e9a0a1a5060d19b5da405ae7d6e","7091568b9a1b74b699ad09df6c130db712ed089d173a235e301a7a7ee0a4ca44","de33aa2a38affd9e71297ef7ec001a4525502878b09744308fb6518159f77d2d","57476e482c9b4e152bd23d0dc3c29375e48afee0de775674a3c1ea63cb4cf843","3ec4ecf6502ebdb1f3e24c712eb70160c606214ba2e71b4304b5a50fc2e4f293","83f7b6c1dc91deece32c3bee746a43f3616b7cc9f6510764bd53451f6712ff25","c23f2e8772304163fa7e4335be11f3dbdfd720d2209057566b7dfef746ef1862","2a26cb78d3de9708cd656787a663902270c9421ef89188286c3b7ec89b63bb15","e61fda2800677c210116c397dd85079a0956c87fd714826c08b25b10fdd56546","ef7bdfb4f157f9c9b9bd7f5766f0f8e07fac8e7482eec071673f3c9d08082982","d2f2ac1436cbb7c8d122cc7de96521345254e5b36591d9d064d9763de2a7b254","3cd2ba07285d01224f9595924dc7f760c7babb386a6eb825cb551f8d829fe6fa","3ae9770861c2ece5849778e9f15567d95b87df0165c0a5b1312181df19458a56","37b51656ff8302a4556e29c775f311eb9ad813948d2c527405cea041dba3baf3","00abf32ca3af92f8be9ecbc9b63090b4909a756317d791927a83cffd24e9c8ac","cd28efe88fac7a92f3f5cfc7dd3c764f0b31bdaaa061ff044de1639810d6a7da","8b2100d3ba68063b7baf5038f26eefe46543dcebf1e7dbaf46277f24307cefcb","131b7f32092aa78e12fcb2a6db7c71e17f85e076c0635ad8d991b80d10130c19","d1c84af1e6d5fa4a5f4badd45b03b67c9255a675df235a3ec25307a0f5524278","aa4d6dc9282133162a76109d99c5795583276c4fd27284f128d484acf12b0841","3355c4c572f076ad963d95f0e28075b8558e2ab492c84eb94f9e2c48f1c2368b","5638cfd48b0c56bc9ed0c779d53a40b92c9cd9c9d6312e3a21c52542d38094f3","827eb54656695635a6e25543f711f0fe86d1083e5e1c0e84f394ffc122bd3ad7","2309cee540edc190aa607149b673b437cb8807f4e8d921bf7f5a50e6aa8d609c","703509e96cc30dce834ef8188c958c69306473b8a7e5cb3a6f324cee05a1f7bb","900daf04dc607dc3858c0f976d6f9e17b829a07de58d62dc6f730eaf06986075","08e0ac95e650bd4c87145b6ab2257b70c06254bf76a0b9f8a7d60c51fb8ed6b8","4b57ec505a035491c692b89af2c6902c312ec22f8fa9b6dae3e93686659fb7e0","7d796672940d3b2d37f2edea4d7bcf4c7993966286006228cbc8fa35ac92871d","132fd53917ed7f55275faa52c35e4d4d41e9576fea231d12740b723df2bade93","de2ecf9b1d6f60338f7b59b6f593ef77af9abd0e70ba8f2942953d0c6e1850af","cf18e9d003f1d3d1d61a04eb2d1cff3e8a8cf9cd306d0532ea82700069f2fc42","393192a39f26f9247a74ecbaea6668972af8e9125c955d1798234dceca6010f7","27ca878cf70b3030e8403f51ce65949d364fa776d6dae3527f91635a40836672","178e2de7a8702742957ad24deaeddec84a48cd913b5d932b16afd2a707b3e416","a45ee7555d019a67fbe092898d1aef0b1d02a9f6679ab84461ff515b4460d706","29c188a2c660f99f1b4835022e011c4268d7af989d4b7dda33c0a69ca1a777f8","1ed0bf138e87912d741e28333b58cbf814ae863783b3b404d2454cbabb9c5fc0","3452ee7d8ef0b1bbd47b2a56924a1dc3c79dc84a19d212e9dc496f92e4943aa0","8c95f96ccd4be0674944077aec1e4f2cccd515ca06d4327562dd017250e7d3fc","6fe7571c8a80808224648046008d1366ba4e29206ac79ce4c56d6fab3350492b","a98be76d8c257aa9e316bdb305b8c4228f0cf904d4b70547fc2999f3f99b5a01","7419d99dfe020d543c8ee736ab7ec17127d6a2c61c40e5f245c6dbd3fa6eaea4","2495815b16258136f98d91e441f4462f9b694520af86bb8c8373724cdc410096","a64568c16a5821575de4f6280ba1ea4686a1ceecd649fa90ba957c8b1b007013","ac46f284c80582f7c1284eef93f2d1c80add2d3b0e8a4076d6ca3db58d3af747","dee4dbaef83bb1061a44f39a91a59300d3dc02528eb57f748222235dd8e02159","a39c32b055d2e6103e5c49b9aed2d7bb5b06571c98fc31105264d280431bdbd7","618ebb93311695a13844118cdc9a7314dd3a2c6f35092d87f76828cac555ddc9","d36c3d116ce59a3f072c0014f0c020c76e916ba906066ddc4f193f546a43bceb","9bed8447acaa89be63540ec500b165442fcb0de020015175b5a5c66d42a61c4a","f128a2d1209d243ba2f7755c2fc313be2c7569fa0d9b4dc5cc60714fb0cc6634","a17e6861b709149f29a2bd896cee94526df2f06b24a2b60614b56649b5e9aabe","9c79ace686f720f4dd833740f7190e12cdce363362c982c164745527a412ef40","439850ca5075c6db55487b2c7fb27a6051fecbf180eee0809b67bb2783a89813","75d48857bc4216880443a24d985071262bb8b89a9952c77fd430cb0caa21f9bf","33e40cf77499b3d9712db82e15683373925e85817dbe82a24ee0ee6e44bffb70","d5bbd453310990e851908183fbbef9e6e2db8e0c86d97b42b723fd5238f71c81","95e76bed30f6e993e1fcc1b90a4675682e4800ae43403547a775d6e3c7ab2b0f","8b206b995edc6dd849b85c1c56531b9780e3ba75302fd02a2d173f008028707e","97040b190f0daa10cf9a15e51a2fac66b26ddefd7b65998bd6027d1dd67647b7","877c25dfae100e555014e45d1d80364496a0c876201e5dea91a0fd0a6a4ff765","d53f9f96afd41359edeb2d5ad98559f3bfad261391d5aef95320fefb0c6a8742","23d98226adf3be74e1f0470f85e7fd154cd7aa979d60b43190a7437f0d0426eb","639f9321a98b734242a3573764d7f1de5369b0b0b10c768ae37639e8bda5dd03","a42c39d8b7d1b1eccb69c7919ea60dcc2670ea672a0af90b70a730974ec0e9fb","dc5fe5f6b39c3fdfaeba333bcd5f0cc98bb3068797a4d7010f585366f549ddf7","4a3ab8cb278bfd1f18f24cc45a02188b63afa6aef50035df6d79c4638f24059a","e724c9ce92f2a8a31ed260764c5455852a13d292e2a31d26acc6840ec0e83208","40220ba1b091aff0cb20df5467202b62af561b09fcf3b24c22a60066d46f9e62","30abf588759f9e828a94f0c7f031eae094bb668c6dd4d902fa296779267c05c5","bd875d031474860131eadb42300aa57a71527bbb2b239d5b31ab6a9e352c84f0","773bf9af93b5027de9b5b4c779d5cda35f0eb92c7f43a97f2ef3ca081495a191","617f2b4f5115969c7b0f225d4962e6bec1cec7e5c687d84370eba4931b7dd047","59625b1fcc91f2686751fd5b623126f434d7b405bd8d866a555963ce2ac69846","5e0dc1bd24b45c46f2188d2f7f4b67f311610c72b706f963c5bf04c2e1fcc35d","fc69ffd599d3e525aba38f80c7cc2ecd187dbf148287364c75f199c8294a00e6","2ad138be6972de52ed966e6402aa6403af79e9f183486e0a725ffa075a0555fb","480274a4f75a7b3bd5c697a55e1905887b62a928592c0db3c282525fb235ba70","967fb6e86b55db228ab50c81f85f39d6a23a0c15bcfa6e19d255e0952d33a65a","c39e7d32dddfcdaa93b18b99fa430ebb1d6ba366459563d400add22f92e3644b","e3932de252bbe43132ad3226865b2a376ad945dbc1d767540c01b7bddc6477c2","b2f52f3cbd863dc4e690614b5cddbf412dea435d0de099db6d8adfd3cbefcd65","557c93b35f3b58e6844a9b8817559da1e0641f7f08f918e3cd1a8efee126746f","80ad2ae93d57dadac5e377ec6743df5e0211ea30bafd4b648c52366af057bb2b","07f90213b5800a0b43a6d6f309517dcca5afc6ffeb4bed396878a29fc5d6ceb0","bb0e637020f81cb40d16f202c3a783f0e269e29547fb84ca9f187a5ea8556965","462da802b50ac0d94a3c8f7f58a6a0aa08108bfc1394449ea56f1e0f63f5132e","2ccea88888048bbfcacbc9531a5596ea48a3e7dcd0a25f531a81bb717903ba4f","b7eece07762a9a944f097ee05d6f6b642b1e2dd87e6fc1b09600d881e5475377","1916218868966583a3c9f18501ee78929cab8450ebb8076ebd609873c258154d","98ca5ae10ab02fe747a7a53138f43525e0129aa1107892ea4e1fe9c99575809c","9760678d20c9faa0d0e1269806bce578bb76598a4a188a4d3987171263be20c5","21f706150e32f03ecd1714d7a7ac55ce3caadc7c7a2a960ba57cc5d39ad84c9d","6954ec87361b77bb8895426909fecfd154e3fd72a2b82f681c8bb15bc46f2389","da1963f37d566ff9f71bf8ca5c628656bae02fc9509050041547e9c7063cc58f","57e4bed825036f7f1328505bc512af492f28b1b57a48f1ff9b6d90b930041a52","3ef0957915b7719ac58153eaea6ce810ff8688276e570f8938455f3ec7930df7","05e0ad043fdd4e2d4874a97bd716174af64d63e43851c09830c00e819a80d395","2dff0ed1eb2046fbdbc2c13914117e1ff1112e217f90542ea5e7f41e39f0393e","a0ba1e2711c2520189ed980225e7a429b0706a1eabf9113e53f0e72550a1b23d","169b66aee819a4b165c397b832b31691f0be8d35cf8f2ec6364c23ee727b20b8","badb4cfbfc6eca3a038be22c76297bec0b5c1478d8b73d60e8b50725b7dcc15c","21e7e0eddddc112f2b891d1066eac74680291db768d3ef9b908965935380ab98","489e195150979dc291520e6f3289f055516cf342f314931c6b4553aebf2859bb","516efe800aaa0b7504b71f2d7e7e9bed5f28eb6c9c739bfdf237f09c7addea46","10ae729013e6620dc937df5dd7077c34e29ad313a28aa75cec39957640cdc8b0","ac5f95dee5e4787fa7c68a81a052cdfa0e03adec8331d3276389384df36cb849","0aaa321f1f662ec931e55c85867d316d8af16b59337111e22901516a0e1caacb","fab58e600970e66547644a44bc9918e3223aa2cbd9e8763cec004b2cfb48827e","a120dfb4736e6ec4c78f1bff5ff7f977d346152e6b7020659ee1ce4717f6f66f","9eda7b58498bed72dd98ebf1d6f8dd3bf5df5004b2f91c610093bf48f970c615","8e7adb22c0adecf7464861fc58ae3fc617b41ffbd70c97aa8493dc0966a82273","755f3cd1d9c1b564cff090e3b0e29200ae55690a91b87cb9e7a64c2dbeb314d3","d6bb7e0a6877b7856c183bff13d09dd9ae599ea43c6f6b33d3d5f72a830ed460","f1b51ae93c762d7c43f559933cd4842dd870367e8d92e90704ffa685dd5b29a3","3f450762fd7c34ed545e738abccb0af6a703572a10521643cf8fc88e3724c99c","bf5d041f2440b4a9391e2b5eb3b8d94cbf1e3b8ff4703b6539d4e65e758c8f37","8516469eb90e723b0eb03df1be098f7e6a4709f6f48fd4532868d20a0a934f6e","d60e9ab369a72d234aac49adbe2900d8ef1408a6ea4db552cf2a48c9d8d6a1bc","0ebb4698803f01e2e7df6acce572fff068f4a20c47221721dafd70a27e372831","03460a54d0e0481d1e11097f66ad43f054bc95efdafe5f81bbc7a82be181af75","ded24ddc7157689a5aa14bd651272ab8cd6e7812da2196a65d8c5e63131cfb23","2cea9689efa8591732096235abe7f084fc29c92badd5b0897a5e876b77e71887","4ed4e504126014fee13aaef5e3fc140f2ff7031ff3a8b5386717905820ea2d09","00e77e0bf106bc1e4f303ab02724df5173506d44acb6c22b4f582a88f22c3250","30d2170e1a718b5035611af55e3618b4ba8f42f0749bb52ee593da6082c4e2ce","568c86c9edf716ae08cd3c8ca61647a3eb43ff52a9aeb7c972f7be62cd35b797","a3b8b6be7620897d1e481e8650c980a210a138fceb6e710eaf95fd9dd0dfe94a","12c89d0e32758c120a569045f21cf5b77244f86792611ced8de7f86b37e77781","14bd47270e654c8eb3b1489fa8c095912ee62a0a29bb92743393203722347c53","cc8b411aec64e03abfc3936a397025c781adb89942ec2fcc66e2a353f93ce677","db5624ecf400ed47648e72353a0ebefd3293d2e6295834a3f013c548ecbd0edf","92cb686a9ca5eb5dd7d5d8d43a3707194c1e91ea07a027b3bcb60b6011b24632","da3ab7396ab4fe390f01091bd0d4c4a4e1e2a15a46d47446d6fb7194897d0f6c","66bbae6120d307ec2021ebd2241b8ad23f832b663e13363ca8b4c8dbc131a4e6","fb14266ae4070bd16db6b071e98887452bc359695c40742e38515a89dbc80a63","4a24d83c0d8f489465c4d38ed9fd87121c8a2cf50c47efe09c2eca93d39fa908","c052e32b9de53cd2596f196a0901801961bd7a31be9fac4ac2f117e4103e3a07","b15cdbb45919bc3b8e6b6f962d65382e85061d70bc26a968604f3dce4ad3a891","d6b58d955981bc1742501b792f1ab9f4cba0c4611f28dcf1c99376c1c33c9f9c","f0b9f6d5db82c3d1679f71b187c4451dbc2875ba734ce416a4804ad47390970a","a5c38939c3e22954a7166d80ab931ac6757283737b000f1e6dc924c6f4402b88","31a863da9da2a3edec16665695bdbc3134e853195f82dafec58e98c8e1bb3119","a00417f73bbba413d1345dd77252ede0bd0c957e37a9cadc9abb4c34cbd0eac1","90d1ad8d2983cb003d6f237b41c56a8f252f72071bbc53576e02b3c96d7ea47a","f3815045e126ec1b9d224782805a915ae01876a1c7d1eb9b3e320ffadbd63535","d07557f21b2ad690bfe37864aa28090bd7d01c7152b77938d92d97c8419c7144","b843ea5227a9873512aa1226b546a7e52ea5e922b89461f8b202a2f2a3f0b013","64b4d440f905da272e0568224ef8d62c5cd730755c6d453043f2e606e060ec5a","d6b58d955981bc1742501b792f1ab9f4cba0c4611f28dcf1c99376c1c33c9f9c","f0b9f6d5db82c3d1679f71b187c4451dbc2875ba734ce416a4804ad47390970a","a5c38939c3e22954a7166d80ab931ac6757283737b000f1e6dc924c6f4402b88","31a863da9da2a3edec16665695bdbc3134e853195f82dafec58e98c8e1bb3119","c0e03327bc548757709a7e2ca3063ca8b46227b5e13cd981ca3483035ef5ac44","b8442e9db28157344d1bc5d8a5a256f1692de213f0c0ddeb84359834015a008c","458111fc89d11d2151277c822dfdc1a28fa5b6b2493cf942e37d4cd0a6ee5f22","da2b6356b84a40111aaecb18304ea4e4fcb43d70efb1c13ca7d7a906445ee0d3","187119ff4f9553676a884e296089e131e8cc01691c546273b1d0089c3533ce42","febf0b2de54781102b00f61653b21377390a048fbf5262718c91860d11ff34a6","6f294731b495c65ecf46a5694f0082954b961cf05463bea823f8014098eaffa0","0aaef8cded245bf5036a7a40b65622dd6c4da71f7a35343112edbe112b348a1e","00baffbe8a2f2e4875367479489b5d43b5fc1429ecb4a4cc98cfc3009095f52a","68a0d0c508e1b6d8d23a519a8a0a3303dc5baa4849ca049f21e5bad41945e3fc","3c92b6dfd43cc1c2485d9eba5ff0b74a19bb8725b692773ef1d66dac48cda4bd","b03afe4bec768ae333582915146f48b161e567a81b5ebc31c4d78af089770ac9","df996e25faa505f85aeb294d15ebe61b399cf1d1e49959cdfaf2cc0815c203f9","4f6a12044ee6f458db11964153830abbc499e73d065c51c329ec97407f4b13dd",{"version":"99e8bb8d262bece129ac203f0c7436a07771e9cf5ba06a308d1b16993606eaf2","signature":"8705a9680ed4afb15edbd7bb9ee24af33060d1165117f293559f3073bf8d0101"},"ebf6e19cb84d78da20d022a95f05e8aef12e56f816a1ee12835a4da40d7b14cf","589357c2f88f1188a0dfc48c4c4cf4d22fac9f654805df5f2789a01b5616b74f","6abe62ec5b9b6a747c1a7687d58ff179cdfb61adee717b6e4882120f7da4399f","5c1301f550be26133f4fd34eadf38815db096ecaf9b75948b444a063097f496d",{"version":"26e64fa5fc9c7fce9daf4131f396fb5012dbdd92fb2e2bcda5aa083a76d18888","signature":"cdca22d444beb7cbe168d11a666b994be4b19c5ed7df1856612ac4dd7c2242fe"},{"version":"34ef3dd636b7074beec81346987a81ac245e1cfd75adf0babc68e6cb6c572ca3","signature":"82065c62b6a8089452cb40191a55299b2d0718ddce833446faa6c01f48f05b29"},{"version":"c1eb1aa5e32fd31d4564bffb458942d8caf500d86388c811cbb853c274e4773a","signature":"a7fe41f597b2631d3fb439d9b3ee32d1606c651a45ce2fa0d170a2614e68d280"},{"version":"327fd9ca522780f73a64e32e400a6c5bcdd89a5e706314d57ff1611bf1a99a0d","signature":"70b3082385b926b4bb0dbcef0b2f444c4f807d312546f27ee248d50b0dfa5877"},{"version":"3b1ea19c2b95501c5d8e87fe4c8044d204c4402a8b48f282bd348f973355f3c5","signature":"01b86f9481ddaf74b65f12e90ae2d5bedbc0e67e64e8cb273c7a1907cc66dbec"},{"version":"897a42f20db3ee955b1cc64506c040b0b1dcebe45d9ba3147e133d110f487f6e","signature":"3089238aed154b07430dd80de65df3115d268f21f1afcd8568a58d65c7827c5f"},{"version":"2d41bac312ef892971b2344a102feb99985e87f79edc18ed2c43ece97703fb91","signature":"2642375958909546f682d51f9c3682f553ae5f919f7b4a77d49262c200bca248"},{"version":"db3db9885deb334e6606785a0bfe7aecdcae172d36a6b4bd55958c756b92ac6c","signature":"79cdba32abf1fd279e588363d3048cb4b3d537a81530d32079cea1df22d66f93"},"1fcb8b15db812281d69a3090d488903f9e93033004aef9d8889ca3ad0753a96f","bdf5a95eb0a2dd1d39805bdf51b46ba012bb9b92b2ddaae16219595bba7678a5","9f794a0e8550a03baff865a3961cc22afbd85bc4ba9672bdda036971928f85f4","66a697d1e4cdbf25cdce4644a8085a8563041fa8c7731d4d9f5e8f22e66ba72c","a0c8e17f4d1ea2704c62d7349bc3b8d9a12e3761b5960cb44144d3f0333b3fcb","3471c0df3d0391e1dffe6e8bf150294531b2b71a2afa5f2b86e52bf84a5db60a","5d4df4de055eddf3187094f938a640f8d96e4c551a47d6686596fdb6ba4c3014","8bc2cad630da1033c1fd8d7df2bffb18af0da6113bd086a8bbec04a2471a1e00","a1059d1bbc8ad46bfe668b8450e7e8002887c4ab987bdb96d6108d8023f8bb8f","5134885e9648e2c6745f8aa1c3e7f5ab3b3617258b3d81ca02de6655ede3d74e","4f1ae3f24125216cf07c5211a3f00d2bb4782d7cc76c0681603f8249f9232ff0","d3fb92a5640f83f7844d60b35317a0f95c27e3658a749d76d218c461ad091668","d1f8bfcd91b284657ef8187c55ace7db91a3c43e642c3f14e54364154932f7e4","f54c92bfcae54f360fe79514746efce4870e4ddabc064e95d406bba291e9f672","175fd7186fa6a70f9db9b270a04a503cae23cf01cb77e3905bac115c38424cf7","c993f7ed1b8e1023c1f2ee5b262dbc3b70b27475674e40a53a58591f9972dacc","c914014ab7c7001178663f29d31a495398234a41219af61f26d7e8e91b46af96","277afd6ab6ec72889e2988e0ddd7d138c1f512e68a1fa4e90eedfd71e2097a51","c0908f85f2b645d375127a3b53a17a65f782e17962d5c1eb68f08b1188acbf15","3fadac5d409cc2f27b1d2f4e7568600f02840205f301c9ae7a3068b46476438b","da6aae64ad559286830fd44c81e3d33303348f184af7db4fde8dd99ae9749407","3633f87c97d359cb55fa7bf0668fb2be8a23342951af6ec2d06e6d0cf7409371","cc3a5427d44fc77ff25e80b3edee4650a51f83de761faf5e633994ecf1ab1b44","b350eda75c6e47299b36002b31d5b220c405c21c365e708989829db013fadbb4","f421882756b6714834ae4687ab1aeadf344a1cc45437d2edffbac020ff3801c1","1d61d6ad832dabafbf63b86c5a79d704f2c8763ada9318e135b17a3cb2d09b32","e5cef5de3e5ad3436d414d20743231e284733b9cf4375dc79eff4fcca4282f99","e624419ba84e33e661e89a28083119ca41f6953dba09a4f82b660684087afe6d","942be430bd0feaced2e3e598273b17e50ea565ec9dac840b580b0b99e1a3cd5c","73350006cec5a0c6b71d53b0b0ddbfb82be96752a9c4e3c904c59e633bc9485e","a7df5c2e9594966c7e0d4a763b13ed5727506d892669df5f7bc9826f539c1d35","258cc5cd6891f6bcbaccefd953997038844e7f65d582cac987ffabf7181bcd4c","00a6db28fc4df6ddf10adbe630d9df620ec13af19039c1869653e60dafa739d2","649324d5abb5464aabe35d86cd0eef16562df811f0971481cee664afa5acbc88",{"version":"628749b6edfc907c32583a77f7dde111642dbfc13265fa566e9a8fa47f224b51","signature":"495944b274165419ec08446dbd612d6276e2c12b92caa1f1e6c645cbc044ef25"},{"version":"e2f7d4348da1a42007547574ec71504de5e9df04d270bcc4c672bec1068257e4","signature":"0d7e153773886e59a74ffe1fac08bef805541411de160b9f3af36f8a6a3c6022"},{"version":"70fa251413c8e1926804d27e8aa01f96bf56141270e8adaeedfeaf0cc7147cef","signature":"2e85c128d27849ff4bd436f75d32d8a64d9013d420f09c82c6eae63cb7131020"},{"version":"334a6eff67fdb6feabbe5a612552a0714c424ccd07abbb096672085e7d43fb4a","signature":"19756a360a54eda2a10138b94b37a87519fd1a27c678a1b82187295e40bbfacd"},{"version":"722e48bdd1c494feabfb081d7d582d4554276abacce92f69128511918c125273","signature":"b195f1ad5886c1600c53bc7296210f9ded9a9a673e01988eecf9f20f48a4d9d5"},{"version":"1b5f109f8e1b74f648bf19b878188928678f443c2b2a21db0861f57d0715ef69","signature":"55310e6719d6bd9462e76cbba6a582712b30a85ee4949b8d98e14e0f46738e78"},{"version":"d184310a8c121c1ed754995dc55f8ca212bb1ef94979f99423dcdc48569b3c51","signature":"99ec28bacd04a3185d90660fe18bae48f33cbb1d50c73c64cae98e67f7c0ca01"},{"version":"48d475a0c6f91f62a89b128923cdec08f1f30a12df0068493f0d9b2774125b01","signature":"6a90b1b75bb0eb776ae223adc1f3f1cc343abf3e68df619933a3248910061290"},{"version":"e581d928f182594fe6aa7c0dd2e0ce02fe65fb53b7d40a59af9c2f171eee6428","signature":"1ac721bca31657133deb33e2ae005d557e8e6e0aa9a466142a2b0388e2e2638c"},{"version":"f2cf0410b95d14c070af93ed700294f19680b00a823d2eb5c8dd6ce433381e4b","signature":"85744b65d5c0962e4e7df8e5b1d1646a759e21fffd160b91d207b78d28da7724"},{"version":"bcc7e1fd0b70240f11846f0c5a284be69834446899b64477371cee7aaca38965","signature":"bab97a4f0736f1c1cd0546f79f993ecf30b34404cf4479a4f39068915880cb1c"},{"version":"6720778d4192df7ececcfd9dfebed8a006c9c44f88fe8b74880ab3ba7e14cce4","signature":"121c82998e23aa414d41a2f08e108074760318a1c11a2a5183b88b0d9be4ef60"},{"version":"e3fe28954899e21bf8a7db496cb4b90313e826bb5ae938d84bc73c3bdaa31cc9","signature":"4e1f22dbfc0754b698f1e291c7c92bf1220834bd5620207084236399cfd02e2d"},{"version":"7f2a2cdf8eecbe353d449055d91c6ee619f90ca3b3a49ba5a44563c44aab5d1a","signature":"8e6165fa13e0d2f40e2403ab20b72804d02c663709a3f7383a320050e893fe8b"},{"version":"a02cb9d0a7363cbbce45fa86bbd7d64615811e30f2a7c47a2718fcf53f20eae7","signature":"9af2721670eb3402e476cd827256d4af7ab1d6db57f1615cbef18c75164df9e5"},"14c94f7888c75007a94132f03caef0f6b58bcb136c2994213fd2d3b99f3d7f85","4695042a55a75a6c62dc57f2efe60ef3c7bbe19adedb5081f6e51dd664bbc3f7","b006ad8d854471e7a270bd8918508090961bdc1cfe77ed51f13f471fe682acac","310901df1081433ff7c3b7918496cabb92ded208b04294d3d2bd35acae2de836","c8646410cd2a6bf05eb7e7a51c881776410d07fd5d8f75092a2c815c9c6fda52",{"version":"127604bb56d364ecc35cbb4927ba7c53f552353fc7913b07a4f5cc957210aabd","signature":"973a1e0a155ab26d66226ff9d64a36cf61227e9240b21cabdc67df29847a6599"},{"version":"dcea5769c8b69d7b7a5ee6ffd4d22260e47d53d22990e91d504cbdc0c0120c14","signature":"16c51743932253da5b661b0a5068eb1423a6f020f62e6783ce8ac5259cff10f2"},{"version":"67be5e00299e02d108b294758dcc0218da9f2a2823dea61d708ddbe705771ae5","signature":"05fe3dec4dc02961a8959758da54c6ff9d32a232183041163d4d52cc6bf39015"},{"version":"e2e5ebf01c7004f157b8c750fdddb9f227fbf3119a87297e3a014db04c3f0887","signature":"fc6fe9c667e291d0bbdc904c921d2c1d385175f8c135d9e549298c96265acaec"},{"version":"6b0da45d7a1027dd4a9b14ac009b018761e8851c84a9e54ecc1be9086f0516c6","signature":"38fd30580198d072da98f6dbcb7535f47359ca91ffe57e3b9bfd1961a3b209ab"},{"version":"407e1457c37a63fc162e15d0684d0f196550e0db0c84903c6057a37359305d20","signature":"4d0ada1fd2655eab6195efa682371a7a4322868bc75c466832a2776c4a7ea4c1"},{"version":"4c408d170f00539f8957a9cfce1d7f3e4a2d36651dbf4b16337a7af5c568d7e5","signature":"a0c04e7f7ac63b60e113ff0a7ad4fbf9214babb1db0572641709a5859710e27c"},"4489c6a9fde8934733aa7df6f7911461ee6e9e4ad092736bd416f6b2cc20b2c6","2c8e55457aaf4902941dfdba4061935922e8ee6e120539c9801cd7b400fae050","8041cfce439ff29d339742389de04c136e3029d6b1817f07b2d7fcbfb7534990","670a76db379b27c8ff42f1ba927828a22862e2ab0b0908e38b671f0e912cc5ed","9d38964b57191567a14b396422c87488cecd48f405c642daa734159875ee81d9","069bebfee29864e3955378107e243508b163e77ab10de6a5ee03ae06939f0bb9",{"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","8841e2aa774b89bd23302dede20663306dc1b9902431ac64b24be8b8d0e3f649","916be7d770b0ae0406be9486ac12eb9825f21514961dd050594c4b250617d5a8","254d9fb8c872d73d34594be8a200fd7311dbfa10a4116bfc465fba408052f2b3","d88a5e779faf033be3d52142a04fbe1cb96009868e3bbdd296b2bc6c59e06c0e","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":[[666],[72,108,109,110,125],[109,110,126,127],[108,109],[108,125,128,131],[108,128,131,132],[129,130,131,133,134],[108,131],[108,125,128,129,130,133],[108,116],[108],[72,108],[60,108],[112,113,114,115,116,117,118,119,120,121,122,123,124],[108,114,115],[108,114,116],[605,609,610],[605,608],[608],[609,611,613],[605,608,609,610,611,612],[605,608,612,616,617,618],[605,608,612,619],[605],[605,606],[606,607],[605,608,614,617,619,620],[615],[616,618,622,625,626],[616,618,626],[108,608,612,616,617,619,622],[616,626],[616,619,623],[608,617,619],[616,619,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637],[616,624],[108,624],[605,612,616,617,618,619,620,623],[616,618,622,624],[616,623,624],[108,135,392,393],[393,394],[392],[108,387],[387,388,389,390,391],[108,359,366,367],[108,359,366,367,387],[108,359,366,367,371],[108,359,366,367,368,370,371],[108,359,366,367,369],[108,359,366,367,372,373,375,376],[365,387],[108,359,366,367,372],[108,359,366,367,370],[108,359,366,367,383],[108,359,366,367,384],[111,356,359,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386],[357],[357,358],[357,366,367,372,373],[359,365,366],[402,403],[72,108,392,395,401],[395,402],[216],[545,546,547],[216,544,545],[249,258,259,262],[249,258,261],[250,253,254,256],[250,251,252],[253,254,256,257],[250,251,255],[261,267],[249,258,261,267],[258,261,267],[258,261,263,264,265,266],[249,258,259,260,261,262,267],[249,258,260,262],[249,258],[250],[144,167],[144],[189,190,191,192,194,195,196],[167,189,190,191,192,194,195],[144,167,193],[198],[201],[144,193],[202,203,204],[197],[193,197,199,200,205,206,213,215,235,236,354],[167,200,205],[208],[207,209,210,211,212],[144,167,193,197],[167,197,214],[167,214,215,234],[167,206,353],[138,144],[654,655,656,657],[568],[548,567],[560,561,562,563,564,565,566],[560],[562],[560,562],[425],[108,228,424],[108,228,425],[108,228,396],[396],[396,397,398,399,400],[108,228],[228,427],[427,428,429,430,431,432,433,434,435,436],[228,427,428],[61,108,228,427,428],[105,108,228,427,428],[228,428],[167,216,223,224],[225],[229],[224,225,226,229,230,231,232,233],[108,226,228,231],[108,167,225,228,229],[224,231],[108,167,216,223,225,228],[167,223],[167,237],[237,238],[237,238,239,240],[167],[188,353,475,492,493,506,531],[529],[167,504,519,527,528,530],[518],[188,353,455,517],[108,223,228,426,437,492,493,494,506],[167,188,492,506],[437,495],[497],[494,495,496,498,501,503],[500],[495,499,504],[437,495,496],[167,437],[502],[495,496],[509,524,525,526],[492,506],[505,506,507,508],[492,506,507],[167,492,505,506],[167,492,506],[522,523],[167,188,485,522],[167,353,485],[167,184,188,353,462,475,485,492,493,504,506,509,511,519,521,524],[520],[167,353,475,492,493,506,527],[144,167,484],[108,167,242],[167,350],[319],[167,242,319,347,350,351,352],[108,167,241,242],[243,244,245,246,348,349],[138,144,347],[138,144,245],[277],[277,283],[277,282,284,285],[277,282,286],[282,283,284,285,286,287],[289,290],[278,279,280,281,288,291,292,293,294,295,296,297,298],[277,291],[277,278,293,295,296],[277,291,294],[277,299,300,301,302],[144,277,299,350],[334],[347],[337,338,339,340,341,342,343,344,345],[167,269],[319,343,350],[269,347,350],[144,320],[269,270,320,323,333,334,335,346],[144,167,303,319],[347,350],[268,270],[270],[350],[320],[167,323],[247,248,271,272,273,274,275,276,321,322,324,325,326,327,328,329,330,331,332],[167,325],[247,248,271,272,273,274,275,276,321,322,324,325,326,327,328,329,330,331,350],[167,268,269],[234,333],[167,270],[317],[144,304],[305,306,307,308,309,310,311,312,313,314,315,316],[304,317,318],[303],[353],[441],[440],[144,167,447],[268],[167,241,353],[144,353,454,455],[438,439,441,442,443,444,445,448,449,450,451,452,453,454,456,457,458,476,477,479,480,481,482,483,486,487,488,489,490,491],[144,167,475],[144,167,353,441,479],[158],[478],[144,167,268,353,477],[144,353,477],[144,353,475,478,480,485,486],[144,167,441,444,456,479,480],[353,455],[486],[536],[536,537,538],[147],[144,147],[145,146,147,148,149,150,151,152,153,154,155,156,159,160,161,162,163,164,165,166],[138,144,145],[135,147,153,155],[147,148],[144,162],[108,362],[360,361,364],[360,363],[108,360],[412,413],[666,667,668,669,670],[666,668],[157],[672,673,674],[73,108],[677],[678],[689],[683,688],[579,581,582,583,584,585,586,587,588,589,590,591],[579,580,582,583,584,585,586,587,588,589,590,591],[580,581,582,583,584,585,586,587,588,589,590,591],[579,580,581,583,584,585,586,587,588,589,590,591],[579,580,581,582,584,585,586,587,588,589,590,591],[579,580,581,582,583,585,586,587,588,589,590,591],[579,580,581,582,583,584,586,587,588,589,590,591],[579,580,581,582,583,584,585,587,588,589,590,591],[579,580,581,582,583,584,585,586,588,589,590,591],[579,580,581,582,583,584,585,586,587,589,590,591],[579,580,581,582,583,584,585,586,587,588,590,591],[579,580,581,582,583,584,585,586,587,588,589,591],[579,580,581,582,583,584,585,586,587,588,589,590],[56],[59],[60,65,92],[61,72,73,80,89,100],[61,62,72,80],[63,101],[64,65,73,81],[65,89,97],[66,68,72,80],[67],[68,69],[72],[71,72],[59,72],[72,73,74,89,100],[72,73,74,89],[72,75,80,89,100],[72,73,75,76,80,89,97,100],[75,77,89,97,100],[56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,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],[72,78],[79,100,105],[68,72,80,89],[81],[82],[59,83],[84,99,105],[85],[86],[72,87],[87,88,101,103],[60,72,89,90,91],[60,89,91],[89,90],[92],[93],[72,95,96],[95,96],[65,80,89,97],[98],[80,99],[60,75,86,100],[65,101],[89,102],[103],[104],[60,65,72,74,83,89,100,103,105],[89,106],[108,227],[697,736],[697,721,736],[736],[697],[697,722,736],[697,698,699,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,717,718,719,720,721,722,723,724,725,726,727,728,729,730,731,732,733,734,735],[722,736],[737],[740],[510],[570,571,572,573,574,575,576],[570],[572],[570,572],[446],[593,594,595],[593],[594],[179],[179,180,181,182,183],[168,169,170,171,172,173,174,175,176,177,178],[681,684],[681,684,685,686],[683],[680,687],[682],[137,139,140,141,142,143],[137,138],[139],[138,139],[137,139],[167,188,355,423,531],[532,534],[423,533],[167,188,241],[460,461],[167,184,185,186],[185],[136,185,186,187],[186],[407],[407,408,411,415],[414],[167,409,410],[541,542,543],[223,541],[167,223,541],[167,216,223],[167,188,553,557],[558],[167,217],[217,218,219,220,221,222],[167,216],[422],[135,167,184,188,355,404,405,421],[72,108,167,188],[406],[406,417,418,419,420],[406,416],[549],[540,549,551,552],[167,188,409,416,539,540,549,550],[167,416,544,548],[167,467,468,469],[241,467,468,470],[465,466,467,468,469,470,474],[167,223,467,469,475],[167,188,416,459,465,467,470],[167,184,188,241,416,459,462,463,464,465,466,468,469,470],[167,223,466,467,468],[471,472,473],[167,223,416,466,468,469],[167,223,466,468,469],[167,188,467],[167,223,468,470],[516],[188,416,512,514,515],[513,514],[515,516],[167,188,553,554],[554,555,556],[108,167,188,553,554],[167,553],[167,597],[167,559,592,597,602],[167,409,410,416,592,597,603],[167,597,599,639],[128,135,167,591,592,597,621,638],[167,416,597],[167,410,416,533,577,592,597,599,643],[72,167,409,553,559,592,597,598,645],[72,167,535,553,577,592,597],[167,409,553,569,577,592,644,647,648],[72,409,416,553,591,592,597],[597,598,600,643,662,664],[72,125,128,135,167,188,241,409,416,462,533,535,553,559,569,577,578,591,592,597,598,600,601,602,603,604,640,641,642,644,645,646,647,648,649,650,651,652,653,661,662,663],[135,167,409,553,559,596],[167,416,592,599],[241,597],[167,409,416,559,592,597,598,601],[416,559,597],[167,409,416,592,597,599],[591,596,597],[167,553,592,597],[416,569,592,597],[167,416,592,659],[52,167,416,592,597,599,614,659,660],[409,416,591,592,597,599,600,664],[52,409,416,597,614],[167,416,597,664],[52,241,416,597,600,614],[597],[597,639],[72,167,553,559,597],[72,167,535,553,597],[167,409,553,569,644,647,648],[72,409,553,597],[158,167],[128,135,167,188,462,535,553,559,569,597,647],[167,409,559,597],[559,597],[158,167,409,597],[167,553,597],[569,597],[167,597,614,660],[409,597,664],[409,597],[597,664]],"referencedMap":[[668,1],[126,2],[128,3],[110,4],[132,5],[133,6],[129,6],[135,7],[130,6],[134,8],[131,9],[117,10],[114,11],[121,12],[115,10],[112,13],[125,14],[119,11],[116,15],[118,16],[611,17],[610,18],[609,19],[614,20],[613,21],[619,22],[620,23],[606,24],[607,25],[608,26],[621,27],[616,28],[627,29],[628,30],[623,31],[629,32],[630,33],[631,33],[622,34],[638,35],[633,29],[632,36],[634,37],[624,38],[635,32],[636,30],[626,39],[637,36],[625,40],[617,18],[394,41],[395,42],[393,43],[390,44],[389,44],[388,44],[392,45],[374,46],[379,47],[368,46],[373,48],[372,49],[370,50],[377,51],[378,46],[380,52],[381,53],[383,54],[384,55],[385,56],[387,57],[358,58],[359,59],[375,60],[367,61],[404,62],[402,63],[403,64],[545,65],[548,66],[546,67],[547,67],[260,68],[262,69],[257,70],[253,71],[254,71],[258,72],[256,73],[263,74],[264,75],[265,76],[267,77],[266,74],[268,78],[261,79],[259,80],[252,81],[255,71],[189,82],[190,83],[191,83],[192,82],[197,84],[196,85],[194,86],[195,82],[199,87],[198,82],[202,88],[201,89],[205,90],[203,82],[204,91],[355,92],[207,86],[208,93],[209,94],[210,86],[213,95],[212,96],[214,86],[215,97],[235,98],[236,97],[354,99],[193,100],[658,101],[569,102],[568,103],[567,104],[561,105],[563,106],[565,107],[564,107],[426,108],[425,109],[424,110],[397,111],[398,112],[401,113],[399,112],[396,65],[400,114],[428,115],[437,116],[433,117],[432,118],[435,117],[434,119],[436,120],[431,120],[430,117],[429,120],[225,121],[226,122],[230,123],[234,124],[232,125],[231,126],[233,127],[229,128],[224,129],[238,130],[239,131],[241,132],[237,133],[216,12],[529,134],[530,135],[531,136],[519,137],[518,138],[495,139],[494,140],[497,141],[498,142],[504,143],[501,144],[500,145],[499,146],[496,147],[503,148],[502,149],[527,150],[507,151],[509,152],[508,153],[506,154],[505,155],[524,156],[523,157],[522,158],[526,151],[525,159],[520,133],[521,160],[528,161],[485,162],[484,82],[351,163],[242,164],[352,165],[353,166],[243,167],[244,133],[245,133],[350,168],[348,169],[246,170],[277,133],[278,171],[279,171],[280,171],[281,171],[282,171],[284,172],[286,173],[287,174],[288,175],[285,171],[283,171],[289,171],[291,176],[290,171],[292,171],[293,171],[299,177],[294,178],[297,179],[298,171],[295,180],[296,171],[303,181],[301,171],[300,171],[302,182],[335,183],[337,133],[338,184],[346,185],[339,133],[341,186],[342,133],[344,187],[343,188],[345,189],[347,190],[320,191],[247,184],[248,192],[271,193],[272,194],[273,193],[275,133],[276,195],[321,196],[324,197],[333,198],[326,199],[325,133],[327,133],[328,164],[332,200],[329,195],[330,197],[331,184],[270,201],[334,202],[323,203],[318,204],[305,205],[314,205],[306,205],[307,205],[316,205],[308,205],[309,205],[317,206],[315,205],[310,205],[313,205],[311,205],[312,205],[319,207],[304,83],[455,208],[439,209],[442,210],[443,211],[445,210],[448,212],[451,213],[453,214],[456,215],[492,216],[476,217],[458,133],[480,218],[481,219],[479,220],[478,221],[482,222],[487,223],[477,100],[486,224],[489,225],[490,226],[491,133],[441,211],[537,227],[538,227],[539,228],[145,83],[146,83],[148,229],[149,83],[150,83],[151,230],[147,83],[167,231],[155,232],[156,233],[159,219],[165,234],[166,235],[363,236],[362,11],[365,237],[360,11],[364,238],[361,239],[414,240],[671,241],[667,1],[669,242],[670,1],[410,11],[158,243],[675,244],[676,245],[678,246],[679,247],[690,248],[689,249],[580,250],[581,251],[579,252],[582,253],[583,254],[584,255],[585,256],[586,257],[587,258],[588,259],[589,260],[590,261],[591,262],[56,263],[57,263],[59,264],[60,265],[61,266],[62,267],[63,268],[64,269],[65,270],[66,271],[67,272],[68,273],[69,273],[70,274],[71,275],[72,276],[73,277],[74,278],[75,279],[76,280],[77,281],[108,282],[78,283],[79,284],[80,285],[81,286],[82,287],[83,288],[84,289],[85,290],[86,291],[87,292],[88,293],[89,294],[91,295],[90,296],[92,297],[93,298],[95,299],[96,300],[97,301],[98,302],[99,303],[100,304],[101,305],[102,306],[103,307],[104,308],[105,309],[106,310],[694,11],[228,311],[696,11],[721,312],[722,313],[697,314],[700,314],[719,312],[720,312],[710,312],[709,315],[707,312],[702,312],[715,312],[713,312],[717,312],[701,312],[714,312],[718,312],[703,312],[704,312],[716,312],[698,312],[705,312],[706,312],[708,312],[712,312],[723,316],[711,312],[699,312],[736,317],[730,316],[732,318],[731,316],[724,316],[725,316],[727,316],[729,316],[733,318],[734,318],[726,318],[728,318],[738,319],[741,320],[511,321],[577,322],[571,323],[573,324],[575,325],[574,325],[447,326],[596,327],[594,328],[595,329],[175,330],[177,330],[176,330],[174,330],[184,331],[179,332],[170,330],[171,330],[172,330],[173,330],[685,333],[687,334],[686,333],[684,335],[688,336],[683,337],[144,338],[139,339],[140,340],[141,340],[142,341],[143,341],[138,342],[532,343],[535,344],[534,345],[460,346],[462,347],[187,348],[186,349],[188,350],[185,351],[408,352],[416,353],[415,354],[411,355],[544,356],[542,357],[543,358],[541,359],[558,360],[559,361],[218,362],[219,362],[221,362],[223,363],[217,364],[222,362],[423,365],[422,366],[406,367],[420,368],[419,368],[421,369],[417,370],[418,368],[550,371],[552,371],[553,372],[551,373],[549,374],[470,375],[469,376],[475,377],[464,378],[468,379],[467,380],[471,381],[474,382],[472,383],[473,384],[465,385],[466,386],[517,387],[516,388],[515,389],[514,390],[555,391],[557,392],[556,393],[554,394],[659,395],[603,396],[604,397],[640,398],[639,399],[641,398],[642,400],[644,401],[646,402],[647,403],[649,404],[648,405],[665,406],[592,133],[664,407],[597,408],[643,409],[650,410],[602,411],[598,412],[651,413],[652,414],[645,415],[653,416],[660,417],[661,418],[601,419],[662,420],[600,421],[663,422]],"exportedModulesMap":[[668,1],[126,2],[128,3],[110,4],[132,5],[133,6],[129,6],[135,7],[130,6],[134,8],[131,9],[117,10],[114,11],[121,12],[115,10],[112,13],[125,14],[119,11],[116,15],[118,16],[611,17],[610,18],[609,19],[614,20],[613,21],[619,22],[620,23],[606,24],[607,25],[608,26],[621,27],[616,28],[627,29],[628,30],[623,31],[629,32],[630,33],[631,33],[622,34],[638,35],[633,29],[632,36],[634,37],[624,38],[635,32],[636,30],[626,39],[637,36],[625,40],[617,18],[394,41],[395,42],[393,43],[390,44],[389,44],[388,44],[392,45],[374,46],[379,47],[368,46],[373,48],[372,49],[370,50],[377,51],[378,46],[380,52],[381,53],[383,54],[384,55],[385,56],[387,57],[358,58],[359,59],[375,60],[367,61],[404,62],[402,63],[403,64],[545,65],[548,66],[546,67],[547,67],[260,68],[262,69],[257,70],[253,71],[254,71],[258,72],[256,73],[263,74],[264,75],[265,76],[267,77],[266,74],[268,78],[261,79],[259,80],[252,81],[255,71],[189,82],[190,83],[191,83],[192,82],[197,84],[196,85],[194,86],[195,82],[199,87],[198,82],[202,88],[201,89],[205,90],[203,82],[204,91],[355,92],[207,86],[208,93],[209,94],[210,86],[213,95],[212,96],[214,86],[215,97],[235,98],[236,97],[354,99],[193,100],[658,101],[569,102],[568,103],[567,104],[561,105],[563,106],[565,107],[564,107],[426,108],[425,109],[424,110],[397,111],[398,112],[401,113],[399,112],[396,65],[400,114],[428,115],[437,116],[433,117],[432,118],[435,117],[434,119],[436,120],[431,120],[430,117],[429,120],[225,121],[226,122],[230,123],[234,124],[232,125],[231,126],[233,127],[229,128],[224,129],[238,130],[239,131],[241,132],[237,133],[216,12],[529,134],[530,135],[531,136],[519,137],[518,138],[495,139],[494,140],[497,141],[498,142],[504,143],[501,144],[500,145],[499,146],[496,147],[503,148],[502,149],[527,150],[507,151],[509,152],[508,153],[506,154],[505,155],[524,156],[523,157],[522,158],[526,151],[525,159],[520,133],[521,160],[528,161],[485,162],[484,82],[351,163],[242,164],[352,165],[353,166],[243,167],[244,133],[245,133],[350,168],[348,169],[246,170],[277,133],[278,171],[279,171],[280,171],[281,171],[282,171],[284,172],[286,173],[287,174],[288,175],[285,171],[283,171],[289,171],[291,176],[290,171],[292,171],[293,171],[299,177],[294,178],[297,179],[298,171],[295,180],[296,171],[303,181],[301,171],[300,171],[302,182],[335,183],[337,133],[338,184],[346,185],[339,133],[341,186],[342,133],[344,187],[343,188],[345,189],[347,190],[320,191],[247,184],[248,192],[271,193],[272,194],[273,193],[275,133],[276,195],[321,196],[324,197],[333,198],[326,199],[325,133],[327,133],[328,164],[332,200],[329,195],[330,197],[331,184],[270,201],[334,202],[323,203],[318,204],[305,205],[314,205],[306,205],[307,205],[316,205],[308,205],[309,205],[317,206],[315,205],[310,205],[313,205],[311,205],[312,205],[319,207],[304,83],[455,208],[439,209],[442,210],[443,211],[445,210],[448,212],[451,213],[453,214],[456,215],[492,216],[476,217],[458,133],[480,218],[481,219],[479,220],[478,221],[482,222],[487,223],[477,100],[486,224],[489,225],[490,226],[491,133],[441,211],[537,227],[538,227],[539,228],[145,83],[146,83],[148,229],[149,83],[150,83],[151,230],[147,83],[167,231],[155,232],[156,233],[159,219],[165,234],[166,235],[363,236],[362,11],[365,237],[360,11],[364,238],[361,239],[414,240],[671,241],[667,1],[669,242],[670,1],[410,11],[158,243],[675,244],[676,245],[678,246],[679,247],[690,248],[689,249],[580,250],[581,251],[579,252],[582,253],[583,254],[584,255],[585,256],[586,257],[587,258],[588,259],[589,260],[590,261],[591,262],[56,263],[57,263],[59,264],[60,265],[61,266],[62,267],[63,268],[64,269],[65,270],[66,271],[67,272],[68,273],[69,273],[70,274],[71,275],[72,276],[73,277],[74,278],[75,279],[76,280],[77,281],[108,282],[78,283],[79,284],[80,285],[81,286],[82,287],[83,288],[84,289],[85,290],[86,291],[87,292],[88,293],[89,294],[91,295],[90,296],[92,297],[93,298],[95,299],[96,300],[97,301],[98,302],[99,303],[100,304],[101,305],[102,306],[103,307],[104,308],[105,309],[106,310],[694,11],[228,311],[696,11],[721,312],[722,313],[697,314],[700,314],[719,312],[720,312],[710,312],[709,315],[707,312],[702,312],[715,312],[713,312],[717,312],[701,312],[714,312],[718,312],[703,312],[704,312],[716,312],[698,312],[705,312],[706,312],[708,312],[712,312],[723,316],[711,312],[699,312],[736,317],[730,316],[732,318],[731,316],[724,316],[725,316],[727,316],[729,316],[733,318],[734,318],[726,318],[728,318],[738,319],[741,320],[511,321],[577,322],[571,323],[573,324],[575,325],[574,325],[447,326],[596,327],[594,328],[595,329],[175,330],[177,330],[176,330],[174,330],[184,331],[179,332],[170,330],[171,330],[172,330],[173,330],[685,333],[687,334],[686,333],[684,335],[688,336],[683,337],[144,338],[139,339],[140,340],[141,340],[142,341],[143,341],[138,342],[532,343],[535,344],[534,345],[460,346],[462,347],[187,348],[186,349],[188,350],[185,351],[408,352],[416,353],[415,354],[411,355],[544,356],[542,357],[543,358],[541,359],[558,360],[559,361],[218,362],[219,362],[221,362],[223,363],[217,364],[222,362],[423,365],[422,366],[406,367],[420,368],[419,368],[421,369],[417,370],[418,368],[550,371],[552,371],[553,372],[551,373],[549,374],[470,375],[469,376],[475,377],[464,378],[468,379],[467,380],[471,381],[474,382],[472,383],[473,384],[465,385],[466,386],[517,387],[516,388],[515,389],[514,390],[555,391],[557,392],[556,393],[554,394],[659,133],[603,423],[604,423],[640,424],[639,395],[641,424],[642,423],[644,395],[646,425],[647,426],[649,427],[648,428],[665,406],[592,429],[664,430],[597,408],[643,133],[650,423],[602,431],[598,432],[651,433],[652,423],[645,434],[653,435],[660,133],[661,436],[601,437],[662,438],[600,439],[663,423]],"semanticDiagnosticsPerFile":[668,666,126,109,128,110,127,132,133,129,135,130,134,131,117,114,121,115,112,120,125,122,123,124,119,116,113,118,611,610,609,614,613,619,620,606,607,608,605,621,616,615,612,627,628,623,629,630,631,622,638,633,632,634,624,635,636,626,637,625,617,618,394,395,393,390,389,388,392,391,374,379,368,373,372,370,377,378,380,382,381,383,384,385,387,358,357,359,375,356,369,367,366,376,371,386,404,402,403,405,545,548,546,547,409,260,262,249,257,253,254,258,256,263,264,265,267,266,268,261,259,252,250,251,255,189,190,191,192,197,196,194,195,199,198,200,202,201,205,203,204,206,355,207,208,209,210,213,211,212,214,215,235,236,354,193,656,654,655,657,658,569,568,566,567,561,560,563,562,565,564,426,425,424,397,398,401,399,396,400,428,437,433,432,435,434,436,427,431,430,429,225,226,230,234,232,231,233,229,224,238,240,239,241,237,216,529,530,531,519,518,495,494,497,498,504,501,500,499,496,503,502,527,507,509,508,506,505,524,523,522,526,525,493,520,521,528,485,484,351,242,352,353,243,244,245,350,348,246,349,277,278,279,280,281,282,284,286,287,288,285,283,289,291,290,292,293,299,294,297,298,295,296,303,301,300,302,269,335,337,338,346,339,340,341,342,344,343,345,336,347,320,247,248,271,272,273,274,275,276,321,322,324,333,326,325,327,328,332,329,330,331,270,334,323,318,305,314,306,307,316,308,309,317,315,310,313,311,312,319,304,455,438,439,442,443,444,445,448,449,450,451,452,453,454,456,457,492,476,458,480,481,479,478,482,483,487,488,477,486,489,490,491,441,440,537,538,539,536,145,146,148,149,150,151,152,153,154,147,167,155,156,159,160,161,162,163,164,165,166,363,362,365,360,364,361,412,414,413,671,667,669,670,410,158,463,672,675,673,676,677,678,679,690,689,674,691,580,581,579,582,583,584,585,586,587,588,589,590,591,692,157,56,57,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,58,107,75,76,77,108,78,79,80,81,82,83,84,85,86,87,88,89,91,90,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,693,694,695,512,228,227,696,721,722,697,700,719,720,710,709,707,702,715,713,717,701,714,718,703,704,716,698,705,706,708,712,723,711,699,736,735,730,732,731,724,725,727,729,733,734,726,728,738,737,739,533,740,741,511,510,576,577,571,570,573,572,575,574,111,680,446,447,578,596,594,595,593,513,178,175,177,176,174,184,179,183,180,182,181,170,171,172,168,169,173,681,685,687,686,684,688,459,683,682,137,144,139,140,141,142,143,138,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,532,535,534,460,461,462,136,187,186,188,185,408,416,415,407,411,544,542,543,541,558,559,218,219,220,221,223,217,222,423,422,406,420,419,421,417,418,540,550,552,553,551,549,470,469,475,464,468,467,471,474,472,473,465,466,517,516,515,514,555,557,556,554,599,659,603,604,640,639,641,642,644,646,647,649,648,665,592,664,597,643,650,602,598,651,652,645,653,660,661,601,662,600,663,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/TransactionController.d.ts b/dist/types/TransactionController.d.ts +index f4c0fa6672b24d7756513bd46c565f9da0ffd61a..37793f450f9e52815dcebc2557ec910ad6ab6021 100644 +--- a/dist/types/TransactionController.d.ts ++++ b/dist/types/TransactionController.d.ts +@@ -115,7 +115,6 @@ export type PendingTransactionOptions = { + * @property transactionHistoryLimit - Transaction history limit. + * @property hooks - The controller hooks. + * @property hooks.afterSign - Additional logic to execute after signing a transaction. Return false to not change the status to signed. +- * @property hooks.beforeApproveOnInit - Additional logic to execute before starting an approval flow for a transaction during initialization. Return false to skip the transaction. + * @property hooks.beforeCheckPendingTransaction - Additional logic to execute before checking pending transactions. Return false to prevent the broadcast of the transaction. + * @property hooks.beforePublish - Additional logic to execute before publishing a transaction. Return false to prevent the broadcast of the transaction. + * @property hooks.getAdditionalSignArguments - Returns additional arguments required to sign a transaction. +@@ -148,7 +147,6 @@ export type TransactionControllerOptions = { + transactionHistoryLimit: number; + hooks: { + afterSign?: (transactionMeta: TransactionMeta, signedTx: TypedTransaction) => boolean; +- beforeApproveOnInit?: (transactionMeta: TransactionMeta) => boolean; + beforeCheckPendingTransaction?: (transactionMeta: TransactionMeta) => boolean; + beforePublish?: (transactionMeta: TransactionMeta) => boolean; + getAdditionalSignArguments?: (transactionMeta: TransactionMeta) => (TransactionMeta | undefined)[]; +@@ -365,7 +363,6 @@ export declare class TransactionController extends BaseController Promise; ++ constructor({ blockTracker, getChainId, getEthQuery, getTransactions, isResubmitEnabled, getGlobalLock, publishTransaction, hooks, }: { + blockTracker: BlockTracker; + getChainId: () => string; + getEthQuery: (networkClientId?: NetworkClientId) => EthQuery; +diff --git a/dist/types/helpers/PendingTransactionTracker.d.ts.map b/dist/types/helpers/PendingTransactionTracker.d.ts.map +index 14d6402240dc02077ae66e3d049b03f529bbde30..bdca97bd140595aa27e63cd7dcb89389c7170341 100644 +--- a/dist/types/helpers/PendingTransactionTracker.d.ts.map ++++ b/dist/types/helpers/PendingTransactionTracker.d.ts.map +@@ -1 +1 @@ +-{"version":3,"file":"PendingTransactionTracker.d.ts","sourceRoot":"","sources":["../../../src/helpers/PendingTransactionTracker.ts"],"names":[],"mappings":";AACA,OAAO,KAAK,QAAQ,MAAM,qBAAqB,CAAC;AAChD,OAAO,KAAK,EACV,YAAY,EACZ,eAAe,EAChB,MAAM,8BAA8B,CAAC;AACtC,OAAO,YAAY,MAAM,QAAQ,CAAC;AAIlC,OAAO,KAAK,EAAE,eAAe,EAAsB,MAAM,UAAU,CAAC;AA6BpE,KAAK,MAAM,GAAG;IACZ,uBAAuB,EAAE,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACnD,qBAAqB,EAAE,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACjD,oBAAoB,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;IAC9D,qBAAqB,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;CAChE,CAAC;AAKF,MAAM,WAAW,qCAAsC,SAAQ,YAAY;IAGzE,EAAE,CAAC,CAAC,SAAS,MAAM,MAAM,EACvB,SAAS,EAAE,CAAC,EACZ,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,GACrC,IAAI,CAAC;IAIR,IAAI,CAAC,CAAC,SAAS,MAAM,MAAM,EAAE,SAAS,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC;CACzE;AAED,qBAAa,yBAAyB;;IACpC,GAAG,EAAE,qCAAqC,CAAC;gBA8B/B,EACV,kBAAkB,EAClB,YAAY,EACZ,UAAU,EACV,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,aAAa,EACb,kBAAkB,EAClB,KAAK,GACN,EAAE;QACD,kBAAkB,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;QAC7D,YAAY,EAAE,YAAY,CAAC;QAC3B,UAAU,EAAE,MAAM,MAAM,CAAC;QACzB,WAAW,EAAE,CAAC,eAAe,CAAC,EAAE,eAAe,KAAK,QAAQ,CAAC;QAC7D,eAAe,EAAE,MAAM,eAAe,EAAE,CAAC;QACzC,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC;QAClC,aAAa,EAAE,MAAM,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC;QACzC,kBAAkB,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;QAC3E,KAAK,CAAC,EAAE;YACN,6BAA6B,CAAC,EAAE,CAC9B,eAAe,EAAE,eAAe,KAC7B,OAAO,CAAC;YACb,aAAa,CAAC,EAAE,CAAC,eAAe,EAAE,eAAe,KAAK,OAAO,CAAC;SAC/D,CAAC;KACH;IAmBD,0BAA0B,aAQxB;IAEF;;;;OAIG;IACG,qBAAqB,CAAC,MAAM,EAAE,eAAe;IAwBnD,IAAI;CAqXL"} +\ No newline at end of file ++{"version":3,"file":"PendingTransactionTracker.d.ts","sourceRoot":"","sources":["../../../src/helpers/PendingTransactionTracker.ts"],"names":[],"mappings":";AACA,OAAO,KAAK,QAAQ,MAAM,qBAAqB,CAAC;AAChD,OAAO,KAAK,EACV,YAAY,EACZ,eAAe,EAChB,MAAM,8BAA8B,CAAC;AACtC,OAAO,YAAY,MAAM,QAAQ,CAAC;AAIlC,OAAO,KAAK,EAAE,eAAe,EAAsB,MAAM,UAAU,CAAC;AA6BpE,KAAK,MAAM,GAAG;IACZ,uBAAuB,EAAE,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACnD,qBAAqB,EAAE,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACjD,oBAAoB,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;IAC9D,qBAAqB,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;CAChE,CAAC;AAKF,MAAM,WAAW,qCAAsC,SAAQ,YAAY;IAGzE,EAAE,CAAC,CAAC,SAAS,MAAM,MAAM,EACvB,SAAS,EAAE,CAAC,EACZ,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,GACrC,IAAI,CAAC;IAIR,IAAI,CAAC,CAAC,SAAS,MAAM,MAAM,EAAE,SAAS,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC;CACzE;AAED,qBAAa,yBAAyB;;IACpC,GAAG,EAAE,qCAAqC,CAAC;gBA4B/B,EACV,YAAY,EACZ,UAAU,EACV,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,aAAa,EACb,kBAAkB,EAClB,KAAK,GACN,EAAE;QACD,YAAY,EAAE,YAAY,CAAC;QAC3B,UAAU,EAAE,MAAM,MAAM,CAAC;QACzB,WAAW,EAAE,CAAC,eAAe,CAAC,EAAE,eAAe,KAAK,QAAQ,CAAC;QAC7D,eAAe,EAAE,MAAM,eAAe,EAAE,CAAC;QACzC,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC;QAClC,aAAa,EAAE,MAAM,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC;QACzC,kBAAkB,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;QAC3E,KAAK,CAAC,EAAE;YACN,6BAA6B,CAAC,EAAE,CAC9B,eAAe,EAAE,eAAe,KAC7B,OAAO,CAAC;YACb,aAAa,CAAC,EAAE,CAAC,eAAe,EAAE,eAAe,KAAK,OAAO,CAAC;SAC/D,CAAC;KACH;IAkBD,0BAA0B,aAQxB;IAEF;;;;OAIG;IACG,qBAAqB,CAAC,MAAM,EAAE,eAAe;IAwBnD,IAAI;CAiXL"} +\ No newline at end of file diff --git a/.yarn/patches/@reduxjs-toolkit-npm-1.9.7-b14925495c.patch b/.yarn/patches/@reduxjs-toolkit-npm-1.9.7-b14925495c.patch index 019aa54534e5..a3c730d3a46b 100644 --- a/.yarn/patches/@reduxjs-toolkit-npm-1.9.7-b14925495c.patch +++ b/.yarn/patches/@reduxjs-toolkit-npm-1.9.7-b14925495c.patch @@ -92,3 +92,29 @@ index bb433432ec76331e12d6b62e200f06530055cb16..9caf4051aa96bd14ee2890ef6c79bf5b return EnhancerArray; }(Array)); function freezeDraftable(val) { +diff --git a/dist/redux-toolkit.esm.js b/dist/redux-toolkit.esm.js +index f26a1669405b4dd92dfecd791dc536078a7e2e12..591e7495fcaf3233d26cfb9c4eae09fd7ae3eb98 100644 +--- a/dist/redux-toolkit.esm.js ++++ b/dist/redux-toolkit.esm.js +@@ -192,9 +192,6 @@ function getMessage(type) { + } + function createActionCreatorInvariantMiddleware(options) { + if (options === void 0) { options = {}; } +- if (process.env.NODE_ENV === "production") { +- return function () { return function (next) { return function (action) { return next(action); }; }; }; +- } + var _c = options.isActionCreator, isActionCreator2 = _c === void 0 ? isActionCreator : _c; + return function () { return function (next) { return function (action) { + if (isActionCreator2(action)) { +diff --git a/package.json b/package.json +index 684ea845ee663f719bff6c140001baebdaa69344..568d6215514a8625bfb3be5e49b6cbfe11231e6a 100644 +--- a/package.json ++++ b/package.json +@@ -23,7 +23,6 @@ + "access": "public" + }, + "main": "dist/index.js", +- "module": "dist/redux-toolkit.esm.js", + "unpkg": "dist/redux-toolkit.umd.min.js", + "types": "dist/index.d.ts", + "devDependencies": { diff --git a/.yarnrc.yml b/.yarnrc.yml index 5d4aa7cd4e73..252333917781 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -43,16 +43,6 @@ npmAuditIgnoreAdvisories: # not appear to be used. - 1092461 - # Issue: path-to-regexp outputs backtracking regular expressions - # URL: https://github.com/advisories/GHSA-9wv6-86v2-598j - # path-to-regexp is used in react-router v5.1.2, which we use. However, the - # vulnerability in path-to-regexp could only be exploited within react-router - # if malicious properties were passed to react-router components or methods - # explicitly from our code. As such, this vulneratibility cannot be exploited - # by an external / malicious actor. Meanwhile, once we update to v6+, - # path-to-regexp will no longer be used. - - 1099518 - # Temp fix for https://github.com/MetaMask/metamask-extension/pull/16920 for the sake of 11.7.1 hotfix # This will be removed in this ticket https://github.com/MetaMask/metamask-extension/issues/22299 - 'ts-custom-error (deprecation)' diff --git a/CHANGELOG.md b/CHANGELOG.md index 529d6764a977..199b83700dde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,61 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [12.3.0] +### Added +- Added the ability to name accounts during the snap account creation flow ([#25191](https://github.com/MetaMask/metamask-extension/pull/25191)) +- Added an experimental settings toggle for the transactions redesign ([#26010](https://github.com/MetaMask/metamask-extension/pull/26010)) +- Added a banner alert to help users manage queued transactions from different dApps ([#25852](https://github.com/MetaMask/metamask-extension/pull/25852)) +- Add "See all permissions" button to Snaps permissions view, and improve scrolling behaviour ([#25175](https://github.com/MetaMask/metamask-extension/pull/25175)) +- Added redesigned UI and new UX for the Snap home page and Snap dialog, including a full screen view and an updated Snap Authorship header ([#25670](https://github.com/MetaMask/metamask-extension/pull/25670)) +- Enabled hardware wallets for smart transactions in MetaMask swaps ([#25742](https://github.com/MetaMask/metamask-extension/pull/25742)) +- Added a "Close extension" button on the Smart Transaction Status Page for pending dapp transactions ([#25965](https://github.com/MetaMask/metamask-extension/pull/25965)) +- Added a toast message to notify users if they quickly send transactions on different networks ([#26114](https://github.com/MetaMask/metamask-extension/pull/26114)) +- Enabled token auto-detection by default ([#26406](https://github.com/MetaMask/metamask-extension/pull/26406)) + +### Changed +- Improve information and presentation of permit signatures, including: PermitSingle, PermitBatch, PermitTransferFrom, PermitBatchTransferFrom and TradeOrder ([#26107](https://github.com/MetaMask/metamask-extension/pull/26107)) +- Updated the design for the NFT details page ([#25524](https://github.com/MetaMask/metamask-extension/pull/25524)) +- Updated the Bitcoin account creation flow to use the new Snap account creation process, including account renaming ([#26183](https://github.com/MetaMask/metamask-extension/pull/26183)) +- Removed the mention of password managers from the Secret Recovery Phrase onboarding instructions and reordered the bullet points ([#25985](https://github.com/MetaMask/metamask-extension/pull/25985)) +- Updated the BNB network logos ([#26140](https://github.com/MetaMask/metamask-extension/pull/26140)) +- Removed support for the eth_sign method ([#24756](https://github.com/MetaMask/metamask-extension/pull/24756)) +- Updated the dApp permission screen for network switching requests ([#25703](https://github.com/MetaMask/metamask-extension/pull/25703)) +- Updated the STX Opt In modal to replace "Manage in settings" with "No thanks" and only show the modal for non-zero balances ([#25848](https://github.com/MetaMask/metamask-extension/pull/25848)) +- Displayed advanced details by default in confirmations for users with nonce editing or hex data enabled ([#25687](https://github.com/MetaMask/metamask-extension/pull/25687)) +- Enhanced the performance of the account list to make it faster and more responsive ([#26379](https://github.com/MetaMask/metamask-extension/pull/26379)) +- Updated logos for Flare Mainnet and Songbird ([#25560](https://github.com/MetaMask/metamask-extension/pull/25560)) +- Updated various icons to improve visual consistency ([#26180](https://github.com/MetaMask/metamask-extension/pull/26180)) +- Added a popover to truncate and display long NFT token IDs ([#26179](https://github.com/MetaMask/metamask-extension/pull/26179)) +- Removed the halo around token avatars ([#26016](https://github.com/MetaMask/metamask-extension/pull/26016)) +- Improved the alignment of token icons to be centered in the token list ([#26013](https://github.com/MetaMask/metamask-extension/pull/26013)) +- Improved the display of "data unavailable" text and contract addresses on the NFT details page ([#25931](https://github.com/MetaMask/metamask-extension/pull/25931)) +- Improved the warning message in the add network modal ([#26250](https://github.com/MetaMask/metamask-extension/pull/26250)) +- Improved performance for large signature request confirmations ([#26209](https://github.com/MetaMask/metamask-extension/pull/26209)) +- Updated the pending transactions badge to display a number instead of three dots ([#26116](https://github.com/MetaMask/metamask-extension/pull/26116)) +- Added a link to the Metametrics page in the onboarding flow to explain data management and profile syncing ([#26038](https://github.com/MetaMask/metamask-extension/pull/26038)) +- Improved the AccountListMenu to hide the back button by default, showing it only when needed ([#27152](https://github.com/MetaMask/metamask-extension/pull/27152)) + +### Fixed +- Fixed an issue where the wallet was not accessible with a new password after resetting it ([#25847](https://github.com/MetaMask/metamask-extension/pull/25847)) +- Fixed number formatting for swap + send transaction details to avoid scientific notation for small token amounts ([#26029](https://github.com/MetaMask/metamask-extension/pull/26029)) +- Fixed an issue with link redirection to ensure proper navigation ([#25983](https://github.com/MetaMask/metamask-extension/pull/25983)) +- Fixed the issue of overlapping modals ([#25962](https://github.com/MetaMask/metamask-extension/pull/25962)) +- Fixed the issue where pressing the Enter key on the Create Account checkbox would trigger show/hide password ([#26394](https://github.com/MetaMask/metamask-extension/pull/26394)) +- Fixed the logic to correctly fetch token decimals for permit and order signatures ([#26292](https://github.com/MetaMask/metamask-extension/pull/26292)) +- Fixed an issue to prevent automatic reconnection to previously unlocked hardware wallets ([#25503](https://github.com/MetaMask/metamask-extension/pull/25503)) +- Updated the text in the popup to inform users about managing notifications in the settings ([#26026](https://github.com/MetaMask/metamask-extension/pull/26026)) +- Fixed UI issues with displaying website URLs in the Snaps permissions interface ([#26422](https://github.com/MetaMask/metamask-extension/pull/26422)) +- Fixed an issue to prevent unnecessary errors when setting network client IDs for domains without account permissions ([#26323](https://github.com/MetaMask/metamask-extension/pull/26323)) +- Fixed an issue by clearing invalid network settings to prevent errors and improve stability ([#26428](https://github.com/MetaMask/metamask-extension/pull/26428)) +- Fixed the issue where the "Switch to this account" option was not showing for single connected accounts on the connections page ([#25609](https://github.com/MetaMask/metamask-extension/pull/25609)) +- Fixed the max width of the permissions page to match other screens in full screen view ([#25870](https://github.com/MetaMask/metamask-extension/pull/25870)) +- Fixed the issue to show the current network when domains are undefined ([#25960](https://github.com/MetaMask/metamask-extension/pull/25960)) +- Fixed the estimated fee calculation in redesigned screens by converting fee values to the correct units ([#27250](https://github.com/MetaMask/metamask-extension/pull/27250)) +- Fixed an issue to allow re-submitting a cancelled swap if it was cancelled via a hardware wallet ([#27210](https://github.com/MetaMask/metamask-extension/pull/27210)) +- Fixed an issue that caused MetaMask to crash when certain permit values were not loaded ([#26791](https://github.com/MetaMask/metamask-extension/pull/26791)) +- Fixed an issue where the "Add a new Bitcoin account (testnet)" option was repeated ([#27116](https://github.com/MetaMask/metamask-extension/pull/27116)) + ## [12.2.4] ### Fixed - Fixes token approvals for users who have the "Decode smart contracts" setting toggled off ([#27203](https://github.com/MetaMask/metamask-extension/pull/27203)) @@ -122,7 +177,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed fallback conversion rates for token market data to ensure accurate market cap ([#26460](https://github.com/MetaMask/metamask-extension/pull/26460)) - Resolved the issue of selecting the correct network when multiple networks have the same chain ID ([#25805](https://github.com/MetaMask/metamask-extension/pull/25805)) - Updated alignment and padding for permit simulations ([#26186](https://github.com/MetaMask/metamask-extension/pull/26186)) -- Updated the pending transactions badge to display a number instead of three dots ([#26116](https://github.com/MetaMask/metamask-extension/pull/26116)) - Adjusted spacing in the send asset picker for proper vertical alignment with other dropdowns ([#25576](https://github.com/MetaMask/metamask-extension/pull/25576)) - Fixed decimal display for Permit values and added a reusable component for displaying token units ([#26105](https://github.com/MetaMask/metamask-extension/pull/26105)) - Fixed precision loss for very large values in signature simulations ([#25968](https://github.com/MetaMask/metamask-extension/pull/25968)) @@ -5052,7 +5106,8 @@ Update styles and spacing on the critical error page ([#20350](https://github.c - Added the ability to restore accounts from seed words. -[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v12.2.4...HEAD +[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v12.3.0...HEAD +[12.3.0]: https://github.com/MetaMask/metamask-extension/compare/v12.2.4...v12.3.0 [12.2.4]: https://github.com/MetaMask/metamask-extension/compare/v12.2.3...v12.2.4 [12.2.3]: https://github.com/MetaMask/metamask-extension/compare/v12.2.2...v12.2.3 [12.2.2]: https://github.com/MetaMask/metamask-extension/compare/v12.2.1...v12.2.2 diff --git a/README.md b/README.md index 5c3e8f5823c1..59c65864f1e3 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ To start a development build (e.g. with logging and file watching) run `yarn sta Alternatively, one can skip wallet onboarding and preload the vault state with a specific SRP by adding `TEST_SRP=''` and `PASSWORD=''` to the `.metamaskrc` file and running `yarn start:skip-onboarding`. +You can also start a development build using the `yarn webpack` command, or `yarn webpack --watch`. This uses an alternative build system that is much faster, but not yet production ready. See the [Webpack README](./development/webpack/README.md) for more information. #### React and Redux DevTools @@ -138,7 +139,7 @@ Note: The `yarn start:test` command (which initiates the testDev build type) has Once you have your test build ready, choose the browser for your e2e tests: - For Firefox, run `yarn test:e2e:firefox`. - - Note: If you are running Firefox as a snap package on Linux, ensure you enable the appropriate environment variable: `FIREFOX_SNAP=true yarn test:e2e:firefox` + - Note: If you are running Firefox as a snap package on Linux, ensure you enable the appropriate environment variable: `FIREFOX_SNAP=true yarn test:e2e:firefox` - For Chrome, run `yarn test:e2e:chrome`. These scripts support additional options for debugging. Use `--help`to see all available options. diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index 0fc8d76afbab..cdc7fdd96669 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -42,7 +42,7 @@ "message": "Verbinden Sie Ihre QR-basierte Hardware-Wallet." }, "QRHardwareWalletSteps2Description": { - "message": "Ngrave (demnächst)" + "message": "Ngrave Zero" }, "SIWEAddressInvalid": { "message": "Die Adresse in der Anmeldeanfrage entspricht nicht der Adresse des Kontos, mit dem Sie sich anmelden." @@ -162,6 +162,9 @@ "accountSelectionRequired": { "message": "Sie müssen ein Konto auswählen!" }, + "accountTypeNotSupported": { + "message": "Kontotyp nicht unterstützt" + }, "accounts": { "message": "Konten" }, @@ -277,6 +280,12 @@ "addNewAccount": { "message": "Ein neues Konto hinzufügen" }, + "addNewBitcoinAccount": { + "message": "Ein neues Bitcoin-Konto hinzufügen (Beta)" + }, + "addNewBitcoinTestnetAccount": { + "message": "Ein neues Bitcoin-Konto hinzufügen (Testnet)" + }, "addNewToken": { "message": "Neues Token hinzufügen" }, @@ -286,6 +295,9 @@ "addNfts": { "message": "NFTs hinzufügen" }, + "addRpcUrl": { + "message": "RPC-URL hinzufügen" + }, "addSnapAccountToggle": { "message": "„Konto-Snap (Beta) hinzufügen“ aktivieren" }, @@ -305,12 +317,21 @@ "message": "Sie können kein Token finden? Sie können ein beliebiges Token manuell hinzufügen, indem Sie seine Adresse eingeben. Token-Contract-Adressen finden Sie auf $1.", "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" }, + "addUrl": { + "message": "URL hinzufügen" + }, "addingCustomNetwork": { "message": "Netzwerk wird hinzugefügt" }, "addingTokens": { "message": "Hinzufügen von Token" }, + "additionalNetworks": { + "message": "Zusätzliche Netzwerke" + }, + "additionalRpcUrl": { + "message": "Weitere RPC-URL" + }, "address": { "message": "Adresse" }, @@ -326,6 +347,18 @@ "advancedConfiguration": { "message": "Erweiterte Einstellungen" }, + "advancedDetailsDataDesc": { + "message": "Daten" + }, + "advancedDetailsHexDesc": { + "message": "Hexadezimal" + }, + "advancedDetailsNonceDesc": { + "message": "Nonce" + }, + "advancedDetailsNonceTooltip": { + "message": "Dies ist die Transaktionsnummer eines Kontos. Die Nonce für die erste Transaktion ist 0 und sie erhöht sich in fortlaufender Reihenfolge." + }, "advancedGasFeeDefaultOptIn": { "message": "Speichern Sie diese Werte als Standard für das $1-Netzwerk.", "description": "$1 is the current network name." @@ -349,6 +382,18 @@ "alert": { "message": "Warnhinweis" }, + "alertActionBuy": { + "message": "EHT kaufen" + }, + "alertActionUpdateGas": { + "message": "Gas-Limit aktualisieren" + }, + "alertActionUpdateGasFee": { + "message": "Gebühr aktualisieren" + }, + "alertActionUpdateGasFeeLevel": { + "message": "Gas-Optionen aktualisieren" + }, "alertBannerMultipleAlertsDescription": { "message": "Wenn Sie diese Anfrage genehmigen, könnten Dritte, die für Betrügereien bekannt sind, alle Ihre Assets an sich reißen." }, @@ -358,6 +403,36 @@ "alertDisableTooltip": { "message": "Dies kann in „Einstellungen > Benachrichtigungen“ geändert werden." }, + "alertMessageGasEstimateFailed": { + "message": "Wir sind nicht in der Lage, eine genaue Gebühr anzugeben, und diese Schätzung könnte zu hoch sein. Wir schlagen vor, dass Sie ein individuelles Gas-Limit eingeben, aber es besteht das Risiko, dass die Transaktion trotzdem fehlschlägt." + }, + "alertMessageGasFeeLow": { + "message": "Wenn Sie eine niedrige Gebühr wählen, müssen Sie mit langsameren Transaktionen und längeren Wartezeiten rechnen. Für schnellere Transaktionen wählen Sie die Gebührenoptionen Markt oder Aggressiv." + }, + "alertMessageGasTooLow": { + "message": "Um mit dieser Transaktion fortzufahren, müssen Sie das Gas-Limit auf 21.000 oder mehr erhöhen." + }, + "alertMessageInsufficientBalance": { + "message": "Sie haben nicht genug ETH auf Ihrem Konto, um die Transaktionsgebühren zu bezahlen." + }, + "alertMessageNetworkBusy": { + "message": "Die Gas-Preise sind hoch und die Schätzungen sind weniger genau." + }, + "alertMessageNoGasPrice": { + "message": "Wir können mit dieser Transaktion nicht fortfahren, bis Sie die Gebühr manuell aktualisieren." + }, + "alertMessagePendingTransactions": { + "message": "Diese Transaktion wird erst dann durchgeführt, wenn eine vorherige Transaktion abgeschlossen ist. Erfahren Sie, wie Sie eine Transaktion abbrechen oder beschleunigen können." + }, + "alertMessageSignInDomainMismatch": { + "message": "Die Website, die die Anfrage stellt, ist nicht die Website, bei der Sie sich anmelden. Dies könnte ein Versuch sein, Ihre Anmeldedaten zu stehlen." + }, + "alertMessageSignInWrongAccount": { + "message": "Diese Seite fordert Sie auf, sich mit dem falschen Konto anzumelden." + }, + "alertMessageSigningOrSubmitting": { + "message": "Diese Transaktion wird erst durchgeführt, wenn Ihre vorherige Transaktion abgeschlossen ist." + }, "alertModalAcknowledge": { "message": "Ich habe das Risiko erkannt und möchte trotzdem fortfahren" }, @@ -367,6 +442,33 @@ "alertModalReviewAllAlerts": { "message": "Alle Benachrichtigungen überprüfen" }, + "alertReasonGasEstimateFailed": { + "message": "Ungenaue Gebühr" + }, + "alertReasonGasFeeLow": { + "message": "Langsame Geschwindigkeit" + }, + "alertReasonGasTooLow": { + "message": "Niedriges Gas-Limit" + }, + "alertReasonInsufficientBalance": { + "message": "Unzureichende Gelder" + }, + "alertReasonNetworkBusy": { + "message": "Netzwerk ist ausgelastet" + }, + "alertReasonNoGasPrice": { + "message": "Gebührenschätzung nicht verfügbar" + }, + "alertReasonPendingTransactions": { + "message": "Ausstehende Transaktion" + }, + "alertReasonSignIn": { + "message": "Verdächtige Anmeldeanfrage" + }, + "alertReasonWrongAccount": { + "message": "Falsches Konto" + }, "alertSettingsUnconnectedAccount": { "message": "Eine Webseite mit einem nicht verknüpften Konto durchsuchen" }, @@ -398,6 +500,12 @@ "allPermissions": { "message": "Alle Genehmigungen" }, + "allTimeHigh": { + "message": "Allzeithoch" + }, + "allTimeLow": { + "message": "Allzeittief" + }, "allYourNFTsOf": { "message": "Alle Ihre NFTs von $1.", "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" @@ -436,9 +544,6 @@ "message": "$1 und $2 ", "description": "$1 is the first item, $2 is the second item. Used in Snap Install Warning modal." }, - "announcements": { - "message": "Ankündigungen" - }, "appDescription": { "message": "Ethereum Browsererweiterung", "description": "The description of the application" @@ -524,6 +629,9 @@ "attemptToCancelSwapForFree": { "message": "Versuch, den Swap kostenlos zu stornieren" }, + "attributes": { + "message": "Attribute" + }, "attributions": { "message": "Zuschreibungen" }, @@ -594,6 +702,10 @@ "basicConfigurationBannerTitle": { "message": "Grundfunktionalität ist ausgeschaltet" }, + "basicConfigurationDescription": { + "message": "MetaMask bietet grundlegende Funktionen wie Token-Details und Gas-Einstellungen über Internetdienste. Wenn Sie Internetdienste nutzen, wird Ihre IP-Adresse weitergegeben, in diesem Fall an MetaMask. Das ist genau so, wie wenn Sie eine beliebige Website besuchen. MetaMask verwendet diese Daten vorübergehend und verkauft Ihre Daten niemals. Sie können ein VPN verwenden oder diese Dienste abschalten, aber das kann Ihr MetaMask-Erlebnis beeinträchtigen. Um mehr zu erfahren, lesen Sie unsere $1.", + "description": "$1 is to be replaced by the message for privacyMsg, and will link to https://consensys.io/privacy-policy" + }, "basicConfigurationLabel": { "message": "Grundfunktionalität" }, @@ -637,6 +749,29 @@ "betaWalletCreationSuccessReminder2": { "message": "MetaMask Beta wird Sie nie nach Ihrer geheimen Wiederherstellungsphrase fragen." }, + "billionAbbreviation": { + "message": "B", + "description": "Shortened form of 'billion'" + }, + "bitcoinActivityNotSupported": { + "message": "Bitcoin-Aktivität wird nicht unterstützt" + }, + "bitcoinSupportSectionTitle": { + "message": "Bitcoin" + }, + "bitcoinSupportToggleDescription": { + "message": "Bei Aktivierung dieser Funktion haben Sie die Möglichkeit, ein Bitcoin-Konto zu Ihrer MetaMask-Erweiterung hinzuzufügen, das von Ihrer bestehenden geheimen Wiederherstellungsphrase abgeleitet ist. Hierbei handelt es sich um eine experimentelle Beta-Funktion, deren Nutzung also auf eigene Gefahr erfolgt. Falls Sie uns Feedback zu dieser neuen Bitcoin-Funktion geben möchten, füllen Sie bitte dieses $1 aus.", + "description": "$1 is the link to a product feedback form" + }, + "bitcoinSupportToggleTitle": { + "message": "Aktivierung der Funktion „Ein neues Bitcoin-Konto hinzufügen (Beta)“" + }, + "bitcoinTestnetSupportToggleDescription": { + "message": "Bei Aktivierung dieser Funktion haben Sie die Möglichkeit, ein Bitcoin-Konto für das Test-Netzwerk hinzuzufügen." + }, + "bitcoinTestnetSupportToggleTitle": { + "message": "Aktivierung der Funktion „Ein neues Bitcoin-Konto hinzufügen (Testnet)“" + }, "blockExplorerAccountAction": { "message": "Konto", "description": "This is used with viewOnEtherscan and viewInExplorer e.g View Account in Explorer" @@ -662,6 +797,9 @@ "blockaid": { "message": "Blockaid" }, + "blockaidAlertInfo": { + "message": "Wir empfehlen nicht, mit dieser Anfrage fortzufahren." + }, "blockaidDescriptionApproveFarming": { "message": "Wenn Sie diese Anfrage genehmigen, könnte eine dritte Partei, die für Betrügereien bekannt ist, Ihre gesamten Assets an sich reißen." }, @@ -669,7 +807,7 @@ "message": "Wenn Sie diese Anfrage genehmigen, kann jemand Ihre bei Blur aufgelisteten Assets stehlen." }, "blockaidDescriptionErrored": { - "message": "Aufgrund eines Fehlers wurde diese Anfrage vom Sicherheitsanbieter nicht überprüft. Gehen Sie mit Bedacht vor." + "message": "Aufgrund eines Fehlers konnten wir nicht auf Sicherheitsalarme prüfen. Fahren Sie nur fort, wenn Sie jeder involvierten Adresse vertrauen." }, "blockaidDescriptionMaliciousDomain": { "message": "Sie interagieren mit einer schädlichen Domain. Wenn Sie diese Anfrage bestätigen, verlieren Sie eventuell Ihre Assets." @@ -683,6 +821,9 @@ "blockaidDescriptionTransferFarming": { "message": "Wenn Sie diese Anfrage genehmigen, wird eine dritte Partei, die für Betrügereien bekannt ist, Ihre gesamten Assets an sich reißen." }, + "blockaidDescriptionWarning": { + "message": "Dies könnte eine betrügerische Anfrage sein. Fahren Sie nur fort, wenn Sie allen beteiligten Adressen vertrauen." + }, "blockaidMessage": { "message": "Wahrung der Privatsphäre – keine Daten werden an Dritte weitergegeben. Verfügbar auf Arbitrum, Avalanche, BNB chain, Ethereum Mainnet, Linea, Optimism, Polygon, Base und Sepolia." }, @@ -690,7 +831,7 @@ "message": "Dies ist eine betrügerische Anfrage." }, "blockaidTitleMayNotBeSafe": { - "message": "Die Anfrage ist möglicherweise nicht sicher." + "message": "Seien Sie vorsichtig" }, "blockaidTitleSuspicious": { "message": "Dies ist eine verdächtige Anfrage." @@ -698,6 +839,9 @@ "blockies": { "message": "Blockies" }, + "boughtFor": { + "message": "Gekauft für" + }, "bridge": { "message": "Bridge" }, @@ -782,6 +926,9 @@ "chromeRequiredForHardwareWallets": { "message": "Sie müssen MetaMask unter Google Chrome nutzen, um sich mit Ihrer Hardware-Wallet zu verbinden." }, + "circulatingSupply": { + "message": "Zirkulierende Versorgung" + }, "clear": { "message": "Löschen" }, @@ -802,7 +949,7 @@ "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" }, "clickToManuallyAdd": { - "message": "Klicken Sie hier, um die Tokens manuell hinzuzufügen." + "message": "Sie können Tokens jederzeit manuell hinzufügen." }, "close": { "message": "Schließen" @@ -816,6 +963,9 @@ "coingecko": { "message": "CoinGecko" }, + "collectionName": { + "message": "Name der Sammlung" + }, "comboNoOptions": { "message": "Keine Option gefunden", "description": "Default text shown in the combo field dropdown if no options." @@ -841,6 +991,9 @@ "confirmAlertModalAcknowledgeMultiple": { "message": "Ich habe die Benachrichtigungen zur Kenntnis genommen und möchte trotzdem fortfahren" }, + "confirmAlertModalAcknowledgeSingle": { + "message": "Ich habe die Benachrichtigung zur Kenntnis genommen und möchte trotzdem fortfahren" + }, "confirmAlertModalDetails": { "message": "Wenn Sie sich anmelden, könnten Dritte, die für Betrügereien bekannt sind, all Ihre Assets an sich reißen. Lesen Sie bitte die Benachrichtigungen, bevor Sie fortfahren." }, @@ -856,18 +1009,42 @@ "confirmConnectionTitle": { "message": "Verbindung mit $1 bestätigen" }, + "confirmDeletion": { + "message": "Löschung bestätigen" + }, + "confirmFieldPaymaster": { + "message": "Gebühr bezahlt von" + }, + "confirmFieldTooltipPaymaster": { + "message": "Die Gebühr für diese Transaktion wird durch den Paymaster Smart Contract bezahlt." + }, "confirmPassword": { "message": "Passwort bestätigen" }, "confirmRecoveryPhrase": { "message": "Geheime Wiederherstellungsphrase bestätigen" }, + "confirmRpcUrlDeletionMessage": { + "message": "Sind Sie sicher, dass Sie die RPC-URL löschen möchten? Ihre Informationen werden für dieses Netzwerk nicht gespeichert." + }, "confirmTitleDescContractInteractionTransaction": { "message": "Bestätigen Sie diese Transaktion nur, wenn Sie den Inhalt vollständig verstehen und der anfragenden Website vertrauen." }, + "confirmTitleDescPermitSignature": { + "message": "Diese Website möchte die Genehmigung, Ihre Tokens auszugeben." + }, + "confirmTitleDescSIWESignature": { + "message": "Eine Website möchte, dass Sie sich anmelden, um zu beweisen, dass Sie dieses Konto besitzen." + }, "confirmTitleDescSignature": { "message": "Bestätigen Sie diese Nachricht nur, wenn Sie dem Inhalt zustimmen und der anfragenden Website vertrauen." }, + "confirmTitlePermitSignature": { + "message": "Antrag auf Ausgabenobergrenze" + }, + "confirmTitleSIWESignature": { + "message": "Anmeldeanfrage" + }, "confirmTitleSignature": { "message": "Signaturanfrage" }, @@ -961,7 +1138,7 @@ "message": "Verbunden mit" }, "connecting": { - "message": "Verbindung wird hergestellt ..." + "message": "Verbinden" }, "connectingTo": { "message": "Verbindung mit $1 wird hergestellt" @@ -1094,6 +1271,9 @@ "createSnapAccountTitle": { "message": "Konto erstellen" }, + "creatorAddress": { + "message": "Adresse des Erstellers" + }, "crossChainSwapsLink": { "message": "Netzwerkübergreifender Austausch mit MetaMask Portfolio" }, @@ -1263,9 +1443,27 @@ "data": { "message": "Daten" }, + "dataCollectionForMarketing": { + "message": "Datenerhebung für das Marketing" + }, + "dataCollectionForMarketingDescription": { + "message": "Wir verwenden MetaMetrics, um zu erfahren, wie Sie mit unserer Marketingkommunikation umgehen. Wir können relevante Neuigkeiten (wie Produktmerkmale und andere Materialien) teilen." + }, + "dataCollectionWarningPopoverButton": { + "message": "Okay" + }, + "dataCollectionWarningPopoverDescription": { + "message": "Sie haben die Datenerhebung für unsere Marketingzwecke deaktiviert. Dies gilt nur für dieses Gerät. Wenn Sie MetaMask auf anderen Geräten verwenden, stellen Sie sicher, dass Sie sich auch dort abmelden." + }, "dataHex": { "message": "Hexadezimal" }, + "dataUnavailable": { + "message": "Daten nicht verfügbar" + }, + "dateCreated": { + "message": "Datum erstellt" + }, "dcent": { "message": "D'Cent" }, @@ -1295,6 +1493,9 @@ "decryptRequest": { "message": "Anfrage entschlüsseln" }, + "defaultRpcUrl": { + "message": "Standard-RPC-URL" + }, "delete": { "message": "Löschen" }, @@ -1311,6 +1512,9 @@ "message": "$1-Netzwerk löschen?", "description": "$1 represents the name of the network" }, + "deleteRpcUrl": { + "message": "RPC-URL löschen" + }, "deposit": { "message": "Einzahlung" }, @@ -1336,18 +1540,6 @@ "details": { "message": "Details" }, - "developerOptions": { - "message": "Entwickler-Optionen" - }, - "developerOptionsResetStatesAnnouncementsDescription": { - "message": "Setzt den Booleschen Wert isShown für alle Ankündigungen auf false zurück. Ankündigungen sind die Benachrichtigungen, die im Was-neu-ist-Popup-Modal angezeigt werden." - }, - "developerOptionsResetStatesOnboarding": { - "message": "Setzt verschiedene Status im Zusammenhang mit dem Onboarding zurück und leitet zur Onboarding-Seite „Sichern Sie Ihre Wallet“ weiter." - }, - "developerOptionsServiceWorkerKeepAlive": { - "message": "Führt dazu, dass ein Zeitstempel kontinuierlich in session.storage gespeichert wird." - }, "disabledGasOptionToolTipMessage": { "message": "“$1” ist deaktiviert, weil es nicht das Minimum einer zehnprozentigen Erhöhung gegenüber der ursprünglichen Gasgebühr erfüllt.", "description": "$1 is gas estimate type which can be market or aggressive" @@ -1524,6 +1716,9 @@ "editGasTooLow": { "message": "Unbekannte Bearbeitungszeit" }, + "editNetworkLink": { + "message": "Das ursprüngliche Netzwerk bearbeiten" + }, "editNonceField": { "message": "Nonce bearbeiten" }, @@ -1555,6 +1750,9 @@ "enabled": { "message": "Aktiviert" }, + "enabledNetworks": { + "message": "Aktivierte Netzwerke" + }, "encryptionPublicKeyNotice": { "message": "$1 wünscht Ihren öffentlichen Verschlüsselungsschlüssel. Durch Ihre Zustimmung kann diese Seite verschlüsselte Nachrichten an Sie verfassen.", "description": "$1 is the web3 site name" @@ -1659,6 +1857,9 @@ "estimatedFee": { "message": "Geschätzte Gebühr" }, + "estimatedFeeTooltip": { + "message": "Betrag, der für die Bearbeitung der Transaktion im Netzwerk gezahlt wurde." + }, "ethGasPriceFetchWarning": { "message": "Der Gas-Preis, der sich aus der Gas-Hauptschätzungsdienst ergibt, ist derzeit nicht verfügbar." }, @@ -1678,6 +1879,12 @@ "etherscanViewOn": { "message": "Auf Etherscan anzeigen" }, + "existingChainId": { + "message": "Die von Ihnen eingegebenen Informationen sind mit einer bestehenden Chain-ID verknüpft." + }, + "existingRpcUrl": { + "message": "Diese URL ist mit einer anderen Chain-ID verknüpft." + }, "expandView": { "message": "Ansicht erweitern" }, @@ -1732,6 +1939,9 @@ "message": "Dateiimport fehlgeschlagen? Bitte hier klicken!", "description": "Helps user import their account from a JSON file" }, + "findTheRightChainId": { + "message": "Finden Sie das Richtige auf:" + }, "flaskWelcomeUninstall": { "message": "Sie sollten diese Erweiterung deinstallieren.", "description": "This request is shown on the Flask Welcome screen. It is intended for non-developers, and will be bolded." @@ -1771,6 +1981,9 @@ "forgotPassword": { "message": "Passwort vergessen?" }, + "form": { + "message": "Formular" + }, "from": { "message": "Von" }, @@ -1979,6 +2192,12 @@ "highLowercase": { "message": "hoch" }, + "highestCurrentBid": { + "message": "Höchstes aktuelles Gebot" + }, + "highestFloorPrice": { + "message": "Höchster Mindestpreis" + }, "history": { "message": "Verlauf" }, @@ -2318,12 +2537,21 @@ "knownTokenWarning": { "message": "Mit dieser Aktion werden Tokens bearbeitet, die bereits in Ihrer Wallet aufgelistet sind und die dazu verwendet werden können, Sie zu betrügen. Genehmigen Sie diese Aktion nur, wenn Sie sicher sind, dass Sie den Wert dieser Tokens ändern möchten. Erfahren Sie mehr über $1." }, + "l1Fee": { + "message": "L1-Gebühr" + }, + "l1FeeTooltip": { + "message": "L1-Gas-Gebühr" + }, + "l2Fee": { + "message": "L2-Gebühr" + }, + "l2FeeTooltip": { + "message": "L2-Gas-Gebühr" + }, "lastConnected": { "message": "Zuletzt verbunden" }, - "lastPriceSold": { - "message": "Letzter Verkaufspreis" - }, "lastSold": { "message": "Zuletzt verkauft" }, @@ -2495,6 +2723,12 @@ "message": "Stellen Sie sicher, dass niemand zuschaut.", "description": "Warning to users to be care while creating and saving their new Secret Recovery Phrase" }, + "marketCap": { + "message": "Marktkapitalisierung" + }, + "marketDetails": { + "message": "Marktdetails" + }, "max": { "message": "Max." }, @@ -2504,6 +2738,9 @@ "maxFee": { "message": "Maximale Gebühr" }, + "maxFeeTooltip": { + "message": "Eine maximale Gebühr, die für die Bezahlung der Transaktion vorgesehen ist." + }, "maxPriorityFee": { "message": "Maximale Prioritätsgebühr" }, @@ -2551,12 +2788,19 @@ "methodData": { "message": "Methode" }, + "methodDataTransactionDesc": { + "message": "Funktion, die auf der Grundlage der dekodierten Eingabedaten ausgeführt wird." + }, "methodNotSupported": { "message": "Bei diesem Konto nicht unterstützt." }, "metrics": { "message": "Metriken" }, + "millionAbbreviation": { + "message": "M", + "description": "Shortened form of 'million'" + }, "mismatchAccount": { "message": "Ihr ausgewähltes Konto ($1) unterscheidet sich von dem Konto, das versucht, zu unterzeichnen ($2)." }, @@ -2669,7 +2913,7 @@ "description": "Description below header used on Permission Connect screen for native permissions." }, "nativeToken": { - "message": "Das native Token dieses Netzwerks ist $1. Dieses Token wird für die Gas-Gebühr verwendet.", + "message": "Das native Token dieses Netzwerks ist $1. Dieses Token wird für die Gas-Gebühr verwendet. ", "description": "$1 represents the name of the native token on the current network" }, "nativeTokenScamWarningConversion": { @@ -2738,6 +2982,9 @@ "networkNameBase": { "message": "Basis" }, + "networkNameBitcoin": { + "message": "Bitcoin" + }, "networkNameDefinition": { "message": "Der diesem Netzwerk zugeordnete Name." }, @@ -2762,6 +3009,9 @@ "networkNameZkSyncEra": { "message": "zkSync Era" }, + "networkOptions": { + "message": "Netzwerkoptionen" + }, "networkProvider": { "message": "Netzwerkanbieter" }, @@ -2830,6 +3080,9 @@ "newNetworkAdded": { "message": "„$1“ wurde erfolgreich hinzugefügt!" }, + "newNetworkEdited": { + "message": "„$1“ wurde erfolgreich bearbeitet!" + }, "newNftAddedMessage": { "message": "NFT wurde erfolgreich hinzugefügt!" }, @@ -2866,6 +3119,9 @@ "nftAlreadyAdded": { "message": "NFT wurde bereits hinzugefügt." }, + "nftAutoDetectionEnabled": { + "message": "Automatische NFT-Erkennung aktiviert" + }, "nftDisclaimer": { "message": "Haftungsausschluss: MetaMask bezieht die Mediendatei aus der Quellen-URL. Diese URL wird manchmal vom Markt, auf dem das NFT erstellt wurde, geändert." }, @@ -2916,6 +3172,9 @@ "noDomainResolution": { "message": "Keine Auflösung für die Domain angegeben." }, + "noHardwareWalletOrSnapsSupport": { + "message": "Snaps und die meisten Hardware-Wallets funktionieren nicht mit Ihrer aktuellen Browserversion." + }, "noNFTs": { "message": "Noch keine NFTs" }, @@ -2946,8 +3205,8 @@ "nonceField": { "message": "Transaktions-Nonce anpassen" }, - "nonceFieldDescription": { - "message": "Aktivieren Sie dies, um die Nonce (Transaktionsnummer) auf den Bestätigungsbildschirmen zu ändern. Dies ist eine erweiterte Funktion, verwenden Sie diese vorsichtig." + "nonceFieldDesc": { + "message": "Aktivieren Sie diese Funktion, um die Nonce (Transaktionsnummer) beim Senden von Assets zu ändern. Es handelt sich hierbei um eine erweiterte Funktion, also nutzen Sie sie mit Bedacht." }, "nonceFieldHeading": { "message": "Eigene Nonce" @@ -2995,7 +3254,7 @@ "message": "Prioritätsgebühr (GWEI)" }, "notificationItemCheckBlockExplorer": { - "message": "Überprüfen Sie den BlockExplorer" + "message": "Auf dem Block-Explorer überprüfen" }, "notificationItemCollection": { "message": "Sammlung" @@ -3149,6 +3408,9 @@ "numberOfNewTokensDetectedSingular": { "message": "1 neues Token in diesem Konto gefunden." }, + "numberOfTokens": { + "message": "Anzahl von Tokens" + }, "ofTextNofM": { "message": "von" }, @@ -3164,8 +3426,36 @@ "on": { "message": "An" }, - "onboarding": { - "message": "Onboarding" + "onboardedMetametricsAccept": { + "message": "Ich stimme zu" + }, + "onboardedMetametricsDisagree": { + "message": "Nein, danke" + }, + "onboardedMetametricsKey1": { + "message": "Neueste Entwicklungen" + }, + "onboardedMetametricsKey2": { + "message": "Produktmerkmale" + }, + "onboardedMetametricsKey3": { + "message": "Andere relevante Werbematerialien" + }, + "onboardedMetametricsLink": { + "message": "MetaMetrics" + }, + "onboardedMetametricsParagraph1": { + "message": "Zusätzlich zu $1 möchten wir Daten verwenden, um zu verstehen, wie Sie mit Marketingkommunikation umgehen.", + "description": "$1 represents the 'onboardedMetametricsLink' locale string" + }, + "onboardedMetametricsParagraph2": { + "message": "Dies hilft uns, das, was wir mit Ihnen teilen, zu personalisieren, wie z. B.:" + }, + "onboardedMetametricsParagraph3": { + "message": "Denken Sie daran, dass wir die von Ihnen bereitgestellten Daten niemals verkaufen und Sie sich jederzeit abmelden können." + }, + "onboardedMetametricsTitle": { + "message": "Helfen Sie uns, Ihr Erlebnis zu verbessern" }, "onboardingAdvancedPrivacyIPFSDescription": { "message": "Das IPFS-Gateway ermöglicht es, auf von Dritten gehostete Daten zuzugreifen und diese einzusehen. Sie können ein benutzerdefiniertes IPFS-Gateway hinzufügen oder weiterhin das Standard-Gateway verwenden." @@ -3203,9 +3493,6 @@ "onboardingMetametricsDescription2": { "message": "Wenn wir Metriken sammeln, wird es immer wie folgt sein ..." }, - "onboardingMetametricsDisagree": { - "message": "Nein, danke!" - }, "onboardingMetametricsInfuraTerms": { "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`" @@ -3240,6 +3527,9 @@ "onboardingMetametricsTitle": { "message": "Helfen Sie uns, MetaMask zu verbessern." }, + "onboardingMetametricsUseDataCheckbox": { + "message": "Wir verwenden diese Daten, um zu erfahren, wie Sie mit unserer Marketingkommunikation umgehen. Wir können relevante Neuigkeiten (wie Produktmerkmale) teilen." + }, "onboardingPinExtensionBillboardAccess": { "message": "Voller Zugriff" }, @@ -3283,6 +3573,22 @@ "message": "Phishing-Warnungen basieren auf der Kommunikation mit $1. jsDeliver hat Zugriff auf Ihre IP-Adresse. $2 ansehen.", "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" }, + "oneDayAbbreviation": { + "message": "1 T", + "description": "Shortened form of '1 day'" + }, + "oneMonthAbbreviation": { + "message": "1 M", + "description": "Shortened form of '1 month'" + }, + "oneWeekAbbreviation": { + "message": "1 W", + "description": "Shortened form of '1 week'" + }, + "oneYearAbbreviation": { + "message": "1 J", + "description": "Shortened form of '1 year'" + }, "onekey": { "message": "OneKey" }, @@ -3635,6 +3941,9 @@ "permissionsPageTourTitle": { "message": "Verbundene Websites sind nun Genehmigungen" }, + "permitSimulationDetailInfo": { + "message": "Sie erteilen dem Spender die Genehmigung, diese Menge an Tokens von Ihrem Konto auszugeben." + }, "personalAddressDetected": { "message": "Personalisierte Adresse identifiziert. Bitte füge die Token-Contract-Adresse ein." }, @@ -3667,6 +3976,10 @@ "popularCustomNetworks": { "message": "Beliebte benutzerdefinierte Netzwerke" }, + "popularNetworkAddToolTip": { + "message": "Einige dieser Netzwerke werden von Dritten betrieben. Die Verbindungen können weniger zuverlässig sein oder Dritten ermöglichen, Aktivitäten zu verfolgen. $1", + "description": "$1 is Learn more link" + }, "portfolio": { "message": "Portfolio" }, @@ -3679,6 +3992,12 @@ "prev": { "message": "Zurück" }, + "price": { + "message": "Preis" + }, + "priceUnavailable": { + "message": "Preis nicht verfügbar" + }, "primaryCurrencySetting": { "message": "Hauptwährung" }, @@ -3831,6 +4150,9 @@ "quoteRate": { "message": "Angebotskurs" }, + "rank": { + "message": "Rang" + }, "reAddAccounts": { "message": "alle anderen Konten erneut hinzuzufügen" }, @@ -4000,9 +4322,6 @@ "reset": { "message": "Zurücksetzen" }, - "resetStates": { - "message": "Status zurücksetzen" - }, "resetWallet": { "message": "Wallet zurücksetzen" }, @@ -4138,6 +4457,9 @@ "searchAccounts": { "message": "Konten durchsuchen" }, + "searchNfts": { + "message": "NFTs suchen" + }, "searchTokens": { "message": "Tokens suchen" }, @@ -4182,13 +4504,10 @@ "seedPhraseIntroRecommendedButtonCopy": { "message": "Meine Wallet sichern (empfohlen)" }, - "seedPhraseIntroSidebarBulletFour": { - "message": "Aufschreiben und an mehreren geheimen Orten aufbewahren" - }, "seedPhraseIntroSidebarBulletOne": { - "message": "In einem Passwort-Manager speichern" + "message": "Aufschreiben und an mehreren geheimen Orten aufbewahren" }, - "seedPhraseIntroSidebarBulletThree": { + "seedPhraseIntroSidebarBulletTwo": { "message": "In einem Schließfach aufbewahren" }, "seedPhraseIntroSidebarCopyOne": { @@ -4258,10 +4577,10 @@ "message": "Token auswählen" }, "selectNFTPrivacyPreference": { - "message": "NFT-Erkennung in den Einstellungen aktivieren" + "message": "Automatische NFT-Erkennung aktivieren" }, "selectPathHelp": { - "message": "Wenn Sie nicht die Konten sehen, die Sie erwarten, versuchen Sie, den HD-Pfad zu ändern." + "message": "Wenn Sie nicht die Konten sehen, die Sie erwarten, versuchen Sie, den HD-Pfad oder das aktuell ausgewählte Netzwerk zu ändern." }, "selectType": { "message": "Typ auswählen" @@ -4272,9 +4591,6 @@ "send": { "message": "Senden" }, - "sendAToken": { - "message": "Token senden" - }, "sendBugReport": { "message": "Übermitteln Sie uns einen Fehlerbericht." }, @@ -4325,9 +4641,6 @@ "sepolia": { "message": "Sepolia-Testnetzwerk" }, - "serviceWorkerKeepAlive": { - "message": "serviceWorkerKeepAlive" - }, "setAdvancedPrivacySettingsDetails": { "message": "MetaMask nutzt diese vertrauenswürdigen Dienstleistungen von Drittanbietern, um die Benutzerfreundlichkeit und Sicherheit der Produkte zu verbessern." }, @@ -4384,6 +4697,9 @@ "showIncomingTransactionsExplainer": { "message": "Dies hängt bei jedem Netzwerk von unterschiedlichen APIs Dritter ab, die Ihre Ethereum- und IP-Adresse offenlegen." }, + "showLess": { + "message": "Weniger anzeigen" + }, "showMore": { "message": "Mehr anzeigen" }, @@ -4414,9 +4730,6 @@ "signatureRequestGuidance": { "message": "Unterzeichnen Sie diese Nachricht nur, wenn Sie den Inhalt vollständig verstehen und der anfragenden Seite vertrauen." }, - "signatureRequestWarning": { - "message": "Das Unterzeichnen dieser Nachricht könnte gefährlich sein. Sie könnten der Gegenseite dieser Nachricht vollständige Kontrolle über Ihr Konto und Ihre Assets gewähren. Das bedeutet, dass sie Ihr Konto jederzeit leeren könnten. Seien Sie vorsichtig. $1." - }, "signed": { "message": "Unterzeichnet" }, @@ -4426,6 +4739,9 @@ "signing": { "message": "Signieren" }, + "signingInWith": { + "message": "Anmelden mit" + }, "simulationDetailsFailed": { "message": "Es ist ein Fehler beim Laden Ihrer Schätzung aufgetreten." }, @@ -4463,6 +4779,24 @@ "simulationsSettingSubHeader": { "message": "Geschätzte Saldoänderungen" }, + "siweIssued": { + "message": "Ausgestellt" + }, + "siweNetwork": { + "message": "Netzwerk" + }, + "siweRequestId": { + "message": "Anfrage-ID" + }, + "siweResources": { + "message": "Ressourcen" + }, + "siweSignatureSimulationDetailInfo": { + "message": "Sie melden sich bei einer Website an und es sind keine Änderungen an Ihrem Konto vorgesehen." + }, + "siweURI": { + "message": "URL" + }, "skip": { "message": "Überspringen" }, @@ -4566,6 +4900,14 @@ "snapAccountsDescription": { "message": "Von Snaps Dritter kontrollierte Konten." }, + "snapConnectTo": { + "message": "Mit $1 verbinden", + "description": "$1 is the website URL or a Snap name. Used for Snaps pre-approved connections." + }, + "snapConnectionPermissionDescription": { + "message": "Lassen Sie $1 automatisch und ohne Ihre Zustimmung mit $2 verbinden.", + "description": "Used for Snap pre-approved connections. $1 is the Snap name, $2 is a website URL." + }, "snapConnectionWarning": { "message": "$1 möchte $2 verwenden", "description": "$2 is the snap and $1 is the dapp requesting connection to the snap." @@ -4577,6 +4919,9 @@ "snapDetailWebsite": { "message": "Webseite" }, + "snapHomeMenu": { + "message": "Snap-Startmenü" + }, "snapInstallRequest": { "message": "Durch die Installation von $1 erhält es die folgenden Berechtigungen.", "description": "$1 is the snap name." @@ -4699,6 +5044,9 @@ "source": { "message": "Quelle" }, + "speed": { + "message": "Geschwindigkeit" + }, "speedUp": { "message": "Beschleunigen" }, @@ -4733,6 +5081,9 @@ "spendLimitTooLarge": { "message": "Ausgabenlimit zu groß" }, + "spender": { + "message": "Spender" + }, "spendingCap": { "message": "Ausgabenobergrenze" }, @@ -4853,9 +5204,6 @@ "stateLogsDescription": { "message": "Die Statusprotokolle enthalten Ihre öffentlichen Kontoadressen und gesendeten Transaktionen." }, - "states": { - "message": "Status" - }, "status": { "message": "Status" }, @@ -4960,6 +5308,13 @@ "submitted": { "message": "Abgesendet" }, + "suggestedBySnap": { + "message": "Vorgeschlagen von $1", + "description": "$1 is the snap name" + }, + "suggestedTokenName": { + "message": "Vorgeschlagener Name:" + }, "suggestedTokenSymbol": { "message": "Vorgeschlagenes Tickersymbol:" }, @@ -5074,7 +5429,7 @@ "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." }, "swapFetchingQuotes": { - "message": "Preisangaben abrufen" + "message": "Angebote einholen..." }, "swapFetchingQuotesErrorDescription": { "message": "Hmmm... etwas ist schiefgelaufen. Versuchen Sie es erneut, oder wenden Sie sich an den Kundensupport, wenn der Fehler weiterhin besteht." @@ -5422,7 +5777,7 @@ "description": "$1 represents the account name, $2 represents the network name" }, "switchedTo": { - "message": "Sie haben gewechselt zu" + "message": "Sie verwenden jetzt" }, "switchingNetworksCancelsPendingConfirmations": { "message": "Das Wechseln der Netzwerke wird alle ausstehenden Bestätigungen stornieren." @@ -5470,6 +5825,10 @@ "thisCollection": { "message": "diese Sammlung" }, + "threeMonthsAbbreviation": { + "message": "3 M", + "description": "Shortened form of '3 months'" + }, "time": { "message": "Zeit" }, @@ -5483,45 +5842,6 @@ "message": "An: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleEthSignBannerDescription": { - "message": "Sie sind einem Risiko auf Phishing-Angriffe ausgesetzt. Schützen Sie sich selbst, indem Sie eth_sign deaktivieren." - }, - "toggleEthSignDescriptionField": { - "message": "Wenn Sie diese Einstellung aktivieren, können Sie Signaturanfragen erhalten, die nicht lesbar sind. Wenn Sie eine Nachricht unterzeichnen, die Sie nicht verstehen, könnten Sie sich damit einverstanden erklären, Ihre Gelder und NFTs zu verschenken." - }, - "toggleEthSignField": { - "message": "eth_sign-Anfragen ein- oder ausschalten" - }, - "toggleEthSignModalBannerBoldText": { - "message": " werden Sie eventuell betrogen." - }, - "toggleEthSignModalBannerText": { - "message": "Wenn Sie gebeten werden, diese Einstellung zu aktivieren," - }, - "toggleEthSignModalCheckBox": { - "message": "Ich bin mir darüber im Klaren, dass ich meine Gelder und meine NFTs verlieren könnte, wenn ich eth_sign-Anfragen aktivere. " - }, - "toggleEthSignModalDescription": { - "message": "Das Erlauben von eth_sign-Anfragen kann Sie für Phishing-Angriffe verwundbar machen. Prüfen Sie die URL immer und sein Sie vorsichtig, wenn Sie Nachrichten unterzeichnen, die Code enthalten." - }, - "toggleEthSignModalFormError": { - "message": "Der Text ist falsch." - }, - "toggleEthSignModalFormLabel": { - "message": "Geben Sie „Ich unterzeichne nur, was ich verstehe“ ein, um fortzufahren." - }, - "toggleEthSignModalFormValidation": { - "message": "Ich unterzeichne nur, was ich verstehe." - }, - "toggleEthSignModalTitle": { - "message": "Nutzung auf eigenes Risiko" - }, - "toggleEthSignOff": { - "message": "AUS (empfohlen)" - }, - "toggleEthSignOn": { - "message": "EIN (nicht empfohlen)" - }, "toggleRequestQueueDescription": { "message": "Dadurch können Sie ein Netzwerk für jede Website auswählen, anstatt ein einziges Netzwerk für alle Websites auszuwählen. Diese Funktion verhindert, dass Sie manuell zwischen den Netzwerken wechseln müssen, was die Benutzerfreundlichkeit auf bestimmten Websites beeinträchtigen könnte." }, @@ -5549,6 +5869,9 @@ "tokenContractAddress": { "message": "Token-Contract-Adresse" }, + "tokenDecimal": { + "message": "Token-Dezimale" + }, "tokenDecimalFetchFailed": { "message": "Tokendezimal erforderlich. Finden Sie es auf: $1" }, @@ -5565,13 +5888,16 @@ "message": "Token-ID" }, "tokenList": { - "message": "Token-Listen:" + "message": "Token-Listen" }, "tokenScamSecurityRisk": { "message": "Token-Betrügereien und Sicherheitsrisiken" }, "tokenShowUp": { - "message": "Ihre Tokens werden möglicherweise nicht automatisch in Ihrer Wallet angezeigt." + "message": "Ihre Tokens werden möglicherweise nicht automatisch in Ihrer Wallet angezeigt. " + }, + "tokenStandard": { + "message": "Token-Standard" }, "tokenSymbol": { "message": "Tokensymbol" @@ -5583,6 +5909,9 @@ "message": "$1 neue Tokens gefunden", "description": "$1 is the number of new tokens detected" }, + "tokensInCollection": { + "message": "Tokens in der Sammlung" + }, "tooltipApproveButton": { "message": "Ich verstehe" }, @@ -5598,6 +5927,9 @@ "total": { "message": "Gesamt" }, + "totalVolume": { + "message": "Gesamtvolumen" + }, "transaction": { "message": "Transaktion" }, @@ -5613,6 +5945,9 @@ "transactionCreated": { "message": "Transaktion mit einem Wert von $1 bei $2 erstellt." }, + "transactionDataFunction": { + "message": "Funktion" + }, "transactionDetailDappGasMoreInfo": { "message": "Seite vorgeschlagen" }, @@ -5703,6 +6038,10 @@ "transferFrom": { "message": "Übertragung von" }, + "trillionAbbreviation": { + "message": "T", + "description": "Shortened form of 'trillion'" + }, "troubleConnectingToLedgerU2FOnFirefox": { "message": "Wir haben Probleme mit der Verbindung zu Ihrem Ledger. $1", "description": "$1 is a link to the wallet connection guide;" @@ -5781,6 +6120,9 @@ "message": "U2F", "description": "A name on an API for the browser to interact with devices that support the U2F protocol. On some browsers we use it to connect MetaMask to Ledger devices." }, + "unMatchedChain": { + "message": "Laut unseren Aufzeichnungen stimmt diese URL nicht mit einem bekannten Anbieter für diese Chain-ID überein." + }, "unapproved": { "message": "Nicht genehmigt" }, @@ -5832,12 +6174,21 @@ "update": { "message": "Update" }, + "updateOrEditNetworkInformations": { + "message": "Aktualisieren Sie Ihre Informationen oder" + }, "updateRequest": { "message": "Aktualisierungsanfrage" }, "updatedWithDate": { "message": "$1 aktualisiert" }, + "uploadDropFile": { + "message": "Legen Sie Ihre Datei hier ab" + }, + "uploadFile": { + "message": "Datei hochladen" + }, "urlErrorMsg": { "message": "URIs benötigen die korrekten HTTP/HTTPS Präfixe." }, @@ -6053,6 +6404,12 @@ "whatsThis": { "message": "Was ist das?" }, + "wrongChainId": { + "message": "Diese Chain-ID stimmt nicht mit dem Netzwerknamen überein." + }, + "wrongNetworkName": { + "message": "Laut unseren Aufzeichnungen stimmt dieser Netzwerkname nicht mit dieser Chain-ID überein." + }, "xOfYPending": { "message": "$1 von $2 ausstehend", "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" @@ -6076,8 +6433,11 @@ "yourAccounts": { "message": "Ihre Konten" }, - "yourFundsMayBeAtRisk": { - "message": "Ihre Gelder könnte gefährdet sein" + "yourActivity": { + "message": "Ihre Aktivität" + }, + "yourBalance": { + "message": "Ihr Kontostand" }, "yourNFTmayBeAtRisk": { "message": "Ihr NFT könnte gefährdet sein" diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json index 696486e9ab7b..3db88285098a 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -42,7 +42,7 @@ "message": "Συνδέστε το πορτοφόλι υλικού μέσω QR" }, "QRHardwareWalletSteps2Description": { - "message": "Ngrave (Έρχεται Σύντομα)" + "message": "Ngrave Zero" }, "SIWEAddressInvalid": { "message": "Η διεύθυνση στο αίτημα σύνδεσης δεν ταιριάζει με τη διεύθυνση του λογαριασμού που χρησιμοποιείτε για να συνδεθείτε." @@ -162,6 +162,9 @@ "accountSelectionRequired": { "message": "Πρέπει να επιλέξετε έναν λογαριασμό!" }, + "accountTypeNotSupported": { + "message": "Ο τύπος λογαριασμού δεν υποστηρίζεται" + }, "accounts": { "message": "Λογαριασμοί" }, @@ -277,6 +280,12 @@ "addNewAccount": { "message": "Προσθήκη νέου λογαριασμού Ethereum" }, + "addNewBitcoinAccount": { + "message": "Προσθήκη νέου λογαριασμού Bitcoin (Beta)" + }, + "addNewBitcoinTestnetAccount": { + "message": "Προσθήκη νέου λογαριασμού Bitcoin (Testnet)" + }, "addNewToken": { "message": "Προσθήκη νέου token" }, @@ -286,6 +295,9 @@ "addNfts": { "message": "Προσθήκη των NFT" }, + "addRpcUrl": { + "message": "Προσθήκη διεύθυνσης URL RPC" + }, "addSnapAccountToggle": { "message": "Ενεργοποίηση της λειτουργίας \"Προσθήκη λογαριασμού Snap (Beta)\"" }, @@ -305,12 +317,21 @@ "message": "Δεν μπορείτε να βρείτε ένα token; Μπορείτε να προσθέσετε χειροκίνητα οποιοδήποτε token επικολλώντας τη διεύθυνσή του. Οι διευθύνσεις συμβολαίων token μπορούν να βρεθούν στο $1", "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" }, + "addUrl": { + "message": "Προσθήκη διεύθυνσης URL" + }, "addingCustomNetwork": { "message": "Προσθήκη δικτύου" }, "addingTokens": { "message": "Προσθήκη tokens" }, + "additionalNetworks": { + "message": "Επιπλέον δίκτυα" + }, + "additionalRpcUrl": { + "message": "Επιπλέον διεύθυνση URL RPC" + }, "address": { "message": "Διεύθυνση" }, @@ -326,6 +347,18 @@ "advancedConfiguration": { "message": "Προηγμένη ρύθμιση παραμέτρων" }, + "advancedDetailsDataDesc": { + "message": "Δεδομένα" + }, + "advancedDetailsHexDesc": { + "message": "Δεκαεξαδικός" + }, + "advancedDetailsNonceDesc": { + "message": "Nonce" + }, + "advancedDetailsNonceTooltip": { + "message": "Πρόκειται για τον αριθμό συναλλαγής ενός λογαριασμού. Το nonce για την πρώτη συναλλαγή είναι 0 και αυξάνεται με διαδοχική σειρά." + }, "advancedGasFeeDefaultOptIn": { "message": "Αποθηκεύστε αυτές τις τιμές ως προεπιλεγμένες για το δίκτυο $1.", "description": "$1 is the current network name." @@ -349,6 +382,18 @@ "alert": { "message": "Ειδοποίηση" }, + "alertActionBuy": { + "message": "Αγορά ETH" + }, + "alertActionUpdateGas": { + "message": "Ενημέρωση ορίου των τελών συναλλαγών" + }, + "alertActionUpdateGasFee": { + "message": "Ενημέρωση των τελών συναλλαγών" + }, + "alertActionUpdateGasFeeLevel": { + "message": "Ενημέρωση επιλογών των τελών συναλλαγών" + }, "alertBannerMultipleAlertsDescription": { "message": "Εάν εγκρίνετε αυτό το αίτημα, ένας τρίτος που είναι γνωστός για απάτες μπορεί να αποκτήσει όλα τα περιουσιακά σας στοιχεία." }, @@ -358,6 +403,36 @@ "alertDisableTooltip": { "message": "Αυτό μπορεί να αλλάξει στις \"Ρυθμίσεις > Ειδοποιήσεις\"" }, + "alertMessageGasEstimateFailed": { + "message": "Δεν μπορούμε να παράσχουμε τα τέλη με ακρίβεια και αυτή η εκτίμηση μπορεί να είναι υψηλή. Σας προτείνουμε να εισάγετε ένα προσαρμοσμένο όριο τελών συναλλαγών, αλλά υπάρχει κίνδυνος η συναλλαγή να αποτύχει και πάλι." + }, + "alertMessageGasFeeLow": { + "message": "Όταν επιλέγετε χαμηλά τέλη, να αναμένετε πιο αργές συναλλαγές και μεγαλύτερους χρόνους αναμονής. Για ταχύτερες συναλλαγές, επιλέξτε τις επιλογές χρέωσης Market (Αγοράς) ή Aggressive (Υψηλότερη αγοραστική τιμή)." + }, + "alertMessageGasTooLow": { + "message": "Για να συνεχίσετε με αυτή τη συναλλαγή, θα πρέπει να αυξήσετε το όριο των τελών συναλλαγών σε 21000 ή περισσότερο." + }, + "alertMessageInsufficientBalance": { + "message": "Δεν έχετε αρκετά ETH στον λογαριασμό σας για να πληρώσετε τα τέλη συναλλαγών." + }, + "alertMessageNetworkBusy": { + "message": "Οι τιμές των τελών συναλλαγών είναι υψηλές και οι εκτιμήσεις είναι λιγότερο ακριβείς." + }, + "alertMessageNoGasPrice": { + "message": "Δεν μπορούμε να συνεχίσουμε με αυτή τη συναλλαγή μέχρι να ενημερώσετε τα τέλη μη αυτόματα." + }, + "alertMessagePendingTransactions": { + "message": "Αυτή η συναλλαγή δεν θα πραγματοποιηθεί μέχρι να ολοκληρωθεί μια προηγούμενη συναλλαγή. Μάθετε πώς να ακυρώσετε ή να επισπεύσετε μια συναλλαγή." + }, + "alertMessageSignInDomainMismatch": { + "message": "Ο ιστότοπος που υποβάλλει το αίτημα δεν είναι ο ιστότοπος στον οποίο έχετε συνδεθεί. Αυτό θα μπορούσε να είναι μια απόπειρα κλοπής των στοιχείων σύνδεσής σας." + }, + "alertMessageSignInWrongAccount": { + "message": "Αυτός ο ιστότοπος σας ζητάει να συνδεθείτε χρησιμοποιώντας λάθος λογαριασμό." + }, + "alertMessageSigningOrSubmitting": { + "message": "Αυτή η συναλλαγή θα πραγματοποιηθεί μόνο όταν ολοκληρωθεί η προηγούμενη συναλλαγή σας." + }, "alertModalAcknowledge": { "message": "Αναγνωρίζω τον κίνδυνο και εξακολουθώ να θέλω να συνεχίσω" }, @@ -367,6 +442,33 @@ "alertModalReviewAllAlerts": { "message": "Έλεγχος όλων των ειδοποιήσεων" }, + "alertReasonGasEstimateFailed": { + "message": "Ανακριβή τέλη" + }, + "alertReasonGasFeeLow": { + "message": "Αργή ταχύτητα" + }, + "alertReasonGasTooLow": { + "message": "Χαμηλό όριο τελών συναλλαγών" + }, + "alertReasonInsufficientBalance": { + "message": "Ανεπαρκή κεφάλαια" + }, + "alertReasonNetworkBusy": { + "message": "Το δίκτυο είναι απασχολημένο" + }, + "alertReasonNoGasPrice": { + "message": "Δεν είναι διαθέσιμη η εκτίμηση των τελών" + }, + "alertReasonPendingTransactions": { + "message": "Εκκρεμής συναλλαγή" + }, + "alertReasonSignIn": { + "message": "Ύποπτο αίτημα σύνδεσης" + }, + "alertReasonWrongAccount": { + "message": "Λάθος λογαριασμός" + }, "alertSettingsUnconnectedAccount": { "message": "Περιήγηση σε έναν ιστότοπο με έναν μη συνδεδεμένο επιλέγμενο λογαριασμό" }, @@ -398,6 +500,12 @@ "allPermissions": { "message": "Όλες οι άδειες χρήσης" }, + "allTimeHigh": { + "message": "Υψηλό όλων των εποχών" + }, + "allTimeLow": { + "message": "Χαμηλό όλων των εποχών" + }, "allYourNFTsOf": { "message": "Όλα τα NFT σας από το $1", "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" @@ -436,9 +544,6 @@ "message": "$1 και $2", "description": "$1 is the first item, $2 is the second item. Used in Snap Install Warning modal." }, - "announcements": { - "message": "Ανακοινώσεις" - }, "appDescription": { "message": "Ένα Πορτοφόλι Ethereum στο Πρόγραμμα Περιήγησής σας", "description": "The description of the application" @@ -516,7 +621,7 @@ "message": "Επιλογές περιουσιακών στοιχείων" }, "attemptSendingAssets": { - "message": "Εάν επιχειρήσετε να στείλετε περιουσιακά στοιχεία απευθείας από ένα δίκτυο σε ένα άλλο, αυτό ενδέχεται να οδηγήσει σε μόνιμη απώλεια περιουσιακών στοιχείων. Βεβαιωθείτε ότι χρησιμοποιείτε μια διασύνδεση." + "message": "Ενδέχεται να χάσετε τα περιουσιακά σας στοιχεία εάν προσπαθήσετε να τα στείλετε από άλλο δίκτυο. Μεταφέρετε κεφάλαια με ασφάλεια μεταξύ δικτύων χρησιμοποιώντας μια διασύνδεση." }, "attemptSendingAssetsWithPortfolio": { "message": "Μπορεί να χάσετε τα περιουσιακά σας στοιχεία αν προσπαθήσετε να τα στείλετε από άλλο δίκτυο. Μεταφέρετε χρήματα με ασφάλεια μεταξύ δικτύων χρησιμοποιώντας μια διασύνδεση, όπως το $1" @@ -524,6 +629,9 @@ "attemptToCancelSwapForFree": { "message": "Προσπάθεια ακύρωσης των ανταλλαγών δωρεάν" }, + "attributes": { + "message": "Χαρακτηριστικά" + }, "attributions": { "message": "Αποδόσεις" }, @@ -594,6 +702,10 @@ "basicConfigurationBannerTitle": { "message": "Οι βασικές λειτουργίες είναι απενεργοποιημένες" }, + "basicConfigurationDescription": { + "message": "Το MetaMask παρέχει βασικές λειτουργίες, όπως λεπτομέρειες για τα tokens και ρυθμίσεις τελών συναλλαγών μέσω υπηρεσιών διαδικτύου. Όταν χρησιμοποιείτε υπηρεσίες διαδικτύου, η διεύθυνση IP σας κοινοποιείται, σε αυτή την περίπτωση με στο MetaMask. Αυτό συμβαίνει ακριβώς όπως όταν επισκέπτεστε οποιονδήποτε ιστότοπο. Το MetaMask χρησιμοποιεί αυτά τα δεδομένα προσωρινά και δεν πωλεί ποτέ τα δεδομένα σας. Μπορείτε να χρησιμοποιήσετε ένα VPN ή να απενεργοποιήσετε αυτές τις υπηρεσίες, αλλά αυτό μπορεί να επηρεάσει την εμπειρία σας στο MetaMask. Για να μάθετε περισσότερα διαβάστε την $1.", + "description": "$1 is to be replaced by the message for privacyMsg, and will link to https://consensys.io/privacy-policy" + }, "basicConfigurationLabel": { "message": "Βασικές λειτουργίες" }, @@ -637,6 +749,29 @@ "betaWalletCreationSuccessReminder2": { "message": "Η δοκιμαστική έκδοση του MetaMask δεν θα σας ζητήσει ποτέ τη Μυστική Φράση Ανάκτησής σας." }, + "billionAbbreviation": { + "message": "Δ", + "description": "Shortened form of 'billion'" + }, + "bitcoinActivityNotSupported": { + "message": "Δεν υποστηρίζεται η δραστηριότητα στα Bitcoins" + }, + "bitcoinSupportSectionTitle": { + "message": "Bitcoin" + }, + "bitcoinSupportToggleDescription": { + "message": "Η ενεργοποίηση αυτής της λειτουργίας θα σας δώσει τη δυνατότητα να προσθέσετε έναν λογαριασμό Bitcoin στην επέκταση σας του MetaMask που προέρχεται από την υπάρχουσα Μυστική Φράση Ανάκτησης. Πρόκειται για μια πειραματική λειτουργία Beta, οπότε θα πρέπει να τη χρησιμοποιήσετε με δική σας ευθύνη. Για να μας δώσετε τα σχόλιά σας σχετικά με αυτή τη νέα εμπειρία Bitcoin, συμπληρώστε αυτή την $1.", + "description": "$1 is the link to a product feedback form" + }, + "bitcoinSupportToggleTitle": { + "message": "Ενεργοποίηση της λειτουργίας \"Προσθήκη νέου λογαριασμού Bitcoin (Beta)\"" + }, + "bitcoinTestnetSupportToggleDescription": { + "message": "Η ενεργοποίηση αυτής της λειτουργίας θα σας δώσει τη δυνατότητα να προσθέσετε έναν λογαριασμό Bitcoin για το δίκτυο δοκιμών." + }, + "bitcoinTestnetSupportToggleTitle": { + "message": "Ενεργοποίηση της λειτουργίας \"Προσθήκη νέου λογαριασμού Bitcoin (Testnet)\"" + }, "blockExplorerAccountAction": { "message": "Λογαριασμός", "description": "This is used with viewOnEtherscan and viewInExplorer e.g View Account in Explorer" @@ -662,6 +797,9 @@ "blockaid": { "message": "Blockaid" }, + "blockaidAlertInfo": { + "message": "Δεν συνιστούμε να προχωρήσετε σε αυτό το αίτημα." + }, "blockaidDescriptionApproveFarming": { "message": "Εάν εγκρίνετε αυτό το αίτημα, ένα τρίτο μέρος που είναι γνωστό για απάτες μπορεί να πάρει όλα τα περιουσιακά σας στοιχεία." }, @@ -669,7 +807,7 @@ "message": "Εάν εγκρίνετε αυτό το αίτημα, κάποιος μπορεί να κλέψει τα περιουσιακά σας στοιχεία που είναι καταχωρημένα στο Blur." }, "blockaidDescriptionErrored": { - "message": "Λόγω κάποιου σφάλματος, αυτό το αίτημα δεν επαληθεύτηκε από τον πάροχο ασφαλείας. Προχωρήστε με προσοχή." + "message": "Λόγω κάποιου σφάλματος, δεν μπορέσαμε να ελέγξουμε τις ειδοποιήσεις ασφαλείας. Συνεχίστε μόνο αν εμπιστεύεστε κάθε διεύθυνση που εμπλέκεται." }, "blockaidDescriptionMaliciousDomain": { "message": "Αλληλεπιδράτε με έναν κακόβουλο τομέα. Εάν εγκρίνετε αυτό το αίτημα, ενδέχεται να χάσετε τα περιουσιακά σας στοιχεία." @@ -683,6 +821,9 @@ "blockaidDescriptionTransferFarming": { "message": "Εάν εγκρίνετε αυτό το αίτημα, ένας τρίτος που είναι γνωστός για απάτες θα πάρει όλα τα περιουσιακά σας στοιχεία." }, + "blockaidDescriptionWarning": { + "message": "Πρόκειται για ένα παραπλανητικό αίτημα. Συνεχίστε μόνο αν εμπιστεύεστε κάθε διεύθυνση που εμπλέκεται." + }, "blockaidMessage": { "message": "Διαφύλαξη της ιδιωτικότητας - δεν κοινοποιούνται δεδομένα σε τρίτους. Διατίθεται στα Arbitrum, Avalanche, BNB chain, Ethereum Mainnet, Linea, Optimism, Polygon, Base και Sepolia." }, @@ -690,7 +831,7 @@ "message": "Αυτό είναι ένα παραπλανητικό αίτημα" }, "blockaidTitleMayNotBeSafe": { - "message": "Το αίτημα μπορεί να μην είναι ασφαλές" + "message": "Να είστε προσεκτικοί" }, "blockaidTitleSuspicious": { "message": "Αυτό είναι ένα ύποπτο αίτημα" @@ -698,6 +839,9 @@ "blockies": { "message": "Blockies" }, + "boughtFor": { + "message": "Αγοράστηκε για" + }, "bridge": { "message": "Διασύνδεση" }, @@ -782,6 +926,9 @@ "chromeRequiredForHardwareWallets": { "message": "Θα πρέπει να χρησιμοποιήσετε το MetaMask στο Google Chrome για να συνδεθείτε στο Πορτοφόλι Υλικού." }, + "circulatingSupply": { + "message": "Διαθέσιμη προσφορά" + }, "clear": { "message": "Εκκαθάριση" }, @@ -802,7 +949,7 @@ "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" }, "clickToManuallyAdd": { - "message": "Κάντε κλικ εδώ για να προσθέσετε χειροκίνητα τα tokens." + "message": "Μπορείτε πάντα να προσθέσετε τα tokens χειροκίνητα." }, "close": { "message": "Κλείσιμο" @@ -816,6 +963,9 @@ "coingecko": { "message": "CoinGecko" }, + "collectionName": { + "message": "Όνομα συλλογής" + }, "comboNoOptions": { "message": "Δεν βρέθηκαν επιλογές", "description": "Default text shown in the combo field dropdown if no options." @@ -841,6 +991,9 @@ "confirmAlertModalAcknowledgeMultiple": { "message": "Έχω ενημερωθεί για τις ειδοποιήσεις και εξακολουθώ να θέλω να συνεχίσω" }, + "confirmAlertModalAcknowledgeSingle": { + "message": "Έχω ενημερωθεί για την ειδοποίηση και εξακολουθώ να θέλω να συνεχίσω" + }, "confirmAlertModalDetails": { "message": "Εάν συνδεθείτε, ένας τρίτος που είναι γνωστός για απάτες μπορεί να αποκτήσει όλα τα περιουσιακά σας στοιχεία. Ελέγξτε τις ειδοποιήσεις πριν συνεχίσετε." }, @@ -856,18 +1009,42 @@ "confirmConnectionTitle": { "message": "Επιβεβαίωση σύνδεσης με το $1" }, + "confirmDeletion": { + "message": "Επιβεβαίωση διαγραφής" + }, + "confirmFieldPaymaster": { + "message": "Τα τέλη καταβλήθηκαν από" + }, + "confirmFieldTooltipPaymaster": { + "message": "Τα τέλη για αυτή τη συναλλαγή θα καταβληθούν από τον υπεύθυνο πληρωμών του έξυπνου συμβολαίου." + }, "confirmPassword": { "message": "Επιβεβαίωση Κωδικού Πρόσβασης" }, "confirmRecoveryPhrase": { "message": "Επιβεβαιώστε τη Μυστική Φράση Ανάκτησης" }, + "confirmRpcUrlDeletionMessage": { + "message": "Είστε σίγουροι ότι θέλετε να διαγράψετε τη διεύθυνση URL RPC; Οι πληροφορίες σας δεν θα αποθηκευτούν για αυτό το δίκτυο." + }, "confirmTitleDescContractInteractionTransaction": { "message": "Επιβεβαιώστε αυτή τη συναλλαγή μόνο εάν κατανοείτε πλήρως το περιεχόμενο και εμπιστεύεστε τον ιστότοπο που τη ζητάει." }, + "confirmTitleDescPermitSignature": { + "message": "Αυτός ο ιστότοπος ζητάει άδεια για να δαπανήσει τα tokens σας." + }, + "confirmTitleDescSIWESignature": { + "message": "Ένας ιστότοπος θέλει να συνδεθείτε για να αποδείξετε ότι είστε ο κάτοχος αυτού του λογαριασμού." + }, "confirmTitleDescSignature": { "message": "Επιβεβαιώστε αυτό το μήνυμα μόνο εάν εγκρίνετε το περιεχόμενο και εμπιστεύεστε τον ιστότοπο που το ζητάει." }, + "confirmTitlePermitSignature": { + "message": "Αίτημα ανώτατου ορίου δαπανών" + }, + "confirmTitleSIWESignature": { + "message": "Αίτημα σύνδεσης" + }, "confirmTitleSignature": { "message": "Αίτημα υπογραφής" }, @@ -961,7 +1138,7 @@ "message": "Συνδέεται με" }, "connecting": { - "message": "Σύνδεση..." + "message": "Σύνδεση" }, "connectingTo": { "message": "Σύνδεση με $1" @@ -1094,6 +1271,9 @@ "createSnapAccountTitle": { "message": "Δημιουργία λογαριασμού" }, + "creatorAddress": { + "message": "Διεύθυνση δημιουργού" + }, "crossChainSwapsLink": { "message": "Εναλλαγή σε διάφορα δίκτυα με το MetaMask Portfolio" }, @@ -1263,9 +1443,27 @@ "data": { "message": "Δεδομένα" }, + "dataCollectionForMarketing": { + "message": "Συγκέντρωση δεδομένων για μάρκετινγκ" + }, + "dataCollectionForMarketingDescription": { + "message": "Θα χρησιμοποιήσουμε το MetaMetrics για να μάθουμε πώς αλληλεπιδράτε με τις επικοινωνίες μάρκετινγκ. Ενδέχεται να κοινοποιησούμε σχετικές πληροφορίες (όπως χαρακτηριστικά προϊόντων και άλλο υλικό)." + }, + "dataCollectionWarningPopoverButton": { + "message": "Εντάξει" + }, + "dataCollectionWarningPopoverDescription": { + "message": "Απενεργοποιήσατε τη συλλογή δεδομένων για σκοπούς μάρκετινγκ. Αυτό ισχύει μόνο για αυτή τη συσκευή. Εάν χρησιμοποιείτε το MetaMask και σε άλλες συσκευές, βεβαιωθείτε ότι έχετε επιλέξει την απενεργοποίηση και εκεί." + }, "dataHex": { "message": "Δεκαεξαδικός" }, + "dataUnavailable": { + "message": "μη διαθέσιμα δεδομένα" + }, + "dateCreated": { + "message": "Ημερομηνία δημιουργίας" + }, "dcent": { "message": "D'Cent" }, @@ -1295,6 +1493,9 @@ "decryptRequest": { "message": "Αποκρυπτογράφηση αιτήματος" }, + "defaultRpcUrl": { + "message": "Προεπιλεγμένη διεύθυνση URL RPC" + }, "delete": { "message": "Διαγραφή" }, @@ -1311,6 +1512,9 @@ "message": "Διαγραφή του δικτύου $1;", "description": "$1 represents the name of the network" }, + "deleteRpcUrl": { + "message": "Διαγραφή της διεύθυνσης URL RPC" + }, "deposit": { "message": "Κατάθεση" }, @@ -1336,18 +1540,6 @@ "details": { "message": "Λεπτομέρειες" }, - "developerOptions": { - "message": "Επιλογές προγραμματιστών" - }, - "developerOptionsResetStatesAnnouncementsDescription": { - "message": "Επαναφέρει το δυαδικό isShown σε ψευδές για όλες τις ανακοινώσεις. Οι ανακοινώσεις είναι οι ειδοποιήσεις που εμφανίζονται στο αναδυόμενο παράθυρο \"Τι νέο υπάρχει\"." - }, - "developerOptionsResetStatesOnboarding": { - "message": "Επαναφέρει διάφορες καταστάσεις που σχετίζονται με την ενσωμάτωση και ανακατευθύνει στη σελίδα ενσωμάτωσης \"Ασφαλίστε το πορτοφόλι σας\"." - }, - "developerOptionsServiceWorkerKeepAlive": { - "message": "Αυτό έχει ως αποτέλεσμα τη συνεχή αποθήκευση μιας χρονοσφραγίδας στο session.storage" - }, "disabledGasOptionToolTipMessage": { "message": "Το \"1$\" είναι απενεργοποιημένο επειδή δεν πληροί την ελάχιστη αύξηση 10% σε σχέση με τα αρχικά τέλη συναλλαγής.", "description": "$1 is gas estimate type which can be market or aggressive" @@ -1524,6 +1716,9 @@ "editGasTooLow": { "message": "Άγνωστος χρόνος επεξεργασίας" }, + "editNetworkLink": { + "message": "επεξεργασία του αρχικού δικτύου" + }, "editNonceField": { "message": "Επεξεργασία Nonce" }, @@ -1555,6 +1750,9 @@ "enabled": { "message": "Ενεργοποιημένο" }, + "enabledNetworks": { + "message": "Ενεργοποιημένα δίκτυα" + }, "encryptionPublicKeyNotice": { "message": "Το $1 θα ήθελε το δημόσιο σας κλειδί κρυπτογράφησης. Με τη συγκατάθεσή σας, αυτός ο ιστότοπος θα είναι σε θέση να συντάσσει κρυπτογραφημένα μηνύματα προς εσάς.", "description": "$1 is the web3 site name" @@ -1659,6 +1857,9 @@ "estimatedFee": { "message": "Εκτιμώμενη χρέωση" }, + "estimatedFeeTooltip": { + "message": "Ποσό που καταβλήθηκε για τη διεκπεραίωση της συναλλαγής στο δίκτυο." + }, "ethGasPriceFetchWarning": { "message": "Η εφεδρική τιμή του τέλους συναλλαγής παρέχεται καθώς η κύρια υπηρεσία εκτίμησης τελών συναλλαγής, δεν είναι διαθέσιμη αυτή τη στιγμή." }, @@ -1678,6 +1879,12 @@ "etherscanViewOn": { "message": "Προβολή στην Etherscan" }, + "existingChainId": { + "message": "Οι πληροφορίες που έχετε εισάγει συνδέονται με ένα υπάρχον αναγνωριστικό αλυσίδας." + }, + "existingRpcUrl": { + "message": "Αυτή η διεύθυνση URL συνδέεται με ένα άλλο αναγνωριστικό αλυσίδας." + }, "expandView": { "message": "Ανάπτυξη προβολής" }, @@ -1732,6 +1939,9 @@ "message": "Η εισαγωγή αρχείων δεν λειτουργεί; Κάντε κλικ εδώ!", "description": "Helps user import their account from a JSON file" }, + "findTheRightChainId": { + "message": "Βρείτε το σωστό στο:" + }, "flaskWelcomeUninstall": { "message": "θα πρέπει να απεγκαταστήσετε αυτή την επέκταση", "description": "This request is shown on the Flask Welcome screen. It is intended for non-developers, and will be bolded." @@ -1771,6 +1981,9 @@ "forgotPassword": { "message": "Ξεχάσατε τον κωδικό πρόσβασής σας;" }, + "form": { + "message": "φόρμα" + }, "from": { "message": "Από" }, @@ -1901,7 +2114,7 @@ "message": "Δοκιμαστικό δίκτυο Goerli" }, "gotIt": { - "message": "Το κατάλαβα!" + "message": "Το κατάλαβα" }, "grantedToWithColon": { "message": "Χορηγήθηκε στο:" @@ -1979,6 +2192,12 @@ "highLowercase": { "message": "υψηλό" }, + "highestCurrentBid": { + "message": "Υψηλότερη τρέχουσα προσφορά" + }, + "highestFloorPrice": { + "message": "Υψηλότερη κατώτατη τιμή" + }, "history": { "message": "Ιστορικό" }, @@ -2318,12 +2537,21 @@ "knownTokenWarning": { "message": "Αυτή η ενέργεια θα επεξεργαστεί τα tokens που είναι ήδη καταχωρημένα στο πορτοφόλι σας, τα οποία μπορούν να χρησιμοποιηθούν για να σας εξαπατήσουν. Εγκρίνετε μόνο αν είστε σίγουροι ότι θέλετε να αλλάξετε αυτό που αντιπροσωπεύουν αυτά τα tokens. Μάθετε περισσότερα στο $1" }, + "l1Fee": { + "message": "Τέλη L1" + }, + "l1FeeTooltip": { + "message": "Τέλη συναλλαγών L1" + }, + "l2Fee": { + "message": "Τέλη L2" + }, + "l2FeeTooltip": { + "message": "Τέλη συναλλαγών L2" + }, "lastConnected": { "message": "Τελευταία σύνδεση" }, - "lastPriceSold": { - "message": "Τελευταία τιμή πώλησης" - }, "lastSold": { "message": "Τελευταία πώληση" }, @@ -2495,6 +2723,12 @@ "message": "Βεβαιωθείτε ότι κανείς δεν κοιτάει", "description": "Warning to users to be care while creating and saving their new Secret Recovery Phrase" }, + "marketCap": { + "message": "Κεφαλαιοποίηση αγοράς" + }, + "marketDetails": { + "message": "Λεπτομέρειες της αγοράς" + }, "max": { "message": "Μέγ." }, @@ -2504,6 +2738,9 @@ "maxFee": { "message": "Μέγιστη χρέωση" }, + "maxFeeTooltip": { + "message": "Τα μέγιστα τέλη που προβλέπονται για την πληρωμή της συναλλαγής." + }, "maxPriorityFee": { "message": "Μέγιστο τέλος προτεραιότητας" }, @@ -2551,12 +2788,19 @@ "methodData": { "message": "Μέθοδος" }, + "methodDataTransactionDesc": { + "message": "Λειτουργία που εκτελείται με βάση τα αποκωδικοποιημένα δεδομένα εισόδου." + }, "methodNotSupported": { "message": "Δεν υποστηρίζεται με αυτόν τον λογαριασμό." }, "metrics": { "message": "Μετρήσεις" }, + "millionAbbreviation": { + "message": "Ε", + "description": "Shortened form of 'million'" + }, "mismatchAccount": { "message": "Ο λογαριασμός ($1) που επιλέξατε είναι διαφορετικός από τον λογαριασμό που προσπαθείτε να υπογράψετε ($2)" }, @@ -2738,6 +2982,9 @@ "networkNameBase": { "message": "Βάση" }, + "networkNameBitcoin": { + "message": "Bitcoin" + }, "networkNameDefinition": { "message": "Το όνομα που συνδέεται με αυτό το δίκτυο." }, @@ -2762,6 +3009,9 @@ "networkNameZkSyncEra": { "message": "zkSync Era" }, + "networkOptions": { + "message": "Επιλογές δικτύου" + }, "networkProvider": { "message": "Πάροχος δικτύου" }, @@ -2830,6 +3080,9 @@ "newNetworkAdded": { "message": "Το “$1” προστέθηκε με επιτυχία!" }, + "newNetworkEdited": { + "message": "Το “$1” έχει επεξεργασθεί με επιτυχία!" + }, "newNftAddedMessage": { "message": "Το NFT προστέθηκε με επιτυχία!" }, @@ -2866,6 +3119,9 @@ "nftAlreadyAdded": { "message": "Το NFT έχει ήδη προστεθεί." }, + "nftAutoDetectionEnabled": { + "message": "Ενεργοποιήθηκε η αυτόματη ανίχνευση NFT" + }, "nftDisclaimer": { "message": "Αποποίηση ευθυνών: Το MetaMask αντλεί το αρχείο πολυμέσων από το url της πηγής. Αυτό το url μερικές φορές αλλάζει ανάλογα με την αγορά στην οποία εκδόθηκε το NFT." }, @@ -2916,6 +3172,9 @@ "noDomainResolution": { "message": "Δεν παρέχεται ανάλυση για τον τομέα." }, + "noHardwareWalletOrSnapsSupport": { + "message": "Τα Snaps και τα περισσότερα πορτοφόλια υλικού δεν θα λειτουργούν με την τρέχουσα έκδοση του προγράμματος περιήγησής σας." + }, "noNFTs": { "message": "Δεν υπάρχουν NFT ακόμα" }, @@ -2946,8 +3205,8 @@ "nonceField": { "message": "Προσαρμόστε τη συναλλαγή nonce" }, - "nonceFieldDescription": { - "message": "Ενεργοποιήστε την επιλογή αυτή για να αλλάξετε τον αριθμό Nonce (αριθμό συναλλαγής) στις οθόνες επιβεβαίωσης. Αυτή είναι μια προηγμένη λειτουργία, χρησιμοποιήστε την με προσοχή." + "nonceFieldDesc": { + "message": "Ενεργοποιήστε την επιλογή αυτή για να αλλάξετε το nonce (αριθμός συναλλαγής) κατά την αποστολή περιουσιακών στοιχείων. Αυτή είναι μια προηγμένη λειτουργία, χρησιμοποιήστε τη με προσοχή." }, "nonceFieldHeading": { "message": "Προσαρμοσμένο Nonce" @@ -3149,6 +3408,9 @@ "numberOfNewTokensDetectedSingular": { "message": "1 νέο token βρέθηκε σε αυτόν τον λογαριασμό" }, + "numberOfTokens": { + "message": "Αριθμός tokens" + }, "ofTextNofM": { "message": "από" }, @@ -3164,8 +3426,36 @@ "on": { "message": "Ενεργό" }, - "onboarding": { - "message": "Ενσωμάτωση" + "onboardedMetametricsAccept": { + "message": "Συμφωνώ" + }, + "onboardedMetametricsDisagree": { + "message": "Όχι, ευχαριστώ" + }, + "onboardedMetametricsKey1": { + "message": "Τελευταίες εξελίξεις" + }, + "onboardedMetametricsKey2": { + "message": "Χαρακτηριστικά προϊόντων" + }, + "onboardedMetametricsKey3": { + "message": "Άλλο σχετικό προωθητικό υλικό" + }, + "onboardedMetametricsLink": { + "message": "MetaMetrics" + }, + "onboardedMetametricsParagraph1": { + "message": "Εκτός από το $1, θα θέλαμε να χρησιμοποιήσουμε δεδομένα για να κατανοήσουμε πώς αλληλεπιδράτε με τις επικοινωνίες μάρκετινγκ.", + "description": "$1 represents the 'onboardedMetametricsLink' locale string" + }, + "onboardedMetametricsParagraph2": { + "message": "Αυτό μας βοηθά να εξατομικεύσουμε αυτά που κοινοποιούμε σε εσάς, όπως:" + }, + "onboardedMetametricsParagraph3": { + "message": "Να θυμάστε ότι δεν πουλάμε ποτέ τα δεδομένα που μας παρέχετε και μπορείτε να εξαιρεθείτε ανά πάσα στιγμή." + }, + "onboardedMetametricsTitle": { + "message": "Βοηθήστε μας να βελτιώσουμε την εμπειρία σας" }, "onboardingAdvancedPrivacyIPFSDescription": { "message": "Η πύλη IPFS επιτρέπει την πρόσβαση και την προβολή δεδομένων που φιλοξενούνται από τρίτους. Μπορείτε να προσθέσετε μια προσαρμοσμένη πύλη IPFS ή να συνεχίσετε να χρησιμοποιείτε την προεπιλεγμένη." @@ -3203,9 +3493,6 @@ "onboardingMetametricsDescription2": { "message": "Όταν συγκεντρώνουμε μετρήσεις, θα είναι πάντα..." }, - "onboardingMetametricsDisagree": { - "message": "Όχι, ευχαριστώ" - }, "onboardingMetametricsInfuraTerms": { "message": "Θα σας ενημερώσουμε εάν αποφασίσουμε να χρησιμοποιήσουμε αυτά τα δεδομένα για άλλους σκοπούς. Για περισσότερες πληροφορίες, μπορείτε να ανατρέξετε στην $1. Να θυμάστε ότι μπορείτε να μεταβείτε στις ρυθμίσεις και να εξαιρεθείτε ανά πάσα στιγμή.", "description": "$1 represents `onboardingMetametricsInfuraTermsPolicy`" @@ -3240,6 +3527,9 @@ "onboardingMetametricsTitle": { "message": "Βοηθήστε μας να βελτιώσουμε το MetaMask" }, + "onboardingMetametricsUseDataCheckbox": { + "message": "Θα χρησιμοποιήσουμε αυτά τα δεδομένα για να μάθουμε πώς αλληλεπιδράτε με τις επικοινωνίες μάρκετινγκ. Ενδέχεται να κοινοποιήσουμε σχετικές πληροφορίες (όπως χαρακτηριστικά προϊόντων)." + }, "onboardingPinExtensionBillboardAccess": { "message": "Πλήρης πρόσβαση" }, @@ -3283,6 +3573,22 @@ "message": "Οι ειδοποιήσεις ανίχνευσης για phishing βασίζονται στην επικοινωνία με το $1. Το jsDeliver θα έχει πρόσβαση στη διεύθυνση IP σας. Δείτε $2.", "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" }, + "oneDayAbbreviation": { + "message": "1Η", + "description": "Shortened form of '1 day'" + }, + "oneMonthAbbreviation": { + "message": "1M", + "description": "Shortened form of '1 month'" + }, + "oneWeekAbbreviation": { + "message": "1ΕΒ", + "description": "Shortened form of '1 week'" + }, + "oneYearAbbreviation": { + "message": "1Ε", + "description": "Shortened form of '1 year'" + }, "onekey": { "message": "OneKey" }, @@ -3406,7 +3712,7 @@ "message": "Ζητήθηκε τώρα" }, "permissionRequestedForAccounts": { - "message": "Ζητείται τώρα για $1", + "message": "Ζητήθηκε τώρα για $1", "description": "Permission cell status for requested permission including accounts, rendered as AvatarGroup which is $1." }, "permissionRevoked": { @@ -3635,6 +3941,9 @@ "permissionsPageTourTitle": { "message": "Οι συνδεδεμένοι ιστότοποι είναι τώρα με άδειες χρήσης" }, + "permitSimulationDetailInfo": { + "message": "Δίνετε στον διαθέτη την άδεια να δαπανήσει τα tokens από τον λογαριασμό σας." + }, "personalAddressDetected": { "message": "Η προσωπική διεύθυνση εντοπίστηκε. Καταχωρίστε τη διεύθυνση συμβολαίου του token." }, @@ -3667,6 +3976,10 @@ "popularCustomNetworks": { "message": "Δημοφιλή προσαρμοσμένα δίκτυα" }, + "popularNetworkAddToolTip": { + "message": "Ορισμένα από αυτά τα δίκτυα βασίζονται σε τρίτους. Οι συνδέσεις μπορεί να είναι λιγότερο αξιόπιστες ή να επιτρέπουν σε τρίτους να παρακολουθούν τη δραστηριότητα. $1", + "description": "$1 is Learn more link" + }, "portfolio": { "message": "Χαρτοφυλάκιο" }, @@ -3679,6 +3992,12 @@ "prev": { "message": "Προηγούμενο" }, + "price": { + "message": "Τιμή" + }, + "priceUnavailable": { + "message": "μη διαθέσιμη τιμή" + }, "primaryCurrencySetting": { "message": "Κύριο νόμισμα" }, @@ -3831,6 +4150,9 @@ "quoteRate": { "message": "Τιμή προσφοράς" }, + "rank": { + "message": "Κατάταξη" + }, "reAddAccounts": { "message": "προσθέστε εκ νέου τυχόν άλλους λογαριασμούς" }, @@ -4000,9 +4322,6 @@ "reset": { "message": "Επαναφορά" }, - "resetStates": { - "message": "Επαναφορά καταστάσεων" - }, "resetWallet": { "message": "Επαναφορά πορτοφολιού" }, @@ -4138,6 +4457,9 @@ "searchAccounts": { "message": "Αναζήτηση λογαριασμών" }, + "searchNfts": { + "message": "Αναζήτηση για NFT" + }, "searchTokens": { "message": "Αναζήτηση tokens" }, @@ -4182,13 +4504,10 @@ "seedPhraseIntroRecommendedButtonCopy": { "message": "Προστατέψτε το πορτοφόλι μου (συνιστάται)" }, - "seedPhraseIntroSidebarBulletFour": { - "message": "Γράψτε και αποθηκεύστε σε πολλά μυστικά μέρη" - }, "seedPhraseIntroSidebarBulletOne": { - "message": "Αποθήκευση σε ένα διαχειριστή κωδικών πρόσβασης" + "message": "Γράψτε και αποθηκεύστε σε πολλά μυστικά μέρη" }, - "seedPhraseIntroSidebarBulletThree": { + "seedPhraseIntroSidebarBulletTwo": { "message": "Αποθήκευση σε θυρίδα ασφαλείας" }, "seedPhraseIntroSidebarCopyOne": { @@ -4258,10 +4577,10 @@ "message": "Επιλέξτε token" }, "selectNFTPrivacyPreference": { - "message": "Ενεργοποιήστε την ανίχνευση των NFT στις Ρυθμίσεις" + "message": "Ενεργοποιήστε την ανίχνευση των NFT" }, "selectPathHelp": { - "message": "Αν δεν βλέπετε τους λογαριασμούς που περιμένατε, δοκιμάστε να αλλάξετε τη διαδρομή HD." + "message": "Αν δεν βλέπετε τους λογαριασμούς που περιμένετε, δοκιμάστε να αλλάξετε τη διαδρομή HD ή το τρέχον επιλεγμένο δίκτυο." }, "selectType": { "message": "Επιλέξτε Τύπο" @@ -4272,9 +4591,6 @@ "send": { "message": "Αποστολή" }, - "sendAToken": { - "message": "Στείλτε ένα token" - }, "sendBugReport": { "message": "Στείλτε μας μια αναφορά σφάλματος." }, @@ -4325,9 +4641,6 @@ "sepolia": { "message": "Δίκτυο δοκιμών Sepolia" }, - "serviceWorkerKeepAlive": { - "message": "Διατηρήστε το Service Worker" - }, "setAdvancedPrivacySettingsDetails": { "message": "Το MetaMask χρησιμοποιεί αυτές τις αξιόπιστες υπηρεσίες τρίτων για να ενισχύσει τη χρηστικότητα και την ασφάλεια των προϊόντων." }, @@ -4384,6 +4697,9 @@ "showIncomingTransactionsExplainer": { "message": "Αυτό βασίζεται σε διαφορετικά API τρίτων για κάθε δίκτυο, τα οποία εκθέτουν τη διεύθυνση Ethereum και τη διεύθυνση IP σας." }, + "showLess": { + "message": "Εμφάνιση λιγότερων" + }, "showMore": { "message": "Εμφάνιση περισσότερων" }, @@ -4414,9 +4730,6 @@ "signatureRequestGuidance": { "message": "Υπογράψτε αυτό το μήνυμα μόνο εάν κατανοείτε πλήρως το περιεχόμενο και εμπιστεύεστε τον ιστότοπο που το ζητάει." }, - "signatureRequestWarning": { - "message": "Η υπογραφή αυτού του μηνύματος μπορεί να είναι επικίνδυνη. Μπορεί να δώσετε τον πλήρη έλεγχο του λογαριασμού και των περιουσιακών σας στοιχείων στο άτομο που βρίσκεται στην άλλη άκρη αυτού του μηνύματος. Αυτό σημαίνει ότι μπορεί να αδειάσει τον λογαριασμό σας ανά πάσα στιγμή. Προχωρήστε με προσοχή. $1." - }, "signed": { "message": "Υπογράφηκε" }, @@ -4426,6 +4739,9 @@ "signing": { "message": "Υπογραφή" }, + "signingInWith": { + "message": "Σύνδεση με" + }, "simulationDetailsFailed": { "message": "Υπήρξε σφάλμα στη φόρτωση της εκτίμησής σας." }, @@ -4463,6 +4779,24 @@ "simulationsSettingSubHeader": { "message": "Εκτίμηση μεταβολών υπολοίπου" }, + "siweIssued": { + "message": "Εκδόθηκε" + }, + "siweNetwork": { + "message": "Δίκτυο" + }, + "siweRequestId": { + "message": "Αναγνωριστικό αιτήματος" + }, + "siweResources": { + "message": "Πόροι" + }, + "siweSignatureSimulationDetailInfo": { + "message": "Συνδέεστε σε έναν ιστότοπο και δεν προβλέπονται αλλαγές στον λογαριασμό σας." + }, + "siweURI": { + "message": "Διεύθυνση URL" + }, "skip": { "message": "Παράλειψη" }, @@ -4566,6 +4900,14 @@ "snapAccountsDescription": { "message": "Λογαριασμοί που ελέγχονται από Snaps τρίτων." }, + "snapConnectTo": { + "message": "Σύνδεση με $1", + "description": "$1 is the website URL or a Snap name. Used for Snaps pre-approved connections." + }, + "snapConnectionPermissionDescription": { + "message": "Επιτρέψτε στο $1 να συνδεθεί αυτόματα με το $2 χωρίς την έγκρισή σας.", + "description": "Used for Snap pre-approved connections. $1 is the Snap name, $2 is a website URL." + }, "snapConnectionWarning": { "message": "Το $1 θέλει να χρησιμοποιήσει $2", "description": "$2 is the snap and $1 is the dapp requesting connection to the snap." @@ -4577,6 +4919,9 @@ "snapDetailWebsite": { "message": "Ιστότοπος" }, + "snapHomeMenu": { + "message": "Αρχικό Μενού του Snap" + }, "snapInstallRequest": { "message": "Η εγκατάσταση του $1 του δίνει τις ακόλουθες άδειες.", "description": "$1 is the snap name." @@ -4699,6 +5044,9 @@ "source": { "message": "Πηγή" }, + "speed": { + "message": "Ταχύτητα" + }, "speedUp": { "message": "Επιτάχυνση" }, @@ -4733,6 +5081,9 @@ "spendLimitTooLarge": { "message": "Πολύ μεγάλο όριο δαπανών" }, + "spender": { + "message": "Διαθέτης" + }, "spendingCap": { "message": "Ανώτατο όριο δαπανών" }, @@ -4853,9 +5204,6 @@ "stateLogsDescription": { "message": "Τα αρχεία καταγραφής κατάστασης περιέχουν τις διευθύνσεις του δημόσιου λογαριασμού σας και τις συναλλαγές οι οποίες έχουν αποσταλεί." }, - "states": { - "message": "Καταστάσεις" - }, "status": { "message": "Κατάσταση" }, @@ -4960,6 +5308,13 @@ "submitted": { "message": "Υποβλήθηκε" }, + "suggestedBySnap": { + "message": "Προτείνεται από $1", + "description": "$1 is the snap name" + }, + "suggestedTokenName": { + "message": "Προτεινόμενο όνομα:" + }, "suggestedTokenSymbol": { "message": "Προτεινόμενο σύμβολο μετοχής:" }, @@ -5074,7 +5429,7 @@ "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." }, "swapFetchingQuotes": { - "message": "Λήψη προσφορών" + "message": "Λήψη προσφορών..." }, "swapFetchingQuotesErrorDescription": { "message": "Χμμμ... κάτι πήγε στραβά. Προσπαθήστε ξανά, ή αν τα σφάλματα επιμένουν, επικοινωνήστε με την υποστήριξη πελατών." @@ -5422,7 +5777,7 @@ "description": "$1 represents the account name, $2 represents the network name" }, "switchedTo": { - "message": "Έχετε αλλάξει σε" + "message": "Τώρα χρησιμοποιείτε το" }, "switchingNetworksCancelsPendingConfirmations": { "message": "Η αλλαγή δικτύων θα ακυρώσει όλες τις εκκρεμείς επιβεβαιώσεις" @@ -5470,6 +5825,10 @@ "thisCollection": { "message": "αυτή η συλλογή" }, + "threeMonthsAbbreviation": { + "message": "3Μ", + "description": "Shortened form of '3 months'" + }, "time": { "message": "Ώρα" }, @@ -5483,45 +5842,6 @@ "message": "Προς: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleEthSignBannerDescription": { - "message": "Κινδυνεύετε από επιθέσεις phishing. Προστατευτείτε απενεργοποιώντας το eth_sign." - }, - "toggleEthSignDescriptionField": { - "message": "Αν ενεργοποιήσετε αυτή τη ρύθμιση, ενδέχεται να λάβετε αιτήματα υπογραφής που δεν είναι αναγνώσιμα. Υπογράφοντας ένα μήνυμα που δεν καταλαβαίνετε, μπορεί να συμφωνείτε να παραχωρήσετε τα κεφάλαια και τα NFT σας." - }, - "toggleEthSignField": { - "message": "Αιτήματα eth_sign" - }, - "toggleEthSignModalBannerBoldText": { - "message": " μπορεί να σας εξαπατήσουν" - }, - "toggleEthSignModalBannerText": { - "message": "Αν σας ζητήθηκε να ενεργοποιήσετε αυτή τη ρύθμιση," - }, - "toggleEthSignModalCheckBox": { - "message": "Κατανοώ ότι μπορεί να χάσω όλα μου τα κεφάλαια και τα NFT αν ενεργοποιήσω τα αιτήματα eth_sign. " - }, - "toggleEthSignModalDescription": { - "message": "Η αποδοχή αιτημάτων eth_sign μπορεί να σας καταστήσει ευάλωτους σε επιθέσεις phishing. Να ελέγχετε πάντα τη διεύθυνση URL και να είστε προσεκτικοί όταν υπογράφετε μηνύματα που περιέχουν κώδικα." - }, - "toggleEthSignModalFormError": { - "message": "Το κείμενο είναι λανθασμένο" - }, - "toggleEthSignModalFormLabel": { - "message": "Πληκτρολογήστε «Υπογράφω μόνο ό,τι κατανοώ» για να συνεχίσετε" - }, - "toggleEthSignModalFormValidation": { - "message": "Υπογράφω μόνο ό,τι κατανοώ" - }, - "toggleEthSignModalTitle": { - "message": "Χρησιμοποιήστε το με δική σας ευθύνη" - }, - "toggleEthSignOff": { - "message": "ΑΝΕΝΕΡΓΟ (συνιστάται)" - }, - "toggleEthSignOn": { - "message": "ΕΝΕΡΓΟ (δεν συνιστάται)" - }, "toggleRequestQueueDescription": { "message": "Αυτό σας επιτρέπει να επιλέξετε ένα δίκτυο για κάθε ιστότοπο αντί για ένα μόνο επιλεγμένο δίκτυο για όλους τους ιστότοπους. Αυτή η λειτουργία θα σας αποτρέψει από το να αλλάζετε δίκτυα χειροκίνητα, το οποίο μπορεί να διαταράξει την εμπειρία του χρήστη σε ορισμένους ιστότοπους." }, @@ -5549,6 +5869,9 @@ "tokenContractAddress": { "message": "Διεύθυνση συμβολαίου του token" }, + "tokenDecimal": { + "message": "Δεκαδικά ψηφία του token" + }, "tokenDecimalFetchFailed": { "message": "Απαιτείται δεκαδικό token. Βρείτε το σε: $1" }, @@ -5565,7 +5888,7 @@ "message": "Αναγνωριστικό του token" }, "tokenList": { - "message": "Λίστες με token:" + "message": "Λίστες με tokens" }, "tokenScamSecurityRisk": { "message": "απάτες με token και κίνδυνοι ασφάλειας" @@ -5573,6 +5896,9 @@ "tokenShowUp": { "message": "Τα tokens σας ενδέχεται να μην εμφανιστούν αυτόματα στο πορτοφόλι σας." }, + "tokenStandard": { + "message": "Πρότυπο Token" + }, "tokenSymbol": { "message": "Σύμβολο του token" }, @@ -5583,6 +5909,9 @@ "message": "Βρέθηκαν $1 νέα tokens", "description": "$1 is the number of new tokens detected" }, + "tokensInCollection": { + "message": "Tokens στη συλλογή" + }, "tooltipApproveButton": { "message": "Κατανοώ" }, @@ -5598,6 +5927,9 @@ "total": { "message": "Σύνολο" }, + "totalVolume": { + "message": "Συνολικός όγκος" + }, "transaction": { "message": "συναλλαγή" }, @@ -5613,6 +5945,9 @@ "transactionCreated": { "message": "Η συναλλαγή δημιουργήθηκε με αξία $1 στις $2." }, + "transactionDataFunction": { + "message": "Λειτουργία" + }, "transactionDetailDappGasMoreInfo": { "message": "Προτεινόμενος ιστότοπος" }, @@ -5703,6 +6038,10 @@ "transferFrom": { "message": "Μεταφορά από" }, + "trillionAbbreviation": { + "message": "Τ", + "description": "Shortened form of 'trillion'" + }, "troubleConnectingToLedgerU2FOnFirefox": { "message": "Έχουμε πρόβλημα με τη σύνδεσή σας στο Ledger. $1", "description": "$1 is a link to the wallet connection guide;" @@ -5781,6 +6120,9 @@ "message": "U2F", "description": "A name on an API for the browser to interact with devices that support the U2F protocol. On some browsers we use it to connect MetaMask to Ledger devices." }, + "unMatchedChain": { + "message": "Σύμφωνα με τα αρχεία μας, αυτή η διεύθυνση URL δεν ταιριάζει με γνωστό πάροχο για αυτό το αναγνωριστικό αλυσίδας." + }, "unapproved": { "message": "Μη εγκεκριμένο" }, @@ -5832,12 +6174,21 @@ "update": { "message": "Ενημέρωση" }, + "updateOrEditNetworkInformations": { + "message": "Ενημερώστε τα στοιχεία σας ή" + }, "updateRequest": { "message": "Αίτημα ενημέρωσης" }, "updatedWithDate": { "message": "Ενημερώθηκε $1" }, + "uploadDropFile": { + "message": "Αφήστε το αρχείο σας εδώ" + }, + "uploadFile": { + "message": "Μεταφόρτωση αρχείου" + }, "urlErrorMsg": { "message": "Οι διευθύνσεις URL απαιτούν το κατάλληλο πρόθεμα HTTP/HTTPS." }, @@ -6053,6 +6404,12 @@ "whatsThis": { "message": "Τι είναι αυτό;" }, + "wrongChainId": { + "message": "Αυτό το αναγνωριστικό αλυσίδας δεν ταιριάζει με το όνομα του δικτύου." + }, + "wrongNetworkName": { + "message": "Σύμφωνα με τα αρχεία μας, το όνομα του δικτύου ενδέχεται να μην αντιστοιχεί με αυτό το αναγνωριστικό αλυσίδας." + }, "xOfYPending": { "message": "$1 από $2 σε εκκρεμότητα", "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" @@ -6076,8 +6433,11 @@ "yourAccounts": { "message": "Οι λογαριασμοί σας" }, - "yourFundsMayBeAtRisk": { - "message": "Τα κεφάλαιά σας μπορεί να κινδυνεύουν" + "yourActivity": { + "message": "Η δραστηριότητά σας" + }, + "yourBalance": { + "message": "Το υπόλοιπό σας" }, "yourNFTmayBeAtRisk": { "message": "Τα NFT μπορεί να κινδυνεύουν" diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index d5a3ef224030..75ff076121df 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -42,7 +42,7 @@ "message": "Connect your QR hardware wallet" }, "QRHardwareWalletSteps2Description": { - "message": "Ngrave (coming soon)" + "message": "Ngrave Zero" }, "SIWEAddressInvalid": { "message": "The address in the sign-in request does not match the address of the account you are using to sign in." @@ -195,6 +195,9 @@ "addAccount": { "message": "Add account" }, + "addAccountToMetaMask": { + "message": "Add account to MetaMask" + }, "addAcquiredTokens": { "message": "Add the tokens you've acquired using MetaMask" }, @@ -320,6 +323,9 @@ "addUrl": { "message": "Add URL" }, + "addingAccount": { + "message": "Adding account" + }, "addingCustomNetwork": { "message": "Adding Network" }, @@ -544,9 +550,6 @@ "message": "$1 and $2", "description": "$1 is the first item, $2 is the second item. Used in Snap Install Warning modal." }, - "announcements": { - "message": "Announcements" - }, "appDescription": { "message": "An Ethereum Wallet in your Browser", "description": "The description of the application" @@ -632,6 +635,9 @@ "attemptToCancelSwapForFree": { "message": "Attempt to cancel swap for free" }, + "attributes": { + "message": "Attributes" + }, "attributions": { "message": "Attributions" }, @@ -839,6 +845,9 @@ "blockies": { "message": "Blockies" }, + "boughtFor": { + "message": "Bought for" + }, "bridge": { "message": "Bridge" }, @@ -960,6 +969,9 @@ "coingecko": { "message": "CoinGecko" }, + "collectionName": { + "message": "Collection name" + }, "comboNoOptions": { "message": "No options found", "description": "Default text shown in the combo field dropdown if no options." @@ -1265,6 +1277,9 @@ "createSnapAccountTitle": { "message": "Create account" }, + "creatorAddress": { + "message": "Creator address" + }, "crossChainSwapsLink": { "message": "Swap across networks with MetaMask Portfolio" }, @@ -1449,6 +1464,12 @@ "dataHex": { "message": "Hex" }, + "dataUnavailable": { + "message": "data unavailable" + }, + "dateCreated": { + "message": "Date created" + }, "dcent": { "message": "D'Cent" }, @@ -1528,24 +1549,6 @@ "developerOptions": { "message": "Developer Options" }, - "developerOptionsEnableConfirmationsRedesignDescription": { - "message": "Enables or disables the confirmations redesign feature currently in development" - }, - "developerOptionsEnableConfirmationsRedesignTitle": { - "message": "Confirmations Redesign" - }, - "developerOptionsNetworkMenuRedesignDescription": { - "message": "Toggles the new design of the Networks menu" - }, - "developerOptionsNetworkMenuRedesignTitle": { - "message": "Network Menu Redesign" - }, - "developerOptionsResetStatesAnnouncementsDescription": { - "message": "Resets isShown boolean to false for all announcements. Announcements are the notifications shown in the What's New popup modal." - }, - "developerOptionsResetStatesOnboarding": { - "message": "Resets various states related to onboarding and redirects to the \"Secure Your Wallet\" onboarding page." - }, "developerOptionsSentryButtonGenerateBackgroundError": { "message": "Generate Background Error" }, @@ -1564,9 +1567,6 @@ "developerOptionsSentryDescriptionGenerateUIError": { "message": "Generate an unhandled $1 in this window." }, - "developerOptionsServiceWorkerKeepAlive": { - "message": "Results in a timestamp being continuously saved to session.storage" - }, "disabledGasOptionToolTipMessage": { "message": "“$1” is disabled because it does not meet the minimum of a 10% increase from the original gas fee.", "description": "$1 is gas estimate type which can be market or aggressive" @@ -1743,6 +1743,9 @@ "editGasTooLow": { "message": "Unknown processing time" }, + "editNetworkLink": { + "message": "edit the original network" + }, "editNonceField": { "message": "Edit nonce" }, @@ -1903,6 +1906,15 @@ "etherscanViewOn": { "message": "View on Etherscan" }, + "existingChainId": { + "message": "The information you have entered is associated with an existing chain ID." + }, + "existingRequestsBannerAlertDesc": { + "message": "To view and confirm your most recent request, you'll need to approve or reject existing requests first." + }, + "existingRpcUrl": { + "message": "This URL is associated with another chain ID." + }, "expandView": { "message": "Expand view" }, @@ -1957,6 +1969,9 @@ "message": "File import not working? Click here!", "description": "Helps user import their account from a JSON file" }, + "findTheRightChainId": { + "message": "Find the right one on:" + }, "flaskWelcomeUninstall": { "message": "you should uninstall this extension", "description": "This request is shown on the Flask Welcome screen. It is intended for non-developers, and will be bolded." @@ -2207,6 +2222,12 @@ "highLowercase": { "message": "high" }, + "highestCurrentBid": { + "message": "Highest current bid" + }, + "highestFloorPrice": { + "message": "Highest floor price" + }, "history": { "message": "History" }, @@ -2561,9 +2582,6 @@ "lastConnected": { "message": "Last connected" }, - "lastPriceSold": { - "message": "Last price sold" - }, "lastSold": { "message": "Last sold" }, @@ -2920,6 +2938,10 @@ "message": "Choose a nickname...", "description": "Placeholder text for name input field in name component modal." }, + "nativeNetworkPermissionRequestDescription": { + "message": "$1 is asking for your approval to:", + "description": "$1 represents dapp name" + }, "nativePermissionRequestDescription": { "message": "Do you want this site to do the following?", "description": "Description below header used on Permission Connect screen for native permissions." @@ -3048,12 +3070,20 @@ "message": "We can't connect to $1", "description": "$1 represents the network name" }, + "networkSwitchMessage": { + "message": "Network switched to $1", + "description": "$1 represents the network name" + }, "networkURL": { "message": "Network URL" }, "networkURLDefinition": { "message": "The URL used to access this network." }, + "networkUrlErrorWarning": { + "message": "Attackers sometimes mimic sites by making small changes to the site address. Make sure you're interacting with the intended site before you continue. Punycode version: $1", + "description": "$1 replaced by RPC URL for network" + }, "networks": { "message": "Networks" }, @@ -3217,12 +3247,15 @@ "nonceField": { "message": "Customize transaction nonce" }, - "nonceFieldDescription": { - "message": "Turn this on to change the nonce (transaction number) on confirmation screens. This is an advanced feature, use cautiously." + "nonceFieldDesc": { + "message": "Turn this on to change the nonce (transaction number) when sending assets. This is an advanced feature, use cautiously." }, "nonceFieldHeading": { "message": "Custom nonce" }, + "none": { + "message": "None" + }, "notBusy": { "message": "Not busy" }, @@ -3420,6 +3453,9 @@ "numberOfNewTokensDetectedSingular": { "message": "1 new token found in this account" }, + "numberOfTokens": { + "message": "Number of tokens" + }, "ofTextNofM": { "message": "of" }, @@ -3466,9 +3502,6 @@ "onboardedMetametricsTitle": { "message": "Help us enhance your experience" }, - "onboarding": { - "message": "Onboarding" - }, "onboardingAdvancedPrivacyIPFSDescription": { "message": "The IPFS gateway makes it possible to access and view data hosted by third parties. You can add a custom IPFS gateway or continue using the default." }, @@ -3505,9 +3538,6 @@ "onboardingMetametricsDescription2": { "message": "When we gather metrics, it will always be..." }, - "onboardingMetametricsDisagree": { - "message": "No thanks" - }, "onboardingMetametricsInfuraTerms": { "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`" @@ -3804,6 +3834,14 @@ "message": "Let $1 access your preferred language from your MetaMask settings. This can be used to localize and display $1's content using your language.", "description": "An extended description for the `snap_getLocale` permission. $1 is the snap name." }, + "permission_getPreferences": { + "message": "See information like your preferred language and fiat currency.", + "description": "The description for the `snap_getPreferences` permission" + }, + "permission_getPreferencesDescription": { + "message": "Let $1 access information like your preferred language and fiat currency in your MetaMask settings. This helps $1 display content tailored to your preferences. ", + "description": "An extended description for the `snap_getPreferences` permission. $1 is the snap name." + }, "permission_homePage": { "message": "Display a custom screen", "description": "The description for the `endowment:page-home` permission" @@ -3933,7 +3971,7 @@ "description": "The description for the `snap_getBip32PublicKey` permission. $1 is a name for the derivation path, e.g., 'Ethereum accounts'." }, "permission_walletSwitchEthereumChain": { - "message": "Switch to and use the following network", + "message": "Use your enabled networks", "description": "The label for the `wallet_switchEthereumChain` permission" }, "permission_webAssembly": { @@ -4010,6 +4048,12 @@ "prev": { "message": "Prev" }, + "price": { + "message": "Price" + }, + "priceUnavailable": { + "message": "price unavailable" + }, "primaryCurrencySetting": { "message": "Primary currency" }, @@ -4162,6 +4206,9 @@ "quoteRate": { "message": "Quote rate" }, + "rank": { + "message": "Rank" + }, "reAddAccounts": { "message": "re-add any other accounts" }, @@ -4216,6 +4263,12 @@ "redesignedConfirmationsToggleDescription": { "message": "Turn this on to see signature requests in an enhanced format." }, + "redesignedTransactionsEnabledToggle": { + "message": "Improved transaction requests" + }, + "redesignedTransactionsToggleDescription": { + "message": "Turn this on to see transactions requests in an enhanced format." + }, "refreshList": { "message": "Refresh list" }, @@ -4331,9 +4384,6 @@ "reset": { "message": "Reset" }, - "resetStates": { - "message": "Reset States" - }, "resetWallet": { "message": "Reset wallet" }, @@ -4419,6 +4469,9 @@ "reviewAlerts": { "message": "Review alerts" }, + "reviewPermissions": { + "message": "Review permissions" + }, "revokeAllTokensTitle": { "message": "Revoke permission to access and transfer all of your $1?", "description": "$1 is the symbol of the token for which the user is revoking approval" @@ -4501,6 +4554,10 @@ "message": "Powered by $1", "description": "The security provider that is providing data" }, + "seeAllPermissions": { + "message": "See all permissions", + "description": "Used for revealing more content (e.g. permission list, etc.)" + }, "seeDetails": { "message": "See details" }, @@ -4516,13 +4573,10 @@ "seedPhraseIntroRecommendedButtonCopy": { "message": "Secure my wallet (recommended)" }, - "seedPhraseIntroSidebarBulletFour": { - "message": "Write down and store in multiple secret places" - }, "seedPhraseIntroSidebarBulletOne": { - "message": "Save in a password manager" + "message": "Write down and store in multiple secret places" }, - "seedPhraseIntroSidebarBulletThree": { + "seedPhraseIntroSidebarBulletTwo": { "message": "Store in a safe deposit box" }, "seedPhraseIntroSidebarCopyOne": { @@ -4606,9 +4660,6 @@ "send": { "message": "Send" }, - "sendAToken": { - "message": "Send a token" - }, "sendBugReport": { "message": "Send us a bug report." }, @@ -4659,9 +4710,6 @@ "sepolia": { "message": "Sepolia test network" }, - "serviceWorkerKeepAlive": { - "message": "Service Worker Keep Alive" - }, "setAdvancedPrivacySettingsDetails": { "message": "MetaMask uses these trusted third-party services to enhance product usability and safety." }, @@ -4718,6 +4766,9 @@ "showIncomingTransactionsExplainer": { "message": "This relies on different third-party APIs for each network, which expose your Ethereum address and your IP address." }, + "showLess": { + "message": "Show less" + }, "showMore": { "message": "Show more" }, @@ -4748,9 +4799,6 @@ "signatureRequestGuidance": { "message": "Only sign this message if you fully understand the content and trust the requesting site." }, - "signatureRequestWarning": { - "message": "Signing this message could be dangerous. You may be giving total control of your account and assets to the party on the other end of this message. That means they could drain your account at any time. Proceed with caution. $1." - }, "signed": { "message": "Signed" }, @@ -4940,6 +4988,9 @@ "snapDetailWebsite": { "message": "Website" }, + "snapHomeMenu": { + "message": "Snap Home Menu" + }, "snapInstallRequest": { "message": "Installing $1 gives it the following permissions.", "description": "$1 is the snap name." @@ -5222,9 +5273,6 @@ "stateLogsDescription": { "message": "State logs contain your public account addresses and sent transactions." }, - "states": { - "message": "States" - }, "status": { "message": "Status" }, @@ -5863,45 +5911,6 @@ "message": "To: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleEthSignBannerDescription": { - "message": "You’re at risk for phishing attacks. Protect yourself by turning off eth_sign." - }, - "toggleEthSignDescriptionField": { - "message": "If you enable this setting, you might get signature requests that aren’t readable. By signing a message you don't understand, you could be agreeing to give away your funds and NFTs." - }, - "toggleEthSignField": { - "message": "Eth_sign requests" - }, - "toggleEthSignModalBannerBoldText": { - "message": " you might be getting scammed" - }, - "toggleEthSignModalBannerText": { - "message": "If you've been asked to turn this setting on," - }, - "toggleEthSignModalCheckBox": { - "message": "I understand that I can lose all of my funds and NFTs if I enable eth_sign requests. " - }, - "toggleEthSignModalDescription": { - "message": "Allowing eth_sign requests can make you vulnerable to phishing attacks. Always review the URL and be careful when signing messages that contain code." - }, - "toggleEthSignModalFormError": { - "message": "The text is incorrect" - }, - "toggleEthSignModalFormLabel": { - "message": "Enter “I only sign what I understand” to continue" - }, - "toggleEthSignModalFormValidation": { - "message": "I only sign what I understand" - }, - "toggleEthSignModalTitle": { - "message": "Use at your own risk" - }, - "toggleEthSignOff": { - "message": "OFF (Recommended)" - }, - "toggleEthSignOn": { - "message": "ON (Not recommended)" - }, "toggleRequestQueueDescription": { "message": "This allows you to select a network for each site instead of a single selected network for all sites. This feature will prevent you from switching networks manually, which may break your user experience on certain sites." }, @@ -5956,6 +5965,9 @@ "tokenShowUp": { "message": "Your tokens may not automatically show up in your wallet. " }, + "tokenStandard": { + "message": "Token standard" + }, "tokenSymbol": { "message": "Token symbol" }, @@ -5966,6 +5978,9 @@ "message": "$1 new tokens found", "description": "$1 is the number of new tokens detected" }, + "tokensInCollection": { + "message": "Tokens in collection" + }, "tooltipApproveButton": { "message": "I understand" }, @@ -6174,6 +6189,9 @@ "message": "U2F", "description": "A name on an API for the browser to interact with devices that support the U2F protocol. On some browsers we use it to connect MetaMask to Ledger devices." }, + "unMatchedChain": { + "message": "According to our records, this URL does not match a known provider for this chain ID." + }, "unapproved": { "message": "Unapproved" }, @@ -6225,6 +6243,9 @@ "update": { "message": "Update" }, + "updateOrEditNetworkInformations": { + "message": "Update your information or" + }, "updateRequest": { "message": "Update request" }, @@ -6452,6 +6473,9 @@ "whatsThis": { "message": "What's this?" }, + "wrongChainId": { + "message": "This chain ID doesn’t match the network name." + }, "wrongNetworkName": { "message": "According to our records, the network name may not correctly match this chain ID." }, @@ -6484,9 +6508,6 @@ "yourBalance": { "message": "Your balance" }, - "yourFundsMayBeAtRisk": { - "message": "Your funds may be at risk" - }, "yourNFTmayBeAtRisk": { "message": "Your NFT may be at risk" }, diff --git a/app/_locales/en_GB/messages.json b/app/_locales/en_GB/messages.json index 1787f059bea9..cbfe48f3d8ef 100644 --- a/app/_locales/en_GB/messages.json +++ b/app/_locales/en_GB/messages.json @@ -556,9 +556,6 @@ "message": "$1 and $2", "description": "$1 is the first item, $2 is the second item. Used in Snap Install Warning modal." }, - "announcements": { - "message": "Announcements" - }, "appDescription": { "message": "An Ethereum Wallet in your Browser", "description": "The description of the application" @@ -1555,30 +1552,6 @@ "details": { "message": "Details" }, - "developerOptions": { - "message": "Developer Options" - }, - "developerOptionsEnableConfirmationsRedesignDescription": { - "message": "Enables or disables the confirmations redesign feature currently in development" - }, - "developerOptionsEnableConfirmationsRedesignTitle": { - "message": "Confirmations Redesign" - }, - "developerOptionsNetworkMenuRedesignDescription": { - "message": "Toggles the new design of the Networks menu" - }, - "developerOptionsNetworkMenuRedesignTitle": { - "message": "Network Menu Redesign" - }, - "developerOptionsResetStatesAnnouncementsDescription": { - "message": "Resets isShown boolean to false for all announcements. Announcements are the notifications shown in the What's New popup modal." - }, - "developerOptionsResetStatesOnboarding": { - "message": "Resets various states related to onboarding and redirects to the \"Secure Your Wallet\" onboarding page." - }, - "developerOptionsServiceWorkerKeepAlive": { - "message": "Results in a timestamp being continuously saved to session.storage" - }, "disabledGasOptionToolTipMessage": { "message": "“$1” is disabled because it does not meet the minimum of a 10% increase from the original gas fee.", "description": "$1 is gas estimate type which can be market or aggressive" @@ -3526,9 +3499,6 @@ "onboardedMetametricsTitle": { "message": "Help us enhance your experience" }, - "onboarding": { - "message": "Onboarding" - }, "onboardingAdvancedPrivacyIPFSDescription": { "message": "The IPFS gateway makes it possible to access and view data hosted by third parties. You can add a custom IPFS gateway or continue using the default." }, @@ -4397,9 +4367,6 @@ "reset": { "message": "Reset" }, - "resetStates": { - "message": "Reset States" - }, "resetWallet": { "message": "Reset wallet" }, @@ -4722,9 +4689,6 @@ "sepolia": { "message": "Sepolia test network" }, - "serviceWorkerKeepAlive": { - "message": "Service Worker Keep Alive" - }, "setAdvancedPrivacySettingsDetails": { "message": "MetaMask uses these trusted third-party services to enhance product usability and safety." }, @@ -4814,9 +4778,6 @@ "signatureRequestGuidance": { "message": "Only sign this message if you fully understand the content and trust the requesting site." }, - "signatureRequestWarning": { - "message": "Signing this message could be dangerous. You may be giving total control of your account and assets to the party on the other end of this message. That means they could drain your account at any time. Proceed with caution. $1." - }, "signed": { "message": "Signed" }, @@ -5291,9 +5252,6 @@ "stateLogsDescription": { "message": "State logs contain your public account addresses and sent transactions." }, - "states": { - "message": "States" - }, "status": { "message": "Status" }, @@ -6568,9 +6526,6 @@ "yourBalance": { "message": "Your balance" }, - "yourFundsMayBeAtRisk": { - "message": "Your funds may be at risk" - }, "yourNFTmayBeAtRisk": { "message": "Your NFT may be at risk" }, diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index 4866e28f5bc7..19031ace0ed8 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -42,7 +42,7 @@ "message": "Conecte su monedero físico QR" }, "QRHardwareWalletSteps2Description": { - "message": "Ngrave (próximamente)" + "message": "Ngrave Zero" }, "SIWEAddressInvalid": { "message": "La dirección de la solicitud de inicio de sesión no coincide con la dirección de la cuenta que está utilizando para iniciar sesión." @@ -162,6 +162,9 @@ "accountSelectionRequired": { "message": "Debe seleccionar una cuenta." }, + "accountTypeNotSupported": { + "message": "Tipo de cuenta no admitido" + }, "accounts": { "message": "Cuentas" }, @@ -277,6 +280,12 @@ "addNewAccount": { "message": "Añadir una cuenta nueva de Ethereum" }, + "addNewBitcoinAccount": { + "message": "Añadir una nueva cuenta de Bitcoin (Beta)" + }, + "addNewBitcoinTestnetAccount": { + "message": "Añadir una nueva cuenta de Bitcoin (Testnet)" + }, "addNewToken": { "message": "Agregar nuevo token" }, @@ -286,6 +295,9 @@ "addNfts": { "message": "Añadir NFT" }, + "addRpcUrl": { + "message": "Agregar URL RPC" + }, "addSnapAccountToggle": { "message": "Activar \"Añadir una cuenta Snap (Beta)\"" }, @@ -305,12 +317,21 @@ "message": "¿No encuentra un token? Puede agregar cualquier token si copia su dirección. Puede encontrar la dirección de contrato del token en $1", "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" }, + "addUrl": { + "message": "Agregar URL" + }, "addingCustomNetwork": { "message": "Agregando red" }, "addingTokens": { "message": "Agregando tokens" }, + "additionalNetworks": { + "message": "Redes adicionales" + }, + "additionalRpcUrl": { + "message": "URL RPC adicional" + }, "address": { "message": "Dirección" }, @@ -326,6 +347,18 @@ "advancedConfiguration": { "message": "Configuración avanzada" }, + "advancedDetailsDataDesc": { + "message": "Datos" + }, + "advancedDetailsHexDesc": { + "message": "Hex" + }, + "advancedDetailsNonceDesc": { + "message": "Nonce" + }, + "advancedDetailsNonceTooltip": { + "message": "Este es el número de transacción de una cuenta. El nonce para la primera transacción es 0 y aumenta en orden secuencial." + }, "advancedGasFeeDefaultOptIn": { "message": "Guarde estos valores como mis valores por defecto para la red de $1.", "description": "$1 is the current network name." @@ -349,6 +382,18 @@ "alert": { "message": "Alerta" }, + "alertActionBuy": { + "message": "Comprar ETH" + }, + "alertActionUpdateGas": { + "message": "Actualizar el límite de gas" + }, + "alertActionUpdateGasFee": { + "message": "Actualizar tarifa" + }, + "alertActionUpdateGasFeeLevel": { + "message": "Actualizar opciones de gas" + }, "alertBannerMultipleAlertsDescription": { "message": "Si aprueba esta solicitud, un tercero conocido por estafas podría quedarse con todos sus activos." }, @@ -358,6 +403,36 @@ "alertDisableTooltip": { "message": "Esto se puede modificar en \"Configuración > Alertas\"" }, + "alertMessageGasEstimateFailed": { + "message": "No podemos proporcionar una tarifa exacta y esta estimación podría ser alta. Le sugerimos que ingrese un límite de gas personalizado, pero existe el riesgo de que la transacción aún falle." + }, + "alertMessageGasFeeLow": { + "message": "Al elegir una tarifa baja, tenga en cuenta que las transacciones serán más lentas y los tiempos de espera más largos. Para transacciones más rápidas, elija las opciones de tarifa de mercado o agresiva." + }, + "alertMessageGasTooLow": { + "message": "Para continuar con esta transacción, deberá aumentar el límite de gas a 21000 o más." + }, + "alertMessageInsufficientBalance": { + "message": "No tiene suficiente ETH en su cuenta para pagar las tarifas de transacción." + }, + "alertMessageNetworkBusy": { + "message": "Los precios del gas son altos y las estimaciones son menos precisas." + }, + "alertMessageNoGasPrice": { + "message": "No podemos seguir adelante con esta transacción hasta que actualice manualmente la tarifa." + }, + "alertMessagePendingTransactions": { + "message": "Esta transacción no se realizará hasta que se complete una transacción anterior. Aprenda cómo cancelar o acelerar una transacción." + }, + "alertMessageSignInDomainMismatch": { + "message": "El sitio que realiza la solicitud no es el sitio en el que está iniciando sesión. Esto podría ser un intento de robar sus credenciales de inicio de sesión." + }, + "alertMessageSignInWrongAccount": { + "message": "Este sitio le pide que inicie sesión con la cuenta incorrecta." + }, + "alertMessageSigningOrSubmitting": { + "message": "Esta transacción solo se realizará una vez que se complete la transacción anterior." + }, "alertModalAcknowledge": { "message": "Soy consciente del riesgo y aun así deseo continuar" }, @@ -367,6 +442,33 @@ "alertModalReviewAllAlerts": { "message": "Revisar todas las alertas" }, + "alertReasonGasEstimateFailed": { + "message": "Tarifa inexacta" + }, + "alertReasonGasFeeLow": { + "message": "Velocidad baja" + }, + "alertReasonGasTooLow": { + "message": "Límite de gas bajo" + }, + "alertReasonInsufficientBalance": { + "message": "Fondos insuficientes" + }, + "alertReasonNetworkBusy": { + "message": "La red está ocupada" + }, + "alertReasonNoGasPrice": { + "message": "Estimación de tarifa no disponible" + }, + "alertReasonPendingTransactions": { + "message": "Transacción pendiente" + }, + "alertReasonSignIn": { + "message": "Solicitud de inicio de sesión sospechosa" + }, + "alertReasonWrongAccount": { + "message": "Cuenta incorrecta" + }, "alertSettingsUnconnectedAccount": { "message": "Explorando un sitio web con una cuenta no conectada seleccionada" }, @@ -398,6 +500,12 @@ "allPermissions": { "message": "Todos los permisos" }, + "allTimeHigh": { + "message": "Punto más alto" + }, + "allTimeLow": { + "message": "Punto más bajo" + }, "allYourNFTsOf": { "message": "Todos sus NFT de $1", "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" @@ -436,9 +544,6 @@ "message": "$1 y $2", "description": "$1 is the first item, $2 is the second item. Used in Snap Install Warning modal." }, - "announcements": { - "message": "Anuncios" - }, "appDescription": { "message": "Un monedero de Ethereum en el explorador", "description": "The description of the application" @@ -516,7 +621,7 @@ "message": "Opciones de activos" }, "attemptSendingAssets": { - "message": "Si intenta enviar activos directamente de una red a otra, esto puede provocar la pérdida permanente de activos. Asegúrese de utilizar un puente." + "message": "Puede perder sus activos si intenta enviarlos desde otra red. Transfiera fondos de forma segura entre redes mediante el uso de un puente." }, "attemptSendingAssetsWithPortfolio": { "message": "Puede perder sus activos si intenta enviarlos desde otra red. Transfiera fondos de forma segura entre redes usando un puente, como $1" @@ -524,9 +629,15 @@ "attemptToCancelSwapForFree": { "message": "Intente cancelar el intercambio de forma gratuita" }, + "attributes": { + "message": "Atributos" + }, "attributions": { "message": "Atribuciones" }, + "auroraRpcDeprecationMessage": { + "message": "La URL de RPC de Infura ya no es compatible con Aurora." + }, "authorizedPermissions": { "message": "Ha autorizado los siguientes permisos" }, @@ -591,6 +702,10 @@ "basicConfigurationBannerTitle": { "message": "La funcionalidad básica está desactivada" }, + "basicConfigurationDescription": { + "message": "MetaMask ofrece funciones básicas como los detalles del token y la configuración de gas mediante servicios de Internet. Al utilizar los servicios de Internet, su dirección IP es compartida, en este caso con MetaMask. Es lo mismo que cuando visita cualquier página web. MetaMask utiliza estos datos temporalmente y nunca los vende. Puede utilizar una VPN o desactivar estos servicios, pero esto podría afectar su experiencia con MetaMask. Para saber más, consulte nuestra $1.", + "description": "$1 is to be replaced by the message for privacyMsg, and will link to https://consensys.io/privacy-policy" + }, "basicConfigurationLabel": { "message": "Funcionalidad básica" }, @@ -634,6 +749,29 @@ "betaWalletCreationSuccessReminder2": { "message": "MetaMask beta nunca le pedirá su frase secreta de recuperación." }, + "billionAbbreviation": { + "message": "mm", + "description": "Shortened form of 'billion'" + }, + "bitcoinActivityNotSupported": { + "message": "La actividad de Bitcoin no es compatible" + }, + "bitcoinSupportSectionTitle": { + "message": "Bitcoin" + }, + "bitcoinSupportToggleDescription": { + "message": "Activar esta función le dará la opción de añadir una cuenta de Bitcoin a su extensión MetaMask derivada de su frase secreta de recuperación existente. Esta es una característica Beta experimental, por lo que debe utilizarla bajo su propio riesgo. Para darnos su opinión sobre esta nueva experiencia Bitcoin, llene este $1.", + "description": "$1 is the link to a product feedback form" + }, + "bitcoinSupportToggleTitle": { + "message": "Activar \"Añadir una nueva cuenta Bitcoin (Beta)\"" + }, + "bitcoinTestnetSupportToggleDescription": { + "message": "La activación de esta función le dará la opción de añadir una cuenta de Bitcoin para la red de prueba." + }, + "bitcoinTestnetSupportToggleTitle": { + "message": "Activar \"Añadir una nueva cuenta Bitcoin (Testnet)\"" + }, "blockExplorerAccountAction": { "message": "Cuenta", "description": "This is used with viewOnEtherscan and viewInExplorer e.g View Account in Explorer" @@ -659,6 +797,9 @@ "blockaid": { "message": "Blockaid" }, + "blockaidAlertInfo": { + "message": "No le recomendamos que siga adelante con esta solicitud." + }, "blockaidDescriptionApproveFarming": { "message": "Si aprueba esta solicitud, un tercero conocido por realizar estafas podría tomar todos sus activos." }, @@ -666,7 +807,7 @@ "message": "Si aprueba esta solicitud, alguien puede robar sus activos enlistados en Blur." }, "blockaidDescriptionErrored": { - "message": "Debido a un error, el proveedor de seguridad no verificó esta solicitud. Proceda con precaución." + "message": "Debido a un error, no pudimos comprobar si hay alertas de seguridad. Continúe solo si confía en todas las direcciones involucradas." }, "blockaidDescriptionMaliciousDomain": { "message": "Está interactuando con un dominio malicioso. Si aprueba esta solicitud, podría perder sus activos." @@ -680,6 +821,9 @@ "blockaidDescriptionTransferFarming": { "message": "Si aprueba esta solicitud, un tercero conocido por estafas tomará todos sus activos." }, + "blockaidDescriptionWarning": { + "message": "Podría tratarse de una solicitud engañosa. Continúe solamente si confía en todas las direcciones implicadas." + }, "blockaidMessage": { "message": "Preservación de la privacidad: no se comparten datos con terceros. Disponible en Arbitrum, Avalanche, BNB Chain, la red principal de Ethereum, Linea, Optimism, Polygon, Base y Sepolia." }, @@ -687,7 +831,7 @@ "message": "Esta es una solicitud engañosa" }, "blockaidTitleMayNotBeSafe": { - "message": "La solicitud puede no ser segura" + "message": "Sea cuidadoso" }, "blockaidTitleSuspicious": { "message": "Esta es una solicitud sospechosa" @@ -695,6 +839,9 @@ "blockies": { "message": "Blockies" }, + "boughtFor": { + "message": "Comprado para" + }, "bridge": { "message": "Puente" }, @@ -779,6 +926,9 @@ "chromeRequiredForHardwareWallets": { "message": "Debe usar MetaMask en Google Chrome para poder conectarse a su monedero físico." }, + "circulatingSupply": { + "message": "Suministro circulante" + }, "clear": { "message": "Borrar" }, @@ -799,7 +949,7 @@ "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" }, "clickToManuallyAdd": { - "message": "Haga clic aquí para agregar manualmente los tokens." + "message": "Siempre puede agregar tókenes manualmente." }, "close": { "message": "Cerrar" @@ -813,6 +963,9 @@ "coingecko": { "message": "CoinGecko" }, + "collectionName": { + "message": "Nombre de la colección" + }, "comboNoOptions": { "message": "No se encontraron opciones", "description": "Default text shown in the combo field dropdown if no options." @@ -838,6 +991,9 @@ "confirmAlertModalAcknowledgeMultiple": { "message": "Soy consciente de las alertas y aun así deseo continuar" }, + "confirmAlertModalAcknowledgeSingle": { + "message": "Soy consciente de la alerta y aun así deseo continuar" + }, "confirmAlertModalDetails": { "message": "Si inicia sesión, un tercero conocido por estafas podría quedarse con todos sus activos. Revise las alertas antes de continuar." }, @@ -853,18 +1009,42 @@ "confirmConnectionTitle": { "message": "Confirmar conexión a $1" }, + "confirmDeletion": { + "message": "Confirmar la eliminación" + }, + "confirmFieldPaymaster": { + "message": "Tarifa pagada por" + }, + "confirmFieldTooltipPaymaster": { + "message": "La tarifa de esta transacción la pagará el contrato inteligente del pagador." + }, "confirmPassword": { "message": "Confirmar contraseña" }, "confirmRecoveryPhrase": { "message": "Confirmar frase secreta de recuperación" }, + "confirmRpcUrlDeletionMessage": { + "message": "¿Está seguro de que desea eliminar la URL RPC? Su información no se guardará para esta red." + }, "confirmTitleDescContractInteractionTransaction": { "message": "Solo confirme esta transacción si entiende completamente el contenido y confía en el sitio solicitante." }, + "confirmTitleDescPermitSignature": { + "message": "Este sitio solicita permiso para gastar sus tokens." + }, + "confirmTitleDescSIWESignature": { + "message": "Un sitio quiere que inicie sesión para demostrar que es el propietario de esta cuenta." + }, "confirmTitleDescSignature": { "message": "Solo confirme este mensaje si aprueba el contenido y confía en el sitio solicitante." }, + "confirmTitlePermitSignature": { + "message": "Solicitud de límite de gasto" + }, + "confirmTitleSIWESignature": { + "message": "Solicitud de inicio de sesión" + }, "confirmTitleSignature": { "message": "Solicitud de firma" }, @@ -958,7 +1138,7 @@ "message": "Conectado con" }, "connecting": { - "message": "Estableciendo conexión…" + "message": "Conectando" }, "connectingTo": { "message": "Estableciendo conexión a $1" @@ -1091,6 +1271,9 @@ "createSnapAccountTitle": { "message": "Crear cuenta" }, + "creatorAddress": { + "message": "Dirección del creador" + }, "crossChainSwapsLink": { "message": "Intercambie entre redes con MetaMask Portfolio" }, @@ -1260,9 +1443,27 @@ "data": { "message": "Datos" }, + "dataCollectionForMarketing": { + "message": "Recopilación de datos para marketing" + }, + "dataCollectionForMarketingDescription": { + "message": "Usaremos MetaMetrics para saber cómo interactúa con nuestras comunicaciones de marketing. Es posible que compartamos noticias relevantes (como características del producto y otros materiales)." + }, + "dataCollectionWarningPopoverButton": { + "message": "Bien" + }, + "dataCollectionWarningPopoverDescription": { + "message": "Desactivó la recopilación de datos para nuestros fines de marketing. Esto solo se aplica a este dispositivo. Si utiliza MetaMask en otros dispositivos, asegúrese de desactivarlo allí también." + }, "dataHex": { "message": "Hex" }, + "dataUnavailable": { + "message": "datos no disponibles" + }, + "dateCreated": { + "message": "Fecha de creación" + }, "dcent": { "message": "D'Cent" }, @@ -1292,6 +1493,9 @@ "decryptRequest": { "message": "Descifrar solicitud" }, + "defaultRpcUrl": { + "message": "URL RPC por defecto" + }, "delete": { "message": "Eliminar" }, @@ -1308,6 +1512,9 @@ "message": "¿Eliminar la red de $1?", "description": "$1 represents the name of the network" }, + "deleteRpcUrl": { + "message": "Eliminar URL RPC" + }, "deposit": { "message": "Depositar" }, @@ -1333,18 +1540,6 @@ "details": { "message": "Detalles" }, - "developerOptions": { - "message": "Opciones de desarrollador" - }, - "developerOptionsResetStatesAnnouncementsDescription": { - "message": "Restablece el valor booleano isShown a falso para todos los anuncios. Los anuncios son las notificaciones que se muestran en la ventana emergente Novedades." - }, - "developerOptionsResetStatesOnboarding": { - "message": "Restablece varios estados relacionados con la incorporación y redirige a la página de incorporación \"Asegure su monedero\"." - }, - "developerOptionsServiceWorkerKeepAlive": { - "message": "El resultado es una marca de tiempo que se guarda continuamente en session.storage" - }, "disabledGasOptionToolTipMessage": { "message": "“$1” está desactivado porque no cumple el mínimo de un aumento del 10 % respecto a la tarifa de gas original.", "description": "$1 is gas estimate type which can be market or aggressive" @@ -1521,6 +1716,9 @@ "editGasTooLow": { "message": "Se desconoce el tiempo de procesamiento" }, + "editNetworkLink": { + "message": "editar la red original" + }, "editNonceField": { "message": "Editar nonce" }, @@ -1552,6 +1750,9 @@ "enabled": { "message": "Activado" }, + "enabledNetworks": { + "message": "Redes habilitadas" + }, "encryptionPublicKeyNotice": { "message": "$1 quisiera su clave pública de cifrado. Al aceptar, este sitio podrá redactar mensajes cifrados para usted.", "description": "$1 is the web3 site name" @@ -1656,6 +1857,9 @@ "estimatedFee": { "message": "Tarifa estimada" }, + "estimatedFeeTooltip": { + "message": "Monto pagado para procesar la transacción en la red." + }, "ethGasPriceFetchWarning": { "message": "Se muestra el precio del gas de respaldo, ya que el servicio para calcular el precio del gas principal no se encuentra disponible en este momento." }, @@ -1675,6 +1879,12 @@ "etherscanViewOn": { "message": "Ver en Etherscan" }, + "existingChainId": { + "message": "La información que ha ingresado está asociada con un ID de cadena existente." + }, + "existingRpcUrl": { + "message": "Esta URL está asociada a otro ID de cadena." + }, "expandView": { "message": "Expandir vista" }, @@ -1729,6 +1939,9 @@ "message": "¿No funciona la importación del archivo? Haga clic aquí.", "description": "Helps user import their account from a JSON file" }, + "findTheRightChainId": { + "message": "Encuentre el correcto en:" + }, "flaskWelcomeUninstall": { "message": "le recomendamos que desinstale esta extensión", "description": "This request is shown on the Flask Welcome screen. It is intended for non-developers, and will be bolded." @@ -1768,6 +1981,9 @@ "forgotPassword": { "message": "¿Olvidó su contraseña?" }, + "form": { + "message": "formulario" + }, "from": { "message": "De" }, @@ -1976,6 +2192,12 @@ "highLowercase": { "message": "alto" }, + "highestCurrentBid": { + "message": "Oferta actual más alta" + }, + "highestFloorPrice": { + "message": "Precio mínimo más alto" + }, "history": { "message": "Historial" }, @@ -2315,12 +2537,21 @@ "knownTokenWarning": { "message": "Esta acción editará tokens que ya estén enumerados en su monedero y que se pueden usar para engañarlo. Apruebe solo si está seguro de que quiere cambiar lo que representan estos tokens. Más información sobre $1" }, + "l1Fee": { + "message": "Tarifa L1" + }, + "l1FeeTooltip": { + "message": "Tarifa de gas L1" + }, + "l2Fee": { + "message": "Tarifa L2" + }, + "l2FeeTooltip": { + "message": "Tarifa de gas L2" + }, "lastConnected": { "message": "Última conexión" }, - "lastPriceSold": { - "message": "Precio de la última venta" - }, "lastSold": { "message": "Última venta" }, @@ -2492,6 +2723,12 @@ "message": "Asegúrese de que nadie esté mirando", "description": "Warning to users to be care while creating and saving their new Secret Recovery Phrase" }, + "marketCap": { + "message": "Capitalización bursátil" + }, + "marketDetails": { + "message": "Detalles del mercado" + }, "max": { "message": "Máx." }, @@ -2501,6 +2738,9 @@ "maxFee": { "message": "Tarifa máxima" }, + "maxFeeTooltip": { + "message": "Una tarifa máxima proporcionada para pagar la transacción." + }, "maxPriorityFee": { "message": "Tarifa de prioridad máxima" }, @@ -2548,12 +2788,19 @@ "methodData": { "message": "Método" }, + "methodDataTransactionDesc": { + "message": "Función ejecutada en base a datos de entrada decodificados." + }, "methodNotSupported": { "message": "No compatible con esta cuenta." }, "metrics": { "message": "Indicadores" }, + "millionAbbreviation": { + "message": "m", + "description": "Shortened form of 'million'" + }, "mismatchAccount": { "message": "Su cuenta seleccionada ($1) es diferente a la cuenta que intenta firmar ($2)" }, @@ -2666,7 +2913,7 @@ "description": "Description below header used on Permission Connect screen for native permissions." }, "nativeToken": { - "message": "El token nativo en esta red es de $1. Es el token utilizado para las tarifas de gas.", + "message": "El token nativo en esta red es de $1. Es el token utilizado para las tarifas de gas. ", "description": "$1 represents the name of the native token on the current network" }, "nativeTokenScamWarningConversion": { @@ -2735,6 +2982,9 @@ "networkNameBase": { "message": "Base" }, + "networkNameBitcoin": { + "message": "Bitcoin" + }, "networkNameDefinition": { "message": "El nombre asociado a esta red." }, @@ -2759,6 +3009,9 @@ "networkNameZkSyncEra": { "message": "zkSync Era" }, + "networkOptions": { + "message": "Opciones de red" + }, "networkProvider": { "message": "Proveedor de red" }, @@ -2827,6 +3080,9 @@ "newNetworkAdded": { "message": "¡\"$1\" se añadió con éxito!" }, + "newNetworkEdited": { + "message": "¡\"$1\" se editó con éxito!" + }, "newNftAddedMessage": { "message": "¡NFT se agregó correctamente!" }, @@ -2863,8 +3119,11 @@ "nftAlreadyAdded": { "message": "NFT ya ha sido añadido." }, + "nftAutoDetectionEnabled": { + "message": "Autodetección de NFT habilitada" + }, "nftDisclaimer": { - "message": "Descargo de responsabilidad: MetaMask extrae el archivo multimedia de la URL de origen. Esta URL a veces es modificada por el mercado en el que se acuñó el NFT." + "message": "Descargo de responsabilidad: MetaMask extrae el archivo multimedia de la URL de origen. A veces, el mercado en el que se acuñó el NFT cambia esta URL." }, "nftOptions": { "message": "Opciones de NFT" @@ -2913,6 +3172,9 @@ "noDomainResolution": { "message": "No se proporcionó resolución para el dominio." }, + "noHardwareWalletOrSnapsSupport": { + "message": "Snaps, y la mayoría de los monederos físicos, no funcionarán con la versión actual de su navegador." + }, "noNFTs": { "message": "No hay ningún NFT aún" }, @@ -2943,8 +3205,8 @@ "nonceField": { "message": "Personalizar nonce de transacción" }, - "nonceFieldDescription": { - "message": "Active esta opción para cambiar el nonce (número de transacción) en las pantallas de confirmación. Esta es una función avanzada, úsela con precaución." + "nonceFieldDesc": { + "message": "Active esta opción para cambiar el nonce (número de transacción) al enviar activos. Esta es una función avanzada, úsela con precaución." }, "nonceFieldHeading": { "message": "Nonce personalizado" @@ -3146,6 +3408,9 @@ "numberOfNewTokensDetectedSingular": { "message": "1 nuevo token encontrado en esta cuenta" }, + "numberOfTokens": { + "message": "Número de tokens" + }, "ofTextNofM": { "message": "de" }, @@ -3161,8 +3426,36 @@ "on": { "message": "Activado" }, - "onboarding": { - "message": "Incorporación" + "onboardedMetametricsAccept": { + "message": "Acepto" + }, + "onboardedMetametricsDisagree": { + "message": "No, gracias" + }, + "onboardedMetametricsKey1": { + "message": "Últimas novedades" + }, + "onboardedMetametricsKey2": { + "message": "Características de productos" + }, + "onboardedMetametricsKey3": { + "message": "Otros materiales promocionales relevantes" + }, + "onboardedMetametricsLink": { + "message": "MetaMetrics" + }, + "onboardedMetametricsParagraph1": { + "message": "Además de $1, nos gustaría utilizar datos para comprender cómo interactúa con las comunicaciones de marketing.", + "description": "$1 represents the 'onboardedMetametricsLink' locale string" + }, + "onboardedMetametricsParagraph2": { + "message": "Esto nos ayuda a personalizar lo que compartimos con usted, como:" + }, + "onboardedMetametricsParagraph3": { + "message": "Recuerde, nunca vendemos los datos que usted proporciona y puede optar por no participar en cualquier momento." + }, + "onboardedMetametricsTitle": { + "message": "Ayúdenos a mejorar su experiencia" }, "onboardingAdvancedPrivacyIPFSDescription": { "message": "La puerta de enlace de IPFS permite acceder y visualizar datos alojados por terceros. Puede agregar una puerta de enlace de IPFS personalizada o continuar usando la predeterminada." @@ -3200,9 +3493,6 @@ "onboardingMetametricsDescription2": { "message": "Al recopilar métricas, siempre será..." }, - "onboardingMetametricsDisagree": { - "message": "No, gracias" - }, "onboardingMetametricsInfuraTerms": { "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`" @@ -3237,6 +3527,9 @@ "onboardingMetametricsTitle": { "message": "Ayúdenos a mejorar MetaMask" }, + "onboardingMetametricsUseDataCheckbox": { + "message": "Utilizaremos estos datos para saber cómo interactúa con nuestras comunicaciones de marketing. Es posible que compartamos noticias relevantes (como características del producto)." + }, "onboardingPinExtensionBillboardAccess": { "message": "Acceso completo" }, @@ -3280,6 +3573,22 @@ "message": "Las alertas de detección de phishing se basan en la comunicación con $1. jsDeliver tendrá acceso a su dirección IP. Ver 2$.", "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" }, + "oneDayAbbreviation": { + "message": "1 d", + "description": "Shortened form of '1 day'" + }, + "oneMonthAbbreviation": { + "message": "1 m", + "description": "Shortened form of '1 month'" + }, + "oneWeekAbbreviation": { + "message": "1 s", + "description": "Shortened form of '1 week'" + }, + "oneYearAbbreviation": { + "message": "1 a", + "description": "Shortened form of '1 year'" + }, "onekey": { "message": "OneKey" }, @@ -3632,6 +3941,9 @@ "permissionsPageTourTitle": { "message": "Los sitios conectados ahora tienen permisos" }, + "permitSimulationDetailInfo": { + "message": "Le está dando permiso al gastador para gastar esta cantidad de tokens de su cuenta." + }, "personalAddressDetected": { "message": "Se detectó una dirección personal. Ingrese la dirección de contrato del token." }, @@ -3664,6 +3976,10 @@ "popularCustomNetworks": { "message": "Redes populares personalizadas" }, + "popularNetworkAddToolTip": { + "message": "Algunas de estas redes dependen de terceros. Las conexiones pueden ser menos confiables o permitir que terceros realicen un seguimiento de la actividad. $1", + "description": "$1 is Learn more link" + }, "portfolio": { "message": "Portafolio" }, @@ -3676,6 +3992,12 @@ "prev": { "message": "Ant." }, + "price": { + "message": "Precio" + }, + "priceUnavailable": { + "message": "precio no disponible" + }, "primaryCurrencySetting": { "message": "Moneda principal" }, @@ -3828,6 +4150,9 @@ "quoteRate": { "message": "Tarifa de cotización" }, + "rank": { + "message": "Rango" + }, "reAddAccounts": { "message": "volver a agregar cualquier otra cuenta" }, @@ -3997,9 +4322,6 @@ "reset": { "message": "Restablecer" }, - "resetStates": { - "message": "Restablecer estados" - }, "resetWallet": { "message": "Restablecer monedero" }, @@ -4135,6 +4457,9 @@ "searchAccounts": { "message": "Buscar cuentas" }, + "searchNfts": { + "message": "Buscar NFT" + }, "searchTokens": { "message": "Buscar tokens" }, @@ -4179,13 +4504,10 @@ "seedPhraseIntroRecommendedButtonCopy": { "message": "Proteger mi monedero (recomendado)" }, - "seedPhraseIntroSidebarBulletFour": { - "message": "Escríbala y guárdela en varios lugares secretos." - }, "seedPhraseIntroSidebarBulletOne": { - "message": "Guárdela en un gestor de contraseñas" + "message": "Escríbala y guárdela en varios lugares secretos." }, - "seedPhraseIntroSidebarBulletThree": { + "seedPhraseIntroSidebarBulletTwo": { "message": "Guárdela en una caja fuerte." }, "seedPhraseIntroSidebarCopyOne": { @@ -4255,10 +4577,10 @@ "message": "Seleccionar token" }, "selectNFTPrivacyPreference": { - "message": "Active la detección de NFT en Configuraciones" + "message": "Habilite la autodetección de NFT" }, "selectPathHelp": { - "message": "Si no ve las cuentas previstas, intente cambiar la ruta HD." + "message": "Si no ve las cuentas previstas, intente cambiar la ruta HD o la red seleccionada actualmente." }, "selectType": { "message": "Seleccionar tipo" @@ -4269,9 +4591,6 @@ "send": { "message": "Enviar" }, - "sendAToken": { - "message": "Enviar un token" - }, "sendBugReport": { "message": "Envíenos un informe de error." }, @@ -4322,9 +4641,6 @@ "sepolia": { "message": "Red de prueba Sepolia" }, - "serviceWorkerKeepAlive": { - "message": "Mantener activo el Service Worker" - }, "setAdvancedPrivacySettingsDetails": { "message": "MetaMask utiliza estos servicios de terceros de confianza para mejorar la usabilidad y la seguridad de los productos." }, @@ -4381,6 +4697,9 @@ "showIncomingTransactionsExplainer": { "message": "Esto se basa en diferentes API de terceros para cada red, que exponen su dirección Ethereum y su dirección IP." }, + "showLess": { + "message": "Mostrar menos" + }, "showMore": { "message": "Mostrar más" }, @@ -4411,9 +4730,6 @@ "signatureRequestGuidance": { "message": "Solo firme este mensaje si comprende completamente el contenido y confía en el sitio solicitante." }, - "signatureRequestWarning": { - "message": "Firmar este mensaje podría ser peligroso. Es posible que le esté otorgando el control total de su cuenta y activos a la contraparte de este mensaje. Eso significa que podrían vaciar su cuenta en cualquier momento. Proceda con precaución. $1." - }, "signed": { "message": "Firmado" }, @@ -4423,6 +4739,9 @@ "signing": { "message": "Firmando" }, + "signingInWith": { + "message": "Iniciar sesión con" + }, "simulationDetailsFailed": { "message": "Se produjo un error al cargar su estimación." }, @@ -4460,6 +4779,24 @@ "simulationsSettingSubHeader": { "message": "Estimar cambios de saldo" }, + "siweIssued": { + "message": "Emitido" + }, + "siweNetwork": { + "message": "Red" + }, + "siweRequestId": { + "message": "Solicitar ID" + }, + "siweResources": { + "message": "Recursos" + }, + "siweSignatureSimulationDetailInfo": { + "message": "Está iniciando sesión en un sitio y no se prevén cambios en su cuenta." + }, + "siweURI": { + "message": "URL" + }, "skip": { "message": "Omitir" }, @@ -4563,6 +4900,14 @@ "snapAccountsDescription": { "message": "Cuentas controladas por Snaps de terceros." }, + "snapConnectTo": { + "message": "Conectarse a $1", + "description": "$1 is the website URL or a Snap name. Used for Snaps pre-approved connections." + }, + "snapConnectionPermissionDescription": { + "message": "Permita que $1 se conecte automáticamente a $2 sin su aprobación.", + "description": "Used for Snap pre-approved connections. $1 is the Snap name, $2 is a website URL." + }, "snapConnectionWarning": { "message": "$1 quiere conectarse a $2", "description": "$2 is the snap and $1 is the dapp requesting connection to the snap." @@ -4574,6 +4919,9 @@ "snapDetailWebsite": { "message": "Sitio web" }, + "snapHomeMenu": { + "message": "Menú de inicio de Snap" + }, "snapInstallRequest": { "message": "Instalar $1 le otorga los siguientes permisos.", "description": "$1 is the snap name." @@ -4696,6 +5044,9 @@ "source": { "message": "Fuente" }, + "speed": { + "message": "Velocidad" + }, "speedUp": { "message": "Acelerar" }, @@ -4730,6 +5081,9 @@ "spendLimitTooLarge": { "message": "El límite de gastos es demasiado alto" }, + "spender": { + "message": "Gastador" + }, "spendingCap": { "message": "Límite de gasto" }, @@ -4850,9 +5204,6 @@ "stateLogsDescription": { "message": "Los registros de estado contienen sus direcciones de cuentas públicas y las transacciones enviadas." }, - "states": { - "message": "Estados" - }, "status": { "message": "Estado" }, @@ -4957,6 +5308,13 @@ "submitted": { "message": "Enviado" }, + "suggestedBySnap": { + "message": "Sugerido por $1", + "description": "$1 is the snap name" + }, + "suggestedTokenName": { + "message": "Nombre sugerido:" + }, "suggestedTokenSymbol": { "message": "Símbolo de cotización sugerido:" }, @@ -5071,7 +5429,7 @@ "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." }, "swapFetchingQuotes": { - "message": "Obtener cotizaciones" + "message": "Obteniendo cotizaciones..." }, "swapFetchingQuotesErrorDescription": { "message": "Se produjo un error. Vuelva a intentarlo o, si el error persiste, póngase en contacto con el soporte al cliente." @@ -5419,7 +5777,7 @@ "description": "$1 represents the account name, $2 represents the network name" }, "switchedTo": { - "message": "Ha cambiado a" + "message": "Ahora está usando" }, "switchingNetworksCancelsPendingConfirmations": { "message": "Cambiar de red cancelará todas las confirmaciones pendientes" @@ -5458,7 +5816,7 @@ "message": "Elija su tema MetaMask preferido." }, "thingsToKeep": { - "message": "Cosas a tener en cuenta:" + "message": "Tenga en cuenta:" }, "thirdPartySoftware": { "message": "Aviso de software de terceros", @@ -5467,6 +5825,10 @@ "thisCollection": { "message": "esta colección" }, + "threeMonthsAbbreviation": { + "message": "3 m", + "description": "Shortened form of '3 months'" + }, "time": { "message": "Tiempo" }, @@ -5480,45 +5842,6 @@ "message": "Para: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleEthSignBannerDescription": { - "message": "Está en riesgo de sufrir ataques de phishing. Protéjase desactivando eth_sign." - }, - "toggleEthSignDescriptionField": { - "message": "Si activa esta opción, es posible que reciba solicitudes de firma que no sean legibles. Al firmar un mensaje que no entiende, podría estar dando su consentimiento para ceder sus fondos y NFT." - }, - "toggleEthSignField": { - "message": "Solicitudes de eth_sign" - }, - "toggleEthSignModalBannerBoldText": { - "message": " usted podría estar siendo estafado" - }, - "toggleEthSignModalBannerText": { - "message": "Si se le ha pedido que active esta configuración," - }, - "toggleEthSignModalCheckBox": { - "message": "Entiendo que puedo perder todos mis fondos y mis NFT si activo las solicitudes de eth_sign. " - }, - "toggleEthSignModalDescription": { - "message": "Permitir solicitudes eth_sign puede hacerlo vulnerable a ataques de phishing. Siempre revise la URL y tenga cuidado al firmar mensajes que contengan código." - }, - "toggleEthSignModalFormError": { - "message": "El texto es incorrecto" - }, - "toggleEthSignModalFormLabel": { - "message": "Ingrese “Firmo solo lo que entiendo” para continuar" - }, - "toggleEthSignModalFormValidation": { - "message": "Firmo solo lo que entiendo" - }, - "toggleEthSignModalTitle": { - "message": "Úselo bajo su propio riesgo" - }, - "toggleEthSignOff": { - "message": "DESACTIVADO (Recomendado)" - }, - "toggleEthSignOn": { - "message": "ACTIVADO (No recomendado)" - }, "toggleRequestQueueDescription": { "message": "Esto le permite seleccionar una red para cada sitio en lugar de una única red seleccionada para todos los sitios. Esta función evitará que cambie de red manualmente, lo que puede afectar su experiencia de usuario en ciertos sitios." }, @@ -5546,6 +5869,9 @@ "tokenContractAddress": { "message": "Dirección de contrato de token" }, + "tokenDecimal": { + "message": "Decimales del token" + }, "tokenDecimalFetchFailed": { "message": "Se requiere decimal del token. Encuéntrelo en: $1" }, @@ -5562,13 +5888,16 @@ "message": "ID de token" }, "tokenList": { - "message": "Listas de tokens:" + "message": "Lista de tókenes" }, "tokenScamSecurityRisk": { "message": "estafas de tokens y riesgos de seguridad" }, "tokenShowUp": { - "message": "Es posible que sus tokens no aparezcan automáticamente en su monedero." + "message": "Es posible que sus tókenes no aparezcan automáticamente en su monedero. " + }, + "tokenStandard": { + "message": "Estándar de tokenes" }, "tokenSymbol": { "message": "Símbolo del token" @@ -5580,6 +5909,9 @@ "message": "$1 nuevos tokens encontrados", "description": "$1 is the number of new tokens detected" }, + "tokensInCollection": { + "message": "Tokens en la colección" + }, "tooltipApproveButton": { "message": "Comprendo" }, @@ -5595,6 +5927,9 @@ "total": { "message": "Total" }, + "totalVolume": { + "message": "Volúmen total" + }, "transaction": { "message": "transacción" }, @@ -5610,6 +5945,9 @@ "transactionCreated": { "message": "La transacción se creó con un valor de $1 en $2." }, + "transactionDataFunction": { + "message": "Función" + }, "transactionDetailDappGasMoreInfo": { "message": "Sitio sugerido" }, @@ -5700,6 +6038,10 @@ "transferFrom": { "message": "Transferir desde" }, + "trillionAbbreviation": { + "message": "b", + "description": "Shortened form of 'trillion'" + }, "troubleConnectingToLedgerU2FOnFirefox": { "message": "Tenemos problemas para conectarnos con su Ledger. $1", "description": "$1 is a link to the wallet connection guide;" @@ -5778,6 +6120,9 @@ "message": "U2F", "description": "A name on an API for the browser to interact with devices that support the U2F protocol. On some browsers we use it to connect MetaMask to Ledger devices." }, + "unMatchedChain": { + "message": "Según nuestros registros, esta URL no coincide con un proveedor conocido para este ID de cadena." + }, "unapproved": { "message": "No aprobado" }, @@ -5829,12 +6174,21 @@ "update": { "message": "Actualizar" }, + "updateOrEditNetworkInformations": { + "message": "Actualice su información o" + }, "updateRequest": { "message": "Solicitud de actualización" }, "updatedWithDate": { "message": "$1 actualizado" }, + "uploadDropFile": { + "message": "Ingrese su archivo aquí" + }, + "uploadFile": { + "message": "Cargar archivo" + }, "urlErrorMsg": { "message": "Las direcciones URL requieren el prefijo HTTP/HTTPS adecuado." }, @@ -6050,6 +6404,12 @@ "whatsThis": { "message": "¿Qué es esto?" }, + "wrongChainId": { + "message": "Este ID de cadena no coincide con el nombre de la red." + }, + "wrongNetworkName": { + "message": "Según nuestros registros, es posible que el nombre de la red no coincida correctamente con este ID de cadena." + }, "xOfYPending": { "message": "$1 de $2 están pendientes", "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" @@ -6073,8 +6433,11 @@ "yourAccounts": { "message": "Sus cuentas" }, - "yourFundsMayBeAtRisk": { - "message": "Sus fondos podrían estar en riesgo" + "yourActivity": { + "message": "Su actividad" + }, + "yourBalance": { + "message": "Su saldo" }, "yourNFTmayBeAtRisk": { "message": "Sus NFT podrían estar en riesgo" diff --git a/app/_locales/es_419/messages.json b/app/_locales/es_419/messages.json index 662a7f3ede27..c406955c36c9 100644 --- a/app/_locales/es_419/messages.json +++ b/app/_locales/es_419/messages.json @@ -42,7 +42,7 @@ "message": "Cartera HW con QR" }, "QRHardwareWalletSteps2Description": { - "message": "AirGap Vault y Ngrave (próximamente)" + "message": "Ngrave Zero" }, "about": { "message": "Acerca de" @@ -1363,9 +1363,6 @@ "nonceField": { "message": "Personalizar nonce de transacción" }, - "nonceFieldDescription": { - "message": "Active esta opción para cambiar el nonce (número de transacción) en las pantallas de confirmación. Esta es una función avanzada, úsela con precaución." - }, "nonceFieldHeading": { "message": "Nonce personalizado" }, @@ -1698,13 +1695,10 @@ "seedPhraseIntroRecommendedButtonCopy": { "message": "Asegurar mi cartera (recomendado)" }, - "seedPhraseIntroSidebarBulletFour": { - "message": "Escríbala y guárdela en varios lugares secretos." - }, "seedPhraseIntroSidebarBulletOne": { - "message": "Guárdela en un administrador de contraseñas" + "message": "Escríbala y guárdela en varios lugares secretos." }, - "seedPhraseIntroSidebarBulletThree": { + "seedPhraseIntroSidebarBulletTwo": { "message": "Guárdela en una caja fuerte." }, "seedPhraseIntroSidebarCopyOne": { diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index b6ed5355ad29..2e7d3697e11d 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -42,7 +42,7 @@ "message": "Connectez votre portefeuille électronique QR" }, "QRHardwareWalletSteps2Description": { - "message": "Ngrave (bientôt disponible)" + "message": "Ngrave Zero" }, "SIWEAddressInvalid": { "message": "L’adresse figurant dans la demande de connexion ne correspond pas à l’adresse du compte que vous utilisez pour vous connecter." @@ -162,6 +162,9 @@ "accountSelectionRequired": { "message": "Vous devez sélectionner un compte !" }, + "accountTypeNotSupported": { + "message": "Ce type de compte n’est pas pris en charge" + }, "accounts": { "message": "Comptes" }, @@ -277,6 +280,12 @@ "addNewAccount": { "message": "Ajouter un nouveau compte Ethereum" }, + "addNewBitcoinAccount": { + "message": "Ajouter un nouveau compte Bitcoin (Bêta)" + }, + "addNewBitcoinTestnetAccount": { + "message": "Ajouter un nouveau compte Bitcoin (Testnet)" + }, "addNewToken": { "message": "Ajouter un nouveau jeton" }, @@ -286,6 +295,9 @@ "addNfts": { "message": "Ajouter des NFT" }, + "addRpcUrl": { + "message": "Ajouter l’URL du RPC" + }, "addSnapAccountToggle": { "message": "Activer « Ajouter un Snap de compte (bêta) »" }, @@ -305,12 +317,21 @@ "message": "Vous n’arrivez pas à trouver un jeton ? Vous pouvez ajouter manuellement n’importe quel jeton en copiant et collant son adresse. Les adresses des contrats de jetons sont disponibles sur $1", "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" }, + "addUrl": { + "message": "Ajouter l'URL" + }, "addingCustomNetwork": { "message": "Ajout de réseau" }, "addingTokens": { "message": "Ajouter des jetons" }, + "additionalNetworks": { + "message": "Réseaux supplémentaires" + }, + "additionalRpcUrl": { + "message": "URL supplémentaire de RPC" + }, "address": { "message": "Adresse" }, @@ -326,6 +347,18 @@ "advancedConfiguration": { "message": "Configuration avancée" }, + "advancedDetailsDataDesc": { + "message": "Données" + }, + "advancedDetailsHexDesc": { + "message": "Hexa" + }, + "advancedDetailsNonceDesc": { + "message": "Nonce" + }, + "advancedDetailsNonceTooltip": { + "message": "Il s’agit du nombre de transactions d’un compte. Le nonce de la première transaction est 0 et il augmente d’une manière séquentielle." + }, "advancedGasFeeDefaultOptIn": { "message": "Enregistrer ces valeurs comme valeurs par défaut pour le réseau $1.", "description": "$1 is the current network name." @@ -349,6 +382,18 @@ "alert": { "message": "Alerte" }, + "alertActionBuy": { + "message": "Acheter de l’ETH" + }, + "alertActionUpdateGas": { + "message": "Mettre à jour la limite de gaz" + }, + "alertActionUpdateGasFee": { + "message": "Actualiser les frais" + }, + "alertActionUpdateGasFeeLevel": { + "message": "Mettre à jour les options de gaz" + }, "alertBannerMultipleAlertsDescription": { "message": "Si vous approuvez cette demande, un tiers connu pour ses activités frauduleuses pourrait s’emparer de tous vos actifs." }, @@ -358,6 +403,36 @@ "alertDisableTooltip": { "message": "Vous pouvez modifier ceci dans « Paramètres > Alertes »" }, + "alertMessageGasEstimateFailed": { + "message": "Nous ne sommes pas en mesure de déterminer le montant exact des frais et cette estimation peut être élevée. Nous vous suggérons d’entrer une limite de gaz personnalisée, mais la transaction pourrait quand même échouer." + }, + "alertMessageGasFeeLow": { + "message": "Si vous choisissez des frais peu élevés, attendez-vous à des transactions plus lentes et à des temps d’attente plus longs. Pour des transactions plus rapides, choisissez les options « Ordre au marché » ou « Agressif »." + }, + "alertMessageGasTooLow": { + "message": "Pour effectuer cette transaction, vous devez augmenter la limite de gaz à 21 000 ou plus." + }, + "alertMessageInsufficientBalance": { + "message": "Vous n’avez pas assez d’ETH sur votre compte pour payer les frais de transaction." + }, + "alertMessageNetworkBusy": { + "message": "Les prix du gaz sont élevés et les estimations sont moins précises." + }, + "alertMessageNoGasPrice": { + "message": "Nous ne pouvons pas valider cette transaction tant que vous n’avez pas mis à jour manuellement les frais." + }, + "alertMessagePendingTransactions": { + "message": "La transaction précédente doit être finalisée avant que celle-ci ne soit traitée. Découvrez comment vous pouvez annuler ou accélérer une transaction." + }, + "alertMessageSignInDomainMismatch": { + "message": "Le site auquel vous êtes en train de vous connecter n’est pas le site à l’origine de la demande. Il pourrait s’agir d’une tentative de vol de vos identifiants de connexion." + }, + "alertMessageSignInWrongAccount": { + "message": "Ce site vous demande de vous connecter en utilisant le mauvais compte." + }, + "alertMessageSigningOrSubmitting": { + "message": "La transaction précédente doit être finalisée avant que celle-ci ne soit traitée." + }, "alertModalAcknowledge": { "message": "Je suis conscient du risque et je souhaite quand même continuer" }, @@ -367,6 +442,33 @@ "alertModalReviewAllAlerts": { "message": "Examiner toutes les alertes" }, + "alertReasonGasEstimateFailed": { + "message": "Frais inexacts" + }, + "alertReasonGasFeeLow": { + "message": "Vitesse lente" + }, + "alertReasonGasTooLow": { + "message": "Limite de gaz trop basse" + }, + "alertReasonInsufficientBalance": { + "message": "Fonds insuffisants" + }, + "alertReasonNetworkBusy": { + "message": "Le réseau est occupé" + }, + "alertReasonNoGasPrice": { + "message": "Estimation des frais non disponible" + }, + "alertReasonPendingTransactions": { + "message": "Transaction en attente" + }, + "alertReasonSignIn": { + "message": "Demande de connexion suspecte" + }, + "alertReasonWrongAccount": { + "message": "Mauvais compte" + }, "alertSettingsUnconnectedAccount": { "message": "Navigation sur un site Web avec un compte non connecté sélectionné" }, @@ -398,6 +500,12 @@ "allPermissions": { "message": "Toutes les autorisations" }, + "allTimeHigh": { + "message": "Le plus haut niveau jamais atteint" + }, + "allTimeLow": { + "message": "Le plus bas niveau jamais atteint" + }, "allYourNFTsOf": { "message": "Tous vos NFT via $1", "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" @@ -436,9 +544,6 @@ "message": "$1 et $2", "description": "$1 is the first item, $2 is the second item. Used in Snap Install Warning modal." }, - "announcements": { - "message": "Annonces" - }, "appDescription": { "message": "Extension Ethereum pour navigateur", "description": "The description of the application" @@ -524,6 +629,9 @@ "attemptToCancelSwapForFree": { "message": "Tentative d’annuler gratuitement le swap" }, + "attributes": { + "message": "Attributs" + }, "attributions": { "message": "Attributions" }, @@ -594,6 +702,10 @@ "basicConfigurationBannerTitle": { "message": "La fonctionnalité de base est désactivée" }, + "basicConfigurationDescription": { + "message": "MetaMask offre des fonctionnalités de base telles que l’affichage des détails des jetons et des paramètres de gaz par le biais de services Internet. Lorsque vous utilisez des services Internet, votre adresse IP est partagée avec le fournisseur de ces services, dans ce cas MetaMask. C’est la même chose que lorsque vous visitez un site web. MetaMask conserve ces données temporairement et ne les vend jamais. Vous pouvez utiliser un VPN ou désactiver ces services, mais cela peut affecter votre expérience avec MetaMask. Pour en savoir plus, lisez notre $1.", + "description": "$1 is to be replaced by the message for privacyMsg, and will link to https://consensys.io/privacy-policy" + }, "basicConfigurationLabel": { "message": "Fonctionnalité de base" }, @@ -637,6 +749,29 @@ "betaWalletCreationSuccessReminder2": { "message": "La version bêta de MetaMask ne vous demandera jamais votre phrase secrète de récupération." }, + "billionAbbreviation": { + "message": "Mrd", + "description": "Shortened form of 'billion'" + }, + "bitcoinActivityNotSupported": { + "message": "L’activité Bitcoin n’est pas prise en charge" + }, + "bitcoinSupportSectionTitle": { + "message": "Bitcoin" + }, + "bitcoinSupportToggleDescription": { + "message": "En activant cette fonctionnalité, vous aurez la possibilité d’ajouter un compte Bitcoin à votre extension MetaMask dérivée de votre phrase secrète de récupération existante. Toute utilisation de cette fonctionnalité bêta expérimentale se fait à vos risques et périls. Pour nous faire part de vos commentaires sur cette nouvelle expérience Bitcoin, veuillez remplir ce $1.", + "description": "$1 is the link to a product feedback form" + }, + "bitcoinSupportToggleTitle": { + "message": "Activer « Ajouter un nouveau compte Bitcoin (Bêta) »" + }, + "bitcoinTestnetSupportToggleDescription": { + "message": "En activant cette fonctionnalité, vous aurez la possibilité d’ajouter un compte Bitcoin pour le réseau de test." + }, + "bitcoinTestnetSupportToggleTitle": { + "message": "Activer « Ajouter un nouveau compte Bitcoin (Testnet) »" + }, "blockExplorerAccountAction": { "message": "Compte", "description": "This is used with viewOnEtherscan and viewInExplorer e.g View Account in Explorer" @@ -662,6 +797,9 @@ "blockaid": { "message": "BlockAid" }, + "blockaidAlertInfo": { + "message": "Nous ne vous recommandons pas de donner suite à cette demande." + }, "blockaidDescriptionApproveFarming": { "message": "Si vous approuvez cette demande, un tiers connu pour ses activités frauduleuses pourrait s’emparer de tous vos actifs." }, @@ -669,7 +807,7 @@ "message": "Si vous approuvez cette demande, quelqu’un pourrait s'emparer de vos actifs répertoriés sur Blur." }, "blockaidDescriptionErrored": { - "message": "À la suite d’une erreur, cette demande n’a pas été vérifiée par le fournisseur de services de sécurité. Veuillez agir avec prudence." + "message": "En raison d’une erreur, nous n’avons pas pu vérifier les alertes de sécurité. Ne continuez que si vous faites confiance à toutes les adresses concernées." }, "blockaidDescriptionMaliciousDomain": { "message": "Vous interagissez avec un domaine malveillant. Si vous approuvez cette demande, vous risquez de perdre vos actifs." @@ -683,6 +821,9 @@ "blockaidDescriptionTransferFarming": { "message": "Si vous approuvez cette demande, un tiers connu pour ses activités frauduleuses pourrait s’emparer de tous vos actifs." }, + "blockaidDescriptionWarning": { + "message": "Il pourrait s’agir d’une demande trompeuse. Ne continuez que si vous faites confiance à toutes les adresses concernées." + }, "blockaidMessage": { "message": "Protection de la vie privée : aucune donnée n’est partagée avec des tiers. Disponible sur Arbitrum, Avalanche, BNB chain, Linea, Optimism, Polygon, Base, Sepolia et le réseau principal Ethereum." }, @@ -690,7 +831,7 @@ "message": "Cette demande trompeuse" }, "blockaidTitleMayNotBeSafe": { - "message": "Cette demande peut présenter des risques" + "message": "Soyez prudent" }, "blockaidTitleSuspicious": { "message": "Cette demande suspecte" @@ -698,6 +839,9 @@ "blockies": { "message": "Blockies" }, + "boughtFor": { + "message": "Acheté pour" + }, "bridge": { "message": "Pont" }, @@ -782,6 +926,9 @@ "chromeRequiredForHardwareWallets": { "message": "Pour connecter votre portefeuille matériel, vous devez utiliser MetaMask pour Google Chrome." }, + "circulatingSupply": { + "message": "Offre en circulation" + }, "clear": { "message": "Effacer" }, @@ -802,7 +949,7 @@ "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" }, "clickToManuallyAdd": { - "message": "Cliquez ici pour ajouter les jetons manuellement." + "message": "Vous pouvez ajouter des jetons manuellement." }, "close": { "message": "Fermer" @@ -816,6 +963,9 @@ "coingecko": { "message": "CoinGecko" }, + "collectionName": { + "message": "Nom de la collection" + }, "comboNoOptions": { "message": "Aucune option trouvée", "description": "Default text shown in the combo field dropdown if no options." @@ -841,6 +991,9 @@ "confirmAlertModalAcknowledgeMultiple": { "message": "J’ai pris connaissance des alertes, mais je souhaite quand même continuer" }, + "confirmAlertModalAcknowledgeSingle": { + "message": "J’ai pris connaissance de l’alerte, mais je souhaite quand même continuer" + }, "confirmAlertModalDetails": { "message": "Si vous vous connectez, un tiers connu pour ses activités frauduleuses pourrait s’emparer de tous vos actifs. Veuillez examiner les alertes avant de continuer." }, @@ -856,18 +1009,42 @@ "confirmConnectionTitle": { "message": "Confirmer la connexion à $1" }, + "confirmDeletion": { + "message": "Confirmer la suppression" + }, + "confirmFieldPaymaster": { + "message": "Frais payés par" + }, + "confirmFieldTooltipPaymaster": { + "message": "Les frais de cette transaction seront payés par le contrat « Paymaster » intelligent." + }, "confirmPassword": { "message": "Confirmer le mot de passe" }, "confirmRecoveryPhrase": { "message": "Confirmer la phrase secrète de récupération" }, + "confirmRpcUrlDeletionMessage": { + "message": "Voulez-vous vraiment supprimer l’URL du RPC ? Vos informations ne seront pas sauvegardées pour ce réseau." + }, "confirmTitleDescContractInteractionTransaction": { "message": "Ne confirmez cette transaction que si vous comprenez parfaitement son contenu et si vous faites confiance au site demandeur." }, + "confirmTitleDescPermitSignature": { + "message": "Ce site demande que vous lui accordiez l'autorisation de dépenser vos jetons." + }, + "confirmTitleDescSIWESignature": { + "message": "Un site vous demande de vous connecter pour prouver que vous êtes le titulaire de ce compte." + }, "confirmTitleDescSignature": { "message": "Ne confirmez ce message que si vous approuvez son contenu et faites confiance au site demandeur." }, + "confirmTitlePermitSignature": { + "message": "Demande de plafonnement des dépenses" + }, + "confirmTitleSIWESignature": { + "message": "Demande de connexion" + }, "confirmTitleSignature": { "message": "Demande de signature" }, @@ -1094,6 +1271,9 @@ "createSnapAccountTitle": { "message": "Créer un compte" }, + "creatorAddress": { + "message": "Adresse du créateur" + }, "crossChainSwapsLink": { "message": "Échanges inter-réseaux avec MetaMask Portfolio" }, @@ -1263,9 +1443,27 @@ "data": { "message": "Données" }, + "dataCollectionForMarketing": { + "message": "Collecte de données à des fins de marketing" + }, + "dataCollectionForMarketingDescription": { + "message": "Nous utiliserons MetaMetrics pour savoir comment vous interagissez avec nos communications commerciales et pour partager avec vous des informations pertinentes (comme les caractéristiques des produits et d’autres contenus)." + }, + "dataCollectionWarningPopoverButton": { + "message": "OK" + }, + "dataCollectionWarningPopoverDescription": { + "message": "Vous avez désactivé la collecte de données à des fins de marketing. Cela ne s’applique qu’à cet appareil. Si vous utilisez MetaMask sur d’autres appareils, n’oubliez pas de désactiver cette fonctionnalité sur ces appareils aussi." + }, "dataHex": { "message": "Hex" }, + "dataUnavailable": { + "message": "données non disponibles" + }, + "dateCreated": { + "message": "Date de création" + }, "dcent": { "message": "D’Cent" }, @@ -1295,6 +1493,9 @@ "decryptRequest": { "message": "Décrypter la demande" }, + "defaultRpcUrl": { + "message": "URL par défaut du RPC" + }, "delete": { "message": "Supprimer" }, @@ -1311,6 +1512,9 @@ "message": "Supprimer le réseau $1 ?", "description": "$1 represents the name of the network" }, + "deleteRpcUrl": { + "message": "Supprimer l’URL du RPC" + }, "deposit": { "message": "Effectuez un dépôt" }, @@ -1336,18 +1540,6 @@ "details": { "message": "Détails" }, - "developerOptions": { - "message": "Options pour les développeurs" - }, - "developerOptionsResetStatesAnnouncementsDescription": { - "message": "Règle la valeur de « isShown boolean » sur faux (false) pour toutes les annonces. Les annonces sont les notifications affichées dans la fenêtre contextuelle « Nouveautés »." - }, - "developerOptionsResetStatesOnboarding": { - "message": "Réinitialise divers états liés au processus d’intégration et redirige vers la page d’accueil « Sécuriser votre portefeuille »." - }, - "developerOptionsServiceWorkerKeepAlive": { - "message": "Les résultats d’un horodatage sont continuellement sauvegardés dans « session.storage »" - }, "disabledGasOptionToolTipMessage": { "message": "« $1 » est désactivé parce qu’il ne correspond pas au minimum d’augmentation de 10 % par rapport aux gas fees initiaux.", "description": "$1 is gas estimate type which can be market or aggressive" @@ -1524,6 +1716,9 @@ "editGasTooLow": { "message": "Délai de traitement inconnu" }, + "editNetworkLink": { + "message": "modifier le réseau d’origine" + }, "editNonceField": { "message": "Modifier le nonce" }, @@ -1555,6 +1750,9 @@ "enabled": { "message": "Activé" }, + "enabledNetworks": { + "message": "Réseaux activés" + }, "encryptionPublicKeyNotice": { "message": "$1 aimerait avoir votre clé publique de cryptage. En y consentant, ce site sera en mesure de vous composer des messages cryptés.", "description": "$1 is the web3 site name" @@ -1659,6 +1857,9 @@ "estimatedFee": { "message": "Frais estimés" }, + "estimatedFeeTooltip": { + "message": "Montant payé pour traiter la transaction sur le réseau." + }, "ethGasPriceFetchWarning": { "message": "Le prix de carburant de sauvegarde est fourni, car le service principal d’estimation du carburant est momentanément indisponible." }, @@ -1678,6 +1879,12 @@ "etherscanViewOn": { "message": "Afficher sur Etherscan" }, + "existingChainId": { + "message": "Les informations que vous avez saisies sont associées à un ID de chaîne existant." + }, + "existingRpcUrl": { + "message": "Cette URL est associée à un autre ID de chaîne." + }, "expandView": { "message": "Agrandir la vue" }, @@ -1732,6 +1939,9 @@ "message": "L’importation de fichier ne fonctionne pas ? Cliquez ici !", "description": "Helps user import their account from a JSON file" }, + "findTheRightChainId": { + "message": "Trouvez le bon ID de chaîne sur :" + }, "flaskWelcomeUninstall": { "message": "vous devriez désinstaller cette extension", "description": "This request is shown on the Flask Welcome screen. It is intended for non-developers, and will be bolded." @@ -1771,6 +1981,9 @@ "forgotPassword": { "message": "Mot de passe oublié ?" }, + "form": { + "message": "formulaire" + }, "from": { "message": "de" }, @@ -1881,7 +2094,7 @@ "message": "Quelque chose a mal tourné…" }, "genericExplorerView": { - "message": "Afficher le compte sur $1" + "message": "Voir le compte sur $1" }, "getStartedWithNFTs": { "message": "Obtenez des $1 pour acheter des NFT", @@ -1901,7 +2114,7 @@ "message": "Testnet Goerli" }, "gotIt": { - "message": "J’ai compris !" + "message": "D’accord" }, "grantedToWithColon": { "message": "Accordé à :" @@ -1979,6 +2192,12 @@ "highLowercase": { "message": "élevé" }, + "highestCurrentBid": { + "message": "Offre actuelle la plus élevée" + }, + "highestFloorPrice": { + "message": "Prix plancher le plus élevé" + }, "history": { "message": "Historique" }, @@ -2318,12 +2537,21 @@ "knownTokenWarning": { "message": "Cette action modifiera les jetons déjà présents dans votre portefeuille, et risque de favoriser les tentatives d’hameçonnage. N’approuvez que si vous êtes certain·e de vouloir modifier ce que ces jetons représentent. En savoir plus sur $1" }, + "l1Fee": { + "message": "Frais L1" + }, + "l1FeeTooltip": { + "message": "Frais de gaz L1" + }, + "l2Fee": { + "message": "Frais L2" + }, + "l2FeeTooltip": { + "message": "Frais de gaz L2" + }, "lastConnected": { "message": "Dernière connexion" }, - "lastPriceSold": { - "message": "Prix de la dernière vente" - }, "lastSold": { "message": "Dernière vente" }, @@ -2495,6 +2723,12 @@ "message": "Assurez-vous que personne ne regarde votre écran", "description": "Warning to users to be care while creating and saving their new Secret Recovery Phrase" }, + "marketCap": { + "message": "Capitalisation boursière" + }, + "marketDetails": { + "message": "Détails du marché" + }, "max": { "message": "Max." }, @@ -2504,6 +2738,9 @@ "maxFee": { "message": "Frais maximaux" }, + "maxFeeTooltip": { + "message": "Frais maximums prévus pour le traitement de la transaction." + }, "maxPriorityFee": { "message": "Frais de priorité maximaux" }, @@ -2551,12 +2788,19 @@ "methodData": { "message": "Méthode" }, + "methodDataTransactionDesc": { + "message": "Fonction exécutée en fonction des données d’entrée décodées." + }, "methodNotSupported": { "message": "Non pris en charge par ce compte." }, "metrics": { "message": "Indicateurs" }, + "millionAbbreviation": { + "message": "M", + "description": "Shortened form of 'million'" + }, "mismatchAccount": { "message": "Le compte sélectionné ($1) est différent du compte que vous essayez de signer ($2)" }, @@ -2669,7 +2913,7 @@ "description": "Description below header used on Permission Connect screen for native permissions." }, "nativeToken": { - "message": "Le jeton natif de ce réseau est $1. C’est le jeton utilisé pour les frais de gaz.\n", + "message": "Le jeton natif de ce réseau est $1. C’est le jeton utilisé pour les frais de gaz. ", "description": "$1 represents the name of the native token on the current network" }, "nativeTokenScamWarningConversion": { @@ -2738,6 +2982,9 @@ "networkNameBase": { "message": "Base" }, + "networkNameBitcoin": { + "message": "Bitcoin" + }, "networkNameDefinition": { "message": "Le nom associé à ce réseau." }, @@ -2762,6 +3009,9 @@ "networkNameZkSyncEra": { "message": "zkSync Era" }, + "networkOptions": { + "message": "Options du réseau" + }, "networkProvider": { "message": "Fournisseur de réseau" }, @@ -2830,6 +3080,9 @@ "newNetworkAdded": { "message": "« $1 » a été ajouté avec succès !" }, + "newNetworkEdited": { + "message": "« $1 » a été modifié !" + }, "newNftAddedMessage": { "message": "Le NFT a été ajouté avec succès !" }, @@ -2866,8 +3119,11 @@ "nftAlreadyAdded": { "message": "Le NFT a déjà été ajouté." }, + "nftAutoDetectionEnabled": { + "message": "Détection automatique des NFT activée" + }, "nftDisclaimer": { - "message": "Avertissement : MetaMask importe le fichier multimédia de l'URL source. Cette URL est parfois modifiée par la place de marché sur laquelle le NFT a été frappé." + "message": "Avertissement : MetaMask importe le fichier multimédia de l'URL source. Cette URL est parfois modifiée par la place de marché sur laquelle le NFT a été minté." }, "nftOptions": { "message": "Options NFT" @@ -2916,6 +3172,9 @@ "noDomainResolution": { "message": "Aucune résolution n’a été fournie pour le domaine." }, + "noHardwareWalletOrSnapsSupport": { + "message": "Les Snaps et la plupart des portefeuilles matériels ne fonctionneront pas avec la version actuelle de votre navigateur." + }, "noNFTs": { "message": "Aucun NFT pour le moment" }, @@ -2946,8 +3205,8 @@ "nonceField": { "message": "Personnaliser le nonce de transaction" }, - "nonceFieldDescription": { - "message": "Activez cette option pour modifier le nonce (numéro de transaction) sur les écrans de confirmation. Il s’agit d’une fonctionnalité avancée, à utiliser avec précaution." + "nonceFieldDesc": { + "message": "Activez cette option pour modifier le nonce (numéro de transaction) lors de l’envoi d’actifs. Il s’agit d’une fonctionnalité avancée que vous devez utiliser avec prudence." }, "nonceFieldHeading": { "message": "Nonce personnalisé" @@ -3149,6 +3408,9 @@ "numberOfNewTokensDetectedSingular": { "message": "1 nouveau jeton trouvé dans ce compte" }, + "numberOfTokens": { + "message": "Nombre de jetons" + }, "ofTextNofM": { "message": "de" }, @@ -3164,8 +3426,36 @@ "on": { "message": "Activé" }, - "onboarding": { - "message": "Intégration" + "onboardedMetametricsAccept": { + "message": "J’accepte" + }, + "onboardedMetametricsDisagree": { + "message": "Non, merci" + }, + "onboardedMetametricsKey1": { + "message": "Dernières avancées" + }, + "onboardedMetametricsKey2": { + "message": "Caractéristiques du produit" + }, + "onboardedMetametricsKey3": { + "message": "Autres matériels promotionnels pertinents" + }, + "onboardedMetametricsLink": { + "message": "MetaMetrics" + }, + "onboardedMetametricsParagraph1": { + "message": "En plus de $1, nous aimerions utiliser les données pour comprendre comment vous interagissez avec les communications commerciales.", + "description": "$1 represents the 'onboardedMetametricsLink' locale string" + }, + "onboardedMetametricsParagraph2": { + "message": "Cela nous aide à personnaliser les informations que nous partageons avec vous, comme :" + }, + "onboardedMetametricsParagraph3": { + "message": "N’oubliez pas que nous ne vendons jamais les données que vous nous fournissez et que vous pouvez vous désinscrire à tout moment." + }, + "onboardedMetametricsTitle": { + "message": "Aidez-nous à améliorer votre expérience utilisateur" }, "onboardingAdvancedPrivacyIPFSDescription": { "message": "La passerelle IPFS vous permet d’accéder aux données hébergées par des tiers et de les visualiser. Vous pouvez ajouter une passerelle IPFS personnalisée ou continuer à utiliser la passerelle par défaut." @@ -3203,9 +3493,6 @@ "onboardingMetametricsDescription2": { "message": "Lorsque nous recueillons des données, elles sont toujours…" }, - "onboardingMetametricsDisagree": { - "message": "Non merci" - }, "onboardingMetametricsInfuraTerms": { "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`" @@ -3240,6 +3527,9 @@ "onboardingMetametricsTitle": { "message": "Aidez-nous à améliorer MetaMask" }, + "onboardingMetametricsUseDataCheckbox": { + "message": "Nous utiliserons ces données pour savoir comment vous interagissez avec nos communications commerciales et pour partager avec vous des informations pertinentes (comme les caractéristiques des produits)." + }, "onboardingPinExtensionBillboardAccess": { "message": "Accès complet" }, @@ -3283,6 +3573,22 @@ "message": "Les alertes de détection d’hameçonnage reposent sur la communication avec $1. jsDeliver aura accès à votre adresse IP. Voir $2.", "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" }, + "oneDayAbbreviation": { + "message": "1 j", + "description": "Shortened form of '1 day'" + }, + "oneMonthAbbreviation": { + "message": "1 mois", + "description": "Shortened form of '1 month'" + }, + "oneWeekAbbreviation": { + "message": "1 sem.", + "description": "Shortened form of '1 week'" + }, + "oneYearAbbreviation": { + "message": "1 an", + "description": "Shortened form of '1 year'" + }, "onekey": { "message": "OneKey" }, @@ -3635,6 +3941,9 @@ "permissionsPageTourTitle": { "message": "Les sites connectés sont maintenant des autorisations" }, + "permitSimulationDetailInfo": { + "message": "Vous autorisez la dépenseur à dépenser ce nombre de jetons de votre compte." + }, "personalAddressDetected": { "message": "Votre adresse personnelle a été détectée. Veuillez saisir à la place l’adresse du contrat du jeton." }, @@ -3667,6 +3976,10 @@ "popularCustomNetworks": { "message": "Réseaux personnalisés populaires" }, + "popularNetworkAddToolTip": { + "message": "Certains de ces réseaux dépendent de services tiers. Ils peuvent être moins fiables ou permettre à des tiers de suivre l’activité des utilisateurs. $1", + "description": "$1 is Learn more link" + }, "portfolio": { "message": "Portefeuille" }, @@ -3679,6 +3992,12 @@ "prev": { "message": "Préc." }, + "price": { + "message": "Prix" + }, + "priceUnavailable": { + "message": "prix non disponible" + }, "primaryCurrencySetting": { "message": "Devise principale" }, @@ -3831,6 +4150,9 @@ "quoteRate": { "message": "Cotation" }, + "rank": { + "message": "Rang" + }, "reAddAccounts": { "message": "ajouter de nouveau tous les autres comptes" }, @@ -4000,9 +4322,6 @@ "reset": { "message": "Reinitialiser" }, - "resetStates": { - "message": "Réinitialiser les états" - }, "resetWallet": { "message": "Réinitialiser le portefeuille" }, @@ -4138,6 +4457,9 @@ "searchAccounts": { "message": "Rechercher des comptes" }, + "searchNfts": { + "message": "Recherche de NFT" + }, "searchTokens": { "message": "Rechercher des jetons" }, @@ -4182,13 +4504,10 @@ "seedPhraseIntroRecommendedButtonCopy": { "message": "Sécuriser mon portefeuille (recommandé)" }, - "seedPhraseIntroSidebarBulletFour": { - "message": "Notez-la et conservez-la dans plusieurs endroits secrets." - }, "seedPhraseIntroSidebarBulletOne": { - "message": "Sauvegarder dans un gestionnaire de mots de passe" + "message": "Notez-la et conservez-la dans plusieurs endroits secrets." }, - "seedPhraseIntroSidebarBulletThree": { + "seedPhraseIntroSidebarBulletTwo": { "message": "Stocker dans un coffre-fort." }, "seedPhraseIntroSidebarCopyOne": { @@ -4258,10 +4577,10 @@ "message": "Sélectionnez un jeton" }, "selectNFTPrivacyPreference": { - "message": "Activez la détection de NFT dans les Paramètres" + "message": "Activez la détection automatique des NFT" }, "selectPathHelp": { - "message": "Si vos comptes n’apparaissent pas ci-dessous, essayez de sélectionner le chemin HD." + "message": "Si vous ne voyez pas les comptes auxquels vous vous attendez, essayez de changer le chemin d’accès au portefeuille HD ou le réseau sélectionné." }, "selectType": { "message": "Sélectionner le type" @@ -4272,9 +4591,6 @@ "send": { "message": "Envoyer" }, - "sendAToken": { - "message": "Envoyer un jeton" - }, "sendBugReport": { "message": "Envoyez-nous un rapport de bogue." }, @@ -4325,9 +4641,6 @@ "sepolia": { "message": "Réseau de test Sepolia" }, - "serviceWorkerKeepAlive": { - "message": "Garder le « Service Worker » activé" - }, "setAdvancedPrivacySettingsDetails": { "message": "MetaMask utilise ces services tiers de confiance pour améliorer la convivialité et la sécurité des produits." }, @@ -4384,6 +4697,9 @@ "showIncomingTransactionsExplainer": { "message": "Ce processus fait appel à différentes API tierces pour chaque réseau et expose ainsi votre adresse Ethereum et votre adresse IP." }, + "showLess": { + "message": "Afficher moins" + }, "showMore": { "message": "Afficher plus" }, @@ -4414,9 +4730,6 @@ "signatureRequestGuidance": { "message": "Ne signez ce message que si vous comprenez parfaitement son contenu et si le site demandeur vous inspire confiance." }, - "signatureRequestWarning": { - "message": "Signer ce message peut être dangereux. Vous risquez de céder le contrôle de tous vos actifs et de votre compte à la personne qui vous a envoyé ce message et si cette personne est malintentionnée, elle pourra vider votre compte à tout moment, alors agissez avec prudence. $1." - }, "signed": { "message": "Signé" }, @@ -4426,6 +4739,9 @@ "signing": { "message": "Signature" }, + "signingInWith": { + "message": "Se connecter avec" + }, "simulationDetailsFailed": { "message": "Une erreur s’est produite lors du chargement de l’estimation." }, @@ -4463,6 +4779,24 @@ "simulationsSettingSubHeader": { "message": "Estimer les changements de solde" }, + "siweIssued": { + "message": "Émis" + }, + "siweNetwork": { + "message": "Réseau" + }, + "siweRequestId": { + "message": "Demander un ID" + }, + "siweResources": { + "message": "Ressources" + }, + "siweSignatureSimulationDetailInfo": { + "message": "Vous êtes en train de vous connecter à un site, aucun changement ne devrait être apporté à votre compte." + }, + "siweURI": { + "message": "URL" + }, "skip": { "message": "Ignorer" }, @@ -4566,6 +4900,14 @@ "snapAccountsDescription": { "message": "Comptes contrôlés par des Snaps tiers." }, + "snapConnectTo": { + "message": "Connexion à $1", + "description": "$1 is the website URL or a Snap name. Used for Snaps pre-approved connections." + }, + "snapConnectionPermissionDescription": { + "message": "Laissez $1 se connecter automatiquement à $2 sans votre approbation.", + "description": "Used for Snap pre-approved connections. $1 is the Snap name, $2 is a website URL." + }, "snapConnectionWarning": { "message": "$1 veut utiliser $2", "description": "$2 is the snap and $1 is the dapp requesting connection to the snap." @@ -4577,6 +4919,9 @@ "snapDetailWebsite": { "message": "Site web" }, + "snapHomeMenu": { + "message": "Menu d’accueil du Snap" + }, "snapInstallRequest": { "message": "L’installation de $1 lui accorde les autorisations suivantes.", "description": "$1 is the snap name." @@ -4699,6 +5044,9 @@ "source": { "message": "Source" }, + "speed": { + "message": "Vitesse" + }, "speedUp": { "message": "Accélérer" }, @@ -4733,6 +5081,9 @@ "spendLimitTooLarge": { "message": "Limite de dépenses trop élevée" }, + "spender": { + "message": "Dépenseur" + }, "spendingCap": { "message": "Plafond de dépenses" }, @@ -4853,9 +5204,6 @@ "stateLogsDescription": { "message": "Les journaux d’état contiennent les adresses publiques de vos comptes et vos transactions envoyées." }, - "states": { - "message": "États" - }, "status": { "message": "État" }, @@ -4960,6 +5308,13 @@ "submitted": { "message": "Envoyé" }, + "suggestedBySnap": { + "message": "Suggéré par $1", + "description": "$1 is the snap name" + }, + "suggestedTokenName": { + "message": "Nom suggéré :" + }, "suggestedTokenSymbol": { "message": "Symbole boursier suggéré :" }, @@ -5074,7 +5429,7 @@ "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." }, "swapFetchingQuotes": { - "message": "Récupération des cotations" + "message": "Récupération des cotations…" }, "swapFetchingQuotesErrorDescription": { "message": "Hum… un problème est survenu. Réessayez et si les erreurs persistent, contactez le service client." @@ -5422,7 +5777,7 @@ "description": "$1 represents the account name, $2 represents the network name" }, "switchedTo": { - "message": "Vous êtes passé à" + "message": "Vous êtes en train d’utiliser" }, "switchingNetworksCancelsPendingConfirmations": { "message": "Le changement de réseau annulera toutes les confirmations en attente" @@ -5470,6 +5825,10 @@ "thisCollection": { "message": "cette collection" }, + "threeMonthsAbbreviation": { + "message": "3 mois", + "description": "Shortened form of '3 months'" + }, "time": { "message": "Temps" }, @@ -5483,45 +5842,6 @@ "message": "Vers : $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleEthSignBannerDescription": { - "message": "Vous êtes vulnérable aux attaques par hameçonnage. Protégez-vous en désactivant eth_sign." - }, - "toggleEthSignDescriptionField": { - "message": "Si vous activez ce paramètre, vous risquez de recevoir des demandes de signature illisibles. En signant un message que vous ne comprenez pas, vous pourriez accepter de céder vos fonds et vos NFTs." - }, - "toggleEthSignField": { - "message": "Requêtes Eth_sign" - }, - "toggleEthSignModalBannerBoldText": { - "message": " il se peut qu’il essaie de vous arnaquer" - }, - "toggleEthSignModalBannerText": { - "message": "Si quelqu’un vous a demandé d’activer ce paramètre," - }, - "toggleEthSignModalCheckBox": { - "message": "Je sais que je peux perdre tous mes fonds et mes NFT si j’active les demandes eth_sign. " - }, - "toggleEthSignModalDescription": { - "message": "L’autorisation des demandes eth_sign peut vous rendre vulnérable aux attaques par hameçonnage. Assurez-vous de l’adresse URL et faites preuve de vigilance avant de signer des messages qui contiennent du code." - }, - "toggleEthSignModalFormError": { - "message": "Le texte est incorrect" - }, - "toggleEthSignModalFormLabel": { - "message": "Tapez « Je ne signe que ce que je comprends » pour continuer" - }, - "toggleEthSignModalFormValidation": { - "message": "Je ne signe que ce que je comprends" - }, - "toggleEthSignModalTitle": { - "message": "Utilisez cette fonctionnalité à vos risques et périls" - }, - "toggleEthSignOff": { - "message": "Désactiver (recommandé)" - }, - "toggleEthSignOn": { - "message": "Activer (recommandé)" - }, "toggleRequestQueueDescription": { "message": "Cette fonction vous permet de sélectionner un réseau pour chaque site au lieu d’un seul réseau pour tous les sites. Vous n’aurez donc pas à changer manuellement de réseau, ce qui pourrait nuire à l’expérience utilisateur sur certains sites." }, @@ -5549,6 +5869,9 @@ "tokenContractAddress": { "message": "Adresse du contrat de jeton" }, + "tokenDecimal": { + "message": "Nombre de décimales du jeton" + }, "tokenDecimalFetchFailed": { "message": "La décimale du jeton est requise. Trouvez-la sur : $1" }, @@ -5565,13 +5888,16 @@ "message": "ID de token" }, "tokenList": { - "message": "Listes de tokens :" + "message": "Listes de jetons" }, "tokenScamSecurityRisk": { "message": "les arnaques et les risques de piratage informatique" }, "tokenShowUp": { - "message": "Vos tokens n’apparaîtront peut-être pas automatiquement dans votre portefeuille." + "message": "Il se peut que vos jetons n’apparaissent pas automatiquement dans votre portefeuille. " + }, + "tokenStandard": { + "message": "Jeton standard" }, "tokenSymbol": { "message": "Symbole du jeton" @@ -5583,6 +5909,9 @@ "message": "$1 nouveaux jetons trouvés", "description": "$1 is the number of new tokens detected" }, + "tokensInCollection": { + "message": "Jetons dans la collection" + }, "tooltipApproveButton": { "message": "Je comprends" }, @@ -5598,6 +5927,9 @@ "total": { "message": "Total" }, + "totalVolume": { + "message": "Volume total" + }, "transaction": { "message": "transaction" }, @@ -5613,6 +5945,9 @@ "transactionCreated": { "message": "Transaction créée avec une valeur de $1 sur $2." }, + "transactionDataFunction": { + "message": "Fonction" + }, "transactionDetailDappGasMoreInfo": { "message": "Site suggéré" }, @@ -5703,6 +6038,10 @@ "transferFrom": { "message": "Transfert depuis" }, + "trillionAbbreviation": { + "message": "B", + "description": "Shortened form of 'trillion'" + }, "troubleConnectingToLedgerU2FOnFirefox": { "message": "Nous avons des difficultés à connecter votre Ledger. $1", "description": "$1 is a link to the wallet connection guide;" @@ -5781,6 +6120,9 @@ "message": "U2F", "description": "A name on an API for the browser to interact with devices that support the U2F protocol. On some browsers we use it to connect MetaMask to Ledger devices." }, + "unMatchedChain": { + "message": "Selon nos informations, cette URL ne correspond pas à celle d’un fournisseur connu pour cet ID de chaîne." + }, "unapproved": { "message": "Non autorisé" }, @@ -5832,12 +6174,21 @@ "update": { "message": "Mise à jour" }, + "updateOrEditNetworkInformations": { + "message": "Mettez à jour vos informations ou" + }, "updateRequest": { "message": "Demande de mise à jour" }, "updatedWithDate": { "message": "Mis à jour $1" }, + "uploadDropFile": { + "message": "Déposez votre fichier ici" + }, + "uploadFile": { + "message": "Télécharger le fichier" + }, "urlErrorMsg": { "message": "Les URLs requièrent un préfixe HTTP/HTTPS approprié." }, @@ -6053,6 +6404,12 @@ "whatsThis": { "message": "Qu’est-ce que c’est ?" }, + "wrongChainId": { + "message": "Cet ID de chaîne ne correspond pas au nom du réseau." + }, + "wrongNetworkName": { + "message": "Selon nos informations, il se peut que le nom du réseau ne corresponde pas exactement à l’ID de chaîne." + }, "xOfYPending": { "message": "$1 sur $2 en attente", "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" @@ -6076,8 +6433,11 @@ "yourAccounts": { "message": "Vos comptes" }, - "yourFundsMayBeAtRisk": { - "message": "Vous risquez de perdre une partie ou la totalité du capital investi" + "yourActivity": { + "message": "Votre activité" + }, + "yourBalance": { + "message": "Votre solde" }, "yourNFTmayBeAtRisk": { "message": "Il peut y avoir des risques associés à votre NFT" diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json index 9d8321abc7e4..1c1e9363d761 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -42,7 +42,7 @@ "message": "अपने QR hardware wallet को कनेक्ट करें" }, "QRHardwareWalletSteps2Description": { - "message": "Ngrave (जल्द आ रहा है)" + "message": "Ngrave Zero" }, "SIWEAddressInvalid": { "message": "साइन-इन रिक्वेस्ट का एड्रेस उस अकाउंट के एड्रेस से मेल नहीं खाता जिसका इस्तेमाल आप साइन इन करने के लिए कर रहे हैं।" @@ -162,6 +162,9 @@ "accountSelectionRequired": { "message": "आपको एक अकाउंट चुनना होगा!" }, + "accountTypeNotSupported": { + "message": "खाता प्रकार सपोर्ट नहीं करता" + }, "accounts": { "message": "अकाउंट्स" }, @@ -277,6 +280,12 @@ "addNewAccount": { "message": "एक नया Ethereum अकाउंट जोड़ें" }, + "addNewBitcoinAccount": { + "message": "एक नया Bitcoin अकाउंट जोड़ें (बीटा)" + }, + "addNewBitcoinTestnetAccount": { + "message": "एक नया Bitcoin अकाउंट जोड़ें (टैस्टनेट (testnet))" + }, "addNewToken": { "message": "नया टोकन जोड़ें" }, @@ -286,6 +295,9 @@ "addNfts": { "message": "NFTs जोड़ें" }, + "addRpcUrl": { + "message": "RPC URL जोड़ें" + }, "addSnapAccountToggle": { "message": "\"अकाउंट Snap जोड़ें (बीटा)\" को चालू करें" }, @@ -305,12 +317,21 @@ "message": "टोकन नहीं मिल रहा है? आप किसी भी टोकन का एड्रेस पेस्ट करके उसे मैन्युअल रूप से भी जोड़ सकते हैं। टोकन कॉन्ट्रैक्ट एड्रेस $1 पर मिल सकते हैं।", "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" }, + "addUrl": { + "message": "URL जोड़ें" + }, "addingCustomNetwork": { "message": "नेटवर्क जोड़ रहे हैं" }, "addingTokens": { "message": "टोकन जोड़ना" }, + "additionalNetworks": { + "message": "अतिरिक्त नेटवर्क" + }, + "additionalRpcUrl": { + "message": "अतिरिक्त RPC URL" + }, "address": { "message": "एड्रेस" }, @@ -326,6 +347,18 @@ "advancedConfiguration": { "message": "एडवांस्ड कॉन्फ़िगरेशन" }, + "advancedDetailsDataDesc": { + "message": "डेटा" + }, + "advancedDetailsHexDesc": { + "message": "Hex" + }, + "advancedDetailsNonceDesc": { + "message": "Nonce" + }, + "advancedDetailsNonceTooltip": { + "message": "यह किसी अकाउंट का ट्रांसेक्शन नंबर है। पहले ट्रांसेक्शन के लिए nonce 0 है और यह क्रमिक क्रम में बढ़ता है।" + }, "advancedGasFeeDefaultOptIn": { "message": "$1 नेटवर्क के लिए इन मूल्यों को मेरे डिफॉल्ट के रूप में सहेजें।", "description": "$1 is the current network name." @@ -349,6 +382,18 @@ "alert": { "message": "एलर्ट" }, + "alertActionBuy": { + "message": "ETH खरीदें" + }, + "alertActionUpdateGas": { + "message": "गैस लिमिट को अपग्रेड करें" + }, + "alertActionUpdateGasFee": { + "message": "शुल्क अपडेट करें" + }, + "alertActionUpdateGasFeeLevel": { + "message": "गैस के विकल्प को अपडेट करें" + }, "alertBannerMultipleAlertsDescription": { "message": "यदि आप इस रिक्वेस्ट को एप्रूव करते हैं, तो स्कैम के लिए मशहूर कोई थर्ड पार्टी आपके सारे एसेट चुरा सकती है।" }, @@ -358,6 +403,36 @@ "alertDisableTooltip": { "message": "इसे \"सेटिंग > अलर्ट\" में बदला जा सकता है" }, + "alertMessageGasEstimateFailed": { + "message": "हम सटीक शुल्क प्रदान करने में असमर्थ हैं और यह अनुमान अधिक हो सकता है। हम आपको एक कस्टम गैस लिमिट दर्ज करने का सुझाव देते हैं, लेकिन जोखिम है कि ट्रांसेक्शन अभी भी विफल हो जाएगा।" + }, + "alertMessageGasFeeLow": { + "message": "कम शुल्क चुनते समय, धीमे ट्रांसेक्शन और लंबे समय तक प्रतीक्षा करने की अपेक्षा करें। तेज़ ट्रांसेक्शन के लिए, मार्केट या एग्रेसिव शुल्क के विकल्प चुनें।" + }, + "alertMessageGasTooLow": { + "message": "इस ट्रांसेक्शन को जारी रखने के लिए, आपको गैस लिमिट को 21000 या अधिक तक बढ़ाना होगा।" + }, + "alertMessageInsufficientBalance": { + "message": "ट्रांसेक्शन फीस का भुगतान करने के लिए आपके अकाउंट में पर्याप्त ETH नहीं है।" + }, + "alertMessageNetworkBusy": { + "message": "गैस प्राइसें अधिक हैं और अनुमान कम सटीक हैं।" + }, + "alertMessageNoGasPrice": { + "message": "जब तक आप शुल्क को मैन्युअल रूप से अपडेट नहीं करते, हम इस ट्रांसेक्शन को आगे नहीं बढ़ा सकते।" + }, + "alertMessagePendingTransactions": { + "message": "यह ट्रांसेक्शन तब तक नहीं होगा जब तक पिछला ट्रांसेक्शन पूरा न हो जाए। किसी ट्रांसेक्शन को रद्द करने या तेज़ करने का तरीका जानें।" + }, + "alertMessageSignInDomainMismatch": { + "message": "अनुरोध करने वाली साइट वह साइट नहीं है जिस पर आप साइन इन कर रहे हैं। यह आपके लॉगिन क्रेडेंशियल चुराने का प्रयास हो सकता है।" + }, + "alertMessageSignInWrongAccount": { + "message": "यह साइट आपसे गलत अकाउंट का उपयोग करके साइन इन करने के लिए कह रही है।" + }, + "alertMessageSigningOrSubmitting": { + "message": "यह ट्रांसेक्शन तभी पूरा होगा जब आपका पिछला ट्रांसेक्शन पूरा हो जाएगा।" + }, "alertModalAcknowledge": { "message": "मैंने जोखिम को स्वीकार कर लिया है और इसके बावजूद आगे बढ़ना चाहता/चाहती हूं" }, @@ -367,6 +442,33 @@ "alertModalReviewAllAlerts": { "message": "सभी एलर्ट की समीक्षा करें" }, + "alertReasonGasEstimateFailed": { + "message": "गलत शुल्क" + }, + "alertReasonGasFeeLow": { + "message": "धीमी गति" + }, + "alertReasonGasTooLow": { + "message": "कम गैस लिमिट" + }, + "alertReasonInsufficientBalance": { + "message": "अपर्याप्त फंड" + }, + "alertReasonNetworkBusy": { + "message": "नेटवर्क व्यस्त है" + }, + "alertReasonNoGasPrice": { + "message": "शुल्क अनुमान अनुपलब्ध है" + }, + "alertReasonPendingTransactions": { + "message": "अभी तक पूरा नहीं हुआ ट्रांसेक्शन" + }, + "alertReasonSignIn": { + "message": "संदिग्ध साइन-इन अनुरोध" + }, + "alertReasonWrongAccount": { + "message": "गलत अकाउंट" + }, "alertSettingsUnconnectedAccount": { "message": "जो कनेक्टेड नहीं है वह अकाउंट चुनकर कोई वेबसाइट ब्राउज़ करना" }, @@ -398,6 +500,12 @@ "allPermissions": { "message": "सभी अनुमतियां" }, + "allTimeHigh": { + "message": "अब तक के सबसे ऊँचे स्तर पर" + }, + "allTimeLow": { + "message": "अब तक के सबसे निचले स्तर पर" + }, "allYourNFTsOf": { "message": "आपके सभी NFTs $1 से शुरू", "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" @@ -436,9 +544,6 @@ "message": "$1 और $2", "description": "$1 is the first item, $2 is the second item. Used in Snap Install Warning modal." }, - "announcements": { - "message": "घोषणाएं" - }, "appDescription": { "message": "आपके ब्राउज़र में एक Ethereum वॉलेट", "description": "The description of the application" @@ -524,9 +629,15 @@ "attemptToCancelSwapForFree": { "message": "स्वैप को मुफ्त में कैंसिल करने की कोशिश करें" }, + "attributes": { + "message": "विशेषताएं" + }, "attributions": { "message": "एट्रीब्यूशन्स" }, + "auroraRpcDeprecationMessage": { + "message": "Infura RPC URL अब Aurora को सपोर्ट नहीं कर रहा है।" + }, "authorizedPermissions": { "message": "आपने निम्नलिखित अनुमतियों को अधिकृत किया है" }, @@ -591,6 +702,10 @@ "basicConfigurationBannerTitle": { "message": "बेसिक फंक्शनलिटी बंद है" }, + "basicConfigurationDescription": { + "message": "MetaMask इंटरनेट सेवाओं के माध्यम से टोकन विवरण और गैस सेटिंग्स जैसी बुनियादी सुविधाएं प्रदान करता है। जब आप इंटरनेट सेवाओं का उपयोग करते हैं, तो आपका आईपी ​​एड्रेस साझा किया जाता है, इस मामले में MetaMask के साथ। यह बिल्कुल वैसा ही है जैसे आप किसी वेबसाइट पर जाते हैं। MetaMask इस डेटा का अस्थायी रूप से उपयोग करता है और कभी भी आपका डेटा नहीं बेचता है। आप वीपीएन का उपयोग कर सकते हैं या इन सेवाओं को बंद कर सकते हैं, लेकिन यह आपके MetaMask अनुभव को प्रभावित कर सकता है। अधिक जानने के लिए हमारी $1 पढ़ें।", + "description": "$1 is to be replaced by the message for privacyMsg, and will link to https://consensys.io/privacy-policy" + }, "basicConfigurationLabel": { "message": "बेसिक फंक्शनलिटी" }, @@ -634,6 +749,29 @@ "betaWalletCreationSuccessReminder2": { "message": "MetaMask बीटा आपसे आपका गुप्त रिकवरी वाक्यांश कभी नहीं मांगेगा।" }, + "billionAbbreviation": { + "message": "B", + "description": "Shortened form of 'billion'" + }, + "bitcoinActivityNotSupported": { + "message": "Bitcoin गतिविधि सपोर्ट नहीं करती है" + }, + "bitcoinSupportSectionTitle": { + "message": "Bitcoin" + }, + "bitcoinSupportToggleDescription": { + "message": "इस सुविधा को चालू करने से आपको अपने मौजूदा सीक्रेट रिकवरी फ्रेज़ से प्राप्त MetaMask एक्सटेंशन में एक Bitcoin अकाउंट जोड़ने का विकल्प मिलेगा। यह एक एक्सपेरिमेंटल बीटा सुविधा है, इसलिए आपको इसका उपयोग अपने जोखिम पर करना होगा। इस नए Bitcoin अनुभव पर हमें प्रतिक्रिया देने के लिए, कृपया यह $1 भरें।", + "description": "$1 is the link to a product feedback form" + }, + "bitcoinSupportToggleTitle": { + "message": "\"एक नया Bitcoin अकाउंट जोड़ें (बीटा)\" को चालू करें" + }, + "bitcoinTestnetSupportToggleDescription": { + "message": "इस सुविधा को चालू करने से आपको परीक्षण नेटवर्क के लिए एक Bitcoin अकाउंट जोड़ने का विकल्प मिलेगा।" + }, + "bitcoinTestnetSupportToggleTitle": { + "message": "\"एक नया Bitcoin अकाउंट जोड़ें (टैस्टनेट (testnet))\" को चालू करें" + }, "blockExplorerAccountAction": { "message": "अकाउंट", "description": "This is used with viewOnEtherscan and viewInExplorer e.g View Account in Explorer" @@ -659,6 +797,9 @@ "blockaid": { "message": "Blockaid" }, + "blockaidAlertInfo": { + "message": "हम इस अनुरोध के साथ आगे बढ़ने का सुझाव नहीं देते।" + }, "blockaidDescriptionApproveFarming": { "message": "यदि आप इस रिक्वेस्ट को स्वीकार करते हैं, तो स्कैम के लिए मशहूर कोई थर्ड पार्टी आपके सारे एसेट चुरा सकती है।" }, @@ -680,6 +821,9 @@ "blockaidDescriptionTransferFarming": { "message": "यदि आप इस रिक्वेस्ट को स्वीकार करते हैं, तो स्कैम के लिए मशहूर कोई थर्ड पार्टी आपके सारे एसेट चुरा सकती है।" }, + "blockaidDescriptionWarning": { + "message": "यह एक भ्रामक अनुरोध हो सकता है। केवल तभी जारी रखें जब आपको इसमें शामिल प्रत्येक एड्रेस पर भरोसा हो।" + }, "blockaidMessage": { "message": "गोपनीयता को सुरक्षित रखना - कोई भी डेटा थर्ड पार्टी के साथ साझा नहीं किया जाता है। Arbitrum, Avalanche, BNB chain, Ethereum Mainnet, Linea, Optimism, Polygon, Base और Sepolia पर उपलब्ध है।" }, @@ -695,6 +839,9 @@ "blockies": { "message": "ब्लॉकीज़" }, + "boughtFor": { + "message": "के लिए खरीदा गया" + }, "bridge": { "message": "ब्रिज" }, @@ -779,6 +926,9 @@ "chromeRequiredForHardwareWallets": { "message": "अपने hardware wallet से कनेक्ट करने के लिए आपको Google Chrome पर MetaMask का इस्तेमाल करने की आवश्यकता है।" }, + "circulatingSupply": { + "message": "सप्लाई सर्कुलेट किया जा रहा है" + }, "clear": { "message": "हटाएं" }, @@ -813,6 +963,9 @@ "coingecko": { "message": "CoinGecko" }, + "collectionName": { + "message": "संग्रह का नाम" + }, "comboNoOptions": { "message": "कोई विकल्प नहीं मिला", "description": "Default text shown in the combo field dropdown if no options." @@ -838,6 +991,9 @@ "confirmAlertModalAcknowledgeMultiple": { "message": "मैंने एलर्ट को स्वीकार कर लिया है और इसके बावजूद आगे बढ़ना चाहता/चाहती हूं" }, + "confirmAlertModalAcknowledgeSingle": { + "message": "मैंने एलर्ट को स्वीकार कर लिया है और इसके बावजूद आगे बढ़ना चाहता/चाहती हूं" + }, "confirmAlertModalDetails": { "message": "यदि आप साइन इन करते हैं, तो स्कैम के लिए मशहूर कोई थर्ड पार्टी आपके सारे एसेट चुरा सकती है। कृपया आगे बढ़ने से पहले एलर्ट की समीक्षा करें।" }, @@ -853,18 +1009,42 @@ "confirmConnectionTitle": { "message": "$1 से कनेक्शन को कन्फर्म करें" }, + "confirmDeletion": { + "message": "हटाना कन्फर्म करें" + }, + "confirmFieldPaymaster": { + "message": "के द्वारा शुल्क का भुगतान किया गया" + }, + "confirmFieldTooltipPaymaster": { + "message": "इस ट्रांसेक्शन के लिए शुल्क का भुगतान पेमास्टर स्मार्ट कॉन्ट्रैक्ट द्वारा किया जाएगा।" + }, "confirmPassword": { "message": "पासवर्ड कन्फर्म करें" }, "confirmRecoveryPhrase": { "message": "सीक्रेट रिकवरी फ्रेज कन्फर्म करें" }, + "confirmRpcUrlDeletionMessage": { + "message": "क्या आप वाकई RPC URL को हटाना चाहते हैं? आपकी जानकारी इस नेटवर्क के लिए सेव नहीं की जाएगी।" + }, "confirmTitleDescContractInteractionTransaction": { "message": "यदि आप कंटेंट को पूरी तरह से समझते हैं और अनुरोध करने वाली साइट पर भरोसा करते हैं तो ही इस ट्रांसेक्शन को कन्फर्म करें" }, + "confirmTitleDescPermitSignature": { + "message": "यह साइट आपके टोकन खर्च करने की अनुमति चाहती है।" + }, + "confirmTitleDescSIWESignature": { + "message": "एक साइट चाहती है कि आप यह साबित करने के लिए साइन इन करें कि यह आपका अकाउंट है।" + }, "confirmTitleDescSignature": { "message": "इस संदेश को केवल तभी कन्फर्म करें जब आप कंटेंट को एप्रूव करते हैं और अनुरोध करने वाली साइट पर भरोसा करते हैं।" }, + "confirmTitlePermitSignature": { + "message": "खर्च करने की सीमा का अनुरोध" + }, + "confirmTitleSIWESignature": { + "message": "साइन-इन अनुरोध" + }, "confirmTitleSignature": { "message": "सिग्नेचर अनुरोध" }, @@ -1091,6 +1271,9 @@ "createSnapAccountTitle": { "message": "अकाउंट बनाएं" }, + "creatorAddress": { + "message": "निर्माता का एड्रेस" + }, "crossChainSwapsLink": { "message": "MetaMask पोर्टफोलियो के साथ पूरे नेटवर्क में कहीं भी स्वैप करें" }, @@ -1260,9 +1443,27 @@ "data": { "message": "डेटा" }, + "dataCollectionForMarketing": { + "message": "विपणन के लिए डेटा संग्रह" + }, + "dataCollectionForMarketingDescription": { + "message": "आप हमारे मार्केटिंग कम्यूनिकेशन्स के साथ कैसे इंटरैक्ट करते हैं, यह जानने के लिए हम MetaMetrics का उपयोग करेंगे। हम प्रासंगिक समाचार (जैसे प्रॉडक्ट फीचर्स और अन्य सामग्री) साझा कर सकते हैं।" + }, + "dataCollectionWarningPopoverButton": { + "message": "ठीक है" + }, + "dataCollectionWarningPopoverDescription": { + "message": "आपने हमारे मार्केटिंग उद्देश्यों के लिए डेटा संग्रहण बंद कर दिया है।  यह केवल इस डिवाइस पर लागू होता है। यदि आप अन्य डिवाइसों पर MetaMask का उपयोग करते हैं, तो वहां भी ऑप्ट आउट करना सुनिश्चित करें।" + }, "dataHex": { "message": "हेक्स" }, + "dataUnavailable": { + "message": "डेटा अनुपलब्ध है" + }, + "dateCreated": { + "message": "बनाने की तारीख" + }, "dcent": { "message": "D'Cent" }, @@ -1292,6 +1493,9 @@ "decryptRequest": { "message": "रिक्वेस्ट डिक्रिप्ट करें" }, + "defaultRpcUrl": { + "message": "डिफॉल्ट RPC URL" + }, "delete": { "message": "मिटाएं" }, @@ -1308,6 +1512,9 @@ "message": "$1 नेटवर्क को हटाएं?", "description": "$1 represents the name of the network" }, + "deleteRpcUrl": { + "message": "RPC URL को हटाएं" + }, "deposit": { "message": "डिपॉज़िट करें" }, @@ -1333,18 +1540,6 @@ "details": { "message": "विस्तृत जानकारी" }, - "developerOptions": { - "message": "डेवलपर विकल्प" - }, - "developerOptionsResetStatesAnnouncementsDescription": { - "message": "सभी घोषणाओं के लिए isShown boolean को गलत पर रीसेट करता है। घोषणाएं व्हाट्स न्यू पॉपअप मोडल में दिखाई जाने वाली सूचनाएं हैं।" - }, - "developerOptionsResetStatesOnboarding": { - "message": "ऑनबोर्डिंग से संबंधित विभिन्न स्टेट को रीसेट करता है और \"सिक्योर योर वॉलेट\" ऑनबोर्डिंग पेज पर रीडायरेक्ट करता है।" - }, - "developerOptionsServiceWorkerKeepAlive": { - "message": "हर टाइमस्टैम्प के परिणाम को लगातार session.storage में सेव किया जा रहा है" - }, "disabledGasOptionToolTipMessage": { "message": "\"$1\" डिसेबल किया गया है क्योंकि यह ओरिजिनल गैस फ़ीस से कम-से-कम 10% वृद्धि को पूरा नहीं करता है।", "description": "$1 is gas estimate type which can be market or aggressive" @@ -1521,6 +1716,9 @@ "editGasTooLow": { "message": "अनजाना प्रोसेसिंग टाइम" }, + "editNetworkLink": { + "message": "मूल नेटवर्क को संपादित करें" + }, "editNonceField": { "message": "Nonce बदलें" }, @@ -1552,6 +1750,9 @@ "enabled": { "message": "इनेबल किया गया" }, + "enabledNetworks": { + "message": "चालू किए गए नेटवर्क" + }, "encryptionPublicKeyNotice": { "message": "$1 आपकी पब्लिक एन्क्रिप्शन की (key) चाहता है। सहमति देने पर, यह साइट आपके लिए एन्क्रिप्ट किए गए मैसेज लिख पाएगी।", "description": "$1 is the web3 site name" @@ -1656,6 +1857,9 @@ "estimatedFee": { "message": "अनुमानित फ़ीस" }, + "estimatedFeeTooltip": { + "message": "नेटवर्क पर ट्रांसेक्शन को प्रोसेस करने के लिए भुगतान की गई राशि।" + }, "ethGasPriceFetchWarning": { "message": "बैकअप गैस प्राइस दिया गया है क्योंकि मेन गैस एस्टीमेशन सर्विस अभी उपलब्ध नहीं है।" }, @@ -1675,6 +1879,12 @@ "etherscanViewOn": { "message": "Etherscan पर देखें" }, + "existingChainId": { + "message": "आपके द्वारा दर्ज की गई जानकारी मौजूदा चेन ID से जुड़ी है।" + }, + "existingRpcUrl": { + "message": "यह URL किसी अन्य चेन ID से जुड़ा है।" + }, "expandView": { "message": "व्यू को बड़ा करें" }, @@ -1698,7 +1908,7 @@ "message": "प्रस्तावित उपनाम" }, "externalNameSourcesSettingDescription": { - "message": "हम Etherscan, Infura और लेंस प्रोटोकॉल जैसे थर्ड पार्टी स्रोतों से उन एड्रेसों के लिए प्रस्तावित उपनाम लाएंगे जिनके साथ आप इंटरैक्ट करते हैं। ये स्रोत उन एड्रेसों और आपके आईपी एड्रेस को देख सकेंगे। आपके अकाउंट का एड्रेस थर्ड पार्टी के सामने नहीं आएगा।" + "message": "हम Etherscan, Infura और Lens Protocol जैसे थर्ड पार्टी सोर्सों से उन एड्रेसों के लिए प्रस्तावित उपनाम लाएंगे जिनके साथ आप इंटरैक्ट करते हैं। ये सोर्स उन एड्रेसों और आपके आईपी एड्रेस को देख सकेंगे। आपके अकाउंट का एड्रेस थर्ड पार्टी के सामने नहीं आएगा।" }, "failed": { "message": "नहीं हो पाया" @@ -1729,6 +1939,9 @@ "message": "फाइल इम्पोर्ट काम नहीं कर रहा है? यहां क्लिक करें!", "description": "Helps user import their account from a JSON file" }, + "findTheRightChainId": { + "message": "यहां पर सही खोजें:" + }, "flaskWelcomeUninstall": { "message": "आपको इस एक्सटेन्शन को अनइंस्टाल करना चाहिए", "description": "This request is shown on the Flask Welcome screen. It is intended for non-developers, and will be bolded." @@ -1768,6 +1981,9 @@ "forgotPassword": { "message": "पासवर्ड भूल गए?" }, + "form": { + "message": "फॉर्म" + }, "from": { "message": "भेजने वाले" }, @@ -1976,6 +2192,12 @@ "highLowercase": { "message": "हाई" }, + "highestCurrentBid": { + "message": "सबसे बड़ी वर्तमान बिड" + }, + "highestFloorPrice": { + "message": "सबसे बड़ी फ्लोर प्राइस" + }, "history": { "message": "इतिहास" }, @@ -2315,12 +2537,21 @@ "knownTokenWarning": { "message": "यह कार्रवाई उन टोकन को संपादित करेगी, जो पहले से ही आपके वॉलेट में लिस्टेड हैं, जिसका इस्तेमाल आपको फ़िश करने के लिए किया जा सकता है। केवल तभी एप्रूव करें, जब आप इस बात को लेकर सुनिश्चित हों कि आप इन टोकन का प्रतिनिधित्व बदलना चाहते हैं। $1 के बारे में और अधिक जानें" }, + "l1Fee": { + "message": "L1 शुल्क" + }, + "l1FeeTooltip": { + "message": "L1 गैस फीस" + }, + "l2Fee": { + "message": "L2 शुल्क" + }, + "l2FeeTooltip": { + "message": "L2 गैस फीस" + }, "lastConnected": { "message": "अंतिम बार जुड़ा" }, - "lastPriceSold": { - "message": "पिछली बार की बिक्री दर" - }, "lastSold": { "message": "पिछली बार बेचा गया" }, @@ -2492,6 +2723,12 @@ "message": "पक्का करें कि इसे कोई भी नहीं देख रहा है", "description": "Warning to users to be care while creating and saving their new Secret Recovery Phrase" }, + "marketCap": { + "message": "मार्केट कैप" + }, + "marketDetails": { + "message": "मार्केट का ब्यौरा" + }, "max": { "message": "अधिकतम" }, @@ -2501,6 +2738,9 @@ "maxFee": { "message": "अधिकतम फ़ीस" }, + "maxFeeTooltip": { + "message": "ट्रांसेक्शन के भुगतान के लिए अधिकतम शुल्क प्रदान किया गया।" + }, "maxPriorityFee": { "message": "अधिकतम प्रायोरिटी फी" }, @@ -2548,12 +2788,19 @@ "methodData": { "message": "तरीका" }, + "methodDataTransactionDesc": { + "message": "डिकोड किए गए इनपुट डेटा के आधार पर फ़ंक्शन पूरा किया गया।" + }, "methodNotSupported": { "message": "इस अकाउंट के साथ सपोर्ट नहीं करता है।" }, "metrics": { "message": "मेट्रिक्स" }, + "millionAbbreviation": { + "message": "M", + "description": "Shortened form of 'million'" + }, "mismatchAccount": { "message": "आपका चुना गया अकाउंट ($1) साइन-इन करने की कोशिश करने वाले अकाउंट ($2) से अलग है" }, @@ -2735,6 +2982,9 @@ "networkNameBase": { "message": "बेस" }, + "networkNameBitcoin": { + "message": "Bitcoin" + }, "networkNameDefinition": { "message": "इस नेटवर्क के साथ जुड़ा नाम।" }, @@ -2759,6 +3009,9 @@ "networkNameZkSyncEra": { "message": "zkSync Era" }, + "networkOptions": { + "message": "नेटवर्क के विकल्प" + }, "networkProvider": { "message": "नेटवर्क प्रोवाइडर" }, @@ -2827,6 +3080,9 @@ "newNetworkAdded": { "message": "\"$1\" सफलतापूर्वक जोड़ा गया था!" }, + "newNetworkEdited": { + "message": "\"$1\" सफलतापूर्वक बदला गया!" + }, "newNftAddedMessage": { "message": "NFT सफलतापूर्वक जोड़ा गया!" }, @@ -2863,6 +3119,9 @@ "nftAlreadyAdded": { "message": "NFT पहले ही जोड़ा जा चुका है।" }, + "nftAutoDetectionEnabled": { + "message": "NFT ऑटोडिटेक्शन चालू किया गया" + }, "nftDisclaimer": { "message": "अस्वीकरण: MetaMask मीडिया फ़ाइल को सोर्स url से खींचता है। यह url कभी-कभी मार्केटप्लेस द्वारा बदल दिया जाता है जिस पर NFT को मिंट किया गया था।" }, @@ -2913,6 +3172,9 @@ "noDomainResolution": { "message": "डोमेन के लिए कोई रिज़ॉल्यूशन प्रदान नहीं किया गया।" }, + "noHardwareWalletOrSnapsSupport": { + "message": "Snap और अधिकांश हार्डवेयर वॉलेट, आपके वर्तमान ब्राउज़र वर्जन के साथ काम नहीं करेंगे।" + }, "noNFTs": { "message": "अभी तक कोई NFT नहीं" }, @@ -2943,8 +3205,8 @@ "nonceField": { "message": "ट्रांसेक्शन Nonce कस्टमाइज़ करें" }, - "nonceFieldDescription": { - "message": "कन्फर्मेशन स्क्रीन पर Nonce (ट्रांसेक्शन संख्या) को बदलने के लिए इसे चालू करें। यह एक एडवांस्ड सुविधा है, सावधानी से इस्तेमाल करें।" + "nonceFieldDesc": { + "message": "एसेट भेजते समय Nonce (ट्रांसेक्शन संख्या) बदलने के लिए इसे चालू करें। यह एक एडवांस्ड सुविधा है, सावधानी से उपयोग करें।" }, "nonceFieldHeading": { "message": "कस्टम Nonce" @@ -3146,6 +3408,9 @@ "numberOfNewTokensDetectedSingular": { "message": "इस अकाउंट में 1 नया टोकन पाया गया" }, + "numberOfTokens": { + "message": "टोकन की संख्या" + }, "ofTextNofM": { "message": "का" }, @@ -3161,8 +3426,36 @@ "on": { "message": "चालू" }, - "onboarding": { - "message": "ऑनबोर्ड हो रहा है" + "onboardedMetametricsAccept": { + "message": "मैं सहमत हूं" + }, + "onboardedMetametricsDisagree": { + "message": "जी नहीं, धन्यवाद" + }, + "onboardedMetametricsKey1": { + "message": "लेटेस्ट डेवलप्मेंट्स" + }, + "onboardedMetametricsKey2": { + "message": "प्रॉडक्ट फीचर्स" + }, + "onboardedMetametricsKey3": { + "message": "अन्य प्रासंगिक प्रमोशनल सामग्री" + }, + "onboardedMetametricsLink": { + "message": "MetaMetrics" + }, + "onboardedMetametricsParagraph1": { + "message": "$1 के अलावा, हम यह समझने के लिए डेटा का उपयोग करना चाहेंगे कि आप मार्केटिंग कम्यूनिकेशन्स के साथ कैसे इंटरैक्ट करते हैं।", + "description": "$1 represents the 'onboardedMetametricsLink' locale string" + }, + "onboardedMetametricsParagraph2": { + "message": "इससे हमें आपके साथ साझा की जाने वाली चीज़ों को निजीकृत करने में मदद मिलती है, जैसे:" + }, + "onboardedMetametricsParagraph3": { + "message": "याद रखें, हम आपके द्वारा प्रदान किया गया डेटा कभी नहीं बेचते हैं और आप किसी भी समय इससे बाहर निकल सकते हैं।" + }, + "onboardedMetametricsTitle": { + "message": "आपके अनुभव को बेहतर करने में हमारी सहायता करें" }, "onboardingAdvancedPrivacyIPFSDescription": { "message": "IPFS (आईपीएफएस) गेटवे थर्ड पार्टियों द्वारा होस्ट किए गए डेटा को एक्सेस करना और देखना संभव बनाता है। आप एक कस्टम IPFS गेटवे जोड़ सकते हैं या डिफॉल्ट का इस्तेमाल जारी रख सकते हैं।" @@ -3200,9 +3493,6 @@ "onboardingMetametricsDescription2": { "message": "जब हम मेट्रिक्स इकट्ठा करते हैं, तो यह हमेशा... रहेगा" }, - "onboardingMetametricsDisagree": { - "message": "जी नहीं, धन्यवाद" - }, "onboardingMetametricsInfuraTerms": { "message": "यदि हम इस डेटा का उपयोग अन्य उद्देश्यों के लिए करने का निर्णय लेते हैं तो हम आपको बताएंगे। अधिक जानकारी के लिए आप हमारे $1 की समीक्षा कर सकते हैं। याद रखें, आप किसी भी समय सेटिंग्स में जाकर ऑप्ट आउट कर सकते हैं।", "description": "$1 represents `onboardingMetametricsInfuraTermsPolicy`" @@ -3237,6 +3527,9 @@ "onboardingMetametricsTitle": { "message": "MetaMask को बेहतर बनाने में हमारी मदद करें" }, + "onboardingMetametricsUseDataCheckbox": { + "message": "हम इस डेटा का उपयोग यह जानने के लिए करेंगे कि आप हमारे मार्केटिंग कम्यूनिकेशन्स के साथ कैसे इंटरैक्ट करते हैं। हम प्रासंगिक समाचार (जैसे प्रॉडक्ट फीचर्स) साझा कर सकते हैं।" + }, "onboardingPinExtensionBillboardAccess": { "message": "पूरी एक्सेस" }, @@ -3280,6 +3573,22 @@ "message": "फिशिंग डिटेक्शन अलर्ट $1 के साथ संचार पर निर्भर करते हैं। jsDeliver की पहुंच आपके IP एड्रेस तक होगी। $2 देखें।", "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" }, + "oneDayAbbreviation": { + "message": "1D", + "description": "Shortened form of '1 day'" + }, + "oneMonthAbbreviation": { + "message": "1M", + "description": "Shortened form of '1 month'" + }, + "oneWeekAbbreviation": { + "message": "1W", + "description": "Shortened form of '1 week'" + }, + "oneYearAbbreviation": { + "message": "1Y", + "description": "Shortened form of '1 year'" + }, "onekey": { "message": "OneKey" }, @@ -3632,6 +3941,9 @@ "permissionsPageTourTitle": { "message": "कनेक्टेड साइटें अब अनुमतियां हैं" }, + "permitSimulationDetailInfo": { + "message": "आप खर्च करने वाले को अपने अकाउंट से इतने सारे टोकन खर्च करने की अनुमति दे रहे हैं।" + }, "personalAddressDetected": { "message": "व्यक्तिगत एड्रेस का एड्रेस चला। टोकन कॉन्ट्रैक्ट एड्रेस डालें।" }, @@ -3664,6 +3976,10 @@ "popularCustomNetworks": { "message": "पॉपुलर कस्टम नेटवर्क" }, + "popularNetworkAddToolTip": { + "message": "इनमें से कुछ नेटवर्क थर्ड पार्टीज़ पर निर्भर हैं। कनेक्शन कम विश्वसनीय हो सकते हैं या गतिविधि को ट्रैक करने के लिए थर्ड पार्टीज़ को चालू कर सकते हैं। $1", + "description": "$1 is Learn more link" + }, "portfolio": { "message": "पोर्टफोलियो" }, @@ -3676,6 +3992,12 @@ "prev": { "message": "पिछला" }, + "price": { + "message": "प्राइस" + }, + "priceUnavailable": { + "message": "प्राइस अनुपलब्ध है" + }, "primaryCurrencySetting": { "message": "प्राथमिक मुद्रा" }, @@ -3828,6 +4150,9 @@ "quoteRate": { "message": "उद्धरण का दर" }, + "rank": { + "message": "रैंक" + }, "reAddAccounts": { "message": "किसी अन्य अकाउंट को फिर से जोड़ें" }, @@ -3997,9 +4322,6 @@ "reset": { "message": "रीसेट करें" }, - "resetStates": { - "message": "स्टेट रीसेट करें" - }, "resetWallet": { "message": "वॉलेट रीसेट करें" }, @@ -4135,6 +4457,9 @@ "searchAccounts": { "message": "अकाउंट्स खोजें" }, + "searchNfts": { + "message": "NFT ढूंढें" + }, "searchTokens": { "message": "टोकनों को ढूंढें" }, @@ -4179,13 +4504,10 @@ "seedPhraseIntroRecommendedButtonCopy": { "message": "मेरा वॉलेट सुरक्षित करें (अनुशंसित)" }, - "seedPhraseIntroSidebarBulletFour": { - "message": "लिखें और कई गुप्त स्थानों में स्टोर करें।" - }, "seedPhraseIntroSidebarBulletOne": { - "message": "पासवर्ड मैनेजर में सेव करें" + "message": "लिखें और कई गुप्त स्थानों में स्टोर करें।" }, - "seedPhraseIntroSidebarBulletThree": { + "seedPhraseIntroSidebarBulletTwo": { "message": "सेफ डिपॉजिट बॉक्स में स्टोर करें।" }, "seedPhraseIntroSidebarCopyOne": { @@ -4255,7 +4577,7 @@ "message": "टोकन चुनें" }, "selectNFTPrivacyPreference": { - "message": "सेटिंग्स में NFT डिटेक्शन चालू करें" + "message": "NFT ऑटोडिटेक्शन चालू करें" }, "selectPathHelp": { "message": "यदि आपको अपनी पसंद के हिसाब से अकाउंट दिखाई नहीं देते हैं, तो HD पाथ या मौजूदा चुना गया नेटवर्क बदलने की कोशिश करें।" @@ -4269,9 +4591,6 @@ "send": { "message": "भेजें" }, - "sendAToken": { - "message": "एक टोकन भेजें" - }, "sendBugReport": { "message": "हमें एक बग रिपोर्ट भेजें।" }, @@ -4322,9 +4641,6 @@ "sepolia": { "message": "Sepolia टेस्ट नेटवर्क" }, - "serviceWorkerKeepAlive": { - "message": "सेवाकर्मी जीवित रहें" - }, "setAdvancedPrivacySettingsDetails": { "message": "MetaMask उत्पाद की उपयोगिता और सुरक्षा को बढ़ाने के लिए इन विश्वसनीय तीसरे-पक्ष की सेवाओं का इस्तेमाल करता है।" }, @@ -4381,6 +4697,9 @@ "showIncomingTransactionsExplainer": { "message": "यह हरेक नेटवर्क के लिए अलग-अलग थर्ड-पार्टी API पर निर्भर करता है, जो आपके Ethereum एड्रेस और आपके आईपी ​​एड्रेस को एक्स्पोज़ करता है।" }, + "showLess": { + "message": "कम दिखाएं" + }, "showMore": { "message": "अधिक दिखाएं" }, @@ -4411,9 +4730,6 @@ "signatureRequestGuidance": { "message": "यदि आप कंटेंट को पूरी तरह से समझते हैं और रिक्वेस्ट करने वाली साइट पर भरोसा करते हैं तो ही इस मैसेज पर हस्ताक्षर करें।" }, - "signatureRequestWarning": { - "message": "इस मैसेज पर हस्ताक्षर करना खतरनाक हो सकता है। हो सकता है कि आप इस मैसेज के दूसरे सिरे पर पार्टी को अपने अकाउंट और संपत्ति का पूर्ण नियंत्रण दे रहे हों। इसका मतलब है कि वे किसी भी समय आपका अकाउंट खाली कर सकते हैं। सावधानी के साथ आगे बढ़ें। $1." - }, "signed": { "message": "हस्ताक्षर किया गया" }, @@ -4423,6 +4739,9 @@ "signing": { "message": "साइन हो रहा है" }, + "signingInWith": { + "message": "के साथ साइन इन करना" + }, "simulationDetailsFailed": { "message": "आपका एस्टीमेशन लोड करने में गड़बड़ी हुई।" }, @@ -4460,6 +4779,24 @@ "simulationsSettingSubHeader": { "message": "बैलेंस अमाउंट में बदलावों का अनुमान लगाएं" }, + "siweIssued": { + "message": "जारी किया गया" + }, + "siweNetwork": { + "message": "नेटवर्क" + }, + "siweRequestId": { + "message": "रिक्वेस्ट ID" + }, + "siweResources": { + "message": "संसाधन" + }, + "siweSignatureSimulationDetailInfo": { + "message": "आप किसी साइट पर साइन इन कर रहे हैं और आपके अकाउंट में कोई अनुमानित परिवर्तन नहीं हैं।" + }, + "siweURI": { + "message": "URL" + }, "skip": { "message": "छोड़ें" }, @@ -4563,6 +4900,14 @@ "snapAccountsDescription": { "message": "थर्ड-पार्टी Snaps द्वारा कंट्रोल किए गए अकाउंट।" }, + "snapConnectTo": { + "message": "$1 से कनेक्ट करें", + "description": "$1 is the website URL or a Snap name. Used for Snaps pre-approved connections." + }, + "snapConnectionPermissionDescription": { + "message": "आपके एप्रूवल के बिना $1 को स्वचालित रूप से $2 से कनेक्ट होने दें।", + "description": "Used for Snap pre-approved connections. $1 is the Snap name, $2 is a website URL." + }, "snapConnectionWarning": { "message": "$1 $2 का उपयोग करना चाहता है", "description": "$2 is the snap and $1 is the dapp requesting connection to the snap." @@ -4574,6 +4919,9 @@ "snapDetailWebsite": { "message": "वेबसाइट" }, + "snapHomeMenu": { + "message": "Snap होम मेन्यू" + }, "snapInstallRequest": { "message": "$1 इंस्टॉल करने से इसे निम्नलिखित अनुमतियां मिलती हैं।", "description": "$1 is the snap name." @@ -4696,6 +5044,9 @@ "source": { "message": "स्त्रोत" }, + "speed": { + "message": "गति" + }, "speedUp": { "message": "जल्दी करें" }, @@ -4730,6 +5081,9 @@ "spendLimitTooLarge": { "message": "खर्च की लिमिट बहुत अधिक है" }, + "spender": { + "message": "खर्च करने वाला" + }, "spendingCap": { "message": "खर्च करने की लिमिट" }, @@ -4850,9 +5204,6 @@ "stateLogsDescription": { "message": "स्टेट लॉग में आपके सार्वजनिक अकाउंट के एड्रेस और भेजे गए ट्रांसेक्शन शामिल होते हैं।" }, - "states": { - "message": "स्टेट" - }, "status": { "message": "स्टेटस" }, @@ -4957,6 +5308,13 @@ "submitted": { "message": "सबमिट किया गया" }, + "suggestedBySnap": { + "message": "$1 के द्वारा सुझाव दिया गया", + "description": "$1 is the snap name" + }, + "suggestedTokenName": { + "message": "सुझाया गया नाम:" + }, "suggestedTokenSymbol": { "message": "सुझाया गया टिकर सिंबल:" }, @@ -5071,7 +5429,7 @@ "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." }, "swapFetchingQuotes": { - "message": "उद्धरण प्राप्त कर रहे हैं" + "message": "कोटेशन प्राप्त कर रहे हैं.." }, "swapFetchingQuotesErrorDescription": { "message": "हम्म्म... कुछ गलत हो गया। फिर से कोशिश करें या यदि गड़बड़ीयां बनी रहती हैं, तो ग्राहक सहायता से कॉन्टेक्ट करें।" @@ -5467,6 +5825,10 @@ "thisCollection": { "message": "यह संग्रह" }, + "threeMonthsAbbreviation": { + "message": "3M", + "description": "Shortened form of '3 months'" + }, "time": { "message": "समय" }, @@ -5480,45 +5842,6 @@ "message": "प्रति: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleEthSignBannerDescription": { - "message": "आपको फ़िशिंग हमलों का जोखिम है। Eth_sign को बंद करके अपनी सुरक्षा करें।" - }, - "toggleEthSignDescriptionField": { - "message": "यदि आप इस सेटिंग को एनेबल करते हैं, तो आपको हस्ताक्षर रिक्वेस्ट प्राप्त हो सकते हैं जो पढ़ने योग्य नहीं हैं। एक ऐसे मैसेज पर हस्ताक्षर करके जिसे आप समझ नहीं पा रहे हैं, आप अपने फंड और NFT दे देने के लिए सहमत हो सकते हैं।" - }, - "toggleEthSignField": { - "message": "Eth_sign अनुरोध" - }, - "toggleEthSignModalBannerBoldText": { - "message": " आपके साथ धोखा हो सकता है" - }, - "toggleEthSignModalBannerText": { - "message": "यदि आपसे यह सेटिंग चालू करने के लिए कहा गया है," - }, - "toggleEthSignModalCheckBox": { - "message": "मैं समझता हूं कि अगर मैं eth_sign रिक्वेस्ट्स को इनेबल करता हूं, तो मैं अपने सभी फंड और NFT's खो सकता हूं। " - }, - "toggleEthSignModalDescription": { - "message": "eth_sign रिक्वेस्ट्स को अनुमति देने से आप फ़िशिंग हमलों के प्रति असुरक्षित हो सकते हैं। URL की हमेशा समीक्षा करें और कोड वाले मैसेज पर हस्ताक्षर करते समय सावधान रहें।" - }, - "toggleEthSignModalFormError": { - "message": "टेक्स्ट गलत है" - }, - "toggleEthSignModalFormLabel": { - "message": "जारी रखने के लिए \"मैं केवल वही हस्ताक्षर करता हूं जो मुझे समझ में आता है\" डालें" - }, - "toggleEthSignModalFormValidation": { - "message": "मैं केवल वही हस्ताक्षर करता हूं जो मुझे समझ में आता है" - }, - "toggleEthSignModalTitle": { - "message": "अपने जोखिम पर इस्तेमाल करें" - }, - "toggleEthSignOff": { - "message": "बंद (अनुशंसित)" - }, - "toggleEthSignOn": { - "message": "चालू (अनुशंसित नहीं)" - }, "toggleRequestQueueDescription": { "message": "ऐसा करके, आप सभी साइटों के लिए कोई सिंगल नेटवर्क चुनने के बजाय हरेक साइट के लिए एक नेटवर्क चुन सकते हैं। यह फीचर आपको मैन्युअल तरीके से नेटवर्क स्विच करने से रोकता है, इस वजह से कुछ साइटों पर आपका यूज़र अनुभव ख़राब हो सकता है।" }, @@ -5546,6 +5869,9 @@ "tokenContractAddress": { "message": "टोकन कॉन्ट्रैक्ट एड्रेस" }, + "tokenDecimal": { + "message": "टोकन डेसिमल" + }, "tokenDecimalFetchFailed": { "message": "टोकन डेसीमल की आवश्यकता है। इसे: $1 पर पाएं" }, @@ -5562,7 +5888,7 @@ "message": "टोकन आइडी" }, "tokenList": { - "message": "टोकन लिस्ट्स:" + "message": "टोकन की सूचियां" }, "tokenScamSecurityRisk": { "message": "टोकन घोटाले और सुरक्षा जोखिम" @@ -5570,6 +5896,9 @@ "tokenShowUp": { "message": "हो सकता है आपके वॉलेट में आपके टोकन ऑटोमेटिकली दिखाई नहीं दें। " }, + "tokenStandard": { + "message": "टोकन स्टैंडर्ड" + }, "tokenSymbol": { "message": "टोकन का प्रतीक" }, @@ -5580,6 +5909,9 @@ "message": "$1 नए टोकन मिले", "description": "$1 is the number of new tokens detected" }, + "tokensInCollection": { + "message": "संग्रह में टोकन" + }, "tooltipApproveButton": { "message": "मैं समझता हूं" }, @@ -5595,6 +5927,9 @@ "total": { "message": "कुलयोग" }, + "totalVolume": { + "message": "टोटल वॉल्यूम" + }, "transaction": { "message": "ट्रांसेक्शन" }, @@ -5610,6 +5945,9 @@ "transactionCreated": { "message": "$2 पर $1 के मूल्य के साथ ट्रांसेक्शन बनाया गया।" }, + "transactionDataFunction": { + "message": "फंक्शन" + }, "transactionDetailDappGasMoreInfo": { "message": "साइट का सुझाव दिया गया" }, @@ -5700,6 +6038,10 @@ "transferFrom": { "message": "इससे ट्रांसफ़र करें" }, + "trillionAbbreviation": { + "message": "T", + "description": "Shortened form of 'trillion'" + }, "troubleConnectingToLedgerU2FOnFirefox": { "message": "हमें आपके Ledger को जोड़ने में समस्या आ रही है। $1", "description": "$1 is a link to the wallet connection guide;" @@ -5778,6 +6120,9 @@ "message": "U2F", "description": "A name on an API for the browser to interact with devices that support the U2F protocol. On some browsers we use it to connect MetaMask to Ledger devices." }, + "unMatchedChain": { + "message": "हमारे रिकॉर्ड के अनुसार, यह URL इस चेन ID के लिए ज्ञात प्रदाता से मेल नहीं खाता है।" + }, "unapproved": { "message": "रिजेक्ट" }, @@ -5829,12 +6174,21 @@ "update": { "message": "अपडेट करें" }, + "updateOrEditNetworkInformations": { + "message": "अपनी जानकारी अपडेट करें या" + }, "updateRequest": { "message": "अपडेट का अनुरोध" }, "updatedWithDate": { "message": "अपडेट किया गया $1" }, + "uploadDropFile": { + "message": "अपनी फ़ाइल यहां छोड़ें" + }, + "uploadFile": { + "message": "फाइल अपलोड करें" + }, "urlErrorMsg": { "message": "URL को उपयुक्त HTTP/HTTPS प्रीफिक्स्ड की आवश्यकता होती है।" }, @@ -6050,6 +6404,12 @@ "whatsThis": { "message": "यह क्या है?" }, + "wrongChainId": { + "message": "यह चेन ID नेटवर्क के नाम से मेल नहीं खाती।" + }, + "wrongNetworkName": { + "message": "हमारे रिकॉर्ड के अनुसार, नेटवर्क का नाम इस चेन ID से ठीक से मेल नहीं खा सकता है।" + }, "xOfYPending": { "message": "$2 में से $1 लंबित", "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" @@ -6073,8 +6433,11 @@ "yourAccounts": { "message": "आपके अकाउंट" }, - "yourFundsMayBeAtRisk": { - "message": "आपके फंड खतरे में हो सकते हैं" + "yourActivity": { + "message": "आपकी एक्टिविटी" + }, + "yourBalance": { + "message": "आपका बैलेंस" }, "yourNFTmayBeAtRisk": { "message": "आपका NFT खतरे में हो सकता है" diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index 5067ed4d9b12..13e7c6d1cbec 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -42,7 +42,7 @@ "message": "Hubungkan dompet perangkat keras QR Anda" }, "QRHardwareWalletSteps2Description": { - "message": "Ngrave (segera hadir)" + "message": "Ngrave Zero" }, "SIWEAddressInvalid": { "message": "Alamat pada permintaan masuk tidak sesuai dengan alamat akun yang Anda gunakan untuk masuk." @@ -162,6 +162,9 @@ "accountSelectionRequired": { "message": "Anda harus memilih satu akun!" }, + "accountTypeNotSupported": { + "message": "Jenis akun tidak didukung" + }, "accounts": { "message": "Akun" }, @@ -277,6 +280,12 @@ "addNewAccount": { "message": "Tambahkan akun Ethereum baru" }, + "addNewBitcoinAccount": { + "message": "Tambahkan akun Bitcoin baru (Beta)" + }, + "addNewBitcoinTestnetAccount": { + "message": "Tambahkan akun Bitcoin baru (Testnet)" + }, "addNewToken": { "message": "Tambahkan token baru" }, @@ -286,6 +295,9 @@ "addNfts": { "message": "Tambahkan NFT" }, + "addRpcUrl": { + "message": "Tambahkan URL RPC" + }, "addSnapAccountToggle": { "message": "Aktifkan \"Tambahkan akun Snap (Beta)\"" }, @@ -305,12 +317,21 @@ "message": "Tidak dapat menemukan token? Tambahkan token secara manual dengan menempelkan alamatnya. Alamat kontrak token dapat ditemukan di $1", "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" }, + "addUrl": { + "message": "Tambahkan URL" + }, "addingCustomNetwork": { "message": "Menambahkan Jaringan" }, "addingTokens": { "message": "Menambahkan token" }, + "additionalNetworks": { + "message": "Jaringan tambahan" + }, + "additionalRpcUrl": { + "message": "URL RPC Tambahan" + }, "address": { "message": "Alamat" }, @@ -326,6 +347,18 @@ "advancedConfiguration": { "message": "Konfigurasi lanjutan" }, + "advancedDetailsDataDesc": { + "message": "Data" + }, + "advancedDetailsHexDesc": { + "message": "Hex" + }, + "advancedDetailsNonceDesc": { + "message": "Nonce" + }, + "advancedDetailsNonceTooltip": { + "message": "Ini adalah nomor transaksi suatu akun. Nonce untuk transaksi pertama adalah 0 dan meningkat secara berurutan." + }, "advancedGasFeeDefaultOptIn": { "message": "Simpan nilai ini sebagai default saya untuk jaringan $1.", "description": "$1 is the current network name." @@ -349,6 +382,18 @@ "alert": { "message": "Peringatan" }, + "alertActionBuy": { + "message": "Beli ETH" + }, + "alertActionUpdateGas": { + "message": "Perbarui batas gas" + }, + "alertActionUpdateGasFee": { + "message": "Perbarui biaya" + }, + "alertActionUpdateGasFeeLevel": { + "message": "Perbarui opsi gas" + }, "alertBannerMultipleAlertsDescription": { "message": "Jika Anda menyetujui permintaan ini, pihak ketiga yang terdeteksi melakukan penipuan dapat mengambil semua aset Anda." }, @@ -358,6 +403,36 @@ "alertDisableTooltip": { "message": "Ini dapat diubah dalam \"Pengaturan > Peringatan\"" }, + "alertMessageGasEstimateFailed": { + "message": "Kami tidak dapat memberikan biaya akurat dan estimasi ini mungkin tinggi. Kami menyarankan Anda untuk memasukkan batas gas kustom, tetapi ada risiko transaksi tetap gagal." + }, + "alertMessageGasFeeLow": { + "message": "Saat memilih biaya rendah, perkirakan transaksi lebih lambat dan waktu tunggu lebih lama. Untuk transaksi lebih cepat, pilih opsi biaya Pasar atau Agresif." + }, + "alertMessageGasTooLow": { + "message": "Untuk melanjutkan transaksi ini, Anda perlu meningkatkan batas gas menjadi 21000 atau lebih tinggi." + }, + "alertMessageInsufficientBalance": { + "message": "Anda tidak memiliki cukup ETH di akun untuk membayar biaya transaksi." + }, + "alertMessageNetworkBusy": { + "message": "Harga gas tinggi dan estimasinya kurang akurat." + }, + "alertMessageNoGasPrice": { + "message": "Kami tidak dapat melanjutkan transaksi ini hingga Anda memperbarui biayanya secara manual." + }, + "alertMessagePendingTransactions": { + "message": "Transaksi ini tidak akan dilanjutkan hingga transaksi sebelumnya selesai. Pelajari cara membatalkan atau mempercepat transaksi." + }, + "alertMessageSignInDomainMismatch": { + "message": "Situs yang membuat permintaan bukanlah situs yang Anda masuki. Ini dapat merupakan upaya untuk mencuri kredensial login Anda." + }, + "alertMessageSignInWrongAccount": { + "message": "Situs ini meminta Anda masuk menggunakan akun yang salah." + }, + "alertMessageSigningOrSubmitting": { + "message": "Transaksi ini hanya akan dilanjutkan setelah transaksi Anda sebelumnya selesai." + }, "alertModalAcknowledge": { "message": "Saya telah mengetahui risikonya dan tetap ingin melanjutkan" }, @@ -367,6 +442,33 @@ "alertModalReviewAllAlerts": { "message": "Tinjau semua peringatan" }, + "alertReasonGasEstimateFailed": { + "message": "Biaya tidak akurat" + }, + "alertReasonGasFeeLow": { + "message": "Kecepatan lambat" + }, + "alertReasonGasTooLow": { + "message": "Batas gas rendah" + }, + "alertReasonInsufficientBalance": { + "message": "Dana tidak cukup" + }, + "alertReasonNetworkBusy": { + "message": "Jaringan sibuk" + }, + "alertReasonNoGasPrice": { + "message": "Estimasi biaya tidak tersedia" + }, + "alertReasonPendingTransactions": { + "message": "Transaksi menunggu" + }, + "alertReasonSignIn": { + "message": "Permintaan masuk mencurigakan" + }, + "alertReasonWrongAccount": { + "message": "Akun salah" + }, "alertSettingsUnconnectedAccount": { "message": "Memilih untuk menjelajahi situs web dengan akun yang tidak terhubung" }, @@ -398,6 +500,12 @@ "allPermissions": { "message": "Semua Izin" }, + "allTimeHigh": { + "message": "Tertinggi sepanjang waktu" + }, + "allTimeLow": { + "message": "Terendah sepanjang waktu" + }, "allYourNFTsOf": { "message": "Seluruh NFT Anda dari $1 ", "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" @@ -436,9 +544,6 @@ "message": "$1 dan $2", "description": "$1 is the first item, $2 is the second item. Used in Snap Install Warning modal." }, - "announcements": { - "message": "Pengumuman" - }, "appDescription": { "message": "Dompet Ethereum pada Browser Anda", "description": "The description of the application" @@ -516,7 +621,7 @@ "message": "Opsi aset" }, "attemptSendingAssets": { - "message": "Jika Anda mencoba untuk mengirim aset secara langsung dari satu jaringan ke jaringan lain, aset Anda berpotensi hilang secara permanen. Pastikan untuk menggunakan bridge." + "message": "Aset Anda berpotensi hilang jika mencoba mengirimnya dari jaringan lain. Transfer dana secara aman antar jaringan menggunakan bridge." }, "attemptSendingAssetsWithPortfolio": { "message": "Aset Anda berpotensi hilang jika mencoba mengirimnya melalui jaringan lain. Transfer dana antar jaringan dengan aman menggunakan bridge, seperti $1" @@ -524,6 +629,9 @@ "attemptToCancelSwapForFree": { "message": "Mencoba membatalkan pertukaran secara gratis" }, + "attributes": { + "message": "Atribut" + }, "attributions": { "message": "Atribusi" }, @@ -594,6 +702,10 @@ "basicConfigurationBannerTitle": { "message": "Fungsionalitas dasar tidak aktif" }, + "basicConfigurationDescription": { + "message": "MetaMask menawarkan fitur dasar seperti detail token dan pengaturan gas melalui layanan internet. Alamat IP dibagikan saat menggunakan layanan internet, dalam hal ini kepada MetaMask. Ini sama seperti saat Anda mengunjungi situs web mana pun. MetaMask menggunakan data ini untuk sementara dan tidak akan pernah menjual data Anda. Anda dapat menggunakan VPN atau menonaktifkan layanan ini, akan tetapi hal ini dapat memengaruhi pengalaman dalam menggunakan MetaMask. Untuk selengkapnya, baca $1 kami.", + "description": "$1 is to be replaced by the message for privacyMsg, and will link to https://consensys.io/privacy-policy" + }, "basicConfigurationLabel": { "message": "Fungsionalitas dasar" }, @@ -637,6 +749,29 @@ "betaWalletCreationSuccessReminder2": { "message": "MetaMask Beta tidak akan pernah menanyakan Frasa Pemulihan Rahasia Anda." }, + "billionAbbreviation": { + "message": "M", + "description": "Shortened form of 'billion'" + }, + "bitcoinActivityNotSupported": { + "message": "Aktivitas Bitcoin tidak didukung" + }, + "bitcoinSupportSectionTitle": { + "message": "Bitcoin" + }, + "bitcoinSupportToggleDescription": { + "message": "Mengaktifkan fitur ini akan memberi Anda opsi untuk menambahkan Akun Bitcoin ke Ekstensi MetaMask yang berasal dari Frasa Pemulihan Rahasia yang ada. Ini merupakan fitur Beta eksperimental, jadi Anda harus menggunakannya dengan risiko yang ditanggung sendiri. Untuk memberikan masukan seputar pengalaman Bitcoin baru ini, isi $1 ini.", + "description": "$1 is the link to a product feedback form" + }, + "bitcoinSupportToggleTitle": { + "message": "Aktifkan \"Tambahkan akun Bitcoin baru (Beta)\"" + }, + "bitcoinTestnetSupportToggleDescription": { + "message": "Mengaktifkan fitur ini akan memberi Anda opsi untuk menambahkan Akun Bitcoin bagi jaringan uji." + }, + "bitcoinTestnetSupportToggleTitle": { + "message": "Aktifkan \"Tambahkan akun Bitcoin baru (Testnet)\"" + }, "blockExplorerAccountAction": { "message": "Akun", "description": "This is used with viewOnEtherscan and viewInExplorer e.g View Account in Explorer" @@ -662,6 +797,9 @@ "blockaid": { "message": "Blockaid" }, + "blockaidAlertInfo": { + "message": "Sebaiknya jangan lanjutkan permintaan ini." + }, "blockaidDescriptionApproveFarming": { "message": "Jika Anda menyetujui permintaan ini, pihak ketiga yang terdeteksi melakukan penipuan dapat mengambil semua aset Anda." }, @@ -669,7 +807,7 @@ "message": "Jika Anda menyetujui permintaan ini, seseorang dapat mencuri aset Anda yang terdaftar di Blur." }, "blockaidDescriptionErrored": { - "message": "Karena terjadi kesalahan, permintaan ini tidak diverifikasi oleh penyedia keamanan. Lanjutkan dengan hati-hati." + "message": "Karena terjadi kesalahan, kami tidak dapat memeriksa peringatan keamanan. Lanjutkan hanya jika Anda memercayai setiap alamat yang terlibat." }, "blockaidDescriptionMaliciousDomain": { "message": "Anda berinteraksi dengan domain berbahaya. Jika Anda menyetujui permintaan ini, aset Anda kemungkinan akan hilang." @@ -683,6 +821,9 @@ "blockaidDescriptionTransferFarming": { "message": "Jika Anda menyetujui permintaan ini, pihak ketiga yang terdeteksi melakukan penipuan akan mengambil semua aset Anda." }, + "blockaidDescriptionWarning": { + "message": "Ini mungkin merupakan permintaan tipuan. Lanjutkan hanya jika Anda memercayai setiap alamat yang terlibat." + }, "blockaidMessage": { "message": "Menjaga privasi - tidak ada data yang dibagikan kepada pihak ketiga. Tersedia di Arbitrum, Avalanche, BNB chain, Mainnet Ethereum, Linea, Optimism, Polygon, Base, dan Sepolia." }, @@ -690,7 +831,7 @@ "message": "Ini adalah permintaan tipuan" }, "blockaidTitleMayNotBeSafe": { - "message": "Permintaan mungkin tidak aman" + "message": "Berhati-hatilah" }, "blockaidTitleSuspicious": { "message": "Ini adalah permintaan yang mencurigakan" @@ -698,6 +839,9 @@ "blockies": { "message": "Blockies" }, + "boughtFor": { + "message": "Dibeli senilai" + }, "bridge": { "message": "Bridge" }, @@ -782,6 +926,9 @@ "chromeRequiredForHardwareWallets": { "message": "Anda perlu menggunakan MetaMask di Google Chrome untuk terhubung ke Dompet Perangkat Keras Anda." }, + "circulatingSupply": { + "message": "Suplai yang beredar" + }, "clear": { "message": "Hapus" }, @@ -802,7 +949,7 @@ "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" }, "clickToManuallyAdd": { - "message": "Klik di sini untuk menambahkan token secara manual." + "message": "Anda dapat menambahkan token secara manual setiap saat." }, "close": { "message": "Tutup" @@ -816,6 +963,9 @@ "coingecko": { "message": "CoinGecko" }, + "collectionName": { + "message": "Nama koleksi" + }, "comboNoOptions": { "message": "Opsi tidak ditemukan", "description": "Default text shown in the combo field dropdown if no options." @@ -841,6 +991,9 @@ "confirmAlertModalAcknowledgeMultiple": { "message": "Saya telah mengetahui peringatannya dan tetap ingin melanjutkan" }, + "confirmAlertModalAcknowledgeSingle": { + "message": "Saya telah mengetahui peringatannya dan tetap ingin melanjutkan" + }, "confirmAlertModalDetails": { "message": "Jika masuk, pihak ketiga yang terdeteksi melakukan penipuan dapat mengambil semua aset Anda. Tinjau peringatannya sebelum melanjutkan." }, @@ -856,18 +1009,42 @@ "confirmConnectionTitle": { "message": "Konfirmasikan koneksi ke $1" }, + "confirmDeletion": { + "message": "Konfirmasikan penghapusan" + }, + "confirmFieldPaymaster": { + "message": "Biaya dibayar oleh" + }, + "confirmFieldTooltipPaymaster": { + "message": "Biaya untuk transaksi ini akan dibayar oleh kontrak cerdas paymaster." + }, "confirmPassword": { "message": "Konfirmasikan kata sandi" }, "confirmRecoveryPhrase": { "message": "Konfirmasikan Frasa Pemulihan Rahasia" }, + "confirmRpcUrlDeletionMessage": { + "message": "Yakin ingin menghapus URL RPC? Informasi Anda tidak akan disimpan untuk jaringan ini." + }, "confirmTitleDescContractInteractionTransaction": { "message": "Konfirmasikan transaksi ini hanya jika Anda benar-benar memahami isinya dan memercayai situs yang memintanya." }, + "confirmTitleDescPermitSignature": { + "message": "Situs ini meminta izin untuk menggunakan token Anda." + }, + "confirmTitleDescSIWESignature": { + "message": "Sebuah situs ingin Anda masuk untuk membuktikan Anda pemilik akun ini." + }, "confirmTitleDescSignature": { "message": "Konfirmasikan pesan ini hanya jika Anda menyetujui isinya dan memercayai situs yang memintanya." }, + "confirmTitlePermitSignature": { + "message": "Permintaan batas penggunaan" + }, + "confirmTitleSIWESignature": { + "message": "Permintaan masuk" + }, "confirmTitleSignature": { "message": "Permintaan tanda tangan" }, @@ -961,7 +1138,7 @@ "message": "Terhubung dengan" }, "connecting": { - "message": "Menghubungkan..." + "message": "Menghubungkan" }, "connectingTo": { "message": "Menghubungkan ke $1" @@ -1094,6 +1271,9 @@ "createSnapAccountTitle": { "message": "Buat akun" }, + "creatorAddress": { + "message": "Alamat pembuat" + }, "crossChainSwapsLink": { "message": "Bertukar antar jaringan dengan Portfolio MetaMask" }, @@ -1263,9 +1443,27 @@ "data": { "message": "Data" }, + "dataCollectionForMarketing": { + "message": "Pengumpulan data untuk pemasaran" + }, + "dataCollectionForMarketingDescription": { + "message": "Kami akan menggunakan MetaMetrics untuk mempelajari cara Anda berinteraksi dengan komunikasi pemasaran. Kami mungkin akan membagikan berita yang relevan (seperti fitur produk dan materi lainnya)." + }, + "dataCollectionWarningPopoverButton": { + "message": "Oke" + }, + "dataCollectionWarningPopoverDescription": { + "message": "Anda menonaktifkan pengumpulan data untuk tujuan pemasaran kami. Ini hanya berlaku untuk perangkat ini. Jika Anda menggunakan MetaMask di perangkat lain, pastikan untuk keluar terlebih dahulu." + }, "dataHex": { "message": "Hex" }, + "dataUnavailable": { + "message": "data tidak tersedia" + }, + "dateCreated": { + "message": "Tanggal dibuat" + }, "dcent": { "message": "D'Cent" }, @@ -1295,6 +1493,9 @@ "decryptRequest": { "message": "Dekrip permintaan" }, + "defaultRpcUrl": { + "message": "URL RPC Default" + }, "delete": { "message": "Hapus" }, @@ -1311,6 +1512,9 @@ "message": "Hapus jaringan $1?", "description": "$1 represents the name of the network" }, + "deleteRpcUrl": { + "message": "Hapus URL RPC" + }, "deposit": { "message": "Deposit" }, @@ -1336,18 +1540,6 @@ "details": { "message": "Detail" }, - "developerOptions": { - "message": "Opsi Pengembang" - }, - "developerOptionsResetStatesAnnouncementsDescription": { - "message": "Reset boolean isShown bernilai salah untuk semua pengumuman. Pengumuman adalah notifikasi yang ditampilkan di modal popup Yang Baru." - }, - "developerOptionsResetStatesOnboarding": { - "message": "Reset berbagai state terkait orientasi dan alihkan ke halaman orientasi \"Amankan Dompet Anda\"." - }, - "developerOptionsServiceWorkerKeepAlive": { - "message": "Menjadikan stempel waktu disimpan ke session.storage secara kontinu" - }, "disabledGasOptionToolTipMessage": { "message": "“$1” dinonaktifkan karena tidak memenuhi kenaikan minimum 10% dari biaya gas asli.", "description": "$1 is gas estimate type which can be market or aggressive" @@ -1524,6 +1716,9 @@ "editGasTooLow": { "message": "Waktu pemrosesan tak diketahui" }, + "editNetworkLink": { + "message": "edit jaringan asli" + }, "editNonceField": { "message": "Edit nonce" }, @@ -1555,6 +1750,9 @@ "enabled": { "message": "Diaktifkan" }, + "enabledNetworks": { + "message": "Jaringan yang diaktifkan" + }, "encryptionPublicKeyNotice": { "message": "$1 menginginkan kunci enkripsi publik Anda. Dengan menyetujui, situs ini akan dapat membuat pesan terenkripsi untuk Anda.", "description": "$1 is the web3 site name" @@ -1659,6 +1857,9 @@ "estimatedFee": { "message": "Estimasi biaya" }, + "estimatedFeeTooltip": { + "message": "Jumlah yang dibayarkan untuk memproses transaksi di jaringan." + }, "ethGasPriceFetchWarning": { "message": "Biaya gas cadangan diberikan karena layanan estimasi gas utama saat ini tidak tersedia." }, @@ -1678,6 +1879,12 @@ "etherscanViewOn": { "message": "Lihat di Etherscan" }, + "existingChainId": { + "message": "Informasi yang Anda masukkan terhubung dengan ID chain yang ada." + }, + "existingRpcUrl": { + "message": "URL ini terhubung dengan ID chain lain." + }, "expandView": { "message": "Perluas tampilan" }, @@ -1701,7 +1908,7 @@ "message": "Nama panggilan yang diusulkan" }, "externalNameSourcesSettingDescription": { - "message": "Kami akan mengambil nama panggilan yang diusulkan untuk alamat yang berinteraksi dengan Anda dari sumber pihak ketiga seperti Etherscan, Infura, dan Lens Protocol. Sumber-sumber ini akan dapat melihat alamat-alamat tersebut dan alamat IP Anda. Alamat akun Anda tidak akan diketahui oleh pihak ketiga." + "message": "Kami akan mengambil nama panggilan yang diusulkan untuk alamat yang berinteraksi dengan Anda dari sumber pihak ketiga seperti Etherscan, Infura, dan Lens Protocol. Sumber-sumber ini dapat melihat alamat-alamat tersebut dan alamat IP Anda. Alamat akun Anda tidak akan diketahui oleh pihak ketiga." }, "failed": { "message": "Gagal" @@ -1732,6 +1939,9 @@ "message": "Impor file tidak bekerja? Klik di sini!", "description": "Helps user import their account from a JSON file" }, + "findTheRightChainId": { + "message": "Temukan yang tepat di:" + }, "flaskWelcomeUninstall": { "message": "Anda harus menghapus ekstensi ini", "description": "This request is shown on the Flask Welcome screen. It is intended for non-developers, and will be bolded." @@ -1771,6 +1981,9 @@ "forgotPassword": { "message": "Lupa kata sandi?" }, + "form": { + "message": "formulir" + }, "from": { "message": "Dari" }, @@ -1901,7 +2114,7 @@ "message": "Jaringan uji Goerli" }, "gotIt": { - "message": "Mengerti!" + "message": "Mengerti" }, "grantedToWithColon": { "message": "Diberikan kepada:" @@ -1979,6 +2192,12 @@ "highLowercase": { "message": "tinggi" }, + "highestCurrentBid": { + "message": "Penawaran tertinggi saat ini" + }, + "highestFloorPrice": { + "message": "Harga dasar tertinggi" + }, "history": { "message": "Riwayat" }, @@ -2318,12 +2537,21 @@ "knownTokenWarning": { "message": "Tindakan ini akan mengedit token yang telah terdaftar dalam dompet Anda, yang dapat digunakan untuk menipu Anda. Setujui hanya jika Anda yakin bahwa Anda ingin mengubah apa yang diwakili token ini. Pelajari selengkapnya seputar $1" }, + "l1Fee": { + "message": "Biaya L1" + }, + "l1FeeTooltip": { + "message": "Biaya gas L1" + }, + "l2Fee": { + "message": "Biaya L2" + }, + "l2FeeTooltip": { + "message": "Biaya gas L2" + }, "lastConnected": { "message": "Terakhir terhubung" }, - "lastPriceSold": { - "message": "Harga terakhir terjual" - }, "lastSold": { "message": "Terakhir terjual" }, @@ -2495,6 +2723,12 @@ "message": "Pastikan tidak ada yang melihat layar Anda", "description": "Warning to users to be care while creating and saving their new Secret Recovery Phrase" }, + "marketCap": { + "message": "Kap pasar" + }, + "marketDetails": { + "message": "Detail pasar" + }, "max": { "message": "Maks" }, @@ -2504,6 +2738,9 @@ "maxFee": { "message": "Biaya maks" }, + "maxFeeTooltip": { + "message": "Biaya maksimum yang diberikan untuk membayar transaksi." + }, "maxPriorityFee": { "message": "Biaya prioritas maks" }, @@ -2551,12 +2788,19 @@ "methodData": { "message": "Metode" }, + "methodDataTransactionDesc": { + "message": "Fungsi dijalankan berdasarkan data masukan yang didekodekan." + }, "methodNotSupported": { "message": "Akun ini tidak mendukung." }, "metrics": { "message": "Metrik" }, + "millionAbbreviation": { + "message": "Jt", + "description": "Shortened form of 'million'" + }, "mismatchAccount": { "message": "Akun yang Anda pilih ($1) berbeda dengan akun yang mencoba ditandatangani ($2)" }, @@ -2669,7 +2913,7 @@ "description": "Description below header used on Permission Connect screen for native permissions." }, "nativeToken": { - "message": "Token asli di jaringan ini adalah $1. Ini merupakan token yang digunakan untuk biaya gas.", + "message": "Token asli di jaringan ini adalah $1. Ini merupakan token yang digunakan untuk biaya gas. ", "description": "$1 represents the name of the native token on the current network" }, "nativeTokenScamWarningConversion": { @@ -2738,6 +2982,9 @@ "networkNameBase": { "message": "Dasar" }, + "networkNameBitcoin": { + "message": "Bitcoin" + }, "networkNameDefinition": { "message": "Nama yang dikaitkan dengan jaringan ini." }, @@ -2762,6 +3009,9 @@ "networkNameZkSyncEra": { "message": "zkSync Era" }, + "networkOptions": { + "message": "Opsi jaringan" + }, "networkProvider": { "message": "Penyedia jaringan" }, @@ -2830,6 +3080,9 @@ "newNetworkAdded": { "message": "“$1” berhasil ditambahkan!" }, + "newNetworkEdited": { + "message": "“$1” berhasil diedit!" + }, "newNftAddedMessage": { "message": "NFT berhasil ditambahkan!" }, @@ -2866,8 +3119,11 @@ "nftAlreadyAdded": { "message": "NFT telah ditambahkan." }, + "nftAutoDetectionEnabled": { + "message": "Autodeteksi NFT diaktifkan" + }, "nftDisclaimer": { - "message": "Penafian: MetaMask mengambil file media dari url sumber. URL ini terkadang diubah oleh pasar tempat NFT dicetak." + "message": "Penafian: MetaMask mengambil file media dari url sumber. Url ini terkadang diubah oleh pasar tempat NFT dicetak." }, "nftOptions": { "message": "Opsi NFT" @@ -2916,6 +3172,9 @@ "noDomainResolution": { "message": "Tidak ada resolusi untuk domain yang tersedia." }, + "noHardwareWalletOrSnapsSupport": { + "message": "Snap, dan sebagian besar dompet perangkat keras, tidak akan berfungsi dengan versi browser saat ini." + }, "noNFTs": { "message": "Belum ada NFT" }, @@ -2946,8 +3205,8 @@ "nonceField": { "message": "Sesuaikan nonce transaksi" }, - "nonceFieldDescription": { - "message": "Aktifkan ini untuk mengubah nonce (nomor transaksi) di layar konfirmasi. Ini merupakan fitur lanjutan, gunakan dengan hati-hati." + "nonceFieldDesc": { + "message": "Aktifkan ini untuk mengubah nonce (nomor transaksi) saat mengirim aset. Ini merupakan fitur lanjutan, gunakan dengan hati-hati." }, "nonceFieldHeading": { "message": "Nonce kustom" @@ -2995,7 +3254,7 @@ "message": "Biaya prioritas (GWEI)" }, "notificationItemCheckBlockExplorer": { - "message": "Periksa di BlockExplorer" + "message": "Periksa di Block Explorer" }, "notificationItemCollection": { "message": "Koleksi" @@ -3149,6 +3408,9 @@ "numberOfNewTokensDetectedSingular": { "message": "1 token baru ditemukan di akun ini" }, + "numberOfTokens": { + "message": "Jumlah token" + }, "ofTextNofM": { "message": "dari" }, @@ -3164,8 +3426,36 @@ "on": { "message": "Nyala" }, - "onboarding": { - "message": "Orientasi" + "onboardedMetametricsAccept": { + "message": "Saya setuju" + }, + "onboardedMetametricsDisagree": { + "message": "Tidak, terima kasih" + }, + "onboardedMetametricsKey1": { + "message": "Perkembangan terkini" + }, + "onboardedMetametricsKey2": { + "message": "Fitur produk" + }, + "onboardedMetametricsKey3": { + "message": "Materi promosi lain yang relevan" + }, + "onboardedMetametricsLink": { + "message": "MetaMetrics" + }, + "onboardedMetametricsParagraph1": { + "message": "Selain $1, kami ingin menggunakan data untuk memahami cara Anda berinteraksi dengan komunikasi pemasaran.", + "description": "$1 represents the 'onboardedMetametricsLink' locale string" + }, + "onboardedMetametricsParagraph2": { + "message": "Ini membantu kami mempersonalisasi hal yang kami bagikan kepada Anda, seperti:" + }, + "onboardedMetametricsParagraph3": { + "message": "Ingat, kami tidak pernah menjual data yang Anda berikan dan Anda dapat memilih untuk keluar setiap saat." + }, + "onboardedMetametricsTitle": { + "message": "Bantu kami meningkatkan pengalaman Anda" }, "onboardingAdvancedPrivacyIPFSDescription": { "message": "Gateway IPFS memungkinkan untuk mengakses dan melihat data yang disimpan oleh pihak ketiga. Anda dapat menambahkan gateway IPFS khusus atau melanjutkan menggunakan default." @@ -3203,9 +3493,6 @@ "onboardingMetametricsDescription2": { "message": "Saat kami mengumpulkan metrik, maka akan selalu..." }, - "onboardingMetametricsDisagree": { - "message": "Tidak, terima kasih" - }, "onboardingMetametricsInfuraTerms": { "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`" @@ -3240,6 +3527,9 @@ "onboardingMetametricsTitle": { "message": "Bantu kami meningkatkan MetaMask" }, + "onboardingMetametricsUseDataCheckbox": { + "message": "Kami akan menggunakan data ini untuk mempelajari cara Anda berinteraksi dengan komunikasi pemasaran. Kami mungkin akan membagikan berita yang relevan (seperti fitur produk)." + }, "onboardingPinExtensionBillboardAccess": { "message": "Akses penuh" }, @@ -3283,6 +3573,22 @@ "message": "Peringatan deteksi pengelabuan bergantung pada komunikasi dengan $1. jsDeliver akan mendapat akses ke alamat IP Anda. Lihat $2.", "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" }, + "oneDayAbbreviation": { + "message": "1H", + "description": "Shortened form of '1 day'" + }, + "oneMonthAbbreviation": { + "message": "1B", + "description": "Shortened form of '1 month'" + }, + "oneWeekAbbreviation": { + "message": "1M", + "description": "Shortened form of '1 week'" + }, + "oneYearAbbreviation": { + "message": "1T", + "description": "Shortened form of '1 year'" + }, "onekey": { "message": "OneKey" }, @@ -3635,6 +3941,9 @@ "permissionsPageTourTitle": { "message": "Situs yang terhubung kini memiliki izin" }, + "permitSimulationDetailInfo": { + "message": "Anda memberikan izin kepada pengguna untuk menggunakan token sebanyak ini dari akun." + }, "personalAddressDetected": { "message": "Alamat pribadi terdeteksi. Masukkan alamat kontrak token." }, @@ -3667,6 +3976,10 @@ "popularCustomNetworks": { "message": "Jaringan khusus populer" }, + "popularNetworkAddToolTip": { + "message": "Beberapa jaringan ini mengandalkan pihak ketiga. Koneksi ini kurang dapat diandalkan atau memungkinkan pihak ketiga melacak aktivitas. $1", + "description": "$1 is Learn more link" + }, "portfolio": { "message": "Portofolio" }, @@ -3679,6 +3992,12 @@ "prev": { "message": "Sebelumnya" }, + "price": { + "message": "Harga" + }, + "priceUnavailable": { + "message": "harga tidak tersedia" + }, "primaryCurrencySetting": { "message": "Mata uang primer" }, @@ -3831,6 +4150,9 @@ "quoteRate": { "message": "Tingkat kuotasi" }, + "rank": { + "message": "Peringkat" + }, "reAddAccounts": { "message": "tambahkan kembali akun lain" }, @@ -4000,9 +4322,6 @@ "reset": { "message": "Atur ulang" }, - "resetStates": { - "message": "Reset State" - }, "resetWallet": { "message": "Reset dompet" }, @@ -4138,6 +4457,9 @@ "searchAccounts": { "message": "Cari akun" }, + "searchNfts": { + "message": "Cari NFT" + }, "searchTokens": { "message": "Cari token" }, @@ -4182,13 +4504,10 @@ "seedPhraseIntroRecommendedButtonCopy": { "message": "Amankan dompet saya (direkomendasikan)" }, - "seedPhraseIntroSidebarBulletFour": { - "message": "Tuliskan dan simpan di beberapa tempat rahasia." - }, "seedPhraseIntroSidebarBulletOne": { - "message": "Simpan di pengelola kata sandi" + "message": "Tuliskan dan simpan di beberapa tempat rahasia." }, - "seedPhraseIntroSidebarBulletThree": { + "seedPhraseIntroSidebarBulletTwo": { "message": "Simpan di deposit box yang aman." }, "seedPhraseIntroSidebarCopyOne": { @@ -4258,10 +4577,10 @@ "message": "Pilih token" }, "selectNFTPrivacyPreference": { - "message": "Aktifkan deteksi NFT pada Pengaturan" + "message": "Aktifkan Autodeteksi NFT" }, "selectPathHelp": { - "message": "Jika Anda tidak menemukan akun yang diharapkan, coba alihkan jalur HD." + "message": "Jika Anda tidak menemukan akun yang diharapkan, coba alihkan jalur HD atau jaringan yang dipilih saat ini." }, "selectType": { "message": "Pilih Jenis" @@ -4272,9 +4591,6 @@ "send": { "message": "Kirim" }, - "sendAToken": { - "message": "Kirimkan token" - }, "sendBugReport": { "message": "Kirimi kami laporan bug." }, @@ -4325,9 +4641,6 @@ "sepolia": { "message": "Jaringan uji Sepolia" }, - "serviceWorkerKeepAlive": { - "message": "Service Worker Tetap Menyala" - }, "setAdvancedPrivacySettingsDetails": { "message": "MetaMask menggunakan layanan pihak ketiga tepercaya ini untuk meningkatkan kegunaan dan keamanan produk." }, @@ -4384,6 +4697,9 @@ "showIncomingTransactionsExplainer": { "message": "Ini bergantung pada API pihak ketiga yang berbeda untuk setiap jaringan, yang mengungkap alamat Ethereum dan alamat IP Anda." }, + "showLess": { + "message": "Ciutkan" + }, "showMore": { "message": "Tampilkan selengkapnya" }, @@ -4414,9 +4730,6 @@ "signatureRequestGuidance": { "message": "Tandatangani pesan ini hanya jika Anda benar-benar memahami isinya dan memercayai situs yang memintanya." }, - "signatureRequestWarning": { - "message": "Berhati-hatilah sebelum menandatangani pesan ini. Anda mungkin memberikan kendali penuh atas akun dan aset Anda kepada pihak lain pesan ini. Artinya, pihak tersebut dapat menguras akun Anda kapan saja. Lanjutkan dengan hati-hati. $1." - }, "signed": { "message": "Ditandatangani" }, @@ -4426,6 +4739,9 @@ "signing": { "message": "Penandatanganan" }, + "signingInWith": { + "message": "Masuk dengan" + }, "simulationDetailsFailed": { "message": "Terjadi kesalahan saat memuat estimasi Anda." }, @@ -4463,6 +4779,24 @@ "simulationsSettingSubHeader": { "message": "Estimasikan perubahan saldo" }, + "siweIssued": { + "message": "Dikeluarkan" + }, + "siweNetwork": { + "message": "Jaringan" + }, + "siweRequestId": { + "message": "Permintaan ID" + }, + "siweResources": { + "message": "Sumber daya" + }, + "siweSignatureSimulationDetailInfo": { + "message": "Anda masuk ke sebuah situs dan tidak ada perkiraan perubahan pada akun Anda." + }, + "siweURI": { + "message": "URL" + }, "skip": { "message": "Lewati" }, @@ -4566,6 +4900,14 @@ "snapAccountsDescription": { "message": "Akun yang dikontrol oleh Snap pihak ketiga." }, + "snapConnectTo": { + "message": "Hubungkan ke $1", + "description": "$1 is the website URL or a Snap name. Used for Snaps pre-approved connections." + }, + "snapConnectionPermissionDescription": { + "message": "Izinkan $1 terhubung secara otomatis ke $2 tanpa persetujuan Anda.", + "description": "Used for Snap pre-approved connections. $1 is the Snap name, $2 is a website URL." + }, "snapConnectionWarning": { "message": "$1 ingin menggunakan $2", "description": "$2 is the snap and $1 is the dapp requesting connection to the snap." @@ -4577,6 +4919,9 @@ "snapDetailWebsite": { "message": "Situs web" }, + "snapHomeMenu": { + "message": "Menu Beranda Snap" + }, "snapInstallRequest": { "message": "Anda memberikan izin berikut dengan menginstal $1.", "description": "$1 is the snap name." @@ -4699,6 +5044,9 @@ "source": { "message": "Sumber" }, + "speed": { + "message": "Kecepatan" + }, "speedUp": { "message": "Percepat" }, @@ -4733,6 +5081,9 @@ "spendLimitTooLarge": { "message": "Batas penggunaan terlalu besar" }, + "spender": { + "message": "Pengguna" + }, "spendingCap": { "message": "Batas penggunaan" }, @@ -4853,9 +5204,6 @@ "stateLogsDescription": { "message": "Log status berisi alamat akun publik Anda dan transaksi terkirim." }, - "states": { - "message": "State" - }, "status": { "message": "Status" }, @@ -4960,6 +5308,13 @@ "submitted": { "message": "Terkirim" }, + "suggestedBySnap": { + "message": "Disarankan oleh $1", + "description": "$1 is the snap name" + }, + "suggestedTokenName": { + "message": "Nama yang disarankan:" + }, "suggestedTokenSymbol": { "message": "Simbol ticker yang disarankan:" }, @@ -5074,7 +5429,7 @@ "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." }, "swapFetchingQuotes": { - "message": "Mengambil kuotasi" + "message": "Mengambil kuotasi..." }, "swapFetchingQuotesErrorDescription": { "message": "Hmmm... terjadi kesalahan. Coba lagi, atau jika masalah terus berlanjut, hubungi dukungan pelanggan." @@ -5422,7 +5777,7 @@ "description": "$1 represents the account name, $2 represents the network name" }, "switchedTo": { - "message": "Anda telah beralih ke" + "message": "Saat ini Anda menggunakan" }, "switchingNetworksCancelsPendingConfirmations": { "message": "Mengalihkan jaringan akan membatalkan semua konfirmasi yang berstatus menunggu" @@ -5461,7 +5816,7 @@ "message": "Pilih tema MetaMask yang Anda sukai." }, "thingsToKeep": { - "message": "Hal-hal yang perlu diingat:" + "message": "Ingatlah:" }, "thirdPartySoftware": { "message": "Pemberitahuan perangkat lunak pihak ketiga", @@ -5470,6 +5825,10 @@ "thisCollection": { "message": "koleksi ini" }, + "threeMonthsAbbreviation": { + "message": "3B", + "description": "Shortened form of '3 months'" + }, "time": { "message": "Waktu" }, @@ -5483,45 +5842,6 @@ "message": "Ke: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleEthSignBannerDescription": { - "message": "Anda berisiko terkena serangan phishing. Lindungi diri dengan menonaktifkan eth_sign." - }, - "toggleEthSignDescriptionField": { - "message": "Jika mengaktifkan pengaturan ini, Anda akan mendapatkan permintaan tanda tangan yang tidak terbaca. Dengan menandatangani pesan yang tidak Anda pahami, Anda mungkin setuju untuk memberikan dana dan NFT Anda." - }, - "toggleEthSignField": { - "message": "Permintaan et_sign" - }, - "toggleEthSignModalBannerBoldText": { - "message": " Anda mungkin ditipu" - }, - "toggleEthSignModalBannerText": { - "message": "Jika Anda diminta mengaktifkan pengaturan ini," - }, - "toggleEthSignModalCheckBox": { - "message": "Saya memahami bahwa saya dapat kehilangan semua dana dan NFT jika mengaktifkan permintaan eth_sign. " - }, - "toggleEthSignModalDescription": { - "message": "Mengizinkan permintaan eth_sign dapat membuat Anda rentan terhadap serangan phishing. Selalu tinjau URL dan berhati-hatilah saat menandatangani pesan yang berisi kode." - }, - "toggleEthSignModalFormError": { - "message": "Teks salah" - }, - "toggleEthSignModalFormLabel": { - "message": "Masukkan \"Saya hanya menandatangani yang saya pahami\" untuk melanjutkan" - }, - "toggleEthSignModalFormValidation": { - "message": "Saya hanya menandatangani yang saya pahami" - }, - "toggleEthSignModalTitle": { - "message": "Gunakan dengan risiko Anda sendiri" - }, - "toggleEthSignOff": { - "message": "NONAKTIF (Disarankan)" - }, - "toggleEthSignOn": { - "message": "AKTIF (Tidak disarankan)" - }, "toggleRequestQueueDescription": { "message": "Hal ini memungkinkan Anda memilih jaringan untuk setiap situs, daripada satu jaringan yang dipilih untuk semua situs. Fitur ini akan mencegah Anda berpindah jaringan secara manual, yang dapat merusak pengalaman pengguna di situs tertentu." }, @@ -5549,6 +5869,9 @@ "tokenContractAddress": { "message": "Alamat kontrak token" }, + "tokenDecimal": { + "message": "Desimal token" + }, "tokenDecimalFetchFailed": { "message": "Desimal token diperlukan. Temukan di: $1" }, @@ -5565,13 +5888,16 @@ "message": "ID token" }, "tokenList": { - "message": "Daftar token:" + "message": "Daftar token" }, "tokenScamSecurityRisk": { "message": "penipuan dan risiko keamanan token" }, "tokenShowUp": { - "message": "Token Anda mungkin tidak secara otomatis muncul di dompet Anda." + "message": "Token Anda mungkin tidak muncul secara otomatis di dompet Anda. " + }, + "tokenStandard": { + "message": "Standar token" }, "tokenSymbol": { "message": "Simbol token" @@ -5583,6 +5909,9 @@ "message": "$1 token baru ditemukan", "description": "$1 is the number of new tokens detected" }, + "tokensInCollection": { + "message": "Token dalam koleksi" + }, "tooltipApproveButton": { "message": "Saya mengerti" }, @@ -5598,6 +5927,9 @@ "total": { "message": "Total" }, + "totalVolume": { + "message": "Volume total" + }, "transaction": { "message": "transaksi" }, @@ -5613,6 +5945,9 @@ "transactionCreated": { "message": "Transaksi dibuat dengan nilai sebesar $1 pada $2." }, + "transactionDataFunction": { + "message": "Fungsi" + }, "transactionDetailDappGasMoreInfo": { "message": "Situs yang disarankan" }, @@ -5703,6 +6038,10 @@ "transferFrom": { "message": "Transfer dari" }, + "trillionAbbreviation": { + "message": "T", + "description": "Shortened form of 'trillion'" + }, "troubleConnectingToLedgerU2FOnFirefox": { "message": "Kami mengalami masalah saat menghubungkan Ledger Anda. $1", "description": "$1 is a link to the wallet connection guide;" @@ -5781,6 +6120,9 @@ "message": "U2F", "description": "A name on an API for the browser to interact with devices that support the U2F protocol. On some browsers we use it to connect MetaMask to Ledger devices." }, + "unMatchedChain": { + "message": "Menurut catatan kami, URL ini tidak sesuai dengan penyedia yang dikenal untuk ID chain ini." + }, "unapproved": { "message": "Tidak disetujui" }, @@ -5832,12 +6174,21 @@ "update": { "message": "Perbarui" }, + "updateOrEditNetworkInformations": { + "message": "Perbarui informasi Anda atau" + }, "updateRequest": { "message": "Permintaan pembaruan" }, "updatedWithDate": { "message": "Diperbarui $1" }, + "uploadDropFile": { + "message": "Letakkan fail di sini" + }, + "uploadFile": { + "message": "Unggah fail" + }, "urlErrorMsg": { "message": "URL memerlukan awalan HTTP/HTTPS yang sesuai." }, @@ -6053,6 +6404,12 @@ "whatsThis": { "message": "Apa ini?" }, + "wrongChainId": { + "message": "ID chain ini tidak sesuai dengan nama jaringan." + }, + "wrongNetworkName": { + "message": "Menurut catatan kami, nama jaringan mungkin tidak cocok dengan ID chain ini." + }, "xOfYPending": { "message": "$1 dari $2 berstatus menunggu", "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" @@ -6076,8 +6433,11 @@ "yourAccounts": { "message": "Akun Anda" }, - "yourFundsMayBeAtRisk": { - "message": "Dana Anda mungkin berisiko" + "yourActivity": { + "message": "Aktivitas Anda" + }, + "yourBalance": { + "message": "Saldo Anda" }, "yourNFTmayBeAtRisk": { "message": "NFT Anda mungkin berisiko" diff --git a/app/_locales/it/messages.json b/app/_locales/it/messages.json index cea9dde6fcfb..9f9085f4ed81 100644 --- a/app/_locales/it/messages.json +++ b/app/_locales/it/messages.json @@ -42,7 +42,7 @@ "message": "Portafoglio HW basato su QR" }, "QRHardwareWalletSteps2Description": { - "message": "Ngrave (in arrivo)" + "message": "Ngrave Zero" }, "SIWEAddressInvalid": { "message": "L'indirizzo nella richiesta di accesso non corrisponde all'indirizzo dell'account che stai utilizzando per accedere." @@ -1138,9 +1138,6 @@ "nonceField": { "message": "Personalizza il numero della transazione" }, - "nonceFieldDescription": { - "message": "Attiva per cambiare il numero della transazione nelle schermate di conferma. Questa è una funzionalità avanzata, usala con cautela." - }, "nonceFieldHeading": { "message": "Numero Transazione Personalizzato" }, diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index ca83ee4bee2e..5935c29f4eab 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -42,7 +42,7 @@ "message": "QRハードウェアウォレットを接続" }, "QRHardwareWalletSteps2Description": { - "message": "Ngrave (近日追加予定)" + "message": "Ngrave Zero" }, "SIWEAddressInvalid": { "message": "サインインリクエストのアドレスが、サインインに使用しているアカウントのアドレスと一致していません。" @@ -162,6 +162,9 @@ "accountSelectionRequired": { "message": "アカウントを選択する必要があります!" }, + "accountTypeNotSupported": { + "message": "アカウントタイプがサポートされていません" + }, "accounts": { "message": "アカウント" }, @@ -277,6 +280,12 @@ "addNewAccount": { "message": "新しいイーサリアムアカウントを追加" }, + "addNewBitcoinAccount": { + "message": "新しいビットコインアカウントの追加 (ベータ)" + }, + "addNewBitcoinTestnetAccount": { + "message": "新しいビットコインアカウントの追加 (テストネット)" + }, "addNewToken": { "message": "新しいトークンを追加" }, @@ -286,6 +295,9 @@ "addNfts": { "message": "NFTを追加" }, + "addRpcUrl": { + "message": "RPC URLを追加" + }, "addSnapAccountToggle": { "message": "「アカウントSnapの追加 (ベータ版)」を有効にする" }, @@ -305,12 +317,21 @@ "message": "トークンが見つからない場合、アドレスを貼り付けて手動でトークンを追加できます。トークンコントラクトアドレスは$1にあります", "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" }, + "addUrl": { + "message": "URLを追加" + }, "addingCustomNetwork": { "message": "ネットワークを追加中" }, "addingTokens": { "message": "トークンを追加しています" }, + "additionalNetworks": { + "message": "他のネットワーク" + }, + "additionalRpcUrl": { + "message": "他のRPC URL" + }, "address": { "message": "アドレス" }, @@ -326,6 +347,18 @@ "advancedConfiguration": { "message": "詳細設定" }, + "advancedDetailsDataDesc": { + "message": "データ" + }, + "advancedDetailsHexDesc": { + "message": "16進法" + }, + "advancedDetailsNonceDesc": { + "message": "ナンス" + }, + "advancedDetailsNonceTooltip": { + "message": "これはアカウントのトランザクション番号です。最初のトランザクションのナンスは0で、順番に上がっていきます。" + }, "advancedGasFeeDefaultOptIn": { "message": "これらの値を$1ネットワークのデフォルトとして保存する", "description": "$1 is the current network name." @@ -349,6 +382,18 @@ "alert": { "message": "アラート" }, + "alertActionBuy": { + "message": "ETHを購入" + }, + "alertActionUpdateGas": { + "message": "ガスリミットを更新" + }, + "alertActionUpdateGasFee": { + "message": "手数料を更新" + }, + "alertActionUpdateGasFeeLevel": { + "message": "ガスオプションを更新" + }, "alertBannerMultipleAlertsDescription": { "message": "このリクエストを承認すると、詐欺が判明しているサードパーティに資産をすべて奪われる可能性があります。" }, @@ -358,6 +403,36 @@ "alertDisableTooltip": { "message": "これは「設定」>「アラート」で変更できます" }, + "alertMessageGasEstimateFailed": { + "message": "正確な手数料を提供できず、この見積もりは高い可能性があります。カスタムガスリミットの入力をお勧めしますが、それでもトランザクションが失敗するリスクがあります。" + }, + "alertMessageGasFeeLow": { + "message": "低い手数料を選択すると、トランザクションに時間がかかり、待機時間が長くなります。より素早くトランザクションを行うには、市場に合った、または積極的な手数料のオプションを選択してください。" + }, + "alertMessageGasTooLow": { + "message": "このトランザクションを続行するには、ガスリミットを21000以上に上げる必要があります。" + }, + "alertMessageInsufficientBalance": { + "message": "アカウントにトランザクション手数料を支払うのに十分なETHがありません。" + }, + "alertMessageNetworkBusy": { + "message": "ガス価格が高く、見積もりはあまり正確ではありません。" + }, + "alertMessageNoGasPrice": { + "message": "手数料を手動で更新するまでこのトランザクションを進めることができません。" + }, + "alertMessagePendingTransactions": { + "message": "前のトランザクションが完了するまでこのトランザクションを実行できません。トランザクションをキャンセルするか加速させる方法をご覧ください。" + }, + "alertMessageSignInDomainMismatch": { + "message": "要求元のサイトはサインインしようとしているサイトではありません。ログイン情報を盗もうとしている可能性があります。" + }, + "alertMessageSignInWrongAccount": { + "message": "このサイトは正しくないアカウントでのサインインを求めています。" + }, + "alertMessageSigningOrSubmitting": { + "message": "このトランザクションは、前のトランザクションが完了しないと実行されません。" + }, "alertModalAcknowledge": { "message": "リスクを承知したうえで続行します" }, @@ -367,6 +442,33 @@ "alertModalReviewAllAlerts": { "message": "すべてのアラートを確認する" }, + "alertReasonGasEstimateFailed": { + "message": "不正確な手数料" + }, + "alertReasonGasFeeLow": { + "message": "低速" + }, + "alertReasonGasTooLow": { + "message": "低ガスリミット" + }, + "alertReasonInsufficientBalance": { + "message": "資金不足" + }, + "alertReasonNetworkBusy": { + "message": "ネットワークが混雑中" + }, + "alertReasonNoGasPrice": { + "message": "手数料の見積もりが利用できません" + }, + "alertReasonPendingTransactions": { + "message": "保留中のトランザクション" + }, + "alertReasonSignIn": { + "message": "不審なサインイン要求" + }, + "alertReasonWrongAccount": { + "message": "正しくないアカウント" + }, "alertSettingsUnconnectedAccount": { "message": "選択した未接続のアカウントを使用してWebサイトをブラウズしています" }, @@ -398,6 +500,12 @@ "allPermissions": { "message": "すべてのアクセス許可" }, + "allTimeHigh": { + "message": "最高記録" + }, + "allTimeLow": { + "message": "最低記録" + }, "allYourNFTsOf": { "message": "$1のすべてのNFT", "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" @@ -436,9 +544,6 @@ "message": "$1および$2", "description": "$1 is the first item, $2 is the second item. Used in Snap Install Warning modal." }, - "announcements": { - "message": "お知らせ" - }, "appDescription": { "message": "ブラウザにあるイーサリアムウォレット", "description": "The description of the application" @@ -516,17 +621,23 @@ "message": "アセットのオプション" }, "attemptSendingAssets": { - "message": "1つのネットワークから別のネットワークに直接アセットを送ろうとすると、アセットが永久に失われる可能性があります。必ずブリッジを使用してください。" + "message": "別のネットワークからアセットを送ろうとすると、アセットが失われる可能性があります。ネットワーク間で安全に資金を移動するには、必ずブリッジを使用してください。" }, "attemptSendingAssetsWithPortfolio": { - "message": "別のネットワークからアセットを送ろうとすると、アセットが失われる可能性があります。$1などのブリッジを使ってネットワーク間で安全に送金してください。" + "message": "別のネットワークからアセットを送ろうとすると、アセットが失われる可能性があります。ネットワーク間で安全に資金を移動するには、必ず$1などのブリッジを使用してください。" }, "attemptToCancelSwapForFree": { "message": "無料でスワップのキャンセルを試行" }, + "attributes": { + "message": "属性" + }, "attributions": { "message": "属性" }, + "auroraRpcDeprecationMessage": { + "message": "Infura RPC URLでAuroraがサポートされなくなりました。" + }, "authorizedPermissions": { "message": "以下の権限を承認しました" }, @@ -591,6 +702,10 @@ "basicConfigurationBannerTitle": { "message": "基本機能はオフになっています" }, + "basicConfigurationDescription": { + "message": "MetaMaskは、インターネットサービスを通じてトークンの詳細やガス設定などの基本的な機能を提供します。インターネットサービスを使用すると、この場合はMetaMaskに、ユーザーのIPアドレスが共有されます。これは他のどのWebサイトにアクセスした場合も同様で、MetaMaskはこのデータを一時的に使用し、ユーザーのデータを販売することは一切ありません。VPNを使用したり、これらのサービスをオフにしたりすることもできますが、MetaMaskでのエクスペリエンスに影響を与える可能性があります。詳細は$1をお読みください。", + "description": "$1 is to be replaced by the message for privacyMsg, and will link to https://consensys.io/privacy-policy" + }, "basicConfigurationLabel": { "message": "基本機能" }, @@ -634,6 +749,29 @@ "betaWalletCreationSuccessReminder2": { "message": "MetaMaskベータ版がユーザーのシークレットリカバリーフレーズを求めることは絶対にありません。" }, + "billionAbbreviation": { + "message": "B", + "description": "Shortened form of 'billion'" + }, + "bitcoinActivityNotSupported": { + "message": "ビットコインアクティビティはサポートされていません" + }, + "bitcoinSupportSectionTitle": { + "message": "ビットコイン" + }, + "bitcoinSupportToggleDescription": { + "message": "この機能をオンにすると、既存のシークレットリカバリーフレーズで取得したビットコインアカウントをMetaMask拡張機能に追加できるようになります。これは試験運用中のベータ機能であるため、自己責任でご使用ください。新しいビットコインエクスペリエンスのフィードバックをご提供いただくには、こちらの$1に入力してください。", + "description": "$1 is the link to a product feedback form" + }, + "bitcoinSupportToggleTitle": { + "message": "「新しいビットコインアカウントの追加 (ベータ)」を有効にする" + }, + "bitcoinTestnetSupportToggleDescription": { + "message": "この機能をオンにすると、テストネットワーク用にビットコインアカウントを追加できるようになります。" + }, + "bitcoinTestnetSupportToggleTitle": { + "message": "「新しいビットコインアカウントの追加 (テストネット)」を有効にする" + }, "blockExplorerAccountAction": { "message": "アカウント", "description": "This is used with viewOnEtherscan and viewInExplorer e.g View Account in Explorer" @@ -659,6 +797,9 @@ "blockaid": { "message": "Blockaid" }, + "blockaidAlertInfo": { + "message": "この要求を承諾することはお勧めしません。" + }, "blockaidDescriptionApproveFarming": { "message": "このリクエストを承認すると、詐欺が判明しているサードパーティに資産をすべて奪われる可能性があります。" }, @@ -666,7 +807,7 @@ "message": "このリクエストを承認すると、Blurに登録されている資産を誰かに盗まれる可能性があります。" }, "blockaidDescriptionErrored": { - "message": "エラーが発生したため、このリクエストはセキュリティプロバイダーにより確認されませんでした。慎重に進めてください。" + "message": "エラーが発生したため、セキュリティアラートをチェックできませんでした。関連するすべてのアドレスが信頼できる場合のみ続行してください。" }, "blockaidDescriptionMaliciousDomain": { "message": "悪質なドメインとやり取りしています。このリクエストを承認すると、資産を失う可能性があります。" @@ -680,6 +821,9 @@ "blockaidDescriptionTransferFarming": { "message": "このリクエストを承認すると、詐欺が判明しているサードパーティに資産をすべて奪われます。" }, + "blockaidDescriptionWarning": { + "message": "これは偽りの要求である可能性があります。関与しているすべてのアドレスを信頼できない限り、続行しないでください。" + }, "blockaidMessage": { "message": "プライバシーを保護 - サードパーティとデータが一切共有されません。Arbitrum、Avalanche、BNB Chain、イーサリアムメインネット、Linea、Optimism、Polygon、Base、Sepoliaで利用可能。" }, @@ -687,7 +831,7 @@ "message": "これは虚偽のリクエストです" }, "blockaidTitleMayNotBeSafe": { - "message": "リクエストは安全でない可能性があります" + "message": "ご注意ください" }, "blockaidTitleSuspicious": { "message": "これは不審なリクエストです" @@ -695,6 +839,9 @@ "blockies": { "message": "Blockie" }, + "boughtFor": { + "message": "購入価格" + }, "bridge": { "message": "ブリッジ" }, @@ -779,6 +926,9 @@ "chromeRequiredForHardwareWallets": { "message": "ハードウェアウォレットに接続するには、MetaMaskをGoogle Chromeで使用する必要があります。" }, + "circulatingSupply": { + "message": "循環供給量" + }, "clear": { "message": "消去" }, @@ -799,7 +949,7 @@ "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" }, "clickToManuallyAdd": { - "message": "トークンを手動で追加するにはこちらをクリックしてください。" + "message": "トークンはいつでも手動で追加できます。" }, "close": { "message": "閉じる" @@ -813,6 +963,9 @@ "coingecko": { "message": "CoinGecko" }, + "collectionName": { + "message": "コレクション名" + }, "comboNoOptions": { "message": "オプションが見つかりません", "description": "Default text shown in the combo field dropdown if no options." @@ -838,6 +991,9 @@ "confirmAlertModalAcknowledgeMultiple": { "message": "アラートを確認したうえで続行します" }, + "confirmAlertModalAcknowledgeSingle": { + "message": "アラートを確認したうえで続行します" + }, "confirmAlertModalDetails": { "message": "サインインすると、詐欺が判明しているサードパーティにすべての資産を奪われる可能性があります。続ける前にアラートを確認してください。" }, @@ -853,18 +1009,42 @@ "confirmConnectionTitle": { "message": "$1への接続の確定" }, + "confirmDeletion": { + "message": "削除の確定" + }, + "confirmFieldPaymaster": { + "message": "手数料の支払元" + }, + "confirmFieldTooltipPaymaster": { + "message": "このトランザクションの手数料は、ペイマスターのスマートコントラクトにより支払われます。" + }, "confirmPassword": { "message": "パスワードの確認" }, "confirmRecoveryPhrase": { "message": "シークレットリカバリーフレーズの確認" }, + "confirmRpcUrlDeletionMessage": { + "message": "RPC URLを削除してよろしいですか?このネットワークの情報は保存されません。" + }, "confirmTitleDescContractInteractionTransaction": { "message": "このトランザクションの内容を完全に理解し、要求元のサイトを信頼する場合にのみ確定してください。" }, + "confirmTitleDescPermitSignature": { + "message": "このサイトがトークンの使用許可を求めています。" + }, + "confirmTitleDescSIWESignature": { + "message": "サイトがこのアカウントを所有することを証明するためにサインインを求めています。" + }, "confirmTitleDescSignature": { "message": "このメッセージの内容を承認し、要求元のサイトを信頼する場合にのみ確定してください。" }, + "confirmTitlePermitSignature": { + "message": "使用上限リクエスト" + }, + "confirmTitleSIWESignature": { + "message": "サインインリクエスト" + }, "confirmTitleSignature": { "message": "署名要求" }, @@ -1091,6 +1271,9 @@ "createSnapAccountTitle": { "message": "アカウントの作成" }, + "creatorAddress": { + "message": "クリエイターのアドレス" + }, "crossChainSwapsLink": { "message": "MetaMask Portfolioでネットワーク間でスワップ" }, @@ -1260,9 +1443,27 @@ "data": { "message": "データ" }, + "dataCollectionForMarketing": { + "message": "マーケティング目的のデータ収集" + }, + "dataCollectionForMarketingDescription": { + "message": "当社はMetaMetricsを使用して、ユーザーによる当社のマーケティングコミュニケーションとのインタラクションを把握します。また、関連ニュースをお伝えする場合もあります (製品の機能、その他資料など)。" + }, + "dataCollectionWarningPopoverButton": { + "message": "OK" + }, + "dataCollectionWarningPopoverDescription": { + "message": "マーケティング目的のデータ収集をオフにしました。これはこのデバイスにのみ適用されます。MetaMaskを他のデバイスで使用する場合は、そのデバイスでもオプトアウトしてください。" + }, "dataHex": { "message": "16進法" }, + "dataUnavailable": { + "message": "データが利用できません" + }, + "dateCreated": { + "message": "作成日" + }, "dcent": { "message": "D'Cent" }, @@ -1292,6 +1493,9 @@ "decryptRequest": { "message": "リクエストを解読" }, + "defaultRpcUrl": { + "message": "デフォルトのRPC URL" + }, "delete": { "message": "削除" }, @@ -1308,6 +1512,9 @@ "message": "$1ネットワークを削除しますか?", "description": "$1 represents the name of the network" }, + "deleteRpcUrl": { + "message": "RPC URLを削除" + }, "deposit": { "message": "入金" }, @@ -1333,18 +1540,6 @@ "details": { "message": "詳細" }, - "developerOptions": { - "message": "開発者用オプション" - }, - "developerOptionsResetStatesAnnouncementsDescription": { - "message": "すべてのお知らせのisShownのブール値をfalseにリセットします。お知らせとは、「最新情報」ポップアップモーダルに表示される通知のことです。" - }, - "developerOptionsResetStatesOnboarding": { - "message": "オンボーディングに関するさまざまなステートをリセットし、「ウォレットのセキュリティ保護」オンボーディングページにリダイレクトします。" - }, - "developerOptionsServiceWorkerKeepAlive": { - "message": "タイムスタンプがセッションストレージに継続的に保存されるようになります" - }, "disabledGasOptionToolTipMessage": { "message": "元のガス代の10%以上という増額の条件を満たしていないため、「$1」は無効になっています。", "description": "$1 is gas estimate type which can be market or aggressive" @@ -1521,6 +1716,9 @@ "editGasTooLow": { "message": "不明な処理時間" }, + "editNetworkLink": { + "message": "元のネットワークを編集" + }, "editNonceField": { "message": "ナンスを編集" }, @@ -1552,6 +1750,9 @@ "enabled": { "message": "有効" }, + "enabledNetworks": { + "message": "ネットワークが有効になりました" + }, "encryptionPublicKeyNotice": { "message": "$1は公開暗号鍵を必要とします。同意することによって、このサイトは暗号化されたメッセージを作成できます。", "description": "$1 is the web3 site name" @@ -1656,6 +1857,9 @@ "estimatedFee": { "message": "予想手数料" }, + "estimatedFeeTooltip": { + "message": "ネットワーク上のトランザクションの処理に支払われる金額" + }, "ethGasPriceFetchWarning": { "message": "現在メインのガスの見積もりサービスが利用できないため、バックアップのガス価格が提供されています。" }, @@ -1675,6 +1879,12 @@ "etherscanViewOn": { "message": "Etherscanで表示" }, + "existingChainId": { + "message": "入力された情報は、既存のチェーンIDと関連付けられています。" + }, + "existingRpcUrl": { + "message": "このURLは別のチェーンIDに関連付けられています。" + }, "expandView": { "message": "ビューを展開" }, @@ -1698,7 +1908,7 @@ "message": "ニックネームの提案" }, "externalNameSourcesSettingDescription": { - "message": "当社はEtherscan、Infura、Lensプロトコルなどのサードパーティソースから、やり取りがあるアドレスのニックネームの提案を取得します。これらのソースは対象となるアドレスとユーザーのIPアドレスを把握できます。ユーザーのアカウントアドレスはサードパーティに公開されません。" + "message": "当社は、Etherscan、Infura、Lensプロトコルなどのサードパーティソースから、やり取りするアドレスに使用するニックネームの提案を取得します。これらのソースは対象となるアドレスとユーザーのIPアドレスを把握できます。ユーザーのアカウントアドレスはサードパーティに公開されません。" }, "failed": { "message": "失敗しました" @@ -1729,6 +1939,9 @@ "message": "ファイルのインポートが機能していない場合、ここをクリックしてください!", "description": "Helps user import their account from a JSON file" }, + "findTheRightChainId": { + "message": "正しいIDを検索:" + }, "flaskWelcomeUninstall": { "message": "この拡張機能はアンインストールしてください", "description": "This request is shown on the Flask Welcome screen. It is intended for non-developers, and will be bolded." @@ -1768,6 +1981,9 @@ "forgotPassword": { "message": "パスワードを忘れた場合" }, + "form": { + "message": "フォーム" + }, "from": { "message": "移動元" }, @@ -1898,7 +2114,7 @@ "message": "Goerliテストネットワーク" }, "gotIt": { - "message": "了解!" + "message": "了解" }, "grantedToWithColon": { "message": "付与先:" @@ -1976,6 +2192,12 @@ "highLowercase": { "message": "高" }, + "highestCurrentBid": { + "message": "現在の最高入札額" + }, + "highestFloorPrice": { + "message": "フロア価格の最高額" + }, "history": { "message": "履歴" }, @@ -2315,12 +2537,21 @@ "knownTokenWarning": { "message": "このアクションは、ウォレットに既に一覧表示されているトークンを編集します。これは、フィッシングに使用される可能性があります。これらのトークンの表す内容を変更する意図が確実な場合にのみ承認します。$1に関する詳細をご覧ください" }, + "l1Fee": { + "message": "L1手数料" + }, + "l1FeeTooltip": { + "message": "L1ガス代" + }, + "l2Fee": { + "message": "L2手数料" + }, + "l2FeeTooltip": { + "message": "L2ガス代" + }, "lastConnected": { "message": "前回の接続" }, - "lastPriceSold": { - "message": "前回の売値" - }, "lastSold": { "message": "前回の売却" }, @@ -2492,6 +2723,12 @@ "message": "誰にも見られていないことを確認してください", "description": "Warning to users to be care while creating and saving their new Secret Recovery Phrase" }, + "marketCap": { + "message": "時価総額" + }, + "marketDetails": { + "message": "マーケットの詳細" + }, "max": { "message": "最大" }, @@ -2501,6 +2738,9 @@ "maxFee": { "message": "最大手数料" }, + "maxFeeTooltip": { + "message": "トランザクションの支払いに提供される最大手数料" + }, "maxPriorityFee": { "message": "最大優先手数料" }, @@ -2534,7 +2774,7 @@ "message": "MetaMask Institutionalバージョン" }, "metamaskNotificationsAreOff": { - "message": "ウォレット通知は現在アクティブではありません" + "message": "ウォレットの通知は現在無効になっています" }, "metamaskPortfolio": { "message": "MetaMask Portfolio。" @@ -2548,12 +2788,19 @@ "methodData": { "message": "方法" }, + "methodDataTransactionDesc": { + "message": "解読された入力データに基づき実行された機能" + }, "methodNotSupported": { "message": "このアカウントではサポートされていません。" }, "metrics": { "message": "メトリクス" }, + "millionAbbreviation": { + "message": "M", + "description": "Shortened form of 'million'" + }, "mismatchAccount": { "message": "選択されたアカウント ($1) は署名しようとしているアカウント ($2) と異なります" }, @@ -2735,6 +2982,9 @@ "networkNameBase": { "message": "Base" }, + "networkNameBitcoin": { + "message": "ビットコイン" + }, "networkNameDefinition": { "message": "このネットワークに関連付けられている名前。" }, @@ -2759,6 +3009,9 @@ "networkNameZkSyncEra": { "message": "zkSync Era" }, + "networkOptions": { + "message": "ネットワークオプション" + }, "networkProvider": { "message": "ネットワークプロバイダー" }, @@ -2827,6 +3080,9 @@ "newNetworkAdded": { "message": "「$1」が追加されました!" }, + "newNetworkEdited": { + "message": "“$1”が編集されました!" + }, "newNftAddedMessage": { "message": "NFTが追加されました!" }, @@ -2863,6 +3119,9 @@ "nftAlreadyAdded": { "message": "NFTがすでに追加されています。" }, + "nftAutoDetectionEnabled": { + "message": "NFTの自動検出が有効になりました" + }, "nftDisclaimer": { "message": "開示事項: MetaMaskはソースURLからメディアファイルを取得します。このURLは時々、NFTがミントされたマーケットプレイスにより変更されることがあります。" }, @@ -2913,6 +3172,9 @@ "noDomainResolution": { "message": "指定されたドメインの名前解決ができません。" }, + "noHardwareWalletOrSnapsSupport": { + "message": "Snap、およびほとんどのハードウェアウォレットは、現在お使いのブラウザのバージョンで使用できません。" + }, "noNFTs": { "message": "NFTはまだありません" }, @@ -2943,8 +3205,8 @@ "nonceField": { "message": "トランザクション ナンスのカスタマイズ" }, - "nonceFieldDescription": { - "message": "承認画面上でナンス (トランザクション番号) を変更するには、この機能をオンにします。これは高度な機能であり、慎重に使用してください。" + "nonceFieldDesc": { + "message": "資産を送る際にナンス (トランザクション番号) を変更するには、この機能をオンにします。これは高度な機能であり、慎重に使用してください。" }, "nonceFieldHeading": { "message": "カスタムナンス" @@ -2992,7 +3254,7 @@ "message": "優先手数料 (gwei)" }, "notificationItemCheckBlockExplorer": { - "message": "BlockExplorerで確認する" + "message": "ブロックエクスプローラーで確認する" }, "notificationItemCollection": { "message": "コレクション" @@ -3010,13 +3272,13 @@ "message": "出金準備ができました" }, "notificationItemLidoStakeReadyToBeWithdrawnMessage": { - "message": "これでステーキングされていない $1 を引き出すことができます" + "message": "これでステーキングが解除された$1を引き出すことができます" }, "notificationItemLidoWithdrawalRequestedMessage": { - "message": "$1 のステーキングを解除するリクエストが送信されました" + "message": "$1のステーキングを解除するリクエストが送信されました" }, "notificationItemNFTReceivedFrom": { - "message": "NFTを次の元から受け取りました:" + "message": "NFTを次の相手から受け取りました:" }, "notificationItemNFTSentTo": { "message": "NFTを次の相手に送りました:" @@ -3031,7 +3293,7 @@ "message": "受け取りました" }, "notificationItemReceivedFrom": { - "message": "次の元から受け取りました:" + "message": "次の相手から受け取りました:" }, "notificationItemSent": { "message": "送りました" @@ -3067,7 +3329,7 @@ "message": "ステーキングの解除が完了しました" }, "notificationItemUnStaked": { - "message": "ステーキングが寛恕されました" + "message": "ステーキングが解除されました" }, "notificationItemUnStakingRequested": { "message": "ステーキングの解除がリクエストされました" @@ -3146,6 +3408,9 @@ "numberOfNewTokensDetectedSingular": { "message": "1つの新しいトークンがこのアカウントで見つかりました" }, + "numberOfTokens": { + "message": "トークンの数" + }, "ofTextNofM": { "message": "中の" }, @@ -3161,8 +3426,36 @@ "on": { "message": "オン" }, - "onboarding": { - "message": "オンボーディング" + "onboardedMetametricsAccept": { + "message": "同意する" + }, + "onboardedMetametricsDisagree": { + "message": "いいえ、結構です" + }, + "onboardedMetametricsKey1": { + "message": "最新情報" + }, + "onboardedMetametricsKey2": { + "message": "製品の機能" + }, + "onboardedMetametricsKey3": { + "message": "その他関連プロモーション資料" + }, + "onboardedMetametricsLink": { + "message": "MetaMetrics" + }, + "onboardedMetametricsParagraph1": { + "message": "$1に加え、マーケティングコミュニケーションとのインタラクションについて把握するためにもデータを使用します。", + "description": "$1 represents the 'onboardedMetametricsLink' locale string" + }, + "onboardedMetametricsParagraph2": { + "message": "これは、次のようなお伝えする情報のカスタマイズに役立ちます:" + }, + "onboardedMetametricsParagraph3": { + "message": "ユーザーから提供されたデータが販売されることは一切なく、いつでもオプトアウトできます。" + }, + "onboardedMetametricsTitle": { + "message": "エクスペリエンスの改善にご協力ください" }, "onboardingAdvancedPrivacyIPFSDescription": { "message": "IPFSゲートウェイにより、第三者がホスティングしているデータへのアクセスと表示が可能になります。カスタムIPFSゲートウェイを追加するか、引き続きデフォルトを使用できます。" @@ -3200,9 +3493,6 @@ "onboardingMetametricsDescription2": { "message": "指標を収集する際、常に次の条件が適用されます..." }, - "onboardingMetametricsDisagree": { - "message": "結構です" - }, "onboardingMetametricsInfuraTerms": { "message": "このデータを他の目的に使用する際は、お知らせします。詳細は当社の$1をご覧ください。設定でいつでもオプトアウトできます。", "description": "$1 represents `onboardingMetametricsInfuraTermsPolicy`" @@ -3237,6 +3527,9 @@ "onboardingMetametricsTitle": { "message": "MetaMaskの改善にご協力ください" }, + "onboardingMetametricsUseDataCheckbox": { + "message": "このデータは、ユーザーによる当社のマーケティングコミュニケーションとのインタラクションを把握するために使用されます。また、関連ニュースをお伝えする場合もあります (製品の機能など)。" + }, "onboardingPinExtensionBillboardAccess": { "message": "フルアクセス" }, @@ -3280,6 +3573,22 @@ "message": "フィッシング検出アラートには$1との通信が必要です。jsDeliverはユーザーのIPアドレスにアクセスします。$2をご覧ください。", "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" }, + "oneDayAbbreviation": { + "message": "1日", + "description": "Shortened form of '1 day'" + }, + "oneMonthAbbreviation": { + "message": "1か月", + "description": "Shortened form of '1 month'" + }, + "oneWeekAbbreviation": { + "message": "1週間", + "description": "Shortened form of '1 week'" + }, + "oneYearAbbreviation": { + "message": "1年", + "description": "Shortened form of '1 year'" + }, "onekey": { "message": "OneKey" }, @@ -3632,6 +3941,9 @@ "permissionsPageTourTitle": { "message": "「接続済みのサイト」が「アクセス許可」に変更されました" }, + "permitSimulationDetailInfo": { + "message": "この数量のトークンをアカウントから転送する権限を使用者に付与しようとしています。" + }, "personalAddressDetected": { "message": "個人アドレスが検出されました。トークンコントラクトアドレスを入力してください。" }, @@ -3664,6 +3976,10 @@ "popularCustomNetworks": { "message": "人気のカスタムネットワーク" }, + "popularNetworkAddToolTip": { + "message": "これらのネットワークの一部はサードパーティに依存しているため、接続の信頼性が低かったり、サードパーティによるアクティビティの追跡が可能になったりする可能性があります。$1", + "description": "$1 is Learn more link" + }, "portfolio": { "message": "Portfolio" }, @@ -3676,6 +3992,12 @@ "prev": { "message": "前へ" }, + "price": { + "message": "価格" + }, + "priceUnavailable": { + "message": "価格が利用できません" + }, "primaryCurrencySetting": { "message": "プライマリ通貨" }, @@ -3828,6 +4150,9 @@ "quoteRate": { "message": "クォートレート" }, + "rank": { + "message": "ランク" + }, "reAddAccounts": { "message": "他のアカウントを再度追加" }, @@ -3997,9 +4322,6 @@ "reset": { "message": "リセット" }, - "resetStates": { - "message": "ステートのリセット" - }, "resetWallet": { "message": "ウォレットをリセット" }, @@ -4135,6 +4457,9 @@ "searchAccounts": { "message": "アカウントを検索" }, + "searchNfts": { + "message": "NFTを検索" + }, "searchTokens": { "message": "トークンを検索" }, @@ -4179,13 +4504,10 @@ "seedPhraseIntroRecommendedButtonCopy": { "message": "ウォレットの安全を確保 (推奨)" }, - "seedPhraseIntroSidebarBulletFour": { - "message": "書き留めて、複数の秘密の場所に保管してください。" - }, "seedPhraseIntroSidebarBulletOne": { - "message": "パスワードマネージャーに保存" + "message": "書き留めて、複数の秘密の場所に保管してください。" }, - "seedPhraseIntroSidebarBulletThree": { + "seedPhraseIntroSidebarBulletTwo": { "message": "セーフティボックスに保管する。" }, "seedPhraseIntroSidebarCopyOne": { @@ -4255,10 +4577,10 @@ "message": "トークンを選択" }, "selectNFTPrivacyPreference": { - "message": "設定でNFTの検出をオンにします" + "message": "NFTの自動検出を有効にする" }, "selectPathHelp": { - "message": "アカウントが見当たらない場合は、HDパスを切り替えてみてください。" + "message": "アカウントが見当たらない場合は、HDパスまたは現在選択されているネットワークを切り替えてみてください。" }, "selectType": { "message": "種類を選択" @@ -4269,9 +4591,6 @@ "send": { "message": "送金" }, - "sendAToken": { - "message": "トークンを送信" - }, "sendBugReport": { "message": "バグの報告をお送りください。" }, @@ -4296,7 +4615,7 @@ "description": "Symbol of the specified token" }, "sendSwapSubmissionWarning": { - "message": "このボタンをクリックすると、直ちにスワップトランザクションが開始します。続ける前に、以下のトランザクションの詳細を確認してください。" + "message": "このボタンをクリックすると、直ちにスワップトランザクションが開始します。続ける前に、トランザクションの詳細を確認してください。" }, "sendTokenAsToken": { "message": "$1を$2として送金", @@ -4322,9 +4641,6 @@ "sepolia": { "message": "Sepoliaテストネットワーク" }, - "serviceWorkerKeepAlive": { - "message": "Service Worker Keep Alive" - }, "setAdvancedPrivacySettingsDetails": { "message": "MetaMaskはこれらの信頼できるサードパーティサービスを使用して、製品の使いやすさと安全性を向上させています。" }, @@ -4381,6 +4697,9 @@ "showIncomingTransactionsExplainer": { "message": "これは、ネットワークごとに異なるサードパーティAPIに依存します。これにより、イーサリアムアドレスとIPアドレスが公開されます。" }, + "showLess": { + "message": "表示量を減らす" + }, "showMore": { "message": "他を表示" }, @@ -4411,9 +4730,6 @@ "signatureRequestGuidance": { "message": "このメッセージの内容を完全に理解し、リクエスト元のサイトを信頼する場合にのみ署名してください。" }, - "signatureRequestWarning": { - "message": "このメッセージに署名するのは危険な可能性があります。このメッセージの相手に、アカウントと資産の完全なコントロールを許可しようとしている可能性があります。つまり、相手がいつでもアカウントからすべてを引き出せるようになります。慎重に進めてください。$1。" - }, "signed": { "message": "署名が完了しました" }, @@ -4423,6 +4739,9 @@ "signing": { "message": "署名" }, + "signingInWith": { + "message": "サインイン方法:" + }, "simulationDetailsFailed": { "message": "予測結果の読み込み中にエラーが発生しました。" }, @@ -4460,6 +4779,24 @@ "simulationsSettingSubHeader": { "message": "予測される残高の増減" }, + "siweIssued": { + "message": "発行済み" + }, + "siweNetwork": { + "message": "ネットワーク" + }, + "siweRequestId": { + "message": "リクエストID" + }, + "siweResources": { + "message": "リソース" + }, + "siweSignatureSimulationDetailInfo": { + "message": "サイトにサインインしようとしていて、予想されるアカウントの変更はありません。" + }, + "siweURI": { + "message": "URL" + }, "skip": { "message": "スキップ" }, @@ -4563,6 +4900,14 @@ "snapAccountsDescription": { "message": "サードパーティSnapが制御するアカウント" }, + "snapConnectTo": { + "message": "$1に接続", + "description": "$1 is the website URL or a Snap name. Used for Snaps pre-approved connections." + }, + "snapConnectionPermissionDescription": { + "message": "$1によるユーザーの承認なしでの$2への自動接続を許可してください。", + "description": "Used for Snap pre-approved connections. $1 is the Snap name, $2 is a website URL." + }, "snapConnectionWarning": { "message": "$1が$2の使用を求めています", "description": "$2 is the snap and $1 is the dapp requesting connection to the snap." @@ -4574,6 +4919,9 @@ "snapDetailWebsite": { "message": "Webサイト" }, + "snapHomeMenu": { + "message": "Snapホームメニュー" + }, "snapInstallRequest": { "message": "$1をインストールすると、次のアクセス許可が付与されます。", "description": "$1 is the snap name." @@ -4696,6 +5044,9 @@ "source": { "message": "ソース" }, + "speed": { + "message": "速度" + }, "speedUp": { "message": "高速化" }, @@ -4730,6 +5081,9 @@ "spendLimitTooLarge": { "message": "使用限度額が大きすぎます" }, + "spender": { + "message": "使用者" + }, "spendingCap": { "message": "使用上限" }, @@ -4850,9 +5204,6 @@ "stateLogsDescription": { "message": "ステートログには、パブリックアカウントアドレスと送信済みトランザクションが含まれています。" }, - "states": { - "message": "ステート" - }, "status": { "message": "ステータス" }, @@ -4957,6 +5308,13 @@ "submitted": { "message": "送信済み" }, + "suggestedBySnap": { + "message": "$1による提案", + "description": "$1 is the snap name" + }, + "suggestedTokenName": { + "message": "提案された名前:" + }, "suggestedTokenSymbol": { "message": "推奨ティッカーシンボル:" }, @@ -5071,7 +5429,7 @@ "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." }, "swapFetchingQuotes": { - "message": "見積もりを取得中" + "message": "クォートを取得中..." }, "swapFetchingQuotesErrorDescription": { "message": "問題が発生しました。もう一度実行してください。エラーが解消されない場合は、カスタマサポートにお問い合わせください。" @@ -5467,6 +5825,10 @@ "thisCollection": { "message": "このコレクション" }, + "threeMonthsAbbreviation": { + "message": "3か月", + "description": "Shortened form of '3 months'" + }, "time": { "message": "時間" }, @@ -5480,45 +5842,6 @@ "message": "移動先: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleEthSignBannerDescription": { - "message": "フィッシング攻撃のリスクがあります。eth_signを無効にして自分の身を守ってください。" - }, - "toggleEthSignDescriptionField": { - "message": "この設定を有効にすると、読めない署名リクエストを受ける可能性があります。理解できないメッセージに署名すると、資金やNFTの提供に同意してしまう可能性があります。" - }, - "toggleEthSignField": { - "message": "Eth_signリクエスト" - }, - "toggleEthSignModalBannerBoldText": { - "message": "騙されている可能性があります" - }, - "toggleEthSignModalBannerText": { - "message": "この設定を有効にするよう求められた場合、" - }, - "toggleEthSignModalCheckBox": { - "message": "私は、eth_signリクエストを有効にすると、すべての資金とNFTを失う可能性があることを理解しています。" - }, - "toggleEthSignModalDescription": { - "message": "eth_signリクエストを許可すると、フィッシング攻撃を受けやすくなる可能性があります。常にURLを確認し、コードを含むメッセージに署名する際には注意してください。" - }, - "toggleEthSignModalFormError": { - "message": "テキストが正しくありません" - }, - "toggleEthSignModalFormLabel": { - "message": "続行するには、「私は理解できるものにしか署名しません」と入力してください" - }, - "toggleEthSignModalFormValidation": { - "message": "私は理解できるものにしか署名しません" - }, - "toggleEthSignModalTitle": { - "message": "自己責任でご利用ください" - }, - "toggleEthSignOff": { - "message": "オフ (推奨)" - }, - "toggleEthSignOn": { - "message": "オン (非推奨)" - }, "toggleRequestQueueDescription": { "message": "これにより、選択した単一のネットワークをすべてのサイトで使用するのではなく、サイトごとにネットワークを選択できます。この機能により、特定のサイトでのユーザーエクスペリエンスの妨げとなる、ネットワークの手動切り替えが不要になります。" }, @@ -5546,6 +5869,9 @@ "tokenContractAddress": { "message": "トークンコントラクトアドレス" }, + "tokenDecimal": { + "message": "トークンの小数桁数" + }, "tokenDecimalFetchFailed": { "message": "トークンの小数点以下の桁数が必要です。確認はこちら: $1" }, @@ -5570,6 +5896,9 @@ "tokenShowUp": { "message": "トークンはウォレットに自動的に表示されない可能性があります。" }, + "tokenStandard": { + "message": "トークン規格" + }, "tokenSymbol": { "message": "トークンシンボル" }, @@ -5580,6 +5909,9 @@ "message": "$1種類の新しいトークンが見つかりました", "description": "$1 is the number of new tokens detected" }, + "tokensInCollection": { + "message": "コレクションにあるトークン" + }, "tooltipApproveButton": { "message": "理解しました" }, @@ -5595,6 +5927,9 @@ "total": { "message": "合計" }, + "totalVolume": { + "message": "合計量" + }, "transaction": { "message": "トランザクション" }, @@ -5610,6 +5945,9 @@ "transactionCreated": { "message": "トランザクションは$1の値が$2で作成されました。" }, + "transactionDataFunction": { + "message": "関数" + }, "transactionDetailDappGasMoreInfo": { "message": "サイトが提案されました" }, @@ -5700,6 +6038,10 @@ "transferFrom": { "message": "送金元" }, + "trillionAbbreviation": { + "message": "T", + "description": "Shortened form of 'trillion'" + }, "troubleConnectingToLedgerU2FOnFirefox": { "message": "Ledgerの接続に問題が発生しました。$1", "description": "$1 is a link to the wallet connection guide;" @@ -5751,7 +6093,7 @@ "message": "通知を使えば、ウォレットで何が起きているか常に把握できます。" }, "turnOnMetamaskNotificationsMessagePrivacyBold": { - "message": "「設定」>「通知」。" + "message": "「設定」>「通知」" }, "turnOnMetamaskNotificationsMessagePrivacyLink": { "message": "この機能を使用する際に当社がどのようにユーザーのプライバシーを保護するのか、ご覧ください。" @@ -5778,6 +6120,9 @@ "message": "U2F", "description": "A name on an API for the browser to interact with devices that support the U2F protocol. On some browsers we use it to connect MetaMask to Ledger devices." }, + "unMatchedChain": { + "message": "当社の記録によると、このURLは、このチェーンIDの既知のプロバイダーと一致しません。" + }, "unapproved": { "message": "未承認" }, @@ -5829,12 +6174,21 @@ "update": { "message": "更新" }, + "updateOrEditNetworkInformations": { + "message": "情報を更新するか" + }, "updateRequest": { "message": "更新リクエスト" }, "updatedWithDate": { "message": "$1が更新されました" }, + "uploadDropFile": { + "message": "ここにファイルをドロップします" + }, + "uploadFile": { + "message": "ファイルをアップロード" + }, "urlErrorMsg": { "message": "URLには適切なHTTP/HTTPSプレフィックスが必要です。" }, @@ -6050,6 +6404,12 @@ "whatsThis": { "message": "これは何ですか?" }, + "wrongChainId": { + "message": "このチェーンIDはネットワーク名と一致しません。" + }, + "wrongNetworkName": { + "message": "弊社の記録によると、ネットワーク名がこのチェーンIDと正しく一致していない可能性があります。" + }, "xOfYPending": { "message": "$2件中$1件が保留中", "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" @@ -6073,8 +6433,11 @@ "yourAccounts": { "message": "アカウント" }, - "yourFundsMayBeAtRisk": { - "message": "資金が危険にさらされている可能性があります" + "yourActivity": { + "message": "アクティビティ" + }, + "yourBalance": { + "message": "残高" }, "yourNFTmayBeAtRisk": { "message": "NFTが危険にさらされている可能性があります" diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index 203edc1c2bcc..a4b9ca10e7b0 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -42,7 +42,7 @@ "message": "QR 하드웨어 지갑을 연결하세요" }, "QRHardwareWalletSteps2Description": { - "message": "Ngrave(출시 예정)" + "message": "Ngrave Zero" }, "SIWEAddressInvalid": { "message": "로그인 요청 주소가 현재 로그인 계정의 주소와 일치하지 않습니다." @@ -162,6 +162,9 @@ "accountSelectionRequired": { "message": "계정을 선택해야 합니다!" }, + "accountTypeNotSupported": { + "message": "지원하지 않는 계정 유형" + }, "accounts": { "message": "계정" }, @@ -277,6 +280,12 @@ "addNewAccount": { "message": "새 이더리움 계정 추가" }, + "addNewBitcoinAccount": { + "message": "새 비트코인 계정 추가(베타)" + }, + "addNewBitcoinTestnetAccount": { + "message": "새 비트코인 계정 추가(테스트넷)" + }, "addNewToken": { "message": "신규 토큰 추가" }, @@ -286,6 +295,9 @@ "addNfts": { "message": "NFT 추가" }, + "addRpcUrl": { + "message": "RPC URL 추가" + }, "addSnapAccountToggle": { "message": "\"계정 Snap 추가(베타)\" 활성화" }, @@ -305,12 +317,21 @@ "message": "이 토큰을 찾을 수 없으신가요? 토큰 주소를 붙여넣으면 토큰을 직접 추가할 수 있습니다. 토큰의 계약 주소는 $1에서 찾을 수 있습니다", "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" }, + "addUrl": { + "message": "URL 추가" + }, "addingCustomNetwork": { "message": "네트워크 추가" }, "addingTokens": { "message": "토큰 추가" }, + "additionalNetworks": { + "message": "추가 네트워크" + }, + "additionalRpcUrl": { + "message": "추가 RPC URL" + }, "address": { "message": "주소" }, @@ -326,6 +347,18 @@ "advancedConfiguration": { "message": "고급 옵션" }, + "advancedDetailsDataDesc": { + "message": "데이터" + }, + "advancedDetailsHexDesc": { + "message": "16진수" + }, + "advancedDetailsNonceDesc": { + "message": "논스" + }, + "advancedDetailsNonceTooltip": { + "message": "계정의 트랜잭션 번호입니다. 첫 트랜잭션의 논스는 0이며 이는 순차적으로 증가합니다." + }, "advancedGasFeeDefaultOptIn": { "message": "이 수치를 $1 네트워크의 기본값으로 저장합니다.", "description": "$1 is the current network name." @@ -349,6 +382,18 @@ "alert": { "message": "경고" }, + "alertActionBuy": { + "message": "ETH 매수" + }, + "alertActionUpdateGas": { + "message": "가스 한도 업데이트" + }, + "alertActionUpdateGasFee": { + "message": "수수료 업데이트" + }, + "alertActionUpdateGasFeeLevel": { + "message": "가스 옵션 업데이트" + }, "alertBannerMultipleAlertsDescription": { "message": "이 요청을 승인하면 스캠을 목적으로 하는 제3자가 회원님의 자산을 모두 가져갈 수 있습니다." }, @@ -358,6 +403,36 @@ "alertDisableTooltip": { "message": "\"설정 > 경고\"에서 변경할 수 있습니다" }, + "alertMessageGasEstimateFailed": { + "message": "정확한 수수료를 제공할 수 없으며 예상 수수료가 높을 수 있습니다. 사용자 지정 가스 한도를 입력하는 것이 좋지만 트랜잭션이 여전히 실패할 위험이 있습니다." + }, + "alertMessageGasFeeLow": { + "message": "낮은 수수료를 선택하면 트랜잭션 속도가 느려지고 대기 시간이 길어집니다. 트랜잭션 속도를 높이려면 시장 수수료 또는 공격적 수수료 옵션을 선택하세요." + }, + "alertMessageGasTooLow": { + "message": "이 트랜잭션을 계속 진행하려면, 가스 한도를 21000 이상으로 늘려야 합니다." + }, + "alertMessageInsufficientBalance": { + "message": "계정에 트랜잭션 수수료를 지불할 수 있는 이더리움이 충분하지 않습니다." + }, + "alertMessageNetworkBusy": { + "message": "가스비가 높고 견적의 정확도도 떨어집니다." + }, + "alertMessageNoGasPrice": { + "message": "수수료를 직접 업데이트할 때까지는 이 트랜잭션을 진행할 수 없습니다." + }, + "alertMessagePendingTransactions": { + "message": "이 트랜잭션은 이전 트랜잭션이 완료될 때까지 진행되지 않습니다. 트랜잭션을 취소하거나 속도를 올리는 법을 알아보세요." + }, + "alertMessageSignInDomainMismatch": { + "message": "요청을 보낸 사이트에 로그인되어 있지 않습니다. 이는 로그인 정보를 도용하려는 시도일 수 있습니다." + }, + "alertMessageSignInWrongAccount": { + "message": "이 사이트에서 잘못된 계정으로 로그인하라고 요청합니다." + }, + "alertMessageSigningOrSubmitting": { + "message": "이 트랜잭션은 이전 트랜잭션이 완료된 경우에만 진행됩니다." + }, "alertModalAcknowledge": { "message": "위험성을 인지했으며, 계속 진행합니다" }, @@ -367,6 +442,33 @@ "alertModalReviewAllAlerts": { "message": "모든 경고 검토하기" }, + "alertReasonGasEstimateFailed": { + "message": "잘못된 수수료" + }, + "alertReasonGasFeeLow": { + "message": "느린 속도" + }, + "alertReasonGasTooLow": { + "message": "낮은 가스 한도" + }, + "alertReasonInsufficientBalance": { + "message": "자금 부족" + }, + "alertReasonNetworkBusy": { + "message": "네트워크 혼잡" + }, + "alertReasonNoGasPrice": { + "message": "수수료 견적 제공 불가" + }, + "alertReasonPendingTransactions": { + "message": "보류 중인 트랜잭션" + }, + "alertReasonSignIn": { + "message": "의심스러운 로그인 요청" + }, + "alertReasonWrongAccount": { + "message": "잘못된 계정" + }, "alertSettingsUnconnectedAccount": { "message": "연결되지 않은 계정을 선택하여 웹사이트 탐색" }, @@ -398,6 +500,12 @@ "allPermissions": { "message": "모든 권한" }, + "allTimeHigh": { + "message": "역대 최고" + }, + "allTimeLow": { + "message": "역대 최저" + }, "allYourNFTsOf": { "message": "$1의 모든 내 NFT", "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" @@ -436,9 +544,6 @@ "message": "$1 및 $2", "description": "$1 is the first item, $2 is the second item. Used in Snap Install Warning modal." }, - "announcements": { - "message": "공지" - }, "appDescription": { "message": "브라우저의 이더리움 지갑", "description": "The description of the application" @@ -516,7 +621,7 @@ "message": "자산 옵션" }, "attemptSendingAssets": { - "message": "한 네트워크에서 다른 네트워크로 자산을 직접 전송하면 자산이 영구적으로 손실될 수 있습니다. 반드시 브릿지를 이용하세요." + "message": "다른 네트워크로 자산을 직접 전송하면 자산이 영구적으로 손실될 수 있습니다. 브릿지를 이용하여 네트워크 간에 자금을 안전하게 전송하세요." }, "attemptSendingAssetsWithPortfolio": { "message": "다른 네트워크에서 자산을 전송하려고 하면 자산이 손실될 수 있습니다. $1 같은 브릿지를 사용하여 네트워크 간에 자산을 안전하게 전송하세요." @@ -524,9 +629,15 @@ "attemptToCancelSwapForFree": { "message": "무료 스왑 취소 시도" }, + "attributes": { + "message": "속성" + }, "attributions": { "message": "속성" }, + "auroraRpcDeprecationMessage": { + "message": "Infura RPC URL은 더 이상 Aurora를 지원하지 않습니다." + }, "authorizedPermissions": { "message": "다음 권한을 승인했습니다." }, @@ -591,6 +702,10 @@ "basicConfigurationBannerTitle": { "message": "기본 기능이 꺼져 있습니다." }, + "basicConfigurationDescription": { + "message": "MetaMask는 인터넷 서비스를 통해 토큰 세부 정보 및 가스 설정과 같은 기본 기능을 제공합니다. 인터넷 서비스를 이용할 때 IP 주소가 공유되며, 이 경우 MetaMask와 공유됩니다. 이는 다른 웹사이트를 방문할 때와 마찬가지입니다. MetaMask는 이 데이터를 일시적으로 사용하며 데이터를 절대 판매하지 않습니다. VPN을 사용하거나 이러한 서비스를 비활성화할 수 있지만 MetaMask 사용 환경에 영향을 미칠 수 있습니다. 자세한 내용은 $1 내용을 참고하세요.", + "description": "$1 is to be replaced by the message for privacyMsg, and will link to https://consensys.io/privacy-policy" + }, "basicConfigurationLabel": { "message": "기본 기능" }, @@ -634,6 +749,29 @@ "betaWalletCreationSuccessReminder2": { "message": "MetaMask 베타는 비밀복구구문을 절대 묻지 않습니다." }, + "billionAbbreviation": { + "message": "B", + "description": "Shortened form of 'billion'" + }, + "bitcoinActivityNotSupported": { + "message": "비트코인 활동이 지원되지 않습니다." + }, + "bitcoinSupportSectionTitle": { + "message": "비트코인" + }, + "bitcoinSupportToggleDescription": { + "message": "이 기능을 켜면 기존 비밀복구구문에서 파생된 MetaMask 확장에 비트코인 계정을 추가할 수 있는 옵션을 이용할 수 있습니다. 이 기능은 실험적 베타 기능이므로 사용자의 책임하에 사용해야 합니다. 이 새로운 비트코인 경험에 대한 피드백을 보내려면 이 $1을(를) 작성해 주세요.", + "description": "$1 is the link to a product feedback form" + }, + "bitcoinSupportToggleTitle": { + "message": "'새 비트코인 계정 추가(베타)' 활성화" + }, + "bitcoinTestnetSupportToggleDescription": { + "message": "이 기능을 켜면 테스트 네트워크에 비트코인 계정을 추가할 수 있는 옵션이 제공됩니다." + }, + "bitcoinTestnetSupportToggleTitle": { + "message": "'새 비트코인 계정 추가(테스트넷)' 활성화" + }, "blockExplorerAccountAction": { "message": "계정", "description": "This is used with viewOnEtherscan and viewInExplorer e.g View Account in Explorer" @@ -659,6 +797,9 @@ "blockaid": { "message": "Blockaid" }, + "blockaidAlertInfo": { + "message": "이 요청은 진행하지 않는 것이 좋습니다." + }, "blockaidDescriptionApproveFarming": { "message": "이 요청을 승인하면 스캠으로 알려진 타사가 회원님의 모든 자산을 가져갈 수 있습니다." }, @@ -666,7 +807,7 @@ "message": "이 요청을 승인하면, Blur에 있는 자산을 타인이 갈취할 수 있습니다." }, "blockaidDescriptionErrored": { - "message": "오류로 인해 보안업체에서 이 요청을 확인하지 못했습니다. 주의하여 진행하세요." + "message": "오류로 인해 보안 알림을 확인할 수 없었습니다. 모든 관련 주소를 신뢰하는 경우에만 계속 진행하세요." }, "blockaidDescriptionMaliciousDomain": { "message": "악성 도메인과 인터렉션하고 있습니다. 이 요청을 승인하면 본인의 자산을 잃을 수도 있습니다." @@ -680,6 +821,9 @@ "blockaidDescriptionTransferFarming": { "message": "이 요청을 승인하면 스캠과 같은 타사가 회원님의 모든 자산을 가져갈 수 있습니다." }, + "blockaidDescriptionWarning": { + "message": "이는 사기성 요청일 수 있습니다. 관련된 모든 주소를 신뢰하는 경우에만 계속 진행하세요." + }, "blockaidMessage": { "message": "개인정보 보호 - 제3자와 데이터를 공유하지 않습니다. Arbitrum, Avalanche, BNB Chain, 이더리움 메인넷, Linea, Optimism, Polygon, Base, Sepolia에서 사용할 수 있습니다." }, @@ -687,7 +831,7 @@ "message": "사기성 요청입니다" }, "blockaidTitleMayNotBeSafe": { - "message": "요청이 안전하지 않을 수 있습니다" + "message": "조심하세요" }, "blockaidTitleSuspicious": { "message": "의심스러운 요청입니다" @@ -695,6 +839,9 @@ "blockies": { "message": "Blockies" }, + "boughtFor": { + "message": "매수:" + }, "bridge": { "message": "브리지" }, @@ -779,6 +926,9 @@ "chromeRequiredForHardwareWallets": { "message": "하드웨어 지갑에 연결하려면 Google Chrome에서 MetaMask를 사용해야 합니다." }, + "circulatingSupply": { + "message": "순환 공급" + }, "clear": { "message": "지우기" }, @@ -799,7 +949,7 @@ "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" }, "clickToManuallyAdd": { - "message": "여기를 클릭하여 토큰을 직접 추가하세요." + "message": "토큰은 언제든지 직접 추가할 수 있습니다." }, "close": { "message": "닫기" @@ -813,6 +963,9 @@ "coingecko": { "message": "CoinGecko" }, + "collectionName": { + "message": "컬렉션 이름" + }, "comboNoOptions": { "message": "옵션을 찾을 수 없습니다", "description": "Default text shown in the combo field dropdown if no options." @@ -838,6 +991,9 @@ "confirmAlertModalAcknowledgeMultiple": { "message": "경고를 인지했으며, 계속 진행합니다" }, + "confirmAlertModalAcknowledgeSingle": { + "message": "경고를 인지했으며, 계속 진행합니다" + }, "confirmAlertModalDetails": { "message": "로그인하면 스캠을 목적으로 하는 제3자가 회원님의 자산을 모두 가져갈 수 있습니다. 계속하기 전에 경고를 검토하세요." }, @@ -853,18 +1009,42 @@ "confirmConnectionTitle": { "message": "$1에 연결 확인" }, + "confirmDeletion": { + "message": "컨펌 삭제" + }, + "confirmFieldPaymaster": { + "message": "수수료 지불:" + }, + "confirmFieldTooltipPaymaster": { + "message": "이 트랜잭션에 대한 수수료는 Paymaster 스마트 계약에서 지불합니다." + }, "confirmPassword": { "message": "비밀번호 컨펌" }, "confirmRecoveryPhrase": { "message": "비밀복구구문 컨펌" }, + "confirmRpcUrlDeletionMessage": { + "message": "정말로 RPC URL을 삭제하시겠습니까? 고객님의 정보는 이 네트워크에 저장되지 않습니다." + }, "confirmTitleDescContractInteractionTransaction": { "message": "요청하는 사이트를 신뢰하고 그 내용을 완전히 이해하는 경우에만 이 트랜젝션을 컨펌하세요." }, + "confirmTitleDescPermitSignature": { + "message": "해당 사이트에서 토큰 사용 승인을 요청합니다." + }, + "confirmTitleDescSIWESignature": { + "message": "회원님이 이 계정을 소유하고 있음을 확인하기 위해 로그인을 요청하는 사이트가 있습니다." + }, "confirmTitleDescSignature": { "message": "요청하는 사이트를 신뢰하고 그 내용을 완전히 이해하는 경우에만 이 메시지를 컨펌하세요." }, + "confirmTitlePermitSignature": { + "message": "지출 한도 요청" + }, + "confirmTitleSIWESignature": { + "message": "로그인 요청" + }, "confirmTitleSignature": { "message": "서명 요청" }, @@ -958,7 +1138,7 @@ "message": "연결 대상:" }, "connecting": { - "message": "연결 중..." + "message": "연결 중" }, "connectingTo": { "message": "$1에 연결 중" @@ -1091,6 +1271,9 @@ "createSnapAccountTitle": { "message": "계정 생성" }, + "creatorAddress": { + "message": "크리에이터 주소" + }, "crossChainSwapsLink": { "message": "MetaMask Portfolio로 네트워크 간 스왑" }, @@ -1260,9 +1443,27 @@ "data": { "message": "데이터" }, + "dataCollectionForMarketing": { + "message": "마케팅을 위한 데이터 수집" + }, + "dataCollectionForMarketingDescription": { + "message": "MetaMetrics를 사용하여 사용자가 마케팅 커뮤니케이션과 어떻게 상호 작용하는지 파악할 것입니다. 관련 뉴스(예: 제품 기능 및 기타 자료)를 공유할 수 있습니다." + }, + "dataCollectionWarningPopoverButton": { + "message": "확인" + }, + "dataCollectionWarningPopoverDescription": { + "message": "마케팅 목적의 데이터 수집을 비활성화했습니다. 이는 이 장치에만 적용됩니다. 다른 장치에서 MetaMask를 사용하면 해당 장치에서도 데이터 수집을 비활성화해야 합니다." + }, "dataHex": { "message": "헥스" }, + "dataUnavailable": { + "message": "데이터 사용 불가" + }, + "dateCreated": { + "message": "생성일" + }, "dcent": { "message": "D'Cent" }, @@ -1292,6 +1493,9 @@ "decryptRequest": { "message": "암호 해독 요청" }, + "defaultRpcUrl": { + "message": "기본 RPC URL" + }, "delete": { "message": "삭제" }, @@ -1308,6 +1512,9 @@ "message": "$1 네트워크를 삭제하시겠습니까?", "description": "$1 represents the name of the network" }, + "deleteRpcUrl": { + "message": "RPC URL 삭제" + }, "deposit": { "message": "예치" }, @@ -1333,18 +1540,6 @@ "details": { "message": "세부 정보" }, - "developerOptions": { - "message": "개발자 옵션" - }, - "developerOptionsResetStatesAnnouncementsDescription": { - "message": "Resets isShown boolean to false for all announcements. Announcements are the notifications shown in the What's New popup modal." - }, - "developerOptionsResetStatesOnboarding": { - "message": "온보딩과 관련된 다양한 상태를 초기화하고 '지갑 보안' 온보딩 페이지로 리디렉션합니다." - }, - "developerOptionsServiceWorkerKeepAlive": { - "message": "session.storage에 타임스탬프가 지속적으로 저장됩니다" - }, "disabledGasOptionToolTipMessage": { "message": "“$1” 유형은 오리지널 가스비를 최소 10% 인상해야 하는 기준에 미치지 못하므로 비활성화되었습니다.", "description": "$1 is gas estimate type which can be market or aggressive" @@ -1521,6 +1716,9 @@ "editGasTooLow": { "message": "알 수 없는 처리 시간" }, + "editNetworkLink": { + "message": "원본 네트워크 편집" + }, "editNonceField": { "message": "논스 편집" }, @@ -1552,6 +1750,9 @@ "enabled": { "message": "활성화됨" }, + "enabledNetworks": { + "message": "활성화된 네트워크" + }, "encryptionPublicKeyNotice": { "message": "$1에서 회원님의 공개 암호화 키를 요구합니다. 동의를 받으면 이 사이트에서 암호화된 메시지를 작성하여 회원님에게 전송할 수 있습니다.", "description": "$1 is the web3 site name" @@ -1656,6 +1857,9 @@ "estimatedFee": { "message": "예상 수수료" }, + "estimatedFeeTooltip": { + "message": "네트워크에서 트랜잭션을 처리하기 위해 지불한 금액입니다." + }, "ethGasPriceFetchWarning": { "message": "현재 주요 가스 견적 서비스를 사용할 수 없으므로 백업 가스 가격을 제공합니다." }, @@ -1675,6 +1879,12 @@ "etherscanViewOn": { "message": "Etherscan에서 보기" }, + "existingChainId": { + "message": "입력한 정보는 기존 체인 ID와 연결되어 있습니다." + }, + "existingRpcUrl": { + "message": "이 URL은 다른 체인 ID랑 연결되어 있습니다." + }, "expandView": { "message": "보기 확장" }, @@ -1698,7 +1908,7 @@ "message": "추천 닉네임" }, "externalNameSourcesSettingDescription": { - "message": "회원님이 상호작용하는 주소에 대한 추천 닉네임을 Etherscan, Infura, Lens Protocol과 같은 제3자에게서 가져옵니다. 이러한 제3자는 해당 주소와 회원님의 IP 주소를 볼 수 있습니다. 회원님의 계정 주소는 제3자에게 노출되지 않습니다." + "message": "Etherscan, Infura, Lens Protocol과 같은 제삼자에게서 회원님이 상호작용하는 주소에 대한 추천 닉네임을 가져올 것입니다. 이러한 제삼자는 해당 주소와 회원님의 IP 주소를 볼 수 있습니다. 회원님의 계정 주소는 제삼자에게 노출되지 않습니다." }, "failed": { "message": "실패" @@ -1729,6 +1939,9 @@ "message": "파일 가져오기가 작동하지 않나요? 여기를 클릭하세요.", "description": "Helps user import their account from a JSON file" }, + "findTheRightChainId": { + "message": "다음에서 적합한 것을 찾아보세요" + }, "flaskWelcomeUninstall": { "message": "이 확장 프로그램을 삭제해야 합니다", "description": "This request is shown on the Flask Welcome screen. It is intended for non-developers, and will be bolded." @@ -1768,6 +1981,9 @@ "forgotPassword": { "message": "비밀번호를 잊으셨나요?" }, + "form": { + "message": "양식" + }, "from": { "message": "발신" }, @@ -1898,7 +2114,7 @@ "message": "Goerli 테스트 네트워크" }, "gotIt": { - "message": "확인했습니다!" + "message": "확인" }, "grantedToWithColon": { "message": "부여 대상:" @@ -1976,6 +2192,12 @@ "highLowercase": { "message": "높음" }, + "highestCurrentBid": { + "message": "현재 최고 입찰" + }, + "highestFloorPrice": { + "message": "최고 바닥 가격" + }, "history": { "message": "기록" }, @@ -2315,12 +2537,21 @@ "knownTokenWarning": { "message": "이 작업은 지갑에 목록에 등재되어 피싱에 사용될 수 있는 토큰을 편집합니다. 해당 토큰이 나타내는 내용을 변경하려는 경우에만 작업을 승인하세요. $1에 대해 알아보기" }, + "l1Fee": { + "message": "L1 수수료" + }, + "l1FeeTooltip": { + "message": "L1 가스비" + }, + "l2Fee": { + "message": "L2 수수료" + }, + "l2FeeTooltip": { + "message": "L2 가스비" + }, "lastConnected": { "message": "마지막 연결" }, - "lastPriceSold": { - "message": "최근 판매 가격" - }, "lastSold": { "message": "최근 판매" }, @@ -2492,6 +2723,12 @@ "message": "다른 사람이 이 화면을 보고 있지는 않은지 확인하세요.", "description": "Warning to users to be care while creating and saving their new Secret Recovery Phrase" }, + "marketCap": { + "message": "시가 총액" + }, + "marketDetails": { + "message": "시장 상세 정보" + }, "max": { "message": "최대" }, @@ -2501,6 +2738,9 @@ "maxFee": { "message": "최대 요금" }, + "maxFeeTooltip": { + "message": "트랜잭션에 지불하기 위해 제공되는 최대 수수료입니다." + }, "maxPriorityFee": { "message": "최대 우선 요금" }, @@ -2548,12 +2788,19 @@ "methodData": { "message": "메소드" }, + "methodDataTransactionDesc": { + "message": "디코딩된 입력 데이터를 기반으로 실행되는 함수입니다." + }, "methodNotSupported": { "message": "이 계정에서는 지원되지 않습니다." }, "metrics": { "message": "메트릭" }, + "millionAbbreviation": { + "message": "M", + "description": "Shortened form of 'million'" + }, "mismatchAccount": { "message": "선택한 계정($1)이 서명하려는 계정($2)과 다릅니다" }, @@ -2666,7 +2913,7 @@ "description": "Description below header used on Permission Connect screen for native permissions." }, "nativeToken": { - "message": "이 네트워크의 네이티브 토큰은 $1입니다. 이는 가스비 지불에 사용하는 토큰입니다.", + "message": "이 네트워크의 네이티브 토큰은 $1입니다. 이는 가스비 지불에 사용하는 토큰입니다. ", "description": "$1 represents the name of the native token on the current network" }, "nativeTokenScamWarningConversion": { @@ -2735,6 +2982,9 @@ "networkNameBase": { "message": "기본" }, + "networkNameBitcoin": { + "message": "비트코인" + }, "networkNameDefinition": { "message": "이 네트워크와 연결된 이름입니다." }, @@ -2759,6 +3009,9 @@ "networkNameZkSyncEra": { "message": "zkSync Era" }, + "networkOptions": { + "message": "네트워크 옵션" + }, "networkProvider": { "message": "네트워크 공급업체" }, @@ -2827,6 +3080,9 @@ "newNetworkAdded": { "message": "“$1”(을)를 성공적으로 추가했습니다!" }, + "newNetworkEdited": { + "message": "“$1”(을)를 성공적으로 편집했습니다!" + }, "newNftAddedMessage": { "message": "NFT를 성공적으로 추가했습니다!" }, @@ -2863,8 +3119,11 @@ "nftAlreadyAdded": { "message": "NFT가 이미 추가되었습니다." }, + "nftAutoDetectionEnabled": { + "message": "NFT 자동 감지 활성화 완료" + }, "nftDisclaimer": { - "message": "면책 조항: MetaMask는 소스 URL에서 미디어 파일을 가져옵니다. 이러한 URL은 때때로 NFT가 민팅된 마켓플레이스에서 변경되기도 합니다." + "message": "면책 조항: MetaMask는 소스 URL에서 미디어 파일을 가져옵니다. 이러한 URL은 때때로 NFT가 민팅된 마켓플레이스에 의해 변경되기도 합니다." }, "nftOptions": { "message": "NFT 옵션" @@ -2913,6 +3172,9 @@ "noDomainResolution": { "message": "도메인에 대한 해결 방법이 제공되지 않았습니다." }, + "noHardwareWalletOrSnapsSupport": { + "message": "Snap 및 대부분의 하드웨어 지갑은 현재 사용 중인 브라우저 버전에서 작동하지 않습니다." + }, "noNFTs": { "message": "아직 NFT가 없음" }, @@ -2935,7 +3197,7 @@ "message": "웹캠을 찾을 수 없음" }, "nonCustodialAccounts": { - "message": "MetaMask Institutional은 이러한 계정을 비밀복구구문 백업에 사용하려는 경우 비수탁형 계정을 사용할 수 있도록 허용합니다." + "message": "이러한 계정을 비밀복구구문 백업에 사용하려는 경우 MetaMask Institutional은 비수탁형 계정을 사용할 수 있도록 허용합니다." }, "nonce": { "message": "논스" @@ -2943,8 +3205,8 @@ "nonceField": { "message": "트랜잭션 논스 맞춤화" }, - "nonceFieldDescription": { - "message": "이 기능을 켜면 컨펌 화면에서 논스(트랜잭션 번호)를 변경할 수 있습니다. 이는 고급 기능이므로 주의해서 사용해야 합니다." + "nonceFieldDesc": { + "message": "자산을 전송할 때 논스(트랜잭션 번호)를 변경하려면 이 기능을 켭니다. 이 기능은 고급 기능이므로 신중하게 사용하세요." }, "nonceFieldHeading": { "message": "맞춤 논스" @@ -3146,6 +3408,9 @@ "numberOfNewTokensDetectedSingular": { "message": "계정에서 1개의 새 토큰을 찾았습니다" }, + "numberOfTokens": { + "message": "토큰 수" + }, "ofTextNofM": { "message": "/" }, @@ -3161,8 +3426,36 @@ "on": { "message": "켜기" }, - "onboarding": { - "message": "온보딩" + "onboardedMetametricsAccept": { + "message": "동의합니다" + }, + "onboardedMetametricsDisagree": { + "message": "아니요, 괜찮습니다" + }, + "onboardedMetametricsKey1": { + "message": "최신 개발" + }, + "onboardedMetametricsKey2": { + "message": "제품 특징" + }, + "onboardedMetametricsKey3": { + "message": "기타 관련 프로모션 자료" + }, + "onboardedMetametricsLink": { + "message": "MetaMetrics" + }, + "onboardedMetametricsParagraph1": { + "message": "$1 외에도 데이터를 활용하여 사용자가 마케팅 커뮤니케이션과 상호 작용하는 방식을 이해하고자 합니다.", + "description": "$1 represents the 'onboardedMetametricsLink' locale string" + }, + "onboardedMetametricsParagraph2": { + "message": "이를 통해 사용자와 공유하는 다음과 같은 콘텐츠를 최적화할 수 있습니다." + }, + "onboardedMetametricsParagraph3": { + "message": "사용자가 제공한 데이터는 절대 판매하지 않습니다. 데이터 수집은 언제든지 거부할 수 있습니다." + }, + "onboardedMetametricsTitle": { + "message": "사용자 경험을 개선할 수 있도록 도와주세요" }, "onboardingAdvancedPrivacyIPFSDescription": { "message": "IPFS 게이트웨이를 사용하면 타사 호스팅 데이터에 액세스하여 이를 볼 수 있습니다. 사용자 지정 IPFS 게이트웨이를 추가하셔도 좋고 기본 설정을 계속 사용하셔도 됩니다." @@ -3200,9 +3493,6 @@ "onboardingMetametricsDescription2": { "message": "메트릭을 수집할 때는 항상..." }, - "onboardingMetametricsDisagree": { - "message": "괜찮습니다" - }, "onboardingMetametricsInfuraTerms": { "message": "이 데이터를 다른 목적으로 사용하기로 결정하면 알려드리겠습니다. 자세한 내용은 $1을(를) 참고하세요. 언제든지 설정으로 이동하여 해제할 수 있습니다.", "description": "$1 represents `onboardingMetametricsInfuraTermsPolicy`" @@ -3237,6 +3527,9 @@ "onboardingMetametricsTitle": { "message": "MetaMask 개선에 도움을 주세요" }, + "onboardingMetametricsUseDataCheckbox": { + "message": "해당 데이터를 사용하여 사용자가 마케팅 커뮤니케이션과 어떻게 상호 작용하는지 파악할 것입니다. 관련 뉴스(예: 제품 기능 및 기타 자료)를 공유할 수 있습니다." + }, "onboardingPinExtensionBillboardAccess": { "message": "전체 액세스" }, @@ -3280,6 +3573,22 @@ "message": "피싱 감지 경고는 $1과(와)의 통신에 의존합니다. jsDeliver는 회원님의 IP 주소에 액세스할 수 있습니다. $2 보기.", "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" }, + "oneDayAbbreviation": { + "message": "1일", + "description": "Shortened form of '1 day'" + }, + "oneMonthAbbreviation": { + "message": "1개월", + "description": "Shortened form of '1 month'" + }, + "oneWeekAbbreviation": { + "message": "1주일", + "description": "Shortened form of '1 week'" + }, + "oneYearAbbreviation": { + "message": "1년", + "description": "Shortened form of '1 year'" + }, "onekey": { "message": "OneKey" }, @@ -3632,6 +3941,9 @@ "permissionsPageTourTitle": { "message": "이제 연결된 사이트에 권한이 부여됩니다" }, + "permitSimulationDetailInfo": { + "message": "내 계정에서 이만큼의 토큰을 사용할 수 있도록 승인합니다." + }, "personalAddressDetected": { "message": "개인 주소가 발견되었습니다. 토큰 계약 주소를 입력하세요." }, @@ -3664,6 +3976,10 @@ "popularCustomNetworks": { "message": "인기 사용자 정의 네트워크" }, + "popularNetworkAddToolTip": { + "message": "이러한 네트워크 중 일부는 제삼자에 의존합니다. 이러한 연결은 안정성이 떨어지거나 제삼자가 활동을 추적할 수 있습니다. $1", + "description": "$1 is Learn more link" + }, "portfolio": { "message": "포트폴리오" }, @@ -3676,6 +3992,12 @@ "prev": { "message": "이전" }, + "price": { + "message": "가격" + }, + "priceUnavailable": { + "message": "가격 사용 불가" + }, "primaryCurrencySetting": { "message": "기본 통화" }, @@ -3828,6 +4150,9 @@ "quoteRate": { "message": "견적 비율" }, + "rank": { + "message": "순위" + }, "reAddAccounts": { "message": "다른 계정을 다시 추가" }, @@ -3997,9 +4322,6 @@ "reset": { "message": "재설정" }, - "resetStates": { - "message": "상태 초기화" - }, "resetWallet": { "message": "지갑 초기화" }, @@ -4135,6 +4457,9 @@ "searchAccounts": { "message": "계정 검색" }, + "searchNfts": { + "message": "NFT 검색" + }, "searchTokens": { "message": "토큰 검색" }, @@ -4179,13 +4504,10 @@ "seedPhraseIntroRecommendedButtonCopy": { "message": "내 지갑 보호(권장)" }, - "seedPhraseIntroSidebarBulletFour": { - "message": "적어서 여러 비밀 장소에 보관하세요." - }, "seedPhraseIntroSidebarBulletOne": { - "message": "비밀번호 수탁자에 저장" + "message": "적어서 여러 비밀 장소에 보관하세요." }, - "seedPhraseIntroSidebarBulletThree": { + "seedPhraseIntroSidebarBulletTwo": { "message": "대여 금고에 보관." }, "seedPhraseIntroSidebarCopyOne": { @@ -4255,10 +4577,10 @@ "message": "토큰 선택" }, "selectNFTPrivacyPreference": { - "message": "설정에서 NFT 감지 켜기" + "message": "NFT 자동 감기 지능 켜기" }, "selectPathHelp": { - "message": "아래에 기존 Ledger 계정이 표시되지 않는다면 경로를 \"Legacy (MEW / MyCrypto)\"로 변경해 보세요." + "message": "원하는 계정이 표시되지 않는다면 HD 경로 또는 현재 선택한 네트워크를 전환해 보세요." }, "selectType": { "message": "유형 선택" @@ -4269,9 +4591,6 @@ "send": { "message": "보내기" }, - "sendAToken": { - "message": "토큰 보내기" - }, "sendBugReport": { "message": "버그 리포트 전송" }, @@ -4322,9 +4641,6 @@ "sepolia": { "message": "Sepolia 테스트 네트워크" }, - "serviceWorkerKeepAlive": { - "message": "서비스 작업자 유지" - }, "setAdvancedPrivacySettingsDetails": { "message": "이와 같이 MetaMask는 신용있는 타사의 서비스를 사용하여 제품 가용성과 안전성을 향상합니다." }, @@ -4381,6 +4697,9 @@ "showIncomingTransactionsExplainer": { "message": "이는 네트워크마다 다른 제삼자 API에 의존하며, 이더리움 주소와 IP 주소가 노출됩니다." }, + "showLess": { + "message": "간략히 보기" + }, "showMore": { "message": "더 보기" }, @@ -4411,9 +4730,6 @@ "signatureRequestGuidance": { "message": "요청하는 사이트를 신뢰하고 그 내용을 완전히 이해하는 경우에만 이 메시지에 서명하세요." }, - "signatureRequestWarning": { - "message": "본 메시지에 서명하는 행위는 위험의 가능성을 내포하고 있습니다. 본 서명을 통해 메시지 발신 당사자에게 귀하의 계정 및 모든 자산에 대해 완전한 권한을 부여할 수 있기 때문입니다. 이를 통해 계정의 모든 잔액을 인출할 수 있기도 하다는 뜻입니다. 주의하여 진행하세요. $1" - }, "signed": { "message": "서명완료" }, @@ -4423,6 +4739,9 @@ "signing": { "message": "서명" }, + "signingInWith": { + "message": "다음으로 로그인:" + }, "simulationDetailsFailed": { "message": "추정치를 불러오는 동안 오류가 발생했습니다." }, @@ -4460,6 +4779,24 @@ "simulationsSettingSubHeader": { "message": "예상 잔액 변동" }, + "siweIssued": { + "message": "발행됨" + }, + "siweNetwork": { + "message": "네트워크" + }, + "siweRequestId": { + "message": "요청 ID" + }, + "siweResources": { + "message": "리소스" + }, + "siweSignatureSimulationDetailInfo": { + "message": "사이트에 로그인 중이며 계정에 예상되는 변경 사항이 없습니다." + }, + "siweURI": { + "message": "URL" + }, "skip": { "message": "건너뛰기" }, @@ -4563,6 +4900,14 @@ "snapAccountsDescription": { "message": "제삼자 Snap이 제어하는 계정입니다." }, + "snapConnectTo": { + "message": "$1에 연결", + "description": "$1 is the website URL or a Snap name. Used for Snaps pre-approved connections." + }, + "snapConnectionPermissionDescription": { + "message": "승인 없이 $1이(가) $2에 자동으로 연결되도록 하세요.", + "description": "Used for Snap pre-approved connections. $1 is the Snap name, $2 is a website URL." + }, "snapConnectionWarning": { "message": "$1에서 $2 사용을 원합니다.", "description": "$2 is the snap and $1 is the dapp requesting connection to the snap." @@ -4574,6 +4919,9 @@ "snapDetailWebsite": { "message": "웹사이트" }, + "snapHomeMenu": { + "message": "Snap 홈 메뉴" + }, "snapInstallRequest": { "message": "$1 설치는 다음과 같은 권한을 허용합니다.", "description": "$1 is the snap name." @@ -4696,6 +5044,9 @@ "source": { "message": "소스" }, + "speed": { + "message": "속도" + }, "speedUp": { "message": "가속화" }, @@ -4730,6 +5081,9 @@ "spendLimitTooLarge": { "message": "지출 한도가 너무 큼" }, + "spender": { + "message": "사용자" + }, "spendingCap": { "message": "지출 한도" }, @@ -4850,9 +5204,6 @@ "stateLogsDescription": { "message": "상태 로그에 공개 계정 주소와 전송된 트랜잭션이 있습니다." }, - "states": { - "message": "상태" - }, "status": { "message": "상태" }, @@ -4957,6 +5308,13 @@ "submitted": { "message": "제출됨" }, + "suggestedBySnap": { + "message": "제안인: $1", + "description": "$1 is the snap name" + }, + "suggestedTokenName": { + "message": "추천 이름:" + }, "suggestedTokenSymbol": { "message": "추천 티커 심볼:" }, @@ -5071,7 +5429,7 @@ "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." }, "swapFetchingQuotes": { - "message": "견적을 가져오는 중" + "message": "견적을 가져오는 중..." }, "swapFetchingQuotesErrorDescription": { "message": "음.... 문제가 발생했습니다. 다시 시도해 보고 오류가 해결되지 않는다면 고객 지원에 문의하세요." @@ -5419,7 +5777,7 @@ "description": "$1 represents the account name, $2 represents the network name" }, "switchedTo": { - "message": "다음으로 변경했습니다:" + "message": "다음을 사용 중입니다:" }, "switchingNetworksCancelsPendingConfirmations": { "message": "네트워크를 전환하면 대기 중인 모든 컨펌 작업이 취소됩니다." @@ -5467,6 +5825,10 @@ "thisCollection": { "message": "이 컬렉션" }, + "threeMonthsAbbreviation": { + "message": "3개월", + "description": "Shortened form of '3 months'" + }, "time": { "message": "시간" }, @@ -5480,45 +5842,6 @@ "message": "수신: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleEthSignBannerDescription": { - "message": "피싱 공격의 위험이 있습니다. eth_sign을 비활성화해 자신을 보호하세요." - }, - "toggleEthSignDescriptionField": { - "message": "이 설정을 사용하면 읽을 수 없는 요청에 서명하게 될 위험이 있습니다. 이해할 수 없는 메시지에 서명하면 자금이나 NFT 손실에 동의할 수도 있습니다." - }, - "toggleEthSignField": { - "message": "Eth_서명 요청" - }, - "toggleEthSignModalBannerBoldText": { - "message": " 사기 가능성이 있습니다" - }, - "toggleEthSignModalBannerText": { - "message": "이 설정을 켜라는 요청을 받았다면," - }, - "toggleEthSignModalCheckBox": { - "message": "eth_sign 요청을 활성화하면 내 모든 자금과 NFT를 잃을 수도 있다는 사실을 이해합니다. " - }, - "toggleEthSignModalDescription": { - "message": "eth_sign 요청을 허용하면 피싱 공격에 취약해질 수 있습니다. 항상 URL을 검토하고 코드가 포함된 메시지에 서명할 때는 주의하세요." - }, - "toggleEthSignModalFormError": { - "message": "텍스트가 잘못되었습니다" - }, - "toggleEthSignModalFormLabel": { - "message": "계속하려면 \"본인은 이해한 사항에만 서명합니다\"라고 입력하세요" - }, - "toggleEthSignModalFormValidation": { - "message": "본인은 이해한 사항에만 서명합니다" - }, - "toggleEthSignModalTitle": { - "message": "위험을 감수하고 사용하기" - }, - "toggleEthSignOff": { - "message": "끄기(권장 사항)" - }, - "toggleEthSignOn": { - "message": "켜기(비권장 사항)" - }, "toggleRequestQueueDescription": { "message": "이 기능을 이용하면 모든 사이트에 한 가지 네트워크를 선택하여 사용하는 대신, 사이트별로 네트워크를 다르게 선택할 수 있습니다. 이 기능을 사용하면 수동으로 네트워크를 전환하지 않아도 되므로 특정 사이트에서 사용자 경험이 저해되지 않습니다." }, @@ -5546,6 +5869,9 @@ "tokenContractAddress": { "message": "토큰 계약 주소" }, + "tokenDecimal": { + "message": "토큰 십진수" + }, "tokenDecimalFetchFailed": { "message": "토큰 십진수가 필요합니다. $1에서 찾아보세요" }, @@ -5568,7 +5894,10 @@ "message": "토큰 사기 및 보안 위험" }, "tokenShowUp": { - "message": "토큰이 지갑에서 자동으로 표시되지 않을 수 있습니다." + "message": "토큰이 지갑에 자동으로 표시되지 않을 수 있습니다. " + }, + "tokenStandard": { + "message": "토큰 표준" }, "tokenSymbol": { "message": "토큰 기호" @@ -5580,6 +5909,9 @@ "message": "$1개의 새 토큰을 찾았습니다", "description": "$1 is the number of new tokens detected" }, + "tokensInCollection": { + "message": "컬렉션 내 토큰" + }, "tooltipApproveButton": { "message": "이해했습니다" }, @@ -5595,6 +5927,9 @@ "total": { "message": "합계" }, + "totalVolume": { + "message": "총 거래량" + }, "transaction": { "message": "트랜잭션" }, @@ -5610,6 +5945,9 @@ "transactionCreated": { "message": "$2에서 $1 값으로 생성된 트랜잭션" }, + "transactionDataFunction": { + "message": "기능" + }, "transactionDetailDappGasMoreInfo": { "message": "추천 사이트" }, @@ -5700,6 +6038,10 @@ "transferFrom": { "message": "전송 위치" }, + "trillionAbbreviation": { + "message": "T", + "description": "Shortened form of 'trillion'" + }, "troubleConnectingToLedgerU2FOnFirefox": { "message": "Ledger 연결에 오류가 발생했습니다. $1", "description": "$1 is a link to the wallet connection guide;" @@ -5778,6 +6120,9 @@ "message": "U2F", "description": "A name on an API for the browser to interact with devices that support the U2F protocol. On some browsers we use it to connect MetaMask to Ledger devices." }, + "unMatchedChain": { + "message": "기록에 따르면 이 URL은 이 체인 ID의 알려진 제공업체와 일치하지 않습니다." + }, "unapproved": { "message": "승인되지 않음" }, @@ -5829,12 +6174,21 @@ "update": { "message": "업데이트" }, + "updateOrEditNetworkInformations": { + "message": "정보를 업데이트하거나" + }, "updateRequest": { "message": "업데이트 요청" }, "updatedWithDate": { "message": "$1에 업데이트됨" }, + "uploadDropFile": { + "message": "여기에 파일을 드롭" + }, + "uploadFile": { + "message": "파일 업로드" + }, "urlErrorMsg": { "message": "URI에는 적절한 HTTP/HTTPS 접두사가 필요합니다." }, @@ -6050,6 +6404,12 @@ "whatsThis": { "message": "이것은 무엇인가요?" }, + "wrongChainId": { + "message": "이 체인 ID는 네트워크 이름과 일치하지 않습니다." + }, + "wrongNetworkName": { + "message": "기록에 따르면 네트워크 이름이 이 체인 ID와 일치하지 않습니다." + }, "xOfYPending": { "message": "$1/$2개 보류 중", "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" @@ -6073,8 +6433,11 @@ "yourAccounts": { "message": "계정" }, - "yourFundsMayBeAtRisk": { - "message": "자금이 위험할 수 있습니다" + "yourActivity": { + "message": "내 활동" + }, + "yourBalance": { + "message": "내 잔액" }, "yourNFTmayBeAtRisk": { "message": "NFT가 위험할 수 있습니다" diff --git a/app/_locales/ph/messages.json b/app/_locales/ph/messages.json index fbace0f2117f..debc2d35640a 100644 --- a/app/_locales/ph/messages.json +++ b/app/_locales/ph/messages.json @@ -900,9 +900,6 @@ "nonceField": { "message": "I-customize ang nonce ng transaksyon" }, - "nonceFieldDescription": { - "message": "I-on ito para baguhin ang nonce (numero ng transaksyon) sa mga screen ng kumpirmasyon. Isa itong advanced na feature, gamitin nang may pag-iingat." - }, "nonceFieldHeading": { "message": "Custom na Nonce" }, @@ -1116,13 +1113,10 @@ "securityAndPrivacy": { "message": "Seguridad at Privacy" }, - "seedPhraseIntroSidebarBulletFour": { - "message": "Isulat ito at itabi sa maraming tagong lugar." - }, "seedPhraseIntroSidebarBulletOne": { - "message": "I-save sa password manager" + "message": "Isulat ito at itabi sa maraming tagong lugar." }, - "seedPhraseIntroSidebarBulletThree": { + "seedPhraseIntroSidebarBulletTwo": { "message": "Ilagay sa safe-deposit box." }, "seedPhraseIntroSidebarCopyOne": { diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json index 6e3644909a00..5706a91b473a 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -42,7 +42,7 @@ "message": "Conecte sua carteira de hardware QR" }, "QRHardwareWalletSteps2Description": { - "message": "Ngrave (em breve)" + "message": "Ngrave Zero" }, "SIWEAddressInvalid": { "message": "O endereço na solicitação de entrada não coincide com o endereço da conta que você está usando para entrar." @@ -162,6 +162,9 @@ "accountSelectionRequired": { "message": "Você precisa selecionar uma conta!" }, + "accountTypeNotSupported": { + "message": "Tipo de conta não compatível" + }, "accounts": { "message": "Contas" }, @@ -277,6 +280,12 @@ "addNewAccount": { "message": "Adicionar uma nova conta Ethereum" }, + "addNewBitcoinAccount": { + "message": "Adicionar uma nova conta Bitcoin (Beta)" + }, + "addNewBitcoinTestnetAccount": { + "message": "Adicionar uma nova conta Bitcoin (Testnet)" + }, "addNewToken": { "message": "Adicionar novo token" }, @@ -286,6 +295,9 @@ "addNfts": { "message": "Adicionar NFTs" }, + "addRpcUrl": { + "message": "Adicionar URL da RPC" + }, "addSnapAccountToggle": { "message": "Ativar \"Adicionar Snap da conta (Beta)\"" }, @@ -305,12 +317,21 @@ "message": "Não consegue encontrar um token? Você pode adicioná-lo manualmente colando seu endereço. Os endereços de contrato do token se encontram em $1", "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" }, + "addUrl": { + "message": "Adicionar URL" + }, "addingCustomNetwork": { "message": "Adicionar rede" }, "addingTokens": { "message": "Adicionando tokens" }, + "additionalNetworks": { + "message": "Redes adicionais" + }, + "additionalRpcUrl": { + "message": "URL da RPC adicional" + }, "address": { "message": "Endereço" }, @@ -326,6 +347,18 @@ "advancedConfiguration": { "message": "Configurações avançadas" }, + "advancedDetailsDataDesc": { + "message": "Dados" + }, + "advancedDetailsHexDesc": { + "message": "Hexadecimal" + }, + "advancedDetailsNonceDesc": { + "message": "Nonce" + }, + "advancedDetailsNonceTooltip": { + "message": "Esse é o número de transação de uma conta. O nonce da primeira transação é 0 e aumenta em ordem sequencial." + }, "advancedGasFeeDefaultOptIn": { "message": "Salvar esses valores como padrão para a rede $1.", "description": "$1 is the current network name." @@ -349,6 +382,18 @@ "alert": { "message": "Alerta" }, + "alertActionBuy": { + "message": "Comprar ETH" + }, + "alertActionUpdateGas": { + "message": "Atualizar limite de gás" + }, + "alertActionUpdateGasFee": { + "message": "Atualizar taxa" + }, + "alertActionUpdateGasFeeLevel": { + "message": "Atualizar opções de gás" + }, "alertBannerMultipleAlertsDescription": { "message": "Se você aprovar esta solicitação, um terceiro conhecido por aplicar golpes poderá se apropriar de todos os seus ativos." }, @@ -358,6 +403,36 @@ "alertDisableTooltip": { "message": "Isso pode ser alterado em \"Configurações > Alertas\"" }, + "alertMessageGasEstimateFailed": { + "message": "Não conseguimos fornecer uma taxa precisa, e essa estimativa pode estar alta. Sugerimos que você informe um limite de gás personalizado, mas há o risco de a transação falhar mesmo assim." + }, + "alertMessageGasFeeLow": { + "message": "Ao escolher uma taxa baixa, a expectativa é de transações mais lentas e tempos de espera maiores. Para transações mais rápidas, escolha as opções de taxa Mercado ou Agressiva." + }, + "alertMessageGasTooLow": { + "message": "Para continuar com essa transação, você precisará aumentar o limite de gás para 21000 ou mais." + }, + "alertMessageInsufficientBalance": { + "message": "Você não tem ETH suficiente em sua conta para pagar as taxas de transação." + }, + "alertMessageNetworkBusy": { + "message": "Os preços do gás são altos e as estimativas são menos precisas." + }, + "alertMessageNoGasPrice": { + "message": "Não podemos prosseguir com essa transação até você atualizar manualmente a taxa." + }, + "alertMessagePendingTransactions": { + "message": "Essa transação não será processada até que a transação anterior seja concluída. Saiba como cancelar ou acelerar uma transação." + }, + "alertMessageSignInDomainMismatch": { + "message": "O site solicitante não é o mesmo em que você está entrando. Isso pode se tratar de uma tentativa de roubar suas credenciais de login." + }, + "alertMessageSignInWrongAccount": { + "message": "Este site está pedindo que você entre usando a conta incorreta." + }, + "alertMessageSigningOrSubmitting": { + "message": "Essa transação só será processada quando sua transação anterior for concluída." + }, "alertModalAcknowledge": { "message": "Eu reconheço o risco e ainda quero prosseguir" }, @@ -367,6 +442,33 @@ "alertModalReviewAllAlerts": { "message": "Conferir todos os alertas" }, + "alertReasonGasEstimateFailed": { + "message": "Taxa imprecisa" + }, + "alertReasonGasFeeLow": { + "message": "Velocidade lenta" + }, + "alertReasonGasTooLow": { + "message": "Baixo limite de gás" + }, + "alertReasonInsufficientBalance": { + "message": "Fundos insuficientes" + }, + "alertReasonNetworkBusy": { + "message": "Rede ocupada" + }, + "alertReasonNoGasPrice": { + "message": "Estimativa de taxa indisponível" + }, + "alertReasonPendingTransactions": { + "message": "Transação pendente" + }, + "alertReasonSignIn": { + "message": "Solicitação de entrada suspeita" + }, + "alertReasonWrongAccount": { + "message": "Conta incorreta" + }, "alertSettingsUnconnectedAccount": { "message": "Navegando em um site com uma conta não conectada selecionada" }, @@ -398,6 +500,12 @@ "allPermissions": { "message": "Todas as permissões" }, + "allTimeHigh": { + "message": "Alta histórica" + }, + "allTimeLow": { + "message": "Baixa histórica" + }, "allYourNFTsOf": { "message": "Todos os seus NFTs de $1", "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" @@ -436,9 +544,6 @@ "message": "$1 e $2", "description": "$1 is the first item, $2 is the second item. Used in Snap Install Warning modal." }, - "announcements": { - "message": "Comunicados" - }, "appDescription": { "message": "Uma carteira de Ethereum no seu navegador", "description": "The description of the application" @@ -516,7 +621,7 @@ "message": "Opções do ativo" }, "attemptSendingAssets": { - "message": "Se você tentar enviar ativos diretamente de uma rede para outra, isso poderá resultar na perda permanente deles. Certifique-se de usar uma ponte." + "message": "Você poderá perder seus ativos se tentar enviá-los a partir de outra rede. Transfira fundos entre redes com segurança usando uma ponte." }, "attemptSendingAssetsWithPortfolio": { "message": "Você poderá perder seus ativos se tentar enviá-los a partir de outra rede. Transfira os fundos com segurança entre redes usando uma ponte, como $1" @@ -524,11 +629,14 @@ "attemptToCancelSwapForFree": { "message": "Tentar cancelar a troca sem custo" }, + "attributes": { + "message": "Atributos" + }, "attributions": { "message": "Atribuições" }, "auroraRpcDeprecationMessage": { - "message": "O URL de RPC (Chamadas de Procedimento Remoto) da Infura não oferece mais suporte à rede Aurora." + "message": "A URL de RPC (Chamadas de Procedimento Remoto) da Infura não suporta mais Aurora." }, "authorizedPermissions": { "message": "Você concedeu as seguintes permissões" @@ -594,6 +702,10 @@ "basicConfigurationBannerTitle": { "message": "A funcionalidade básica está desativada" }, + "basicConfigurationDescription": { + "message": "A MetaMask oferece recursos básicos como detalhes de token e configurações de gás por meio de serviços de internet. Quando você usa serviços de internet, seu endereço IP é compartilhado, neste caso com a MetaMask. É exatamente igual a quando você visita qualquer site. A MetaMask usa esses dados temporariamente e nunca os vende. Você pode usar uma VPN ou desligar esses serviços, mas isso poderá afetar sua experiência com a MetaMask. Para saber mais, leia nossa $1.", + "description": "$1 is to be replaced by the message for privacyMsg, and will link to https://consensys.io/privacy-policy" + }, "basicConfigurationLabel": { "message": "Funcionalidade básica" }, @@ -637,6 +749,29 @@ "betaWalletCreationSuccessReminder2": { "message": "O MetaMask Beta nunca pedirá sua Frase de Recuperação Secreta." }, + "billionAbbreviation": { + "message": "B", + "description": "Shortened form of 'billion'" + }, + "bitcoinActivityNotSupported": { + "message": "Atividades com Bitcoin não são suportadas" + }, + "bitcoinSupportSectionTitle": { + "message": "Bitcoin" + }, + "bitcoinSupportToggleDescription": { + "message": "Ativar esse recurso lhe dará a opção de adicionar uma conta Bitcoin à sua extensão da MetaMask derivada de sua Frase de Recuperação Secreta existente. Este é um recurso beta experimental, portanto seu uso será por sua conta e risco. Para nos dar seu feedback sobre esta nova experiência Bitcoin, preencha este $1.", + "description": "$1 is the link to a product feedback form" + }, + "bitcoinSupportToggleTitle": { + "message": "Ative \"Adicionar uma nova conta Bitcoin (Beta)\"" + }, + "bitcoinTestnetSupportToggleDescription": { + "message": "Ao ativar esse recurso, você terá a opção de adicionar uma conta Bitcoin para a rede de teste." + }, + "bitcoinTestnetSupportToggleTitle": { + "message": "Ative \"Adicionar uma nova conta Bitcoin (Testnet)\"" + }, "blockExplorerAccountAction": { "message": "Conta", "description": "This is used with viewOnEtherscan and viewInExplorer e.g View Account in Explorer" @@ -662,6 +797,9 @@ "blockaid": { "message": "Blockaid" }, + "blockaidAlertInfo": { + "message": "Não é recomendável prosseguir com esta solicitação." + }, "blockaidDescriptionApproveFarming": { "message": "Se você aprovar essa solicitação, algum terceiro conhecido por aplicar golpes poderá tomar todos os seus ativos." }, @@ -669,7 +807,7 @@ "message": "Se você aprovar essa solicitação, alguém poderá roubar seus ativos listados na Blur." }, "blockaidDescriptionErrored": { - "message": "Em razão de um erro, essa solicitação não foi confirmada pelo provedor de segurança. Prossiga com cautela." + "message": "Devido a um erro, não foi possível verificar os alertas de segurança. Prossiga somente se você confiar em todos os endereços envolvidos." }, "blockaidDescriptionMaliciousDomain": { "message": "Você está interagindo com um domínio mal-intencionado. Se você aprovar essa solicitação, poderá perder seus ativos." @@ -683,6 +821,9 @@ "blockaidDescriptionTransferFarming": { "message": "Se você aprovar essa solicitação, algum terceiro conhecido por aplicar golpes poderá tomar todos os seus ativos." }, + "blockaidDescriptionWarning": { + "message": "Este pedido pode ser fraudulento. Continue somente se você confiar em todos os endereços envolvidos." + }, "blockaidMessage": { "message": "Proteção de privacidade: nenhum dado é compartilhado com terceiros. Disponível em Arbitrum, Avalanche, BNB Chain, Mainnet da Ethereum, Linea, Optimism, Polygon, Base e Sepolia." }, @@ -690,7 +831,7 @@ "message": "Esta solicitação é enganosa" }, "blockaidTitleMayNotBeSafe": { - "message": "A solicitação pode não ser segura" + "message": "Tenha cautela" }, "blockaidTitleSuspicious": { "message": "Esta solicitação é suspeita" @@ -698,6 +839,9 @@ "blockies": { "message": "Blockies" }, + "boughtFor": { + "message": "Comprado para" + }, "bridge": { "message": "Ponte" }, @@ -782,6 +926,9 @@ "chromeRequiredForHardwareWallets": { "message": "Você precisa usar a MetaMask no Google Chrome para se conectar com a sua carteira de hardware." }, + "circulatingSupply": { + "message": "Suprimento em circulação" + }, "clear": { "message": "Limpar" }, @@ -802,7 +949,7 @@ "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" }, "clickToManuallyAdd": { - "message": "Clique aqui para adicionar os tokens manualmente." + "message": "Sempre é possível adicionar tokens manualmente." }, "close": { "message": "Fechar" @@ -816,6 +963,9 @@ "coingecko": { "message": "CoinGecko" }, + "collectionName": { + "message": "Nome da coleção" + }, "comboNoOptions": { "message": "Nenhuma opção encontrada", "description": "Default text shown in the combo field dropdown if no options." @@ -841,6 +991,9 @@ "confirmAlertModalAcknowledgeMultiple": { "message": "Confirmo que recebi os alertas e ainda quero prosseguir" }, + "confirmAlertModalAcknowledgeSingle": { + "message": "Reconheço o alerta e quero prosseguir mesmo assim" + }, "confirmAlertModalDetails": { "message": "Se você fizer login, um terceiro conhecido por aplicar golpes poderá se apropriar de todos os seus ativos. Confira os alertas antes de prosseguir." }, @@ -856,18 +1009,42 @@ "confirmConnectionTitle": { "message": "Confirmar conexão ao $1" }, + "confirmDeletion": { + "message": "Confirmar exclusão" + }, + "confirmFieldPaymaster": { + "message": "Taxa paga por" + }, + "confirmFieldTooltipPaymaster": { + "message": "A taxa dessa transação será paga pelo contrato inteligente do tesoureiro." + }, "confirmPassword": { "message": "Confirmar a senha" }, "confirmRecoveryPhrase": { "message": "Confirmar Frase de Recuperação Secreta" }, + "confirmRpcUrlDeletionMessage": { + "message": "Tem certeza de que deseja excluir o URL da RPC? Suas informações não serão salvas para essa rede." + }, "confirmTitleDescContractInteractionTransaction": { "message": "Só confirme essa transação se você entende integralmente o conteúdo e confia no site solicitante." }, + "confirmTitleDescPermitSignature": { + "message": "Este site quer permissão para gastar seus tokens." + }, + "confirmTitleDescSIWESignature": { + "message": "Um site quer que você faça login para comprovar que é titular desta conta." + }, "confirmTitleDescSignature": { "message": "Confirme esta mensagem somente se você aprova o conteúdo e confia no site solicitante." }, + "confirmTitlePermitSignature": { + "message": "Solicitação de limite de gastos" + }, + "confirmTitleSIWESignature": { + "message": "Solicitação de entrada" + }, "confirmTitleSignature": { "message": "Solicitação de assinatura" }, @@ -961,7 +1138,7 @@ "message": "Conectado com" }, "connecting": { - "message": "Conectando..." + "message": "Conectando" }, "connectingTo": { "message": "Conectando a $1" @@ -1094,6 +1271,9 @@ "createSnapAccountTitle": { "message": "Criar conta" }, + "creatorAddress": { + "message": "Endereço do criador" + }, "crossChainSwapsLink": { "message": "Faça trocas entre redes com o MetaMask Portfolio" }, @@ -1263,9 +1443,27 @@ "data": { "message": "Dados" }, + "dataCollectionForMarketing": { + "message": "Coleta de dados para marketing" + }, + "dataCollectionForMarketingDescription": { + "message": "Usaremos o MetaMetrics para saber como você interage com nossas comunicações de marketing. Poderemos compartilhar novidades relevantes (como recursos de produtos e outros materiais)." + }, + "dataCollectionWarningPopoverButton": { + "message": "OK" + }, + "dataCollectionWarningPopoverDescription": { + "message": "Você desativou a coleta de dados para fins de marketing. Isso é aplicável apenas a este dispositivo. Se você usa a MetaMask em outros dispositivos, desative-a neles também." + }, "dataHex": { "message": "Hex" }, + "dataUnavailable": { + "message": "dados não disponíveis" + }, + "dateCreated": { + "message": "Data de criação" + }, "dcent": { "message": "D'Cent" }, @@ -1295,6 +1493,9 @@ "decryptRequest": { "message": "Solicitação de descriptografia" }, + "defaultRpcUrl": { + "message": "URL padrão da RPC" + }, "delete": { "message": "Excluir" }, @@ -1311,6 +1512,9 @@ "message": "Excluir rede $1?", "description": "$1 represents the name of the network" }, + "deleteRpcUrl": { + "message": "Excluir URL da RPC" + }, "deposit": { "message": "Depositar" }, @@ -1336,18 +1540,6 @@ "details": { "message": "Detalhes" }, - "developerOptions": { - "message": "Opções para desenvolvedores" - }, - "developerOptionsResetStatesAnnouncementsDescription": { - "message": "Redefine o valor booleano de isShown para falso em todos os comunicados. Comunicados são as notificações exibidas na tela pop-up de \"Novidades\"." - }, - "developerOptionsResetStatesOnboarding": { - "message": "Redefine vários estados relacionados a integração e redireciona para a página \"Proteja sua carteira\" da integração." - }, - "developerOptionsServiceWorkerKeepAlive": { - "message": "Resulta no salvamento contínuo de um carimbo de data/hora em session.storage" - }, "disabledGasOptionToolTipMessage": { "message": "“$1” está desativado porque não satisfaz o aumento mínimo de 10% em relação à taxa de gás original.", "description": "$1 is gas estimate type which can be market or aggressive" @@ -1524,6 +1716,9 @@ "editGasTooLow": { "message": "Tempo de processamento desconhecido" }, + "editNetworkLink": { + "message": "editar a rede original" + }, "editNonceField": { "message": "Editar nonce" }, @@ -1555,6 +1750,9 @@ "enabled": { "message": "Ativado" }, + "enabledNetworks": { + "message": "Redes habilitadas" + }, "encryptionPublicKeyNotice": { "message": "$1 gostaria da sua chave pública de criptografia. Ao consentir, este site conseguirá redigir mensagens criptografadas para você.", "description": "$1 is the web3 site name" @@ -1659,6 +1857,9 @@ "estimatedFee": { "message": "Taxa estimada" }, + "estimatedFeeTooltip": { + "message": "Valor pago para processar a transação na rede." + }, "ethGasPriceFetchWarning": { "message": "O preço de backup do gás é fornecido porque a estimativa de gás principal está indisponível no momento." }, @@ -1678,6 +1879,12 @@ "etherscanViewOn": { "message": "Ver no Etherscan" }, + "existingChainId": { + "message": "As informações que você inseriu estão associadas a um ID de cadeia existente." + }, + "existingRpcUrl": { + "message": "Este URL está associado a outro ID de cadeia." + }, "expandView": { "message": "Expandir exibição" }, @@ -1701,7 +1908,7 @@ "message": "Apelidos propostos" }, "externalNameSourcesSettingDescription": { - "message": "Buscaremos os apelidos propostos para os endereços com os quais você interage em fontes terceirizadas como Etherscan, Infura e Lens Protocol. Essas fontes poderão ver esses endereços e seu endereço IP. O endereço da sua conta não será exposto a terceiros." + "message": "Buscaremos os apelidos propostos para os endereços com os quais você interage em fontes terceirizadas como Etherscan, Infura e Lens Protocol. Essas fontes poderão ver esses endereços e o seu endereço IP. O endereço da sua conta não será exposto a terceiros." }, "failed": { "message": "Falha" @@ -1732,6 +1939,9 @@ "message": "A importação de arquivo não está funcionando? Clique aqui!", "description": "Helps user import their account from a JSON file" }, + "findTheRightChainId": { + "message": "Encontre a opção correta em:" + }, "flaskWelcomeUninstall": { "message": "você deve desinstalar essa extensão", "description": "This request is shown on the Flask Welcome screen. It is intended for non-developers, and will be bolded." @@ -1771,6 +1981,9 @@ "forgotPassword": { "message": "Esqueceu a senha?" }, + "form": { + "message": "formulário" + }, "from": { "message": "De" }, @@ -1901,7 +2114,7 @@ "message": "Rede de teste Goerli" }, "gotIt": { - "message": "Entendi!" + "message": "Entendi" }, "grantedToWithColon": { "message": "Concedido a:" @@ -1979,6 +2192,12 @@ "highLowercase": { "message": "alta" }, + "highestCurrentBid": { + "message": "Maior lance atual" + }, + "highestFloorPrice": { + "message": "Maior preço mínimo" + }, "history": { "message": "Histórico" }, @@ -2318,12 +2537,21 @@ "knownTokenWarning": { "message": "Essa ação editará os tokens já listados na sua carteira, que podem ser usados para praticar phishing contra você. Só aprove se você tiver certeza de que quer alterar o que esses tokens representam. Saiba mais sobre $1" }, + "l1Fee": { + "message": "Taxa da L1" + }, + "l1FeeTooltip": { + "message": "Taxa de gás da L1" + }, + "l2Fee": { + "message": "Taxa da L2" + }, + "l2FeeTooltip": { + "message": "Taxa de gás da L2" + }, "lastConnected": { "message": "Última conexão" }, - "lastPriceSold": { - "message": "Último preço de venda" - }, "lastSold": { "message": "Última venda" }, @@ -2495,6 +2723,12 @@ "message": "Certifique-se de que ninguém está olhando", "description": "Warning to users to be care while creating and saving their new Secret Recovery Phrase" }, + "marketCap": { + "message": "Capitalização de mercado" + }, + "marketDetails": { + "message": "Detalhes do mercado" + }, "max": { "message": "Máximo" }, @@ -2504,6 +2738,9 @@ "maxFee": { "message": "Taxa máxima" }, + "maxFeeTooltip": { + "message": "Uma taxa máxima fornecida para pagar pela transação." + }, "maxPriorityFee": { "message": "Taxa de prioridade máxima" }, @@ -2551,12 +2788,19 @@ "methodData": { "message": "Método" }, + "methodDataTransactionDesc": { + "message": "Função executada com base nos dados de entrada decodificados." + }, "methodNotSupported": { "message": "Não suportado com esta conta." }, "metrics": { "message": "Métricas" }, + "millionAbbreviation": { + "message": "M", + "description": "Shortened form of 'million'" + }, "mismatchAccount": { "message": "Sua conta selecionada ($1) é diferente da conta que está tentando assinar ($2)" }, @@ -2738,6 +2982,9 @@ "networkNameBase": { "message": "Base" }, + "networkNameBitcoin": { + "message": "Bitcoin" + }, "networkNameDefinition": { "message": "O nome associado a essa rede." }, @@ -2762,6 +3009,9 @@ "networkNameZkSyncEra": { "message": "zkSync Era" }, + "networkOptions": { + "message": "Opções da rede" + }, "networkProvider": { "message": "Provedor de rede" }, @@ -2830,6 +3080,9 @@ "newNetworkAdded": { "message": "“$1” foi adicionado com sucesso!" }, + "newNetworkEdited": { + "message": "“$1” foi editada com sucesso!" + }, "newNftAddedMessage": { "message": "O NFT foi adicionado com sucesso!" }, @@ -2866,8 +3119,11 @@ "nftAlreadyAdded": { "message": "O NFT já foi adicionado." }, + "nftAutoDetectionEnabled": { + "message": "Detecção automática de NFTs ativada" + }, "nftDisclaimer": { - "message": "Aviso: a MetaMask obtém o arquivo de mídia do URL de origem. Às vezes, esse URL é modificado pelo marketplace onde o NFT foi mintado." + "message": "Aviso legal: a MetaMask obtém o arquivo de mídia do URL de origem. Às vezes, esse URL é modificado pelo marketplace onde o NFT foi mintado." }, "nftOptions": { "message": "Opções de NFT" @@ -2916,6 +3172,9 @@ "noDomainResolution": { "message": "Nenhuma resolução fornecida para o domínio." }, + "noHardwareWalletOrSnapsSupport": { + "message": "Snaps, e a maioria das carteiras de hardware, não funcionarão com a versão atual do navegador." + }, "noNFTs": { "message": "Nenhum NFT até agora" }, @@ -2946,8 +3205,8 @@ "nonceField": { "message": "Personalizar o nonce da transação" }, - "nonceFieldDescription": { - "message": "Ative essa opção para alterar o nonce (número da transação) nas telas de confirmação. Trata-se de um recurso avançado, por isso use com cuidado." + "nonceFieldDesc": { + "message": "Ative esta opção para alterar o nonce (número da transação) ao enviar ativos. Use com cautela, pois este é um recurso avançado." }, "nonceFieldHeading": { "message": "Nonce personalizado" @@ -3149,6 +3408,9 @@ "numberOfNewTokensDetectedSingular": { "message": "1 novo token encontrado nesta conta" }, + "numberOfTokens": { + "message": "Número de tokens" + }, "ofTextNofM": { "message": "de" }, @@ -3164,8 +3426,36 @@ "on": { "message": "Ativado" }, - "onboarding": { - "message": "Integração" + "onboardedMetametricsAccept": { + "message": "Concordo" + }, + "onboardedMetametricsDisagree": { + "message": "Não, obrigado" + }, + "onboardedMetametricsKey1": { + "message": "Últimos desenvolvimentos" + }, + "onboardedMetametricsKey2": { + "message": "Recursos de produtos" + }, + "onboardedMetametricsKey3": { + "message": "Outros materiais promocionais relevantes" + }, + "onboardedMetametricsLink": { + "message": "MetaMetrics" + }, + "onboardedMetametricsParagraph1": { + "message": "Além do $1, gostaríamos de usar dados para entender como você interage com comunicações de marketing.", + "description": "$1 represents the 'onboardedMetametricsLink' locale string" + }, + "onboardedMetametricsParagraph2": { + "message": "Isso nos ajuda a personalizar o que compartilhamos com você, como:" + }, + "onboardedMetametricsParagraph3": { + "message": "Lembre-se, nunca vendemos os dados que você fornece e você pode desativar quando quiser." + }, + "onboardedMetametricsTitle": { + "message": "Ajude-nos a melhorar sua experiência" }, "onboardingAdvancedPrivacyIPFSDescription": { "message": "O gateway IPFS possibilita acessar e visualizar dados hospedados por terceiros. Você pode adicionar um gateway IPFS personalizado ou continuar usando o padrão." @@ -3203,9 +3493,6 @@ "onboardingMetametricsDescription2": { "message": "Quando coletamos as métricas, elas sempre são..." }, - "onboardingMetametricsDisagree": { - "message": "Não, obrigado" - }, "onboardingMetametricsInfuraTerms": { "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`" @@ -3240,6 +3527,9 @@ "onboardingMetametricsTitle": { "message": "Ajude-nos a melhorar a MetaMask" }, + "onboardingMetametricsUseDataCheckbox": { + "message": "Usaremos esses dados para saber como você interage com nossas comunicações de marketing. Podemos compartilhar novidades relevantes (como recursos de produtos)." + }, "onboardingPinExtensionBillboardAccess": { "message": "Acesso total" }, @@ -3283,6 +3573,22 @@ "message": "Os alertas de detecção de phishing dependem de comunicação com $1. O jsDeliver terá acesso ao seu endereço IP. Veja $2.", "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" }, + "oneDayAbbreviation": { + "message": "1D", + "description": "Shortened form of '1 day'" + }, + "oneMonthAbbreviation": { + "message": "1M", + "description": "Shortened form of '1 month'" + }, + "oneWeekAbbreviation": { + "message": "1S", + "description": "Shortened form of '1 week'" + }, + "oneYearAbbreviation": { + "message": "1A", + "description": "Shortened form of '1 year'" + }, "onekey": { "message": "OneKey" }, @@ -3635,6 +3941,9 @@ "permissionsPageTourTitle": { "message": "Sites conectados agora são permissões" }, + "permitSimulationDetailInfo": { + "message": "Você está autorizando o consumidor a gastar esta quantidade de tokens de sua conta." + }, "personalAddressDetected": { "message": "Endereço pessoal detectado. Insira o endereço de contrato do token." }, @@ -3667,6 +3976,10 @@ "popularCustomNetworks": { "message": "Redes personalizadas populares" }, + "popularNetworkAddToolTip": { + "message": "Algumas dessas redes dependem de terceiros. As conexões podem ser menos confiáveis ​​ou permitir que terceiros rastreiem atividades. $1", + "description": "$1 is Learn more link" + }, "portfolio": { "message": "Portfólio" }, @@ -3679,6 +3992,12 @@ "prev": { "message": "Anterior" }, + "price": { + "message": "Preço" + }, + "priceUnavailable": { + "message": "preço não disponível" + }, "primaryCurrencySetting": { "message": "Moeda principal" }, @@ -3831,6 +4150,9 @@ "quoteRate": { "message": "Taxa de cotação" }, + "rank": { + "message": "Classificação" + }, "reAddAccounts": { "message": "readicione outras contas" }, @@ -4000,9 +4322,6 @@ "reset": { "message": "Redefinir" }, - "resetStates": { - "message": "Redefinir estados" - }, "resetWallet": { "message": "Redefinir carteira" }, @@ -4138,6 +4457,9 @@ "searchAccounts": { "message": "Pesquisar contas" }, + "searchNfts": { + "message": "Pesquisar NFTs" + }, "searchTokens": { "message": "Pesquisar tokens" }, @@ -4182,13 +4504,10 @@ "seedPhraseIntroRecommendedButtonCopy": { "message": "Proteger minha carteira (recomendado)" }, - "seedPhraseIntroSidebarBulletFour": { - "message": "Anote e guarde em vários locais secretos." - }, "seedPhraseIntroSidebarBulletOne": { - "message": "Salve em um gerenciador de senhas" + "message": "Anote e guarde em vários locais secretos." }, - "seedPhraseIntroSidebarBulletThree": { + "seedPhraseIntroSidebarBulletTwo": { "message": "Guarde em um cofre de banco." }, "seedPhraseIntroSidebarCopyOne": { @@ -4258,10 +4577,10 @@ "message": "Selecionar token" }, "selectNFTPrivacyPreference": { - "message": "Ative a detecção de NFTs nas Configurações" + "message": "Ativar detecção automática de NFTs" }, "selectPathHelp": { - "message": "Se você não vir as contas esperadas, tente alternar o caminho do HD." + "message": "Se as contas que você esperava não forem exibidas, tente mudar o caminho do HD ou a rede atualmente selecionada." }, "selectType": { "message": "Selecione o tipo" @@ -4272,9 +4591,6 @@ "send": { "message": "Enviar" }, - "sendAToken": { - "message": "Enviar um token" - }, "sendBugReport": { "message": "Envie-nos um relatório de bugs." }, @@ -4325,9 +4641,6 @@ "sepolia": { "message": "Rede de teste Sepolia" }, - "serviceWorkerKeepAlive": { - "message": "Service Worker Keep Alive" - }, "setAdvancedPrivacySettingsDetails": { "message": "A MetaMask utiliza esses serviços terceirizados de confiança para aumentar a usabilidade e a segurança dos produtos." }, @@ -4384,6 +4697,9 @@ "showIncomingTransactionsExplainer": { "message": "Isso depende de diferentes APIs de terceiros para cada rede, o que expõe seu endereço Ethereum e seu endereço IP." }, + "showLess": { + "message": "Mostrar menos" + }, "showMore": { "message": "Exibir mais" }, @@ -4414,9 +4730,6 @@ "signatureRequestGuidance": { "message": "Assine essa mensagem apenas se entende integralmente o conteúdo e confia no site solicitante." }, - "signatureRequestWarning": { - "message": "Assinar essa mensagem pode ser perigoso. Você pode estar dando controle total da sua conta e ativos à pessoa na outra ponta dessa mensagem. Isso significa que ela pode zerar sua conta a qualquer momento. Prossiga com cautela. $1." - }, "signed": { "message": "Assinado" }, @@ -4426,6 +4739,9 @@ "signing": { "message": "Assinando" }, + "signingInWith": { + "message": "Assinando com" + }, "simulationDetailsFailed": { "message": "Houve um erro ao carregar sua estimativa." }, @@ -4463,6 +4779,24 @@ "simulationsSettingSubHeader": { "message": "Estimar alterações de saldo" }, + "siweIssued": { + "message": "Emitido" + }, + "siweNetwork": { + "message": "Rede" + }, + "siweRequestId": { + "message": "ID da solicitação" + }, + "siweResources": { + "message": "Recursos" + }, + "siweSignatureSimulationDetailInfo": { + "message": "Você está fazendo login em um site e não há alterações previstas em sua conta." + }, + "siweURI": { + "message": "URL" + }, "skip": { "message": "Pular" }, @@ -4566,6 +4900,14 @@ "snapAccountsDescription": { "message": "Contas controladas por Snaps de terceiros." }, + "snapConnectTo": { + "message": "Conectar-se a $1", + "description": "$1 is the website URL or a Snap name. Used for Snaps pre-approved connections." + }, + "snapConnectionPermissionDescription": { + "message": "Permitir conexão automática de $1 com $2 sem a sua aprovação.", + "description": "Used for Snap pre-approved connections. $1 is the Snap name, $2 is a website URL." + }, "snapConnectionWarning": { "message": "$1 quer usar $2", "description": "$2 is the snap and $1 is the dapp requesting connection to the snap." @@ -4577,6 +4919,9 @@ "snapDetailWebsite": { "message": "Site" }, + "snapHomeMenu": { + "message": "Menu inicial do Snap" + }, "snapInstallRequest": { "message": "Ao instalar $1, serão dadas as permissões a seguir.", "description": "$1 is the snap name." @@ -4699,6 +5044,9 @@ "source": { "message": "Fonte" }, + "speed": { + "message": "Velocidade" + }, "speedUp": { "message": "Acelerar" }, @@ -4733,6 +5081,9 @@ "spendLimitTooLarge": { "message": "O limite de gastos está alto demais" }, + "spender": { + "message": "Consumidor" + }, "spendingCap": { "message": "Limite de gastos" }, @@ -4853,9 +5204,6 @@ "stateLogsDescription": { "message": "Logs de estado podem conter o seu endereço e transações enviadas da sua conta pública." }, - "states": { - "message": "Estados" - }, "status": { "message": "Status" }, @@ -4960,6 +5308,13 @@ "submitted": { "message": "Enviada" }, + "suggestedBySnap": { + "message": "Sugerido por $1", + "description": "$1 is the snap name" + }, + "suggestedTokenName": { + "message": "Nome sugerido:" + }, "suggestedTokenSymbol": { "message": "Símbolo do ticker sugerido:" }, @@ -5074,7 +5429,7 @@ "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." }, "swapFetchingQuotes": { - "message": "Buscando cotações" + "message": "Buscando cotações..." }, "swapFetchingQuotesErrorDescription": { "message": "Hmmm, ocorreu algum erro. Tente novamente. Ou, se os erros persistirem, entre em contato com o Suporte." @@ -5470,6 +5825,10 @@ "thisCollection": { "message": "esta coleção" }, + "threeMonthsAbbreviation": { + "message": "3M", + "description": "Shortened form of '3 months'" + }, "time": { "message": "Hora" }, @@ -5483,45 +5842,6 @@ "message": "Para: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleEthSignBannerDescription": { - "message": "Você está vulnerável a ataques de phishing. Proteja-se desativando eth_sign." - }, - "toggleEthSignDescriptionField": { - "message": "Se você ativar essa configuração, poderá receber solicitações de assinatura ilegíveis. Ao assinar uma mensagem que não entende, você poderá estar concordando em ceder seus fundos e NFTs." - }, - "toggleEthSignField": { - "message": "Solicitações eth_sign" - }, - "toggleEthSignModalBannerBoldText": { - "message": " você pode estar sendo vítima de um golpe" - }, - "toggleEthSignModalBannerText": { - "message": "Se pediram para você ativar essa configuração," - }, - "toggleEthSignModalCheckBox": { - "message": "Entendo que poderei perder todos os meus fundos e NFTs se eu ativar solicitações eth_sign. " - }, - "toggleEthSignModalDescription": { - "message": "Permitir solicitações eth_sign pode deixar você vulnerável a ataques de phishing. Sempre revise o URL e tenha cuidado ao assinar mensagens que contenham código." - }, - "toggleEthSignModalFormError": { - "message": "O texto está incorreto" - }, - "toggleEthSignModalFormLabel": { - "message": "Insira \"Eu só assino o que eu entendo\" para continuar" - }, - "toggleEthSignModalFormValidation": { - "message": "Eu só assino o que eu entendo" - }, - "toggleEthSignModalTitle": { - "message": "Use por sua conta e risco" - }, - "toggleEthSignOff": { - "message": "DESLIGADO (recomendado)" - }, - "toggleEthSignOn": { - "message": "LIGADO (não recomendado)" - }, "toggleRequestQueueDescription": { "message": "Isso permite que você selecione uma rede para cada site em vez de uma única rede selecionada para todos eles. O recurso evitará que você alterne manualmente entre redes, o que pode atrapalhar sua experiência de usuário em determinados sites." }, @@ -5549,6 +5869,9 @@ "tokenContractAddress": { "message": "Endereço de contrato do token" }, + "tokenDecimal": { + "message": "Decimal do token" + }, "tokenDecimalFetchFailed": { "message": "É necessário o decimal do token. Encontre-o em: $1" }, @@ -5565,7 +5888,7 @@ "message": "ID do token" }, "tokenList": { - "message": "Listas de tokens:" + "message": "Listas de tokens" }, "tokenScamSecurityRisk": { "message": "golpes e riscos de segurança envolvendo tokens" @@ -5573,6 +5896,9 @@ "tokenShowUp": { "message": "Seus tokens podem não aparecer automaticamente em sua carteira." }, + "tokenStandard": { + "message": "Padrão de token" + }, "tokenSymbol": { "message": "Símbolo do token" }, @@ -5583,6 +5909,9 @@ "message": "$1 novos tokens encontrados", "description": "$1 is the number of new tokens detected" }, + "tokensInCollection": { + "message": "Tokens em coleção" + }, "tooltipApproveButton": { "message": "Estou ciente" }, @@ -5598,6 +5927,9 @@ "total": { "message": "Total" }, + "totalVolume": { + "message": "Volume total" + }, "transaction": { "message": "transação" }, @@ -5613,6 +5945,9 @@ "transactionCreated": { "message": "Transação criada com valor de $1 às $2." }, + "transactionDataFunction": { + "message": "Função" + }, "transactionDetailDappGasMoreInfo": { "message": "Site sugerido" }, @@ -5703,6 +6038,10 @@ "transferFrom": { "message": "Transferir de" }, + "trillionAbbreviation": { + "message": "T", + "description": "Shortened form of 'trillion'" + }, "troubleConnectingToLedgerU2FOnFirefox": { "message": "Estamos com problemas para conectar o seu Ledger. $1", "description": "$1 is a link to the wallet connection guide;" @@ -5781,6 +6120,9 @@ "message": "U2F", "description": "A name on an API for the browser to interact with devices that support the U2F protocol. On some browsers we use it to connect MetaMask to Ledger devices." }, + "unMatchedChain": { + "message": "De acordo com nossos registros, este URL não corresponde a um provedor conhecido para este ID de cadeia." + }, "unapproved": { "message": "Não aprovado" }, @@ -5832,12 +6174,21 @@ "update": { "message": "Atualizar" }, + "updateOrEditNetworkInformations": { + "message": "Atualize suas informações ou" + }, "updateRequest": { "message": "Solicitação de atualização" }, "updatedWithDate": { "message": "Atualizado em $1" }, + "uploadDropFile": { + "message": "Solte seu arquivo aqui" + }, + "uploadFile": { + "message": "Fazer upload de arquivo" + }, "urlErrorMsg": { "message": "URLs precisam do prefixo HTTP/HTTPS adequado." }, @@ -6053,6 +6404,12 @@ "whatsThis": { "message": "O que é isso?" }, + "wrongChainId": { + "message": "Este ID de cadeia não corresponde ao nome da rede." + }, + "wrongNetworkName": { + "message": "De acordo com os nossos registros, o nome da rede pode não corresponder a esta ID de cadeia." + }, "xOfYPending": { "message": "$1 de $2 pendente(s)", "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" @@ -6076,8 +6433,11 @@ "yourAccounts": { "message": "Suas contas" }, - "yourFundsMayBeAtRisk": { - "message": "Seus fundos podem estar em risco" + "yourActivity": { + "message": "Sua atividade" + }, + "yourBalance": { + "message": "Seu saldo" }, "yourNFTmayBeAtRisk": { "message": "Seu NFT pode estar em risco" diff --git a/app/_locales/pt_BR/messages.json b/app/_locales/pt_BR/messages.json index 688f5758ac26..a4b211bb0529 100644 --- a/app/_locales/pt_BR/messages.json +++ b/app/_locales/pt_BR/messages.json @@ -42,7 +42,7 @@ "message": "Carteira de hardware baseada em QR code" }, "QRHardwareWalletSteps2Description": { - "message": "AirGap Vault e Ngrave (em breve)" + "message": "Ngrave Zero" }, "about": { "message": "Sobre" @@ -1363,9 +1363,6 @@ "nonceField": { "message": "Personalizar nonce da transação" }, - "nonceFieldDescription": { - "message": "Ative isso para alterar o nonce (número da transação) nas telas de confirmação. Esse é um recurso avançado; use com cautela." - }, "nonceFieldHeading": { "message": "Nonce personalizado" }, @@ -1702,13 +1699,10 @@ "seedPhraseIntroRecommendedButtonCopy": { "message": "Proteger minha carteira (recomendado)" }, - "seedPhraseIntroSidebarBulletFour": { - "message": "Anote e guarde em vários locais secretos." - }, "seedPhraseIntroSidebarBulletOne": { - "message": "Salve em um gerenciador de senhas" + "message": "Anote e guarde em vários locais secretos." }, - "seedPhraseIntroSidebarBulletThree": { + "seedPhraseIntroSidebarBulletTwo": { "message": "Guarde em um cofre de banco." }, "seedPhraseIntroSidebarCopyOne": { diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index c85ceb96b33a..d08ae9c67865 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -42,7 +42,7 @@ "message": "Подключите свой аппаратный кошелек на основе QR-кодов" }, "QRHardwareWalletSteps2Description": { - "message": "Ngrave (скоро появятся)" + "message": "Ngrave Zero" }, "SIWEAddressInvalid": { "message": "Адрес в запросе на вход не соответствует адресу счета, который вы используете для входа." @@ -162,6 +162,9 @@ "accountSelectionRequired": { "message": "Вам необходимо выбрать счет!" }, + "accountTypeNotSupported": { + "message": "Тип счета не поддерживается" + }, "accounts": { "message": "Счета" }, @@ -277,6 +280,12 @@ "addNewAccount": { "message": "Добавить новый счет Ethereum" }, + "addNewBitcoinAccount": { + "message": "Добавить новый счет в биткойнах (бета-версия)" + }, + "addNewBitcoinTestnetAccount": { + "message": "Добавить новый счет в биткойнах (тестнет)" + }, "addNewToken": { "message": "Добавить новый токен" }, @@ -286,6 +295,9 @@ "addNfts": { "message": "Добавить NFT" }, + "addRpcUrl": { + "message": "Добавить URL-адрес RPC" + }, "addSnapAccountToggle": { "message": "Включите «Добавить Snap счета (бета)»" }, @@ -305,12 +317,21 @@ "message": "Не можете найти токен? Можно вручную добавить любой токен, вставив его адрес. Адреса контракта токена можно найти на $1", "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" }, + "addUrl": { + "message": "Добавить URL-адрес" + }, "addingCustomNetwork": { "message": "Добавление сети" }, "addingTokens": { "message": "Добавление токенов" }, + "additionalNetworks": { + "message": "Дополнительные сети" + }, + "additionalRpcUrl": { + "message": "Дополнительный URL-адрес RPC" + }, "address": { "message": "Адрес" }, @@ -326,6 +347,18 @@ "advancedConfiguration": { "message": "Дополнительная конфигурация" }, + "advancedDetailsDataDesc": { + "message": "Данные" + }, + "advancedDetailsHexDesc": { + "message": "Шестнадцатиричные" + }, + "advancedDetailsNonceDesc": { + "message": "Одноразовый код" + }, + "advancedDetailsNonceTooltip": { + "message": "Это номер транзакции счета. Одноразовый код для первой транзакции равен 0 и увеличивается в последовательном порядке." + }, "advancedGasFeeDefaultOptIn": { "message": "Сохраните эти значения как значения по умолчанию для сети $1.", "description": "$1 is the current network name." @@ -349,6 +382,18 @@ "alert": { "message": "Оповещение" }, + "alertActionBuy": { + "message": "Купить ETH" + }, + "alertActionUpdateGas": { + "message": "Обновить лимит газа" + }, + "alertActionUpdateGasFee": { + "message": "Изменить комиссию" + }, + "alertActionUpdateGasFeeLevel": { + "message": "Обновить параметры газа" + }, "alertBannerMultipleAlertsDescription": { "message": "Если вы одобрите этот запрос, третья сторона, которая, как известно, совершала мошеннические действия, может похитить все ваши активы." }, @@ -358,6 +403,36 @@ "alertDisableTooltip": { "message": "Это можно изменить в разделе «Настройки» > «Оповещения»" }, + "alertMessageGasEstimateFailed": { + "message": "Мы не можем указать точную сумму комиссии, и эта оценка может быть высокой. Мы предлагаем вам ввести индивидуальный лимит газа, но существует риск, что транзакция все равно не удастся." + }, + "alertMessageGasFeeLow": { + "message": "При выборе низкой комиссии транзакций будут медленнее, а время ожидания — дольше. Для более быстрых транзакций выберите вариант комиссии «Рыночная» или «Агрессивная»." + }, + "alertMessageGasTooLow": { + "message": "Чтобы продолжить эту транзакцию, вам необходимо увеличить лимит газа до 21 000 или выше." + }, + "alertMessageInsufficientBalance": { + "message": "На вашем счету недостаточно ETH для оплаты комиссий за транзакцию." + }, + "alertMessageNetworkBusy": { + "message": "Цены газа высоки, а оценки менее точны." + }, + "alertMessageNoGasPrice": { + "message": "Мы не сможем продолжить эту транзакцию, пока вы не обновите комиссию вручную." + }, + "alertMessagePendingTransactions": { + "message": "Эта транзакция не будет выполнена, пока не завершится предыдущая транзакция. Узнайте, как отменить или ускорить транзакцию." + }, + "alertMessageSignInDomainMismatch": { + "message": "Сайт, отправляющий запрос, не является сайтом, на который вы входите. Это может быть попыткой украсть ваши учетные данные." + }, + "alertMessageSignInWrongAccount": { + "message": "Этот сайт просит вас войти в систему, используя неправильный счет." + }, + "alertMessageSigningOrSubmitting": { + "message": "Эта транзакция будет выполнена только после завершения вашей предыдущей транзакции." + }, "alertModalAcknowledge": { "message": "Я осознал(-а) риск и все еще хочу продолжить" }, @@ -367,6 +442,33 @@ "alertModalReviewAllAlerts": { "message": "Просмотреть все оповещения" }, + "alertReasonGasEstimateFailed": { + "message": "Неточная комиссия" + }, + "alertReasonGasFeeLow": { + "message": "Низкая скорость" + }, + "alertReasonGasTooLow": { + "message": "Низкий лимит газа" + }, + "alertReasonInsufficientBalance": { + "message": "Недостаточно средств" + }, + "alertReasonNetworkBusy": { + "message": "Сеть занята" + }, + "alertReasonNoGasPrice": { + "message": "Оценка комиссии недоступна" + }, + "alertReasonPendingTransactions": { + "message": "Ожидается транзакция" + }, + "alertReasonSignIn": { + "message": "Подозрительный запрос на вход" + }, + "alertReasonWrongAccount": { + "message": "Неверный счет" + }, "alertSettingsUnconnectedAccount": { "message": "Просмотр веб-сайта с выбранным неподключенным счетом" }, @@ -398,6 +500,12 @@ "allPermissions": { "message": "Все разрешения" }, + "allTimeHigh": { + "message": "Максимум за все время" + }, + "allTimeLow": { + "message": "Минимум за все время" + }, "allYourNFTsOf": { "message": "Все ваши NFT из $1", "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" @@ -436,9 +544,6 @@ "message": "$1 и $2", "description": "$1 is the first item, $2 is the second item. Used in Snap Install Warning modal." }, - "announcements": { - "message": "Объявления" - }, "appDescription": { "message": "Кошелек Ethereum в вашем браузере", "description": "The description of the application" @@ -516,7 +621,7 @@ "message": "Параметры актива" }, "attemptSendingAssets": { - "message": "Попытка отправки активов напрямую из одной сети в другую может привести к необратимой потере активов. Следует использовать мост." + "message": "Вы можете потерять свои активы, если попытаетесь отправить их из другой сети. Безопасно переводите средства между сетями с помощью моста." }, "attemptSendingAssetsWithPortfolio": { "message": "Попытка отправки активов напрямую из одной сети в другую может привести к необратимой потере активов. Следует использовать мост, такой как $1" @@ -524,6 +629,9 @@ "attemptToCancelSwapForFree": { "message": "Бесплатная попытка отменить своп" }, + "attributes": { + "message": "Атрибуты" + }, "attributions": { "message": "Атрибуции" }, @@ -594,6 +702,10 @@ "basicConfigurationBannerTitle": { "message": "Базовый функционал отключен" }, + "basicConfigurationDescription": { + "message": "MetaMask предлагает базовые функции, такие как сведения о токенах и настройки газа, через интернет-службы. Когда вы пользуетесь интернет-службами, ваш IP-адрес передается, в данном случае MetaMask. Это похоже на посещенеи вами любого веб-сайта. MetaMask временно использует ваши данные и никогда не продает их. Вы можете использовать VPN или отключить эти службы, но это может негативно повлиять на вашу работу с MetaMask. Чтобы узнать больше, прочитайте нашу $1.", + "description": "$1 is to be replaced by the message for privacyMsg, and will link to https://consensys.io/privacy-policy" + }, "basicConfigurationLabel": { "message": "Базовый функционал" }, @@ -637,6 +749,29 @@ "betaWalletCreationSuccessReminder2": { "message": "Бета-версия MetaMask никогда не запрашивает у вас секретную фразу для восстановления." }, + "billionAbbreviation": { + "message": "Б", + "description": "Shortened form of 'billion'" + }, + "bitcoinActivityNotSupported": { + "message": "Активность биткойна не поддерживается" + }, + "bitcoinSupportSectionTitle": { + "message": "Биткойн" + }, + "bitcoinSupportToggleDescription": { + "message": "Включение этой функции даст вам возможность добавить в ваше расширение MetaMask счет для биткойнов, созданный на основе вашей существующей секретной фразы для восстановления. Это экспериментальная бета-функция, которая используется на свой страх и риск. Пожалуйста, оставьте нам отзыв об этом новом способе взаимодействия с биткойном, заполнив эту $1.", + "description": "$1 is the link to a product feedback form" + }, + "bitcoinSupportToggleTitle": { + "message": "Включить «Добавить новый счет в биткойнах (бета-версия)»" + }, + "bitcoinTestnetSupportToggleDescription": { + "message": "Включение этой функции даст вам возможность добавить счет в биткойнах для тестнета." + }, + "bitcoinTestnetSupportToggleTitle": { + "message": "Включить «Добавить новый счет в биткойнах (тестнет)»" + }, "blockExplorerAccountAction": { "message": "Счет", "description": "This is used with viewOnEtherscan and viewInExplorer e.g View Account in Explorer" @@ -662,6 +797,9 @@ "blockaid": { "message": "Blockaid" }, + "blockaidAlertInfo": { + "message": "Мы не рекомендуем продолжать выполнение этого запроса." + }, "blockaidDescriptionApproveFarming": { "message": "Если вы одобрите этот запрос, третья сторона, известная мошенничеством, может похитить все ваши активы." }, @@ -683,6 +821,9 @@ "blockaidDescriptionTransferFarming": { "message": "Если вы одобрите этот запрос, третья сторона, известная мошенничеством, похитит все ваши активы." }, + "blockaidDescriptionWarning": { + "message": "Это может быть обманный запрос. Продолжайте, только если вы доверяете каждому задействованному адресу." + }, "blockaidMessage": { "message": "Сохранение конфиденциальности – никакие данные не передаются третьим сторонам. Доступно в Arbitrum, Avalanche, BNB Chain, Мейн-нете Ethereum, Linea, Optimism, Polygon, Base и Sepolia." }, @@ -690,7 +831,7 @@ "message": "Это запрос с целью обмана" }, "blockaidTitleMayNotBeSafe": { - "message": "Запрос может быть небезопасным" + "message": "Будьте осторожны" }, "blockaidTitleSuspicious": { "message": "Это подозрительный запрос" @@ -698,6 +839,9 @@ "blockies": { "message": "Blockies" }, + "boughtFor": { + "message": "Куплено за" + }, "bridge": { "message": "Мост" }, @@ -782,6 +926,9 @@ "chromeRequiredForHardwareWallets": { "message": "Вам необходимо использовать MetaMask в Google Chrome, чтобы подключиться к аппаратному кошельку." }, + "circulatingSupply": { + "message": "Циркулирующее предложение" + }, "clear": { "message": "Очистить" }, @@ -802,7 +949,7 @@ "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" }, "clickToManuallyAdd": { - "message": "Нажмите здесь, чтобы вручную добавить токены." + "message": "Вы также можете добавлять токены вручную." }, "close": { "message": "Закрыть" @@ -816,6 +963,9 @@ "coingecko": { "message": "CoinGecko" }, + "collectionName": { + "message": "Имя коллекции" + }, "comboNoOptions": { "message": "Вариантов не найдено", "description": "Default text shown in the combo field dropdown if no options." @@ -841,6 +991,9 @@ "confirmAlertModalAcknowledgeMultiple": { "message": "Я подтвердил(-а) получение оповещений и все еще хочу продолжить" }, + "confirmAlertModalAcknowledgeSingle": { + "message": "Я подтвердил(-а) получение предупреждения и все еще хочу продолжить" + }, "confirmAlertModalDetails": { "message": "Если вы войдете, третья сторона, которая, как известно, совершала мошеннические действия, может похитиь твсе ваши активы. Прежде чем продолжить, просмотрите оповещения." }, @@ -856,18 +1009,42 @@ "confirmConnectionTitle": { "message": "Подтвердите подключение к $1" }, + "confirmDeletion": { + "message": "Подтвердить удаление" + }, + "confirmFieldPaymaster": { + "message": "Комиссия оплачена" + }, + "confirmFieldTooltipPaymaster": { + "message": "Комиссия за эту транзакцию будет оплачена смарт-контрактом paymaster." + }, "confirmPassword": { "message": "Подтвердить пароль" }, "confirmRecoveryPhrase": { "message": "Подтвердите секретную фразу для восстановления" }, + "confirmRpcUrlDeletionMessage": { + "message": "Уверены, что хотите удалить URL-адрес RPC? Ваша информация не будет сохранена для этой сети." + }, "confirmTitleDescContractInteractionTransaction": { "message": "Подтверждайте эту транзакцию только в том случае, если вы полностью понимаете ее содержание и доверяете запрашивающему сайту." }, + "confirmTitleDescPermitSignature": { + "message": "Этот сайт хочет получить разрешение на расходование ваших токенов." + }, + "confirmTitleDescSIWESignature": { + "message": "Сайт требует, чтобы вы вошли в систему и доказали, что вы являетесь владельцем этого счета." + }, "confirmTitleDescSignature": { "message": "Подтверждайте это сообщение только в том случае, если вы одобряете его содержание и доверяете запрашивающему сайту." }, + "confirmTitlePermitSignature": { + "message": "Запрос лимита расходов" + }, + "confirmTitleSIWESignature": { + "message": "Запрос на вход" + }, "confirmTitleSignature": { "message": "Запрос подписи" }, @@ -1094,6 +1271,9 @@ "createSnapAccountTitle": { "message": "Создать счет" }, + "creatorAddress": { + "message": "Адрес автора" + }, "crossChainSwapsLink": { "message": "Меняйте сети с помощью MetaMask Portfolio" }, @@ -1263,9 +1443,27 @@ "data": { "message": "Данные" }, + "dataCollectionForMarketing": { + "message": "Сбор данных для маркетинга" + }, + "dataCollectionForMarketingDescription": { + "message": "Мы будем использовать MetaMetrics, чтобы узнать, как вы взаимодействуете с нашими маркетинговыми сообщениями. Мы можем делиться соответствующими новостями (например, новостями о функциях продукта и другими материалами)." + }, + "dataCollectionWarningPopoverButton": { + "message": "Хорошо" + }, + "dataCollectionWarningPopoverDescription": { + "message": "Вы отключили сбор данных для наших маркетинговых целей. Это касается только этого устройства. Если вы используете MetaMask на других устройствах, обязательно отключите его и там." + }, "dataHex": { "message": "Шестнадцатиричные" }, + "dataUnavailable": { + "message": "данные недоступны" + }, + "dateCreated": { + "message": "Дата создания" + }, "dcent": { "message": "D'Cent" }, @@ -1295,6 +1493,9 @@ "decryptRequest": { "message": "Расшифровать запрос" }, + "defaultRpcUrl": { + "message": "URL-адрес RPC по умолчанию" + }, "delete": { "message": "Удалить" }, @@ -1311,6 +1512,9 @@ "message": "Удалить сеть $1?", "description": "$1 represents the name of the network" }, + "deleteRpcUrl": { + "message": "Удалить URL-адрес RPC" + }, "deposit": { "message": "Депозит" }, @@ -1336,18 +1540,6 @@ "details": { "message": "Подробности" }, - "developerOptions": { - "message": "Параметры разработчика" - }, - "developerOptionsResetStatesAnnouncementsDescription": { - "message": "Сбрасывает логическое значение isShown в значение false для всех объявлений. Объявления — это уведомления, отображаемые во всплывающем модальном окне «Что нового»." - }, - "developerOptionsResetStatesOnboarding": { - "message": "Сбрасывает различные состояния, связанные с регистрацией, и перенаправляет на страницу регистрации «Защитите свой кошелек»." - }, - "developerOptionsServiceWorkerKeepAlive": { - "message": "В результате отметка времени постоянно сохраняется в session.storage" - }, "disabledGasOptionToolTipMessage": { "message": "$1 отключена, поскольку не соответствует минимальному увеличению на 10% от первоначальной платы за газ.", "description": "$1 is gas estimate type which can be market or aggressive" @@ -1524,6 +1716,9 @@ "editGasTooLow": { "message": "Время обработки неизвестно" }, + "editNetworkLink": { + "message": "изменить исходную сеть" + }, "editNonceField": { "message": "Изменить одноразовый номер" }, @@ -1555,6 +1750,9 @@ "enabled": { "message": "Включено" }, + "enabledNetworks": { + "message": "Включенные сети" + }, "encryptionPublicKeyNotice": { "message": "$1 запрашивает ваш открытый ключ шифрования. После получения вашего согласия на это данный сайт сможет создавать зашифрованные сообщения для отправки в ваш адрес.", "description": "$1 is the web3 site name" @@ -1659,6 +1857,9 @@ "estimatedFee": { "message": "Примерная комиссия" }, + "estimatedFeeTooltip": { + "message": "Сумма, уплаченная за обработку транзакции в сети." + }, "ethGasPriceFetchWarning": { "message": "Указана резервная цена газа, поскольку основной сервис определения цены газа сейчас недоступен." }, @@ -1678,6 +1879,12 @@ "etherscanViewOn": { "message": "Посмотреть на Etherscan" }, + "existingChainId": { + "message": "Введенная вами информация связана с существующим ID блокчейна." + }, + "existingRpcUrl": { + "message": "Этот URL-адрес связан с другим ID блокчейна." + }, "expandView": { "message": "Развернуть представление" }, @@ -1732,6 +1939,9 @@ "message": "Импорт файлов не работает? Нажмите здесь!", "description": "Helps user import their account from a JSON file" }, + "findTheRightChainId": { + "message": "Найдите подходящий в:" + }, "flaskWelcomeUninstall": { "message": "вам нужно должны удалить это расширение", "description": "This request is shown on the Flask Welcome screen. It is intended for non-developers, and will be bolded." @@ -1771,6 +1981,9 @@ "forgotPassword": { "message": "Забыли пароль?" }, + "form": { + "message": "форма" + }, "from": { "message": "От" }, @@ -1979,6 +2192,12 @@ "highLowercase": { "message": "высокая" }, + "highestCurrentBid": { + "message": "Самое высокое текущее предложение" + }, + "highestFloorPrice": { + "message": "Самая высокая минимальная цена" + }, "history": { "message": "История" }, @@ -2318,12 +2537,21 @@ "knownTokenWarning": { "message": "Это действие изменит токены, уже указанные в вашем кошельке, которые можно использовать для фишинга. Утверждайте, только если вы уверены, что хотите изменить то, что представляют эти токены. Узнайте подробнее о $1" }, + "l1Fee": { + "message": "Комиссия L1" + }, + "l1FeeTooltip": { + "message": "Плата за газ L1" + }, + "l2Fee": { + "message": "Комиссия L2" + }, + "l2FeeTooltip": { + "message": "Плата за газ L2" + }, "lastConnected": { "message": "Последнее подключение" }, - "lastPriceSold": { - "message": "Последняя цена продажи" - }, "lastSold": { "message": "Последняя продажа" }, @@ -2495,6 +2723,12 @@ "message": "Убедитесь, что никто не смотрит", "description": "Warning to users to be care while creating and saving their new Secret Recovery Phrase" }, + "marketCap": { + "message": "Рыночная капитализация" + }, + "marketDetails": { + "message": "Сведения о рынке" + }, "max": { "message": "Макс." }, @@ -2504,6 +2738,9 @@ "maxFee": { "message": "Максимальная комиссия" }, + "maxFeeTooltip": { + "message": "Максимальная комиссия, предусмотренная для оплаты транзакции." + }, "maxPriorityFee": { "message": "Максимальная плата за приоритет" }, @@ -2551,12 +2788,19 @@ "methodData": { "message": "Метод" }, + "methodDataTransactionDesc": { + "message": "Функция выполняется на основе декодированных входных данных." + }, "methodNotSupported": { "message": "Не поддерживается с этим счётом." }, "metrics": { "message": "Показатели" }, + "millionAbbreviation": { + "message": "млн", + "description": "Shortened form of 'million'" + }, "mismatchAccount": { "message": "Выбранный вами счет ($1) отличается от счета, который вы пытаетесь подписать ($2)" }, @@ -2669,7 +2913,7 @@ "description": "Description below header used on Permission Connect screen for native permissions." }, "nativeToken": { - "message": "Нативный токен этой сети — $1. Этот токен используется для внесения платы за газ.", + "message": "Нативный токен этой сети — $1. Этот токен используется для внесения платы за газ. ", "description": "$1 represents the name of the native token on the current network" }, "nativeTokenScamWarningConversion": { @@ -2738,6 +2982,9 @@ "networkNameBase": { "message": "Base" }, + "networkNameBitcoin": { + "message": "Биткоин" + }, "networkNameDefinition": { "message": "Имя, связанное с этой сетью." }, @@ -2762,6 +3009,9 @@ "networkNameZkSyncEra": { "message": "zkSync Era" }, + "networkOptions": { + "message": "Параметры сети" + }, "networkProvider": { "message": "Поставщик услуг сети" }, @@ -2830,6 +3080,9 @@ "newNetworkAdded": { "message": "«$1» успешно добавлен!" }, + "newNetworkEdited": { + "message": "«$1» успешно изменен!" + }, "newNftAddedMessage": { "message": "NFT успешно добавлен!" }, @@ -2866,6 +3119,9 @@ "nftAlreadyAdded": { "message": "NFT уже добавлен." }, + "nftAutoDetectionEnabled": { + "message": "Автоопределение NFT включено" + }, "nftDisclaimer": { "message": "Отказ от ответственности: MetaMask извлекает медиафайл из исходного URL. Этот URL иногда изменяется торговой площадкой, на которой был выполнен минтинг NFT." }, @@ -2916,6 +3172,9 @@ "noDomainResolution": { "message": "Разрешение для домена не предоставлено." }, + "noHardwareWalletOrSnapsSupport": { + "message": "Snaps и большинство аппаратных кошельков не будут работать с вашей текущей версией браузера." + }, "noNFTs": { "message": "Пока нет NFT" }, @@ -2946,8 +3205,8 @@ "nonceField": { "message": "Настроить одноразовый номер транзакции" }, - "nonceFieldDescription": { - "message": "Включите это, чтобы изменить одноразовый номер (номер транзакции) на экранах подтверждения. Это продвинутая функция, используйте ее с осторожностью." + "nonceFieldDesc": { + "message": "Включите это, чтобы изменить одноразовый номер (номер транзакции) при отправке активов. Это продвинутая функция, используйте ее с осторожностью." }, "nonceFieldHeading": { "message": "Пользовательский одноразовый номер" @@ -3149,6 +3408,9 @@ "numberOfNewTokensDetectedSingular": { "message": "1 новый токен найден в этом счете" }, + "numberOfTokens": { + "message": "Количество токенов" + }, "ofTextNofM": { "message": "из" }, @@ -3164,8 +3426,36 @@ "on": { "message": "Вкл." }, - "onboarding": { - "message": "Регистрация" + "onboardedMetametricsAccept": { + "message": "Я согласен(-на)" + }, + "onboardedMetametricsDisagree": { + "message": "Нет, спасибо" + }, + "onboardedMetametricsKey1": { + "message": "Последние разработки" + }, + "onboardedMetametricsKey2": { + "message": "Особенности продукта" + }, + "onboardedMetametricsKey3": { + "message": "Другие соответствующие промоматериалы" + }, + "onboardedMetametricsLink": { + "message": "MetaMetrics," + }, + "onboardedMetametricsParagraph1": { + "message": "Кроме $1, мы хотели бы использовать данные, чтобы понять, как вы взаимодействуете с рекламными сообщениями.", + "description": "$1 represents the 'onboardedMetametricsLink' locale string" + }, + "onboardedMetametricsParagraph2": { + "message": "Это помогает нам персонализировать то, чем мы делимся с вами, например:" + }, + "onboardedMetametricsParagraph3": { + "message": "Помните, что мы никогда не продаем предоставленные вами данные и вы можете отказаться от их предоставления в любое время." + }, + "onboardedMetametricsTitle": { + "message": "Помогите нам улучшить ваш опыт" }, "onboardingAdvancedPrivacyIPFSDescription": { "message": "Шлюз IPFS позволяет получать доступ к данным, размещенным третьими сторонами, и просматривать их. Вы можете добавить пользовательский шлюз IPFS или продолжить использовать шлюз по умолчанию." @@ -3203,9 +3493,6 @@ "onboardingMetametricsDescription2": { "message": "Когда мы собираем показатели, они всегда будут..." }, - "onboardingMetametricsDisagree": { - "message": "Нет, спасибо" - }, "onboardingMetametricsInfuraTerms": { "message": "Мы сообщим вам, если решим использовать эти данные для других целей. Вы можете ознакомиться с нашей $1 для получения дополнительной информации. Помните, что вы можете перейти в настройки и отказаться в любой момент.", "description": "$1 represents `onboardingMetametricsInfuraTermsPolicy`" @@ -3240,6 +3527,9 @@ "onboardingMetametricsTitle": { "message": "Помогите нам улучшить MetaMask" }, + "onboardingMetametricsUseDataCheckbox": { + "message": "Мы будем использовать эти данные, чтобы узнать, как вы взаимодействуете с нашими маркетинговыми сообщениями. Мы можем отправлять соответствующие новости (например, об особенностях продукта)." + }, "onboardingPinExtensionBillboardAccess": { "message": "Полный доступ" }, @@ -3283,6 +3573,22 @@ "message": "Оповещения об обнаружении фишинга зависят от связи с $1. jsDeliver получит доступ к вашему IP-адресу. Посмотрите $ 2.", "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" }, + "oneDayAbbreviation": { + "message": "1 Д", + "description": "Shortened form of '1 day'" + }, + "oneMonthAbbreviation": { + "message": "1 М", + "description": "Shortened form of '1 month'" + }, + "oneWeekAbbreviation": { + "message": "1 Н", + "description": "Shortened form of '1 week'" + }, + "oneYearAbbreviation": { + "message": "1 Г", + "description": "Shortened form of '1 year'" + }, "onekey": { "message": "OneKey" }, @@ -3635,6 +3941,9 @@ "permissionsPageTourTitle": { "message": "Подключенные сайты теперь имеют разрешения" }, + "permitSimulationDetailInfo": { + "message": "Вы даёте расходующему лицу разрешение потратить именно столько токенов из вашего аккаунта" + }, "personalAddressDetected": { "message": "Обнаружен личный адрес. Введите адрес контракта токена." }, @@ -3667,6 +3976,10 @@ "popularCustomNetworks": { "message": "Популярные пользовательские сети" }, + "popularNetworkAddToolTip": { + "message": "Некоторые из этих сетей являются зависимыми от третьих сторон. Соединения могут быть менее надежными или позволять третьим сторонам отслеживать активность. $1", + "description": "$1 is Learn more link" + }, "portfolio": { "message": "Portfolio" }, @@ -3679,6 +3992,12 @@ "prev": { "message": "Пред." }, + "price": { + "message": "Цена" + }, + "priceUnavailable": { + "message": "цена недоступна" + }, "primaryCurrencySetting": { "message": "Основная валюта" }, @@ -3831,6 +4150,9 @@ "quoteRate": { "message": "Курс котировки" }, + "rank": { + "message": "Рейтинг" + }, "reAddAccounts": { "message": "повторно добавить любые другие счета" }, @@ -4000,9 +4322,6 @@ "reset": { "message": "Сбросить" }, - "resetStates": { - "message": "Сбросить состояния" - }, "resetWallet": { "message": "Сбросить кошелек" }, @@ -4138,6 +4457,9 @@ "searchAccounts": { "message": "Поиск счетов" }, + "searchNfts": { + "message": "Поиск NFT" + }, "searchTokens": { "message": "Поиск токенов" }, @@ -4182,13 +4504,10 @@ "seedPhraseIntroRecommendedButtonCopy": { "message": "Защитить мой кошелек (рекомендуется)" }, - "seedPhraseIntroSidebarBulletFour": { - "message": "Запишите и храните в нескольких секретных местах." - }, "seedPhraseIntroSidebarBulletOne": { - "message": "Сохранить в менджере паролей" + "message": "Запишите и храните в нескольких секретных местах." }, - "seedPhraseIntroSidebarBulletThree": { + "seedPhraseIntroSidebarBulletTwo": { "message": "Храните в банковской ячейке." }, "seedPhraseIntroSidebarCopyOne": { @@ -4258,10 +4577,10 @@ "message": "Выбрать токен" }, "selectNFTPrivacyPreference": { - "message": "Включите определение NFT в настройках" + "message": "Включите автоопределение NFT" }, "selectPathHelp": { - "message": "Если вы не видите ожидаемые счета, попробуйте переключиться на путь HD." + "message": "Если вы не видите ожидаемые счета, попробуйте переключиться на путь HD или текущую выбранную сеть." }, "selectType": { "message": "Выбрать тип" @@ -4272,9 +4591,6 @@ "send": { "message": "Отправить" }, - "sendAToken": { - "message": "Отправить токен" - }, "sendBugReport": { "message": "Отправьте нам отчет об ошибке." }, @@ -4325,9 +4641,6 @@ "sepolia": { "message": "Тестовая сеть Sepolia" }, - "serviceWorkerKeepAlive": { - "message": "Поддерживать активным сервисный модуль" - }, "setAdvancedPrivacySettingsDetails": { "message": "MetaMask использует эти доверенные сторонние сервисы для повышения удобства использования и безопасности продукта." }, @@ -4384,6 +4697,9 @@ "showIncomingTransactionsExplainer": { "message": "Это базируется на различных сторонних API для каждой сети, которые раскрывают ваш адрес Ethereum и ваш IP-адрес." }, + "showLess": { + "message": "Показать меньше" + }, "showMore": { "message": "Показать больше" }, @@ -4414,9 +4730,6 @@ "signatureRequestGuidance": { "message": "Подписывайте это сообщение только в том случае, если вы полностью понимаете его содержание и доверяете запрашивающему сайту." }, - "signatureRequestWarning": { - "message": "Подписание этого сообщения может быть опасным. Возможно, вы предоставляете полный контроль над своим счетом и активами стороне на другом конце этого сообщения. Это означает, что она может опустошить ваш счет в любое время. Действуйте с осторожностью. $1." - }, "signed": { "message": "Подписано" }, @@ -4426,6 +4739,9 @@ "signing": { "message": "Подписание" }, + "signingInWith": { + "message": "Вход с помощью" + }, "simulationDetailsFailed": { "message": "Не удалось загрузить прогноз." }, @@ -4463,6 +4779,24 @@ "simulationsSettingSubHeader": { "message": "Спрогнозировать изменения баланса" }, + "siweIssued": { + "message": "Выдано" + }, + "siweNetwork": { + "message": "Сеть" + }, + "siweRequestId": { + "message": "ID запроса" + }, + "siweResources": { + "message": "Ресурсы" + }, + "siweSignatureSimulationDetailInfo": { + "message": "Вы входите на сайт, и в вашем счете не происходит никаких прогнозируемых изменений." + }, + "siweURI": { + "message": "URL-адрес" + }, "skip": { "message": "Пропустить" }, @@ -4566,6 +4900,14 @@ "snapAccountsDescription": { "message": "Счета, контролируемые сторонними Snap." }, + "snapConnectTo": { + "message": "Подключиться к $1", + "description": "$1 is the website URL or a Snap name. Used for Snaps pre-approved connections." + }, + "snapConnectionPermissionDescription": { + "message": "Позвольте $1 автоматически подключаться к $2 без вашего одобрения.", + "description": "Used for Snap pre-approved connections. $1 is the Snap name, $2 is a website URL." + }, "snapConnectionWarning": { "message": "$1 хочет использовать $2", "description": "$2 is the snap and $1 is the dapp requesting connection to the snap." @@ -4577,6 +4919,9 @@ "snapDetailWebsite": { "message": "Веб-сайт" }, + "snapHomeMenu": { + "message": "Главное меню Snap" + }, "snapInstallRequest": { "message": "Установка $1 дает ему следующие разрешения.", "description": "$1 is the snap name." @@ -4699,6 +5044,9 @@ "source": { "message": "Источник" }, + "speed": { + "message": "Скорость" + }, "speedUp": { "message": "Ускорить" }, @@ -4733,6 +5081,9 @@ "spendLimitTooLarge": { "message": "Лимит расходов слишком велик" }, + "spender": { + "message": "Расходующее лицо" + }, "spendingCap": { "message": "Лимит расходов" }, @@ -4853,9 +5204,6 @@ "stateLogsDescription": { "message": "Журналы состояния содержат ваши адреса публичных счетов и отправленные транзакции." }, - "states": { - "message": "Состояния" - }, "status": { "message": "Статус" }, @@ -4960,6 +5308,13 @@ "submitted": { "message": "Отправлено" }, + "suggestedBySnap": { + "message": "Рекомендовано $1", + "description": "$1 is the snap name" + }, + "suggestedTokenName": { + "message": "Предлагаемое имя:" + }, "suggestedTokenSymbol": { "message": "Рекомендуемый символ-тикер:" }, @@ -5422,7 +5777,7 @@ "description": "$1 represents the account name, $2 represents the network name" }, "switchedTo": { - "message": "Вы переключились на" + "message": "Сейчас вы используете" }, "switchingNetworksCancelsPendingConfirmations": { "message": "В случае смены сетей все ожидающие подтверждения будут отменены" @@ -5470,6 +5825,10 @@ "thisCollection": { "message": "эта коллекция" }, + "threeMonthsAbbreviation": { + "message": "3 М", + "description": "Shortened form of '3 months'" + }, "time": { "message": "Время" }, @@ -5483,45 +5842,6 @@ "message": "Адресат: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleEthSignBannerDescription": { - "message": "Вы подвержены риску фишинговых атак. Защитите себя, отключив eth_sign." - }, - "toggleEthSignDescriptionField": { - "message": "Если вы включите этот параметр, вы можете получать запросы подписи, которые невозможно прочитать. Подписав сообщение, которое вы не понимаете, вы можете согласиться отдать свои средства и NFT." - }, - "toggleEthSignField": { - "message": "Запросы eth_sign" - }, - "toggleEthSignModalBannerBoldText": { - "message": " вас могут обмануть" - }, - "toggleEthSignModalBannerText": { - "message": "Если вас попросили включить этот параметр," - }, - "toggleEthSignModalCheckBox": { - "message": "Я понимаю, что могу потерять все свои средства и NFT, если включу запросы eth_sign. " - }, - "toggleEthSignModalDescription": { - "message": "Разрешение запросов eth_sign может сделать вас уязвимыми для фишинговых атак. Всегда проверяйте URL и будьте осторожны при подписании сообщений, содержащих код." - }, - "toggleEthSignModalFormError": { - "message": "Текст неверный" - }, - "toggleEthSignModalFormLabel": { - "message": "Введите «Я подписываю только то, что понимаю», чтобы продолжить." - }, - "toggleEthSignModalFormValidation": { - "message": "Я подписываю только то, что понимаю" - }, - "toggleEthSignModalTitle": { - "message": "Используйте на свой риск" - }, - "toggleEthSignOff": { - "message": "ВЫКЛ. (рекомендуется)" - }, - "toggleEthSignOn": { - "message": "ВКЛ. (не рекомендуется)" - }, "toggleRequestQueueDescription": { "message": "Это позволяет вам выбрать сеть для каждого сайта вместо одной выбранной сети для всех сайтов. Эта функция не позволит вам переключать сети вручную, что может снизить удобство использования некоторых сайтов." }, @@ -5549,6 +5869,9 @@ "tokenContractAddress": { "message": "Адрес контракта токена" }, + "tokenDecimal": { + "message": "Число десятичных знаков токена" + }, "tokenDecimalFetchFailed": { "message": "Требуется десятичный токен. Найдите его здесь: $1" }, @@ -5571,7 +5894,10 @@ "message": "мошенничество с токенами и угрозы безопасности" }, "tokenShowUp": { - "message": "Ваши токены могут не отображаться автоматически в вашем кошельке." + "message": "Ваши токены могут не отображаться автоматически в вашем кошельке. " + }, + "tokenStandard": { + "message": "Стандарт токена" }, "tokenSymbol": { "message": "Символ токена" @@ -5583,6 +5909,9 @@ "message": "Найдены $1 новых токена(-ов)", "description": "$1 is the number of new tokens detected" }, + "tokensInCollection": { + "message": "Токены в коллекции" + }, "tooltipApproveButton": { "message": "Я понимаю" }, @@ -5598,6 +5927,9 @@ "total": { "message": "Итого" }, + "totalVolume": { + "message": "Общий объем" + }, "transaction": { "message": "транзакция" }, @@ -5613,6 +5945,9 @@ "transactionCreated": { "message": "В $2 создана транзакция на сумму $1." }, + "transactionDataFunction": { + "message": "Функция" + }, "transactionDetailDappGasMoreInfo": { "message": "Рекомендовано сайтом" }, @@ -5703,6 +6038,10 @@ "transferFrom": { "message": "Перевести из" }, + "trillionAbbreviation": { + "message": "трлн", + "description": "Shortened form of 'trillion'" + }, "troubleConnectingToLedgerU2FOnFirefox": { "message": "У нас возникли проблемы с подключением вашего Ledger. $1", "description": "$1 is a link to the wallet connection guide;" @@ -5781,6 +6120,9 @@ "message": "U2F", "description": "A name on an API for the browser to interact with devices that support the U2F protocol. On some browsers we use it to connect MetaMask to Ledger devices." }, + "unMatchedChain": { + "message": "Согласно нашим данным, этот URL-адрес не соответствует известному поставщику для этого ID блокчейна." + }, "unapproved": { "message": "Не одобрен" }, @@ -5832,12 +6174,21 @@ "update": { "message": "Обновить" }, + "updateOrEditNetworkInformations": { + "message": "Обновите свою информацию или" + }, "updateRequest": { "message": "Запрос обновления" }, "updatedWithDate": { "message": "Обновлено $1" }, + "uploadDropFile": { + "message": "Переместите свой файл сюда" + }, + "uploadFile": { + "message": "Загрузить файл" + }, "urlErrorMsg": { "message": "URL должен иметь соответствующий префикс HTTP/HTTPS." }, @@ -6053,6 +6404,12 @@ "whatsThis": { "message": "Что это?" }, + "wrongChainId": { + "message": "Этот ID блокчейна не соответствует имени сети." + }, + "wrongNetworkName": { + "message": "Согласно нашим записям, имя сети может не соответствовать этому ID блокчейна." + }, "xOfYPending": { "message": "$1 из $2 ожидающих", "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" @@ -6076,8 +6433,11 @@ "yourAccounts": { "message": "Ваши счета" }, - "yourFundsMayBeAtRisk": { - "message": "Ваши средства могут быть в опасности" + "yourActivity": { + "message": "Ваша деятельность" + }, + "yourBalance": { + "message": "Ваш баланс" }, "yourNFTmayBeAtRisk": { "message": "Ваш NFT могут быть в опасности" diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json index 2b5dc3d9e7b7..65268e6199d6 100644 --- a/app/_locales/tl/messages.json +++ b/app/_locales/tl/messages.json @@ -42,7 +42,7 @@ "message": "Ikonekta ang iyong QR na wallet na hardware" }, "QRHardwareWalletSteps2Description": { - "message": "AirGap Vault at Ngrave (paparating na)" + "message": "Ngrave Zero" }, "SIWEAddressInvalid": { "message": "Ang address sa kahilingan sa pag-sign in ay hindi tugma sa address ng account na ginagamit mo sa pag-sign in." @@ -162,6 +162,9 @@ "accountSelectionRequired": { "message": "Kailangan mong pumili ng account!" }, + "accountTypeNotSupported": { + "message": "Hindi suportado ang uri ng account" + }, "accounts": { "message": "Mga Account" }, @@ -277,6 +280,12 @@ "addNewAccount": { "message": "Magdagdag ng bagong account sa Ethereum" }, + "addNewBitcoinAccount": { + "message": "Magdagdag ng bagong account sa Bitcoin (Beta)" + }, + "addNewBitcoinTestnetAccount": { + "message": "Magdagdag ng bagong account sa Bitcoin (Testnet)" + }, "addNewToken": { "message": "Magdagdag ng bagong token" }, @@ -286,6 +295,9 @@ "addNfts": { "message": "Magdagdag ng mga NFT" }, + "addRpcUrl": { + "message": "Magdagdag ng RPC URL" + }, "addSnapAccountToggle": { "message": "Paganahin ang \"Idagdag ang account Snap (Beta)\"" }, @@ -305,12 +317,21 @@ "message": "Hindi makahanap ng token? Maaari kang manu-manong magdagdag ng anumang token sa pamamagitan ng pag-paste ng address nito. Ang mga address ng kontrata ng token ay matatagpuan sa $1", "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" }, + "addUrl": { + "message": "Idagdag ang URL" + }, "addingCustomNetwork": { "message": "Nagdaragdag ng Network" }, "addingTokens": { "message": "Nagdaragdag ng mga token" }, + "additionalNetworks": { + "message": "Mga karagdagang network" + }, + "additionalRpcUrl": { + "message": "Karagdagang RPC URL" + }, "address": { "message": "Address" }, @@ -326,6 +347,18 @@ "advancedConfiguration": { "message": "Advanced na pagsasaayos" }, + "advancedDetailsDataDesc": { + "message": "Data" + }, + "advancedDetailsHexDesc": { + "message": "Hex" + }, + "advancedDetailsNonceDesc": { + "message": "Nonce" + }, + "advancedDetailsNonceTooltip": { + "message": "Ito ang numero ng transaksyon ng account. Ang nonce para sa unang transaksyon ay 0 at nadaragdagan ito sa sunod-sunod na pagkakaayos." + }, "advancedGasFeeDefaultOptIn": { "message": "I-save ang mga value na ito bilang default ko para sa $1 network.", "description": "$1 is the current network name." @@ -349,6 +382,18 @@ "alert": { "message": "Alerto" }, + "alertActionBuy": { + "message": "Bumili ng ETH" + }, + "alertActionUpdateGas": { + "message": "I-update ang gas limit" + }, + "alertActionUpdateGasFee": { + "message": "I-update ang bayad" + }, + "alertActionUpdateGasFeeLevel": { + "message": "I-update ang mga opsyon sa gas" + }, "alertBannerMultipleAlertsDescription": { "message": "Kung aaprubahan mo ang kahilingang ito, maaaring kunin ng third party na kilala sa mga panloloko ang lahat asset mo." }, @@ -358,6 +403,36 @@ "alertDisableTooltip": { "message": "Puwede itong baguhin sa \"Mga Setting > Mga Alerto\"" }, + "alertMessageGasEstimateFailed": { + "message": "Hindi kami makapagbigay ng tumpak na bayad at ang pagtantiya na ito ay maaaring mataas. Iminumungkahi namin na maglagay ka ng naka-custom na gas limit, ngunit may panganib na mabigo pa rin ang transaksyon." + }, + "alertMessageGasFeeLow": { + "message": "Kapag pumipili ng mababang bayad, asahan ang mas mabagal na mga transaksyon at mas matagal na paghihintay. Para sa mas mabilis na transaksyon, piliin ang Market o Aggressive na mga pagpipilian sa bayad." + }, + "alertMessageGasTooLow": { + "message": "Para magpatuloy sa transaksyong ito, kakailanganin mong dagdagan ang gas limit sa 21000 o mas mataas." + }, + "alertMessageInsufficientBalance": { + "message": "Wala kang sapat na ETH sa iyong account para bayaran ang mga bayad sa transaksyon." + }, + "alertMessageNetworkBusy": { + "message": "Ang mga presyo ng gas ay mataas at ang pagtantiya ay hindi gaanong tumpak." + }, + "alertMessageNoGasPrice": { + "message": "Hindi tayo makakapagpatuloy sa transaksyong ito hanggang sa manwal mong i-update ang bayad." + }, + "alertMessagePendingTransactions": { + "message": "Hindi magpapatuloy ang transaksyong ito hanggang makumpleto ang naunang transaksyon. Alamin kung paano kanselahin o pabilisin ang transaksyon." + }, + "alertMessageSignInDomainMismatch": { + "message": "Ang site na humihiling ay hindi ang site kung saan ka nagsa-signin. Ito ay maaring isang pagtatangka para nakawin ang iyong mga kredensiyal sa pag-login." + }, + "alertMessageSignInWrongAccount": { + "message": "Hinihingi sa iyo ng site na ito na mag-sign in gamit ang maling account." + }, + "alertMessageSigningOrSubmitting": { + "message": "Magpapatuloy lamang ang transaksyong ito kapag nakumpleto mo na ang naunang transaksyon." + }, "alertModalAcknowledge": { "message": "Kinikilala ko ang panganib at nais ko pa rin magpatuloy" }, @@ -367,6 +442,33 @@ "alertModalReviewAllAlerts": { "message": "Suriin ang lahat ng alerto" }, + "alertReasonGasEstimateFailed": { + "message": "Hindi tumpak na bayad" + }, + "alertReasonGasFeeLow": { + "message": "Mabagal na bilis" + }, + "alertReasonGasTooLow": { + "message": "Mababang gas limit" + }, + "alertReasonInsufficientBalance": { + "message": "Hindi sapat ang pondo" + }, + "alertReasonNetworkBusy": { + "message": "Busy ang network" + }, + "alertReasonNoGasPrice": { + "message": "Hindi available ang pagtantiya sa bayad" + }, + "alertReasonPendingTransactions": { + "message": "Nakabinbin na transaksyon" + }, + "alertReasonSignIn": { + "message": "Kahina-hinalang hiling na pag-signin" + }, + "alertReasonWrongAccount": { + "message": "Maling account" + }, "alertSettingsUnconnectedAccount": { "message": "Nagba-browse sa isang website na may napiling hindi konektadong account" }, @@ -398,6 +500,12 @@ "allPermissions": { "message": "Lahat ng Pahintulot" }, + "allTimeHigh": { + "message": "All time high" + }, + "allTimeLow": { + "message": "All time low" + }, "allYourNFTsOf": { "message": "Lahat ng iyong NFT mula sa $1", "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" @@ -436,9 +544,6 @@ "message": "$1 at $2", "description": "$1 is the first item, $2 is the second item. Used in Snap Install Warning modal." }, - "announcements": { - "message": "Mga Anunsiyo" - }, "appDescription": { "message": "Ethereum Wallet sa iyong Browser", "description": "The description of the application" @@ -516,7 +621,7 @@ "message": "Mga opsyon ng asset" }, "attemptSendingAssets": { - "message": "Kung tatangkain mong magpadala ng mga asset nang direkta mula sa isang network papunta sa isa pa, maaari itong magresulta sa permanenteng pagkawala ng asset. Siguraduhing gumamit ng bridge." + "message": "Maaari mong mawala ang mga asset kung susubukan mo itong ipadala mula sa isang network papunta sa isa pa. Ligtas ng maglipat ng pondo sa pagitan ng mga network sa pamamagitan ng paggamit ng bridge." }, "attemptSendingAssetsWithPortfolio": { "message": "Maaaring mawala ang iyong mga asset kung susubukan mong ipadala ito mula sa ibang network. Ilipat ang mga pondo nang ligtas sa pagitan ng mga network gamit ang isang bridge, tulad ng $1" @@ -524,9 +629,15 @@ "attemptToCancelSwapForFree": { "message": "Subukang kanselahin ang swap nang libre" }, + "attributes": { + "message": "Mga Attribute" + }, "attributions": { "message": "Mga Attribution" }, + "auroraRpcDeprecationMessage": { + "message": "Ang Infura RPC URL ay hindi na sinusuportahan ang Aurora." + }, "authorizedPermissions": { "message": "Inawtorisahan mo ang mga sumusunod na pahintulot" }, @@ -591,6 +702,10 @@ "basicConfigurationBannerTitle": { "message": "Naka-off ang batayang kapakinabangan" }, + "basicConfigurationDescription": { + "message": "Nag-aalok ang MetaMask ng mga batayang tampok tulad ng mga detalye ng token at mga setting ng gas sa pamamagitan ng serbisyo sa internet. Kapag gumamit ka ng serbisyo sa internet, ibabahagi ang iyong IP address, sa kasong ito sa MetaMask. Tulad ito ng pagbisita mo sa anumang website. Ang MetaMask ay pansamantalang ginagamit ang datos na ito at hindi ibebenta ang iyong datos. Maaari kang gumamit ng VPN o i-off ang mga serbisyon ito, ngunit maaari itong makaapekto sa iyong karanasan sa MetaMask. Upang matuto pa basahin ang aming $1.", + "description": "$1 is to be replaced by the message for privacyMsg, and will link to https://consensys.io/privacy-policy" + }, "basicConfigurationLabel": { "message": "Batayang kapakinabangan" }, @@ -634,6 +749,29 @@ "betaWalletCreationSuccessReminder2": { "message": "Hindi kailanman hihingiin sa iyo ng MetaMask Beta ang iyong Lihim na Parirala sa Pagbawi." }, + "billionAbbreviation": { + "message": "B", + "description": "Shortened form of 'billion'" + }, + "bitcoinActivityNotSupported": { + "message": "Ang aktibidad ng Bitcoin ay hindi suportado" + }, + "bitcoinSupportSectionTitle": { + "message": "Bitcoin" + }, + "bitcoinSupportToggleDescription": { + "message": "Ang pag-on sa tampok na ito ay nagbibigay sa iyo ng opsyon na magdagdag ng Account sa Bitcoin sa iyong MetaMask Extension na nagmula sa iyong umiiral na Lihim na Parirala sa Pagbawi. Ito ay isang eksperimental na tampok sa Beta, kaya gamitin ito nang may-pag-iingat. Upang bigyan kami ng feedback sa bagong karanasan na ito sa Bitcoin, mangyaring sagutan ito $1.", + "description": "$1 is the link to a product feedback form" + }, + "bitcoinSupportToggleTitle": { + "message": "Paganahin ang \"Magdagdag ng bagong account sa Bitcoin (Beta)\"" + }, + "bitcoinTestnetSupportToggleDescription": { + "message": "Ang pag-on sa tampok na ito ay magbibigay sa iyo ng opsyon na magdagdag ng Account sa Bitcoin para sa test network." + }, + "bitcoinTestnetSupportToggleTitle": { + "message": "Paganahin ang \"Magdagdag ng bagong account sa Bitcoin (Testnet)\"" + }, "blockExplorerAccountAction": { "message": "Account", "description": "This is used with viewOnEtherscan and viewInExplorer e.g View Account in Explorer" @@ -659,6 +797,9 @@ "blockaid": { "message": "Blockaid" }, + "blockaidAlertInfo": { + "message": "Hindi namin inirerekomendang magpatuloy sa hiling na ito." + }, "blockaidDescriptionApproveFarming": { "message": "Kung aaprubahan mo ang kahilingang ito, posibleng kunin ng third party na kilala sa mga panloloko ang lahat ng asset mo." }, @@ -666,7 +807,7 @@ "message": "Kung aaprubahan mo ang kahilingang ito, posibleng nakawin ng ibang tao ang mga asset mo na nakalista sa Blur." }, "blockaidDescriptionErrored": { - "message": "Dahil sa error, hindi na-verify ang kahilingang ito ng tagapagbigay ng seguridad. Magpatuloy nang may pag-iingat." + "message": "Dahil sa error, hindi namin masuri para sa alerto sa seguridad. Magpatuloy lamang kung pinagkakatiwalan mo ang kaugnay na address." }, "blockaidDescriptionMaliciousDomain": { "message": "Nakikipag-ugnayan ka sa isang mapaminsalang domain. Kung aaprubahan mo ang kahilingang ito, posibleng mawala sa iyo ang mga asset mo." @@ -680,6 +821,9 @@ "blockaidDescriptionTransferFarming": { "message": "Kung aaprubahan mo ang kahilingang ito, kukunin ng third party na kilala sa mga panloloko ang lahat ng asset mo." }, + "blockaidDescriptionWarning": { + "message": "Maaaring ito ay isang mapanlinlang na hiling. Magpatuloy lang kung pinagkakatiwalaan mo ang bawat address na kaugnay." + }, "blockaidMessage": { "message": "Pagpapanatili ng privacy - walang data na ibinabahagi sa mga third party. Available sa Arbitrum, Avalanche, BNB chain, Ethereum Mainnet, Linea, Optimism, Polygon, Base at Sepolia." }, @@ -687,7 +831,7 @@ "message": "Ito ay mapanlinlang na kahilingan" }, "blockaidTitleMayNotBeSafe": { - "message": "Maaaring hindi ligtas ang kahilingan" + "message": "Maging maingat" }, "blockaidTitleSuspicious": { "message": "Ito ay kahina-hinalang kahilingan" @@ -695,6 +839,9 @@ "blockies": { "message": "Mga Blocky" }, + "boughtFor": { + "message": "Binili para sa/kay" + }, "bridge": { "message": "Bridge" }, @@ -779,6 +926,9 @@ "chromeRequiredForHardwareWallets": { "message": "Kailangan mong gamitin ang MetaMask sa Google Chrome para maikonekta sa iyong Wallet na Hardware." }, + "circulatingSupply": { + "message": "Umiikot na supply" + }, "clear": { "message": "I-clear" }, @@ -799,7 +949,7 @@ "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" }, "clickToManuallyAdd": { - "message": "Pindutin ito para manu-manong idagdag ang mga token." + "message": "Maaari ka ring magdagdag ng mga token nang manu-mano." }, "close": { "message": "Isara" @@ -813,6 +963,9 @@ "coingecko": { "message": "CoinGecko" }, + "collectionName": { + "message": "Pangalan ng koleksyon" + }, "comboNoOptions": { "message": "Walang opsyon na nahanap", "description": "Default text shown in the combo field dropdown if no options." @@ -836,6 +989,9 @@ "message": "Kumpirmahin" }, "confirmAlertModalAcknowledgeMultiple": { + "message": "Kinikilala ko ang mga alerto at nais ko pa ring magpatuloy" + }, + "confirmAlertModalAcknowledgeSingle": { "message": "Kinikilala ko ang mga alerto at nais ko pa rin magpatuloy" }, "confirmAlertModalDetails": { @@ -853,18 +1009,42 @@ "confirmConnectionTitle": { "message": "Kumpirmahin ang koneksyon sa $1" }, + "confirmDeletion": { + "message": "Kumpirmahin ang pagtanggal" + }, + "confirmFieldPaymaster": { + "message": "Ang bayarin ay binayaran ni" + }, + "confirmFieldTooltipPaymaster": { + "message": "Ang bayarin para sa transaksyong ito ay babayaran ng paymaster smart contract." + }, "confirmPassword": { "message": "Kumpirmahin ang password" }, "confirmRecoveryPhrase": { "message": "Kumpirmahin ang Lihim na Parirala sa Pagbawi" }, + "confirmRpcUrlDeletionMessage": { + "message": "Sigurado ka ba na nais mong tanggalin ang RPC URL? Ang iyong impormasyon ay hindi mase-save sa network na ito." + }, "confirmTitleDescContractInteractionTransaction": { "message": "Kumpirmahin lamang ang transaksyong ito kung ganap mong nauunawaan ang nilalaman at nagtitiwala sa site na humihiling." }, + "confirmTitleDescPermitSignature": { + "message": "Ang site na ito ay nais ng permiso para gamitin ang iyong mga token." + }, + "confirmTitleDescSIWESignature": { + "message": "Nais ng site na mag-sign in ka upang patunayan na pag-aari mo ang account na ito." + }, "confirmTitleDescSignature": { "message": "Kumpirmahin lamang ang mensaheng ito kung ganap mong nauunawaan ang nilalaman at nagtitiwala sa site na humihiling." }, + "confirmTitlePermitSignature": { + "message": "Hiling sa limitasyon ng paggastos" + }, + "confirmTitleSIWESignature": { + "message": "Kahilingan sa pag-sign in" + }, "confirmTitleSignature": { "message": "Kahilingan sa paglagda" }, @@ -958,7 +1138,7 @@ "message": "Nakakonekta sa" }, "connecting": { - "message": "Kumokonekta..." + "message": "Kumokonekta" }, "connectingTo": { "message": "Kumokonekta sa $1" @@ -1091,6 +1271,9 @@ "createSnapAccountTitle": { "message": "Gumawa ng account" }, + "creatorAddress": { + "message": "Address ng creator" + }, "crossChainSwapsLink": { "message": "Mag-swap sa mga network gamit ang MetaMask Portfolio" }, @@ -1260,9 +1443,27 @@ "data": { "message": "Datos" }, + "dataCollectionForMarketing": { + "message": "Koleksyon ng data para sa marketing" + }, + "dataCollectionForMarketingDescription": { + "message": "Gagamit kami ng MetaMetrics upang matutunan kung paano ka nakikipag-ugnayan sa aming komunikasyon sa marketing. Maaari naming ibahagi ang mga may kaugnayang balita (tulad ng mga tampok ng produkto at iba pang materyal)." + }, + "dataCollectionWarningPopoverButton": { + "message": "Okay" + }, + "dataCollectionWarningPopoverDescription": { + "message": "In-off mo ang koleksyon ng data para sa layunin sa marketing. Ito ay inilalapat lamang sa device na ito. Kung gumagamit ka ng MetaMask sa ibang mga device, siguraduhing mag-opt out din doon." + }, "dataHex": { "message": "Hex" }, + "dataUnavailable": { + "message": "hindi available ang data" + }, + "dateCreated": { + "message": "Petsa ng paggawa" + }, "dcent": { "message": "D'Cent" }, @@ -1292,6 +1493,9 @@ "decryptRequest": { "message": "Kahilingan sa pag-decrypt" }, + "defaultRpcUrl": { + "message": "Default na RPC URL" + }, "delete": { "message": "Tanggalin" }, @@ -1308,6 +1512,9 @@ "message": "Tanggalin ang $1 network?", "description": "$1 represents the name of the network" }, + "deleteRpcUrl": { + "message": "Tanggalin ang RPC URL" + }, "deposit": { "message": "Deposito" }, @@ -1333,18 +1540,6 @@ "details": { "message": "Mga Detalye" }, - "developerOptions": { - "message": "Mga Opsyon ng Developer" - }, - "developerOptionsResetStatesAnnouncementsDescription": { - "message": "Ang mga reset ay ipinapakita sa boolean para maging false sa lahat ng anunsiyo. Ang mga anunisyo ay mga notipikasyon na ipinapakita sa What's New popup modal." - }, - "developerOptionsResetStatesOnboarding": { - "message": "Nire-reset ang iba't ibang estado kaugnay sa onboarding at nire-redirect sa \"Secure Your Wallet\" onboarding page." - }, - "developerOptionsServiceWorkerKeepAlive": { - "message": "Magreresulta sa isang timestamp na tuloy-tuloy na naka-save sa session.storage" - }, "disabledGasOptionToolTipMessage": { "message": "Ang “$1” ay hindi pinagagana dahil hindi nito naabot ang pinakamababang 10% na dagdag mula sa orihinal na bayad sa gas.", "description": "$1 is gas estimate type which can be market or aggressive" @@ -1521,6 +1716,9 @@ "editGasTooLow": { "message": "Hindi kilalang oras ng pagproseso" }, + "editNetworkLink": { + "message": "i-edit ang orihinal na network" + }, "editNonceField": { "message": "I-edit sa Nonce" }, @@ -1552,6 +1750,9 @@ "enabled": { "message": "Pinagana" }, + "enabledNetworks": { + "message": "Mga pinapaganang network" + }, "encryptionPublicKeyNotice": { "message": "Kailangan ng $1 ang iyong pampublikong encryption key. Sa pamamagitan ng pagbibigay ng pahintulot, makakagawa ang site na ito ng mga naka-encrypt na mensahe para sa iyo.", "description": "$1 is the web3 site name" @@ -1656,6 +1857,9 @@ "estimatedFee": { "message": "Tinatayang singil" }, + "estimatedFeeTooltip": { + "message": "Halaga na binayaran para iproseso ang transaksyon sa network." + }, "ethGasPriceFetchWarning": { "message": "Ang backup na presyo ng gas ay ibinigay dahil ang pangunahing serbisyo ng pagtantya ng gas ay hindi available sa ngayon." }, @@ -1675,6 +1879,12 @@ "etherscanViewOn": { "message": "Tingnan sa Etherscan" }, + "existingChainId": { + "message": "Ang impormasyon na iyong inilagay ay nauugnay sa isang umiiral na ID ng chain." + }, + "existingRpcUrl": { + "message": "Ang URL na ito ay nauugnay sa ibang ID ng chain." + }, "expandView": { "message": "I-expand ang view" }, @@ -1729,6 +1939,9 @@ "message": "Hindi gumagana ang pag-import ng file? Mag-click dito!", "description": "Helps user import their account from a JSON file" }, + "findTheRightChainId": { + "message": "Hanapin ang tama sa:" + }, "flaskWelcomeUninstall": { "message": "dapat mong i-uninstall ang extension na ito", "description": "This request is shown on the Flask Welcome screen. It is intended for non-developers, and will be bolded." @@ -1768,6 +1981,9 @@ "forgotPassword": { "message": "Nakalimutan ang password?" }, + "form": { + "message": "form" + }, "from": { "message": "Mula kay/sa" }, @@ -1898,7 +2114,7 @@ "message": "Goerli Test Network" }, "gotIt": { - "message": "OK!" + "message": "Nakuha ko" }, "grantedToWithColon": { "message": "Ipinagkaloob kay:" @@ -1976,6 +2192,12 @@ "highLowercase": { "message": "mataas" }, + "highestCurrentBid": { + "message": "Pinakamataas na kasalukuyang bid" + }, + "highestFloorPrice": { + "message": "Pinakamataas na floor price" + }, "history": { "message": "History" }, @@ -2315,12 +2537,21 @@ "knownTokenWarning": { "message": "Mae-edit ng aksyong ito ang mga token na nakalista na sa iyong wallet, na puwedeng gamitin para i-phish ka. Aprubahan lang kung sigurado kang gusto mong baguhin kung ano ang kinakatawan ng mga token na ito. Alamin pa ang tungkol sa $1" }, + "l1Fee": { + "message": "L1 na bayad" + }, + "l1FeeTooltip": { + "message": "L1 na bayad sa gas" + }, + "l2Fee": { + "message": "L2 na bayad" + }, + "l2FeeTooltip": { + "message": "L2 na bayad sa gas" + }, "lastConnected": { "message": "Huling Kumonekta" }, - "lastPriceSold": { - "message": "Huling presyong naibenta" - }, "lastSold": { "message": "Huling naibenta" }, @@ -2492,6 +2723,12 @@ "message": "Siguraduhing walang tumitingin", "description": "Warning to users to be care while creating and saving their new Secret Recovery Phrase" }, + "marketCap": { + "message": "Market cap" + }, + "marketDetails": { + "message": "Mga detalye ng market" + }, "max": { "message": "Max" }, @@ -2501,6 +2738,9 @@ "maxFee": { "message": "Pinakamataas na bayad" }, + "maxFeeTooltip": { + "message": "Ang pinakamalaking bayad na inilaan para bayaran ang transaksyon." + }, "maxPriorityFee": { "message": "Pinakamataas na bayad sa priyoridad" }, @@ -2548,12 +2788,19 @@ "methodData": { "message": "Pamamaraan" }, + "methodDataTransactionDesc": { + "message": "Isinakatuparang function batay sa na-decode na input data." + }, "methodNotSupported": { "message": "Hindi suportado sa account na ito." }, "metrics": { "message": "Metrics" }, + "millionAbbreviation": { + "message": "M", + "description": "Shortened form of 'million'" + }, "mismatchAccount": { "message": "Ang pinili mong account na ($1) ay kakaiba sa account na sinusubukan mong mag-sign in ($2)" }, @@ -2666,7 +2913,7 @@ "description": "Description below header used on Permission Connect screen for native permissions." }, "nativeToken": { - "message": "Ang native token sa network na ito ay $1. Ito ang token na ginagamit para sa mga bayad sa gas.", + "message": "Ang native token sa network na ito ay $1. Ito ang token na ginagamit para sa mga gas fee. ", "description": "$1 represents the name of the native token on the current network" }, "nativeTokenScamWarningConversion": { @@ -2735,6 +2982,9 @@ "networkNameBase": { "message": "Base" }, + "networkNameBitcoin": { + "message": "Bitcoin" + }, "networkNameDefinition": { "message": "Ang pangalan ay nauugnay sa network na ito." }, @@ -2759,6 +3009,9 @@ "networkNameZkSyncEra": { "message": "zkSync Era" }, + "networkOptions": { + "message": "Mga pagpipilian na network" + }, "networkProvider": { "message": "Network provider" }, @@ -2827,6 +3080,9 @@ "newNetworkAdded": { "message": "Ang “$1” matagumpay na naidagdag!" }, + "newNetworkEdited": { + "message": "Ang “$1” ay matagumpay na na-edit!" + }, "newNftAddedMessage": { "message": "Matagumpay na naidagdag ang NFT!" }, @@ -2863,8 +3119,11 @@ "nftAlreadyAdded": { "message": "Naidagdag na ang NFT." }, + "nftAutoDetectionEnabled": { + "message": "Pinagana ang autodetection ng NFT" + }, "nftDisclaimer": { - "message": "Disclaimer: Kinukuha ng MetaMask ang media file mula sa pinagmulang url. Ang url na ito kung minsan ay pinapalitan ng marketplace kung saan naka-mint ang NFT." + "message": "Disclaimer: Kinukuha ng MetaMask ang media file mula sa pinagmulang url. Ang url na ito kung minsan ay pinapalitan ng marketplace kung saan nalikha ang NFT." }, "nftOptions": { "message": "Mga Pagpipilian sa NFT" @@ -2913,6 +3172,9 @@ "noDomainResolution": { "message": "Walang resolusyon para sa ibinigay na domain." }, + "noHardwareWalletOrSnapsSupport": { + "message": "Ang mga Snap, at karamihan sa mga hardware wallet, ay hindi gagana sa iyong kasalukuyang bersyon ng browser." + }, "noNFTs": { "message": "Wala pang mga NFT" }, @@ -2943,8 +3205,8 @@ "nonceField": { "message": "I-customize ang nonce ng transaksyon" }, - "nonceFieldDescription": { - "message": "I-on ito para baguhin ang nonce (numero ng transaksyon) sa mga screen ng kumpirmasyon. Ito ay isang advanced na feature, gamitin nang maingat." + "nonceFieldDesc": { + "message": "I-on ito para baguhin ang nonce (numero ng transaksyon) kapag nagpapadala ng mga asset. Ito ay isang advanced na feature, gamitin nang may pag-iingat." }, "nonceFieldHeading": { "message": "I-custom ang nonce" @@ -2992,7 +3254,7 @@ "message": "Bayad sa priyoridad (GWEI)" }, "notificationItemCheckBlockExplorer": { - "message": "Tingnan sa BlockExplorer" + "message": "Tingnan sa Block Explorer" }, "notificationItemCollection": { "message": "Koleksyon" @@ -3146,6 +3408,9 @@ "numberOfNewTokensDetectedSingular": { "message": "1 bagong token ang nakita sa account na ito" }, + "numberOfTokens": { + "message": "Bilang ng token" + }, "ofTextNofM": { "message": "ng" }, @@ -3161,8 +3426,36 @@ "on": { "message": "Naka-on" }, - "onboarding": { - "message": "Onboarding" + "onboardedMetametricsAccept": { + "message": "Sumasang-ayon ako" + }, + "onboardedMetametricsDisagree": { + "message": "Huwag na lang" + }, + "onboardedMetametricsKey1": { + "message": "Pinakabagong mga pagpapaunlad" + }, + "onboardedMetametricsKey2": { + "message": "Mga tampok ng produkto" + }, + "onboardedMetametricsKey3": { + "message": "Iba pang kaugnay na mga materyal sa promosyon" + }, + "onboardedMetametricsLink": { + "message": "MetaMetrics" + }, + "onboardedMetametricsParagraph1": { + "message": "Dagdag pa sa $1, nais naming gamitin ang data upang maunawaan kung paano ka nakikipag-ugnayan sa komunikasyon sa marketing.", + "description": "$1 represents the 'onboardedMetametricsLink' locale string" + }, + "onboardedMetametricsParagraph2": { + "message": "Tumutulong ito sa amin na maiangkop ang aming ibinabahagi sa iyo, tulad ng:" + }, + "onboardedMetametricsParagraph3": { + "message": "Tandaan, hindi namin ibebenta ang data na iyong ibibigay at maaari kang mag-opt out anumang oras." + }, + "onboardedMetametricsTitle": { + "message": "Tulungan kami na paghusayin ang iyong karanasan" }, "onboardingAdvancedPrivacyIPFSDescription": { "message": "Ginagawang posible ng IPFS gateway na ma-access at makita ang datos na pinangangasiwaan ng mga third party. Maaari kang magdagdag ng custom na IPFS gateway o magpatuloy sa paggamit ng default." @@ -3200,9 +3493,6 @@ "onboardingMetametricsDescription2": { "message": "Kapag kami ay nangangalap ng metrics, ito ay palaging..." }, - "onboardingMetametricsDisagree": { - "message": "Salamat nalang" - }, "onboardingMetametricsInfuraTerms": { "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`" @@ -3237,6 +3527,9 @@ "onboardingMetametricsTitle": { "message": "Tulungan kaming mapahusay ang MetaMask" }, + "onboardingMetametricsUseDataCheckbox": { + "message": "Gagamitin namin ang data na ito upang matutunan kung paano ka nakikipag-ugnayan sa aming mga komunikasyon sa marketing. Maaari kaming magbahagi ng kaugnay na balita (tulad ng mga tampok ng produkto)." + }, "onboardingPinExtensionBillboardAccess": { "message": "Buong Access" }, @@ -3280,6 +3573,22 @@ "message": "Ang mga alerto sa pagtuklas ng phishing ay umaasa sa komunikasyon sa $1. Ang jsDeliver ay magkakaroon ng access sa iyong IP address. Tingnan ang $2.", "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" }, + "oneDayAbbreviation": { + "message": "1D", + "description": "Shortened form of '1 day'" + }, + "oneMonthAbbreviation": { + "message": "1M", + "description": "Shortened form of '1 month'" + }, + "oneWeekAbbreviation": { + "message": "1W", + "description": "Shortened form of '1 week'" + }, + "oneYearAbbreviation": { + "message": "1Y", + "description": "Shortened form of '1 year'" + }, "onekey": { "message": "OneKey" }, @@ -3632,6 +3941,9 @@ "permissionsPageTourTitle": { "message": "Ang mga konektadong site ay pahintulot na ngayon" }, + "permitSimulationDetailInfo": { + "message": "Binibigyan mo ang gumagastos ng permiso upang gumastos ng ganito karaming token mula sa iyong account." + }, "personalAddressDetected": { "message": "Natukoy ang personal na address. Ilagay ang address ng kontrata ng token." }, @@ -3664,6 +3976,10 @@ "popularCustomNetworks": { "message": "Mga sikat na custom na network" }, + "popularNetworkAddToolTip": { + "message": "Ang ilan sa mga network na ito ay umaasa sa mga third party. Ang koneksyon na ito ay maaaring hindi gaanong maaasahan o binibigyang-daan ang mga third-party na mag-track ng aktibidad. $1", + "description": "$1 is Learn more link" + }, "portfolio": { "message": "Portfolio" }, @@ -3676,6 +3992,12 @@ "prev": { "message": "Nakaraan" }, + "price": { + "message": "Presyo" + }, + "priceUnavailable": { + "message": "hindi available ang presyo" + }, "primaryCurrencySetting": { "message": "Pangunahing Currency" }, @@ -3828,6 +4150,9 @@ "quoteRate": { "message": "Quote rate" }, + "rank": { + "message": "Rank" + }, "reAddAccounts": { "message": "idagdag muli ang anumang ibang mga account" }, @@ -3997,9 +4322,6 @@ "reset": { "message": "I-reset" }, - "resetStates": { - "message": "I-reset ang Mga Estado" - }, "resetWallet": { "message": "I-reset ang wallet" }, @@ -4135,6 +4457,9 @@ "searchAccounts": { "message": "Maghanap ng mga account" }, + "searchNfts": { + "message": "Hanapin ang mga NFT" + }, "searchTokens": { "message": "Maghanap ng mga token" }, @@ -4179,13 +4504,10 @@ "seedPhraseIntroRecommendedButtonCopy": { "message": "I-secure ang aking wallet (inirerekomenda)" }, - "seedPhraseIntroSidebarBulletFour": { - "message": "Isulat at itago sa maraming sikretong lugar." - }, "seedPhraseIntroSidebarBulletOne": { - "message": "I-save sa tagapamahala ng password" + "message": "Isulat at itago sa maraming sikretong lugar." }, - "seedPhraseIntroSidebarBulletThree": { + "seedPhraseIntroSidebarBulletTwo": { "message": "Itago sa safe-deposit box." }, "seedPhraseIntroSidebarCopyOne": { @@ -4255,10 +4577,10 @@ "message": "Pumili ng token" }, "selectNFTPrivacyPreference": { - "message": "I-on ang pagtuklas ng NFT sa Mga Setting" + "message": "Paganahin ang autodetection ng NFT" }, "selectPathHelp": { - "message": "Kung hindi mo makita ang mga account na inaasahan mo, subukang lumipat ng HD path." + "message": "Kung hindi mo makita ang mga account na inaasahan mo, subukang lumipat ng HD path o kasalukuyang piniling network." }, "selectType": { "message": "Pumili ng Uri" @@ -4269,9 +4591,6 @@ "send": { "message": "Magpadala" }, - "sendAToken": { - "message": "Magpadala ng token" - }, "sendBugReport": { "message": "Padalhan kami ng ulat ng bug." }, @@ -4322,9 +4641,6 @@ "sepolia": { "message": "Sepolia test network" }, - "serviceWorkerKeepAlive": { - "message": "Service Worker Keep Alive" - }, "setAdvancedPrivacySettingsDetails": { "message": "Ginagamit ng MetaMask ang mga pinagkakatiwalaang serbisyo ng third-party na ito para mapahusay ang kakayahang magamit at kaligtasan ng produkto." }, @@ -4381,6 +4697,9 @@ "showIncomingTransactionsExplainer": { "message": "Ito ay umaasa sa ibang third party na API para sa bawat network, na naglalantad sa iyong address sa Ethereum at iyong IP address." }, + "showLess": { + "message": "Magpakita ng mas kaunti" + }, "showMore": { "message": "Ipakita ang iba pa" }, @@ -4411,9 +4730,6 @@ "signatureRequestGuidance": { "message": "Pirmahan lang ang mensaheng ito kung ganap mong nauunawaan ang nilalaman at nagtitiwala sa site na humihiling." }, - "signatureRequestWarning": { - "message": "Ang pagpirma sa mensaheng ito ay maaaring mapanganib. Maaari kang magbigay ng buong kontrol ng iyong account at mga asset sa partido sa kabilang dulo ng mensaheng ito. Nangangahulugan iyon na maaari nitong ubusin ang laman ng iyong account anumang oras. Magpatuloy nang may pag-iingat. $1." - }, "signed": { "message": "Napirmahan" }, @@ -4423,6 +4739,9 @@ "signing": { "message": "Pinipirmahan" }, + "signingInWith": { + "message": "Nagsa-sign in gamit ang" + }, "simulationDetailsFailed": { "message": "Mayroong error sa pag-load ng iyong pagtataya." }, @@ -4460,6 +4779,24 @@ "simulationsSettingSubHeader": { "message": "Tinatayang mga pagbabago sa balanse" }, + "siweIssued": { + "message": "Ibinigay" + }, + "siweNetwork": { + "message": "Network" + }, + "siweRequestId": { + "message": "Request ID" + }, + "siweResources": { + "message": "Mga Mapagkukunan" + }, + "siweSignatureSimulationDetailInfo": { + "message": "Ikaw ay nagsa-sign in sa isang site at walang mga inaasahang pagbabago sa iyong account." + }, + "siweURI": { + "message": "URL" + }, "skip": { "message": "Laktawan" }, @@ -4563,6 +4900,14 @@ "snapAccountsDescription": { "message": "Mga account na kontrol lang ng mga third-party na Snap." }, + "snapConnectTo": { + "message": "Kumonekta sa $1", + "description": "$1 is the website URL or a Snap name. Used for Snaps pre-approved connections." + }, + "snapConnectionPermissionDescription": { + "message": "Hayaan ang $1 na awtomatikong kumonekta sa $2 nang wala ang iyong pag-apruba.", + "description": "Used for Snap pre-approved connections. $1 is the Snap name, $2 is a website URL." + }, "snapConnectionWarning": { "message": "Si $1 ay gustong gumamit ng $2", "description": "$2 is the snap and $1 is the dapp requesting connection to the snap." @@ -4574,6 +4919,9 @@ "snapDetailWebsite": { "message": "Website" }, + "snapHomeMenu": { + "message": "Snap Home Menu" + }, "snapInstallRequest": { "message": "Ang pag-install ng $1 ay nagbibigay ng mga sumusunod na pahintulot.", "description": "$1 is the snap name." @@ -4696,6 +5044,9 @@ "source": { "message": "Pinagmulan" }, + "speed": { + "message": "Bilis" + }, "speedUp": { "message": "Pabilisin" }, @@ -4730,6 +5081,9 @@ "spendLimitTooLarge": { "message": "Masyadong malaki ang limitasyon sa paggastos" }, + "spender": { + "message": "Gumagastos" + }, "spendingCap": { "message": "Limitasyon sa paggastos" }, @@ -4850,9 +5204,6 @@ "stateLogsDescription": { "message": "Naglalaman ang mga log ng estado ng iyong mga address ng pampublikong account at ipinadalang transaksyon." }, - "states": { - "message": "Mga estado" - }, "status": { "message": "Katayuan" }, @@ -4957,6 +5308,13 @@ "submitted": { "message": "Naisumite" }, + "suggestedBySnap": { + "message": "Iminumungkahi ng $1", + "description": "$1 is the snap name" + }, + "suggestedTokenName": { + "message": "Iminumungkahing pangalan:" + }, "suggestedTokenSymbol": { "message": "Iminumungkahing ticket symbol:" }, @@ -5071,7 +5429,7 @@ "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." }, "swapFetchingQuotes": { - "message": "Kinukuha ang mga quote" + "message": "Kinukuha ang mga quote..." }, "swapFetchingQuotesErrorDescription": { "message": "Hmmm... nagkaproblema. Subukan ulit, o kung magpapatuloy ang mga error, makipag-ugnayan sa customer support." @@ -5467,6 +5825,10 @@ "thisCollection": { "message": "koleksyong ito" }, + "threeMonthsAbbreviation": { + "message": "3M", + "description": "Shortened form of '3 months'" + }, "time": { "message": "Oras" }, @@ -5480,45 +5842,6 @@ "message": "Para kay/sa: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleEthSignBannerDescription": { - "message": "Nanganganib ka sa mga phishing na pag-atake. Protektahan ang sarili mo sa pamamagitan ng pag-off sa eth_sign." - }, - "toggleEthSignDescriptionField": { - "message": "Kung pagaganahin mo ang setting na ito, maaari kang makakuha ng mga kahilingan sa paglagda na hindi nababasa. Sa pamamagitan ng pagpirma sa isang mensaheng hindi mo naiintindihan, posible kang sumang-ayon na ipamigay ang iyong mga pondo at NFT." - }, - "toggleEthSignField": { - "message": "Mga kahilingan sa Eth_sign" - }, - "toggleEthSignModalBannerBoldText": { - "message": " baka niloloko ka" - }, - "toggleEthSignModalBannerText": { - "message": "Kung hiniling sa iyo na i-on ang setting na ito," - }, - "toggleEthSignModalCheckBox": { - "message": "Nauunawaan ko na maaaring mawala ang aking pondo at mga NFT kapag pinagana ko ang mga kahilingan sa eth-sign. " - }, - "toggleEthSignModalDescription": { - "message": "Ang pagbibigay ng pahintulot sa mga kahilingan ng eth_sign ay magpapahina sa iyo upang atakehin ng phishing. Palaging i-review ang URL a t mag-ingat kapag nagsa-sign ng mga mensahe na naglalaman ng code." - }, - "toggleEthSignModalFormError": { - "message": "Mali ang text" - }, - "toggleEthSignModalFormLabel": { - "message": "Ilagay ang “pipirmahan ko lang kung ano ang nauunawaan ko” para magpatuloy" - }, - "toggleEthSignModalFormValidation": { - "message": "Pipirmahan ko lang kung ano ang nauunawaan ko" - }, - "toggleEthSignModalTitle": { - "message": "Gamitin sa sarili mong pananagutan" - }, - "toggleEthSignOff": { - "message": "I-OFF (Inirerekomenda)" - }, - "toggleEthSignOn": { - "message": "I-ON (Hindi inirerekomenda)" - }, "toggleRequestQueueDescription": { "message": "Pinahihintulutan ka nitong pumili ng network para sa bawat site sa halip na iisang piniling network para sa lahat ng site. Pipigilan ka ng feature na ito na magpalit ng network nang mano-mano, na maaaring makasira sa iyong karanasan bilang user sa ilang partikular na site." }, @@ -5546,6 +5869,9 @@ "tokenContractAddress": { "message": "Address ng kontrata ng token" }, + "tokenDecimal": { + "message": "Decimal na token" + }, "tokenDecimalFetchFailed": { "message": "Kailangan ng decimal ng token. Hanapin ito sa: $1" }, @@ -5562,13 +5888,16 @@ "message": "ID ng Token" }, "tokenList": { - "message": "Mga listahan ng token:" + "message": "Mga listahan ng token" }, "tokenScamSecurityRisk": { "message": "mga panloloko sa token at panganib sa seguridad" }, "tokenShowUp": { - "message": "Maaaring hindi awtomatikong lumabas ang iyong mga token sa iyong wallet." + "message": "Maaaring hindi awtomatikong lumabas ang iyong mga token sa iyong wallet. " + }, + "tokenStandard": { + "message": "Pamantayan ng token" }, "tokenSymbol": { "message": "Simbolo ng token" @@ -5580,6 +5909,9 @@ "message": "$1 bagong token ang nakita", "description": "$1 is the number of new tokens detected" }, + "tokensInCollection": { + "message": "Mga token sa koleksyon" + }, "tooltipApproveButton": { "message": "Nauunawaan ko" }, @@ -5595,6 +5927,9 @@ "total": { "message": "Kabuuan" }, + "totalVolume": { + "message": "Kabuuang volume" + }, "transaction": { "message": "transaksyon" }, @@ -5610,6 +5945,9 @@ "transactionCreated": { "message": "Nagawa ang transaksyon na nagkakahalagang $1 sa $2." }, + "transactionDataFunction": { + "message": "Function" + }, "transactionDetailDappGasMoreInfo": { "message": "Minungkahing site" }, @@ -5700,6 +6038,10 @@ "transferFrom": { "message": "Maglipat mula kay/sa" }, + "trillionAbbreviation": { + "message": "T", + "description": "Shortened form of 'trillion'" + }, "troubleConnectingToLedgerU2FOnFirefox": { "message": "Nagkakaproblema kami sa pagkonekta ng iyong Ledger. $1", "description": "$1 is a link to the wallet connection guide;" @@ -5778,6 +6120,9 @@ "message": "U2F", "description": "A name on an API for the browser to interact with devices that support the U2F protocol. On some browsers we use it to connect MetaMask to Ledger devices." }, + "unMatchedChain": { + "message": "Ayon sa aming mga talaan, ang URL na ito ay hindi tumutugma sa isang kilalang provider para sa ID ng chain na ito." + }, "unapproved": { "message": "Hindi inaprubahan" }, @@ -5829,12 +6174,21 @@ "update": { "message": "I-update" }, + "updateOrEditNetworkInformations": { + "message": "I-update ang iyong impormasyon o" + }, "updateRequest": { "message": "Hiling sa pag-update" }, "updatedWithDate": { "message": "Na-update noong $1" }, + "uploadDropFile": { + "message": "I-drop ang file mo rito" + }, + "uploadFile": { + "message": "I-upload ang file" + }, "urlErrorMsg": { "message": "Kinakailangan ng mga URL ang naaangkop na HTTP/HTTPS prefix." }, @@ -6050,6 +6404,12 @@ "whatsThis": { "message": "Ano ito?" }, + "wrongChainId": { + "message": "Ang ID ng chain na ito ay hindi tugma sa pangalan ng network." + }, + "wrongNetworkName": { + "message": "Ayon sa aming mga talaan, ang pangalan ng network ay maaaring hindi tumugma nang tama sa ID ng chain na ito." + }, "xOfYPending": { "message": "$1 ng $2 ang nakabinbin", "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" @@ -6073,8 +6433,11 @@ "yourAccounts": { "message": "Mga account mo" }, - "yourFundsMayBeAtRisk": { - "message": "Maaaring nasa panganib ang iyong pondo" + "yourActivity": { + "message": "Iyong aktibidad" + }, + "yourBalance": { + "message": "Iyong balanse" }, "yourNFTmayBeAtRisk": { "message": "Maaaring nasa panganib ang iyong NFT" diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index 9d727b687e32..b58a54040cb1 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -42,7 +42,7 @@ "message": "QR donanım cüzdanınızı bağlayın" }, "QRHardwareWalletSteps2Description": { - "message": "Ngrave (çok yakında)" + "message": "Ngrave Zero" }, "SIWEAddressInvalid": { "message": "Giriş talebindeki adres, giriş yapmak için kullandığınız hesapla uyumlu değil." @@ -162,6 +162,9 @@ "accountSelectionRequired": { "message": "Bir hesap seçmeniz gerekiyor!" }, + "accountTypeNotSupported": { + "message": "Hesap türü desteklenmiyor" + }, "accounts": { "message": "Hesaplar" }, @@ -277,6 +280,12 @@ "addNewAccount": { "message": "Yeni bir Ethereum hesabı ekle" }, + "addNewBitcoinAccount": { + "message": "Yeni bir Bitcoin hesabı ekle (Beta)" + }, + "addNewBitcoinTestnetAccount": { + "message": "Yeni bir Bitcoin hesabı ekle (Test Ağı)" + }, "addNewToken": { "message": "Yeni token ekleyin" }, @@ -286,6 +295,9 @@ "addNfts": { "message": "NFT ekleyin" }, + "addRpcUrl": { + "message": "RPC URL adresi ekle" + }, "addSnapAccountToggle": { "message": "\"Snap hesabı ekle (Beta)\" özelliğini etkinleştir" }, @@ -305,12 +317,21 @@ "message": "Bir tokeni bulamadınız mı? Adresini yapıştırarak dilediğiniz tokeni manuel olarak ekleyebilirsiniz. Token sözleşme adreslerini $1 alanında bulabilirsiniz", "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" }, + "addUrl": { + "message": "URL ekle" + }, "addingCustomNetwork": { "message": "Ağ Ekleniyor" }, "addingTokens": { "message": "Token'leri ekleme" }, + "additionalNetworks": { + "message": "İlave ağlar" + }, + "additionalRpcUrl": { + "message": "Diğer RPC URL" + }, "address": { "message": "Adres" }, @@ -326,6 +347,18 @@ "advancedConfiguration": { "message": "Gelişmiş yapılandırma" }, + "advancedDetailsDataDesc": { + "message": "Veri" + }, + "advancedDetailsHexDesc": { + "message": "On Altılı" + }, + "advancedDetailsNonceDesc": { + "message": "Nonce" + }, + "advancedDetailsNonceTooltip": { + "message": "Bir hesabın işlem numarasıdır. İlk işlem için nonce 0 olup sıralı olarak artar." + }, "advancedGasFeeDefaultOptIn": { "message": "Bu değerleri $1 ağı için varsayılanım olarak kaydet.", "description": "$1 is the current network name." @@ -349,6 +382,18 @@ "alert": { "message": "Uyarı" }, + "alertActionBuy": { + "message": "ETH Al" + }, + "alertActionUpdateGas": { + "message": "Gaz limitini güncelle" + }, + "alertActionUpdateGasFee": { + "message": "Ücreti güncelle" + }, + "alertActionUpdateGasFeeLevel": { + "message": "Gaz seçeneklerini güncelle" + }, "alertBannerMultipleAlertsDescription": { "message": "Bu talebi onaylarsanız dolandırıcılıkla bilinen üçüncü bir taraf tüm varlıklarınızı çalabilir." }, @@ -358,6 +403,36 @@ "alertDisableTooltip": { "message": "\"Ayarlar > Uyarılar\" kısmında değiştirilebilir" }, + "alertMessageGasEstimateFailed": { + "message": "Kesin bir ücret sunamıyoruz ve bu tahmin yüksek olabilir. Özel bir gaz limiti girmenizi öneririz ancak işlemin yine de başarısız olma riski vardır." + }, + "alertMessageGasFeeLow": { + "message": "Düşük bir ücret seçerken işlemlerin daha yavaş olmasını ve bekleme sürelerinin daha uzun olmasını bekleyebilirsiniz. Daha hızlı işlemler için Piyasa veya Agresif ücret seçeneklerini seçin." + }, + "alertMessageGasTooLow": { + "message": "Bu işlemle devam etmek için gaz limitini 21000 veya üzeri olacak şekilde artırmanız gerekecek." + }, + "alertMessageInsufficientBalance": { + "message": "Hesabınızda işlem ücretlerini ödemek için yeterli ETH yok." + }, + "alertMessageNetworkBusy": { + "message": "Gaz fiyatları yüksektir ve tahmin daha az kesindir." + }, + "alertMessageNoGasPrice": { + "message": "Siz ücreti manuel olarak güncelleyene dek bu işleme devam edemiyoruz." + }, + "alertMessagePendingTransactions": { + "message": "Önceki bir işlem tamamlanana dek bu işlem gerçekleşmeyecektir. Bir işlemi nasıl iptal edeceğinizi veya hızlandıracağınızı öğrenin." + }, + "alertMessageSignInDomainMismatch": { + "message": "Talepte bulunan site giriş yaptığınız site değil. Bu durum oturum açma bilgilerinizi çalma teşebbüsü olabilir." + }, + "alertMessageSignInWrongAccount": { + "message": "Bu site sizden yanlış hesabı kullanarak giriş yapmanızı istiyor." + }, + "alertMessageSigningOrSubmitting": { + "message": "Bu işlem sadece önceki işleminiz tamamlandıktan sonra gerçekleşecek." + }, "alertModalAcknowledge": { "message": "Riski anlıyor ve yine de ilerlemek istiyorum" }, @@ -367,6 +442,33 @@ "alertModalReviewAllAlerts": { "message": "Tüm uyarıları incele" }, + "alertReasonGasEstimateFailed": { + "message": "Ücret yanlış" + }, + "alertReasonGasFeeLow": { + "message": "Hız yavaş" + }, + "alertReasonGasTooLow": { + "message": "Gaz limiti düşük" + }, + "alertReasonInsufficientBalance": { + "message": "Para yetersiz" + }, + "alertReasonNetworkBusy": { + "message": "Ağ meşgul" + }, + "alertReasonNoGasPrice": { + "message": "Ücret tahmini mevcut değil" + }, + "alertReasonPendingTransactions": { + "message": "İşlem beklemede" + }, + "alertReasonSignIn": { + "message": "Şüpheli giriş talebi" + }, + "alertReasonWrongAccount": { + "message": "Yanlış hesap" + }, "alertSettingsUnconnectedAccount": { "message": "Bağlı olmayan bir hesap ile bir web sitesine göz atma seçildi" }, @@ -398,6 +500,12 @@ "allPermissions": { "message": "Tüm İzinler" }, + "allTimeHigh": { + "message": "Tüm zamanların en yükseği" + }, + "allTimeLow": { + "message": "Tüm zamanların en düşüğü" + }, "allYourNFTsOf": { "message": "Tüm $1 NFT'leriniz", "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" @@ -436,9 +544,6 @@ "message": "$1 ve $2", "description": "$1 is the first item, $2 is the second item. Used in Snap Install Warning modal." }, - "announcements": { - "message": "Duyurular" - }, "appDescription": { "message": "Tarayıcında bir Ethereum Cüzdanı", "description": "The description of the application" @@ -516,7 +621,7 @@ "message": "Varlık seçenekleri" }, "attemptSendingAssets": { - "message": "Varlıkları doğrudan bir ağdan diğerine göndermeye çalışırsanız bu durum kalıcı varlık kaybına neden olabilir. Bir köprü kullandığınızdan emin olun." + "message": "Varlıklarınızı doğrudan bir ağdan diğerine göndermeye çalışırsanız onları kaybedebilirsiniz. Fonları köprü kullanarak ağlar arasında güvenli bir şekilde transfer edin." }, "attemptSendingAssetsWithPortfolio": { "message": "Varlıkları doğrudan bir ağdan diğerine göndermeye çalışırsanız bu durum kalıcı varlık kaybına neden olabilir. $1 gibi bir köprü kullandığınızdan emin olun" @@ -524,6 +629,9 @@ "attemptToCancelSwapForFree": { "message": "Swap işlemini ücretsiz iptal etme girişimi" }, + "attributes": { + "message": "Özellikler" + }, "attributions": { "message": "Özellikler" }, @@ -594,6 +702,10 @@ "basicConfigurationBannerTitle": { "message": "Temel işlevsellik kapalı" }, + "basicConfigurationDescription": { + "message": "MetaMask, internet hizmetleri üzerinden token bilgileri ve gaz ayarları gibi temel özellikler sunar. İnternet hizmetlerini kullandığınızda IP adresiniz, bu durumda MetaMask ile, paylaşılır. Bu tıpkı herhangi bir web sitesini ziyaret ettiğinizde olduğu gibidir. MetaMask bu verileri geçici olarak kullanır ve verilerinizi hiçbir zaman satmaz. Bir VPN kullanabilir veya bu hizmetleri kapatabilirsiniz ancak bu durum MetaMask deneyiminizi etkileyebilir. Daha fazla bilgi için $1 bölümümüzü okuyun.", + "description": "$1 is to be replaced by the message for privacyMsg, and will link to https://consensys.io/privacy-policy" + }, "basicConfigurationLabel": { "message": "Temel işlevsellik" }, @@ -637,6 +749,29 @@ "betaWalletCreationSuccessReminder2": { "message": "MetaMask hiçbir zaman Gizli Kurtarma İfadenizi istemez." }, + "billionAbbreviation": { + "message": "MR", + "description": "Shortened form of 'billion'" + }, + "bitcoinActivityNotSupported": { + "message": "Bitcoin aktivitesi desteklenmiyor" + }, + "bitcoinSupportSectionTitle": { + "message": "Bitcoin" + }, + "bitcoinSupportToggleDescription": { + "message": "Bu özelliği açtığınızda mevcut Gizli Kurtarma İfadenizden türetilen MetaMask Uzantınıza bir Bitcoin Hesabı ekleme seçeneğiniz olacaktır. Bu, deneysel bir Beta özelliğidir, bu yüzden riski kendinize ait olarak kullanmalısınız. Bu yeni Bitcoin deneyimi hakkında bize geri bildirim göndermek için lütfen bu $1 bölümünü doldurun.", + "description": "$1 is the link to a product feedback form" + }, + "bitcoinSupportToggleTitle": { + "message": "\"Yeni bir Bitcoin hesabı ekle (Beta)\" seçeneğini etkinleştir" + }, + "bitcoinTestnetSupportToggleDescription": { + "message": "Bu özelliği açtığınızda test ağı için bir Bitcoin Hesabı ekleme seçeneğiniz olacak." + }, + "bitcoinTestnetSupportToggleTitle": { + "message": "\"Yeni bir Bitcoin hesabı ekle (Test Ağı)\" seçeneğini etkinleştir" + }, "blockExplorerAccountAction": { "message": "Hesap", "description": "This is used with viewOnEtherscan and viewInExplorer e.g View Account in Explorer" @@ -662,6 +797,9 @@ "blockaid": { "message": "Blockaid" }, + "blockaidAlertInfo": { + "message": "Bu taleple ilerlemenizi önermiyoruz." + }, "blockaidDescriptionApproveFarming": { "message": "Bu talebi onaylarsanız dolandırıcılıkla ünlü üçüncü bir taraf tüm varlıklarınızı çalabilir." }, @@ -669,7 +807,7 @@ "message": "Bu talebi onaylarsanız birisi Blur üzerinde yer alan varlıklarınızı çalabilir." }, "blockaidDescriptionErrored": { - "message": "Bu talep bir hatadan dolayı güvenlik sağlayıcısı tarafından doğrulanmadı. Dikkatli bir şekilde ilerleyin." + "message": "Bir hatadan dolayı güvenlik uyarılarını kontrol edemedik. Sadece ilgili her adrese güveniyorsanız ilerleyin." }, "blockaidDescriptionMaliciousDomain": { "message": "Kötü niyetli bir alanla etkileşimde bulunuyorsunuz. Bu talebi onaylarsanız varlıklarınızı kaybedebilirsiniz." @@ -683,6 +821,9 @@ "blockaidDescriptionTransferFarming": { "message": "Bu talebi onaylarsanız dolandırıcılıkla ünlü üçüncü bir taraf tüm varlıklarınızı çalar." }, + "blockaidDescriptionWarning": { + "message": "Bu aldatıcı bir talep olabilir. Sadece ilgili her adrese güveniyorsanız devam edin." + }, "blockaidMessage": { "message": "Gizlilik koruması - hiçbir veri üçüncü taraflarla paylaşılmaz. Arbitrum, Avalanche, BNB chain, Ethereum Ana Ağı, Linea, Optimism, Polygon, Base ve Sepolia için sunulur." }, @@ -690,7 +831,7 @@ "message": "Bu aldatıcı bir talep" }, "blockaidTitleMayNotBeSafe": { - "message": "Talep güvenli olmayabilir" + "message": "Dikkatli olun" }, "blockaidTitleSuspicious": { "message": "Bu şüpheli bir talep" @@ -698,6 +839,9 @@ "blockies": { "message": "Blockies" }, + "boughtFor": { + "message": "Satın alma amacı" + }, "bridge": { "message": "Köprü" }, @@ -782,6 +926,9 @@ "chromeRequiredForHardwareWallets": { "message": "Donanım Cüzdanınıza bağlamak için MetaMask'ı Google Chrome'da kullanmanız gerekir." }, + "circulatingSupply": { + "message": "Dolaşımdaki arz" + }, "clear": { "message": "Temizle" }, @@ -802,7 +949,7 @@ "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" }, "clickToManuallyAdd": { - "message": "Tokenleri manuel olarak eklemek için buaraya tıklayın." + "message": "Dilediğiniz zaman tokenleri manuel olarak ekleyebilirsiniz." }, "close": { "message": "Kapat" @@ -816,6 +963,9 @@ "coingecko": { "message": "CoinGecko" }, + "collectionName": { + "message": "Koleksiyon adı" + }, "comboNoOptions": { "message": "Hiçbir seçenek bulunamadı", "description": "Default text shown in the combo field dropdown if no options." @@ -841,6 +991,9 @@ "confirmAlertModalAcknowledgeMultiple": { "message": "Uyarıları kabul ediyorum ve yine de ilerlemek istiyorum" }, + "confirmAlertModalAcknowledgeSingle": { + "message": "Uyarıyı kabul ediyor ve yine de ilerlemek istiyorum" + }, "confirmAlertModalDetails": { "message": "Oturum açarsanız dolandırıcıklarla bilinen üçüncü bir taraf tüm varlıklarınızı ele geçirebilir. İlerlemeden önce lütfen uyarıları inceleyin." }, @@ -856,18 +1009,42 @@ "confirmConnectionTitle": { "message": "$1 ile bağlantıyı onayla" }, + "confirmDeletion": { + "message": "Silme işlemini onayla" + }, + "confirmFieldPaymaster": { + "message": "Ücreti ödeyen taraf" + }, + "confirmFieldTooltipPaymaster": { + "message": "Bu işlemin ücreti paymaster akıllı sözleşmesi tarafından ödenecektir." + }, "confirmPassword": { "message": "Şifreyi onayla" }, "confirmRecoveryPhrase": { "message": "Gizli Kurtarma İfadesini Onayla" }, + "confirmRpcUrlDeletionMessage": { + "message": "RPC URL adresini silmek istediğinizden emin misiniz? Bilgileriniz bu ağ için kaydedilmeyecektir." + }, "confirmTitleDescContractInteractionTransaction": { "message": "Bu işlemi sadece içeriği tam olarak anlıyorsanız ve talepte bulunan siteye güveniyorsanız onaylayın." }, + "confirmTitleDescPermitSignature": { + "message": "Bu site token'lerinizi harcamak için izin istiyor." + }, + "confirmTitleDescSIWESignature": { + "message": "Bir site bu hesabın sahibi olduğunuzu kanıtlamak için giriş yapmanızı istiyor." + }, "confirmTitleDescSignature": { "message": "Bu mesajı sadece içeriği onaylıyorsanız ve talepte bulunan siteye güveniyorsanız onaylayın." }, + "confirmTitlePermitSignature": { + "message": "Harcama üst limiti talebi" + }, + "confirmTitleSIWESignature": { + "message": "Giriş talebi" + }, "confirmTitleSignature": { "message": "İmza talebi" }, @@ -961,7 +1138,7 @@ "message": "Şununla bağlanıldı:" }, "connecting": { - "message": "Bağlanıyor..." + "message": "Bağlanıyor" }, "connectingTo": { "message": "Şuna bağlanılıyor: $1" @@ -1094,6 +1271,9 @@ "createSnapAccountTitle": { "message": "Hesap oluştur" }, + "creatorAddress": { + "message": "Oluşturucu adresi" + }, "crossChainSwapsLink": { "message": "MetaMask Portfolio ile ağlar arasında swap gerçekleştirin" }, @@ -1263,9 +1443,27 @@ "data": { "message": "Veri" }, + "dataCollectionForMarketing": { + "message": "Pazarlama amacıyla veri toplama" + }, + "dataCollectionForMarketingDescription": { + "message": "MetaMetrics'i, pazarlama iletişimlerimizle nasıl etkileşimde bulunduğunuzu öğrenmek için kullanacağız. İlgili haberleri (ürün özellikleri ve diğer materyaller gibi) paylaşabiliriz." + }, + "dataCollectionWarningPopoverButton": { + "message": "Tamam" + }, + "dataCollectionWarningPopoverDescription": { + "message": "Pazarlama amacıyla veri toplama seçeneğini kapattınız. Bu, sadece bu cihaz için geçerlidir. MetaMask'i başka cihazlarda kullanırsanız bu cihazlarda da vazgeçtiğinizden emin olun." + }, "dataHex": { "message": "On Altılı" }, + "dataUnavailable": { + "message": "veri mevcut değil" + }, + "dateCreated": { + "message": "Oluşturulma tarihi" + }, "dcent": { "message": "D'Cent" }, @@ -1295,6 +1493,9 @@ "decryptRequest": { "message": "Şifre çözme talebi" }, + "defaultRpcUrl": { + "message": "Varsayılan RPC URL adresi" + }, "delete": { "message": "Sil" }, @@ -1311,6 +1512,9 @@ "message": "$1 ağını sil?", "description": "$1 represents the name of the network" }, + "deleteRpcUrl": { + "message": "RPC URL'sini sil" + }, "deposit": { "message": "Para Yatır" }, @@ -1336,18 +1540,6 @@ "details": { "message": "Ayrıntılar" }, - "developerOptions": { - "message": "Geliştirici Seçenekleri" - }, - "developerOptionsResetStatesAnnouncementsDescription": { - "message": "Tüm duyurular için isShown boolean değerini false olarak sıfırlar. Duyurular, Yenilikler açılır penceresi modalında gösterilen bildirimlerdir." - }, - "developerOptionsResetStatesOnboarding": { - "message": "Katılım ile ilgili çeşitli durumları sıfırlar ve \"Cüzdanınızı Koruyun\" katılım sayfasına yeniden yönlendirir." - }, - "developerOptionsServiceWorkerKeepAlive": { - "message": "Zaman damgasının sürekli olarak oturum depolama alanına kaydedilmesine neden olur" - }, "disabledGasOptionToolTipMessage": { "message": "Orijinal gaz ücretinden minimum %10'luk bir artışı karşılamadığı için \"$1\" devre dışı bırakıldı.", "description": "$1 is gas estimate type which can be market or aggressive" @@ -1524,6 +1716,9 @@ "editGasTooLow": { "message": "Bilinmeyen işlem süresi" }, + "editNetworkLink": { + "message": "orijinal ağı düzenle" + }, "editNonceField": { "message": "Nonce'u düzenle" }, @@ -1555,6 +1750,9 @@ "enabled": { "message": "Etkinleştirildi" }, + "enabledNetworks": { + "message": "Etkinleştirilen ağlar" + }, "encryptionPublicKeyNotice": { "message": "$1 genel şifreleme anahtarınızı istiyor. Bunu onayladığınızda bu site sizin için şifrelenmiş mesajlar oluşturabilecektir.", "description": "$1 is the web3 site name" @@ -1659,6 +1857,9 @@ "estimatedFee": { "message": "Tahmini ücret" }, + "estimatedFeeTooltip": { + "message": "Ağda işlemi gerçekleştirmek için ödenen tutar." + }, "ethGasPriceFetchWarning": { "message": "Ana gaz tahmini hizmeti olarak sunulan yedek gaz fiyatı şu anda kullanılamıyor." }, @@ -1678,6 +1879,12 @@ "etherscanViewOn": { "message": "Etherscan'de görüntüle" }, + "existingChainId": { + "message": "Girdiğiniz bilgiler mevcut bir zincir kimliği ile ilişkilidir." + }, + "existingRpcUrl": { + "message": "Bu URL adresi başka bir zincir kimliği ile ilişkilidir." + }, "expandView": { "message": "Görünümü genişlet" }, @@ -1701,7 +1908,7 @@ "message": "Önerilen takma adlar" }, "externalNameSourcesSettingDescription": { - "message": "Etherscan, Infura ve Lens Protocol gibi üçüncü taraf kaynaklardan etkileşimde bulunduğunuz adresler için önerilen takma adları alırız. Bu kaynaklar o adresleri ve sizin IP adresinizi görebilir. Hesap adresiniz üçüncü taraflarla paylaşılmaz." + "message": "Etherscan, Infura ve Lens Protocol gibi üçüncü taraf kaynaklardan etkileşimde bulunduğunuz adeesler için önerilen takma adları alırız. Bu kaynaklar o adresleri ve sizin IP adresinizi görebilir. Hesap adresiniz üçüncü taraflarla paylaşılmaz." }, "failed": { "message": "Başarısız oldu" @@ -1732,6 +1939,9 @@ "message": "Dosya içe aktarma çalışmıyor mu? Buraya tıklayın!", "description": "Helps user import their account from a JSON file" }, + "findTheRightChainId": { + "message": "Doğrusunu şurada bulabilirsiniz:" + }, "flaskWelcomeUninstall": { "message": "bu uzantıyı kaldırmalısın", "description": "This request is shown on the Flask Welcome screen. It is intended for non-developers, and will be bolded." @@ -1771,6 +1981,9 @@ "forgotPassword": { "message": "Şifrenizi mi unuttunuz?" }, + "form": { + "message": "form" + }, "from": { "message": "Kimden" }, @@ -1901,7 +2114,7 @@ "message": "Goerli test ağı" }, "gotIt": { - "message": "Anladım!" + "message": "Anladım" }, "grantedToWithColon": { "message": "İzin verilen:" @@ -1979,6 +2192,12 @@ "highLowercase": { "message": "yüksek" }, + "highestCurrentBid": { + "message": "Mevcut en yüksek teklif" + }, + "highestFloorPrice": { + "message": "En yüksek taban fiyat" + }, "history": { "message": "Geçmiş" }, @@ -2318,12 +2537,21 @@ "knownTokenWarning": { "message": "Bu eylem kimlik avı için kullanılabilecek şekilde cüzdanınızda zaten listelenmiş olan tokenleri düzenleyecektir. Sadece bu tokenlerin neyi temsil ettiğini değiştirmek istediğinizden eminseniz onaylayın. $1 hakkında daha fazla bilgi edinin" }, + "l1Fee": { + "message": "L1 ücreti" + }, + "l1FeeTooltip": { + "message": "L1 gaz ücreti" + }, + "l2Fee": { + "message": "L2 ücreti" + }, + "l2FeeTooltip": { + "message": "L2 gaz ücreti" + }, "lastConnected": { "message": "Son bağlanma" }, - "lastPriceSold": { - "message": "Son satış fiyatı" - }, "lastSold": { "message": "Son satış" }, @@ -2495,6 +2723,12 @@ "message": "Hiç kimsenin bakmadığından emin olun", "description": "Warning to users to be care while creating and saving their new Secret Recovery Phrase" }, + "marketCap": { + "message": "Piyasa değeri" + }, + "marketDetails": { + "message": "Piyasa bilgileri" + }, "max": { "message": "Maksimum" }, @@ -2504,6 +2738,9 @@ "maxFee": { "message": "Maks. ücret" }, + "maxFeeTooltip": { + "message": "İşlemi ödemek için sunulan maksimum ücret." + }, "maxPriorityFee": { "message": "Maks. öncelik ücreti" }, @@ -2551,12 +2788,19 @@ "methodData": { "message": "Yöntem" }, + "methodDataTransactionDesc": { + "message": "Şifresi çözülmüş giriş verilerine göre gerçekleştirilen işlev." + }, "methodNotSupported": { "message": "Bu hesap ile desteklenmez." }, "metrics": { "message": "Metrikler" }, + "millionAbbreviation": { + "message": "MN", + "description": "Shortened form of 'million'" + }, "mismatchAccount": { "message": "Seçili hesap ($1) imza atmaya çalışan hesaptan ($2) farklı" }, @@ -2669,7 +2913,7 @@ "description": "Description below header used on Permission Connect screen for native permissions." }, "nativeToken": { - "message": "Bu ağdaki yerli token $1. Bu gaz ücretleri için kullanılan tokendir.", + "message": "Bu ağdaki yerli token $1. Bu, gaz ücretleri için kullanılan tokendir. ", "description": "$1 represents the name of the native token on the current network" }, "nativeTokenScamWarningConversion": { @@ -2738,6 +2982,9 @@ "networkNameBase": { "message": "Temel" }, + "networkNameBitcoin": { + "message": "Bitcoin" + }, "networkNameDefinition": { "message": "Bu ağ ile ilişkilendirilmiş ad." }, @@ -2762,6 +3009,9 @@ "networkNameZkSyncEra": { "message": "zkSync Era" }, + "networkOptions": { + "message": "Ağ seçenekleri" + }, "networkProvider": { "message": "Ağ sağlayıcısı" }, @@ -2830,6 +3080,9 @@ "newNetworkAdded": { "message": "\"$1\" başarılı bir şekilde eklendi!" }, + "newNetworkEdited": { + "message": "“$1” başarılı bir şekilde düzenlendi!" + }, "newNftAddedMessage": { "message": "NFT başarılı bir şekilde eklendi!" }, @@ -2866,6 +3119,9 @@ "nftAlreadyAdded": { "message": "NFT zaten eklenmiş." }, + "nftAutoDetectionEnabled": { + "message": "NFT otomatik algılama etkinleştirildi" + }, "nftDisclaimer": { "message": "Sorumluluğun Reddi: MetaMask medya dosyasını kaynak url adresinden çeker. Bu url adresi bazen NFT'nin mint edildiği pazar yeri tarafından değiştirilir." }, @@ -2916,6 +3172,9 @@ "noDomainResolution": { "message": "Alan adı için çözümleme sunulmamış." }, + "noHardwareWalletOrSnapsSupport": { + "message": "Geçerli tarayıcı sürümünüzle snap'ler ve çoğu donanım cüzdanı çalışmayacak." + }, "noNFTs": { "message": "Henüz NFT yok" }, @@ -2946,8 +3205,8 @@ "nonceField": { "message": "İşlem nonce'unu özelleştir" }, - "nonceFieldDescription": { - "message": "Onay ekranlarında nonce'u (işlem numarası) değiştirmek için bunu açın. Bu gelişmiş bir özelliktir, dikkatli kullanın." + "nonceFieldDesc": { + "message": "Varlık gönderirken nonce'u (işlem numarasını) değiştirmek için bunu açın. Bu gelişmiş bir özelliktir, dikkatli kullanın." }, "nonceFieldHeading": { "message": "Özel nonce" @@ -3149,6 +3408,9 @@ "numberOfNewTokensDetectedSingular": { "message": "Bu hesapta 1 yeni token bulundu" }, + "numberOfTokens": { + "message": "Token sayısı" + }, "ofTextNofM": { "message": "/" }, @@ -3164,8 +3426,36 @@ "on": { "message": "Açık" }, - "onboarding": { - "message": "Katılım" + "onboardedMetametricsAccept": { + "message": "Kabul ediyorum" + }, + "onboardedMetametricsDisagree": { + "message": "Hayır, istemiyorum" + }, + "onboardedMetametricsKey1": { + "message": "En yeni gelişmeler" + }, + "onboardedMetametricsKey2": { + "message": "Ürün özellikleri" + }, + "onboardedMetametricsKey3": { + "message": "İlgili diğer promosyon materyalleri" + }, + "onboardedMetametricsLink": { + "message": "MetaMetrics" + }, + "onboardedMetametricsParagraph1": { + "message": "$1 bağlantısına ek olarak pazarlama iletişimleri ile nasıl etkileşimde bulunduğunuzu anlamak amacıyla verileri kullanmak istiyoruz.", + "description": "$1 represents the 'onboardedMetametricsLink' locale string" + }, + "onboardedMetametricsParagraph2": { + "message": "Bu, sizinle neleri paylaşacağımızı kişiselleştirebilmemize yardımcı olur, örneğin:" + }, + "onboardedMetametricsParagraph3": { + "message": "Unutmayın, sunduğunuz verileri hiçbir zaman satmayız ve dilediğiniz zaman tercihinizi değiştirebilirsiniz." + }, + "onboardedMetametricsTitle": { + "message": "Deneyiminizi iyileştirmemize yardımcı olun" }, "onboardingAdvancedPrivacyIPFSDescription": { "message": "IPFS ağ geçidi üçüncü tarafların barındırdığı veriler için erişim ve görüntülemeyi mümkün kılar. Özel bir IPFS ağ geçidi ekleyebilir veya varsayılanı kullanmaya devam edebilirsiniz." @@ -3203,9 +3493,6 @@ "onboardingMetametricsDescription2": { "message": "Ölçümleri toplarken bu her zaman aşağıdaki gibi olacaktır..." }, - "onboardingMetametricsDisagree": { - "message": "Hayır, istemiyorum" - }, "onboardingMetametricsInfuraTerms": { "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`" @@ -3240,6 +3527,9 @@ "onboardingMetametricsTitle": { "message": "MetaMask'i iyileştirmemize yardımcı olun" }, + "onboardingMetametricsUseDataCheckbox": { + "message": "Bu verileri, pazarlama iletişimlerimizle nasıl etkileşimde bulunduğunuzu öğrenmek için kullanacağız. İlgili haberleri (ürün özellikleri gibi) paylaşabiliriz." + }, "onboardingPinExtensionBillboardAccess": { "message": "Tam erişim" }, @@ -3283,6 +3573,22 @@ "message": "Kimlik avı tespiti uyarıları $1 ile iletişime bağlıdır. jsDeliver IP adresinize erişim sağlayacaktır. Şunu görüntüleyin: $2.", "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" }, + "oneDayAbbreviation": { + "message": "1G", + "description": "Shortened form of '1 day'" + }, + "oneMonthAbbreviation": { + "message": "1A", + "description": "Shortened form of '1 month'" + }, + "oneWeekAbbreviation": { + "message": "1H", + "description": "Shortened form of '1 week'" + }, + "oneYearAbbreviation": { + "message": "1Y", + "description": "Shortened form of '1 year'" + }, "onekey": { "message": "OneKey" }, @@ -3635,6 +3941,9 @@ "permissionsPageTourTitle": { "message": "Bağlı siteler şimdi izinler oldu" }, + "permitSimulationDetailInfo": { + "message": "Harcama yapan tarafa hesabınızdan bu kadar çok token'i harcama izni veriyorsunuz." + }, "personalAddressDetected": { "message": "Kişisel adres algılandı. Token sözleşme adresini girin." }, @@ -3667,6 +3976,10 @@ "popularCustomNetworks": { "message": "Popüler özel ağlar" }, + "popularNetworkAddToolTip": { + "message": "Bu ağların bazıları üçüncü taraflara dayalıdır. Bağlantılar daha az güvenilir olabilir veya üçüncü tarafların aktiviteleri takip etmesine olanak sağlayabilir. $1", + "description": "$1 is Learn more link" + }, "portfolio": { "message": "Portföy" }, @@ -3679,6 +3992,12 @@ "prev": { "message": "Önceki" }, + "price": { + "message": "Fiyat" + }, + "priceUnavailable": { + "message": "fiyat mevcut değil" + }, "primaryCurrencySetting": { "message": "Öncelikli para birimi" }, @@ -3831,6 +4150,9 @@ "quoteRate": { "message": "Kota oranı" }, + "rank": { + "message": "Sıralama" + }, "reAddAccounts": { "message": "diğer hesapları yeniden ekle" }, @@ -4000,9 +4322,6 @@ "reset": { "message": "Sıfırla" }, - "resetStates": { - "message": "Durumları Sıfırla" - }, "resetWallet": { "message": "Cüzdanı sıfırla" }, @@ -4138,6 +4457,9 @@ "searchAccounts": { "message": "Hesapları ara" }, + "searchNfts": { + "message": "NFT ara" + }, "searchTokens": { "message": "Token ara" }, @@ -4182,13 +4504,10 @@ "seedPhraseIntroRecommendedButtonCopy": { "message": "Cüzdanımı koru (önerilir)" }, - "seedPhraseIntroSidebarBulletFour": { - "message": "Birden fazla gizli yerde not ederek saklayın." - }, "seedPhraseIntroSidebarBulletOne": { - "message": "Bir şifre yöneticisine kaydedin" + "message": "Birden fazla gizli yerde not ederek saklayın." }, - "seedPhraseIntroSidebarBulletThree": { + "seedPhraseIntroSidebarBulletTwo": { "message": "Bir kasada saklayın." }, "seedPhraseIntroSidebarCopyOne": { @@ -4258,10 +4577,10 @@ "message": "Token seç" }, "selectNFTPrivacyPreference": { - "message": "Ayarlarda NFT algılamayı açın" + "message": "NFT Otomatik Algılamayı etkinleştir" }, "selectPathHelp": { - "message": "Beklediğiniz hesapları görmüyorsanız HD yoluna geçmeyi deneyin." + "message": "Beklediğiniz hesapları görmüyorsanız HD yoluna veya geçerli seçilen ağa geçmeyi deneyin." }, "selectType": { "message": "Tür Seç" @@ -4272,9 +4591,6 @@ "send": { "message": "Gönder" }, - "sendAToken": { - "message": "Bir token gönder" - }, "sendBugReport": { "message": "Bize bir hata raporu gönder." }, @@ -4325,9 +4641,6 @@ "sepolia": { "message": "Sepolia test ağı" }, - "serviceWorkerKeepAlive": { - "message": "Service Worker'ı Canlı Tut" - }, "setAdvancedPrivacySettingsDetails": { "message": "MetaMask, ürünün kullanılabilirliğini ve güvenliğini iyileştirmek amacıyla bu güvenilir üçüncü taraf hizmetlerini kullanır." }, @@ -4384,6 +4697,9 @@ "showIncomingTransactionsExplainer": { "message": "Ethereum adresinizi ve IP adresinizi açığa çıkaran her ağ için farklı üçüncü taraf API'lerine güvenir." }, + "showLess": { + "message": "Daha az göster" + }, "showMore": { "message": "Daha fazlasını göster" }, @@ -4414,9 +4730,6 @@ "signatureRequestGuidance": { "message": "Bumesajı sadece içeriği tam olarak anlıyorsanız ve talepte bulunan siteye güveniyorsanız imzalayın." }, - "signatureRequestWarning": { - "message": "Bu mesajın imzalanması tehlikeli olabilir. Hesabınızın ve varlıklarınızın tüm kontrolünü bu mesajın diğer ucundaki tarafa veriyor olabilirsiniz. Başka bir deyişle istedikleri zaman hesabınızı boşaltabilirler. Dikkatle ilerleyin. $1." - }, "signed": { "message": "İmzalandı" }, @@ -4426,6 +4739,9 @@ "signing": { "message": "İmzalanıyor" }, + "signingInWith": { + "message": "Şununla giriş yap:" + }, "simulationDetailsFailed": { "message": "Tahmininiz yüklenirken bir hata oldu." }, @@ -4463,6 +4779,24 @@ "simulationsSettingSubHeader": { "message": "Bakiye değişikliklerini tahmin edin" }, + "siweIssued": { + "message": "Düzenlendi" + }, + "siweNetwork": { + "message": "Ağ" + }, + "siweRequestId": { + "message": "Kimlik Talebi" + }, + "siweResources": { + "message": "Kaynaklar" + }, + "siweSignatureSimulationDetailInfo": { + "message": "Bir siteye giriş yapıyorsunuz ve hesabınızda öngörülen herhangi bir değişiklik yok." + }, + "siweURI": { + "message": "URL adresi" + }, "skip": { "message": "Atla" }, @@ -4566,6 +4900,14 @@ "snapAccountsDescription": { "message": "Üçüncü taraf Snapleri tarafından kontrol edilen hesaplar." }, + "snapConnectTo": { + "message": "$1 adresine bağlan", + "description": "$1 is the website URL or a Snap name. Used for Snaps pre-approved connections." + }, + "snapConnectionPermissionDescription": { + "message": "Onayınız olmadan $1 otomatik olarak $2 adresine bağlansın.", + "description": "Used for Snap pre-approved connections. $1 is the Snap name, $2 is a website URL." + }, "snapConnectionWarning": { "message": "$1 şunu kullanmak istiyor: $2", "description": "$2 is the snap and $1 is the dapp requesting connection to the snap." @@ -4577,6 +4919,9 @@ "snapDetailWebsite": { "message": "Web Sitesi" }, + "snapHomeMenu": { + "message": "Snap Ana Menüsü" + }, "snapInstallRequest": { "message": "$1 adlı snap'i yüklemek ona aşağıdaki izinleri verir.", "description": "$1 is the snap name." @@ -4699,6 +5044,9 @@ "source": { "message": "Kaynak" }, + "speed": { + "message": "Hız" + }, "speedUp": { "message": "Hızlandır" }, @@ -4733,6 +5081,9 @@ "spendLimitTooLarge": { "message": "Harcama limiti çok büyük" }, + "spender": { + "message": "Harcama Yapan Taraf" + }, "spendingCap": { "message": "Harcama üst limiti" }, @@ -4853,9 +5204,6 @@ "stateLogsDescription": { "message": "Durum günlükleri genel hesap adreslerinizi ve gönderilen işlemleri içerir." }, - "states": { - "message": "Durumlar" - }, "status": { "message": "Durum" }, @@ -4960,6 +5308,13 @@ "submitted": { "message": "Gönderildi" }, + "suggestedBySnap": { + "message": "$1 tarafından öneriliyor", + "description": "$1 is the snap name" + }, + "suggestedTokenName": { + "message": "Önerilen isim:" + }, "suggestedTokenSymbol": { "message": "Önerilen ticker sembolü:" }, @@ -5074,7 +5429,7 @@ "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." }, "swapFetchingQuotes": { - "message": "Teklifler alınıyor" + "message": "Teklifler alınıyor..." }, "swapFetchingQuotesErrorDescription": { "message": "Hımmm... bir hata oluştu. Tekrar deneyin veya sorun devam ederse müşteri hizmetleri destek bölümüyle iletişime geçin." @@ -5422,7 +5777,7 @@ "description": "$1 represents the account name, $2 represents the network name" }, "switchedTo": { - "message": "Şuna geçiş yaptınız:" + "message": "Şu anda kullandığınız ağ" }, "switchingNetworksCancelsPendingConfirmations": { "message": "Ağ değiştirmek bekleyen tüm onayları iptal eder" @@ -5470,6 +5825,10 @@ "thisCollection": { "message": "bu koleksiyon" }, + "threeMonthsAbbreviation": { + "message": "3A", + "description": "Shortened form of '3 months'" + }, "time": { "message": "Zaman" }, @@ -5483,45 +5842,6 @@ "message": "Alıcı: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleEthSignBannerDescription": { - "message": "Kimlik avı saldırıları bakımından risk altındasınız. eth_sign özelliğini kapatarak kendinizi koruyun." - }, - "toggleEthSignDescriptionField": { - "message": "Bu ayarı etkinleştirirseniz okunabilir olmayan imza talepleri alabilirsiniz. Anlamadığınız bir mesajı imzaladığınızda paranızı ve NFT'lerinizi vermeyi kabul ediyor olabilirsiniz." - }, - "toggleEthSignField": { - "message": "Eth_sign talepleri" - }, - "toggleEthSignModalBannerBoldText": { - "message": " dolandırılıyor olabilirsiniz" - }, - "toggleEthSignModalBannerText": { - "message": "Bu ayarı açmanız istendi ise" - }, - "toggleEthSignModalCheckBox": { - "message": "eth_sign taleplerini etkinleştirirsem tüm paramı ve NFT'lerimi kaybedebileceğimi anlıyorum. " - }, - "toggleEthSignModalDescription": { - "message": "eth_sign taleplerine izin vermeniz sizi kimlik avı saldırılarına karşı hassas hale getirebilir. Her zaman URL adresini inceleyin ve kod içeren mesajları imzalarken dikkat edin." - }, - "toggleEthSignModalFormError": { - "message": "Metin yanlış" - }, - "toggleEthSignModalFormLabel": { - "message": "Devam etmek için \"Sadece anladığım şeyleri imzalıyorum\" girin" - }, - "toggleEthSignModalFormValidation": { - "message": "Sadece anladığım şeyleri imzalıyorum" - }, - "toggleEthSignModalTitle": { - "message": "Kullanım riski size aittir" - }, - "toggleEthSignOff": { - "message": "Kapalı (Önerilir)" - }, - "toggleEthSignOn": { - "message": "Açık (Önerilmez)" - }, "toggleRequestQueueDescription": { "message": "Bu, tüm siteler için tek bir seçili ağ yerine her bir site için bir ağ seçebilmenize olanak sağlar. Bu özellik, manuel olarak ağ değiştirmenizi önleyebilir ve bu da belirli sitelerde kullanıcı deneyiminizi bozabilir." }, @@ -5549,6 +5869,9 @@ "tokenContractAddress": { "message": "Token sözleşme adresi" }, + "tokenDecimal": { + "message": "Token ondalığı" + }, "tokenDecimalFetchFailed": { "message": "Token ondalığı gereklidir. Şurada bulabilirsiniz: $1" }, @@ -5571,7 +5894,10 @@ "message": "token dolandırıcılıkları ve güvenlik riskleri" }, "tokenShowUp": { - "message": "Tokenleriniz cüzdanınızda otomatik olarak görünmeyebilir." + "message": "Tokenleriniz cüzdanınızda otomatik olarak görünmeyebilir. " + }, + "tokenStandard": { + "message": "Token standardı" }, "tokenSymbol": { "message": "Token sembolü" @@ -5583,6 +5909,9 @@ "message": "$1 yeni token bulundu", "description": "$1 is the number of new tokens detected" }, + "tokensInCollection": { + "message": "Koleksiyondaki tokenler" + }, "tooltipApproveButton": { "message": "Anladım" }, @@ -5598,6 +5927,9 @@ "total": { "message": "Toplam" }, + "totalVolume": { + "message": "Toplam hacim" + }, "transaction": { "message": "işlem" }, @@ -5613,6 +5945,9 @@ "transactionCreated": { "message": "İşlem $2 itibariyle $1 değeriyle oluşturuldu." }, + "transactionDataFunction": { + "message": "İşlev" + }, "transactionDetailDappGasMoreInfo": { "message": "Site önerisi" }, @@ -5703,6 +6038,10 @@ "transferFrom": { "message": "Transfer kaynağı:" }, + "trillionAbbreviation": { + "message": "T", + "description": "Shortened form of 'trillion'" + }, "troubleConnectingToLedgerU2FOnFirefox": { "message": "Ledger'ınıza bağlanırken sorun yaşıyoruz. $1", "description": "$1 is a link to the wallet connection guide;" @@ -5781,6 +6120,9 @@ "message": "U2F", "description": "A name on an API for the browser to interact with devices that support the U2F protocol. On some browsers we use it to connect MetaMask to Ledger devices." }, + "unMatchedChain": { + "message": "Kayıtlarımıza göre bu URL adresi bu zincir kimliğinin bilinen bir sağlayıcısı ile uyumlu değil." + }, "unapproved": { "message": "Onaylanmadı" }, @@ -5832,12 +6174,21 @@ "update": { "message": "Güncelle" }, + "updateOrEditNetworkInformations": { + "message": "Bilgilerinizi güncelleyin veya" + }, "updateRequest": { "message": "Talebi güncelle" }, "updatedWithDate": { "message": "$1 güncellendi" }, + "uploadDropFile": { + "message": "Dosyanızı buraya sürükleyin" + }, + "uploadFile": { + "message": "Dosya yükle" + }, "urlErrorMsg": { "message": "URL adresleri için uygun HTTP/HTTPS ön eki gerekir." }, @@ -6053,6 +6404,12 @@ "whatsThis": { "message": "Bu nedir?" }, + "wrongChainId": { + "message": "Bu zincir kimliği ağ adı ile uyumlu değil." + }, + "wrongNetworkName": { + "message": "Kayıtlarımıza göre ağ adı bu zincir kimliği ile doğru şekilde eşleşmiyor olabilir." + }, "xOfYPending": { "message": "$1 / $2 bekliyor", "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" @@ -6076,8 +6433,11 @@ "yourAccounts": { "message": "Hesaplarınız" }, - "yourFundsMayBeAtRisk": { - "message": "Paranız tehlikede olabilir" + "yourActivity": { + "message": "Aktiviteniz" + }, + "yourBalance": { + "message": "Bakiyeniz" }, "yourNFTmayBeAtRisk": { "message": "NFT'niz tehlikede olabilir" diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index b481906efda9..bc956cf2e936 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -42,7 +42,7 @@ "message": "Kết nối với ví cứng QR của bạn" }, "QRHardwareWalletSteps2Description": { - "message": "Ngrave (sắp ra mắt)" + "message": "Ngrave Zero" }, "SIWEAddressInvalid": { "message": "Địa chỉ trong yêu cầu đăng nhập không trùng khớp với địa chỉ của tài khoản bạn đang sử dụng để đăng nhập." @@ -162,6 +162,9 @@ "accountSelectionRequired": { "message": "Bạn cần chọn một tài khoản!" }, + "accountTypeNotSupported": { + "message": "Loại tài khoản không được hỗ trợ" + }, "accounts": { "message": "Tài khoản" }, @@ -277,6 +280,12 @@ "addNewAccount": { "message": "Thêm tài khoản Ethereum mới" }, + "addNewBitcoinAccount": { + "message": "Thêm tài khoản Bitcoin mới (Beta)" + }, + "addNewBitcoinTestnetAccount": { + "message": "Thêm tài khoản Bitcoin mới (Mạng thử nghiệm)" + }, "addNewToken": { "message": "Thêm token mới" }, @@ -286,6 +295,9 @@ "addNfts": { "message": "Thêm NFT" }, + "addRpcUrl": { + "message": "Thêm URL RPC" + }, "addSnapAccountToggle": { "message": "Bật \"Thêm tài khoản Snap (Beta)\"" }, @@ -305,12 +317,21 @@ "message": "Bạn không tìm thấy token? Bạn có thể dán địa chỉ của bất kỳ token nào để thêm token đó theo cách thủ công. Bạn có thể tìm thấy địa chỉ hợp đồng token trên $1", "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" }, + "addUrl": { + "message": "Thêm URL" + }, "addingCustomNetwork": { "message": "Thêm mạng" }, "addingTokens": { "message": "Đang thêm token" }, + "additionalNetworks": { + "message": "Mạng bổ sung" + }, + "additionalRpcUrl": { + "message": "URL RPC Bổ sung" + }, "address": { "message": "Địa chỉ" }, @@ -326,6 +347,18 @@ "advancedConfiguration": { "message": "Cấu hình nâng cao" }, + "advancedDetailsDataDesc": { + "message": "Dữ liệu" + }, + "advancedDetailsHexDesc": { + "message": "Thập lục phân" + }, + "advancedDetailsNonceDesc": { + "message": "Số nonce" + }, + "advancedDetailsNonceTooltip": { + "message": "Đây là số giao dịch của một tài khoản. Số nonce cho giao dịch đầu tiên là 0 và tăng dần theo thứ tự." + }, "advancedGasFeeDefaultOptIn": { "message": "Lưu các giá trị này làm giá trị mặc định cho mạng $1.", "description": "$1 is the current network name." @@ -349,6 +382,18 @@ "alert": { "message": "Cảnh báo" }, + "alertActionBuy": { + "message": "Mua ETH" + }, + "alertActionUpdateGas": { + "message": "Cập nhập hạn mức phí gas" + }, + "alertActionUpdateGasFee": { + "message": "Cập nhật phí" + }, + "alertActionUpdateGasFeeLevel": { + "message": "Cập nhật tùy chọn phí gas" + }, "alertBannerMultipleAlertsDescription": { "message": "Nếu bạn chấp thuận yêu cầu này, một bên thứ ba nổi tiếng là lừa đảo có thể lấy hết tài sản của bạn." }, @@ -358,6 +403,36 @@ "alertDisableTooltip": { "message": "Bạn có thể thay đổi trong phần \"Cài đặt > Cảnh báo\"" }, + "alertMessageGasEstimateFailed": { + "message": "Chúng tôi không thể cung cấp phí chính xác và ước tính này có thể cao. Chúng tôi khuyên bạn nên nhập hạn mức phí gas tùy chỉnh, nhưng vẫn có rủi ro giao dịch sẽ thất bại." + }, + "alertMessageGasFeeLow": { + "message": "Khi chọn phí thấp, hãy lưu ý giao dịch sẽ chậm hơn và thời gian chờ đợi lâu hơn. Để giao dịch nhanh hơn, hãy chọn các tùy chọn phí Thị trường hoặc Cao." + }, + "alertMessageGasTooLow": { + "message": "Để tiếp tục giao dịch này, bạn cần tăng giới hạn phí gas lên 21000 hoặc cao hơn." + }, + "alertMessageInsufficientBalance": { + "message": "Bạn không có đủ ETH trong tài khoản để thanh toán phí giao dịch." + }, + "alertMessageNetworkBusy": { + "message": "Phí gas cao và ước tính kém chính xác hơn." + }, + "alertMessageNoGasPrice": { + "message": "Chúng tôi không thể tiếp tục giao dịch này cho đến khi bạn cập nhật phí thủ công." + }, + "alertMessagePendingTransactions": { + "message": "Giao dịch này sẽ không được thực hiện cho đến khi giao dịch trước đó hoàn tất. Tìm hiểu cách hủy hoặc đẩy nhanh giao dịch." + }, + "alertMessageSignInDomainMismatch": { + "message": "Trang web đưa ra yêu cầu không phải là trang web bạn đang đăng nhập. Đây có thể là một nỗ lực đánh cắp thông tin đăng nhập của bạn." + }, + "alertMessageSignInWrongAccount": { + "message": "Trang web này yêu cầu bạn đăng nhập bằng tài khoản không đúng." + }, + "alertMessageSigningOrSubmitting": { + "message": "Giao dịch này sẽ chỉ được thực hiện sau khi giao dịch trước đó của bạn hoàn tất." + }, "alertModalAcknowledge": { "message": "Tôi đã nhận thức được rủi ro và vẫn muốn tiếp tục" }, @@ -367,6 +442,33 @@ "alertModalReviewAllAlerts": { "message": "Xem lại tất cả cảnh báo" }, + "alertReasonGasEstimateFailed": { + "message": "Phí không chính xác" + }, + "alertReasonGasFeeLow": { + "message": "Tốc độ chậm" + }, + "alertReasonGasTooLow": { + "message": "Hạn mức phí gas thấp" + }, + "alertReasonInsufficientBalance": { + "message": "Không đủ tiền" + }, + "alertReasonNetworkBusy": { + "message": "Mạng đang bận" + }, + "alertReasonNoGasPrice": { + "message": "Ước tính phí không có sẵn" + }, + "alertReasonPendingTransactions": { + "message": "Giao dịch đang chờ xử lý" + }, + "alertReasonSignIn": { + "message": "Yêu cầu đăng nhập đáng ngờ" + }, + "alertReasonWrongAccount": { + "message": "Tài khoản không đúng" + }, "alertSettingsUnconnectedAccount": { "message": "Đang duyệt trang web khi chọn một tài khoản không được kết nối" }, @@ -398,6 +500,12 @@ "allPermissions": { "message": "Tất cả các quyền" }, + "allTimeHigh": { + "message": "Cao nhất lịch sử" + }, + "allTimeLow": { + "message": "Thấp nhất lịch sử" + }, "allYourNFTsOf": { "message": "Tất cả NFT của bạn từ $1", "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" @@ -436,9 +544,6 @@ "message": "$1 và $2", "description": "$1 is the first item, $2 is the second item. Used in Snap Install Warning modal." }, - "announcements": { - "message": "Thông báo" - }, "appDescription": { "message": "Ví Ethereum trên trình duyệt của bạn", "description": "The description of the application" @@ -516,7 +621,7 @@ "message": "Tùy chọn tài sản" }, "attemptSendingAssets": { - "message": "Nếu bạn cố gắng gửi tài sản trực tiếp từ mạng này sang mạng khác, bạn có thể bị mất tài sản vĩnh viễn. Hãy nhớ sử dụng cầu nối." + "message": "Bạn có thể bị mất tài sản nếu cố gắng gửi tài sản từ một mạng khác. Chuyển tiền an toàn giữa các mạng bằng cách sử dụng cầu nối." }, "attemptSendingAssetsWithPortfolio": { "message": "Bạn có thể bị mất tài sản nếu cố gắng gửi tài sản từ một mạng khác. Chuyển tiền an toàn giữa các mạng bằng cách sử dụng cầu nối, chẳng hạn như $1" @@ -524,9 +629,15 @@ "attemptToCancelSwapForFree": { "message": "Cố gắng hủy hoán đổi miễn phí" }, + "attributes": { + "message": "Thuộc tính" + }, "attributions": { "message": "Ghi nhận đóng góp" }, + "auroraRpcDeprecationMessage": { + "message": "URL Infura RPC không còn hỗ trợ Aurora nữa." + }, "authorizedPermissions": { "message": "Bạn đã cấp các quyền sau đây" }, @@ -591,6 +702,10 @@ "basicConfigurationBannerTitle": { "message": "Chức năng cơ bản đã tắt" }, + "basicConfigurationDescription": { + "message": "MetaMask cung cấp các tính năng cơ bản như chi tiết token và cài đặt gas thông qua các dịch vụ Internet. Khi bạn sử dụng các dịch vụ Internet, địa chỉ IP của bạn sẽ được chia sẻ, trong trường hợp này là với MetaMask. Điều này giống như khi bạn truy cập bất kỳ trang web nào. MetaMask sử dụng dữ liệu này tạm thời và không bao giờ bán dữ liệu của bạn. Bạn có thể sử dụng VPN hoặc tắt các dịch vụ này, nhưng nó có thể ảnh hưởng đến trải nghiệm sử dụng MetaMask của bạn. Để tìm hiểu thêm, vui lòng đọc $1.", + "description": "$1 is to be replaced by the message for privacyMsg, and will link to https://consensys.io/privacy-policy" + }, "basicConfigurationLabel": { "message": "Chức năng cơ bản" }, @@ -634,6 +749,29 @@ "betaWalletCreationSuccessReminder2": { "message": "MetaMask Beta sẽ không bao giờ hỏi về Cụm từ khôi phục bí mật của bạn." }, + "billionAbbreviation": { + "message": "Tỷ", + "description": "Shortened form of 'billion'" + }, + "bitcoinActivityNotSupported": { + "message": "Hoạt động Bitcoin không được hỗ trợ" + }, + "bitcoinSupportSectionTitle": { + "message": "Bitcoin" + }, + "bitcoinSupportToggleDescription": { + "message": "Bật tính năng này sẽ cung cấp cho bạn tùy chọn thêm Tài khoản Bitcoin vào Tiện ích mở rộng MetaMask bắt nguồn từ Cụm từ khôi phục bí mật hiện có của bạn. Đây là một tính năng Beta thử nghiệm, nên bạn phải tự chịu rủi ro khi sử dụng nó. Để cung cấp phản hồi cho chúng tôi về trải nghiệm Bitcoin mới này, vui lòng điền vào $1 này.", + "description": "$1 is the link to a product feedback form" + }, + "bitcoinSupportToggleTitle": { + "message": "Bật \"Thêm tài khoản Bitcoin mới (Beta)\"" + }, + "bitcoinTestnetSupportToggleDescription": { + "message": "Bật tính năng này sẽ cung cấp cho bạn tùy chọn thêm Tài khoản Bitcoin cho mạng thử nghiệm." + }, + "bitcoinTestnetSupportToggleTitle": { + "message": "Bật \"Thêm tài khoản Bitcoin mới (Mạng thử nghiệm)\"" + }, "blockExplorerAccountAction": { "message": "Tài khoản", "description": "This is used with viewOnEtherscan and viewInExplorer e.g View Account in Explorer" @@ -659,6 +797,9 @@ "blockaid": { "message": "Blockaid" }, + "blockaidAlertInfo": { + "message": "Chúng tôi khuyên bạn không nên tiếp tục với yêu cầu này." + }, "blockaidDescriptionApproveFarming": { "message": "Nếu bạn chấp thuận yêu cầu này, một bên thứ ba nổi tiếng là lừa đảo có thể lấy hết tài sản của bạn." }, @@ -666,7 +807,7 @@ "message": "Nếu bạn chấp thuận yêu cầu này, người khác có thể đánh cắp tài sản được niêm yết trên Blur của bạn." }, "blockaidDescriptionErrored": { - "message": "Do có lỗi, yêu cầu này đã không được nhà cung cấp dịch vụ bảo mật xác minh. Hãy thực hiện cẩn thận." + "message": "Do có lỗi, chúng tôi không thể kiểm tra các cảnh báo bảo mật. Chỉ tiếp tục nếu bạn tin tưởng tất cả các địa chỉ liên quan." }, "blockaidDescriptionMaliciousDomain": { "message": "Bạn đang tương tác với một tên miền độc hại. Nếu bạn chấp thuận yêu cầu này, bạn có thể mất tài sản của mình." @@ -680,6 +821,9 @@ "blockaidDescriptionTransferFarming": { "message": "Nếu bạn chấp thuận yêu cầu này, một bên thứ ba nổi tiếng là lừa đảo sẽ lấy hết tài sản của bạn." }, + "blockaidDescriptionWarning": { + "message": "Đây có thể là một yêu cầu lừa đảo. Chỉ tiếp tục nếu bạn tin tưởng mọi địa chỉ liên quan." + }, "blockaidMessage": { "message": "Bảo vệ quyền riêng tư - không có dữ liệu nào được chia sẻ với các bên thứ ba. Có sẵn trên Arbitrum, Avalanche, BNB chain, Ethereum Mainnet, Linea, Optimism, Polygon, Base và Sepolia." }, @@ -687,7 +831,7 @@ "message": "Đây là một yêu cầu lừa đảo" }, "blockaidTitleMayNotBeSafe": { - "message": "Yêu cầu có thể không an toàn" + "message": "Hãy cẩn thận" }, "blockaidTitleSuspicious": { "message": "Đây là một yêu cầu đáng ngờ" @@ -695,6 +839,9 @@ "blockies": { "message": "Blockies" }, + "boughtFor": { + "message": "Đã mua với giá" + }, "bridge": { "message": "Cầu nối" }, @@ -779,6 +926,9 @@ "chromeRequiredForHardwareWallets": { "message": "Bạn cần sử dụng MetaMask trên Google Chrome để kết nối với Ví cứng của bạn." }, + "circulatingSupply": { + "message": "Cung lưu hành" + }, "clear": { "message": "Xóa" }, @@ -799,7 +949,7 @@ "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" }, "clickToManuallyAdd": { - "message": "Nhấp vào đây để thêm token theo cách thủ công." + "message": "Bạn luôn có thể thêm token theo cách thủ công." }, "close": { "message": "Đóng" @@ -813,6 +963,9 @@ "coingecko": { "message": "CoinGecko" }, + "collectionName": { + "message": "Tên bộ sưu tập" + }, "comboNoOptions": { "message": "Không tìm thấy tùy chọn nào", "description": "Default text shown in the combo field dropdown if no options." @@ -838,6 +991,9 @@ "confirmAlertModalAcknowledgeMultiple": { "message": "Tôi đã hiểu rõ các cảnh báo và vẫn muốn tiếp tục" }, + "confirmAlertModalAcknowledgeSingle": { + "message": "Tôi đã hiểu rõ cảnh báo và vẫn muốn tiếp tục" + }, "confirmAlertModalDetails": { "message": "Nếu bạn đăng nhập, một bên thứ ba nổi tiếng là lừa đảo có thể lấy tất cả tài sản của bạn. Vui lòng xem lại các cảnh báo trước khi tiếp tục." }, @@ -853,18 +1009,42 @@ "confirmConnectionTitle": { "message": "Xác nhận kết nối với $1" }, + "confirmDeletion": { + "message": "Xác nhận xóa" + }, + "confirmFieldPaymaster": { + "message": "Phí được thanh toán bởi" + }, + "confirmFieldTooltipPaymaster": { + "message": "Phí cho giao dịch này sẽ được thanh toán bởi hợp đồng thông minh Paymaster." + }, "confirmPassword": { "message": "Xác nhận mật khẩu" }, "confirmRecoveryPhrase": { "message": "Xác nhận Cụm từ khôi phục bí mật" }, + "confirmRpcUrlDeletionMessage": { + "message": "Bạn có chắc chắn muốn xóa URL RPC? Thông tin của bạn sẽ không được lưu cho mạng này." + }, "confirmTitleDescContractInteractionTransaction": { "message": "Chỉ xác nhận giao dịch này nếu bạn hoàn toàn hiểu nội dung và tin tưởng trang web yêu cầu." }, + "confirmTitleDescPermitSignature": { + "message": "Trang web này muốn được cấp quyền để chi tiêu số token của bạn." + }, + "confirmTitleDescSIWESignature": { + "message": "Một trang web yêu cầu bạn đăng nhập để chứng minh quyền sở hữu tài khoản này." + }, "confirmTitleDescSignature": { "message": "Chỉ xác nhận thông báo này nếu bạn chấp thuận nội dung và tin tưởng trang web yêu cầu." }, + "confirmTitlePermitSignature": { + "message": "Yêu cầu hạn mức chi tiêu" + }, + "confirmTitleSIWESignature": { + "message": "Yêu cầu đăng nhập" + }, "confirmTitleSignature": { "message": "Yêu cầu chữ ký" }, @@ -958,7 +1138,7 @@ "message": "Đã kết nối với" }, "connecting": { - "message": "Đang kết nối..." + "message": "Đang kết nối" }, "connectingTo": { "message": "Đang kết nối với $1" @@ -1091,6 +1271,9 @@ "createSnapAccountTitle": { "message": "Tạo tài khoản" }, + "creatorAddress": { + "message": "Địa chỉ của người tạo" + }, "crossChainSwapsLink": { "message": "Hoán đổi trên các mạng với MetaMask Portfolio" }, @@ -1260,9 +1443,27 @@ "data": { "message": "Dữ liệu" }, + "dataCollectionForMarketing": { + "message": "Thu thập dữ liệu cho mục đích tiếp thị" + }, + "dataCollectionForMarketingDescription": { + "message": "Chúng tôi sẽ sử dụng MetaMetrics để tìm hiểu cách bạn tương tác với các thông tin tiếp thị. Chúng tôi có thể chia sẻ tin tức liên quan (chẳng hạn như tính năng sản phẩm và các tài liệu khác)." + }, + "dataCollectionWarningPopoverButton": { + "message": "Đồng ý" + }, + "dataCollectionWarningPopoverDescription": { + "message": "Bạn đã tắt tùy chọn thu thập dữ liệu cho mục đích tiếp thị của chúng tôi. Thao tác này chỉ áp dụng cho thiết bị này. Nếu bạn sử dụng MetaMask trên các thiết bị khác, nhớ chọn không tham gia trên các thiết bị đó." + }, "dataHex": { "message": "Thập lục phân" }, + "dataUnavailable": { + "message": "dữ liệu không khả dụng" + }, + "dateCreated": { + "message": "Ngày tạo" + }, "dcent": { "message": "D'Cent" }, @@ -1292,6 +1493,9 @@ "decryptRequest": { "message": "Giải mã yêu cầu" }, + "defaultRpcUrl": { + "message": "URL RPC mặc định" + }, "delete": { "message": "Xóa" }, @@ -1308,6 +1512,9 @@ "message": "Xóa mạng $1?", "description": "$1 represents the name of the network" }, + "deleteRpcUrl": { + "message": "Xóa URL RPC" + }, "deposit": { "message": "Nạp" }, @@ -1333,18 +1540,6 @@ "details": { "message": "Chi tiết" }, - "developerOptions": { - "message": "Tùy chọn Nhà phát triển" - }, - "developerOptionsResetStatesAnnouncementsDescription": { - "message": "Đặt lại giá trị isShown thành \"false\" cho tất cả thông báo. Thông báo là các nội dung hiển thị trong cửa sổ bật lên \"Xem tính năng mới\"." - }, - "developerOptionsResetStatesOnboarding": { - "message": "Đặt lại các trạng thái khác nhau liên quan đến phần giới thiệu và chuyển hướng đến trang giới thiệu \"Bảo mật ví của bạn\"." - }, - "developerOptionsServiceWorkerKeepAlive": { - "message": "Kết quả trong dấu thời gian được lưu liên tục vào session.storage" - }, "disabledGasOptionToolTipMessage": { "message": "“$1” bị vô hiệu hóa vì không đạt mức tăng tối thiểu 10% so với phí gas ban đầu.", "description": "$1 is gas estimate type which can be market or aggressive" @@ -1521,6 +1716,9 @@ "editGasTooLow": { "message": "Thời gian xử lý không rõ" }, + "editNetworkLink": { + "message": "chỉnh sửa mạng gốc" + }, "editNonceField": { "message": "Chỉnh sửa số nonce" }, @@ -1552,6 +1750,9 @@ "enabled": { "message": "Đã bật" }, + "enabledNetworks": { + "message": "Mạng được bật" + }, "encryptionPublicKeyNotice": { "message": "$1 muốn biết khóa mã hóa công khai của bạn. Bằng việc đồng ý, trang web này sẽ có thể gửi thông báo được mã hóa cho bạn.", "description": "$1 is the web3 site name" @@ -1656,6 +1857,9 @@ "estimatedFee": { "message": "Phí ước tính" }, + "estimatedFeeTooltip": { + "message": "Số tiền được chi trả để xử lý giao dịch trên mạng." + }, "ethGasPriceFetchWarning": { "message": "Giá gas dự phòng được cung cấp vì dịch vụ ước tính giá gas chính hiện không hoạt động." }, @@ -1675,6 +1879,12 @@ "etherscanViewOn": { "message": "Xem trên Etherscan" }, + "existingChainId": { + "message": "Thông tin bạn đã nhập được liên kết với một ID chuỗi hiện có." + }, + "existingRpcUrl": { + "message": "URL này được liên kết với một ID chuỗi khác." + }, "expandView": { "message": "Chế độ xem mở rộng" }, @@ -1729,6 +1939,9 @@ "message": "Tính năng nhập tập tin không hoạt động? Nhấp vào đây!", "description": "Helps user import their account from a JSON file" }, + "findTheRightChainId": { + "message": "Tìm ID chuỗi đúng trên:" + }, "flaskWelcomeUninstall": { "message": "bạn nên gỡ cài đặt tiện ích mở rộng này", "description": "This request is shown on the Flask Welcome screen. It is intended for non-developers, and will be bolded." @@ -1768,6 +1981,9 @@ "forgotPassword": { "message": "Quên mật khẩu?" }, + "form": { + "message": "mẫu" + }, "from": { "message": "Từ" }, @@ -1898,7 +2114,7 @@ "message": "Mạng thử nghiệm Goerli" }, "gotIt": { - "message": "Đã hiểu!" + "message": "Đã hiểu" }, "grantedToWithColon": { "message": "Cấp cho:" @@ -1976,6 +2192,12 @@ "highLowercase": { "message": "cao" }, + "highestCurrentBid": { + "message": "Giá thầu hiện tại cao nhất" + }, + "highestFloorPrice": { + "message": "Giá sàn cao nhất" + }, "history": { "message": "Lịch sử" }, @@ -2315,12 +2537,21 @@ "knownTokenWarning": { "message": "Hành động này sẽ chỉnh sửa các token đã niêm yết trong ví của bạn, kẻ xấu có thể lợi dụng việc này để lừa đảo bạn. Chỉ chấp thuận nếu bạn chắc chắn rằng bạn muốn thay đổi giá trị mà những token này đại diện cho. Tìm hiểu thêm về $1" }, + "l1Fee": { + "message": "Phí L1" + }, + "l1FeeTooltip": { + "message": "Phí gas L1" + }, + "l2Fee": { + "message": "Phí L2" + }, + "l2FeeTooltip": { + "message": "Phí gas L2" + }, "lastConnected": { "message": "Đã kết nối lần cuối" }, - "lastPriceSold": { - "message": "Giá bán gần nhất" - }, "lastSold": { "message": "Đã bán gần nhất" }, @@ -2492,6 +2723,12 @@ "message": "Đảm bảo không có ai đang nhìn", "description": "Warning to users to be care while creating and saving their new Secret Recovery Phrase" }, + "marketCap": { + "message": "Vốn hóa thị trường" + }, + "marketDetails": { + "message": "Chi tiết thị trường" + }, "max": { "message": "Tối đa" }, @@ -2501,6 +2738,9 @@ "maxFee": { "message": "Phí tối đa" }, + "maxFeeTooltip": { + "message": "Một khoản phí tối đa được cung cấp để thanh toán cho giao dịch." + }, "maxPriorityFee": { "message": "Phí ưu tiên tối đa" }, @@ -2548,12 +2788,19 @@ "methodData": { "message": "Phương thức" }, + "methodDataTransactionDesc": { + "message": "Chức năng được thực hiện dựa trên dữ liệu đầu vào đã giải mã." + }, "methodNotSupported": { "message": "Không được hỗ trợ với tài khoản này." }, "metrics": { "message": "Chỉ số" }, + "millionAbbreviation": { + "message": "Triệu", + "description": "Shortened form of 'million'" + }, "mismatchAccount": { "message": "Tài khoản bạn đã chọn ($1) khác với tài khoản sử dụng để ký ($2)" }, @@ -2735,6 +2982,9 @@ "networkNameBase": { "message": "Base" }, + "networkNameBitcoin": { + "message": "Bitcoin" + }, "networkNameDefinition": { "message": "Tên được liên kết với mạng này." }, @@ -2759,6 +3009,9 @@ "networkNameZkSyncEra": { "message": "zkSync Era" }, + "networkOptions": { + "message": "Tùy chọn mạng" + }, "networkProvider": { "message": "Nhà cung cấp mạng" }, @@ -2827,6 +3080,9 @@ "newNetworkAdded": { "message": "“$1” đã được thêm thành công!" }, + "newNetworkEdited": { + "message": "“$1” đã được chỉnh sửa thành công!" + }, "newNftAddedMessage": { "message": "NFT đã được thêm thành công!" }, @@ -2863,8 +3119,11 @@ "nftAlreadyAdded": { "message": "NFT đã được thêm vào." }, + "nftAutoDetectionEnabled": { + "message": "Tính năng tự động phát hiện NFT đã được bật" + }, "nftDisclaimer": { - "message": "Tuyên bố miễn trừ trách nhiệm: MetaMask lấy tập tin phương tiện từ URL nguồn. URL này đôi khi bị thay đổi bởi thị trường mà NFT được đào." + "message": "Tuyên bố miễn trừ trách nhiệm: MetaMask lấy tập tin đa phương tiện từ URL nguồn. URL này đôi khi bị thay đổi bởi thị trường mà NFT được đào." }, "nftOptions": { "message": "Tùy chọn NFT" @@ -2913,6 +3172,9 @@ "noDomainResolution": { "message": "Không có nội dung phân giải cho tên miền được cung cấp." }, + "noHardwareWalletOrSnapsSupport": { + "message": "Snap và hầu hết các ví cứng sẽ không hoạt động với phiên bản trình duyệt hiện tại của bạn." + }, "noNFTs": { "message": "Chưa có NFT" }, @@ -2943,8 +3205,8 @@ "nonceField": { "message": "Tùy chỉnh số nonce của giao dịch" }, - "nonceFieldDescription": { - "message": "Bật tùy chọn này để thay đổi số nonce (số giao dịch) trên màn hình xác nhận. Đây là tính năng nâng cao, hãy dùng một cách thận trọng." + "nonceFieldDesc": { + "message": "Bật tính năng này để thay đổi số nonce (số giao dịch) khi gửi tài sản. Đây là một tính năng nâng cao, hãy sử dụng thận trọng." }, "nonceFieldHeading": { "message": "Số nonce tùy chỉnh" @@ -2992,7 +3254,7 @@ "message": "Phí ưu tiên (GWEI)" }, "notificationItemCheckBlockExplorer": { - "message": "Kiểm tra trên BlockExplorer" + "message": "Kiểm tra trên Block Explorer" }, "notificationItemCollection": { "message": "Bộ sưu tập" @@ -3146,6 +3408,9 @@ "numberOfNewTokensDetectedSingular": { "message": "Tìm thấy 1 token mới trong tài khoản này" }, + "numberOfTokens": { + "message": "Số lượng token" + }, "ofTextNofM": { "message": "trên" }, @@ -3161,8 +3426,36 @@ "on": { "message": "Bật" }, - "onboarding": { - "message": "Giới thiệu" + "onboardedMetametricsAccept": { + "message": "Tôi đồng ý" + }, + "onboardedMetametricsDisagree": { + "message": "Không, cảm ơn" + }, + "onboardedMetametricsKey1": { + "message": "Bước tiến mới nhất" + }, + "onboardedMetametricsKey2": { + "message": "Tính năng sản phẩm" + }, + "onboardedMetametricsKey3": { + "message": "Các tài liệu khuyến mại liên quan khác" + }, + "onboardedMetametricsLink": { + "message": "MetaMetrics" + }, + "onboardedMetametricsParagraph1": { + "message": "Ngoài $1, chúng tôi muốn sử dụng dữ liệu để hiểu cách bạn tương tác với với các thông tin tiếp thị.", + "description": "$1 represents the 'onboardedMetametricsLink' locale string" + }, + "onboardedMetametricsParagraph2": { + "message": "Điều này sẽ giúp chúng tôi cá nhân hóa nội dung mà chúng tôi chia sẻ với bạn, chẳng hạn như:" + }, + "onboardedMetametricsParagraph3": { + "message": "Hãy nhớ rằng chúng tôi không bán dữ liệu mà bạn cung cấp và bạn có thể chọn không tham gia bất kỳ lúc nào." + }, + "onboardedMetametricsTitle": { + "message": "Hãy giúp chúng tôi nâng cao trải nghiệm của bạn" }, "onboardingAdvancedPrivacyIPFSDescription": { "message": "Cổng IPFS cho phép truy cập và xem dữ liệu do bên thứ ba lưu trữ. Bạn có thể thêm cổng IPFS tùy chỉnh hoặc tiếp tục sử dụng cổng mặc định." @@ -3200,9 +3493,6 @@ "onboardingMetametricsDescription2": { "message": "Khi thu thập số liệu, chúng tôi sẽ luôn cam kết điều này..." }, - "onboardingMetametricsDisagree": { - "message": "Không, cảm ơn" - }, "onboardingMetametricsInfuraTerms": { "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`" @@ -3237,6 +3527,9 @@ "onboardingMetametricsTitle": { "message": "Giúp chúng tôi cải thiện MetaMask" }, + "onboardingMetametricsUseDataCheckbox": { + "message": "Chúng tôi sẽ sử dụng dữ liệu này để tìm hiểu cách bạn tương tác với các thông tin tiếp thị. Chúng tôi có thể chia sẻ tin tức liên quan (chẳng hạn như tính năng sản phẩm)." + }, "onboardingPinExtensionBillboardAccess": { "message": "Toàn quyền truy cập" }, @@ -3280,6 +3573,22 @@ "message": "Thông báo phát hiện dấu hiệu lừa đảo tùy thuộc vào quá trình truyền tin với $1. jsDeliver sẽ có quyền truy cập vào địa chỉ IP của bạn. Xem $2.", "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" }, + "oneDayAbbreviation": { + "message": "1 Ngày", + "description": "Shortened form of '1 day'" + }, + "oneMonthAbbreviation": { + "message": "1 Tháng", + "description": "Shortened form of '1 month'" + }, + "oneWeekAbbreviation": { + "message": "1 Tuần", + "description": "Shortened form of '1 week'" + }, + "oneYearAbbreviation": { + "message": "1 Năm", + "description": "Shortened form of '1 year'" + }, "onekey": { "message": "OneKey" }, @@ -3377,7 +3686,7 @@ "description": "For importing an account from a private key" }, "paymasterInUse": { - "message": "Phí gas cho giao dịch này sẽ được Paymaster (tài khoản hợp đồng thông minh tài trợ cho các giao dịch) thanh toán.", + "message": "Phí gas cho giao dịch này sẽ được thanh toán bởi bên chi trả.", "description": "Alert shown in transaction confirmation if paymaster in use." }, "pending": { @@ -3632,6 +3941,9 @@ "permissionsPageTourTitle": { "message": "Các trang web đã kết nối hiện đã được cấp quyền" }, + "permitSimulationDetailInfo": { + "message": "Bạn đang cấp cho người chi tiêu quyền chi tiêu số lượng token này từ tài khoản của bạn." + }, "personalAddressDetected": { "message": "Đã tìm thấy địa chỉ cá nhân. Nhập địa chỉ hợp đồng token." }, @@ -3664,6 +3976,10 @@ "popularCustomNetworks": { "message": "Mạng tùy chỉnh phổ biến" }, + "popularNetworkAddToolTip": { + "message": "Một vài mạng trong số này phụ thuộc vào bên thứ ba. Kết nối có thể kém tin cậy hơn hoặc cho phép bên thứ ba theo dõi hoạt động. $1", + "description": "$1 is Learn more link" + }, "portfolio": { "message": "Danh mục đầu tư" }, @@ -3676,6 +3992,12 @@ "prev": { "message": "Trước" }, + "price": { + "message": "Giá" + }, + "priceUnavailable": { + "message": "giá không khả dụng" + }, "primaryCurrencySetting": { "message": "Tiền tệ chính" }, @@ -3828,6 +4150,9 @@ "quoteRate": { "message": "Tỷ giá báo giá" }, + "rank": { + "message": "Xếp hạng" + }, "reAddAccounts": { "message": "thêm lại bất kỳ tài khoản nào khác" }, @@ -3997,9 +4322,6 @@ "reset": { "message": "Đặt lại" }, - "resetStates": { - "message": "Đặt lại trạng thái" - }, "resetWallet": { "message": "Đặt lại ví" }, @@ -4135,6 +4457,9 @@ "searchAccounts": { "message": "Tìm kiếm tài khoản" }, + "searchNfts": { + "message": "Tìm kiếm NFT" + }, "searchTokens": { "message": "Tìm kiếm token" }, @@ -4179,13 +4504,10 @@ "seedPhraseIntroRecommendedButtonCopy": { "message": "Bảo mật ví của tôi (khuyến khích)" }, - "seedPhraseIntroSidebarBulletFour": { - "message": "Viết ra và cất ở nhiều nơi bí mật." - }, "seedPhraseIntroSidebarBulletOne": { - "message": "Lưu trong một trình quản lý mật khẩu" + "message": "Viết ra và cất ở nhiều nơi bí mật." }, - "seedPhraseIntroSidebarBulletThree": { + "seedPhraseIntroSidebarBulletTwo": { "message": "Lưu giữ trong hộp ký gửi an toàn." }, "seedPhraseIntroSidebarCopyOne": { @@ -4255,10 +4577,10 @@ "message": "Chọn token" }, "selectNFTPrivacyPreference": { - "message": "Bật phát hiện NFT trong phần Cài Đặt" + "message": "Bật tính năng Tự động phát hiện NFT" }, "selectPathHelp": { - "message": "Nếu bạn không thấy các tài khoản như mong đợi, hãy chuyển sang đường dẫn HD." + "message": "Nếu bạn không thấy các tài khoản như mong đợi, hãy chuyển sang đường dẫn HD hoặc mạng đã chọn hiện tại." }, "selectType": { "message": "Chọn loại" @@ -4269,9 +4591,6 @@ "send": { "message": "Gửi" }, - "sendAToken": { - "message": "Gửi token" - }, "sendBugReport": { "message": "Gửi báo cáo lỗi." }, @@ -4322,9 +4641,6 @@ "sepolia": { "message": "Mạng thử nghiệm Sepolia" }, - "serviceWorkerKeepAlive": { - "message": "Duy Trì Hoạt Động Service Worker" - }, "setAdvancedPrivacySettingsDetails": { "message": "MetaMask sử dụng các dịch vụ của bên thứ ba đáng tin cậy này để nâng cao sự hữu ích và an toàn của sản phẩm." }, @@ -4381,6 +4697,9 @@ "showIncomingTransactionsExplainer": { "message": "Điều này dựa trên các API khác nhau của bên thứ ba cho mỗi mạng, có thể làm lộ địa chỉ Ethereum và địa chỉ IP của bạn." }, + "showLess": { + "message": "Thu gọn" + }, "showMore": { "message": "Hiển thị thêm" }, @@ -4411,9 +4730,6 @@ "signatureRequestGuidance": { "message": "Chỉ ký vào thông báo này nếu bạn hoàn toàn hiểu nội dung và tin tưởng trang web yêu cầu." }, - "signatureRequestWarning": { - "message": "Việc ký vào thông báo này có thể gây nguy hiểm. Bạn có thể trao toàn quyền kiểm soát tài khoản và tài sản của mình cho bên kia. Nghĩa là họ có thể tiêu hoặc rút hết tiền trong tài khoản của bạn bất cứ lúc nào. Hãy tiến hành thận trọng. $1." - }, "signed": { "message": "Đã ký" }, @@ -4423,6 +4739,9 @@ "signing": { "message": "Đang ký" }, + "signingInWith": { + "message": "Đăng nhập bằng" + }, "simulationDetailsFailed": { "message": "Đã xảy ra lỗi khi tải kết quả ước tính." }, @@ -4460,6 +4779,24 @@ "simulationsSettingSubHeader": { "message": "Ước tính thay đổi số dư" }, + "siweIssued": { + "message": "Đã phát hành" + }, + "siweNetwork": { + "message": "Mạng" + }, + "siweRequestId": { + "message": "ID yêu cầu" + }, + "siweResources": { + "message": "Tài nguyên" + }, + "siweSignatureSimulationDetailInfo": { + "message": "Bạn đang đăng nhập vào một trang web và dự kiến không có thay đổi nào đối với tài khoản của bạn." + }, + "siweURI": { + "message": "URL" + }, "skip": { "message": "Bỏ qua" }, @@ -4563,6 +4900,14 @@ "snapAccountsDescription": { "message": "Tài khoản được kiểm soát bởi Snap bên thứ ba." }, + "snapConnectTo": { + "message": "Kết nối với $1", + "description": "$1 is the website URL or a Snap name. Used for Snaps pre-approved connections." + }, + "snapConnectionPermissionDescription": { + "message": "Cho phép $1 tự động kết nối với $2 mà không cần bạn chấp thuận.", + "description": "Used for Snap pre-approved connections. $1 is the Snap name, $2 is a website URL." + }, "snapConnectionWarning": { "message": "$1 muốn sử dụng $2", "description": "$2 is the snap and $1 is the dapp requesting connection to the snap." @@ -4574,6 +4919,9 @@ "snapDetailWebsite": { "message": "Trang web" }, + "snapHomeMenu": { + "message": "Trình đơn Trang chủ Snap" + }, "snapInstallRequest": { "message": "Cài đặt $1 sẽ cấp cho nó các quyền sau.", "description": "$1 is the snap name." @@ -4696,6 +5044,9 @@ "source": { "message": "Nguồn" }, + "speed": { + "message": "Tốc độ" + }, "speedUp": { "message": "Tăng tốc" }, @@ -4730,6 +5081,9 @@ "spendLimitTooLarge": { "message": "Hạn mức chi tiêu quá lớn" }, + "spender": { + "message": "Người chi tiêu" + }, "spendingCap": { "message": "Hạn mức chi tiêu" }, @@ -4850,9 +5204,6 @@ "stateLogsDescription": { "message": "Nhật ký trạng thái có chứa các địa chỉ tài khoản công khai của bạn và các giao dịch đã gửi." }, - "states": { - "message": "Trạng thái" - }, "status": { "message": "Trạng thái" }, @@ -4957,6 +5308,13 @@ "submitted": { "message": "Đã gửi" }, + "suggestedBySnap": { + "message": "Được đề xuất bởi $1", + "description": "$1 is the snap name" + }, + "suggestedTokenName": { + "message": "Tên đề xuất:" + }, "suggestedTokenSymbol": { "message": "Mã chứng khoán được đề xuất:" }, @@ -5071,7 +5429,7 @@ "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." }, "swapFetchingQuotes": { - "message": "Tìm nạp báo giá" + "message": "Tìm nạp báo giá..." }, "swapFetchingQuotesErrorDescription": { "message": "Rất tiếc... đã xảy ra sự cố. Hãy thử lại. Nếu lỗi vẫn tiếp diễn, hãy liên hệ với bộ phận hỗ trợ khách hàng." @@ -5419,7 +5777,7 @@ "description": "$1 represents the account name, $2 represents the network name" }, "switchedTo": { - "message": "Bạn đã chuyển sang" + "message": "Bạn hiện đang sử dụng" }, "switchingNetworksCancelsPendingConfirmations": { "message": "Khi bạn chuyển mạng, mọi xác nhận đang chờ xử lý sẽ bị hủy" @@ -5467,6 +5825,10 @@ "thisCollection": { "message": "bộ sưu tập này" }, + "threeMonthsAbbreviation": { + "message": "3 Tháng", + "description": "Shortened form of '3 months'" + }, "time": { "message": "Thời gian" }, @@ -5480,45 +5842,6 @@ "message": "Đến: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleEthSignBannerDescription": { - "message": "Bạn có nguy cơ bị tấn công lừa đảo. Tự bảo vệ mình bằng cách tắt eth_sign." - }, - "toggleEthSignDescriptionField": { - "message": "Nếu bật chế độ cài đặt này, bạn có thể nhận các yêu cầu chữ ký mà bạn không đọc được. Khi ký vào một tin nhắn mà bạn không hiểu, bạn có thể đang đồng ý cho đi tiền và NFT của mình." - }, - "toggleEthSignField": { - "message": "Yêu cầu eth_sign" - }, - "toggleEthSignModalBannerBoldText": { - "message": " bạn có thể bị lừa đảo" - }, - "toggleEthSignModalBannerText": { - "message": "Nếu bạn được yêu cầu bật cài đặt này," - }, - "toggleEthSignModalCheckBox": { - "message": "Tôi hiểu rằng tôi có thể mất toàn bộ tiền và NFT của mình nếu tôi kích hoạt yêu cầu eth_sign. " - }, - "toggleEthSignModalDescription": { - "message": "Việc cho phép yêu cầu eth_sign có thể khiến bạn dễ bị tấn công lừa đảo. Nhớ luôn xem lại URL và cẩn thận khi ký các tin nhắn có chứa mã." - }, - "toggleEthSignModalFormError": { - "message": "Văn bản không đúng" - }, - "toggleEthSignModalFormLabel": { - "message": "Nhập “Tôi chỉ ký những gì tôi hiểu” để tiếp tục" - }, - "toggleEthSignModalFormValidation": { - "message": "Tôi chỉ ký những gì tôi hiểu" - }, - "toggleEthSignModalTitle": { - "message": "Bạn tự chịu rủi ro khi sử dụng" - }, - "toggleEthSignOff": { - "message": "TẮT (Khuyến khích)" - }, - "toggleEthSignOn": { - "message": "BẬT (Không khuyến khích)" - }, "toggleRequestQueueDescription": { "message": "Tính năng này cho phép bạn chọn mạng cho từng trang web thay vì một mạng duy nhất được chọn cho tất cả các trang web. Tính năng này sẽ ngăn bạn chuyển đổi mạng theo cách thủ công, điều này có thể ảnh hưởng đến trải nghiệm người dùng của bạn trên một số trang web." }, @@ -5546,6 +5869,9 @@ "tokenContractAddress": { "message": "Địa chỉ hợp đồng token" }, + "tokenDecimal": { + "message": "Số thập phân của token" + }, "tokenDecimalFetchFailed": { "message": "Yêu cầu số thập phân của token. Tìm trên: $1" }, @@ -5562,13 +5888,16 @@ "message": "ID Token" }, "tokenList": { - "message": "Danh sách token:" + "message": "Danh sách token" }, "tokenScamSecurityRisk": { "message": "rủi ro về bảo mật và lừa đảo token" }, "tokenShowUp": { - "message": "Các token có thể không tự động hiển thị trong ví của bạn." + "message": "Các token có thể không tự động hiển thị trong ví của bạn. " + }, + "tokenStandard": { + "message": "Tiêu chuẩn token" }, "tokenSymbol": { "message": "Ký hiệu token" @@ -5580,6 +5909,9 @@ "message": "Đã tìm thấy $1 token mới", "description": "$1 is the number of new tokens detected" }, + "tokensInCollection": { + "message": "Token trong bộ sưu tập" + }, "tooltipApproveButton": { "message": "Tôi đã hiểu" }, @@ -5595,6 +5927,9 @@ "total": { "message": "Tổng" }, + "totalVolume": { + "message": "Tổng khối lượng giao dịch" + }, "transaction": { "message": "giao dịch" }, @@ -5610,6 +5945,9 @@ "transactionCreated": { "message": "Đã tạo giao dịch với giá trị $1 lúc $2." }, + "transactionDataFunction": { + "message": "Chức năng" + }, "transactionDetailDappGasMoreInfo": { "message": "Trang web gợi ý" }, @@ -5700,6 +6038,10 @@ "transferFrom": { "message": "Chuyển từ" }, + "trillionAbbreviation": { + "message": "Nghìn Tỷ", + "description": "Shortened form of 'trillion'" + }, "troubleConnectingToLedgerU2FOnFirefox": { "message": "Chúng tôi đang gặp sự cố khi kết nối với Ledger của bạn. $1", "description": "$1 is a link to the wallet connection guide;" @@ -5778,6 +6120,9 @@ "message": "U2F", "description": "A name on an API for the browser to interact with devices that support the U2F protocol. On some browsers we use it to connect MetaMask to Ledger devices." }, + "unMatchedChain": { + "message": "Theo hồ sơ của chúng tôi, URL này không khớp với nhà cung cấp đã biết cho ID chuỗi này." + }, "unapproved": { "message": "Chưa chấp thuận" }, @@ -5829,12 +6174,21 @@ "update": { "message": "Cập nhật" }, + "updateOrEditNetworkInformations": { + "message": "Cập nhật thông tin của bạn hoặc" + }, "updateRequest": { "message": "Yêu cầu cập nhật" }, "updatedWithDate": { "message": "Đã cập nhật $1" }, + "uploadDropFile": { + "message": "Thả tập tin của bạn vào đây" + }, + "uploadFile": { + "message": "Tải lên tập tin" + }, "urlErrorMsg": { "message": "URL phải có tiền tố HTTP/HTTPS phù hợp." }, @@ -6050,6 +6404,12 @@ "whatsThis": { "message": "Đây là gì?" }, + "wrongChainId": { + "message": "ID chuỗi này không khớp với tên mạng." + }, + "wrongNetworkName": { + "message": "Theo hồ sơ của chúng tôi, tên mạng có thể không khớp chính xác với ID chuỗi này." + }, "xOfYPending": { "message": "$1/$2 đang chờ xử lý", "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" @@ -6073,8 +6433,11 @@ "yourAccounts": { "message": "Tài khoản của bạn" }, - "yourFundsMayBeAtRisk": { - "message": "Tiền của bạn có thể gặp rủi ro" + "yourActivity": { + "message": "Hoạt động của bạn" + }, + "yourBalance": { + "message": "Số dư của bạn" }, "yourNFTmayBeAtRisk": { "message": "NFT của bạn có thể gặp rủi ro" diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index 052b49e03a08..7265f579a30f 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -42,7 +42,7 @@ "message": "关联您的二维码硬件钱包" }, "QRHardwareWalletSteps2Description": { - "message": "Ngrave (即将上线)" + "message": "Ngrave Zero" }, "SIWEAddressInvalid": { "message": "登录请求中的地址与您用于登录的账户地址不匹配。" @@ -162,6 +162,9 @@ "accountSelectionRequired": { "message": "您需要选择一个账户!" }, + "accountTypeNotSupported": { + "message": "账户类型不受支持" + }, "accounts": { "message": "账户" }, @@ -277,6 +280,12 @@ "addNewAccount": { "message": "添加新账户" }, + "addNewBitcoinAccount": { + "message": "添加新的比特币账户(测试版)" + }, + "addNewBitcoinTestnetAccount": { + "message": "添加新的比特币账户(测试网)" + }, "addNewToken": { "message": "添加新代币" }, @@ -286,6 +295,9 @@ "addNfts": { "message": "添加 NFT" }, + "addRpcUrl": { + "message": "添加 RPC(远程过程调用)URL" + }, "addSnapAccountToggle": { "message": "启用“添加账户Snap(测试版)”" }, @@ -305,12 +317,21 @@ "message": "找不到代币?您可以通过粘贴其地址手动添加任何代币。代币合约地址可以在 $1 上找到", "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" }, + "addUrl": { + "message": "添加 URL" + }, "addingCustomNetwork": { "message": "正在添加网络" }, "addingTokens": { "message": "正在添加代币" }, + "additionalNetworks": { + "message": "其他网络" + }, + "additionalRpcUrl": { + "message": "其他 RPC(远程过程调用)URL" + }, "address": { "message": "地址" }, @@ -326,6 +347,18 @@ "advancedConfiguration": { "message": "高级配置" }, + "advancedDetailsDataDesc": { + "message": "数据" + }, + "advancedDetailsHexDesc": { + "message": "十六进制" + }, + "advancedDetailsNonceDesc": { + "message": "唯一交易标识号" + }, + "advancedDetailsNonceTooltip": { + "message": "这是一个账户的交易编号。第一笔交易的唯一交易标识号为 0,然后按顺序递增。" + }, "advancedGasFeeDefaultOptIn": { "message": "将这些值保存为 $1 网络的默认值。", "description": "$1 is the current network name." @@ -349,6 +382,18 @@ "alert": { "message": "提醒" }, + "alertActionBuy": { + "message": "买入 ETH" + }, + "alertActionUpdateGas": { + "message": "更新燃料限制" + }, + "alertActionUpdateGasFee": { + "message": "更新费用" + }, + "alertActionUpdateGasFeeLevel": { + "message": "更新燃料选项" + }, "alertBannerMultipleAlertsDescription": { "message": "如果您批准此请求,以欺诈闻名的第三方可能会拿走您的所有资产。" }, @@ -358,6 +403,36 @@ "alertDisableTooltip": { "message": "这可以在“设置 > 提醒”中进行更改" }, + "alertMessageGasEstimateFailed": { + "message": "我们无法提供准确的费用,估算可能较高。我们建议您输入自定义的燃料限制,但交易仍存在失败风险。" + }, + "alertMessageGasFeeLow": { + "message": "在选择低级型费用时,预计交易速度较慢,等待时间较长。如需更快交易,请选择市场型或激进型费用选项。" + }, + "alertMessageGasTooLow": { + "message": "要继续此交易,您需要将燃料限制提高到 21000 或更高。" + }, + "alertMessageInsufficientBalance": { + "message": "您的账户中没有足够的 ETH 来支付交易费用。" + }, + "alertMessageNetworkBusy": { + "message": "燃料价格很高,估算不太准确。" + }, + "alertMessageNoGasPrice": { + "message": "您手动更新费用后,我们才能继续进行此交易。" + }, + "alertMessagePendingTransactions": { + "message": "上一笔交易完成后,此交易才能继续进行。了解如何取消或加快交易。" + }, + "alertMessageSignInDomainMismatch": { + "message": "提出请求的网站不是您正在登录的网站。这可能试图窃取您的登录凭据。" + }, + "alertMessageSignInWrongAccount": { + "message": "此网站要求您使用错误的账户登录。" + }, + "alertMessageSigningOrSubmitting": { + "message": "您的上一笔交易完成后,此交易才能继续进行。" + }, "alertModalAcknowledge": { "message": "我已知晓风险并仍想继续" }, @@ -367,6 +442,33 @@ "alertModalReviewAllAlerts": { "message": "查看所有提醒" }, + "alertReasonGasEstimateFailed": { + "message": "费用不准确" + }, + "alertReasonGasFeeLow": { + "message": "速度慢" + }, + "alertReasonGasTooLow": { + "message": "燃料限制较低" + }, + "alertReasonInsufficientBalance": { + "message": "资金不足" + }, + "alertReasonNetworkBusy": { + "message": "网络繁忙" + }, + "alertReasonNoGasPrice": { + "message": "无法使用费用估算" + }, + "alertReasonPendingTransactions": { + "message": "待定交易" + }, + "alertReasonSignIn": { + "message": "可疑登录请求" + }, + "alertReasonWrongAccount": { + "message": "错误账户" + }, "alertSettingsUnconnectedAccount": { "message": "浏览网站时选择的账户未连接" }, @@ -398,6 +500,12 @@ "allPermissions": { "message": "所有许可" }, + "allTimeHigh": { + "message": "有史以来新高" + }, + "allTimeLow": { + "message": "有史以来新低" + }, "allYourNFTsOf": { "message": "您所有在$1的NFT", "description": "$1 is a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" @@ -436,9 +544,6 @@ "message": "$1 和 $2", "description": "$1 is the first item, $2 is the second item. Used in Snap Install Warning modal." }, - "announcements": { - "message": "公告" - }, "appDescription": { "message": "浏览器中的以太坊钱包", "description": "The description of the application" @@ -524,9 +629,15 @@ "attemptToCancelSwapForFree": { "message": "尝试免费取消兑换" }, + "attributes": { + "message": "属性" + }, "attributions": { "message": "参与者" }, + "auroraRpcDeprecationMessage": { + "message": "Infura RPC(远程过程调用)URL 不再支持 Aurora。" + }, "authorizedPermissions": { "message": "您已授权以下权限" }, @@ -591,6 +702,10 @@ "basicConfigurationBannerTitle": { "message": "基本功能已关闭" }, + "basicConfigurationDescription": { + "message": "MetaMask 通过互联网服务提供代币详情和燃料设置等基本功能。当您使用互联网服务时,您的 IP 地址会共享,在本例中与 MetaMask 共享。这就像您访问任何网站一样。MetaMask 暂时使用这些数据,并且绝不会出卖您的数据。您可以使用 VPN 或关闭这些服务,但这可能会影响您的 MetaMask 体验。要了解详情,请参阅我们的 $1。", + "description": "$1 is to be replaced by the message for privacyMsg, and will link to https://consensys.io/privacy-policy" + }, "basicConfigurationLabel": { "message": "基本功能" }, @@ -634,6 +749,29 @@ "betaWalletCreationSuccessReminder2": { "message": "MetaMask 测试版绝对不会向您索要账户私钥助记词。" }, + "billionAbbreviation": { + "message": "十亿", + "description": "Shortened form of 'billion'" + }, + "bitcoinActivityNotSupported": { + "message": "比特币活动不受支持" + }, + "bitcoinSupportSectionTitle": { + "message": "比特币" + }, + "bitcoinSupportToggleDescription": { + "message": "启用此功能后,您可以选择将比特币账户添加到衍生自现有私钥助记词的 MetaMask Extension 中。这是一个实验性的测试版功能,因此您应自行承担使用风险。为了向我们提供有关此新比特币体验的反馈,请填写此 $1。", + "description": "$1 is the link to a product feedback form" + }, + "bitcoinSupportToggleTitle": { + "message": "启用“添加新的比特币账户(测试版)”" + }, + "bitcoinTestnetSupportToggleDescription": { + "message": "启用此功能后,您可以选择为测试网络添加比特币账户。" + }, + "bitcoinTestnetSupportToggleTitle": { + "message": "启用“添加新的比特币账户(测试网)”" + }, "blockExplorerAccountAction": { "message": "账户", "description": "This is used with viewOnEtherscan and viewInExplorer e.g View Account in Explorer" @@ -659,6 +797,9 @@ "blockaid": { "message": "Blockaid" }, + "blockaidAlertInfo": { + "message": "我们不建议继续处理此请求。" + }, "blockaidDescriptionApproveFarming": { "message": "如果您批准此请求,以欺诈闻名的第三方可能会拿走您的所有资产。" }, @@ -680,6 +821,9 @@ "blockaidDescriptionTransferFarming": { "message": "如果您批准此请求,以欺诈闻名的第三方将会拿走您的所有资产。" }, + "blockaidDescriptionWarning": { + "message": "这可能是欺骗性请求。仅在您信任所涉及的每个地址时才能继续。" + }, "blockaidMessage": { "message": "隐私保护 - 不会与第三方共享任何数据。适用于 Arbitrum、Avalanche、BNB Chain、以太坊主网、Linea、Optimism、Polygon、Base 和 Sepolia。" }, @@ -695,6 +839,9 @@ "blockies": { "message": "Blockies" }, + "boughtFor": { + "message": "购买用于" + }, "bridge": { "message": "跨链桥" }, @@ -714,7 +861,7 @@ "message": "忙碌中" }, "buyAndSell": { - "message": "买入和卖出" + "message": "出入金" }, "buyAsset": { "message": "购买$1", @@ -779,6 +926,9 @@ "chromeRequiredForHardwareWallets": { "message": "您需要在 Google Chrome 上使用 MetaMask 以连接到您的硬件钱包。" }, + "circulatingSupply": { + "message": "循环供应" + }, "clear": { "message": "清除" }, @@ -813,6 +963,9 @@ "coingecko": { "message": "CoinGecko" }, + "collectionName": { + "message": "收藏品名称" + }, "comboNoOptions": { "message": "找不到任何选项", "description": "Default text shown in the combo field dropdown if no options." @@ -838,6 +991,9 @@ "confirmAlertModalAcknowledgeMultiple": { "message": "我已知晓提醒并仍想继续" }, + "confirmAlertModalAcknowledgeSingle": { + "message": "我已知晓提醒并仍想继续" + }, "confirmAlertModalDetails": { "message": "如果您登录,以欺诈闻名的第三方可能会拿走您的所有资产。在继续操作之前,请查看提醒。" }, @@ -853,18 +1009,42 @@ "confirmConnectionTitle": { "message": "确认连接到$1" }, + "confirmDeletion": { + "message": "确认删除" + }, + "confirmFieldPaymaster": { + "message": "费用支付方" + }, + "confirmFieldTooltipPaymaster": { + "message": "此交易的费用将由 paymaster 智能合约支付。" + }, "confirmPassword": { "message": "确认密码" }, "confirmRecoveryPhrase": { "message": "确认私钥助记词" }, + "confirmRpcUrlDeletionMessage": { + "message": "您确定要删除 RPC(远程过程调用)URL 吗?您的信息将不会保存到此网络。" + }, "confirmTitleDescContractInteractionTransaction": { "message": "仅在您完全理解内容并信任请求网站的情况下,才能确认此交易。" }, + "confirmTitleDescPermitSignature": { + "message": "此网站需要获得许可来使用您的代币。" + }, + "confirmTitleDescSIWESignature": { + "message": "某个网站要求您登录以证明您拥有该账户。" + }, "confirmTitleDescSignature": { "message": "仅在您批准该内容并信任请求网站的情况下,才能确认此消息。" }, + "confirmTitlePermitSignature": { + "message": "支出上限请求" + }, + "confirmTitleSIWESignature": { + "message": "登录请求" + }, "confirmTitleSignature": { "message": "签名请求" }, @@ -1091,6 +1271,9 @@ "createSnapAccountTitle": { "message": "创建账户" }, + "creatorAddress": { + "message": "创建者地址" + }, "crossChainSwapsLink": { "message": "使用 MetaMask Portfolio 跨网络兑换" }, @@ -1260,9 +1443,27 @@ "data": { "message": "数据" }, + "dataCollectionForMarketing": { + "message": "用于营销目的的数据收集" + }, + "dataCollectionForMarketingDescription": { + "message": "我们将使用 MetaMetrics 来了解您如何与我们的营销通信交互。我们可能会分享相关资讯(例如产品特点和其他内容)。" + }, + "dataCollectionWarningPopoverButton": { + "message": "好的" + }, + "dataCollectionWarningPopoverDescription": { + "message": "您关闭了我们用于营销目的的数据收集。此操作仅适用于此设备。如果您在其他设备上使用 MetaMask,请务必也在其他设备上选择关闭。" + }, "dataHex": { "message": "十六进制" }, + "dataUnavailable": { + "message": "数据不可用" + }, + "dateCreated": { + "message": "创建日期" + }, "dcent": { "message": "D'Cent" }, @@ -1292,6 +1493,9 @@ "decryptRequest": { "message": "解密请求" }, + "defaultRpcUrl": { + "message": "默认 RPC(远程过程调用)URL" + }, "delete": { "message": "删除" }, @@ -1308,6 +1512,9 @@ "message": "要删除$1网络吗?", "description": "$1 represents the name of the network" }, + "deleteRpcUrl": { + "message": "删除 RPC(远程过程调用)URL" + }, "deposit": { "message": "保证金" }, @@ -1333,18 +1540,6 @@ "details": { "message": "详细信息" }, - "developerOptions": { - "message": "开发者选项" - }, - "developerOptionsResetStatesAnnouncementsDescription": { - "message": "将所有公告的布尔值显示重设为 false。公告是显示在“最新资讯”(What's New)弹出模式中的通知。" - }, - "developerOptionsResetStatesOnboarding": { - "message": "重设与入门相关的各种状态,并重定向到“保护您的钱包”入门页面。" - }, - "developerOptionsServiceWorkerKeepAlive": { - "message": "导致时间戳持续保存到 session.storage" - }, "disabledGasOptionToolTipMessage": { "message": "“$1”已被禁用,因为它不满足在原来的燃料费用基础上至少增加10%的要求。", "description": "$1 is gas estimate type which can be market or aggressive" @@ -1521,6 +1716,9 @@ "editGasTooLow": { "message": "处理时间未知" }, + "editNetworkLink": { + "message": "编辑原始网络" + }, "editNonceField": { "message": "编辑 nonce" }, @@ -1552,6 +1750,9 @@ "enabled": { "message": "已启用" }, + "enabledNetworks": { + "message": "启用的网络" + }, "encryptionPublicKeyNotice": { "message": "$1 想要您的加密公钥。同意后,该网站将可以向您发送加密消息。", "description": "$1 is the web3 site name" @@ -1656,6 +1857,9 @@ "estimatedFee": { "message": "预估费用" }, + "estimatedFeeTooltip": { + "message": "为在网络上处理交易而支付的金额。" + }, "ethGasPriceFetchWarning": { "message": "由于目前主要的燃料估算服务不可用,因此提供了备用燃料价格。" }, @@ -1675,6 +1879,12 @@ "etherscanViewOn": { "message": "在 Etherscan 上查看" }, + "existingChainId": { + "message": "您输入的信息与现有的链 ID 相关联。" + }, + "existingRpcUrl": { + "message": "此 URL 与另一个链 ID 相关联。" + }, "expandView": { "message": "展开视图" }, @@ -1729,6 +1939,9 @@ "message": "文件导入失败?点击这里!", "description": "Helps user import their account from a JSON file" }, + "findTheRightChainId": { + "message": "在下方找到合适的:" + }, "flaskWelcomeUninstall": { "message": "您应该卸载此扩展程序", "description": "This request is shown on the Flask Welcome screen. It is intended for non-developers, and will be bolded." @@ -1768,6 +1981,9 @@ "forgotPassword": { "message": "忘记密码了?" }, + "form": { + "message": "表格" + }, "from": { "message": "自" }, @@ -1898,7 +2114,7 @@ "message": "Goerli 测试网络" }, "gotIt": { - "message": "知道了!" + "message": "知道了" }, "grantedToWithColon": { "message": "授予:" @@ -1976,6 +2192,12 @@ "highLowercase": { "message": "高" }, + "highestCurrentBid": { + "message": "当前最高出价" + }, + "highestFloorPrice": { + "message": "最高底价" + }, "history": { "message": "历史记录" }, @@ -2315,12 +2537,21 @@ "knownTokenWarning": { "message": "此操作将编辑已经在您的钱包中列出的代币,有肯能被用来欺骗您。只有确定要更改这些代币的内容时,才通过此操作。了解更多关于 $1" }, + "l1Fee": { + "message": "L1 费用" + }, + "l1FeeTooltip": { + "message": "L1 燃料费" + }, + "l2Fee": { + "message": "L2 费用" + }, + "l2FeeTooltip": { + "message": "L2 燃料费" + }, "lastConnected": { "message": "最后连接" }, - "lastPriceSold": { - "message": "最后售价" - }, "lastSold": { "message": "最后售出" }, @@ -2492,6 +2723,12 @@ "message": "请确保没有人在看您的屏幕", "description": "Warning to users to be care while creating and saving their new Secret Recovery Phrase" }, + "marketCap": { + "message": "市值" + }, + "marketDetails": { + "message": "市场详情" + }, "max": { "message": "最大" }, @@ -2501,6 +2738,9 @@ "maxFee": { "message": "最大费用" }, + "maxFeeTooltip": { + "message": "为支付交易而提供的最高费用。" + }, "maxPriorityFee": { "message": "最大优先费用" }, @@ -2548,12 +2788,19 @@ "methodData": { "message": "方法" }, + "methodDataTransactionDesc": { + "message": "基于解码输入数据而执行的功能。" + }, "methodNotSupported": { "message": "不支持此账户。" }, "metrics": { "message": "指标" }, + "millionAbbreviation": { + "message": "百万", + "description": "Shortened form of 'million'" + }, "mismatchAccount": { "message": "您选中的账户($1)与尝试登录的账户($2)不同" }, @@ -2666,7 +2913,7 @@ "description": "Description below header used on Permission Connect screen for native permissions." }, "nativeToken": { - "message": "此网络上的原生代币为$1。它是用于燃料费的代币。", + "message": "此网络上的原生代币为 $1。它是用于燃料费的代币。 ", "description": "$1 represents the name of the native token on the current network" }, "nativeTokenScamWarningConversion": { @@ -2735,6 +2982,9 @@ "networkNameBase": { "message": "基础" }, + "networkNameBitcoin": { + "message": "比特币" + }, "networkNameDefinition": { "message": "与此网络关联的名称。" }, @@ -2759,6 +3009,9 @@ "networkNameZkSyncEra": { "message": "zkSync Era" }, + "networkOptions": { + "message": "网络选项" + }, "networkProvider": { "message": "网络提供商" }, @@ -2827,6 +3080,9 @@ "newNetworkAdded": { "message": "成功添加了 “$1”!" }, + "newNetworkEdited": { + "message": "“$1” 已经成功编辑!" + }, "newNftAddedMessage": { "message": "NFT已成功添加!" }, @@ -2863,6 +3119,9 @@ "nftAlreadyAdded": { "message": "此NFT已添加。" }, + "nftAutoDetectionEnabled": { + "message": "已启用 NFT 自动检测" + }, "nftDisclaimer": { "message": "免责声明:MetaMask 从源网址中提取媒体文件。该网址有时会因铸造 NFT 的市场而改变。" }, @@ -2913,6 +3172,9 @@ "noDomainResolution": { "message": "没有提供域名解析。" }, + "noHardwareWalletOrSnapsSupport": { + "message": "Snaps 和大多数硬件钱包与您的当前浏览器版本不兼容。" + }, "noNFTs": { "message": "尚无 NFT" }, @@ -2943,8 +3205,8 @@ "nonceField": { "message": "自定义交易 nonce" }, - "nonceFieldDescription": { - "message": "打开这个功能可以改变确认屏幕上的 nonce(交易号码)。这是一个高级功能,请谨慎使用。" + "nonceFieldDesc": { + "message": "启用此功能可更改发送资产时的唯一交易标识号(交易编号)。这是一个高级功能,请谨慎使用。" }, "nonceFieldHeading": { "message": "自定义 nonce" @@ -3146,6 +3408,9 @@ "numberOfNewTokensDetectedSingular": { "message": "在此账户中找到1枚新代币" }, + "numberOfTokens": { + "message": "代币数量" + }, "ofTextNofM": { "message": "/" }, @@ -3161,8 +3426,36 @@ "on": { "message": "开" }, - "onboarding": { - "message": "入门" + "onboardedMetametricsAccept": { + "message": "我同意" + }, + "onboardedMetametricsDisagree": { + "message": "不,谢谢" + }, + "onboardedMetametricsKey1": { + "message": "最新发展动态" + }, + "onboardedMetametricsKey2": { + "message": "产品特点" + }, + "onboardedMetametricsKey3": { + "message": "其他相关宣传材料" + }, + "onboardedMetametricsLink": { + "message": "MetaMetrics" + }, + "onboardedMetametricsParagraph1": { + "message": "除了 $1,我们还想使用数据来了解您如何与营销通信交互。", + "description": "$1 represents the 'onboardedMetametricsLink' locale string" + }, + "onboardedMetametricsParagraph2": { + "message": "这可以帮助我们个性化与您分享的内容,例如:" + }, + "onboardedMetametricsParagraph3": { + "message": "请记住,我们绝不会出卖您提供的数据,而且您可以随时选择退出。" + }, + "onboardedMetametricsTitle": { + "message": "帮助我们改善您的体验" }, "onboardingAdvancedPrivacyIPFSDescription": { "message": "IPFS 网关使访问和查看第三方托管的数据成为可能。您可以添加自定义 IPFS 网关或继续使用默认网关。" @@ -3200,9 +3493,6 @@ "onboardingMetametricsDescription2": { "message": "当我们收集指标时,总是..." }, - "onboardingMetametricsDisagree": { - "message": "不,谢谢" - }, "onboardingMetametricsInfuraTerms": { "message": "如果我们决定将这些数据用于其他目的,我们会通知您。您可以查看我们的 $1 以了解更多信息。请记住,您可以随时转到设置并选择退出。", "description": "$1 represents `onboardingMetametricsInfuraTermsPolicy`" @@ -3237,6 +3527,9 @@ "onboardingMetametricsTitle": { "message": "请帮助我们改进 MetaMask" }, + "onboardingMetametricsUseDataCheckbox": { + "message": "我们将使用此数据来了解您如何与我们的营销通信交互。我们可能会分享相关资讯(例如产品特点)。" + }, "onboardingPinExtensionBillboardAccess": { "message": "完全访问权限" }, @@ -3280,6 +3573,22 @@ "message": "网络钓鱼检测提醒依赖于与 $1 的通信。jsDeliver 将有权访问您的 IP 地址。查看 $2。", "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link" }, + "oneDayAbbreviation": { + "message": "1 天", + "description": "Shortened form of '1 day'" + }, + "oneMonthAbbreviation": { + "message": "1 个月", + "description": "Shortened form of '1 month'" + }, + "oneWeekAbbreviation": { + "message": "1 周", + "description": "Shortened form of '1 week'" + }, + "oneYearAbbreviation": { + "message": "1 年", + "description": "Shortened form of '1 year'" + }, "onekey": { "message": "OneKey" }, @@ -3632,6 +3941,9 @@ "permissionsPageTourTitle": { "message": "已连接的站点现已获得许可" }, + "permitSimulationDetailInfo": { + "message": "您将授予该消费者许可从您的账户中支出这些代币。" + }, "personalAddressDetected": { "message": "检测到个人地址。请输入代币合约地址。" }, @@ -3664,6 +3976,10 @@ "popularCustomNetworks": { "message": "流行自定义网络" }, + "popularNetworkAddToolTip": { + "message": "这些网络中的其中一些依赖于第三方。此连接可能不太可靠,或使第三方可进行活动跟踪。$1", + "description": "$1 is Learn more link" + }, "portfolio": { "message": "Portfolio" }, @@ -3676,6 +3992,12 @@ "prev": { "message": "上一个" }, + "price": { + "message": "价格" + }, + "priceUnavailable": { + "message": "价格不可用" + }, "primaryCurrencySetting": { "message": "主要货币" }, @@ -3828,6 +4150,9 @@ "quoteRate": { "message": "报价" }, + "rank": { + "message": "排名" + }, "reAddAccounts": { "message": "重新添加任何其他账户" }, @@ -3997,9 +4322,6 @@ "reset": { "message": "重置" }, - "resetStates": { - "message": "重设状态" - }, "resetWallet": { "message": "重置钱包" }, @@ -4135,6 +4457,9 @@ "searchAccounts": { "message": "搜索账户" }, + "searchNfts": { + "message": "搜索 NFT" + }, "searchTokens": { "message": "搜索代币" }, @@ -4179,13 +4504,10 @@ "seedPhraseIntroRecommendedButtonCopy": { "message": "保护我的钱包(推荐)" }, - "seedPhraseIntroSidebarBulletFour": { - "message": "写下并存储在多个秘密位置。" - }, "seedPhraseIntroSidebarBulletOne": { - "message": "保存到密码管理工具" + "message": "写下并存储在多个秘密位置。" }, - "seedPhraseIntroSidebarBulletThree": { + "seedPhraseIntroSidebarBulletTwo": { "message": "安全存放在保险箱内。" }, "seedPhraseIntroSidebarCopyOne": { @@ -4269,9 +4591,6 @@ "send": { "message": "发送" }, - "sendAToken": { - "message": "发送代币" - }, "sendBugReport": { "message": "向我们发送错误报告。" }, @@ -4322,9 +4641,6 @@ "sepolia": { "message": "Sepolia测试网络" }, - "serviceWorkerKeepAlive": { - "message": "Service Worker 保持活跃" - }, "setAdvancedPrivacySettingsDetails": { "message": "MetaMask 使用这些可信的第三方服务来提高产品可用性和安全性。" }, @@ -4381,6 +4697,9 @@ "showIncomingTransactionsExplainer": { "message": "这依赖于每个网络的不同第三方 API,这些 API 会导致您的以太坊地址和 IP 地址被获悉。" }, + "showLess": { + "message": "收起" + }, "showMore": { "message": "展开" }, @@ -4411,9 +4730,6 @@ "signatureRequestGuidance": { "message": "只有在您完全理解内容并信任请求网站的情况下,才能签署此消息。" }, - "signatureRequestWarning": { - "message": "签署此消息可能会很危险。您可能会将您的账户和资产的全部控制权交给此消息另一端的一方。这意味着他们可以随时耗尽您的账户。请谨慎行事。$1。" - }, "signed": { "message": "已签名" }, @@ -4423,6 +4739,9 @@ "signing": { "message": "签名" }, + "signingInWith": { + "message": "使用以下登录方式" + }, "simulationDetailsFailed": { "message": "加载估算时出错。" }, @@ -4460,6 +4779,24 @@ "simulationsSettingSubHeader": { "message": "预计余额变化" }, + "siweIssued": { + "message": "已签发" + }, + "siweNetwork": { + "message": "网络" + }, + "siweRequestId": { + "message": "请求 ID" + }, + "siweResources": { + "message": "资源" + }, + "siweSignatureSimulationDetailInfo": { + "message": "您正在登录某个网站,并且您的账户没有预期变化。" + }, + "siweURI": { + "message": "URL" + }, "skip": { "message": "跳过" }, @@ -4563,6 +4900,14 @@ "snapAccountsDescription": { "message": "由第三方 Snap 控制的账户。" }, + "snapConnectTo": { + "message": "连接到 $1", + "description": "$1 is the website URL or a Snap name. Used for Snaps pre-approved connections." + }, + "snapConnectionPermissionDescription": { + "message": "让 $1 自动连接到 $2,无需您的批准。", + "description": "Used for Snap pre-approved connections. $1 is the Snap name, $2 is a website URL." + }, "snapConnectionWarning": { "message": "$1想连接$2。", "description": "$2 is the snap and $1 is the dapp requesting connection to the snap." @@ -4574,6 +4919,9 @@ "snapDetailWebsite": { "message": "网站" }, + "snapHomeMenu": { + "message": "Snap 主菜单" + }, "snapInstallRequest": { "message": "安装 $1 即赋予其以下许可。", "description": "$1 is the snap name." @@ -4696,6 +5044,9 @@ "source": { "message": "来源" }, + "speed": { + "message": "速度" + }, "speedUp": { "message": "加速" }, @@ -4730,6 +5081,9 @@ "spendLimitTooLarge": { "message": "消费限额过大" }, + "spender": { + "message": "消费者" + }, "spendingCap": { "message": "支出上限" }, @@ -4850,9 +5204,6 @@ "stateLogsDescription": { "message": "状态日志包含您的公共账户地址和已发送的交易。" }, - "states": { - "message": "状态" - }, "status": { "message": "状态" }, @@ -4957,6 +5308,13 @@ "submitted": { "message": "已提交" }, + "suggestedBySnap": { + "message": "由 $1 建议", + "description": "$1 is the snap name" + }, + "suggestedTokenName": { + "message": "建议名称:" + }, "suggestedTokenSymbol": { "message": "建议的股票代码:" }, @@ -5071,7 +5429,7 @@ "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." }, "swapFetchingQuotes": { - "message": "取得报价中" + "message": "获取报价中……" }, "swapFetchingQuotesErrorDescription": { "message": "呃……出错了。再试一次,如果错误仍存在,请联系客服。" @@ -5419,7 +5777,7 @@ "description": "$1 represents the account name, $2 represents the network name" }, "switchedTo": { - "message": "您已切换到" + "message": "您现在使用的是" }, "switchingNetworksCancelsPendingConfirmations": { "message": "切换网络将取消所有待处理的确认" @@ -5467,6 +5825,10 @@ "thisCollection": { "message": "这个收藏品" }, + "threeMonthsAbbreviation": { + "message": "3 个月", + "description": "Shortened form of '3 months'" + }, "time": { "message": "时间" }, @@ -5480,45 +5842,6 @@ "message": "至:$1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleEthSignBannerDescription": { - "message": "您面临网络钓鱼攻击的风险。通过关闭 eth_sign 保护自己。" - }, - "toggleEthSignDescriptionField": { - "message": "如果启用此设置,您可能会收到不可读的签名请求。通过签署一条您不理解的信息,您可能同意放弃您的资金和 NFT。" - }, - "toggleEthSignField": { - "message": "Eth_sign 请求" - }, - "toggleEthSignModalBannerBoldText": { - "message": " 您可能遭受了欺诈" - }, - "toggleEthSignModalBannerText": { - "message": "如果要求您打开此设置," - }, - "toggleEthSignModalCheckBox": { - "message": "我明白,如果我启用 eth_sign 请求,我可能失去所有资金和 NFT。 " - }, - "toggleEthSignModalDescription": { - "message": "允许 eth_sign 请求可能会使您易于受到网络钓鱼攻击。始终检查 URL,并且在签署包含代码的消息时保持谨慎。" - }, - "toggleEthSignModalFormError": { - "message": "文本不正确" - }, - "toggleEthSignModalFormLabel": { - "message": "输入“我只签署我理解的内容”以继续" - }, - "toggleEthSignModalFormValidation": { - "message": "我只签署我理解的内容" - }, - "toggleEthSignModalTitle": { - "message": "使用风险自负" - }, - "toggleEthSignOff": { - "message": "关闭(推荐)" - }, - "toggleEthSignOn": { - "message": "开启(不推荐)" - }, "toggleRequestQueueDescription": { "message": "这使您可以为每个网站选择网络,而不是为所有网站选择同一个网络。此功能将阻止您手动切换网络,这可能会破坏您在某些网站上的用户体验。" }, @@ -5546,6 +5869,9 @@ "tokenContractAddress": { "message": "代币合约地址" }, + "tokenDecimal": { + "message": "代币小数位" + }, "tokenDecimalFetchFailed": { "message": "需要代币小数位。请在下方查找:$1" }, @@ -5570,6 +5896,9 @@ "tokenShowUp": { "message": "您的代币可能不会自动显示在您的钱包中。" }, + "tokenStandard": { + "message": "代币标准" + }, "tokenSymbol": { "message": "代币符号" }, @@ -5580,6 +5909,9 @@ "message": "发现$1新代币", "description": "$1 is the number of new tokens detected" }, + "tokensInCollection": { + "message": "收藏品中的代币" + }, "tooltipApproveButton": { "message": "我理解" }, @@ -5595,6 +5927,9 @@ "total": { "message": "共计" }, + "totalVolume": { + "message": "总交易额" + }, "transaction": { "message": "交易" }, @@ -5610,6 +5945,9 @@ "transactionCreated": { "message": "在 $2 创建了值为 $1 的交易。" }, + "transactionDataFunction": { + "message": "功能" + }, "transactionDetailDappGasMoreInfo": { "message": "建议的网站" }, @@ -5700,6 +6038,10 @@ "transferFrom": { "message": "转账自" }, + "trillionAbbreviation": { + "message": "万亿", + "description": "Shortened form of 'trillion'" + }, "troubleConnectingToLedgerU2FOnFirefox": { "message": "我们在连接您的 Ledger 时遇到问题。$1", "description": "$1 is a link to the wallet connection guide;" @@ -5778,6 +6120,9 @@ "message": "U2F", "description": "A name on an API for the browser to interact with devices that support the U2F protocol. On some browsers we use it to connect MetaMask to Ledger devices." }, + "unMatchedChain": { + "message": "根据我们的记录,此 URL 与此链 ID 的已知提供者不匹配。" + }, "unapproved": { "message": "未批准" }, @@ -5829,12 +6174,21 @@ "update": { "message": "更新" }, + "updateOrEditNetworkInformations": { + "message": "更新您的信息或者" + }, "updateRequest": { "message": "更新请求" }, "updatedWithDate": { "message": "已于 $1 更新" }, + "uploadDropFile": { + "message": "将您的文件放在此处" + }, + "uploadFile": { + "message": "上传文件" + }, "urlErrorMsg": { "message": "URL 需要相应的 HTTP/HTTPS 前缀。" }, @@ -6050,6 +6404,12 @@ "whatsThis": { "message": "这是什么?" }, + "wrongChainId": { + "message": "此链 ID 与网络名称不匹配。" + }, + "wrongNetworkName": { + "message": "根据我们的记录,该网络名称可能与此链 ID 不匹配。" + }, "xOfYPending": { "message": "$1 / $2 待处理", "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" @@ -6073,8 +6433,11 @@ "yourAccounts": { "message": "您的账户" }, - "yourFundsMayBeAtRisk": { - "message": "您的资金可能面临风险" + "yourActivity": { + "message": "您的活动" + }, + "yourBalance": { + "message": "您的余额" }, "yourNFTmayBeAtRisk": { "message": "您的 NFT 可能面临风险" diff --git a/app/_locales/zh_TW/messages.json b/app/_locales/zh_TW/messages.json index d91d749ccffa..83c0fc09e565 100644 --- a/app/_locales/zh_TW/messages.json +++ b/app/_locales/zh_TW/messages.json @@ -893,9 +893,6 @@ "nonceField": { "message": "自訂交易 nonce" }, - "nonceFieldDescription": { - "message": "打開此選項來在交易確認視窗中更改 nonce (交易編號)。這是一個進階功能,請小心使用。" - }, "nonceFieldHeading": { "message": "自訂 nonce" }, @@ -1087,13 +1084,10 @@ "securityAndPrivacy": { "message": "安全&隱私" }, - "seedPhraseIntroSidebarBulletFour": { - "message": "寫下來並且存放在不同的秘密地點。" - }, "seedPhraseIntroSidebarBulletOne": { - "message": "儲存在密碼管理器裡" + "message": "寫下來並且存放在不同的秘密地點。" }, - "seedPhraseIntroSidebarBulletThree": { + "seedPhraseIntroSidebarBulletTwo": { "message": "存在安全保管箱裡。" }, "seedPhraseIntroSidebarCopyOne": { diff --git a/app/background.html b/app/background.html index a140318ba8d2..9892810f3ffa 100644 --- a/app/background.html +++ b/app/background.html @@ -4,6 +4,6 @@ - + diff --git a/app/home.html b/app/home.html index 11cfed10242e..7308064505b0 100644 --- a/app/home.html +++ b/app/home.html @@ -2,13 +2,13 @@ - + <% if (it.isMMI && !it.isTest) { %> MetaMask Institutional <% } else { %> MetaMask <% } %> - +
@@ -16,6 +16,6 @@
- + diff --git a/app/images/bnb.png b/app/images/bnb.png index 64ef1c940b82..0fc84b2c6e71 100644 Binary files a/app/images/bnb.png and b/app/images/bnb.png differ diff --git a/app/images/bnb.svg b/app/images/bnb.svg index f39dba1be163..b1f1841b84e0 100644 --- a/app/images/bnb.svg +++ b/app/images/bnb.svg @@ -1,20 +1,3 @@ - - - - - - - - - - + + diff --git a/app/images/flare-mainnet.svg b/app/images/flare-mainnet.svg index f2ec35a35668..d1d42c278f25 100644 --- a/app/images/flare-mainnet.svg +++ b/app/images/flare-mainnet.svg @@ -1 +1,13 @@ - \ No newline at end of file + + + + + + + + + + + + + diff --git a/app/images/icons/arrow-left.svg b/app/images/icons/arrow-left.svg index fafa71811f79..b725fa4acb15 100644 --- a/app/images/icons/arrow-left.svg +++ b/app/images/icons/arrow-left.svg @@ -1,3 +1,3 @@ - + diff --git a/app/images/icons/arrow-right.svg b/app/images/icons/arrow-right.svg index 95f99af30a16..ff2bd612f662 100644 --- a/app/images/icons/arrow-right.svg +++ b/app/images/icons/arrow-right.svg @@ -1,3 +1,3 @@ - + diff --git a/app/images/icons/more-horizontal.svg b/app/images/icons/more-horizontal.svg index 504ec123ad24..844ac6276f2f 100644 --- a/app/images/icons/more-horizontal.svg +++ b/app/images/icons/more-horizontal.svg @@ -1,3 +1,3 @@ - + diff --git a/app/images/icons/more-vertical.svg b/app/images/icons/more-vertical.svg index 72d8ba1647b9..d295a71996ed 100644 --- a/app/images/icons/more-vertical.svg +++ b/app/images/icons/more-vertical.svg @@ -1,3 +1,3 @@ - + diff --git a/app/images/songbird.svg b/app/images/songbird.svg index 3adced1d1d69..e82207af073d 100644 --- a/app/images/songbird.svg +++ b/app/images/songbird.svg @@ -1 +1,20 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + diff --git a/app/loading.html b/app/loading.html index 1c85bd296cc6..6319f760fb23 100644 --- a/app/loading.html +++ b/app/loading.html @@ -2,13 +2,13 @@ - <% if (it.shouldIncludeSnow) { %> - - - <% } %> MetaMask Loading + <% if (it.shouldIncludeSnow) { %> + + + <% } %> - +
@@ -50,6 +50,6 @@ />
- + diff --git a/app/popup.html b/app/popup.html index 3cb985b27a25..92806e42047a 100644 --- a/app/popup.html +++ b/app/popup.html @@ -2,9 +2,9 @@ - + MetaMask - +
@@ -12,6 +12,6 @@
- + diff --git a/app/scripts/background.js b/app/scripts/background.js index 12f0d81ac296..11a0b059a25f 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -19,6 +19,7 @@ import { ApprovalType } from '@metamask/controller-utils'; import PortStream from 'extension-port-stream'; import { ethErrors } from 'eth-rpc-errors'; +import { DIALOG_APPROVAL_TYPES } from '@metamask/snaps-rpc-methods'; import { ENVIRONMENT_TYPE_POPUP, ENVIRONMENT_TYPE_NOTIFICATION, @@ -41,10 +42,15 @@ import { checkForLastErrorAndLog } from '../../shared/modules/browser-runtime.ut import { isManifestV3 } from '../../shared/modules/mv3.utils'; import { maskObject } from '../../shared/modules/object.utils'; import { FIXTURE_STATE_METADATA_VERSION } from '../../test/e2e/default-fixture'; +import { getSocketBackgroundToMocha } from '../../test/e2e/background-socket/socket-background-to-mocha'; import { OffscreenCommunicationTarget, OffscreenCommunicationEvents, } from '../../shared/constants/offscreen-communication'; +import { + FakeLedgerBridge, + FakeTrezorBridge, +} from '../../test/stub/keyring-bridge'; import migrations from './migrations'; import Migrator from './lib/migrator'; import ExtensionPlatform from './platforms/extension'; @@ -74,6 +80,7 @@ import { createOffscreen } from './offscreen'; /* eslint-enable import/first */ import { TRIGGER_TYPES } from './controllers/metamask-notifications/constants/notification-schema'; +import { COOKIE_ID_MARKETING_WHITELIST_ORIGINS } from './constants/marketing-site-whitelist'; // eslint-disable-next-line @metamask/design-tokens/color-no-hex const BADGE_COLOR_APPROVAL = '#0376C9'; @@ -117,6 +124,7 @@ if (inTest || process.env.METAMASK_DEBUG) { } const phishingPageUrl = new URL(process.env.PHISHING_WARNING_PAGE_URL); + // normalized (adds a trailing slash to the end of the domain if it's missing) // the URL once and reuse it: const phishingPageHref = phishingPageUrl.toString(); @@ -356,16 +364,14 @@ function saveTimestamp() { * @property {object} accountsByChainId - An object mapping lower-case hex addresses to objects with "balance" and "address" keys, both storing hex string values keyed by chain id. * @property {hex} currentBlockGasLimit - The most recently seen block gas limit, in a lower case hex prefixed string. * @property {object} currentBlockGasLimitByChainId - The most recently seen block gas limit, in a lower case hex prefixed string keyed by chain id. - * @property {object} unapprovedMsgs - An object of messages pending approval, mapping a unique ID to the options. - * @property {number} unapprovedMsgCount - The number of messages in unapprovedMsgs. * @property {object} unapprovedPersonalMsgs - An object of messages pending approval, mapping a unique ID to the options. * @property {number} unapprovedPersonalMsgCount - The number of messages in unapprovedPersonalMsgs. * @property {object} unapprovedEncryptionPublicKeyMsgs - An object of messages pending approval, mapping a unique ID to the options. * @property {number} unapprovedEncryptionPublicKeyMsgCount - The number of messages in EncryptionPublicKeyMsgs. * @property {object} unapprovedDecryptMsgs - An object of messages pending approval, mapping a unique ID to the options. * @property {number} unapprovedDecryptMsgCount - The number of messages in unapprovedDecryptMsgs. - * @property {object} unapprovedTypedMsgs - An object of messages pending approval, mapping a unique ID to the options. - * @property {number} unapprovedTypedMsgCount - The number of messages in unapprovedTypedMsgs. + * @property {object} unapprovedTypedMessages - An object of messages pending approval, mapping a unique ID to the options. + * @property {number} unapprovedTypedMessagesCount - The number of messages in unapprovedTypedMessages. * @property {number} pendingApprovalCount - The number of pending request in the approval controller. * @property {Keyring[]} keyrings - An array of keyring descriptions, summarizing the accounts that are available for use, and what keyrings they belong to. * @property {string} selectedAddress - A lower case hex string of the currently selected address. @@ -396,6 +402,14 @@ async function initialize() { let isFirstMetaMaskControllerSetup; + // We only want to start this if we are running a test build, not for the release build. + // `navigator.webdriver` is true if Selenium, Puppeteer, or Playwright are running. + // In MV3, the Service Worker sees `navigator.webdriver` as `undefined`, so this will trigger from + // an Offscreen Document message instead. Because it's a singleton class, it's safe to start multiple times. + if (process.env.IN_TEST && window.navigator?.webdriver) { + getSocketBackgroundToMocha(); + } + if (isManifestV3) { // Save the timestamp immediately and then every `SAVE_TIMESTAMP_INTERVAL` // miliseconds. This keeps the service worker alive. @@ -415,10 +429,19 @@ async function initialize() { await browser.storage.session.set({ isFirstMetaMaskControllerSetup }); } + const overrides = inTest + ? { + keyrings: { + trezorBridge: FakeTrezorBridge, + ledgerBridge: FakeLedgerBridge, + }, + } + : {}; + setupController( initState, initLangCode, - {}, + overrides, isFirstMetaMaskControllerSetup, initData.meta, offscreenPromise, @@ -844,10 +867,10 @@ export function setupController( senderUrl.origin === phishingPageUrl.origin && senderUrl.pathname === phishingPageUrl.pathname ) { - const portStream = + const portStreamForPhishingPage = overrides?.getPortStream?.(remotePort) || new PortStream(remotePort); controller.setupPhishingCommunication({ - connectionStream: portStream, + connectionStream: portStreamForPhishingPage, }); } else { // this is triggered when a new tab is opened, or origin(url) is changed @@ -867,6 +890,18 @@ export function setupController( } }); } + if ( + senderUrl && + COOKIE_ID_MARKETING_WHITELIST_ORIGINS.some( + (origin) => origin === senderUrl.origin, + ) + ) { + const portStreamForCookieHandlerPage = + overrides?.getPortStream?.(remotePort) || new PortStream(remotePort); + controller.setUpCookieHandlerCommunication({ + connectionStream: portStreamForCookieHandlerPage, + }); + } connectExternalExtension(remotePort); } }; @@ -951,8 +986,6 @@ export function setupController( updateBadge, ); - controller.txController.initApprovals(); - /** * Formats a count for display as a badge label. * @@ -1082,6 +1115,7 @@ export function setupController( switch (type) { case ApprovalType.SnapDialogAlert: case ApprovalType.SnapDialogPrompt: + case DIALOG_APPROVAL_TYPES.default: controller.approvalController.accept(id, null); break; case ApprovalType.SnapDialogConfirmation: @@ -1229,6 +1263,7 @@ async function initBackground() { window.document?.documentElement?.classList.add('controller-loaded'); } } + localStore.cleanUpMostRecentRetrievedState(); } catch (error) { log.error(error); } diff --git a/app/scripts/constants/marketing-site-whitelist.ts b/app/scripts/constants/marketing-site-whitelist.ts new file mode 100644 index 000000000000..8ff0cfd2de78 --- /dev/null +++ b/app/scripts/constants/marketing-site-whitelist.ts @@ -0,0 +1,15 @@ +export const COOKIE_ID_MARKETING_WHITELIST = [ + 'https://metamask.io', + 'https://learn.metamask.io', + 'https://mmi-support.zendesk.com', + 'https://community.metamask.io', + 'https://support.metamask.io', +]; + +if (process.env.IN_TEST) { + COOKIE_ID_MARKETING_WHITELIST.push('http://127.0.0.1:8080'); +} + +// Extract the origin of each URL in the whitelist +export const COOKIE_ID_MARKETING_WHITELIST_ORIGINS = + COOKIE_ID_MARKETING_WHITELIST.map((url) => new URL(url).origin); diff --git a/app/scripts/constants/sentry-state.ts b/app/scripts/constants/sentry-state.ts index 2d55f8cac565..ae00352b4e72 100644 --- a/app/scripts/constants/sentry-state.ts +++ b/app/scripts/constants/sentry-state.ts @@ -156,6 +156,7 @@ export const SENTRY_BACKGROUND_STATE = { segmentApiCalls: false, traits: false, dataCollectionForMarketing: false, + marketingCampaignCookieId: true, }, NameController: { names: false, @@ -206,7 +207,6 @@ export const SENTRY_BACKGROUND_STATE = { PreferencesController: { advancedGasFee: true, currentLocale: true, - disabledRpcMethodPreferences: true, dismissSeedBackUpReminder: true, featureFlags: true, forgottenPassword: true, @@ -222,6 +222,7 @@ export const SENTRY_BACKGROUND_STATE = { autoLockTimeLimit: true, hideZeroBalanceTokens: true, redesignedConfirmationsEnabled: true, + redesignedTransactionsEnabled: false, isRedesignedConfirmationsDeveloperEnabled: false, showExtensionInFullSizeView: true, showFiatInTestnets: true, @@ -229,6 +230,7 @@ export const SENTRY_BACKGROUND_STATE = { smartTransactionsOptInStatus: true, useNativeCurrencyAsPrimaryCurrency: true, petnamesEnabled: true, + showConfirmationAdvancedDetails: true, }, useExternalServices: false, selectedAddress: false, @@ -261,8 +263,6 @@ export const SENTRY_BACKGROUND_STATE = { }, SelectedNetworkController: { domains: false }, SignatureController: { - unapprovedMsgCount: true, - unapprovedMsgs: false, unapprovedPersonalMsgCount: true, unapprovedPersonalMsgs: false, unapprovedTypedMessages: false, diff --git a/app/scripts/constants/stream.ts b/app/scripts/constants/stream.ts new file mode 100644 index 000000000000..8552b49357dd --- /dev/null +++ b/app/scripts/constants/stream.ts @@ -0,0 +1,17 @@ +// contexts +export const CONTENT_SCRIPT = 'metamask-contentscript'; +export const METAMASK_INPAGE = 'metamask-inpage'; + +// stream channels +export const METAMASK_PROVIDER = 'metamask-provider'; +export const METAMASK_COOKIE_HANDLER = 'metamask-cookie-handler'; +export const PHISHING_SAFELIST = 'metamask-phishing-safelist'; +export const PHISHING_STREAM = 'phishing'; + +// For more information about these legacy streams, see here: +// https://github.com/MetaMask/metamask-extension/issues/15491 +// TODO:LegacyProvider: Delete +export const LEGACY_CONTENT_SCRIPT = 'contentscript'; +export const LEGACY_INPAGE = 'inpage'; +export const LEGACY_PROVIDER = 'provider'; +export const LEGACY_PUBLIC_CONFIG = 'publicConfig'; diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index ab2483c9f9a3..d0ff7119498b 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -9,6 +9,16 @@ import { getIsBrowserPrerenderBroken, } from '../../shared/modules/browser-runtime.utils'; import shouldInjectProvider from '../../shared/modules/provider-injection'; +import { + initializeCookieHandlerSteam, + isDetectedCookieMarketingSite, +} from './streams/cookie-handler-stream'; +import { logStreamDisconnectWarning } from './streams/shared'; +import { + METAMASK_COOKIE_HANDLER, + PHISHING_STREAM, + METAMASK_PROVIDER, +} from './constants/stream'; // contexts const CONTENT_SCRIPT = 'metamask-contentscript'; @@ -73,6 +83,11 @@ function setupPhishingPageStreams() { ); phishingPageChannel = phishingPageMux.createStream(PHISHING_SAFELIST); + phishingPageMux.ignoreStream(METAMASK_COOKIE_HANDLER); + phishingPageMux.ignoreStream(LEGACY_PUBLIC_CONFIG); + phishingPageMux.ignoreStream(LEGACY_PROVIDER); + phishingPageMux.ignoreStream(METAMASK_PROVIDER); + phishingPageMux.ignoreStream(PHISHING_STREAM); } const setupPhishingExtStreams = () => { @@ -116,6 +131,11 @@ const setupPhishingExtStreams = () => { error, ), ); + phishingExtMux.ignoreStream(METAMASK_COOKIE_HANDLER); + phishingExtMux.ignoreStream(LEGACY_PUBLIC_CONFIG); + phishingExtMux.ignoreStream(LEGACY_PROVIDER); + phishingExtMux.ignoreStream(METAMASK_PROVIDER); + phishingExtMux.ignoreStream(PHISHING_STREAM); // eslint-disable-next-line no-use-before-define phishingExtPort.onDisconnect.addListener(onDisconnectDestroyPhishingStreams); @@ -213,6 +233,11 @@ const setupPageStreams = () => { ); pageChannel = pageMux.createStream(PROVIDER); + pageMux.ignoreStream(METAMASK_COOKIE_HANDLER); + pageMux.ignoreStream(LEGACY_PROVIDER); + pageMux.ignoreStream(LEGACY_PUBLIC_CONFIG); + pageMux.ignoreStream(PHISHING_SAFELIST); + pageMux.ignoreStream(PHISHING_STREAM); }; // The field below is used to ensure that replay is done only once for each restart. @@ -248,6 +273,10 @@ const setupExtensionStreams = () => { extensionPhishingStream = extensionMux.createStream('phishing'); extensionPhishingStream.once('data', redirectToPhishingWarning); + extensionMux.ignoreStream(METAMASK_COOKIE_HANDLER); + extensionMux.ignoreStream(LEGACY_PROVIDER); + extensionMux.ignoreStream(PHISHING_SAFELIST); + // eslint-disable-next-line no-use-before-define extensionPort.onDisconnect.addListener(onDisconnectDestroyStreams); }; @@ -288,6 +317,11 @@ const setupLegacyPageStreams = () => { legacyPageMux.createStream(LEGACY_PROVIDER); legacyPagePublicConfigChannel = legacyPageMux.createStream(LEGACY_PUBLIC_CONFIG); + + legacyPageMux.ignoreStream(METAMASK_COOKIE_HANDLER); + legacyPageMux.ignoreStream(METAMASK_PROVIDER); + legacyPageMux.ignoreStream(PHISHING_SAFELIST); + legacyPageMux.ignoreStream(PHISHING_STREAM); }; // TODO:LegacyProvider: Delete @@ -331,6 +365,10 @@ const setupLegacyExtensionStreams = () => { error, ), ); + legacyExtMux.ignoreStream(METAMASK_COOKIE_HANDLER); + legacyExtMux.ignoreStream(LEGACY_PROVIDER); + legacyExtMux.ignoreStream(PHISHING_SAFELIST); + legacyExtMux.ignoreStream(PHISHING_STREAM); }; /** @@ -431,19 +469,6 @@ function getNotificationTransformStream() { return stream; } -/** - * Error handler for page to extension stream disconnections - * - * @param {string} remoteLabel - Remote stream name - * @param {Error} error - Stream connection error - */ -function logStreamDisconnectWarning(remoteLabel, error) { - console.debug( - `MetaMask: Content script lost connection to "${remoteLabel}".`, - error, - ); -} - /** * The function notifies inpage when the extension stream connection is ready. When the * 'metamask_chainChanged' method is received from the extension, it implies that the @@ -525,6 +550,10 @@ const start = () => { return; } + if (isDetectedCookieMarketingSite) { + initializeCookieHandlerSteam(); + } + if (shouldInjectProvider()) { initStreams(); diff --git a/app/scripts/controllers/app-state.js b/app/scripts/controllers/app-state.js index 1a511523778f..08b11facf9ca 100644 --- a/app/scripts/controllers/app-state.js +++ b/app/scripts/controllers/app-state.js @@ -15,6 +15,8 @@ import { } from '../../../shared/constants/app'; import { DEFAULT_AUTO_LOCK_TIME_LIMIT } from '../../../shared/constants/preferences'; +/** @typedef {import('../../../shared/types/confirm').LastInteractedConfirmationInfo} LastInteractedConfirmationInfo */ + export default class AppStateController extends EventEmitter { /** * @param {object} opts @@ -73,6 +75,7 @@ export default class AppStateController extends EventEmitter { switchedNetworkDetails: null, switchedNetworkNeverShowMessage: false, currentExtensionPopupId: 0, + lastInteractedConfirmationInfo: undefined, }); this.timer = null; @@ -558,6 +561,26 @@ export default class AppStateController extends EventEmitter { }); } + /** + * The function returns information about the last confirmation user interacted with + * + * @type {LastInteractedConfirmationInfo}: Information about the last confirmation user interacted with. + */ + getLastInteractedConfirmationInfo() { + return this.store.getState().lastInteractedConfirmationInfo; + } + + /** + * Update the information about the last confirmation user interacted with + * + * @type {LastInteractedConfirmationInfo} - information about transaction user last interacted with. + */ + setLastInteractedConfirmationInfo(lastInteractedConfirmationInfo) { + this.store.updateState({ + lastInteractedConfirmationInfo, + }); + } + /** * A getter to retrieve currentPopupId saved in the appState */ diff --git a/app/scripts/controllers/app-state.test.js b/app/scripts/controllers/app-state.test.js index 82c42d46d314..c4a24a258f02 100644 --- a/app/scripts/controllers/app-state.test.js +++ b/app/scripts/controllers/app-state.test.js @@ -296,6 +296,27 @@ describe('AppStateController', () => { }); }); + describe('setLastInteractedConfirmationInfo', () => { + it('sets information about last confirmation user has interacted with', () => { + const lastInteractedConfirmationInfo = { + id: '123', + chainId: '0x1', + timestamp: new Date().getTime(), + }; + appStateController.setLastInteractedConfirmationInfo( + lastInteractedConfirmationInfo, + ); + expect(appStateController.getLastInteractedConfirmationInfo()).toBe( + lastInteractedConfirmationInfo, + ); + + appStateController.setLastInteractedConfirmationInfo(undefined); + expect(appStateController.getLastInteractedConfirmationInfo()).toBe( + undefined, + ); + }); + }); + describe('setSnapsInstallPrivacyWarningShownStatus', () => { it('updates the status of snaps install privacy warning', () => { appStateController = createAppStateController(); diff --git a/app/scripts/controllers/authentication/auth-snap-requests.ts b/app/scripts/controllers/authentication/auth-snap-requests.ts deleted file mode 100644 index 81c8beaafd40..000000000000 --- a/app/scripts/controllers/authentication/auth-snap-requests.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { SnapId } from '@metamask/snaps-sdk'; -import type { HandleSnapRequest } from '@metamask/snaps-controllers'; -import { HandlerType } from '@metamask/snaps-utils'; - -type SnapRPCRequest = Parameters[0]; - -const snapId = 'npm:@metamask/message-signing-snap' as SnapId; - -export function createSnapPublicKeyRequest(): SnapRPCRequest { - return { - snapId, - origin: '', - handler: HandlerType.OnRpcRequest, - request: { - method: 'getPublicKey', - }, - }; -} - -export function createSnapSignMessageRequest( - message: `metamask:${string}`, -): SnapRPCRequest { - return { - snapId, - origin: '', - handler: HandlerType.OnRpcRequest, - request: { - method: 'signMessage', - params: { message }, - }, - }; -} diff --git a/app/scripts/controllers/authentication/authentication-controller.test.ts b/app/scripts/controllers/authentication/authentication-controller.test.ts deleted file mode 100644 index 1cd01dfb74fc..000000000000 --- a/app/scripts/controllers/authentication/authentication-controller.test.ts +++ /dev/null @@ -1,409 +0,0 @@ -import { ControllerMessenger } from '@metamask/base-controller'; -import AuthenticationController, { - AllowedActions, - AllowedEvents, - AuthenticationControllerState, -} from './authentication-controller'; -import { - mockEndpointAccessToken, - mockEndpointGetNonce, - mockEndpointLogin, -} from './mocks/mockServices'; -import { MOCK_ACCESS_TOKEN, MOCK_LOGIN_RESPONSE } from './mocks/mockResponses'; - -const mockSignedInState = (): AuthenticationControllerState => ({ - isSignedIn: true, - sessionData: { - accessToken: 'MOCK_ACCESS_TOKEN', - expiresIn: new Date().toString(), - profile: { - identifierId: MOCK_LOGIN_RESPONSE.profile.identifier_id, - profileId: MOCK_LOGIN_RESPONSE.profile.profile_id, - }, - }, -}); - -describe('authentication/authentication-controller - constructor() tests', () => { - test('should initialize with default state', () => { - const metametrics = createMockAuthMetaMetrics(); - const controller = new AuthenticationController({ - messenger: createMockAuthenticationMessenger().messenger, - metametrics, - }); - - expect(controller.state.isSignedIn).toBe(false); - expect(controller.state.sessionData).toBeUndefined(); - }); - - test('should initialize with override state', () => { - const metametrics = createMockAuthMetaMetrics(); - const controller = new AuthenticationController({ - messenger: createMockAuthenticationMessenger().messenger, - state: mockSignedInState(), - metametrics, - }); - - expect(controller.state.isSignedIn).toBe(true); - expect(controller.state.sessionData).toBeDefined(); - }); -}); - -describe('authentication/authentication-controller - performSignIn() tests', () => { - test('Should create access token and update state', async () => { - const metametrics = createMockAuthMetaMetrics(); - const mockEndpoints = mockAuthenticationFlowEndpoints(); - const { messenger, mockSnapGetPublicKey, mockSnapSignMessage } = - createMockAuthenticationMessenger(); - - const controller = new AuthenticationController({ messenger, metametrics }); - - const result = await controller.performSignIn(); - expect(mockSnapGetPublicKey).toBeCalled(); - expect(mockSnapSignMessage).toBeCalled(); - mockEndpoints.mockGetNonceEndpoint.done(); - mockEndpoints.mockLoginEndpoint.done(); - mockEndpoints.mockAccessTokenEndpoint.done(); - expect(result).toBe(MOCK_ACCESS_TOKEN); - - // Assert - state shows user is logged in - expect(controller.state.isSignedIn).toBe(true); - expect(controller.state.sessionData).toBeDefined(); - }); - - test('Should error when nonce endpoint fails', async () => { - await testAndAssertFailingEndpoints('nonce'); - }); - - test('Should error when login endpoint fails', async () => { - await testAndAssertFailingEndpoints('login'); - }); - - test('Should error when tokens endpoint fails', async () => { - await testAndAssertFailingEndpoints('token'); - }); - - // When the wallet is locked, we are unable to call the snap - test('Should error when wallet is locked', async () => { - const { messenger, mockKeyringControllerGetState } = - createMockAuthenticationMessenger(); - const metametrics = createMockAuthMetaMetrics(); - - // Mock wallet is locked - mockKeyringControllerGetState.mockReturnValue({ isUnlocked: false }); - - const controller = new AuthenticationController({ messenger, metametrics }); - - await expect(controller.performSignIn()).rejects.toThrow(); - }); - - async function testAndAssertFailingEndpoints( - endpointFail: 'nonce' | 'login' | 'token', - ) { - const mockEndpoints = mockAuthenticationFlowEndpoints({ - endpointFail, - }); - const { messenger } = createMockAuthenticationMessenger(); - const metametrics = createMockAuthMetaMetrics(); - const controller = new AuthenticationController({ messenger, metametrics }); - - await expect(controller.performSignIn()).rejects.toThrow(); - expect(controller.state.isSignedIn).toBe(false); - - const endpointsCalled = [ - mockEndpoints.mockGetNonceEndpoint.isDone(), - mockEndpoints.mockLoginEndpoint.isDone(), - mockEndpoints.mockAccessTokenEndpoint.isDone(), - ]; - if (endpointFail === 'nonce') { - expect(endpointsCalled).toEqual([true, false, false]); - } - - if (endpointFail === 'login') { - expect(endpointsCalled).toEqual([true, true, false]); - } - - if (endpointFail === 'token') { - expect(endpointsCalled).toEqual([true, true, true]); - } - } -}); - -describe('authentication/authentication-controller - performSignOut() tests', () => { - test('Should remove signed in user and any access tokens', () => { - const metametrics = createMockAuthMetaMetrics(); - const { messenger } = createMockAuthenticationMessenger(); - const controller = new AuthenticationController({ - messenger, - state: mockSignedInState(), - metametrics, - }); - - controller.performSignOut(); - expect(controller.state.isSignedIn).toBe(false); - expect(controller.state.sessionData).toBeUndefined(); - }); - - test('Should throw error if attempting to sign out when user is not logged in', () => { - const metametrics = createMockAuthMetaMetrics(); - const { messenger } = createMockAuthenticationMessenger(); - const controller = new AuthenticationController({ - messenger, - state: { isSignedIn: false }, - metametrics, - }); - - expect(() => controller.performSignOut()).toThrow(); - }); -}); - -describe('authentication/authentication-controller - getBearerToken() tests', () => { - test('Should throw error if not logged in', async () => { - const metametrics = createMockAuthMetaMetrics(); - const { messenger } = createMockAuthenticationMessenger(); - const controller = new AuthenticationController({ - messenger, - state: { isSignedIn: false }, - metametrics, - }); - - await expect(controller.getBearerToken()).rejects.toThrow(); - }); - - test('Should return original access token in state', async () => { - const metametrics = createMockAuthMetaMetrics(); - const { messenger } = createMockAuthenticationMessenger(); - const originalState = mockSignedInState(); - const controller = new AuthenticationController({ - messenger, - state: originalState, - metametrics, - }); - - const result = await controller.getBearerToken(); - expect(result).toBeDefined(); - expect(result).toBe(originalState.sessionData?.accessToken); - }); - - test('Should return new access token if state is invalid', async () => { - const metametrics = createMockAuthMetaMetrics(); - const { messenger } = createMockAuthenticationMessenger(); - mockAuthenticationFlowEndpoints(); - - // Invalid/old state - const originalState = mockSignedInState(); - if (originalState.sessionData) { - originalState.sessionData.accessToken = 'ACCESS_TOKEN_1'; - - const d = new Date(); - d.setMinutes(d.getMinutes() - 31); // expires at 30 mins - originalState.sessionData.expiresIn = d.toString(); - } - - const controller = new AuthenticationController({ - messenger, - state: originalState, - metametrics, - }); - - const result = await controller.getBearerToken(); - expect(result).toBeDefined(); - expect(result).toBe(MOCK_ACCESS_TOKEN); - }); - - // If the state is invalid, we need to re-login. - // But as wallet is locked, we will not be able to call the snap - test('Should throw error if wallet is locked', async () => { - const metametrics = createMockAuthMetaMetrics(); - const { messenger, mockKeyringControllerGetState } = - createMockAuthenticationMessenger(); - mockAuthenticationFlowEndpoints(); - - // Invalid/old state - const originalState = mockSignedInState(); - if (originalState.sessionData) { - originalState.sessionData.accessToken = 'ACCESS_TOKEN_1'; - - const d = new Date(); - d.setMinutes(d.getMinutes() - 31); // expires at 30 mins - originalState.sessionData.expiresIn = d.toString(); - } - - // Mock wallet is locked - mockKeyringControllerGetState.mockReturnValue({ isUnlocked: false }); - - const controller = new AuthenticationController({ - messenger, - state: originalState, - metametrics, - }); - - await expect(controller.getBearerToken()).rejects.toThrow(); - }); -}); - -describe('authentication/authentication-controller - getSessionProfile() tests', () => { - test('Should throw error if not logged in', async () => { - const metametrics = createMockAuthMetaMetrics(); - const { messenger } = createMockAuthenticationMessenger(); - const controller = new AuthenticationController({ - messenger, - state: { isSignedIn: false }, - metametrics, - }); - - await expect(controller.getSessionProfile()).rejects.toThrow(); - }); - - test('Should return original access token in state', async () => { - const metametrics = createMockAuthMetaMetrics(); - const { messenger } = createMockAuthenticationMessenger(); - const originalState = mockSignedInState(); - const controller = new AuthenticationController({ - messenger, - state: originalState, - metametrics, - }); - - const result = await controller.getSessionProfile(); - expect(result).toBeDefined(); - expect(result).toEqual(originalState.sessionData?.profile); - }); - - test('Should return new access token if state is invalid', async () => { - const metametrics = createMockAuthMetaMetrics(); - const { messenger } = createMockAuthenticationMessenger(); - mockAuthenticationFlowEndpoints(); - - // Invalid/old state - const originalState = mockSignedInState(); - if (originalState.sessionData) { - originalState.sessionData.profile.identifierId = 'ID_1'; - - const d = new Date(); - d.setMinutes(d.getMinutes() - 31); // expires at 30 mins - originalState.sessionData.expiresIn = d.toString(); - } - - const controller = new AuthenticationController({ - messenger, - state: originalState, - metametrics, - }); - - const result = await controller.getSessionProfile(); - expect(result).toBeDefined(); - expect(result.identifierId).toBe(MOCK_LOGIN_RESPONSE.profile.identifier_id); - expect(result.profileId).toBe(MOCK_LOGIN_RESPONSE.profile.profile_id); - }); - - // If the state is invalid, we need to re-login. - // But as wallet is locked, we will not be able to call the snap - test('Should throw error if wallet is locked', async () => { - const metametrics = createMockAuthMetaMetrics(); - const { messenger, mockKeyringControllerGetState } = - createMockAuthenticationMessenger(); - mockAuthenticationFlowEndpoints(); - - // Invalid/old state - const originalState = mockSignedInState(); - if (originalState.sessionData) { - originalState.sessionData.profile.identifierId = 'ID_1'; - - const d = new Date(); - d.setMinutes(d.getMinutes() - 31); // expires at 30 mins - originalState.sessionData.expiresIn = d.toString(); - } - - // Mock wallet is locked - mockKeyringControllerGetState.mockReturnValue({ isUnlocked: false }); - - const controller = new AuthenticationController({ - messenger, - state: originalState, - metametrics, - }); - - await expect(controller.getSessionProfile()).rejects.toThrow(); - }); -}); - -function createAuthenticationMessenger() { - const messenger = new ControllerMessenger(); - return messenger.getRestricted({ - name: 'AuthenticationController', - allowedActions: [ - `SnapController:handleRequest`, - 'KeyringController:getState', - ], - allowedEvents: ['KeyringController:lock', 'KeyringController:unlock'], - }); -} - -function createMockAuthenticationMessenger() { - const messenger = createAuthenticationMessenger(); - const mockCall = jest.spyOn(messenger, 'call'); - const mockSnapGetPublicKey = jest.fn().mockResolvedValue('MOCK_PUBLIC_KEY'); - const mockSnapSignMessage = jest - .fn() - .mockResolvedValue('MOCK_SIGNED_MESSAGE'); - - const mockKeyringControllerGetState = jest - .fn() - .mockReturnValue({ isUnlocked: true }); - - mockCall.mockImplementation((...args) => { - const [actionType, params] = args; - if (actionType === 'SnapController:handleRequest') { - if (params?.request.method === 'getPublicKey') { - return mockSnapGetPublicKey(); - } - - if (params?.request.method === 'signMessage') { - return mockSnapSignMessage(); - } - - throw new Error( - `MOCK_FAIL - unsupported SnapController:handleRequest call: ${params?.request.method}`, - ); - } - - if (actionType === 'KeyringController:getState') { - return mockKeyringControllerGetState(); - } - - throw new Error(`MOCK_FAIL - unsupported messenger call: ${actionType}`); - }); - - return { - messenger, - mockSnapGetPublicKey, - mockSnapSignMessage, - mockKeyringControllerGetState, - }; -} - -function mockAuthenticationFlowEndpoints(params?: { - endpointFail: 'nonce' | 'login' | 'token'; -}) { - const mockGetNonceEndpoint = mockEndpointGetNonce( - params?.endpointFail === 'nonce' ? { status: 500 } : undefined, - ); - const mockLoginEndpoint = mockEndpointLogin( - params?.endpointFail === 'login' ? { status: 500 } : undefined, - ); - const mockAccessTokenEndpoint = mockEndpointAccessToken( - params?.endpointFail === 'token' ? { status: 500 } : undefined, - ); - - return { - mockGetNonceEndpoint, - mockLoginEndpoint, - mockAccessTokenEndpoint, - }; -} - -function createMockAuthMetaMetrics() { - const getMetaMetricsId = jest.fn().mockReturnValue('MOCK_METAMETRICS_ID'); - - return { getMetaMetricsId }; -} diff --git a/app/scripts/controllers/authentication/authentication-controller.ts b/app/scripts/controllers/authentication/authentication-controller.ts deleted file mode 100644 index 4bcb1528c323..000000000000 --- a/app/scripts/controllers/authentication/authentication-controller.ts +++ /dev/null @@ -1,384 +0,0 @@ -import { - BaseController, - RestrictedControllerMessenger, - StateMetadata, -} from '@metamask/base-controller'; -import type { - KeyringControllerGetStateAction, - KeyringControllerLockEvent, - KeyringControllerUnlockEvent, -} from '@metamask/keyring-controller'; -import { HandleSnapRequest } from '@metamask/snaps-controllers'; -import { - createSnapPublicKeyRequest, - createSnapSignMessageRequest, -} from './auth-snap-requests'; -import { - createLoginRawMessage, - getAccessToken, - getNonce, - login, -} from './services'; - -const THIRTY_MIN_MS = 1000 * 60 * 30; - -const controllerName = 'AuthenticationController'; - -// State -type SessionProfile = { - identifierId: string; - profileId: string; -}; - -type SessionData = { - /** profile - anonymous profile data for the given logged in user */ - profile: SessionProfile; - /** accessToken - used to make requests authorized endpoints */ - accessToken: string; - /** expiresIn - string date to determine if new access token is required */ - expiresIn: string; -}; - -type MetaMetricsAuth = { - getMetaMetricsId: () => string; -}; - -export type AuthenticationControllerState = { - /** - * Global isSignedIn state. - * Can be used to determine if "Profile Syncing" is enabled. - */ - isSignedIn: boolean; - sessionData?: SessionData; -}; -const defaultState: AuthenticationControllerState = { isSignedIn: false }; -const metadata: StateMetadata = { - isSignedIn: { - persist: true, - anonymous: true, - }, - sessionData: { - persist: true, - anonymous: false, - }, -}; - -// Messenger Actions -type CreateActionsObj = { - [K in T]: { - type: `${typeof controllerName}:${K}`; - handler: AuthenticationController[K]; - }; -}; -type ActionsObj = CreateActionsObj< - | 'performSignIn' - | 'performSignOut' - | 'getBearerToken' - | 'getSessionProfile' - | 'isSignedIn' ->; -export type Actions = ActionsObj[keyof ActionsObj]; -export type AuthenticationControllerPerformSignIn = ActionsObj['performSignIn']; -export type AuthenticationControllerPerformSignOut = - ActionsObj['performSignOut']; -export type AuthenticationControllerGetBearerToken = - ActionsObj['getBearerToken']; -export type AuthenticationControllerGetSessionProfile = - ActionsObj['getSessionProfile']; -export type AuthenticationControllerIsSignedIn = ActionsObj['isSignedIn']; - -// Allowed Actions -export type AllowedActions = - | HandleSnapRequest - | KeyringControllerGetStateAction; - -export type AllowedEvents = - | KeyringControllerLockEvent - | KeyringControllerUnlockEvent; - -// Messenger -export type AuthenticationControllerMessenger = RestrictedControllerMessenger< - typeof controllerName, - Actions | AllowedActions, - AllowedEvents, - AllowedActions['type'], - AllowedEvents['type'] ->; - -/** - * Controller that enables authentication for restricted endpoints. - * Used for Global Profile Syncing and Notifications - */ -export default class AuthenticationController extends BaseController< - typeof controllerName, - AuthenticationControllerState, - AuthenticationControllerMessenger -> { - #metametrics: MetaMetricsAuth; - - #isUnlocked = false; - - #keyringController = { - setupLockedStateSubscriptions: () => { - const { isUnlocked } = this.messagingSystem.call( - 'KeyringController:getState', - ); - this.#isUnlocked = isUnlocked; - - this.messagingSystem.subscribe('KeyringController:unlock', () => { - this.#isUnlocked = true; - }); - - this.messagingSystem.subscribe('KeyringController:lock', () => { - this.#isUnlocked = false; - }); - }, - }; - - constructor({ - messenger, - state, - metametrics, - }: { - messenger: AuthenticationControllerMessenger; - state?: AuthenticationControllerState; - /** - * Not using the Messaging System as we - * do not want to tie this strictly to extension - */ - metametrics: MetaMetricsAuth; - }) { - super({ - messenger, - metadata, - name: controllerName, - state: { ...defaultState, ...state }, - }); - - this.#metametrics = metametrics; - - this.#keyringController.setupLockedStateSubscriptions(); - this.#registerMessageHandlers(); - } - - /** - * Constructor helper for registering this controller's messaging system - * actions. - */ - #registerMessageHandlers(): void { - this.messagingSystem.registerActionHandler( - 'AuthenticationController:getBearerToken', - this.getBearerToken.bind(this), - ); - - this.messagingSystem.registerActionHandler( - 'AuthenticationController:getSessionProfile', - this.getSessionProfile.bind(this), - ); - - this.messagingSystem.registerActionHandler( - 'AuthenticationController:isSignedIn', - this.isSignedIn.bind(this), - ); - - this.messagingSystem.registerActionHandler( - 'AuthenticationController:performSignIn', - this.performSignIn.bind(this), - ); - - this.messagingSystem.registerActionHandler( - 'AuthenticationController:performSignOut', - this.performSignOut.bind(this), - ); - } - - public async performSignIn(): Promise { - const { accessToken } = await this.#performAuthenticationFlow(); - return accessToken; - } - - public performSignOut(): void { - this.#assertLoggedIn(); - - this.update((state) => { - state.isSignedIn = false; - state.sessionData = undefined; - }); - } - - public async getBearerToken(): Promise { - this.#assertLoggedIn(); - - if (this.#hasValidSession(this.state.sessionData)) { - return this.state.sessionData.accessToken; - } - - const { accessToken } = await this.#performAuthenticationFlow(); - return accessToken; - } - - /** - * Will return a session profile. - * Throws if a user is not logged in. - * - * @returns profile for the session. - */ - public async getSessionProfile(): Promise { - this.#assertLoggedIn(); - - if (this.#hasValidSession(this.state.sessionData)) { - return this.state.sessionData.profile; - } - - const { profile } = await this.#performAuthenticationFlow(); - return profile; - } - - public isSignedIn(): boolean { - return this.state.isSignedIn; - } - - #assertLoggedIn(): void { - if (!this.state.isSignedIn) { - throw new Error( - `${controllerName}: Unable to call method, user is not authenticated`, - ); - } - } - - async #performAuthenticationFlow(): Promise<{ - profile: SessionProfile; - accessToken: string; - }> { - try { - // 1. Nonce - const publicKey = await this.#snapGetPublicKey(); - const nonce = await getNonce(publicKey); - if (!nonce) { - throw new Error(`Unable to get nonce`); - } - - // 2. Login - const rawMessage = createLoginRawMessage(nonce, publicKey); - const signature = await this.#snapSignMessage(rawMessage); - const loginResponse = await login( - rawMessage, - signature, - this.#metametrics.getMetaMetricsId(), - ); - if (!loginResponse?.token) { - throw new Error(`Unable to login`); - } - - const profile: SessionProfile = { - identifierId: loginResponse.profile.identifier_id, - profileId: loginResponse.profile.profile_id, - }; - - // 3. Trade for Access Token - const accessToken = await getAccessToken(loginResponse.token); - if (!accessToken) { - throw new Error(`Unable to get Access Token`); - } - - // Update Internal State - this.update((state) => { - state.isSignedIn = true; - const expiresIn = new Date(); - expiresIn.setTime(expiresIn.getTime() + THIRTY_MIN_MS); - state.sessionData = { - profile, - accessToken, - expiresIn: expiresIn.toString(), - }; - }); - - return { - profile, - accessToken, - }; - } catch (e) { - console.error('Failed to authenticate', e); - const errorMessage = - e instanceof Error ? e.message : JSON.stringify(e ?? ''); - throw new Error( - `${controllerName}: Failed to authenticate - ${errorMessage}`, - ); - } - } - - #hasValidSession( - sessionData: SessionData | undefined, - ): sessionData is SessionData { - if (!sessionData) { - return false; - } - - const prevDate = Date.parse(sessionData.expiresIn); - if (isNaN(prevDate)) { - return false; - } - - const currentDate = new Date(); - const diffMs = Math.abs(currentDate.getTime() - prevDate); - - return THIRTY_MIN_MS > diffMs; - } - - #_snapPublicKeyCache: string | undefined; - - /** - * Returns the auth snap public key. - * - * @returns The snap public key. - */ - async #snapGetPublicKey(): Promise { - if (this.#_snapPublicKeyCache) { - return this.#_snapPublicKeyCache; - } - - if (!this.#isUnlocked) { - throw new Error( - '#snapGetPublicKey - unable to call snap, wallet is locked', - ); - } - - const result = (await this.messagingSystem.call( - 'SnapController:handleRequest', - createSnapPublicKeyRequest(), - )) as string; - - this.#_snapPublicKeyCache = result; - - return result; - } - - #_snapSignMessageCache: Record<`metamask:${string}`, string> = {}; - - /** - * Signs a specific message using an underlying auth snap. - * - * @param message - A specific tagged message to sign. - * @returns A Signature created by the snap. - */ - async #snapSignMessage(message: `metamask:${string}`): Promise { - if (this.#_snapSignMessageCache[message]) { - return this.#_snapSignMessageCache[message]; - } - - if (!this.#isUnlocked) { - throw new Error( - '#snapSignMessage - unable to call snap, wallet is locked', - ); - } - - const result = (await this.messagingSystem.call( - 'SnapController:handleRequest', - createSnapSignMessageRequest(message), - )) as string; - - this.#_snapSignMessageCache[message] = result; - - return result; - } -} diff --git a/app/scripts/controllers/authentication/mocks/mockResponses.ts b/app/scripts/controllers/authentication/mocks/mockResponses.ts deleted file mode 100644 index 4882bd81d812..000000000000 --- a/app/scripts/controllers/authentication/mocks/mockResponses.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { - AUTH_LOGIN_ENDPOINT, - AUTH_NONCE_ENDPOINT, - LoginResponse, - NonceResponse, - OAuthTokenResponse, - OIDC_TOKENS_ENDPOINT, -} from '../services'; - -type MockResponse = { - url: string; - requestMethod: 'GET' | 'POST' | 'PUT'; - response: unknown; -}; - -export const MOCK_NONCE = '4cbfqzoQpcNxVImGv'; -export const MOCK_NONCE_RESPONSE: NonceResponse = { - nonce: MOCK_NONCE, -}; - -export function getMockAuthNonceResponse() { - return { - url: AUTH_NONCE_ENDPOINT, - requestMethod: 'GET', - response: MOCK_NONCE_RESPONSE, - } satisfies MockResponse; -} - -export const MOCK_JWT = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'; -export const MOCK_LOGIN_RESPONSE: LoginResponse = { - token: MOCK_JWT, - expires_in: new Date().toString(), - profile: { - identifier_id: 'MOCK_IDENTIFIER', - profile_id: 'MOCK_PROFILE_ID', - }, -}; - -export function getMockAuthLoginResponse() { - return { - url: AUTH_LOGIN_ENDPOINT, - requestMethod: 'POST', - response: MOCK_LOGIN_RESPONSE, - } satisfies MockResponse; -} - -export const MOCK_ACCESS_TOKEN = `MOCK_ACCESS_TOKEN-${MOCK_JWT}`; -export const MOCK_OATH_TOKEN_RESPONSE: OAuthTokenResponse = { - access_token: MOCK_ACCESS_TOKEN, - expires_in: new Date().getTime(), -}; - -export function getMockAuthAccessTokenResponse() { - return { - url: OIDC_TOKENS_ENDPOINT, - requestMethod: 'POST', - response: MOCK_OATH_TOKEN_RESPONSE, - } satisfies MockResponse; -} diff --git a/app/scripts/controllers/authentication/mocks/mockServices.ts b/app/scripts/controllers/authentication/mocks/mockServices.ts deleted file mode 100644 index 69d3cf56c5d5..000000000000 --- a/app/scripts/controllers/authentication/mocks/mockServices.ts +++ /dev/null @@ -1,42 +0,0 @@ -import nock from 'nock'; -import { - getMockAuthAccessTokenResponse, - getMockAuthLoginResponse, - getMockAuthNonceResponse, -} from './mockResponses'; - -type MockReply = { - status: nock.StatusCode; - body?: nock.Body; -}; - -export function mockEndpointGetNonce(mockReply?: MockReply) { - const mockResponse = getMockAuthNonceResponse(); - const reply = mockReply ?? { status: 200, body: mockResponse.response }; - const mockNonceEndpoint = nock(mockResponse.url) - .get('') - .query(true) - .reply(reply.status, reply.body); - - return mockNonceEndpoint; -} - -export function mockEndpointLogin(mockReply?: MockReply) { - const mockResponse = getMockAuthLoginResponse(); - const reply = mockReply ?? { status: 200, body: mockResponse.response }; - const mockLoginEndpoint = nock(mockResponse.url) - .post('') - .reply(reply.status, reply.body); - - return mockLoginEndpoint; -} - -export function mockEndpointAccessToken(mockReply?: MockReply) { - const mockResponse = getMockAuthAccessTokenResponse(); - const reply = mockReply ?? { status: 200, body: mockResponse.response }; - const mockOidcTokensEndpoint = nock(mockResponse.url) - .post('') - .reply(reply.status, reply.body); - - return mockOidcTokensEndpoint; -} diff --git a/app/scripts/controllers/authentication/services.test.ts b/app/scripts/controllers/authentication/services.test.ts deleted file mode 100644 index 759b3c535936..000000000000 --- a/app/scripts/controllers/authentication/services.test.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { MOCK_ACCESS_TOKEN, MOCK_JWT, MOCK_NONCE } from './mocks/mockResponses'; -import { - mockEndpointAccessToken, - mockEndpointGetNonce, - mockEndpointLogin, -} from './mocks/mockServices'; -import { - createLoginRawMessage, - getAccessToken, - getNonce, - login, -} from './services'; - -const MOCK_METAMETRICS_ID = '0x123'; - -describe('authentication/services.ts - getNonce() tests', () => { - test('returns nonce on valid request', async () => { - const mockNonceEndpoint = mockEndpointGetNonce(); - const response = await getNonce('MOCK_PUBLIC_KEY'); - - mockNonceEndpoint.done(); - expect(response).toBe(MOCK_NONCE); - }); - - test('returns null if request is invalid', async () => { - async function testInvalidResponse( - status: number, - body: Record, - ) { - const mockNonceEndpoint = mockEndpointGetNonce({ status, body }); - const response = await getNonce('MOCK_PUBLIC_KEY'); - - mockNonceEndpoint.done(); - expect(response).toBe(null); - } - - await testInvalidResponse(500, { error: 'mock server error' }); - await testInvalidResponse(400, { error: 'mock bad request' }); - }); -}); - -describe('authentication/services.ts - login() tests', () => { - test('returns single-use jwt if successful login', async () => { - const mockLoginEndpoint = mockEndpointLogin(); - const response = await login( - 'mock raw message', - 'mock signature', - MOCK_METAMETRICS_ID, - ); - - mockLoginEndpoint.done(); - expect(response?.token).toBe(MOCK_JWT); - expect(response?.profile).toBeDefined(); - }); - - test('returns null if request is invalid', async () => { - async function testInvalidResponse( - status: number, - body: Record, - ) { - const mockLoginEndpoint = mockEndpointLogin({ status, body }); - const response = await login( - 'mock raw message', - 'mock signature', - MOCK_METAMETRICS_ID, - ); - - mockLoginEndpoint.done(); - expect(response).toBe(null); - } - - await testInvalidResponse(500, { error: 'mock server error' }); - await testInvalidResponse(400, { error: 'mock bad request' }); - }); -}); - -describe('authentication/services.ts - getAccessToken() tests', () => { - test('returns access token jwt if successful OIDC token request', async () => { - const mockLoginEndpoint = mockEndpointAccessToken(); - const response = await getAccessToken('mock single-use jwt'); - - mockLoginEndpoint.done(); - expect(response).toBe(MOCK_ACCESS_TOKEN); - }); - - test('returns null if request is invalid', async () => { - async function testInvalidResponse( - status: number, - body: Record, - ) { - const mockLoginEndpoint = mockEndpointAccessToken({ status, body }); - const response = await getAccessToken('mock single-use jwt'); - - mockLoginEndpoint.done(); - expect(response).toBe(null); - } - - await testInvalidResponse(500, { error: 'mock server error' }); - await testInvalidResponse(400, { error: 'mock bad request' }); - }); -}); - -describe('authentication/services.ts - createLoginRawMessage() tests', () => { - test('creates the raw message format for login request', () => { - const message = createLoginRawMessage('NONCE', 'PUBLIC_KEY'); - expect(message).toBe('metamask:NONCE:PUBLIC_KEY'); - }); -}); diff --git a/app/scripts/controllers/authentication/services.ts b/app/scripts/controllers/authentication/services.ts deleted file mode 100644 index a4bbbf2f11cb..000000000000 --- a/app/scripts/controllers/authentication/services.ts +++ /dev/null @@ -1,120 +0,0 @@ -const AUTH_ENDPOINT = process.env.AUTH_API || ''; -export const AUTH_NONCE_ENDPOINT = `${AUTH_ENDPOINT}/api/v2/nonce`; -export const AUTH_LOGIN_ENDPOINT = `${AUTH_ENDPOINT}/api/v2/srp/login`; - -const OIDC_ENDPOINT = process.env.OIDC_API || ''; -export const OIDC_TOKENS_ENDPOINT = `${OIDC_ENDPOINT}/oauth2/token`; -const OIDC_CLIENT_ID = process.env.OIDC_CLIENT_ID || ''; -const OIDC_GRANT_TYPE = process.env.OIDC_GRANT_TYPE || ''; - -export type NonceResponse = { - nonce: string; -}; -export async function getNonce(publicKey: string): Promise { - const nonceUrl = new URL(AUTH_NONCE_ENDPOINT); - nonceUrl.searchParams.set('identifier', publicKey); - - try { - const nonceResponse = await fetch(nonceUrl.toString()); - if (!nonceResponse.ok) { - return null; - } - - const nonceJson: NonceResponse = await nonceResponse.json(); - return nonceJson?.nonce ?? null; - } catch (e) { - console.error('authentication-controller/services: unable to get nonce', e); - return null; - } -} - -export type LoginResponse = { - token: string; - expires_in: string; - /** - * Contains anonymous information about the logged in profile. - * - * @property identifier_id - a deterministic unique identifier on the method used to sign in - * @property profile_id - a unique id for a given profile - * @property metametrics_id - an anonymous server id - */ - profile: { - identifier_id: string; - profile_id: string; - }; -}; -export async function login( - rawMessage: string, - signature: string, - clientMetaMetricsId: string, -): Promise { - try { - const response = await fetch(AUTH_LOGIN_ENDPOINT, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - signature, - raw_message: rawMessage, - metametrics: { - metametrics_id: clientMetaMetricsId, - agent: 'extension', - }, - }), - }); - - if (!response.ok) { - return null; - } - - const loginResponse: LoginResponse = await response.json(); - return loginResponse ?? null; - } catch (e) { - console.error('authentication-controller/services: unable to login', e); - return null; - } -} - -export type OAuthTokenResponse = { - access_token: string; - expires_in: number; -}; -export async function getAccessToken(jwtToken: string): Promise { - const headers = new Headers({ - 'Content-Type': 'application/x-www-form-urlencoded', - }); - - const urlEncodedBody = new URLSearchParams(); - urlEncodedBody.append('grant_type', OIDC_GRANT_TYPE); - urlEncodedBody.append('client_id', OIDC_CLIENT_ID); - urlEncodedBody.append('assertion', jwtToken); - - try { - const response = await fetch(OIDC_TOKENS_ENDPOINT, { - method: 'POST', - headers, - body: urlEncodedBody.toString(), - }); - - if (!response.ok) { - return null; - } - - const accessTokenResponse: OAuthTokenResponse = await response.json(); - return accessTokenResponse?.access_token ?? null; - } catch (e) { - console.error( - 'authentication-controller/services: unable to get access token', - e, - ); - return null; - } -} - -export function createLoginRawMessage( - nonce: string, - publicKey: string, -): `metamask:${string}:${string}` { - return `metamask:${nonce}:${publicKey}` as const; -} diff --git a/app/scripts/controllers/bridge.test.ts b/app/scripts/controllers/bridge.test.ts deleted file mode 100644 index a6001f7aa0d7..000000000000 --- a/app/scripts/controllers/bridge.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import BridgeController from './bridge'; - -const EMPTY_INIT_STATE = { - bridgeState: { - bridgeFeatureFlags: { - extensionSupport: false, - }, - }, -}; - -describe('BridgeController', function () { - let bridgeController: BridgeController; - - beforeAll(function () { - bridgeController = new BridgeController(); - }); - - it('constructor should setup correctly', function () { - expect(bridgeController.store.getState()).toStrictEqual(EMPTY_INIT_STATE); - }); - - it('setBridgeFeatureFlags should set the bridge feature flags', function () { - const featureFlagsResponse = { extensionSupport: true }; - bridgeController.setBridgeFeatureFlags(featureFlagsResponse); - expect( - bridgeController.store.getState().bridgeState.bridgeFeatureFlags, - ).toStrictEqual(featureFlagsResponse); - }); -}); diff --git a/app/scripts/controllers/bridge.ts b/app/scripts/controllers/bridge.ts deleted file mode 100644 index 23323371ea4c..000000000000 --- a/app/scripts/controllers/bridge.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { ObservableStore } from '@metamask/obs-store'; - -export enum BridgeFeatureFlagsKey { - EXTENSION_SUPPORT = 'extensionSupport', -} - -export type BridgeFeatureFlags = { - [BridgeFeatureFlagsKey.EXTENSION_SUPPORT]: boolean; -}; - -const initialState = { - bridgeState: { - bridgeFeatureFlags: { - [BridgeFeatureFlagsKey.EXTENSION_SUPPORT]: false, - }, - }, -}; - -export default class BridgeController { - store = new ObservableStore(initialState); - - resetState = () => { - this.store.updateState({ - bridgeState: { - ...initialState.bridgeState, - }, - }); - }; - - setBridgeFeatureFlags = (bridgeFeatureFlags: BridgeFeatureFlags) => { - const { bridgeState } = this.store.getState(); - this.store.updateState({ - bridgeState: { ...bridgeState, bridgeFeatureFlags }, - }); - }; -} diff --git a/app/scripts/controllers/bridge/bridge-controller.test.ts b/app/scripts/controllers/bridge/bridge-controller.test.ts new file mode 100644 index 000000000000..7ccad836e2b6 --- /dev/null +++ b/app/scripts/controllers/bridge/bridge-controller.test.ts @@ -0,0 +1,42 @@ +import nock from 'nock'; +import { BRIDGE_API_BASE_URL } from '../../../../shared/constants/bridge'; +import BridgeController from './bridge-controller'; +import { BridgeControllerMessenger } from './types'; +import { DEFAULT_BRIDGE_CONTROLLER_STATE } from './constants'; + +const EMPTY_INIT_STATE = { + bridgeState: DEFAULT_BRIDGE_CONTROLLER_STATE, +}; + +const messengerMock = { + call: jest.fn(), + registerActionHandler: jest.fn(), + registerInitialEventPayload: jest.fn(), + publish: jest.fn(), +} as unknown as jest.Mocked; + +describe('BridgeController', function () { + let bridgeController: BridgeController; + + beforeAll(function () { + bridgeController = new BridgeController({ messenger: messengerMock }); + }); + + it('constructor should setup correctly', function () { + expect(bridgeController.state).toStrictEqual(EMPTY_INIT_STATE); + }); + + it('setBridgeFeatureFlags should fetch and set the bridge feature flags', async function () { + nock(BRIDGE_API_BASE_URL).get('/getAllFeatureFlags').reply(200, { + 'extension-support': true, + }); + expect(bridgeController.state.bridgeState.bridgeFeatureFlags).toStrictEqual( + { extensionSupport: false }, + ); + + await bridgeController.setBridgeFeatureFlags(); + expect(bridgeController.state.bridgeState.bridgeFeatureFlags).toStrictEqual( + { extensionSupport: true }, + ); + }); +}); diff --git a/app/scripts/controllers/bridge/bridge-controller.ts b/app/scripts/controllers/bridge/bridge-controller.ts new file mode 100644 index 000000000000..f899879605cf --- /dev/null +++ b/app/scripts/controllers/bridge/bridge-controller.ts @@ -0,0 +1,50 @@ +import { BaseController, StateMetadata } from '@metamask/base-controller'; +import { fetchBridgeFeatureFlags } from '../../../../ui/pages/bridge/bridge.util'; +import { + BRIDGE_CONTROLLER_NAME, + DEFAULT_BRIDGE_CONTROLLER_STATE, +} from './constants'; +import { BridgeControllerState, BridgeControllerMessenger } from './types'; + +const metadata: StateMetadata<{ bridgeState: BridgeControllerState }> = { + bridgeState: { + persist: false, + anonymous: false, + }, +}; + +export default class BridgeController extends BaseController< + typeof BRIDGE_CONTROLLER_NAME, + { bridgeState: BridgeControllerState }, + BridgeControllerMessenger +> { + constructor({ messenger }: { messenger: BridgeControllerMessenger }) { + super({ + name: BRIDGE_CONTROLLER_NAME, + metadata, + messenger, + state: { bridgeState: DEFAULT_BRIDGE_CONTROLLER_STATE }, + }); + + this.messagingSystem.registerActionHandler( + `${BRIDGE_CONTROLLER_NAME}:setBridgeFeatureFlags`, + this.setBridgeFeatureFlags.bind(this), + ); + } + + resetState = () => { + this.update((_state) => { + _state.bridgeState = { + ...DEFAULT_BRIDGE_CONTROLLER_STATE, + }; + }); + }; + + setBridgeFeatureFlags = async () => { + const { bridgeState } = this.state; + const bridgeFeatureFlags = await fetchBridgeFeatureFlags(); + this.update((_state) => { + _state.bridgeState = { ...bridgeState, bridgeFeatureFlags }; + }); + }; +} diff --git a/app/scripts/controllers/bridge/constants.ts b/app/scripts/controllers/bridge/constants.ts new file mode 100644 index 000000000000..b69f529bd339 --- /dev/null +++ b/app/scripts/controllers/bridge/constants.ts @@ -0,0 +1,9 @@ +import { BridgeControllerState, BridgeFeatureFlagsKey } from './types'; + +export const BRIDGE_CONTROLLER_NAME = 'BridgeController'; + +export const DEFAULT_BRIDGE_CONTROLLER_STATE: BridgeControllerState = { + bridgeFeatureFlags: { + [BridgeFeatureFlagsKey.EXTENSION_SUPPORT]: false, + }, +}; diff --git a/app/scripts/controllers/bridge/types.ts b/app/scripts/controllers/bridge/types.ts new file mode 100644 index 000000000000..1ab02b07f7a7 --- /dev/null +++ b/app/scripts/controllers/bridge/types.ts @@ -0,0 +1,46 @@ +import { + ControllerStateChangeEvent, + RestrictedControllerMessenger, +} from '@metamask/base-controller'; +import BridgeController from './bridge-controller'; +import { BRIDGE_CONTROLLER_NAME } from './constants'; + +export enum BridgeFeatureFlagsKey { + EXTENSION_SUPPORT = 'extensionSupport', +} + +export type BridgeFeatureFlags = { + [BridgeFeatureFlagsKey.EXTENSION_SUPPORT]: boolean; +}; + +export type BridgeControllerState = { + bridgeFeatureFlags: BridgeFeatureFlags; +}; +export enum BridgeBackgroundAction { + SET_FEATURE_FLAGS = 'setBridgeFeatureFlags', +} + +type BridgeControllerAction = { + type: `${typeof BRIDGE_CONTROLLER_NAME}:${FunctionName}`; + handler: BridgeController[FunctionName]; +}; + +// Maps to BridgeController function names +type BridgeControllerActions = + BridgeControllerAction; + +type BridgeControllerEvents = ControllerStateChangeEvent< + typeof BRIDGE_CONTROLLER_NAME, + BridgeControllerState +>; + +/** + * The messenger for the BridgeController. + */ +export type BridgeControllerMessenger = RestrictedControllerMessenger< + typeof BRIDGE_CONTROLLER_NAME, + BridgeControllerActions, + BridgeControllerEvents, + never, + never +>; diff --git a/app/scripts/controllers/metamask-notifications/metamask-notifications.test.ts b/app/scripts/controllers/metamask-notifications/metamask-notifications.test.ts index c96ccfe2e989..125b7f965bac 100644 --- a/app/scripts/controllers/metamask-notifications/metamask-notifications.test.ts +++ b/app/scripts/controllers/metamask-notifications/metamask-notifications.test.ts @@ -7,16 +7,9 @@ import { } from '@metamask/keyring-controller'; import { waitFor } from '@testing-library/react'; import { - AuthenticationControllerGetBearerToken, - AuthenticationControllerIsSignedIn, -} from '../authentication/authentication-controller'; -import { MOCK_ACCESS_TOKEN } from '../authentication/mocks/mockResponses'; -import { - UserStorageControllerGetStorageKey, - UserStorageControllerPerformGetStorage, - UserStorageControllerPerformSetStorage, - UserStorageControllerEnableProfileSyncing, -} from '../user-storage/user-storage-controller'; + AuthenticationController, + UserStorageController, +} from '@metamask/profile-sync-controller'; import { PushPlatformNotificationsControllerEnablePushNotifications, PushPlatformNotificationsControllerDisablePushNotifications, @@ -50,6 +43,8 @@ import * as OnChainNotifications from './services/onchain-notifications'; import { UserStorage } from './types/user-storage/user-storage'; import * as MetamaskNotificationsUtils from './utils/utils'; +const AuthMocks = AuthenticationController.Mocks; + // Mock type used for testing purposes // eslint-disable-next-line @typescript-eslint/no-explicit-any type MockVar = any; @@ -654,12 +649,14 @@ function mockNotificationMessenger() { typedMockAction().mockResolvedValue([]); const mockGetBearerToken = - typedMockAction().mockResolvedValue( - MOCK_ACCESS_TOKEN, + typedMockAction().mockResolvedValue( + AuthMocks.MOCK_ACCESS_TOKEN, ); const mockIsSignedIn = - typedMockAction().mockReturnValue(true); + typedMockAction().mockReturnValue( + true, + ); const mockDisablePushNotifications = typedMockAction(); @@ -671,20 +668,20 @@ function mockNotificationMessenger() { typedMockAction(); const mockGetStorageKey = - typedMockAction().mockResolvedValue( + typedMockAction().mockResolvedValue( 'MOCK_STORAGE_KEY', ); const mockEnableProfileSyncing = - typedMockAction(); + typedMockAction(); const mockPerformGetStorage = - typedMockAction().mockResolvedValue( + typedMockAction().mockResolvedValue( JSON.stringify(createMockFullUserStorage()), ); const mockPerformSetStorage = - typedMockAction(); + typedMockAction(); jest.spyOn(messenger, 'call').mockImplementation((...args) => { const [actionType] = args; @@ -746,10 +743,7 @@ function mockNotificationMessenger() { return { isUnlocked: true } as MockVar; } - function exhaustedMessengerMocks(action: never) { - return new Error(`MOCK_FAIL - unsupported messenger call: ${action}`); - } - throw exhaustedMessengerMocks(actionType); + throw new Error(`MOCK_FAIL - unsupported messenger call: ${actionType}`); }); return { diff --git a/app/scripts/controllers/metamask-notifications/metamask-notifications.ts b/app/scripts/controllers/metamask-notifications/metamask-notifications.ts index 5e63669f6cd5..a2fba0fe3cef 100644 --- a/app/scripts/controllers/metamask-notifications/metamask-notifications.ts +++ b/app/scripts/controllers/metamask-notifications/metamask-notifications.ts @@ -14,21 +14,15 @@ import { KeyringControllerUnlockEvent, } from '@metamask/keyring-controller'; import { - AuthenticationControllerGetBearerToken, - AuthenticationControllerIsSignedIn, -} from '../authentication/authentication-controller'; + AuthenticationController, + UserStorageController, +} from '@metamask/profile-sync-controller'; import { PushPlatformNotificationsControllerEnablePushNotifications, PushPlatformNotificationsControllerDisablePushNotifications, PushPlatformNotificationsControllerUpdateTriggerPushNotifications, PushPlatformNotificationsControllerOnNewNotificationEvent, } from '../push-platform-notifications/push-platform-notifications'; -import { - UserStorageControllerEnableProfileSyncing, - UserStorageControllerGetStorageKey, - UserStorageControllerPerformGetStorage, - UserStorageControllerPerformSetStorage, -} from '../user-storage/user-storage-controller'; import { TRIGGER_TYPES, TRIGGER_TYPES_GROUPS, @@ -200,18 +194,19 @@ export type AllowedActions = | KeyringControllerGetAccountsAction | KeyringControllerGetStateAction // Auth Controller Requests - | AuthenticationControllerGetBearerToken - | AuthenticationControllerIsSignedIn + | AuthenticationController.AuthenticationControllerGetBearerToken + | AuthenticationController.AuthenticationControllerIsSignedIn // User Storage Controller Requests - | UserStorageControllerEnableProfileSyncing - | UserStorageControllerGetStorageKey - | UserStorageControllerPerformGetStorage - | UserStorageControllerPerformSetStorage + | UserStorageController.UserStorageControllerEnableProfileSyncing + | UserStorageController.UserStorageControllerGetStorageKey + | UserStorageController.UserStorageControllerPerformGetStorage + | UserStorageController.UserStorageControllerPerformSetStorage // Push Notifications Controller Requests | PushPlatformNotificationsControllerEnablePushNotifications | PushPlatformNotificationsControllerDisablePushNotifications | PushPlatformNotificationsControllerUpdateTriggerPushNotifications; +// Events export type Events = | MetamaskNotificationsControllerNotificationsListUpdatedEvent | MetamaskNotificationsControllerMarkNotificationsAsRead; @@ -294,13 +289,13 @@ export class MetamaskNotificationsController extends BaseController< getNotificationStorage: async () => { return await this.messagingSystem.call( 'UserStorageController:performGetStorage', - 'notification_settings', + 'notifications.notification_settings', ); }, setNotificationStorage: async (state: string) => { return await this.messagingSystem.call( 'UserStorageController:performSetStorage', - 'notification_settings', + 'notifications.notification_settings', state, ); }, diff --git a/app/scripts/controllers/metamask-notifications/services/onchain-notifications.ts b/app/scripts/controllers/metamask-notifications/services/onchain-notifications.ts index a43e678ac798..d4a95bf1aa4e 100644 --- a/app/scripts/controllers/metamask-notifications/services/onchain-notifications.ts +++ b/app/scripts/controllers/metamask-notifications/services/onchain-notifications.ts @@ -1,4 +1,5 @@ import log from 'loglevel'; +import { UserStorageController } from '@metamask/profile-sync-controller'; import type { UserStorage } from '../types/user-storage/user-storage'; import type { OnChainRawNotification } from '../types/on-chain-notification/on-chain-notification'; import { @@ -8,7 +9,8 @@ import { } from '../utils/utils'; import { TRIGGER_TYPES } from '../constants/notification-schema'; import type { components } from '../types/on-chain-notification/schema'; -import { createSHA256Hash } from '../../user-storage/encryption'; + +const { createSHA256Hash } = UserStorageController; export type NotificationTrigger = { id: string; diff --git a/app/scripts/controllers/metametrics.js b/app/scripts/controllers/metametrics.js index 8aafa0893d53..5fa34ac0cbd1 100644 --- a/app/scripts/controllers/metametrics.js +++ b/app/scripts/controllers/metametrics.js @@ -155,6 +155,7 @@ export default class MetaMetricsController { participateInMetaMetrics: null, metaMetricsId: null, dataCollectionForMarketing: null, + marketingCampaignCookieId: null, eventsBeforeMetricsOptIn: [], traits: {}, previousUserTraits: {}, @@ -466,6 +467,8 @@ export default class MetaMetricsController { if (participateInMetaMetrics) { this.trackEventsAfterMetricsOptIn(); this.clearEventsAfterMetricsOptIn(); + } else if (this.state.marketingCampaignCookieId) { + this.setMarketingCampaignCookieId(null); } ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) @@ -477,10 +480,20 @@ export default class MetaMetricsController { setDataCollectionForMarketing(dataCollectionForMarketing) { const { metaMetricsId } = this.state; + this.store.updateState({ dataCollectionForMarketing }); + + if (!dataCollectionForMarketing && this.state.marketingCampaignCookieId) { + this.setMarketingCampaignCookieId(null); + } + return metaMetricsId; } + setMarketingCampaignCookieId(marketingCampaignCookieId) { + this.store.updateState({ marketingCampaignCookieId }); + } + get state() { return this.store.getState(); } @@ -704,6 +717,7 @@ export default class MetaMetricsController { userAgent: window.navigator.userAgent, page, referrer, + marketingCampaignCookieId: this.state.marketingCampaignCookieId, }; } diff --git a/app/scripts/controllers/metametrics.test.js b/app/scripts/controllers/metametrics.test.js index 4206de67a7fd..1df31a8d2f31 100644 --- a/app/scripts/controllers/metametrics.test.js +++ b/app/scripts/controllers/metametrics.test.js @@ -17,6 +17,7 @@ const VERSION = '0.0.1-test'; const FAKE_CHAIN_ID = '0x1338'; const LOCALE = 'en_US'; const TEST_META_METRICS_ID = '0xabc'; +const TEST_GA_COOKIE_ID = '123456.123455'; const DUMMY_ACTION_ID = 'DUMMY_ACTION_ID'; const MOCK_EXTENSION_ID = 'testid'; @@ -50,6 +51,7 @@ const DEFAULT_TEST_CONTEXT = { page: METAMETRICS_BACKGROUND_PAGE_OBJECT, referrer: undefined, userAgent: window.navigator.userAgent, + marketingCampaignCookieId: null, }; const DEFAULT_SHARED_PROPERTIES = { @@ -113,6 +115,7 @@ const SAMPLE_NON_PERSISTED_EVENT = { function getMetaMetricsController({ participateInMetaMetrics = true, metaMetricsId = TEST_META_METRICS_ID, + marketingCampaignCookieId = null, preferencesStore = getMockPreferencesStore(), getCurrentChainId = () => FAKE_CHAIN_ID, onNetworkDidChange = () => { @@ -130,6 +133,7 @@ function getMetaMetricsController({ initState: { participateInMetaMetrics, metaMetricsId, + marketingCampaignCookieId, fragments: { testid: SAMPLE_PERSISTED_EVENT, testid2: SAMPLE_NON_PERSISTED_EVENT, @@ -160,6 +164,9 @@ describe('MetaMetricsController', function () { expect(metaMetricsController.state.metaMetricsId).toStrictEqual( TEST_META_METRICS_ID, ); + expect( + metaMetricsController.state.marketingCampaignCookieId, + ).toStrictEqual(null); expect(metaMetricsController.locale).toStrictEqual( LOCALE.replace('_', '-'), ); @@ -339,6 +346,21 @@ describe('MetaMetricsController', function () { TEST_META_METRICS_ID, ); }); + it('should nullify the marketingCampaignCookieId when participateInMetaMetrics is toggled off', async function () { + const metaMetricsController = getMetaMetricsController({ + participateInMetaMetrics: true, + metaMetricsId: TEST_META_METRICS_ID, + dataCollectionForMarketing: true, + marketingCampaignCookieId: TEST_GA_COOKIE_ID, + }); + expect( + metaMetricsController.state.marketingCampaignCookieId, + ).toStrictEqual(TEST_GA_COOKIE_ID); + await metaMetricsController.setParticipateInMetaMetrics(false); + expect( + metaMetricsController.state.marketingCampaignCookieId, + ).toStrictEqual(null); + }); }); describe('submitEvent', function () { @@ -1252,7 +1274,65 @@ describe('MetaMetricsController', function () { expect(Object.keys(segmentApiCalls).length === 0).toStrictEqual(true); }); }); - + describe('setMarketingCampaignCookieId', function () { + it('should update marketingCampaignCookieId in the context when cookieId is available', async function () { + const metaMetricsController = getMetaMetricsController({ + participateInMetaMetrics: true, + metaMetricsId: TEST_META_METRICS_ID, + dataCollectionForMarketing: true, + }); + metaMetricsController.setMarketingCampaignCookieId(TEST_GA_COOKIE_ID); + expect( + metaMetricsController.state.marketingCampaignCookieId, + ).toStrictEqual(TEST_GA_COOKIE_ID); + 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, + marketingCampaignCookieId: TEST_GA_COOKIE_ID, + }, + properties: { + test: 1, + ...DEFAULT_EVENT_PROPERTIES, + }, + messageId: Utils.generateRandomId(), + timestamp: new Date(), + }, + spy.mock.calls[0][1], + ); + }); + }); + describe('setDataCollectionForMarketing', function () { + it('should nullify the marketingCampaignCookieId when Data collection for marketing is toggled off', async function () { + const metaMetricsController = getMetaMetricsController({ + participateInMetaMetrics: true, + metaMetricsId: TEST_META_METRICS_ID, + dataCollectionForMarketing: true, + marketingCampaignCookieId: TEST_GA_COOKIE_ID, + }); + expect( + metaMetricsController.state.marketingCampaignCookieId, + ).toStrictEqual(TEST_GA_COOKIE_ID); + await metaMetricsController.setDataCollectionForMarketing(false); + expect( + metaMetricsController.state.marketingCampaignCookieId, + ).toStrictEqual(null); + }); + }); afterEach(function () { // flush the queues manually after each test segment.flush(); diff --git a/app/scripts/controllers/mmi-controller.test.ts b/app/scripts/controllers/mmi-controller.test.ts index 102cd63d52f3..2c9f749d2490 100644 --- a/app/scripts/controllers/mmi-controller.test.ts +++ b/app/scripts/controllers/mmi-controller.test.ts @@ -456,26 +456,6 @@ 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']), - }); - - const result = await mmiController.getCustodianAccountsByAddress( - 'token', - 'envName', - 'address', - 'mock-custodian-type', - ); - - expect(result).toEqual(['account1']); - }); - }); - describe('getCustodianTransactionDeepLink', () => { it('should return a transaction deep link', async () => { mmiController.custodyController.getCustodyTypeByAddress = jest diff --git a/app/scripts/controllers/mmi-controller.ts b/app/scripts/controllers/mmi-controller.ts index 755cab0f8fbf..5530f876786d 100644 --- a/app/scripts/controllers/mmi-controller.ts +++ b/app/scripts/controllers/mmi-controller.ts @@ -570,33 +570,6 @@ export default class MMIController extends EventEmitter { return accounts; } - async getCustodianAccountsByAddress( - token: string, - envName: string, - address: string, - custodianType: string, - ) { - let keyring; - - if (custodianType) { - const custodian = CUSTODIAN_TYPES[custodianType.toUpperCase()]; - if (!custodian) { - throw new Error('No such custodian'); - } - - keyring = await this.addKeyringIfNotExists(custodian.keyringClass.type); - } else { - throw new Error('No custodian specified'); - } - - const accounts = await keyring.getCustodianAccounts( - token, - envName, - address, - ); - return accounts; - } - async getCustodianTransactionDeepLink(address: string, txId: string) { const custodyType = this.custodyController.getCustodyTypeByAddress( toChecksumHexAddress(address), @@ -817,23 +790,25 @@ export default class MMIController extends EventEmitter { const isCustodial = Boolean(accountDetails); const updatedMsgParams = { ...msgParams, deferSetAsSigned: isCustodial }; - if (req.method.includes('eth_signTypedData')) { + if ( + req.method === 'eth_signTypedData' || + req.method === 'eth_signTypedData_v3' || + req.method === 'eth_signTypedData_v4' + ) { return await this.signatureController.newUnsignedTypedMessage( updatedMsgParams as PersonalMessageParams, req as OriginalRequest, version, { parseJsonData: false }, ); - } else if (req.method.includes('personal_sign')) { + } else if (req.method === 'personal_sign') { return await this.signatureController.newUnsignedPersonalMessage( updatedMsgParams as PersonalMessageParams, req as OriginalRequest, ); } - return await this.signatureController.newUnsignedMessage( - updatedMsgParams as PersonalMessageParams, - req as OriginalRequest, - ); + + throw new Error('Unexpected method'); } async handleSigningEvents( diff --git a/app/scripts/controllers/permissions/specifications.js b/app/scripts/controllers/permissions/specifications.js index eab746b42608..e88c12a74e1a 100644 --- a/app/scripts/controllers/permissions/specifications.js +++ b/app/scripts/controllers/permissions/specifications.js @@ -78,6 +78,11 @@ export const getCaveatSpecifications = ({ type: CaveatTypes.restrictNetworkSwitching, validator: (caveat, _origin, _target) => validateCaveatNetworks(caveat.value, findNetworkClientIdByChainId), + merger: (leftValue, rightValue) => { + const newValue = Array.from(new Set([...leftValue, ...rightValue])); + const diff = newValue.filter((value) => !leftValue.includes(value)); + return [newValue, diff]; + }, }, ...snapsCaveatsSpecifications, @@ -312,7 +317,6 @@ function validateCaveatNetworks( export const unrestrictedEthSigningMethods = Object.freeze([ 'eth_sendRawTransaction', 'eth_sendTransaction', - 'eth_sign', 'eth_signTypedData', 'eth_signTypedData_v1', 'eth_signTypedData_v3', @@ -366,7 +370,6 @@ export const unrestrictedMethods = Object.freeze([ 'eth_requestAccounts', 'eth_sendRawTransaction', 'eth_sendTransaction', - 'eth_sign', 'eth_signTypedData', 'eth_signTypedData_v1', 'eth_signTypedData_v3', @@ -405,6 +408,7 @@ export const unrestrictedMethods = Object.freeze([ 'snap_createInterface', 'snap_updateInterface', 'snap_getInterfaceState', + 'snap_resolveInterface', ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) 'metamaskinstitutional_authenticate', 'metamaskinstitutional_reauthenticate', diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 48246c02ef68..fb0c1498d65c 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -47,9 +47,6 @@ export default class PreferencesController { useNonceField: false, usePhishDetect: true, dismissSeedBackUpReminder: false, - disabledRpcMethodPreferences: { - eth_sign: false, - }, useMultiAccountBalanceChecker: true, useSafeChainsListValidation: true, // set to true means the dynamic list from the API is being used @@ -93,8 +90,10 @@ export default class PreferencesController { hideZeroBalanceTokens: false, petnamesEnabled: true, redesignedConfirmationsEnabled: true, + redesignedTransactionsEnabled: true, featureNotificationsEnabled: false, isRedesignedConfirmationsDeveloperEnabled: false, + showConfirmationAdvancedDetails: false, }, // ENS decentralized website resolution ipfsGateway: IPFS_DEFAULT_GATEWAY_URL, @@ -572,25 +571,6 @@ export default class PreferencesController { }); } - /** - * A setter for the user preference to enable/disable rpc methods - * - * @param {string} methodName - The RPC method name to change the setting of - * @param {bool} isEnabled - true to enable the rpc method - */ - async setDisabledRpcMethodPreference(methodName, isEnabled) { - const currentRpcMethodPreferences = - this.store.getState().disabledRpcMethodPreferences; - const updatedRpcMethodPreferences = { - ...currentRpcMethodPreferences, - [methodName]: isEnabled, - }; - - this.store.updateState({ - disabledRpcMethodPreferences: updatedRpcMethodPreferences, - }); - } - /** * A setter for the incomingTransactions in preference to be updated * @@ -607,10 +587,6 @@ export default class PreferencesController { this.store.updateState({ enableMV3TimestampSave: value }); } - getRpcMethodPreferences() { - return this.store.getState().disabledRpcMethodPreferences; - } - ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) setSnapsAddSnapAccountModalDismissed(value) { this.store.updateState({ snapsAddSnapAccountModalDismissed: value }); diff --git a/app/scripts/controllers/push-platform-notifications/push-platform-notifications.test.ts b/app/scripts/controllers/push-platform-notifications/push-platform-notifications.test.ts index 0b596fdb4992..2387c4dcfd9c 100644 --- a/app/scripts/controllers/push-platform-notifications/push-platform-notifications.test.ts +++ b/app/scripts/controllers/push-platform-notifications/push-platform-notifications.test.ts @@ -1,111 +1,107 @@ import { ControllerMessenger } from '@metamask/base-controller'; -import type { AuthenticationControllerGetBearerToken } from '../authentication/authentication-controller'; -import { PushPlatformNotificationsController } from './push-platform-notifications'; - -import * as services from './services/services'; +import { AuthenticationController } from '@metamask/profile-sync-controller'; +import { isManifestV3 } from '../../../../shared/modules/mv3.utils'; import type { PushPlatformNotificationsControllerMessenger, PushPlatformNotificationsControllerState, } from './push-platform-notifications'; +import { PushPlatformNotificationsController } from './push-platform-notifications'; +import * as services from './services/services'; const MOCK_JWT = 'mockJwt'; const MOCK_FCM_TOKEN = 'mockFcmToken'; const MOCK_TRIGGERS = ['uuid1', 'uuid2']; -describe('PushPlatformNotificationsController', () => { - if (!process.env.ENABLE_MV3) { - it('No MV2 tests, this functionality is not enabled', () => { - expect(true).toBe(true); +const describeOnlyMV3 = isManifestV3 + ? describe + : (title: string, fn: (this: Mocha.Suite) => void) => + describe.skip( + `${title} skipped: No MV2 tests, this functionality is not enabled`, + fn, + ); + +describeOnlyMV3('PushPlatformNotificationsController', () => { + describe('enablePushNotifications', () => { + afterEach(() => { + jest.clearAllMocks(); }); - } - - if (process.env.ENABLE_MV3) { - describe('enablePushNotifications', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - it('should update the state with the fcmToken', async () => { - await withController(async ({ controller, messenger }) => { - mockAuthBearerTokenCall(messenger); - jest - .spyOn(services, 'activatePushNotifications') - .mockResolvedValue(MOCK_FCM_TOKEN); + it('should update the state with the fcmToken', async () => { + await withController(async ({ controller, messenger }) => { + mockAuthBearerTokenCall(messenger); + jest + .spyOn(services, 'activatePushNotifications') + .mockResolvedValue(MOCK_FCM_TOKEN); - const unsubscribeMock = jest.fn(); - jest - .spyOn(services, 'listenToPushNotifications') - .mockResolvedValue(unsubscribeMock); + const unsubscribeMock = jest.fn(); + jest + .spyOn(services, 'listenToPushNotifications') + .mockResolvedValue(unsubscribeMock); - await controller.enablePushNotifications(MOCK_TRIGGERS); - expect(controller.state.fcmToken).toBe(MOCK_FCM_TOKEN); + await controller.enablePushNotifications(MOCK_TRIGGERS); + expect(controller.state.fcmToken).toBe(MOCK_FCM_TOKEN); - expect(services.listenToPushNotifications).toHaveBeenCalled(); - }); + expect(services.listenToPushNotifications).toHaveBeenCalled(); }); + }); - it('should fail if a jwt token is not provided', async () => { - await withController(async ({ messenger, controller }) => { - mockAuthBearerTokenCall(messenger).mockResolvedValue( - null as unknown as string, - ); - await expect( - controller.enablePushNotifications([]), - ).rejects.toThrow(); - }); + it('should fail if a jwt token is not provided', async () => { + await withController(async ({ messenger, controller }) => { + mockAuthBearerTokenCall(messenger).mockResolvedValue( + null as unknown as string, + ); + await expect(controller.enablePushNotifications([])).rejects.toThrow(); }); }); + }); - describe('disablePushNotifications', () => { - afterEach(() => { - jest.clearAllMocks(); - }); + describe('disablePushNotifications', () => { + afterEach(() => { + jest.clearAllMocks(); + }); - it('should update the state removing the fcmToken', async () => { - await withController(async ({ messenger, controller }) => { - mockAuthBearerTokenCall(messenger); - await controller.disablePushNotifications(MOCK_TRIGGERS); - expect(controller.state.fcmToken).toBe(''); - }); + it('should update the state removing the fcmToken', async () => { + await withController(async ({ messenger, controller }) => { + mockAuthBearerTokenCall(messenger); + await controller.disablePushNotifications(MOCK_TRIGGERS); + expect(controller.state.fcmToken).toBe(''); }); + }); - it('should fail if a jwt token is not provided', async () => { - await withController(async ({ messenger, controller }) => { - mockAuthBearerTokenCall(messenger).mockResolvedValue( - null as unknown as string, - ); - await expect( - controller.disablePushNotifications([]), - ).rejects.toThrow(); - }); + it('should fail if a jwt token is not provided', async () => { + await withController(async ({ messenger, controller }) => { + mockAuthBearerTokenCall(messenger).mockResolvedValue( + null as unknown as string, + ); + await expect(controller.disablePushNotifications([])).rejects.toThrow(); }); }); + }); - describe('updateTriggerPushNotifications', () => { - afterEach(() => { - jest.clearAllMocks(); - }); + describe('updateTriggerPushNotifications', () => { + afterEach(() => { + jest.clearAllMocks(); + }); - it('should call updateTriggerPushNotifications with the correct parameters', async () => { - await withController(async ({ messenger, controller }) => { - mockAuthBearerTokenCall(messenger); - const spy = jest - .spyOn(services, 'updateTriggerPushNotifications') - .mockResolvedValue({ - isTriggersLinkedToPushNotifications: true, - }); - - await controller.updateTriggerPushNotifications(MOCK_TRIGGERS); - - expect(spy).toHaveBeenCalledWith( - controller.state.fcmToken, - MOCK_JWT, - MOCK_TRIGGERS, - ); - }); + it('should call updateTriggerPushNotifications with the correct parameters', async () => { + await withController(async ({ messenger, controller }) => { + mockAuthBearerTokenCall(messenger); + const spy = jest + .spyOn(services, 'updateTriggerPushNotifications') + .mockResolvedValue({ + isTriggersLinkedToPushNotifications: true, + }); + + await controller.updateTriggerPushNotifications(MOCK_TRIGGERS); + + expect(spy).toHaveBeenCalledWith( + controller.state.fcmToken, + MOCK_JWT, + MOCK_TRIGGERS, + ); }); }); - } + }); }); // Test helper functions @@ -122,7 +118,7 @@ type WithControllerCallback = ({ function buildMessenger() { return new ControllerMessenger< - AuthenticationControllerGetBearerToken, + AuthenticationController.AuthenticationControllerGetBearerToken, never >(); } @@ -156,7 +152,8 @@ async function withController( function mockAuthBearerTokenCall( messenger: PushPlatformNotificationsControllerMessenger, ) { - type Fn = AuthenticationControllerGetBearerToken['handler']; + type Fn = + AuthenticationController.AuthenticationControllerGetBearerToken['handler']; const mockAuthGetBearerToken = jest .fn, Parameters>() .mockResolvedValue(MOCK_JWT); diff --git a/app/scripts/controllers/push-platform-notifications/push-platform-notifications.ts b/app/scripts/controllers/push-platform-notifications/push-platform-notifications.ts index fa28505bb55d..51cc81c14e7c 100644 --- a/app/scripts/controllers/push-platform-notifications/push-platform-notifications.ts +++ b/app/scripts/controllers/push-platform-notifications/push-platform-notifications.ts @@ -3,9 +3,11 @@ import { RestrictedControllerMessenger, ControllerGetStateAction, } from '@metamask/base-controller'; +import { AuthenticationController } from '@metamask/profile-sync-controller'; + import log from 'loglevel'; -import type { AuthenticationControllerGetBearerToken } from '../authentication/authentication-controller'; +import { isManifestV3 } from '../../../../shared/modules/mv3.utils'; import type { Notification } from '../metamask-notifications/types/notification/notification'; import { activatePushNotifications, @@ -43,7 +45,8 @@ export type PushPlatformNotificationsControllerMessengerActions = | PushPlatformNotificationsControllerUpdateTriggerPushNotifications | ControllerGetStateAction<'state', PushPlatformNotificationsControllerState>; -type AllowedActions = AuthenticationControllerGetBearerToken; +type AllowedActions = + AuthenticationController.AuthenticationControllerGetBearerToken; export type PushPlatformNotificationsControllerOnNewNotificationEvent = { type: `${typeof controllerName}:onNewNotifications`; @@ -152,7 +155,7 @@ export class PushPlatformNotificationsController extends BaseController< public async enablePushNotifications(UUIDs: string[]) { // TEMP: disabling push notifications if browser does not support MV3. // Will need work to support firefox on MV2 - if (!process.env.ENABLE_MV3) { + if (!isManifestV3) { return; } @@ -202,7 +205,7 @@ export class PushPlatformNotificationsController extends BaseController< public async disablePushNotifications(UUIDs: string[]) { // TEMP: disabling push notifications if browser does not support MV3. // Will need work to support firefox on MV2 - if (!process.env.ENABLE_MV3) { + if (!isManifestV3) { return; } @@ -248,7 +251,7 @@ export class PushPlatformNotificationsController extends BaseController< public async updateTriggerPushNotifications(UUIDs: string[]) { // TEMP: disabling push notifications if browser does not support MV3. // Will need work to support firefox on MV2 - if (!process.env.ENABLE_MV3) { + if (!isManifestV3) { return; } diff --git a/app/scripts/controllers/swaps.types.ts b/app/scripts/controllers/swaps.types.ts deleted file mode 100644 index 0164e253678e..000000000000 --- a/app/scripts/controllers/swaps.types.ts +++ /dev/null @@ -1,189 +0,0 @@ -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.ts b/app/scripts/controllers/swaps/index.ts similarity index 63% rename from app/scripts/controllers/swaps.ts rename to app/scripts/controllers/swaps/index.ts index daa6c6954ff1..0b623068d8f3 100644 --- a/app/scripts/controllers/swaps.ts +++ b/app/scripts/controllers/swaps/index.ts @@ -4,76 +4,85 @@ import { JsonRpcFetchFunc, Web3Provider, } from '@ethersproject/providers'; +import { BaseController, StateMetadata } from '@metamask/base-controller'; 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 { TransactionParams } from '@metamask/transaction-controller'; import { captureException } from '@sentry/browser'; 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 { EtherDenomination } from '../../../../shared/constants/common'; +import { GasEstimateTypes } from '../../../../shared/constants/gas'; import { MetaMetricsEventCategory, MetaMetricsEventErrorType, MetaMetricsEventName, -} from '../../../shared/constants/metametrics'; -import { CHAIN_IDS } from '../../../shared/constants/network'; +} from '../../../../shared/constants/metametrics'; +import { CHAIN_IDS } from '../../../../shared/constants/network'; import { FALLBACK_SMART_TRANSACTIONS_MAX_FEE_MULTIPLIER, FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME, FALLBACK_SMART_TRANSACTIONS_DEADLINE, -} from '../../../shared/constants/smartTransactions'; +} from '../../../../shared/constants/smartTransactions'; 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'; +} 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'; +} from '../../../../shared/lib/swaps-utils'; import { calcGasTotal, calcTokenAmount, -} from '../../../shared/lib/transactions-controller-utils'; +} from '../../../../shared/lib/transactions-controller-utils'; import { decGWEIToHexWEI, sumHexes, -} from '../../../shared/modules/conversion.utils'; -import { Numeric } from '../../../shared/modules/Numeric'; -import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils'; -import { isSwapsDefaultTokenAddress } from '../../../shared/modules/swaps.utils'; +} from '../../../../shared/modules/conversion.utils'; +import { Numeric } from '../../../../shared/modules/Numeric'; +import { isEqualCaseInsensitive } from '../../../../shared/modules/string-utils'; +import { isSwapsDefaultTokenAddress } from '../../../../shared/modules/swaps.utils'; import { + controllerName, FALLBACK_QUOTE_REFRESH_TIME, MAX_GAS_LIMIT, POLL_COUNT_LIMIT, - swapsControllerInitialState, + getDefaultSwapsControllerState, } from './swaps.constants'; +import { + calculateGasEstimateWithRefund, + getMedianEthValueQuote, +} from './swaps.utils'; import type { FetchTradesInfoParams, FetchTradesInfoParamsMetadata, - Quote, - QuoteSavings, + SwapsControllerMessenger, SwapsControllerOptions, SwapsControllerState, - SwapsControllerStore, + Quote, + QuoteSavings, Trade, } from './swaps.types'; -import { - calculateGasEstimateWithRefund, - getMedianEthValueQuote, -} from './swaps.utils'; -export default class SwapsController { - public store: SwapsControllerStore; +const metadata: StateMetadata = { + swapsState: { + persist: false, + anonymous: false, + }, +}; +export default class SwapsController extends BaseController< + typeof controllerName, + SwapsControllerState, + SwapsControllerMessenger +> { public getBufferedGasLimit: ( params: { txParams: { @@ -86,19 +95,6 @@ export default class SwapsController { factor: number, ) => Promise<{ gasLimit: string; simulationFails: boolean }>; - public getProviderConfig: () => ProviderConfig; - - public getTokenRatesState: () => { - marketData: Record< - string, - { - [tokenAddress: string]: { - price: number; - }; - } - >; - }; - public resetState: () => void; public trackMetaMetricsEvent: (event: { @@ -107,17 +103,24 @@ export default class SwapsController { properties: Record; }) => void; - private _ethersProvider: Web3Provider; + #ethersProvider: Web3Provider; + + #ethersProviderChainId: ChainId; - private _ethersProviderChainId: ChainId; + #indexOfNewestCallInFlight: number; - private _indexOfNewestCallInFlight: number; + #pollCount: number; - private _pollCount: number; + #pollingTimeout: ReturnType | null = null; - private _pollingTimeout: ReturnType | null = null; + #provider: ExternalProvider | JsonRpcFetchFunc; - private _provider: ExternalProvider | JsonRpcFetchFunc; + #getEIP1559GasFeeEstimates: () => Promise; + + #getLayer1GasFee: (params: { + transactionParams: TransactionParams; + chainId: ChainId; + }) => Promise; private _fetchTradesInfo: ( fetchParams: FetchTradesInfoParams, @@ -126,94 +129,198 @@ export default class SwapsController { [aggId: string]: Quote; }> = defaultFetchTradesInfo; - private _getCurrentChainId: () => ChainId; + constructor(opts: SwapsControllerOptions, state: SwapsControllerState) { + super({ + name: controllerName, + metadata, + messenger: opts.messenger, + state: { + swapsState: { + ...getDefaultSwapsControllerState().swapsState, + swapsFeatureFlags: state?.swapsState?.swapsFeatureFlags || {}, + }, + }, + }); - private _getEIP1559GasFeeEstimates: () => Promise; + this.messagingSystem.registerActionHandler( + `SwapsController:fetchAndSetQuotes`, + this.fetchAndSetQuotes.bind(this), + ); - private _getLayer1GasFee: (params: { - transactionParams: TransactionParams; - chainId: ChainId; - }) => Promise; + this.messagingSystem.registerActionHandler( + `SwapsController:setSelectedQuoteAggId`, + this.setSelectedQuoteAggId.bind(this), + ); - constructor( - 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: { - ...swapsControllerInitialState.swapsState, - swapsFeatureFlags: state?.swapsState?.swapsFeatureFlags || {}, - }, - }); + this.messagingSystem.registerActionHandler( + `SwapsController:resetSwapsState`, + this.resetSwapsState.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `SwapsController:setSwapsTokens`, + this.setSwapsTokens.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `SwapsController:clearSwapsQuotes`, + this.clearSwapsQuotes.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `SwapsController:setApproveTxId`, + this.setApproveTxId.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `SwapsController:setTradeTxId`, + this.setTradeTxId.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `SwapsController:setSwapsTxGasPrice`, + this.setSwapsTxGasPrice.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `SwapsController:setSwapsTxGasLimit`, + this.setSwapsTxGasLimit.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `SwapsController:setSwapsTxMaxFeePerGas`, + this.setSwapsTxMaxFeePerGas.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `SwapsController:setSwapsTxMaxFeePriorityPerGas`, + this.setSwapsTxMaxFeePriorityPerGas.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `SwapsController:safeRefetchQuotes`, + this.safeRefetchQuotes.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `SwapsController:stopPollingForQuotes`, + this.stopPollingForQuotes.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `SwapsController:setBackgroundSwapRouteState`, + this.setBackgroundSwapRouteState.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `SwapsController:resetPostFetchState`, + this.resetPostFetchState.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `SwapsController:setSwapsErrorKey`, + this.setSwapsErrorKey.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `SwapsController:setInitialGasEstimate`, + this.setInitialGasEstimate.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `SwapsController:setCustomApproveTxData`, + this.setCustomApproveTxData.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `SwapsController:setSwapsLiveness`, + this.setSwapsLiveness.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `SwapsController:setSwapsFeatureFlags`, + this.setSwapsFeatureFlags.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `SwapsController:setSwapsUserFeeLevel`, + this.setSwapsUserFeeLevel.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `SwapsController:setSwapsQuotesPollingLimitEnabled`, + this.setSwapsQuotesPollingLimitEnabled.bind(this), + ); 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: { - ...swapsControllerInitialState.swapsState, - swapsFeatureFlags: state?.swapsState?.swapsFeatureFlags, - }, + this.update((_state) => { + _state.swapsState = { + ...getDefaultSwapsControllerState().swapsState, + swapsFeatureFlags: _state?.swapsState.swapsFeatureFlags, + }; }); }; + 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; + + // TODO: this should be private, but since a lot of tests depends on spying on it + // we cannot enforce privacy 100% 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; } public clearSwapsQuotes() { - const { swapsState } = this.store.getState(); - this.store.updateState({ swapsState: { ...swapsState, quotes: {} } }); + this.update((_state) => { + _state.swapsState.quotes = {}; + _state.swapsState.selectedAggId = null; + _state.swapsState.topAggId = null; + }); } public async fetchAndSetQuotes( fetchParams: FetchTradesInfoParams, fetchParamsMetaData: FetchTradesInfoParamsMetadata, isPolledRequest = false, - ) { + ): Promise<[Record | null, string | null] | null> { if (!fetchParams) { return null; } const { chainId } = fetchParamsMetaData; - if (chainId !== this._ethersProviderChainId) { - this._ethersProvider = new Web3Provider(this._provider); - this._ethersProviderChainId = chainId; + if (chainId !== this.#ethersProviderChainId) { + this.#ethersProvider = new Web3Provider(this.#provider); + this.#ethersProviderChainId = chainId; } - const { - swapsState: { quotesPollingLimitEnabled, saveFetchedQuotes }, - } = this.store.getState(); + const { quotesPollingLimitEnabled, saveFetchedQuotes } = + this.state.swapsState; // 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 - if (this._pollingTimeout) { - 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); @@ -224,9 +331,8 @@ export default class SwapsController { this._setSwapsNetworkConfig(), ]); - const { - swapsState: { saveFetchedQuotes: saveFetchedQuotesAfterResponse }, - } = this.store.getState(); + const { saveFetchedQuotes: saveFetchedQuotesAfterResponse } = + this.state.swapsState; // If saveFetchedQuotesAfterResponse is false, it means a user left Swaps (we cleaned the state) // and we don't want to set any API response with quotes into state. @@ -250,7 +356,7 @@ export default class SwapsController { await Promise.all( Object.values(newQuotes).map(async (quote) => { if (quote.trade) { - const multiLayerL1TradeFeeTotal = await this._getLayer1GasFee({ + const multiLayerL1TradeFeeTotal = await this.#getLayer1GasFee({ transactionParams: quote.trade, chainId, }); @@ -322,7 +428,7 @@ export default class SwapsController { if (Object.values(newQuotes).length === 0) { this.setSwapsErrorKey(QUOTES_NOT_AVAILABLE_ERROR); } else { - const topQuoteAndSavings = await this._findTopQuoteAndCalculateSavings( + const topQuoteAndSavings = await this.getTopQuoteWithCalculatedSavings( newQuotes, ); if (Array.isArray(topQuoteAndSavings)) { @@ -333,34 +439,33 @@ export default class SwapsController { // 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; + let { selectedAggId } = this.state.swapsState; if (!selectedAggId || !newQuotes[selectedAggId]) { selectedAggId = null; } - this.store.updateState({ - swapsState: { - ...swapsState, - quotes: newQuotes, - fetchParams: { ...fetchParams, metaData: fetchParamsMetaData }, - quotesLastFetched, - selectedAggId, - topAggId, - }, + this.update((_state) => { + _state.swapsState.quotes = newQuotes; + _state.swapsState.fetchParams = { + ...fetchParams, + metaData: fetchParamsMetaData, + }; + _state.swapsState.quotesLastFetched = quotesLastFetched; + _state.swapsState.selectedAggId = selectedAggId; + _state.swapsState.topAggId = topAggId; }); 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) { + if (!quotesPollingLimitEnabled || this.#pollCount < POLL_COUNT_LIMIT + 1) { this._pollForNewQuotes(); } else { this.resetPostFetchState(); @@ -371,235 +476,16 @@ export default class SwapsController { return [newQuotes, topAggId]; } - public resetPostFetchState() { - const { swapsState } = this.store.getState(); - 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); - } - } - - public resetSwapsState() { - const { swapsState } = this.store.getState(); - this.store.updateState({ - swapsState: { - ...swapsControllerInitialState.swapsState, - swapsQuoteRefreshTime: swapsState.swapsQuoteRefreshTime, - swapsQuotePrefetchingRefreshTime: - swapsState.swapsQuotePrefetchingRefreshTime, - swapsFeatureFlags: swapsState.swapsFeatureFlags, - }, - }); - if (this._pollingTimeout) { - clearTimeout(this._pollingTimeout); - } - } - - public safeRefetchQuotes() { - const { swapsState } = this.store.getState(); - if (!this._pollingTimeout && swapsState.fetchParams) { - this.fetchAndSetQuotes(swapsState.fetchParams, { - ...swapsState.fetchParams.metaData, - }); - } - } - - public setApproveTxId(approveTxId: string | null) { - const { swapsState } = this.store.getState(); - 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 }, - }); - } - - public async setInitialGasEstimate(initialAggId: string) { - const { swapsState } = this.store.getState(); - - const quoteToUpdate = { ...swapsState.quotes[initialAggId] }; - - const { gasLimit: newGasEstimate, simulationFails } = quoteToUpdate.trade - ? await this._timedoutGasReturn( - quoteToUpdate.trade, - quoteToUpdate.aggregator, - ) - : { gasLimit: null, simulationFails: true }; - - if (newGasEstimate && !simulationFails) { - const gasEstimateWithRefund = calculateGasEstimateWithRefund( - quoteToUpdate.maxGas, - quoteToUpdate.estimatedRefund, - newGasEstimate, - ); - - quoteToUpdate.gasEstimate = newGasEstimate; - quoteToUpdate.gasEstimateWithRefund = gasEstimateWithRefund; - } - - this.store.updateState({ - swapsState: { - ...swapsState, - quotes: { ...swapsState.quotes, [initialAggId]: quoteToUpdate }, - }, - }); - } - - public setSelectedQuoteAggId(selectedAggId: string) { - const { swapsState } = this.store.getState(); - this.store.updateState({ swapsState: { ...swapsState, selectedAggId } }); - } - - public setSwapsFeatureFlags(swapsFeatureFlags: Record) { - const { swapsState } = this.store.getState(); - this.store.updateState({ - swapsState: { ...swapsState, swapsFeatureFlags }, - }); - } - - public setSwapsErrorKey(errorKey: string) { - const { swapsState } = this.store.getState(); - this.store.updateState({ swapsState: { ...swapsState, errorKey } }); - } - - public setSwapsLiveness(swapsLiveness: { swapsFeatureIsLive: boolean }) { - const { swapsState } = this.store.getState(); - const { swapsFeatureIsLive } = swapsLiveness; - this.store.updateState({ - swapsState: { ...swapsState, swapsFeatureIsLive }, - }); - } - - public setSwapsQuotesPollingLimitEnabled(quotesPollingLimitEnabled: boolean) { - const { swapsState } = this.store.getState(); - this.store.updateState({ - swapsState: { ...swapsState, quotesPollingLimitEnabled }, - }); - } - - public setSwapsTokens(tokens: string[]) { - const { swapsState } = this.store.getState(); - this.store.updateState({ swapsState: { ...swapsState, tokens } }); - } - - public setSwapsTxGasLimit(gasLimit: string) { - const { swapsState } = this.store.getState(); - this.store.updateState({ - swapsState: { ...swapsState, customMaxGas: gasLimit }, - }); - } - - public setSwapsTxGasPrice(gasPrice: string | null) { - const { swapsState } = this.store.getState(); - this.store.updateState({ - swapsState: { ...swapsState, customGasPrice: gasPrice }, - }); - } - - public setSwapsTxMaxFeePerGas(maxFeePerGas: string | null) { - const { swapsState } = this.store.getState(); - this.store.updateState({ - swapsState: { ...swapsState, customMaxFeePerGas: maxFeePerGas }, - }); - } - - public setSwapsTxMaxFeePriorityPerGas(maxPriorityFeePerGas: string | null) { - const { swapsState } = this.store.getState(); - this.store.updateState({ - swapsState: { - ...swapsState, - customMaxPriorityFeePerGas: maxPriorityFeePerGas, - }, - }); - } - - public setSwapsUserFeeLevel(swapsUserFeeLevel: string) { - const { swapsState } = this.store.getState(); - this.store.updateState({ - swapsState: { ...swapsState, swapsUserFeeLevel }, - }); - } - - public setTradeTxId(tradeTxId: string | null) { - const { swapsState } = this.store.getState(); - this.store.updateState({ swapsState: { ...swapsState, tradeTxId } }); - } - - /** - * 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', - }); - 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, - }; - } - - private async _findTopQuoteAndCalculateSavings( + public async getTopQuoteWithCalculatedSavings( quotes: Record = {}, ): Promise<[string | null, Record] | Record> { - const { marketData } = this.getTokenRatesState(); + const { marketData } = this._getTokenRatesState(); const chainId = this._getCurrentChainId(); const tokenConversionRates = marketData?.[chainId] ?? {}; - const { - swapsState: { customGasPrice, customMaxPriorityFeePerGas }, - } = this.store.getState(); + const { customGasPrice, customMaxPriorityFeePerGas } = + this.state.swapsState; const numQuotes = Object.keys(quotes).length; if (numQuotes === 0) { @@ -609,7 +495,7 @@ export default class SwapsController { const newQuotes = cloneDeep(quotes); const { gasFeeEstimates, gasEstimateType } = - await this._getEIP1559GasFeeEstimates(); + await this.#getEIP1559GasFeeEstimates(); let usedGasPrice = '0x0'; @@ -651,7 +537,7 @@ export default class SwapsController { aggregator, approvalNeeded, averageGas, - destinationAmount = 0, + destinationAmount, destinationToken, destinationTokenInfo, gasEstimateWithRefund, @@ -711,7 +597,7 @@ export default class SwapsController { : totalEthCost; const decimalAdjustedDestinationAmount = calcTokenAmount( - destinationAmount, + destinationAmount ?? '0', destinationTokenInfo.decimals, ); @@ -833,6 +719,228 @@ export default class SwapsController { return [topAggId, newQuotes]; } + public resetPostFetchState() { + this.update((_state) => { + _state.swapsState = { + ...getDefaultSwapsControllerState().swapsState, + tokens: _state.swapsState.tokens, + fetchParams: _state.swapsState.fetchParams, + swapsFeatureIsLive: _state.swapsState.swapsFeatureIsLive, + swapsQuoteRefreshTime: _state.swapsState.swapsQuoteRefreshTime, + swapsQuotePrefetchingRefreshTime: + _state.swapsState.swapsQuotePrefetchingRefreshTime, + swapsFeatureFlags: _state.swapsState.swapsFeatureFlags, + }; + }); + if (this.#pollingTimeout) { + clearTimeout(this.#pollingTimeout); + } + } + + public resetSwapsState() { + this.update((_state) => { + _state.swapsState = { + ...getDefaultSwapsControllerState().swapsState, + swapsQuoteRefreshTime: _state.swapsState.swapsQuoteRefreshTime, + swapsQuotePrefetchingRefreshTime: + _state.swapsState.swapsQuotePrefetchingRefreshTime, + swapsFeatureFlags: _state.swapsState.swapsFeatureFlags, + }; + }); + + if (this.#pollingTimeout) { + clearTimeout(this.#pollingTimeout); + } + } + + public safeRefetchQuotes() { + if (!this.#pollingTimeout && this.state.swapsState.fetchParams) { + this.fetchAndSetQuotes(this.state.swapsState.fetchParams, { + ...this.state.swapsState.fetchParams.metaData, + }); + } + } + + public setApproveTxId(approveTxId: string | null) { + this.update((_state) => { + _state.swapsState.approveTxId = approveTxId; + }); + } + + public setBackgroundSwapRouteState(routeState: string) { + this.update((_state) => { + _state.swapsState.routeState = routeState; + }); + } + + public setCustomApproveTxData(customApproveTxData: string) { + this.update((_state) => { + _state.swapsState.customApproveTxData = customApproveTxData; + }); + } + + public async setInitialGasEstimate(initialAggId: string) { + const quoteToUpdate = { ...this.state.swapsState.quotes[initialAggId] }; + + const { gasLimit: newGasEstimate, simulationFails } = quoteToUpdate.trade + ? await this._timedoutGasReturn( + quoteToUpdate.trade, + quoteToUpdate.aggregator, + ) + : { gasLimit: null, simulationFails: true }; + + if (newGasEstimate && !simulationFails) { + const gasEstimateWithRefund = calculateGasEstimateWithRefund( + quoteToUpdate.maxGas, + quoteToUpdate.estimatedRefund, + newGasEstimate, + ); + + quoteToUpdate.gasEstimate = newGasEstimate; + quoteToUpdate.gasEstimateWithRefund = gasEstimateWithRefund; + } + + this.update((_state) => { + _state.swapsState.quotes = { + ..._state.swapsState.quotes, + [initialAggId]: quoteToUpdate, + }; + }); + } + + public setSelectedQuoteAggId(selectedAggId: string) { + this.update((_state) => { + _state.swapsState.selectedAggId = selectedAggId; + }); + } + + public setSwapsFeatureFlags(swapsFeatureFlags: Record) { + this.update((_state) => { + _state.swapsState.swapsFeatureFlags = swapsFeatureFlags; + }); + } + + public setSwapsErrorKey(errorKey: string) { + this.update((_state) => { + _state.swapsState.errorKey = errorKey; + }); + } + + public setSwapsLiveness(swapsLiveness: { swapsFeatureIsLive: boolean }) { + const { swapsFeatureIsLive } = swapsLiveness; + this.update((_state) => { + _state.swapsState.swapsFeatureIsLive = swapsFeatureIsLive; + }); + } + + public setSwapsQuotesPollingLimitEnabled(quotesPollingLimitEnabled: boolean) { + this.update((_state) => { + _state.swapsState.quotesPollingLimitEnabled = quotesPollingLimitEnabled; + }); + } + + public setSwapsTokens(tokens: string[]) { + this.update((_state) => { + _state.swapsState.tokens = tokens; + }); + } + + public setSwapsTxGasLimit(customMaxGas: string) { + this.update((_state) => { + _state.swapsState.customMaxGas = customMaxGas; + }); + } + + public setSwapsTxGasPrice(customGasPrice: string | null) { + this.update((_state) => { + _state.swapsState.customGasPrice = customGasPrice; + }); + } + + public setSwapsTxMaxFeePerGas(customMaxFeePerGas: string | null) { + this.update((_state) => { + _state.swapsState.customMaxFeePerGas = customMaxFeePerGas; + }); + } + + public setSwapsTxMaxFeePriorityPerGas( + customMaxPriorityFeePerGas: string | null, + ) { + this.update((_state) => { + _state.swapsState.customMaxPriorityFeePerGas = customMaxPriorityFeePerGas; + }); + } + + public setSwapsUserFeeLevel(swapsUserFeeLevel: string) { + this.update((_state) => { + _state.swapsState.swapsUserFeeLevel = swapsUserFeeLevel; + }); + } + + public setTradeTxId(tradeTxId: string | null) { + this.update((_state) => { + _state.swapsState.tradeTxId = tradeTxId; + }); + } + + /** + * 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); + } + } + + /** + * This method is used to update the state of the controller for testing purposes. + * DO NOT USE OUTSIDE OF TESTING + * + * @param newState - The new state to set + */ + public __test__updateState = (newState: Partial) => { + this.update((oldState) => { + return { swapsState: { ...oldState.swapsState, ...newState.swapsState } }; + }); + }; + + // Private Methods + private async _fetchSwapsNetworkConfig(chainId: 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, + swapsStxStatusDeadline: parameters.stxStatusDeadline, + }; + } + private async _getAllQuotesWithGasEstimates(quotes: Record) { const quoteGasData = await Promise.all( Object.values(quotes).map(async (quote) => { @@ -877,12 +985,25 @@ export default class SwapsController { return newQuotes; } + private _getCurrentChainId(): ChainId { + const { selectedNetworkClientId } = this.messagingSystem.call( + 'NetworkController:getState', + ); + const { + configuration: { chainId }, + } = this.messagingSystem.call( + 'NetworkController:getNetworkClientById', + selectedNetworkClientId, + ); + return chainId as ChainId; + } + private async _getERC20Allowance( contractAddress: string, walletAddress: string, chainId: ChainId, ) { - const contract = new Contract(contractAddress, abi, this._ethersProvider); + const contract = new Contract(contractAddress, abi, this.#ethersProvider); return await contract.allowance( walletAddress, SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[ @@ -891,65 +1012,83 @@ export default class SwapsController { ); } + private _getTokenRatesState(): { + marketData: Record< + string, + { + [tokenAddress: string]: { + price: number; + }; + } + >; + } { + const { marketData } = this.messagingSystem.call( + 'TokenRatesController:getState', + ); + return { marketData }; + } + private _pollForNewQuotes() { const { - swapsState: { - swapsQuoteRefreshTime, - swapsQuotePrefetchingRefreshTime, - quotesPollingLimitEnabled, - }, - } = this.store.getState(); + swapsQuoteRefreshTime, + swapsQuotePrefetchingRefreshTime, + quotesPollingLimitEnabled, + } = this.state.swapsState; // 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.#pollingTimeout = setTimeout(() => { this.fetchAndSetQuotes( - swapsState.fetchParams as FetchTradesInfoParams, - swapsState.fetchParams?.metaData as FetchTradesInfoParamsMetadata, + this.state.swapsState.fetchParams as FetchTradesInfoParams, + this.state.swapsState.fetchParams + ?.metaData as FetchTradesInfoParamsMetadata, true, ); }, quotesRefreshRateInMs); } private _setSaveFetchedQuotes(status: boolean) { - const { swapsState } = this.store.getState(); - this.store.updateState({ - swapsState: { ...swapsState, saveFetchedQuotes: status }, + this.update((_state) => { + _state.swapsState.saveFetchedQuotes = status; }); } // Sets the network config from the MetaSwap API. private async _setSwapsNetworkConfig() { const chainId = this._getCurrentChainId(); - let swapsNetworkConfig; + let swapsNetworkConfig: { + quotes: number; + quotesPrefetching: number; + stxGetTransactions: number; + stxBatchStatus: number; + stxStatusDeadline: number; + stxMaxFeeMultiplier: number; + swapsStxStatusDeadline: number; + } | null = null; + 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, - swapsStxMaxFeeMultiplier: - swapsNetworkConfig?.stxMaxFeeMultiplier || - FALLBACK_SMART_TRANSACTIONS_MAX_FEE_MULTIPLIER, - swapsStxStatusDeadline: - swapsNetworkConfig?.stxStatusDeadline || - FALLBACK_SMART_TRANSACTIONS_DEADLINE, - }, + this.update((_state) => { + _state.swapsState.swapsQuoteRefreshTime = + swapsNetworkConfig?.quotes || FALLBACK_QUOTE_REFRESH_TIME; + _state.swapsState.swapsQuotePrefetchingRefreshTime = + swapsNetworkConfig?.quotesPrefetching || FALLBACK_QUOTE_REFRESH_TIME; + _state.swapsState.swapsStxGetTransactionsRefreshTime = + swapsNetworkConfig?.stxGetTransactions || + FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME; + _state.swapsState.swapsStxBatchStatusRefreshTime = + swapsNetworkConfig?.stxBatchStatus || + FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME; + _state.swapsState.swapsStxMaxFeeMultiplier = + swapsNetworkConfig?.stxMaxFeeMultiplier || + FALLBACK_SMART_TRANSACTIONS_MAX_FEE_MULTIPLIER; + _state.swapsState.swapsStxStatusDeadline = + swapsNetworkConfig?.swapsStxStatusDeadline || + FALLBACK_SMART_TRANSACTIONS_DEADLINE; }); } diff --git a/app/scripts/controllers/swaps.constants.ts b/app/scripts/controllers/swaps/swaps.constants.ts similarity index 88% rename from app/scripts/controllers/swaps.constants.ts rename to app/scripts/controllers/swaps/swaps.constants.ts index 6228dd2bcb66..1e5b566387b8 100644 --- a/app/scripts/controllers/swaps.constants.ts +++ b/app/scripts/controllers/swaps/swaps.constants.ts @@ -2,11 +2,13 @@ import { FALLBACK_SMART_TRANSACTIONS_DEADLINE, FALLBACK_SMART_TRANSACTIONS_MAX_FEE_MULTIPLIER, FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME, -} from '../../../shared/constants/smartTransactions'; -import { MINUTE } from '../../../shared/constants/time'; +} from '../../../../shared/constants/smartTransactions'; +import { MINUTE } from '../../../../shared/constants/time'; import type { SwapsControllerState } from './swaps.types'; +export const controllerName = 'SwapsController'; + // 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; @@ -18,8 +20,8 @@ export const POLL_COUNT_LIMIT = 3; // provide a reasonable fallback to avoid further errors export const FALLBACK_QUOTE_REFRESH_TIME = MINUTE; -export const swapsControllerInitialState: { swapsState: SwapsControllerState } = - { +export function getDefaultSwapsControllerState(): SwapsControllerState { + return { swapsState: { quotes: {}, quotesPollingLimitEnabled: false, @@ -50,3 +52,4 @@ export const swapsControllerInitialState: { swapsState: SwapsControllerState } = swapsFeatureFlags: {}, }, }; +} diff --git a/app/scripts/controllers/swaps.test.js b/app/scripts/controllers/swaps/swaps.test.ts similarity index 58% rename from app/scripts/controllers/swaps.test.js rename to app/scripts/controllers/swaps/swaps.test.ts index 63b910f35b0e..8b2fbd22d032 100644 --- a/app/scripts/controllers/swaps.test.js +++ b/app/scripts/controllers/swaps/swaps.test.ts @@ -1,19 +1,23 @@ import { BigNumber } from '@ethersproject/bignumber'; -import { mapValues } from 'lodash'; +import { ExternalProvider, JsonRpcFetchFunc } from '@ethersproject/providers'; +import { ChainId } from '@metamask/controller-utils'; import BigNumberjs from 'bignumber.js'; -import { CHAIN_IDS } from '../../../shared/constants/network'; -import { ETH_SWAPS_TOKEN_OBJECT } from '../../../shared/constants/swaps'; -import { createTestProviderTools } from '../../../test/stub/provider'; -import { SECOND } from '../../../shared/constants/time'; -import { GasEstimateTypes } from '../../../shared/constants/gas'; +import { mapValues } from 'lodash'; +import { GasEstimateTypes } from '../../../../shared/constants/gas'; +import { CHAIN_IDS } from '../../../../shared/constants/network'; +import { ETH_SWAPS_TOKEN_OBJECT } from '../../../../shared/constants/swaps'; +import { createTestProviderTools } from '../../../../test/stub/provider'; +import { getDefaultSwapsControllerState } from './swaps.constants'; import { - FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME, - FALLBACK_SMART_TRANSACTIONS_MAX_FEE_MULTIPLIER, -} from '../../../shared/constants/smartTransactions'; -import SwapsController from './swaps'; + FetchTradesInfoParams, + FetchTradesInfoParamsMetadata, + Quote, + SwapsControllerMessenger, +} from './swaps.types'; import { getMedianEthValueQuote } from './swaps.utils'; +import SwapsController from '.'; -const MOCK_FETCH_PARAMS = { +const MOCK_FETCH_PARAMS: FetchTradesInfoParams = { slippage: 3, sourceToken: '0x6b175474e89094c44da98b954eedeac495271d0f', sourceDecimals: 18, @@ -21,6 +25,7 @@ const MOCK_FETCH_PARAMS = { value: '1000000000000000000', fromAddress: '0x7F18BB4Dd92CF2404C54CBa1A9BE4A1153bdb078', exchangeList: 'zeroExV1', + balanceError: false, }; const TEST_AGG_ID_1 = 'TEST_AGG_1'; @@ -32,7 +37,7 @@ const TEST_AGG_ID_6 = 'TEST_AGG_6'; const TEST_AGG_ID_BEST = 'TEST_AGG_BEST'; const TEST_AGG_ID_APPROVAL = 'TEST_AGG_APPROVAL'; -const POLLING_TIMEOUT = SECOND * 1000; +// const POLLING_TIMEOUT = SECOND * 1000; const MOCK_APPROVAL_NEEDED = { data: '0x095ea7b300000000000000000000000095e6f48254609a6ee006f7d493c8e5fb97094cef0000000000000000000000000000000000000000004a817c7ffffffdabf41c00', @@ -70,88 +75,90 @@ const MOCK_QUOTES_APPROVAL_REQUIRED = { }, }; -const MOCK_FETCH_METADATA = { +const MOCK_FETCH_METADATA: FetchTradesInfoParamsMetadata = { destinationTokenInfo: { symbol: 'FOO', decimals: 18, + address: '0xSomeAddress', + }, + sourceTokenInfo: { + symbol: 'BAR', + decimals: 18, + address: '0xSomeOtherAddress', }, chainId: CHAIN_IDS.MAINNET, }; -const MOCK_TOKEN_RATES_STORE = () => ({ - marketData: { - '0x1': { - '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48': { price: 2 }, - '0x1111111111111111111111111111111111111111': { price: 0.1 }, - }, - }, -}); - -const MOCK_GET_PROVIDER_CONFIG = () => ({ type: 'FAKE_NETWORK' }); - const MOCK_GET_BUFFERED_GAS_LIMIT = async () => ({ - gasLimit: 2000000, - simulationFails: undefined, + gasLimit: '2000000', + simulationFails: false, }); -const EMPTY_INIT_STATE = { - swapsState: { - quotes: {}, - quotesPollingLimitEnabled: false, - fetchParams: null, - tokens: null, - tradeTxId: null, - approveTxId: null, - quotesLastFetched: null, - customMaxFeePerGas: null, - customMaxGas: '', - customMaxPriorityFeePerGas: null, - customGasPrice: null, - selectedAggId: null, - customApproveTxData: '', - errorKey: '', - topAggId: null, - routeState: '', - swapsFeatureIsLive: true, - swapsFeatureFlags: {}, - swapsQuoteRefreshTime: 60000, - swapsQuotePrefetchingRefreshTime: 60000, - swapsStxBatchStatusRefreshTime: FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME, - swapsStxGetTransactionsRefreshTime: - FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME, - swapsStxMaxFeeMultiplier: FALLBACK_SMART_TRANSACTIONS_MAX_FEE_MULTIPLIER, - swapsStxStatusDeadline: 180, - swapsUserFeeLevel: '', - saveFetchedQuotes: false, - }, -}; - 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, }); +const trackMetaMetricsEventStub = jest.fn(); + +// Create a single mock object +const messengerMock = { + call: jest.fn(), + registerActionHandler: jest.fn(), + registerInitialEventPayload: jest.fn(), + publish: jest.fn(), +} as unknown as jest.Mocked; + +const networkControllerGetStateCallbackMock = jest + .fn() + .mockReturnValue({ selectedNetworkClientId: 'metamask' }); + +const networkControllerGetNetworkClientByIdCallbackMock = jest + .fn() + .mockReturnValue({ configuration: { chainId: CHAIN_IDS.MAINNET } }); + +const tokenRatesControllerGetStateCallbackMock = jest.fn().mockReturnValue({ + marketData: { + '0x1': { + '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48': { price: 2 }, + '0x1111111111111111111111111111111111111111': { price: 0.1 }, + }, + }, +}); + +messengerMock.call.mockImplementation((actionName, ..._rest) => { + if (actionName === 'NetworkController:getState') { + return networkControllerGetStateCallbackMock(); + } + if (actionName === 'NetworkController:getNetworkClientById') { + return networkControllerGetNetworkClientByIdCallbackMock(); + } + if (actionName === 'TokenRatesController:getState') { + return tokenRatesControllerGetStateCallbackMock(); + } + return undefined; +}); describe('SwapsController', function () { - let provider; - - const getSwapsController = (_provider = provider) => { - return new SwapsController({ - getBufferedGasLimit: MOCK_GET_BUFFERED_GAS_LIMIT, - provider: _provider, - getProviderConfig: MOCK_GET_PROVIDER_CONFIG, - getTokenRatesState: MOCK_TOKEN_RATES_STORE, - fetchTradesInfo: fetchTradesInfoStub, - getCurrentChainId: getCurrentChainIdStub, - getEIP1559GasFeeEstimates: getEIP1559GasFeeEstimatesStub, - getNetworkClientId: getNetworkClientIdStub, - getLayer1GasFee: getLayer1GasFeeStub, - }); + let provider: ExternalProvider | JsonRpcFetchFunc; + const getSwapsController = ( + _provider: ExternalProvider | JsonRpcFetchFunc = provider, + ) => { + return new SwapsController( + { + getBufferedGasLimit: MOCK_GET_BUFFERED_GAS_LIMIT, + provider: _provider, + fetchTradesInfo: fetchTradesInfoStub, + getEIP1559GasFeeEstimates: getEIP1559GasFeeEstimatesStub, + getLayer1GasFee: getLayer1GasFeeStub, + trackMetaMetricsEvent: trackMetaMetricsEventStub, + messenger: messengerMock, + }, + getDefaultSwapsControllerState(), + ); }; beforeEach(function () { @@ -164,7 +171,7 @@ describe('SwapsController', function () { provider = createTestProviderTools({ scaffold: providerResultStub, networkId: 1, - chainId: 1, + chainId: CHAIN_IDS.MAINNET as ChainId, }).provider; jest.useFakeTimers(); }); @@ -177,19 +184,17 @@ describe('SwapsController', function () { describe('constructor', function () { it('should setup correctly', function () { const swapsController = getSwapsController(); - expect(swapsController.store.getState()).toStrictEqual(EMPTY_INIT_STATE); + expect(swapsController.state).toStrictEqual( + getDefaultSwapsControllerState(), + ); expect(swapsController.getBufferedGasLimit).toStrictEqual( MOCK_GET_BUFFERED_GAS_LIMIT, ); - expect(swapsController._pollCount).toStrictEqual(0); - expect(swapsController.getProviderConfig).toStrictEqual( - MOCK_GET_PROVIDER_CONFIG, - ); }); }); describe('API', function () { - let swapsController; + let swapsController: SwapsController; beforeEach(function () { swapsController = getSwapsController(); }); @@ -198,79 +203,91 @@ describe('SwapsController', function () { it('should set selected quote agg id', function () { const selectedAggId = 'test'; swapsController.setSelectedQuoteAggId(selectedAggId); - expect( - swapsController.store.getState().swapsState.selectedAggId, - ).toStrictEqual(selectedAggId); + expect(swapsController.state.swapsState.selectedAggId).toStrictEqual( + selectedAggId, + ); }); it('should set swaps tokens', function () { - const tokens = []; + const tokens: string[] = []; swapsController.setSwapsTokens(tokens); - expect( - swapsController.store.getState().swapsState.tokens, - ).toStrictEqual(tokens); + expect(swapsController.state.swapsState.tokens).toStrictEqual(tokens); }); it('should set trade tx id', function () { const tradeTxId = 'test'; swapsController.setTradeTxId(tradeTxId); - expect( - swapsController.store.getState().swapsState.tradeTxId, - ).toStrictEqual(tradeTxId); + expect(swapsController.state.swapsState.tradeTxId).toStrictEqual( + tradeTxId, + ); }); it('should set swaps tx gas price', function () { - const gasPrice = 1; + const gasPrice = '1'; swapsController.setSwapsTxGasPrice(gasPrice); - expect( - swapsController.store.getState().swapsState.customGasPrice, - ).toStrictEqual(gasPrice); + expect(swapsController.state.swapsState.customGasPrice).toStrictEqual( + gasPrice, + ); }); it('should set swaps tx gas limit', function () { const gasLimit = '1'; swapsController.setSwapsTxGasLimit(gasLimit); - expect( - swapsController.store.getState().swapsState.customMaxGas, - ).toStrictEqual(gasLimit); + expect(swapsController.state.swapsState.customMaxGas).toStrictEqual( + gasLimit, + ); }); it('should set background swap route state', function () { const routeState = 'test'; swapsController.setBackgroundSwapRouteState(routeState); - expect( - swapsController.store.getState().swapsState.routeState, - ).toStrictEqual(routeState); + expect(swapsController.state.swapsState.routeState).toStrictEqual( + routeState, + ); }); it('should set swaps error key', function () { const errorKey = 'test'; swapsController.setSwapsErrorKey(errorKey); - expect( - swapsController.store.getState().swapsState.errorKey, - ).toStrictEqual(errorKey); + expect(swapsController.state.swapsState.errorKey).toStrictEqual( + errorKey, + ); }); it('should set initial gas estimate', async function () { const initialAggId = TEST_AGG_ID_1; - const baseGasEstimate = 10; - const { maxGas, estimatedRefund } = getMockQuotes()[TEST_AGG_ID_1]; + const { maxGas, estimatedRefund, trade } = + getMockQuotes()[TEST_AGG_ID_1]; - const { swapsState } = swapsController.store.getState(); - // Set mock quotes in order to have data for the test agg - swapsController.store.updateState({ - swapsState: { ...swapsState, quotes: getMockQuotes() }, + // eslint-disable-next-line jest/no-if + if (!trade) { + throw new Error('Trade data is required'); + } + + // Override state with mock quotes in order to have data for the test agg + swapsController.__test__updateState({ + swapsState: { + ...swapsController.state.swapsState, + quotes: getMockQuotes(), + }, }); - await swapsController.setInitialGasEstimate( - initialAggId, - baseGasEstimate, - ); + await swapsController.setInitialGasEstimate(initialAggId); const { gasLimit: bufferedGasLimit } = - await swapsController.getBufferedGasLimit(); + await swapsController.getBufferedGasLimit( + { + txParams: { + value: trade.value, + data: trade.data, + from: trade.from, + to: trade.to, + }, + }, + 1, + ); const { gasEstimate, gasEstimateWithRefund } = - swapsController.store.getState().swapsState.quotes[initialAggId]; + swapsController.state.swapsState.quotes[initialAggId]; expect(gasEstimate).toStrictEqual(bufferedGasLimit); expect(gasEstimateWithRefund).toStrictEqual( @@ -284,34 +301,39 @@ describe('SwapsController', function () { const data = 'test'; swapsController.setCustomApproveTxData(data); expect( - swapsController.store.getState().swapsState.customApproveTxData, + swapsController.state.swapsState.customApproveTxData, ).toStrictEqual(data); }); }); - describe('_findTopQuoteAndCalculateSavings', function () { + describe('getTopQuoteWithCalculatedSavings', function () { beforeEach(function () { - const { swapsState } = swapsController.store.getState(); - swapsController.store.updateState({ - swapsState: { ...swapsState, customGasPrice: '0x174876e800' }, + swapsController.__test__updateState({ + swapsState: { + ...swapsController.state.swapsState, + customGasPrice: '0x174876e800', + }, }); }); it('returns empty object if passed undefined or empty object', async function () { expect( - await swapsController._findTopQuoteAndCalculateSavings(), + await swapsController.getTopQuoteWithCalculatedSavings(), ).toStrictEqual({}); expect( - await swapsController._findTopQuoteAndCalculateSavings({}), + await swapsController.getTopQuoteWithCalculatedSavings({}), ).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 () { - const [topAggId, resultQuotes] = - await swapsController._findTopQuoteAndCalculateSavings( + const topQuoteAndSavings = + await swapsController.getTopQuoteWithCalculatedSavings( getTopQuoteAndSavingsMockQuotes(), ); + + const topAggId = topQuoteAndSavings[0]; + const resultQuotes = topQuoteAndSavings[1]; expect(topAggId).toStrictEqual(TEST_AGG_ID_1); expect(resultQuotes).toStrictEqual( getTopQuoteAndSavingsBaseExpectedResults(), @@ -319,11 +341,26 @@ describe('SwapsController', function () { }); it('returns the top aggId and quotes with savings and fee values if passed necessary data and an odd number of quotes', async function () { - const testInput = getTopQuoteAndSavingsMockQuotes(); - delete testInput[TEST_AGG_ID_6]; - const expectedResultQuotes = getTopQuoteAndSavingsBaseExpectedResults(); - delete expectedResultQuotes[TEST_AGG_ID_6]; - expectedResultQuotes[TEST_AGG_ID_1].savings = { + const completeTestInput = getTopQuoteAndSavingsMockQuotes(); + const partialTestInput = { + [TEST_AGG_ID_1]: completeTestInput[TEST_AGG_ID_1], + [TEST_AGG_ID_2]: completeTestInput[TEST_AGG_ID_2], + [TEST_AGG_ID_3]: completeTestInput[TEST_AGG_ID_3], + [TEST_AGG_ID_4]: completeTestInput[TEST_AGG_ID_4], + [TEST_AGG_ID_5]: completeTestInput[TEST_AGG_ID_5], + }; + + const completeExpectedResultQuotes = + getTopQuoteAndSavingsBaseExpectedResults(); + const partialExpectedResultQuotes = { + [TEST_AGG_ID_1]: completeExpectedResultQuotes[TEST_AGG_ID_1], + [TEST_AGG_ID_2]: completeExpectedResultQuotes[TEST_AGG_ID_2], + [TEST_AGG_ID_3]: completeExpectedResultQuotes[TEST_AGG_ID_3], + [TEST_AGG_ID_4]: completeExpectedResultQuotes[TEST_AGG_ID_4], + [TEST_AGG_ID_5]: completeExpectedResultQuotes[TEST_AGG_ID_5], + }; + + completeExpectedResultQuotes[TEST_AGG_ID_1].savings = { total: '0.0092', performance: '0.0297', fee: '0', @@ -331,10 +368,15 @@ describe('SwapsController', function () { medianMetaMaskFee: '0.0202', }; - const [topAggId, resultQuotes] = - await swapsController._findTopQuoteAndCalculateSavings(testInput); + const topQuoteAndSavings = + await swapsController.getTopQuoteWithCalculatedSavings( + partialTestInput, + ); + const topAggId = topQuoteAndSavings[0]; + const resultQuotes = topQuoteAndSavings[1]; + expect(topAggId).toStrictEqual(TEST_AGG_ID_1); - expect(resultQuotes).toStrictEqual(expectedResultQuotes); + expect(resultQuotes).toStrictEqual(partialExpectedResultQuotes); }); 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 () { @@ -372,8 +414,10 @@ describe('SwapsController', function () { }, }; - const [topAggId, resultQuotes] = - await swapsController._findTopQuoteAndCalculateSavings(testInput); + const topQuoteAndSavings = + await swapsController.getTopQuoteWithCalculatedSavings(testInput); + const topAggId = topQuoteAndSavings[0]; + const resultQuotes = topQuoteAndSavings[1]; expect(topAggId).toStrictEqual(TEST_AGG_ID_1); expect(resultQuotes).toStrictEqual(expectedResultQuotes); }); @@ -385,7 +429,9 @@ describe('SwapsController', function () { ...quote, sourceToken: ETH_SWAPS_TOKEN_OBJECT.address, destinationToken: '0x1111111111111111111111111111111111111111', - trade: { value: '0x8ac7230489e80000' }, + trade: { + value: '0x8ac7230489e80000', + }, }), ); const baseExpectedResultQuotes = @@ -435,8 +481,12 @@ describe('SwapsController', function () { }, }; - const [topAggId, resultQuotes] = - await swapsController._findTopQuoteAndCalculateSavings(testInput); + const topQuoteAndSavings = + await swapsController.getTopQuoteWithCalculatedSavings( + testInput as Record, + ); + const topAggId = topQuoteAndSavings[0]; + const resultQuotes = topQuoteAndSavings[1]; expect(topAggId).toStrictEqual(TEST_AGG_ID_1); expect(resultQuotes).toStrictEqual(expectedResultQuotes); }); @@ -448,7 +498,9 @@ describe('SwapsController', function () { ...quote, sourceToken: ETH_SWAPS_TOKEN_OBJECT.address, destinationToken: '0x1111111111111111111111111111111111111111', - trade: { value: '0x8ac7230489e80000' }, + trade: { + value: '0x8ac7230489e80000', + }, }), ); // 0.04 ETH fee included in trade value @@ -508,11 +560,18 @@ describe('SwapsController', function () { overallValueOfQuote: '1.6805', }, }; + // @ts-expect-error - we are removing a property that we know exists even if its optional in the type definition delete expectedResultQuotes[TEST_AGG_ID_1].isBestQuote; + // @ts-expect-error - we are removing a property that we know exists even if its optional in the type definition delete expectedResultQuotes[TEST_AGG_ID_1].savings; - const [topAggId, resultQuotes] = - await swapsController._findTopQuoteAndCalculateSavings(testInput); + const topQuoteAndSavings = + await swapsController.getTopQuoteWithCalculatedSavings( + testInput as Record, + ); + const topAggId = topQuoteAndSavings[0]; + const resultQuotes = topQuoteAndSavings[1]; + expect(topAggId).toStrictEqual(TEST_AGG_ID_2); expect(resultQuotes).toStrictEqual(expectedResultQuotes); }); @@ -520,6 +579,7 @@ describe('SwapsController', function () { 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 () { const testInput = getTopQuoteAndSavingsMockQuotes(); // 0.04 ETH fee included in trade value + // @ts-expect-error - trade can be undefined but in this case since its mocked it will always be defined testInput[TEST_AGG_ID_1].trade.value = '0x8e1bc9bf040000'; const baseExpectedResultQuotes = getTopQuoteAndSavingsBaseExpectedResults(); @@ -543,11 +603,16 @@ describe('SwapsController', function () { }, }, }; + // @ts-expect-error - we are removing a property that we know exists even if its optional in the type definition delete expectedResultQuotes[TEST_AGG_ID_1].isBestQuote; + // @ts-expect-error - we are removing a property that we know exists even if its optional in the type definition delete expectedResultQuotes[TEST_AGG_ID_1].savings; - const [topAggId, resultQuotes] = - await swapsController._findTopQuoteAndCalculateSavings(testInput); + const topQuoteAndSavings = + await swapsController.getTopQuoteWithCalculatedSavings(testInput); + const topAggId = topQuoteAndSavings[0]; + const resultQuotes = topQuoteAndSavings[1]; + expect(topAggId).toStrictEqual(TEST_AGG_ID_2); expect(resultQuotes).toStrictEqual(expectedResultQuotes); }); @@ -555,38 +620,65 @@ describe('SwapsController', function () { describe('fetchAndSetQuotes', function () { it('returns null if fetchParams is not provided', async function () { + // @ts-expect-error - we are testing the case where fetchParams is not provided const quotes = await swapsController.fetchAndSetQuotes(undefined); expect(quotes).toStrictEqual(null); }); it('calls fetchTradesInfo with the given fetchParams and returns the correct quotes', async function () { + fetchTradesInfoStub.mockReset(); + const providerResultStub = { + // 1 gwei + eth_gasPrice: '0x0de0b6b3a7640000', + // by default, all accounts are external accounts (not contracts) + eth_getCode: '0x', + }; + const mainnetProvider = createTestProviderTools({ + scaffold: providerResultStub, + networkId: 1, + chainId: CHAIN_IDS.MAINNET as ChainId, + }).provider; + + swapsController = getSwapsController(mainnetProvider); + const fetchTradesInfoSpy = jest - .spyOn(swapsController, '_fetchTradesInfo') + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .spyOn(swapsController as any, '_fetchTradesInfo') .mockReturnValue(getMockQuotes()); // Make it so approval is not required jest - .spyOn(swapsController, '_getERC20Allowance') + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .spyOn(swapsController as any, '_getERC20Allowance') .mockReturnValue(BigNumber.from(1)); // Make the network fetch error message disappear - jest.spyOn(swapsController, '_setSwapsNetworkConfig').mockReturnValue(); + jest + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .spyOn(swapsController as any, '_setSwapsNetworkConfig') + .mockReturnValue(undefined); - const [newQuotes] = await swapsController.fetchAndSetQuotes( + const fetchResponse = await swapsController.fetchAndSetQuotes( MOCK_FETCH_PARAMS, MOCK_FETCH_METADATA, ); + if (!fetchResponse?.[0]) { + throw new Error('Quotes should be defined'); + } + + const [newQuotes] = fetchResponse; + expect(newQuotes[TEST_AGG_ID_BEST]).toStrictEqual({ ...getMockQuotes()[TEST_AGG_ID_BEST], - sourceTokenInfo: undefined, destinationTokenInfo: { + address: '0xSomeAddress', symbol: 'FOO', decimals: 18, }, isBestQuote: true, // TODO: find a way to calculate these values dynamically - gasEstimate: 2000000, + gasEstimate: '2000000', gasEstimateWithRefund: '0xb8cae', savings: { fee: '-0.061067', @@ -599,6 +691,11 @@ describe('SwapsController', function () { overallValueOfQuote: '49.886464', metaMaskFeeInEth: '0.50505050505050505050505050505050505', ethValueOfTokens: '50', + sourceTokenInfo: { + address: '0xSomeOtherAddress', + decimals: 18, + symbol: 'BAR', + }, }); expect(fetchTradesInfoSpy).toHaveBeenCalledTimes(1); @@ -611,7 +708,7 @@ describe('SwapsController', function () { fetchTradesInfoStub.mockReset(); const OPTIMISM_MOCK_FETCH_METADATA = { ...MOCK_FETCH_METADATA, - chainId: CHAIN_IDS.OPTIMISM, + chainId: CHAIN_IDS.OPTIMISM as ChainId, }; const optimismProviderResultStub = { // 1 gwei @@ -624,38 +721,49 @@ describe('SwapsController', function () { const optimismProvider = createTestProviderTools({ scaffold: optimismProviderResultStub, networkId: 10, - chainId: 10, + chainId: CHAIN_IDS.OPTIMISM as ChainId, }).provider; swapsController = getSwapsController(optimismProvider); const fetchTradesInfoSpy = jest - .spyOn(swapsController, '_fetchTradesInfo') + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .spyOn(swapsController as any, '_fetchTradesInfo') .mockReturnValue(getMockQuotes()); // Make it so approval is not required jest - .spyOn(swapsController, '_getERC20Allowance') + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .spyOn(swapsController as any, '_getERC20Allowance') .mockReturnValue(BigNumber.from(1)); // Make the network fetch error message disappear - jest.spyOn(swapsController, '_setSwapsNetworkConfig').mockReturnValue(); + jest + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .spyOn(swapsController as any, '_setSwapsNetworkConfig') + .mockReturnValue(undefined); - const [newQuotes] = await swapsController.fetchAndSetQuotes( + const fetchResponse = await swapsController.fetchAndSetQuotes( MOCK_FETCH_PARAMS, OPTIMISM_MOCK_FETCH_METADATA, ); + if (!fetchResponse?.[0]) { + throw new Error('Quotes should be defined'); + } + + const [newQuotes] = fetchResponse; + expect(newQuotes[TEST_AGG_ID_BEST]).toStrictEqual({ ...getMockQuotes()[TEST_AGG_ID_BEST], - sourceTokenInfo: undefined, destinationTokenInfo: { + address: '0xSomeAddress', symbol: 'FOO', decimals: 18, }, isBestQuote: true, // TODO: find a way to calculate these values dynamically - gasEstimate: 2000000, + gasEstimate: '2000000', gasEstimateWithRefund: '0xb8cae', savings: { fee: '-0.061067', @@ -669,6 +777,11 @@ describe('SwapsController', function () { overallValueOfQuote: '49.886464', metaMaskFeeInEth: '0.50505050505050505050505050505050505', ethValueOfTokens: '50', + sourceTokenInfo: { + address: '0xSomeOtherAddress', + decimals: 18, + symbol: 'BAR', + }, }); expect(fetchTradesInfoSpy).toHaveBeenCalledTimes(1); @@ -678,17 +791,17 @@ describe('SwapsController', function () { }); it('performs the allowance check', async function () { - jest - .spyOn(swapsController, '_fetchTradesInfo') - .mockReturnValue(getMockQuotes()); - // Make it so approval is not required const getERC20AllowanceSpy = jest - .spyOn(swapsController, '_getERC20Allowance') + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .spyOn(swapsController as any, '_getERC20Allowance') .mockReturnValue(BigNumber.from(1)); // Make the network fetch error message disappear - jest.spyOn(swapsController, '_setSwapsNetworkConfig').mockReturnValue(); + jest + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .spyOn(swapsController as any, '_setSwapsNetworkConfig') + .mockReturnValue(undefined); await swapsController.fetchAndSetQuotes( MOCK_FETCH_PARAMS, @@ -705,20 +818,26 @@ describe('SwapsController', function () { it('gets the gas limit if approval is required', async function () { jest - .spyOn(swapsController, '_fetchTradesInfo') + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .spyOn(swapsController as any, '_fetchTradesInfo') .mockReturnValue(MOCK_QUOTES_APPROVAL_REQUIRED); // Ensure approval is required jest - .spyOn(swapsController, '_getERC20Allowance') + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .spyOn(swapsController as any, '_getERC20Allowance') .mockReturnValue(BigNumber.from(0)); // Make the network fetch error message disappear - jest.spyOn(swapsController, '_setSwapsNetworkConfig').mockReturnValue(); + jest + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .spyOn(swapsController as any, '_setSwapsNetworkConfig') + .mockReturnValue(undefined); const timedoutGasReturnResult = { gasLimit: 1000000 }; const timedoutGasReturnSpy = jest - .spyOn(swapsController, '_timedoutGasReturn') + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .spyOn(swapsController as any, '_timedoutGasReturn') .mockReturnValue(timedoutGasReturnResult); await swapsController.fetchAndSetQuotes( @@ -736,22 +855,33 @@ describe('SwapsController', function () { it('marks the best quote', async function () { jest - .spyOn(swapsController, '_fetchTradesInfo') + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .spyOn(swapsController as any, '_fetchTradesInfo') .mockReturnValue(getMockQuotes()); // Make it so approval is not required jest - .spyOn(swapsController, '_getERC20Allowance') + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .spyOn(swapsController as any, '_getERC20Allowance') .mockReturnValue(BigNumber.from(1)); // Make the network fetch error message disappear - jest.spyOn(swapsController, '_setSwapsNetworkConfig').mockReturnValue(); + jest + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .spyOn(swapsController as any, '_setSwapsNetworkConfig') + .mockReturnValue(undefined); - const [newQuotes, topAggId] = await swapsController.fetchAndSetQuotes( + const fetchResponse = await swapsController.fetchAndSetQuotes( MOCK_FETCH_PARAMS, MOCK_FETCH_METADATA, ); + if (!fetchResponse?.[0] || !fetchResponse[1]) { + throw new Error('newQuotes and topAggId should be defined'); + } + + const [newQuotes, topAggId] = fetchResponse; + expect(topAggId).toStrictEqual(TEST_AGG_ID_BEST); expect(newQuotes[topAggId].isBestQuote).toStrictEqual(true); }); @@ -769,243 +899,281 @@ describe('SwapsController', function () { .add((100e18).toString()) .toString(), }; + const quotes = { ...getMockQuotes(), [bestAggId]: bestQuote }; - jest.spyOn(swapsController, '_fetchTradesInfo').mockReturnValue(quotes); + jest + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .spyOn(swapsController as any, '_fetchTradesInfo') + .mockReturnValue(quotes); // Make it so approval is not required jest - .spyOn(swapsController, '_getERC20Allowance') + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .spyOn(swapsController as any, '_getERC20Allowance') .mockReturnValue(BigNumber.from(1)); // Make the network fetch error message disappear - jest.spyOn(swapsController, '_setSwapsNetworkConfig').mockReturnValue(); + jest + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .spyOn(swapsController as any, '_setSwapsNetworkConfig') + .mockReturnValue(undefined); - const [newQuotes, topAggId] = await swapsController.fetchAndSetQuotes( + const fetchResponse = await swapsController.fetchAndSetQuotes( MOCK_FETCH_PARAMS, MOCK_FETCH_METADATA, ); + if (!fetchResponse?.[0] || !fetchResponse[1]) { + throw new Error('newQuotes and topAggId should be defined'); + } + + const [newQuotes, topAggId] = fetchResponse; + 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 () { jest - .spyOn(swapsController, '_fetchTradesInfo') + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .spyOn(swapsController as any, '_fetchTradesInfo') .mockReturnValue(getMockQuotes()); // Make it so approval is not required jest - .spyOn(swapsController, '_getERC20Allowance') + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .spyOn(swapsController as any, '_getERC20Allowance') .mockReturnValue(BigNumber.from(1)); // Make the network fetch error message disappear - jest.spyOn(swapsController, '_setSwapsNetworkConfig').mockReturnValue(); + jest + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .spyOn(swapsController as any, '_setSwapsNetworkConfig') + .mockReturnValue(undefined); - swapsController.getTokenRatesState = () => ({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (swapsController as any)._getTokenRatesState = () => ({ marketData: { '0x1': {}, }, }); - const [newQuotes, topAggId] = await swapsController.fetchAndSetQuotes( + const fetchResponse = await swapsController.fetchAndSetQuotes( MOCK_FETCH_PARAMS, MOCK_FETCH_METADATA, ); - 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.mockReset(); - - const _swapsController = getSwapsController(); - - 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; - 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 () { - const _swapsController = new SwapsController({ - getBufferedGasLimit: MOCK_GET_BUFFERED_GAS_LIMIT, - provider, - getProviderConfig: MOCK_GET_PROVIDER_CONFIG, - getTokenRatesState: MOCK_TOKEN_RATES_STORE, - fetchTradesInfo: fetchTradesInfoStub, - getCurrentChainId: getCurrentChainIdStub, - }); - const currentEthersInstance = _swapsController._ethersProvider; - - // Make the network fetch error message disappear - jest.spyOn(swapsController, '_setSwapsNetworkConfig').mockReturnValue(); + if (!fetchResponse?.[0] || !fetchResponse[1]) { + throw new Error('newQuotes and topAggId should be defined'); + } - await swapsController.fetchAndSetQuotes(MOCK_FETCH_PARAMS, { - ...MOCK_FETCH_METADATA, - chainId: CHAIN_IDS.MAINNET, - }); + const [newQuotes, topAggId] = fetchResponse; - const newEthersInstance = _swapsController._ethersProvider; - expect(currentEthersInstance).toStrictEqual(newEthersInstance); + expect(newQuotes[topAggId].isBestQuote).toStrictEqual(undefined); }); - 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 () { - const _swapsController = new SwapsController({ - getBufferedGasLimit: MOCK_GET_BUFFERED_GAS_LIMIT, - provider, - getProviderConfig: MOCK_GET_PROVIDER_CONFIG, - getTokenRatesState: MOCK_TOKEN_RATES_STORE, - fetchTradesInfo: fetchTradesInfoStub, - getCurrentChainId: getCurrentChainIdStub, - getLayer1GasFee: getLayer1GasFeeStub, - getNetworkClientId: getNetworkClientIdStub, - }); - 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 secondEthersProviderChainId = - _swapsController._ethersProviderChainId; - - expect(firstEthersInstance).not.toStrictEqual(secondEthersInstance); - expect(firstEthersInstance).not.toStrictEqual( - secondEthersProviderChainId, - ); - - await _swapsController.fetchAndSetQuotes(MOCK_FETCH_PARAMS, { - ...MOCK_FETCH_METADATA, - chainId: CHAIN_IDS.LOCALHOST, - }); - - const thirdEthersInstance = _swapsController._ethersProvider; - const thirdEthersProviderChainId = - _swapsController._ethersProviderChainId; - - expect(firstEthersProviderChainId).not.toStrictEqual( - thirdEthersInstance, - ); - expect(secondEthersInstance).not.toStrictEqual(thirdEthersInstance); - expect(firstEthersInstance).not.toStrictEqual( - thirdEthersProviderChainId, - ); - expect(secondEthersProviderChainId).not.toStrictEqual( - thirdEthersProviderChainId, - ); - - await _swapsController.fetchAndSetQuotes(MOCK_FETCH_PARAMS, { - ...MOCK_FETCH_METADATA, - chainId: CHAIN_IDS.MAINNET, - }); - - const lastEthersProviderChainId = - _swapsController._ethersProviderChainId; - - expect(firstEthersProviderChainId).toStrictEqual( - lastEthersProviderChainId, - ); - }); + // TODO: Re think how to test this without exposing internal state + + // it('should replace ethers instance when called with a different chainId than was current when the controller was instantiated', async function () { + // fetchTradesInfoStub.mockReset(); + + // const _swapsController = getSwapsController(); + + // 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; + // 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 () { + // const _swapsController = new SwapsController({ + // getBufferedGasLimit: MOCK_GET_BUFFERED_GAS_LIMIT, + // provider, + // fetchTradesInfo: fetchTradesInfoStub, + // }); + // 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; + // 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 () { + // const _swapsController = new SwapsController({ + // getBufferedGasLimit: MOCK_GET_BUFFERED_GAS_LIMIT, + // provider, + // fetchTradesInfo: fetchTradesInfoStub, + // getLayer1GasFee: getLayer1GasFeeStub, + // }); + // 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 secondEthersProviderChainId = + // _swapsController._ethersProviderChainId; + + // expect(firstEthersInstance).not.toStrictEqual(secondEthersInstance); + // expect(firstEthersInstance).not.toStrictEqual( + // secondEthersProviderChainId, + // ); + + // await _swapsController.fetchAndSetQuotes(MOCK_FETCH_PARAMS, { + // ...MOCK_FETCH_METADATA, + // chainId: CHAIN_IDS.LOCALHOST, + // }); + + // const thirdEthersInstance = _swapsController._ethersProvider; + // const thirdEthersProviderChainId = + // _swapsController._ethersProviderChainId; + + // expect(firstEthersProviderChainId).not.toStrictEqual( + // thirdEthersInstance, + // ); + // expect(secondEthersInstance).not.toStrictEqual(thirdEthersInstance); + // expect(firstEthersInstance).not.toStrictEqual( + // thirdEthersProviderChainId, + // ); + // expect(secondEthersProviderChainId).not.toStrictEqual( + // thirdEthersProviderChainId, + // ); + + // await _swapsController.fetchAndSetQuotes(MOCK_FETCH_PARAMS, { + // ...MOCK_FETCH_METADATA, + // chainId: CHAIN_IDS.MAINNET, + // }); + + // const lastEthersProviderChainId = + // _swapsController._ethersProviderChainId; + + // expect(firstEthersProviderChainId).toStrictEqual( + // lastEthersProviderChainId, + // ); + // }); }); describe('resetSwapsState', function () { it('resets the swaps state correctly', function () { - const { swapsState: old } = swapsController.store.getState(); + const oldState = swapsController.state; swapsController.resetSwapsState(); - const { swapsState } = swapsController.store.getState(); + const newState = swapsController.state; - expect(swapsState).toStrictEqual({ - ...EMPTY_INIT_STATE.swapsState, - tokens: old.tokens, - swapsQuoteRefreshTime: old.swapsQuoteRefreshTime, + expect(newState.swapsState).toStrictEqual({ + ...getDefaultSwapsControllerState().swapsState, + tokens: oldState.swapsState.tokens, + swapsQuoteRefreshTime: oldState.swapsState.swapsQuoteRefreshTime, swapsQuotePrefetchingRefreshTime: - old.swapsQuotePrefetchingRefreshTime, + oldState.swapsState.swapsQuotePrefetchingRefreshTime, swapsStxGetTransactionsRefreshTime: - old.swapsStxGetTransactionsRefreshTime, - swapsStxBatchStatusRefreshTime: old.swapsStxBatchStatusRefreshTime, + oldState.swapsState.swapsStxGetTransactionsRefreshTime, + swapsStxBatchStatusRefreshTime: + oldState.swapsState.swapsStxBatchStatusRefreshTime, }); }); - it('clears polling timeout', function () { - swapsController._pollingTimeout = setTimeout(() => { - throw new Error('Polling timeout not cleared'); - }, POLLING_TIMEOUT); + // it('clears polling timeout', function () { + // swapsController._pollingTimeout = setTimeout(() => { + // throw new Error('Polling timeout not cleared'); + // }, POLLING_TIMEOUT); - // Reseting swaps state should clear the polling timeout - swapsController.resetSwapsState(); + // // Reseting swaps state should clear the polling timeout + // swapsController.resetSwapsState(); - // Verify by ensuring the error is not thrown, indicating that the timer was cleared - expect(jest.runOnlyPendingTimers).not.toThrow(); - }); + // // 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(() => { - throw new Error('Polling timeout not cleared'); - }, POLLING_TIMEOUT); + // TODO: Re think how to test this without exposing internal state - // Stop polling for quotes should clear the polling timeout - swapsController.stopPollingForQuotes(); + // it('clears polling timeout', function () { + // swapsController._pollingTimeout = setTimeout(() => { + // throw new Error('Polling timeout not cleared'); + // }, POLLING_TIMEOUT); - // Verify by ensuring the error is not thrown, indicating that the timer was cleared - expect(jest.runOnlyPendingTimers).not.toThrow(); - }); + // // Stop polling for quotes should clear the polling timeout + // swapsController.stopPollingForQuotes(); + + // // 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(); - expect(swapsState.quotes).toStrictEqual({}); - expect(swapsState.quotesLastFetched).toStrictEqual(null); + const swapsState = swapsController.state; + expect(swapsState.swapsState.quotes).toStrictEqual({}); + expect(swapsState.swapsState.quotesLastFetched).toStrictEqual(null); }); }); describe('resetPostFetchState', function () { - it('clears polling timeout', function () { - swapsController._pollingTimeout = setTimeout(() => { - throw new Error('Polling timeout not cleared'); - }, POLLING_TIMEOUT); + // TODO: Re think how to test this without exposing internal state - // Reset post fetch state should clear the polling timeout - swapsController.resetPostFetchState(); + // it('clears polling timeout', function () { + // swapsController._pollingTimeout = setTimeout(() => { + // throw new Error('Polling timeout not cleared'); + // }, POLLING_TIMEOUT); - // Verify by ensuring the error is not thrown, indicating that the timer was cleared - expect(jest.runOnlyPendingTimers).not.toThrow(); - }); + // // Reset post fetch state should clear the polling timeout + // swapsController.resetPostFetchState(); + + // // Verify by ensuring the error is not thrown, indicating that the timer was cleared + // expect(jest.runOnlyPendingTimers).not.toThrow(); + // }); it('updates state correctly', function () { - const tokens = 'test'; - const fetchParams = 'test'; + const tokens = ['']; + const fetchParams: FetchTradesInfoParams & { + metaData: FetchTradesInfoParamsMetadata; + } = { + sourceToken: '', + destinationToken: '', + sourceDecimals: 18, + slippage: 2, + value: '0x0', + fromAddress: '', + exchangeList: 'zeroExV1', + balanceError: false, + metaData: {} as FetchTradesInfoParamsMetadata, + }; const swapsFeatureIsLive = false; const swapsFeatureFlags = {}; const swapsQuoteRefreshTime = 0; const swapsQuotePrefetchingRefreshTime = 0; const swapsStxBatchStatusRefreshTime = 0; const swapsStxGetTransactionsRefreshTime = 0; - const swapsStxStatusDeadline = 0; - swapsController.store.updateState({ + swapsController.__test__updateState({ swapsState: { + ...swapsController.state.swapsState, tokens, fetchParams, swapsFeatureIsLive, @@ -1014,15 +1182,14 @@ describe('SwapsController', function () { swapsQuotePrefetchingRefreshTime, swapsStxBatchStatusRefreshTime, swapsStxGetTransactionsRefreshTime, - swapsStxStatusDeadline, }, }); swapsController.resetPostFetchState(); - const { swapsState } = swapsController.store.getState(); + const { swapsState } = swapsController.state; expect(swapsState).toStrictEqual({ - ...EMPTY_INIT_STATE.swapsState, + ...getDefaultSwapsControllerState().swapsState, tokens, fetchParams, swapsFeatureIsLive, @@ -1061,7 +1228,7 @@ describe('SwapsController', function () { metaMaskFeeInEth: '6', ethValueOfTokens: '0.6', }, - ]; + ] as Quote[]; const median = getMedianEthValueQuote(values); expect(median).toStrictEqual(expectedResult); @@ -1099,7 +1266,7 @@ describe('SwapsController', function () { metaMaskFeeInEth: '6', ethValueOfTokens: '0.6', }, - ]; + ] as Quote[]; const median = getMedianEthValueQuote(values); expect(median).toStrictEqual(expectedResult); @@ -1155,7 +1322,7 @@ describe('SwapsController', function () { ethFee: '2', metaMaskFeeInEth: '0.8', }, - ]; + ] as Quote[]; const median = getMedianEthValueQuote(values); expect(median).toStrictEqual(expectedResult); @@ -1205,136 +1372,181 @@ describe('SwapsController', function () { ethFee: '2', metaMaskFeeInEth: '0.8', }, - ]; + ] as Quote[]; const median = getMedianEthValueQuote(values); expect(median).toStrictEqual(expectedResult); }); - it('throws on empty or non-array sample', function () { + it('throws on empty array', function () { expect(() => getMedianEthValueQuote([])).toThrow( 'Expected non-empty array param.', ); - - expect(() => getMedianEthValueQuote()).toThrow( - 'Expected non-empty array param.', - ); - - expect(() => getMedianEthValueQuote({})).toThrow( - 'Expected non-empty array param.', - ); }); }); }); }); -function getMockQuotes() { +function getMockQuotes(): Record { return { [TEST_AGG_ID_1]: { - trade: { - from: '0xe18035bf8712672935fdb4e5e431b1a0183d2dfc', - value: '0x0', - gas: '0x61a80', // 4e5 - to: '0x881D40237659C251811CEC9c364ef91dC08D300C', - }, - sourceAmount: '10000000000000000000', // 10e18 - destinationAmount: '20000000000000000000', // 20e18 - error: null, - sourceToken: '0x6b175474e89094c44da98b954eedeac495271d0f', - destinationToken: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', - approvalNeeded: null, - maxGas: 600000, - averageGas: 120000, - estimatedRefund: 80000, - fetchTime: 607, aggregator: TEST_AGG_ID_1, aggType: 'AGG', - slippage: 2, - sourceTokenInfo: { - address: '0x6b175474e89094c44da98b954eedeac495271d0f', - symbol: 'DAI', - decimals: 18, - iconUrl: 'https://foo.bar/logo.png', - }, + approvalNeeded: null, + averageGas: 120000, + destinationAmount: '20000000000000000000', + destinationToken: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', destinationTokenInfo: { address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', symbol: 'USDC', decimals: 18, }, + destinationTokenRate: 2, + error: null, + estimatedRefund: '80000', + ethFee: '0.006', + ethValueOfTokens: '0.1', fee: 1, - }, - - [TEST_AGG_ID_BEST]: { + fetchTime: 607, + gasEstimate: '120000', + gasEstimateWithRefund: '100000', + gasMultiplier: 1.1, + hasRoute: true, + maxGas: 600000, + metaMaskFeeInEth: '0.001', + overallValueOfQuote: '19.994', + priceSlippage: { + bucket: 'low', + calculationError: '', + destinationAmountInETH: 0.1, + destinationAmountInNativeCurrency: 20, + ratio: 0.995, + sourceAmountInETH: 0.05, + sourceAmountInNativeCurrency: 10, + sourceAmountInUSD: 10, + destinationAmountInUSD: 20, + }, + quoteRefreshSeconds: 60, + sourceAmount: '10000000000000000000', + sourceToken: '0x6b175474e89094c44da98b954eedeac495271d0f', + sourceTokenRate: 1, trade: { + data: '0x', from: '0xe18035bf8712672935fdb4e5e431b1a0183d2dfc', + to: '0x881D40237659C251811CEC9c364ef91dC08D300C', value: '0x0', gas: '0x61a80', - to: '0x881D40237659C251811CEC9c364ef91dC08D300C', }, - sourceAmount: '10000000000000000000', - destinationAmount: '25000000000000000000', // 25e18 - error: null, - sourceToken: '0x6b175474e89094c44da98b954eedeac495271d0f', - destinationToken: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', - approvalNeeded: null, - maxGas: 1100000, - averageGas: 411000, - estimatedRefund: 343090, - fetchTime: 1003, + }, + [TEST_AGG_ID_BEST]: { aggregator: TEST_AGG_ID_BEST, aggType: 'AGG', - slippage: 2, - sourceTokenInfo: { - address: '0x6b175474e89094c44da98b954eedeac495271d0f', - symbol: 'DAI', - decimals: 18, - iconUrl: 'https://foo.bar/logo.png', - }, + approvalNeeded: null, + averageGas: 411000, + destinationAmount: '25000000000000000000', + destinationToken: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', destinationTokenInfo: { address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', symbol: 'USDC', decimals: 18, }, + destinationTokenRate: 2.5, + error: null, + estimatedRefund: '343090', + ethFee: '0.008', + ethValueOfTokens: '0.125', fee: 1, - }, - - [TEST_AGG_ID_2]: { + fetchTime: 1003, + gasEstimate: '411000', + gasEstimateWithRefund: '380000', + gasMultiplier: 1.2, + hasRoute: true, + maxGas: 1100000, + metaMaskFeeInEth: '0.0015', + overallValueOfQuote: '24.9905', + priceSlippage: { + bucket: 'medium', + calculationError: '', + destinationAmountInETH: 0.125, + destinationAmountInNativeCurrency: 25, + ratio: 0.98, + sourceAmountInETH: 0.05, + sourceAmountInNativeCurrency: 10, + sourceAmountInUSD: 10, + destinationAmountInUSD: 25, + }, + quoteRefreshSeconds: 60, + sourceAmount: '10000000000000000000', + sourceToken: '0x6b175474e89094c44da98b954eedeac495271d0f', + sourceTokenRate: 1, trade: { + data: '0x', from: '0xe18035bf8712672935fdb4e5e431b1a0183d2dfc', + to: '0x881D40237659C251811CEC9c364ef91dC08D300C', value: '0x0', gas: '0x61a80', - to: '0x881D40237659C251811CEC9c364ef91dC08D300C', }, - sourceAmount: '10000000000000000000', - destinationAmount: '22000000000000000000', // 22e18 - error: null, - sourceToken: '0x6b175474e89094c44da98b954eedeac495271d0f', - destinationToken: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', - approvalNeeded: null, - maxGas: 368000, - averageGas: 197000, - estimatedRefund: 18205, - fetchTime: 1354, + savings: { + total: '0.005', + performance: '0.003', + fee: '0.002', + metaMaskFee: '0.001', + medianMetaMaskFee: '0.0012', + }, + }, + [TEST_AGG_ID_2]: { aggregator: TEST_AGG_ID_2, aggType: 'AGG', - slippage: 2, - sourceTokenInfo: { - address: '0x6b175474e89094c44da98b954eedeac495271d0f', - symbol: 'DAI', - decimals: 18, - iconUrl: 'https://foo.bar/logo.png', - }, + approvalNeeded: null, + averageGas: 197000, + destinationAmount: '22000000000000000000', + destinationToken: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', destinationTokenInfo: { address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', symbol: 'USDC', decimals: 18, }, + destinationTokenRate: 2.2, + error: null, + estimatedRefund: '18205', + ethFee: '0.007', + ethValueOfTokens: '0.11', fee: 1, + fetchTime: 1354, + gasEstimate: '197000', + gasEstimateWithRefund: '190000', + gasMultiplier: 1.15, + hasRoute: true, + maxGas: 368000, + metaMaskFeeInEth: '0.00125', + overallValueOfQuote: '21.99175', + priceSlippage: { + bucket: 'high', + calculationError: '', + destinationAmountInETH: 0.11, + destinationAmountInNativeCurrency: 22, + ratio: 0.99, + sourceAmountInETH: 0.05, + sourceAmountInNativeCurrency: 10, + sourceAmountInUSD: 10, + destinationAmountInUSD: 22, + }, + quoteRefreshSeconds: 60, + sourceAmount: '10000000000000000000', + sourceToken: '0x6b175474e89094c44da98b954eedeac495271d0f', + sourceTokenRate: 1, + trade: { + data: '0x', + from: '0xe18035bf8712672935fdb4e5e431b1a0183d2dfc', + to: '0x881D40237659C251811CEC9c364ef91dC08D300C', + value: '0x0', + gas: '0x61a80', + }, }, }; } -function getTopQuoteAndSavingsMockQuotes() { +function getTopQuoteAndSavingsMockQuotes(): Record { // These destination amounts are calculated using the following "pre-fee" amounts // TEST_AGG_ID_1: 20.5 // TEST_AGG_ID_2: 20.4 @@ -1350,84 +1562,108 @@ function getTopQuoteAndSavingsMockQuotes() { gasEstimate: '0x186a0', destinationAmount: '20295000000000000000', destinationToken: '0x1111111111111111111111111111111111111111', - destinationTokenInfo: { decimals: 18 }, + destinationTokenInfo: { + decimals: 18, + address: '0xsomeERC20TokenAddress', + symbol: 'FOO', + }, sourceAmount: '10000000000000000000', sourceToken: '0xsomeERC20TokenAddress', trade: { value: '0x0', }, fee: 1, - }, + } as Quote, [TEST_AGG_ID_2]: { aggregator: TEST_AGG_ID_2, approvalNeeded: null, gasEstimate: '0x30d40', destinationAmount: '20196000000000000000', destinationToken: '0x1111111111111111111111111111111111111111', - destinationTokenInfo: { decimals: 18 }, + destinationTokenInfo: { + decimals: 18, + address: '0xsomeERC20TokenAddress', + symbol: 'FOO', + }, sourceAmount: '10000000000000000000', sourceToken: '0xsomeERC20TokenAddress', trade: { value: '0x0', }, fee: 1, - }, + } as Quote, [TEST_AGG_ID_3]: { aggregator: TEST_AGG_ID_3, approvalNeeded: null, gasEstimate: '0x493e0', destinationAmount: '19998000000000000000', destinationToken: '0x1111111111111111111111111111111111111111', - destinationTokenInfo: { decimals: 18 }, + destinationTokenInfo: { + decimals: 18, + address: '0xsomeERC20TokenAddress', + symbol: 'FOO', + }, sourceAmount: '10000000000000000000', sourceToken: '0xsomeERC20TokenAddress', trade: { value: '0x0', }, fee: 1, - }, + } as Quote, [TEST_AGG_ID_4]: { aggregator: TEST_AGG_ID_4, approvalNeeded: null, gasEstimate: '0x61a80', destinationAmount: '19800000000000000000', destinationToken: '0x1111111111111111111111111111111111111111', - destinationTokenInfo: { decimals: 18 }, + destinationTokenInfo: { + decimals: 18, + address: '0xsomeERC20TokenAddress', + symbol: 'FOO', + }, sourceAmount: '10000000000000000000', sourceToken: '0xsomeERC20TokenAddress', trade: { value: '0x0', }, fee: 1, - }, + } as Quote, [TEST_AGG_ID_5]: { aggregator: TEST_AGG_ID_5, approvalNeeded: null, gasEstimate: '0x7a120', destinationAmount: '19602000000000000000', destinationToken: '0x1111111111111111111111111111111111111111', - destinationTokenInfo: { decimals: 18 }, + destinationTokenInfo: { + decimals: 18, + address: '0xsomeERC20TokenAddress', + symbol: 'FOO', + }, sourceAmount: '10000000000000000000', sourceToken: '0xsomeERC20TokenAddress', trade: { value: '0x0', }, fee: 1, - }, + } as Quote, [TEST_AGG_ID_6]: { aggregator: TEST_AGG_ID_6, approvalNeeded: null, gasEstimate: '0x927c0', destinationAmount: '19305000000000000000', destinationToken: '0x1111111111111111111111111111111111111111', - destinationTokenInfo: { decimals: 18 }, + destinationTokenInfo: { + decimals: 18, + address: '0xsomeERC20TokenAddress', + symbol: 'FOO', + }, sourceAmount: '10000000000000000000', sourceToken: '0xsomeERC20TokenAddress', trade: { value: '0x0', }, fee: 1, - }, + } as Quote, }; } diff --git a/app/scripts/controllers/swaps/swaps.types.ts b/app/scripts/controllers/swaps/swaps.types.ts new file mode 100644 index 000000000000..44e4d4939742 --- /dev/null +++ b/app/scripts/controllers/swaps/swaps.types.ts @@ -0,0 +1,430 @@ +import { ExternalProvider, JsonRpcFetchFunc } from '@ethersproject/providers'; +import { TokenRatesControllerGetStateAction } from '@metamask/assets-controllers'; +import { + ControllerGetStateAction, + ControllerStateChangeEvent, + RestrictedControllerMessenger, +} from '@metamask/base-controller'; +import type { ChainId } from '@metamask/controller-utils'; +import { GasFeeState } from '@metamask/gas-fee-controller'; +import { + NetworkControllerGetNetworkClientByIdAction, + NetworkControllerGetStateAction, +} from '@metamask/network-controller'; +import { TransactionParams } from '@metamask/transaction-controller'; +import type { + MetaMetricsEventCategory, + MetaMetricsEventName, +} from '../../../../shared/constants/metametrics'; +import { fetchTradesInfo as defaultFetchTradesInfo } from '../../../../shared/lib/swaps-utils'; +import { controllerName } from './swaps.constants'; +import SwapsController from '.'; + +export type SwapsControllerState = { + swapsState: { + 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; + }; +}; + +/** + * The action that fetches the state of the {@link SwapsController}. + */ +export type SwapsControllerGetStateAction = ControllerGetStateAction< + typeof controllerName, + SwapsControllerState +>; + +/** + * The event that {@link SwapsController} can emit. + */ +export type SwapsControllerStateChangeEvent = ControllerStateChangeEvent< + typeof controllerName, + SwapsControllerState +>; + +/** + * The external actions available to the {@link SwapsController}. + */ +export type AllowedActions = + | NetworkControllerGetStateAction + | NetworkControllerGetNetworkClientByIdAction + | TokenRatesControllerGetStateAction; + +/** + * The internal actions available to the SwapsController. + */ +export type SwapsControllerActions = + | SwapsControllerGetStateAction + | SwapsControllerFetchAndSetQuotesAction + | SwapsControllerSetSelectedQuoteAggIdAction + | SwapsControllerResetSwapsStateAction + | SwapsControllerSetSwapsTokensAction + | SwapsControllerClearSwapsQuotesAction + | SwapsControllerSetApproveTxIdAction + | SwapsControllerSetTradeTxIdAction + | SwapsControllerSetSwapsTxGasPriceAction + | SwapsControllerSetSwapsTxGasLimitAction + | SwapsControllerSetSwapsTxMaxFeePerGasAction + | SwapsControllerSetSwapsTxMaxFeePriorityPerGasAction + | SwapsControllerSafeRefetchQuotesAction + | SwapsControllerStopPollingForQuotesAction + | SwapsControllerSetBackgroundSwapRouteStateAction + | SwapsControllerResetPostFetchStateAction + | SwapsControllerSetSwapsErrorKeyAction + | SwapsControllerSetInitialGasEstimateAction + | SwapsControllerSetCustomApproveTxDataAction + | SwapsControllerSetSwapsLivenessAction + | SwapsControllerSetSwapsFeatureFlagsAction + | SwapsControllerSetSwapsUserFeeLevelAction + | SwapsControllerSetSwapsQuotesPollingLimitEnabledAction; + +/** + * The internal events available to the SwapsController. + */ +export type SwapsControllerEvents = SwapsControllerStateChangeEvent; + +/** + * The messenger for the SwapsController. + */ +export type SwapsControllerMessenger = RestrictedControllerMessenger< + typeof controllerName, + SwapsControllerActions | AllowedActions, + SwapsControllerEvents, + AllowedActions['type'], + never +>; + +/** + * The action that fetches and sets quotes in the {@link SwapsController}. + */ +export type SwapsControllerFetchAndSetQuotesAction = { + type: `SwapsController:fetchAndSetQuotes`; + handler: SwapsController['fetchAndSetQuotes']; +}; + +/** + * The action that sets the selected quote aggregation ID in the {@link SwapsController}. + */ +export type SwapsControllerSetSelectedQuoteAggIdAction = { + type: `SwapsController:setSelectedQuoteAggId`; + handler: SwapsController['setSelectedQuoteAggId']; +}; + +/** + * The action that resets the swaps state in the {@link SwapsController}. + */ +export type SwapsControllerResetSwapsStateAction = { + type: `SwapsController:resetSwapsState`; + handler: SwapsController['resetSwapsState']; +}; + +/** + * The action that sets the swaps tokens in the {@link SwapsController}. + */ +export type SwapsControllerSetSwapsTokensAction = { + type: `SwapsController:setSwapsTokens`; + handler: SwapsController['setSwapsTokens']; +}; + +/** + * The action that clears the swaps quotes in the {@link SwapsController}. + */ +export type SwapsControllerClearSwapsQuotesAction = { + type: `SwapsController:clearSwapsQuotes`; + handler: SwapsController['clearSwapsQuotes']; +}; + +/** + * The action that sets the approve transaction ID in the {@link SwapsController}. + */ +export type SwapsControllerSetApproveTxIdAction = { + type: `SwapsController:setApproveTxId`; + handler: SwapsController['setApproveTxId']; +}; + +/** + * The action that sets the trade transaction ID in the {@link SwapsController}. + */ +export type SwapsControllerSetTradeTxIdAction = { + type: `SwapsController:setTradeTxId`; + handler: SwapsController['setTradeTxId']; +}; + +/** + * The action that sets the swaps transaction gas price in the {@link SwapsController}. + */ +export type SwapsControllerSetSwapsTxGasPriceAction = { + type: `SwapsController:setSwapsTxGasPrice`; + handler: SwapsController['setSwapsTxGasPrice']; +}; + +/** + * The action that sets the swaps transaction gas limit in the {@link SwapsController}. + */ +export type SwapsControllerSetSwapsTxGasLimitAction = { + type: `SwapsController:setSwapsTxGasLimit`; + handler: SwapsController['setSwapsTxGasLimit']; +}; + +/** + * The action that sets the swaps transaction max fee per gas in the {@link SwapsController}. + */ +export type SwapsControllerSetSwapsTxMaxFeePerGasAction = { + type: `SwapsController:setSwapsTxMaxFeePerGas`; + handler: SwapsController['setSwapsTxMaxFeePerGas']; +}; + +/** + * The action that sets the swaps transaction max fee priority per gas in the {@link SwapsController}. + */ +export type SwapsControllerSetSwapsTxMaxFeePriorityPerGasAction = { + type: `SwapsController:setSwapsTxMaxFeePriorityPerGas`; + handler: SwapsController['setSwapsTxMaxFeePriorityPerGas']; +}; + +/** + * The action that safely refetches quotes in the {@link SwapsController}. + */ +export type SwapsControllerSafeRefetchQuotesAction = { + type: `SwapsController:safeRefetchQuotes`; + handler: SwapsController['safeRefetchQuotes']; +}; + +/** + * The action that stops polling for quotes in the {@link SwapsController}. + */ +export type SwapsControllerStopPollingForQuotesAction = { + type: `SwapsController:stopPollingForQuotes`; + handler: SwapsController['stopPollingForQuotes']; +}; + +/** + * The action that sets the background swap route state in the {@link SwapsController}. + */ +export type SwapsControllerSetBackgroundSwapRouteStateAction = { + type: `SwapsController:setBackgroundSwapRouteState`; + handler: SwapsController['setBackgroundSwapRouteState']; +}; + +/** + * The action that resets the post-fetch state in the {@link SwapsController}. + */ +export type SwapsControllerResetPostFetchStateAction = { + type: `SwapsController:resetPostFetchState`; + handler: SwapsController['resetPostFetchState']; +}; + +/** + * The action that sets the swaps error key in the {@link SwapsController}. + */ +export type SwapsControllerSetSwapsErrorKeyAction = { + type: `SwapsController:setSwapsErrorKey`; + handler: SwapsController['setSwapsErrorKey']; +}; + +/** + * The action that sets the initial gas estimate in the {@link SwapsController}. + */ +export type SwapsControllerSetInitialGasEstimateAction = { + type: `SwapsController:setInitialGasEstimate`; + handler: SwapsController['setInitialGasEstimate']; +}; + +/** + * The action that sets custom approve transaction data in the {@link SwapsController}. + */ +export type SwapsControllerSetCustomApproveTxDataAction = { + type: `SwapsController:setCustomApproveTxData`; + handler: SwapsController['setCustomApproveTxData']; +}; + +/** + * The action that sets the swaps liveness in the {@link SwapsController}. + */ +export type SwapsControllerSetSwapsLivenessAction = { + type: `SwapsController:setSwapsLiveness`; + handler: SwapsController['setSwapsLiveness']; +}; + +/** + * The action that sets the swaps feature flags in the {@link SwapsController}. + */ +export type SwapsControllerSetSwapsFeatureFlagsAction = { + type: `SwapsController:setSwapsFeatureFlags`; + handler: SwapsController['setSwapsFeatureFlags']; +}; + +/** + * The action that sets the swaps user fee level in the {@link SwapsController}. + */ +export type SwapsControllerSetSwapsUserFeeLevelAction = { + type: `SwapsController:setSwapsUserFeeLevel`; + handler: SwapsController['setSwapsUserFeeLevel']; +}; + +/** + * The action that sets the swaps quotes polling limit enabled in the {@link SwapsController}. + */ +export type SwapsControllerSetSwapsQuotesPollingLimitEnabledAction = { + type: `SwapsController:setSwapsQuotesPollingLimitEnabled`; + handler: SwapsController['setSwapsQuotesPollingLimitEnabled']; +}; + +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 SwapsControllerOptions = { + getBufferedGasLimit: ( + params: { + txParams: { + value: string; + data: string; + to: string; + from: string; + }; + }, + factor: number, + ) => Promise<{ gasLimit: string; simulationFails: boolean }>; + provider: ExternalProvider | JsonRpcFetchFunc; + fetchTradesInfo: typeof defaultFetchTradesInfo; + getLayer1GasFee: (params: { + transactionParams: TransactionParams; + chainId: ChainId; + }) => Promise; + getEIP1559GasFeeEstimates: () => Promise; + trackMetaMetricsEvent: (event: { + event: MetaMetricsEventName; + category: MetaMetricsEventCategory; + properties: Record; + }) => void; + messenger: SwapsControllerMessenger; +}; + +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/swaps.utils.ts similarity index 100% rename from app/scripts/controllers/swaps.utils.ts rename to app/scripts/controllers/swaps/swaps.utils.ts diff --git a/app/scripts/controllers/user-storage/encryption/cache.ts b/app/scripts/controllers/user-storage/encryption/cache.ts deleted file mode 100644 index 7e9d80c78442..000000000000 --- a/app/scripts/controllers/user-storage/encryption/cache.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { base64ToByteArray, byteArrayToBase64 } from './utils'; - -type CachedEntry = { - salt: Uint8Array; - base64Salt: string; - key: Uint8Array; -}; - -const MAX_PASSWORD_CACHES = 3; -const MAX_SALT_CACHES = 10; - -/** - * In-Memory Caching derived keys based from a given salt and password. - */ -type PasswordMemCachedKDF = { - [hashedPassword: string]: Map; -}; -let inMemCachedKDF: PasswordMemCachedKDF = {}; -const getPasswordCache = (hashedPassword: string) => { - inMemCachedKDF[hashedPassword] ??= new Map(); - return inMemCachedKDF[hashedPassword]; -}; - -/** - * Returns a given cached derived key from a hashed password and salt - * - * @param hashedPassword - hashed password for cache lookup - * @param salt - provide salt to receive cached key - * @returns cached key - */ -export function getCachedKeyBySalt( - hashedPassword: string, - salt: Uint8Array, -): CachedEntry | undefined { - const cache = getPasswordCache(hashedPassword); - const base64Salt = byteArrayToBase64(salt); - const cachedKey = cache.get(base64Salt); - if (!cachedKey) { - return undefined; - } - - return { - salt, - base64Salt, - key: cachedKey, - }; -} - -/** - * Gets any cached key for a given hashed password - * - * @param hashedPassword - hashed password for cache lookup - * @returns any (the first) cached key - */ -export function getAnyCachedKey( - hashedPassword: string, -): CachedEntry | undefined { - const cache = getPasswordCache(hashedPassword); - - // Takes 1 item from an Iterator via Map.entries() - const cachedEntry: [string, Uint8Array] | undefined = cache - .entries() - .next().value; - - if (!cachedEntry) { - return undefined; - } - - const base64Salt = cachedEntry[0]; - const bytesSalt = base64ToByteArray(base64Salt); - return { - salt: bytesSalt, - base64Salt, - key: cachedEntry[1], - }; -} - -/** - * Sets a key to the in memory cache. - * We have set an arbitrary size of 10 cached keys per hashed password. - * - * @param hashedPassword - hashed password for cache lookup - * @param salt - salt to set new derived key - * @param key - derived key we are setting - */ -export function setCachedKey( - hashedPassword: string, - salt: Uint8Array, - key: Uint8Array, -): void { - // Max password caches - if (Object.keys(inMemCachedKDF).length > MAX_PASSWORD_CACHES) { - inMemCachedKDF = {}; - } - - const cache = getPasswordCache(hashedPassword); - const base64Salt = byteArrayToBase64(salt); - - // Max salt caches - if (cache.size > MAX_SALT_CACHES) { - cache.clear(); - } - - cache.set(base64Salt, key); -} diff --git a/app/scripts/controllers/user-storage/encryption/encryption.test.ts b/app/scripts/controllers/user-storage/encryption/encryption.test.ts deleted file mode 100644 index dbe34a73a5e8..000000000000 --- a/app/scripts/controllers/user-storage/encryption/encryption.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { createMockFullUserStorage } from '../../metamask-notifications/mocks/mock-notification-user-storage'; -import encryption, { createSHA256Hash } from './encryption'; - -describe('encryption tests', () => { - const PASSWORD = '123'; - const DATA1 = 'Hello World'; - const DATA2 = JSON.stringify({ foo: 'bar' }); - const DATA3 = JSON.stringify(createMockFullUserStorage()); - - it('Should encrypt and decrypt data', () => { - function actEncryptDecrypt(data: string) { - const encryptedString = encryption.encryptString(data, PASSWORD); - const decryptString = encryption.decryptString(encryptedString, PASSWORD); - return decryptString; - } - - expect(actEncryptDecrypt(DATA1)).toBe(DATA1); - expect(actEncryptDecrypt(DATA2)).toBe(DATA2); - expect(actEncryptDecrypt(DATA3)).toBe(DATA3); - }); - - it('Should decrypt some existing data', () => { - const encryptedData = `{"v":"1","t":"scrypt","d":"WNEp1QXUZsxCfW9b27uzZ18CtsMvKP6+cqLq8NLAItXeYcFcUjtKprfvedHxf5JN9Q7pe50qnA==","o":{"N":131072,"r":8,"p":1,"dkLen":16},"saltLen":16}`; - const result = encryption.decryptString(encryptedData, PASSWORD); - expect(result).toBe(DATA1); - }); - - it('Should sha-256 hash a value and should be deterministic', () => { - const DATA = 'Hello World'; - const EXPECTED_HASH = - 'a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e'; - - const hash1 = createSHA256Hash(DATA); - expect(hash1).toBe(EXPECTED_HASH); - - // Hash should be deterministic (same output with same input) - const hash2 = createSHA256Hash(DATA); - expect(hash1).toBe(hash2); - }); -}); diff --git a/app/scripts/controllers/user-storage/encryption/encryption.ts b/app/scripts/controllers/user-storage/encryption/encryption.ts deleted file mode 100644 index cdd8feaf77b3..000000000000 --- a/app/scripts/controllers/user-storage/encryption/encryption.ts +++ /dev/null @@ -1,196 +0,0 @@ -import { scrypt } from '@noble/hashes/scrypt'; -import { sha256 } from '@noble/hashes/sha256'; -import { utf8ToBytes, concatBytes, bytesToHex } from '@noble/hashes/utils'; -import { gcm } from '@noble/ciphers/aes'; -import { randomBytes } from '@noble/ciphers/webcrypto'; -import { getAnyCachedKey, getCachedKeyBySalt, setCachedKey } from './cache'; -import { base64ToByteArray, byteArrayToBase64, bytesToUtf8 } from './utils'; - -export type EncryptedPayload = { - // version - v: '1'; - - // key derivation function algorithm - scrypt - t: 'scrypt'; - - // data - d: string; - - // encryption options - scrypt - o: { - N: number; - r: number; - p: number; - dkLen: number; - }; - - // Salt options - saltLen: number; -}; - -class EncryptorDecryptor { - #ALGORITHM_NONCE_SIZE: number = 12; // 12 bytes - - #ALGORITHM_KEY_SIZE: number = 16; // 16 bytes - - #SCRYPT_SALT_SIZE: number = 16; // 16 bytes - - // see: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#scrypt - #SCRYPT_N: number = 2 ** 17; // CPU/memory cost parameter (must be a power of 2, > 1) - - #SCRYPT_r: number = 8; // Block size parameter - - #SCRYPT_p: number = 1; // Parallelization parameter - - encryptString(plaintext: string, password: string): string { - try { - return this.#encryptStringV1(plaintext, password); - } catch (e) { - const errorMessage = e instanceof Error ? e.message : e; - throw new Error(`Unable to encrypt string - ${errorMessage}`); - } - } - - decryptString(encryptedDataStr: string, password: string): string { - try { - const encryptedData: EncryptedPayload = JSON.parse(encryptedDataStr); - if (encryptedData.v === '1') { - if (encryptedData.t === 'scrypt') { - return this.#decryptStringV1(encryptedData, password); - } - } - throw new Error( - `Unsupported encrypted data payload - ${encryptedDataStr}`, - ); - } catch (e) { - const errorMessage = e instanceof Error ? e.message : e; - throw new Error(`Unable to decrypt string - ${errorMessage}`); - } - } - - #encryptStringV1(plaintext: string, password: string): string { - const { key, salt } = this.#getOrGenerateScryptKey(password, { - N: this.#SCRYPT_N, - r: this.#SCRYPT_r, - p: this.#SCRYPT_p, - dkLen: this.#ALGORITHM_KEY_SIZE, - }); - - // Encrypt and prepend salt. - const plaintextRaw = utf8ToBytes(plaintext); - const ciphertextAndNonceAndSalt = concatBytes( - salt, - this.#encrypt(plaintextRaw, key), - ); - - // Convert to Base64 - const encryptedData = byteArrayToBase64(ciphertextAndNonceAndSalt); - - const encryptedPayload: EncryptedPayload = { - v: '1', - t: 'scrypt', - d: encryptedData, - o: { - N: this.#SCRYPT_N, - r: this.#SCRYPT_r, - p: this.#SCRYPT_p, - dkLen: this.#ALGORITHM_KEY_SIZE, - }, - saltLen: this.#SCRYPT_SALT_SIZE, - }; - - return JSON.stringify(encryptedPayload); - } - - #decryptStringV1(data: EncryptedPayload, password: string): string { - const { o, d: base64CiphertextAndNonceAndSalt, saltLen } = data; - - // Decode the base64. - const ciphertextAndNonceAndSalt = base64ToByteArray( - base64CiphertextAndNonceAndSalt, - ); - - // Create buffers of salt and ciphertextAndNonce. - const salt = ciphertextAndNonceAndSalt.slice(0, saltLen); - const ciphertextAndNonce = ciphertextAndNonceAndSalt.slice( - saltLen, - ciphertextAndNonceAndSalt.length, - ); - - // Derive the key. - const { key } = this.#getOrGenerateScryptKey( - password, - { - N: o.N, - r: o.r, - p: o.p, - dkLen: o.dkLen, - }, - salt, - ); - - // Decrypt and return result. - return bytesToUtf8(this.#decrypt(ciphertextAndNonce, key)); - } - - #encrypt(plaintext: Uint8Array, key: Uint8Array): Uint8Array { - const nonce = randomBytes(this.#ALGORITHM_NONCE_SIZE); - - // Encrypt and prepend nonce. - const ciphertext = gcm(key, nonce).encrypt(plaintext); - - return concatBytes(nonce, ciphertext); - } - - #decrypt(ciphertextAndNonce: Uint8Array, key: Uint8Array): Uint8Array { - // Create buffers of nonce and ciphertext. - const nonce = ciphertextAndNonce.slice(0, this.#ALGORITHM_NONCE_SIZE); - const ciphertext = ciphertextAndNonce.slice( - this.#ALGORITHM_NONCE_SIZE, - ciphertextAndNonce.length, - ); - - // Decrypt and return result. - return gcm(key, nonce).decrypt(ciphertext); - } - - #getOrGenerateScryptKey( - password: string, - o: EncryptedPayload['o'], - salt?: Uint8Array, - ) { - const hashedPassword = createSHA256Hash(password); - const cachedKey = salt - ? getCachedKeyBySalt(hashedPassword, salt) - : getAnyCachedKey(hashedPassword); - - if (cachedKey) { - return { - key: cachedKey.key, - salt: cachedKey.salt, - }; - } - - const newSalt = salt ?? randomBytes(this.#SCRYPT_SALT_SIZE); - const newKey = scrypt(password, newSalt, { - N: o.N, - r: o.r, - p: o.p, - dkLen: o.dkLen, - }); - setCachedKey(hashedPassword, newSalt, newKey); - - return { - key: newKey, - salt: newSalt, - }; - } -} - -const encryption = new EncryptorDecryptor(); -export default encryption; - -export function createSHA256Hash(data: string): string { - const hashedData = sha256(data); - return bytesToHex(hashedData); -} diff --git a/app/scripts/controllers/user-storage/encryption/index.ts b/app/scripts/controllers/user-storage/encryption/index.ts deleted file mode 100644 index 3582e3b9e2a1..000000000000 --- a/app/scripts/controllers/user-storage/encryption/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import Encryption from './encryption'; - -export * from './encryption'; -export default Encryption; diff --git a/app/scripts/controllers/user-storage/encryption/utils.ts b/app/scripts/controllers/user-storage/encryption/utils.ts deleted file mode 100644 index 76ceda77eb7b..000000000000 --- a/app/scripts/controllers/user-storage/encryption/utils.ts +++ /dev/null @@ -1,12 +0,0 @@ -export function byteArrayToBase64(byteArray: Uint8Array) { - return Buffer.from(byteArray).toString('base64'); -} - -export function base64ToByteArray(base64: string) { - return new Uint8Array(Buffer.from(base64, 'base64')); -} - -export function bytesToUtf8(byteArray: Uint8Array) { - const decoder = new TextDecoder('utf-8'); - return decoder.decode(byteArray); -} diff --git a/app/scripts/controllers/user-storage/mocks/mockResponses.ts b/app/scripts/controllers/user-storage/mocks/mockResponses.ts deleted file mode 100644 index c1b3896f446f..000000000000 --- a/app/scripts/controllers/user-storage/mocks/mockResponses.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { createEntryPath } from '../schema'; -import { GetUserStorageResponse, USER_STORAGE_ENDPOINT } from '../services'; -import { MOCK_ENCRYPTED_STORAGE_DATA, MOCK_STORAGE_KEY } from './mockStorage'; - -type MockResponse = { - url: string; - requestMethod: 'GET' | 'POST' | 'PUT'; - response: unknown; -}; - -export const MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT = `${USER_STORAGE_ENDPOINT}${createEntryPath( - 'notification_settings', - MOCK_STORAGE_KEY, -)}`; - -const MOCK_GET_USER_STORAGE_RESPONSE: GetUserStorageResponse = { - HashedKey: 'HASHED_KEY', - Data: MOCK_ENCRYPTED_STORAGE_DATA, -}; - -export function getMockUserStorageGetResponse() { - return { - url: MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT, - requestMethod: 'GET', - response: MOCK_GET_USER_STORAGE_RESPONSE, - } satisfies MockResponse; -} - -export function getMockUserStoragePutResponse() { - return { - url: MOCK_USER_STORAGE_NOTIFICATIONS_ENDPOINT, - requestMethod: 'PUT', - response: null, - } satisfies MockResponse; -} diff --git a/app/scripts/controllers/user-storage/mocks/mockServices.ts b/app/scripts/controllers/user-storage/mocks/mockServices.ts deleted file mode 100644 index d612ebe6ed55..000000000000 --- a/app/scripts/controllers/user-storage/mocks/mockServices.ts +++ /dev/null @@ -1,34 +0,0 @@ -import nock from 'nock'; -import { - getMockUserStorageGetResponse, - getMockUserStoragePutResponse, -} from './mockResponses'; - -type MockReply = { - status: nock.StatusCode; - body?: nock.Body; -}; - -export function mockEndpointGetUserStorage(mockReply?: MockReply) { - const mockResponse = getMockUserStorageGetResponse(); - const reply = mockReply ?? { - status: 200, - body: mockResponse.response, - }; - - const mockEndpoint = nock(mockResponse.url) - .get('') - .reply(reply.status, reply.body); - - return mockEndpoint; -} - -export function mockEndpointUpsertUserStorage( - mockReply?: Pick, -) { - const mockResponse = getMockUserStoragePutResponse(); - const mockEndpoint = nock(mockResponse.url) - .put('') - .reply(mockReply?.status ?? 204); - return mockEndpoint; -} diff --git a/app/scripts/controllers/user-storage/mocks/mockStorage.ts b/app/scripts/controllers/user-storage/mocks/mockStorage.ts deleted file mode 100644 index 4a43a80556e1..000000000000 --- a/app/scripts/controllers/user-storage/mocks/mockStorage.ts +++ /dev/null @@ -1,9 +0,0 @@ -import encryption, { createSHA256Hash } from '../encryption'; - -export const MOCK_STORAGE_KEY_SIGNATURE = 'mockStorageKey'; -export const MOCK_STORAGE_KEY = createSHA256Hash(MOCK_STORAGE_KEY_SIGNATURE); -export const MOCK_STORAGE_DATA = JSON.stringify({ hello: 'world' }); -export const MOCK_ENCRYPTED_STORAGE_DATA = encryption.encryptString( - MOCK_STORAGE_DATA, - MOCK_STORAGE_KEY, -); diff --git a/app/scripts/controllers/user-storage/schema.test.ts b/app/scripts/controllers/user-storage/schema.test.ts deleted file mode 100644 index d08b0802bf49..000000000000 --- a/app/scripts/controllers/user-storage/schema.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { USER_STORAGE_ENTRIES, createEntryPath } from './schema'; - -describe('schema.ts - createEntryPath()', () => { - const MOCK_STORAGE_KEY = 'MOCK_STORAGE_KEY'; - - test('creates a valid entry path', () => { - const result = createEntryPath('notification_settings', MOCK_STORAGE_KEY); - - // Ensures that the path and the entry name are correct. - // If this differs then indicates a potential change on how this path is computed - const expected = `/${USER_STORAGE_ENTRIES.notification_settings.path}/50f65447980018849b991e038d7ad87de5cf07fbad9736b0280e93972e17bac8`; - expect(result).toBe(expected); - }); - - test('Should throw if using an entry that does not exist', () => { - expect(() => { - // @ts-expect-error mocking a fake entry for testing. - createEntryPath('fake_entry'); - }).toThrow(); - }); -}); diff --git a/app/scripts/controllers/user-storage/schema.ts b/app/scripts/controllers/user-storage/schema.ts deleted file mode 100644 index 19bc0ccfae52..000000000000 --- a/app/scripts/controllers/user-storage/schema.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { createSHA256Hash } from './encryption'; - -type UserStorageEntry = { path: string; entryName: string }; - -/** - * The User Storage Endpoint requires a path and an entry name. - * Developers can provide additional paths by extending this variable below - */ -export const USER_STORAGE_ENTRIES = { - notification_settings: { - path: 'notifications', - entryName: 'notification_settings', - }, -} satisfies Record; - -export type UserStorageEntryKeys = keyof typeof USER_STORAGE_ENTRIES; - -/** - * Constructs a unique entry path for a user. - * This can be done due to the uniqueness of the storage key (no users will share the same storage key). - * The users entry is a unique hash that cannot be reversed. - * - * @param entryKey - * @param storageKey - * @returns - */ -export function createEntryPath( - entryKey: UserStorageEntryKeys, - storageKey: string, -): string { - const entry = USER_STORAGE_ENTRIES[entryKey]; - if (!entry) { - throw new Error(`user-storage - invalid entry provided: ${entryKey}`); - } - - const hashedKey = createSHA256Hash(entry.entryName + storageKey); - return `/${entry.path}/${hashedKey}`; -} diff --git a/app/scripts/controllers/user-storage/services.test.ts b/app/scripts/controllers/user-storage/services.test.ts deleted file mode 100644 index a746dcee858f..000000000000 --- a/app/scripts/controllers/user-storage/services.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { - mockEndpointGetUserStorage, - mockEndpointUpsertUserStorage, -} from './mocks/mockServices'; -import { - MOCK_ENCRYPTED_STORAGE_DATA, - MOCK_STORAGE_DATA, - MOCK_STORAGE_KEY, -} from './mocks/mockStorage'; -import { - GetUserStorageResponse, - getUserStorage, - upsertUserStorage, -} from './services'; - -describe('user-storage/services.ts - getUserStorage() tests', () => { - test('returns user storage data', async () => { - const mockGetUserStorage = mockEndpointGetUserStorage(); - const result = await actCallGetUserStorage(); - - mockGetUserStorage.done(); - expect(result).toBe(MOCK_STORAGE_DATA); - }); - - test('returns null if endpoint does not have entry', async () => { - const mockGetUserStorage = mockEndpointGetUserStorage({ status: 404 }); - const result = await actCallGetUserStorage(); - - mockGetUserStorage.done(); - expect(result).toBe(null); - }); - - test('returns null if endpoint fails', async () => { - const mockGetUserStorage = mockEndpointGetUserStorage({ status: 500 }); - const result = await actCallGetUserStorage(); - - mockGetUserStorage.done(); - expect(result).toBe(null); - }); - - test('returns null if unable to decrypt data', async () => { - const badResponseData: GetUserStorageResponse = { - HashedKey: 'MOCK_HASH', - Data: 'Bad Encrypted Data', - }; - const mockGetUserStorage = mockEndpointGetUserStorage({ - status: 200, - body: badResponseData, - }); - const result = await actCallGetUserStorage(); - - mockGetUserStorage.done(); - expect(result).toBe(null); - }); - - function actCallGetUserStorage() { - return getUserStorage({ - bearerToken: 'MOCK_BEARER_TOKEN', - entryKey: 'notification_settings', - storageKey: MOCK_STORAGE_KEY, - }); - } -}); - -describe('user-storage/services.ts - upsertUserStorage() tests', () => { - test('invokes upsert endpoint with no errors', async () => { - const mockUpsertUserStorage = mockEndpointUpsertUserStorage(); - await actCallUpsertUserStorage(); - - expect(mockUpsertUserStorage.isDone()).toBe(true); - }); - - test('throws error if unable to upsert user storage', async () => { - const mockUpsertUserStorage = mockEndpointUpsertUserStorage({ - status: 500, - }); - - await expect(actCallUpsertUserStorage()).rejects.toThrow(); - mockUpsertUserStorage.done(); - }); - - function actCallUpsertUserStorage() { - return upsertUserStorage(MOCK_ENCRYPTED_STORAGE_DATA, { - bearerToken: 'MOCK_BEARER_TOKEN', - entryKey: 'notification_settings', - storageKey: MOCK_STORAGE_KEY, - }); - } -}); diff --git a/app/scripts/controllers/user-storage/services.ts b/app/scripts/controllers/user-storage/services.ts deleted file mode 100644 index 269009850079..000000000000 --- a/app/scripts/controllers/user-storage/services.ts +++ /dev/null @@ -1,83 +0,0 @@ -import log from 'loglevel'; - -import encryption from './encryption'; -import { UserStorageEntryKeys, createEntryPath } from './schema'; - -export const USER_STORAGE_API = process.env.USER_STORAGE_API || ''; -export const USER_STORAGE_ENDPOINT = `${USER_STORAGE_API}/api/v1/userstorage`; - -export type GetUserStorageResponse = { - HashedKey: string; - Data: string; -}; - -export type UserStorageOptions = { - bearerToken: string; - entryKey: UserStorageEntryKeys; - storageKey: string; -}; - -export async function getUserStorage( - opts: UserStorageOptions, -): Promise { - try { - const path = createEntryPath(opts.entryKey, opts.storageKey); - const url = new URL(`${USER_STORAGE_ENDPOINT}${path}`); - - const userStorageResponse = await fetch(url.toString(), { - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${opts.bearerToken}`, - }, - }); - - // Acceptable error - since indicates entry does not exist. - if (userStorageResponse.status === 404) { - return null; - } - - if (userStorageResponse.status !== 200) { - throw new Error('Unable to get User Storage'); - } - - const userStorage: GetUserStorageResponse | null = - await userStorageResponse.json(); - const encryptedData = userStorage?.Data ?? null; - - if (!encryptedData) { - return null; - } - - const decryptedData = encryption.decryptString( - encryptedData, - opts.storageKey, - ); - - return decryptedData; - } catch (e) { - log.error('Failed to get user storage', e); - return null; - } -} - -export async function upsertUserStorage( - data: string, - opts: UserStorageOptions, -): Promise { - const encryptedData = encryption.encryptString(data, opts.storageKey); - const path = createEntryPath(opts.entryKey, opts.storageKey); - const url = new URL(`${USER_STORAGE_ENDPOINT}${path}`); - - const res = await fetch(url.toString(), { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${opts.bearerToken}`, - }, - body: JSON.stringify({ data: encryptedData }), - }); - - if (!res.ok) { - throw new Error('user-storage - unable to upsert data'); - } -} diff --git a/app/scripts/controllers/user-storage/user-storage-controller.test.ts b/app/scripts/controllers/user-storage/user-storage-controller.test.ts deleted file mode 100644 index 8d3f61d6ab21..000000000000 --- a/app/scripts/controllers/user-storage/user-storage-controller.test.ts +++ /dev/null @@ -1,465 +0,0 @@ -import nock from 'nock'; -import { ControllerMessenger } from '@metamask/base-controller'; -import { - AuthenticationControllerGetBearerToken, - AuthenticationControllerGetSessionProfile, - AuthenticationControllerIsSignedIn, - AuthenticationControllerPerformSignIn, -} from '../authentication/authentication-controller'; -import { - MetamaskNotificationsControllerDisableMetamaskNotifications, - MetamaskNotificationsControllerSelectIsMetamaskNotificationsEnabled, -} from '../metamask-notifications/metamask-notifications'; -import { - MOCK_STORAGE_DATA, - MOCK_STORAGE_KEY, - MOCK_STORAGE_KEY_SIGNATURE, -} from './mocks/mockStorage'; -import UserStorageController, { - AllowedActions, - AllowedEvents, -} from './user-storage-controller'; -import { - mockEndpointGetUserStorage, - mockEndpointUpsertUserStorage, -} from './mocks/mockServices'; - -const typedMockFn = unknown>() => - jest.fn, Parameters>(); - -describe('user-storage/user-storage-controller - constructor() tests', () => { - test('Creates UserStorage with default state', () => { - const { messengerMocks } = arrangeMocks(); - const controller = new UserStorageController({ - messenger: messengerMocks.messenger, - getMetaMetricsState: () => true, - }); - - expect(controller.state.isProfileSyncingEnabled).toBe(true); - }); - - function arrangeMocks() { - return { - messengerMocks: mockUserStorageMessenger(), - }; - } -}); - -describe('user-storage/user-storage-controller - performGetStorage() tests', () => { - test('returns users notification storage', async () => { - const { messengerMocks, mockAPI } = arrangeMocks(); - const controller = new UserStorageController({ - messenger: messengerMocks.messenger, - getMetaMetricsState: () => true, - }); - - const result = await controller.performGetStorage('notification_settings'); - mockAPI.done(); - expect(result).toBe(MOCK_STORAGE_DATA); - }); - - test('rejects if UserStorage is not enabled', async () => { - const { messengerMocks } = arrangeMocks(); - const controller = new UserStorageController({ - messenger: messengerMocks.messenger, - getMetaMetricsState: () => true, - state: { - isProfileSyncingEnabled: false, - isProfileSyncingUpdateLoading: false, - }, - }); - - await expect( - controller.performGetStorage('notification_settings'), - ).rejects.toThrow(); - }); - - test('rejects if wallet is locked', async () => { - const { messengerMocks } = arrangeMocks(); - - // Mock wallet is locked - messengerMocks.mockKeyringControllerGetState.mockReturnValue({ - isUnlocked: false, - }); - - const controller = new UserStorageController({ - messenger: messengerMocks.messenger, - getMetaMetricsState: () => true, - }); - - await expect( - controller.performGetStorage('notification_settings'), - ).rejects.toThrow(); - }); - - // @ts-expect-error This is missing from the Mocha type definitions - test.each([ - [ - 'fails when no bearer token is found (auth errors)', - (messengerMocks: ReturnType) => - messengerMocks.mockAuthGetBearerToken.mockRejectedValue( - new Error('MOCK FAILURE'), - ), - ], - [ - 'fails when no session identifier is found (auth errors)', - (messengerMocks: ReturnType) => - messengerMocks.mockAuthGetSessionProfile.mockRejectedValue( - new Error('MOCK FAILURE'), - ), - ], - ])( - 'rejects on auth failure - %s', - async ( - _: string, - arrangeFailureCase: ( - messengerMocks: ReturnType, - ) => void, - ) => { - const { messengerMocks } = arrangeMocks(); - arrangeFailureCase(messengerMocks); - const controller = new UserStorageController({ - messenger: messengerMocks.messenger, - getMetaMetricsState: () => true, - }); - - await expect( - controller.performGetStorage('notification_settings'), - ).rejects.toThrow(); - }, - ); - - function arrangeMocks() { - return { - messengerMocks: mockUserStorageMessenger(), - mockAPI: mockEndpointGetUserStorage(), - }; - } -}); - -describe('user-storage/user-storage-controller - performSetStorage() tests', () => { - test('saves users storage', async () => { - const { messengerMocks, mockAPI } = arrangeMocks(); - const controller = new UserStorageController({ - messenger: messengerMocks.messenger, - getMetaMetricsState: () => true, - }); - - await controller.performSetStorage('notification_settings', 'new data'); - mockAPI.done(); - }); - - test('rejects if UserStorage is not enabled', async () => { - const { messengerMocks } = arrangeMocks(); - const controller = new UserStorageController({ - messenger: messengerMocks.messenger, - getMetaMetricsState: () => true, - state: { - isProfileSyncingEnabled: false, - isProfileSyncingUpdateLoading: false, - }, - }); - - await expect( - controller.performSetStorage('notification_settings', 'new data'), - ).rejects.toThrow(); - }); - - test('rejects if wallet is locked', async () => { - const { messengerMocks } = arrangeMocks(); - - // Mock wallet is locked - messengerMocks.mockKeyringControllerGetState.mockReturnValue({ - isUnlocked: false, - }); - - const controller = new UserStorageController({ - messenger: messengerMocks.messenger, - getMetaMetricsState: () => true, - }); - - await expect( - controller.performSetStorage('notification_settings', 'new data'), - ).rejects.toThrow(); - }); - - // @ts-expect-error This is missing from the Mocha type definitions - test.each([ - [ - 'fails when no bearer token is found (auth errors)', - (messengerMocks: ReturnType) => - messengerMocks.mockAuthGetBearerToken.mockRejectedValue( - new Error('MOCK FAILURE'), - ), - ], - [ - 'fails when no session identifier is found (auth errors)', - (messengerMocks: ReturnType) => - messengerMocks.mockAuthGetSessionProfile.mockRejectedValue( - new Error('MOCK FAILURE'), - ), - ], - ])( - 'rejects on auth failure - %s', - async ( - _: string, - arrangeFailureCase: ( - messengerMocks: ReturnType, - ) => void, - ) => { - const { messengerMocks } = arrangeMocks(); - arrangeFailureCase(messengerMocks); - const controller = new UserStorageController({ - messenger: messengerMocks.messenger, - getMetaMetricsState: () => true, - }); - - await expect( - controller.performSetStorage('notification_settings', 'new data'), - ).rejects.toThrow(); - }, - ); - - test('rejects if api call fails', async () => { - const { messengerMocks } = arrangeMocks({ - mockAPI: mockEndpointUpsertUserStorage({ status: 500 }), - }); - const controller = new UserStorageController({ - messenger: messengerMocks.messenger, - getMetaMetricsState: () => true, - }); - await expect( - controller.performSetStorage('notification_settings', 'new data'), - ).rejects.toThrow(); - }); - - function arrangeMocks(overrides?: { mockAPI?: nock.Scope }) { - return { - messengerMocks: mockUserStorageMessenger(), - mockAPI: overrides?.mockAPI ?? mockEndpointUpsertUserStorage(), - }; - } -}); - -describe('user-storage/user-storage-controller - performSetStorage() tests', () => { - test('Should return a storage key', async () => { - const { messengerMocks } = arrangeMocks(); - const controller = new UserStorageController({ - messenger: messengerMocks.messenger, - getMetaMetricsState: () => true, - }); - - const result = await controller.getStorageKey(); - expect(result).toBe(MOCK_STORAGE_KEY); - }); - - test('rejects if UserStorage is not enabled', async () => { - const { messengerMocks } = arrangeMocks(); - const controller = new UserStorageController({ - messenger: messengerMocks.messenger, - getMetaMetricsState: () => true, - state: { - isProfileSyncingEnabled: false, - isProfileSyncingUpdateLoading: false, - }, - }); - - await expect(controller.getStorageKey()).rejects.toThrow(); - }); - - function arrangeMocks() { - return { - messengerMocks: mockUserStorageMessenger(), - }; - } -}); - -describe('user-storage/user-storage-controller - disableProfileSyncing() tests', () => { - test('should disable user storage / profile syncing when called', async () => { - const { messengerMocks } = arrangeMocks(); - const controller = new UserStorageController({ - messenger: messengerMocks.messenger, - getMetaMetricsState: () => true, - }); - - expect(controller.state.isProfileSyncingEnabled).toBe(true); - await controller.disableProfileSyncing(); - expect(controller.state.isProfileSyncingEnabled).toBe(false); - }); - - function arrangeMocks() { - return { - messengerMocks: mockUserStorageMessenger(), - }; - } -}); - -describe('user-storage/user-storage-controller - enableProfileSyncing() tests', () => { - test('should enable user storage / profile syncing', async () => { - const { messengerMocks } = arrangeMocks(); - messengerMocks.mockAuthIsSignedIn.mockReturnValue(false); // mock that auth is not enabled - - const controller = new UserStorageController({ - messenger: messengerMocks.messenger, - getMetaMetricsState: () => true, - state: { - isProfileSyncingEnabled: false, - isProfileSyncingUpdateLoading: false, - }, - }); - - expect(controller.state.isProfileSyncingEnabled).toBe(false); - await controller.enableProfileSyncing(); - expect(controller.state.isProfileSyncingEnabled).toBe(true); - expect(messengerMocks.mockAuthIsSignedIn).toBeCalled(); - expect(messengerMocks.mockAuthPerformSignIn).toBeCalled(); - }); - - function arrangeMocks() { - return { - messengerMocks: mockUserStorageMessenger(), - }; - } -}); - -function mockUserStorageMessenger() { - const messenger = new ControllerMessenger< - AllowedActions, - AllowedEvents - >().getRestricted({ - name: 'UserStorageController', - allowedActions: [ - 'KeyringController:getState', - 'SnapController:handleRequest', - 'AuthenticationController:getBearerToken', - 'AuthenticationController:getSessionProfile', - 'AuthenticationController:isSignedIn', - 'AuthenticationController:performSignIn', - 'AuthenticationController:performSignOut', - 'MetamaskNotificationsController:disableMetamaskNotifications', - 'MetamaskNotificationsController:selectIsMetamaskNotificationsEnabled', - ], - allowedEvents: ['KeyringController:lock', 'KeyringController:unlock'], - }); - - const mockSnapGetPublicKey = jest.fn().mockResolvedValue('MOCK_PUBLIC_KEY'); - const mockSnapSignMessage = jest - .fn() - .mockResolvedValue(MOCK_STORAGE_KEY_SIGNATURE); - - const mockAuthGetBearerToken = - typedMockFn< - AuthenticationControllerGetBearerToken['handler'] - >().mockResolvedValue('MOCK_BEARER_TOKEN'); - - const mockAuthGetSessionProfile = typedMockFn< - AuthenticationControllerGetSessionProfile['handler'] - >().mockResolvedValue({ - identifierId: '', - profileId: 'MOCK_PROFILE_ID', - }); - - const mockAuthPerformSignIn = - typedMockFn< - AuthenticationControllerPerformSignIn['handler'] - >().mockResolvedValue('New Access Token'); - - const mockAuthIsSignedIn = - typedMockFn< - AuthenticationControllerIsSignedIn['handler'] - >().mockReturnValue(true); - - const mockAuthPerformSignOut = - typedMockFn< - AuthenticationControllerIsSignedIn['handler'] - >().mockReturnValue(true); - - const mockMetamaskNotificationsIsMetamaskNotificationsEnabled = - typedMockFn< - MetamaskNotificationsControllerSelectIsMetamaskNotificationsEnabled['handler'] - >().mockReturnValue(true); - - const mockMetamaskNotificationsDisableNotifications = - typedMockFn< - MetamaskNotificationsControllerDisableMetamaskNotifications['handler'] - >().mockResolvedValue(); - - const mockKeyringControllerGetState = typedMockFn< - () => { isUnlocked: boolean } - >().mockReturnValue({ isUnlocked: true }); - - jest.spyOn(messenger, 'call').mockImplementation((...args) => { - const [actionType, params] = args; - if (actionType === 'SnapController:handleRequest') { - if (params?.request.method === 'getPublicKey') { - return mockSnapGetPublicKey(); - } - - if (params?.request.method === 'signMessage') { - return mockSnapSignMessage(); - } - - throw new Error( - `MOCK_FAIL - unsupported SnapController:handleRequest call: ${params?.request.method}`, - ); - } - - if (actionType === 'AuthenticationController:getBearerToken') { - return mockAuthGetBearerToken(); - } - - if (actionType === 'AuthenticationController:getSessionProfile') { - return mockAuthGetSessionProfile(); - } - - if (actionType === 'AuthenticationController:performSignIn') { - return mockAuthPerformSignIn(); - } - - if (actionType === 'AuthenticationController:isSignedIn') { - return mockAuthIsSignedIn(); - } - - if ( - actionType === - 'MetamaskNotificationsController:selectIsMetamaskNotificationsEnabled' - ) { - return mockMetamaskNotificationsIsMetamaskNotificationsEnabled(); - } - - if ( - actionType === - 'MetamaskNotificationsController:disableMetamaskNotifications' - ) { - return mockMetamaskNotificationsDisableNotifications(); - } - - if (actionType === 'AuthenticationController:performSignOut') { - return mockAuthPerformSignOut(); - } - - if (actionType === 'KeyringController:getState') { - return mockKeyringControllerGetState(); - } - - function exhaustedMessengerMocks(action: never) { - throw new Error(`MOCK_FAIL - unsupported messenger call: ${action}`); - } - - return exhaustedMessengerMocks(actionType); - }); - - return { - messenger, - mockSnapGetPublicKey, - mockSnapSignMessage, - mockAuthGetBearerToken, - mockAuthGetSessionProfile, - mockAuthPerformSignIn, - mockAuthIsSignedIn, - mockMetamaskNotificationsIsMetamaskNotificationsEnabled, - mockMetamaskNotificationsDisableNotifications, - mockAuthPerformSignOut, - mockKeyringControllerGetState, - }; -} diff --git a/app/scripts/controllers/user-storage/user-storage-controller.ts b/app/scripts/controllers/user-storage/user-storage-controller.ts deleted file mode 100644 index 9a286f801330..000000000000 --- a/app/scripts/controllers/user-storage/user-storage-controller.ts +++ /dev/null @@ -1,434 +0,0 @@ -import { - BaseController, - RestrictedControllerMessenger, - StateMetadata, -} from '@metamask/base-controller'; -import type { - KeyringControllerGetStateAction, - KeyringControllerLockEvent, - KeyringControllerUnlockEvent, -} from '@metamask/keyring-controller'; -import { HandleSnapRequest } from '@metamask/snaps-controllers'; -import { - AuthenticationControllerGetBearerToken, - AuthenticationControllerGetSessionProfile, - AuthenticationControllerIsSignedIn, - AuthenticationControllerPerformSignIn, - AuthenticationControllerPerformSignOut, -} from '../authentication/authentication-controller'; -import { - MetamaskNotificationsControllerDisableMetamaskNotifications, - MetamaskNotificationsControllerSelectIsMetamaskNotificationsEnabled, -} from '../metamask-notifications/metamask-notifications'; -import { createSnapSignMessageRequest } from '../authentication/auth-snap-requests'; -import { getUserStorage, upsertUserStorage } from './services'; -import { UserStorageEntryKeys } from './schema'; -import { createSHA256Hash } from './encryption'; - -const controllerName = 'UserStorageController'; - -// State -export type UserStorageControllerState = { - /** - * Condition used by UI and to determine if we can use some of the User Storage methods. - */ - isProfileSyncingEnabled: boolean | null; - /** - * Loading state for the profile syncing update - */ - isProfileSyncingUpdateLoading: boolean; -}; - -const defaultState: UserStorageControllerState = { - isProfileSyncingEnabled: true, - isProfileSyncingUpdateLoading: false, -}; - -const metadata: StateMetadata = { - isProfileSyncingEnabled: { - persist: true, - anonymous: true, - }, - isProfileSyncingUpdateLoading: { - persist: false, - anonymous: false, - }, -}; - -// Messenger Actions -type CreateActionsObj = { - [K in T]: { - type: `${typeof controllerName}:${K}`; - handler: UserStorageController[K]; - }; -}; -type ActionsObj = CreateActionsObj< - | 'performGetStorage' - | 'performSetStorage' - | 'getStorageKey' - | 'enableProfileSyncing' - | 'disableProfileSyncing' ->; -export type Actions = ActionsObj[keyof ActionsObj]; -export type UserStorageControllerPerformGetStorage = - ActionsObj['performGetStorage']; -export type UserStorageControllerPerformSetStorage = - ActionsObj['performSetStorage']; -export type UserStorageControllerGetStorageKey = ActionsObj['getStorageKey']; -export type UserStorageControllerEnableProfileSyncing = - ActionsObj['enableProfileSyncing']; -export type UserStorageControllerDisableProfileSyncing = - ActionsObj['disableProfileSyncing']; - -export type AllowedActions = - // Keyring Requests - | KeyringControllerGetStateAction - // Snap Requests - | HandleSnapRequest - // Auth Requests - | AuthenticationControllerGetBearerToken - | AuthenticationControllerGetSessionProfile - | AuthenticationControllerPerformSignIn - | AuthenticationControllerIsSignedIn - | AuthenticationControllerPerformSignOut - // Metamask Notifications - | MetamaskNotificationsControllerDisableMetamaskNotifications - | MetamaskNotificationsControllerSelectIsMetamaskNotificationsEnabled; - -export type AllowedEvents = - | KeyringControllerLockEvent - | KeyringControllerUnlockEvent; - -// Messenger -export type UserStorageControllerMessenger = RestrictedControllerMessenger< - typeof controllerName, - Actions | AllowedActions, - AllowedEvents, - AllowedActions['type'], - AllowedEvents['type'] ->; - -/** - * Reusable controller that allows any team to store synchronized data for a given user. - * These can be settings shared cross MetaMask clients, or data we want to persist when uninstalling/reinstalling. - * - * NOTE: - * - data stored on UserStorage is FULLY encrypted, with the only keys stored/managed on the client. - * - No one can access this data unless they are have the SRP and are able to run the signing snap. - */ -export default class UserStorageController extends BaseController< - typeof controllerName, - UserStorageControllerState, - UserStorageControllerMessenger -> { - #auth = { - getBearerToken: async () => { - return await this.messagingSystem.call( - 'AuthenticationController:getBearerToken', - ); - }, - getProfileId: async () => { - const sessionProfile = await this.messagingSystem.call( - 'AuthenticationController:getSessionProfile', - ); - return sessionProfile?.profileId; - }, - isAuthEnabled: () => { - return this.messagingSystem.call('AuthenticationController:isSignedIn'); - }, - signIn: async () => { - return await this.messagingSystem.call( - 'AuthenticationController:performSignIn', - ); - }, - signOut: async () => { - return await this.messagingSystem.call( - 'AuthenticationController:performSignOut', - ); - }, - }; - - #metamaskNotifications = { - disableMetamaskNotifications: async () => { - return await this.messagingSystem.call( - 'MetamaskNotificationsController:disableMetamaskNotifications', - ); - }, - selectIsMetamaskNotificationsEnabled: async () => { - return await this.messagingSystem.call( - 'MetamaskNotificationsController:selectIsMetamaskNotificationsEnabled', - ); - }, - }; - - #isUnlocked = false; - - #keyringController = { - setupLockedStateSubscriptions: () => { - const { isUnlocked } = this.messagingSystem.call( - 'KeyringController:getState', - ); - this.#isUnlocked = isUnlocked; - - this.messagingSystem.subscribe('KeyringController:unlock', () => { - this.#isUnlocked = true; - }); - - this.messagingSystem.subscribe('KeyringController:lock', () => { - this.#isUnlocked = false; - }); - }, - }; - - getMetaMetricsState: () => boolean; - - constructor(params: { - messenger: UserStorageControllerMessenger; - state?: UserStorageControllerState; - getMetaMetricsState: () => boolean; - }) { - super({ - messenger: params.messenger, - metadata, - name: controllerName, - state: { ...defaultState, ...params.state }, - }); - - this.getMetaMetricsState = params.getMetaMetricsState; - this.#keyringController.setupLockedStateSubscriptions(); - this.#registerMessageHandlers(); - } - - /** - * Constructor helper for registering this controller's messaging system - * actions. - */ - #registerMessageHandlers(): void { - this.messagingSystem.registerActionHandler( - 'UserStorageController:performGetStorage', - this.performGetStorage.bind(this), - ); - - this.messagingSystem.registerActionHandler( - 'UserStorageController:performSetStorage', - this.performSetStorage.bind(this), - ); - - this.messagingSystem.registerActionHandler( - 'UserStorageController:getStorageKey', - this.getStorageKey.bind(this), - ); - - this.messagingSystem.registerActionHandler( - 'UserStorageController:enableProfileSyncing', - this.enableProfileSyncing.bind(this), - ); - - this.messagingSystem.registerActionHandler( - 'UserStorageController:disableProfileSyncing', - this.disableProfileSyncing.bind(this), - ); - } - - public async enableProfileSyncing(): Promise { - try { - this.#setIsProfileSyncingUpdateLoading(true); - - const authEnabled = this.#auth.isAuthEnabled(); - if (!authEnabled) { - await this.#auth.signIn(); - } - - this.update((state) => { - state.isProfileSyncingEnabled = true; - }); - - this.#setIsProfileSyncingUpdateLoading(false); - } catch (e) { - this.#setIsProfileSyncingUpdateLoading(false); - const errorMessage = e instanceof Error ? e.message : e; - throw new Error( - `${controllerName} - failed to enable profile syncing - ${errorMessage}`, - ); - } - } - - public async setIsProfileSyncingEnabled( - isProfileSyncingEnabled: boolean, - ): Promise { - this.update((state) => { - state.isProfileSyncingEnabled = isProfileSyncingEnabled; - }); - } - - public async disableProfileSyncing(): Promise { - const isAlreadyDisabled = !this.state.isProfileSyncingEnabled; - if (isAlreadyDisabled) { - return; - } - - try { - this.#setIsProfileSyncingUpdateLoading(true); - - const isMetamaskNotificationsEnabled = - await this.#metamaskNotifications.selectIsMetamaskNotificationsEnabled(); - - if (isMetamaskNotificationsEnabled) { - await this.#metamaskNotifications.disableMetamaskNotifications(); - } - - const isMetaMetricsParticipation = this.getMetaMetricsState(); - - if (!isMetaMetricsParticipation) { - await this.messagingSystem.call( - 'AuthenticationController:performSignOut', - ); - } - - this.#setIsProfileSyncingUpdateLoading(false); - - this.update((state) => { - state.isProfileSyncingEnabled = false; - }); - } catch (e) { - this.#setIsProfileSyncingUpdateLoading(false); - const errorMessage = e instanceof Error ? e.message : e; - throw new Error( - `${controllerName} - failed to disable profile syncing - ${errorMessage}`, - ); - } - } - - /** - * Allows retrieval of stored data. Data stored is string formatted. - * Developers can extend the entry path and entry name through the `schema.ts` file. - * - * @param entryKey - * @returns the decrypted string contents found from user storage (or null if not found) - */ - public async performGetStorage( - entryKey: UserStorageEntryKeys, - ): Promise { - this.#assertProfileSyncingEnabled(); - const { bearerToken, storageKey } = - await this.#getStorageKeyAndBearerToken(); - const result = await getUserStorage({ - entryKey, - bearerToken, - storageKey, - }); - - return result; - } - - /** - * Allows storage of user data. Data stored must be string formatted. - * Developers can extend the entry path and entry name through the `schema.ts` file. - * - * @param entryKey - * @param value - The string data you want to store. - * @returns nothing. NOTE that an error is thrown if fails to store data. - */ - public async performSetStorage( - entryKey: UserStorageEntryKeys, - value: string, - ): Promise { - this.#assertProfileSyncingEnabled(); - const { bearerToken, storageKey } = - await this.#getStorageKeyAndBearerToken(); - - await upsertUserStorage(value, { - entryKey, - bearerToken, - storageKey, - }); - } - - /** - * Retrieves the storage key, for internal use only! - * - * @returns the storage key - */ - public async getStorageKey(): Promise { - this.#assertProfileSyncingEnabled(); - const storageKey = await this.#createStorageKey(); - return storageKey; - } - - #assertProfileSyncingEnabled(): void { - if (!this.state.isProfileSyncingEnabled) { - throw new Error( - `${controllerName}: Unable to call method, user is not authenticated`, - ); - } - } - - /** - * Utility to get the bearer token and storage key - */ - async #getStorageKeyAndBearerToken(): Promise<{ - bearerToken: string; - storageKey: string; - }> { - const bearerToken = await this.#auth.getBearerToken(); - if (!bearerToken) { - throw new Error('UserStorageController - unable to get bearer token'); - } - const storageKey = await this.#createStorageKey(); - - return { bearerToken, storageKey }; - } - - /** - * Rather than storing the storage key, we can compute the storage key when needed. - * - * @returns the storage key - */ - async #createStorageKey(): Promise { - const id = await this.#auth.getProfileId(); - if (!id) { - throw new Error('UserStorageController - unable to create storage key'); - } - - const storageKeySignature = await this.#snapSignMessage(`metamask:${id}`); - const storageKey = createSHA256Hash(storageKeySignature); - return storageKey; - } - - #_snapSignMessageCache: Record<`metamask:${string}`, string> = {}; - - /** - * Signs a specific message using an underlying auth snap. - * - * @param message - A specific tagged message to sign. - * @returns A Signature created by the snap. - */ - async #snapSignMessage(message: `metamask:${string}`): Promise { - if (this.#_snapSignMessageCache[message]) { - return this.#_snapSignMessageCache[message]; - } - - if (!this.#isUnlocked) { - throw new Error( - '#snapSignMessage - unable to call snap, wallet is locked', - ); - } - - const result = (await this.messagingSystem.call( - 'SnapController:handleRequest', - createSnapSignMessageRequest(message), - )) as string; - - this.#_snapSignMessageCache[message] = result; - - return result; - } - - async #setIsProfileSyncingUpdateLoading( - isProfileSyncingUpdateLoading: boolean, - ): Promise { - this.update((state) => { - state.isProfileSyncingUpdateLoading = isProfileSyncingUpdateLoading; - }); - } -} diff --git a/app/scripts/lib/backup.test.js b/app/scripts/lib/backup.test.js index 6dd5c9c40f4e..0de359da7758 100644 --- a/app/scripts/lib/backup.test.js +++ b/app/scripts/lib/backup.test.js @@ -298,7 +298,6 @@ describe('Backup', function () { }, methods: [ 'personal_sign', - 'eth_sign', 'eth_signTransaction', 'eth_signTypedData_v1', 'eth_signTypedData_v3', diff --git a/app/scripts/lib/createEvmMethodsToNonEvmAccountReqFilterMiddleware.test.ts b/app/scripts/lib/createEvmMethodsToNonEvmAccountReqFilterMiddleware.test.ts index 3b677225a148..a5e04f6b7834 100644 --- a/app/scripts/lib/createEvmMethodsToNonEvmAccountReqFilterMiddleware.test.ts +++ b/app/scripts/lib/createEvmMethodsToNonEvmAccountReqFilterMiddleware.test.ts @@ -32,11 +32,6 @@ describe('createEvmMethodsToNonEvmAccountReqFilterMiddleware', () => { method: 'eth_sendTransaction', calledNext: false, }, - { - accountType: BtcAccountType.P2wpkh, - method: 'eth_sign', - calledNext: false, - }, { accountType: BtcAccountType.P2wpkh, method: 'eth_signTypedData', @@ -72,11 +67,6 @@ describe('createEvmMethodsToNonEvmAccountReqFilterMiddleware', () => { method: 'eth_sendTransaction', calledNext: true, }, - { - accountType: EthAccountType.Eoa, - method: 'eth_sign', - calledNext: true, - }, { accountType: EthAccountType.Eoa, method: 'eth_signTypedData', diff --git a/app/scripts/lib/createMetamaskMiddleware.js b/app/scripts/lib/createMetamaskMiddleware.js index c0114dd2323c..d48ae32dc4a3 100644 --- a/app/scripts/lib/createMetamaskMiddleware.js +++ b/app/scripts/lib/createMetamaskMiddleware.js @@ -9,7 +9,6 @@ export default function createMetamaskMiddleware({ version, getAccounts, processTransaction, - processEthSignMessage, processTypedMessage, processTypedMessageV3, processTypedMessageV4, @@ -27,7 +26,6 @@ export default function createMetamaskMiddleware({ createWalletMiddleware({ getAccounts, processTransaction, - processEthSignMessage, processTypedMessage, processTypedMessageV3, processTypedMessageV4, diff --git a/app/scripts/lib/createRPCMethodTrackingMiddleware.js b/app/scripts/lib/createRPCMethodTrackingMiddleware.js index 1d7f42710cd6..abfb436d09f7 100644 --- a/app/scripts/lib/createRPCMethodTrackingMiddleware.js +++ b/app/scripts/lib/createRPCMethodTrackingMiddleware.js @@ -14,9 +14,10 @@ import { BlockaidReason, } from '../../../shared/constants/security-provider'; import { - EIP712_PRIMARY_TYPE_PERMIT, - SIGNING_METHODS, -} from '../../../shared/constants/transaction'; + PRIMARY_TYPES_ORDER, + PRIMARY_TYPES_PERMIT, +} from '../../../shared/constants/signatures'; +import { SIGNING_METHODS } from '../../../shared/constants/transaction'; import { getBlockaidMetricsProps } from '../../../ui/helpers/utils/metrics'; import { REDESIGN_APPROVAL_TYPES } from '../../../ui/pages/confirmations/utils/confirm'; import { getSnapAndHardwareInfoForMetrics } from './snap-keyring/metrics'; @@ -37,7 +38,6 @@ const RATE_LIMIT_TYPES = { * default is RANDOM_SAMPLE */ const RATE_LIMIT_MAP = { - [MESSAGE_TYPE.ETH_SIGN]: RATE_LIMIT_TYPES.NON_RATE_LIMITED, [MESSAGE_TYPE.ETH_SIGN_TYPED_DATA]: RATE_LIMIT_TYPES.NON_RATE_LIMITED, [MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V3]: RATE_LIMIT_TYPES.NON_RATE_LIMITED, [MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4]: RATE_LIMIT_TYPES.NON_RATE_LIMITED, @@ -56,7 +56,6 @@ const RATE_LIMIT_MAP = { const MESSAGE_TYPE_TO_APPROVAL_TYPE = { [MESSAGE_TYPE.PERSONAL_SIGN]: ApprovalType.PersonalSign, - [MESSAGE_TYPE.ETH_SIGN]: ApprovalType.Sign, [MESSAGE_TYPE.SIGN]: ApprovalType.SignTransaction, [MESSAGE_TYPE.ETH_SIGN_TYPED_DATA]: ApprovalType.EthSignTypedData, [MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V1]: ApprovalType.EthSignTypedData, @@ -70,12 +69,6 @@ const MESSAGE_TYPE_TO_APPROVAL_TYPE = { * appropriate event names. */ const EVENT_NAME_MAP = { - [MESSAGE_TYPE.ETH_SIGN]: { - APPROVED: MetaMetricsEventName.SignatureApproved, - FAILED: MetaMetricsEventName.SignatureFailed, - REJECTED: MetaMetricsEventName.SignatureRejected, - REQUESTED: MetaMetricsEventName.SignatureRequested, - }, [MESSAGE_TYPE.ETH_SIGN_TYPED_DATA]: { APPROVED: MetaMetricsEventName.SignatureApproved, REJECTED: MetaMetricsEventName.SignatureRejected, @@ -148,7 +141,6 @@ let globalRateLimitCount = 0; * @param {Function} opts.getDeviceModel * @param {Function} opts.isConfirmationRedesignEnabled * @param {RestrictedControllerMessenger} opts.snapAndHardwareMessenger - * @param {AppStateController} opts.appStateController * @param {number} [opts.globalRateLimitTimeout] - time, in milliseconds, of the sliding * time window that should limit the number of method calls tracked to globalRateLimitMaxAmount. * @param {number} [opts.globalRateLimitMaxAmount] - max number of method calls that should @@ -300,11 +292,16 @@ 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) { + if (PRIMARY_TYPES_PERMIT.includes(primaryType)) { eventProperties.ui_customizations = [ ...(eventProperties.ui_customizations || []), MetaMetricsEventUiCustomization.Permit, ]; + } else if (PRIMARY_TYPES_ORDER.includes(primaryType)) { + eventProperties.ui_customizations = [ + ...(eventProperties.ui_customizations || []), + MetaMetricsEventUiCustomization.Order, + ]; } } } catch (e) { @@ -345,18 +342,8 @@ export default function createRPCMethodTrackingMiddleware({ return callback(); } - // The rpc error methodNotFound implies that 'eth_sign' is disabled in Advanced Settings - const isDisabledEthSignAdvancedSetting = - method === MESSAGE_TYPE.ETH_SIGN && - res.error?.code === errorCodes.rpc.methodNotFound; - - const isDisabledRPCMethod = isDisabledEthSignAdvancedSetting; - let event; - if (isDisabledRPCMethod) { - event = eventType.FAILED; - eventProperties.error = res.error; - } else if (res.error?.code === errorCodes.provider.userRejectedRequest) { + if (res.error?.code === errorCodes.provider.userRejectedRequest) { event = eventType.REJECTED; } else if ( res.error?.code === errorCodes.rpc.internal && @@ -370,17 +357,15 @@ export default function createRPCMethodTrackingMiddleware({ } let blockaidMetricProps = {}; - if (!isDisabledRPCMethod) { - if (SIGNING_METHODS.includes(method)) { - const securityAlertResponse = - appStateController.getSignatureSecurityAlertResponse( - req.securityAlertResponse?.securityAlertId, - ); - - blockaidMetricProps = getBlockaidMetricsProps({ - securityAlertResponse, - }); - } + if (SIGNING_METHODS.includes(method)) { + const securityAlertResponse = + appStateController.getSignatureSecurityAlertResponse( + req.securityAlertResponse?.securityAlertId, + ); + + blockaidMetricProps = getBlockaidMetricsProps({ + securityAlertResponse, + }); } const properties = { diff --git a/app/scripts/lib/createRPCMethodTrackingMiddleware.test.js b/app/scripts/lib/createRPCMethodTrackingMiddleware.test.js index a0c0da552db1..b55b52425216 100644 --- a/app/scripts/lib/createRPCMethodTrackingMiddleware.test.js +++ b/app/scripts/lib/createRPCMethodTrackingMiddleware.test.js @@ -99,7 +99,7 @@ describe('createRPCMethodTrackingMiddleware', () => { describe('before participateInMetaMetrics is set', () => { it('should not track an event for a signature request', async () => { const req = { - method: MESSAGE_TYPE.ETH_SIGN, + method: MESSAGE_TYPE.PERSONAL_SIGN, origin: 'some.dapp', }; @@ -121,7 +121,7 @@ describe('createRPCMethodTrackingMiddleware', () => { it('should not track an event for a signature request', async () => { const req = { - method: MESSAGE_TYPE.ETH_SIGN, + method: MESSAGE_TYPE.PERSONAL_SIGN, origin: 'some.dapp', }; @@ -143,7 +143,7 @@ describe('createRPCMethodTrackingMiddleware', () => { it(`should immediately track a ${MetaMetricsEventName.SignatureRequested} event`, async () => { const req = { - method: MESSAGE_TYPE.ETH_SIGN, + method: MESSAGE_TYPE.PERSONAL_SIGN, origin: 'some.dapp', securityAlertResponse: { result_type: BlockaidResultType.Malicious, @@ -163,7 +163,7 @@ describe('createRPCMethodTrackingMiddleware', () => { category: MetaMetricsEventCategory.InpageProvider, event: MetaMetricsEventName.SignatureRequested, properties: { - signature_type: MESSAGE_TYPE.ETH_SIGN, + signature_type: MESSAGE_TYPE.PERSONAL_SIGN, security_alert_response: BlockaidResultType.Malicious, security_alert_reason: BlockaidReason.maliciousDomain, }, @@ -173,7 +173,7 @@ describe('createRPCMethodTrackingMiddleware', () => { it(`should track an event with correct blockaid parameters when providerRequestsCount is provided`, async () => { const req = { - method: MESSAGE_TYPE.ETH_SIGN, + method: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4, origin: 'some.dapp', securityAlertResponse: { result_type: BlockaidResultType.Malicious, @@ -204,7 +204,7 @@ describe('createRPCMethodTrackingMiddleware', () => { category: MetaMetricsEventCategory.InpageProvider, event: MetaMetricsEventName.SignatureRequested, properties: { - signature_type: MESSAGE_TYPE.ETH_SIGN, + signature_type: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4, security_alert_response: BlockaidResultType.Malicious, security_alert_reason: BlockaidReason.maliciousDomain, ppom_eth_call_count: 5, @@ -537,40 +537,10 @@ describe('createRPCMethodTrackingMiddleware', () => { }); }); - describe(`when '${MESSAGE_TYPE.ETH_SIGN}' is disabled in advanced settings`, () => { - it(`should track ${MetaMetricsEventName.SignatureFailed} and include error property`, async () => { - const mockError = { code: errorCodes.rpc.methodNotFound }; - const req = { - method: MESSAGE_TYPE.ETH_SIGN, - origin: 'some.dapp', - }; - const res = { - error: mockError, - }; - const { next, executeMiddlewareStack } = getNext(); - const handler = createHandler(); - - await handler(req, res, next); - await executeMiddlewareStack(); - - expect(trackEvent).toHaveBeenCalledTimes(2); - - expect(trackEvent.mock.calls[1][0]).toMatchObject({ - category: MetaMetricsEventCategory.InpageProvider, - event: MetaMetricsEventName.SignatureFailed, - properties: { - signature_type: MESSAGE_TYPE.ETH_SIGN, - error: mockError, - }, - referrer: { url: 'some.dapp' }, - }); - }); - }); - describe('when request is flagged as safe by security provider', () => { it(`should immediately track a ${MetaMetricsEventName.SignatureRequested} event`, async () => { const req = { - method: MESSAGE_TYPE.ETH_SIGN, + method: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4, origin: 'some.dapp', }; const res = { @@ -586,7 +556,7 @@ describe('createRPCMethodTrackingMiddleware', () => { category: MetaMetricsEventCategory.InpageProvider, event: MetaMetricsEventName.SignatureRequested, properties: { - signature_type: MESSAGE_TYPE.ETH_SIGN, + signature_type: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4, }, referrer: { url: 'some.dapp' }, }); diff --git a/app/scripts/lib/hardware-keyring-builder-factory.ts b/app/scripts/lib/hardware-keyring-builder-factory.ts index 4579c2a20375..ab88a103510f 100644 --- a/app/scripts/lib/hardware-keyring-builder-factory.ts +++ b/app/scripts/lib/hardware-keyring-builder-factory.ts @@ -1,13 +1,15 @@ import type { TrezorBridge } from '@metamask/eth-trezor-keyring'; import type { LedgerBridge } from '@metamask/eth-ledger-bridge-keyring'; import { KeyringClass, Json } from '@metamask/utils'; +import { FakeKeyringBridge } from '../../../test/stub/keyring-bridge'; /** * A transport bridge between the keyring and the hardware device. */ export type HardwareTransportBridgeClass = | (new () => TrezorBridge) - | (new () => LedgerBridge); + | (new () => LedgerBridge) + | (new () => FakeKeyringBridge); /** * Get builder function for Hardware keyrings which require an additional `opts` diff --git a/app/scripts/lib/local-store.js b/app/scripts/lib/local-store.js index 40908af5e2f5..886693ff9469 100644 --- a/app/scripts/lib/local-store.js +++ b/app/scripts/lib/local-store.js @@ -17,6 +17,7 @@ export default class ExtensionStore { // data persistence errors to sentry this.dataPersistenceFailing = false; this.mostRecentRetrievedState = null; + this.isExtensionInitialized = false; } setMetadata(initMetaData) { @@ -51,6 +52,8 @@ export default class ExtensionStore { captureException(err); } log.error('error setting state in local store:', err); + } finally { + this.isExtensionInitialized = true; } } @@ -63,6 +66,7 @@ export default class ExtensionStore { if (!this.isSupported) { return undefined; } + const result = await this._get(); // extension.storage.local always returns an obj // if the object is empty, treat it as undefined @@ -70,7 +74,9 @@ export default class ExtensionStore { this.mostRecentRetrievedState = null; return undefined; } - this.mostRecentRetrievedState = result; + if (!this.isExtensionInitialized) { + this.mostRecentRetrievedState = result; + } return result; } @@ -114,6 +120,12 @@ export default class ExtensionStore { }); }); } + + cleanUpMostRecentRetrievedState() { + if (this.mostRecentRetrievedState) { + this.mostRecentRetrievedState = null; + } + } } /** diff --git a/app/scripts/lib/local-store.test.js b/app/scripts/lib/local-store.test.js index 8b786ca819f0..34289185de79 100644 --- a/app/scripts/lib/local-store.test.js +++ b/app/scripts/lib/local-store.test.js @@ -28,9 +28,13 @@ describe('LocalStore', () => { it('should initialize mostRecentRetrievedState to null', () => { const localStore = setup({ localMock: false }); - expect(localStore.mostRecentRetrievedState).toBeNull(); }); + + it('should initialize isExtensionInitialized to false', () => { + const localStore = setup({ localMock: false }); + expect(localStore.isExtensionInitialized).toBeFalsy(); + }); }); describe('setMetadata', () => { @@ -74,6 +78,13 @@ describe('LocalStore', () => { localStore.set({ appState: { test: true } }); }).not.toThrow(); }); + + it('should set isExtensionInitialized if data is set with no error', async () => { + const localStore = setup(); + localStore.setMetadata({ version: 74 }); + await localStore.set({ appState: { test: true } }); + expect(localStore.isExtensionInitialized).toBeTruthy(); + }); }); describe('get', () => { @@ -112,5 +123,44 @@ describe('LocalStore', () => { expect(localStore.mostRecentRetrievedState).toStrictEqual(null); }); + + it('should set mostRecentRetrievedState to current state if isExtensionInitialized is true', async () => { + const localStore = setup({ + localMock: { + get: jest.fn().mockImplementation(() => Promise.resolve({})), + }, + }); + localStore.setMetadata({ version: 74 }); + await localStore.set({ appState: { test: true } }); + await localStore.get(); + expect(localStore.mostRecentRetrievedState).toStrictEqual(null); + }); + }); + + describe('cleanUpMostRecentRetrievedState', () => { + it('should set mostRecentRetrievedState to null if it is defined', async () => { + const localStore = setup({ + localMock: { + get: jest + .fn() + .mockImplementation(() => + Promise.resolve({ appState: { test: true } }), + ), + }, + }); + await localStore.get(); + + // mostRecentRetrievedState should be { appState: { test: true } } at this stage + await localStore.cleanUpMostRecentRetrievedState(); + expect(localStore.mostRecentRetrievedState).toStrictEqual(null); + }); + + it('should not set mostRecentRetrievedState if it is null', async () => { + const localStore = setup(); + + expect(localStore.mostRecentRetrievedState).toStrictEqual(null); + await localStore.cleanUpMostRecentRetrievedState(); + expect(localStore.mostRecentRetrievedState).toStrictEqual(null); + }); }); }); diff --git a/app/scripts/lib/network-store.js b/app/scripts/lib/network-store.js index 3a0326a2b2e6..e167807c7d9d 100644 --- a/app/scripts/lib/network-store.js +++ b/app/scripts/lib/network-store.js @@ -89,4 +89,10 @@ export default class ReadOnlyNetworkStore { } this._state = { data: state, meta: this._metadata }; } + + cleanUpMostRecentRetrievedState() { + if (this.mostRecentRetrievedState) { + this.mostRecentRetrievedState = null; + } + } } diff --git a/app/scripts/lib/ppom/ppom-middleware.test.ts b/app/scripts/lib/ppom/ppom-middleware.test.ts index 6f4dd73f224c..04f9b5075cb3 100644 --- a/app/scripts/lib/ppom/ppom-middleware.test.ts +++ b/app/scripts/lib/ppom/ppom-middleware.test.ts @@ -11,6 +11,7 @@ import { createPPOMMiddleware, PPOMMiddlewareRequest } from './ppom-middleware'; import { generateSecurityAlertId, handlePPOMError, + isChainSupported, validateRequestWithPPOM, } from './ppom-util'; import { SecurityAlertResponse } from './types'; @@ -18,6 +19,7 @@ import { SecurityAlertResponse } from './types'; jest.mock('./ppom-util'); const SECURITY_ALERT_ID_MOCK = '123'; +const INTERNAL_ACCOUNT_ADDRESS = '0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b'; const SECURITY_ALERT_RESPONSE_MOCK: SecurityAlertResponse = { securityAlertId: SECURITY_ALERT_ID_MOCK, @@ -70,6 +72,10 @@ const createMiddleware = ( addSignatureSecurityAlertResponse: () => undefined, }; + const accountsController = { + listAccounts: () => [{ address: INTERNAL_ACCOUNT_ADDRESS }], + }; + return createPPOMMiddleware( // TODO: Replace `any` with type // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -83,6 +89,8 @@ const createMiddleware = ( // TODO: Replace `any` with type // eslint-disable-next-line @typescript-eslint/no-explicit-any appStateController as any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + accountsController as any, updateSecurityAlertResponse, ); }; @@ -91,6 +99,7 @@ describe('PPOMMiddleware', () => { const validateRequestWithPPOMMock = jest.mocked(validateRequestWithPPOM); const generateSecurityAlertIdMock = jest.mocked(generateSecurityAlertId); const handlePPOMErrorMock = jest.mocked(handlePPOMError); + const isChainSupportedMock = jest.mocked(isChainSupported); beforeEach(() => { jest.resetAllMocks(); @@ -98,6 +107,15 @@ describe('PPOMMiddleware', () => { validateRequestWithPPOMMock.mockResolvedValue(SECURITY_ALERT_RESPONSE_MOCK); generateSecurityAlertIdMock.mockReturnValue(SECURITY_ALERT_ID_MOCK); handlePPOMErrorMock.mockReturnValue(SECURITY_ALERT_RESPONSE_MOCK); + isChainSupportedMock.mockResolvedValue(true); + + globalThis.sentry = { + withIsolationScope: jest + .fn() + .mockImplementation((fn) => fn({ setTags: jest.fn() })), + startSpan: jest.fn().mockImplementation((_, fn) => fn({})), + startSpanManual: jest.fn().mockImplementation((_, fn) => fn({})), + }; }); it('updates alert response after validating request', async () => { @@ -168,6 +186,7 @@ describe('PPOMMiddleware', () => { }); it('does not do validation if user is not on a supported network', async () => { + isChainSupportedMock.mockResolvedValue(false); const middlewareFunction = createMiddleware({ chainId: '0x2', }); @@ -207,6 +226,26 @@ describe('PPOMMiddleware', () => { expect(validateRequestWithPPOM).not.toHaveBeenCalled(); }); + it('does not do validation when request is send to users own account', async () => { + const middlewareFunction = createMiddleware(); + + const req = { + ...REQUEST_MOCK, + params: [{ to: INTERNAL_ACCOUNT_ADDRESS }], + method: 'eth_sendTransaction', + securityAlertResponse: undefined, + }; + + await middlewareFunction( + req, + { ...JsonRpcResponseStruct }, + () => undefined, + ); + + expect(req.securityAlertResponse).toBeUndefined(); + expect(validateRequestWithPPOM).not.toHaveBeenCalled(); + }); + it('does not do validation for SIWE signature', async () => { const middlewareFunction = createMiddleware({ securityAlertsEnabled: true, diff --git a/app/scripts/lib/ppom/ppom-middleware.ts b/app/scripts/lib/ppom/ppom-middleware.ts index a246e9010e8a..caa2bcd5f472 100644 --- a/app/scripts/lib/ppom/ppom-middleware.ts +++ b/app/scripts/lib/ppom/ppom-middleware.ts @@ -1,3 +1,4 @@ +import { AccountsController } from '@metamask/accounts-controller'; import { PPOMController } from '@metamask/ppom-validator'; import { NetworkController } from '@metamask/network-controller'; import { @@ -8,16 +9,15 @@ import { } from '@metamask/utils'; import { detectSIWE } from '@metamask/controller-utils'; +import { MESSAGE_TYPE } from '../../../../shared/constants/app'; import { SIGNING_METHODS } from '../../../../shared/constants/transaction'; import { PreferencesController } from '../../controllers/preferences'; import { AppStateController } from '../../controllers/app-state'; -import { - LOADING_SECURITY_ALERT_RESPONSE, - SECURITY_PROVIDER_SUPPORTED_CHAIN_IDS, -} from '../../../../shared/constants/security-provider'; +import { LOADING_SECURITY_ALERT_RESPONSE } from '../../../../shared/constants/security-provider'; import { generateSecurityAlertId, handlePPOMError, + isChainSupported, validateRequestWithPPOM, } from './ppom-util'; import { SecurityAlertResponse } from './types'; @@ -47,6 +47,7 @@ export type PPOMMiddlewareRequest< * @param preferencesController - Instance of PreferenceController. * @param networkController - Instance of NetworkController. * @param appStateController + * @param accountsController - Instance of AccountsController. * @param updateSecurityAlertResponse * @returns PPOMMiddleware function. */ @@ -58,6 +59,7 @@ export function createPPOMMiddleware< preferencesController: PreferencesController, networkController: NetworkController, appStateController: AppStateController, + accountsController: AccountsController, updateSecurityAlertResponse: ( method: string, signatureAlertId: string, @@ -74,11 +76,12 @@ export function createPPOMMiddleware< preferencesController.store.getState()?.securityAlertsEnabled; const { chainId } = networkController.state.providerConfig; + const isSupportedChain = await isChainSupported(chainId); if ( !securityAlertsEnabled || !CONFIRMATION_METHODS.includes(req.method) || - !SECURITY_PROVIDER_SUPPORTED_CHAIN_IDS.includes(chainId) + !isSupportedChain ) { return; } @@ -88,6 +91,17 @@ export function createPPOMMiddleware< return; } + if (req.method === MESSAGE_TYPE.ETH_SEND_TRANSACTION) { + const { to: toAddress } = req?.params?.[0] ?? {}; + const internalAccounts = accountsController.listAccounts(); + const isToInternalAccount = internalAccounts.some( + ({ address }) => address?.toLowerCase() === toAddress?.toLowerCase(), + ); + if (isToInternalAccount) { + return; + } + } + const securityAlertId = generateSecurityAlertId(); validateRequestWithPPOM({ diff --git a/app/scripts/lib/ppom/ppom-util.test.ts b/app/scripts/lib/ppom/ppom-util.test.ts index 425b1548a1d3..fb93ee54e4b3 100644 --- a/app/scripts/lib/ppom/ppom-util.test.ts +++ b/app/scripts/lib/ppom/ppom-util.test.ts @@ -16,6 +16,7 @@ import { import { AppStateController } from '../../controllers/app-state'; import { generateSecurityAlertId, + isChainSupported, updateSecurityAlertResponse, validateRequestWithPPOM, } from './ppom-util'; @@ -101,6 +102,10 @@ describe('PPOM Utils', () => { const normalizeTransactionParamsMock = jest.mocked( normalizeTransactionParams, ); + const getSupportedChainIdsMock = jest.spyOn( + securityAlertAPI, + 'getSecurityAlertsAPISupportedChainIds', + ); let isSecurityAlertsEnabledMock: jest.SpyInstance; beforeEach(() => { @@ -353,4 +358,36 @@ describe('PPOM Utils', () => { ); }); }); + + describe('isChainSupported', () => { + describe('when security alerts API is enabled', () => { + beforeEach(async () => { + isSecurityAlertsEnabledMock.mockReturnValue(true); + getSupportedChainIdsMock.mockResolvedValue([CHAIN_ID_MOCK]); + }); + + it('returns true if chain is supported', async () => { + expect(await isChainSupported(CHAIN_ID_MOCK)).toStrictEqual(true); + }); + + it('returns false if chain is not supported', async () => { + expect(await isChainSupported('0x2')).toStrictEqual(false); + }); + + it('returns correctly if security alerts API throws', async () => { + getSupportedChainIdsMock.mockRejectedValue(new Error('Test Error')); + expect(await isChainSupported(CHAIN_ID_MOCK)).toStrictEqual(true); + }); + }); + + describe('when security alerts API is disabled', () => { + it('returns true if chain is supported', async () => { + expect(await isChainSupported(CHAIN_ID_MOCK)).toStrictEqual(true); + }); + + it('returns false if chain is not supported', async () => { + expect(await isChainSupported('0x2')).toStrictEqual(false); + }); + }); + }); }); diff --git a/app/scripts/lib/ppom/ppom-util.ts b/app/scripts/lib/ppom/ppom-util.ts index 81d9954dd971..9c6491522782 100644 --- a/app/scripts/lib/ppom/ppom-util.ts +++ b/app/scripts/lib/ppom/ppom-util.ts @@ -11,12 +11,14 @@ import { SignatureController } from '@metamask/signature-controller'; import { BlockaidReason, BlockaidResultType, + SECURITY_PROVIDER_SUPPORTED_CHAIN_IDS, SecurityAlertSource, } from '../../../../shared/constants/security-provider'; import { SIGNING_METHODS } from '../../../../shared/constants/transaction'; import { AppStateController } from '../../controllers/app-state'; import { SecurityAlertResponse } from './types'; import { + getSecurityAlertsAPISupportedChainIds, isSecurityAlertsAPIEnabled, validateWithSecurityAlertsAPI, } from './security-alerts-api'; @@ -115,6 +117,22 @@ export function handlePPOMError( }; } +export async function isChainSupported(chainId: Hex): Promise { + let supportedChainIds = SECURITY_PROVIDER_SUPPORTED_CHAIN_IDS; + + try { + if (isSecurityAlertsAPIEnabled()) { + supportedChainIds = await getSecurityAlertsAPISupportedChainIds(); + } + } catch (error: unknown) { + handlePPOMError( + error, + `Error fetching supported chains from security alerts API`, + ); + } + return supportedChainIds.includes(chainId); +} + function normalizePPOMRequest( request: PPOMRequest | JsonRpcRequest, ): PPOMRequest | JsonRpcRequest { diff --git a/app/scripts/lib/ppom/security-alerts-api.test.ts b/app/scripts/lib/ppom/security-alerts-api.test.ts index 08915633a01a..9d2d97652d4f 100644 --- a/app/scripts/lib/ppom/security-alerts-api.test.ts +++ b/app/scripts/lib/ppom/security-alerts-api.test.ts @@ -3,6 +3,7 @@ import { BlockaidResultType, } from '../../../../shared/constants/security-provider'; import { + getSecurityAlertsAPISupportedChainIds, isSecurityAlertsAPIEnabled, validateWithSecurityAlertsAPI, } from './security-alerts-api'; @@ -86,4 +87,31 @@ describe('Security Alerts API', () => { expect(isEnabled).toBe(false); }); }); + + describe('getSecurityAlertsAPISupportedChainIds', () => { + it('sends GET request', async () => { + const SUPPORTED_CHAIN_IDS_MOCK = ['0x1', '0x2']; + fetchMock.mockResolvedValue({ + ok: true, + json: async () => SUPPORTED_CHAIN_IDS_MOCK, + }); + const response = await getSecurityAlertsAPISupportedChainIds(); + + expect(response).toEqual(SUPPORTED_CHAIN_IDS_MOCK); + + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(fetchMock).toHaveBeenCalledWith( + `https://example.com/supportedChains`, + undefined, + ); + }); + + it('throws an error if response is not ok', async () => { + fetchMock.mockResolvedValue({ ok: false, status: 404 }); + + await expect(getSecurityAlertsAPISupportedChainIds()).rejects.toThrow( + 'Security alerts API request failed with status: 404', + ); + }); + }); }); diff --git a/app/scripts/lib/ppom/security-alerts-api.ts b/app/scripts/lib/ppom/security-alerts-api.ts index eb706975c19c..9cbb7ed1a2b8 100644 --- a/app/scripts/lib/ppom/security-alerts-api.ts +++ b/app/scripts/lib/ppom/security-alerts-api.ts @@ -1,6 +1,8 @@ +import { Hex } from '@metamask/utils'; import { SecurityAlertResponse } from './types'; const ENDPOINT_VALIDATE = 'validate'; +const ENDPOINT_SUPPORTED_CHAINS = 'supportedChains'; export type SecurityAlertsAPIRequest = { method: string; @@ -14,22 +16,26 @@ export function isSecurityAlertsAPIEnabled() { export async function validateWithSecurityAlertsAPI( chainId: string, - request: SecurityAlertsAPIRequest, + body: SecurityAlertsAPIRequest, ): Promise { const endpoint = `${ENDPOINT_VALIDATE}/${chainId}`; - return postRequest(endpoint, request); -} - -async function postRequest(endpoint: string, body: unknown) { - const url = getUrl(endpoint); - - const response = await fetch(url, { + return request(endpoint, { method: 'POST', body: JSON.stringify(body), headers: { 'Content-Type': 'application/json', }, }); +} + +export async function getSecurityAlertsAPISupportedChainIds(): Promise { + return request(ENDPOINT_SUPPORTED_CHAINS); +} + +async function request(endpoint: string, options?: RequestInit) { + const url = getUrl(endpoint); + + const response = await fetch(url, options); if (!response.ok) { throw new Error( diff --git a/app/scripts/lib/rpc-method-middleware/handlers/ethereum-chain-utils.js b/app/scripts/lib/rpc-method-middleware/handlers/ethereum-chain-utils.js index 80408d0f6e03..3d5e0de38c5b 100644 --- a/app/scripts/lib/rpc-method-middleware/handlers/ethereum-chain-utils.js +++ b/app/scripts/lib/rpc-method-middleware/handlers/ethereum-chain-utils.js @@ -207,10 +207,7 @@ export async function switchChain( permissionedChainIds === undefined || !permissionedChainIds.includes(chainId) ) { - await requestPermittedChainsPermission([ - ...(permissionedChainIds ?? []), - chainId, - ]); + await requestPermittedChainsPermission([chainId]); } } else { await requestUserApproval({ diff --git a/app/scripts/lib/setup-initial-state-hooks.js b/app/scripts/lib/setup-initial-state-hooks.js index 3cd13a77e29e..d0b689c9cb30 100644 --- a/app/scripts/lib/setup-initial-state-hooks.js +++ b/app/scripts/lib/setup-initial-state-hooks.js @@ -40,10 +40,14 @@ const persistedStateMask = { globalThis.stateHooks.getSentryState = function () { const sentryState = { browser: window.navigator.userAgent, + // we use the manifest.json version from getVersion and not + // `process.env.METAMASK_VERSION` as they can be different (see `getVersion` + // for more info) version: platform.getVersion(), }; // If `getSentryAppState` is set, it implies that initialization has completed if (globalThis.stateHooks.getSentryAppState) { + sentryLocalStore.cleanUpMostRecentRetrievedState(); return { ...sentryState, state: globalThis.stateHooks.getSentryAppState(), diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index 24f087209383..eaad01672722 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -36,27 +36,6 @@ export const ERROR_URL_ALLOWLIST = { SEGMENT: 'segment.io', }; -export default function setupSentry() { - if (!RELEASE) { - throw new Error('Missing release'); - } - - if (!getSentryTarget()) { - log('Skipped initialization'); - return undefined; - } - - log('Initializing'); - - integrateLogging(); - setSentryClient(); - - return { - ...Sentry, - getMetaMetricsEnabled, - }; -} - function getClientOptions() { const environment = getSentryEnvironment(); const sentryTarget = getSentryTarget(); @@ -87,6 +66,26 @@ function getClientOptions() { }; } +export default function setupSentry() { + if (!RELEASE) { + throw new Error('Missing release'); + } + + if (!getSentryTarget()) { + log('Skipped initialization'); + return undefined; + } + + log('Initializing'); + + integrateLogging(); + setSentryClient(); + + return { + ...Sentry, + getMetaMetricsEnabled, + }; +} /** * Returns whether MetaMetrics is enabled, given the application state. * @@ -237,7 +236,6 @@ function setSentryClient() { .catch((error) => { console.log('Error getting extension installType', error); }); - /** * Sentry throws on initialization as it wants to avoid polluting the global namespace and * potentially clashing with a website also using Sentry, but this could only happen in the content script. @@ -351,6 +349,7 @@ export function rewriteReport(report) { } report.extra.appState = appState; + if (browser.runtime && browser.runtime.id) { report.extra.extensionId = browser.runtime.id; } diff --git a/app/scripts/lib/snap-keyring/bitcoin-wallet-snap.ts b/app/scripts/lib/snap-keyring/bitcoin-wallet-snap.ts index b5340a095c09..1ffca624f759 100644 --- a/app/scripts/lib/snap-keyring/bitcoin-wallet-snap.ts +++ b/app/scripts/lib/snap-keyring/bitcoin-wallet-snap.ts @@ -10,6 +10,9 @@ import { handleSnapRequest } from '../../../../ui/store/actions'; export const BITCOIN_WALLET_SNAP_ID: SnapId = BitcoinWalletSnap.snapId as SnapId; +export const BITCOIN_WALLET_NAME: string = + BitcoinWalletSnap.manifest.proposedName; + export class BitcoinWalletSnapSender implements Sender { send = async (request: JsonRpcRequest): Promise => { // We assume the caller of this module is aware of this. If we try to use this module diff --git a/app/scripts/lib/snap-keyring/keyring-snaps-permissions.ts b/app/scripts/lib/snap-keyring/keyring-snaps-permissions.ts index 89de19da70b9..f7d9c829a3cc 100644 --- a/app/scripts/lib/snap-keyring/keyring-snaps-permissions.ts +++ b/app/scripts/lib/snap-keyring/keyring-snaps-permissions.ts @@ -13,6 +13,7 @@ const PORTFOLIO_ORIGINS: string[] = [ 'https://dev.portfolio.metamask.io', 'https://stage.portfolio.metamask.io', 'https://ramps-dev.portfolio.metamask.io', + 'https://portfolio-builds.metafi-dev.codefi.network', ///: END:ONLY_INCLUDE_IF ]; diff --git a/app/scripts/lib/snap-keyring/metrics.test.ts b/app/scripts/lib/snap-keyring/metrics.test.ts index fe49b2be73e4..f9c71792f63c 100644 --- a/app/scripts/lib/snap-keyring/metrics.test.ts +++ b/app/scripts/lib/snap-keyring/metrics.test.ts @@ -45,7 +45,6 @@ describe('getSnapAndHardwareInfoForMetrics', () => { options: {}, methods: [ 'personal_sign', - 'eth_sign', 'eth_signTransaction', 'eth_signTypedData_v1', 'eth_signTypedData_v3', @@ -97,7 +96,6 @@ describe('getSnapAndHardwareInfoForMetrics', () => { options: {}, methods: [ 'personal_sign', - 'eth_sign', 'eth_signTransaction', 'eth_signTypedData_v1', 'eth_signTypedData_v3', diff --git a/app/scripts/lib/snap-keyring/snap-keyring.test.ts b/app/scripts/lib/snap-keyring/snap-keyring.test.ts new file mode 100644 index 000000000000..4136fd1fd1fc --- /dev/null +++ b/app/scripts/lib/snap-keyring/snap-keyring.test.ts @@ -0,0 +1,560 @@ +import { ControllerMessenger } from '@metamask/base-controller'; +import { EthAccountType, InternalAccount } from '@metamask/keyring-api'; +import { SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES } from '../../../../shared/constants/app'; +import { + MetaMetricsEventCategory, + MetaMetricsEventName, +} from '../../../../shared/constants/metametrics'; +import { + showAccountCreationDialog, + showAccountNameSuggestionDialog, + snapKeyringBuilder, +} from './snap-keyring'; +import { + SnapKeyringBuilderAllowActions, + SnapKeyringBuilderMessenger, +} from './types'; + +const mockAddRequest = jest.fn(); +const mockStartFlow = jest.fn(); +const mockEndFlow = jest.fn(); +const mockShowSuccess = jest.fn(); +const mockShowError = jest.fn(); +const mockGetAccounts = jest.fn(); +const mockSnapId = 'snapId'; +const mockSnapName = 'mock-snap'; +const mockSnapController = jest.fn(); +const mockPersisKeyringHelper = jest.fn(); +const mockSetSelectedAccount = jest.fn(); +const mockSetAccountName = jest.fn(); +const mockRemoveAccountHelper = jest.fn(); +const mockTrackEvent = jest.fn(); +const mockGetAccountByAddress = jest.fn(); + +const mockFlowId = '123'; +const address = '0x2a4d4b667D5f12C3F9Bf8F14a7B9f8D8d9b8c8fA'; +const accountNameSuggestion = 'Suggested Account Name'; +const mockAccount = { + type: EthAccountType.Eoa, + id: '3afa663e-0600-4d93-868a-61c2e553013b', + address, + methods: [], + options: {}, +}; +const mockInternalAccount = { + ...mockAccount, + metadata: { + snap: { + enabled: true, + id: mockSnapId, + name: mockSnapName, + }, + name: accountNameSuggestion, + keyring: { + type: '', + }, + importTime: 0, + }, +}; + +const createControllerMessenger = ({ + account = mockInternalAccount, +}: { + account?: InternalAccount; +} = {}): SnapKeyringBuilderMessenger => { + const messenger = new ControllerMessenger< + SnapKeyringBuilderAllowActions, + never + >().getRestricted({ + name: 'SnapKeyringBuilder', + allowedActions: [ + 'ApprovalController:addRequest', + 'ApprovalController:acceptRequest', + 'ApprovalController:rejectRequest', + 'ApprovalController:startFlow', + 'ApprovalController:endFlow', + 'ApprovalController:showSuccess', + 'ApprovalController:showError', + 'PhishingController:maybeUpdateState', + 'KeyringController:getAccounts', + 'AccountsController:setSelectedAccount', + 'AccountsController:getAccountByAddress', + ], + allowedEvents: [], + }); + + jest.spyOn(messenger, 'call').mockImplementation((...args) => { + // This mock implementation does not have a nice discriminate union where types/parameters can be correctly inferred + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const [actionType, ...params]: any[] = args; + + switch (actionType) { + case 'ApprovalController:addRequest': + return mockAddRequest(params); + + case 'ApprovalController:startFlow': + return mockStartFlow.mockReturnValue({ id: mockFlowId })(); + + case 'ApprovalController:endFlow': + return mockEndFlow.mockReturnValue(true)(params); + + case 'ApprovalController:showSuccess': + return mockShowSuccess(); + + case 'ApprovalController:showError': + return mockShowError(); + + case 'KeyringController:getAccounts': + return mockGetAccounts.mockResolvedValue([])(); + + case 'AccountsController:getAccountByAddress': + return mockGetAccountByAddress.mockReturnValue(account)(params); + + case 'AccountsController:setSelectedAccount': + return mockSetSelectedAccount(params); + + case 'AccountsController:setAccountName': + return mockSetAccountName.mockReturnValue(null)(params); + + default: + throw new Error( + `MOCK_FAIL - unsupported messenger call: ${actionType}`, + ); + } + }); + + return messenger; +}; + +const createSnapKeyringBuilder = ({ + snapName = mockSnapName, + isSnapPreinstalled = true, +}: { + snapName?: string; + isSnapPreinstalled?: boolean; +} = {}) => { + return snapKeyringBuilder( + createControllerMessenger(), + mockSnapController, + mockPersisKeyringHelper, + mockRemoveAccountHelper, + mockTrackEvent, + () => snapName, + () => isSnapPreinstalled, + ); +}; + +describe('Snap Keyring Methods', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + + describe('helpers', () => { + describe('showAccountCreationDialog', () => { + it('shows account creation dialog and return true on user confirmation', async () => { + const controllerMessenger = createControllerMessenger(); + controllerMessenger.call('ApprovalController:startFlow'); + + await showAccountCreationDialog(mockSnapId, controllerMessenger); + + expect(mockAddRequest).toHaveBeenCalledTimes(1); + expect(mockAddRequest).toHaveBeenCalledWith([ + { + origin: mockSnapId, + type: SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.confirmAccountCreation, + }, + true, + ]); + }); + }); + + describe('showAccountNameSuggestionDialog', () => { + it('shows account name suggestion dialog and return true on user confirmation', async () => { + const controllerMessenger = createControllerMessenger(); + controllerMessenger.call('ApprovalController:startFlow'); + + await showAccountNameSuggestionDialog( + mockSnapId, + controllerMessenger, + accountNameSuggestion, + ); + + expect(mockAddRequest).toHaveBeenCalledTimes(1); + expect(mockAddRequest).toHaveBeenCalledWith([ + { + origin: mockSnapId, + type: SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.showNameSnapAccount, + requestData: { + snapSuggestedAccountName: accountNameSuggestion, + }, + }, + true, + ]); + }); + }); + }); + + describe('addAccount', () => { + beforeEach(() => { + mockAddRequest.mockReturnValue(true).mockReturnValue({ success: true }); + }); + afterEach(() => { + jest.resetAllMocks(); + }); + + it('handles account creation with confirmations and without a user defined name', async () => { + const builder = createSnapKeyringBuilder(); + await builder().handleKeyringSnapMessage(mockSnapId, { + method: 'notify:accountCreated', + params: { + account: mockAccount, + displayConfirmation: true, + }, + }); + + expect(mockStartFlow).toHaveBeenCalledTimes(1); + // First request for show account creation dialog + // Second request for account name suggestion dialog + expect(mockAddRequest).toHaveBeenCalledTimes(2); + expect(mockAddRequest).toHaveBeenNthCalledWith(1, [ + { + origin: mockSnapId, + type: SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.confirmAccountCreation, + }, + true, + ]); + // First call is from addAccount after user confirmation + // Second call is from within the SnapKeyring after ending the addAccount flow + expect(mockPersisKeyringHelper).toHaveBeenCalledTimes(2); + expect(mockAddRequest).toHaveBeenNthCalledWith(2, [ + { + origin: mockSnapId, + type: SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.showNameSnapAccount, + requestData: { + snapSuggestedAccountName: '', + }, + }, + true, + ]); + expect(mockGetAccountByAddress).toHaveBeenCalledTimes(1); + expect(mockGetAccountByAddress).toHaveBeenCalledWith([ + mockAccount.address.toLowerCase(), + ]); + expect(mockTrackEvent).toHaveBeenCalledTimes(3); + expect(mockTrackEvent).toHaveBeenNthCalledWith(1, { + category: MetaMetricsEventCategory.Accounts, + event: MetaMetricsEventName.AddSnapAccountSuccessViewed, + properties: { + account_type: 'snap', + snap_id: mockSnapId, + snap_name: mockSnapName, + }, + }); + expect(mockTrackEvent).toHaveBeenNthCalledWith(2, { + category: MetaMetricsEventCategory.Accounts, + event: MetaMetricsEventName.AddSnapAccountSuccessClicked, + properties: { + account_type: 'snap', + snap_id: mockSnapId, + snap_name: mockSnapName, + }, + }); + expect(mockTrackEvent).toHaveBeenNthCalledWith(3, { + category: MetaMetricsEventCategory.Accounts, + event: MetaMetricsEventName.AccountAdded, + properties: { + account_type: 'snap', + snap_id: mockSnapId, + snap_name: mockSnapName, + }, + }); + expect(mockShowSuccess).toHaveBeenCalledTimes(1); + expect(mockSetAccountName).not.toHaveBeenCalled(); + expect(mockEndFlow).toHaveBeenCalledTimes(1); + expect(mockEndFlow).toHaveBeenCalledWith([{ id: mockFlowId }]); + }); + + it('handles account creation with skipping confirmation and without user defined name', async () => { + const builder = createSnapKeyringBuilder(); + + await builder().handleKeyringSnapMessage(mockSnapId, { + method: 'notify:accountCreated', + params: { + account: mockAccount, + displayConfirmation: false, + }, + }); + + expect(mockStartFlow).toHaveBeenCalledTimes(1); + expect(mockAddRequest).toHaveBeenCalledTimes(1); + // First call is from addAccount after user confirmation + // Second call is from within the SnapKeyring after ending the addAccount flow + expect(mockPersisKeyringHelper).toHaveBeenCalledTimes(2); + expect(mockAddRequest).toHaveBeenNthCalledWith(1, [ + { + origin: mockSnapId, + type: SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.showNameSnapAccount, + requestData: { + // No user defined name + snapSuggestedAccountName: '', + }, + }, + true, + ]); + expect(mockGetAccountByAddress).toHaveBeenCalledTimes(1); + expect(mockGetAccountByAddress).toHaveBeenCalledWith([ + mockAccount.address.toLowerCase(), + ]); + expect(mockTrackEvent).toHaveBeenCalledTimes(1); + expect(mockTrackEvent).toHaveBeenNthCalledWith(1, { + category: MetaMetricsEventCategory.Accounts, + event: MetaMetricsEventName.AccountAdded, + properties: { + account_type: 'snap', + snap_id: mockSnapId, + snap_name: mockSnapName, + }, + }); + expect(mockSetAccountName).not.toHaveBeenCalled(); + expect(mockEndFlow).toHaveBeenCalledTimes(1); + expect(mockEndFlow).toHaveBeenCalledWith([{ id: mockFlowId }]); + }); + + it('handles account creation with confirmations and with a user defined name', async () => { + const mockNameSuggestion = 'new name'; + mockAddRequest.mockReturnValueOnce(true).mockReturnValueOnce({ + success: true, + name: mockNameSuggestion, + }); + const builder = createSnapKeyringBuilder(); + await builder().handleKeyringSnapMessage(mockSnapId, { + method: 'notify:accountCreated', + params: { + account: mockAccount, + displayConfirmation: true, + accountNameSuggestion: mockNameSuggestion, + }, + }); + + expect(mockStartFlow).toHaveBeenCalledTimes(1); + // First request for show account creation dialog + // Second request for account name suggestion second + expect(mockAddRequest).toHaveBeenCalledTimes(2); + expect(mockAddRequest).toHaveBeenNthCalledWith(1, [ + { + origin: mockSnapId, + type: SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.confirmAccountCreation, + }, + true, + ]); + // First call is from addAccount after user confirmation + // Second call is from within the SnapKeyring + expect(mockPersisKeyringHelper).toHaveBeenCalledTimes(2); + expect(mockAddRequest).toHaveBeenNthCalledWith(2, [ + { + origin: mockSnapId, + type: SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.showNameSnapAccount, + requestData: { + snapSuggestedAccountName: mockNameSuggestion, + }, + }, + true, + ]); + expect(mockGetAccountByAddress).toHaveBeenCalledTimes(1); + expect(mockGetAccountByAddress).toHaveBeenCalledWith([ + mockAccount.address.toLowerCase(), + ]); + expect(mockTrackEvent).toHaveBeenCalledTimes(3); + expect(mockTrackEvent).toHaveBeenNthCalledWith(1, { + category: MetaMetricsEventCategory.Accounts, + event: MetaMetricsEventName.AddSnapAccountSuccessViewed, + properties: { + account_type: 'snap', + snap_id: mockSnapId, + snap_name: mockSnapName, + }, + }); + expect(mockTrackEvent).toHaveBeenNthCalledWith(2, { + category: MetaMetricsEventCategory.Accounts, + event: MetaMetricsEventName.AddSnapAccountSuccessClicked, + properties: { + account_type: 'snap', + snap_id: mockSnapId, + snap_name: mockSnapName, + }, + }); + expect(mockTrackEvent).toHaveBeenNthCalledWith(3, { + category: MetaMetricsEventCategory.Accounts, + event: MetaMetricsEventName.AccountAdded, + properties: { + account_type: 'snap', + snap_id: mockSnapId, + snap_name: mockSnapName, + }, + }); + expect(mockSetAccountName).toHaveBeenCalledWith([ + mockAccount.id, + mockNameSuggestion, + ]); + expect(mockShowSuccess).toHaveBeenCalledTimes(1); + expect(mockEndFlow).toHaveBeenCalledTimes(1); + expect(mockEndFlow).toHaveBeenCalledWith([{ id: mockFlowId }]); + }); + + it('handles account creation with skipping confirmation and with user defined name', async () => { + const mockNameSuggestion = 'suggested name'; + mockAddRequest.mockReturnValueOnce({ + success: true, + name: mockNameSuggestion, + }); + const builder = createSnapKeyringBuilder(); + await builder().handleKeyringSnapMessage(mockSnapId, { + method: 'notify:accountCreated', + params: { + account: mockAccount, + displayConfirmation: false, + accountNameSuggestion: mockNameSuggestion, + }, + }); + + expect(mockStartFlow).toHaveBeenCalledTimes(1); + expect(mockAddRequest).toHaveBeenCalledTimes(1); + // First call is from addAccount after user confirmation + // Second call is from within the SnapKeyring after ending the addAccount flow + expect(mockPersisKeyringHelper).toHaveBeenCalledTimes(2); + expect(mockAddRequest).toHaveBeenNthCalledWith(1, [ + { + origin: mockSnapId, + type: SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.showNameSnapAccount, + requestData: { + snapSuggestedAccountName: mockNameSuggestion, + }, + }, + true, + ]); + expect(mockGetAccountByAddress).toHaveBeenCalledTimes(1); + expect(mockGetAccountByAddress).toHaveBeenCalledWith([ + mockAccount.address.toLowerCase(), + ]); + expect(mockTrackEvent).toHaveBeenCalledTimes(1); + expect(mockTrackEvent).toHaveBeenNthCalledWith(1, { + category: MetaMetricsEventCategory.Accounts, + event: MetaMetricsEventName.AccountAdded, + properties: { + account_type: 'snap', + snap_id: mockSnapId, + snap_name: mockSnapName, + }, + }); + expect(mockSetAccountName).toHaveBeenCalledTimes(1); + expect(mockSetAccountName).toHaveBeenCalledWith([ + mockAccount.id, + mockNameSuggestion, + ]); + expect(mockEndFlow).toHaveBeenCalledTimes(1); + expect(mockEndFlow).toHaveBeenCalledWith([{ id: mockFlowId }]); + }); + + it('handles account creation with confirmations and with a user defined name', async () => { + const mockNameSuggestion = 'new name'; + mockAddRequest.mockReturnValueOnce(true).mockReturnValueOnce({ + success: true, + name: mockNameSuggestion, + }); + const builder = createSnapKeyringBuilder(); + await builder().handleKeyringSnapMessage(mockSnapId, { + method: 'notify:accountCreated', + params: { + account: mockAccount, + displayConfirmation: true, + accountNameSuggestion: mockNameSuggestion, + }, + }); + + expect(mockStartFlow).toHaveBeenCalledTimes(1); + // First request for show account creation dialog + // Second request for account name suggestion second + expect(mockAddRequest).toHaveBeenCalledTimes(2); + expect(mockAddRequest).toHaveBeenNthCalledWith(1, [ + { + origin: mockSnapId, + type: SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.confirmAccountCreation, + }, + true, + ]); + // First call is from addAccount after user confirmation + // Second call is from within the SnapKeyring + expect(mockPersisKeyringHelper).toHaveBeenCalledTimes(2); + expect(mockAddRequest).toHaveBeenNthCalledWith(2, [ + { + origin: mockSnapId, + type: SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.showNameSnapAccount, + requestData: { + snapSuggestedAccountName: mockNameSuggestion, + }, + }, + true, + ]); + expect(mockGetAccountByAddress).toHaveBeenCalledTimes(1); + expect(mockGetAccountByAddress).toHaveBeenCalledWith([ + mockAccount.address.toLowerCase(), + ]); + expect(mockTrackEvent).toHaveBeenCalledTimes(3); + expect(mockTrackEvent).toHaveBeenNthCalledWith(1, { + category: MetaMetricsEventCategory.Accounts, + event: MetaMetricsEventName.AddSnapAccountSuccessViewed, + properties: { + account_type: 'snap', + snap_id: mockSnapId, + snap_name: mockSnapName, + }, + }); + expect(mockTrackEvent).toHaveBeenNthCalledWith(2, { + category: MetaMetricsEventCategory.Accounts, + event: MetaMetricsEventName.AddSnapAccountSuccessClicked, + properties: { + account_type: 'snap', + snap_id: mockSnapId, + snap_name: mockSnapName, + }, + }); + expect(mockTrackEvent).toHaveBeenNthCalledWith(3, { + category: MetaMetricsEventCategory.Accounts, + event: MetaMetricsEventName.AccountAdded, + properties: { + account_type: 'snap', + snap_id: mockSnapId, + snap_name: mockSnapName, + }, + }); + expect(mockSetAccountName).toHaveBeenCalledTimes(1); + expect(mockSetAccountName).toHaveBeenCalledWith([ + mockAccount.id, + mockNameSuggestion, + ]); + expect(mockShowSuccess).toHaveBeenCalledTimes(1); + expect(mockEndFlow).toHaveBeenCalledTimes(1); + expect(mockEndFlow).toHaveBeenCalledWith([{ id: mockFlowId }]); + }); + + it('ends approval flow on error', async () => { + const errorMessage = 'save error'; + mockPersisKeyringHelper.mockRejectedValue(new Error(errorMessage)); + const builder = createSnapKeyringBuilder(); + await expect( + builder().handleKeyringSnapMessage(mockSnapId, { + method: 'notify:accountCreated', + params: { + account: mockAccount, + displayConfirmation: true, + }, + }), + ).rejects.toThrow( + `Error occurred while creating snap account: ${errorMessage}`, + ); + expect(mockStartFlow).toHaveBeenCalledTimes(1); + expect(mockEndFlow).toHaveBeenCalledTimes(1); + expect(mockEndFlow).toHaveBeenCalledWith([{ id: mockFlowId }]); + }); + }); +}); diff --git a/app/scripts/lib/snap-keyring/snap-keyring.ts b/app/scripts/lib/snap-keyring/snap-keyring.ts index 1ce13ef07fe7..d707b0bb61c4 100644 --- a/app/scripts/lib/snap-keyring/snap-keyring.ts +++ b/app/scripts/lib/snap-keyring/snap-keyring.ts @@ -30,13 +30,74 @@ export const getAccountsBySnapId = async ( return await snapKeyring.getAccountsBySnapId(snapId); }; +/** + * Show the account creation dialog for a given Snap. + * This function will start the approval flow, show the account creation dialog, and end the flow. + * + * @param snapId - Snap ID to show the account creation dialog for. + * @param controllerMessenger - The controller messenger instance. + * @returns The user's confirmation result. + */ +export async function showAccountCreationDialog( + snapId: string, + controllerMessenger: SnapKeyringBuilderMessenger, +) { + try { + const confirmationResult = Boolean( + await controllerMessenger.call( + 'ApprovalController:addRequest', + { + origin: snapId, + type: SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.confirmAccountCreation, + }, + true, + ), + ); + return confirmationResult; + } catch (e) { + throw new Error( + `Error occurred while showing account creation dialog.\n${e}`, + ); + } +} + +/** + * Show the account name suggestion confirmation dialog for a given Snap. + * + * @param snapId - Snap ID to show the account name suggestion dialog for. + * @param controllerMessenger - The controller messenger instance. + * @param accountNameSuggestion - Suggested name for the new account. + * @returns The user's confirmation result. + */ +export async function showAccountNameSuggestionDialog( + snapId: string, + controllerMessenger: SnapKeyringBuilderMessenger, + accountNameSuggestion: string, +): Promise<{ success: boolean; name?: string }> { + try { + const confirmationResult = (await controllerMessenger.call( + 'ApprovalController:addRequest', + { + origin: snapId, + type: SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.showNameSnapAccount, + requestData: { + snapSuggestedAccountName: accountNameSuggestion, + }, + }, + true, + )) as { success: boolean; name?: string }; + return confirmationResult; + } catch (e) { + throw new Error(`Error occurred while showing name account dialog.\n${e}`); + } +} + /** * Constructs a SnapKeyring builder with specified handlers for managing snap accounts. * * @param controllerMessenger - The controller messenger instance. * @param getSnapController - A function that retrieves the Snap Controller instance. * @param persistKeyringHelper - A function that persists all keyrings in the vault. - * @param setSelectedAccountHelper - A function to update current selected account. * @param removeAccountHelper - A function to help remove an account based on its address. * @param trackEvent - A function to track MetaMetrics events. * @param getSnapName - A function to get a snap's localized @@ -51,7 +112,6 @@ export const snapKeyringBuilder = ( controllerMessenger: SnapKeyringBuilderMessenger, getSnapController: () => SnapController, persistKeyringHelper: () => Promise, - setSelectedAccountHelper: (address: string) => void, // TODO: Replace `any` with type // eslint-disable-next-line @typescript-eslint/no-explicit-any removeAccountHelper: (address: string) => Promise, @@ -122,10 +182,13 @@ export const snapKeyringBuilder = ( address: string, snapId: string, handleUserInput: (accepted: boolean) => Promise, - _accountNameSuggestion?: string, + accountNameSuggestion: string = '', displayConfirmation: boolean = false, ) => { const snapName = getSnapName(snapId); + const { id: addAccountFlowId } = controllerMessenger.call( + 'ApprovalController:startFlow', + ); const trackSnapAccountEvent = (event: MetaMetricsEventName) => { trackEvent({ @@ -139,60 +202,67 @@ export const snapKeyringBuilder = ( }); }; - const learnMoreLink = - '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 = - isSnapPreinstalled(snapId) && !displayConfirmation; - // If confirmation dialog is skipped, we consider the account creation to be confirmed - let confirmationResult = skipConfirmation; - let confirmationApprovalId = ''; try { - if (!skipConfirmation) { - const { id } = controllerMessenger.call( - 'ApprovalController:startFlow', - ); - confirmationApprovalId = id; - confirmationResult = Boolean( - await controllerMessenger.call( - 'ApprovalController:addRequest', - { - origin: snapId, - type: SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.confirmAccountCreation, - }, - true, - ), - ); + const learnMoreLink = + '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 = + isSnapPreinstalled(snapId) && !displayConfirmation; + // If confirmation dialog are skipped, we consider the account creation to be confirmed until the account name dialog is closed + const accountCreationConfirmationResult = + skipConfirmation || + (await showAccountCreationDialog(snapId, controllerMessenger)); + + if (!accountCreationConfirmationResult) { + // User has cancelled account creation + await handleUserInput(accountCreationConfirmationResult); + + throw new Error('User denied account creation'); } - if (confirmationResult) { + const accountNameConfirmationResult = + await showAccountNameSuggestionDialog( + snapId, + controllerMessenger, + accountNameSuggestion, + ); + + if (accountNameConfirmationResult?.success) { try { - await handleUserInput(confirmationResult); + // Persist the account so we can rename it afterward await persistKeyringHelper(); - setSelectedAccountHelper(address); - const internalAccount = controllerMessenger.call( + await handleUserInput(accountNameConfirmationResult.success); + const account = controllerMessenger.call( 'AccountsController:getAccountByAddress', address, ); - if (!internalAccount) { + if (!account) { throw new Error( `Internal account not found for address: ${address}`, ); } + // Set the selected account to the new account controllerMessenger.call( 'AccountsController:setSelectedAccount', - internalAccount.id, + account.id, ); - // TODO: Add events tracking to the dialog itself, so that events are more - // "linked" to UI actions - // User should now see the "Successfuly added account" page - trackSnapAccountEvent( - MetaMetricsEventName.AddSnapAccountSuccessViewed, - ); + if (accountNameConfirmationResult.name) { + controllerMessenger.call( + 'AccountsController:setAccountName', + account.id, + accountNameConfirmationResult.name, + ); + } if (!skipConfirmation) { + // TODO: Add events tracking to the dialog itself, so that events are more + // "linked" to UI actions + // User should now see the "Successfuly added account" page + trackSnapAccountEvent( + MetaMetricsEventName.AddSnapAccountSuccessViewed, + ); await showSuccess( controllerMessenger, snapId, @@ -206,13 +276,15 @@ export const snapKeyringBuilder = ( learnMoreLink, }, ); + // User has clicked on "OK" + trackSnapAccountEvent( + MetaMetricsEventName.AddSnapAccountSuccessClicked, + ); } - // User has clicked on "OK" - trackSnapAccountEvent( - MetaMetricsEventName.AddSnapAccountSuccessClicked, - ); + trackSnapAccountEvent(MetaMetricsEventName.AccountAdded); } catch (e) { + // Error occurred while naming the account const error = (e as Error).message; await showError( @@ -232,30 +304,20 @@ export const snapKeyringBuilder = ( }, ); - trackSnapAccountEvent(MetaMetricsEventName.AccountAddFailed); - throw new Error( `Error occurred while creating snap account: ${error}`, ); } } else { - // User has cancelled account creation - await handleUserInput(confirmationResult); + // User has cancelled account creation so remove the account from the keyring + await handleUserInput(accountNameConfirmationResult?.success); throw new Error('User denied account creation'); } } finally { - // We do not have a `else` clause here, as it's used if the request was - // canceled by the user, thus it's not a "fail" (not an error). - if (confirmationResult) { - trackSnapAccountEvent(MetaMetricsEventName.AccountAdded); - } - // End the approval flow if it was started - if (!skipConfirmation) { - controllerMessenger.call('ApprovalController:endFlow', { - id: confirmationApprovalId, - }); - } + controllerMessenger.call('ApprovalController:endFlow', { + id: addAccountFlowId, + }); } }, removeAccount: async ( diff --git a/app/scripts/lib/snap-keyring/types.ts b/app/scripts/lib/snap-keyring/types.ts index 5f6f210ab945..701198d1d503 100644 --- a/app/scripts/lib/snap-keyring/types.ts +++ b/app/scripts/lib/snap-keyring/types.ts @@ -4,6 +4,7 @@ import type { KeyringControllerGetAccountsAction } from '@metamask/keyring-contr import { GetSubjectMetadata } from '@metamask/permission-controller'; import { AccountsControllerGetAccountByAddressAction, + AccountsControllerSetAccountNameAction, AccountsControllerSetSelectedAccountAction, } from '@metamask/accounts-controller'; import type { @@ -16,7 +17,7 @@ import type { StartFlow, } from '@metamask/approval-controller'; -type SnapKeyringBuilderAllowActions = +export type SnapKeyringBuilderAllowActions = | StartFlow | EndFlow | ShowSuccess @@ -29,7 +30,8 @@ type SnapKeyringBuilderAllowActions = | KeyringControllerGetAccountsAction | GetSubjectMetadata | AccountsControllerSetSelectedAccountAction - | AccountsControllerGetAccountByAddressAction; + | AccountsControllerGetAccountByAddressAction + | AccountsControllerSetAccountNameAction; export type SnapKeyringBuilderMessenger = RestrictedControllerMessenger< 'SnapKeyringBuilder', diff --git a/app/scripts/lib/transaction/metrics.test.ts b/app/scripts/lib/transaction/metrics.test.ts index b63f7dcc5b78..e8158c56baaf 100644 --- a/app/scripts/lib/transaction/metrics.test.ts +++ b/app/scripts/lib/transaction/metrics.test.ts @@ -72,8 +72,11 @@ const mockTransactionMetricsRequest = { trackEvent: jest.fn(), getIsSmartTransaction: jest.fn(), getSmartTransactionByMinedTxHash: jest.fn(), + getRedesignedTransactionsEnabled: jest.fn(), getRedesignedConfirmationsEnabled: jest.fn(), + getMethodData: jest.fn(), getIsRedesignedConfirmationsDeveloperEnabled: jest.fn(), + getIsConfirmationAdvancedDetailsOpen: jest.fn(), } as TransactionMetricsRequest; describe('Transaction metrics', () => { @@ -154,6 +157,7 @@ describe('Transaction metrics', () => { transaction_speed_up: false, transaction_type: TransactionType.simpleSend, ui_customizations: null, + transaction_advanced_view: null, }; expectedSensitiveProperties = { diff --git a/app/scripts/lib/transaction/metrics.ts b/app/scripts/lib/transaction/metrics.ts index 5ecabc36862b..6ce19ec65303 100644 --- a/app/scripts/lib/transaction/metrics.ts +++ b/app/scripts/lib/transaction/metrics.ts @@ -94,8 +94,11 @@ export type TransactionMetricsRequest = { getSmartTransactionByMinedTxHash: ( txhash: string | undefined, ) => SmartTransaction; + getRedesignedTransactionsEnabled: () => boolean; getRedesignedConfirmationsEnabled: () => boolean; + getMethodData: (data: string) => Promise<{ name: string }>; getIsRedesignedConfirmationsDeveloperEnabled: () => boolean; + getIsConfirmationAdvancedDetailsOpen: () => boolean; }; export const METRICS_STATUS_FAILED = 'failed on-chain'; @@ -793,7 +796,6 @@ async function buildEventFragmentProperties({ currentTokenBalance, originalApprovalAmount, finalApprovalAmount, - contractMethodName, securityProviderResponse, simulationFails, } = transactionMeta; @@ -806,6 +808,14 @@ async function buildEventFragmentProperties({ transactionMetricsRequest.getTokenStandardAndDetails, ); + let contractMethodName; + if (transactionMeta.txParams.data) { + const { name } = await transactionMetricsRequest.getMethodData( + transactionMeta.txParams.data, + ); + contractMethodName = name; + } + // TODO: Replace `any` with type // eslint-disable-next-line @typescript-eslint/no-explicit-any const gasParams = {} as Record; @@ -963,6 +973,7 @@ async function buildEventFragmentProperties({ } const uiCustomizations = []; + let isAdvancedDetailsOpen = null; /** securityProviderResponse is used by the OpenSea <> Blockaid provider */ // eslint-disable-next-line no-lonely-if @@ -987,19 +998,22 @@ async function buildEventFragmentProperties({ } const isRedesignedConfirmationsDeveloperSettingEnabled = transactionMetricsRequest.getIsRedesignedConfirmationsDeveloperEnabled() || - process.env.ENABLE_CONFIRMATION_REDESIGN; + Boolean(process.env.ENABLE_CONFIRMATION_REDESIGN); - const isRedesignedConfirmationsUserSettingEnabled = - transactionMetricsRequest.getRedesignedConfirmationsEnabled(); + const isRedesignedTransactionsUserSettingEnabled = + transactionMetricsRequest.getRedesignedTransactionsEnabled(); if ( (isRedesignedConfirmationsDeveloperSettingEnabled || - isRedesignedConfirmationsUserSettingEnabled) && + isRedesignedTransactionsUserSettingEnabled) && REDESIGN_TRANSACTION_TYPES.includes(transactionMeta.type as TransactionType) ) { uiCustomizations.push( MetaMetricsEventUiCustomization.RedesignedConfirmation, ); + + isAdvancedDetailsOpen = + transactionMetricsRequest.getIsConfirmationAdvancedDetailsOpen(); } const smartTransactionMetricsProperties = getSmartTransactionMetricsProperties( @@ -1034,6 +1048,7 @@ async function buildEventFragmentProperties({ ...blockaidProperties, // ui_customizations must come after ...blockaidProperties ui_customizations: uiCustomizations.length > 0 ? uiCustomizations : null, + transaction_advanced_view: isAdvancedDetailsOpen, ...smartTransactionMetricsProperties, ...swapAndSendMetricsProperties, // TODO: Replace `any` with type diff --git a/app/scripts/lib/transaction/mmi-hooks.test.ts b/app/scripts/lib/transaction/mmi-hooks.test.ts index fee5dff83de5..45d0b83c2341 100644 --- a/app/scripts/lib/transaction/mmi-hooks.test.ts +++ b/app/scripts/lib/transaction/mmi-hooks.test.ts @@ -2,7 +2,6 @@ import { TransactionStatus } from '@metamask/transaction-controller'; import { afterTransactionSign, beforeCheckPendingTransaction, - beforeTransactionApproveOnInit, beforeTransactionPublish, getAdditionalSignArguments, } from './mmi-hooks'; @@ -85,24 +84,6 @@ describe('MMI hooks', () => { }); }); - describe('beforeTransactionApproveOnInit', () => { - it('returns true if txMeta has custodyStatus', () => { - // TODO: Replace `any` with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const txMeta = { custodyStatus: TransactionStatus.approved } as any; - const result = beforeTransactionApproveOnInit(txMeta); - expect(result).toBe(false); - }); - - it('returns false if txMeta has no custodyStatus', () => { - // TODO: Replace `any` with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const txMeta = { to: toMocked } as any; - const result = beforeTransactionApproveOnInit(txMeta); - expect(result).toBe(true); - }); - }); - describe('beforeCheckPendingTransaction', () => { it('returns true if txMeta has custodyStatus', () => { const txMeta = { diff --git a/app/scripts/lib/transaction/mmi-hooks.ts b/app/scripts/lib/transaction/mmi-hooks.ts index d4dc3cc4dcda..1716fe738c04 100644 --- a/app/scripts/lib/transaction/mmi-hooks.ts +++ b/app/scripts/lib/transaction/mmi-hooks.ts @@ -53,18 +53,6 @@ export function getAdditionalSignArguments( return [txMeta.custodyStatus ? txMeta : undefined]; } -/** - * Whether or not should run the logic before approve the transaction when transaction controller is rebooted. - * - * @param txMeta - The transaction meta. - */ - -export function beforeTransactionApproveOnInit( - txMeta: TransactionMeta, -): boolean { - return !txMeta?.custodyStatus; -} - /** * Whether or not should run the logic before checking the transaction when checking pending transactions. * diff --git a/app/scripts/lib/transaction/smart-transactions.test.ts b/app/scripts/lib/transaction/smart-transactions.test.ts index eed52a909f67..357be3d4b33a 100644 --- a/app/scripts/lib/transaction/smart-transactions.test.ts +++ b/app/scripts/lib/transaction/smart-transactions.test.ts @@ -110,6 +110,7 @@ describe('submitSmartTransactionHook', () => { smartTransactionsController: createSmartTransactionsControllerMock(), transactionController: createTransactionControllerMock(), isSmartTransaction: true, + isHardwareWallet: false, controllerMessenger: createSmartTransactionsControllerMessengerMock(), featureFlags: { extensionActive: true, @@ -148,6 +149,13 @@ describe('submitSmartTransactionHook', () => { expect(result).toEqual({ transactionHash: undefined }); }); + it('falls back to regular transaction submit if a hardware wallet is used', async () => { + const request: SubmitSmartTransactionRequestMocked = createRequest(); + request.isHardwareWallet = true; + 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 21a95e94494d..822439839d35 100644 --- a/app/scripts/lib/transaction/smart-transactions.ts +++ b/app/scripts/lib/transaction/smart-transactions.ts @@ -63,6 +63,7 @@ export type SubmitSmartTransactionRequest = { smartTransactionsController: SmartTransactionsController; transactionController: TransactionController; isSmartTransaction: boolean; + isHardwareWallet: boolean; controllerMessenger: SmartTransactionsControllerMessenger; featureFlags: FeatureFlags; }; @@ -90,6 +91,8 @@ class SmartTransactionHook { #isSmartTransaction: boolean; + #isHardwareWallet: boolean; + #smartTransactionsController: SmartTransactionsController; #transactionController: TransactionController; @@ -104,6 +107,7 @@ class SmartTransactionHook { smartTransactionsController, transactionController, isSmartTransaction, + isHardwareWallet, controllerMessenger, featureFlags, } = request; @@ -113,6 +117,7 @@ class SmartTransactionHook { this.#smartTransactionsController = smartTransactionsController; this.#transactionController = transactionController; this.#isSmartTransaction = isSmartTransaction; + this.#isHardwareWallet = isHardwareWallet; this.#controllerMessenger = controllerMessenger; this.#featureFlags = featureFlags; this.#isDapp = transactionMeta.origin !== ORIGIN_METAMASK; @@ -132,6 +137,7 @@ class SmartTransactionHook { const useRegularTransactionSubmit = { transactionHash: undefined }; if ( !this.#isSmartTransaction || + this.#isHardwareWallet || isUnsupportedTransactionTypeForSmartTransaction ) { return useRegularTransactionSubmit; diff --git a/app/scripts/lib/transaction/util.test.ts b/app/scripts/lib/transaction/util.test.ts index f8b07cc7b17c..a27844f53b41 100644 --- a/app/scripts/lib/transaction/util.test.ts +++ b/app/scripts/lib/transaction/util.test.ts @@ -1,4 +1,4 @@ -import { InternalAccount } from '@metamask/keyring-api'; +import { EthAccountType, InternalAccount } from '@metamask/keyring-api'; import { TransactionParams } from '@metamask/eth-json-rpc-middleware'; import { TransactionController, @@ -9,6 +9,7 @@ import { UserOperationController } from '@metamask/user-operation-controller'; import { cloneDeep } from 'lodash'; import { generateSecurityAlertId, + isChainSupported, validateRequestWithPPOM, } from '../ppom/ppom-util'; import { @@ -38,6 +39,26 @@ jest.mock('uuid', () => { const SECURITY_ALERT_ID_MOCK = '123'; +const INTERNAL_ACCOUNT_ADDRESS = '0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b'; + +const mockAccount = { + type: EthAccountType.Eoa, + id: '3afa663e-0600-4d93-868a-61c2e553013b', + address: INTERNAL_ACCOUNT_ADDRESS, + methods: [], + options: {}, +}; +const mockInternalAccount = { + ...mockAccount, + metadata: { + name: `Account 1`, + importTime: Date.now(), + keyring: { + type: '', + }, + }, +}; + const TRANSACTION_PARAMS_MOCK: TransactionParams = { from: '0x1', }; @@ -69,7 +90,8 @@ const TRANSACTION_REQUEST_MOCK: AddTransactionRequest = { transactionParams: TRANSACTION_PARAMS_MOCK, transactionOptions: TRANSACTION_OPTIONS_MOCK, waitForSubmit: false, -} as AddTransactionRequest; + internalAccounts: [], +} as unknown as AddTransactionRequest; const SECURITY_ALERT_RESPONSE_MOCK: SecurityAlertResponse = { result_type: BlockaidResultType.Malicious, @@ -97,6 +119,7 @@ describe('Transaction Utils', () => { let userOperationController: jest.Mocked; const validateRequestWithPPOMMock = jest.mocked(validateRequestWithPPOM); const generateSecurityAlertIdMock = jest.mocked(generateSecurityAlertId); + const isChainSupportedMock = jest.mocked(isChainSupported); beforeEach(() => { jest.resetAllMocks(); @@ -121,6 +144,7 @@ describe('Transaction Utils', () => { }); generateSecurityAlertIdMock.mockReturnValue(SECURITY_ALERT_ID_MOCK); + isChainSupportedMock.mockResolvedValue(true); request.transactionController = transactionController; request.userOperationController = userOperationController; @@ -466,7 +490,38 @@ describe('Transaction Utils', () => { expect(validateRequestWithPPOMMock).toHaveBeenCalledTimes(0); }); + it('send to users own acccount', async () => { + const sendRequest = { + ...request, + transactionParams: { + ...request.transactionParams, + to: INTERNAL_ACCOUNT_ADDRESS, + }, + }; + await addTransaction({ + ...sendRequest, + securityAlertsEnabled: false, + chainId: '0x1', + internalAccounts: [mockInternalAccount], + }); + + expect( + request.transactionController.addTransaction, + ).toHaveBeenCalledTimes(1); + + expect( + request.transactionController.addTransaction, + ).toHaveBeenCalledWith( + sendRequest.transactionParams, + TRANSACTION_OPTIONS_MOCK, + ); + + expect(validateRequestWithPPOMMock).toHaveBeenCalledTimes(0); + }); + it('unless chain is not supported', async () => { + isChainSupportedMock.mockResolvedValue(false); + await addTransaction({ ...request, securityAlertsEnabled: true, diff --git a/app/scripts/lib/transaction/util.ts b/app/scripts/lib/transaction/util.ts index 8069d72372c0..988ec66ee5bb 100644 --- a/app/scripts/lib/transaction/util.ts +++ b/app/scripts/lib/transaction/util.ts @@ -16,13 +16,13 @@ import { PPOMController } from '@metamask/ppom-validator'; import { generateSecurityAlertId, handlePPOMError, + isChainSupported, validateRequestWithPPOM, } from '../ppom/ppom-util'; import { SecurityAlertResponse } from '../ppom/types'; import { LOADING_SECURITY_ALERT_RESPONSE, SECURITY_PROVIDER_EXCLUDED_TRANSACTION_TYPES, - SECURITY_PROVIDER_SUPPORTED_CHAIN_IDS, } from '../../../../shared/constants/security-provider'; export type AddTransactionOptions = NonNullable< @@ -43,6 +43,7 @@ type BaseAddTransactionRequest = { securityAlertResponse: SecurityAlertResponse, ) => void; userOperationController: UserOperationController; + internalAccounts: InternalAccount[]; }; type FinalAddTransactionRequest = BaseAddTransactionRequest & { @@ -86,7 +87,7 @@ export async function addDappTransaction( export async function addTransaction( request: AddTransactionRequest, ): Promise { - validateSecurity(request); + await validateSecurity(request); const { transactionMeta, waitForHash } = await addTransactionOrUserOperation( request, @@ -215,7 +216,7 @@ function getTransactionByHash( ); } -function validateSecurity(request: AddTransactionRequest) { +async function validateSecurity(request: AddTransactionRequest) { const { chainId, ppomController, @@ -223,10 +224,13 @@ function validateSecurity(request: AddTransactionRequest) { transactionOptions, transactionParams, updateSecurityAlertResponse, + internalAccounts, } = request; const { type } = transactionOptions; + const isCurrentChainSupported = await isChainSupported(chainId); + const typeIsExcludedFromPPOM = SECURITY_PROVIDER_EXCLUDED_TRANSACTION_TYPES.includes( type as TransactionType, @@ -234,12 +238,21 @@ function validateSecurity(request: AddTransactionRequest) { if ( !securityAlertsEnabled || - !SECURITY_PROVIDER_SUPPORTED_CHAIN_IDS.includes(chainId) || + !isCurrentChainSupported || typeIsExcludedFromPPOM ) { return; } + if ( + internalAccounts.some( + ({ address }) => + address.toLowerCase() === transactionParams.to?.toLowerCase(), + ) + ) { + return; + } + try { const { from, to, value, data } = transactionParams; const { actionId, origin } = transactionOptions; @@ -261,6 +274,7 @@ function validateSecurity(request: AddTransactionRequest) { const securityAlertId = generateSecurityAlertId(); + // Intentionally not awaited to avoid blocking the confirmation process while the validation occurs. validateRequestWithPPOM({ ppomController, request: ppomRequest, diff --git a/app/scripts/lib/util.test.js b/app/scripts/lib/util.test.js index 1868007f7684..7ae7e7b36a88 100644 --- a/app/scripts/lib/util.test.js +++ b/app/scripts/lib/util.test.js @@ -14,6 +14,7 @@ import { PLATFORM_OPERA, } from '../../../shared/constants/app'; import { isPrefixedFormattedHexString } from '../../../shared/modules/network.utils'; +import * as FourBiteUtils from '../../../shared/lib/four-byte'; import { shouldEmitDappViewedEvent, addUrlProtocolPrefix, @@ -23,6 +24,7 @@ import { getPlatform, getValidUrl, isWebUrl, + getMethodDataName, } from './util'; describe('app utils', () => { @@ -357,4 +359,81 @@ describe('app utils', () => { expect(result).toStrictEqual(expectedResult); }); }); + + describe('getMethodDataName', () => { + const knownMethodData = { + '0x60806040': { + name: 'Approve Tokens', + }, + '0x095ea7b3': { + name: 'Approve Tokens', + }, + }; + + it('return null if use4ByteResolution is not true', async () => { + expect( + await getMethodDataName(knownMethodData, false, '0x60806040'), + ).toStrictEqual(null); + }); + + it('return null if prefixedData is not defined', async () => { + expect( + await getMethodDataName(knownMethodData, true, undefined), + ).toStrictEqual(null); + }); + + it('return details from knownMethodData if defined', async () => { + expect( + await getMethodDataName(knownMethodData, true, '0x60806040'), + ).toStrictEqual(knownMethodData['0x60806040']); + }); + + it('invoke getMethodDataAsync if details not available in knownMethodData', async () => { + const DUMMY_METHOD_NAME = { + name: 'Dummy Method Name', + }; + jest + .spyOn(FourBiteUtils, 'getMethodDataAsync') + .mockResolvedValue(DUMMY_METHOD_NAME); + expect( + await getMethodDataName(knownMethodData, true, '0x123', jest.fn()), + ).toStrictEqual(DUMMY_METHOD_NAME); + }); + + it('invoke addKnownMethodData if details not available in knownMethodData', async () => { + const DUMMY_METHOD_NAME = { + name: 'Dummy Method Name', + }; + const addKnownMethodData = jest.fn(); + jest + .spyOn(FourBiteUtils, 'getMethodDataAsync') + .mockResolvedValue(DUMMY_METHOD_NAME); + expect( + await getMethodDataName( + knownMethodData, + true, + '0x123', + addKnownMethodData, + ), + ).toStrictEqual(DUMMY_METHOD_NAME); + expect(addKnownMethodData).toHaveBeenCalledTimes(1); + }); + + it('does not invoke addKnownMethodData if no method data available', async () => { + const addKnownMethodData = jest.fn(); + + jest.spyOn(FourBiteUtils, 'getMethodDataAsync').mockResolvedValue({}); + + expect( + await getMethodDataName( + knownMethodData, + true, + '0x123', + addKnownMethodData, + ), + ).toStrictEqual({}); + + expect(addKnownMethodData).toHaveBeenCalledTimes(0); + }); + }); }); diff --git a/app/scripts/lib/util.ts b/app/scripts/lib/util.ts index f44dc48628fa..e41b2a00b670 100644 --- a/app/scripts/lib/util.ts +++ b/app/scripts/lib/util.ts @@ -19,6 +19,7 @@ import { } from '../../../shared/constants/app'; import { CHAIN_IDS, TEST_CHAINS } from '../../../shared/constants/network'; import { stripHexPrefix } from '../../../shared/modules/hexstring-utils'; +import { getMethodDataAsync } from '../../../shared/lib/four-byte'; /** * @see {@link getEnvironmentType} @@ -382,3 +383,37 @@ export function formatValue( return includeParentheses ? `(${formattedNumber})` : formattedNumber; } + +type MethodData = { + name: string; + params: { type: string }[]; +}; + +export const getMethodDataName = async ( + knownMethodData: Record, + use4ByteResolution: boolean, + prefixedData: string, + addKnownMethodData: (fourBytePrefix: string, methodData: MethodData) => void, + provider: object, +) => { + if (!prefixedData || !use4ByteResolution) { + return null; + } + const fourBytePrefix = prefixedData.slice(0, 10); + + if (knownMethodData?.[fourBytePrefix]) { + return knownMethodData?.[fourBytePrefix]; + } + + const methodData = await getMethodDataAsync( + fourBytePrefix, + use4ByteResolution, + provider, + ); + + if (methodData?.name) { + addKnownMethodData(fourBytePrefix, methodData as MethodData); + } + + return methodData; +}; diff --git a/app/scripts/load/_initialize.ts b/app/scripts/load/_initialize.ts new file mode 100644 index 000000000000..5e0537876b73 --- /dev/null +++ b/app/scripts/load/_initialize.ts @@ -0,0 +1,28 @@ +/* eslint-disable @typescript-eslint/no-require-imports */ +// currently only used in webpack build. + +// The root compartment will populate this with hooks +global.stateHooks = {} as typeof stateHooks; + +if (process.env.ENABLE_LAVAMOAT === 'true') { + // TODO: lavamoat support + throw new Error('LAVAMOAT not supported in webpack build yet'); +} else { + if (process.env.ENABLE_SENTRY === 'true') { + require('../sentry-install'); + } + if (process.env.ENABLE_SNOW === 'true') { + require('@lavamoat/snow/snow.prod'); + require('../use-snow'); + } + if (process.env.ENABLE_LOCKDOWN === 'true') { + require('../lockdown-install'); + require('../lockdown-run'); + require('../lockdown-more'); + } + + require('../init-globals'); + require('../runtime-cjs'); +} + +export {}; diff --git a/app/scripts/load/background.ts b/app/scripts/load/background.ts new file mode 100644 index 000000000000..1017d516e361 --- /dev/null +++ b/app/scripts/load/background.ts @@ -0,0 +1,9 @@ +// currently only used in webpack build. + +import './_initialize'; +import '../background'; + +if (process.env.IN_TEST) { + // only used for testing + document.documentElement.classList.add('metamask-loaded'); +} diff --git a/app/scripts/load/ui.ts b/app/scripts/load/ui.ts new file mode 100644 index 000000000000..471489bf948f --- /dev/null +++ b/app/scripts/load/ui.ts @@ -0,0 +1,9 @@ +// currently only used in webpack build. + +import './_initialize'; +import '../ui'; + +if (process.env.IN_TEST) { + // only used for testing + document.documentElement.classList.add('metamask-loaded'); +} diff --git a/app/scripts/lockdown-install.js b/app/scripts/lockdown-install.js new file mode 100644 index 000000000000..add418dea917 --- /dev/null +++ b/app/scripts/lockdown-install.js @@ -0,0 +1,6 @@ +// currently only used in webpack build. + +import 'ses'; +// lockdown() is called in lockdown-run.js + +export {}; diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 82de51055a5b..141fab5aebc8 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -143,9 +143,10 @@ import { Interface } from '@ethersproject/abi'; import { abiERC1155, abiERC721 } from '@metamask/metamask-eth-abis'; import { isEvmAccountType } from '@metamask/keyring-api'; import { - methodsRequiringNetworkSwitch, - methodsWithConfirmation, -} from '../../shared/constants/methods-tags'; + AuthenticationController, + UserStorageController, +} from '@metamask/profile-sync-controller'; +import { methodsRequiringNetworkSwitch } from '../../shared/constants/methods-tags'; ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils'; @@ -181,12 +182,12 @@ import { UI_NOTIFICATIONS } from '../../shared/notifications'; import { MILLISECOND, SECOND } from '../../shared/constants/time'; import { ORIGIN_METAMASK, - SNAP_DIALOG_TYPES, POLLING_TOKEN_ENVIRONMENT_TYPES, } from '../../shared/constants/app'; import { MetaMetricsEventCategory, MetaMetricsEventName, + MetaMetricsUserTrait, } from '../../shared/constants/metametrics'; import { LOG_EVENT } from '../../shared/constants/logs'; @@ -203,9 +204,11 @@ import { isManifestV3 } from '../../shared/modules/mv3.utils'; import { convertNetworkId } from '../../shared/modules/network.utils'; import { getIsSmartTransaction, + isHardwareWallet, getFeatureFlagsByChainId, getSmartTransactionsOptInStatus, getCurrentChainSupportsSmartTransactions, + getHardwareWalletType, } from '../../shared/modules/selectors'; import { createCaipStream } from '../../shared/modules/caip-stream'; import { BaseUrl } from '../../shared/constants/urls'; @@ -234,7 +237,6 @@ import { afterTransactionSign as afterTransactionSignMMI, beforeCheckPendingTransaction as beforeCheckPendingTransactionMMI, beforeTransactionPublish as beforeTransactionPublishMMI, - beforeTransactionApproveOnInit as beforeApproveOnInitMMI, getAdditionalSignArguments as getAdditionalSignArgumentsMMI, } from './lib/transaction/mmi-hooks'; ///: END:ONLY_INCLUDE_IF @@ -281,7 +283,11 @@ import SwapsController from './controllers/swaps'; import MetaMetricsController from './controllers/metametrics'; import { segment } from './lib/segment'; import createMetaRPCHandler from './lib/createMetaRPCHandler'; -import { previousValueComparator } from './lib/util'; +import { + addHexPrefix, + getMethodDataName, + previousValueComparator, +} from './lib/util'; import createMetamaskMiddleware from './lib/createMetamaskMiddleware'; import { hardwareKeyringBuilderFactory } from './lib/hardware-keyring-builder-factory'; import EncryptionPublicKeyController from './controllers/encryption-public-key'; @@ -312,18 +318,19 @@ import { addDappTransaction, addTransaction } from './lib/transaction/util'; import { LatticeKeyringOffscreen } from './lib/offscreen-bridge/lattice-offscreen-keyring'; import PREINSTALLED_SNAPS from './snaps/preinstalled-snaps'; import { WeakRefObjectMap } from './lib/WeakRefObjectMap'; +import { METAMASK_COOKIE_HANDLER } from './constants/stream'; // Notification controllers -import AuthenticationController from './controllers/authentication/authentication-controller'; -import UserStorageController from './controllers/user-storage/user-storage-controller'; import { PushPlatformNotificationsController } from './controllers/push-platform-notifications/push-platform-notifications'; import { MetamaskNotificationsController } from './controllers/metamask-notifications/metamask-notifications'; import { createTxVerificationMiddleware } from './lib/tx-verification/tx-verification-middleware'; import { updateSecurityAlertResponse } from './lib/ppom/ppom-util'; import createEvmMethodsToNonEvmAccountReqFilterMiddleware from './lib/createEvmMethodsToNonEvmAccountReqFilterMiddleware'; import { isEthAddress } from './lib/multichain/address'; -import BridgeController from './controllers/bridge'; import { decodeTransactionData } from './lib/transaction/decode/util'; +import { BridgeBackgroundAction } from './controllers/bridge/types'; +import BridgeController from './controllers/bridge/bridge-controller'; +import { BRIDGE_CONTROLLER_NAME } from './controllers/bridge/constants'; export const METAMASK_CONTROLLER_EVENTS = { // Fired after state changes that impact the extension badge (unapproved msg count) @@ -362,7 +369,7 @@ export default class MetamaskController extends EventEmitter { this.platform = opts.platform; this.notificationManager = opts.notificationManager; const initState = opts.initState || {}; - const version = this.platform.getVersion(); + const version = process.env.METAMASK_VERSION; this.recordFirstTimeInfo(initState); this.featureFlags = opts.featureFlags; @@ -456,7 +463,6 @@ export default class MetamaskController extends EventEmitter { }), showApprovalRequest: opts.showUserConfirmation, typesExcludedFromRateLimiting: [ - ApprovalType.EthSign, ApprovalType.PersonalSign, ApprovalType.EthSignTypedData, ApprovalType.Transaction, @@ -724,7 +730,7 @@ export default class MetamaskController extends EventEmitter { }, getCurrentChainId: () => this.networkController.state.providerConfig.chainId, - version: this.platform.getVersion(), + version: process.env.METAMASK_VERSION, environment: process.env.METAMASK_ENVIRONMENT, extension: this.extension, initState: initState.MetaMetricsController, @@ -835,7 +841,10 @@ export default class MetamaskController extends EventEmitter { }), storageBackend: new IndexedDBPPOMStorage('PPOMDB', 1), provider: this.provider, - ppomProvider: { PPOM: PPOMModule.PPOM, ppomInit: PPOMModule.default }, + ppomProvider: { + PPOM: PPOMModule.PPOM, + ppomInit: () => PPOMModule.default(process.env.PPOM_URI), + }, state: initState.PPOMController, chainId: this.networkController.state.providerConfig.chainId, securityAlertsEnabled: @@ -956,9 +965,9 @@ export default class MetamaskController extends EventEmitter { let additionalKeyrings = [keyringBuilderFactory(QRHardwareKeyring)]; - if (isManifestV3 === false) { - const keyringOverrides = this.opts.overrides?.keyrings; + const keyringOverrides = this.opts.overrides?.keyrings; + if (isManifestV3 === false) { const additionalKeyringTypes = [ keyringOverrides?.lattice || LatticeKeyring, QRHardwareKeyring, @@ -989,8 +998,14 @@ export default class MetamaskController extends EventEmitter { ); } else { additionalKeyrings.push( - hardwareKeyringBuilderFactory(TrezorKeyring, TrezorOffscreenBridge), - hardwareKeyringBuilderFactory(LedgerKeyring, LedgerOffscreenBridge), + hardwareKeyringBuilderFactory( + TrezorKeyring, + keyringOverrides?.trezorBridge || TrezorOffscreenBridge, + ), + hardwareKeyringBuilderFactory( + LedgerKeyring, + keyringOverrides?.ledgerBridge || LedgerOffscreenBridge, + ), keyringBuilderFactory(LatticeKeyringOffscreen), ); } @@ -1022,6 +1037,7 @@ export default class MetamaskController extends EventEmitter { 'KeyringController:getAccounts', 'AccountsController:setSelectedAccount', 'AccountsController:getAccountByAddress', + 'AccountsController:setAccountName', ], }); @@ -1067,7 +1083,6 @@ export default class MetamaskController extends EventEmitter { snapKeyringBuildMessenger, getSnapController, persistAndUpdateAccounts, - (address) => this.preferencesController.setSelectedAddress(address), (address) => this.removeAccount(address), this.metaMetricsController.trackEvent.bind(this.metaMetricsController), getSnapName, @@ -1391,6 +1406,8 @@ export default class MetamaskController extends EventEmitter { allowedActions: [ `${this.phishingController.name}:maybeUpdateState`, `${this.phishingController.name}:testOrigin`, + `${this.approvalController.name}:hasRequest`, + `${this.approvalController.name}:acceptRequest`, ], }); @@ -1400,25 +1417,25 @@ export default class MetamaskController extends EventEmitter { }); // Notification Controllers - this.authenticationController = new AuthenticationController({ + this.authenticationController = new AuthenticationController.Controller({ state: initState.AuthenticationController, messenger: this.controllerMessenger.getRestricted({ name: 'AuthenticationController', allowedActions: [ 'KeyringController:getState', 'SnapController:handleRequest', - 'UserStorageController:disableProfileSyncing', ], allowedEvents: ['KeyringController:lock', 'KeyringController:unlock'], }), metametrics: { getMetaMetricsId: () => this.metaMetricsController.getMetaMetricsId(), + agent: 'extension', }, }); - this.userStorageController = new UserStorageController({ + this.userStorageController = new UserStorageController.Controller({ getMetaMetricsState: () => - this.metaMetricsController.state.participateInMetaMetrics, + this.metaMetricsController.state.participateInMetaMetrics ?? false, state: initState.UserStorageController, messenger: this.controllerMessenger.getRestricted({ name: 'UserStorageController', @@ -1430,8 +1447,8 @@ export default class MetamaskController extends EventEmitter { 'AuthenticationController:isSignedIn', 'AuthenticationController:performSignOut', 'AuthenticationController:performSignIn', - 'MetamaskNotificationsController:disableMetamaskNotifications', - 'MetamaskNotificationsController:selectIsMetamaskNotificationsEnabled', + 'NotificationServicesController:disableNotificationServices', + 'NotificationServicesController:selectIsNotificationServicesEnabled', ], allowedEvents: ['KeyringController:lock', 'KeyringController:unlock'], }), @@ -1471,6 +1488,8 @@ export default class MetamaskController extends EventEmitter { notification_id: notification.id, notification_type: notification.type, chain_id: notification?.chain_id, + notification_is_read: notification.isRead, + click_type: 'push_notification', }, }); }, @@ -1502,6 +1521,17 @@ export default class MetamaskController extends EventEmitter { state: initState.MetamaskNotificationsController, }); + // Temporary add missing methods (due to notification controller migration) + this.controllerMessenger.registerActionHandler( + 'NotificationServicesController:disableNotificationServices', + () => this.metamaskNotificationsController.disableMetamaskNotifications(), + ); + this.controllerMessenger.registerActionHandler( + 'NotificationServicesController:selectIsNotificationServicesEnabled', + () => + this.metamaskNotificationsController.selectIsMetamaskNotificationsEnabled(), + ); + // account tracker watches balances, nonces, and any code at their address this.accountTracker = new AccountTracker({ provider: this.provider, @@ -1659,6 +1689,7 @@ export default class MetamaskController extends EventEmitter { `${this.approvalController.name}:addRequest`, 'NetworkController:findNetworkClientIdByChainId', 'NetworkController:getNetworkClientById', + 'AccountsController:getSelectedAccount', ], allowedEvents: [`NetworkController:stateChange`], }); @@ -1685,8 +1716,6 @@ export default class MetamaskController extends EventEmitter { this.preferencesController.store.getState().advancedGasFee[ this.networkController.state.providerConfig.chainId ], - getSelectedAddress: () => - this.accountsController.getSelectedAccount().address, incomingTransactions: { includeTokenTransfers: false, isEnabled: () => @@ -1732,7 +1761,6 @@ export default class MetamaskController extends EventEmitter { ), beforeCheckPendingTransaction: beforeCheckPendingTransactionMMI.bind(this), - beforeApproveOnInit: beforeApproveOnInitMMI.bind(this), beforePublish: beforeTransactionPublishMMI.bind(this), getAdditionalSignArguments: getAdditionalSignArgumentsMMI.bind(this), ///: END:ONLY_INCLUDE_IF @@ -1793,9 +1821,6 @@ export default class MetamaskController extends EventEmitter { `${this.loggingController.name}:add`, ], }), - isEthSignEnabled: () => - this.preferencesController.store.getState() - ?.disabledRpcMethodPreferences?.eth_sign, getAllState: this.getState.bind(this), getCurrentChainId: () => this.networkController.state.providerConfig.chainId, @@ -1877,8 +1902,27 @@ export default class MetamaskController extends EventEmitter { }); ///: END:ONLY_INCLUDE_IF + const swapsControllerMessenger = this.controllerMessenger.getRestricted({ + name: 'SwapsController', + // TODO: allow these internal calls once GasFeeController and TransactionController + // export these action types and register its action handlers + // allowedActions: [ + // 'GasFeeController:getEIP1559GasFeeEstimates', + // 'TransactionController:getLayer1GasFee', + // ], + allowedActions: [ + 'NetworkController:getState', + 'NetworkController:getNetworkClientById', + 'TokenRatesController:getState', + ], + allowedEvents: [], + }); + this.swapsController = new SwapsController( { + messenger: swapsControllerMessenger, + provider: this.provider, + // TODO: Remove once TransactionController exports this action type getBufferedGasLimit: async (txMeta, multiplier) => { const { gas: gasLimit, simulationFails } = await this.txController.estimateGasBuffered( @@ -1888,27 +1932,31 @@ export default class MetamaskController extends EventEmitter { return { gasLimit, simulationFails }; }, - provider: this.provider, - getProviderConfig: () => this.networkController.state.providerConfig, - getTokenRatesState: () => this.tokenRatesController.state, - getCurrentChainId: () => - this.networkController.state.providerConfig.chainId, + // TODO: Remove once GasFeeController exports this action type getEIP1559GasFeeEstimates: this.gasFeeController.fetchGasFeeEstimates.bind( this.gasFeeController, ), + // TODO: Remove once TransactionController exports this action type getLayer1GasFee: this.txController.getLayer1GasFee.bind( this.txController, ), - getNetworkClientId: () => - this.networkController.state.selectedNetworkClientId, trackMetaMetricsEvent: this.metaMetricsController.trackEvent.bind( this.metaMetricsController, ), }, initState.SwapsController, ); - this.bridgeController = new BridgeController(); + + const bridgeControllerMessenger = this.controllerMessenger.getRestricted({ + name: BRIDGE_CONTROLLER_NAME, + allowedActions: [], + allowedEvents: [], + }); + this.bridgeController = new BridgeController({ + messenger: bridgeControllerMessenger, + }); + this.smartTransactionsController = new SmartTransactionsController( { getNetworkClientById: this.networkController.getNetworkClientById.bind( @@ -1928,6 +1976,20 @@ export default class MetamaskController extends EventEmitter { trackMetaMetricsEvent: this.metaMetricsController.trackEvent.bind( this.metaMetricsController, ), + getMetaMetricsProps: async () => { + const selectedAddress = + this.accountsController.getSelectedAccount().address; + const accountHardwareType = await getHardwareWalletType( + this._getMetaMaskState(), + ); + const accountType = await this.getAccountType(selectedAddress); + const deviceModel = await this.getDeviceModel(selectedAddress); + return { + accountHardwareType, + accountType, + deviceModel, + }; + }, }, { supportedChainIds: getAllowedSmartTransactionsChainIds(), @@ -2058,9 +2120,6 @@ export default class MetamaskController extends EventEmitter { ), // msg signing ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) - processEthSignMessage: this.signatureController.newUnsignedMessage.bind( - this.signatureController, - ), processTypedMessage: this.signatureController.newUnsignedTypedMessage.bind( this.signatureController, @@ -2081,9 +2140,6 @@ export default class MetamaskController extends EventEmitter { ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) /* eslint-disable no-dupe-keys */ - processEthSignMessage: this.mmiController.newUnsignedMessage.bind( - this.mmiController, - ), processTypedMessage: this.mmiController.newUnsignedMessage.bind( this.mmiController, ), @@ -2137,8 +2193,8 @@ export default class MetamaskController extends EventEmitter { DecryptMessageController: this.decryptMessageController, EncryptionPublicKeyController: this.encryptionPublicKeyController, SignatureController: this.signatureController, - SwapsController: this.swapsController.store, - BridgeController: this.bridgeController.store, + SwapsController: this.swapsController, + BridgeController: this.bridgeController, EnsController: this.ensController, ApprovalController: this.approvalController, PPOMController: this.ppomController, @@ -2260,7 +2316,8 @@ export default class MetamaskController extends EventEmitter { this.encryptionPublicKeyController, ), this.signatureController.resetState.bind(this.signatureController), - this.swapsController.resetState, + this.swapsController.resetState.bind(this.swapsController), + this.bridgeController.resetState.bind(this.bridgeController), this.ensController.resetState.bind(this.ensController), this.approvalController.clear.bind(this.approvalController), // WE SHOULD ADD TokenListController.resetState here too. But it's not implemented yet. @@ -2328,6 +2385,26 @@ export default class MetamaskController extends EventEmitter { if (usePhishDetect) { this.phishingController.maybeUpdateState(); } + + // post onboarding emit detectTokens event + const preferencesControllerState = + this.preferencesController.store.getState(); + const { useTokenDetection, useNftDetection } = + preferencesControllerState ?? {}; + this.metaMetricsController.trackEvent({ + category: MetaMetricsEventCategory.Onboarding, + event: MetaMetricsUserTrait.TokenDetectionEnabled, + properties: { + [MetaMetricsUserTrait.TokenDetectionEnabled]: useTokenDetection, + }, + }); + this.metaMetricsController.trackEvent({ + category: MetaMetricsEventCategory.Onboarding, + event: MetaMetricsUserTrait.NftAutodetectionEnabled, + properties: { + [MetaMetricsUserTrait.NftAutodetectionEnabled]: useNftDetection, + }, + }); } triggerNetworkrequests() { @@ -2493,7 +2570,11 @@ export default class MetamaskController extends EventEmitter { ...buildSnapRestrictedMethodSpecifications( Object.keys(ExcludedSnapPermissions), { - getLocale: this.getLocale.bind(this), + getPreferences: () => { + const locale = this.getLocale(); + const currency = this.currencyRateController.state.currentCurrency; + return { locale, currency }; + }, clearSnapState: this.controllerMessenger.call.bind( this.controllerMessenger, 'SnapController:clearSnapState', @@ -2511,12 +2592,10 @@ export default class MetamaskController extends EventEmitter { this.controllerMessenger, 'SnapController:getSnapState', ), - showDialog: (origin, type, id, placeholder) => - this.approvalController.addAndShowApprovalRequest({ - origin, - type: SNAP_DIALOG_TYPES[type], - requestData: { id, placeholder }, - }), + requestUserApproval: + this.approvalController.addAndShowApprovalRequest.bind( + this.approvalController, + ), showNativeNotification: (origin, args) => this.controllerMessenger.call( 'RateLimitController:call', @@ -2958,8 +3037,6 @@ export default class MetamaskController extends EventEmitter { onboardingController, permissionController, preferencesController, - bridgeController, - swapsController, tokensController, smartTransactionsController, txController, @@ -3063,6 +3140,10 @@ export default class MetamaskController extends EventEmitter { metaMetricsController.setDataCollectionForMarketing.bind( metaMetricsController, ), + setMarketingCampaignCookieId: + metaMetricsController.setMarketingCampaignCookieId.bind( + metaMetricsController, + ), setCurrentLocale: preferencesController.setCurrentLocale.bind( preferencesController, ), @@ -3180,14 +3261,6 @@ export default class MetamaskController extends EventEmitter { preferencesController.setDismissSeedBackUpReminder.bind( preferencesController, ), - setDisabledRpcMethodPreference: - preferencesController.setDisabledRpcMethodPreference.bind( - preferencesController, - ), - getRpcMethodPreferences: - preferencesController.getRpcMethodPreferences.bind( - preferencesController, - ), setAdvancedGasFee: preferencesController.setAdvancedGasFee.bind( preferencesController, ), @@ -3211,7 +3284,6 @@ export default class MetamaskController extends EventEmitter { accountsController.setAccountName.bind(accountsController), setAccountLabel: (address, label) => { - this.preferencesController.setAccountLabel(address, label); const account = this.accountsController.getAccountByAddress(address); if (account === undefined) { throw new Error(`No account found for address: ${address}`); @@ -3316,6 +3388,14 @@ export default class MetamaskController extends EventEmitter { appStateController.setSwitchedNetworkNeverShowMessage.bind( appStateController, ), + getLastInteractedConfirmationInfo: + appStateController.getLastInteractedConfirmationInfo.bind( + appStateController, + ), + setLastInteractedConfirmationInfo: + appStateController.setLastInteractedConfirmationInfo.bind( + appStateController, + ), // EnsController tryReverseResolveAddress: @@ -3427,10 +3507,6 @@ export default class MetamaskController extends EventEmitter { getCustodianAccounts: this.mmiController.getCustodianAccounts.bind( this.mmiController, ), - getCustodianAccountsByAddress: - this.mmiController.getCustodianAccountsByAddress.bind( - this.mmiController, - ), getCustodianTransactionDeepLink: this.mmiController.getCustodianTransactionDeepLink.bind( this.mmiController, @@ -3529,47 +3605,101 @@ export default class MetamaskController extends EventEmitter { ), // swaps - fetchAndSetQuotes: - swapsController.fetchAndSetQuotes.bind(swapsController), - setSelectedQuoteAggId: - swapsController.setSelectedQuoteAggId.bind(swapsController), - resetSwapsState: swapsController.resetSwapsState.bind(swapsController), - setSwapsTokens: swapsController.setSwapsTokens.bind(swapsController), - clearSwapsQuotes: swapsController.clearSwapsQuotes.bind(swapsController), - setApproveTxId: swapsController.setApproveTxId.bind(swapsController), - setTradeTxId: swapsController.setTradeTxId.bind(swapsController), - setSwapsTxGasPrice: - swapsController.setSwapsTxGasPrice.bind(swapsController), - setSwapsTxGasLimit: - swapsController.setSwapsTxGasLimit.bind(swapsController), - setSwapsTxMaxFeePerGas: - swapsController.setSwapsTxMaxFeePerGas.bind(swapsController), - setSwapsTxMaxFeePriorityPerGas: - swapsController.setSwapsTxMaxFeePriorityPerGas.bind(swapsController), - safeRefetchQuotes: - swapsController.safeRefetchQuotes.bind(swapsController), - stopPollingForQuotes: - swapsController.stopPollingForQuotes.bind(swapsController), - setBackgroundSwapRouteState: - swapsController.setBackgroundSwapRouteState.bind(swapsController), - resetPostFetchState: - swapsController.resetPostFetchState.bind(swapsController), - setSwapsErrorKey: swapsController.setSwapsErrorKey.bind(swapsController), - setInitialGasEstimate: - swapsController.setInitialGasEstimate.bind(swapsController), - setCustomApproveTxData: - swapsController.setCustomApproveTxData.bind(swapsController), - setSwapsLiveness: swapsController.setSwapsLiveness.bind(swapsController), - setSwapsFeatureFlags: - swapsController.setSwapsFeatureFlags.bind(swapsController), - setSwapsUserFeeLevel: - swapsController.setSwapsUserFeeLevel.bind(swapsController), - setSwapsQuotesPollingLimitEnabled: - swapsController.setSwapsQuotesPollingLimitEnabled.bind(swapsController), + fetchAndSetQuotes: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SwapsController:fetchAndSetQuotes', + ), + setSelectedQuoteAggId: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SwapsController:setSelectedQuoteAggId', + ), + resetSwapsState: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SwapsController:resetSwapsState', + ), + setSwapsTokens: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SwapsController:setSwapsTokens', + ), + clearSwapsQuotes: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SwapsController:clearSwapsQuotes', + ), + setApproveTxId: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SwapsController:setApproveTxId', + ), + setTradeTxId: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SwapsController:setTradeTxId', + ), + setSwapsTxGasPrice: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SwapsController:setSwapsTxGasPrice', + ), + setSwapsTxGasLimit: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SwapsController:setSwapsTxGasLimit', + ), + setSwapsTxMaxFeePerGas: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SwapsController:setSwapsTxMaxFeePerGas', + ), + setSwapsTxMaxFeePriorityPerGas: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SwapsController:setSwapsTxMaxFeePriorityPerGas', + ), + safeRefetchQuotes: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SwapsController:safeRefetchQuotes', + ), + stopPollingForQuotes: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SwapsController:stopPollingForQuotes', + ), + setBackgroundSwapRouteState: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SwapsController:setBackgroundSwapRouteState', + ), + resetPostFetchState: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SwapsController:resetPostFetchState', + ), + setSwapsErrorKey: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SwapsController:setSwapsErrorKey', + ), + setInitialGasEstimate: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SwapsController:setInitialGasEstimate', + ), + setCustomApproveTxData: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SwapsController:setCustomApproveTxData', + ), + setSwapsLiveness: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SwapsController:setSwapsLiveness', + ), + setSwapsFeatureFlags: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SwapsController:setSwapsFeatureFlags', + ), + setSwapsUserFeeLevel: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SwapsController:setSwapsUserFeeLevel', + ), + setSwapsQuotesPollingLimitEnabled: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SwapsController:setSwapsQuotesPollingLimitEnabled', + ), // Bridge - setBridgeFeatureFlags: - bridgeController.setBridgeFeatureFlags.bind(bridgeController), + [BridgeBackgroundAction.SET_FEATURE_FLAGS]: + this.controllerMessenger.call.bind( + this.controllerMessenger, + `${BRIDGE_CONTROLLER_NAME}:${BridgeBackgroundAction.SET_FEATURE_FLAGS}`, + ), // Smart Transactions fetchSmartTransactionFees: smartTransactionsController.getFees.bind( @@ -4620,6 +4750,7 @@ export default class MetamaskController extends EventEmitter { dappRequest, }) { return { + internalAccounts: this.accountsController.listAccounts(), dappRequest, networkClientId: dappRequest?.networkClientId ?? @@ -4928,6 +5059,42 @@ export default class MetamaskController extends EventEmitter { ); } + setUpCookieHandlerCommunication({ connectionStream }) { + const { + metaMetricsId, + dataCollectionForMarketing, + participateInMetaMetrics, + } = this.metaMetricsController.store.getState(); + + if ( + metaMetricsId && + dataCollectionForMarketing && + participateInMetaMetrics + ) { + // setup multiplexing + const mux = setupMultiplex(connectionStream); + const metamaskCookieHandlerStream = mux.createStream( + METAMASK_COOKIE_HANDLER, + ); + // set up postStream transport + metamaskCookieHandlerStream.on( + 'data', + createMetaRPCHandler( + { + getCookieFromMarketingPage: + this.getCookieFromMarketingPage.bind(this), + }, + metamaskCookieHandlerStream, + ), + ); + } + } + + getCookieFromMarketingPage(data) { + const { ga_client_id: cookieId } = data; + this.metaMetricsController.setMarketingCampaignCookieId(cookieId); + } + /** * Called when we detect a suspicious domain. Requests the browser redirects * to our anti-phishing page. @@ -5199,16 +5366,7 @@ export default class MetamaskController extends EventEmitter { this.preferencesController, ), shouldEnqueueRequest: (request) => { - if ( - request.method === 'eth_requestAccounts' && - this.permissionController.hasPermission( - request.origin, - PermissionNames.eth_accounts, - ) - ) { - return false; - } - return methodsWithConfirmation.includes(request.method); + return methodsRequiringNetworkSwitch.includes(request.method); }, }); engine.push(requestQueueMiddleware); @@ -5247,6 +5405,7 @@ export default class MetamaskController extends EventEmitter { this.preferencesController, this.networkController, this.appStateController, + this.accountsController, this.updateSecurityAlertResponse.bind(this), ), ); @@ -5366,7 +5525,7 @@ export default class MetamaskController extends EventEmitter { { eth_accounts: {} }, ), requestPermittedChainsPermission: (chainIds) => - this.permissionController.requestPermissions( + this.permissionController.requestPermissionsIncremental( { origin }, { [PermissionNames.permittedChains]: { @@ -5519,9 +5678,25 @@ export default class MetamaskController extends EventEmitter { getIsLocked: () => { return !this.appStateController.isUnlocked(); }, - ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) - hasPermission: this.permissionController.hasPermission.bind( - this.permissionController, + getInterfaceState: (...args) => + this.controllerMessenger.call( + 'SnapInterfaceController:getInterface', + origin, + ...args, + ).state, + createInterface: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SnapInterfaceController:createInterface', + origin, + ), + updateInterface: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SnapInterfaceController:updateInterface', + origin, + ), + resolveInterface: this.controllerMessenger.call.bind( + this.controllerMessenger, + 'SnapInterfaceController:resolveInterface', origin, ), getSnap: this.controllerMessenger.call.bind( @@ -5532,28 +5707,17 @@ export default class MetamaskController extends EventEmitter { this.controllerMessenger, 'SnapController:getAll', ), + ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) + hasPermission: this.permissionController.hasPermission.bind( + this.permissionController, + origin, + ), handleSnapRpcRequest: (args) => this.handleSnapRequest({ ...args, origin }), getAllowedKeyringMethods: keyringSnapPermissionsBuilder( this.subjectMetadataController, origin, ), - createInterface: this.controllerMessenger.call.bind( - this.controllerMessenger, - 'SnapInterfaceController:createInterface', - origin, - ), - getInterfaceState: (...args) => - this.controllerMessenger.call( - 'SnapInterfaceController:getInterface', - origin, - ...args, - ).state, - updateInterface: this.controllerMessenger.call.bind( - this.controllerMessenger, - 'SnapInterfaceController:updateInterface', - origin, - ), ///: END:ONLY_INCLUDE_IF }), ); @@ -5948,13 +6112,23 @@ export default class MetamaskController extends EventEmitter { this.controllerMessenger.subscribe( 'TransactionController:transactionNewSwap', ({ transactionMeta }) => - this.swapsController.setTradeTxId(transactionMeta.id), + // TODO: This can be called internally by the TransactionController + // since Swaps Controller registers this action handler + this.controllerMessenger.call( + 'SwapsController:setTradeTxId', + transactionMeta.id, + ), ); this.controllerMessenger.subscribe( 'TransactionController:transactionNewSwapApproval', ({ transactionMeta }) => - this.swapsController.setApproveTxId(transactionMeta.id), + // TODO: This can be called internally by the TransactionController + // since Swaps Controller registers this action handler + this.controllerMessenger.call( + 'SwapsController:setApproveTxId', + transactionMeta.id, + ), ); this.controllerMessenger.subscribe( @@ -6018,10 +6192,34 @@ export default class MetamaskController extends EventEmitter { getRedesignedConfirmationsEnabled: () => { return this.preferencesController.getRedesignedConfirmationsEnabled; }, + getRedesignedTransactionsEnabled: () => { + return this.preferencesController.getRedesignedTransactionsEnabled; + }, + getMethodData: (data) => { + if (!data) { + return null; + } + const { knownMethodData, use4ByteResolution } = + this.preferencesController.store.getState(); + const prefixedData = addHexPrefix(data); + return getMethodDataName( + knownMethodData, + use4ByteResolution, + prefixedData, + this.preferencesController.addKnownMethodData.bind( + this.preferencesController, + ), + this.provider, + ); + }, getIsRedesignedConfirmationsDeveloperEnabled: () => { return this.preferencesController.store.getState().preferences .isRedesignedConfirmationsDeveloperEnabled; }, + getIsConfirmationAdvancedDetailsOpen: () => { + return this.preferencesController.store.getState().preferences + .showConfirmationAdvancedDetails; + }, }; return { ...controllerActions, @@ -6103,7 +6301,7 @@ export default class MetamaskController extends EventEmitter { */ recordFirstTimeInfo(initState) { if (!('firstTimeInfo' in initState)) { - const version = this.platform.getVersion(); + const version = process.env.METAMASK_VERSION; initState.firstTimeInfo = { version, date: Date.now(), @@ -6621,6 +6819,7 @@ export default class MetamaskController extends EventEmitter { smartTransactionsController: this.smartTransactionsController, controllerMessenger: this.controllerMessenger, isSmartTransaction, + isHardwareWallet: isHardwareWallet(state), featureFlags, }); } diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js index 8b6c4acd38bc..9803f2f21c4f 100644 --- a/app/scripts/metamask-controller.test.js +++ b/app/scripts/metamask-controller.test.js @@ -27,9 +27,9 @@ import { RatesController, TokenListController, } from '@metamask/assets-controllers'; +import ObjectMultiplex from '@metamask/object-multiplex'; import { TrezorKeyring } from '@metamask/eth-trezor-keyring'; import { LedgerKeyring } from '@metamask/eth-ledger-bridge-keyring'; -import ObjectMultiplex from '@metamask/object-multiplex'; import { NETWORK_TYPES } from '../../shared/constants/network'; import { createTestProviderTools } from '../../test/stub/provider'; import { HardwareDeviceNames } from '../../shared/constants/hardware-wallets'; @@ -358,6 +358,10 @@ describe('MetaMaskController', () => { }, ]), ); + + globalThis.sentry = { + withIsolationScope: jest.fn(), + }; }); afterEach(() => { @@ -460,17 +464,20 @@ describe('MetaMaskController', () => { }, ); + const metamaskVersion = process.env.METAMASK_VERSION; + afterEach(() => { + // reset `METAMASK_VERSION` env var + process.env.METAMASK_VERSION = metamaskVersion; + }); + it('should details with LoggingController', async () => { const mockVersion = '1.3.7'; - const mockGetVersionInfo = jest.fn().mockReturnValue(mockVersion); + process.env.METAMASK_VERSION = mockVersion; jest.spyOn(LoggingController.prototype, 'add'); const localController = new MetaMaskController({ initLangCode: 'en_US', - platform: { - getVersion: mockGetVersionInfo, - }, browser: browserPolyfillMock, infuraProjectId: 'foo', }); @@ -488,7 +495,7 @@ describe('MetaMaskController', () => { it('should openExtensionInBrowser if version is 8.1.0', () => { const mockVersion = '8.1.0'; - const mockGetVersionInfo = jest.fn().mockReturnValue(mockVersion); + process.env.METAMASK_VERSION = mockVersion; const openExtensionInBrowserMock = jest.fn(); @@ -496,7 +503,6 @@ describe('MetaMaskController', () => { new MetaMaskController({ initLangCode: 'en_US', platform: { - getVersion: mockGetVersionInfo, openExtensionInBrowser: openExtensionInBrowserMock, }, browser: browserPolyfillMock, diff --git a/app/scripts/migrations/120.2.test.ts b/app/scripts/migrations/120.2.test.ts index 9cd64275580b..a1b98acde2bd 100644 --- a/app/scripts/migrations/120.2.test.ts +++ b/app/scripts/migrations/120.2.test.ts @@ -573,4 +573,58 @@ describe('migration #120.2', () => { }); }); }); + + it('migrates state from all controllers', async () => { + const oldState = { + NetworkController: { + networkDetails: {}, + networkId: 'example', + networkStatus: 'example', + previousProviderStore: 'example', + provider: 'example', + providerConfig: { + id: 'some-id', + }, + selectedNetworkClientId: 'example', + }, + PhishingController: { + listState: {}, + phishingLists: [], + }, + SelectedNetworkController: { + domains: { + 'https://metamask.io': { + network: 'mainnet', + }, + }, + perDomainNetwork: true, + }, + SnapController: { + snapErrors: {}, + snapStates: {}, + unencryptedSnapStates: {}, + snaps: {}, + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: cloneDeep(oldState), + }); + + expect(transformedState.data).toEqual({ + NetworkController: { + providerConfig: {}, + selectedNetworkClientId: 'example', + }, + PhishingController: { + phishingLists: [], + }, + SnapController: { + snapStates: {}, + unencryptedSnapStates: {}, + snaps: {}, + }, + }); + }); }); diff --git a/app/scripts/migrations/120.2.ts b/app/scripts/migrations/120.2.ts index 06e6cc091b9e..ce79c30e2fac 100644 --- a/app/scripts/migrations/120.2.ts +++ b/app/scripts/migrations/120.2.ts @@ -10,7 +10,8 @@ type VersionedData = { export const version = 120.2; /** - * This migration removes any dangling instances of SelectedNetworkController.perDomainNetwork and SnapController.snapErrors + * This migration removes obsolete state from various controllers. In all cases, this was done to + * address Sentry errors. * * @param originalVersionedData - Versioned MetaMask extension state, exactly what we persist to dist. * @param originalVersionedData.meta - State metadata. diff --git a/app/scripts/migrations/123.test.ts b/app/scripts/migrations/123.test.ts new file mode 100644 index 000000000000..d9d3d4b440a1 --- /dev/null +++ b/app/scripts/migrations/123.test.ts @@ -0,0 +1,119 @@ +import { migrate, version } from './123'; + +const oldVersion = 122; + +describe('migration #123', () => { + it('updates the version metadata', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: {}, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.meta).toStrictEqual({ version }); + }); + + it('does nothing if no preferences controller state is set', async () => { + const oldState = { + OtherController: {}, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: oldState, + }); + + expect(transformedState.data).toEqual(oldState); + }); + + it('does nothing if no preferences state is set', async () => { + const oldState = { + PreferencesController: {}, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: oldState, + }); + + expect(transformedState.data).toEqual(oldState); + }); + + it('returns state with advanced details opened if `useNonceField` is enabled', async () => { + const initialState = { + PreferencesController: { + preferences: { showConfirmationAdvancedDetails: false }, + useNonceField: true, + }, + }; + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: initialState, + }); + + expect(transformedState).toEqual({ + meta: { version: oldVersion + 1 }, + data: { + ...initialState, + PreferencesController: { + ...initialState.PreferencesController, + preferences: { showConfirmationAdvancedDetails: true }, + }, + }, + }); + }); + + it('returns state with advanced details opened if `sendHexData` is enabled', async () => { + const initialState = { + PreferencesController: { + preferences: { showConfirmationAdvancedDetails: false }, + featureFlags: { + sendHexData: true, + }, + }, + }; + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: initialState, + }); + + expect(transformedState).toEqual({ + meta: { version: oldVersion + 1 }, + data: { + ...initialState, + PreferencesController: { + ...initialState.PreferencesController, + preferences: { showConfirmationAdvancedDetails: true }, + }, + }, + }); + }); + + it('returns state with advanced details closed if `sendHexData` and `useNonceField` are disabled', async () => { + const initialState = { + PreferencesController: { + preferences: { showConfirmationAdvancedDetails: false }, + useNonceField: false, + featureFlags: { + sendHexData: false, + }, + }, + }; + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: initialState, + }); + + expect(transformedState).toEqual({ + meta: { version: oldVersion + 1 }, + data: { + ...initialState, + PreferencesController: { + ...initialState.PreferencesController, + preferences: { showConfirmationAdvancedDetails: false }, + }, + }, + }); + }); +}); diff --git a/app/scripts/migrations/123.ts b/app/scripts/migrations/123.ts new file mode 100644 index 000000000000..c674bac963d2 --- /dev/null +++ b/app/scripts/migrations/123.ts @@ -0,0 +1,45 @@ +import { cloneDeep } from 'lodash'; + +type VersionedData = { + meta: { version: number }; + data: Record; +}; + +export const version = 123; + +/** + * This migration sets the preference `showConfirmationAdvancedDetails` to + * `true` if the user has enabled `useNonceField` or `sendHexData`. + * + * @param originalVersionedData - Versioned MetaMask extension state, exactly + * what we persist to dist. + * @param originalVersionedData.meta - State metadata. + * @param originalVersionedData.meta.version - The current state version. + * @param originalVersionedData.data - The persisted MetaMask state, keyed by + * controller. + * @returns Updated versioned MetaMask extension state. + */ +export async function migrate( + originalVersionedData: VersionedData, +): Promise { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + transformState(versionedData.data); + return versionedData; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function transformState(state: Record) { + const preferencesControllerState = state?.PreferencesController; + + if (preferencesControllerState?.preferences) { + const isCustomNonceFieldEnabled = preferencesControllerState?.useNonceField; + const isHexDataVisibilityEnabled = + preferencesControllerState?.featureFlags?.sendHexData; + + preferencesControllerState.preferences.showConfirmationAdvancedDetails = + isCustomNonceFieldEnabled || isHexDataVisibilityEnabled; + } + + return state; +} diff --git a/app/scripts/migrations/124.test.ts b/app/scripts/migrations/124.test.ts new file mode 100644 index 000000000000..9b5d6925ad36 --- /dev/null +++ b/app/scripts/migrations/124.test.ts @@ -0,0 +1,55 @@ +import { migrate, version } from './124'; + +const oldVersion = 123; + +describe('migration #124', () => { + it('updates the version metadata', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: {}, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.meta).toStrictEqual({ version }); + }); + + it('does nothing if no preferences controller state is set', async () => { + const oldState = { + OtherController: {}, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: oldState, + }); + + expect(transformedState.data).toEqual(oldState); + }); + + it('adds property if migration runs', async () => { + const oldState = { + PreferencesController: { + preferences: { + somePreference: true, + }, + }, + }; + + const expectedState = { + PreferencesController: { + preferences: { + redesignedTransactionsEnabled: false, + somePreference: true, + }, + }, + }; + + const transformedState = await migrate({ + meta: { version: oldVersion }, + data: oldState, + }); + + expect(transformedState.data).toEqual(expectedState); + }); +}); diff --git a/app/scripts/migrations/124.ts b/app/scripts/migrations/124.ts new file mode 100644 index 000000000000..3584a5052cdd --- /dev/null +++ b/app/scripts/migrations/124.ts @@ -0,0 +1,44 @@ +import { cloneDeep } from 'lodash'; + +type VersionedData = { + meta: { version: number }; + data: Record; +}; + +export const version = 124; + +/** + * This migration sets the preference `redesignedTransactionsEnabled` if the + * user has existing data. + * + * @param originalVersionedData - Versioned MetaMask extension state, exactly + * what we persist to dist. + * @param originalVersionedData.meta - State metadata. + * @param originalVersionedData.meta.version - The current state version. + * @param originalVersionedData.data - The persisted MetaMask state, keyed by + * controller. + * @returns Updated versioned MetaMask extension state. + */ +export async function migrate( + originalVersionedData: VersionedData, +): Promise { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + transformState(versionedData.data); + return versionedData; +} + +function transformState(state: Record): void { + const preferencesControllerState = state?.PreferencesController as + | Record + | undefined; + + const preferences = preferencesControllerState?.preferences as + | Record + | undefined; + + if (preferences) { + // Existing MetaMask users will have the option off by default + preferences.redesignedTransactionsEnabled = false; + } +} diff --git a/app/scripts/migrations/125.1.test.ts b/app/scripts/migrations/125.1.test.ts new file mode 100644 index 000000000000..eb00db9d1e07 --- /dev/null +++ b/app/scripts/migrations/125.1.test.ts @@ -0,0 +1,107 @@ +import { migrate, version } from './125.1'; + +const oldVersion = 125; + +describe(`migration #${version}`, () => { + afterEach(() => jest.resetAllMocks()); + + it('updates the version metadata', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: {}, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.meta).toStrictEqual({ version }); + }); + + it('Gracefully handles empty/undefined PreferencesController', async () => { + for (const PreferencesController of [{}, undefined, null, 1, '', []]) { + const oldStorage = { + meta: { version: oldVersion }, + data: { PreferencesController }, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data.TxController).toStrictEqual(undefined); + } + }); + + it('Enables token autodetection when basic functionality is on', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PreferencesController: { + useExternalServices: true, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toEqual({ + PreferencesController: { + useExternalServices: true, + useTokenDetection: true, + }, + }); + }); + + it('Does not enable token autodetection when basic functionality is off', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PreferencesController: { + useExternalServices: false, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toEqual({ + PreferencesController: { + useExternalServices: false, + }, + }); + }); + + it('Removes showTokenAutodetectModalOnUpgrade from the app metadata controller', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + AppMetadataController: { + previousMigrationVersion: oldVersion, + currentMigrationVersion: version, + showTokenAutodetectModalOnUpgrade: null, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toEqual({ + AppMetadataController: { + previousMigrationVersion: oldVersion, + currentMigrationVersion: version, + }, + }); + }); + + it('Does nothing if showTokenAutodetectModalOnUpgrade is not in the app metadata controller', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + AppMetadataController: { + previousMigrationVersion: oldVersion, + currentMigrationVersion: version, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toEqual({ + AppMetadataController: { + previousMigrationVersion: oldVersion, + currentMigrationVersion: version, + }, + }); + }); +}); diff --git a/app/scripts/migrations/125.1.ts b/app/scripts/migrations/125.1.ts new file mode 100644 index 000000000000..d3c975a78a11 --- /dev/null +++ b/app/scripts/migrations/125.1.ts @@ -0,0 +1,50 @@ +import { hasProperty, isObject } from '@metamask/utils'; +import { cloneDeep } from 'lodash'; + +type VersionedData = { + meta: { version: number }; + data: Record; +}; + +export const version = 125.1; + +/** + * This migration enables token auto-detection if the basic functionality toggle is on. + * + * It also removes an unused property `showTokenAutodetectModalOnUpgrade` from the app metadata controller. + * + * @param originalVersionedData - Versioned MetaMask extension state, exactly + * what we persist to dist. + * @param originalVersionedData.meta - State metadata. + * @param originalVersionedData.meta.version - The current state version. + * @param originalVersionedData.data - The persisted MetaMask state, keyed by + * controller. + * @returns Updated versioned MetaMask extension state. + */ +export async function migrate( + originalVersionedData: VersionedData, +): Promise { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + transformState(versionedData.data); + return versionedData; +} + +function transformState(state: Record) { + if ( + hasProperty(state, 'PreferencesController') && + isObject(state.PreferencesController) && + state.PreferencesController.useExternalServices === true + ) { + state.PreferencesController.useTokenDetection = true; + } + + if ( + hasProperty(state, 'AppMetadataController') && + isObject(state.AppMetadataController) + ) { + delete state.AppMetadataController.showTokenAutodetectModalOnUpgrade; + } + + return state; +} diff --git a/app/scripts/migrations/125.test.ts b/app/scripts/migrations/125.test.ts new file mode 100644 index 000000000000..a320d607263d --- /dev/null +++ b/app/scripts/migrations/125.test.ts @@ -0,0 +1,31 @@ +import { migrate, version } from './125'; + +const oldVersion = 124; + +describe('migration #125', () => { + afterEach(() => jest.resetAllMocks()); + + it('updates the version metadata', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: {}, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.meta).toStrictEqual({ version }); + }); + + it('deletes the deprecated Txcontroller key', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + Txcontroller: { + transactions: [], + }, + }, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data.TxController).toStrictEqual(undefined); + }); +}); diff --git a/app/scripts/migrations/125.ts b/app/scripts/migrations/125.ts new file mode 100644 index 000000000000..66ea80a2a911 --- /dev/null +++ b/app/scripts/migrations/125.ts @@ -0,0 +1,36 @@ +import { hasProperty } from '@metamask/utils'; +import { cloneDeep } from 'lodash'; + +type VersionedData = { + meta: { version: number }; + data: Record; +}; + +export const version = 125; + +/** + * This migration removes depreciated `Txcontroller` key if it is present in state. + * + * @param originalVersionedData - Versioned MetaMask extension state, exactly + * what we persist to dist. + * @param originalVersionedData.meta - State metadata. + * @param originalVersionedData.meta.version - The current state version. + * @param originalVersionedData.data - The persisted MetaMask state, keyed by + * controller. + * @returns Updated versioned MetaMask extension state. + */ +export async function migrate( + originalVersionedData: VersionedData, +): Promise { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + transformState(versionedData.data); + return versionedData; +} + +function transformState(state: Record) { + if (hasProperty(state, 'TxController')) { + delete state.TxController; + } + return state; +} diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index fd6683e9b802..bb64ec957f75 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -141,6 +141,10 @@ const migrations = [ require('./121.1'), require('./121.2'), require('./122'), + require('./123'), + require('./124'), + require('./125'), + require('./125.1'), ]; export default migrations; diff --git a/app/scripts/offscreen.js b/app/scripts/offscreen.js index ba796874f2fc..159a2c8a5773 100644 --- a/app/scripts/offscreen.js +++ b/app/scripts/offscreen.js @@ -1,4 +1,6 @@ +import { captureException } from '@sentry/browser'; import { OffscreenCommunicationTarget } from '../../shared/constants/offscreen-communication'; +import { getSocketBackgroundToMocha } from '../../test/e2e/background-socket/socket-background-to-mocha'; /** * Creates an offscreen document that can be used to load additional scripts @@ -9,29 +11,57 @@ import { OffscreenCommunicationTarget } from '../../shared/constants/offscreen-c */ export async function createOffscreen() { const { chrome } = globalThis; - if (!chrome.offscreen || (await chrome.offscreen.hasDocument())) { + if (!chrome.offscreen) { return; } + let offscreenDocumentLoadedListener; const loadPromise = new Promise((resolve) => { - const messageListener = (msg) => { + offscreenDocumentLoadedListener = (msg) => { if ( msg.target === OffscreenCommunicationTarget.extensionMain && msg.isBooted ) { - chrome.runtime.onMessage.removeListener(messageListener); + chrome.runtime.onMessage.removeListener( + offscreenDocumentLoadedListener, + ); resolve(); + + // If the Offscreen Document sees `navigator.webdriver === true` and we are in a test environment, + // start the SocketBackgroundToMocha. + if (process.env.IN_TEST && msg.webdriverPresent) { + getSocketBackgroundToMocha(); + } } }; - chrome.runtime.onMessage.addListener(messageListener); + chrome.runtime.onMessage.addListener(offscreenDocumentLoadedListener); }); - await chrome.offscreen.createDocument({ - url: './offscreen.html', - reasons: ['IFRAME_SCRIPTING'], - justification: - 'Used for Hardware Wallet and Snaps scripts to communicate with the extension.', - }); + try { + await chrome.offscreen.createDocument({ + url: './offscreen.html', + reasons: ['IFRAME_SCRIPTING'], + justification: + 'Used for Hardware Wallet and Snaps scripts to communicate with the extension.', + }); + } catch (error) { + if (offscreenDocumentLoadedListener) { + chrome.runtime.onMessage.removeListener(offscreenDocumentLoadedListener); + } + if ( + error?.message?.startsWith( + 'Only a single offscreen document may be created', + ) + ) { + console.debug('Offscreen document already exists; skipping creation'); + } else { + // Report unrecongized errors without halting wallet initialization + // Failures to create the offscreen document does not compromise wallet data integrity or + // core functionality, it's just needed for specific features. + captureException(error); + } + return; + } // 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) => { diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js index 2430be23f66a..7440b0cea9cc 100644 --- a/app/scripts/platforms/extension.js +++ b/app/scripts/platforms/extension.js @@ -44,38 +44,15 @@ export default class ExtensionPlatform { browser.windows.remove(windowDetails.id); } + /** + * Returns the version of the extension by reading the manifest. + */ getVersion() { - const { version, version_name: versionName } = - browser.runtime.getManifest(); - - const versionParts = version.split('.'); - if (versionName) { - if (versionParts.length < 4) { - throw new Error(`Version missing build number: '${version}'`); - } - // On Chrome, a more descriptive representation of the version is stored in the - // `version_name` field for display purposes. We use this field instead of the `version` - // field on Chrome for non-main builds (i.e. Flask, Beta) because we want to show the - // version in the SemVer-compliant format "v[major].[minor].[patch]-[build-type].[build-number]", - // yet Chrome does not allow letters in the `version` field. - return versionName; - // A fourth version part is sometimes present for "rollback" Chrome builds - } else if (![3, 4].includes(versionParts.length)) { - throw new Error(`Invalid version: ${version}`); - } else if (versionParts[2].match(/[^\d]/u)) { - // On Firefox, the build type and build version are in the third part of the version. - const [major, minor, patchAndPrerelease] = versionParts; - const matches = patchAndPrerelease.match(/^(\d+)([A-Za-z]+)(\d)+$/u); - if (matches === null) { - throw new Error(`Version contains invalid prerelease: ${version}`); - } - const [, patch, buildType, buildVersion] = matches; - return `${major}.${minor}.${patch}-${buildType}.${buildVersion}`; - } - - // If there is no `version_name` and there are only 3 or 4 version parts, then this is not a - // prerelease and the version requires no modification. - return version; + // return the "live" version of the extension, as the bundle of code running + // might be from a different version of the application than the manifest. + // This isn't supposed to happen, but we've seen it before in Sentry. + // This should *not* be updated to the static `process.env.METAMASK_VERSION` + return browser.runtime.getManifest().version; } getExtensionURL(route = null, queryString = null) { diff --git a/app/scripts/platforms/extension.test.js b/app/scripts/platforms/extension.test.js index 14ec2e66b40b..c26a0e43569f 100644 --- a/app/scripts/platforms/extension.test.js +++ b/app/scripts/platforms/extension.test.js @@ -17,14 +17,23 @@ jest.mock('webextension-polyfill', () => { }); describe('extension platform', () => { + const metamaskVersion = process.env.METAMASK_VERSION; beforeEach(() => { // TODO: Delete this an enable 'resetMocks' in `jest.config.js` instead jest.resetAllMocks(); }); + afterEach(() => { + // reset `METAMASK_VERSION` env var + process.env.METAMASK_VERSION = metamaskVersion; + }); + describe('getVersion', () => { it('should return non-prerelease version', () => { - browser.runtime.getManifest.mockReturnValue({ version: '1.2.3' }); + process.env.METAMASK_VERSION = 'should.not.return.me'; + browser.runtime.getManifest.mockReturnValue({ + version: '1.2.3', + }); const extensionPlatform = new ExtensionPlatform(); const version = extensionPlatform.getVersion(); @@ -33,29 +42,21 @@ describe('extension platform', () => { }); it('should return rollback version', () => { - browser.runtime.getManifest.mockReturnValue({ version: '1.2.3.1' }); - const extensionPlatform = new ExtensionPlatform(); - - const version = extensionPlatform.getVersion(); - - expect(version).toBe('1.2.3.1'); - }); - - it('should return SemVer-formatted version for Chrome style manifest of prerelease', () => { + process.env.METAMASK_VERSION = 'should.not.return.me'; browser.runtime.getManifest.mockReturnValue({ - version: '1.2.3.0', - version_name: '1.2.3-beta.0', + version: '1.2.3.1', }); const extensionPlatform = new ExtensionPlatform(); const version = extensionPlatform.getVersion(); - expect(version).toBe('1.2.3-beta.0'); + expect(version).toBe('1.2.3.1'); }); - it('should return SemVer-formatted version for Firefox style manifest of prerelease', () => { + it('should return SemVer-formatted version manifest of prerelease', () => { + process.env.METAMASK_VERSION = 'should.not.return.me'; browser.runtime.getManifest.mockReturnValue({ - version: '1.2.3beta0', + version: '1.2.3-beta.0', }); const extensionPlatform = new ExtensionPlatform(); @@ -63,40 +64,6 @@ describe('extension platform', () => { expect(version).toBe('1.2.3-beta.0'); }); - - it('should throw error if build version is missing from Chrome style prerelease manifest', () => { - browser.runtime.getManifest.mockReturnValue({ - version: '1.2.3', - version_name: '1.2.3-beta.0', - }); - const extensionPlatform = new ExtensionPlatform(); - - expect(() => extensionPlatform.getVersion()).toThrow( - 'Version missing build number:', - ); - }); - - it('should throw error if build version is missing from Firefox style prerelease manifest', () => { - browser.runtime.getManifest.mockReturnValue({ - version: '1.2.3beta', - }); - const extensionPlatform = new ExtensionPlatform(); - - expect(() => extensionPlatform.getVersion()).toThrow( - 'Version contains invalid prerelease:', - ); - }); - - it('should throw error if patch is missing from Firefox style prerelease manifest', () => { - browser.runtime.getManifest.mockReturnValue({ - version: '1.2.beta0', - }); - const extensionPlatform = new ExtensionPlatform(); - - expect(() => extensionPlatform.getVersion()).toThrow( - 'Version contains invalid prerelease:', - ); - }); }); describe('getExtensionURL', () => { diff --git a/app/scripts/runtime-cjs.ts b/app/scripts/runtime-cjs.ts new file mode 100644 index 000000000000..e0169d25bd2e --- /dev/null +++ b/app/scripts/runtime-cjs.ts @@ -0,0 +1,5 @@ +// currently only used in webpack build. + +import '@lavamoat/lavapack/src/runtime-cjs'; + +export {}; diff --git a/app/scripts/sentry-install.js b/app/scripts/sentry-install.js index cbbc03ab10e2..9a88a77c9f7e 100644 --- a/app/scripts/sentry-install.js +++ b/app/scripts/sentry-install.js @@ -1,7 +1,7 @@ import setupSentry from './lib/setupSentry'; // The root compartment will populate this with hooks -global.stateHooks = {}; +global.stateHooks = global.stateHooks || {}; // setup sentry error reporting global.sentry = setupSentry(); diff --git a/app/scripts/skip-onboarding.js b/app/scripts/skip-onboarding.js index 39c3b0b61865..17e56cdd9e1e 100644 --- a/app/scripts/skip-onboarding.js +++ b/app/scripts/skip-onboarding.js @@ -137,7 +137,6 @@ function generateAccountsControllerState(account) { options: {}, methods: [ 'personal_sign', - 'eth_sign', 'eth_signTransaction', 'eth_signTypedData_v1', 'eth_signTypedData_v3', diff --git a/app/scripts/snaps/preinstalled-snaps.ts b/app/scripts/snaps/preinstalled-snaps.ts index db52c3dc312f..6be0bb7f352d 100644 --- a/app/scripts/snaps/preinstalled-snaps.ts +++ b/app/scripts/snaps/preinstalled-snaps.ts @@ -4,6 +4,7 @@ import MessageSigningSnap from '@metamask/message-signing-snap/dist/preinstalled import BitcoinWalletSnap from '@metamask/bitcoin-wallet-snap/dist/preinstalled-snap.json'; ///: END:ONLY_INCLUDE_IF +// The casts here are less than ideal but we expect the SnapController to validate the inputs. const PREINSTALLED_SNAPS = Object.freeze([ MessageSigningSnap as PreinstalledSnap, ///: BEGIN:ONLY_INCLUDE_IF(build-flask) diff --git a/app/scripts/streams/cookie-handler-stream.ts b/app/scripts/streams/cookie-handler-stream.ts new file mode 100644 index 000000000000..1b218e8d0cec --- /dev/null +++ b/app/scripts/streams/cookie-handler-stream.ts @@ -0,0 +1,193 @@ +import browser from 'webextension-polyfill'; +import { WindowPostMessageStream } from '@metamask/post-message-stream'; +import ObjectMultiplex from '@metamask/object-multiplex'; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-expect-error types/readable-stream.d.ts does not get picked up by ts-node +import { pipeline } from 'readable-stream'; +import { Substream } from '@metamask/object-multiplex/dist/Substream'; +import PortStream from 'extension-port-stream'; +import { EXTENSION_MESSAGES } from '../../../shared/constants/app'; +import { COOKIE_ID_MARKETING_WHITELIST_ORIGINS } from '../constants/marketing-site-whitelist'; +import { checkForLastError } from '../../../shared/modules/browser-runtime.utils'; +import { + METAMASK_COOKIE_HANDLER, + CONTENT_SCRIPT, + LEGACY_PUBLIC_CONFIG, + METAMASK_PROVIDER, + PHISHING_SAFELIST, + LEGACY_PROVIDER, + PHISHING_STREAM, +} from '../constants/stream'; +import { logStreamDisconnectWarning } from './shared'; + +export const isDetectedCookieMarketingSite: boolean = + COOKIE_ID_MARKETING_WHITELIST_ORIGINS.some( + (origin) => origin === window.location.origin, + ); + +let cookieHandlerPageMux: ObjectMultiplex, + cookieHandlerPageChannel: Substream, + cookieHandlerExtPort: browser.Runtime.Port, + cookieHandlerExtStream: PortStream | null, + cookieHandlerMux: ObjectMultiplex, + cookieHandlerExtChannel: Substream; + +function setupCookieHandlerStreamsFromOrigin(origin: string): void { + const cookieHandlerPageStream = new WindowPostMessageStream({ + name: CONTENT_SCRIPT, + target: 'CookieHandlerPage', + targetWindow: window, + targetOrigin: origin, + }); + + // create and connect channel muxers + // so we can handle the channels individually + cookieHandlerPageMux = new ObjectMultiplex(); + cookieHandlerPageMux.setMaxListeners(25); + + pipeline( + cookieHandlerPageMux, + cookieHandlerPageStream, + cookieHandlerPageMux, + (err: Error) => + logStreamDisconnectWarning('MetaMask Inpage Multiplex', err), + ); + + cookieHandlerPageChannel = cookieHandlerPageMux.createStream( + METAMASK_COOKIE_HANDLER, + ); + cookieHandlerPageMux.ignoreStream(LEGACY_PUBLIC_CONFIG); + cookieHandlerPageMux.ignoreStream(LEGACY_PROVIDER); + cookieHandlerPageMux.ignoreStream(METAMASK_PROVIDER); + cookieHandlerPageMux.ignoreStream(PHISHING_SAFELIST); + cookieHandlerPageMux.ignoreStream(PHISHING_STREAM); +} + +/** + * establishes a communication stream between the content script and background.js + */ +export const setupCookieHandlerExtStreams = (): void => { + cookieHandlerExtPort = browser.runtime.connect({ + name: CONTENT_SCRIPT, + }); + cookieHandlerExtStream = new PortStream(cookieHandlerExtPort); + + // create and connect channel muxers + // so we can handle the channels individually + cookieHandlerMux = new ObjectMultiplex(); + cookieHandlerMux.setMaxListeners(25); + + pipeline( + cookieHandlerMux, + cookieHandlerExtStream, + cookieHandlerMux, + (err: Error) => { + logStreamDisconnectWarning('MetaMask Background Multiplex', err); + window.postMessage( + { + target: 'CookieHandlerPage', + data: { + // this object gets passed to @metamask/object-multiplex + name: METAMASK_COOKIE_HANDLER, // the @metamask/object-multiplex channel name + data: { + jsonrpc: '2.0', + method: 'METAMASK_STREAM_FAILURE', + }, + }, + }, + window.location.origin, + ); + }, + ); + + // forward communication across inpage-background for these channels only + cookieHandlerExtChannel = cookieHandlerMux.createStream( + METAMASK_COOKIE_HANDLER, + ); + cookieHandlerMux.ignoreStream(LEGACY_PUBLIC_CONFIG); + cookieHandlerMux.ignoreStream(LEGACY_PROVIDER); + cookieHandlerMux.ignoreStream(METAMASK_PROVIDER); + cookieHandlerMux.ignoreStream(PHISHING_SAFELIST); + cookieHandlerMux.ignoreStream(PHISHING_STREAM); + pipeline( + cookieHandlerPageChannel, + cookieHandlerExtChannel, + cookieHandlerPageChannel, + (error: Error) => + console.debug( + `MetaMask: Muxed traffic for channel "${METAMASK_COOKIE_HANDLER}" failed.`, + error, + ), + ); + + cookieHandlerExtPort.onDisconnect.addListener( + // eslint-disable-next-line @typescript-eslint/no-use-before-define + onDisconnectDestroyCookieStreams, + ); +}; + +/** Destroys all of the cookie handler extension streams */ +const destroyCookieExtStreams = () => { + cookieHandlerPageChannel.removeAllListeners(); + + cookieHandlerMux.removeAllListeners(); + cookieHandlerMux.destroy(); + + cookieHandlerExtChannel.removeAllListeners(); + cookieHandlerExtChannel.destroy(); + + cookieHandlerExtStream = null; +}; + +/** + * This listener destroys the phishing extension streams when the extension port is disconnected, + * so that streams may be re-established later the phishing extension port is reconnected. + */ +const onDisconnectDestroyCookieStreams = () => { + const err = checkForLastError(); + + cookieHandlerExtPort.onDisconnect.removeListener( + onDisconnectDestroyCookieStreams, + ); + + destroyCookieExtStreams(); + + /** + * If an error is found, reset the streams. When running two or more dapps, resetting the service + * worker may cause the error, "Error: Could not establish connection. Receiving end does not + * exist.", due to a race-condition. The disconnect event may be called by runtime.connect which + * may cause issues. We suspect that this is a chromium bug as this event should only be called + * once the port and connections are ready. Delay time is arbitrary. + */ + if (err) { + console.warn(`${err} Resetting the phishing streams.`); + setTimeout(setupCookieHandlerExtStreams, 1000); + } +}; + +const onMessageSetUpCookieHandlerStreams = (msg: { + name: string; + origin: string; +}): Promise | undefined => { + if (msg.name === EXTENSION_MESSAGES.READY) { + if (!cookieHandlerExtStream) { + setupCookieHandlerExtStreams(); + } + return Promise.resolve( + `MetaMask: handled "${EXTENSION_MESSAGES.READY}" for phishing streams`, + ); + } + return undefined; +}; + +/** + * Initializes two-way communication streams between the browser extension and + * the cookie id submission page context. This function also creates an event listener to + * reset the streams if the service worker resets. + */ +export const initializeCookieHandlerSteam = (): void => { + const { origin } = window.location; + setupCookieHandlerStreamsFromOrigin(origin); + setupCookieHandlerExtStreams(); + browser.runtime.onMessage.addListener(onMessageSetUpCookieHandlerStreams); +}; diff --git a/app/scripts/streams/shared.ts b/app/scripts/streams/shared.ts new file mode 100644 index 000000000000..73dc6ec1d6e5 --- /dev/null +++ b/app/scripts/streams/shared.ts @@ -0,0 +1,15 @@ +/** + * Error handler for page to extension stream disconnections + * + * @param remoteLabel - Remote stream name + * @param error - Stream connection error + */ +export function logStreamDisconnectWarning( + remoteLabel: string, + error: Error, +): void { + console.debug( + `MetaMask: Content script lost connection to "${remoteLabel}".`, + error, + ); +} diff --git a/app/scripts/ui.js b/app/scripts/ui.js index 4b5deae61a6c..f85a1855f2f1 100644 --- a/app/scripts/ui.js +++ b/app/scripts/ui.js @@ -3,6 +3,7 @@ // This import sets up global functions required for Sentry to function. // It must be run first in case an error is thrown later during initialization. import './lib/setup-initial-state-hooks'; +import '../../development/wdyr'; // dev only, "react-devtools" import is skipped in prod builds import 'react-devtools'; diff --git a/app/trezor-usb-permissions.html b/app/trezor-usb-permissions.html index 8c92552cfa02..ef87f9a1b8aa 100644 --- a/app/trezor-usb-permissions.html +++ b/app/trezor-usb-permissions.html @@ -2,13 +2,13 @@ - <% if (it.shouldIncludeSnow) { %> - - - <% } %> TrezorConnect | Trezor + <% if (it.shouldIncludeSnow) { %> + + + <% } %> diff --git a/attribution.txt b/attribution.txt index 820ffd0021e7..4932c2f809c5 100644 --- a/attribution.txt +++ b/attribution.txt @@ -50,34 +50,6 @@ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -****************************** - -aes-js -3.0.0 -The MIT License (MIT) - -Copyright (c) 2015 Richard Moore - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - - - ****************************** aes-js @@ -1587,7 +1559,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************** @babel/runtime -7.24.6 +7.24.5 MIT License Copyright (c) 2014-present Sebastian McKenzie and other contributors @@ -2398,7 +2370,7 @@ SOFTWARE. ****************************** @blockaid/ppom_release -1.5.1 <> +1.4.6 <> Blockaid BSL License, Version 1.0 (EPL-1.0) Licensor: Blockaid, Inc. @@ -10208,33 +10180,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -****************************** - -@ethersproject/json-wallets -5.7.0 -MIT License - -Copyright (c) 2019 Richard Moore - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ****************************** @ethersproject/keccak256 @@ -10539,33 +10484,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -****************************** - -@ethersproject/wallet -5.7.0 -MIT License - -Copyright (c) 2019 Richard Moore - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ****************************** @ethersproject/web @@ -11419,7 +11337,7 @@ SOFTWARE. ****************************** fast-xml-parser -4.4.1 +4.3.4 MIT License Copyright (c) 2017 Amit Kumar Gupta @@ -12703,7 +12621,7 @@ SOFTWARE. ****************************** @grpc/grpc-js -1.9.15 +1.9.14 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -17943,7 +17861,7 @@ authors: Maarten Zuidhoorn ****************************** @metamask/accounts-controller -15.0.0 +14.0.0 MIT License Copyright (c) 2018 MetaMask @@ -18073,7 +17991,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE ****************************** @metamask/assets-controllers -30.0.0 +29.0.0 MIT License Copyright (c) 2018 MetaMask @@ -18242,32 +18160,6 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -****************************** - -@metamask/controller-utils -10.0.0 -MIT License - -Copyright (c) 2018 MetaMask - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - - ****************************** @metamask/controller-utils @@ -19030,7 +18922,7 @@ SOFTWARE. ****************************** @metamask-institutional/custody-controller -0.2.30 +0.2.27 MIT License Copyright (c) 2023 ConsenSys Vertical Apps @@ -19057,7 +18949,7 @@ SOFTWARE. ****************************** @metamask-institutional/custody-keyring -2.0.3 +2.0.0 MIT License Copyright (c) 2023 ConsenSys Vertical Apps @@ -19084,7 +18976,7 @@ SOFTWARE. ****************************** @metamask-institutional/extension -0.3.27 +0.3.24 MIT License Copyright (c) 2023 ConsenSys Vertical Apps @@ -19111,7 +19003,7 @@ SOFTWARE. ****************************** @metamask-institutional/institutional-features -1.3.5 +1.3.2 MIT License Copyright (c) 2023 ConsenSys Vertical Apps @@ -19192,7 +19084,7 @@ SOFTWARE. ****************************** @metamask-institutional/sdk -0.1.30 +0.1.27 MIT License Copyright (c) 2023 ConsenSys Vertical Apps @@ -19246,7 +19138,7 @@ SOFTWARE. ****************************** @metamask-institutional/transaction-update -0.2.5 +0.2.2 MIT License Copyright (c) 2023 ConsenSys Vertical Apps @@ -19300,7 +19192,7 @@ SOFTWARE. ****************************** @metamask-institutional/websocket-client -0.2.5 +0.2.2 MIT License Copyright (c) 2023 ConsenSys Vertical Apps @@ -19387,27 +19279,6 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -****************************** - -@metamask/json-rpc-engine -9.0.0 -ISC License - -Copyright (c) 2022 MetaMask - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - ****************************** @metamask/json-rpc-middleware-stream @@ -19429,27 +19300,6 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -****************************** - -@metamask/json-rpc-middleware-stream -8.0.0 -ISC License - -Copyright (c) 2020 MetaMask - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - ****************************** @metamask/keyring-api @@ -19460,7 +19310,7 @@ authors: undefined ****************************** @metamask/keyring-api -6.4.0 +6.3.1 license: Custom: https://docs.metamask.io/snaps/ authors: undefined @@ -19627,7 +19477,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ****************************** @metamask/name-controller -8.0.0 +6.0.1 MIT License Copyright (c) 2023 MetaMask @@ -19822,7 +19672,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ****************************** @metamask/obs-store -9.0.0 +8.1.0 ISC License Copyright (c) 2020 MetaMask @@ -19842,28 +19692,23 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ****************************** -@metamask/permission-controller -10.0.0 -MIT License - -Copyright (c) 2018 MetaMask +@metamask/obs-store +9.0.0 +ISC License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) 2020 MetaMask -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ****************************** @@ -19919,33 +19764,7 @@ If you have any questions, comments or interest in pursuing any other use cases, ****************************** @metamask/phishing-controller -10.0.0 -MIT License - -Copyright (c) 2018 MetaMask - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - - -****************************** - -@metamask/phishing-controller -9.0.4 +9.0.2 MIT License Copyright (c) 2018 MetaMask @@ -20104,7 +19923,7 @@ SOFTWARE. ****************************** @metamask/providers -17.0.0 +16.1.0 MIT License Copyright (c) 2020 MetaMask @@ -20131,7 +19950,7 @@ SOFTWARE. ****************************** @metamask/queued-request-controller -2.0.0 +0.10.0 MIT License Copyright (c) 2023 MetaMask @@ -20338,7 +20157,7 @@ authors: Dan Finlay ****************************** @metamask/smart-transactions-controller -10.1.2 +10.0.1 Copyright ConsenSys Software Inc. 2020. All rights reserved. You acknowledge and agree that ConsenSys Software Inc. (“ConsenSys”) (or ConsenSys’s licensors) own all legal right, title and interest in and to the work, software, application, source code, documentation and any other documents in this repository (collectively, the “Program”), including any intellectual property rights which subsist in the Program (whether those rights happen to be registered or not, and wherever in the world those rights may exist), whether in source code or any other form. @@ -20362,31 +20181,7 @@ If you have any questions, comments or interest in pursuing any other use cases, ****************************** @metamask/snaps-controllers -8.4.0 -Copyright ConsenSys Software Inc. 2021. All rights reserved. - -You acknowledge and agree that ConsenSys Software Inc. (“ConsenSys”) (or ConsenSys’s licensors) own all legal right, title and interest in and to the work, software, application, source code, documentation and any other documents in this repository (collectively, the “Program”), including any intellectual property rights which subsist in the Program (whether those rights happen to be registered or not, and wherever in the world those rights may exist), whether in source code or any other form. - -Subject to the limited license below, you may not (and you may not permit anyone else to) distribute, publish, copy, modify, merge, combine with another program, create derivative works of, reverse engineer, decompile or otherwise attempt to extract the source code of, the Program or any part thereof, except that you may contribute to this repository. - -You are granted a non-exclusive, non-transferable, non-sublicensable license to distribute, publish, copy, modify, merge, combine with another program or create derivative works of the Program (such resulting program, collectively, the “Resulting Program”) solely for Non-Commercial Use as long as you: - 1. give prominent notice (“Notice”) with each copy of the Resulting Program that the Program is used in the Resulting Program and that the Program is the copyright of ConsenSys; and - 2. subject the Resulting Program and any distribution, publication, copy, modification, merger therewith, combination with another program or derivative works thereof to the same Notice requirement and Non-Commercial Use restriction set forth herein. - -“Non-Commercial Use” means each use as described in clauses (1)-(3) below, as reasonably determined by ConsenSys in its sole discretion: - 1. personal use for research, personal study, private entertainment, hobby projects or amateur pursuits, in each case without any anticipated commercial application; - 2. use by any charitable organization, educational institution, public research organization, public safety or health organization, environmental protection organization or government institution; or - 3. the number of monthly active users of the Resulting Program across all versions thereof and platforms globally do not exceed 10,000 at any time. - -You will not use any trade mark, service mark, trade name, logo of ConsenSys or any other company or organization in a way that is likely or intended to cause confusion about the owner or authorized user of such marks, names or logos. - -If you have any questions, comments or interest in pursuing any other use cases, please reach out to us at metamask.license@consensys.net. - - -****************************** - -@metamask/snaps-controllers -9.0.0 +8.2.0 Copyright ConsenSys Software Inc. 2021. All rights reserved. You acknowledge and agree that ConsenSys Software Inc. (“ConsenSys”) (or ConsenSys’s licensors) own all legal right, title and interest in and to the work, software, application, source code, documentation and any other documents in this repository (collectively, the “Program”), including any intellectual property rights which subsist in the Program (whether those rights happen to be registered or not, and wherever in the world those rights may exist), whether in source code or any other form. @@ -20410,7 +20205,7 @@ If you have any questions, comments or interest in pursuing any other use cases, ****************************** @metamask/snaps-execution-environments -6.4.0 +6.2.0 Copyright ConsenSys Software Inc. 2022. All rights reserved. You acknowledge and agree that ConsenSys Software Inc. (“ConsenSys”) (or ConsenSys’s licensors) own all legal right, title and interest in and to the work, software, application, source code, documentation and any other documents in this repository (collectively, the “Program”), including any intellectual property rights which subsist in the Program (whether those rights happen to be registered or not, and wherever in the world those rights may exist), whether in source code or any other form. @@ -20643,7 +20438,7 @@ If you have any questions, comments or interest in pursuing any other use cases, ****************************** @metamask/snaps-rpc-methods -9.1.3 +9.1.0 Copyright ConsenSys Software Inc. 2021. All rights reserved. You acknowledge and agree that ConsenSys Software Inc. (“ConsenSys”) (or ConsenSys’s licensors) own all legal right, title and interest in and to the work, software, application, source code, documentation and any other documents in this repository (collectively, the “Program”), including any intellectual property rights which subsist in the Program (whether those rights happen to be registered or not, and wherever in the world those rights may exist), whether in source code or any other form. @@ -20667,7 +20462,7 @@ If you have any questions, comments or interest in pursuing any other use cases, ****************************** @metamask/snaps-sdk -5.0.0 +4.3.0 ISC License Copyright (c) 2023 MetaMask @@ -20688,7 +20483,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ****************************** @metamask/snaps-utils -7.6.0 +7.4.0 ISC License Copyright (c) 2022 MetaMask @@ -23276,7 +23071,7 @@ authors: Kazuhiko Arase ****************************** qrcode.react -3.1.0 +1.0.1 ISC License Copyright (c) 2015, Paul O’Shannessy @@ -23293,8 +23088,18 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -This product bundles QR Code Generator, which is available under a -"MIT" license. For details, see src/third-party/qrcodegen. + +****************************** + +qr.js +0.0.0 +Copyright (c) 2013 Roman Shtylman + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************** @@ -23748,7 +23553,7 @@ SOFTWARE. ****************************** react-redux -7.2.9 +7.2.0 The MIT License (MIT) Copyright (c) 2015-present Dan Abramov @@ -25597,7 +25402,7 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice ****************************** rpc-websockets -8.0.1 +7.11.1 Copyright (c) Elpheria j.d.o.o. rpc-websockets is an Open Source project licensed under the terms of @@ -28844,33 +28649,6 @@ authors: Mohamed Hegazy SOFTWARE -****************************** - -@types/hoist-non-react-statics -3.3.1 - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE - - ****************************** @types/jsonwebtoken @@ -29141,33 +28919,6 @@ authors: Mohamed Hegazy SOFTWARE -****************************** - -@types/react-redux -7.1.33 - MIT License - - Copyright (c) Microsoft Corporation. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE - - ****************************** @types/react-transition-group @@ -31335,7 +31086,34 @@ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH RE ****************************** ws -7.5.10 +7.4.6 +The MIT License (MIT) + +Copyright (c) 2011 Einar Otto Stangvik + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +****************************** + +ws +7.5.9 The MIT License (MIT) Copyright (c) 2011 Einar Otto Stangvik @@ -31362,7 +31140,7 @@ SOFTWARE. ****************************** ws -8.17.1 +8.16.0 Copyright (c) 2011 Einar Otto Stangvik Copyright (c) 2013 Arnout Kazemier and contributors Copyright (c) 2016 Luigi Pinca and contributors diff --git a/builds.yml b/builds.yml index ec4370dd5b03..ff12b626a44f 100644 --- a/builds.yml +++ b/builds.yml @@ -14,6 +14,7 @@ default: &default main # Note: These build types should be kept in sync with the list in `.github/workflows/update-lavamoat-policies.yml` buildTypes: main: + id: 10 features: - build-main - keyring-snaps @@ -25,7 +26,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.5.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.6.2/index.html - ACCOUNT_SNAPS_DIRECTORY_URL: https://snaps.metamask.io/account-management # Main build uses the default browser manifest manifestOverrides: false @@ -34,6 +35,7 @@ buildTypes: buildNameOverride: MetaMask beta: + id: 11 features: - build-beta - keyring-snaps @@ -44,7 +46,7 @@ buildTypes: - SEGMENT_WRITE_KEY_REF: SEGMENT_BETA_WRITE_KEY - ALLOW_LOCAL_SNAPS: false - REQUIRE_SNAPS_ALLOWLIST: true - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.5.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.6.2/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 @@ -54,6 +56,7 @@ buildTypes: buildNameOverride: MetaMask Beta flask: + id: 15 # Code surrounded using code fences for that feature # will not be removed features: @@ -64,7 +67,7 @@ buildTypes: - SEGMENT_FLASK_WRITE_KEY - ALLOW_LOCAL_SNAPS: true - REQUIRE_SNAPS_ALLOWLIST: false - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.5.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.6.2/index.html - SUPPORT_LINK: https://support.metamask.io/ - SUPPORT_REQUEST_LINK: https://support.metamask.io/ - INFURA_ENV_KEY_REF: INFURA_FLASK_PROJECT_ID @@ -76,6 +79,7 @@ buildTypes: buildNameOverride: MetaMask Flask mmi: + id: 20 features: - build-mmi env: @@ -86,7 +90,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.5.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.6.2/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 @@ -147,6 +151,7 @@ env: - SUPPORT_LINK: https://support.metamask.io - SUPPORT_REQUEST_LINK: https://support.metamask.io - SKIP_BACKGROUND_INITIALIZATION: false + - PPOM_URI: ./ppom_bg.wasm # 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 @@ -283,3 +288,8 @@ env: ### - EIP_4337_ENTRYPOINT: null + ### + # Enable/disable why did you render debug tool: https://github.com/welldone-software/why-did-you-render + # This should NEVER be enabled in production since it slows down react + ### + - ENABLE_WHY_DID_YOU_RENDER: false diff --git a/development/build/index.js b/development/build/index.js index f9a778ed8966..9fcf43a19142 100755 --- a/development/build/index.js +++ b/development/build/index.js @@ -119,6 +119,7 @@ async function defineAndRunBuildTasks() { 'OffscreenCanvas', // Used by browser to generate notifications // globals chromedriver needs to function /cdc_[a-zA-Z0-9]+_[a-zA-Z]+/iu, + 'name', 'performance', 'parseFloat', 'innerWidth', diff --git a/development/build/manifest.js b/development/build/manifest.js index d0042af75c67..bc5325b372eb 100644 --- a/development/build/manifest.js +++ b/development/build/manifest.js @@ -2,14 +2,12 @@ const { promises: fs } = require('fs'); const path = require('path'); const childProcess = require('child_process'); const { mergeWith, cloneDeep } = require('lodash'); +const { isManifestV3 } = require('../../shared/modules/mv3.utils'); -const IS_MV3_ENABLED = - process.env.ENABLE_MV3 === 'true' || process.env.ENABLE_MV3 === undefined; - -const baseManifest = IS_MV3_ENABLED +const baseManifest = isManifestV3 ? require('../../app/manifest/v3/_base.json') : require('../../app/manifest/v2/_base.json'); -const baradDurManifest = IS_MV3_ENABLED +const baradDurManifest = isManifestV3 ? require('../../app/manifest/v3/_barad_dur.json') : require('../../app/manifest/v2/_barad_dur.json'); const { loadBuildTypesConfig } = require('../lib/build-type'); @@ -38,7 +36,7 @@ function createManifestTasks({ '..', '..', 'app', - IS_MV3_ENABLED ? 'manifest/v3' : 'manifest/v2', + isManifestV3 ? 'manifest/v3' : 'manifest/v2', `${platform}.json`, ), ); @@ -71,6 +69,7 @@ function createManifestTasks({ ...manifest.permissions, 'webRequestBlocking', 'http://localhost/*', + 'tabs', // test builds need tabs permission for switchToWindowWithTitle ]; }); @@ -80,6 +79,7 @@ function createManifestTasks({ ...manifest.permissions, 'webRequestBlocking', 'http://localhost/*', + 'tabs', // test builds need tabs permission for switchToWindowWithTitle ]; }); @@ -143,7 +143,7 @@ function createManifestTasks({ buildType, applyLavaMoat, shouldIncludeSnow, - shouldIncludeMV3: IS_MV3_ENABLED, + isManifestV3, }); manifest.description = `${environment} build from git id: ${gitRevisionStr}`; diff --git a/development/build/scripts.js b/development/build/scripts.js index a5f6b7084a13..7cb89b663dde 100644 --- a/development/build/scripts.js +++ b/development/build/scripts.js @@ -29,6 +29,7 @@ const terser = require('terser'); const bifyModuleGroups = require('bify-module-groups'); const { streamFlatMap } = require('../stream-flat-map'); +const { isManifestV3 } = require('../../shared/modules/mv3.utils'); const { setEnvironmentVariables } = require('./set-environment-variables'); const { BUILD_TARGETS } = require('./constants'); const { getConfig } = require('./config'); @@ -52,9 +53,6 @@ const { createRemoveFencedCodeTransform, } = require('./transforms/remove-fenced-code'); -const isEnableMV3 = - process.env.ENABLE_MV3 === 'true' || process.env.ENABLE_MV3 === undefined; - // map dist files to bag of needed native APIs against LM scuttling const scuttlingConfigBase = { 'scripts/sentry-install.js': { @@ -91,18 +89,15 @@ const scuttlingConfigBase = { extra: '', stateHooks: '', nw: '', + // Sentry Custom Tracing + document: '', + isNaN: '', + parseInt: '', }, }; const mv3ScuttlingConfig = { ...scuttlingConfigBase }; - -const standardScuttlingConfig = { - ...scuttlingConfigBase, - 'scripts/sentry-install.js': { - ...scuttlingConfigBase['scripts/sentry-install.js'], - document: '', - }, -}; +const standardScuttlingConfig = { ...scuttlingConfigBase }; const noopWriteStream = through.obj((_file, _fileEncoding, callback) => callback(), @@ -194,7 +189,7 @@ function createScriptTasks({ // In MV3 we will need to build our offscreen entry point bundle and any // entry points for iframes that we want to lockdown with LavaMoat. - if (isEnableMV3) { + if (isManifestV3) { standardEntryPoints.push('offscreen'); } @@ -355,7 +350,7 @@ function createScriptTasks({ () => { // MV3 injects inpage into the tab's main world, but in MV2 we need // to do it manually: - if (isEnableMV3) { + if (isManifestV3) { return; } // stringify scripts/inpage.js into itself, and then make it inject itself into the page @@ -721,7 +716,7 @@ function createFactoredBuild({ applyLavaMoat, scripts, }); - if (isEnableMV3) { + if (isManifestV3) { const jsBundles = [ ...commonSet.values(), ...groupSet.values(), @@ -935,7 +930,11 @@ function setupBundlerDefaults( [ babelify, { - only: ['./**/node_modules/firebase', './**/node_modules/@firebase'], + only: [ + './**/node_modules/firebase', + './**/node_modules/@firebase', + './**/node_modules/marked', + ], global: true, }, ], @@ -980,7 +979,7 @@ function setupBundlerDefaults( // Setup source maps setupSourcemaps(buildConfiguration, { buildTarget }); // Setup wrapping of code against scuttling (before sourcemaps generation) - setupScuttlingWrapping(buildConfiguration, applyLavaMoat, envVars); + setupScuttlingWrapping(buildConfiguration, applyLavaMoat); } } @@ -1034,13 +1033,10 @@ function setupMinification(buildConfiguration) { }); } -function setupScuttlingWrapping(buildConfiguration, applyLavaMoat, envVars) { - const scuttlingConfig = - envVars.ENABLE_MV3 === 'true' || - envVars.ENABLE_MV3 === undefined || - envVars.ENABLE_MV3 === true - ? mv3ScuttlingConfig - : standardScuttlingConfig; +function setupScuttlingWrapping(buildConfiguration, applyLavaMoat) { + const scuttlingConfig = isManifestV3 + ? mv3ScuttlingConfig + : standardScuttlingConfig; const { events } = buildConfiguration; events.on('configurePipeline', ({ pipeline }) => { pipeline.get('scuttle').push( @@ -1217,19 +1213,19 @@ function renderHtmlFile({ const eta = new Eta(); const htmlOutput = eta - .renderString(htmlTemplate, { - isMMI, - isTest, - shouldIncludeSnow, - }) + .renderString(htmlTemplate, { isMMI, isTest, shouldIncludeSnow }) // these replacements are added to support the webpack build's automatic // compilation of html files, which the gulp-based process doesn't support. - .replace('', scriptTags) + .replace('./scripts/load/background.ts', './load-background.js') .replace( '', `${scriptTags}\n `, ) - .replace('', scriptTags); + .replace('', scriptTags) + .replace('', scriptTags) + .replace('../ui/css/index.scss', './index.css') + .replace('@lavamoat/snow/snow.prod.js', './scripts/snow.js'); + browserPlatforms.forEach((platform) => { const dest = `./dist/${platform}/${htmlName}.html`; // we dont have a way of creating async events atm diff --git a/development/build/static.js b/development/build/static.js index ff14657b621b..2c0854d4b3c8 100644 --- a/development/build/static.js +++ b/development/build/static.js @@ -5,6 +5,7 @@ const glob = require('fast-glob'); const { loadBuildTypesConfig } = require('../lib/build-type'); +const { isManifestV3 } = require('../../shared/modules/mv3.utils'); const { TASKS } = require('./constants'); const { createTask, composeSeries } = require('./task'); const { getPathInsideNodeModules } = require('./utils'); @@ -145,9 +146,7 @@ function getCopyTargets(shouldIncludeLockdown, shouldIncludeSnow) { ...(shouldIncludeSnow ? [ { - src: shouldIncludeSnow - ? `./node_modules/@lavamoat/snow/snow.prod.js` - : EMPTY_JS_FILE, + src: `./node_modules/@lavamoat/snow/snow.prod.js`, dest: `scripts/snow.js`, }, { @@ -191,14 +190,9 @@ function getCopyTargets(shouldIncludeLockdown, shouldIncludeSnow) { { src: getPathInsideNodeModules('@blockaid/ppom_release', '/'), pattern: '*.wasm', - dest: - process.env.ENABLE_MV3 === 'true' || - process.env.ENABLE_MV3 === undefined - ? 'scripts/' - : '', - }, - ...(process.env.ENABLE_MV3 === 'true' || - process.env.ENABLE_MV3 === undefined + dest: isManifestV3 ? 'scripts/' : '', + }, + ...(isManifestV3 ? [ { src: getPathInsideNodeModules( diff --git a/development/build/transforms/remove-fenced-code.test.js b/development/build/transforms/remove-fenced-code.test.js index 345fc55a0926..de6960ba1ec9 100644 --- a/development/build/transforms/remove-fenced-code.test.js +++ b/development/build/transforms/remove-fenced-code.test.js @@ -1,3 +1,6 @@ +/** + * @jest-environment node + */ const buildUtils = require('@metamask/build-utils'); const { createRemoveFencedCodeTransform } = require('./remove-fenced-code'); const transformUtils = require('./utils'); @@ -36,6 +39,10 @@ describe('build/transforms/remove-fenced-code', () => { lintTransformedFileMock.mockImplementation(() => Promise.resolve()); }); + afterEach(() => { + jest.resetAllMocks(); + }); + it('returns a PassThrough stream for files with ignored extensions', async () => { const fileContent = '"Valid JSON content"\n'; const stream = createRemoveFencedCodeTransform( diff --git a/development/build/transforms/utils.test.js b/development/build/transforms/utils.test.js index d5cd57bfd461..952d0f46f181 100644 --- a/development/build/transforms/utils.test.js +++ b/development/build/transforms/utils.test.js @@ -1,3 +1,6 @@ +/** + * @jest-environment node + */ const { getESLintInstance } = require('./utils'); let mockESLint; diff --git a/development/build/utils.js b/development/build/utils.js index 746290fb8e2b..525815d2520a 100644 --- a/development/build/utils.js +++ b/development/build/utils.js @@ -70,10 +70,38 @@ function getBrowserVersionMap(platforms, version) { const versionParts = [major, minor, patch]; const browserSpecificVersion = {}; if (prerelease) { - if (platform === 'firefox') { - versionParts[2] = `${versionParts[2]}${buildType}${buildVersion}`; - } else { - versionParts.push(buildVersion); + const { id } = loadBuildTypesConfig().buildTypes[buildType]; + if (id < 10 || id > 64 || buildVersion < 0 || buildVersion > 999) { + throw new Error( + `Build id must be 10-64 and release version must be 0-999 +(inclusive). Received an id of '${id}' and a release version of +'${buildVersion}'. + +Wait, but that seems so arbitrary? +================================== + +We encode the build id and the release version into the extension version by +concatenating the two numbers together. The maximum value for the concatenated +number is 65535 (a Chromium limitation). The value cannot start with a '0'. We +utilize 2 digits for the build id and 3 for the release version. This affords us +55 release types and 1000 releases per 'version' + build type (for a minimum +value of 10000 and a maximum value of 64999). + +Okay, so how do I fix it? +========================= + +You'll need to adjust the build 'id' (in builds.yml) or the release version to +fit within these limits or bump the version number in package.json and start the +release version number over from 0. If you can't do that you'll need to come up +with a new way of encoding this information, or re-evaluate the need for this +metadata. + +Good luck on your endeavors.`, + ); + } + versionParts.push(`${id}${buildVersion}`); + if (platform !== 'firefox') { + // firefox doesn't support `version_name` browserSpecificVersion.version_name = version; } } @@ -227,7 +255,7 @@ function getPathInsideNodeModules(packageName, pathToFiles) { * @param {string} options.buildType - The build type of the current build. * @param {boolean} options.applyLavaMoat - Flag if lavamoat was applied. * @param {boolean} options.shouldIncludeSnow - Flag if snow should be included in the build name. - * @param {boolean} options.shouldIncludeMV3 - Flag if mv3 should be included in the build name. + * @param {boolean} options.isManifestV3 - Flag if mv3 should be included in the build name. * @param options.environment * @returns {string} The build name. */ @@ -236,7 +264,7 @@ function getBuildName({ buildType, applyLavaMoat, shouldIncludeSnow, - shouldIncludeMV3, + isManifestV3, }) { const config = loadBuildTypesConfig(); @@ -245,7 +273,7 @@ function getBuildName({ `MetaMask ${capitalize(buildType)}`; if (environment !== ENVIRONMENT.PRODUCTION) { - const mv3Str = shouldIncludeMV3 ? ' MV3' : ''; + const mv3Str = isManifestV3 ? ' MV3' : ''; const lavamoatStr = applyLavaMoat ? ' lavamoat' : ''; const snowStr = shouldIncludeSnow ? ' snow' : ''; name += `${mv3Str}${lavamoatStr}${snowStr}`; diff --git a/development/fitness-functions/common/constants.ts b/development/fitness-functions/common/constants.ts index 0514c6ed9002..5758d4e2a6e1 100644 --- a/development/fitness-functions/common/constants.ts +++ b/development/fitness-functions/common/constants.ts @@ -1,7 +1,7 @@ // include JS, TS, JSX, TSX files only excluding files in the e2e tests and // fitness functions directories const EXCLUDE_E2E_TESTS_REGEX = - '^(?!test/e2e)(?!development/fitness).*.(js|ts|jsx|tsx)$'; + '^(?!test/e2e)(?!development/fitness|development/webpack).*.(js|ts|jsx|tsx)$'; // include JS and JSX files in the shared directory only const SHARED_FOLDER_JS_REGEX = '^(shared).*.(js|jsx)$'; diff --git a/development/generate-attributions/package.json b/development/generate-attributions/package.json index 4102bbb8b887..f6da9feb6d82 100644 --- a/development/generate-attributions/package.json +++ b/development/generate-attributions/package.json @@ -9,7 +9,7 @@ }, "engines": { "node": ">= 20", - "yarn": "^4.0.2" + "yarn": "^4.2.2" }, "lavamoat": { "allowScripts": { diff --git a/development/generate-rc-commits.js b/development/generate-rc-commits.js index a09103c35db9..8b91ec0ff4f1 100644 --- a/development/generate-rc-commits.js +++ b/development/generate-rc-commits.js @@ -10,7 +10,7 @@ const octokit = new Octokit({ /** * This script is used to filter and group commits by teams based on unique commit messages. * It takes two branches as input and generates a CSV file with the commit message, author,PR link, team,release tag and commit hash - * The teams and their members are defined in the 'authorTeams' object. + * The teams and their members are defined in the 'teams.json' file. * * Command to run the script: node development/generate-rc-commits.js origin/branchA origin/branchB * @@ -19,96 +19,24 @@ const octokit = new Octokit({ * Output: the generated commits will be in a file named 'commits.csv'. */ -// JSON mapping authors to teams -const authorTeams = { - Accounts: [ - 'Owen Craston', - 'Gustavo Antunes', - 'Monte Lai', - 'Daniel Rocha', - 'Howard Braham', - 'Kate Johnson', - 'Xiaoming Wang', - 'Charly Chevalier', - 'Mike B', - ], - 'Wallet UX': ['David Walsh', 'Nidhi Kumari', 'Jony Bursztyn'], - 'Extension Platform': [ - 'chloeYue', - 'Chloe Gao', - 'danjm', - 'Danica Shen', - 'Brad Decker', - 'hjetpoluru', - 'Harika Jetpoluru', - 'Marina Boboc', - 'Gauthier Petetin', - 'Dan Miller', - 'Dan J Miller', - 'David Murdoch', - 'Niranjana Binoy', - 'Victor Thomas', - 'vthomas13', - 'seaona', - 'Norbert Elter', - ], - 'Wallet API': ['tmashuang', 'jiexi', 'BelfordZ', 'Shane'], - Confirmations: [ - 'Pedro Figueiredo', - 'Sylva Elendu', - 'Olusegun Akintayo', - 'Jyoti Puri', - 'Ariella Vu', - 'OGPoyraz', - 'vinistevam', - 'Matthew Walsh', - 'cryptotavares', - 'Vinicius Stevam', - 'Derek Brans', - 'sleepytanya', - 'Priya', - ], - 'Design Systems': [ - 'georgewrmarshall', - 'Garrett Bear', - 'George Marshall', - 'Devin', - ], - Snaps: [ - 'David Drazic', - 'hmalik88', - 'Montoya', - 'Mrtenz', - 'Frederik Bolding', - 'Bowen Sanders', - 'Guillaume Roux', - 'Hassan Malik', - 'Maarten Zuidhoorn', - 'Jonathan Ferreira', - ], - Assets: ['salimtb', 'sahar-fehri', 'Brian Bergeron'], - Linea: ['VGau', 'Victorien Gauch'], - lavamoat: ['weizman', 'legobeat', 'kumavis', 'LeoTM'], - 'Wallet Framework': [ - 'Michele Esposito', - 'Elliot Winkler', - 'Gudahtt', - 'Jongsun Suh', - 'Mark Stacey', - ], - MMI: [ - 'António Regadas', - 'Albert Olivé', - 'Ramon AC', - 'Shane T', - 'Bernardo Garces Chapero', - ], - Swaps: ['Daniel', 'Davide Brocchetto', 'Nicolas Ferro', 'infiniteflower'], - Devex: ['Thomas Huang', 'Alex Donesky', 'jiexi', 'Zachary Belford'], - Notifications: ['Prithpal-Sooriya', 'Matteo Scurati', 'Prithpal Sooriya'], - Bridging: ['Bilal', 'micaelae', 'Ethan Wessel'], - Ramps: ['George Weiler'], -}; +// Function to fetch author teams mapping file from teams.json +async function fetchAuthorTeamsFile() { + try { + const { data } = await octokit.request( + 'GET /repos/{owner}/{repo}/contents/{path}', + { + owner: 'MetaMask', + repo: 'MetaMask-planning', + path: 'teams.json', + }, + ); + const content = Buffer.from(data.content, 'base64').toString('utf-8'); + return JSON.parse(content); // Assuming the file is in JSON format + } catch (error) { + console.error('Error fetching author teams mapping file:', error); + return {}; + } +} // Function to get PR labels async function getPRLabels(owner, repo, prNumber) { @@ -129,18 +57,26 @@ async function getPRLabels(owner, repo, prNumber) { } } -// Function to get the team for a given author -function getTeamForAuthor(authorName) { - for (const [team, authors] of Object.entries(authorTeams)) { - if (authors.includes(authorName)) { - return team; - } +// Function to get the GitHub username for a given commit hash +async function getGitHubUsername(commitHash) { + try { + const { data } = await octokit.request( + 'GET /repos/{owner}/{repo}/commits/{ref}', + { + owner: 'MetaMask', + repo: 'metamask-extension', + ref: commitHash, + }, + ); + return data.author ? data.author.login : null; + } catch (error) { + console.error('Error fetching GitHub username:', error); + return null; } - return 'Other/Unknown'; // Default team for unknown authors } // Function to filter commits based on unique commit messages and group by teams -async function filterCommitsByTeam(branchA, branchB) { +async function filterCommitsByTeam(branchA, branchB, authorTeams) { try { const git = simpleGit(); @@ -157,17 +93,27 @@ async function filterCommitsByTeam(branchA, branchB) { const log = await git.log(logOptions); const seenMessages = new Set(); const commitsByTeam = {}; + let processedCommits = 0; const MAX_COMMITS = 500; // Limit the number of commits to process + console.log('Generation of the CSV file "commits.csv" is in progress...'); for (const commit of log.all) { - const { author, message, hash } = commit; - if (commitsByTeam.length >= MAX_COMMITS) { + if (processedCommits >= MAX_COMMITS) { break; } - const team = getTeamForAuthor(author); + const { author, message, hash } = commit; + const githubUsername = await getGitHubUsername(hash); + let team = authorTeams[githubUsername] || 'Other/Unknown'; + + // Format the team label + team = team + .replace(/^team-/u, '') // Remove the "team-" prefix + .split('-') // Split the string into an array of words + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) // Capitalize the first letter of each word + .join(' '); // Join the words back into a string with spaces // Extract PR number from the commit message using regex const prMatch = message.match(/\(#(\d+)\)/u); @@ -206,9 +152,9 @@ async function filterCommitsByTeam(branchA, branchB) { releaseLabel, hash: hash.substring(0, 10), }); + processedCommits += 1; } } - return commitsByTeam; } catch (error) { console.error(error); @@ -233,7 +179,7 @@ function formatAsCSV(commitsByTeam) { }); } csvContent.unshift( - 'Commit Message,Author,PR Link,Team,Release Label, Commit Hash', + 'Commit Message,Author,PR Link,Team,Release Label,Commit Hash', ); return csvContent; @@ -250,7 +196,14 @@ async function main() { const branchA = args[0]; const branchB = args[1]; - const commitsByTeam = await filterCommitsByTeam(branchA, branchB); + // Fetch author teams mapping from the teams.json file + const authorTeams = await fetchAuthorTeamsFile(); + + const commitsByTeam = await filterCommitsByTeam( + branchA, + branchB, + authorTeams, + ); if (Object.keys(commitsByTeam).length === 0) { console.log('No unique commits found.'); diff --git a/development/jest.config.js b/development/jest.config.js deleted file mode 100644 index d95157a49635..000000000000 --- a/development/jest.config.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = { - displayName: '/development', - collectCoverageFrom: ['/build/transforms/**/*.js'], - coverageDirectory: '../coverage', - coverageReporters: ['json'], - resetMocks: true, - restoreMocks: true, - testEnvironment: 'node', - testMatch: ['/build/transforms/**/*.test.js'], - testTimeout: 2500, -}; diff --git a/development/lib/build-type.js b/development/lib/build-type.js index 2d96557cc84a..621122921fb6 100644 --- a/development/lib/build-type.js +++ b/development/lib/build-type.js @@ -12,6 +12,7 @@ const { boolean, coerce, union, + number, unknown, validate, nullable, @@ -62,7 +63,26 @@ const EnvArrayStruct = unique( }, ); +/** + * Ensures a number is within a given range + * + * @param {number} min + * @param {number} max + */ +const isInRange = (min, max) => { + /** + * + * @param {number} value + * @returns boolean + */ + function check(value) { + return value >= min && value <= max; + } + return refine(number(), 'range', check); +}; + const BuildTypeStruct = object({ + id: isInRange(10, 64), features: optional(unique(array(string()))), env: optional(EnvArrayStruct), isPrerelease: optional(boolean()), diff --git a/development/lib/run-command.js b/development/lib/run-command.js index fab1eca361d4..62fe2284ea6d 100644 --- a/development/lib/run-command.js +++ b/development/lib/run-command.js @@ -96,7 +96,7 @@ async function runInShell(command, args, output) { const internalError = new Error('Internal'); try { await new Promise((resolve, reject) => { - const childProcess = spawn(command, args); + const childProcess = spawn(command, args, { shell: true }); childProcess.stdout.setEncoding('utf8'); childProcess.stderr.setEncoding('utf8'); childProcess.stdout.pipe(process.stdout); diff --git a/development/metamaskbot-build-announce.js b/development/metamaskbot-build-announce.js index 5d9ef37850a8..f85d64faa887 100755 --- a/development/metamaskbot-build-announce.js +++ b/development/metamaskbot-build-announce.js @@ -38,12 +38,6 @@ function getPercentageChange(from, to) { return parseFloat(((to - from) / Math.abs(from)) * 100).toFixed(2); } -function getBetaVersion(commitMsg, defaultVersion = VERSION) { - const versionPattern = /Version\s(v\d+\.\d+\.\d+-beta\.\d+)/u; - - return commitMsg.match(versionPattern)?.[1] ?? defaultVersion; -} - async function start() { const { GITHUB_COMMENT_TOKEN, @@ -52,7 +46,6 @@ async function start() { CIRCLE_BUILD_NUM, CIRCLE_WORKFLOW_JOB_ID, PARENT_COMMIT, - SHA1_COMMIT_TITLE, } = process.env; console.log('CIRCLE_PULL_REQUEST', CIRCLE_PULL_REQUEST); @@ -82,9 +75,7 @@ async function start() { return `${platform}`; }) .join(', '); - - const betaVersion = getBetaVersion(SHA1_COMMIT_TITLE, VERSION); - const betaBuildLinks = `chrome`; + const betaBuildLinks = `chrome`; const flaskBuildLinks = platforms .map((platform) => { const url = diff --git a/development/sourcemap-validator.js b/development/sourcemap-validator.js index 231e582530ae..545751400aae 100755 --- a/development/sourcemap-validator.js +++ b/development/sourcemap-validator.js @@ -22,25 +22,38 @@ start().catch((error) => { async function start() { const targetFiles = [ - `common-0.js`, - `background-0.js`, - `ui-0.js`, - `scripts/contentscript.js`, - // `scripts/inpage.js`, skipped because the validator can't sample the inlined `scripts/inpage.js` script + 'background-0.js', + 'common-0.js', + 'content-script-0.js', + 'ui-0.js', + 'scripts/contentscript.js', + 'scripts/disable-console.js', + 'scripts/policy-load.js', + // TODO: Investigate why these are failing + // 'scripts/sentry-install.js', + // `scripts/inpage.js`, ]; + const optionalTargetFiles = ['scripts/app-init.js', 'offscreen-0.js']; let valid = true; for (const buildName of targetFiles) { const fileIsValid = await validateSourcemapForFile({ buildName }); valid = valid && fileIsValid; } + for (const buildName of optionalTargetFiles) { + const fileIsValid = await validateSourcemapForFile({ + buildName, + optional: true, + }); + valid = valid && fileIsValid; + } if (!valid) { process.exit(1); } } -async function validateSourcemapForFile({ buildName }) { +async function validateSourcemapForFile({ buildName, optional = false }) { console.log(`build "${buildName}"`); const platform = `chrome`; // load build and sourcemaps @@ -56,6 +69,12 @@ async function validateSourcemapForFile({ buildName }) { // empty } if (!rawBuild) { + if (optional) { + console.warn( + `SourcemapValidator - file not found, skipping "${buildName}"`, + ); + return true; + } throw new Error( `SourcemapValidator - failed to load source file for "${buildName}"`, ); diff --git a/development/ts-migration-dashboard/app/public/index.html b/development/ts-migration-dashboard/app/public/index.html index 2a196a45e656..67412836f505 100644 --- a/development/ts-migration-dashboard/app/public/index.html +++ b/development/ts-migration-dashboard/app/public/index.html @@ -2,7 +2,7 @@ - + Extension TypeScript Migration Status diff --git a/development/ts-migration-dashboard/files-to-convert.json b/development/ts-migration-dashboard/files-to-convert.json index ed5615e8c9d0..d5063250db16 100644 --- a/development/ts-migration-dashboard/files-to-convert.json +++ b/development/ts-migration-dashboard/files-to-convert.json @@ -641,10 +641,6 @@ "ui/components/app/selected-account/selected-account-component.test.js", "ui/components/app/selected-account/selected-account.component.js", "ui/components/app/selected-account/selected-account.container.js", - "ui/components/app/signature-request-original/index.js", - "ui/components/app/signature-request-original/signature-request-original.component.js", - "ui/components/app/signature-request-original/signature-request-original.container.js", - "ui/components/app/signature-request-original/signature-request-original.stories.js", "ui/components/app/signature-request-siwe/index.js", "ui/components/app/signature-request-siwe/signature-request-siwe-header/index.js", "ui/components/app/signature-request-siwe/signature-request-siwe-header/signature-request-siwe-header.js", diff --git a/development/wdyr.ts b/development/wdyr.ts new file mode 100644 index 000000000000..a976e6bdc20c --- /dev/null +++ b/development/wdyr.ts @@ -0,0 +1,11 @@ +// eslint-disable-next-line spaced-comment +/// +import React from 'react'; + +if (process.env.ENABLE_WHY_DID_YOU_RENDER) { + // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires + const whyDidYouRender = require('@welldone-software/why-did-you-render'); + whyDidYouRender(React, { + trackAllPureComponents: true, + }); +} diff --git a/development/webpack/.eslintrc.js b/development/webpack/.eslintrc.js new file mode 100644 index 000000000000..1b142e9a39d1 --- /dev/null +++ b/development/webpack/.eslintrc.js @@ -0,0 +1,41 @@ +// this file is named .eslintrc.js because eslint checks for that file first + +module.exports = { + rules: { + '@typescript-eslint/no-shadow': [ + 'error', + { + allow: [ + // so uh, these aren't always globals, ya know. + 'describe', + 'it', + 'test', + 'afterEach', + 'beforeEach', + ], + }, + ], + // useful for lazy `require`s (makes start up faster) + '@typescript-eslint/no-require-imports': 'off', + // useful for modifying properties of `require`d modules (something `import`ed modules don't allow) + '@typescript-eslint/no-var-requires': 'off', + // Fun fact: ESM imports _require_ extensions. So silly. + 'import/extensions': 'off', + // sometimes its nice to do things like `something = else = null;` + 'no-multi-assign': ['error', { ignoreNonDeclaration: true }], + // Why? What's next, no addition? + 'no-bitwise': 'off', + // `void` is useful to ignore return values, the option `allowAsStatement: true` is broken for lambda functions, e.g., `() => void something()`. + 'no-void': 'off', + // `if (condition) return;` is useful for early returns without adding noise. + curly: ['error', 'multi-line'], + // require is required to load dynamic modules (well, JSON, mostly) synchronously (with Node's require cache, too!). + 'import/no-dynamic-require': 'off', + // uh, they're bullet points in markdown in a JSDoc comment. Stop this nonsense. + 'jsdoc/no-multi-asterisks': ['error', { allowWhitespace: true }], + // Really? I was joking about "no addition" above, but its (almost) real! + 'no-plusplus': 'off', + // I want to increment a variable outside my loop. This prevents that. + 'no-loop-func': 'off', + }, +}; diff --git a/development/webpack/README.md b/development/webpack/README.md new file mode 100644 index 000000000000..aa85fb67d444 --- /dev/null +++ b/development/webpack/README.md @@ -0,0 +1,214 @@ +# MetaMask Development Build Tool + +This tool is used to build the MetaMask extension for development purposes. It is not (yet) intended for production builds. + +## Usage + +For usage, examples, and options, run the following command: + +```bash +yarn webpack --help +``` + +To build the MetaMask extension, run the following command: + +```bash +yarn webpack +``` + +This will create a `dist/chrome` directory containing the built extension. See usage for more options. + +To watch for changes and rebuild the extension automatically, run the following command: + +```bash +yarn webpack --watch +``` + +### Set options using a `config.json` file + +You can skip using command line options and specify options using a JSON file +instead. You can use the same options as the command line, but in JSON form. For +example, to build a zip of the extension for Chrome and Firefox, create a +`config.json` file as follows (notice the use of an array for the `browser` +option): + +```json +{ + "browser": ["chrome", "firefox"], + "zip": true +} +``` + +Then you can use it as follows: + +```bash +yarn webpack --config config.json +``` + +And you can combine it with CLI options, too: + +```bash +yarn webpack --config config.json --dry-run +``` + +Run `yarn webpack --help` for the list of options. + +### Set options using environment variables + +You can use environment variables instead of command line options: + +```bash +BUNDLE_MINIFY=true yarn webpack +``` + +Run `yarn webpack --help` for the list of options. + +Note: multiple array options cannot be set this way, due to this bug in yargs: https://github.com/yargs/yargs/issues/821 + +You can also combine environment variables with `--config` and CLI options: + +```bash +BUNDLE_MINIFY=true yarn webpack --config config.json --dry-run +``` + +## Cache Invalidation + +The cache is invalidated when the build tool's code itself changes, or when the `package.json` file changes. The cache +is keyed by the effective options, so changing the options will also invalidate the cache. Not all options affect +the cache, but most do. Search for "`cacheKey`" in [./utils/cli.ts](./utils/cli.ts) to see which options affect the cache. + +## Tips + +- You can use the `--config` flag to specify your own JSON config file to use as the build configuration. This is useful + if you want to customize the defaults +- You can specify options via environment variables by prefixing the option name with `BUNDLE_`, e.g., + `BUNDLE_BROWSER=opera yarn webpack` on \*nix. +- don't run the build process with the Node Debugger attached; it will make things build much more slowly. + +## Development + +### Debugging the Build Process + +Webpack makes use of a cache to speed up builds. If you encounter issues with the build tool, try clearing the cache by +running the following command: + +```bash +yarn webpack:clearcache +``` + +You can also avoid using the cache by setting the `--no-cache` option. + +``` +yarn webpack --no-cache +``` + +Please to file an issue if you do encounter issues! + +### Linting + +Linting is exactly the same as the rest of the MetaMask project. To lint the build tool, run the following command: + +```bash +yarn lint +``` + +That said, the webpack build has its [own eslint configuration](./.eslintrc.js) that overrides some restrictive rules +that either: don't work well when optimizing for performance, or disable JavaScript features that are useful and +generally necessary. + +### Testing + +To run the build tool's test suite, run the following command: + +```bash +yarn test:unit:webpack +``` + +This will run the test suite for the build tool. These tests are also run as part of the MetaMask test suite in CI. + +To output an HTML, JSON, and text coverage reports, run the following command: + +```bash +yarn test:unit:webpack:coverage +``` + +Test coverage should be around 100% for the build tool, exceptions are made for some edge cases that are overly +difficult or complex to test, like exceptions. + +Testing uses node's built-in `node:test` and `node:assert` modules instead of jest/mocha. + +Unit tests are organized in `development/webpack/test` and are named `*.test.ts`, where \* is the name of the file being +tested. This is a guideline and not a rule. + +When checking coverage its is sometimes good to check if your coverage is intentional. One way to do that is to run the +test coverage on a single test file. This can be done by running the following command: + +```bash +yarn nyc --reporter=html tsx --test development/webpack/test/your-test-file.test.ts +``` + +### Performance + +The build tool only exists to build the project quickly. Don't make it slow. If you're adding a feature that makes the +build tool slower, go for a walk and maybe don't come back until you change your mind. + +Some things that might make the build tool slower: + +- using JavaScript (this tool is only fast because it uses [SWC](https://swc.rs/) for compilation, which is written in + Rust) +- requiring/importing large libraries +- functional programming paradigms (JavaScript is not Haskell after all) + - like chaining map, filter, reduce, etc. when a single loop would do. + - try to avoid looping over the same data/file multiple times +- using async IO when sync IO would do + - non-blocking IO is great, but not when it's the only IO happening at the time and we don't care about blocking the + main process. +- launching shells, workers, or other processes without measuring the cost +- unnecessary IO +- validation, linting, or other checks that are not necessary + +If you must add something that slows it down, consider putting it behind a flag. If it must be in the default mode, try +to run it in parallel with other tasks. + +### The Cache + +The build process uses a cache to speed up successive builds. The cache is stored in the `node_modules/.cache/webpack` +directory. + +The cache is slow. Very slow. It takes about 50% of the total time just to create the cache. But you shouldn't notice +that because the caching step is pushed to a background process. + +The way this works is by running the build in a background child process, and then detaching that child process from the +parent process once the build is complete (and cache reconciliation and persistance begins). + +Launching the build in a background process does take time, but its much less time than cache creation, so it works out. + +The child process is run with its own TTY for `stderr` and `stdout`; the child's stdio dimensions are kept in sync with +the parent's, and all TTY features of the parent are available in the child (formatting, colors, cursors, etc.). On +Windows an IPC channel is used to communicate between the parent and child processes, on \*nix this is done via signals. +The parent process listens for the child process and signal the parent, and when it does, the parent disconnects from +the child and shuts down, leaving the child to run in the background so the cache can be processed and persisted. + +### To do: + +- [define and wrangle the difference between `lockdown` and `lavamoat` options.](https://github.com/MetaMask/metamask-extension/issues/26254) +- [MV3 support](https://github.com/MetaMask/metamask-extension/issues/26255) + - Service workers, used by MV3, must load dependencies via `importScripts`. + - there are existing webpack plugins that do this, but they are not yet integrated into this build tool and would + require changes to our code and existing gulp-based build process to work. +- [Make lavamoat work so we can run production builds](https://github.com/MetaMask/metamask-extension/issues/26256) +- [Make LiveReload, Hot Module Reloading, and/or React Refresh work](https://github.com/MetaMask/metamask-extension/issues/26257) + - prerequisite: https://github.com/MetaMask/metamask-extension/issues/22450 +- [Make the build tool even faster (switch to RSPack once it hits 1.0.0?)](https://github.com/MetaMask/metamask-extension/issues/26258) +- [enable `yarn webpack completion`](https://github.com/MetaMask/metamask-extension/issues/26259) + - It doesn't work with multiple-word commands (`yarn webpack ...`) and is currently disabled. +- [implement overrides for icons and manifests fields for non-main builds](https://github.com/MetaMask/metamask-extension/issues/26260) + +### Ideas + +- investigate using `DLLPlugin` for even faster builds +- make it work in Bun.js and/or Deno +- investigate adding a long-running background daemon mode for always up-to-date builds +- investigate adding linting, testing, validation, AI code review, etc.; especially in `--watch` mode +- investigate a "one CLI to rule them all" approach to MetaMask developer tooling and scripts +- allow changing some options without restarting the build process diff --git a/development/webpack/build.ts b/development/webpack/build.ts new file mode 100644 index 000000000000..12ce2c95e693 --- /dev/null +++ b/development/webpack/build.ts @@ -0,0 +1,62 @@ +import { webpack } from 'webpack'; +import type WebpackDevServerType from 'webpack-dev-server'; +import { noop, logStats, __HMR_READY__ } from './utils/helpers'; +import config from './webpack.config.js'; + +// disable browserslist stats as it needlessly traverses the filesystem multiple +// times looking for a stats file that doesn't exist. +require('browserslist/node').getStat = noop; + +/** + * Builds the extension + * + * @param onComplete + */ +export function build(onComplete: () => void = noop) { + const isDevelopment = config.mode === 'development'; + + const { watch, ...options } = config; + const compiler = webpack(options); + if (__HMR_READY__ && watch) { + // DISABLED BECAUSE WE AREN'T `__HMR_READY__` YET + // Use `webpack-dev-server` to enable HMR + const WebpackDevServer: typeof WebpackDevServerType = require('webpack-dev-server'); + const serverOptions = { + hot: isDevelopment, + liveReload: isDevelopment, + server: { + // TODO: is there any benefit to using https? + type: 'https', + }, + // always use loopback, as 0.0.0.0 tends to fail on some machines (WSL2?) + host: 'localhost', + devMiddleware: { + // browsers need actual files on disk + writeToDisk: true, + }, + // we don't need/have a "static" directory, so disable it + static: false, + allowedHosts: 'all', + } as const satisfies WebpackDevServerType.Configuration; + + const server = new WebpackDevServer(serverOptions, compiler); + server.start().then(() => console.log('🦊 Watching for changes…')); + } else { + console.error(`🦊 Running ${options.mode} build…`); + if (watch) { + // once HMR is ready (__HMR_READY__ variable), this section should be removed. + compiler.watch(options.watchOptions, (err, stats) => { + logStats(err ?? undefined, stats); + console.error('🦊 Watching for changes…'); + }); + } else { + compiler.run((err, stats) => { + logStats(err ?? undefined, stats); + // `onComplete` must be called synchronously _before_ `compiler.close` + // or the caller might observe output from the `close` command. + onComplete(); + compiler.close(noop); + }); + } + } +} diff --git a/development/webpack/fork.mts b/development/webpack/fork.mts new file mode 100644 index 000000000000..73fb7c4a7e4d --- /dev/null +++ b/development/webpack/fork.mts @@ -0,0 +1,27 @@ +/** + * @file Executes the build process in a child process environment, ensuring it + * was correctly spawned by checking for a `PPID` environment variable that + * matches the parent's process ID. This script is responsible for running the + * build logic defined in './build' and managing output streams to prevent + * unwanted output after completion. It leverages IPC for communication back to + * the parent process or falls back to sending a POSIX signal (`SIGUSR2`) to + * signal completion. + * @see {@link ./launch.ts} + */ + +const PPID = Number(process.env.PPID); +if (isNaN(PPID) || PPID !== process.ppid) { + throw new Error( + `${__filename} must be run with a \`PPID\` environment variable. See ${__dirname}/launch.ts for an example.`, + ); +} + +const { build } = await import('./build.ts'); +build(() => { + // stop writing now because the parent process is still listening to these + // streams and we don't want any more output to be shown to the user. + process.stdout.write = process.stderr.write = () => true; + + // use IPC if we have it, otherwise send a POSIX signal + process.send?.('SIGUSR2') || process.kill(PPID, 'SIGUSR2'); +}); diff --git a/development/webpack/launch.ts b/development/webpack/launch.ts new file mode 100755 index 000000000000..acf1149045fe --- /dev/null +++ b/development/webpack/launch.ts @@ -0,0 +1,181 @@ +#!/usr/bin/env -S node --require "./node_modules/tsx/dist/preflight.cjs" --import "./node_modules/tsx/dist/loader.mjs" + +/** + * @file This script optimizes build processes by conditionally forking child + * processes based on command-line arguments. It handles memory management, + * stdio stream creation, and process lifecycle to improve performance and + * maintainability. Supports cross-platform execution with specific + * considerations for Windows environments. + * + * On Linux-like systems you can skip the overhead of running `yarn` by + * executing this file directly, e.g., `./development/webpack/launch.ts`, or via + * bun or tsx. + */ + +// Note: minimize non-`type` imports to decrease load time. +import { join } from 'node:path'; +import { spawn, type StdioOptions } from 'node:child_process'; +import parser from 'yargs-parser'; +import type { Child, PTY, Stdio, StdName } from './types.ts'; + +const rawArgv = process.argv.slice(2); + +const alias = { cache: 'c', help: 'h', watch: 'h' }; +type Args = { [x in keyof typeof alias]?: boolean }; +const args = parser(rawArgv, { alias, boolean: Object.keys(alias) }) as Args; + +if (args.cache === false || args.help === true || args.watch === true) { + // there are no time savings to running the build in a child process if: the + // cache is disabled, we need to output "help", or we're in watch mode. + require('./build.ts').build(); +} else { + fork(process, join(__dirname, 'fork.mts'), rawArgv); +} + +/** + * Runs the `file` in a child process. This allows the parent process to + * exit as soon as the build completes, but lets the child process continue to + * serialize and persist the cache in the background. + * + * @param process - The parent process, like `globalThis.process` + * @param file - Path to the file to run, given as an argument to the command + * @param argv - Arguments to pass to the executable + */ +function fork(process: NodeJS.Process, file: string, argv: string[]) { + const env = { NODE_OPTIONS: '', ...process.env, PPID: `${process.pid}` }; + // node recommends using 75% of the available memory for `max-old-space-size` + // https://github.com/nodejs/node/blob/dd67bf08cb1ab039b4060d381cc68179ee78701a/doc/api/cli.md#--max-old-space-sizesize-in-megabytes + const maxOldSpaceMB = ~~((require('node:os').totalmem() * 0.75) / (1 << 20)); + // `--huge-max-old-generation-size` and `--max-semi-space-size=128` reduce + // garbage collection pauses; 128MB provided max benefit in perf testing. + const nodeOptions = [ + `--max-old-space-size=${maxOldSpaceMB}`, + '--max-semi-space-size=128', + '--huge-max-old-generation-size', + ]; + + // run the build in a child process so that we can exit the parent process as + // soon as the build completes, but let the cache serialization finish in the + // background (the cache can take 30% of build-time to serialize and persist). + const { connectToChild, destroy, stdio } = createOutputStreams(process); + + const node = process.execPath; + const options = { detached: true, env, stdio }; + spawn(node, [...nodeOptions, ...process.execArgv, file, ...argv], options) + .once('close', destroy) // clean up if the child crashes + .once('spawn', connectToChild); +} + +/** + * Create the stdio streams (stderr and stdout) for the child process to use and + * for the parent to control and listen to. + * + * @param process - The parent process, like `globalThis.process` + * @returns The stdio streams for the child process to use + */ +function createOutputStreams(process: NodeJS.Process) { + const { isatty } = require('node:tty'); + const isWindows = process.platform === 'win32'; + // use IPC for communication on Windows, as it doesn't support POSIX signals + const ipc = isWindows ? 'ipc' : 'ignore'; + const outs = (['stdout', 'stderr'] as const).map(function createStream(name) { + const parentStream = process[name]; + // TODO: get Windows PTY working + return !isWindows && isatty(parentStream.fd) + ? createTTYStream(parentStream) + : createNonTTYStream(parentStream, name); + }) as [Stdio, Stdio]; + + return { + /** + * + * @param this + * @param child + */ + connectToChild(this: Child, child = this) { + // hook up the child's stdio to the parent's & unref so we can exit later + outs.forEach((stream) => { + stream.listen(child); + stream.unref(child); + }); + + listenForShutdownSignal(process, child); + + process + // kill the child process if we didn't exit cleanly + .on('exit', (code) => code > 128 && child.kill(code - 128)) + // `SIGWINCH` means the terminal was resized + .on('SIGWINCH', function handleSigwinch(signal) { + // resize the tty's + outs.forEach((out) => out.resize()); + // then tell the child process to update its dimensions + child.kill(signal); + }); + }, + destroy: () => outs.forEach((out) => out.destroy()), + stdio: ['ignore', outs[0].pty, outs[1].pty, ipc] as StdioOptions, + }; +} + +/** + * Create a non-TTY (pipe) stream for the child process to use as its stdio. + * + * @param stream - The parent process's stdio stream + * @param name - Either `stdout` or `stderr` + * @returns The stream for the child process to use + */ +function createNonTTYStream(stream: NodeJS.WriteStream, name: StdName): Stdio { + return { + destroy: () => undefined, + listen: (child: Child) => void child[name].pipe(stream), + pty: 'pipe', // let Node create the Pipes + resize: () => undefined, + unref: (child: Child) => void child[name].unref(), + }; +} + +/** + * Create a PTY stream for the child process to use as its stdio. + * + * @param stream - The parent process's stdio stream + * @returns The PTY stream for the child process to use + */ +function createTTYStream(stream: NodeJS.WriteStream): Stdio { + // create a PTY (Pseudo TTY) so the child stream behaves like a TTY + const options = { cols: stream.columns, encoding: null, rows: stream.rows }; + const pty: PTY = require('@lydell/node-pty').open(options); + + return { + destroy: () => { + pty.master.destroy(); + pty.slave.destroy(); + }, + listen: (_child: Child) => void pty.master.pipe(stream), + pty: pty.slave, + resize: () => pty.resize(stream.columns, stream.rows), + unref: (_child: Child) => { + pty.master.unref(); + pty.slave.unref(); + }, + }; +} + +/** + * Listens for a shutdown signal either on the child's IPC channel or via the + * parent process's `SIGUSR2` event. When the signal is received, the child + * process is unref'd so that it can continue running in the background. + * + * Once the child process is unref'd, the parent process may exit on its own. + * + * @param process - The parent process, like `globalThis.process` + * @param child - The child process to listen to + */ +function listenForShutdownSignal(process: NodeJS.Process, child: Child) { + // exit gracefully when the child signals the parent via `SIGUSR2` + if (child.channel === null || child.channel === undefined) { + process.on('SIGUSR2', () => child.unref()); + } else { + child.channel.unref(); + child.on('message', (signal) => signal === 'SIGUSR2' && child.unref()); + } +} diff --git a/development/webpack/test/cli.test.ts b/development/webpack/test/cli.test.ts new file mode 100644 index 000000000000..7f9d5f2fea53 --- /dev/null +++ b/development/webpack/test/cli.test.ts @@ -0,0 +1,83 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert'; +import { getDryRunMessage, parseArgv } from '../utils/cli'; +import { getBuildTypes } from '../utils/config'; +import { Browsers } from '../utils/helpers'; + +describe('./utils/cli.ts', () => { + const defaultArgs = { + env: 'development', + watch: false, + cache: true, + progress: true, + releaseVersion: 0, + devtool: 'source-map', + sentry: false, + test: false, + zip: false, + minify: false, + browser: ['chrome'], + manifest_version: 2, + type: 'main', + lavamoat: false, + lockdown: false, + snow: false, + dryRun: false, + stats: false, + }; + + it('should return defaults', () => { + const { args, cacheKey, features } = parseArgv([], getBuildTypes()); + assert.deepStrictEqual(args, defaultArgs); + assert.strictEqual( + typeof cacheKey, + 'string', + 'cacheKey should be a string', + ); + assert(cacheKey.length > 0, 'cacheKey should not be empty'); + // features come from build.yml, and change often, so let's just check the shape + assert(features, 'features should be defined'); + assert(features.all instanceof Set, 'features.all should be a Set'); + assert(features.active instanceof Set, 'features.active should be a Set'); + }); + + it('getDryRunMessage', () => { + const { args, features } = parseArgv([], getBuildTypes()); + const message = getDryRunMessage(args, features); + // testing the exact message could be nice, but verbose and maybe a bit + // brittle, so we just check that it returns a string + assert.strictEqual( + typeof message, + 'string', + 'Dry run message should be a string', + ); + assert(message.length > 0, 'Dry run message should not be empty'); + }); + + it('should allow for build types with no features', () => { + const buildTypesConfig = getBuildTypes(); + delete buildTypesConfig.buildTypes.main.features; + const { features } = parseArgv([], buildTypesConfig); + assert.strictEqual( + features.active.size, + 0, + 'features.active should be an empty Set', + ); + }); + + it('should allow for a build type with no features section', () => { + const buildTypesConfig = getBuildTypes(); + delete buildTypesConfig.buildTypes.main.features; + const { features } = parseArgv([], buildTypesConfig); + assert.strictEqual( + features.active.size, + 0, + 'features.active should be an empty Set', + ); + }); + + it('should return all browsers when `--browser all` is specified', () => { + const { args } = parseArgv(['--browser', 'all'], getBuildTypes()); + assert.deepStrictEqual(args.browser, Browsers); + }); +}); diff --git a/development/webpack/test/config.test.ts b/development/webpack/test/config.test.ts new file mode 100644 index 000000000000..c19e4871fbb8 --- /dev/null +++ b/development/webpack/test/config.test.ts @@ -0,0 +1,110 @@ +import fs from 'node:fs'; +import { describe, it, after, mock } from 'node:test'; +import assert from 'node:assert'; +import { resolve } from 'node:path'; +import * as config from '../utils/config'; +import { parseArgv } from '../utils/cli'; +import { version } from '../../../package.json'; + +describe('./utils/config.ts', () => { + // variables logic is complex, and is "owned" mostly by the other build + // system, so we don't check for everything, just that the interface is + // behaving + describe('variables', () => { + const originalReadFileSync = fs.readFileSync; + function mockRc(env: Record = {}) { + mock.method(fs, 'readFileSync', (path: string, options: object) => { + if (path === resolve(__dirname, '../../../.metamaskrc')) { + // mock `.metamaskrc`, as users might have customized it which may + // break our tests + return ` +${Object.entries(env) + .map(([key, value]) => `${key}=${value}`) + .join('\n')} +`; + } + return originalReadFileSync(path, options); + }); + } + after(() => mock.restoreAll()); + + it('should return valid build variables for the default build', () => { + const buildTypes = config.getBuildTypes(); + const { args } = parseArgv([], buildTypes); + const { variables, safeVariables } = config.getVariables( + args, + buildTypes, + ); + + assert.strictEqual(variables.get('METAMASK_VERSION'), version); + assert.strictEqual(variables.get('IN_TEST'), args.test); + assert.strictEqual(variables.get('METAMASK_BUILD_TYPE'), args.type); + assert.strictEqual(variables.get('NODE_ENV'), args.env); + + // PPOM_URI is unique in that it is code, and has not been JSON.stringified, so we check it separately: + assert.strictEqual( + safeVariables.PPOM_URI, + `new URL('@blockaid/ppom_release/ppom_bg.wasm', import.meta.url)`, + ); + }); + + it('should prefer .metamaskrc variables over others', () => { + const buildTypes = config.getBuildTypes(); + const { args } = parseArgv([], buildTypes); + const defaultVars = config.getVariables(args, buildTypes); + + // verify the default value of the main build is false + assert.strictEqual(defaultVars.variables.get('ALLOW_LOCAL_SNAPS'), false); + + mockRc({ + ALLOW_LOCAL_SNAPS: 'true', + }); + + const overrides = config.getVariables(args, buildTypes); + + // verify the value of the main build is set to the value in .metamaskrc + assert.strictEqual(overrides.variables.get('ALLOW_LOCAL_SNAPS'), true); + }); + + it('should return valid build variables for a non-default build', () => { + mockRc({ + // required by the `beta` build type + SEGMENT_BETA_WRITE_KEY: '.', + }); + const buildTypes = config.getBuildTypes(); + const { args } = parseArgv( + ['--type', 'beta', '--test', '--env', 'production'], + buildTypes, + ); + const { variables } = config.getVariables(args, buildTypes); + assert.strictEqual( + variables.get('METAMASK_VERSION'), + `${version}-${args.type}.0`, + ); + assert.strictEqual(variables.get('IN_TEST'), args.test); + assert.strictEqual(variables.get('METAMASK_BUILD_TYPE'), args.type); + assert.strictEqual(variables.get('NODE_ENV'), args.env); + }); + + it("should handle true/false/null/'' in rc", () => { + const buildTypes = config.getBuildTypes(); + const { args } = parseArgv([], buildTypes); + + mockRc({ + TESTING_TRUE: 'true', + TESTING_FALSE: 'false', + TESTING_NULL: 'null', + TESTING_MISC: 'MISC', + TESTING_EMPTY_STRING: '', + }); + + const { variables } = config.getVariables(args, buildTypes); + + assert.strictEqual(variables.get('TESTING_TRUE'), true); + assert.strictEqual(variables.get('TESTING_FALSE'), false); + assert.strictEqual(variables.get('TESTING_NULL'), null); + assert.strictEqual(variables.get('TESTING_MISC'), 'MISC'); + assert.strictEqual(variables.get('TESTING_EMPTY_STRING'), null); + }); + }); +}); diff --git a/development/webpack/test/fixtures/ManifestPlugin/complex/manifest/v2/_base.json b/development/webpack/test/fixtures/ManifestPlugin/complex/manifest/v2/_base.json new file mode 100644 index 000000000000..46c463a53ef8 --- /dev/null +++ b/development/webpack/test/fixtures/ManifestPlugin/complex/manifest/v2/_base.json @@ -0,0 +1,4 @@ +{ + "description": "base description", + "web_accessible_resources": ["file.png"] +} diff --git a/development/webpack/test/fixtures/ManifestPlugin/complex/manifest/v2/chrome.json b/development/webpack/test/fixtures/ManifestPlugin/complex/manifest/v2/chrome.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/development/webpack/test/fixtures/ManifestPlugin/complex/manifest/v2/chrome.json @@ -0,0 +1 @@ +{} diff --git a/development/webpack/test/fixtures/ManifestPlugin/complex/manifest/v3/_base.json b/development/webpack/test/fixtures/ManifestPlugin/complex/manifest/v3/_base.json new file mode 100644 index 000000000000..57cd1ac506d9 --- /dev/null +++ b/development/webpack/test/fixtures/ManifestPlugin/complex/manifest/v3/_base.json @@ -0,0 +1,10 @@ +{ + "description": "base description", + "manifest_version": 3, + "web_accessible_resources": [ + { + "matches": [""], + "resources": ["file.png"] + } + ] +} diff --git a/development/webpack/test/fixtures/ManifestPlugin/complex/manifest/v3/chrome.json b/development/webpack/test/fixtures/ManifestPlugin/complex/manifest/v3/chrome.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/development/webpack/test/fixtures/ManifestPlugin/complex/manifest/v3/chrome.json @@ -0,0 +1 @@ +{} diff --git a/development/webpack/test/fixtures/ManifestPlugin/empty/manifest/v2/_base.json b/development/webpack/test/fixtures/ManifestPlugin/empty/manifest/v2/_base.json new file mode 100644 index 000000000000..5e74046537e7 --- /dev/null +++ b/development/webpack/test/fixtures/ManifestPlugin/empty/manifest/v2/_base.json @@ -0,0 +1,3 @@ +{ + "description": "base description" +} diff --git a/development/webpack/test/fixtures/ManifestPlugin/empty/manifest/v2/chrome.json b/development/webpack/test/fixtures/ManifestPlugin/empty/manifest/v2/chrome.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/development/webpack/test/fixtures/ManifestPlugin/empty/manifest/v2/chrome.json @@ -0,0 +1 @@ +{} diff --git a/development/webpack/test/fixtures/ManifestPlugin/empty/manifest/v3/_base.json b/development/webpack/test/fixtures/ManifestPlugin/empty/manifest/v3/_base.json new file mode 100644 index 000000000000..4869ffc9cb61 --- /dev/null +++ b/development/webpack/test/fixtures/ManifestPlugin/empty/manifest/v3/_base.json @@ -0,0 +1,4 @@ +{ + "description": "base description", + "manifest_version": 3 +} diff --git a/development/webpack/test/fixtures/ManifestPlugin/empty/manifest/v3/chrome.json b/development/webpack/test/fixtures/ManifestPlugin/empty/manifest/v3/chrome.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/development/webpack/test/fixtures/ManifestPlugin/empty/manifest/v3/chrome.json @@ -0,0 +1 @@ +{} diff --git a/development/webpack/test/fixtures/git/HEAD b/development/webpack/test/fixtures/git/HEAD new file mode 100644 index 000000000000..b870d82622c1 --- /dev/null +++ b/development/webpack/test/fixtures/git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/main diff --git a/development/webpack/test/fixtures/git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 b/development/webpack/test/fixtures/git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 new file mode 100644 index 000000000000..adf64119a33d Binary files /dev/null and b/development/webpack/test/fixtures/git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 differ diff --git a/development/webpack/test/fixtures/git/objects/63/4cad6dd342dc5317b6c1677dc9ead3fb72f680 b/development/webpack/test/fixtures/git/objects/63/4cad6dd342dc5317b6c1677dc9ead3fb72f680 new file mode 100644 index 000000000000..332274685e38 Binary files /dev/null and b/development/webpack/test/fixtures/git/objects/63/4cad6dd342dc5317b6c1677dc9ead3fb72f680 differ diff --git a/development/webpack/test/fixtures/git/refs/heads/main b/development/webpack/test/fixtures/git/refs/heads/main new file mode 100644 index 000000000000..38d8162043b8 --- /dev/null +++ b/development/webpack/test/fixtures/git/refs/heads/main @@ -0,0 +1 @@ +634cad6dd342dc5317b6c1677dc9ead3fb72f680 diff --git a/development/webpack/test/git.test.ts b/development/webpack/test/git.test.ts new file mode 100644 index 000000000000..ee7d9bef65d1 --- /dev/null +++ b/development/webpack/test/git.test.ts @@ -0,0 +1,33 @@ +import assert from 'node:assert'; +import { describe, it } from 'node:test'; +import { join } from 'node:path'; +import { getLatestCommit } from '../utils/git'; + +describe('getLatestCommit', () => { + const gitDir = join(__dirname, '.', 'fixtures', 'git'); + it('should return some values by default', () => { + const { hash, timestamp } = getLatestCommit(); + + assert.strictEqual(hash().length, 8, 'The hash length is wrong'); + assert.ok(typeof timestamp() === 'number', 'The timestamp type is wrong'); + }); + + it('should return the latest commit hash and timestamp', () => { + const { hash, timestamp } = getLatestCommit(gitDir); + + assert.strictEqual(hash(), '634cad6d', 'The hash is wrong'); + assert.strictEqual(timestamp(), 1711385030000, 'The timestamp is wrong'); + }); + + it('should use the cache', () => { + const firstCallCustom = getLatestCommit(gitDir); + const firstCallDefault = getLatestCommit(); + const secondCallCustom = getLatestCommit(gitDir); + const secondCallDefault = getLatestCommit(); + + assert.notStrictEqual(firstCallCustom, firstCallDefault); + assert.notStrictEqual(secondCallCustom, secondCallDefault); + assert.strictEqual(firstCallCustom, secondCallCustom); + assert.strictEqual(firstCallDefault, secondCallDefault); + }); +}); diff --git a/development/webpack/test/helpers.test.ts b/development/webpack/test/helpers.test.ts new file mode 100644 index 000000000000..fc1955eec3c6 --- /dev/null +++ b/development/webpack/test/helpers.test.ts @@ -0,0 +1,371 @@ +import fs from 'node:fs'; +import { describe, it, afterEach, beforeEach, mock } from 'node:test'; +import assert from 'node:assert'; +import { join } from 'node:path'; +import { + version, + type Chunk, + type Stats, + type Compilation, + type StatsOptions, + type StatsCompilation, +} from 'webpack'; +import * as helpers from '../utils/helpers'; +import { type Combination, generateCases } from './helpers'; + +describe('./utils/helpers.ts', () => { + afterEach(() => mock.restoreAll()); + + it('should return undefined when noop it called', () => { + const nothing = helpers.noop(); + assert.strictEqual(nothing, undefined); + }); + + it('should return all entries listed in the manifest and file system for manifest_version 2', () => { + const originalReaddirSync = fs.readdirSync; + const otherHtmlEntries = ['one.html', 'two.html']; + const appRoot = ''; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + mock.method(fs, 'readdirSync', function (path: string, options: any) { + if (path === appRoot) { + return [...otherHtmlEntries, 'three.not-html']; + } + return originalReaddirSync.call(fs, path, options); + }); + + const manifest = { + manifest_version: 2, + background: { + scripts: ['background.js'], + page: 'background.html', + }, + browser_action: { + // use one from `otherHtmlEntries`, to ensure we don't duplicate things + default_popup: otherHtmlEntries[0], + }, + // images/test.ing.png will be omitted from entry points + web_accessible_resources: ['images/test.ing.png', 'testing.js'], + content_scripts: [ + { + matches: ['file://*/*', 'http://*/*', 'https://*/*'], + js: ['scripts/contentscript.js', 'scripts/inpage.js'], + run_at: 'document_start', + all_frames: true, + }, + { + matches: ['*://connect.trezor.io/*/popup.html'], + js: ['vendor/trezor/content-script.js'], + }, + ], + } as helpers.ManifestV2; + const { entry, canBeChunked } = helpers.collectEntries(manifest, appRoot); + const expectedScripts = { + 'background.js': { + chunkLoading: false, + filename: 'background.js', + import: join(appRoot, `background.js`), + }, + 'scripts/contentscript.js': { + chunkLoading: false, + filename: 'scripts/contentscript.js', + import: join(appRoot, `scripts/contentscript.js`), + }, + 'scripts/inpage.js': { + chunkLoading: false, + filename: 'scripts/inpage.js', + import: join(appRoot, `/scripts/inpage.js`), + }, + 'vendor/trezor/content-script.js': { + chunkLoading: false, + filename: 'vendor/trezor/content-script.js', + import: join(appRoot, `vendor/trezor/content-script.js`), + }, + 'testing.js': { + chunkLoading: false, + filename: 'testing.js', + import: join(appRoot, `testing.js`), + }, + }; + const expectedHtml = { + background: join(appRoot, `background.html`), + one: join(appRoot, `one.html`), + two: join(appRoot, `two.html`), + // notice: three.not-html is NOT included, since it doesn't have an `.html` extension + }; + const expectedEntries = { ...expectedScripts, ...expectedHtml }; + assert.deepStrictEqual(entry, expectedEntries); + + const jsFiles = Object.keys(entry).filter((key) => key.endsWith('.js')); + assert(jsFiles.length > 0, "JS files weren't found in the manifest"); + jsFiles.forEach((name) => { + assert.strictEqual(canBeChunked({ name } as Chunk), false); + }); + + // scripts that are *not* in our manifest *can* be chunked + assert.strictEqual(canBeChunked({ name: 'anything.js' } as Chunk), true); + }); + + it('should return all entries listed in the manifest and file system for manifest_version 3', () => { + const originalReaddirSync = fs.readdirSync; + const otherHtmlEntries = ['one.html', 'two.html']; + const appRoot = ''; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + mock.method(fs, 'readdirSync', (path: string, options: any) => { + if (path === appRoot) { + return [...otherHtmlEntries, 'three.not-html']; + } + return originalReaddirSync.call(fs, path, options); + }); + + const manifest = { + name: 'MetaMask', + version: '1.0.0', + manifest_version: 3, + background: { + service_worker: 'background.js', + }, + web_accessible_resources: [ + { + matches: [''], + // images/test.ing.png will be omitted from entry points + resources: ['images/test.ing.png', 'testing.js'], + }, + ], + browser_action: { + // use one from `otherHtmlEntries`, to ensure we don't duplicate things + default_popup: otherHtmlEntries[0], + }, + content_scripts: [ + { + matches: ['file://*/*', 'http://*/*', 'https://*/*'], + js: ['scripts/contentscript.js'], + run_at: 'document_start', + all_frames: true, + }, + { + matches: ['*://connect.trezor.io/*/popup.html'], + js: ['vendor/trezor/content-script.js'], + }, + ], + } as helpers.ManifestV3; + const { entry, canBeChunked } = helpers.collectEntries(manifest, appRoot); + const expectedScripts = { + 'scripts/contentscript.js': { + chunkLoading: false, + filename: 'scripts/contentscript.js', + import: join(appRoot, `scripts/contentscript.js`), + }, + 'vendor/trezor/content-script.js': { + chunkLoading: false, + filename: 'vendor/trezor/content-script.js', + import: join(appRoot, `vendor/trezor/content-script.js`), + }, + 'background.js': { + chunkLoading: false, + filename: 'background.js', + import: join(appRoot, `background.js`), + }, + 'testing.js': { + chunkLoading: false, + filename: 'testing.js', + import: join(appRoot, `testing.js`), + }, + }; + const expectedHtml = { + one: join(appRoot, `one.html`), + two: join(appRoot, `two.html`), + // notice: three.not-html is NOT included, since it doesn't have an `.html` extension + }; + const expectedEntries = { + ...expectedScripts, + ...expectedHtml, + }; + assert.deepStrictEqual(entry, expectedEntries); + + const jsFiles = Object.keys(entry).filter((key) => key.endsWith('.js')); + assert(jsFiles.length > 0, "JS files weren't found in the manifest"); + jsFiles.forEach((name) => { + assert.strictEqual(canBeChunked({ name } as Chunk), false); + }); + + // scripts that are *not* in our manifest *can* be chunked + assert.strictEqual(canBeChunked({ name: 'anything.js' } as Chunk), true); + }); + + it('should handle manifest.json files with empty sections', () => { + const originalReaddirSync = fs.readdirSync; + const appRoot = ''; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + mock.method(fs, 'readdirSync', (path: string, options: any) => { + if (path === appRoot) { + return []; + } + return originalReaddirSync.call(fs, path, options); + }); + + const manifestv2 = { + manifest_version: 2, + background: {}, + } as helpers.ManifestV2; + const { entry: entryv2 } = helpers.collectEntries(manifestv2, appRoot); + assert.deepStrictEqual(entryv2, {}); + + const manifestv3 = { + name: 'MetaMask', + version: '1.0.0', + manifest_version: 3, + background: {}, + } as helpers.ManifestV3; + const { entry: entryv3 } = helpers.collectEntries(manifestv3, appRoot); + assert.deepStrictEqual(entryv3, {}); + }); + + it('should throw if an entry file starts with an underscore', () => { + const manifest = { + manifest_version: 2, + background: { + page: '_badfile.html', + }, + } as helpers.ManifestV2; + assert.throws( + () => helpers.collectEntries(manifest, ''), + /Error: Invalid Entrypoint Filename Detected/u, + ); + }); + + describe('logStats', () => { + const getStatsMock = ( + stats: 'normal' | 'none', + mode: 'development' | 'production', + hasError: boolean, + hasWarning: boolean, + ) => { + return { + hash: 'test-hash', + toJson: null as unknown as () => StatsCompilation, + endTime: 1000, + startTime: 0, + hasErrors: mock.fn(() => hasError), + hasWarnings: mock.fn(() => hasWarning), + compilation: { + options: { + mode, + stats, + }, + compiler: { + name: 'test-compiler-name', + }, + } as Compilation, + toString: mock.fn((_?: unknown) => 'test-stats'), + } as const satisfies Stats; + }; + + it('should log nothing if err and stats are both not defined', () => { + const { mock: error } = mock.method(console, 'error', helpers.noop); + helpers.logStats(undefined, undefined); + assert.strictEqual(error.callCount(), 0, 'error should not be called'); + }); + + it('should log only the error when error and stats are provided', () => { + const stats = getStatsMock('normal', 'production', false, false); + const { mock: error } = mock.method(console, 'error', helpers.noop); + const errorToLog = new Error('test error'); + + // should only log the error, and nothing else + helpers.logStats(errorToLog, stats); + + assert.strictEqual(error.callCount(), 1, 'error should be called'); + assert.deepStrictEqual( + error.calls[0].arguments, + [errorToLog], + 'error should be logged', + ); + assert.strictEqual( + stats.toString.mock.callCount(), + 0, + 'stats.toString should not be called', + ); + }); + + const matrix = { + colorDepth: [undefined, 1, 4, 8, 24] as const, + level: ['normal', 'none'] as const, + env: ['development', 'production'] as const, + hasErrors: [true, false] as const, + hasWarnings: [true, false] as const, + }; + + generateCases(matrix).forEach(runTest); + + function runTest(settings: Combination) { + const { colorDepth, level, env, hasErrors, hasWarnings } = settings; + + let testHelpers: typeof import('../utils/helpers'); + const originalGetColorDepth = process.stderr.getColorDepth; + beforeEach(() => { + // getColorDepth is undefined sometimes, so we need to mock it like this + process.stderr.getColorDepth = ( + colorDepth ? mock.fn(() => colorDepth) : colorDepth + ) as (env?: object | undefined) => number; + + // helpers caches `getColorDepth` on initialization, so we need to a new + // one after we mock `getColorDepth`. + delete require.cache[require.resolve('../utils/helpers')]; + testHelpers = require('../utils/helpers'); + }); + + afterEach(() => { + process.stderr.getColorDepth = originalGetColorDepth; + }); + + it(`should log message when stats is "${level}" and env is "${env}", with errors: \`${hasErrors}\` and warnings: \`${hasWarnings}\``, () => { + const stats = getStatsMock(level, env, hasErrors, hasWarnings); + const { mock: error } = mock.method(console, 'error', testHelpers.noop); + + testHelpers.logStats(null, stats); // <- this is what we are testing + + assert.strictEqual(error.callCount(), 1, 'error should be called once'); + + let toStringOptions: StatsOptions | undefined; + if (level === 'normal') { + toStringOptions = { colors: testHelpers.colors }; + } else if (hasErrors || hasWarnings) { + toStringOptions = { + colors: testHelpers.colors, + preset: 'errors-warnings', + }; + } + if (toStringOptions) { + assert.strictEqual( + stats.toString.mock.callCount(), + 1, + 'stats.toString should be called once', + ); + assert.deepStrictEqual( + stats.toString.mock.calls[0].arguments, + [toStringOptions], + 'stats should be called with the colors option', + ); + assert.deepStrictEqual( + error.calls[0].arguments, + [stats.toString(toStringOptions)], + 'stats should be logged', + ); + } else { + assert.strictEqual( + stats.toString.mock.callCount(), + 0, + 'stats.toString should not be called', + ); + const colorFn = + env === 'production' ? testHelpers.toOrange : testHelpers.toPurple; + const name = colorFn(`🦊 ${stats.compilation.compiler.name}`); + const status = testHelpers.toGreen('successfully'); + const time = stats.endTime - stats.startTime; + const expectedMessage = `${name} (webpack ${version}) compiled ${status} in ${time} ms`; + assert.deepStrictEqual(error.calls[0].arguments, [expectedMessage]); + } + }); + } + }); +}); diff --git a/development/webpack/test/helpers.ts b/development/webpack/test/helpers.ts new file mode 100644 index 000000000000..b57ec7b46a6e --- /dev/null +++ b/development/webpack/test/helpers.ts @@ -0,0 +1,115 @@ +import { mock } from 'node:test'; +import { + sources, + type Compiler, + type Chunk, + type WebpackOptionsNormalized, + type Asset, + type Compilation, +} from 'webpack'; + +const { SourceMapSource, RawSource } = sources; + +type Assets = { [k: string]: unknown }; + +export type Combination = { + [P in keyof T]: T[P] extends readonly (infer U)[] ? U : never; +}; + +export function generateCases(obj: T): Combination[] { + return Object.entries(obj).reduce( + (acc, [key, value]) => { + return acc.flatMap((cases) => + value.map((cas: unknown) => ({ ...cases, [key]: cas })), + ); + }, + [{} as Combination], + ); +} + +export function mockWebpack( + files: string[], + contents: (string | Buffer)[], + maps: (string | null)[], + devtool: 'source-map' | 'hidden-source-map' | false = 'source-map', +) { + const assets = files.reduce((acc, name, i) => { + const source = contents[i]; + const map = maps?.[i]; + const webpackSource = map + ? new SourceMapSource(source, name, map) + : new RawSource(source); + acc[name] = { + name, + info: { + size: webpackSource.size(), + }, + source: webpackSource, + }; + return acc; + }, {} as Record); + let done: () => void; + const promise = new Promise((resolve) => { + done = resolve; + }); + const compilation = { + get assets() { + return Object.fromEntries( + Object.entries(assets).map(([name, asset]) => [name, asset.source]), + ); + }, + emitAsset: mock.fn((name, source, info) => { + assets[name] = { + name, + info, + source, + }; + }), + options: { + devtool, + } as unknown as WebpackOptionsNormalized, + chunks: new Set([ + { + files: new Set(Object.keys(assets)), + } as Chunk, + ]), + getAsset: mock.fn((name) => assets[name]), + updateAsset: mock.fn( + (name: string, fn: (source: sources.Source) => sources.Source) => { + return fn(assets[name].source); + }, + ), + deleteAsset: mock.fn((name: string) => { + delete assets[name]; + }), + hooks: { + processAssets: { + async tapPromise(_: unknown, fn: (assets: Assets) => Promise) { + await fn(compilation.assets); + done(); + }, + tap(_: unknown, fn: (assets: Assets) => void) { + fn(compilation.assets); + done(); + }, + }, + }, + }; + const compiler = { + hooks: { + compilation: { + tap(_: unknown, fn: (compilation: Compilation) => void) { + fn(compilation as unknown as Compilation); + }, + }, + }, + webpack: { + sources: { SourceMapSource, RawSource }, + }, + } as Compiler; + return { + compiler, + compilation: compilation as Compilation & typeof compilation, + promise, + }; +} diff --git a/development/webpack/test/loaders.codeFenceLoader.test.ts b/development/webpack/test/loaders.codeFenceLoader.test.ts new file mode 100644 index 000000000000..a8a4dda59508 --- /dev/null +++ b/development/webpack/test/loaders.codeFenceLoader.test.ts @@ -0,0 +1,100 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert'; +import { LoaderContext } from 'webpack'; +import { FeatureLabels } from '@metamask/build-utils'; +import codeFenceLoader, { + getCodeFenceLoader, + CodeFenceLoaderOptions, +} from '../utils/loaders/codeFenceLoader'; + +describe('codeFenceLoader', () => { + type CallbackArgs = Parameters< + LoaderContext['callback'] + >; + + function generateData({ omitFeature }: { omitFeature: boolean }) { + const featureLabel = 'feature-label'; + const fencedSource = `///: BEGIN:ONLY_INCLUDE_IF(${featureLabel}) +console.log('I am Groot.'); +///: END:ONLY_INCLUDE_IF`; + const source = ` +console.log('I am Groot.'); +${fencedSource} +console.log('I am Groot.'); +`; + const expected = omitFeature + ? source.replace(`${fencedSource}\n`, '') + : source; + + let resolveCallback: (value: CallbackArgs) => void; + const mockContext = { + getOptions: () => { + return { + features: { + active: new Set(omitFeature ? [] : [featureLabel]), + all: new Set([featureLabel]), + }, + }; + }, + resourcePath: '', + callback: (...args: CallbackArgs) => resolveCallback(args), + } as unknown as LoaderContext; + const deferredPromise = new Promise((resolve) => { + resolveCallback = resolve; + }); + mockContext.callback = mockContext.callback.bind(mockContext); + return { context: mockContext, source, expected, deferredPromise }; + } + + [false, true].forEach((omitFeature) => { + it(`should ${omitFeature ? '' : 'not '}remove source when feature is ${ + omitFeature ? 'not ' : '' + }active`, async () => { + const data = generateData({ omitFeature }); + const returnValue = codeFenceLoader.call(data.context, data.source); + + assert.strictEqual(returnValue, undefined, 'should return undefined'); + const [err, content] = await data.deferredPromise; + assert.strictEqual(err, null); + assert.strictEqual(content, data.expected); + }); + }); + + it('should throw an error when options are invalid', () => { + const data = generateData({ omitFeature: false }); + data.context.getOptions = () => { + // invalid options + return {} as unknown as CodeFenceLoaderOptions; + }; + assert.throws( + () => codeFenceLoader.call(data.context, data.source), + /Invalid configuration object/u, + ); + }); + + it('should return an error when code fences are invalid', async () => { + const data = generateData({ omitFeature: false }); + data.source = '///: BEGIN:ONLY_INCLUDE_IF\nconsole.log("I am Groot.");\n'; // invalid because there is no end comment + const returnValue = codeFenceLoader.call(data.context, data.source); + assert.strictEqual(returnValue, undefined, 'should return undefined'); + const [err, content] = await data.deferredPromise; + assert(err); + assert.deepStrictEqual( + err.message, + 'Invalid code fence parameters in file "":\nNo parameters specified.', + ); + assert.strictEqual(content, undefined); + }); + + describe('getCodeFenceLoader', () => { + it('should return a loader with correct properties', () => { + const features: FeatureLabels = { active: new Set(), all: new Set() }; + const result = getCodeFenceLoader(features); + + assert.deepStrictEqual(result, { + loader: require.resolve('../utils/loaders/codeFenceLoader'), + options: { features }, + }); + }); + }); +}); diff --git a/development/webpack/test/loaders.swcLoader.test.ts b/development/webpack/test/loaders.swcLoader.test.ts new file mode 100644 index 000000000000..4e2e787c0e73 --- /dev/null +++ b/development/webpack/test/loaders.swcLoader.test.ts @@ -0,0 +1,132 @@ +import { describe, it, afterEach } from 'node:test'; +import assert from 'node:assert'; +import { LoaderContext } from 'webpack'; +import swcLoader, { + type SwcLoaderOptions, + type SwcConfig, +} from '../utils/loaders/swcLoader'; +import { Combination, generateCases } from './helpers'; + +describe('swcLoader', () => { + type CallbackArgs = Parameters['callback']>; + + function generateData() { + const source = ` export function hello(message: string) { + console.log(message) +}; `; + const expected = `export function hello(message) { + console.log(message); +} +`; + + // swc doesn't use node's fs module, so we can't mock + const resourcePath = 'test.ts'; + + let resolveCallback: (value: CallbackArgs) => void; + const mockContext = { + mode: 'production', + sourceMap: true, + getOptions: () => { + return {}; + }, + resourcePath, + async: () => { + return (...args: CallbackArgs) => { + resolveCallback(args); + }; + }, + } as unknown as LoaderContext; + const deferredPromise = new Promise((resolve) => { + resolveCallback = resolve; + }); + mockContext.async = mockContext.async.bind(mockContext); + return { context: mockContext, source, expected, deferredPromise }; + } + + it('should transform code', async () => { + const { context, source, deferredPromise, expected } = generateData(); + const returnValue = swcLoader.call(context, source); + + assert.strictEqual(returnValue, undefined, 'should return undefined'); + const [err, content, map] = await deferredPromise; + assert.strictEqual(err, null); + assert.strictEqual(content, expected); + const mapObj = JSON.parse(map as string); + assert.deepStrictEqual(mapObj.sources, [context.resourcePath]); + }); + + it('should throw an error when options are invalid', () => { + const { context, source } = generateData(); + context.getOptions = () => { + return { + invalid: true, + } as unknown as SwcLoaderOptions; + }; + assert.throws( + () => swcLoader.call(context, source), + /[ValidationError]: Invalid configuration object/u, + ); + }); + + it('should return an error when code is invalid', async () => { + const { context, deferredPromise } = generateData(); + const brokenSource = 'this is not real code;'; + swcLoader.call(context, brokenSource); + const [err, content, map] = await deferredPromise; + assert(err); + assert.match(err.message, /Syntax Error/u); + assert.strictEqual(content, undefined); + assert.strictEqual(map, undefined); + }); + + describe('getSwcLoader', () => { + const matrix = { + syntax: ['typescript', 'ecmascript'] as const, + enableJsx: [true, false] as const, + watch: [true, false] as const, + isDevelopment: [true, false] as const, + }; + generateCases(matrix).forEach(runTest); + + type TestCase = Combination; + + afterEach(() => { + delete process.env.__HMR_READY__; + }); + function runTest({ syntax, enableJsx, watch, isDevelopment }: TestCase) { + it(`should return a loader with correct properties when syntax is ${syntax}, jsx is ${enableJsx}, watch is ${watch}, and isDevelopment is ${isDevelopment}`, () => { + process.env.__HMR_READY__ = 'true'; + // helpers caches `__HMR_READY__` on initialization, so we need to a new + // one after we mock `process.env.__HMR_READY__`. + delete require.cache[require.resolve('../utils/helpers')]; + delete require.cache[require.resolve('../utils/loaders/swcLoader')]; + const { + getSwcLoader, + }: typeof import('../utils/loaders/swcLoader') = require('../utils/loaders/swcLoader'); + + // note: this test isn't exhaustive of all possible `swcConfig` + // properties; it is mostly intended as sanity check. + const swcConfig: SwcConfig = { + args: { watch }, + safeVariables: {}, + browsersListQuery: '', + isDevelopment, + }; + + const loader = getSwcLoader(syntax, enableJsx, swcConfig); + assert.strictEqual( + loader.loader, + require.resolve('../utils/loaders/swcLoader'), + ); + assert.deepStrictEqual(loader.options.jsc.parser, { + syntax, + [syntax === 'typescript' ? 'tsx' : 'jsx']: enableJsx, + }); + assert.deepStrictEqual(loader.options.jsc.transform.react, { + development: isDevelopment, + refresh: isDevelopment && watch, + }); + }); + } + }); +}); diff --git a/development/webpack/test/plugins.ManifestPlugin.test.ts b/development/webpack/test/plugins.ManifestPlugin.test.ts new file mode 100644 index 000000000000..ff14904bb8d6 --- /dev/null +++ b/development/webpack/test/plugins.ManifestPlugin.test.ts @@ -0,0 +1,284 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert'; +import { join } from 'node:path'; +import { type Compilation } from 'webpack'; +import { ManifestPlugin } from '../utils/plugins/ManifestPlugin'; +import { ZipOptions } from '../utils/plugins/ManifestPlugin/types'; +import { Manifest } from '../utils/helpers'; +import { transformManifest } from '../utils/plugins/ManifestPlugin/helpers'; +import { generateCases, type Combination, mockWebpack } from './helpers'; + +describe('ManifestPlugin', () => { + describe('Plugin', () => { + const matrix = { + zip: [true, false], + files: [ + [ + { + // will be compressed + name: 'filename.js', + source: Buffer.from('console.log(1 + 2);', 'utf8'), + }, + ], + [ + { + // will be compressed + name: 'filename.js', + source: Buffer.from('console.log(1 + 2);', 'utf8'), + }, + { + // will be omitted + name: 'filename.js.map', + source: Buffer.alloc(0), + }, + { + // will not be compressed + name: 'pixel.png', + source: Buffer.from([ + 137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, + 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 55, 110, 249, 36, 0, 0, 0, 10, + 73, 68, 65, 84, 120, 1, 99, 96, 0, 0, 0, 2, 0, 1, 115, 117, 1, 24, + 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130, + ]), + }, + ], + ], + browsers: [['chrome', 'firefox'], ['chrome']] as const, + fixture: ['empty', 'complex'], + description: [null, 'description'], + manifestVersion: [2, 3] as const, + webAccessibleResources: [undefined, ['filename.map.js']], + }; + generateCases(matrix).forEach(runTest); + + type TestCase = Combination; + + function runTest(testCase: TestCase) { + const { + browsers, + fixture, + files, + description, + manifestVersion, + webAccessibleResources, + zip, + } = testCase; + const context = join(__dirname, `fixtures/ManifestPlugin/${fixture}`); + const baseManifest = require(join( + context, + `manifest/v${manifestVersion}`, + '_base.json', + )); + const expectedAssets = getExpectedAssets(zip, browsers, files); + const validateManifest = getValidateManifest(testCase, baseManifest); + + it(`should produce a ${ + zip ? 'zip file' : 'folder' + } for browsers [${browsers.join( + ', ', + )}] using the v${manifestVersion} "${fixture}" manifest, including files [${files + .map((file) => file.name) + .join(', ')}], ${ + description ? 'a description' : 'no description' + }, and ${ + webAccessibleResources ? webAccessibleResources.length : 0 + } web_accessible_resources`, async () => { + const { compiler, compilation, promise } = mockWebpack( + files.map(({ name }) => name), + files.map(({ source }) => source), + files.map(() => null), + ); + compilation.options.context = context; + const manifestPlugin = new ManifestPlugin({ + browsers, + manifest_version: manifestVersion, + version: '1.0.0.0', + versionName: '1.0.0', + description, + web_accessible_resources: webAccessibleResources, + ...getZipOptions(zip), + }); + manifestPlugin.apply(compiler); + await promise; + + assert.deepStrictEqual(Object.keys(compilation.assets), expectedAssets); + validateManifest(compilation as unknown as Compilation); + }); + } + + function getZipOptions( + zip: boolean, + ): ({ zip: true } & ZipOptions) | { zip: false } { + if (zip) { + return { + zip: true, + zipOptions: { + level: 0, + mtime: 1711141205825, + excludeExtensions: ['.map'], + outFilePath: '[browser]/extension.zip', + }, + }; + } + return { + zip: false, + }; + } + + function getExpectedAssets( + zip: boolean, + browsers: readonly string[], + files: { name: string }[], + ) { + const assets: string[] = []; + if (zip) { + browsers.forEach((browser) => { + assets.push(`${browser}/extension.zip`); + }); + } + browsers.forEach((browser) => { + assets.push(`${browser}/manifest.json`); + assets.push(...files.map(({ name }) => `${browser}/${name}`)); + }); + return [...new Set(assets)]; // unique + } + function getValidateManifest(testCase: TestCase, baseManifest: Manifest) { + // Handle case when the output is a zip file + if (testCase.zip) { + return () => { + // Assume the validation is successful, as unzipping and checking contents is skipped + assert.ok(true, 'Zip file creation assumed successful.'); + }; + } + + // Common validation for non-zip outputs, applicable to both manifest versions 2 and 3 + return (compilation: Compilation) => { + testCase.browsers.forEach((browser) => { + const manifest = compilation.assets[`${browser}/manifest.json`]; + const json = JSON.parse(manifest.source().toString()) as Manifest; + + // Validate description, if applicable + if (testCase.description) { + assert( + json.description, + "should have a 'description' in the manifest", + ); + const descMessage = `should have the correct description in ${browser} manifest`; + assert( + json.description.endsWith(testCase.description), + descMessage, + ); + } + + // Validate web accessible resources + let expectedWar: Manifest['web_accessible_resources']; + if (testCase.webAccessibleResources) { + if (baseManifest.manifest_version === 3) { + // Extend expected resources for manifest version 3 + expectedWar = baseManifest.web_accessible_resources || []; + expectedWar = [ + { + // the manifest plugin only supports `` for manifest version 3 + // so we don't test other `matches`. + matches: [''], + resources: [ + ...(expectedWar[0]?.resources || []), + ...testCase.webAccessibleResources, + ], + }, + ]; + } else { + expectedWar = baseManifest.web_accessible_resources || []; + // Keep or extend expected resources for manifest version 2 + expectedWar = [ + ...expectedWar, + ...testCase.webAccessibleResources, + ]; + } + } else { + expectedWar = baseManifest.web_accessible_resources || []; + } + + assert.deepStrictEqual( + json.web_accessible_resources || [], + expectedWar, + "should have the correct 'web_accessible_resources' in the manifest", + ); + }); + }; + } + }); + + describe('should transform the manifest object', () => { + const keep = ['scripts/contentscript.js', 'scripts/inpage.js']; + const argsMatrix = { + lockdown: [true, false], + test: [true, false], + }; + const manifestMatrix = { + content_scripts: [ + undefined, + [], + [{ js: [...keep] }], + [{ js: ['lockdown.js', ...keep] }], + ], + permissions: [undefined, [], ['tabs'], ['something']], + }; + generateCases(argsMatrix).forEach(setupTests); + + function setupTests(args: Combination) { + generateCases(manifestMatrix).forEach(runTest); + + function runTest(baseManifest: Combination) { + const manifest = baseManifest as unknown as chrome.runtime.Manifest; + const hasTabsPermission = (manifest.permissions || []).includes('tabs'); + const transform = transformManifest(args); + + if (args.test && hasTabsPermission) { + it("throws in test mode when manifest already contains 'tabs' permission", () => { + assert(transform, 'transform should be truthy'); + const p = () => { + transform(manifest, 'chrome'); + }; + assert.throws( + p, + /manifest contains 'tabs' already; this transform should be removed./u, + 'should throw when manifest contains tabs already', + ); + }); + } else if (!args.lockdown || args.test) { + it(`works for args.test of ${args.test} and args.lockdown of ${ + args.lockdown + }. Manifest: ${JSON.stringify(manifest)}`, () => { + assert(transform, 'transform should be truthy'); + const transformed = transform(manifest, 'chrome'); + if (args.lockdown) { + assert.deepStrictEqual( + transformed.content_scripts, + manifest.content_scripts, + 'nothing should change in lockdown mode', + ); + } else { + const stripped = manifest.content_scripts?.[0]?.js?.filter( + (js) => js !== 'lockdown.js', + ); + assert.deepStrictEqual( + transformed.content_scripts?.[0]?.js, + stripped, + 'lockdown.js should be removed when not in lockdown mode.', + ); + } + + if (args.test) { + assert.deepStrictEqual( + transformed.permissions, + [...(manifest.permissions || []), 'tabs'], + "manifest should have 'tabs' permission", + ); + } + }); + } + } + } + }); +}); diff --git a/development/webpack/test/plugins.SelfInjectPlugin.test.ts b/development/webpack/test/plugins.SelfInjectPlugin.test.ts new file mode 100644 index 000000000000..3a3ef729eacf --- /dev/null +++ b/development/webpack/test/plugins.SelfInjectPlugin.test.ts @@ -0,0 +1,95 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert'; +import { SelfInjectPlugin } from '../utils/plugins/SelfInjectPlugin'; +import { generateCases, type Combination, mockWebpack } from './helpers'; + +describe('SelfInjectPlugin', () => { + const matrix = { + test: [/\.js$/u, /\.ts$/u] as const, + filename: ['file.js', 'file.ts'], + source: ['console.log(3);'], + // sourceMap generated via https://www.digitalocean.com/community/tools/minify + map: [ + null, + '{"version":3,"file":"file.js","names":["console","log"],"sources":["0"],"mappings":"AAAAA,QAAQC,IAAI"}', + ], + devtool: ['source-map', 'hidden-source-map', false] as const, + }; + + generateCases(matrix).forEach(runTest); + + function runTest({ + test, + filename, + source, + map, + devtool, + }: Combination) { + it(`should produce valid output when test is ${test}, filename is ${filename}, map is ${ + map ? 'available' : 'missing' + }, and devtool is ${devtool}`, () => { + const { compiler, compilation } = mockWebpack( + [filename], + [source], + [map], + devtool, + ); + + const plugin = new SelfInjectPlugin({ test }); + plugin.apply(compiler); + + if (filename.match(test)) { + // we should have matched our file so it should have been updated: + + assert.strictEqual(compilation.updateAsset.mock.callCount(), 1); + const newAsset = compilation.updateAsset.mock.calls[0].result; + assert(newAsset, 'newAsset should be defined'); + const { source: newSource, map: newMap } = newAsset.sourceAndMap(); + + // `newMap` should be `null` here, because the file has been transformed + // to be self-injecting, so there is no way to map it anymore. + assert.strictEqual(newMap, null); + + if (map !== null && devtool === 'source-map') { + // if we have a map and devtool is `source-map` the newSource should + // reference the `sourceMappingURL` + assert.strictEqual( + newSource, + `{let d=document,s=d.createElement('script');s.textContent="${source}\\n//# sourceMappingURL=${filename}.map"+\`\\n//# sourceURL=\${(globalThis.browser||chrome).runtime.getURL("${filename}")};\`;d.documentElement.appendChild(s).remove()}`, + ); + } else { + // the new source should NOT reference the new sourcemap, since it's + // "hidden" (or we aren't generating source maps at all). Notice that + // we DO still include `sourceURL`, as this aids in debugging + // (and development) gives the injected source a name that will show + // in the console if the source throws an exception or logs to the + // console. + assert.strictEqual( + newSource, + `{let d=document,s=d.createElement('script');s.textContent="console.log(3);"+\`\\n//# sourceURL=\${(globalThis.browser||chrome).runtime.getURL("${filename}")};\`;d.documentElement.appendChild(s).remove()}`, + ); + } + + if (map) { + // If we provided a `map` the source map should have been emitted as + // a separate asset. Note that this happens even when devtool is set + // to `false`, as this means the map file already existed and we + // should not remove it (we don't care how it got there). The devtool + // directive is about whether to generate a new map, not whether to + // emit an existing one. + assert.strictEqual(compilation.emitAsset.mock.callCount(), 1); + const [sourceMapFilename, sourceMapSource] = + compilation.emitAsset.mock.calls[0].arguments; + assert.strictEqual(sourceMapFilename, `${filename}.map`); + assert.strictEqual(sourceMapSource.source(), map); + } + } else { + // we should not have matched our file so there should be no changes + assert.strictEqual(compilation.updateAsset.mock.callCount(), 0); + + // and no new assets should have been emitted + assert.strictEqual(compilation.emitAsset.mock.callCount(), 0); + } + }); + } +}); diff --git a/development/webpack/test/version.test.ts b/development/webpack/test/version.test.ts new file mode 100644 index 000000000000..14a98939df98 --- /dev/null +++ b/development/webpack/test/version.test.ts @@ -0,0 +1,118 @@ +import { describe, it, before } from 'node:test'; +import assert from 'node:assert'; +import { getExtensionVersion } from '../utils/version'; + +describe('getMetaMaskVersion', () => { + const MIN_ID = 10; + const MAX_ID = 64; + const MIN_RELEASE = 0; + const MAX_RELEASE = 999; + + describe('exceptions', () => { + it(`should throw for build with negative id (-1)`, () => { + const test = () => getExtensionVersion('main', { id: -1 }, 0); + assert.throws(test); + }); + + it('should throw for build with an invalid id (0)', () => { + const test = () => getExtensionVersion('main', { id: 0 }, 0); + assert.throws(test); + }); + + it(`should throw for build with an invalid id (${MIN_ID - 1})`, () => { + const test = () => getExtensionVersion('main', { id: MIN_ID - 1 }, 0); + assert.throws(test); + }); + + it(`should throw for build with invalid id (${MAX_ID + 1})`, () => { + const test = () => getExtensionVersion('main', { id: MAX_ID + 1 }, 0); + assert.throws(test); + }); + + it('should throw when computing the version for build with prerelease implicitly disallowed, release version: 1', () => { + const test = () => getExtensionVersion('main', { id: 10 }, 1); + assert.throws(test); + }); + + it('should throw when computing the version for build with prerelease explicitly disallowed, release version: 1', () => { + const test = () => + getExtensionVersion('main', { id: 10, isPrerelease: false }, 1); + assert.throws(test); + }); + + it(`should throw when computing the version for build with prerelease disallowed, release version: ${ + MAX_RELEASE + 1 + }`, () => { + const test = () => + getExtensionVersion('main', { id: 10 }, MAX_RELEASE + 1); + assert.throws(test); + }); + + it(`should throw for allowed prerelease, bad release version: ${ + MIN_RELEASE - 1 + }`, () => { + const test = () => + getExtensionVersion( + 'beta', + { id: 11, isPrerelease: true }, + MIN_RELEASE - 1, + ); + assert.throws(test); + }); + + it(`should throw when computing the version for allowed prerelease, bad release version: ${ + MAX_RELEASE + 1 + }`, () => { + const test = () => + getExtensionVersion( + 'beta', + { id: 11, isPrerelease: true }, + MAX_RELEASE + 1, + ); + assert.throws(test); + }); + }); + + describe('success', () => { + let pVersion: string; + before(() => { + pVersion = require('../../../package.json').version; + }); + + it(`for build with prerelease disallowed, id: ${MIN_ID}, release version: ${MIN_RELEASE}`, () => { + const mmVersion = getExtensionVersion( + 'main', + { id: MIN_ID }, + MIN_RELEASE, + ); + assert.deepStrictEqual(mmVersion, { + version: `${pVersion}.0`, + versionName: pVersion, + }); + }); + + it(`should return the computed version for allowed prerelease, id: ${MIN_ID}, release version: ${MIN_RELEASE}`, () => { + const mmVersion = getExtensionVersion( + 'beta', + { id: MIN_ID, isPrerelease: true }, + MIN_RELEASE, + ); + assert.deepStrictEqual(mmVersion, { + version: `${pVersion}.${MIN_ID}${MIN_RELEASE}`, + versionName: `${pVersion}-beta.${MIN_RELEASE}`, + }); + }); + + it(`should return the computed version for allowed prerelease, id: ${MAX_ID}, release version: ${MAX_RELEASE}`, () => { + const mmVersion = getExtensionVersion( + 'beta', + { id: MAX_ID, isPrerelease: true }, + MAX_RELEASE, + ); + assert.deepStrictEqual(mmVersion, { + version: `${pVersion}.${MAX_ID}${MAX_RELEASE}`, + versionName: `${pVersion}-beta.${MAX_RELEASE}`, + }); + }); + }); +}); diff --git a/development/webpack/test/webpack.config.test.ts b/development/webpack/test/webpack.config.test.ts new file mode 100644 index 000000000000..e1d11f953829 --- /dev/null +++ b/development/webpack/test/webpack.config.test.ts @@ -0,0 +1,307 @@ +import fs from 'node:fs'; +import { describe, it, afterEach, before, after, mock } from 'node:test'; +import assert from 'node:assert'; +import process from 'node:process'; +import { resolve } from 'node:path'; +import { + type Configuration, + webpack, + Compiler, + WebpackPluginInstance, +} from 'webpack'; +import { noop } from '../utils/helpers'; +import { ManifestPlugin } from '../utils/plugins/ManifestPlugin'; +import { getLatestCommit } from '../utils/git'; +import { ManifestPluginOptions } from '../utils/plugins/ManifestPlugin/types'; + +function getWebpackInstance(config: Configuration) { + // webpack logs a warning if we pass config.watch to it without a callback + // we don't want a callback because that will cause the build to run + // so we just delete the watch property. + delete config.watch; + return webpack(config); +} + +/** + * These tests are aimed at testing conditional branches in webpack.config.ts. + * These tests do *not* test the actual webpack build process itself, or that + * the parsed command line args are even valid. Instead, these tests ensure the + * branches of configuration options are reached and applied correctly. + */ + +describe('webpack.config.test.ts', () => { + let originalArgv: string[]; + let originalEnv: NodeJS.ProcessEnv; + const originalReadFileSync = fs.readFileSync; + before(() => { + // cache originals before we start messing with them + originalArgv = process.argv; + originalEnv = process.env; + }); + after(() => { + // restore originals for other tests + process.argv = originalArgv; + process.env = originalEnv; + }); + afterEach(() => { + // reset argv to avoid affecting other tests + process.argv = [process.argv0, process.argv[1]]; + // each test needs to load a fresh config, so we need to clear webpack's cache + // TODO: can we use `await import` instead to get a fresh copy each time? + const cliPath = require.resolve('../utils/cli.ts'); + const helpersPath = require.resolve('../utils/helpers.ts'); + const webpackConfigPath = require.resolve('../webpack.config.ts'); + delete require.cache[cliPath]; + delete require.cache[helpersPath]; + delete require.cache[webpackConfigPath]; + mock.restoreAll(); + }); + + function getWebpackConfig(args: string[] = [], env: NodeJS.ProcessEnv = {}) { + // argv is automatically read when webpack.config is required/imported. + // first two args are always ignored. + process.argv = [...process.argv.slice(0, 2), ...args]; + process.env = { ...env }; + mock.method(fs, 'readFileSync', (path: string, options?: null) => { + if (path === resolve(__dirname, '../../../.metamaskrc')) { + // mock `.metamaskrc`, as users might have customized it which may + // break our tests + return ` +${Object.entries(env) + .map(([key, value]) => `${key}=${value}`) + .join('\n')} +`; + } + return originalReadFileSync.call(fs, path, options); + }); + return require('../webpack.config.ts').default; + } + + it('should have the correct defaults', () => { + const config: Configuration = getWebpackConfig(); + // check that options are valid + const { options } = webpack(config); + assert.strictEqual(options.name, 'MetaMask – development'); + assert.strictEqual(options.mode, 'development'); + assert(options.cache); + assert.strictEqual(options.cache.type, 'filesystem'); + assert.strictEqual(options.devtool, 'source-map'); + const stats = options.stats as { preset: string }; + assert.strictEqual(stats.preset, 'none'); + const fallback = options.resolve.fallback as Record; + assert.strictEqual(typeof fallback['react-devtools'], 'string'); + assert.strictEqual(typeof fallback['remote-redux-devtools'], 'string'); + assert.strictEqual(options.optimization.minimize, false); + assert.strictEqual(options.optimization.sideEffects, false); + assert.strictEqual(options.optimization.providedExports, false); + assert.strictEqual(options.optimization.removeAvailableModules, false); + assert.strictEqual(options.optimization.usedExports, false); + assert.strictEqual(options.watch, false); + + const runtimeChunk = options.optimization.runtimeChunk as + | { + name?: (chunk: { name?: string }) => string | false; + } + | undefined; + assert(runtimeChunk); + assert(runtimeChunk.name); + assert(typeof runtimeChunk.name, 'function'); + assert.strictEqual( + runtimeChunk.name({ name: 'snow.prod' }), + false, + 'snow.prod should not be chunked', + ); + assert.strictEqual( + runtimeChunk.name({ name: 'use-snow' }), + false, + 'use-snow should not be chunked', + ); + assert.strictEqual( + runtimeChunk.name({ name: '< random >' }), + 'runtime', + 'other names should be chunked', + ); + assert.strictEqual( + runtimeChunk.name({}), + 'runtime', + 'chunks without a name name should be chunked', + ); + + const manifestPlugin = options.plugins.find( + (plugin) => plugin && plugin.constructor.name === 'ManifestPlugin', + ) as ManifestPlugin; + assert(manifestPlugin, 'Manifest plugin should be present'); + assert.deepStrictEqual(manifestPlugin.options.web_accessible_resources, [ + 'scripts/inpage.js.map', + 'scripts/contentscript.js.map', + ]); + assert.deepStrictEqual( + manifestPlugin.options.description, + `development build from git id: ${getLatestCommit().hash()}`, + ); + assert(manifestPlugin.options.transform); + assert.deepStrictEqual( + manifestPlugin.options.transform( + { + manifest_version: 3, + name: 'name', + version: '1.2.3', + content_scripts: [ + { + js: [ + 'ignored', + 'scripts/contentscript.js', + 'scripts/inpage.js', + 'ignored', + ], + }, + ], + }, + 'brave', + ), + { + manifest_version: 3, + name: 'name', + version: '1.2.3', + content_scripts: [ + { + js: ['scripts/contentscript.js', 'scripts/inpage.js'], + }, + ], + }, + ); + assert.strictEqual(manifestPlugin.options.zip, false); + const manifestOpts = manifestPlugin.options as ManifestPluginOptions; + assert.strictEqual(manifestOpts.zipOptions, undefined); + + const progressPlugin = options.plugins.find( + (plugin) => plugin && plugin.constructor.name === 'ProgressPlugin', + ); + assert(progressPlugin, 'Progress plugin should present'); + }); + + it('should apply non-default options', () => { + const removeUnsupportedFeatures = ['--no-lavamoat']; + const config: Configuration = getWebpackConfig( + [ + '--env', + 'production', + '--watch', + '--stats', + '--no-progress', + '--no-cache', + '--zip', + ...removeUnsupportedFeatures, + ], + { + INFURA_PROD_PROJECT_ID: '00000000000000000000000000000000', + SEGMENT_WRITE_KEY: '-', + SEGMENT_PROD_WRITE_KEY: '-', + }, + ); + // webpack logs a warning if we specify `watch: true`, `getWebpackInstance` + // removes the property, so we test it here instead + assert.strictEqual(config.watch, true); + + // check that options are valid + const instance: Compiler = getWebpackInstance(config); + assert.strictEqual(instance.options.name, 'MetaMask – production'); + assert.strictEqual(instance.options.mode, 'production'); + assert.ok(instance.options.cache); + assert.strictEqual(instance.options.cache.type, 'memory'); + assert.strictEqual(instance.options.devtool, 'hidden-source-map'); + const stats = instance.options.stats as { preset: string }; + assert.strictEqual(stats.preset, 'normal'); + const fallback = instance.options.resolve.fallback as Record; + assert.strictEqual(fallback['react-devtools'], false); + assert.strictEqual(fallback['remote-redux-devtools'], false); + assert.strictEqual(instance.options.optimization.minimize, true); + assert.strictEqual(instance.options.optimization.sideEffects, true); + assert.strictEqual(instance.options.optimization.providedExports, true); + assert.strictEqual( + instance.options.optimization.removeAvailableModules, + true, + ); + assert.strictEqual(instance.options.optimization.usedExports, true); + + const manifestPlugin = instance.options.plugins.find( + (plugin) => plugin && plugin.constructor.name === 'ManifestPlugin', + ) as WebpackPluginInstance; + assert.deepStrictEqual(manifestPlugin.options.web_accessible_resources, []); + assert.deepStrictEqual(manifestPlugin.options.description, null); + assert.deepStrictEqual(manifestPlugin.options.zip, true); + assert(manifestPlugin.options.zipOptions, 'Zip options should be present'); + assert.strictEqual(manifestPlugin.options.transform, undefined); + + const progressPlugin = instance.options.plugins.find( + (plugin) => plugin && plugin.constructor.name === 'ProgressPlugin', + ); + assert.strictEqual( + progressPlugin, + undefined, + 'Progress plugin should be absent', + ); + }); + + it('should allow disabling source maps', () => { + const config: Configuration = getWebpackConfig(['--devtool', 'none']); + // check that options are valid + const instance = getWebpackInstance(config); + assert.strictEqual(instance.options.devtool, false); + }); + + it('should write the `dry-run` message then call exit(0)', () => { + const exit = mock.method(process, 'exit', noop, { times: 1 }); + const error = mock.method(console, 'error', noop, { times: 1 }); + + // we don't care about the return value, just that it logs and calls `exit` + getWebpackConfig(['--dry-run']); + assert.strictEqual(error.mock.calls.length, 1); + assert.strictEqual(error.mock.calls[0].arguments.length, 1); + // we don't care about the message, just that it is logged + assert.strictEqual(typeof error.mock.calls[0].arguments[0], 'string'); + + assert.strictEqual(exit.mock.calls.length, 1); + assert.strictEqual(exit.mock.calls[0].arguments.length, 1); + assert.strictEqual(exit.mock.calls[0].arguments[0], 0); + }); + + it('should write the `dryRun` message then call exit(0)', () => { + const exit = mock.method(process, 'exit', noop, { times: 1 }); + const error = mock.method(console, 'error', noop, { times: 1 }); + + // we don't care about the return value, just that it logs and calls `exit` + getWebpackConfig(['--dryRun']); + assert.strictEqual(error.mock.calls.length, 1); + assert.strictEqual(error.mock.calls[0].arguments.length, 1); + // we don't care about the message, just that it is logged + assert.strictEqual(typeof error.mock.calls[0].arguments[0], 'string'); + + assert.strictEqual(exit.mock.calls.length, 1); + assert.strictEqual(exit.mock.calls[0].arguments.length, 1); + assert.strictEqual(exit.mock.calls[0].arguments[0], 0); + }); + + it('should enable ReactRefreshPlugin in a development env when `--watch` is specified', () => { + const config: Configuration = getWebpackConfig(['--watch'], { + __HMR_READY__: 'true', + }); + delete config.watch; + const instance = webpack(config); + const reactRefreshPlugin = instance.options.plugins.find( + (plugin) => plugin && plugin.constructor.name === 'ReactRefreshPlugin', + ); + assert(reactRefreshPlugin, 'ReactRefreshPlugin should be present'); + }); + + // these tests should be temporary until the below options are supported + const unsupportedOptions = [['--lavamoat'], ['--manifest_version', '3']]; + for (const args of unsupportedOptions) { + it(`should throw on unsupported option \`${args.join('=')}\``, () => { + assert.throws( + () => getWebpackConfig(args), + `Unsupported option: ${args.join(' ')}`, + ); + }); + } +}); diff --git a/development/webpack/types.ts b/development/webpack/types.ts new file mode 100644 index 000000000000..f2739861246b --- /dev/null +++ b/development/webpack/types.ts @@ -0,0 +1,34 @@ +import type { ChildProcess } from 'node:child_process'; +import { type Readable } from 'node:stream'; +import { type Socket } from 'node:net'; +import { type IPty } from '@lydell/node-pty'; + +/** + * A more complete type for the `node-pty` module's `IPty` interface + */ +export type PTY = IPty & { + master: Socket; + slave: Socket; +}; + +/** + * Node's ChildProcess type extended with `stderr` and `stdout`'s `unref` + * method, which is missing from the standard Node.js types. + */ +export type Child = ChildProcess & { + stderr: Readable & { unref: () => Readable }; + stdout: Readable & { unref: () => Readable }; +}; + +export type StdName = 'stdout' | 'stderr'; + +/** + * The control interface for a child process's stdio streams. + */ +export type Stdio = { + destroy: () => void; + listen: (child: Child) => void; + pty: Socket | 'pipe'; + resize: () => void; + unref: (child: Child) => void; +}; diff --git a/development/webpack/utils/cli.ts b/development/webpack/utils/cli.ts new file mode 100644 index 000000000000..05e18f8cf3b8 --- /dev/null +++ b/development/webpack/utils/cli.ts @@ -0,0 +1,391 @@ +/** + * @file This file contains the CLI parser for the webpack build script. + * It is responsible for parsing the command line arguments and returning a + * structured object representing the parsed arguments. + */ + +import type { Options as YargsOptions } from 'yargs'; +import yargs from 'yargs/yargs'; +import parser from 'yargs-parser'; +import { + Browsers, + type Manifest, + type Browser, + uniqueSort, + toOrange, +} from './helpers'; +import { type BuildConfig } from './config'; + +const ENV_PREFIX = 'BUNDLE'; +const addFeat = 'addFeature' as const; +const omitFeat = 'omitFeature' as const; +type YargsOptionsMap = { [key: string]: YargsOptions }; +type OptionsKeys = keyof Omit; + +/** + * Some options affect the default values of other options. + */ +const prerequisites = { + env: { + alias: 'e', + array: false, + default: 'development' as const, + description: 'Enables/disables production optimizations/development hints', + choices: ['development', 'production'] as const, + group: toOrange('Build options:'), + type: 'string', + }, + // `as const` makes it easier for developers to see the values of the type + // when hovering over it in their IDE. `satisfies Options` enables type + // checking, without loosing the `const` property of the values, which is + // necessary for yargs to infer the final types +} as const satisfies YargsOptionsMap; + +/** + * Parses the given args from `argv` and returns whether or not the requested + * build is a production build or not. + * + * @param argv + * @param opts + * @returns `true` if this is a production build, otherwise `false` + */ +function preParse( + argv: string[], + opts: typeof prerequisites, +): { env: 'production' | 'development' } { + const options: { [k: string]: { [k: string]: unknown } } = { + configuration: { + envPrefix: ENV_PREFIX, + }, + }; + // convert the `opts` object into a format that `yargs-parser` can understand + for (const [arg, val] of Object.entries(opts)) { + for (const [key, valEntry] of Object.entries(val)) { + if (!options[key]) { + options[key] = {}; + } + options[key][arg] = valEntry; + } + } + + const { env } = parser(argv, options); + return { env }; +} + +/** + * Type representing the parsed arguments + */ +export type Args = ReturnType['args']; +export type Features = ReturnType['features']; + +/** + * Parses an array of command line arguments into a structured format. + * + * @param argv - An array of command line arguments, excluding the program + * executable and file name. Typically used as + * `parseArgv(process.argv.slice(2))`. + * @param buildConfig - The build config. + * @param buildConfig.buildTypes - The build types. + * @param buildConfig.features - The features. + * @returns An object representing the parsed arguments. + */ +export function parseArgv( + argv: string[], + { buildTypes, features }: BuildConfig, +) { + const allBuildTypeNames = Object.keys(buildTypes); + const allFeatureNames = Object.keys(features); + + // args like `production` may change our CLI defaults, so we pre-parse them + const preconditions = preParse(argv, prerequisites); + const options = getOptions(preconditions, allBuildTypeNames, allFeatureNames); + const args = getCli(options, 'yarn webpack').parseSync(argv); + // the properties `$0` and `_` are added by yargs, but we don't need them. We + // transform `add` and `omit`, so we also remove them from the config object. + const { $0, _, addFeature: add, omitFeature: omit, ...config } = args; + + // set up feature flags + const active = new Set(); + const defaultFeaturesForBuildType = buildTypes[config.type].features ?? []; + const setActive = (f: string) => omit.includes(f) || active.add(f); + [defaultFeaturesForBuildType, add].forEach((feat) => feat.forEach(setActive)); + + const ignore = new Set(['$0', 'conf', 'progress', 'stats', 'watch']); + const cacheKey = Object.entries(args) + .filter(([key]) => key.length > 1 && !ignore.has(key) && !key.includes('-')) + .sort(([x], [y]) => x.localeCompare(y)); + return { + // narrow the `config` type to only the options we're returning + args: config as { [key in OptionsKeys]: (typeof config)[key] }, + cacheKey: JSON.stringify(cacheKey), + features: { + active, + all: new Set(allFeatureNames), + }, + }; +} + +/** + * Gets a yargs instance for parsing CLI arguments. + * + * @param options + * @param name + */ +function getCli(options: T, name: string) { + const cli = yargs() + // Ensure unrecognized commands/options are reported as errors. + .strict() + // disable yargs's version, as we use it ourselves + .version(false) + // use the scriptName in `--help` output + .scriptName(name) + // wrap output at a maximum of 120 characters or `process.stdout.columns` + .wrap(Math.min(120, process.stdout.columns)) + // enable the `--config` command, which allows the user to specify a custom + // config file containing webpack options + .config() + .parserConfiguration({ + 'strip-aliased': true, + 'strip-dashed': true, + }) + // enable ENV parsing, which allows the user to specify webpack options via + // environment variables prefixed with `BUNDLE_` + // TODO: choose a better name than `BUNDLE` (it looks like `MM` is already being used in CI for ✨something✨) + .env(ENV_PREFIX) + // TODO: enable completion once https://github.com/yargs/yargs/pull/2422 is released. + // enable the `completion` command, which outputs a bash completion script + // .completion( + // 'completion', + // 'Enable bash/zsh completions; concat the script generated by running this command to your .bashrc or .bash_profile', + // ) + .example( + '$0 --env development --browser brave --browser chrome --zip', + 'Builds the extension for development for Chrome & Brave; generate zip files for both', + ) + // TODO: enable completion once https://github.com/yargs/yargs/pull/2422 is released. + // .example( + // '$0 completion', + // `Generates a bash completion script for the \`${name}\` command`, + // ) + .updateStrings({ + 'Options:': toOrange('Options:'), + 'Examples:': toOrange('Examples:'), + }) + .options(options); + return cli; +} + +type Options = ReturnType; + +function getOptions( + { env }: ReturnType, + buildTypes: string[], + allFeatures: string[], +) { + const isProduction = env === 'production'; + const prodDefaultDesc = "If `env` is 'production', `true`, otherwise `false`"; + return { + watch: { + alias: 'w', + array: false, + default: false, + description: 'Build then watch for files changes', + group: toOrange('Developer assistance:'), + type: 'boolean', + }, + cache: { + alias: 'c', + array: false, + default: true, + description: 'Cache build for faster rebuilds', + group: toOrange('Developer assistance:'), + type: 'boolean', + }, + progress: { + alias: 'p', + array: false, + default: true, + description: 'Show build progress', + group: toOrange('Developer assistance:'), + type: 'boolean', + }, + devtool: { + alias: 'd', + array: false, + default: isProduction ? 'hidden-source-map' : 'source-map', + defaultDescription: + "If `env` is 'production', 'hidden-source-map', otherwise 'source-map'", + description: 'Sourcemap type to generate', + choices: ['none', 'source-map', 'hidden-source-map'] as const, + group: toOrange('Developer assistance:'), + type: 'string', + }, + sentry: { + array: false, + default: isProduction, + defaultDescription: prodDefaultDesc, + description: 'Enables/disables Sentry Application Monitoring', + group: toOrange('Developer assistance:'), + type: 'boolean', + }, + test: { + array: false, + default: false, + description: 'Enables/disables testing mode', + group: toOrange('Developer assistance:'), + type: 'boolean', + }, + + ...prerequisites, + zip: { + alias: 'z', + array: false, + default: false, + description: 'Generate a zip file of the build', + group: toOrange('Build options:'), + type: 'boolean', + }, + minify: { + alias: 'm', + array: false, + default: isProduction, + defaultDescription: "If `env` is 'production', `true`, otherwise `false`", + description: 'Minify the output', + group: toOrange('Build options:'), + type: 'boolean', + }, + browser: { + alias: 'b', + array: true, + choices: ['all', ...Browsers], + coerce: (browsers: (Browser | 'all')[]) => { + type OneOrMoreBrowsers = [Browser, ...Browser[]]; + // sort browser for determinism (important for caching) + const set = new Set(browsers.sort()); + return (set.has('all') ? [...Browsers] : [...set]) as OneOrMoreBrowsers; + }, + default: 'chrome', + description: 'Browsers to build for', + group: toOrange('Build options:'), + type: 'string', + }, + manifest_version: { + alias: 'v', + array: false, + choices: [2, 3] as Manifest['manifest_version'][], + default: 2 as Manifest['manifest_version'], + description: "Changes manifest.json format to the given version's schema", + group: toOrange('Build options:'), + type: 'number', + }, + releaseVersion: { + alias: 'r', + array: false, + default: 0, + description: + 'The (pre)release version of the extension, e.g., the `6` in `18.7.25-flask.6`.', + group: toOrange('Build options:'), + type: 'number', + }, + type: { + alias: 't', + array: false, + choices: ['none', ...buildTypes], + default: 'main' as const, + description: 'Configure features for the build (main, beta, etc)', + group: toOrange('Build options:'), + type: 'string', + }, + [addFeat]: { + alias: 'a', + array: true, + choices: allFeatures, + coerce: uniqueSort, + default: [] as typeof allFeatures, + description: 'Add features not be included in the selected build `type`', + group: toOrange('Build options:'), + type: 'string', + }, + [omitFeat]: { + alias: 'o', + array: true, + choices: allFeatures, + coerce: uniqueSort, + default: [] as typeof allFeatures, + description: 'Omit features included in the selected build `type`', + group: toOrange('Build options:'), + type: 'string', + }, + + lavamoat: { + alias: 'l', + array: false, + default: isProduction, + defaultDescription: prodDefaultDesc, + description: 'Apply LavaMoat to the build assets', + group: toOrange('Security:'), + type: 'boolean', + }, + lockdown: { + alias: 'k', + array: false, + default: isProduction, + defaultDescription: prodDefaultDesc, + description: 'Enable/disable runtime hardening (also see --snow)', + group: toOrange('Security:'), + type: 'boolean', + }, + snow: { + alias: 's', + array: false, + default: isProduction, + defaultDescription: prodDefaultDesc, + description: 'Apply Snow to the build assets', + group: toOrange('Security:'), + type: 'boolean', + }, + + dryRun: { + array: false, + default: false, + description: 'Outputs the config without building', + group: toOrange('Options:'), + type: 'boolean', + }, + stats: { + array: false, + default: false, + description: 'Display build stats after building', + group: toOrange('Options:'), + type: 'boolean', + }, + } as const satisfies YargsOptionsMap; +} + +/** + * Returns a string representation of the given arguments and features. + * + * @param args + * @param features + */ +export function getDryRunMessage(args: Args, features: Features) { + return `🦊 Build Config 🦊 + +Environment: ${args.env} +Minify: ${args.minify} +Watch: ${args.watch} +Cache: ${args.cache} +Progress: ${args.progress} +Zip: ${args.zip} +Snow: ${args.snow} +LavaMoat: ${args.lavamoat} +Lockdown: ${args.lockdown} +Manifest version: ${args.manifest_version} +Release version: ${args.releaseVersion} +Browsers: ${args.browser.join(', ')} +Devtool: ${args.devtool} +Build type: ${args.type} +Features: ${[...features.active].join(', ')} +Test: ${args.test} +`; +} diff --git a/development/webpack/utils/config.ts b/development/webpack/utils/config.ts new file mode 100644 index 000000000000..49f4f9e97ba4 --- /dev/null +++ b/development/webpack/utils/config.ts @@ -0,0 +1,221 @@ +import { join } from 'node:path'; +import { readFileSync } from 'node:fs'; +import { parse as parseYaml } from 'yaml'; +import { parse } from 'dotenv'; +import { setEnvironmentVariables } from '../../build/set-environment-variables'; +import type { Variables } from '../../lib/variables'; +import { type Args } from './cli'; +import { getExtensionVersion } from './version'; + +const BUILDS_YML_PATH = join(__dirname, '../../../builds.yml'); + +/** + * Coerce `"true"`, `"false"`, and `"null"` to their respective JavaScript + * values. Coerce the empty string (`""`) to `undefined`; + * + * @param value + * @returns + */ +function coerce(value: string) { + if (value === 'true') return true; + if (value === 'false') return false; + if (value === 'null') return null; + if (value === '') return null; + return value; +} + +/** + * @returns The definitions loaded from process.env. + */ +function loadEnv(): Map { + const definitions = new Map(); + Object.entries(process.env).forEach(([key, value]) => { + if (typeof value === 'undefined') return; + definitions.set(key, coerce(value)); + }); + return definitions; +} + +/** + * @param definitions + * @param rcFilePath - The path to the rc file. + */ +function addRc(definitions: Map, rcFilePath: string): void { + try { + const rc = parse(readFileSync(rcFilePath, 'utf8')); + Object.entries(rc).forEach(([key, value]) => { + if (definitions.has(key)) return; + definitions.set(key, coerce(value)); + }); + } catch { + // ignore + } +} + +/** + * Get the name for the current build. + * + * @param type + * @param build + * @param isDev + * @param args + */ +export function getBuildName( + type: string, + build: BuildType, + isDev: boolean, + args: Pick, +) { + const buildName = + build.buildNameOverride || + `MetaMask ${type.slice(0, 1).toUpperCase()}${type.slice(1)}`; + if (isDev) { + const mv3Str = args.manifest_version === 3 ? ' MV3' : ''; + const lavamoatStr = args.lavamoat ? ' lavamoat' : ''; + const snowStr = args.snow ? ' snow' : ''; + const lockdownStr = args.lockdown ? ' lockdown' : ''; + return `${buildName}${mv3Str}${lavamoatStr}${snowStr}${lockdownStr}`; + } + return buildName; +} + +/** + * Computes the `variables` (extension runtime's `process.env.*`). + * + * @param args + * @param args.type + * @param args.test + * @param args.env + * @param buildConfig + */ +export function getVariables( + { type, env, ...args }: Args, + buildConfig: BuildConfig, +) { + const activeBuild = buildConfig.buildTypes[type]; + const variables = loadConfigVars(activeBuild, buildConfig); + const version = getExtensionVersion(type, activeBuild, args.releaseVersion); + const isDevBuild = env === 'development'; + + function set(key: string, value: unknown): void; + function set(key: Record): void; + function set(key: string | Record, value?: unknown): void { + if (typeof key === 'object') { + Object.entries(key).forEach(([k, v]) => variables.set(k, v)); + } else { + variables.set(key, value); + } + } + + // use the gulp-build's function to set the environment variables + setEnvironmentVariables({ + buildName: getBuildName(type, activeBuild, isDevBuild, args), + buildType: type, + environment: env, + isDevBuild, + isTestBuild: args.test, + version: version.versionName, + variables: { + set, + isDefined(key: string): boolean { + return variables.has(key); + }, + get(key: string): unknown { + return variables.get(key); + }, + getMaybe(key: string): unknown { + return variables.get(key); + }, + } as Variables, + }); + + // variables that are used in the webpack build's entry points. Our runtime + // code checks for the _string_ `"true"`, so we cast to string here. + variables.set('ENABLE_SENTRY', args.sentry.toString()); + variables.set('ENABLE_SNOW', args.snow.toString()); + variables.set('ENABLE_LAVAMOAT', args.lavamoat.toString()); + variables.set('ENABLE_LOCKDOWN', args.lockdown.toString()); + + // convert the variables to a format that can be used by SWC, which expects + // values be JSON stringified, as it JSON.parses them internally. + const safeVariables: Record = {}; + variables.forEach((value, key) => { + if (value === null || value === undefined) return; + safeVariables[key] = JSON.stringify(value); + }); + + // special location for the PPOM_URI, as we don't want to copy the wasm file + // to the build directory like the gulp build does + variables.set( + 'PPOM_URI', + `new URL('@blockaid/ppom_release/ppom_bg.wasm', import.meta.url)`, + ); + // the `PPOM_URI` shouldn't be JSON stringified, as it's actually code + safeVariables.PPOM_URI = variables.get('PPOM_URI') as string; + + return { variables, safeVariables, version }; +} + +export type BuildType = { + id: number; + features?: string[]; + env?: (string | { [k: string]: unknown })[]; + isPrerelease?: boolean; + buildNameOverride?: string; +}; + +export type BuildConfig = { + buildTypes: Record; + env: (string | Record)[]; + features: Record< + string, + null | { env?: (string | { [k: string]: unknown })[] } + >; +}; + +/** + * + */ +export function getBuildTypes(): BuildConfig { + return parseYaml(readFileSync(BUILDS_YML_PATH, 'utf8')); +} + +/** + * Loads configuration variables from process.env, .metamaskrc, and build.yml. + * + * The order of precedence is: + * 1. process.env + * 2. .metamaskrc + * 3. build.yml + * + * i.e., if a variable is defined in `process.env`, it will take precedence over + * the same variable defined in `.metamaskrc` or `build.yml`. + * + * @param activeBuild + * @param build + * @param build.env + * @param build.features + * @returns + */ +function loadConfigVars( + activeBuild: Pick, + { env, features }: BuildConfig, +) { + const definitions = loadEnv(); + addRc(definitions, join(__dirname, '../../../.metamaskrc')); + addVars(activeBuild.env); + activeBuild.features?.forEach((feature) => addVars(features[feature]?.env)); + addVars(env); + + function addVars(pairs?: (string | Record)[]): void { + pairs?.forEach((pair) => { + if (typeof pair === 'string') return; + Object.entries(pair).forEach(([key, value]) => { + if (definitions.has(key)) return; + definitions.set(key, value); + }); + }); + } + + return definitions; +} diff --git a/development/webpack/utils/git.ts b/development/webpack/utils/git.ts new file mode 100644 index 000000000000..887db184ea01 --- /dev/null +++ b/development/webpack/utils/git.ts @@ -0,0 +1,60 @@ +import { spawnSync } from 'node:child_process'; +import { join, normalize } from 'node:path'; + +type Commit = { + hash: () => string; + timestamp: () => number; +}; + +/** + * Cache variable to store the most recent commit information. This is used to + * avoid repeated calls to the git command for performance optimization. + */ +const cache: Map = new Map(); + +/** + * Retrieves the most recent Git commit information, including its short hash + * and timestamp. If the information is cached, the cached value is returned to + * improve performance. Otherwise, it executes a git command to retrieve the + * latest commit's hash and timestamp, caches it, and then returns the + * information. + * + * @param gitDir + * @returns The latest commit's hash and timestamp. + */ +export function getLatestCommit( + gitDir: string = join(__dirname, '../../../.git'), +): Commit { + const cached = cache.get(gitDir); + if (cached) return cached; + + // execute the `git` command to get the latest commit's 8 character hash + // (`%h` and `--abbrev=8`) and authorship timestamp (seconds since the Unix + // epoch) + const hashLength = 8; + const args = [ + `--git-dir`, + normalize(gitDir), + 'log', + '-1', + '--format=%h%at', + `--abbrev=${hashLength}`, + ] as const; + const { stdout } = spawnSync('git', args, { + encoding: 'buffer', + env: process.env, + maxBuffer: 256, // we really only need like 19 bytes + stdio: ['ignore', 'pipe', 'ignore'], + }); + const response = { + hash() { + return stdout.toString('utf8', 0, hashLength); + }, + timestamp() { + // convert to milliseconds + return Number(stdout.toString('utf8', hashLength)) * 1000; + }, + }; + cache.set(gitDir, response); + return response; +} diff --git a/development/webpack/utils/helpers.ts b/development/webpack/utils/helpers.ts new file mode 100644 index 000000000000..2e7dc25b6da3 --- /dev/null +++ b/development/webpack/utils/helpers.ts @@ -0,0 +1,234 @@ +import { readdirSync } from 'node:fs'; +import { parse, join, relative, sep } from 'node:path'; +import type { Chunk, EntryObject, Stats } from 'webpack'; +import type TerserPluginType from 'terser-webpack-plugin'; + +export type Manifest = chrome.runtime.Manifest; +export type ManifestV2 = chrome.runtime.ManifestV2; +export type ManifestV3 = chrome.runtime.ManifestV3; + +// HMR (Hot Module Reloading) can't be used until all circular dependencies in +// the codebase are removed +// See: https://github.com/MetaMask/metamask-extension/issues/22450 +// TODO: remove this variable when HMR is ready. The env var is for tests and +// must also be removed everywhere. +export const __HMR_READY__ = Boolean(process.env.__HMR_READY__) || false; + +/** + * Target browsers + */ +export const Browsers = ['brave', 'chrome', 'firefox'] as const; +export type Browser = (typeof Browsers)[number]; + +const slash = `(?:\\${sep})?`; +/** + * Regular expression to match files in any `node_modules` directory + * Uses a platform-specific path separator: `/` on Unix-like systems and `\` on + * Windows. + */ +export const NODE_MODULES_RE = new RegExp(`${slash}node_modules${slash}`, 'u'); + +/** + * No Operation. A function that does nothing and returns nothing. + * + * @returns `undefined` + */ +export const noop = () => undefined; + +/** + * Collects all entry files for use with webpack. + * + * TODO: move this logic into the ManifestPlugin + * + * @param manifest - Base manifest file + * @param appRoot - Absolute directory to search for entry files listed in the + * base manifest + * @returns an `entry` object containing html and JS entry points for use with + * webpack, and an array, `manifestScripts`, list of filepaths of all scripts + * that were added to it. + */ +export function collectEntries(manifest: Manifest, appRoot: string) { + const entry: EntryObject = {}; + /** + * Scripts that must be self-contained and not split into chunks. + */ + const selfContainedScripts: Set = new Set([ + // Snow shouldn't be chunked + 'snow.prod', + 'use-snow', + ]); + + function addManifestScript(filename?: string) { + if (filename) { + selfContainedScripts.add(filename); + entry[filename] = { + chunkLoading: false, + filename, // output filename + import: join(appRoot, filename), // the path to the file to use as an entry + }; + } + } + + function addHtml(filename?: string) { + if (filename) { + assertValidEntryFileName(filename, appRoot); + entry[parse(filename).name] = join(appRoot, filename); + } + } + + // add content_scripts to entries + manifest.content_scripts?.forEach((s) => s.js?.forEach(addManifestScript)); + + if (manifest.manifest_version === 3) { + addManifestScript(manifest.background?.service_worker); + manifest.web_accessible_resources?.forEach(({ resources }) => + resources.forEach((filename) => { + filename.endsWith('.js') && addManifestScript(filename); + }), + ); + } else { + manifest.web_accessible_resources?.forEach((filename) => { + filename.endsWith('.js') && addManifestScript(filename); + }); + manifest.background?.scripts?.forEach(addManifestScript); + addHtml(manifest.background?.page); + } + + for (const filename of readdirSync(appRoot)) { + // ignore non-htm/html files + if (/\.html?$/iu.test(filename)) { + addHtml(filename); + } + } + + /** + * Ignore scripts that were found in the manifest, as these are only loaded by + * the browser extension platform itself. + * + * @param chunk + * @param chunk.name + * @returns + */ + function canBeChunked({ name }: Chunk): boolean { + return !name || !selfContainedScripts.has(name); + } + return { entry, canBeChunked }; +} + +/** + * @param filename + * @param appRoot + * @throws Throws an `Error` if the file is an invalid entrypoint filename + * (a file starting with "_") + */ +function assertValidEntryFileName(filename: string, appRoot: string) { + if (!filename.startsWith('_')) { + return; + } + + const relativeFile = relative(process.cwd(), join(appRoot, filename)); + const error = `Invalid Entrypoint Filename Detected\nPath: ${relativeFile}`; + const reason = `Filenames at the root of the extension directory starting with "_" are reserved for use by the browser.`; + const newFile = filename.slice(1); + const solutions = [ + `Rename this file to remove the underscore, e.g., '${filename}' to '${newFile}'`, + `Move this file to a subdirectory and, if necessary, add it manually to the build 😱`, + ]; + const context = `This file was included in the build automatically by our build script, which adds all HTML files at the root of '${appRoot}'.`; + + const message = `${error} + Reason: ${reason} + + Suggested Actions: + ${solutions.map((solution) => ` • ${solution}`).join('\n')} + ${`\n ${context}`} + `; + + throw new Error(message); +} + +/** + * It gets minimizers for the webpack build. + */ +export function getMinimizers() { + const TerserPlugin: typeof TerserPluginType = require('terser-webpack-plugin'); + return [ + new TerserPlugin({ + // use SWC to minify (about 7x faster than Terser) + minify: TerserPlugin.swcMinify, + // do not minify snow. + exclude: /snow\.prod/u, + }), + ]; +} + +/** + * Helpers for logging to the console with color. + */ +export const { colors, toGreen, toOrange, toPurple } = ((depth, esc) => { + if (depth === 1) { + const echo = (message: string): string => message; + return { colors: false, toGreen: echo, toOrange: echo, toPurple: echo }; + } + // 24: metamask green, 8: close to metamask green, 4: green + const green = { 24: '38;2;186;242;74', 8: '38;5;191', 4: '33' }[depth]; + // 24: metamask orange, 8: close to metamask orange, 4: red :-( + const orange = { 24: '38;2;247;85;25', 8: '38;5;208', 4: '31' }[depth]; + // 24: metamask purple, 8: close to metamask purple, 4: purple + const purple = { 24: '38;2;208;117;255', 8: '38;5;177', 4: '35' }[depth]; + return { + colors: { green: `${esc}[1;${green}m`, orange: `${esc}[1;${orange}m` }, + toGreen: (message: string) => `${esc}[1;${green}m${message}${esc}[0m`, + toOrange: (message: string) => `${esc}[1;${orange}m${message}${esc}[0m`, + toPurple: (message: string) => `${esc}[1;${purple}m${message}${esc}[0m`, + }; +})((process.stderr.getColorDepth?.() as 1 | 4 | 8 | 24) || 1, '\u001b'); + +/** + * Logs a summary of build information to `process.stderr` (webpack logs to + * stderr). + * + * Note: `err` and stats.hasErrors() are different. `err` prevents compilation + * from starting, while `stats.hasErrors()` is true if there were errors during + * compilation itself. + * + * @param err - If not `undefined`, logs the error to `process.stderr`. + * @param stats - If not `undefined`, logs the stats to `process.stderr`. + */ +export function logStats(err?: Error | null, stats?: Stats) { + if (err) { + console.error(err); + return; + } + + if (!stats) { + // technically this shouldn't happen, but webpack's TypeScript interface + // doesn't enforce that `err` and `stats` are mutually exclusive. + return; + } + + const { options } = stats.compilation; + // orange for production builds, purple for development + const colorFn = options.mode === 'production' ? toOrange : toPurple; + stats.compilation.name = colorFn(`🦊 ${stats.compilation.compiler.name}`); + if (options.stats === 'normal') { + // log everything (computing stats is slow, so we only do it if asked). + console.error(stats.toString({ colors })); + } else if (stats.hasErrors() || stats.hasWarnings()) { + // always log errors and warnings, if we have them. + console.error(stats.toString({ colors, preset: 'errors-warnings' })); + } else { + // otherwise, just log a simple update + const { name } = stats.compilation; + const status = toGreen('successfully'); + const time = `${stats.endTime - stats.startTime} ms`; + const { version } = require('webpack'); + console.error(`${name} (webpack ${version}) compiled ${status} in ${time}`); + } +} + +/** + * @param array + * @returns a new array with duplicate values removed and sorted + */ +export const uniqueSort = (array: string[]) => [...new Set(array)].sort(); diff --git a/development/webpack/utils/loaders/codeFenceLoader.ts b/development/webpack/utils/loaders/codeFenceLoader.ts new file mode 100644 index 000000000000..5088cb161f61 --- /dev/null +++ b/development/webpack/utils/loaders/codeFenceLoader.ts @@ -0,0 +1,59 @@ +import type { LoaderContext, RuleSetRule } from 'webpack'; +import type { JSONSchema7 } from 'schema-utils/declarations/validate'; +import { validate } from 'schema-utils'; +import { removeFencedCode, type FeatureLabels } from '@metamask/build-utils'; + +const schema: JSONSchema7 = { + $schema: 'http://json-schema.org/draft-07/schema#', + type: 'object', + required: ['features'], + properties: { + features: { + type: 'object', + description: + 'Configuration for code fence removal, specifying active and all possible features.', + required: ['active', 'all'], + properties: { + active: { + description: 'Features that should be included in the output.', + type: 'object', + }, + all: { + description: 'All features that can be toggled.', + type: 'object', + }, + }, + additionalProperties: false, + }, + }, + additionalProperties: false, +}; + +export type CodeFenceLoaderOptions = { features: FeatureLabels }; + +type Context = LoaderContext; +function codeFenceLoader(this: Context, content: string, map?: string) { + const options = this.getOptions(); + validate(schema, options, { name: 'codeFenceLoader' }); + try { + const result = removeFencedCode( + this.resourcePath, + content, + options.features, + ); + this.callback(null, result[0], map); + } catch (error: unknown) { + this.callback(error as Error); + } +} + +export default codeFenceLoader; + +export type Loader = RuleSetRule & { options: CodeFenceLoaderOptions }; + +export function getCodeFenceLoader(features: FeatureLabels): Loader { + return { + loader: __filename, + options: { features }, + }; +} diff --git a/development/webpack/utils/loaders/swcLoader.ts b/development/webpack/utils/loaders/swcLoader.ts new file mode 100644 index 000000000000..b6976ff8d66d --- /dev/null +++ b/development/webpack/utils/loaders/swcLoader.ts @@ -0,0 +1,208 @@ +import type { LoaderContext } from 'webpack'; +import type { JSONSchema7 } from 'schema-utils/declarations/validate'; +import type { FromSchema } from 'json-schema-to-ts'; +import { validate } from 'schema-utils'; +import { transform, type Options } from '@swc/core'; +import { type Args } from '../cli'; +import { __HMR_READY__ } from '../helpers'; + +// the schema here is limited to only the options we actually use +// there are loads more options available to SWC we could add. +const schema = { + $schema: 'http://json-schema.org/draft-07/schema#', + type: 'object', + properties: { + env: { + type: 'object', + properties: { + targets: { + description: 'The browsers to target (browserslist format).', + type: 'string', + }, + }, + additionalProperties: false, + }, + jsc: { + type: 'object', + properties: { + externalHelpers: { + type: 'boolean', + default: false, + }, + transform: { + type: 'object', + properties: { + optimizer: { + type: 'object', + properties: { + globals: { + description: '', + type: 'object', + properties: { + envs: { + description: + 'Replaces environment variables (`if (process.env.DEBUG) `) with specified values/expressions at compile time.', + anyOf: [ + { + type: 'array', + items: { + type: 'string', + }, + }, + { + type: 'object', + additionalProperties: { + type: 'string', + }, + }, + ], + }, + vars: { + description: + 'Replaces variables `if(__DEBUG__){}` with specified values/expressions at compile time.', + type: 'object', + additionalProperties: { + type: 'string', + }, + }, + }, + additionalProperties: false, + }, + }, + additionalProperties: false, + }, + react: { + description: 'Effective only if `syntax` supports ƒ.', + type: 'object', + properties: { + development: { + description: + 'Toggles plugins that aid in development, such as @swc/plugin-transform-react-jsx-self and @swc/plugin-transform-react-jsx-source. Defaults to `false`.', + type: 'boolean', + }, + refresh: { + description: 'Enable fast refresh feature for React app', + type: 'boolean', + }, + }, + additionalProperties: false, + }, + }, + additionalProperties: false, + }, + parser: { + description: 'Defaults to EsParserConfig (syntax: ecmascript)', + type: 'object', + properties: { + syntax: { + type: 'string', + default: 'ecmascript', + enum: ['ecmascript', 'typescript'], + }, + }, + oneOf: [ + { + properties: { + syntax: { + const: 'typescript', + }, + tsx: { + default: false, + type: 'boolean', + }, + }, + additionalProperties: false, + required: ['syntax'], + }, + { + properties: { + syntax: { + const: 'ecmascript', + }, + jsx: { + default: false, + type: 'boolean', + }, + }, + additionalProperties: false, + required: ['syntax'], + }, + ], + }, + }, + additionalProperties: false, + }, + }, + additionalProperties: false, +} as const satisfies JSONSchema7; + +type SchemaOptions = { keepDefaultedPropertiesOptional: true }; +export type SwcLoaderOptions = FromSchema; + +type Context = LoaderContext; +export default function swcLoader(this: Context, src: string, srcMap?: string) { + const pluginOptions = this.getOptions(); + validate(schema, pluginOptions, { name: 'swcLoader' }); + + const options: Options = { + ...pluginOptions, + envName: this.mode, + filename: this.resourcePath, + inputSourceMap: srcMap, + sourceFileName: this.resourcePath, + sourceMaps: this.sourceMap, + swcrc: false, + }; + + const cb = this.async(); + transform(src, options).then(({ code, map }) => cb(null, code, map), cb); +} + +export type SwcConfig = { + args: Pick; + safeVariables: Record; + browsersListQuery: string; + isDevelopment: boolean; +}; + +/** + * Gets the Speedy Web Compiler (SWC) loader for the given syntax. + * + * @param syntax + * @param enableJsx + * @param swcConfig + * @returns + */ +export function getSwcLoader( + syntax: 'typescript' | 'ecmascript', + enableJsx: boolean, + swcConfig: SwcConfig, +) { + return { + loader: __filename, + options: { + env: { + targets: swcConfig.browsersListQuery, + }, + jsc: { + externalHelpers: true, + transform: { + react: { + development: swcConfig.isDevelopment, + refresh: + __HMR_READY__ && swcConfig.isDevelopment && swcConfig.args.watch, + }, + optimizer: { + globals: { + envs: swcConfig.safeVariables, + }, + }, + }, + parser: { + syntax, + [syntax === 'typescript' ? 'tsx' : 'jsx']: enableJsx, + }, + }, + } as const satisfies SwcLoaderOptions, + }; +} diff --git a/development/webpack/utils/plugins/ManifestPlugin/helpers.ts b/development/webpack/utils/plugins/ManifestPlugin/helpers.ts new file mode 100644 index 000000000000..82efa9acf253 --- /dev/null +++ b/development/webpack/utils/plugins/ManifestPlugin/helpers.ts @@ -0,0 +1,56 @@ +/** + * Returns a function that will transform a manifest JSON object based on the + * given build args. + * + * Applies the following transformations: + * - If `lockdown` is `false`, removes lockdown scripts from content_scripts + * - If `test` is `true`, adds the "tabs" permission to the manifest + * + * @param args + * @param args.lockdown + * @param args.test + * @returns a function that will transform the manifest JSON object + * @throws an error if the manifest already contains the "tabs" permission and + * `test` is `true` + */ +export function transformManifest(args: { lockdown: boolean; test: boolean }) { + const transforms: ((manifest: chrome.runtime.Manifest) => void)[] = []; + + function removeLockdown(browserManifest: chrome.runtime.Manifest) { + const mainScripts = browserManifest.content_scripts?.[0]; + if (mainScripts) { + const keep = ['scripts/contentscript.js', 'scripts/inpage.js']; + mainScripts.js = mainScripts.js?.filter((js) => keep.includes(js)); + } + } + + if (!args.lockdown) { + // remove lockdown scripts from content_scripts + transforms.push(removeLockdown); + } + + function addTabsPermission(browserManifest: chrome.runtime.Manifest) { + if (browserManifest.permissions) { + if (browserManifest.permissions.includes('tabs')) { + throw new Error( + "manifest contains 'tabs' already; this transform should be removed.", + ); + } + browserManifest.permissions.push('tabs'); + } else { + browserManifest.permissions = ['tabs']; + } + } + if (args.test) { + // test builds need "tabs" permission for switchToWindowWithTitle + transforms.push(addTabsPermission); + } + + return transforms.length + ? (browserManifest: chrome.runtime.Manifest, _browser: string) => { + const clone = structuredClone(browserManifest); + transforms.forEach((transform) => transform(clone)); + return clone; + } + : undefined; +} diff --git a/development/webpack/utils/plugins/ManifestPlugin/index.ts b/development/webpack/utils/plugins/ManifestPlugin/index.ts new file mode 100644 index 000000000000..c08cfd7ba6e6 --- /dev/null +++ b/development/webpack/utils/plugins/ManifestPlugin/index.ts @@ -0,0 +1,351 @@ +import { extname, join } from 'node:path/posix'; +import { readFileSync } from 'node:fs'; +import { + sources, + ProgressPlugin, + type Compilation, + type Compiler, + type Asset, +} from 'webpack'; +import { validate } from 'schema-utils'; +import { + type DeflateOptions, + Zip, + AsyncZipDeflate, + ZipPassThrough, +} from 'fflate'; +import { noop, type Manifest, Browser } from '../../helpers'; +import { schema } from './schema'; +import type { ManifestPluginOptions } from './types'; + +const { RawSource, ConcatSource } = sources; + +type Assets = Compilation['assets']; + +const NAME = 'ManifestPlugin'; +const BROWSER_TEMPLATE_RE = /\[browser\]/gu; + +/** + * Clones a Buffer or Uint8Array and returns it + * + * @param data + * @returns + */ +function clone(data: Buffer | Uint8Array): Buffer { + return Buffer.from(data); +} + +/** + * Adds the given asset to the zip file + * + * @param asset - The asset to add + * @param assetName - The name of the asset + * @param compress - Whether to compress the asset + * @param compressionOptions - The options to use for compression + * @param mtime - The modification time of the asset + * @param zip - The zip file to add the asset to + */ +function addAssetToZip( + asset: Buffer, + assetName: string, + compress: boolean, + compressionOptions: DeflateOptions | undefined, + mtime: number, + zip: Zip, +): void { + const zipFile = compress + ? new AsyncZipDeflate(assetName, compressionOptions) + : new ZipPassThrough(assetName); + zipFile.mtime = mtime; + zip.add(zipFile); + // use a copy of the Buffer, as Zip will consume it + zipFile.push(asset, true); +} + +/** + * A webpack plugin that generates extension manifests for browsers and organizes + * assets into browser-specific directories and optionally zips them. + * + * TODO: it'd be great if the logic to find entry points was also in this plugin + * instead of in helpers.ts. Moving that here would allow us to utilize the + * this.options.transform function to modify the manifest before collecting the + * entry points. + */ +export class ManifestPlugin { + /** + * File types that can be compressed well using DEFLATE compression, used when + * zipping assets. + */ + static compressibleFileTypes = new Set([ + '.bmp', + '.cjs', + '.css', + '.csv', + '.eot', + '.html', + '.js', + '.json', + '.log', + '.map', + '.md', + '.mjs', + '.svg', + '.txt', + '.wasm', + '.vtt', // very slow to process? + // ttf is disabled as some were getting corrupted during compression. You + // can test this by uncommenting it, running with --zip, and then unzipping + // the resulting zip file. If it is still broken the unzip operation will + // show an error. + // '.ttf', + '.wav', + '.xml', + ]); + + options: ManifestPluginOptions; + + manifests: Map = new Map(); + + constructor(options: ManifestPluginOptions) { + validate(schema, options, { name: NAME }); + this.options = options; + this.manifests = new Map(); + } + + apply(compiler: Compiler) { + compiler.hooks.compilation.tap(NAME, this.hookIntoPipelines.bind(this)); + } + + private async zipAssets( + compilation: Compilation, + assets: Assets, // an object of asset names to assets + options: ManifestPluginOptions, + ): Promise { + // TODO(perf): this zips (and compresses) every file individually for each + // browser. Can we share the compression and crc steps to save time? + const { browsers, zipOptions } = options; + const { excludeExtensions, level, outFilePath, mtime } = zipOptions; + const compressionOptions: DeflateOptions = { level }; + const assetsArray = Object.entries(assets); + + let filesProcessed = 0; + const numAssetsPerBrowser = assetsArray.length + 1; + const totalWork = numAssetsPerBrowser * browsers.length; // +1 for each browser's manifest.json + const reportProgress = + ProgressPlugin.getReporter(compilation.compiler) || noop; + // TODO(perf): run this in parallel. If you try without carefully optimizing the + // process will run out of memory pretty quickly, and crash. Fun! + for (const browser of browsers) { + const manifest = this.manifests.get(browser) as sources.Source; + const source = await new Promise((resolve, reject) => { + // since Zipping is async, a past chunk could cause an error after we've + // started processing additional chunks. We'll use this errored flag to + // short-circuit the rest of the processing if that happens. + let errored = false; + const zipSource = new ConcatSource(); + const zip = new Zip((error, data, final) => { + if (errored) return; // ignore additional errors + if (error) { + // set error flag to prevent additional processing + errored = true; + reject(error); + } else { + zipSource.add(new RawSource(clone(data))); + // we've received our final bit of data, return the zipSource + if (final) resolve(zipSource); + } + }); + + // add the browser's manifest.json file to the zip + addAssetToZip( + manifest.buffer(), + 'manifest.json', + true, + compressionOptions, + mtime, + zip, + ); + + const message = `${++filesProcessed}/${totalWork} assets zipped for ${browser}`; + reportProgress(0, message, 'manifest.json'); + + for (const [assetName, asset] of assetsArray) { + if (errored) return; + + const extName = extname(assetName); + if (excludeExtensions.includes(extName)) continue; + + addAssetToZip( + // make a copy of the asset Buffer as Zipping will *consume* it, + // which breaks things if we are compiling for multiple browsers. + clone(asset.buffer()), + assetName, + ManifestPlugin.compressibleFileTypes.has(extName), + compressionOptions, + mtime, + zip, + ); + reportProgress( + 0, + `${++filesProcessed}/${totalWork} assets zipped for ${browser}`, + assetName, + ); + } + + zip.end(); + }); + + // add the zip file to webpack's assets. + const zipFilePath = outFilePath.replace(BROWSER_TEMPLATE_RE, browser); + compilation.emitAsset(zipFilePath, source, { + javascriptModule: false, + compressed: true, + contentType: 'application/zip', + development: true, + }); + } + } + + /** + * Moves the assets to the correct browser locations and adds each browser's + * extension manifest.json file to the list of assets. + * + * @param compilation + * @param assets + * @param options + */ + private moveAssets( + compilation: Compilation, + assets: Assets, + options: ManifestPluginOptions, + ): void { + // we need to wait to delete assets until after we've zipped them all + const assetDeletions = new Set(); + const { browsers } = options; + const assetEntries = Object.entries(assets); + browsers.forEach((browser) => { + const manifest = this.manifests.get(browser) as sources.Source; + compilation.emitAsset(join(browser, 'manifest.json'), manifest, { + javascriptModule: false, + contentType: 'application/json', + }); + for (const [name, asset] of assetEntries) { + // move the assets to their final browser-relative locations + const assetDetails = compilation.getAsset(name) as Readonly; + compilation.emitAsset(join(browser, name), asset, assetDetails.info); + assetDeletions.add(name); + } + }); + // delete the assets after we've zipped them all + assetDeletions.forEach((assetName) => compilation.deleteAsset(assetName)); + } + + private prepareManifests(compilation: Compilation): void { + const context = compilation.options.context as string; + const manifestPath = join( + context, + `manifest/v${this.options.manifest_version}`, + ); + // Load the base manifest + const basePath = join(manifestPath, `_base.json`); + const baseManifest: Manifest = JSON.parse(readFileSync(basePath, 'utf8')); + + const { transform } = this.options; + const resources = this.options.web_accessible_resources; + const description = this.options.description + ? `${baseManifest.description} – ${this.options.description}` + : baseManifest.description; + const { version } = this.options; + + this.options.browsers.forEach((browser) => { + let manifest: Manifest = { ...baseManifest, description, version }; + + if (browser !== 'firefox') { + // version_name isn't used by FireFox, but is by Chrome, et al. + manifest.version_name = this.options.versionName; + } + + try { + const browserManifestPath = join(manifestPath, `${browser}.json`); + // merge browser-specific overrides into the browser manifest + manifest = { + ...manifest, + ...require(browserManifestPath), + }; + } catch { + // ignore if the file doesn't exist, as some browsers might not need overrides + } + + // merge provided `web_accessible_resources` + if (resources && resources.length > 0) { + if (manifest.manifest_version === 3) { + manifest.web_accessible_resources = + manifest.web_accessible_resources || []; + const war = manifest.web_accessible_resources.find((resource) => + resource.matches.includes(''), + ); + if (war) { + // merge the resources into the existing resource, ensure uniqueness using `Set` + war.resources = [...new Set([...war.resources, ...resources])]; + } else { + // add a new resource + manifest.web_accessible_resources.push({ + matches: [''], + resources: [...resources], + }); + } + } else { + manifest.web_accessible_resources = [ + ...(manifest.web_accessible_resources || []), + ...resources, + ]; + } + } + + // allow the user to `transform` the manifest. Use a copy of the manifest + // so modifications for one browser don't affect other browsers. + if (transform) { + manifest = transform?.(JSON.parse(JSON.stringify(manifest)), browser); + } + + // Add the manifest file to the assets + const source = new RawSource(JSON.stringify(manifest, null, 2)); + this.manifests.set(browser, source); + }); + } + + private hookIntoPipelines(compilation: Compilation): void { + // prepare manifests early so we can catch errors early instead of waiting + // until the end of the compilation. + this.prepareManifests(compilation); + + // TODO: MV3 needs to be handled differently. Specifically, it needs to + // load the files it needs via a function call to `importScripts`, plus some + // other shenanigans. + + // hook into the processAssets hook to move/zip assets + const tapOptions = { + name: NAME, + stage: Infinity, + }; + if (this.options.zip) { + const options = this.options as ManifestPluginOptions; + compilation.hooks.processAssets.tapPromise( + tapOptions, + async (assets: Assets) => { + await this.zipAssets(compilation, assets, options); + this.moveAssets( + compilation, + assets, + this.options as ManifestPluginOptions, + ); + }, + ); + } else { + const options = this.options as ManifestPluginOptions; + compilation.hooks.processAssets.tap(tapOptions, (assets: Assets) => { + this.moveAssets(compilation, assets, options); + }); + } + } +} diff --git a/development/webpack/utils/plugins/ManifestPlugin/schema.ts b/development/webpack/utils/plugins/ManifestPlugin/schema.ts new file mode 100644 index 000000000000..e6d5efac7ff6 --- /dev/null +++ b/development/webpack/utils/plugins/ManifestPlugin/schema.ts @@ -0,0 +1,115 @@ +import { ExtendedJSONSchema } from 'json-schema-to-ts'; +import { Browsers } from '../../helpers'; + +type Writeable = { -readonly [P in keyof T]: T[P] }; + +export const schema = { + $schema: 'http://json-schema.org/draft-07/schema#', + type: 'object', + required: ['browsers', 'description', 'manifest_version', 'version', 'zip'], + properties: { + browsers: { + description: 'The browsers to build for.', + type: 'array', + items: { + type: 'string', + enum: Browsers as Writeable, + }, + minItems: 1, + maxItems: Browsers.length, + uniqueItems: true, + }, + version: { + description: + 'One to four dot-separated integers identifying the version of this extension.', + type: 'string', + }, + versionName: { + description: + 'A Semantic Versioning-compliant version number for the extension.', + type: 'string', + }, + description: { + description: 'A plain text string that describes the extension.', + type: ['string', 'null'], + maxLength: 132, + }, + manifest_version: { + description: + 'An integer specifying the version of the manifest file format your package requires.', + type: 'number', + enum: [2, 3], + }, + web_accessible_resources: { + description: + 'An array of strings specifying the paths of additional web-accessible resources.', + type: 'array', + items: { + type: 'string', + }, + }, + transform: { + description: 'Function to transform the manifest file.', + instanceof: 'Function', + tsType: '((manifest: Manifest, browser: Browser) => Manifest)', + }, + zip: { + description: 'Whether or not to zip the individual browser builds.', + type: 'boolean', + }, + zipOptions: { + required: ['outFilePath'], + properties: { + level: { + description: + 'Compression level for compressible assets. 0 is no compression, 9 is maximum compression. 6 is default.', + type: 'number', + default: 6, + minimum: 0, + maximum: 9, + }, + mtime: { + description: + 'Modification time for all files in the zip, specified as a UNIX timestamp (milliseconds since 1 January 1970 UTC). This property sets a uniform modification time for the contents of the zip file. Note: Zip files use FAT file timestamps, which have a limited range. Therefore, datetimes before 1980-01-01 (timestamp value of 315532800000) are invalid in standard Zip files, and datetimes on or after 2100-01-01 (timestamp value of 4102444800000) are also invalid. Values must fall within this range.', + type: 'number', + // Zip files use FAT file timestamps, which have a limited range. + // Datetimes before 1980-01-01 are invalid in standard Zip files. + minimum: Date.UTC(1980, 0, 1), + // datetimes after 2099-12-31 are invalid in zip files + exclusiveMaximum: Date.UTC(2100, 0, 1), + get default() { + return Date.now(); + }, + }, + excludeExtensions: { + description: 'File extensions to exclude from zip.', + type: 'array', + uniqueItems: true, + items: { + type: 'string', + pattern: '^\\.[a-zA-Z0-9]+$', + }, + default: [], + }, + outFilePath: { + description: + 'File path template for zip file relative to webpack output directory. You must include `[browser]` in the file path template, which will be replaced with the browser name. For example, `builds/[browser].zip`.', + type: 'string', + pattern: '.*\\[browser\\].*', + }, + }, + additionalProperties: false, + }, + }, + additionalProperties: false, + if: { + properties: { + zip: { + const: true, + }, + }, + }, + then: { + required: ['zipOptions'], + }, +} satisfies ExtendedJSONSchema>; diff --git a/development/webpack/utils/plugins/ManifestPlugin/types.ts b/development/webpack/utils/plugins/ManifestPlugin/types.ts new file mode 100644 index 000000000000..174d09d4f6b6 --- /dev/null +++ b/development/webpack/utils/plugins/ManifestPlugin/types.ts @@ -0,0 +1,94 @@ +import type { Browser, Manifest } from '../../helpers'; + +export type BaseManifestPluginOptions = { + /** + * The browsers to build for. + */ + browsers: readonly [Browser, ...Browser[]]; + + /** + * An array of strings specifying the paths of additional web-accessible resources. + */ + web_accessible_resources?: readonly string[]; + + /** + * An integer specifying the version of the manifest file format your package requires + */ + manifest_version: 2 | 3; + + /** + * One to four dot-separated integers identifying the version of this extension. A couple of rules apply to the integers: + * + * * The integers must be between 0 and 65535, inclusive. + * * Non-zero integers can't start with 0. For example, 032 is invalid because it begins with a zero. + * * They must not be all zero. For example, 0 and 0.0.0.0 are invalid while 0.1.0.0 is valid. + * + * Here are some examples of valid versions: + * + * * "version": "1" + * * "version": "1.0" + * * "version": "2.10.2" + * * "version": "3.1.2.4567" + * + * If the published extension has a newer version string than the installed extension, then the extension is automatically updated. + * + * The comparison starts with the leftmost integers. Then, if those integers are equal, the integers to the right are compared, and so on. For example, 1.2.0 is a newer version than 1.1.9.9999. + * + * A missing integer is equal to zero. For example, 1.1.9.9999 is newer than 1.1, and 1.1.9.9999 is older than 1.2. + */ + version: string; + + /** + * A Semantic Versioning-compliant version number for the extension. Not used in Firefox builds since Firefox doesn't currently support it. + */ + versionName: string; + + /** + * A plain text string (no HTML or other formatting; no more than 132 characters) that describes the extension. + * + * The description should be suitable for both the browser's Extensions page, e.g., chrome://extensions, and extension web stores. You can specify locale-specific strings for this field. + */ + description: string | null; + + /** + * Function to transform the manifest file. + * + * @param manifest + * @param browser + * @returns + */ + transform?: (manifest: Manifest, browser: Browser) => Manifest; + + /** + * Whether or not to zip the individual browser builds. + */ + zip: Zip; +}; + +export type ZipOptions = { + /** + * Options for the zip. + */ + zipOptions: { + /** + * Compression level for compressible assets. 0 is no compression, 9 is maximum compression. 6 is default. + */ + level: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; + /** + * Modification time for all files in the zip, specified as a UNIX timestamp (milliseconds since 1 January 1970 UTC). This property sets a uniform modification time for the contents of the zip file. Note: Zip files use FAT file timestamps, which have a limited range. Therefore, datetimes before 1980-01-01 (timestamp value of 315532800000) are invalid in standard Zip files, and datetimes on or after 2100-01-01 (timestamp value of 4102444800000) are also invalid. Values must fall within this range. + */ + mtime: number; + /** + * File extensions to exclude from zip; should include the `.`, e.g., [`.map`]. + */ + excludeExtensions: string[]; + + /** + * File path template for zip file relative to webpack output directory. You must include `[browser]` in the file path template, which will be replaced with the browser name. For example, `builds/[browser].zip`. + */ + outFilePath: string; + }; +}; + +export type ManifestPluginOptions = + BaseManifestPluginOptions & (Zip extends true ? ZipOptions : object); diff --git a/development/webpack/utils/plugins/SelfInjectPlugin/index.ts b/development/webpack/utils/plugins/SelfInjectPlugin/index.ts new file mode 100644 index 000000000000..b80f6102ab75 --- /dev/null +++ b/development/webpack/utils/plugins/SelfInjectPlugin/index.ts @@ -0,0 +1,175 @@ +import { dirname, relative } from 'node:path'; +import { ModuleFilenameHelpers, Compilation, sources } from 'webpack'; +import { validate } from 'schema-utils'; +import { schema } from './schema'; +import type { SelfInjectPluginOptions, Source, Compiler } from './types'; + +export { type SelfInjectPluginOptions } from './types'; + +/** + * Default options for the SelfInjectPlugin. + */ +const defaultOptions = { + // The default `sourceUrlExpression` is configured for browser extensions. + // It generates the absolute url of the given file as an extension url. + // e.g., `chrome-extension:///scripts/inpage.js` + sourceUrlExpression: (filename: string) => + `(globalThis.browser||chrome).runtime.getURL(${JSON.stringify(filename)})`, +} satisfies SelfInjectPluginOptions; + +/** + * Modifies processed assets to inject a script tag that will execute the asset + * as an inline script. Primarily used in Chromium extensions that need to + * access a tab's `window` object from a `content_script`. + * + * @example + * Input: + * ```js + * // webpack.config.js + * module.exports = {plugins: [new SelfInjectPlugin({ test: /\.js$/ })]}; + * ``` + * + * ```js + * // src/index.js + * console.log("hello world"); + * ``` + * Output: + * ```js + * // dist/main.js + * {let d=document,s=d.createElement('script');s.textContent="console.log(\"hello world\");\n//# sourceMappingURL=main.js.map"+`\n//# sourceURL=${(globalThis.browser||chrome).runtime.getURL("main.js")};`;d.documentElement.appendChild(s).remove()} + * ``` + * ```json + * // dist/main.js.map (example) + * {"version":3,"file":"x","mappings":"AAAAA,QAAQC,IAAI","sources":["webpack://./src/index.js"],"sourcesContent":["console.log(\"hello world\");"],"names":["console","log"]} + * ``` + */ +export class SelfInjectPlugin { + private options: SelfInjectPluginOptions & typeof defaultOptions; + + constructor(options: SelfInjectPluginOptions) { + validate(schema, options, { name: SelfInjectPlugin.name }); + + this.options = { ...defaultOptions, ...options }; + } + + apply(compiler: Compiler): void { + compiler.hooks.compilation.tap(SelfInjectPlugin.name, (compilation) => { + this.processAssets(compilation); + }); + } + + /** + * Hooks into the compilation process to modify assets. + * + * @param compilation + */ + processAssets(compilation: Compilation): void { + const opts = { + name: SelfInjectPlugin.name, + stage: Compilation.PROCESS_ASSETS_STAGE_DEV_TOOLING, + }; + compilation.hooks.processAssets.tap(opts, () => this.process(compilation)); + } + + /** + * Processes compilation assets to inject a script tag that will execute the + * asset as an inline script. + * + * @param compilation + */ + process(compilation: Compilation): void { + const { test } = this.options; + const match = ModuleFilenameHelpers.matchObject.bind(null, { test }); + + for (const chunk of compilation.chunks) { + for (const file of chunk.files) { + if (match(file)) { + compilation.updateAsset(file, (asset: Source) => { + return this.updateAsset(compilation, file, asset); + }); + } + } + } + } + + /** + * Updates the given asset to inject a script tag that will execute the asset + * as an inline script. + * + * @param compilation + * @param file + * @param asset + */ + updateAsset(compilation: Compilation, file: string, asset: Source): Source { + const { ConcatSource, RawSource } = sources; + const { map, source } = asset.sourceAndMap(); + + let sourceMappingURLComment = ''; + // emit a separate source map file (if this asset already has one) + if (map /* `map` can be `null`; webpack's types are wrong */) { + const { devtool } = compilation.options; + const sourceMapPath = `${file}.map`; + + // we're removing the source map from the original webpack asset, since + // it's now a different file that isn't mappable, so we need to re-add it + // as a new asset: + const mapSource = new RawSource(JSON.stringify(map)); + compilation.emitAsset(sourceMapPath, mapSource); + + // we must "hide" the `sourceMappingURL` from the file when `hidden` + // source maps are requested by omitting the reference from the source + if (devtool && !devtool.startsWith('hidden-')) { + // `sourceMappingURL` needs to be relative to the file so that the + // browser's dev tools can find it. + const sourceMappingURL = relative(dirname(file), sourceMapPath); + sourceMappingURLComment = `\n//# sourceMappingURL=${sourceMappingURL}`; + } + } + + // generate the new self-injecting source code: + const newSource = new ConcatSource(); + // wrapped in a new lexical scope so we don't pollute the global namespace + newSource.add(`{`); + newSource.add(`let d=document,s=d.createElement('script');`); + newSource.add(`s.textContent=`); + newSource.add(this.escapeJs(source + sourceMappingURLComment)); + newSource.add(`+`); + // The browser's dev tools can't map our inline javascript back to its + // source. We add a sourceURL directive to help with that. It also helps + // organize the Sources panel in browser dev tools by separating the inline + // script into its own origin. + newSource.add( + `\`\\n//# sourceURL=\${${this.options.sourceUrlExpression(file)}};\``, + ); + newSource.add(`;`); + // add and immediately remove the script to avoid modifying the DOM. + newSource.add(`d.documentElement.appendChild(s).remove()`); + newSource.add(`}`); + + return newSource; + } + + /** + * Escapes the given JavaScript source as a JavaScript string. + * + * Replaces line separators and paragraph separators with their unicode escape + * sequences. + * + * @example + * ```js + * escapeJs(`console.log('hello world');`); + * // => "\"console.log('hello world');\"" + * ``` + * @param source + * @returns + */ + private escapeJs(source: string): string { + return ( + JSON.stringify(source) + // replace line separators + .replace(/\u2028/gu, '\\u2028') + // and paragraph separators + .replace(/\u2029/gu, '\\u2029') + ); + } +} diff --git a/development/webpack/utils/plugins/SelfInjectPlugin/schema.ts b/development/webpack/utils/plugins/SelfInjectPlugin/schema.ts new file mode 100644 index 000000000000..2b674ba93f31 --- /dev/null +++ b/development/webpack/utils/plugins/SelfInjectPlugin/schema.ts @@ -0,0 +1,58 @@ +import { ExtendedJSONSchema } from 'json-schema-to-ts'; + +export const schema = { + $schema: 'http://json-schema.org/draft-07/schema#', + type: 'object', + properties: { + exclude: { + oneOf: [ + { type: 'string' }, + { instanceof: 'RegExp', tsType: 'RegExp' }, + { + type: 'array', + items: { + oneOf: [ + { type: 'string' }, + { instanceof: 'RegExp', tsType: 'RegExp' }, + ], + }, + }, + ], + }, + include: { + oneOf: [ + { type: 'string' }, + { instanceof: 'RegExp' }, + { + type: 'array', + items: { + oneOf: [ + { type: 'string' }, + { instanceof: 'RegExp', tsType: 'RegExp' }, + ], + }, + }, + ], + }, + test: { + oneOf: [ + { type: 'string' }, + { instanceof: 'RegExp', tsType: 'RegExp' }, + { + type: 'array', + items: { + oneOf: [ + { type: 'string' }, + { instanceof: 'RegExp', tsType: 'RegExp' }, + ], + }, + }, + ], + }, + sourceUrlExpression: { + instanceof: 'Function', + tsType: '((filename: string) => string)', + }, + }, + additionalProperties: false, +} satisfies ExtendedJSONSchema>; diff --git a/development/webpack/utils/plugins/SelfInjectPlugin/types.ts b/development/webpack/utils/plugins/SelfInjectPlugin/types.ts new file mode 100644 index 000000000000..2240227bd76a --- /dev/null +++ b/development/webpack/utils/plugins/SelfInjectPlugin/types.ts @@ -0,0 +1,49 @@ +import type { Asset } from 'webpack'; + +export type { Compiler } from 'webpack'; +export type Source = Asset['source']; + +/** + * Options for the SelfInjectPlugin. + */ +export type SelfInjectPluginOptions = { + /** + * Specify which chunks to apply the transformation to. + * + * @example + * ```js + * { + * test: /inpage/, + * } + * ``` + */ + test?: string | RegExp | (string | RegExp)[]; + /** + * A function that returns a JavaScript expression escaped as a string which + * will be injected into matched file to provide a sourceURL for the self + * injected script. + * + * Defaults to `(filename: string) => (globalThis.browser||globalThis.chrome).runtime.getURL("${filename}")` + * + * @example Custom + * ```js + * Appends a runtime URL for a website, e.g., + * `// //# sourceURL=https://google.com/scripts/myfile.js` + * { + * sourceUrlExpression: (filename) => `document.location.origin/${filename}` + * } + * ``` + * @example Default + * Appends a runtime URL for a browser extension, e.g., + * `//# sourceURL=chrome-extension:///scripts/inpage.js` + * + * ```js + * { + * sourceUrlExpression: (filename) => `(globalThis.browser||globalThis.chrome).runtime.getURL("${filename}")` + * } + * ``` + * @param filename - the chunk's relative filename as it will exist in the output directory + * @returns + */ + sourceUrlExpression?: (filename: string) => string; +}; diff --git a/development/webpack/utils/version.ts b/development/webpack/utils/version.ts new file mode 100644 index 000000000000..ea9cbe7b6e24 --- /dev/null +++ b/development/webpack/utils/version.ts @@ -0,0 +1,70 @@ +import type { BuildType } from './config'; + +/** + * Computes the version number for use in the extension manifest. Uses the + * `version` field in the project's `package.json`. + * + * @param type + * @param options + * @param options.id + * @param options.isPrerelease + * @param releaseVersion + * @returns Returns the version and version_name values for the extension. + */ +export const getExtensionVersion = ( + type: string, + { id, isPrerelease }: Pick, + releaseVersion: number, +): { version: string; versionName: string } => { + const { version } = require('../../../package.json') as { version: string }; + + if (id < 10 || id > 64 || releaseVersion < 0 || releaseVersion > 999) { + throw new Error( + `Build id must be 10-64 and release version must be 0-999 +(inclusive). Received an id of '${id}' and a release version of +'${releaseVersion}'. + +Wait, but that seems so arbitrary? +================================== + +We encode the build id and the release version into the extension version by +concatenating the two numbers together. The maximum value for the concatenated +number is 65535 (a Chromium limitation). The value cannot start with a '0'. We +utilize 2 digits for the build id and 3 for the release version. This affords us +55 release types and 1000 releases per 'version' + build type. + +Okay, so how do I fix it? +========================= + +You'll need to adjust the build 'id' (in builds.yml) or the release version to +fit within these limits or bump the version number in package.json and start the +release version number over from 0. If you can't do that you'll need to come up +with a new way of encoding this information, or re-evaluate the need for this +metadata. + +Good luck on your endeavors.`, + ); + } + + if (!isPrerelease) { + if (releaseVersion !== 0) { + throw new Error( + `A '${type}' build's release version must always be '0'. Got '${releaseVersion}' instead.`, + ); + } + // main build (non-prerelease) version_name is just a plain version number + // the version field needs the `.0` because some runtime code freaks out + // if it's missing. + return { + version: `${version}.0`, + versionName: version, + }; + } + return { + // if version=18.7.25, id=10, releaseVersion=12 we get 18.7.25.1012 + version: `${version}.${id}${releaseVersion}`, + // The manifest.json's `version_name` field can be anything we want, so we + // make it human readable, e.g., `18.7.25-beta.123`. + versionName: `${version}-${type}.${releaseVersion}`, + }; +}; diff --git a/development/webpack/webpack.config.ts b/development/webpack/webpack.config.ts new file mode 100644 index 000000000000..d207b3ae741c --- /dev/null +++ b/development/webpack/webpack.config.ts @@ -0,0 +1,395 @@ +/** + * @file The main webpack configuration file for the browser extension. + */ + +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { argv, exit } from 'node:process'; +import { + ProvidePlugin, + type Configuration, + type WebpackPluginInstance, + type Chunk, + type MemoryCacheOptions, + type FileCacheOptions, +} from 'webpack'; +import CopyPlugin from 'copy-webpack-plugin'; +import HtmlBundlerPlugin from 'html-bundler-webpack-plugin'; +import rtlCss from 'postcss-rtlcss'; +import autoprefixer from 'autoprefixer'; +import type ReactRefreshPluginType from '@pmmmwh/react-refresh-webpack-plugin'; +import { SelfInjectPlugin } from './utils/plugins/SelfInjectPlugin'; +import { + type Manifest, + collectEntries, + getMinimizers, + NODE_MODULES_RE, + __HMR_READY__, +} from './utils/helpers'; +import { transformManifest } from './utils/plugins/ManifestPlugin/helpers'; +import { parseArgv, getDryRunMessage } from './utils/cli'; +import { getCodeFenceLoader } from './utils/loaders/codeFenceLoader'; +import { getSwcLoader } from './utils/loaders/swcLoader'; +import { getBuildTypes, getVariables } from './utils/config'; +import { ManifestPlugin } from './utils/plugins/ManifestPlugin'; +import { getLatestCommit } from './utils/git'; + +const buildTypes = getBuildTypes(); +const { args, cacheKey, features } = parseArgv(argv.slice(2), buildTypes); +if (args.dryRun) { + console.error(getDryRunMessage(args, features)); + exit(0); +} + +// #region short circuit for unsupported build configurations +if (args.lavamoat) { + throw new Error("The webpack build doesn't support LavaMoat yet. So sorry."); +} +if (args.manifest_version === 3) { + throw new Error( + "The webpack build doesn't support manifest_version version 3 yet. So sorry.", + ); +} +// #endregion short circuit for unsupported build configurations + +const context = join(__dirname, '../../app'); +const isDevelopment = args.env === 'development'; +const MANIFEST_VERSION = args.manifest_version; +const manifestPath = join(context, `manifest/v${MANIFEST_VERSION}/_base.json`); +const manifest: Manifest = require(manifestPath); +const { entry, canBeChunked } = collectEntries(manifest, context); +const codeFenceLoader = getCodeFenceLoader(features); +const browsersListPath = join(context, '../.browserslistrc'); +// read .browserslist now to stop it from searching for the file over and over +const browsersListQuery = readFileSync(browsersListPath, 'utf8'); +const { variables, safeVariables, version } = getVariables(args, buildTypes); +const webAccessibleResources = + args.devtool === 'source-map' + ? ['scripts/inpage.js.map', 'scripts/contentscript.js.map'] + : []; + +// #region cache +const cache = args.cache + ? ({ + type: 'filesystem', + name: `MetaMask—${args.env}`, + version: cacheKey, + idleTimeout: 0, + idleTimeoutForInitialStore: 0, + idleTimeoutAfterLargeChanges: 0, + // small performance gain by increase memory generations + maxMemoryGenerations: Infinity, + // Disable allowCollectingMemory because it can slow the build by 10%! + allowCollectingMemory: false, + buildDependencies: { + defaultConfig: [__filename], + // Invalidates the build cache when the listed files change. + // `__filename` makes all `require`d dependencies of *this* file + // `buildDependencies` + config: [ + __filename, + join(context, '../.metamaskrc'), + join(context, '../builds.yml'), + browsersListPath, + ], + }, + } as const satisfies FileCacheOptions) + : ({ type: 'memory' } as const satisfies MemoryCacheOptions); +// #endregion cache + +// #region plugins +const commitHash = isDevelopment ? getLatestCommit().hash() : null; +const plugins: WebpackPluginInstance[] = [ + new SelfInjectPlugin({ test: /^scripts\/inpage\.js$/u }), + // HtmlBundlerPlugin treats HTML files as entry points + new HtmlBundlerPlugin({ + preprocessorOptions: { useWith: false }, + minify: args.minify, + integrity: 'auto', + test: /\.html$/u, // default is eta/html, we only want html + data: { + isMMI: args.type === 'mmi', + isTest: args.test, + shouldIncludeSnow: args.snow, + }, + }), + new ManifestPlugin({ + web_accessible_resources: webAccessibleResources, + manifest_version: MANIFEST_VERSION, + description: commitHash + ? `${args.env} build from git id: ${commitHash.substring(0, 8)}` + : null, + version: version.version, + versionName: version.versionName, + browsers: args.browser, + transform: transformManifest(args), + zip: args.zip, + ...(args.zip + ? { + zipOptions: { + outFilePath: `../../builds/metamask-[browser]-${version.versionName}.zip`, // relative to output.path + mtime: getLatestCommit().timestamp(), + excludeExtensions: ['.map'], + // `level: 9` is the highest; it may increase build time by ~5% over level 1 + level: 9, + }, + } + : {}), + }), + // use ProvidePlugin to polyfill *global* node variables + new ProvidePlugin({ + // Make a global `Buffer` variable that points to the `buffer` package. + Buffer: ['buffer', 'Buffer'], + // Make a global `process` variable that points to the `process` package. + process: 'process/browser', + // polyfill usages of `setImmediate`, ideally this would be automatically + // handled by `swcLoader`'s `env.usage = 'entry'` option, but that setting + // results in a compilation error: `Module parse failed: 'import' and + // 'export' may appear only with 'sourceType: module' (2:0)`. I spent a few + // hours trying to figure it out but couldn't. So, this is the workaround. + // Note: we should probably remove usages of `setImmediate` from our + // codebase so we don't have to polyfill it. + setImmediate: 'core-js-pure/actual/set-immediate', + }), + new CopyPlugin({ + patterns: [ + { from: join(context, '_locales'), to: '_locales' }, // translations + // misc images + // TODO: fix overlap between this folder and automatically bundled assets + { from: join(context, 'images'), to: 'images' }, + ], + }), +]; +// enable React Refresh in 'development' mode when `watch` is enabled +if (__HMR_READY__ && isDevelopment && args.watch) { + const ReactRefreshWebpackPlugin: typeof ReactRefreshPluginType = require('@pmmmwh/react-refresh-webpack-plugin'); + plugins.push(new ReactRefreshWebpackPlugin()); +} +if (args.progress) { + const { ProgressPlugin } = require('webpack'); + plugins.push(new ProgressPlugin()); +} +// #endregion plugins + +const swcConfig = { args, safeVariables, browsersListQuery, isDevelopment }; +const tsxLoader = getSwcLoader('typescript', true, swcConfig); +const jsxLoader = getSwcLoader('ecmascript', true, swcConfig); +const ecmaLoader = getSwcLoader('ecmascript', false, swcConfig); + +const config = { + entry, + cache, + plugins, + context, + mode: args.env, + stats: args.stats ? 'normal' : 'none', + name: `MetaMask – ${args.env}`, + // use the `.browserlistrc` file directly to avoid browserslist searching + target: `browserslist:${browsersListPath}:defaults`, + // TODO: look into using SourceMapDevToolPlugin and its exclude option to speed up the build + // TODO: put source maps in an upper level directory (like the gulp build does now) + // see: https://webpack.js.org/plugins/source-map-dev-tool-plugin/#host-source-maps-externally + devtool: args.devtool === 'none' ? false : args.devtool, + output: { + wasmLoading: 'fetch', + // required for `integrity` to work in the browser + crossOriginLoading: 'anonymous', + // filenames for *initial* files (essentially JS entry points) + filename: '[name].[contenthash].js', + path: join(context, '..', 'dist'), + // Clean the output directory before emit, so that only the latest build + // files remain. Nearly 0 performance penalty for this clean up step. + clean: true, + // relative to HTML page. This value is essentially prepended to asset URLs + // in the output HTML, i.e., ` + +
Mock Page for E2E Cookie Handler
+