diff --git a/.github/scripts/add-release-label-to-pr-and-linked-issues.ts b/.github/scripts/add-release-label-to-pr-and-linked-issues.ts index a9b84532c8e6..1c4d551828c3 100644 --- a/.github/scripts/add-release-label-to-pr-and-linked-issues.ts +++ b/.github/scripts/add-release-label-to-pr-and-linked-issues.ts @@ -6,6 +6,7 @@ import { retrieveLinkedIssues } from './shared/issue'; import { Label } from './shared/label'; import { Labelable, addLabelToLabelable } from './shared/labelable'; import { retrievePullRequest } from './shared/pull-request'; +import { isValidVersionFormat } from './shared/utils'; main().catch((error: Error): void => { console.error(error); @@ -90,9 +91,3 @@ async function main(): Promise { await addLabelToLabelable(octokit, linkedIssue, releaseLabel); } } - -// This helper function checks if version has the correct format: "x.y.z" where "x", "y" and "z" are numbers. -function isValidVersionFormat(str: string): boolean { - const regex = /^\d+\.\d+\.\d+$/; - return regex.test(str); -} diff --git a/.github/scripts/check-template-and-add-labels.ts b/.github/scripts/check-template-and-add-labels.ts index 1a2bc0410beb..dd54fe7e849e 100644 --- a/.github/scripts/check-template-and-add-labels.ts +++ b/.github/scripts/check-template-and-add-labels.ts @@ -13,6 +13,8 @@ import { } from './shared/labelable'; import { Label, + RegressionStage, + craftRegressionLabel, externalContributorLabel, flakyTestsLabel, invalidIssueTemplateLabel, @@ -21,14 +23,6 @@ import { import { TemplateType, templates } from './shared/template'; import { retrievePullRequest } from './shared/pull-request'; -enum RegressionStage { - DevelopmentFeature, - DevelopmentMain, - Testing, - Beta, - Production, -} - const knownBots = [ 'metamaskbot', 'dependabot', @@ -345,59 +339,3 @@ async function userBelongsToMetaMaskOrg( return Boolean(userBelongsToMetaMaskOrgResult?.user?.organization?.id); } - -// 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.DevelopmentFeature: - return { - name: `feature-branch-bug`, - color: '5319E7', // violet - description: `bug that was found on a feature branch, but not yet merged in main branch`, - }; - - case RegressionStage.DevelopmentMain: - return { - name: `regression-main`, - color: '5319E7', // violet - description: `Regression bug that was found on main 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-main', '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/scripts/create-bug-report-issue.ts b/.github/scripts/create-bug-report-issue.ts new file mode 100644 index 000000000000..cfd2d7da93ae --- /dev/null +++ b/.github/scripts/create-bug-report-issue.ts @@ -0,0 +1,129 @@ +import * as core from '@actions/core'; +import { context, getOctokit } from '@actions/github'; +import { GitHub } from '@actions/github/lib/utils'; + +import { createIssue, retrieveIssueByTitle } from './shared/issue'; +import { + Label, + RegressionStage, + craftRegressionLabel, + craftTeamLabel, + createOrRetrieveLabel, + typeBugLabel, +} from './shared/label'; +import { codeRepoToPlanningRepo, codeRepoToPlatform, getCurrentDateFormatted, isValidVersionFormat } from './shared/utils'; +import { addIssueToGithubProject, GithubProject, GithubProjectField, retrieveGithubProject, updateGithubProjectDateFieldValue } from './shared/project'; + +main().catch((error: Error): void => { + console.error(error); + process.exit(1); +}); + +async function main(): Promise { + // "GITHUB_TOKEN" is an automatically generated, repository-specific access token provided by GitHub Actions. + // We can't use "GITHUB_TOKEN" here, as its permissions don't allow neither to create new labels + // nor to retrieve the content of organisations Github Projects. + // In our case, we may want to create "regression-RC-x.y.z" label when it doesn't already exist. + // We may also want to retrieve the content of organisation's Github Projects. + // As a consequence, we need to create our own "BUG_REPORT_TOKEN" with "repo" and "read:org" permissions. + // Such a token allows both to create new labels and fetch the content of organisation's Github Projects. + const personalAccessToken = process.env.BUG_REPORT_TOKEN; + if (!personalAccessToken) { + core.setFailed('BUG_REPORT_TOKEN not found'); + process.exit(1); + } + + const projectNumber = Number(process.env.RELEASES_GITHUB_PROJECT_BOARD_NUMBER); + if (!projectNumber) { + core.setFailed('RELEASES_GITHUB_PROJECT_BOARD_NUMBER not found'); + process.exit(1); + } + + const projectViewNumber = Number(process.env.RELEASES_GITHUB_PROJECT_BOARD_VIEW_NUMBER); + if (!projectViewNumber) { + core.setFailed('RELEASES_GITHUB_PROJECT_BOARD_VIEW_NUMBER not found'); + process.exit(1); + } + + const releaseVersion = process.env.RELEASE_VERSION; + if (!releaseVersion) { + core.setFailed('RELEASE_VERSION not found'); + process.exit(1); + } + if (!isValidVersionFormat(releaseVersion)) { + core.setFailed(`Invalid format for RELEASE_VERSION: ${releaseVersion}. Expected format: x.y.z`); + process.exit(1); + } + + const repoOwner = context.repo.owner; + if (!repoOwner) { + core.setFailed('repo owner not found'); + process.exit(1); + } + const codeRepoName = context.repo.repo; + if (!codeRepoName) { + core.setFailed('code repo name not found'); + process.exit(1); + } + const planningRepoName = codeRepoToPlanningRepo[codeRepoName]; + if (!planningRepoName) { + core.setFailed('planning repo name not found'); + process.exit(1); + } + + // Retrieve platform name + const platformName = codeRepoToPlatform[codeRepoName]; + if (!platformName) { + core.setFailed('platform name not found'); + process.exit(1); + } + + // Initialise octokit, required to call Github GraphQL API + const octokit: InstanceType = getOctokit(personalAccessToken, { + previews: ['bane'], // The "bane" preview is required for adding, updating, creating and deleting labels. + }); + + // Craft regression labels to add + const regressionLabelTesting: Label = craftRegressionLabel(RegressionStage.Testing, releaseVersion); + const regressionLabelProduction: Label = craftRegressionLabel(RegressionStage.Production, releaseVersion); + const teamLabel: Label = craftTeamLabel(`${platformName}-platform`); + + // Create or retrieve the different labels + await createOrRetrieveLabel(octokit, repoOwner, codeRepoName, regressionLabelProduction); + await createOrRetrieveLabel(octokit, repoOwner, codeRepoName, regressionLabelTesting); + await createOrRetrieveLabel(octokit, repoOwner, planningRepoName, regressionLabelProduction); + const regressionLabelTestingId = await createOrRetrieveLabel(octokit, repoOwner, planningRepoName, regressionLabelTesting); + const typeBugLabelId = await createOrRetrieveLabel(octokit, repoOwner, planningRepoName, typeBugLabel); + const teamLabelId = await createOrRetrieveLabel(octokit, repoOwner, planningRepoName, teamLabel); + + const issueTitle = `v${releaseVersion} Bug Report`; + const issueWithSameTitle = await retrieveIssueByTitle(octokit, repoOwner, planningRepoName, issueTitle); + if (issueWithSameTitle) { + core.setFailed(`Bug report already exists: https://github.com/${repoOwner}/${planningRepoName}/issues/${issueWithSameTitle.number}. This is not desired, but can happen in cases where a release gets re-cut.`); + process.exit(1); + } + + const issueBody = `**What is this bug report issue for?**\n\n1. This issue is used to track release dates on this [Github Project board](https://github.com/orgs/MetaMask/projects/${projectNumber}/views/${projectViewNumber}), which content then gets pulled into our metrics system.\n\n2. This issue is also used by our Zapier automations, to determine if automated notifications shall be sent on Slack for release \`${releaseVersion}\`. Notifications will only be sent as long as this issue is open.\n\n**Who created and/or closed this issue?**\n\n- This issue was automatically created by a GitHub action upon the creation of the release branch \`Version-v${releaseVersion}\`, indicating the release was cut.\n\n- This issue gets automatically closed by another GitHub action, once the \`Version-v${releaseVersion}\` branch merges into \`main\`, indicating the release is prepared for store submission.`; + const issueId = await createIssue(octokit, repoOwner, planningRepoName, issueTitle, issueBody, [regressionLabelTestingId, typeBugLabelId, teamLabelId]); + + // Retrieve project, in order to obtain its ID + const project: GithubProject = await retrieveGithubProject(octokit, projectNumber); + + const projectFieldName: string = "RC Cut"; + + const projectField: GithubProjectField | undefined = project.fields.find(field => field.name === projectFieldName); + + if (!projectField) { + throw new Error(`Project field with name ${projectFieldName} was not found on Github Project with ID ${project.id}.`); + } + + if (!projectField.id) { + throw new Error(`Project field with name ${projectFieldName} was found on Github Project with ID ${project.id}, but it has no 'id' property.`); + } + + // Add bug report issue to 'Releases' Github Project Board + await addIssueToGithubProject(octokit, project.id, issueId); + + // Update bug report issue's date property on 'Releases' Github Project Board + await updateGithubProjectDateFieldValue(octokit, project.id, projectField.id, issueId, getCurrentDateFormatted()); +} diff --git a/.github/scripts/shared/issue.ts b/.github/scripts/shared/issue.ts index e5c6804630ed..809667a24b8e 100644 --- a/.github/scripts/shared/issue.ts +++ b/.github/scripts/shared/issue.ts @@ -1,6 +1,30 @@ import { GitHub } from '@actions/github/lib/utils'; import { LabelableType, Labelable } from './labelable'; +import { retrieveRepo } from './repo'; + +interface RawIssue { + id: string; + title: string; + number: number; + createdAt: string; + body: string; + author: { + login: string; + }; + labels: { + nodes: { + id: string; + name: string; + }[]; + }; + repository: { + name: string; + owner: { + login: string; + }; + }; +} // This function retrieves an issue on a specific repo export async function retrieveIssue( @@ -14,6 +38,8 @@ export async function retrieveIssue( repository(owner: $repoOwner, name: $repoName) { issue(number: $issueNumber) { id + title + number createdAt body author { @@ -25,6 +51,12 @@ export async function retrieveIssue( name } } + repository { + name + owner { + login + } + } } } } @@ -32,20 +64,7 @@ export async function retrieveIssue( const retrieveIssueResult: { repository: { - issue: { - id: string; - createdAt: string; - body: string; - author: { - login: string; - }; - labels: { - nodes: { - id: string; - name: string; - }[]; - }; - }; + issue: RawIssue; }; } = await octokit.graphql(retrieveIssueQuery, { repoOwner, @@ -68,6 +87,78 @@ export async function retrieveIssue( return issue; } +// This function retrieves an issue by title on a specific repo +export async function retrieveIssueByTitle( + octokit: InstanceType, + repoOwner: string, + repoName: string, + issueTitle: string, +): Promise { + const searchQuery = `repo:${repoOwner}/${repoName} type:issue in:title ${issueTitle}`; + + const retrieveIssueByTitleQuery = ` + query GetIssueByTitle($searchQuery: String!) { + search( + query: $searchQuery + type: ISSUE + first: 10 + ) { + nodes { + ... on Issue { + id + title + number + createdAt + body + author { + login + } + labels(first: 100) { + nodes { + id + name + } + } + repository { + name + owner { + login + } + } + } + } + issueCount + } + } + `; + + const retrieveIssueByTitleResult: { + search: { + nodes: RawIssue[]; + }; + } = await octokit.graphql(retrieveIssueByTitleQuery, { + searchQuery, + }); + + const issueWithSameTitle = retrieveIssueByTitleResult?.search?.nodes?.find(rawIssue => rawIssue.title === issueTitle); + + const issue: Labelable | undefined = issueWithSameTitle + ? { + id: issueWithSameTitle?.id, + type: LabelableType.Issue, + number: issueWithSameTitle?.number, + repoOwner: repoOwner, + repoName: repoName, + createdAt: issueWithSameTitle?.createdAt, + body: issueWithSameTitle?.body, + author: issueWithSameTitle?.author?.login, + labels: issueWithSameTitle?.labels?.nodes, + } + : undefined; + + return issue; +} + // This function retrieves the list of linked issues for a pull request export async function retrieveLinkedIssues( octokit: InstanceType, @@ -75,6 +166,7 @@ export async function retrieveLinkedIssues( repoName: string, prNumber: number, ): Promise { + // We assume there won't be more than 100 linked issues const retrieveLinkedIssuesQuery = ` query ($repoOwner: String!, $repoName: String!, $prNumber: Int!) { @@ -83,6 +175,7 @@ export async function retrieveLinkedIssues( closingIssuesReferences(first: 100) { nodes { id + title number createdAt body @@ -112,27 +205,7 @@ export async function retrieveLinkedIssues( repository: { pullRequest: { closingIssuesReferences: { - nodes: Array<{ - id: string; - number: number; - createdAt: string; - body: string; - author: { - login: string; - }; - labels: { - nodes: { - id: string; - name: string; - }[]; - }; - repository: { - name: string; - owner: { - login: string; - }; - }; - }>; + nodes: RawIssue[]; }; }; }; @@ -144,27 +217,7 @@ export async function retrieveLinkedIssues( const linkedIssues: Labelable[] = retrieveLinkedIssuesResult?.repository?.pullRequest?.closingIssuesReferences?.nodes?.map( - (issue: { - id: string; - number: number; - createdAt: string; - body: string; - author: { - login: string; - }; - labels: { - nodes: { - id: string; - name: string; - }[]; - }; - repository: { - name: string; - owner: { - login: string; - }; - }; - }) => { + (issue: RawIssue) => { return { id: issue?.id, type: LabelableType.Issue, @@ -181,3 +234,60 @@ export async function retrieveLinkedIssues( return linkedIssues; } + +// This function creates an issue on a specific repo +export async function createIssue( + octokit: InstanceType, + repoOwner: string, + repoName: string, + issueTitle: string, + issueBody: string, + labelIds: string[], +): Promise { + // Retrieve PR's repo + const repoId = await retrieveRepo(octokit, repoOwner, repoName); + + const createIssueMutation = ` + mutation CreateIssue($repoId: ID!, $issueTitle: String!, $issueBody: String!, $labelIds: [ID!]) { + createIssue(input: {repositoryId: $repoId, title: $issueTitle, body: $issueBody, labelIds: $labelIds}) { + issue { + id + title + number + createdAt + body + author { + login + } + labels(first: 100) { + nodes { + id + name + } + } + repository { + name + owner { + login + } + } + } + } + } + `; + + const createIssueResult: { + createIssue: { + issue: RawIssue; + }; + } = await octokit.graphql(createIssueMutation, { + repoId, + issueTitle, + issueBody, + labelIds, + }); + + const issueId = createIssueResult?.createIssue?.issue?.id; + + return issueId; +} diff --git a/.github/scripts/shared/label.ts b/.github/scripts/shared/label.ts index d218dcf42570..42447e27937f 100644 --- a/.github/scripts/shared/label.ts +++ b/.github/scripts/shared/label.ts @@ -2,12 +2,25 @@ import { GitHub } from '@actions/github/lib/utils'; import { retrieveRepo } from './repo'; +export enum RegressionStage { + DevelopmentFeature, + DevelopmentMain, + Testing, + Beta, + Production, +} export interface Label { name: string; color: string; description: string; } +export const typeBugLabel: Label = { + name: 'type-bug', + color: 'D73A4A', + description: `Something isn't working`, +}; + export const externalContributorLabel: Label = { name: 'external-contributor', color: '7057FF', @@ -32,6 +45,88 @@ export const invalidPullRequestTemplateLabel: Label = { description: "PR's body doesn't match template", }; +// This function crafts appropriate label, corresponding to regression stage and release version. +export function craftRegressionLabel( + regressionStage: RegressionStage | undefined, + releaseVersion: string | undefined, +): Label { + switch (regressionStage) { + case RegressionStage.DevelopmentFeature: + return { + name: `feature-branch-bug`, + color: '5319E7', // violet + description: `bug that was found on a feature branch, but not yet merged in main branch`, + }; + + case RegressionStage.DevelopmentMain: + return { + name: `regression-main`, + color: '5319E7', // violet + description: `Regression bug that was found on main 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-main', '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.`, + }; + } +} + +// This function crafts appropriate label, corresponding to team name. +export function craftTeamLabel(teamName: string): Label { + switch (teamName) { + case 'extension-platform': + return { + name: `team-${teamName}`, + color: '#BFD4F2', // light blue + description: `Extension Platform team`, + }; + + case 'mobile-platform': + return { + name: `team-${teamName}`, + color: '#76E9D0', // light green + description: `Mobile Platform team`, + }; + + default: + return { + name: `team-*`, + color: 'EDEDED', // grey + description: `TODO: Unknown team. Please replace with correct team label.`, + }; + } +} + // This function creates or retrieves the label on a specific repo export async function createOrRetrieveLabel( octokit: InstanceType, diff --git a/.github/scripts/shared/project.ts b/.github/scripts/shared/project.ts new file mode 100644 index 000000000000..5dc672107d16 --- /dev/null +++ b/.github/scripts/shared/project.ts @@ -0,0 +1,270 @@ +import { GitHub } from '@actions/github/lib/utils'; +import { isValidDateFormat } from './utils'; + +const MAX_NB_FETCHES = 10; // For protection against infinite loops. + +export interface GithubProject { + id: string; + fields: GithubProjectField[]; +} + +export interface GithubProjectField { + id: string; + name: string; +} + +export interface GithubProjectIssueFieldValues { + id: string; // ID of the issue (unrelated to the Github Project board) + itemId: string; // ID of the issue, as an item of the Github Project board + cutDate: string; // "RC cut date" field value of the issue, as an item of the Github Project board +} + +interface RawGithubProjectIssueFieldValues { + id: string; + content: { + id: string; + }; + cutDate: { + date: string; + }; +} + +interface RawGithubProjectIssuesFieldValues { + pageInfo: { + endCursor: string; + }; + nodes: RawGithubProjectIssueFieldValues[]; +} + +// This function retrieves a Github Project +export async function retrieveGithubProject( + octokit: InstanceType, + projectNumber: number, +): Promise { + const retrieveProjectQuery = ` + query ($projectNumber: Int!) { + organization(login: "MetaMask") { + projectV2(number: $projectNumber) { + id + fields(first: 20) { + nodes { + ... on ProjectV2Field { + id + name + } + } + } + } + } + } + `; + + const retrieveProjectResult: { + organization: { + projectV2: { + id: string; + fields: { + nodes: { + id: string; + name: string; + }[]; + }; + }; + }; + } = await octokit.graphql(retrieveProjectQuery, { + projectNumber, + }); + + const project: GithubProject = { + id: retrieveProjectResult?.organization?.projectV2?.id, + fields: retrieveProjectResult?.organization.projectV2?.fields?.nodes, + }; + + if (!project) { + throw new Error(`Project with number ${projectNumber} was not found.`); + } + + if (!project.id) { + throw new Error(`Project with number ${projectNumber} was found, but it has no 'id' property.`); + } + + if (!project.fields) { + throw new Error(`Project with number ${projectNumber} was found, but it has no 'fields' property.`); + } + + return project; +} + +// This function retrieves a Github Project's issues' field values +export async function retrieveGithubProjectIssuesFieldValues( + octokit: InstanceType, + projectId: string, + cursor: string | undefined, +): Promise { + const after = cursor ? `after: "${cursor}"` : ''; + + const retrieveProjectIssuesFieldValuesQuery = ` + query ($projectId: ID!) { + node(id: $projectId) { + ... on ProjectV2 { + items( + first: 100 + ${after} + ) { + pageInfo { + endCursor + } + nodes { + id + content { + ... on Issue { + id + } + } + cutDate: fieldValueByName(name: "RC Cut") { + ... on ProjectV2ItemFieldDateValue { + date + } + } + } + } + } + } + } + `; + + const retrieveProjectIssuesFieldValuesResult: { + node: { + items: { + totalCount: number; + pageInfo: { + endCursor: string; + }; + nodes: { + id: string; + content: { + id: string; + }; + cutDate: { + date: string; + }; + }[]; + }; + }; + } = await octokit.graphql(retrieveProjectIssuesFieldValuesQuery, { + projectId, + }); + + const projectIssuesFieldValues: RawGithubProjectIssuesFieldValues = retrieveProjectIssuesFieldValuesResult.node.items; + + return projectIssuesFieldValues; +} + +// This function retrieves a Github Project's issue field values recursively +export async function retrieveGithubProjectIssueFieldValuesRecursively( + nbFetches: number, + octokit: InstanceType, + projectId: string, + issueId: string, + cursor: string | undefined, +): Promise { + if (nbFetches >= MAX_NB_FETCHES) { + throw new Error(`Forbidden: Trying to do more than ${MAX_NB_FETCHES} fetches (${nbFetches}).`); + } + + const projectIssuesFieldValuesResponse: RawGithubProjectIssuesFieldValues = await retrieveGithubProjectIssuesFieldValues( + octokit, + projectId, + cursor, + ); + + const projectIssueFieldValuesResponseWithSameId: RawGithubProjectIssueFieldValues | undefined = + projectIssuesFieldValuesResponse.nodes.find( + (issue) => issue.content?.id === issueId + ); // 'issue.content' can be equal to null in edge case where the Github Project board includes private repo issues that can't be accessed by the access token we're using + + if (projectIssueFieldValuesResponseWithSameId) { + const projectIssueFieldValues: GithubProjectIssueFieldValues = { + id: projectIssueFieldValuesResponseWithSameId.content?.id, + itemId: projectIssueFieldValuesResponseWithSameId.id, + cutDate: projectIssueFieldValuesResponseWithSameId.cutDate?.date, + }; + return projectIssueFieldValues; + } + + const newCursor = projectIssuesFieldValuesResponse.pageInfo.endCursor; + if (newCursor) { + return await retrieveGithubProjectIssueFieldValuesRecursively(nbFetches + 1, octokit, projectId, issueId, newCursor); + } else { + return undefined; + } +} + +// This function adds an issue to a Github Project +export async function addIssueToGithubProject( + octokit: InstanceType, + projectId: string, + issueId: string, +): Promise { + const addIssueToProjectMutation = ` + mutation ($projectId: ID!, $contentId: ID!) { + addProjectV2ItemById(input: {projectId: $projectId, contentId: $contentId}) { + clientMutationId + } + } + `; + + await octokit.graphql(addIssueToProjectMutation, { + projectId: projectId, + contentId: issueId, + }); +} + +// This function updates Github Project issue's date field value +export async function updateGithubProjectDateFieldValue( + octokit: InstanceType, + projectId: string, + projectFieldId: string, + issueId: string, + newDatePropertyValue: string, +): Promise { + if (!isValidDateFormat(newDatePropertyValue)) { + throw new Error(`Invalid input: date ${newDatePropertyValue} doesn't match "YYYY-MM-DD" format.`); + } + + const issue: GithubProjectIssueFieldValues | undefined = await retrieveGithubProjectIssueFieldValuesRecursively( + 0, + octokit, + projectId, + issueId, + undefined, + ); + + if (!issue) { + throw new Error(`Issue with ID ${issueId} was not found on Github Project with ID ${projectId}.`); + } + + const updateGithubProjectDatePropertyMutation = ` + mutation ($projectId: ID!, $itemId: ID!, $fieldId: ID!, $date: Date!) { + updateProjectV2ItemFieldValue( + input: { + projectId: $projectId + itemId: $itemId + fieldId: $fieldId + value: { date: $date } + } + ) { + projectV2Item { + id + } + } + } + `; + + await octokit.graphql(updateGithubProjectDatePropertyMutation, { + projectId: projectId, + itemId: issue.itemId, + fieldId: projectFieldId, + date: newDatePropertyValue, + }); +} diff --git a/.github/scripts/shared/repo.ts b/.github/scripts/shared/repo.ts index 09e475350016..d87a7fb5a198 100644 --- a/.github/scripts/shared/repo.ts +++ b/.github/scripts/shared/repo.ts @@ -25,5 +25,9 @@ export async function retrieveRepo( const repoId = retrieveRepoResult?.repository?.id; + if (!repoId) { + throw new Error(`Repo with owner ${repoOwner} and name ${repoName} was not found.`); + } + return repoId; } diff --git a/.github/scripts/shared/utils.ts b/.github/scripts/shared/utils.ts new file mode 100644 index 000000000000..6ecc4f4d092a --- /dev/null +++ b/.github/scripts/shared/utils.ts @@ -0,0 +1,50 @@ +// This helper function checks if version has the correct format: "x.y.z" where "x", "y" and "z" are numbers. +export function isValidVersionFormat(str: string): boolean { + const regex = /^\d+\.\d+\.\d+$/; + return regex.test(str); +} + +// This helper function checks if a string has the date format "YYYY-MM-DD". +export function isValidDateFormat(dateString: string): boolean { + // Regular expression to match the date format "YYYY-MM-DD" + const dateFormatRegex = /^\d{4}-\d{2}-\d{2}$/; + + // Check if the dateString matches the regex + if (!dateFormatRegex.test(dateString)) { + return false; + } + + // Parse the date components + const [year, month, day] = dateString.split('-').map(Number); + + // Check if the date components form a valid date + const date = new Date(year, month - 1, day); + return ( + date.getFullYear() === year && + date.getMonth() === month - 1 && + date.getDate() === day + ); +} + +// This helper function generates the current date in that format: "YYYY-MM-DD" +export function getCurrentDateFormatted(): string { + const date = new Date(); + + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); // Months are zero-based, so add 1 + const day = String(date.getDate()).padStart(2, '0'); + + return `${year}-${month}-${day}`; +} + +// This mapping is used to know what planning repo is used for each code repo +export const codeRepoToPlanningRepo: { [key: string]: string } = { + "metamask-extension": "MetaMask-planning", + "metamask-mobile": "mobile-planning" +} + +// This mapping is used to know what platform each code repo is used for +export const codeRepoToPlatform: { [key: string]: string } = { + "metamask-extension": "extension", + "metamask-mobile": "mobile", +} diff --git a/.github/workflows/create-bug-report.yml b/.github/workflows/create-bug-report.yml index c5164af00c02..65ff2f4ab9e5 100644 --- a/.github/workflows/create-bug-report.yml +++ b/.github/workflows/create-bug-report.yml @@ -12,24 +12,24 @@ jobs: if [[ "$GITHUB_REF" =~ ^refs/heads/Version-v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then version="${GITHUB_REF#refs/heads/Version-v}" echo "New release branch($version), continue next steps" - echo "version=$version" >> "$GITHUB_ENV" + echo "version=$version" >> "$GITHUB_OUTPUT" else echo "Not a release branch, skip next steps" fi + - name: Checkout repository + if: steps.extract_version.outputs.version + uses: actions/checkout@v4 + + - name: Setup environment + if: steps.extract_version.outputs.version + uses: metamask/github-tools/.github/actions/setup-environment@main + - name: Create bug report issue on planning repo - if: env.version - run: | - payload=$(cat < { + it('returns a restricted messenger', () => { + const messenger = new Messenger(); + const multichainNetworkControllerMessenger = + getMultichainNetworkControllerMessenger(messenger); + + expect(multichainNetworkControllerMessenger).toBeInstanceOf( + RestrictedMessenger, + ); + }); +}); diff --git a/app/scripts/controller-init/messengers/multichain/multichain-network-controller-messenger.ts b/app/scripts/controller-init/messengers/multichain/multichain-network-controller-messenger.ts new file mode 100644 index 000000000000..f002cde6595b --- /dev/null +++ b/app/scripts/controller-init/messengers/multichain/multichain-network-controller-messenger.ts @@ -0,0 +1,36 @@ +import { Messenger } from '@metamask/base-controller'; +import { AccountsControllerSelectedAccountChangeEvent } from '@metamask/accounts-controller'; +import { + type NetworkControllerSetActiveNetworkAction, + type NetworkControllerGetStateAction, +} from '@metamask/network-controller'; + +type Actions = + | NetworkControllerSetActiveNetworkAction + | NetworkControllerGetStateAction; + +type Events = AccountsControllerSelectedAccountChangeEvent; + +export type MultichainNetworkControllerMessenger = ReturnType< + typeof getMultichainNetworkControllerMessenger +>; + +/** + * Get a restricted messenger for the Multichain Network controller. This is scoped to the + * actions and events that the Multichain Network controller is allowed to handle. + * + * @param messenger - The controller messenger to restrict. + * @returns The restricted controller messenger. + */ +export function getMultichainNetworkControllerMessenger( + messenger: Messenger, +) { + return messenger.getRestricted({ + name: 'MultichainNetworkController', + allowedActions: [ + 'NetworkController:setActiveNetwork', + 'NetworkController:getState', + ], + allowedEvents: ['AccountsController:selectedAccountChange'], + }); +} diff --git a/app/scripts/controller-init/multichain/index.ts b/app/scripts/controller-init/multichain/index.ts index 4e7d2e132ced..fc3c0a9d8d33 100644 --- a/app/scripts/controller-init/multichain/index.ts +++ b/app/scripts/controller-init/multichain/index.ts @@ -1,4 +1,5 @@ export { MultichainAssetsControllerInit } from './multichain-assets-controller-init'; export { MultichainBalancesControllerInit } from './multichain-balances-controller-init'; export { MultichainTransactionsControllerInit } from './multichain-transactions-controller-init'; +export { MultichainNetworkControllerInit } from './multichain-network-controller-init'; export { MultiChainAssetsRatesControllerInit } from './multichain-rates-assets-controller-init'; diff --git a/app/scripts/controller-init/multichain/multichain-network-controller-init.test.ts b/app/scripts/controller-init/multichain/multichain-network-controller-init.test.ts new file mode 100644 index 000000000000..5a581c6d2db9 --- /dev/null +++ b/app/scripts/controller-init/multichain/multichain-network-controller-init.test.ts @@ -0,0 +1,52 @@ +import { MultichainNetworkController } from '@metamask/multichain-network-controller'; +import { Messenger } from '@metamask/base-controller'; +import { buildControllerInitRequestMock } from '../test/utils'; +import { ControllerInitRequest } from '../types'; +import { + MultichainNetworkControllerMessenger, + getMultichainNetworkControllerMessenger, +} from '../messengers/multichain'; +import { MultichainNetworkControllerInit } from './multichain-network-controller-init'; + +jest.mock('@metamask/multichain-network-controller'); + +const buildInitRequestMock = (): jest.Mocked< + ControllerInitRequest +> => { + const baseControllerMessenger = new Messenger(); + + return { + ...buildControllerInitRequestMock(), + controllerMessenger: getMultichainNetworkControllerMessenger( + baseControllerMessenger, + ), + initMessenger: undefined, + }; +}; + +describe('MultichainNetworkControllerInit', () => { + const multichainNetworkControllerClassMock = jest.mocked( + MultichainNetworkController, + ); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('returns controller instance', () => { + const requestMock = buildInitRequestMock(); + expect( + MultichainNetworkControllerInit(requestMock).controller, + ).toBeInstanceOf(MultichainNetworkController); + }); + + it('initializes with correct messenger and state', () => { + const requestMock = buildInitRequestMock(); + MultichainNetworkControllerInit(requestMock); + + expect(multichainNetworkControllerClassMock).toHaveBeenCalledWith({ + messenger: requestMock.controllerMessenger, + state: requestMock.persistedState.MultichainNetworkController, + }); + }); +}); diff --git a/app/scripts/controller-init/multichain/multichain-network-controller-init.ts b/app/scripts/controller-init/multichain/multichain-network-controller-init.ts new file mode 100644 index 000000000000..575df3283b6e --- /dev/null +++ b/app/scripts/controller-init/multichain/multichain-network-controller-init.ts @@ -0,0 +1,25 @@ +import { MultichainNetworkController } from '@metamask/multichain-network-controller'; +import { ControllerInitFunction } from '../types'; +import { MultichainNetworkControllerMessenger } from '../messengers/multichain'; + +/** + * Initialize the Multichain Network controller. + * + * @param request - The request object. + * @param request.controllerMessenger - The messenger to use for the controller. + * @param request.persistedState - The persisted state of the extension. + * @returns The initialized controller. + */ +export const MultichainNetworkControllerInit: ControllerInitFunction< + MultichainNetworkController, + MultichainNetworkControllerMessenger +> = ({ controllerMessenger, persistedState }) => { + const controller = new MultichainNetworkController({ + messenger: controllerMessenger, + state: persistedState.MultichainNetworkController, + }); + + return { + controller, + }; +}; diff --git a/app/scripts/controller-init/snaps/cronjob-controller-init.ts b/app/scripts/controller-init/snaps/cronjob-controller-init.ts index f959d87786ad..ce3b7ee2d00f 100644 --- a/app/scripts/controller-init/snaps/cronjob-controller-init.ts +++ b/app/scripts/controller-init/snaps/cronjob-controller-init.ts @@ -19,7 +19,6 @@ export const CronjobControllerInit: ControllerInitFunction< // with the expected type. // TODO: Look into the type mismatch. state: persistedState.CronjobController, - // @ts-expect-error TODO: Resolve mismatch between base-controller versions. messenger: controllerMessenger, }); diff --git a/app/scripts/controller-init/snaps/execution-service-init.ts b/app/scripts/controller-init/snaps/execution-service-init.ts index fff8cd4f2b32..01763747d259 100644 --- a/app/scripts/controller-init/snaps/execution-service-init.ts +++ b/app/scripts/controller-init/snaps/execution-service-init.ts @@ -53,7 +53,6 @@ export const ExecutionServiceInit: ControllerInitFunction< memStateKey: null, persistedStateKey: null, controller: new OffscreenExecutionService({ - // @ts-expect-error TODO: Resolve mismatch between base-controller versions. messenger: controllerMessenger, setupSnapProvider, offscreenPromise, @@ -68,7 +67,6 @@ export const ExecutionServiceInit: ControllerInitFunction< memStateKey: null, persistedStateKey: null, controller: new IframeExecutionService({ - // @ts-expect-error TODO: Resolve mismatch between base-controller versions. messenger: controllerMessenger, iframeUrl: new URL(iframeUrl), setupSnapProvider, diff --git a/app/scripts/controller-init/snaps/snap-insights-controller-init.ts b/app/scripts/controller-init/snaps/snap-insights-controller-init.ts index f7fc09baecbd..7ba84b1f4c46 100644 --- a/app/scripts/controller-init/snaps/snap-insights-controller-init.ts +++ b/app/scripts/controller-init/snaps/snap-insights-controller-init.ts @@ -19,7 +19,6 @@ export const SnapInsightsControllerInit: ControllerInitFunction< // compatible with the expected type. // TODO: Look into the type mismatch. state: persistedState.SnapInsightsController, - // @ts-expect-error TODO: Resolve mismatch between base-controller versions. messenger: controllerMessenger, }); diff --git a/app/scripts/controller-init/snaps/snap-interface-controller-init.ts b/app/scripts/controller-init/snaps/snap-interface-controller-init.ts index 4a3791c03580..f4947c7f90d2 100644 --- a/app/scripts/controller-init/snaps/snap-interface-controller-init.ts +++ b/app/scripts/controller-init/snaps/snap-interface-controller-init.ts @@ -19,7 +19,6 @@ export const SnapInterfaceControllerInit: ControllerInitFunction< // with the expected type. // TODO: Look into the type mismatch. state: persistedState.SnapInterfaceController, - // @ts-expect-error TODO: Resolve mismatch between base-controller versions. messenger: controllerMessenger, }); diff --git a/app/scripts/controller-init/snaps/snaps-registry-init.ts b/app/scripts/controller-init/snaps/snaps-registry-init.ts index 86d1297e17c6..f9dc8f720afa 100644 --- a/app/scripts/controller-init/snaps/snaps-registry-init.ts +++ b/app/scripts/controller-init/snaps/snaps-registry-init.ts @@ -22,7 +22,6 @@ export const SnapsRegistryInit: ControllerInitFunction< // with the expected type. // TODO: Look into the type mismatch. state: persistedState.SnapsRegistry, - // @ts-expect-error TODO: Resolve mismatch between base-controller versions. messenger: controllerMessenger, refetchOnAllowlistMiss: requireAllowlist, }); diff --git a/app/scripts/controllers/bridge/bridge-controller.ts b/app/scripts/controllers/bridge/bridge-controller.ts index 90a2a4dee193..27d7f230805d 100644 --- a/app/scripts/controllers/bridge/bridge-controller.ts +++ b/app/scripts/controllers/bridge/bridge-controller.ts @@ -207,9 +207,7 @@ export default class BridgeController extends StaticIntervalPollingController
{ this.#abortController?.abort('New quote request'); this.#abortController = new AbortController(); - if (updatedQuoteRequest.srcChainId === updatedQuoteRequest.destChainId) { - return; - } + const { bridgeState } = this.state; this.update((_state) => { _state.bridgeState = { diff --git a/app/scripts/controllers/mmi-controller.test.ts b/app/scripts/controllers/mmi-controller.test.ts index 72e5bfd724f6..591603c0f2d5 100644 --- a/app/scripts/controllers/mmi-controller.test.ts +++ b/app/scripts/controllers/mmi-controller.test.ts @@ -117,6 +117,7 @@ describe('MMIController', function () { 'SnapKeyring:accountAssetListUpdated', 'SnapKeyring:accountBalancesUpdated', 'SnapKeyring:accountTransactionsUpdated', + 'MultichainNetworkController:networkDidChange', ], allowedActions: [ 'AccountsController:setCurrentAccount', @@ -773,6 +774,9 @@ describe('MMIController', function () { describe('handleMmiDashboardData', () => { it('should return internalAccounts as identities', async () => { + jest + .spyOn(mmiController.keyringController, 'getAccounts') + .mockReturnValue([mockAccount.address, mockAccount2.address]); const controllerMessengerSpy = jest.spyOn(mmiControllerMessenger, 'call'); await mmiController.handleMmiDashboardData(); diff --git a/app/scripts/controllers/preferences-controller.test.ts b/app/scripts/controllers/preferences-controller.test.ts index efcd766ce1c9..f3703f3c94e7 100644 --- a/app/scripts/controllers/preferences-controller.test.ts +++ b/app/scripts/controllers/preferences-controller.test.ts @@ -4,6 +4,7 @@ import { Messenger } from '@metamask/base-controller'; import { AccountsController } from '@metamask/accounts-controller'; import { KeyringControllerStateChangeEvent } from '@metamask/keyring-controller'; +import type { MultichainNetworkControllerNetworkDidChangeEvent } from '@metamask/multichain-network-controller'; import { SnapControllerStateChangeEvent } from '@metamask/snaps-controllers'; import { Hex } from '@metamask/utils'; import { @@ -52,6 +53,7 @@ const setupController = ({ | SnapKeyringAccountAssetListUpdatedEvent | SnapKeyringAccountBalancesUpdatedEvent | SnapKeyringAccountTransactionsUpdatedEvent + | MultichainNetworkControllerNetworkDidChangeEvent >(); const preferencesControllerMessenger: PreferencesControllerMessenger = messenger.getRestricted({ @@ -85,6 +87,7 @@ const setupController = ({ 'SnapKeyring:accountAssetListUpdated', 'SnapKeyring:accountBalancesUpdated', 'SnapKeyring:accountTransactionsUpdated', + 'MultichainNetworkController:networkDidChange', ], allowedActions: [], }); diff --git a/app/scripts/metamask-controller.actions.test.js b/app/scripts/metamask-controller.actions.test.js index f3dc22b46865..5ee364171ce2 100644 --- a/app/scripts/metamask-controller.actions.test.js +++ b/app/scripts/metamask-controller.actions.test.js @@ -202,7 +202,10 @@ describe('MetaMaskController', function () { describe('#setLocked', function () { it('should lock the wallet', async function () { + await metamaskController.createNewVaultAndKeychain('test@123'); + await metamaskController.setLocked(); + expect( metamaskController.keyringController.state.isUnlocked, ).toStrictEqual(false); diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 7596e11cb93a..29ea67419583 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -351,14 +351,15 @@ import { handleBridgeTransactionFailed, handleTransactionFailedTypeBridge, } from './lib/bridge-status/metrics'; -///: BEGIN:ONLY_INCLUDE_IF(build-flask) import { + ///: BEGIN:ONLY_INCLUDE_IF(build-flask) MultichainAssetsControllerInit, MultichainTransactionsControllerInit, MultichainBalancesControllerInit, MultiChainAssetsRatesControllerInit, + ///: END:ONLY_INCLUDE_IF + MultichainNetworkControllerInit, } from './controller-init/multichain'; -///: END:ONLY_INCLUDE_IF import { TransactionControllerInit } from './controller-init/confirmations/transaction-controller-init'; import { PPOMControllerInit } from './controller-init/confirmations/ppom-controller-init'; import { initControllers } from './controller-init/utils'; @@ -626,6 +627,7 @@ export default class MetamaskController extends EventEmitter { 'SnapKeyring:accountAssetListUpdated', 'SnapKeyring:accountBalancesUpdated', 'SnapKeyring:accountTransactionsUpdated', + 'MultichainNetworkController:networkDidChange', ], allowedActions: [ 'KeyringController:getAccounts', @@ -2021,6 +2023,7 @@ export default class MetamaskController extends EventEmitter { MultichainBalancesController: MultichainBalancesControllerInit, MultichainTransactionsController: MultichainTransactionsControllerInit, ///: END:ONLY_INCLUDE_IF + MultichainNetworkController: MultichainNetworkControllerInit, }; const { @@ -2058,6 +2061,8 @@ export default class MetamaskController extends EventEmitter { this.multiChainAssetsRatesController = controllersByName.MultiChainAssetsRatesController; ///: END:ONLY_INCLUDE_IF + this.multichainNetworkController = + controllersByName.MultichainNetworkController; this.controllerMessenger.subscribe( 'TransactionController:transactionStatusUpdated', @@ -2185,6 +2190,7 @@ export default class MetamaskController extends EventEmitter { MetaMetricsDataDeletionController: this.metaMetricsDataDeletionController, AddressBookController: this.addressBookController, CurrencyController: this.currencyRateController, + MultichainNetworkController: this.multichainNetworkController, NetworkController: this.networkController, AlertController: this.alertController, OnboardingController: this.onboardingController, @@ -2234,6 +2240,7 @@ export default class MetamaskController extends EventEmitter { MultichainTransactionsController: this.multichainTransactionsController, MultiChainAssetsRatesController: this.multiChainAssetsRatesController, ///: END:ONLY_INCLUDE_IF + MultichainNetworkController: this.multichainNetworkController, NetworkController: this.networkController, KeyringController: this.keyringController, PreferencesController: this.preferencesController, @@ -2544,14 +2551,32 @@ export default class MetamaskController extends EventEmitter { } /** - * Gets whether the privacy mode is enabled from the PreferencesController. + * Gets a subset of preferences from the PreferencesController to pass to a snap. * - * @returns {boolean} Whether the privacy mode is enabled. + * @returns {object} A subset of preferences. */ - getPrivacyMode() { - const { privacyMode } = this.preferencesController.state; + getPreferences() { + const { + preferences, + securityAlertsEnabled, + useCurrencyRateCheck, + useTransactionSimulations, + useTokenDetection, + useMultiAccountBalanceChecker, + openSeaEnabled, + useNftDetection, + } = this.preferencesController.state; - return privacyMode; + return { + privacyMode: preferences.privacyMode, + securityAlertsEnabled, + useCurrencyRateCheck, + useTransactionSimulations, + useTokenDetection, + useMultiAccountBalanceChecker, + openSeaEnabled, + useNftDetection, + }; } /** @@ -2566,8 +2591,28 @@ export default class MetamaskController extends EventEmitter { getPreferences: () => { const locale = this.getLocale(); const currency = this.currencyRateController.state.currentCurrency; - const hideBalances = this.getPrivacyMode(); - return { locale, currency, hideBalances }; + const { + privacyMode, + securityAlertsEnabled, + useCurrencyRateCheck, + useTransactionSimulations, + useTokenDetection, + useMultiAccountBalanceChecker, + openSeaEnabled, + useNftDetection, + } = this.getPreferences(); + return { + locale, + currency, + hideBalances: privacyMode, + useSecurityAlerts: securityAlertsEnabled, + useExternalPricingData: useCurrencyRateCheck, + simulateOnChainActions: useTransactionSimulations, + useTokenDetection, + batchCheckBalances: useMultiAccountBalanceChecker, + displayNftMedia: openSeaEnabled, + useNftDetection, + }; }, clearSnapState: this.controllerMessenger.call.bind( this.controllerMessenger, diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js index 932acec5a519..ab0f4cb64f5c 100644 --- a/app/scripts/metamask-controller.test.js +++ b/app/scripts/metamask-controller.test.js @@ -638,6 +638,7 @@ describe('MetaMaskController', () => { describe('setLocked', () => { it('should lock KeyringController', async () => { + await metamaskController.createNewVaultAndKeychain('password'); jest.spyOn(metamaskController.keyringController, 'setLocked'); await metamaskController.setLocked(); @@ -2650,17 +2651,18 @@ describe('MetaMaskController', () => { }); describe('#addNewAccount', () => { - it('errors when an primary keyring is does not exist', async () => { + it('throws an error if the keyring controller is locked', async () => { const addNewAccount = metamaskController.addNewAccount(); - - await expect(addNewAccount).rejects.toThrow('No HD keyring found'); + await expect(addNewAccount).rejects.toThrow( + 'KeyringController - The operation cannot be completed while the controller is locked.', + ); }); }); describe('#getSeedPhrase', () => { - it('errors when no password is provided', async () => { + it('throws error if keyring controller is locked', async () => { await expect(metamaskController.getSeedPhrase()).rejects.toThrow( - 'KeyringController - Cannot unlock without a previous vault.', + 'KeyringController - The operation cannot be completed while the controller is locked.', ); }); diff --git a/builds.yml b/builds.yml index eea8601d2754..a3b7fc3b71ff 100644 --- a/builds.yml +++ b/builds.yml @@ -27,7 +27,7 @@ buildTypes: - ALLOW_LOCAL_SNAPS: false - REQUIRE_SNAPS_ALLOWLIST: true - REJECT_INVALID_SNAPS_PLATFORM_VERSION: true - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.14.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/7.0.0/index.html - ACCOUNT_SNAPS_DIRECTORY_URL: https://snaps.metamask.io/account-management # Main build uses the default browser manifest manifestOverrides: false @@ -48,7 +48,7 @@ buildTypes: - ALLOW_LOCAL_SNAPS: false - REQUIRE_SNAPS_ALLOWLIST: true - REJECT_INVALID_SNAPS_PLATFORM_VERSION: true - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.14.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/7.0.0/index.html - ACCOUNT_SNAPS_DIRECTORY_URL: https://snaps.metamask.io/account-management # Modifies how the version is displayed. # eg. instead of 10.25.0 -> 10.25.0-beta.2 @@ -72,7 +72,7 @@ buildTypes: - ALLOW_LOCAL_SNAPS: true - REQUIRE_SNAPS_ALLOWLIST: false - REJECT_INVALID_SNAPS_PLATFORM_VERSION: false - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.14.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/7.0.0/index.html - SUPPORT_LINK: https://support.metamask.io/ - SUPPORT_REQUEST_LINK: https://support.metamask.io/ - INFURA_ENV_KEY_REF: INFURA_FLASK_PROJECT_ID @@ -96,7 +96,7 @@ buildTypes: - ALLOW_LOCAL_SNAPS: false - REQUIRE_SNAPS_ALLOWLIST: true - REJECT_INVALID_SNAPS_PLATFORM_VERSION: true - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.14.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/7.0.0/index.html - MMI_CONFIGURATION_SERVICE_URL: https://configuration.metamask-institutional.io/v2/configuration/default - SUPPORT_LINK: https://support.metamask-institutional.io - SUPPORT_REQUEST_LINK: https://support.metamask-institutional.io diff --git a/development/circular-deps.jsonc b/development/circular-deps.jsonc index 62b7b0a36dd5..84e435dfeb36 100644 --- a/development/circular-deps.jsonc +++ b/development/circular-deps.jsonc @@ -48,16 +48,8 @@ "ui/components/multichain/pages/send/components/index.ts", "ui/components/multichain/pages/send/components/your-accounts.tsx" ], - [ - "ui/pages/asset/components/asset-page.tsx", - "ui/pages/asset/components/token-buttons.tsx" - ], [ "ui/pages/notifications/notifications-list.tsx", "ui/pages/notifications/notifications.tsx" - ], - [ - "ui/pages/swaps/swaps.util.ts", - "ui/store/actions.ts" ] ] diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index 907bd244f3ec..f2be8708fa4f 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -894,7 +894,7 @@ "console.info": true }, "packages": { - "@metamask/approval-controller>@metamask/base-controller": true, + "@metamask/base-controller": true, "@metamask/rpc-errors": true, "nanoid": true } @@ -928,7 +928,7 @@ "@metamask/metamask-eth-abis": true, "@metamask/polling-controller": true, "@metamask/rpc-errors": true, - "@metamask/snaps-utils": true, + "@metamask/assets-controllers>@metamask/snaps-utils": true, "@metamask/assets-controllers>@metamask/utils": true, "@metamask/name-controller>async-mutex": true, "bn.js": true, @@ -954,14 +954,6 @@ "immer": true } }, - "@metamask/approval-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, "@metamask/network-controller>@metamask/base-controller": { "globals": { "setTimeout": true @@ -1010,14 +1002,6 @@ "immer": true } }, - "@metamask/snaps-controllers>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, "@metamask/transaction-controller>@metamask/base-controller": { "globals": { "setTimeout": true @@ -1434,6 +1418,14 @@ "@metamask/keyring-api>bech32": true } }, + "@metamask/multichain-transactions-controller>@metamask/keyring-snap-client>@metamask/keyring-api": { + "packages": { + "@metamask/keyring-api>@metamask/keyring-utils": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/multichain-transactions-controller>@metamask/utils": true, + "@metamask/keyring-api>bech32": true + } + }, "@metamask/keyring-controller": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, @@ -1460,6 +1452,14 @@ "@metamask/keyring-snap-client>uuid": true } }, + "@metamask/multichain-transactions-controller>@metamask/keyring-snap-client": { + "packages": { + "@metamask/multichain-transactions-controller>@metamask/keyring-snap-client>@metamask/keyring-api": true, + "@metamask/keyring-api>@metamask/keyring-utils": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/multichain-transactions-controller>@metamask/keyring-snap-client>uuid": true + } + }, "@metamask/keyring-api>@metamask/keyring-utils": { "globals": { "URL": true @@ -1518,6 +1518,27 @@ "lodash": true } }, + "@metamask/multichain-network-controller": { + "packages": { + "@metamask/base-controller": true, + "@metamask/keyring-api": true, + "@metamask/network-controller": true, + "@metamask/multichain-network-controller>@metamask/utils": true, + "@metamask/multichain-network-controller>@solana/addresses": true + } + }, + "@metamask/multichain-transactions-controller": { + "globals": { + "console.error": true + }, + "packages": { + "@metamask/base-controller": true, + "@metamask/keyring-api": true, + "@metamask/multichain-transactions-controller>@metamask/keyring-snap-client": true, + "@metamask/multichain-transactions-controller>@metamask/snaps-utils": true, + "@metamask/multichain-transactions-controller>@metamask/utils": true + } + }, "@metamask/name-controller": { "globals": { "fetch": true @@ -1849,7 +1870,7 @@ "setTimeout": true }, "packages": { - "@metamask/snaps-controllers>@metamask/base-controller": true, + "@metamask/base-controller": true, "@metamask/json-rpc-engine": true, "@metamask/json-rpc-middleware-stream": true, "@metamask/object-multiplex": true, @@ -1954,6 +1975,76 @@ "@metamask/snaps-utils>validate-npm-package-name": true } }, + "@metamask/assets-controllers>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "crypto": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/assets-controllers>@metamask/utils": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "chalk": true, + "@metamask/snaps-utils>cron-parser": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/multichain-transactions-controller>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "crypto": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/multichain-transactions-controller>@metamask/utils": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "chalk": true, + "@metamask/snaps-utils>cron-parser": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, "@metamask/transaction-controller": { "globals": { "clearTimeout": true, @@ -2397,6 +2488,36 @@ "semver": true } }, + "@metamask/multichain-network-controller>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, + "@metamask/multichain-transactions-controller>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, "@metamask/name-controller>@metamask/utils": { "globals": { "TextDecoder": true, @@ -2982,6 +3103,20 @@ "@solana/addresses>@solana/errors": true } }, + "@metamask/multichain-network-controller>@solana/addresses": { + "globals": { + "Intl.Collator": true, + "TextEncoder": true, + "crypto.subtle.digest": true, + "crypto.subtle.exportKey": true + }, + "packages": { + "@metamask/multichain-network-controller>@solana/addresses>@solana/assertions": true, + "@metamask/multichain-network-controller>@solana/addresses>@solana/codecs-core": true, + "@metamask/multichain-network-controller>@solana/addresses>@solana/codecs-strings": true, + "@metamask/multichain-network-controller>@solana/addresses>@solana/errors": true + } + }, "@solana/addresses>@solana/assertions": { "globals": { "crypto": true, @@ -2991,11 +3126,25 @@ "@solana/addresses>@solana/errors": true } }, + "@metamask/multichain-network-controller>@solana/addresses>@solana/assertions": { + "globals": { + "crypto": true, + "isSecureContext": true + }, + "packages": { + "@metamask/multichain-network-controller>@solana/addresses>@solana/errors": true + } + }, "@solana/addresses>@solana/codecs-core": { "packages": { "@solana/addresses>@solana/errors": true } }, + "@metamask/multichain-network-controller>@solana/addresses>@solana/codecs-core": { + "packages": { + "@metamask/multichain-network-controller>@solana/addresses>@solana/errors": true + } + }, "@solana/addresses>@solana/codecs-strings": { "globals": { "TextDecoder": true, @@ -3008,11 +3157,28 @@ "@solana/addresses>@solana/errors": true } }, + "@metamask/multichain-network-controller>@solana/addresses>@solana/codecs-strings": { + "globals": { + "TextDecoder": true, + "TextEncoder": true, + "atob": true, + "btoa": true + }, + "packages": { + "@metamask/multichain-network-controller>@solana/addresses>@solana/codecs-core": true, + "@metamask/multichain-network-controller>@solana/addresses>@solana/errors": true + } + }, "@solana/addresses>@solana/errors": { "globals": { "btoa": true } }, + "@metamask/multichain-network-controller>@solana/addresses>@solana/errors": { + "globals": { + "btoa": true + } + }, "@metamask/controller-utils>@spruceid/siwe-parser": { "globals": { "console.error": true, @@ -5874,6 +6040,11 @@ "crypto": true } }, + "@metamask/multichain-transactions-controller>@metamask/keyring-snap-client>uuid": { + "globals": { + "crypto": true + } + }, "eth-lattice-keyring>gridplus-sdk>uuid": { "globals": { "crypto": true diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index 7c180c8360db..f2be8708fa4f 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -894,7 +894,7 @@ "console.info": true }, "packages": { - "@metamask/approval-controller>@metamask/base-controller": true, + "@metamask/base-controller": true, "@metamask/rpc-errors": true, "nanoid": true } @@ -928,7 +928,7 @@ "@metamask/metamask-eth-abis": true, "@metamask/polling-controller": true, "@metamask/rpc-errors": true, - "@metamask/snaps-utils": true, + "@metamask/assets-controllers>@metamask/snaps-utils": true, "@metamask/assets-controllers>@metamask/utils": true, "@metamask/name-controller>async-mutex": true, "bn.js": true, @@ -954,14 +954,6 @@ "immer": true } }, - "@metamask/approval-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, "@metamask/network-controller>@metamask/base-controller": { "globals": { "setTimeout": true @@ -1010,14 +1002,6 @@ "immer": true } }, - "@metamask/snaps-controllers>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, "@metamask/transaction-controller>@metamask/base-controller": { "globals": { "setTimeout": true @@ -1534,6 +1518,15 @@ "lodash": true } }, + "@metamask/multichain-network-controller": { + "packages": { + "@metamask/base-controller": true, + "@metamask/keyring-api": true, + "@metamask/network-controller": true, + "@metamask/multichain-network-controller>@metamask/utils": true, + "@metamask/multichain-network-controller>@solana/addresses": true + } + }, "@metamask/multichain-transactions-controller": { "globals": { "console.error": true @@ -1542,7 +1535,7 @@ "@metamask/base-controller": true, "@metamask/keyring-api": true, "@metamask/multichain-transactions-controller>@metamask/keyring-snap-client": true, - "@metamask/snaps-utils": true, + "@metamask/multichain-transactions-controller>@metamask/snaps-utils": true, "@metamask/multichain-transactions-controller>@metamask/utils": true } }, @@ -1877,7 +1870,7 @@ "setTimeout": true }, "packages": { - "@metamask/snaps-controllers>@metamask/base-controller": true, + "@metamask/base-controller": true, "@metamask/json-rpc-engine": true, "@metamask/json-rpc-middleware-stream": true, "@metamask/object-multiplex": true, @@ -1982,6 +1975,76 @@ "@metamask/snaps-utils>validate-npm-package-name": true } }, + "@metamask/assets-controllers>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "crypto": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/assets-controllers>@metamask/utils": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "chalk": true, + "@metamask/snaps-utils>cron-parser": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/multichain-transactions-controller>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "crypto": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/multichain-transactions-controller>@metamask/utils": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "chalk": true, + "@metamask/snaps-utils>cron-parser": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, "@metamask/transaction-controller": { "globals": { "clearTimeout": true, @@ -2425,6 +2488,21 @@ "semver": true } }, + "@metamask/multichain-network-controller>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, "@metamask/multichain-transactions-controller>@metamask/utils": { "globals": { "TextDecoder": true, @@ -3025,6 +3103,20 @@ "@solana/addresses>@solana/errors": true } }, + "@metamask/multichain-network-controller>@solana/addresses": { + "globals": { + "Intl.Collator": true, + "TextEncoder": true, + "crypto.subtle.digest": true, + "crypto.subtle.exportKey": true + }, + "packages": { + "@metamask/multichain-network-controller>@solana/addresses>@solana/assertions": true, + "@metamask/multichain-network-controller>@solana/addresses>@solana/codecs-core": true, + "@metamask/multichain-network-controller>@solana/addresses>@solana/codecs-strings": true, + "@metamask/multichain-network-controller>@solana/addresses>@solana/errors": true + } + }, "@solana/addresses>@solana/assertions": { "globals": { "crypto": true, @@ -3034,11 +3126,25 @@ "@solana/addresses>@solana/errors": true } }, + "@metamask/multichain-network-controller>@solana/addresses>@solana/assertions": { + "globals": { + "crypto": true, + "isSecureContext": true + }, + "packages": { + "@metamask/multichain-network-controller>@solana/addresses>@solana/errors": true + } + }, "@solana/addresses>@solana/codecs-core": { "packages": { "@solana/addresses>@solana/errors": true } }, + "@metamask/multichain-network-controller>@solana/addresses>@solana/codecs-core": { + "packages": { + "@metamask/multichain-network-controller>@solana/addresses>@solana/errors": true + } + }, "@solana/addresses>@solana/codecs-strings": { "globals": { "TextDecoder": true, @@ -3051,11 +3157,28 @@ "@solana/addresses>@solana/errors": true } }, + "@metamask/multichain-network-controller>@solana/addresses>@solana/codecs-strings": { + "globals": { + "TextDecoder": true, + "TextEncoder": true, + "atob": true, + "btoa": true + }, + "packages": { + "@metamask/multichain-network-controller>@solana/addresses>@solana/codecs-core": true, + "@metamask/multichain-network-controller>@solana/addresses>@solana/errors": true + } + }, "@solana/addresses>@solana/errors": { "globals": { "btoa": true } }, + "@metamask/multichain-network-controller>@solana/addresses>@solana/errors": { + "globals": { + "btoa": true + } + }, "@metamask/controller-utils>@spruceid/siwe-parser": { "globals": { "console.error": true, diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index 907bd244f3ec..f2be8708fa4f 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -894,7 +894,7 @@ "console.info": true }, "packages": { - "@metamask/approval-controller>@metamask/base-controller": true, + "@metamask/base-controller": true, "@metamask/rpc-errors": true, "nanoid": true } @@ -928,7 +928,7 @@ "@metamask/metamask-eth-abis": true, "@metamask/polling-controller": true, "@metamask/rpc-errors": true, - "@metamask/snaps-utils": true, + "@metamask/assets-controllers>@metamask/snaps-utils": true, "@metamask/assets-controllers>@metamask/utils": true, "@metamask/name-controller>async-mutex": true, "bn.js": true, @@ -954,14 +954,6 @@ "immer": true } }, - "@metamask/approval-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, "@metamask/network-controller>@metamask/base-controller": { "globals": { "setTimeout": true @@ -1010,14 +1002,6 @@ "immer": true } }, - "@metamask/snaps-controllers>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, "@metamask/transaction-controller>@metamask/base-controller": { "globals": { "setTimeout": true @@ -1434,6 +1418,14 @@ "@metamask/keyring-api>bech32": true } }, + "@metamask/multichain-transactions-controller>@metamask/keyring-snap-client>@metamask/keyring-api": { + "packages": { + "@metamask/keyring-api>@metamask/keyring-utils": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/multichain-transactions-controller>@metamask/utils": true, + "@metamask/keyring-api>bech32": true + } + }, "@metamask/keyring-controller": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, @@ -1460,6 +1452,14 @@ "@metamask/keyring-snap-client>uuid": true } }, + "@metamask/multichain-transactions-controller>@metamask/keyring-snap-client": { + "packages": { + "@metamask/multichain-transactions-controller>@metamask/keyring-snap-client>@metamask/keyring-api": true, + "@metamask/keyring-api>@metamask/keyring-utils": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/multichain-transactions-controller>@metamask/keyring-snap-client>uuid": true + } + }, "@metamask/keyring-api>@metamask/keyring-utils": { "globals": { "URL": true @@ -1518,6 +1518,27 @@ "lodash": true } }, + "@metamask/multichain-network-controller": { + "packages": { + "@metamask/base-controller": true, + "@metamask/keyring-api": true, + "@metamask/network-controller": true, + "@metamask/multichain-network-controller>@metamask/utils": true, + "@metamask/multichain-network-controller>@solana/addresses": true + } + }, + "@metamask/multichain-transactions-controller": { + "globals": { + "console.error": true + }, + "packages": { + "@metamask/base-controller": true, + "@metamask/keyring-api": true, + "@metamask/multichain-transactions-controller>@metamask/keyring-snap-client": true, + "@metamask/multichain-transactions-controller>@metamask/snaps-utils": true, + "@metamask/multichain-transactions-controller>@metamask/utils": true + } + }, "@metamask/name-controller": { "globals": { "fetch": true @@ -1849,7 +1870,7 @@ "setTimeout": true }, "packages": { - "@metamask/snaps-controllers>@metamask/base-controller": true, + "@metamask/base-controller": true, "@metamask/json-rpc-engine": true, "@metamask/json-rpc-middleware-stream": true, "@metamask/object-multiplex": true, @@ -1954,6 +1975,76 @@ "@metamask/snaps-utils>validate-npm-package-name": true } }, + "@metamask/assets-controllers>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "crypto": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/assets-controllers>@metamask/utils": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "chalk": true, + "@metamask/snaps-utils>cron-parser": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/multichain-transactions-controller>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "crypto": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/multichain-transactions-controller>@metamask/utils": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "chalk": true, + "@metamask/snaps-utils>cron-parser": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, "@metamask/transaction-controller": { "globals": { "clearTimeout": true, @@ -2397,6 +2488,36 @@ "semver": true } }, + "@metamask/multichain-network-controller>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, + "@metamask/multichain-transactions-controller>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, "@metamask/name-controller>@metamask/utils": { "globals": { "TextDecoder": true, @@ -2982,6 +3103,20 @@ "@solana/addresses>@solana/errors": true } }, + "@metamask/multichain-network-controller>@solana/addresses": { + "globals": { + "Intl.Collator": true, + "TextEncoder": true, + "crypto.subtle.digest": true, + "crypto.subtle.exportKey": true + }, + "packages": { + "@metamask/multichain-network-controller>@solana/addresses>@solana/assertions": true, + "@metamask/multichain-network-controller>@solana/addresses>@solana/codecs-core": true, + "@metamask/multichain-network-controller>@solana/addresses>@solana/codecs-strings": true, + "@metamask/multichain-network-controller>@solana/addresses>@solana/errors": true + } + }, "@solana/addresses>@solana/assertions": { "globals": { "crypto": true, @@ -2991,11 +3126,25 @@ "@solana/addresses>@solana/errors": true } }, + "@metamask/multichain-network-controller>@solana/addresses>@solana/assertions": { + "globals": { + "crypto": true, + "isSecureContext": true + }, + "packages": { + "@metamask/multichain-network-controller>@solana/addresses>@solana/errors": true + } + }, "@solana/addresses>@solana/codecs-core": { "packages": { "@solana/addresses>@solana/errors": true } }, + "@metamask/multichain-network-controller>@solana/addresses>@solana/codecs-core": { + "packages": { + "@metamask/multichain-network-controller>@solana/addresses>@solana/errors": true + } + }, "@solana/addresses>@solana/codecs-strings": { "globals": { "TextDecoder": true, @@ -3008,11 +3157,28 @@ "@solana/addresses>@solana/errors": true } }, + "@metamask/multichain-network-controller>@solana/addresses>@solana/codecs-strings": { + "globals": { + "TextDecoder": true, + "TextEncoder": true, + "atob": true, + "btoa": true + }, + "packages": { + "@metamask/multichain-network-controller>@solana/addresses>@solana/codecs-core": true, + "@metamask/multichain-network-controller>@solana/addresses>@solana/errors": true + } + }, "@solana/addresses>@solana/errors": { "globals": { "btoa": true } }, + "@metamask/multichain-network-controller>@solana/addresses>@solana/errors": { + "globals": { + "btoa": true + } + }, "@metamask/controller-utils>@spruceid/siwe-parser": { "globals": { "console.error": true, @@ -5874,6 +6040,11 @@ "crypto": true } }, + "@metamask/multichain-transactions-controller>@metamask/keyring-snap-client>uuid": { + "globals": { + "crypto": true + } + }, "eth-lattice-keyring>gridplus-sdk>uuid": { "globals": { "crypto": true diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index 6372097263b5..44d73b794f60 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -986,7 +986,7 @@ "console.info": true }, "packages": { - "@metamask/approval-controller>@metamask/base-controller": true, + "@metamask/base-controller": true, "@metamask/rpc-errors": true, "nanoid": true } @@ -1020,7 +1020,7 @@ "@metamask/metamask-eth-abis": true, "@metamask/polling-controller": true, "@metamask/rpc-errors": true, - "@metamask/snaps-utils": true, + "@metamask/assets-controllers>@metamask/snaps-utils": true, "@metamask/assets-controllers>@metamask/utils": true, "@metamask/name-controller>async-mutex": true, "bn.js": true, @@ -1046,14 +1046,6 @@ "immer": true } }, - "@metamask/approval-controller>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, "@metamask/network-controller>@metamask/base-controller": { "globals": { "setTimeout": true @@ -1102,14 +1094,6 @@ "immer": true } }, - "@metamask/snaps-controllers>@metamask/base-controller": { - "globals": { - "setTimeout": true - }, - "packages": { - "immer": true - } - }, "@metamask/transaction-controller>@metamask/base-controller": { "globals": { "setTimeout": true @@ -1526,6 +1510,14 @@ "@metamask/keyring-api>bech32": true } }, + "@metamask/multichain-transactions-controller>@metamask/keyring-snap-client>@metamask/keyring-api": { + "packages": { + "@metamask/keyring-api>@metamask/keyring-utils": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/multichain-transactions-controller>@metamask/utils": true, + "@metamask/keyring-api>bech32": true + } + }, "@metamask/keyring-controller": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, @@ -1552,6 +1544,14 @@ "@metamask/keyring-snap-client>uuid": true } }, + "@metamask/multichain-transactions-controller>@metamask/keyring-snap-client": { + "packages": { + "@metamask/multichain-transactions-controller>@metamask/keyring-snap-client>@metamask/keyring-api": true, + "@metamask/keyring-api>@metamask/keyring-utils": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/multichain-transactions-controller>@metamask/keyring-snap-client>uuid": true + } + }, "@metamask/keyring-api>@metamask/keyring-utils": { "globals": { "URL": true @@ -1610,6 +1610,27 @@ "lodash": true } }, + "@metamask/multichain-network-controller": { + "packages": { + "@metamask/base-controller": true, + "@metamask/keyring-api": true, + "@metamask/network-controller": true, + "@metamask/multichain-network-controller>@metamask/utils": true, + "@metamask/multichain-network-controller>@solana/addresses": true + } + }, + "@metamask/multichain-transactions-controller": { + "globals": { + "console.error": true + }, + "packages": { + "@metamask/base-controller": true, + "@metamask/keyring-api": true, + "@metamask/multichain-transactions-controller>@metamask/keyring-snap-client": true, + "@metamask/multichain-transactions-controller>@metamask/snaps-utils": true, + "@metamask/multichain-transactions-controller>@metamask/utils": true + } + }, "@metamask/name-controller": { "globals": { "fetch": true @@ -1941,7 +1962,7 @@ "setTimeout": true }, "packages": { - "@metamask/snaps-controllers>@metamask/base-controller": true, + "@metamask/base-controller": true, "@metamask/json-rpc-engine": true, "@metamask/json-rpc-middleware-stream": true, "@metamask/object-multiplex": true, @@ -2046,6 +2067,76 @@ "@metamask/snaps-utils>validate-npm-package-name": true } }, + "@metamask/assets-controllers>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "crypto": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/assets-controllers>@metamask/utils": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "chalk": true, + "@metamask/snaps-utils>cron-parser": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, + "@metamask/multichain-transactions-controller>@metamask/snaps-utils": { + "globals": { + "File": true, + "FileReader": true, + "TextDecoder": true, + "TextEncoder": true, + "URL": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "crypto": true, + "document.body.appendChild": true, + "document.createElement": true, + "fetch": true + }, + "packages": { + "@metamask/snaps-sdk>@metamask/key-tree": true, + "@metamask/permission-controller": true, + "@metamask/rpc-errors": true, + "@metamask/snaps-utils>@metamask/slip44": true, + "@metamask/snaps-sdk": true, + "@metamask/utils>@metamask/superstruct": true, + "@metamask/multichain-transactions-controller>@metamask/utils": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "chalk": true, + "@metamask/snaps-utils>cron-parser": true, + "@metamask/snaps-utils>fast-json-stable-stringify": true, + "@metamask/snaps-utils>fast-xml-parser": true, + "@metamask/snaps-utils>marked": true, + "@metamask/snaps-utils>rfdc": true, + "semver": true, + "@metamask/snaps-utils>validate-npm-package-name": true + } + }, "@metamask/transaction-controller": { "globals": { "clearTimeout": true, @@ -2489,6 +2580,36 @@ "semver": true } }, + "@metamask/multichain-network-controller>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, + "@metamask/multichain-transactions-controller>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@metamask/utils>@metamask/superstruct": true, + "@noble/hashes": true, + "@metamask/utils>@scure/base": true, + "browserify>buffer": true, + "nock>debug": true, + "@metamask/utils>pony-cause": true, + "semver": true + } + }, "@metamask/name-controller>@metamask/utils": { "globals": { "TextDecoder": true, @@ -3074,6 +3195,20 @@ "@solana/addresses>@solana/errors": true } }, + "@metamask/multichain-network-controller>@solana/addresses": { + "globals": { + "Intl.Collator": true, + "TextEncoder": true, + "crypto.subtle.digest": true, + "crypto.subtle.exportKey": true + }, + "packages": { + "@metamask/multichain-network-controller>@solana/addresses>@solana/assertions": true, + "@metamask/multichain-network-controller>@solana/addresses>@solana/codecs-core": true, + "@metamask/multichain-network-controller>@solana/addresses>@solana/codecs-strings": true, + "@metamask/multichain-network-controller>@solana/addresses>@solana/errors": true + } + }, "@solana/addresses>@solana/assertions": { "globals": { "crypto": true, @@ -3083,11 +3218,25 @@ "@solana/addresses>@solana/errors": true } }, + "@metamask/multichain-network-controller>@solana/addresses>@solana/assertions": { + "globals": { + "crypto": true, + "isSecureContext": true + }, + "packages": { + "@metamask/multichain-network-controller>@solana/addresses>@solana/errors": true + } + }, "@solana/addresses>@solana/codecs-core": { "packages": { "@solana/addresses>@solana/errors": true } }, + "@metamask/multichain-network-controller>@solana/addresses>@solana/codecs-core": { + "packages": { + "@metamask/multichain-network-controller>@solana/addresses>@solana/errors": true + } + }, "@solana/addresses>@solana/codecs-strings": { "globals": { "TextDecoder": true, @@ -3100,11 +3249,28 @@ "@solana/addresses>@solana/errors": true } }, + "@metamask/multichain-network-controller>@solana/addresses>@solana/codecs-strings": { + "globals": { + "TextDecoder": true, + "TextEncoder": true, + "atob": true, + "btoa": true + }, + "packages": { + "@metamask/multichain-network-controller>@solana/addresses>@solana/codecs-core": true, + "@metamask/multichain-network-controller>@solana/addresses>@solana/errors": true + } + }, "@solana/addresses>@solana/errors": { "globals": { "btoa": true } }, + "@metamask/multichain-network-controller>@solana/addresses>@solana/errors": { + "globals": { + "btoa": true + } + }, "@metamask/controller-utils>@spruceid/siwe-parser": { "globals": { "console.error": true, @@ -5966,6 +6132,11 @@ "crypto": true } }, + "@metamask/multichain-transactions-controller>@metamask/keyring-snap-client>uuid": { + "globals": { + "crypto": true + } + }, "eth-lattice-keyring>gridplus-sdk>uuid": { "globals": { "crypto": true diff --git a/package.json b/package.json index 29c0ecf61c27..173f1c240d7c 100644 --- a/package.json +++ b/package.json @@ -120,6 +120,7 @@ "check-pr-has-required-labels": "ts-node ./.github/scripts/check-pr-has-required-labels.ts", "close-release-bug-report-issue": "ts-node ./.github/scripts/close-release-bug-report-issue.ts", "check-template-and-add-labels": "ts-node ./.github/scripts/check-template-and-add-labels.ts", + "create-bug-report-issue": "ts-node ./.github/scripts/create-bug-report-issue.ts", "audit": "yarn npm audit --recursive --environment production --severity moderate --ignore '@metamask/types (deprecation)'", "download-builds": "tsx .devcontainer/download-builds.ts prep-build", "download-builds:test": "tsx .devcontainer/download-builds.ts prep-build-test", @@ -221,7 +222,7 @@ "@trezor/schema-utils@npm:1.0.2": "patch:@trezor/schema-utils@npm%3A1.0.2#~/.yarn/patches/@trezor-schema-utils-npm-1.0.2-7dd48689b2.patch", "lavamoat-core@npm:^15.1.1": "patch:lavamoat-core@npm%3A15.1.1#~/.yarn/patches/lavamoat-core-npm-15.1.1-51fbe39988.patch", "lavamoat-core@npm:^16.2.2": "patch:lavamoat-core@npm%3A16.2.2#~/.yarn/patches/lavamoat-core-npm-16.2.2-e361ff1f8a.patch", - "@metamask/snaps-sdk": "^6.17.1", + "@metamask/snaps-sdk": "^6.18.0", "@swc/types@0.1.5": "^0.1.6", "@babel/core": "patch:@babel/core@npm%3A7.25.9#~/.yarn/patches/@babel-core-npm-7.25.9-4ae3bff7f3.patch", "@babel/runtime": "patch:@babel/runtime@npm%3A7.25.9#~/.yarn/patches/@babel-runtime-npm-7.25.9-fe8c62510a.patch", @@ -292,7 +293,7 @@ "@metamask-institutional/types": "^1.2.0", "@metamask/abi-utils": "^2.0.2", "@metamask/account-watcher": "^4.1.2", - "@metamask/accounts-controller": "^23.0.1", + "@metamask/accounts-controller": "^24.0.0", "@metamask/address-book-controller": "^6.0.3", "@metamask/announcement-controller": "^7.0.3", "@metamask/approval-controller": "^7.0.0", @@ -318,7 +319,7 @@ "@metamask/json-rpc-engine": "^10.0.0", "@metamask/json-rpc-middleware-stream": "^8.0.4", "@metamask/keyring-api": "^17.0.0", - "@metamask/keyring-controller": "^19.0.7", + "@metamask/keyring-controller": "^19.1.0", "@metamask/keyring-internal-api": "^4.0.2", "@metamask/keyring-snap-client": "^4.0.0", "@metamask/logging-controller": "^6.0.4", @@ -327,6 +328,7 @@ "@metamask/message-signing-snap": "^0.6.0", "@metamask/metamask-eth-abis": "^3.1.1", "@metamask/multichain": "^2.1.0", + "@metamask/multichain-network-controller": "^0.1.0", "@metamask/multichain-transactions-controller": "^0.3.0", "@metamask/name-controller": "^8.0.3", "@metamask/network-controller": "patch:@metamask/network-controller@npm%3A22.1.1#~/.yarn/patches/@metamask-network-controller-npm-22.1.1-09b6510f1e.patch", @@ -351,11 +353,11 @@ "@metamask/selected-network-controller": "^19.0.0", "@metamask/signature-controller": "^23.1.0", "@metamask/smart-transactions-controller": "^16.0.1", - "@metamask/snaps-controllers": "^9.19.1", - "@metamask/snaps-execution-environments": "^6.14.0", - "@metamask/snaps-rpc-methods": "^11.11.0", - "@metamask/snaps-sdk": "^6.17.1", - "@metamask/snaps-utils": "^8.10.0", + "@metamask/snaps-controllers": "^10.0.0", + "@metamask/snaps-execution-environments": "^7.0.0", + "@metamask/snaps-rpc-methods": "^11.12.0", + "@metamask/snaps-sdk": "^6.18.0", + "@metamask/snaps-utils": "^9.0.0", "@metamask/solana-wallet-snap": "^1.2.0", "@metamask/transaction-controller": "^45.0.0", "@metamask/user-operation-controller": "^24.0.1", diff --git a/test/data/mock-accounts.ts b/test/data/mock-accounts.ts index 0ac8f31c27a4..932ed9b736f0 100644 --- a/test/data/mock-accounts.ts +++ b/test/data/mock-accounts.ts @@ -4,8 +4,11 @@ import { EthAccountType, BtcMethod, BtcAccountType, + SolAccountType, EthScope, BtcScope, + SolMethod, + SolScope, } from '@metamask/keyring-api'; import { ETH_EOA_METHODS, @@ -73,6 +76,21 @@ export const MOCK_ACCOUNT_BIP122_P2WPKH_TESTNET: InternalAccount = { }, }; +export const MOCK_ACCOUNT_SOLANA_MAINNET: InternalAccount = { + id: 'a3f9c2d4-6b8e-4d3a-9b2e-7f4b8e1a9c3d', + address: '3yZe7d5m8V9x2Q1w4u6t8b9n7k5j3h2g1f4d6s8a9p7q2r5t8v', + options: {}, + methods: [SolMethod.SendAndConfirmTransaction], + scopes: [SolScope.Mainnet], + type: SolAccountType.DataAccount, + metadata: { + name: 'Solana Account', + keyring: { type: KeyringTypes.snap }, + importTime: 1691592567600, + lastSelected: 1955565999999, + }, +}; + export const MOCK_ACCOUNTS = { [MOCK_ACCOUNT_EOA.id]: MOCK_ACCOUNT_EOA, [MOCK_ACCOUNT_ERC4337.id]: MOCK_ACCOUNT_ERC4337, diff --git a/test/e2e/snaps/enums.js b/test/e2e/snaps/enums.js index 8844ee3eca89..be4ec454a198 100644 --- a/test/e2e/snaps/enums.js +++ b/test/e2e/snaps/enums.js @@ -1,3 +1,3 @@ module.exports = { - TEST_SNAPS_WEBSITE_URL: 'https://metamask.github.io/snaps/test-snaps/2.18.1', + TEST_SNAPS_WEBSITE_URL: 'https://metamask.github.io/snaps/test-snaps/2.19.0', }; diff --git a/test/e2e/tests/confirmations/transactions/speed-up-and-cancel-confirmations.spec.ts b/test/e2e/tests/confirmations/transactions/speed-up-and-cancel-confirmations.spec.ts index bc41a491708f..fd85c073a1d1 100644 --- a/test/e2e/tests/confirmations/transactions/speed-up-and-cancel-confirmations.spec.ts +++ b/test/e2e/tests/confirmations/transactions/speed-up-and-cancel-confirmations.spec.ts @@ -10,7 +10,6 @@ import { createDappTransaction } from '../../../page-objects/flows/transaction'; import Confirmation from '../../../page-objects/pages/confirmations/redesign/confirmation'; import ActivityListPage from '../../../page-objects/pages/home/activity-list'; import HomePage from '../../../page-objects/pages/home/homepage'; -import { SMART_CONTRACTS } from '../../../seeder/smart-contracts'; import { TestSuiteArguments } from './shared'; const { WINDOW_TITLES, withFixtures } = require('../../../helpers'); @@ -28,10 +27,9 @@ describe('Speed Up and Cancel Transaction Tests', function () { .withPermissionControllerConnectedToTestDapp() .build(), localNodeOptions: defaultGanacheOptionsForType2Transactions, - smartContract: SMART_CONTRACTS.PIGGYBANK, title: this.test?.fullTitle(), }, - async ({ driver }: TestSuiteArguments) => { + async ({ driver, ganacheServer }: TestSuiteArguments) => { await unlockWallet(driver); // Create initial stuck transaction @@ -43,7 +41,6 @@ describe('Speed Up and Cancel Transaction Tests', function () { }); // Wait for confirmation dialog and confirm initial transaction - await driver.waitUntilXWindowHandles(3); await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); const confirmationPage = new Confirmation(driver); @@ -53,6 +50,7 @@ describe('Speed Up and Cancel Transaction Tests', function () { await driver.switchToWindowWithTitle( WINDOW_TITLES.ExtensionInFullScreenView, ); + await ganacheServer?.mineBlock(); const homePage = new HomePage(driver); await homePage.goToActivityList(); @@ -63,6 +61,7 @@ describe('Speed Up and Cancel Transaction Tests', function () { await activityListPage.click_transactionListItem(); await activityListPage.click_speedUpTransaction(); await activityListPage.click_confirmTransactionReplacement(); + await ganacheServer?.mineBlock(); await activityListPage.check_waitForTransactionStatus('confirmed'); }, @@ -79,10 +78,9 @@ describe('Speed Up and Cancel Transaction Tests', function () { .withPermissionControllerConnectedToTestDapp() .build(), localNodeOptions: defaultGanacheOptionsForType2Transactions, - smartContract: SMART_CONTRACTS.PIGGYBANK, title: this.test?.fullTitle(), }, - async ({ driver }: TestSuiteArguments) => { + async ({ driver, ganacheServer }: TestSuiteArguments) => { await unlockWallet(driver); // Create initial stuck transaction @@ -93,11 +91,11 @@ describe('Speed Up and Cancel Transaction Tests', function () { to: DEFAULT_FIXTURE_ACCOUNT, }); - await driver.waitUntilXWindowHandles(3); await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); const confirmationPage = new Confirmation(driver); await confirmationPage.clickFooterConfirmButton(); + await ganacheServer?.mineBlock(); await driver.switchToWindowWithTitle( WINDOW_TITLES.ExtensionInFullScreenView, @@ -111,6 +109,7 @@ describe('Speed Up and Cancel Transaction Tests', function () { await activityListPage.click_cancelTransaction(); await activityListPage.click_confirmTransactionReplacement(); + await ganacheServer?.mineBlock(); await activityListPage.check_waitForTransactionStatus('cancelled'); }, diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json index 2e84b6944a18..f62e291abfd0 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json @@ -143,6 +143,7 @@ "metaMetricsDataDeletionId": null, "metaMetricsDataDeletionTimestamp": 0 }, + "MultichainNetworkController": "object", "MultichainRatesController": { "fiatCurrency": "usd", "rates": { diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json index 8446d437617f..0c193a44a642 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -157,6 +157,9 @@ "previousAppVersion": "", "previousMigrationVersion": 0, "currentMigrationVersion": "number", + "multichainNetworkConfigurationsByChainId": "object", + "selectedMultichainNetworkChainId": "string", + "isEvmSelected": "boolean", "selectedNetworkClientId": "string", "networksMetadata": { "networkConfigurationId": { diff --git a/test/e2e/webdriver/driver.js b/test/e2e/webdriver/driver.js index 1ec1f195c354..6eb3390af5ed 100644 --- a/test/e2e/webdriver/driver.js +++ b/test/e2e/webdriver/driver.js @@ -704,7 +704,7 @@ class Driver { * @param rawLocator - Element locator * @param timeout - The maximum time in ms to wait for the element */ - async clickElementSafe(rawLocator, timeout = 1000) { + async clickElementSafe(rawLocator, timeout = 2000) { try { const locator = this.buildLocator(rawLocator); const elements = await this.driver.wait( diff --git a/ui/components/app/assets/nfts/nft-grid/nft-grid.tsx b/ui/components/app/assets/nfts/nft-grid/nft-grid.tsx index 626010dbea18..c07f7fb66a45 100644 --- a/ui/components/app/assets/nfts/nft-grid/nft-grid.tsx +++ b/ui/components/app/assets/nfts/nft-grid/nft-grid.tsx @@ -1,7 +1,11 @@ import React from 'react'; import { useSelector } from 'react-redux'; import { Hex } from '@metamask/utils'; -import { Display } from '../../../../../helpers/constants/design-system'; +import { + AlignItems, + Display, + JustifyContent, +} from '../../../../../helpers/constants/design-system'; import { Box } from '../../../../component-library'; import Spinner from '../../../../ui/spinner'; import { getNftImageAlt } from '../../../../../helpers/utils/nfts'; @@ -99,15 +103,21 @@ export default function NftGrid({ ); })} - {nftsStillFetchingIndication ? ( - - - - ) : null} + {nftsStillFetchingIndication ? ( + + + + ) : null} ); } diff --git a/ui/components/app/assets/nfts/nfts-tab/nfts-tab.tsx b/ui/components/app/assets/nfts/nfts-tab/nfts-tab.tsx index 3faae7c68e16..5acda9db178d 100644 --- a/ui/components/app/assets/nfts/nfts-tab/nfts-tab.tsx +++ b/ui/components/app/assets/nfts/nfts-tab/nfts-tab.tsx @@ -134,7 +134,13 @@ export default function NftsTab() { if (!hasAnyNfts && nftsStillFetchingIndication) { return ( - + = ({ element, ...params @@ -46,7 +69,10 @@ export const box: UIComponentFactory = ({ ? FlexDirection.Row : FlexDirection.Column, justifyContent: generateJustifyContent(element.props.alignment), - alignItems: element.props.center && AlignItems.center, + alignItems: generateAlignItems( + element.props.crossAlignment, + element.props.center, + ), className: 'snap-ui-renderer__panel', color: TextColor.textDefault, }, diff --git a/ui/components/component-library/textarea/textarea.scss b/ui/components/component-library/textarea/textarea.scss index d6ea35fee110..fc312562ae1c 100644 --- a/ui/components/component-library/textarea/textarea.scss +++ b/ui/components/component-library/textarea/textarea.scss @@ -1,6 +1,8 @@ .mm-textarea { $resize: none, both, horizontal, vertical, initial, inherit; + max-width: 100%; + &--is-disabled, &:disabled { opacity: var(--opacity-disabled); diff --git a/ui/components/multichain/network-list-menu/index.scss b/ui/components/multichain/network-list-menu/index.scss index 33e4c2b6caae..d8b60b1958ef 100644 --- a/ui/components/multichain/network-list-menu/index.scss +++ b/ui/components/multichain/network-list-menu/index.scss @@ -2,6 +2,22 @@ &__dialog { height: 100vh; max-height: 100%; + // Needed to ensure the drag and drop works. All other modals use a slide up animation with transform: translateY(0) + // https://github.com/MetaMask/metamask-extension/issues/30436 + transform: unset; + + // Custom animation for network menu modal that doesn't use transform: translateY(0) to ensure the drag and drop works + animation: network-menu-fade 400ms cubic-bezier(0.3, 0.8, 0.3, 1) forwards; + } +} + +@keyframes network-menu-fade { + from { + opacity: 0; + } + + to { + opacity: 1; } } diff --git a/ui/css/base-styles.scss b/ui/css/base-styles.scss index 408e0e0eb973..7d252baa4c65 100644 --- a/ui/css/base-styles.scss +++ b/ui/css/base-styles.scss @@ -17,7 +17,10 @@ body { html { min-height: 500px; +} +html, +body { @include design-system.screen-sm-max { &:not([data-theme]) { background-color: var(--color-background-default); diff --git a/ui/helpers/utils/notification.util.ts b/ui/helpers/utils/notification.util.ts index afbba2b88172..9f57bce5e2e2 100644 --- a/ui/helpers/utils/notification.util.ts +++ b/ui/helpers/utils/notification.util.ts @@ -102,7 +102,7 @@ export function formatMenuItemDate(date: Date) { ); } - // E.g. 21 Oct + // E.g. Oct 21 if (isSameYear(currentDate, date)) { return new Intl.DateTimeFormat('en', { month: 'short', @@ -110,7 +110,7 @@ export function formatMenuItemDate(date: Date) { }).format(date); } - // E.g. 21 Oct 2022 + // E.g. Oct 21, 2022 return new Intl.DateTimeFormat('en', { year: 'numeric', month: 'short', diff --git a/ui/helpers/utils/notification.utils.test.ts b/ui/helpers/utils/notification.utils.test.ts index 2f4e66c26504..920262440378 100644 --- a/ui/helpers/utils/notification.utils.test.ts +++ b/ui/helpers/utils/notification.utils.test.ts @@ -20,7 +20,7 @@ describe('formatMenuItemDate', () => { const assertToday = (modifyDate?: (d: Date) => void) => { const testDate = new Date(); modifyDate?.(testDate); - expect(formatMenuItemDate(testDate)).toMatch(/^\d{2}:\d{2}$/u); + expect(formatMenuItemDate(testDate)).toMatch(/^\d{2}:\d{2}$/u); // E.g. HH:mm }; // assert current date @@ -53,11 +53,11 @@ describe('formatMenuItemDate', () => { }); }); - it('should format date as "DD Mon" if the date is this year but not today or yesterday', () => { + it('should format date as "MM DD" if the date is this year but not today or yesterday', () => { const assertMonthsAgo = (modifyDate: (d: Date) => Date | void) => { let testDate = new Date(); testDate = modifyDate(testDate) ?? testDate; - expect(formatMenuItemDate(testDate)).toMatch(/^\w{3} \d{1,2}$/u); + expect(formatMenuItemDate(testDate)).toMatch(/^\w{3} \d{1,2}$/u); // E.g. Oct 21 }; // assert exactly 1 month ago @@ -77,11 +77,11 @@ describe('formatMenuItemDate', () => { }); }); - it('should format date as "Mon DD, YYYY" if the date is not this year', () => { + it('should format date as "MM DD, YYYY" if the date is not this year', () => { const assertYearsAgo = (modifyDate: (d: Date) => Date | void) => { let testDate = new Date(); testDate = modifyDate(testDate) ?? testDate; - expect(formatMenuItemDate(testDate)).toMatch(/^\w{3} \d{1,2}, \d{4}$/u); + expect(formatMenuItemDate(testDate)).toMatch(/^\w{3} \d{1,2}, \d{4}$/u); // E.g. Oct 21, 2022 }; // assert exactly 1 year ago diff --git a/ui/hooks/snaps/useDisplayName.ts b/ui/hooks/snaps/useDisplayName.ts index 6a6d3d7e6b51..3ac5080d4a9f 100644 --- a/ui/hooks/snaps/useDisplayName.ts +++ b/ui/hooks/snaps/useDisplayName.ts @@ -1,5 +1,8 @@ -import { NamespaceId } from '@metamask/snaps-utils'; -import { CaipChainId, KnownCaipNamespace } from '@metamask/utils'; +import { + CaipChainId, + KnownCaipNamespace, + CaipNamespace, +} from '@metamask/utils'; import { useSelector } from 'react-redux'; import { getMemoizedAccountName, @@ -12,7 +15,7 @@ import { decimalToHex } from '../../../shared/modules/conversion.utils'; export type UseDisplayNameParams = { chain: { - namespace: NamespaceId; + namespace: CaipNamespace; reference: string; }; chainId: CaipChainId; diff --git a/ui/pages/asset/components/token-buttons.tsx b/ui/pages/asset/components/token-buttons.tsx index 9730a23a7475..c92f1c6e877b 100644 --- a/ui/pages/asset/components/token-buttons.tsx +++ b/ui/pages/asset/components/token-buttons.tsx @@ -56,7 +56,7 @@ import { ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) import { getIsNativeTokenBuyable } from '../../../ducks/ramps'; ///: END:ONLY_INCLUDE_IF -import { Asset } from './asset-page'; +import type { Asset } from './asset-page'; const TokenButtons = ({ token, diff --git a/ui/pages/bridge/prepare/components/bridge-asset-picker-button.tsx b/ui/pages/bridge/prepare/components/bridge-asset-picker-button.tsx index b04da2b981fa..8296a950f541 100644 --- a/ui/pages/bridge/prepare/components/bridge-asset-picker-button.tsx +++ b/ui/pages/bridge/prepare/components/bridge-asset-picker-button.tsx @@ -68,9 +68,9 @@ export const BridgeAssetPickerButton = ({ diff --git a/ui/pages/bridge/prepare/prepare-bridge-page.tsx b/ui/pages/bridge/prepare/prepare-bridge-page.tsx index affc55ccde9f..afc6ad6886c1 100644 --- a/ui/pages/bridge/prepare/prepare-bridge-page.tsx +++ b/ui/pages/bridge/prepare/prepare-bridge-page.tsx @@ -178,6 +178,8 @@ const PrepareBridgePage = () => { const millisecondsUntilNextRefresh = useCountdownTimer(); + const isSwap = useIsMultichainSwap(); + const [rotateSwitchTokens, setRotateSwitchTokens] = useState(false); // Resets the banner visibility when the estimated return is low @@ -275,9 +277,10 @@ const PrepareBridgePage = () => { srcChainId: fromChain?.chainId ? Number(hexToDecimal(fromChain.chainId)) : undefined, - destChainId: toChain?.chainId - ? Number(hexToDecimal(toChain.chainId)) - : undefined, + destChainId: + isSwap && fromChain?.chainId + ? Number(hexToDecimal(fromChain.chainId)) + : toChain?.chainId && Number(hexToDecimal(toChain.chainId)), // This override allows quotes to be returned when the rpcUrl is a tenderly fork // Otherwise quotes get filtered out by the bridge-api when the wallet's real // balance is less than the tenderly balance @@ -285,6 +288,7 @@ const PrepareBridgePage = () => { slippage, }), [ + isSwap, fromToken, toToken, fromChain?.chainId, @@ -366,8 +370,6 @@ const PrepareBridgePage = () => { } }, [fromChain, fromToken, fromTokens, search, isFromTokensLoading]); - const isSwap = useIsMultichainSwap(); - return ( { value: token.address, }); }} - networkProps={{ - network: fromChain, - networks: fromChains, - onNetworkChange: (networkConfig) => { - networkConfig.chainId !== fromChain?.chainId && - trackInputEvent({ - input: 'chain_source', - value: networkConfig.chainId, - }); - if (networkConfig.chainId === toChain?.chainId) { - dispatch(setToChainId(null)); - dispatch(setToToken(null)); - } - if (isNetworkAdded(networkConfig)) { - dispatch( - setActiveNetwork( - networkConfig.rpcEndpoints[ - networkConfig.defaultRpcEndpointIndex - ].networkClientId, - ), - ); - } - dispatch(setFromToken(null)); - dispatch(setFromTokenInputValue(null)); - }, - header: t('yourNetworks'), - }} - isMultiselectEnabled + networkProps={ + isSwap + ? undefined + : { + network: fromChain, + networks: fromChains, + onNetworkChange: (networkConfig) => { + networkConfig.chainId !== fromChain?.chainId && + trackInputEvent({ + input: 'chain_source', + value: networkConfig.chainId, + }); + if (networkConfig.chainId === toChain?.chainId) { + dispatch(setToChainId(null)); + dispatch(setToToken(null)); + } + if (isNetworkAdded(networkConfig)) { + dispatch( + setActiveNetwork( + networkConfig.rpcEndpoints[ + networkConfig.defaultRpcEndpointIndex + ].networkClientId, + ), + ); + } + dispatch(setFromToken(null)); + dispatch(setFromTokenInputValue(null)); + }, + header: t('yourNetworks'), + } + } + isMultiselectEnabled={!isSwap} onMaxButtonClick={(value: string) => { dispatch(setFromTokenInputValue(value)); }} @@ -468,10 +474,10 @@ const PrepareBridgePage = () => { disabled={ isSwitchingTemporarilyDisabled || !isValidQuoteRequest(quoteRequest, false) || - !isNetworkAdded(toChain) + (!isSwap && !isNetworkAdded(toChain)) } onClick={() => { - if (!isNetworkAdded(toChain)) { + if (!isSwap && !isNetworkAdded(toChain)) { return; } setRotateSwitchTokens(!rotateSwitchTokens); @@ -480,15 +486,19 @@ const PrepareBridgePage = () => { event: MetaMetricsEventName.InputSourceDestinationFlipped, properties: flippedRequestProperties, }); - const toChainClientId = - toChain?.defaultRpcEndpointIndex !== undefined && - toChain?.rpcEndpoints - ? toChain.rpcEndpoints[toChain.defaultRpcEndpointIndex] - .networkClientId - : undefined; - toChainClientId && dispatch(setActiveNetwork(toChainClientId)); + if (!isSwap) { + // Only flip networks if bridging + const toChainClientId = + toChain?.defaultRpcEndpointIndex !== undefined && + toChain?.rpcEndpoints && + isNetworkAdded(toChain) + ? toChain.rpcEndpoints[toChain.defaultRpcEndpointIndex] + .networkClientId + : undefined; + toChainClientId && dispatch(setActiveNetwork(toChainClientId)); + fromChain?.chainId && dispatch(setToChainId(fromChain.chainId)); + } dispatch(setFromToken(toToken)); - fromChain?.chainId && dispatch(setToChainId(fromChain.chainId)); dispatch(setToToken(fromToken)); }} /> @@ -505,23 +515,30 @@ const PrepareBridgePage = () => { }); dispatch(setToToken(token)); }} - networkProps={{ - network: toChain, - networks: toChains, - onNetworkChange: (networkConfig) => { - networkConfig.chainId !== toChain?.chainId && - trackInputEvent({ - input: 'chain_destination', - value: networkConfig.chainId, - }); - dispatch(setToChainId(networkConfig.chainId)); - dispatch(setToToken(null)); - }, - header: isSwap ? t('swapSwapTo') : t('bridgeTo'), - shouldDisableNetwork: ({ chainId }) => - chainId === fromChain?.chainId, - }} - customTokenListGenerator={toChain ? toTokenListGenerator : undefined} + networkProps={ + isSwap + ? undefined + : { + network: toChain, + networks: toChains, + onNetworkChange: (networkConfig) => { + networkConfig.chainId !== toChain?.chainId && + trackInputEvent({ + input: 'chain_destination', + value: networkConfig.chainId, + }); + dispatch(setToChainId(networkConfig.chainId)); + dispatch(setToToken(null)); + }, + header: isSwap ? t('swapSwapTo') : t('bridgeTo'), + shouldDisableNetwork: ({ chainId }) => + chainId === fromChain?.chainId, + } + } + customTokenListGenerator={ + // TODO use custom generator when we have a way to get all tokens for an unimported chain + toChain && !isSwap ? toTokenListGenerator : undefined + } amountInFiat={ activeQuote?.toTokenAmount?.valueInCurrency || undefined } diff --git a/ui/pages/bridge/quotes/multichain-bridge-quote-card.stories.tsx b/ui/pages/bridge/quotes/multichain-bridge-quote-card.stories.tsx new file mode 100644 index 000000000000..79616e27271a --- /dev/null +++ b/ui/pages/bridge/quotes/multichain-bridge-quote-card.stories.tsx @@ -0,0 +1,101 @@ +import React from 'react'; +import { Provider } from 'react-redux'; +import configureStore from '../../../store/store'; +import { MultichainBridgeQuoteCard } from './multichain-bridge-quote-card'; +import { createBridgeMockStore } from '../../../../test/jest/mock-store'; +import mockBridgeQuotesErc20Erc20 from '../../../../test/data/bridge/mock-quotes-erc20-erc20.json'; + +const storybook = { + title: 'Pages/Bridge/MultichainBridgeQuoteCard', + component: MultichainBridgeQuoteCard, +}; + +const Container = ({ children }: { children: React.ReactNode }) => ( +
{children}
+); + +export const DefaultStory = () => { + return ( + + + + ); +}; +DefaultStory.storyName = 'Default'; +DefaultStory.decorators = [ + (story) => ( + + {story()} + + ), +]; + +export const WithDestinationAddress = () => { + return ( + + + + ); +}; +WithDestinationAddress.decorators = [ + (story) => ( + + {story()} + + ), +]; + +export const WithLowEstimatedReturn = () => { + return ( + + + + ); +}; +WithLowEstimatedReturn.decorators = [ + (story) => ( + + {story()} + + ), +]; + +export default storybook; diff --git a/ui/pages/bridge/quotes/multichain-bridge-quote-card.tsx b/ui/pages/bridge/quotes/multichain-bridge-quote-card.tsx new file mode 100644 index 000000000000..87767298f014 --- /dev/null +++ b/ui/pages/bridge/quotes/multichain-bridge-quote-card.tsx @@ -0,0 +1,312 @@ +import React, { useState } from 'react'; +import { useSelector } from 'react-redux'; +import { + Text, + PopoverPosition, + IconName, + ButtonLink, + Icon, + IconSize, + AvatarNetwork, + AvatarNetworkSize, +} from '../../../components/component-library'; +import { + getBridgeQuotes, + getFromChain, + getToChain, + getValidationErrors, +} from '../../../ducks/bridge/selectors'; +import { useI18nContext } from '../../../hooks/useI18nContext'; +import { + formatCurrencyAmount, + formatTokenAmount, + formatEtaInMinutes, +} from '../utils/quote'; +import { + getCurrentCurrency, + getNativeCurrency, +} from '../../../ducks/metamask/metamask'; +import { useCrossChainSwapsEventTracker } from '../../../hooks/bridge/useCrossChainSwapsEventTracker'; +import { useRequestProperties } from '../../../hooks/bridge/events/useRequestProperties'; +import { useRequestMetadataProperties } from '../../../hooks/bridge/events/useRequestMetadataProperties'; +import { useQuoteProperties } from '../../../hooks/bridge/events/useQuoteProperties'; +import { MetaMetricsEventName } from '../../../../shared/constants/metametrics'; +import { + AlignItems, + BackgroundColor, + BlockSize, + IconColor, + JustifyContent, + TextColor, + TextVariant, +} from '../../../helpers/constants/design-system'; +import { Row, Column, Tooltip } from '../layout'; +import { + BRIDGE_MM_FEE_RATE, + NETWORK_TO_SHORT_NETWORK_NAME_MAP, +} from '../../../../shared/constants/bridge'; +import { CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP } from '../../../../shared/constants/network'; +import { decimalToHex } from '../../../../shared/modules/conversion.utils'; +import { TERMS_OF_USE_LINK } from '../../../../shared/constants/terms'; +import { getIntlLocale } from '../../../ducks/locale/locale'; +import { shortenString } from '../../../helpers/utils/util'; +import { BridgeQuotesModal } from './bridge-quotes-modal'; + +type MultichainBridgeQuoteCardProps = { + destinationAddress?: string; +}; + +export const MultichainBridgeQuoteCard = ({ + destinationAddress, +}: MultichainBridgeQuoteCardProps) => { + const t = useI18nContext(); + const { activeQuote } = useSelector(getBridgeQuotes); + const currency = useSelector(getCurrentCurrency); + const ticker = useSelector(getNativeCurrency); + const { isEstimatedReturnLow } = useSelector(getValidationErrors); + + const trackCrossChainSwapsEvent = useCrossChainSwapsEventTracker(); + const { quoteRequestProperties } = useRequestProperties(); + const requestMetadataProperties = useRequestMetadataProperties(); + const quoteListProperties = useQuoteProperties(); + + const fromChain = useSelector(getFromChain); + const toChain = useSelector(getToChain); + const locale = useSelector(getIntlLocale); + + const [showAllQuotes, setShowAllQuotes] = useState(false); + const [shouldShowNetworkFeesInGasToken, setShouldShowNetworkFeesInGasToken] = + useState(false); + + return ( + <> + setShowAllQuotes(false)} + /> + {activeQuote ? ( + + + + {t('bestPrice')} + + {t('howQuotesWorkExplanation', [BRIDGE_MM_FEE_RATE])} + + + + { + quoteRequestProperties && + requestMetadataProperties && + quoteListProperties && + trackCrossChainSwapsEvent({ + event: MetaMetricsEventName.AllQuotesOpened, + properties: { + ...quoteRequestProperties, + ...requestMetadataProperties, + ...quoteListProperties, + }, + }); + setShowAllQuotes(true); + }} + > + {t('moreQuotes')} + + + + + + + + + { + NETWORK_TO_SHORT_NETWORK_NAME_MAP[ + `0x${decimalToHex( + activeQuote.quote.srcChainId, + )}` as keyof typeof NETWORK_TO_SHORT_NETWORK_NAME_MAP + ] + } + + + + + { + NETWORK_TO_SHORT_NETWORK_NAME_MAP[ + `0x${decimalToHex( + activeQuote.quote.destChainId, + )}` as keyof typeof NETWORK_TO_SHORT_NETWORK_NAME_MAP + ] + } + + + {destinationAddress && ( + + {shortenString(destinationAddress)} + + )} + + + + + + + {shouldShowNetworkFeesInGasToken + ? `${ + activeQuote.totalNetworkFee?.valueInCurrency + ? formatTokenAmount( + locale, + activeQuote.totalNetworkFee?.amount, + ) + : undefined + } - ${ + activeQuote.totalMaxNetworkFee?.valueInCurrency + ? formatTokenAmount( + locale, + activeQuote.totalMaxNetworkFee?.amount, + ticker, + ) + : undefined + }` + : `${ + formatCurrencyAmount( + activeQuote.totalNetworkFee?.valueInCurrency, + currency, + 2, + ) ?? + formatTokenAmount( + locale, + activeQuote.totalNetworkFee?.amount, + ) + } - ${ + formatCurrencyAmount( + activeQuote.totalMaxNetworkFee?.valueInCurrency, + currency, + 2, + ) ?? + formatTokenAmount( + locale, + activeQuote.totalMaxNetworkFee?.amount, + ticker, + ) + }`} + + + setShouldShowNetworkFeesInGasToken( + !shouldShowNetworkFeesInGasToken, + ) + } + /> + + + + + + {t('bridgeTimingMinutes', [ + formatEtaInMinutes( + activeQuote.estimatedProcessingTimeInSeconds, + ), + ])} + + + + + + + {t('rateIncludesMMFee', [BRIDGE_MM_FEE_RATE])} + + + {t('bridgeTerms')} + + + + + ) : null} + + ); +}; diff --git a/ui/pages/error-page/error-page.component.tsx b/ui/pages/error-page/error-page.component.tsx index 9a094057dcc0..17da4edab91d 100644 --- a/ui/pages/error-page/error-page.component.tsx +++ b/ui/pages/error-page/error-page.component.tsx @@ -110,16 +110,27 @@ const ErrorPage: React.FC = ({ error }) => { size={IconSize.Xl} color={IconColor.warningDefault} /> - + {t('errorPageTitle')}
- {t('errorPageInfo')} + + {t('errorPageInfo')} +
- {t('errorPageMessageTitle')} + + {t('errorPageMessageTitle')} + = ({ error }) => { variant={TextVariant.bodyXs} marginBottom={2} data-testid="error-page-error-message" + color={TextColor.inherit} > {t('errorMessage', [error.message])} @@ -145,6 +157,7 @@ const ErrorPage: React.FC = ({ error }) => { variant={TextVariant.bodyXs} marginBottom={2} data-testid="error-page-error-code" + color={TextColor.inherit} > {t('errorCode', [error.code])} @@ -154,13 +167,18 @@ const ErrorPage: React.FC = ({ error }) => { variant={TextVariant.bodyXs} marginBottom={2} data-testid="error-page-error-name" + color={TextColor.inherit} > {t('errorName', [error.name])} ) : null} {error.stack ? ( <> - + {t('errorStack')}
 {
+  return submitRequestToBackground('estimateGasFee', [request]);
+}
diff --git a/ui/pages/swaps/swaps.util.test.js b/ui/pages/swaps/swaps.util.test.js
index d081e8d58ee1..0973577638de 100644
--- a/ui/pages/swaps/swaps.util.test.js
+++ b/ui/pages/swaps/swaps.util.test.js
@@ -18,13 +18,6 @@ import {
   LINEA,
   BASE,
 } from '../../../shared/constants/swaps';
-import { estimateGasFee } from '../../store/actions';
-import {
-  TOKENS,
-  EXPECTED_TOKENS_RESULT,
-  AGGREGATOR_METADATA,
-  TOP_ASSETS,
-} from './swaps-util-test-constants';
 import {
   fetchTokens,
   fetchAggregatorMetadata,
@@ -39,13 +32,20 @@ import {
   fetchTopAssetsList,
   getSwap1559GasFeeEstimates,
 } from './swaps.util';
+import { estimateGasFee } from './swaps.util.gas';
+import {
+  TOKENS,
+  EXPECTED_TOKENS_RESULT,
+  AGGREGATOR_METADATA,
+  TOP_ASSETS,
+} from './swaps-util-test-constants';
 
 jest.mock('../../../shared/lib/storage-helpers', () => ({
   getStorageItem: jest.fn(),
   setStorageItem: jest.fn(),
 }));
 
-jest.mock('../../store/actions', () => ({
+jest.mock('./swaps.util.gas', () => ({
   estimateGasFee: jest.fn(),
 }));
 
diff --git a/ui/pages/swaps/swaps.util.ts b/ui/pages/swaps/swaps.util.ts
index 7beb613bce58..0ccb2dcc99f7 100644
--- a/ui/pages/swaps/swaps.util.ts
+++ b/ui/pages/swaps/swaps.util.ts
@@ -50,7 +50,7 @@ import {
   sumHexes,
 } from '../../../shared/modules/conversion.utils';
 import { EtherDenomination } from '../../../shared/constants/common';
-import { estimateGasFee } from '../../store/actions';
+import { estimateGasFee } from './swaps.util.gas';
 
 const CACHE_REFRESH_FIVE_MINUTES = 300000;
 const USD_CURRENCY_CODE = 'usd';
diff --git a/ui/selectors/multichain/networks.test.ts b/ui/selectors/multichain/networks.test.ts
new file mode 100644
index 000000000000..b5efe28b7173
--- /dev/null
+++ b/ui/selectors/multichain/networks.test.ts
@@ -0,0 +1,331 @@
+import { BtcScope, SolScope } from '@metamask/keyring-api';
+import {
+  type NetworkConfiguration,
+  RpcEndpointType,
+  NetworkStatus,
+} from '@metamask/network-controller';
+import type { Hex, CaipChainId } from '@metamask/utils';
+import { type MultichainNetworkConfiguration } from '@metamask/multichain-network-controller';
+
+import { type NetworkState } from '../../../shared/modules/selectors/networks';
+import type { AccountsState } from '../accounts';
+import {
+  MOCK_ACCOUNT_EOA,
+  MOCK_ACCOUNT_BIP122_P2WPKH,
+  MOCK_ACCOUNT_SOLANA_MAINNET,
+} from '../../../test/data/mock-accounts';
+import {
+  type MultichainNetworkControllerState,
+  getNonEvmMultichainNetworkConfigurationsByChainId,
+  getMultichainNetworkConfigurationsByChainId,
+  getSelectedMultichainNetworkChainId,
+  getSelectedMultichainNetworkConfiguration,
+  getIsEvmMultichainNetworkSelected,
+} from './networks';
+
+type TestState = AccountsState &
+  MultichainNetworkControllerState &
+  NetworkState & {
+    metamask: { solanaSupportEnabled: boolean; bitcoinSupportEnabled: boolean };
+  };
+
+const mockNonEvmNetworks: Record =
+  {
+    [SolScope.Mainnet]: {
+      chainId: SolScope.Mainnet,
+      name: 'Solana Mainnet',
+      nativeCurrency: `${SolScope.Mainnet}/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`,
+      isEvm: false,
+    },
+    [BtcScope.Mainnet]: {
+      chainId: BtcScope.Mainnet,
+      name: 'Bitcoin Mainnet',
+      nativeCurrency: `${BtcScope.Mainnet}/slip44:0`,
+      isEvm: false,
+    },
+  };
+
+const mockEvmNetworksWithNewConfig: Record<
+  CaipChainId,
+  MultichainNetworkConfiguration
+> = {
+  'eip155:1': {
+    chainId: 'eip155:1',
+    name: 'Ethereum Mainnet',
+    nativeCurrency: 'ETH',
+    blockExplorerUrls: ['https://etherscan.io'],
+    defaultBlockExplorerUrlIndex: 0,
+    isEvm: true,
+  },
+  'eip155:11155111': {
+    chainId: 'eip155:11155111',
+    name: 'Sepolia',
+    nativeCurrency: 'SepoliaETH',
+    blockExplorerUrls: ['https://sepolia.etherscan.io'],
+    defaultBlockExplorerUrlIndex: 0,
+    isEvm: true,
+  },
+};
+
+const mockEvmNetworksWithOldConfig: Record = {
+  '0x1': {
+    chainId: '0x1',
+    name: 'Ethereum Mainnet',
+    nativeCurrency: 'ETH',
+    rpcEndpoints: [
+      {
+        networkClientId: 'mainnet',
+        type: RpcEndpointType.Infura,
+        url: 'https://mainnet.infura.io/v3/{infuraProjectId}',
+      },
+    ],
+    defaultRpcEndpointIndex: 1,
+    blockExplorerUrls: ['https://etherscan.io'],
+    defaultBlockExplorerUrlIndex: 0,
+    lastUpdatedAt: 1739466375574,
+  },
+  '0xaa36a7': {
+    blockExplorerUrls: ['https://sepolia.etherscan.io'],
+    chainId: '0xaa36a7',
+    defaultBlockExplorerUrlIndex: 0,
+    defaultRpcEndpointIndex: 0,
+    name: 'Sepolia',
+    nativeCurrency: 'SepoliaETH',
+    rpcEndpoints: [
+      {
+        networkClientId: 'sepolia',
+        type: RpcEndpointType.Infura,
+        url: 'https://sepolia.infura.io/v3/{infuraProjectId}',
+      },
+    ],
+  },
+};
+
+const mockState: TestState = {
+  metamask: {
+    solanaSupportEnabled: true,
+    bitcoinSupportEnabled: true,
+    multichainNetworkConfigurationsByChainId: {
+      ...mockNonEvmNetworks,
+    },
+    selectedMultichainNetworkChainId: SolScope.Mainnet,
+    isEvmSelected: false,
+    selectedNetworkClientId: 'mainnet',
+    networkConfigurationsByChainId: {
+      ...mockEvmNetworksWithOldConfig,
+    },
+    networksMetadata: {
+      mainnet: {
+        EIPS: { 1559: true },
+        status: NetworkStatus.Available,
+      },
+      sepolia: {
+        EIPS: { 1559: true },
+        status: NetworkStatus.Available,
+      },
+    },
+    internalAccounts: {
+      selectedAccount: MOCK_ACCOUNT_EOA.id,
+      accounts: {
+        [MOCK_ACCOUNT_EOA.id]: MOCK_ACCOUNT_EOA,
+      },
+    },
+  },
+};
+
+describe('Multichain network selectors', () => {
+  describe('getNonEvmMultichainNetworkConfigurationsByChainId', () => {
+    it('returns the non-EVM multichain network configurations by chain ID', () => {
+      expect(
+        getNonEvmMultichainNetworkConfigurationsByChainId(mockState),
+      ).toStrictEqual({
+        ...mockNonEvmNetworks,
+      });
+    });
+  });
+
+  describe('getMultichainNetworkConfigurationsByChainId', () => {
+    it('returns all multichain network configurations by chain ID when Solana and Bitcoin are enabled', () => {
+      expect(
+        getMultichainNetworkConfigurationsByChainId(mockState),
+      ).toStrictEqual({
+        ...mockNonEvmNetworks,
+        ...mockEvmNetworksWithNewConfig,
+      });
+    });
+
+    it('returns all multichain network configurations by chain ID excluding Solana when support is disabled and there is no Solana account', () => {
+      const mockMultichainNetworkStateWithSolanaSupportDisabled = {
+        ...mockState,
+        metamask: {
+          ...mockState.metamask,
+          solanaSupportEnabled: false,
+        },
+      };
+
+      expect(
+        getMultichainNetworkConfigurationsByChainId(
+          mockMultichainNetworkStateWithSolanaSupportDisabled,
+        ),
+      ).toStrictEqual({
+        [BtcScope.Mainnet]: mockNonEvmNetworks[BtcScope.Mainnet],
+        ...mockEvmNetworksWithNewConfig,
+      });
+    });
+
+    it('returns all multichain network configurations by chain ID excluding Bitcoin when support is disabled and there no Bitcoin account', () => {
+      const mockMultichainNetworkStateWithBitcoinSupportDisabled = {
+        ...mockState,
+        metamask: {
+          ...mockState.metamask,
+          bitcoinSupportEnabled: false,
+        },
+      };
+
+      expect(
+        getMultichainNetworkConfigurationsByChainId(
+          mockMultichainNetworkStateWithBitcoinSupportDisabled,
+        ),
+      ).toStrictEqual({
+        [SolScope.Mainnet]: mockNonEvmNetworks[SolScope.Mainnet],
+        ...mockEvmNetworksWithNewConfig,
+      });
+    });
+
+    it('returns all multichain network configurations by chain ID excluding Bitcoin and Solana when support is disabled and no accounts related to those networks', () => {
+      const mockMultichainNetworkStateWithBitcoinSupportDisabled = {
+        ...mockState,
+        metamask: {
+          ...mockState.metamask,
+          solanaSupportEnabled: false,
+          bitcoinSupportEnabled: false,
+        },
+      };
+
+      expect(
+        getMultichainNetworkConfigurationsByChainId(
+          mockMultichainNetworkStateWithBitcoinSupportDisabled,
+        ),
+      ).toStrictEqual({ ...mockEvmNetworksWithNewConfig });
+    });
+
+    it('returns Solana as part of the multichain network configurations if there is a Solana account', () => {
+      const mockMultichainNetworkStateWithBitcoinSupportDisabled = {
+        ...mockState,
+        metamask: {
+          ...mockState.metamask,
+          solanaSupportEnabled: false,
+          bitcoinSupportEnabled: false,
+          internalAccounts: {
+            ...mockState.metamask.internalAccounts,
+            accounts: {
+              ...mockState.metamask.internalAccounts.accounts,
+              [MOCK_ACCOUNT_SOLANA_MAINNET.id]: MOCK_ACCOUNT_SOLANA_MAINNET,
+            },
+          },
+        },
+      };
+
+      expect(
+        getMultichainNetworkConfigurationsByChainId(
+          mockMultichainNetworkStateWithBitcoinSupportDisabled,
+        ),
+      ).toStrictEqual({
+        ...mockEvmNetworksWithNewConfig,
+        [SolScope.Mainnet]: mockNonEvmNetworks[SolScope.Mainnet],
+      });
+    });
+
+    it('returns Bitcoin as part of the multichain network configurations if there is a Bitcoin account', () => {
+      const mockMultichainNetworkStateWithBitcoinSupportDisabled = {
+        ...mockState,
+        metamask: {
+          ...mockState.metamask,
+          solanaSupportEnabled: false,
+          bitcoinSupportEnabled: false,
+          internalAccounts: {
+            ...mockState.metamask.internalAccounts,
+            accounts: {
+              ...mockState.metamask.internalAccounts.accounts,
+              [MOCK_ACCOUNT_BIP122_P2WPKH.id]: MOCK_ACCOUNT_BIP122_P2WPKH,
+            },
+          },
+        },
+      };
+
+      expect(
+        getMultichainNetworkConfigurationsByChainId(
+          mockMultichainNetworkStateWithBitcoinSupportDisabled,
+        ),
+      ).toStrictEqual({
+        ...mockEvmNetworksWithNewConfig,
+        [BtcScope.Mainnet]: mockNonEvmNetworks[BtcScope.Mainnet],
+      });
+    });
+
+    it('returns Bitcoin and Solana as part of the multichain network configurations if there is Bitcoin and Solana accounts', () => {
+      const mockMultichainNetworkStateWithBitcoinSupportDisabled = {
+        ...mockState,
+        metamask: {
+          ...mockState.metamask,
+          solanaSupportEnabled: false,
+          bitcoinSupportEnabled: false,
+          internalAccounts: {
+            ...mockState.metamask.internalAccounts,
+            accounts: {
+              ...mockState.metamask.internalAccounts.accounts,
+              [MOCK_ACCOUNT_BIP122_P2WPKH.id]: MOCK_ACCOUNT_BIP122_P2WPKH,
+              [MOCK_ACCOUNT_SOLANA_MAINNET.id]: MOCK_ACCOUNT_SOLANA_MAINNET,
+            },
+          },
+        },
+      };
+
+      expect(
+        getMultichainNetworkConfigurationsByChainId(
+          mockMultichainNetworkStateWithBitcoinSupportDisabled,
+        ),
+      ).toStrictEqual({
+        ...mockEvmNetworksWithNewConfig,
+        ...mockNonEvmNetworks,
+      });
+    });
+  });
+
+  describe('getSelectedMultichainNetworkChainId', () => {
+    it('returns the selected multichain network chain ID', () => {
+      expect(getSelectedMultichainNetworkChainId(mockState)).toStrictEqual(
+        SolScope.Mainnet,
+      );
+    });
+  });
+
+  describe('getIsEvmMultichainNetworkSelected', () => {
+    it('returns whether the EVM network is selected', () => {
+      expect(getIsEvmMultichainNetworkSelected(mockState)).toStrictEqual(false);
+    });
+  });
+
+  describe('getSelectedMultichainNetworkConfiguration', () => {
+    it('returns the selected non EVM multichain network configuration if isEvmSelected is false', () => {
+      expect(
+        getSelectedMultichainNetworkConfiguration(mockState),
+      ).toStrictEqual(mockNonEvmNetworks[SolScope.Mainnet]);
+    });
+    it('returns the selected EVM multichain network configuration if isEvmSelected is true', () => {
+      const mockMultichainNetworkStateWithEvmSelected = {
+        ...mockState,
+        metamask: {
+          ...mockState.metamask,
+          isEvmSelected: true,
+        },
+      };
+
+      expect(
+        getSelectedMultichainNetworkConfiguration(
+          mockMultichainNetworkStateWithEvmSelected,
+        ),
+      ).toStrictEqual(mockEvmNetworksWithNewConfig['eip155:1']);
+    });
+  });
+});
diff --git a/ui/selectors/multichain/networks.ts b/ui/selectors/multichain/networks.ts
new file mode 100644
index 000000000000..d64e0ee12470
--- /dev/null
+++ b/ui/selectors/multichain/networks.ts
@@ -0,0 +1,160 @@
+import {
+  type MultichainNetworkControllerState as InternalMultichainNetworkState,
+  type MultichainNetworkConfiguration as InternalMultichainNetworkConfiguration,
+  toEvmCaipChainId,
+  toMultichainNetworkConfigurationsByChainId,
+} from '@metamask/multichain-network-controller';
+import { type NetworkConfiguration as InternalNetworkConfiguration } from '@metamask/network-controller';
+import { type CaipChainId, BtcScope, SolScope } from '@metamask/keyring-api';
+
+import {
+  type ProviderConfigState,
+  type SelectedNetworkClientIdState,
+  getProviderConfig,
+  getNetworkConfigurationsByChainId,
+} from '../../../shared/modules/selectors/networks';
+import { createDeepEqualSelector } from '../../../shared/modules/selectors/util';
+import {
+  getIsBitcoinSupportEnabled,
+  getIsSolanaSupportEnabled,
+} from '../selectors';
+import { getInternalAccounts } from '../accounts';
+
+// Selector types
+
+export type MultichainNetworkControllerState = {
+  metamask: InternalMultichainNetworkState;
+};
+
+export type SelectedNetworkChainIdState = {
+  metamask: Pick<
+    InternalMultichainNetworkState,
+    'selectedMultichainNetworkChainId'
+  >;
+};
+
+export type IsEvmSelectedState = {
+  metamask: Pick;
+};
+
+export type MultichainNetworkConfigurationsByChainIdState = {
+  metamask: {
+    multichainNetworkConfigurationsByChainId: Record<
+      string,
+      InternalMultichainNetworkConfiguration
+    >;
+    networkConfigurationsByChainId: Record<
+      string,
+      InternalNetworkConfiguration
+    >;
+  };
+};
+
+/**
+ * This type takes into account the state
+ * of the multichain-network-controller and
+ * the network-controller.
+ */
+export type MultichainNetworkConfigState =
+  MultichainNetworkConfigurationsByChainIdState &
+    SelectedNetworkChainIdState &
+    IsEvmSelectedState &
+    SelectedNetworkClientIdState &
+    ProviderConfigState;
+
+// Selectors
+
+export const getNonEvmMultichainNetworkConfigurationsByChainId = (
+  state: MultichainNetworkConfigurationsByChainIdState,
+) => state.metamask.multichainNetworkConfigurationsByChainId;
+
+export const getIsNonEvmNetworksEnabled = createDeepEqualSelector(
+  getIsBitcoinSupportEnabled,
+  getIsSolanaSupportEnabled,
+  getInternalAccounts,
+  (isBitcoinEnabled, isSolanaEnabled, internalAccounts) => {
+    if (isBitcoinEnabled && isSolanaEnabled) {
+      return { bitcoinEnabled: true, solanaEnabled: true };
+    }
+
+    let bitcoinEnabled = isBitcoinEnabled;
+    let solanaEnabled = isSolanaEnabled;
+
+    for (const { scopes } of internalAccounts) {
+      if (scopes.includes(BtcScope.Mainnet)) {
+        bitcoinEnabled = true;
+      }
+      if (scopes.includes(SolScope.Mainnet)) {
+        solanaEnabled = true;
+      }
+      if (bitcoinEnabled && solanaEnabled) {
+        break;
+      }
+    }
+
+    return { bitcoinEnabled, solanaEnabled };
+  },
+);
+
+export const getMultichainNetworkConfigurationsByChainId =
+  createDeepEqualSelector(
+    getNonEvmMultichainNetworkConfigurationsByChainId,
+    getNetworkConfigurationsByChainId,
+    getIsNonEvmNetworksEnabled,
+    (
+      nonEvmNetworkConfigurationsByChainId,
+      networkConfigurationsByChainId,
+      isNonEvmNetworksEnabled,
+    ): Record => {
+      const filteredNonEvmNetworkConfigurationsByChainId: Record<
+        CaipChainId,
+        InternalMultichainNetworkConfiguration
+      > = {};
+
+      // This is not ideal but since there are only two non EVM networks
+      // we can just filter them out based on the support enabled
+      const { bitcoinEnabled, solanaEnabled } = isNonEvmNetworksEnabled;
+      if (bitcoinEnabled) {
+        filteredNonEvmNetworkConfigurationsByChainId[BtcScope.Mainnet] =
+          nonEvmNetworkConfigurationsByChainId[BtcScope.Mainnet];
+      }
+
+      if (solanaEnabled) {
+        filteredNonEvmNetworkConfigurationsByChainId[SolScope.Mainnet] =
+          nonEvmNetworkConfigurationsByChainId[SolScope.Mainnet];
+      }
+
+      const networks = {
+        ...filteredNonEvmNetworkConfigurationsByChainId,
+        ...toMultichainNetworkConfigurationsByChainId(
+          networkConfigurationsByChainId,
+        ),
+      };
+
+      return networks;
+    },
+  );
+
+export const getIsEvmMultichainNetworkSelected = (state: IsEvmSelectedState) =>
+  state.metamask.isEvmSelected;
+
+export const getSelectedMultichainNetworkChainId = (
+  state: MultichainNetworkConfigState,
+) => {
+  const isEvmSelected = getIsEvmMultichainNetworkSelected(state);
+
+  if (isEvmSelected) {
+    const evmNetworkConfig = getProviderConfig(state);
+    return toEvmCaipChainId(evmNetworkConfig.chainId);
+  }
+  return state.metamask.selectedMultichainNetworkChainId;
+};
+
+export const getSelectedMultichainNetworkConfiguration = (
+  state: MultichainNetworkConfigState,
+) => {
+  const chainId = getSelectedMultichainNetworkChainId(state);
+  const networkConfigurationsByChainId =
+    getMultichainNetworkConfigurationsByChainId(state);
+  return networkConfigurationsByChainId[chainId];
+};
diff --git a/ui/store/actions.ts b/ui/store/actions.ts
index deb1c10650e2..033cf0185161 100644
--- a/ui/store/actions.ts
+++ b/ui/store/actions.ts
@@ -29,7 +29,6 @@ import {
   UpdateProposedNamesResult,
 } from '@metamask/name-controller';
 import {
-  GasFeeEstimates,
   TransactionMeta,
   TransactionParams,
   TransactionType,
@@ -4511,14 +4510,6 @@ export function estimateGas(params: TransactionParams): Promise {
   return submitRequestToBackground('estimateGas', [params]);
 }
 
-export function estimateGasFee(request: {
-  transactionParams: TransactionParams;
-  chainId?: Hex;
-  networkClientId?: NetworkClientId;
-}): Promise<{ estimates: GasFeeEstimates }> {
-  return submitRequestToBackground('estimateGasFee', [request]);
-}
-
 export async function updateTokenType(
   tokenAddress: string,
 ): Promise {
diff --git a/yarn.lock b/yarn.lock
index c8148932a092..875664b1f752 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4822,15 +4822,16 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@metamask/accounts-controller@npm:^23.0.1":
-  version: 23.0.1
-  resolution: "@metamask/accounts-controller@npm:23.0.1"
+"@metamask/accounts-controller@npm:^24.0.0":
+  version: 24.0.0
+  resolution: "@metamask/accounts-controller@npm:24.0.0"
   dependencies:
     "@ethereumjs/util": "npm:^8.1.0"
     "@metamask/base-controller": "npm:^8.0.0"
     "@metamask/eth-snap-keyring": "npm:^10.0.0"
     "@metamask/keyring-api": "npm:^17.0.0"
     "@metamask/keyring-internal-api": "npm:^4.0.1"
+    "@metamask/network-controller": "npm:^22.2.1"
     "@metamask/snaps-sdk": "npm:^6.17.1"
     "@metamask/snaps-utils": "npm:^8.10.0"
     "@metamask/utils": "npm:^11.1.0"
@@ -4840,10 +4841,11 @@ __metadata:
     uuid: "npm:^8.3.2"
   peerDependencies:
     "@metamask/keyring-controller": ^19.0.0
+    "@metamask/network-controller": ^22.0.0
     "@metamask/providers": ^18.1.0
     "@metamask/snaps-controllers": ^9.19.0
     webextension-polyfill: ^0.10.0 || ^0.11.0 || ^0.12.0
-  checksum: 10/65ae8e09fe90de224ed7705dd23ef232ef2308cf5e8e7c1d3e4d13f9692c610e78553ca5d5095352f069a0038d587756bcbedcf14147ad5fd446bf855db69915
+  checksum: 10/2a67f5cd13bfde1abfec8d5676d33447dc1520c8b50d4322d56ea97904b72dc7b4d7b8b10f5e4c947a989a7b7dcb0cc421da23982ee5c0eea466048cfa4e18e6
   languageName: node
   linkType: hard
 
@@ -4874,15 +4876,15 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@metamask/approval-controller@npm:^7.0.0, @metamask/approval-controller@npm:^7.1.2":
-  version: 7.1.2
-  resolution: "@metamask/approval-controller@npm:7.1.2"
+"@metamask/approval-controller@npm:^7.0.0, @metamask/approval-controller@npm:^7.1.2, @metamask/approval-controller@npm:^7.1.3":
+  version: 7.1.3
+  resolution: "@metamask/approval-controller@npm:7.1.3"
   dependencies:
-    "@metamask/base-controller": "npm:^7.1.1"
+    "@metamask/base-controller": "npm:^8.0.0"
     "@metamask/rpc-errors": "npm:^7.0.2"
-    "@metamask/utils": "npm:^11.0.1"
+    "@metamask/utils": "npm:^11.1.0"
     nanoid: "npm:^3.3.8"
-  checksum: 10/e5903e8c3799484a3f42b2683ed733e68aca5204070c84f6012ee3830b04ebffdc7fc21fe80d2ea46f2a9ee3557a38e4031186f47e84d9079c2c902361543b0d
+  checksum: 10/2d88378dcc4f6c32ad544766cf26b7f59b1aee1d7ca0909b966c8c08eaac1d7109004120ca254d7ad008a6e5241b144d4dd831f926ec928a66f93cbd2e6f1693
   languageName: node
   linkType: hard
 
@@ -5517,15 +5519,15 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@metamask/json-rpc-middleware-stream@npm:^8.0.4, @metamask/json-rpc-middleware-stream@npm:^8.0.6":
-  version: 8.0.6
-  resolution: "@metamask/json-rpc-middleware-stream@npm:8.0.6"
+"@metamask/json-rpc-middleware-stream@npm:^8.0.4, @metamask/json-rpc-middleware-stream@npm:^8.0.6, @metamask/json-rpc-middleware-stream@npm:^8.0.7":
+  version: 8.0.7
+  resolution: "@metamask/json-rpc-middleware-stream@npm:8.0.7"
   dependencies:
-    "@metamask/json-rpc-engine": "npm:^10.0.2"
+    "@metamask/json-rpc-engine": "npm:^10.0.3"
     "@metamask/safe-event-emitter": "npm:^3.0.0"
-    "@metamask/utils": "npm:^11.0.1"
+    "@metamask/utils": "npm:^11.1.0"
     readable-stream: "npm:^3.6.2"
-  checksum: 10/4df2ddf068ee935b5ea29b833df243ee43e0a17ea0151bc312d4eaeec541612f7416761be2b66f316c0b12f577f0257831b83844f6b9addbaf5fe9d9c5638262
+  checksum: 10/54dadd16876ad1637b1fccd0d35c66dcc9a03d8614814d78b6730f9bb325e80ad1fdbb99d5b75785245aadf4064417ac35bc710a5549d13128f12153d0cc0432
   languageName: node
   linkType: hard
 
@@ -5566,9 +5568,9 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@metamask/keyring-controller@npm:^19.0.7":
-  version: 19.0.7
-  resolution: "@metamask/keyring-controller@npm:19.0.7"
+"@metamask/keyring-controller@npm:^19.0.7, @metamask/keyring-controller@npm:^19.1.0":
+  version: 19.1.0
+  resolution: "@metamask/keyring-controller@npm:19.1.0"
   dependencies:
     "@ethereumjs/util": "npm:^8.1.0"
     "@keystonehq/metamask-airgapped-keyring": "npm:^0.14.1"
@@ -5584,7 +5586,7 @@ __metadata:
     async-mutex: "npm:^0.5.0"
     ethereumjs-wallet: "npm:^1.0.1"
     immer: "npm:^9.0.6"
-  checksum: 10/9b385b2e4f17126ed992ff4b4ef2cfa86c7404fbda464b482f57ed2aa1254b0ea98b59f9182ee73a4a6dfc6eea93646950eced9370536be2837987556c3b16c3
+  checksum: 10/2121c8935f019edc92c1fef9901d9b67c1555905b96ac90640b3a991896c33261a6ca59fc23aeb995ca9f39d6c3b33a7b04d95ef48ec440f9683c3dd309bfd7b
   languageName: node
   linkType: hard
 
@@ -5705,6 +5707,21 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@metamask/multichain-network-controller@npm:^0.1.0":
+  version: 0.1.1
+  resolution: "@metamask/multichain-network-controller@npm:0.1.1"
+  dependencies:
+    "@metamask/base-controller": "npm:^8.0.0"
+    "@metamask/keyring-api": "npm:^17.0.0"
+    "@metamask/utils": "npm:^11.1.0"
+    "@solana/addresses": "npm:^2.0.0"
+  peerDependencies:
+    "@metamask/accounts-controller": ^24.0.0
+    "@metamask/network-controller": ^22.0.0
+  checksum: 10/0753831d802d84d154dbdfacac8ad8f7277d25957a3ec369a5fa588bee219656c4b6b0992699191eafa965ef2b43d1d4c827c0c7e417b82d94bf244040ca8451
+  languageName: node
+  linkType: hard
+
 "@metamask/multichain-transactions-controller@npm:^0.3.0":
   version: 0.3.0
   resolution: "@metamask/multichain-transactions-controller@npm:0.3.0"
@@ -6060,27 +6077,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@metamask/providers@npm:^18.3.1":
-  version: 18.3.1
-  resolution: "@metamask/providers@npm:18.3.1"
-  dependencies:
-    "@metamask/json-rpc-engine": "npm:^10.0.2"
-    "@metamask/json-rpc-middleware-stream": "npm:^8.0.6"
-    "@metamask/object-multiplex": "npm:^2.0.0"
-    "@metamask/rpc-errors": "npm:^7.0.2"
-    "@metamask/safe-event-emitter": "npm:^3.1.1"
-    "@metamask/utils": "npm:^11.0.1"
-    detect-browser: "npm:^5.2.0"
-    extension-port-stream: "npm:^4.1.0"
-    fast-deep-equal: "npm:^3.1.3"
-    is-stream: "npm:^2.0.0"
-    readable-stream: "npm:^3.6.2"
-  peerDependencies:
-    webextension-polyfill: ^0.10.0 || ^0.11.0 || ^0.12.0
-  checksum: 10/0e21ba9cce926a49dedbfe30fc964cd2349ee6bf9156f525fb894dcbc147a3ae480384884131a6b1a0a508989b547d8c8d2aeb3d10e11f67a8ee5230c45631a8
-  languageName: node
-  linkType: hard
-
 "@metamask/providers@npm:^20.0.0":
   version: 20.0.0
   resolution: "@metamask/providers@npm:20.0.0"
@@ -6239,7 +6235,48 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@metamask/snaps-controllers@npm:^9.19.0, @metamask/snaps-controllers@npm:^9.19.1":
+"@metamask/snaps-controllers@npm:^10.0.0":
+  version: 10.0.0
+  resolution: "@metamask/snaps-controllers@npm:10.0.0"
+  dependencies:
+    "@metamask/approval-controller": "npm:^7.1.3"
+    "@metamask/base-controller": "npm:^8.0.0"
+    "@metamask/json-rpc-engine": "npm:^10.0.2"
+    "@metamask/json-rpc-middleware-stream": "npm:^8.0.7"
+    "@metamask/key-tree": "npm:^10.0.2"
+    "@metamask/object-multiplex": "npm:^2.1.0"
+    "@metamask/permission-controller": "npm:^11.0.6"
+    "@metamask/phishing-controller": "npm:^12.3.2"
+    "@metamask/post-message-stream": "npm:^9.0.0"
+    "@metamask/rpc-errors": "npm:^7.0.2"
+    "@metamask/snaps-registry": "npm:^3.2.3"
+    "@metamask/snaps-rpc-methods": "npm:^11.12.0"
+    "@metamask/snaps-sdk": "npm:^6.18.0"
+    "@metamask/snaps-utils": "npm:^9.0.0"
+    "@metamask/utils": "npm:^11.2.0"
+    "@xstate/fsm": "npm:^2.0.0"
+    async-mutex: "npm:^0.5.0"
+    browserify-zlib: "npm:^0.2.0"
+    concat-stream: "npm:^2.0.0"
+    fast-deep-equal: "npm:^3.1.3"
+    get-npm-tarball-url: "npm:^2.0.3"
+    immer: "npm:^9.0.6"
+    luxon: "npm:^3.5.0"
+    nanoid: "npm:^3.1.31"
+    readable-stream: "npm:^3.6.2"
+    readable-web-to-node-stream: "npm:^3.0.2"
+    semver: "npm:^7.5.4"
+    tar-stream: "npm:^3.1.7"
+  peerDependencies:
+    "@metamask/snaps-execution-environments": ^7.0.0
+  peerDependenciesMeta:
+    "@metamask/snaps-execution-environments":
+      optional: true
+  checksum: 10/eeb094883987a309ee7087bf540428ecd27d39189e494c81cc5f55623f46577fc9b6b4b907f2fc14cc5940486c56fca9074d204f5e43924d793e974cb3f63f34
+  languageName: node
+  linkType: hard
+
+"@metamask/snaps-controllers@npm:^9.19.0":
   version: 9.19.1
   resolution: "@metamask/snaps-controllers@npm:9.19.1"
   dependencies:
@@ -6280,22 +6317,22 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@metamask/snaps-execution-environments@npm:^6.14.0":
-  version: 6.14.0
-  resolution: "@metamask/snaps-execution-environments@npm:6.14.0"
+"@metamask/snaps-execution-environments@npm:^7.0.0":
+  version: 7.0.0
+  resolution: "@metamask/snaps-execution-environments@npm:7.0.0"
   dependencies:
     "@metamask/json-rpc-engine": "npm:^10.0.2"
     "@metamask/object-multiplex": "npm:^2.1.0"
     "@metamask/post-message-stream": "npm:^9.0.0"
-    "@metamask/providers": "npm:^18.3.1"
+    "@metamask/providers": "npm:^20.0.0"
     "@metamask/rpc-errors": "npm:^7.0.2"
-    "@metamask/snaps-sdk": "npm:^6.17.0"
-    "@metamask/snaps-utils": "npm:^8.10.0"
+    "@metamask/snaps-sdk": "npm:^6.18.0"
+    "@metamask/snaps-utils": "npm:^9.0.0"
     "@metamask/superstruct": "npm:^3.1.0"
-    "@metamask/utils": "npm:^11.0.1"
+    "@metamask/utils": "npm:^11.2.0"
     nanoid: "npm:^3.1.31"
     readable-stream: "npm:^3.6.2"
-  checksum: 10/7ee10aacc5b94f51a1e4414aef097509c35625a5bccf7d8b693a87ced85a466244a64471452ee2fa8ee8b0a953b8859c0703bfff30b2ac89c327561d62228e46
+  checksum: 10/c63b9e827cf6069af3fd8b5378f6f595c28cced32a46ea81ab7a5729d2425c8e466094dad935d360f79fbf5905e1b51b74f14aacc711effe03a315cd650fab9d
   languageName: node
   linkType: hard
 
@@ -6311,33 +6348,33 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@metamask/snaps-rpc-methods@npm:^11.11.0":
-  version: 11.11.0
-  resolution: "@metamask/snaps-rpc-methods@npm:11.11.0"
+"@metamask/snaps-rpc-methods@npm:^11.11.0, @metamask/snaps-rpc-methods@npm:^11.12.0":
+  version: 11.12.0
+  resolution: "@metamask/snaps-rpc-methods@npm:11.12.0"
   dependencies:
     "@metamask/key-tree": "npm:^10.0.2"
-    "@metamask/permission-controller": "npm:^11.0.5"
+    "@metamask/permission-controller": "npm:^11.0.6"
     "@metamask/rpc-errors": "npm:^7.0.2"
-    "@metamask/snaps-sdk": "npm:^6.17.0"
-    "@metamask/snaps-utils": "npm:^8.10.0"
+    "@metamask/snaps-sdk": "npm:^6.18.0"
+    "@metamask/snaps-utils": "npm:^9.0.0"
     "@metamask/superstruct": "npm:^3.1.0"
-    "@metamask/utils": "npm:^11.0.1"
+    "@metamask/utils": "npm:^11.2.0"
     "@noble/hashes": "npm:^1.3.1"
     luxon: "npm:^3.5.0"
-  checksum: 10/cd88db675062e848a65dc4edcd26ed24184430af77ed58f3e7949879255cbf94d1b5fcc51127646494a239c390fe6398c2ffaa5f3d2f63e7f859225e2eeae832
+  checksum: 10/75e83542b7fa7302e02ff010561d1a598cac348f3e31f874cb99b86032e0e1d634a96a0a5037df2f0dc67b87af9a40b202cf2d9e4b44863495deac6a358f5239
   languageName: node
   linkType: hard
 
-"@metamask/snaps-sdk@npm:^6.17.1":
-  version: 6.17.1
-  resolution: "@metamask/snaps-sdk@npm:6.17.1"
+"@metamask/snaps-sdk@npm:^6.18.0":
+  version: 6.18.0
+  resolution: "@metamask/snaps-sdk@npm:6.18.0"
   dependencies:
     "@metamask/key-tree": "npm:^10.0.2"
-    "@metamask/providers": "npm:^18.3.1"
+    "@metamask/providers": "npm:^20.0.0"
     "@metamask/rpc-errors": "npm:^7.0.2"
     "@metamask/superstruct": "npm:^3.1.0"
-    "@metamask/utils": "npm:^11.0.1"
-  checksum: 10/05c5170c6250115535bc6d06a417157bb55005dd6fe86e768d70fabfba610ec8114cf45a8a5aad1219b1cfb0bcf5e080974735a0ac9a8c8bd0ac102f5c3cf42f
+    "@metamask/utils": "npm:^11.2.0"
+  checksum: 10/9ab80f884b6f63916af854c71c27fcee60ac9090a4f47253f06c8eeac7a8a3b9bfcee84e5f40fbb34391de4b0d77b936fe8b51d822f2c4944f9b4e98b14e6fd2
   languageName: node
   linkType: hard
 
@@ -6372,6 +6409,37 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@metamask/snaps-utils@npm:^9.0.0":
+  version: 9.0.0
+  resolution: "@metamask/snaps-utils@npm:9.0.0"
+  dependencies:
+    "@babel/core": "npm:^7.23.2"
+    "@babel/types": "npm:^7.23.0"
+    "@metamask/base-controller": "npm:^8.0.0"
+    "@metamask/key-tree": "npm:^10.0.2"
+    "@metamask/permission-controller": "npm:^11.0.6"
+    "@metamask/rpc-errors": "npm:^7.0.2"
+    "@metamask/slip44": "npm:^4.1.0"
+    "@metamask/snaps-registry": "npm:^3.2.3"
+    "@metamask/snaps-sdk": "npm:^6.18.0"
+    "@metamask/superstruct": "npm:^3.1.0"
+    "@metamask/utils": "npm:^11.2.0"
+    "@noble/hashes": "npm:^1.3.1"
+    "@scure/base": "npm:^1.1.1"
+    chalk: "npm:^4.1.2"
+    cron-parser: "npm:^4.5.0"
+    fast-deep-equal: "npm:^3.1.3"
+    fast-json-stable-stringify: "npm:^2.1.0"
+    fast-xml-parser: "npm:^4.4.1"
+    marked: "npm:^12.0.1"
+    rfdc: "npm:^1.3.0"
+    semver: "npm:^7.5.4"
+    ses: "npm:^1.1.0"
+    validate-npm-package-name: "npm:^5.0.0"
+  checksum: 10/0aaaa584e5950daee8c8277f6e8a405ba416a829bef01f8c729f510afa634a06ee1c89a28d25074921b0c2bdd77631c5f37ec0b25111b9bd789a0082d306905d
+  languageName: node
+  linkType: hard
+
 "@metamask/solana-wallet-snap@npm:^1.2.0":
   version: 1.2.0
   resolution: "@metamask/solana-wallet-snap@npm:1.2.0"
@@ -6500,9 +6568,9 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@metamask/utils@npm:^11.0.1, @metamask/utils@npm:^11.1.0":
-  version: 11.1.0
-  resolution: "@metamask/utils@npm:11.1.0"
+"@metamask/utils@npm:^11.0.1, @metamask/utils@npm:^11.1.0, @metamask/utils@npm:^11.2.0":
+  version: 11.2.0
+  resolution: "@metamask/utils@npm:11.2.0"
   dependencies:
     "@ethereumjs/tx": "npm:^4.2.0"
     "@metamask/superstruct": "npm:^3.1.0"
@@ -6513,7 +6581,7 @@ __metadata:
     pony-cause: "npm:^2.1.10"
     semver: "npm:^7.5.4"
     uuid: "npm:^9.0.1"
-  checksum: 10/756f13987881fe26adaa0a54354bc5af20cedee4dd228a736d481697dc634adb9e6e54d8f1dcc1d487b2376ab4ba8c576ecbb24beab2fb63aff721d0d5c0f5fe
+  checksum: 10/9cc2cb6af4627085e72a310ba9b8921c69757d94e2992d4664627e5a0d99b1f2f7f8069c6f22262515135e1172bd66b82d00512d90ea2ec6da4e768f3d7d4ae2
   languageName: node
   linkType: hard
 
@@ -8115,6 +8183,31 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@solana/addresses@npm:^2.0.0":
+  version: 2.0.0
+  resolution: "@solana/addresses@npm:2.0.0"
+  dependencies:
+    "@solana/assertions": "npm:2.0.0"
+    "@solana/codecs-core": "npm:2.0.0"
+    "@solana/codecs-strings": "npm:2.0.0"
+    "@solana/errors": "npm:2.0.0"
+  peerDependencies:
+    typescript: ">=5"
+  checksum: 10/f99d09c72046c73858aa8b7bc323e634a60b1023a4d280036bc94489e431075c7f29d2889e8787e33a04cfdecbe77cd8ca26c31ded73f735dc98e49c3151cc17
+  languageName: node
+  linkType: hard
+
+"@solana/assertions@npm:2.0.0":
+  version: 2.0.0
+  resolution: "@solana/assertions@npm:2.0.0"
+  dependencies:
+    "@solana/errors": "npm:2.0.0"
+  peerDependencies:
+    typescript: ">=5"
+  checksum: 10/c1af37ae1bd79b1657395d9315ac261dabc9908a64af6ed80e3b7e5140909cd8c8c757f0c41fff084e26fbb4d32f091c89c092a8c1ed5e6f4565dfe7426c0979
+  languageName: node
+  linkType: hard
+
 "@solana/assertions@npm:2.0.0-rc.4":
   version: 2.0.0-rc.4
   resolution: "@solana/assertions@npm:2.0.0-rc.4"
@@ -8135,6 +8228,17 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@solana/codecs-core@npm:2.0.0":
+  version: 2.0.0
+  resolution: "@solana/codecs-core@npm:2.0.0"
+  dependencies:
+    "@solana/errors": "npm:2.0.0"
+  peerDependencies:
+    typescript: ">=5"
+  checksum: 10/e58a72e67bee3e5da60201eecda345c604b49138d5298e39b8e7d4d57a4dee47be3b0ecc8fc3429a2a60a42c952eaf860d43d3df1eb2b1d857e35368eca9c820
+  languageName: node
+  linkType: hard
+
 "@solana/codecs-core@npm:2.0.0-rc.4":
   version: 2.0.0-rc.4
   resolution: "@solana/codecs-core@npm:2.0.0-rc.4"
@@ -8146,6 +8250,18 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@solana/codecs-numbers@npm:2.0.0":
+  version: 2.0.0
+  resolution: "@solana/codecs-numbers@npm:2.0.0"
+  dependencies:
+    "@solana/codecs-core": "npm:2.0.0"
+    "@solana/errors": "npm:2.0.0"
+  peerDependencies:
+    typescript: ">=5"
+  checksum: 10/500144d549ea0292c2f672300610df9054339a31cb6a4e61b29623308ef3b14f15eb587ee6139cf3334d2e0f29db1da053522da244b12184bb8fbdb097b7102b
+  languageName: node
+  linkType: hard
+
 "@solana/codecs-numbers@npm:2.0.0-rc.4":
   version: 2.0.0-rc.4
   resolution: "@solana/codecs-numbers@npm:2.0.0-rc.4"
@@ -8158,6 +8274,20 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@solana/codecs-strings@npm:2.0.0":
+  version: 2.0.0
+  resolution: "@solana/codecs-strings@npm:2.0.0"
+  dependencies:
+    "@solana/codecs-core": "npm:2.0.0"
+    "@solana/codecs-numbers": "npm:2.0.0"
+    "@solana/errors": "npm:2.0.0"
+  peerDependencies:
+    fastestsmallesttextencoderdecoder: ^1.0.22
+    typescript: ">=5"
+  checksum: 10/4380136e2603c2cee12a28438817beb34b0fe45da222b8c38342c5b3680f02086ec7868cde0bb7b4e5dd459af5988613af1d97230c6a193db3be1c45122aba39
+  languageName: node
+  linkType: hard
+
 "@solana/codecs-strings@npm:2.0.0-rc.4":
   version: 2.0.0-rc.4
   resolution: "@solana/codecs-strings@npm:2.0.0-rc.4"
@@ -8172,6 +8302,20 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@solana/errors@npm:2.0.0":
+  version: 2.0.0
+  resolution: "@solana/errors@npm:2.0.0"
+  dependencies:
+    chalk: "npm:^5.3.0"
+    commander: "npm:^12.1.0"
+  peerDependencies:
+    typescript: ">=5"
+  bin:
+    errors: bin/cli.mjs
+  checksum: 10/4191f96cad47c64266ec501ae1911a6245fd02b2f68a2c53c3dabbc63eb7c5462f170a765b584348b195da2387e7ca02096d792c67352c2c30a4f3a3cc7e4270
+  languageName: node
+  linkType: hard
+
 "@solana/errors@npm:2.0.0-rc.4":
   version: 2.0.0-rc.4
   resolution: "@solana/errors@npm:2.0.0-rc.4"
@@ -26651,7 +26795,7 @@ __metadata:
     "@metamask-institutional/types": "npm:^1.2.0"
     "@metamask/abi-utils": "npm:^2.0.2"
     "@metamask/account-watcher": "npm:^4.1.2"
-    "@metamask/accounts-controller": "npm:^23.0.1"
+    "@metamask/accounts-controller": "npm:^24.0.0"
     "@metamask/address-book-controller": "npm:^6.0.3"
     "@metamask/announcement-controller": "npm:^7.0.3"
     "@metamask/api-specs": "npm:^0.10.15"
@@ -26688,7 +26832,7 @@ __metadata:
     "@metamask/json-rpc-engine": "npm:^10.0.0"
     "@metamask/json-rpc-middleware-stream": "npm:^8.0.4"
     "@metamask/keyring-api": "npm:^17.0.0"
-    "@metamask/keyring-controller": "npm:^19.0.7"
+    "@metamask/keyring-controller": "npm:^19.1.0"
     "@metamask/keyring-internal-api": "npm:^4.0.2"
     "@metamask/keyring-snap-client": "npm:^4.0.0"
     "@metamask/logging-controller": "npm:^6.0.4"
@@ -26697,6 +26841,7 @@ __metadata:
     "@metamask/message-signing-snap": "npm:^0.6.0"
     "@metamask/metamask-eth-abis": "npm:^3.1.1"
     "@metamask/multichain": "npm:^2.1.0"
+    "@metamask/multichain-network-controller": "npm:^0.1.0"
     "@metamask/multichain-transactions-controller": "npm:^0.3.0"
     "@metamask/name-controller": "npm:^8.0.3"
     "@metamask/network-controller": "patch:@metamask/network-controller@npm%3A22.1.1#~/.yarn/patches/@metamask-network-controller-npm-22.1.1-09b6510f1e.patch"
@@ -26723,11 +26868,11 @@ __metadata:
     "@metamask/selected-network-controller": "npm:^19.0.0"
     "@metamask/signature-controller": "npm:^23.1.0"
     "@metamask/smart-transactions-controller": "npm:^16.0.1"
-    "@metamask/snaps-controllers": "npm:^9.19.1"
-    "@metamask/snaps-execution-environments": "npm:^6.14.0"
-    "@metamask/snaps-rpc-methods": "npm:^11.11.0"
-    "@metamask/snaps-sdk": "npm:^6.17.1"
-    "@metamask/snaps-utils": "npm:^8.10.0"
+    "@metamask/snaps-controllers": "npm:^10.0.0"
+    "@metamask/snaps-execution-environments": "npm:^7.0.0"
+    "@metamask/snaps-rpc-methods": "npm:^11.12.0"
+    "@metamask/snaps-sdk": "npm:^6.18.0"
+    "@metamask/snaps-utils": "npm:^9.0.0"
     "@metamask/solana-wallet-snap": "npm:^1.2.0"
     "@metamask/test-bundler": "npm:^1.0.0"
     "@metamask/test-dapp": "npm:9.0.0"