From 91dfc0f7cbf8feaa66dd1a51d1c4beef89e58027 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Mon, 6 Jul 2026 12:49:52 +0200 Subject: [PATCH 01/24] chore: use preview builds --- package.json | 8 ++- yarn.lock | 151 ++++++++++++++++++++++++++++++++++----------------- 2 files changed, 108 insertions(+), 51 deletions(-) diff --git a/package.json b/package.json index c3d3fb90e4..13b6730a52 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,13 @@ "resolutions": { "elliptic@6.5.4": "^6.5.7", "fast-xml-parser@^4.3.4": "^4.4.1", - "ws@7.4.6": "^7.5.10" + "ws@7.4.6": "^7.5.10", + "@metamask/keyring-api": "npm:@metamask-previews/keyring-api@23.4.0-5951828", + "@metamask/keyring-internal-api": "npm:@metamask-previews/keyring-internal-api@11.0.1-5951828", + "@metamask/keyring-internal-snap-client": "npm:@metamask-previews/keyring-internal-snap-client@10.0.4-5951828", + "@metamask/keyring-snap-client": "npm:@metamask-previews/keyring-snap-client@9.1.0-5951828", + "@metamask/keyring-snap-sdk": "npm:@metamask-previews/keyring-snap-sdk@9.1.0-5951828", + "@metamask/eth-snap-keyring": "npm:@metamask-previews/eth-snap-keyring@22.4.0-5951828" }, "simple-git-hooks": { "pre-push": "yarn lint" diff --git a/yarn.lock b/yarn.lock index 1cb7aab1b1..441a3c1380 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6814,29 +6814,28 @@ __metadata: languageName: node linkType: hard -"@metamask/eth-snap-keyring@npm:^22.3.0": - version: 22.3.0 - resolution: "@metamask/eth-snap-keyring@npm:22.3.0" +"@metamask/eth-snap-keyring@npm:@metamask-previews/eth-snap-keyring@22.4.0-5951828": + version: 22.4.0-5951828 + resolution: "@metamask-previews/eth-snap-keyring@npm:22.4.0-5951828" dependencies: "@ethereumjs/tx": "npm:^5.4.0" "@metamask/eth-sig-util": "npm:^8.2.0" - "@metamask/keyring-internal-api": "npm:^11.0.1" - "@metamask/keyring-internal-snap-client": "npm:^10.0.3" - "@metamask/keyring-sdk": "npm:^2.2.0" - "@metamask/keyring-snap-sdk": "npm:^9.0.2" - "@metamask/keyring-utils": "npm:^3.3.1" + "@metamask/keyring-api": "npm:23.4.0" + "@metamask/keyring-internal-api": "npm:11.0.1" + "@metamask/keyring-internal-snap-client": "npm:10.0.4" + "@metamask/keyring-sdk": "npm:2.2.0" + "@metamask/keyring-snap-sdk": "npm:9.1.0" + "@metamask/keyring-utils": "npm:3.3.1" "@metamask/messenger": "npm:^1.1.1" "@metamask/snaps-controllers": "npm:^19.0.1" "@metamask/snaps-sdk": "npm:^11.0.0" - "@metamask/snaps-utils": "npm:^12.1.3" - "@metamask/superstruct": "npm:^3.1.0" + "@metamask/snaps-utils": "npm:^12.2.1" + "@metamask/superstruct": "npm:^3.3.0" "@metamask/utils": "npm:^11.11.0" "@types/uuid": "npm:^9.0.8" async-mutex: "npm:^0.5.0" uuid: "npm:^9.0.1" - peerDependencies: - "@metamask/keyring-api": ^23.0.0 - checksum: 10/d110a2cd97e0cd22eae7d5e87ea14ae09530d3d47204b4bc565ffd70f8af75133a2c3c15b0f51e49478b379ee40ea9980d3591c85a8e24c21e3768b8f1d8c1b8 + checksum: 10/b2dbd33b28a845e2e5234a879c18a6a99cc3cd257ab6b121c64469eb78a2bcb36087b10c752ee921d08a48f765636631ebec6132eb09005939469992717b2230 languageName: node linkType: hard @@ -7178,15 +7177,15 @@ __metadata: languageName: node linkType: hard -"@metamask/keyring-api@npm:^23.1.0, @metamask/keyring-api@npm:^23.2.0, @metamask/keyring-api@npm:^23.3.0": - version: 23.3.0 - resolution: "@metamask/keyring-api@npm:23.3.0" +"@metamask/keyring-api@npm:@metamask-previews/keyring-api@23.4.0-5951828": + version: 23.4.0-5951828 + resolution: "@metamask-previews/keyring-api@npm:23.4.0-5951828" dependencies: - "@metamask/keyring-utils": "npm:^3.3.1" - "@metamask/superstruct": "npm:^3.1.0" + "@metamask/keyring-utils": "npm:3.3.1" + "@metamask/superstruct": "npm:^3.3.0" "@metamask/utils": "npm:^11.11.0" bitcoin-address-validation: "npm:^2.2.3" - checksum: 10/c6ad3bf4294b09730d752d548b1cd0c1c622f357a12c7287af2b5889a01e99a4d20316cc9e96840d5c5619eee0f9305d4dfc03febd0bd136e72de95ce3fd0a1e + checksum: 10/4b378bc4b2ddd3affe51c4ec06aaa979ebcc7786488b04ad5cc6af44255b50af4d55f216d18739249c6a52a96a8110fc382261fdd5426d0bc0b1f0ea6557cad3 languageName: node linkType: hard @@ -7230,31 +7229,31 @@ __metadata: languageName: unknown linkType: soft -"@metamask/keyring-internal-api@npm:^11.0.1": - version: 11.0.1 - resolution: "@metamask/keyring-internal-api@npm:11.0.1" +"@metamask/keyring-internal-api@npm:@metamask-previews/keyring-internal-api@11.0.1-5951828": + version: 11.0.1-5951828 + resolution: "@metamask-previews/keyring-internal-api@npm:11.0.1-5951828" dependencies: - "@metamask/keyring-api": "npm:^23.1.0" - "@metamask/keyring-utils": "npm:^3.2.0" - "@metamask/superstruct": "npm:^3.1.0" - checksum: 10/d35a5b45887566f47e41c1109e9f4eca5108fd1b8402773d40a882c904de0f33cc70e0cc7d1e1a34397ae434646c597d9793c31dbedc83cdf64e41f8e719ccc4 + "@metamask/keyring-api": "npm:23.4.0" + "@metamask/keyring-utils": "npm:3.3.1" + "@metamask/superstruct": "npm:^3.3.0" + checksum: 10/826576eb366fcf61b7e4536b68355bd87a5223d475d2f2eaa817f02c17105d2dd83e3c4a939c014237df70a4d4f2b7add74a1141ec3d775251c0eb2dfe94a33b languageName: node linkType: hard -"@metamask/keyring-internal-snap-client@npm:^10.0.3": - version: 10.0.3 - resolution: "@metamask/keyring-internal-snap-client@npm:10.0.3" +"@metamask/keyring-internal-snap-client@npm:@metamask-previews/keyring-internal-snap-client@10.0.4-5951828": + version: 10.0.4-5951828 + resolution: "@metamask-previews/keyring-internal-snap-client@npm:10.0.4-5951828" dependencies: - "@metamask/keyring-api": "npm:^23.1.0" - "@metamask/keyring-internal-api": "npm:^11.0.1" - "@metamask/keyring-snap-client": "npm:^9.0.2" - "@metamask/keyring-utils": "npm:^3.2.0" + "@metamask/keyring-api": "npm:23.4.0" + "@metamask/keyring-internal-api": "npm:11.0.1" + "@metamask/keyring-snap-client": "npm:9.1.0" + "@metamask/keyring-utils": "npm:3.3.1" "@metamask/messenger": "npm:^1.1.1" - checksum: 10/30c00a2f39125e7f99bd6513096f23fa243f30cd7ce4e77aa96f52c66fa99c077dfba0544d2693e16c283a2a5610f6b844eaa39130d1f760390d6ca1f53f0507 + checksum: 10/420bb2f414a12cc66c03b37a2969a8203aa6e9f4404696523c8e9e979f4e50b1b29a3878d709b1475fc52333dd0d2597efdd9600eb64d6b104b85f5a36605840 languageName: node linkType: hard -"@metamask/keyring-sdk@npm:^2.0.2, @metamask/keyring-sdk@npm:^2.2.0": +"@metamask/keyring-sdk@npm:2.2.0, @metamask/keyring-sdk@npm:^2.0.2, @metamask/keyring-sdk@npm:^2.2.0": version: 2.2.0 resolution: "@metamask/keyring-sdk@npm:2.2.0" dependencies: @@ -7272,39 +7271,39 @@ __metadata: languageName: node linkType: hard -"@metamask/keyring-snap-client@npm:^9.0.2": - version: 9.0.2 - resolution: "@metamask/keyring-snap-client@npm:9.0.2" +"@metamask/keyring-snap-client@npm:@metamask-previews/keyring-snap-client@9.1.0-5951828": + version: 9.1.0-5951828 + resolution: "@metamask-previews/keyring-snap-client@npm:9.1.0-5951828" dependencies: - "@metamask/keyring-api": "npm:^23.1.0" - "@metamask/keyring-utils": "npm:^3.2.0" - "@metamask/superstruct": "npm:^3.1.0" + "@metamask/keyring-api": "npm:23.4.0" + "@metamask/keyring-utils": "npm:3.3.1" + "@metamask/superstruct": "npm:^3.3.0" "@types/uuid": "npm:^9.0.8" uuid: "npm:^9.0.1" webextension-polyfill: "npm:^0.12.0" peerDependencies: "@metamask/providers": ^19.0.0 - checksum: 10/f4de52be334d941fffafb6458260875706a09315d68856577a66dd36b069de00260632cfcd0fe9b56528382512dee2c226f61430aafb619869f1b1711d3ffffd + checksum: 10/36a34a06e0eb65ba514d3c7339b4aa0db660790469649c493c3bb319d940285e828f1704833edc89ddd102a41d50ece12f8105ce710175cdcc4e0845550b00d4 languageName: node linkType: hard -"@metamask/keyring-snap-sdk@npm:^9.0.2": - version: 9.0.2 - resolution: "@metamask/keyring-snap-sdk@npm:9.0.2" +"@metamask/keyring-snap-sdk@npm:@metamask-previews/keyring-snap-sdk@9.1.0-5951828": + version: 9.1.0-5951828 + resolution: "@metamask-previews/keyring-snap-sdk@npm:9.1.0-5951828" dependencies: - "@metamask/keyring-utils": "npm:^3.3.1" + "@metamask/keyring-api": "npm:23.4.0" + "@metamask/keyring-utils": "npm:3.3.1" "@metamask/snaps-sdk": "npm:^11.0.0" - "@metamask/superstruct": "npm:^3.1.0" + "@metamask/superstruct": "npm:^3.3.0" "@metamask/utils": "npm:^11.11.0" webextension-polyfill: "npm:^0.12.0" peerDependencies: - "@metamask/keyring-api": ^23.0.0 "@metamask/providers": ^19.0.0 - checksum: 10/a3fa4c306b058f484042e4460f8d19d509c4725cb49c40328dd905555bae33e07e29262891500acbcb845891af3721a562a36914286fc876f37668ce63f1bee1 + checksum: 10/f1793bc56a1dbe58ad5ca149845e22c378fa88531a134c3de38208af5e38ab500279937d8e84e45132ed03fb2df758c321ca243e86216e163934df5d0925c17a languageName: node linkType: hard -"@metamask/keyring-utils@npm:^3.2.0, @metamask/keyring-utils@npm:^3.3.1": +"@metamask/keyring-utils@npm:3.3.1, @metamask/keyring-utils@npm:^3.2.0, @metamask/keyring-utils@npm:^3.3.1": version: 3.3.1 resolution: "@metamask/keyring-utils@npm:3.3.1" dependencies: @@ -8637,6 +8636,20 @@ __metadata: languageName: node linkType: hard +"@metamask/snaps-sdk@npm:^11.1.1": + version: 11.1.1 + resolution: "@metamask/snaps-sdk@npm:11.1.1" + dependencies: + "@metamask/key-tree": "npm:^10.1.1" + "@metamask/providers": "npm:^22.1.1" + "@metamask/rpc-errors": "npm:^7.0.3" + "@metamask/superstruct": "npm:^3.2.1" + "@metamask/utils": "npm:^11.11.0" + luxon: "npm:^3.5.0" + checksum: 10/8419625775b83045d98c4ed920002ad884471e2303f6650104c97457c238f095ff35030663ddf111ebb6ce8f4cd915f57b7b9a5597d00288093701dbb79e787c + languageName: node + linkType: hard + "@metamask/snaps-utils@npm:^12.1.2, @metamask/snaps-utils@npm:^12.1.3, @metamask/snaps-utils@npm:^12.2.0": version: 12.2.0 resolution: "@metamask/snaps-utils@npm:12.2.0" @@ -8668,6 +8681,37 @@ __metadata: languageName: node linkType: hard +"@metamask/snaps-utils@npm:^12.2.1": + version: 12.2.1 + resolution: "@metamask/snaps-utils@npm:12.2.1" + dependencies: + "@babel/core": "npm:^7.23.2" + "@babel/types": "npm:^7.23.0" + "@metamask/key-tree": "npm:^10.1.1" + "@metamask/messenger": "npm:^1.2.0" + "@metamask/permission-controller": "npm:^13.1.1" + "@metamask/rpc-errors": "npm:^7.0.3" + "@metamask/slip44": "npm:^4.4.0" + "@metamask/snaps-registry": "npm:^4.0.0" + "@metamask/snaps-sdk": "npm:^11.1.1" + "@metamask/superstruct": "npm:^3.2.1" + "@metamask/utils": "npm:^11.11.0" + "@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:^5.5.6" + luxon: "npm:^3.5.0" + marked: "npm:^12.0.1" + rfdc: "npm:^1.3.0" + semver: "npm:^7.5.4" + ses: "npm:^1.15.0" + validate-npm-package-name: "npm:^5.0.0" + checksum: 10/fda8caefa414d9755a2305b345b5bbbf30ee614215a49070449f314d5d906c645457cf0d7f4881a82f69d398aff5ad2fe9c648bbb2a5003c29c2d01e2f42f199 + languageName: node + linkType: hard + "@metamask/social-controllers@workspace:packages/social-controllers": version: 0.0.0-use.local resolution: "@metamask/social-controllers@workspace:packages/social-controllers" @@ -8786,6 +8830,13 @@ __metadata: languageName: node linkType: hard +"@metamask/superstruct@npm:^3.3.0": + version: 3.3.0 + resolution: "@metamask/superstruct@npm:3.3.0" + checksum: 10/664d5e330484a86420bc004b1c7f8301e1501cce712f611fe657176b2979edbd7cc6f4c77c8b1610486a685ebbd0b72dacf4bd6cf143d6b52038069d2f4e84ab + languageName: node + linkType: hard + "@metamask/swappable-obj-proxy@npm:^2.3.0": version: 2.3.0 resolution: "@metamask/swappable-obj-proxy@npm:2.3.0" From 4e6c4d3c36618cb5f73b00b5ed866063962c0286 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Mon, 6 Jul 2026 13:20:25 +0200 Subject: [PATCH 02/24] feat(snap-account-service): expose snap-only keyring methods through the service --- packages/snap-account-service/CHANGELOG.md | 7 + packages/snap-account-service/package.json | 1 + .../SnapAccountService-method-action-types.ts | 58 +++- .../src/SnapAccountService.test.ts | 289 ++++++++++++++++++ .../src/SnapAccountService.ts | 101 +++++- packages/snap-account-service/src/index.ts | 4 + yarn.lock | 1 + 7 files changed, 458 insertions(+), 3 deletions(-) diff --git a/packages/snap-account-service/CHANGELOG.md b/packages/snap-account-service/CHANGELOG.md index 25324f0261..a6cf33b721 100644 --- a/packages/snap-account-service/CHANGELOG.md +++ b/packages/snap-account-service/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add `SnapAccountService:getAccountAssets`, `SnapAccountService:getAccountBalances`, `SnapAccountService:getAccountTransactions`, and `SnapAccountService:resolveAccountAddress` messenger actions ([#XXXX](https://github.com/MetaMask/core/pull/XXXX)) + - These actions proxy the corresponding `KeyringClient` (v2) methods, with `snapId` as the first parameter to identify the target Snap. + - Each call goes through `ensureReady` to guarantee the Snap is ready before the request is sent. + - The service messenger now additionally requires `SnapController:handleRequest`. + ### Changed - Bump `@metamask/keyring-api` from `^23.1.0` to `^23.3.0` ([#9249](https://github.com/MetaMask/core/pull/9249)) diff --git a/packages/snap-account-service/package.json b/packages/snap-account-service/package.json index c072e2bffa..ed8cc297ef 100644 --- a/packages/snap-account-service/package.json +++ b/packages/snap-account-service/package.json @@ -58,6 +58,7 @@ "@metamask/eth-snap-keyring": "^22.3.0", "@metamask/keyring-api": "^23.3.0", "@metamask/keyring-controller": "^27.1.0", + "@metamask/keyring-internal-snap-client": "^10.0.4", "@metamask/keyring-snap-sdk": "^9.0.2", "@metamask/messenger": "^2.0.0", "@metamask/snaps-controllers": "^19.0.0", diff --git a/packages/snap-account-service/src/SnapAccountService-method-action-types.ts b/packages/snap-account-service/src/SnapAccountService-method-action-types.ts index 8434c285eb..d79f9f6377 100644 --- a/packages/snap-account-service/src/SnapAccountService-method-action-types.ts +++ b/packages/snap-account-service/src/SnapAccountService-method-action-types.ts @@ -62,6 +62,58 @@ export type SnapAccountServiceHandleKeyringSnapMessageAction = { handler: SnapAccountService['handleKeyringSnapMessage']; }; +/** + * Returns the CAIP-19 asset type/ID list supported by an account. + * + * @param snapId - ID of the Snap. + * @param id - ID of the account. + * @returns A promise resolving to the list of supported CAIP-19 asset type/IDs. + */ +export type SnapAccountServiceGetAccountAssetsAction = { + type: `SnapAccountService:getAccountAssets`; + handler: SnapAccountService['getAccountAssets']; +}; + +/** + * Returns the balances for an account for the requested asset types. + * + * @param snapId - ID of the Snap. + * @param id - ID of the account. + * @param assets - List of CAIP-19 fungible asset types to fetch balances for. + * @returns A promise resolving to a map of asset type to balance. + */ +export type SnapAccountServiceGetAccountBalancesAction = { + type: `SnapAccountService:getAccountBalances`; + handler: SnapAccountService['getAccountBalances']; +}; + +/** + * Returns a page of transactions for an account. + * + * @param snapId - ID of the Snap. + * @param id - ID of the account. + * @param pagination - Pagination options. + * @returns A promise resolving to a page of transactions. + */ +export type SnapAccountServiceGetAccountTransactionsAction = { + type: `SnapAccountService:getAccountTransactions`; + handler: SnapAccountService['getAccountTransactions']; +}; + +/** + * Resolves the account address to use for routing a signing request. + * + * @param snapId - ID of the Snap. + * @param scope - CAIP-2 chain ID of the signing request. + * @param request - The signing JSON-RPC request. + * @returns A promise resolving to the resolved address, or `null` if the + * Snap cannot determine an address for this request. + */ +export type SnapAccountServiceResolveAccountAddressAction = { + type: `SnapAccountService:resolveAccountAddress`; + handler: SnapAccountService['resolveAccountAddress']; +}; + /** * Union of all SnapAccountService action types. */ @@ -69,4 +121,8 @@ export type SnapAccountServiceMethodActions = | SnapAccountServiceGetSnapsAction | SnapAccountServiceEnsureReadyAction | SnapAccountServiceEnsureMigratedAction - | SnapAccountServiceHandleKeyringSnapMessageAction; + | SnapAccountServiceGetAccountAssetsAction + | SnapAccountServiceGetAccountBalancesAction + | SnapAccountServiceGetAccountTransactionsAction + | SnapAccountServiceHandleKeyringSnapMessageAction + | SnapAccountServiceResolveAccountAddressAction; diff --git a/packages/snap-account-service/src/SnapAccountService.test.ts b/packages/snap-account-service/src/SnapAccountService.test.ts index 4beae87549..7e85b42ad5 100644 --- a/packages/snap-account-service/src/SnapAccountService.test.ts +++ b/packages/snap-account-service/src/SnapAccountService.test.ts @@ -6,9 +6,17 @@ import type { AccountAssetListUpdatedEventPayload, AccountBalancesUpdatedEventPayload, AccountTransactionsUpdatedEventPayload, + Balance, + CaipAssetType, + CaipAssetTypeOrId, + CaipChainId, + Pagination, + ResolvedAccountAddress, + TransactionsPage, } from '@metamask/keyring-api'; import { KeyringEvent } from '@metamask/keyring-api'; import { KeyringType } from '@metamask/keyring-api/v2'; +import { KeyringInternalSnapClient } from '@metamask/keyring-internal-snap-client/v2'; import { KeyringControllerError, KeyringControllerErrorMessage, @@ -37,6 +45,8 @@ import type { import { SnapAccountService } from './SnapAccountService'; import type { AccountGroupObject } from './types'; +jest.mock('@metamask/keyring-internal-snap-client/v2'); + type RootMessenger = Messenger< MockAnyNamespace, MessengerActions, @@ -57,6 +67,7 @@ type Mocks = { SnapController: { getState: jest.MockedFunction<() => SnapControllerState>; getRunnableSnaps: jest.MockedFunction<() => TruncatedSnap[]>; + handleRequest: jest.Mock; }; // eslint-disable-next-line @typescript-eslint/naming-convention KeyringController: { @@ -103,6 +114,7 @@ function getMessenger( 'SnapController:getState', 'SnapController:getSnap', 'SnapController:getRunnableSnaps', + 'SnapController:handleRequest', 'KeyringController:getState', 'KeyringController:withController', 'KeyringController:withKeyringV2', @@ -406,6 +418,7 @@ async function setup({ .fn() .mockReturnValue({ isReady: snapIsReady } as SnapControllerState), getRunnableSnaps: jest.fn().mockReturnValue(runnableSnaps), + handleRequest: jest.fn(), }, KeyringController: { getState: jest.fn().mockReturnValue({ keyrings: [] }), @@ -427,6 +440,10 @@ async function setup({ 'SnapController:getRunnableSnaps', mocks.SnapController.getRunnableSnaps, ); + rootMessenger.registerActionHandler( + 'SnapController:handleRequest', + mocks.SnapController.handleRequest, + ); rootMessenger.registerActionHandler( 'KeyringController:getState', mocks.KeyringController.getState, @@ -460,6 +477,35 @@ async function setup({ const MOCK_SNAP_ID = 'npm:@metamask/mock-snap' as SnapId; const MOCK_OTHER_SNAP_ID = 'npm:@metamask/other-snap' as SnapId; +/** + * Configures `KeyringInternalSnapClient` (mocked via `jest.mock`) so that + * `withSnapId` returns an object whose methods can be controlled per-test. + * + * @returns The per-snap client methods as individual jest fns. + */ +function buildKeyringClientMock(): { + getAccountAssets: jest.Mock; + getAccountBalances: jest.Mock; + getAccountTransactions: jest.Mock; + resolveAccountAddress: jest.Mock; +} { + const clientMethods = { + getAccountAssets: jest.fn(), + getAccountBalances: jest.fn(), + getAccountTransactions: jest.fn(), + resolveAccountAddress: jest.fn(), + }; + ( + KeyringInternalSnapClient as jest.MockedClass + ).mockImplementation( + () => + ({ + withSnapId: jest.fn().mockReturnValue(clientMethods), + }) as unknown as KeyringInternalSnapClient, + ); + return clientMethods; +} + describe('SnapAccountService', () => { describe('getSnaps', () => { it('exposes tracked Snaps seeded during construction', async () => { @@ -1526,4 +1572,247 @@ describe('SnapAccountService', () => { expect(service).toBeDefined(); }); }); + + describe('getAccountAssets', () => { + const MOCK_ACCOUNT_ID = '00000000-0000-4000-8000-000000000001'; + const MOCK_ASSETS: CaipAssetTypeOrId[] = [ + 'eip155:1/slip44:60', + 'eip155:1/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + ]; + + it('returns the list of assets from the client', async () => { + const clientMethods = buildKeyringClientMock(); + const { service, mocks } = await setup({ + runnableSnaps: [buildSnap(MOCK_SNAP_ID)], + }); + clientMethods.getAccountAssets.mockResolvedValue(MOCK_ASSETS); + await triggerMigration(service, mocks); + + const result = await service.getAccountAssets(MOCK_SNAP_ID, MOCK_ACCOUNT_ID); + + expect(clientMethods.getAccountAssets).toHaveBeenCalledWith(MOCK_ACCOUNT_ID); + expect(result).toStrictEqual(MOCK_ASSETS); + }); + + it('calls ensureReady before delegating to the client', async () => { + buildKeyringClientMock(); + const { service } = await setup(); + + await expect( + service.getAccountAssets(MOCK_SNAP_ID, MOCK_ACCOUNT_ID), + ).rejects.toThrow(`Unknown snap: "${MOCK_SNAP_ID}"`); + }); + + it('is exposed as a messenger action', async () => { + const clientMethods = buildKeyringClientMock(); + const { service, messenger, mocks } = await setup({ + runnableSnaps: [buildSnap(MOCK_SNAP_ID)], + }); + clientMethods.getAccountAssets.mockResolvedValue(MOCK_ASSETS); + await triggerMigration(service, mocks); + + expect(service).toBeDefined(); + const result = await messenger.call( + 'SnapAccountService:getAccountAssets', + MOCK_SNAP_ID, + MOCK_ACCOUNT_ID, + ); + + expect(result).toStrictEqual(MOCK_ASSETS); + }); + }); + + describe('getAccountBalances', () => { + const MOCK_ACCOUNT_ID = '00000000-0000-4000-8000-000000000001'; + const MOCK_ASSET_TYPES: CaipAssetType[] = ['eip155:1/slip44:60']; + const MOCK_BALANCES: Record = { + 'eip155:1/slip44:60': { amount: '1.5', unit: 'ETH' }, + }; + + it('returns the balances from the client', async () => { + const clientMethods = buildKeyringClientMock(); + const { service, mocks } = await setup({ + runnableSnaps: [buildSnap(MOCK_SNAP_ID)], + }); + clientMethods.getAccountBalances.mockResolvedValue(MOCK_BALANCES); + await triggerMigration(service, mocks); + + const result = await service.getAccountBalances( + MOCK_SNAP_ID, + MOCK_ACCOUNT_ID, + MOCK_ASSET_TYPES, + ); + + expect(clientMethods.getAccountBalances).toHaveBeenCalledWith( + MOCK_ACCOUNT_ID, + MOCK_ASSET_TYPES, + ); + expect(result).toStrictEqual(MOCK_BALANCES); + }); + + it('calls ensureReady before delegating to the client', async () => { + buildKeyringClientMock(); + const { service } = await setup(); + + await expect( + service.getAccountBalances(MOCK_SNAP_ID, MOCK_ACCOUNT_ID, MOCK_ASSET_TYPES), + ).rejects.toThrow(`Unknown snap: "${MOCK_SNAP_ID}"`); + }); + + it('is exposed as a messenger action', async () => { + const clientMethods = buildKeyringClientMock(); + const { service, messenger, mocks } = await setup({ + runnableSnaps: [buildSnap(MOCK_SNAP_ID)], + }); + clientMethods.getAccountBalances.mockResolvedValue(MOCK_BALANCES); + await triggerMigration(service, mocks); + + expect(service).toBeDefined(); + const result = await messenger.call( + 'SnapAccountService:getAccountBalances', + MOCK_SNAP_ID, + MOCK_ACCOUNT_ID, + MOCK_ASSET_TYPES, + ); + + expect(result).toStrictEqual(MOCK_BALANCES); + }); + }); + + describe('getAccountTransactions', () => { + const MOCK_ACCOUNT_ID = '00000000-0000-4000-8000-000000000001'; + const MOCK_PAGINATION: Pagination = { limit: 10, next: null }; + const MOCK_TRANSACTIONS_PAGE: TransactionsPage = { + data: [], + next: null, + }; + + it('returns the transactions page from the client', async () => { + const clientMethods = buildKeyringClientMock(); + const { service, mocks } = await setup({ + runnableSnaps: [buildSnap(MOCK_SNAP_ID)], + }); + clientMethods.getAccountTransactions.mockResolvedValue(MOCK_TRANSACTIONS_PAGE); + await triggerMigration(service, mocks); + + const result = await service.getAccountTransactions( + MOCK_SNAP_ID, + MOCK_ACCOUNT_ID, + MOCK_PAGINATION, + ); + + expect(clientMethods.getAccountTransactions).toHaveBeenCalledWith( + MOCK_ACCOUNT_ID, + MOCK_PAGINATION, + ); + expect(result).toStrictEqual(MOCK_TRANSACTIONS_PAGE); + }); + + it('calls ensureReady before delegating to the client', async () => { + buildKeyringClientMock(); + const { service } = await setup(); + + await expect( + service.getAccountTransactions(MOCK_SNAP_ID, MOCK_ACCOUNT_ID, MOCK_PAGINATION), + ).rejects.toThrow(`Unknown snap: "${MOCK_SNAP_ID}"`); + }); + + it('is exposed as a messenger action', async () => { + const clientMethods = buildKeyringClientMock(); + const { service, messenger, mocks } = await setup({ + runnableSnaps: [buildSnap(MOCK_SNAP_ID)], + }); + clientMethods.getAccountTransactions.mockResolvedValue(MOCK_TRANSACTIONS_PAGE); + await triggerMigration(service, mocks); + + expect(service).toBeDefined(); + const result = await messenger.call( + 'SnapAccountService:getAccountTransactions', + MOCK_SNAP_ID, + MOCK_ACCOUNT_ID, + MOCK_PAGINATION, + ); + + expect(result).toStrictEqual(MOCK_TRANSACTIONS_PAGE); + }); + }); + + describe('resolveAccountAddress', () => { + const MOCK_SCOPE = 'eip155:1' as CaipChainId; + const MOCK_REQUEST = { + jsonrpc: '2.0' as const, + id: 1, + method: 'eth_signTypedData_v4', + params: [], + }; + const MOCK_RESOLVED: ResolvedAccountAddress = { + address: 'eip155:1:0xabcdef1234567890abcdef1234567890abcdef12', + }; + + it('returns the resolved address from the client', async () => { + const clientMethods = buildKeyringClientMock(); + const { service, mocks } = await setup({ + runnableSnaps: [buildSnap(MOCK_SNAP_ID)], + }); + clientMethods.resolveAccountAddress.mockResolvedValue(MOCK_RESOLVED); + await triggerMigration(service, mocks); + + const result = await service.resolveAccountAddress( + MOCK_SNAP_ID, + MOCK_SCOPE, + MOCK_REQUEST, + ); + + expect(clientMethods.resolveAccountAddress).toHaveBeenCalledWith( + MOCK_SCOPE, + MOCK_REQUEST, + ); + expect(result).toStrictEqual(MOCK_RESOLVED); + }); + + it('returns null when the Snap cannot determine an address', async () => { + const clientMethods = buildKeyringClientMock(); + const { service, mocks } = await setup({ + runnableSnaps: [buildSnap(MOCK_SNAP_ID)], + }); + clientMethods.resolveAccountAddress.mockResolvedValue(null); + await triggerMigration(service, mocks); + + const result = await service.resolveAccountAddress( + MOCK_SNAP_ID, + MOCK_SCOPE, + MOCK_REQUEST, + ); + + expect(result).toBeNull(); + }); + + it('calls ensureReady before delegating to the client', async () => { + buildKeyringClientMock(); + const { service } = await setup(); + + await expect( + service.resolveAccountAddress(MOCK_SNAP_ID, MOCK_SCOPE, MOCK_REQUEST), + ).rejects.toThrow(`Unknown snap: "${MOCK_SNAP_ID}"`); + }); + + it('is exposed as a messenger action', async () => { + const clientMethods = buildKeyringClientMock(); + const { service, messenger, mocks } = await setup({ + runnableSnaps: [buildSnap(MOCK_SNAP_ID)], + }); + clientMethods.resolveAccountAddress.mockResolvedValue(MOCK_RESOLVED); + await triggerMigration(service, mocks); + + expect(service).toBeDefined(); + const result = await messenger.call( + 'SnapAccountService:resolveAccountAddress', + MOCK_SNAP_ID, + MOCK_SCOPE, + MOCK_REQUEST, + ); + + expect(result).toStrictEqual(MOCK_RESOLVED); + }); + }); }); diff --git a/packages/snap-account-service/src/SnapAccountService.ts b/packages/snap-account-service/src/SnapAccountService.ts index d3e369ad23..898ddd572a 100644 --- a/packages/snap-account-service/src/SnapAccountService.ts +++ b/packages/snap-account-service/src/SnapAccountService.ts @@ -12,6 +12,13 @@ import type { AccountAssetListUpdatedEventPayload, AccountBalancesUpdatedEventPayload, AccountTransactionsUpdatedEventPayload, + Balance, + CaipAssetType, + CaipAssetTypeOrId, + CaipChainId, + Pagination, + ResolvedAccountAddress, + TransactionsPage, } from '@metamask/keyring-api'; import { AccountAssetListUpdatedEventStruct, @@ -32,13 +39,15 @@ import { isKeyringNotFoundError, KeyringTypes, } from '@metamask/keyring-controller'; +import { KeyringInternalSnapClient } from '@metamask/keyring-internal-snap-client/v2'; import { SnapManageAccountsMethod } from '@metamask/keyring-snap-sdk'; -import type { AccountId, BaseKeyring } from '@metamask/keyring-utils'; +import type { AccountId, BaseKeyring, JsonRpcRequest } from '@metamask/keyring-utils'; import type { Messenger } from '@metamask/messenger'; import type { SnapControllerGetRunnableSnapsAction, SnapControllerGetSnapAction, SnapControllerGetStateAction, + SnapControllerHandleRequestAction, SnapControllerSnapBlockedEvent, SnapControllerSnapDisabledEvent, SnapControllerSnapEnabledEvent, @@ -55,8 +64,12 @@ import { projectLogger as log } from './logger'; import type { SnapAccountServiceEnsureReadyAction, SnapAccountServiceEnsureMigratedAction, + SnapAccountServiceGetAccountAssetsAction, + SnapAccountServiceGetAccountBalancesAction, + SnapAccountServiceGetAccountTransactionsAction, SnapAccountServiceGetSnapsAction, SnapAccountServiceHandleKeyringSnapMessageAction, + SnapAccountServiceResolveAccountAddressAction, } from './SnapAccountService-method-action-types'; import { SnapPlatformWatcher } from './SnapPlatformWatcher'; import type { SnapPlatformWatcherConfig } from './SnapPlatformWatcher'; @@ -84,8 +97,12 @@ export const serviceName = 'SnapAccountService'; const MESSENGER_EXPOSED_METHODS = [ 'ensureMigrated', 'ensureReady', + 'getAccountAssets', + 'getAccountBalances', + 'getAccountTransactions', 'getSnaps', 'handleKeyringSnapMessage', + 'resolveAccountAddress', ] as const; /** @@ -94,8 +111,12 @@ const MESSENGER_EXPOSED_METHODS = [ export type SnapAccountServiceActions = | SnapAccountServiceEnsureMigratedAction | SnapAccountServiceEnsureReadyAction + | SnapAccountServiceGetAccountAssetsAction + | SnapAccountServiceGetAccountBalancesAction + | SnapAccountServiceGetAccountTransactionsAction | SnapAccountServiceGetSnapsAction - | SnapAccountServiceHandleKeyringSnapMessageAction; + | SnapAccountServiceHandleKeyringSnapMessageAction + | SnapAccountServiceResolveAccountAddressAction; /** * Actions from other messengers that {@link SnapAccountService} calls. @@ -104,6 +125,7 @@ type AllowedActions = | SnapControllerGetStateAction | SnapControllerGetSnapAction | SnapControllerGetRunnableSnapsAction + | SnapControllerHandleRequestAction | KeyringControllerGetStateAction | KeyringControllerWithControllerAction | KeyringControllerWithKeyringV2Action @@ -231,6 +253,8 @@ export class SnapAccountService { readonly #tracker: SnapTracker; + readonly #client: KeyringInternalSnapClient; + #migrated = false; #migratePromise: Promise | null = null; @@ -250,6 +274,12 @@ export class SnapAccountService { config?.snapPlatformWatcher, ); this.#tracker = new SnapTracker(messenger); + this.#client = new KeyringInternalSnapClient({ + messenger: messenger.buildChild({ + namespace: 'KeyringInternalSnapClient', + actions: ['SnapController:handleRequest'], + }), + }); this.#messenger.registerMethodActionHandlers( this, @@ -562,6 +592,73 @@ export class SnapAccountService { ); } + /** + * Returns the CAIP-19 asset type/ID list supported by an account. + * + * @param snapId - ID of the Snap. + * @param id - ID of the account. + * @returns A promise resolving to the list of supported CAIP-19 asset type/IDs. + */ + async getAccountAssets( + snapId: SnapId, + id: AccountId, + ): Promise { + await this.ensureReady(snapId); + return this.#client.withSnapId(snapId).getAccountAssets(id); + } + + /** + * Returns the balances for an account for the requested asset types. + * + * @param snapId - ID of the Snap. + * @param id - ID of the account. + * @param assets - List of CAIP-19 fungible asset types to fetch balances for. + * @returns A promise resolving to a map of asset type to balance. + */ + async getAccountBalances( + snapId: SnapId, + id: AccountId, + assets: CaipAssetType[], + ): Promise> { + await this.ensureReady(snapId); + return this.#client.withSnapId(snapId).getAccountBalances(id, assets); + } + + /** + * Returns a page of transactions for an account. + * + * @param snapId - ID of the Snap. + * @param id - ID of the account. + * @param pagination - Pagination options. + * @returns A promise resolving to a page of transactions. + */ + async getAccountTransactions( + snapId: SnapId, + id: AccountId, + pagination: Pagination, + ): Promise { + await this.ensureReady(snapId); + return this.#client.withSnapId(snapId).getAccountTransactions(id, pagination); + } + + /** + * Resolves the account address to use for routing a signing request. + * + * @param snapId - ID of the Snap. + * @param scope - CAIP-2 chain ID of the signing request. + * @param request - The signing JSON-RPC request. + * @returns A promise resolving to the resolved address, or `null` if the + * Snap cannot determine an address for this request. + */ + async resolveAccountAddress( + snapId: SnapId, + scope: CaipChainId, + request: JsonRpcRequest, + ): Promise { + await this.ensureReady(snapId); + return this.#client.withSnapId(snapId).resolveAccountAddress(scope, request); + } + /** * Handle a message from a Snap. * diff --git a/packages/snap-account-service/src/index.ts b/packages/snap-account-service/src/index.ts index 6cda8e7a67..14b7f293a9 100644 --- a/packages/snap-account-service/src/index.ts +++ b/packages/snap-account-service/src/index.ts @@ -12,8 +12,12 @@ export type { export type { SnapAccountServiceEnsureMigratedAction, SnapAccountServiceEnsureReadyAction, + SnapAccountServiceGetAccountAssetsAction, + SnapAccountServiceGetAccountBalancesAction, + SnapAccountServiceGetAccountTransactionsAction, SnapAccountServiceGetSnapsAction, SnapAccountServiceHandleKeyringSnapMessageAction, + SnapAccountServiceResolveAccountAddressAction, } from './SnapAccountService-method-action-types'; export { SnapPlatformWatcher } from './SnapPlatformWatcher'; export type { SnapPlatformWatcherConfig } from './SnapPlatformWatcher'; diff --git a/yarn.lock b/yarn.lock index 441a3c1380..f2c5d46eb1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8531,6 +8531,7 @@ __metadata: "@metamask/eth-snap-keyring": "npm:^22.3.0" "@metamask/keyring-api": "npm:^23.3.0" "@metamask/keyring-controller": "npm:^27.1.0" + "@metamask/keyring-internal-snap-client": "npm:^10.0.4" "@metamask/keyring-snap-sdk": "npm:^9.0.2" "@metamask/keyring-utils": "npm:^3.3.1" "@metamask/messenger": "npm:^2.0.0" From 3bbbc62e3e6ca21acf1da4c32f5c3ed717044e30 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Mon, 6 Jul 2026 13:22:39 +0200 Subject: [PATCH 03/24] chore: changelog --- packages/snap-account-service/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/snap-account-service/CHANGELOG.md b/packages/snap-account-service/CHANGELOG.md index a6cf33b721..fa4422e148 100644 --- a/packages/snap-account-service/CHANGELOG.md +++ b/packages/snap-account-service/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Add `SnapAccountService:getAccountAssets`, `SnapAccountService:getAccountBalances`, `SnapAccountService:getAccountTransactions`, and `SnapAccountService:resolveAccountAddress` messenger actions ([#XXXX](https://github.com/MetaMask/core/pull/XXXX)) +- **BREAKING:** Add `SnapAccountService:getAccount{Assets,Balances,Transactions}` and `SnapAccountService:resolveAccountAddress` messenger actions ([#9390](https://github.com/MetaMask/core/pull/9390)) - These actions proxy the corresponding `KeyringClient` (v2) methods, with `snapId` as the first parameter to identify the target Snap. - Each call goes through `ensureReady` to guarantee the Snap is ready before the request is sent. - The service messenger now additionally requires `SnapController:handleRequest`. From 3914b6f3f5cabbd305bae014255b7a80b7062410 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Mon, 6 Jul 2026 13:35:13 +0200 Subject: [PATCH 04/24] chore: dedupe --- yarn.lock | 58 +++---------------------------------------------------- 1 file changed, 3 insertions(+), 55 deletions(-) diff --git a/yarn.lock b/yarn.lock index f2c5d46eb1..93a786a642 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8623,21 +8623,7 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-sdk@npm:^11.0.0, @metamask/snaps-sdk@npm:^11.1.0": - version: 11.1.0 - resolution: "@metamask/snaps-sdk@npm:11.1.0" - dependencies: - "@metamask/key-tree": "npm:^10.1.1" - "@metamask/providers": "npm:^22.1.1" - "@metamask/rpc-errors": "npm:^7.0.3" - "@metamask/superstruct": "npm:^3.2.1" - "@metamask/utils": "npm:^11.11.0" - luxon: "npm:^3.5.0" - checksum: 10/138c616584d537b9976ae48123090ab5731848d79d5d1f4e979c797dfdfe061329cbf18a5e84d8bd068fe36d5b9d169337f6d74efab0736f30c31ddf4088f70b - languageName: node - linkType: hard - -"@metamask/snaps-sdk@npm:^11.1.1": +"@metamask/snaps-sdk@npm:^11.0.0, @metamask/snaps-sdk@npm:^11.1.0, @metamask/snaps-sdk@npm:^11.1.1": version: 11.1.1 resolution: "@metamask/snaps-sdk@npm:11.1.1" dependencies: @@ -8651,38 +8637,7 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-utils@npm:^12.1.2, @metamask/snaps-utils@npm:^12.1.3, @metamask/snaps-utils@npm:^12.2.0": - version: 12.2.0 - resolution: "@metamask/snaps-utils@npm:12.2.0" - dependencies: - "@babel/core": "npm:^7.23.2" - "@babel/types": "npm:^7.23.0" - "@metamask/key-tree": "npm:^10.1.1" - "@metamask/messenger": "npm:^1.1.1" - "@metamask/permission-controller": "npm:^12.3.0" - "@metamask/rpc-errors": "npm:^7.0.3" - "@metamask/slip44": "npm:^4.4.0" - "@metamask/snaps-registry": "npm:^4.0.0" - "@metamask/snaps-sdk": "npm:^11.1.0" - "@metamask/superstruct": "npm:^3.2.1" - "@metamask/utils": "npm:^11.11.0" - "@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:^5.5.6" - luxon: "npm:^3.5.0" - marked: "npm:^12.0.1" - rfdc: "npm:^1.3.0" - semver: "npm:^7.5.4" - ses: "npm:^1.15.0" - validate-npm-package-name: "npm:^5.0.0" - checksum: 10/0e7cb5a4deebad3dc98404486b7767be049e9f86173195e9b1ae577f197e67cdd392f1e05ebb146a199ffc3cd52013636637a4d1ac7f9fec164f3189be97df55 - languageName: node - linkType: hard - -"@metamask/snaps-utils@npm:^12.2.1": +"@metamask/snaps-utils@npm:^12.1.2, @metamask/snaps-utils@npm:^12.1.3, @metamask/snaps-utils@npm:^12.2.0, @metamask/snaps-utils@npm:^12.2.1": version: 12.2.1 resolution: "@metamask/snaps-utils@npm:12.2.1" dependencies: @@ -8824,14 +8779,7 @@ __metadata: languageName: unknown linkType: soft -"@metamask/superstruct@npm:^3.1.0, @metamask/superstruct@npm:^3.2.1": - version: 3.2.1 - resolution: "@metamask/superstruct@npm:3.2.1" - checksum: 10/9e29380f2cf8b129283ccb2b568296d92682b705109ba62dbd7739ffd6a1982fe38c7228cdcf3cbee94dbcdd5fcc1c846ab9d1dd3582167154f914422fcff547 - languageName: node - linkType: hard - -"@metamask/superstruct@npm:^3.3.0": +"@metamask/superstruct@npm:^3.1.0, @metamask/superstruct@npm:^3.2.1, @metamask/superstruct@npm:^3.3.0": version: 3.3.0 resolution: "@metamask/superstruct@npm:3.3.0" checksum: 10/664d5e330484a86420bc004b1c7f8301e1501cce712f611fe657176b2979edbd7cc6f4c77c8b1610486a685ebbd0b72dacf4bd6cf143d6b52038069d2f4e84ab From 03ba9a8e45041fa1f2d15aed4384048b041778d8 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Mon, 6 Jul 2026 14:39:26 +0200 Subject: [PATCH 05/24] fix: use handleKeyringSnapMessage for v1 only --- .../src/SnapAccountService.test.ts | 23 +++++++++++++++---- .../src/SnapAccountService.ts | 16 ++++++++++--- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/packages/snap-account-service/src/SnapAccountService.test.ts b/packages/snap-account-service/src/SnapAccountService.test.ts index 7e85b42ad5..e47c72c6c1 100644 --- a/packages/snap-account-service/src/SnapAccountService.test.ts +++ b/packages/snap-account-service/src/SnapAccountService.test.ts @@ -835,13 +835,13 @@ describe('SnapAccountService', () => { '00000000-0000-0000-0000-000000000002', ]; - it('forwards the message to the matching v2 Snap keyring and returns its result', async () => { + it('forwards the message to the v1 layer of the Snap keyring and returns its result', async () => { const { service, mocks } = await setup(); const handleKeyringSnapMessage = jest .fn() .mockResolvedValue({ ok: true }); mockWithKeyringV2Unsafe(mocks, { - [MOCK_SNAP_ID]: { handleKeyringSnapMessage }, + [MOCK_SNAP_ID]: { v1: { handleKeyringSnapMessage } }, }); const result = await service.handleKeyringSnapMessage( @@ -853,6 +853,19 @@ describe('SnapAccountService', () => { expect(result).toStrictEqual({ ok: true }); }); + it('throws when the keyring is v2-only (no v1 layer)', async () => { + const { service, mocks } = await setup(); + mockWithKeyringV2Unsafe(mocks, { + [MOCK_SNAP_ID]: { v1: undefined }, + }); + + await expect( + service.handleKeyringSnapMessage(MOCK_SNAP_ID, MOCK_MESSAGE), + ).rejects.toThrow( + `Cannot delegate keyring Snap message, keyring for Snap "${MOCK_SNAP_ID}" is v2, not v1.`, + ); + }); + it('short-circuits GetSelectedAccounts by returning only the selected group accounts the Snap actually owns', async () => { const { service, mocks } = await setup(); mocks.AccountTreeController.getSelectedAccountGroup.mockReturnValue( @@ -949,7 +962,7 @@ describe('SnapAccountService', () => { const error = new Error('snap boom'); const handleKeyringSnapMessage = jest.fn().mockRejectedValue(error); mockWithKeyringV2Unsafe(mocks, { - [MOCK_SNAP_ID]: { handleKeyringSnapMessage }, + [MOCK_SNAP_ID]: { v1: { handleKeyringSnapMessage } }, }); await expect( @@ -966,7 +979,7 @@ describe('SnapAccountService', () => { const { addNewKeyring } = mockWithController(mocks, []); const handleKeyringSnapMessage = jest.fn().mockResolvedValue(null); mockWithKeyringV2Unsafe(mocks, { - [MOCK_SNAP_ID]: { handleKeyringSnapMessage }, + [MOCK_SNAP_ID]: { v1: { handleKeyringSnapMessage } }, }); await service.ensureMigrated(); @@ -988,7 +1001,7 @@ describe('SnapAccountService', () => { const { service, mocks, messenger } = await setup(); const handleKeyringSnapMessage = jest.fn().mockResolvedValue('pong'); mockWithKeyringV2Unsafe(mocks, { - [MOCK_SNAP_ID]: { handleKeyringSnapMessage }, + [MOCK_SNAP_ID]: { v1: { handleKeyringSnapMessage } }, }); // Reference `service` so it isn't flagged as unused; constructing it diff --git a/packages/snap-account-service/src/SnapAccountService.ts b/packages/snap-account-service/src/SnapAccountService.ts index 898ddd572a..88d55141a4 100644 --- a/packages/snap-account-service/src/SnapAccountService.ts +++ b/packages/snap-account-service/src/SnapAccountService.ts @@ -720,9 +720,19 @@ export class SnapAccountService { // 1: withKeyring(..., ({ keyring }) => { keyring.removeAccount(...) }) // 2. removeAccount(...) -> handleKeyringSnapMessage(..., { method: 'accountRemoved', ... }) // 3. handleKeyringSnapMessage tries to acquire the same lock again via withKeyringV2 -> deadlock. - return await this.#withKeyringV2Unsafe(snapId, async (keyring) => - keyring.handleKeyringSnapMessage(message), - ); + return await this.#withKeyringV2Unsafe(snapId, async (keyring) => { + if (!keyring.v1) { + log( + `Received message "${event}" for Snap "${snapId}", but that's a v2 keyring... Rejecting.`, + ); + + throw new Error( + `Cannot delegate keyring Snap message, keyring for Snap "${snapId}" is v2, not v1.`, + ); + } + + return await keyring.v1.handleKeyringSnapMessage(message); + }); } catch (error) { if (isKeyringNotFoundError(error)) { log( From b81379b6aa92629629769878c1e81125322f4dba Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Mon, 6 Jul 2026 14:44:45 +0200 Subject: [PATCH 06/24] chore: lint --- package.json | 8 ++--- .../src/SnapAccountService.test.ts | 35 ++++++++++++++----- .../src/SnapAccountService.ts | 14 ++++++-- 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 13b6730a52..6c72bed149 100644 --- a/package.json +++ b/package.json @@ -102,15 +102,15 @@ "yargs": "^17.7.2" }, "resolutions": { - "elliptic@6.5.4": "^6.5.7", - "fast-xml-parser@^4.3.4": "^4.4.1", - "ws@7.4.6": "^7.5.10", + "@metamask/eth-snap-keyring": "npm:@metamask-previews/eth-snap-keyring@22.4.0-5951828", "@metamask/keyring-api": "npm:@metamask-previews/keyring-api@23.4.0-5951828", "@metamask/keyring-internal-api": "npm:@metamask-previews/keyring-internal-api@11.0.1-5951828", "@metamask/keyring-internal-snap-client": "npm:@metamask-previews/keyring-internal-snap-client@10.0.4-5951828", "@metamask/keyring-snap-client": "npm:@metamask-previews/keyring-snap-client@9.1.0-5951828", "@metamask/keyring-snap-sdk": "npm:@metamask-previews/keyring-snap-sdk@9.1.0-5951828", - "@metamask/eth-snap-keyring": "npm:@metamask-previews/eth-snap-keyring@22.4.0-5951828" + "elliptic@6.5.4": "^6.5.7", + "fast-xml-parser@^4.3.4": "^4.4.1", + "ws@7.4.6": "^7.5.10" }, "simple-git-hooks": { "pre-push": "yarn lint" diff --git a/packages/snap-account-service/src/SnapAccountService.test.ts b/packages/snap-account-service/src/SnapAccountService.test.ts index e47c72c6c1..eab333ceb0 100644 --- a/packages/snap-account-service/src/SnapAccountService.test.ts +++ b/packages/snap-account-service/src/SnapAccountService.test.ts @@ -16,7 +16,6 @@ import type { } from '@metamask/keyring-api'; import { KeyringEvent } from '@metamask/keyring-api'; import { KeyringType } from '@metamask/keyring-api/v2'; -import { KeyringInternalSnapClient } from '@metamask/keyring-internal-snap-client/v2'; import { KeyringControllerError, KeyringControllerErrorMessage, @@ -27,6 +26,7 @@ import type { KeyringSelectorV2, RestrictedController, } from '@metamask/keyring-controller'; +import { KeyringInternalSnapClient } from '@metamask/keyring-internal-snap-client/v2'; import { SnapManageAccountsMethod } from '@metamask/keyring-snap-sdk'; import { Messenger, MOCK_ANY_NAMESPACE } from '@metamask/messenger'; import type { @@ -496,7 +496,9 @@ function buildKeyringClientMock(): { resolveAccountAddress: jest.fn(), }; ( - KeyringInternalSnapClient as jest.MockedClass + KeyringInternalSnapClient as jest.MockedClass< + typeof KeyringInternalSnapClient + > ).mockImplementation( () => ({ @@ -1601,9 +1603,14 @@ describe('SnapAccountService', () => { clientMethods.getAccountAssets.mockResolvedValue(MOCK_ASSETS); await triggerMigration(service, mocks); - const result = await service.getAccountAssets(MOCK_SNAP_ID, MOCK_ACCOUNT_ID); + const result = await service.getAccountAssets( + MOCK_SNAP_ID, + MOCK_ACCOUNT_ID, + ); - expect(clientMethods.getAccountAssets).toHaveBeenCalledWith(MOCK_ACCOUNT_ID); + expect(clientMethods.getAccountAssets).toHaveBeenCalledWith( + MOCK_ACCOUNT_ID, + ); expect(result).toStrictEqual(MOCK_ASSETS); }); @@ -1668,7 +1675,11 @@ describe('SnapAccountService', () => { const { service } = await setup(); await expect( - service.getAccountBalances(MOCK_SNAP_ID, MOCK_ACCOUNT_ID, MOCK_ASSET_TYPES), + service.getAccountBalances( + MOCK_SNAP_ID, + MOCK_ACCOUNT_ID, + MOCK_ASSET_TYPES, + ), ).rejects.toThrow(`Unknown snap: "${MOCK_SNAP_ID}"`); }); @@ -1705,7 +1716,9 @@ describe('SnapAccountService', () => { const { service, mocks } = await setup({ runnableSnaps: [buildSnap(MOCK_SNAP_ID)], }); - clientMethods.getAccountTransactions.mockResolvedValue(MOCK_TRANSACTIONS_PAGE); + clientMethods.getAccountTransactions.mockResolvedValue( + MOCK_TRANSACTIONS_PAGE, + ); await triggerMigration(service, mocks); const result = await service.getAccountTransactions( @@ -1726,7 +1739,11 @@ describe('SnapAccountService', () => { const { service } = await setup(); await expect( - service.getAccountTransactions(MOCK_SNAP_ID, MOCK_ACCOUNT_ID, MOCK_PAGINATION), + service.getAccountTransactions( + MOCK_SNAP_ID, + MOCK_ACCOUNT_ID, + MOCK_PAGINATION, + ), ).rejects.toThrow(`Unknown snap: "${MOCK_SNAP_ID}"`); }); @@ -1735,7 +1752,9 @@ describe('SnapAccountService', () => { const { service, messenger, mocks } = await setup({ runnableSnaps: [buildSnap(MOCK_SNAP_ID)], }); - clientMethods.getAccountTransactions.mockResolvedValue(MOCK_TRANSACTIONS_PAGE); + clientMethods.getAccountTransactions.mockResolvedValue( + MOCK_TRANSACTIONS_PAGE, + ); await triggerMigration(service, mocks); expect(service).toBeDefined(); diff --git a/packages/snap-account-service/src/SnapAccountService.ts b/packages/snap-account-service/src/SnapAccountService.ts index 88d55141a4..a70492af70 100644 --- a/packages/snap-account-service/src/SnapAccountService.ts +++ b/packages/snap-account-service/src/SnapAccountService.ts @@ -41,7 +41,11 @@ import { } from '@metamask/keyring-controller'; import { KeyringInternalSnapClient } from '@metamask/keyring-internal-snap-client/v2'; import { SnapManageAccountsMethod } from '@metamask/keyring-snap-sdk'; -import type { AccountId, BaseKeyring, JsonRpcRequest } from '@metamask/keyring-utils'; +import type { + AccountId, + BaseKeyring, + JsonRpcRequest, +} from '@metamask/keyring-utils'; import type { Messenger } from '@metamask/messenger'; import type { SnapControllerGetRunnableSnapsAction, @@ -638,7 +642,9 @@ export class SnapAccountService { pagination: Pagination, ): Promise { await this.ensureReady(snapId); - return this.#client.withSnapId(snapId).getAccountTransactions(id, pagination); + return this.#client + .withSnapId(snapId) + .getAccountTransactions(id, pagination); } /** @@ -656,7 +662,9 @@ export class SnapAccountService { request: JsonRpcRequest, ): Promise { await this.ensureReady(snapId); - return this.#client.withSnapId(snapId).resolveAccountAddress(scope, request); + return this.#client + .withSnapId(snapId) + .resolveAccountAddress(scope, request); } /** From 15dd06f904776915cdad02c96491fcafdc5c58b9 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Mon, 6 Jul 2026 15:11:00 +0200 Subject: [PATCH 07/24] refactor: add :setSelectedAccounts action --- packages/snap-account-service/CHANGELOG.md | 3 +- .../SnapAccountService-method-action-types.ts | 18 ++- .../src/SnapAccountService.test.ts | 140 +++++++++++++++--- .../src/SnapAccountService.ts | 59 +++++++- packages/snap-account-service/src/index.ts | 1 + 5 files changed, 196 insertions(+), 25 deletions(-) diff --git a/packages/snap-account-service/CHANGELOG.md b/packages/snap-account-service/CHANGELOG.md index fa4422e148..3bb1ef7ece 100644 --- a/packages/snap-account-service/CHANGELOG.md +++ b/packages/snap-account-service/CHANGELOG.md @@ -9,9 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- **BREAKING:** Add `SnapAccountService:getAccount{Assets,Balances,Transactions}` and `SnapAccountService:resolveAccountAddress` messenger actions ([#9390](https://github.com/MetaMask/core/pull/9390)) +- **BREAKING:** Add `SnapAccountService:getAccount{Assets,Balances,Transactions}`, `SnapAccountService:resolveAccountAddress`, and `SnapAccountService:setSelectedAccounts` messenger actions ([#9390](https://github.com/MetaMask/core/pull/9390)) - These actions proxy the corresponding `KeyringClient` (v2) methods, with `snapId` as the first parameter to identify the target Snap. - Each call goes through `ensureReady` to guarantee the Snap is ready before the request is sent. + - `setSelectedAccounts` routes through the keyring's v1 interface for v1 Snaps, and via the RPC client for v2 Snaps. - The service messenger now additionally requires `SnapController:handleRequest`. ### Changed diff --git a/packages/snap-account-service/src/SnapAccountService-method-action-types.ts b/packages/snap-account-service/src/SnapAccountService-method-action-types.ts index d79f9f6377..0427392e58 100644 --- a/packages/snap-account-service/src/SnapAccountService-method-action-types.ts +++ b/packages/snap-account-service/src/SnapAccountService-method-action-types.ts @@ -114,6 +114,21 @@ export type SnapAccountServiceResolveAccountAddressAction = { handler: SnapAccountService['resolveAccountAddress']; }; +/** + * Notifies a Snap of the currently selected accounts. + * + * For v1 Snaps the call goes through the keyring (signing interface); for + * v2 Snaps it is routed via the RPC client because the keyring only covers + * keyring-only operations (signing, account lifecycle). + * + * @param snapId - ID of the Snap. + * @param accounts - IDs of the accounts to mark as selected. + */ +export type SnapAccountServiceSetSelectedAccountsAction = { + type: `SnapAccountService:setSelectedAccounts`; + handler: SnapAccountService['setSelectedAccounts']; +}; + /** * Union of all SnapAccountService action types. */ @@ -125,4 +140,5 @@ export type SnapAccountServiceMethodActions = | SnapAccountServiceGetAccountBalancesAction | SnapAccountServiceGetAccountTransactionsAction | SnapAccountServiceHandleKeyringSnapMessageAction - | SnapAccountServiceResolveAccountAddressAction; + | SnapAccountServiceResolveAccountAddressAction + | SnapAccountServiceSetSelectedAccountsAction; diff --git a/packages/snap-account-service/src/SnapAccountService.test.ts b/packages/snap-account-service/src/SnapAccountService.test.ts index eab333ceb0..5b204ccb8c 100644 --- a/packages/snap-account-service/src/SnapAccountService.test.ts +++ b/packages/snap-account-service/src/SnapAccountService.test.ts @@ -343,9 +343,13 @@ function mockWithKeyringV2Unsafe( keyrings: Record< string, { - setSelectedAccounts?: jest.Mock; + v1?: + | { + setSelectedAccounts?: jest.Mock; + handleKeyringSnapMessage?: jest.Mock; + } + | undefined; hasAccount?: (id: string) => boolean; - handleKeyringSnapMessage?: jest.Mock; } >, ): void { @@ -488,12 +492,14 @@ function buildKeyringClientMock(): { getAccountBalances: jest.Mock; getAccountTransactions: jest.Mock; resolveAccountAddress: jest.Mock; + setSelectedAccounts: jest.Mock; } { const clientMethods = { getAccountAssets: jest.fn(), getAccountBalances: jest.fn(), getAccountTransactions: jest.fn(), resolveAccountAddress: jest.fn(), + setSelectedAccounts: jest.fn(), }; ( KeyringInternalSnapClient as jest.MockedClass< @@ -1090,11 +1096,11 @@ describe('SnapAccountService', () => { // Snap A owns the first account; Snap B owns the second. mockWithKeyringV2Unsafe(mocks, { [MOCK_SNAP_ID]: { - setSelectedAccounts: setSelectedAccounts1, + v1: { setSelectedAccounts: setSelectedAccounts1 }, hasAccount: (id) => id === MOCK_ACCOUNTS[0], }, [MOCK_OTHER_SNAP_ID]: { - setSelectedAccounts: setSelectedAccounts2, + v1: { setSelectedAccounts: setSelectedAccounts2 }, hasAccount: (id) => id === MOCK_ACCOUNTS[1], }, }); @@ -1120,7 +1126,7 @@ describe('SnapAccountService', () => { const setSelectedAccounts = jest.fn().mockResolvedValue(undefined); mockWithKeyringV2Unsafe(mocks, { [MOCK_SNAP_ID]: { - setSelectedAccounts, + v1: { setSelectedAccounts }, hasAccount: () => false, }, }); @@ -1141,7 +1147,7 @@ describe('SnapAccountService', () => { }); const setSelectedAccounts = jest.fn().mockResolvedValue(undefined); mockWithKeyringV2Unsafe(mocks, { - [MOCK_SNAP_ID]: { setSelectedAccounts }, + [MOCK_SNAP_ID]: { v1: { setSelectedAccounts } }, }); mocks.AccountTreeController.getAccountGroupObject.mockReturnValue( buildGroup(MOCK_GROUP_ID, MOCK_ACCOUNTS), @@ -1196,7 +1202,7 @@ describe('SnapAccountService', () => { runnableSnaps: [buildSnap(MOCK_SNAP_ID)], }); mockWithKeyringV2Unsafe(mocks, { - [MOCK_SNAP_ID]: { setSelectedAccounts: jest.fn() }, + [MOCK_SNAP_ID]: { v1: { setSelectedAccounts: jest.fn() } }, }); await triggerMigration(service, mocks); @@ -1217,7 +1223,7 @@ describe('SnapAccountService', () => { runnableSnaps: [buildSnap(MOCK_SNAP_ID)], }); mockWithKeyringV2Unsafe(mocks, { - [MOCK_SNAP_ID]: { setSelectedAccounts: jest.fn() }, + [MOCK_SNAP_ID]: { v1: { setSelectedAccounts: jest.fn() } }, }); mocks.AccountTreeController.getAccountGroupObject.mockReturnValue( undefined, @@ -1236,6 +1242,29 @@ describe('SnapAccountService', () => { expect(service).toBeDefined(); }); + it('invokes setSelectedAccounts via the client for a v2-only Snap keyring', async () => { + const clientMethods = buildKeyringClientMock(); + const { service, rootMessenger, mocks } = await setup({ + runnableSnaps: [buildSnap(MOCK_SNAP_ID)], + }); + clientMethods.setSelectedAccounts.mockResolvedValue(undefined); + mockWithKeyringV2Unsafe(mocks, { + [MOCK_SNAP_ID]: { v1: undefined }, + }); + mocks.AccountTreeController.getAccountGroupObject.mockReturnValue( + buildGroup(MOCK_GROUP_ID, MOCK_ACCOUNTS), + ); + await triggerMigration(service, mocks); + + publishSelectedAccountGroupChange(rootMessenger, MOCK_GROUP_ID); + await flushMicrotasks(); + + expect(clientMethods.setSelectedAccounts).toHaveBeenCalledWith( + MOCK_ACCOUNTS, + ); + expect(service).toBeDefined(); + }); + it('logs an error when forwarding to a v2 Snap keyring fails, but still forwards to the others', async () => { const { service, rootMessenger, mocks } = await setup({ runnableSnaps: [buildSnap(MOCK_SNAP_ID), buildSnap(MOCK_OTHER_SNAP_ID)], @@ -1244,8 +1273,10 @@ describe('SnapAccountService', () => { const setSelectedAccounts1 = jest.fn().mockRejectedValue(error); const setSelectedAccounts2 = jest.fn().mockResolvedValue(undefined); mockWithKeyringV2Unsafe(mocks, { - [MOCK_SNAP_ID]: { setSelectedAccounts: setSelectedAccounts1 }, - [MOCK_OTHER_SNAP_ID]: { setSelectedAccounts: setSelectedAccounts2 }, + [MOCK_SNAP_ID]: { v1: { setSelectedAccounts: setSelectedAccounts1 } }, + [MOCK_OTHER_SNAP_ID]: { + v1: { setSelectedAccounts: setSelectedAccounts2 }, + }, }); mocks.AccountTreeController.getAccountGroupObject.mockReturnValue( buildGroup(MOCK_GROUP_ID, MOCK_ACCOUNTS), @@ -1275,7 +1306,7 @@ describe('SnapAccountService', () => { const innerError = new Error('inner boom'); mockWithKeyringV2Unsafe(mocks, { [MOCK_SNAP_ID]: { - setSelectedAccounts: jest.fn().mockRejectedValue(innerError), + v1: { setSelectedAccounts: jest.fn().mockRejectedValue(innerError) }, }, }); mocks.AccountTreeController.getAccountGroupObject.mockReturnValue( @@ -1348,7 +1379,7 @@ describe('SnapAccountService', () => { }); const setSelectedAccounts = jest.fn().mockResolvedValue(undefined); mockWithKeyringV2Unsafe(mocks, { - [MOCK_SNAP_ID]: { setSelectedAccounts }, + [MOCK_SNAP_ID]: { v1: { setSelectedAccounts } }, }); mocks.AccountTreeController.getSelectedAccountGroup.mockReturnValue( MOCK_GROUP_ID, @@ -1383,7 +1414,7 @@ describe('SnapAccountService', () => { }); const setSelectedAccounts = jest.fn().mockResolvedValue(undefined); mockWithKeyringV2Unsafe(mocks, { - [MOCK_SNAP_ID]: { setSelectedAccounts }, + [MOCK_SNAP_ID]: { v1: { setSelectedAccounts } }, }); mocks.AccountTreeController.getSelectedAccountGroup.mockReturnValue( MOCK_GROUP_ID, @@ -1410,7 +1441,7 @@ describe('SnapAccountService', () => { runnableSnaps: [buildSnap(MOCK_SNAP_ID)], }); mockWithKeyringV2Unsafe(mocks, { - [MOCK_SNAP_ID]: { setSelectedAccounts: jest.fn() }, + [MOCK_SNAP_ID]: { v1: { setSelectedAccounts: jest.fn() } }, }); mocks.AccountTreeController.getSelectedAccountGroup.mockReturnValue(''); @@ -1433,7 +1464,7 @@ describe('SnapAccountService', () => { const error = new Error('forward boom'); const setSelectedAccounts = jest.fn().mockRejectedValue(error); mockWithKeyringV2Unsafe(mocks, { - [MOCK_SNAP_ID]: { setSelectedAccounts }, + [MOCK_SNAP_ID]: { v1: { setSelectedAccounts } }, }); mocks.AccountTreeController.getSelectedAccountGroup.mockReturnValue( MOCK_GROUP_ID, @@ -1475,7 +1506,7 @@ describe('SnapAccountService', () => { }); const setSelectedAccounts = jest.fn().mockResolvedValue(undefined); mockWithKeyringV2Unsafe(mocks, { - [MOCK_SNAP_ID]: { setSelectedAccounts }, + [MOCK_SNAP_ID]: { v1: { setSelectedAccounts } }, }); mocks.AccountTreeController.getSelectedAccountGroup.mockReturnValue( MOCK_GROUP_ID, @@ -1497,7 +1528,7 @@ describe('SnapAccountService', () => { runnableSnaps: [buildSnap(MOCK_SNAP_ID)], }); mockWithKeyringV2Unsafe(mocks, { - [MOCK_SNAP_ID]: { setSelectedAccounts: jest.fn() }, + [MOCK_SNAP_ID]: { v1: { setSelectedAccounts: jest.fn() } }, }); mocks.AccountTreeController.getSelectedAccountGroup.mockReturnValue( OTHER_GROUP_ID, @@ -1520,7 +1551,7 @@ describe('SnapAccountService', () => { runnableSnaps: [buildSnap(MOCK_SNAP_ID)], }); mockWithKeyringV2Unsafe(mocks, { - [MOCK_SNAP_ID]: { setSelectedAccounts: jest.fn() }, + [MOCK_SNAP_ID]: { v1: { setSelectedAccounts: jest.fn() } }, }); mocks.AccountTreeController.getSelectedAccountGroup.mockReturnValue(''); @@ -1547,7 +1578,7 @@ describe('SnapAccountService', () => { }); const setSelectedAccounts = jest.fn().mockResolvedValue(undefined); mockWithKeyringV2Unsafe(mocks, { - [MOCK_SNAP_ID]: { setSelectedAccounts }, + [MOCK_SNAP_ID]: { v1: { setSelectedAccounts } }, }); mocks.AccountTreeController.getSelectedAccountGroup.mockReturnValue( MOCK_GROUP_ID, @@ -1569,7 +1600,7 @@ describe('SnapAccountService', () => { runnableSnaps: [buildSnap(MOCK_SNAP_ID)], }); mockWithKeyringV2Unsafe(mocks, { - [MOCK_SNAP_ID]: { setSelectedAccounts: jest.fn() }, + [MOCK_SNAP_ID]: { v1: { setSelectedAccounts: jest.fn() } }, }); mocks.AccountTreeController.getSelectedAccountGroup.mockReturnValue( OTHER_GROUP_ID, @@ -1847,4 +1878,73 @@ describe('SnapAccountService', () => { expect(result).toStrictEqual(MOCK_RESOLVED); }); }); + + describe('setSelectedAccounts', () => { + const MOCK_ACCOUNTS = [ + '00000000-0000-0000-0000-000000000001', + '00000000-0000-0000-0000-000000000002', + ]; + + it('calls v1.setSelectedAccounts on the keyring for a v1 Snap', async () => { + const { service, mocks } = await setup({ + runnableSnaps: [buildSnap(MOCK_SNAP_ID)], + }); + const setSelectedAccounts = jest.fn().mockResolvedValue(undefined); + mockWithKeyringV2Unsafe(mocks, { + [MOCK_SNAP_ID]: { v1: { setSelectedAccounts } }, + }); + await triggerMigration(service, mocks); + + await service.setSelectedAccounts(MOCK_SNAP_ID, MOCK_ACCOUNTS); + + expect(setSelectedAccounts).toHaveBeenCalledWith(MOCK_ACCOUNTS); + }); + + it('calls setSelectedAccounts via the client for a v2-only Snap', async () => { + const clientMethods = buildKeyringClientMock(); + const { service, mocks } = await setup({ + runnableSnaps: [buildSnap(MOCK_SNAP_ID)], + }); + clientMethods.setSelectedAccounts.mockResolvedValue(undefined); + mockWithKeyringV2Unsafe(mocks, { + [MOCK_SNAP_ID]: { v1: undefined }, + }); + await triggerMigration(service, mocks); + + await service.setSelectedAccounts(MOCK_SNAP_ID, MOCK_ACCOUNTS); + + expect(clientMethods.setSelectedAccounts).toHaveBeenCalledWith( + MOCK_ACCOUNTS, + ); + }); + + it('calls ensureReady before delegating', async () => { + buildKeyringClientMock(); + const { service } = await setup(); + + await expect( + service.setSelectedAccounts(MOCK_SNAP_ID, MOCK_ACCOUNTS), + ).rejects.toThrow(`Unknown snap: "${MOCK_SNAP_ID}"`); + }); + + it('is exposed as a messenger action', async () => { + const { service, messenger, mocks } = await setup({ + runnableSnaps: [buildSnap(MOCK_SNAP_ID)], + }); + const setSelectedAccounts = jest.fn().mockResolvedValue(undefined); + mockWithKeyringV2Unsafe(mocks, { + [MOCK_SNAP_ID]: { v1: { setSelectedAccounts } }, + }); + await triggerMigration(service, mocks); + + expect(service).toBeDefined(); + await messenger.call( + 'SnapAccountService:setSelectedAccounts', + MOCK_SNAP_ID, + MOCK_ACCOUNTS, + ); + + expect(setSelectedAccounts).toHaveBeenCalledWith(MOCK_ACCOUNTS); + }); + }); }); diff --git a/packages/snap-account-service/src/SnapAccountService.ts b/packages/snap-account-service/src/SnapAccountService.ts index a70492af70..7934e0eb23 100644 --- a/packages/snap-account-service/src/SnapAccountService.ts +++ b/packages/snap-account-service/src/SnapAccountService.ts @@ -74,6 +74,7 @@ import type { SnapAccountServiceGetSnapsAction, SnapAccountServiceHandleKeyringSnapMessageAction, SnapAccountServiceResolveAccountAddressAction, + SnapAccountServiceSetSelectedAccountsAction, } from './SnapAccountService-method-action-types'; import { SnapPlatformWatcher } from './SnapPlatformWatcher'; import type { SnapPlatformWatcherConfig } from './SnapPlatformWatcher'; @@ -107,6 +108,7 @@ const MESSENGER_EXPOSED_METHODS = [ 'getSnaps', 'handleKeyringSnapMessage', 'resolveAccountAddress', + 'setSelectedAccounts', ] as const; /** @@ -120,7 +122,8 @@ export type SnapAccountServiceActions = | SnapAccountServiceGetAccountTransactionsAction | SnapAccountServiceGetSnapsAction | SnapAccountServiceHandleKeyringSnapMessageAction - | SnapAccountServiceResolveAccountAddressAction; + | SnapAccountServiceResolveAccountAddressAction + | SnapAccountServiceSetSelectedAccountsAction; /** * Actions from other messengers that {@link SnapAccountService} calls. @@ -667,6 +670,51 @@ export class SnapAccountService { .resolveAccountAddress(scope, request); } + /** + * Notifies a Snap of the currently selected accounts. + * + * For v1 Snaps the call goes through the keyring (signing interface); for + * v2 Snaps it is routed via the RPC client because the keyring only covers + * keyring-only operations (signing, account lifecycle). + * + * @param snapId - ID of the Snap. + * @param accounts - IDs of the accounts to mark as selected. + */ + async setSelectedAccounts( + snapId: SnapId, + accounts: AccountId[], + ): Promise { + await this.ensureReady(snapId); + await this.#withKeyringV2Unsafe(snapId, async (keyring) => { + await this.#setSelectedAccountsForKeyring(snapId, keyring, accounts); + }); + } + + /** + * Dispatches a `setSelectedAccounts` call to the correct layer based on + * whether the keyring has a v1 interface or not. + * + * The keyring is a pure interface for keyring-only operations (signing, + * account lifecycle). Extra Snap-level methods like `setSelectedAccounts` + * are invoked via the client for v2 Snaps, which communicates with the Snap + * over RPC. + * + * @param snapId - ID of the Snap. + * @param keyring - The Snap keyring (v2) instance. + * @param accounts - IDs of the accounts to mark as selected. + */ + async #setSelectedAccountsForKeyring( + snapId: SnapId, + keyring: SnapKeyring, + accounts: AccountId[], + ): Promise { + if (keyring.v1) { + await keyring.v1.setSelectedAccounts(accounts); + } else { + await this.#client.withSnapId(snapId).setSelectedAccounts(accounts); + } + } + /** * Handle a message from a Snap. * @@ -844,8 +892,13 @@ export class SnapAccountService { // forward the subset this Snap actually owns. An empty // subset still gets forwarded to explicitly clear the // Snap selected accounts. - await keyring.setSelectedAccounts( - accounts.filter((id) => keyring.hasAccount(id)), + const snapAccounts = accounts.filter((id) => + keyring.hasAccount(id), + ); + await this.#setSelectedAccountsForKeyring( + snapId, + keyring, + snapAccounts, ); }); } catch (error) { diff --git a/packages/snap-account-service/src/index.ts b/packages/snap-account-service/src/index.ts index 14b7f293a9..73409f011f 100644 --- a/packages/snap-account-service/src/index.ts +++ b/packages/snap-account-service/src/index.ts @@ -18,6 +18,7 @@ export type { SnapAccountServiceGetSnapsAction, SnapAccountServiceHandleKeyringSnapMessageAction, SnapAccountServiceResolveAccountAddressAction, + SnapAccountServiceSetSelectedAccountsAction, } from './SnapAccountService-method-action-types'; export { SnapPlatformWatcher } from './SnapPlatformWatcher'; export type { SnapPlatformWatcherConfig } from './SnapPlatformWatcher'; From 0221bce7944055ecbca1fb92b7674af0249856a1 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Mon, 6 Jul 2026 15:59:36 +0200 Subject: [PATCH 08/24] chore: yarn.lock --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 93a786a642..67847b9ffa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7410,7 +7410,7 @@ __metadata: languageName: unknown linkType: soft -"@metamask/messenger@npm:^1.0.0, @metamask/messenger@npm:^1.1.0, @metamask/messenger@npm:^1.1.1": +"@metamask/messenger@npm:^1.0.0, @metamask/messenger@npm:^1.1.0, @metamask/messenger@npm:^1.1.1, @metamask/messenger@npm:^1.2.0": version: 1.2.0 resolution: "@metamask/messenger@npm:1.2.0" dependencies: From a38272575b348492dc04eb40e42c25518cfaa49a Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Mon, 6 Jul 2026 16:04:48 +0200 Subject: [PATCH 09/24] fix: re-generate actions --- .../SnapAccountService-method-action-types.ts | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/snap-account-service/src/SnapAccountService-method-action-types.ts b/packages/snap-account-service/src/SnapAccountService-method-action-types.ts index 0427392e58..038fbe8b9a 100644 --- a/packages/snap-account-service/src/SnapAccountService-method-action-types.ts +++ b/packages/snap-account-service/src/SnapAccountService-method-action-types.ts @@ -50,18 +50,6 @@ export type SnapAccountServiceEnsureMigratedAction = { handler: SnapAccountService['ensureMigrated']; }; -/** - * Handle a message from a Snap. - * - * @param snapId - ID of the Snap. - * @param message - Message sent by the Snap. - * @returns The execution result. - */ -export type SnapAccountServiceHandleKeyringSnapMessageAction = { - type: `SnapAccountService:handleKeyringSnapMessage`; - handler: SnapAccountService['handleKeyringSnapMessage']; -}; - /** * Returns the CAIP-19 asset type/ID list supported by an account. * @@ -129,6 +117,18 @@ export type SnapAccountServiceSetSelectedAccountsAction = { handler: SnapAccountService['setSelectedAccounts']; }; +/** + * Handle a message from a Snap. + * + * @param snapId - ID of the Snap. + * @param message - Message sent by the Snap. + * @returns The execution result. + */ +export type SnapAccountServiceHandleKeyringSnapMessageAction = { + type: `SnapAccountService:handleKeyringSnapMessage`; + handler: SnapAccountService['handleKeyringSnapMessage']; +}; + /** * Union of all SnapAccountService action types. */ @@ -139,6 +139,6 @@ export type SnapAccountServiceMethodActions = | SnapAccountServiceGetAccountAssetsAction | SnapAccountServiceGetAccountBalancesAction | SnapAccountServiceGetAccountTransactionsAction - | SnapAccountServiceHandleKeyringSnapMessageAction | SnapAccountServiceResolveAccountAddressAction - | SnapAccountServiceSetSelectedAccountsAction; + | SnapAccountServiceSetSelectedAccountsAction + | SnapAccountServiceHandleKeyringSnapMessageAction; From 43cf91707c063cec0d7e5a17841978e062ec515b Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Mon, 6 Jul 2026 16:55:19 +0200 Subject: [PATCH 10/24] fix: use v1.createAccount --- .../src/providers/BtcAccountProvider.test.ts | 19 ++++++++ .../src/providers/BtcAccountProvider.ts | 7 ++- .../src/providers/SnapAccountProvider.test.ts | 11 +++-- .../src/providers/SnapAccountProvider.ts | 47 +++++++++---------- .../src/providers/SolAccountProvider.test.ts | 19 ++++++++ .../src/providers/SolAccountProvider.ts | 7 ++- .../src/providers/TrxAccountProvider.test.ts | 19 ++++++++ .../src/providers/TrxAccountProvider.ts | 7 ++- .../src/providers/XlmAccountProvider.test.ts | 26 +++++++++- .../src/providers/XlmAccountProvider.ts | 7 ++- 10 files changed, 136 insertions(+), 33 deletions(-) diff --git a/packages/multichain-account-service/src/providers/BtcAccountProvider.test.ts b/packages/multichain-account-service/src/providers/BtcAccountProvider.test.ts index bde6eba947..14d0d24a2a 100644 --- a/packages/multichain-account-service/src/providers/BtcAccountProvider.test.ts +++ b/packages/multichain-account-service/src/providers/BtcAccountProvider.test.ts @@ -132,6 +132,12 @@ class MockBtcKeyring { return account; }); }); + + deleteAccount = jest.fn().mockResolvedValue(undefined); + + get v1() { + return { createAccount: this.createAccount }; + } } class MockBtcAccountProvider extends BtcAccountProvider { @@ -457,6 +463,19 @@ describe('BtcAccountProvider', () => { // Provider should now expose one account (newly created) expect(provider.getAccounts()).toHaveLength(1); }); + + it('throws when the Snap is v2-only and does not support v1 account creation', async () => { + const { provider, keyring } = setup({ accounts: [] }); + jest.spyOn(keyring, 'v1', 'get').mockReturnValue(undefined); + + await expect( + provider.createAccounts({ + type: AccountCreationType.Bip44DeriveIndex, + entropySource: MOCK_HD_KEYRING_1.metadata.id, + groupIndex: 0, + }), + ).rejects.toThrow('is v2-only and does not support v1 account creation'); + }); }); describe('v2 - batched', () => { diff --git a/packages/multichain-account-service/src/providers/BtcAccountProvider.ts b/packages/multichain-account-service/src/providers/BtcAccountProvider.ts index 5661dbaa30..8e718461a7 100644 --- a/packages/multichain-account-service/src/providers/BtcAccountProvider.ts +++ b/packages/multichain-account-service/src/providers/BtcAccountProvider.ts @@ -80,7 +80,12 @@ export class BtcAccountProvider extends SnapAccountProvider { groupIndex, }: { entropySource: EntropySourceId; groupIndex: number }, ): Promise { - return keyring.createAccount({ + if (!keyring.v1) { + throw new Error( + `Snap "${BtcAccountProvider.BTC_SNAP_ID}" is v2-only and does not support v1 account creation`, + ); + } + return keyring.v1.createAccount({ entropySource, index: groupIndex, addressType: BtcAccountType.P2wpkh, diff --git a/packages/multichain-account-service/src/providers/SnapAccountProvider.test.ts b/packages/multichain-account-service/src/providers/SnapAccountProvider.test.ts index 20b53f60b0..c9847c226b 100644 --- a/packages/multichain-account-service/src/providers/SnapAccountProvider.test.ts +++ b/packages/multichain-account-service/src/providers/SnapAccountProvider.test.ts @@ -111,9 +111,12 @@ class MockSnapAccountProvider extends SnapAccountProvider { keyring: RestrictedSnapKeyring, options: { entropySource: EntropySourceId; groupIndex: number }, ): Promise { + if (!keyring.v1) { + throw new Error('Snap is v2-only and does not support v1 account creation'); + } // Forward to the mocked keyring (no real purposes apart from implementing - // this abstract method).. - return await keyring.createAccount(options); + // this abstract method). + return await keyring.v1.createAccount(options); } async createAccounts( @@ -244,7 +247,9 @@ const setup = ({ const keyring = { type: keyringOverrides.type ?? 'snap', snapId: keyringOverrides.snapId ?? TEST_SNAP_ID, - createAccount: jest.fn(), + v1: { + createAccount: jest.fn(), + }, createAccounts: jest.fn(), deleteAccount: jest.fn().mockResolvedValue(undefined), lookupByAddress: jest diff --git a/packages/multichain-account-service/src/providers/SnapAccountProvider.ts b/packages/multichain-account-service/src/providers/SnapAccountProvider.ts index e387c23372..0b2410d30b 100644 --- a/packages/multichain-account-service/src/providers/SnapAccountProvider.ts +++ b/packages/multichain-account-service/src/providers/SnapAccountProvider.ts @@ -33,7 +33,15 @@ import { BaseBip44AccountProvider } from './BaseBip44AccountProvider'; import { withTimeout } from './utils'; export type RestrictedSnapKeyring = { - createAccount: SnapKeyringV2['createAccount']; + /** + * V1 interface, present only when the Snap does not declare v2 capabilities. + * `undefined` for v2-only Snaps. + * + * Use this to make v1 calls explicit: + * if (!keyring.v1) { throw new Error('Snap is v2-only'); } + * keyring.v1.createAccount(options); + */ + v1: { createAccount(options: Record): Promise } | undefined; createAccounts: SnapKeyringV2['createAccounts']; deleteAccount: SnapKeyringV2['deleteAccount']; }; @@ -154,29 +162,20 @@ export abstract class SnapAccountProvider extends BaseBip44AccountProvider { // Also, creating account that way won't invalidate the Snap keyring state. The // account will get created and persisted properly with the Snap account creation // flow "asynchronously" (with `notify:accountCreated`). - const { createAccount, createAccounts } = await this.#withSnapKeyring<{ - createAccount: SnapKeyringV2['createAccount']; - createAccounts: SnapKeyringV2['createAccounts']; - }>(async ({ keyring }) => ({ - createAccount: keyring.createAccount.bind(keyring), - createAccounts: keyring.createAccounts.bind(keyring), - })); - - return { - createAccount: async (options) => - // We use the "unguarded" account creation here (see explanation above). - await createAccount(options, { - displayAccountNameSuggestion: false, - displayConfirmation: false, - setSelectedAccount: false, - }), - createAccounts: async (options) => await createAccounts(options), - deleteAccount: async (id: string) => - // Though, when removing account, we can use the normal flow. - await this.#withSnapKeyring(async ({ keyring }) => { - await keyring.deleteAccount(id); - }), - }; + return this.#withSnapKeyring(async ({ keyring }) => { + let v1: RestrictedSnapKeyring['v1']; + if (keyring.v1) { + v1 = { + createAccount: keyring.v1.createAccount.bind(keyring.v1), + }; + } + + return { + v1, + createAccounts: keyring.createAccounts.bind(keyring), + deleteAccount: keyring.deleteAccount.bind(keyring), + }; + }); } #getKeyringClientFromSnapId(snapId: string): KeyringClient { diff --git a/packages/multichain-account-service/src/providers/SolAccountProvider.test.ts b/packages/multichain-account-service/src/providers/SolAccountProvider.test.ts index b2c424af5f..bcbcd9badd 100644 --- a/packages/multichain-account-service/src/providers/SolAccountProvider.test.ts +++ b/packages/multichain-account-service/src/providers/SolAccountProvider.test.ts @@ -114,6 +114,12 @@ class MockSolanaKeyring { return account; }); }); + + deleteAccount = jest.fn().mockResolvedValue(undefined); + + get v1() { + return { createAccount: this.createAccount }; + } } class MockSolAccountProvider extends SolAccountProvider { @@ -437,6 +443,19 @@ describe('SolAccountProvider', () => { // Provider should now expose one account (newly created) expect(provider.getAccounts()).toHaveLength(1); }); + + it('throws when the Snap is v2-only and does not support v1 account creation', async () => { + const { provider, keyring } = setup({ accounts: [] }); + jest.spyOn(keyring, 'v1', 'get').mockReturnValue(undefined); + + await expect( + provider.createAccounts({ + type: AccountCreationType.Bip44DeriveIndex, + entropySource: MOCK_HD_KEYRING_1.metadata.id, + groupIndex: 0, + }), + ).rejects.toThrow('is v2-only and does not support v1 account creation'); + }); }); describe('v2 - batched', () => { diff --git a/packages/multichain-account-service/src/providers/SolAccountProvider.ts b/packages/multichain-account-service/src/providers/SolAccountProvider.ts index c5f1af5977..508c7611b2 100644 --- a/packages/multichain-account-service/src/providers/SolAccountProvider.ts +++ b/packages/multichain-account-service/src/providers/SolAccountProvider.ts @@ -87,7 +87,12 @@ export class SolAccountProvider extends SnapAccountProvider { groupIndex, }: { entropySource: EntropySourceId; groupIndex: number }, ): Promise { - return keyring.createAccount({ + if (!keyring.v1) { + throw new Error( + `Snap "${SolAccountProvider.SOLANA_SNAP_ID}" is v2-only and does not support v1 account creation`, + ); + } + return keyring.v1.createAccount({ entropySource, derivationPath: this.#getDerivationPath(groupIndex), }); diff --git a/packages/multichain-account-service/src/providers/TrxAccountProvider.test.ts b/packages/multichain-account-service/src/providers/TrxAccountProvider.test.ts index d8b617ad1e..30c2f067f4 100644 --- a/packages/multichain-account-service/src/providers/TrxAccountProvider.test.ts +++ b/packages/multichain-account-service/src/providers/TrxAccountProvider.test.ts @@ -103,6 +103,12 @@ class MockTronKeyring { // Add discoverAccounts method to match the provider's usage discoverAccounts = jest.fn().mockResolvedValue([]); + + deleteAccount = jest.fn().mockResolvedValue(undefined); + + get v1() { + return { createAccount: this.createAccount }; + } } class MockTrxAccountProvider extends TrxAccountProvider { @@ -438,6 +444,19 @@ describe('TrxAccountProvider', () => { // Provider should now expose one account (newly created) expect(provider.getAccounts()).toHaveLength(1); }); + + it('throws when the Snap is v2-only and does not support v1 account creation', async () => { + const { provider, keyring } = setup({ accounts: [] }); + jest.spyOn(keyring, 'v1', 'get').mockReturnValue(undefined); + + await expect( + provider.createAccounts({ + type: AccountCreationType.Bip44DeriveIndex, + entropySource: MOCK_HD_KEYRING_1.metadata.id, + groupIndex: 0, + }), + ).rejects.toThrow('is v2-only and does not support v1 account creation'); + }); }); describe('v2 - batched', () => { diff --git a/packages/multichain-account-service/src/providers/TrxAccountProvider.ts b/packages/multichain-account-service/src/providers/TrxAccountProvider.ts index 9c3ac48be5..f852389dcb 100644 --- a/packages/multichain-account-service/src/providers/TrxAccountProvider.ts +++ b/packages/multichain-account-service/src/providers/TrxAccountProvider.ts @@ -81,7 +81,12 @@ export class TrxAccountProvider extends SnapAccountProvider { groupIndex, }: { entropySource: EntropySourceId; groupIndex: number }, ): Promise { - return keyring.createAccount({ + if (!keyring.v1) { + throw new Error( + `Snap "${TrxAccountProvider.TRX_SNAP_ID}" is v2-only and does not support v1 account creation`, + ); + } + return keyring.v1.createAccount({ entropySource, index: groupIndex, addressType: TrxAccountType.Eoa, diff --git a/packages/multichain-account-service/src/providers/XlmAccountProvider.test.ts b/packages/multichain-account-service/src/providers/XlmAccountProvider.test.ts index 50223da501..1d6880e984 100644 --- a/packages/multichain-account-service/src/providers/XlmAccountProvider.test.ts +++ b/packages/multichain-account-service/src/providers/XlmAccountProvider.test.ts @@ -49,9 +49,9 @@ class MockStellarKeyring { this.accounts = accounts; } - createAccount: SnapKeyring['createAccount'] = jest + createAccount = jest .fn() - .mockImplementation((_, options: Record) => { + .mockImplementation((options: Record) => { const { index } = options; if (typeof index === 'number') { const found = this.accounts.find( @@ -107,6 +107,12 @@ class MockStellarKeyring { }); discoverAccounts = jest.fn().mockResolvedValue([]); + + deleteAccount = jest.fn().mockResolvedValue(undefined); + + get v1() { + return { createAccount: this.createAccount }; + } } class MockXlmAccountProvider extends XlmAccountProvider { @@ -311,6 +317,22 @@ describe('XlmAccountProvider', () => { expect(mocks.keyring.createAccount).toHaveBeenCalled(); expect(mocks.keyring.createAccounts).not.toHaveBeenCalled(); }); + + it('throws when the Snap is v2-only and does not support v1 account creation', async () => { + const { provider, keyring } = setup({ + accounts: [], + config: asConfig({ createAccounts: { batched: false } }), + }); + jest.spyOn(keyring, 'v1', 'get').mockReturnValue(undefined); + + await expect( + provider.createAccounts({ + type: AccountCreationType.Bip44DeriveIndex, + entropySource: MOCK_HD_KEYRING_2.metadata.id, + groupIndex: 0, + }), + ).rejects.toThrow('is v2-only and does not support v1 account creation'); + }); }); describe('v2 - batched', () => { diff --git a/packages/multichain-account-service/src/providers/XlmAccountProvider.ts b/packages/multichain-account-service/src/providers/XlmAccountProvider.ts index 5d169cf1ad..d5256d14ef 100644 --- a/packages/multichain-account-service/src/providers/XlmAccountProvider.ts +++ b/packages/multichain-account-service/src/providers/XlmAccountProvider.ts @@ -81,7 +81,12 @@ export class XlmAccountProvider extends SnapAccountProvider { groupIndex, }: { entropySource: EntropySourceId; groupIndex: number }, ): Promise { - return keyring.createAccount({ + if (!keyring.v1) { + throw new Error( + `Snap "${XlmAccountProvider.XLM_SNAP_ID}" is v2-only and does not support v1 account creation`, + ); + } + return keyring.v1.createAccount({ entropySource, index: groupIndex, addressType: XlmAccountType.Account, From a7db83616ad656db63f9f38bb4403fbb01760824 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Mon, 6 Jul 2026 17:02:31 +0200 Subject: [PATCH 11/24] chore: lint + cosmetic --- .../src/providers/SnapAccountProvider.test.ts | 4 +++- .../src/providers/SnapAccountProvider.ts | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/multichain-account-service/src/providers/SnapAccountProvider.test.ts b/packages/multichain-account-service/src/providers/SnapAccountProvider.test.ts index c9847c226b..4d2febe412 100644 --- a/packages/multichain-account-service/src/providers/SnapAccountProvider.test.ts +++ b/packages/multichain-account-service/src/providers/SnapAccountProvider.test.ts @@ -112,7 +112,9 @@ class MockSnapAccountProvider extends SnapAccountProvider { options: { entropySource: EntropySourceId; groupIndex: number }, ): Promise { if (!keyring.v1) { - throw new Error('Snap is v2-only and does not support v1 account creation'); + throw new Error( + 'Snap is v2-only and does not support v1 account creation', + ); } // Forward to the mocked keyring (no real purposes apart from implementing // this abstract method). diff --git a/packages/multichain-account-service/src/providers/SnapAccountProvider.ts b/packages/multichain-account-service/src/providers/SnapAccountProvider.ts index 0b2410d30b..5079d632cb 100644 --- a/packages/multichain-account-service/src/providers/SnapAccountProvider.ts +++ b/packages/multichain-account-service/src/providers/SnapAccountProvider.ts @@ -1,6 +1,7 @@ import { assertIsBip44Account } from '@metamask/account-api'; import type { Bip44Account } from '@metamask/account-api'; import type { TraceCallback, TraceRequest } from '@metamask/controller-utils'; +import { SnapKeyringV1 } from '@metamask/eth-snap-keyring'; import type { SnapKeyring as SnapKeyringV2 } from '@metamask/eth-snap-keyring/v2'; import { isSnapKeyring } from '@metamask/eth-snap-keyring/v2'; import { @@ -41,7 +42,7 @@ export type RestrictedSnapKeyring = { * if (!keyring.v1) { throw new Error('Snap is v2-only'); } * keyring.v1.createAccount(options); */ - v1: { createAccount(options: Record): Promise } | undefined; + v1?: { createAccount: SnapKeyringV1['createAccount'] }; createAccounts: SnapKeyringV2['createAccounts']; deleteAccount: SnapKeyringV2['deleteAccount']; }; From c6e3490d216a7201cb313f823483857e98938a87 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Mon, 6 Jul 2026 17:13:16 +0200 Subject: [PATCH 12/24] chore: lint --- .../src/providers/BtcAccountProvider.test.ts | 7 +++++-- .../src/providers/SolAccountProvider.test.ts | 7 +++++-- .../src/providers/TrxAccountProvider.test.ts | 7 +++++-- .../src/providers/XlmAccountProvider.test.ts | 9 ++++++--- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/packages/multichain-account-service/src/providers/BtcAccountProvider.test.ts b/packages/multichain-account-service/src/providers/BtcAccountProvider.test.ts index 14d0d24a2a..68266f4b64 100644 --- a/packages/multichain-account-service/src/providers/BtcAccountProvider.test.ts +++ b/packages/multichain-account-service/src/providers/BtcAccountProvider.test.ts @@ -24,7 +24,10 @@ import { BTC_ACCOUNT_PROVIDER_NAME, BtcAccountProvider, } from './BtcAccountProvider'; -import type { SnapAccountProviderConfig } from './SnapAccountProvider'; +import type { + RestrictedSnapKeyring, + SnapAccountProviderConfig, +} from './SnapAccountProvider'; function asConfig( partial: DeepPartial, @@ -135,7 +138,7 @@ class MockBtcKeyring { deleteAccount = jest.fn().mockResolvedValue(undefined); - get v1() { + get v1(): Required { return { createAccount: this.createAccount }; } } diff --git a/packages/multichain-account-service/src/providers/SolAccountProvider.test.ts b/packages/multichain-account-service/src/providers/SolAccountProvider.test.ts index bcbcd9badd..ee1ab6eb24 100644 --- a/packages/multichain-account-service/src/providers/SolAccountProvider.test.ts +++ b/packages/multichain-account-service/src/providers/SolAccountProvider.test.ts @@ -18,7 +18,10 @@ import { } from '../tests'; import type { RootMessenger, DeepPartial } from '../tests'; import { AccountProviderWrapper } from './AccountProviderWrapper'; -import type { SnapAccountProviderConfig } from './SnapAccountProvider'; +import type { + RestrictedSnapKeyring, + SnapAccountProviderConfig, +} from './SnapAccountProvider'; import { SOL_ACCOUNT_PROVIDER_DEFAULT_CONFIG, SOL_ACCOUNT_PROVIDER_NAME, @@ -117,7 +120,7 @@ class MockSolanaKeyring { deleteAccount = jest.fn().mockResolvedValue(undefined); - get v1() { + get v1(): Required { return { createAccount: this.createAccount }; } } diff --git a/packages/multichain-account-service/src/providers/TrxAccountProvider.test.ts b/packages/multichain-account-service/src/providers/TrxAccountProvider.test.ts index 30c2f067f4..25cd260556 100644 --- a/packages/multichain-account-service/src/providers/TrxAccountProvider.test.ts +++ b/packages/multichain-account-service/src/providers/TrxAccountProvider.test.ts @@ -18,7 +18,10 @@ import { } from '../tests'; import type { RootMessenger, DeepPartial } from '../tests'; import { AccountProviderWrapper } from './AccountProviderWrapper'; -import type { SnapAccountProviderConfig } from './SnapAccountProvider'; +import type { + RestrictedSnapKeyring, + SnapAccountProviderConfig, +} from './SnapAccountProvider'; import { TRX_ACCOUNT_PROVIDER_DEFAULT_CONFIG, TRX_ACCOUNT_PROVIDER_NAME, @@ -106,7 +109,7 @@ class MockTronKeyring { deleteAccount = jest.fn().mockResolvedValue(undefined); - get v1() { + get v1(): Required { return { createAccount: this.createAccount }; } } diff --git a/packages/multichain-account-service/src/providers/XlmAccountProvider.test.ts b/packages/multichain-account-service/src/providers/XlmAccountProvider.test.ts index 1d6880e984..40416b1891 100644 --- a/packages/multichain-account-service/src/providers/XlmAccountProvider.test.ts +++ b/packages/multichain-account-service/src/providers/XlmAccountProvider.test.ts @@ -19,7 +19,10 @@ import { } from '../tests'; import type { RootMessenger, DeepPartial } from '../tests'; import { AccountProviderWrapper } from './AccountProviderWrapper'; -import type { SnapAccountProviderConfig } from './SnapAccountProvider'; +import type { + RestrictedSnapKeyring, + SnapAccountProviderConfig, +} from './SnapAccountProvider'; import { XLM_ACCOUNT_PROVIDER_DEFAULT_CONFIG, XLM_ACCOUNT_PROVIDER_NAME, @@ -110,7 +113,7 @@ class MockStellarKeyring { deleteAccount = jest.fn().mockResolvedValue(undefined); - get v1() { + get v1(): Required { return { createAccount: this.createAccount }; } } @@ -213,7 +216,7 @@ function setup({ mocks: { handleRequest: mockHandleRequest, keyring: { - createAccount: keyring.createAccount as jest.Mock, + createAccount: keyring.createAccount, createAccounts: keyring.createAccounts as jest.Mock, discoverAccounts: keyring.discoverAccounts, }, From 272ce446541e8c7aa3624b70fb271f9881fcc399 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Mon, 6 Jul 2026 17:21:06 +0200 Subject: [PATCH 13/24] chore: changelog --- packages/multichain-account-service/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/multichain-account-service/CHANGELOG.md b/packages/multichain-account-service/CHANGELOG.md index ffdd38b3a3..a7dad1c244 100644 --- a/packages/multichain-account-service/CHANGELOG.md +++ b/packages/multichain-account-service/CHANGELOG.md @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- **BREAKING:** `RestrictedSnapKeyring.createAccount` has been replaced by `RestrictedSnapKeyring.v1`, which is `undefined` for v2-only Snaps ([#43446](https://github.com/MetaMask/core/pull/43446)) + - Any subclass implementing `SnapAccountProvider.createAccountV1` must now check `keyring.v1` guard followed by `keyring.v1.createAccount(options)`. - Wallet alignment now only creates the missing `(provider, group index)` pairs instead of re-creating the whole range for every provider, avoiding redundant `createAccounts` calls (and their traces) ([#9269](https://github.com/MetaMask/core/pull/9269)) - Bump `@metamask/accounts-controller` from `^39.0.3` to `^39.0.4` ([#9349](https://github.com/MetaMask/core/pull/9349)) - Bump `@metamask/messenger` from `^1.2.0` to `^2.0.0` ([#9392](https://github.com/MetaMask/core/pull/9392)) From bae54f61212577dbba875ee8bbb71a716d76eabc Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Tue, 7 Jul 2026 12:10:22 +0200 Subject: [PATCH 14/24] build: remove use of preview builds --- package.json | 6 - packages/account-tree-controller/package.json | 2 +- packages/accounts-controller/package.json | 4 +- packages/assets-controller/package.json | 4 +- packages/assets-controllers/package.json | 4 +- packages/bridge-controller/package.json | 2 +- packages/earn-controller/package.json | 2 +- packages/keyring-controller/package.json | 2 +- .../money-account-controller/package.json | 2 +- .../multichain-account-service/package.json | 6 +- .../package.json | 2 +- .../package.json | 4 +- .../package.json | 2 +- packages/profile-sync-controller/package.json | 2 +- packages/snap-account-service/package.json | 8 +- .../src/SnapAccountService.ts | 2 + yarn.lock | 135 +++++++++--------- 17 files changed, 93 insertions(+), 96 deletions(-) diff --git a/package.json b/package.json index 6c72bed149..c3d3fb90e4 100644 --- a/package.json +++ b/package.json @@ -102,12 +102,6 @@ "yargs": "^17.7.2" }, "resolutions": { - "@metamask/eth-snap-keyring": "npm:@metamask-previews/eth-snap-keyring@22.4.0-5951828", - "@metamask/keyring-api": "npm:@metamask-previews/keyring-api@23.4.0-5951828", - "@metamask/keyring-internal-api": "npm:@metamask-previews/keyring-internal-api@11.0.1-5951828", - "@metamask/keyring-internal-snap-client": "npm:@metamask-previews/keyring-internal-snap-client@10.0.4-5951828", - "@metamask/keyring-snap-client": "npm:@metamask-previews/keyring-snap-client@9.1.0-5951828", - "@metamask/keyring-snap-sdk": "npm:@metamask-previews/keyring-snap-sdk@9.1.0-5951828", "elliptic@6.5.4": "^6.5.7", "fast-xml-parser@^4.3.4": "^4.4.1", "ws@7.4.6": "^7.5.10" diff --git a/packages/account-tree-controller/package.json b/packages/account-tree-controller/package.json index fbac1099f4..245766b4ba 100644 --- a/packages/account-tree-controller/package.json +++ b/packages/account-tree-controller/package.json @@ -55,7 +55,7 @@ "dependencies": { "@metamask/accounts-controller": "^39.0.4", "@metamask/base-controller": "^9.1.0", - "@metamask/keyring-api": "^23.3.0", + "@metamask/keyring-api": "^23.5.0", "@metamask/keyring-controller": "^27.1.0", "@metamask/messenger": "^2.0.0", "@metamask/multichain-account-service": "^11.1.0", diff --git a/packages/accounts-controller/package.json b/packages/accounts-controller/package.json index 277a082060..5ec907e654 100644 --- a/packages/accounts-controller/package.json +++ b/packages/accounts-controller/package.json @@ -55,8 +55,8 @@ "dependencies": { "@ethereumjs/util": "^9.1.0", "@metamask/base-controller": "^9.1.0", - "@metamask/eth-snap-keyring": "^22.3.0", - "@metamask/keyring-api": "^23.3.0", + "@metamask/eth-snap-keyring": "^23.0.0", + "@metamask/keyring-api": "^23.5.0", "@metamask/keyring-controller": "^27.1.0", "@metamask/keyring-internal-api": "^11.0.1", "@metamask/keyring-sdk": "^2.2.0", diff --git a/packages/assets-controller/package.json b/packages/assets-controller/package.json index 9fa1ae7755..ae445e99ef 100644 --- a/packages/assets-controller/package.json +++ b/packages/assets-controller/package.json @@ -63,10 +63,10 @@ "@metamask/client-controller": "^1.0.1", "@metamask/controller-utils": "^12.3.0", "@metamask/core-backend": "^6.5.0", - "@metamask/keyring-api": "^23.3.0", + "@metamask/keyring-api": "^23.5.0", "@metamask/keyring-controller": "^27.1.0", "@metamask/keyring-internal-api": "^11.0.1", - "@metamask/keyring-snap-client": "^9.0.2", + "@metamask/keyring-snap-client": "^9.2.0", "@metamask/messenger": "^2.0.0", "@metamask/network-controller": "^34.0.0", "@metamask/network-enablement-controller": "^5.4.1", diff --git a/packages/assets-controllers/package.json b/packages/assets-controllers/package.json index d0d96ee042..a2661c1fd8 100644 --- a/packages/assets-controllers/package.json +++ b/packages/assets-controllers/package.json @@ -68,7 +68,7 @@ "@metamask/controller-utils": "^12.3.0", "@metamask/core-backend": "^6.5.0", "@metamask/eth-query": "^4.0.0", - "@metamask/keyring-api": "^23.3.0", + "@metamask/keyring-api": "^23.5.0", "@metamask/keyring-controller": "^27.1.0", "@metamask/messenger": "^2.0.0", "@metamask/metamask-eth-abis": "^3.1.1", @@ -106,7 +106,7 @@ "@metamask/auto-changelog": "^6.1.0", "@metamask/ethjs-provider-http": "^0.3.0", "@metamask/keyring-internal-api": "^11.0.1", - "@metamask/keyring-snap-client": "^9.0.2", + "@metamask/keyring-snap-client": "^9.2.0", "@metamask/providers": "^22.1.0", "@ts-bridge/cli": "^0.6.4", "@types/jest": "^29.5.14", diff --git a/packages/bridge-controller/package.json b/packages/bridge-controller/package.json index fd9d26b8fe..702d8131f8 100644 --- a/packages/bridge-controller/package.json +++ b/packages/bridge-controller/package.json @@ -63,7 +63,7 @@ "@metamask/base-controller": "^9.1.0", "@metamask/controller-utils": "^12.3.0", "@metamask/gas-fee-controller": "^26.2.4", - "@metamask/keyring-api": "^23.3.0", + "@metamask/keyring-api": "^23.5.0", "@metamask/messenger": "^2.0.0", "@metamask/metamask-eth-abis": "^3.1.1", "@metamask/multichain-network-controller": "^3.2.1", diff --git a/packages/earn-controller/package.json b/packages/earn-controller/package.json index 7f25ebd1e2..351f305a4c 100644 --- a/packages/earn-controller/package.json +++ b/packages/earn-controller/package.json @@ -58,7 +58,7 @@ "@metamask/account-tree-controller": "^7.5.3", "@metamask/base-controller": "^9.1.0", "@metamask/controller-utils": "^12.3.0", - "@metamask/keyring-api": "^23.3.0", + "@metamask/keyring-api": "^23.5.0", "@metamask/messenger": "^2.0.0", "@metamask/network-controller": "^34.0.0", "@metamask/stake-sdk": "^3.2.1", diff --git a/packages/keyring-controller/package.json b/packages/keyring-controller/package.json index 78be02c727..0242aa6735 100644 --- a/packages/keyring-controller/package.json +++ b/packages/keyring-controller/package.json @@ -59,7 +59,7 @@ "@metamask/eth-hd-keyring": "^14.1.1", "@metamask/eth-sig-util": "^8.2.0", "@metamask/eth-simple-keyring": "^12.0.2", - "@metamask/keyring-api": "^23.3.0", + "@metamask/keyring-api": "^23.5.0", "@metamask/keyring-internal-api": "^11.0.1", "@metamask/messenger": "^2.0.0", "@metamask/utils": "^11.11.0", diff --git a/packages/money-account-controller/package.json b/packages/money-account-controller/package.json index 10edb817c1..27a145ae21 100644 --- a/packages/money-account-controller/package.json +++ b/packages/money-account-controller/package.json @@ -56,7 +56,7 @@ "@metamask/accounts-controller": "^39.0.4", "@metamask/base-controller": "^9.1.0", "@metamask/eth-money-keyring": "^2.0.4", - "@metamask/keyring-api": "^23.3.0", + "@metamask/keyring-api": "^23.5.0", "@metamask/keyring-controller": "^27.1.0", "@metamask/messenger": "^2.0.0", "async-mutex": "^0.5.0" diff --git a/packages/multichain-account-service/package.json b/packages/multichain-account-service/package.json index ec742cad28..541c256360 100644 --- a/packages/multichain-account-service/package.json +++ b/packages/multichain-account-service/package.json @@ -56,12 +56,12 @@ "@ethereumjs/util": "^9.1.0", "@metamask/accounts-controller": "^39.0.4", "@metamask/base-controller": "^9.1.0", - "@metamask/eth-snap-keyring": "^22.3.0", + "@metamask/eth-snap-keyring": "^23.0.0", "@metamask/key-tree": "^10.1.1", - "@metamask/keyring-api": "^23.3.0", + "@metamask/keyring-api": "^23.5.0", "@metamask/keyring-controller": "^27.1.0", "@metamask/keyring-internal-api": "^11.0.1", - "@metamask/keyring-snap-client": "^9.0.2", + "@metamask/keyring-snap-client": "^9.2.0", "@metamask/keyring-utils": "^3.3.1", "@metamask/messenger": "^2.0.0", "@metamask/snap-account-service": "^1.0.0", diff --git a/packages/multichain-network-controller/package.json b/packages/multichain-network-controller/package.json index 2ef8bf4546..ce8807629a 100644 --- a/packages/multichain-network-controller/package.json +++ b/packages/multichain-network-controller/package.json @@ -56,7 +56,7 @@ "@metamask/accounts-controller": "^39.0.4", "@metamask/base-controller": "^9.1.0", "@metamask/controller-utils": "^12.3.0", - "@metamask/keyring-api": "^23.3.0", + "@metamask/keyring-api": "^23.5.0", "@metamask/keyring-internal-api": "^11.0.1", "@metamask/messenger": "^2.0.0", "@metamask/network-controller": "^34.0.0", diff --git a/packages/multichain-transactions-controller/package.json b/packages/multichain-transactions-controller/package.json index 42c3766771..2b197c625c 100644 --- a/packages/multichain-transactions-controller/package.json +++ b/packages/multichain-transactions-controller/package.json @@ -55,9 +55,9 @@ "dependencies": { "@metamask/accounts-controller": "^39.0.4", "@metamask/base-controller": "^9.1.0", - "@metamask/keyring-api": "^23.3.0", + "@metamask/keyring-api": "^23.5.0", "@metamask/keyring-internal-api": "^11.0.1", - "@metamask/keyring-snap-client": "^9.0.2", + "@metamask/keyring-snap-client": "^9.2.0", "@metamask/messenger": "^2.0.0", "@metamask/polling-controller": "^16.0.8", "@metamask/snaps-controllers": "^19.0.0", diff --git a/packages/network-enablement-controller/package.json b/packages/network-enablement-controller/package.json index d6c1d580b1..c9c2c339c6 100644 --- a/packages/network-enablement-controller/package.json +++ b/packages/network-enablement-controller/package.json @@ -55,7 +55,7 @@ "dependencies": { "@metamask/base-controller": "^9.1.0", "@metamask/controller-utils": "^12.3.0", - "@metamask/keyring-api": "^23.3.0", + "@metamask/keyring-api": "^23.5.0", "@metamask/messenger": "^2.0.0", "@metamask/multichain-network-controller": "^3.2.1", "@metamask/network-controller": "^34.0.0", diff --git a/packages/profile-sync-controller/package.json b/packages/profile-sync-controller/package.json index cbab4ab925..c3c93b8851 100644 --- a/packages/profile-sync-controller/package.json +++ b/packages/profile-sync-controller/package.json @@ -124,7 +124,7 @@ "@lavamoat/allow-scripts": "^3.0.4", "@lavamoat/preinstall-always-fail": "^2.1.0", "@metamask/auto-changelog": "^6.1.0", - "@metamask/keyring-api": "^23.3.0", + "@metamask/keyring-api": "^23.5.0", "@metamask/keyring-internal-api": "^11.0.1", "@metamask/providers": "^22.1.0", "@ts-bridge/cli": "^0.6.4", diff --git a/packages/snap-account-service/package.json b/packages/snap-account-service/package.json index ed8cc297ef..4205efa3c2 100644 --- a/packages/snap-account-service/package.json +++ b/packages/snap-account-service/package.json @@ -55,11 +55,11 @@ "dependencies": { "@metamask/account-api": "^1.0.4", "@metamask/account-tree-controller": "^7.5.3", - "@metamask/eth-snap-keyring": "^22.3.0", - "@metamask/keyring-api": "^23.3.0", + "@metamask/eth-snap-keyring": "^23.0.0", + "@metamask/keyring-api": "^23.5.0", "@metamask/keyring-controller": "^27.1.0", - "@metamask/keyring-internal-snap-client": "^10.0.4", - "@metamask/keyring-snap-sdk": "^9.0.2", + "@metamask/keyring-internal-snap-client": "^10.0.5", + "@metamask/keyring-snap-sdk": "^9.2.0", "@metamask/messenger": "^2.0.0", "@metamask/snaps-controllers": "^19.0.0", "@metamask/snaps-sdk": "^11.0.0", diff --git a/packages/snap-account-service/src/SnapAccountService.ts b/packages/snap-account-service/src/SnapAccountService.ts index 7934e0eb23..a12cf3229b 100644 --- a/packages/snap-account-service/src/SnapAccountService.ts +++ b/packages/snap-account-service/src/SnapAccountService.ts @@ -709,8 +709,10 @@ export class SnapAccountService { accounts: AccountId[], ): Promise { if (keyring.v1) { + console.log('@@ SnapAccountService: setSelectedAccounts for v1 snap', { snapId, accounts }); await keyring.v1.setSelectedAccounts(accounts); } else { + console.log('@@ SnapAccountService: setSelectedAccounts for v2 snap @@', { snapId, accounts }); await this.#client.withSnapId(snapId).setSelectedAccounts(accounts); } } diff --git a/yarn.lock b/yarn.lock index 67847b9ffa..b6c144e722 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5427,7 +5427,7 @@ __metadata: "@metamask/accounts-controller": "npm:^39.0.4" "@metamask/auto-changelog": "npm:^6.1.0" "@metamask/base-controller": "npm:^9.1.0" - "@metamask/keyring-api": "npm:^23.3.0" + "@metamask/keyring-api": "npm:^23.5.0" "@metamask/keyring-controller": "npm:^27.1.0" "@metamask/messenger": "npm:^2.0.0" "@metamask/multichain-account-service": "npm:^11.1.0" @@ -5464,8 +5464,8 @@ __metadata: "@metamask/auto-changelog": "npm:^6.1.0" "@metamask/base-controller": "npm:^9.1.0" "@metamask/controller-utils": "npm:^12.3.0" - "@metamask/eth-snap-keyring": "npm:^22.3.0" - "@metamask/keyring-api": "npm:^23.3.0" + "@metamask/eth-snap-keyring": "npm:^23.0.0" + "@metamask/keyring-api": "npm:^23.5.0" "@metamask/keyring-controller": "npm:^27.1.0" "@metamask/keyring-internal-api": "npm:^11.0.1" "@metamask/keyring-sdk": "npm:^2.2.0" @@ -5675,10 +5675,10 @@ __metadata: "@metamask/client-controller": "npm:^1.0.1" "@metamask/controller-utils": "npm:^12.3.0" "@metamask/core-backend": "npm:^6.5.0" - "@metamask/keyring-api": "npm:^23.3.0" + "@metamask/keyring-api": "npm:^23.5.0" "@metamask/keyring-controller": "npm:^27.1.0" "@metamask/keyring-internal-api": "npm:^11.0.1" - "@metamask/keyring-snap-client": "npm:^9.0.2" + "@metamask/keyring-snap-client": "npm:^9.2.0" "@metamask/messenger": "npm:^2.0.0" "@metamask/network-controller": "npm:^34.0.0" "@metamask/network-enablement-controller": "npm:^5.4.1" @@ -5730,10 +5730,10 @@ __metadata: "@metamask/core-backend": "npm:^6.5.0" "@metamask/eth-query": "npm:^4.0.0" "@metamask/ethjs-provider-http": "npm:^0.3.0" - "@metamask/keyring-api": "npm:^23.3.0" + "@metamask/keyring-api": "npm:^23.5.0" "@metamask/keyring-controller": "npm:^27.1.0" "@metamask/keyring-internal-api": "npm:^11.0.1" - "@metamask/keyring-snap-client": "npm:^9.0.2" + "@metamask/keyring-snap-client": "npm:^9.2.0" "@metamask/messenger": "npm:^2.0.0" "@metamask/metamask-eth-abis": "npm:^3.1.1" "@metamask/multichain-account-service": "npm:^11.1.0" @@ -5943,7 +5943,7 @@ __metadata: "@metamask/controller-utils": "npm:^12.3.0" "@metamask/eth-json-rpc-provider": "npm:^6.0.1" "@metamask/gas-fee-controller": "npm:^26.2.4" - "@metamask/keyring-api": "npm:^23.3.0" + "@metamask/keyring-api": "npm:^23.5.0" "@metamask/messenger": "npm:^2.0.0" "@metamask/metamask-eth-abis": "npm:^3.1.1" "@metamask/multichain-network-controller": "npm:^3.2.1" @@ -6460,7 +6460,7 @@ __metadata: "@metamask/auto-changelog": "npm:^6.1.0" "@metamask/base-controller": "npm:^9.1.0" "@metamask/controller-utils": "npm:^12.3.0" - "@metamask/keyring-api": "npm:^23.3.0" + "@metamask/keyring-api": "npm:^23.5.0" "@metamask/messenger": "npm:^2.0.0" "@metamask/network-controller": "npm:^34.0.0" "@metamask/stake-sdk": "npm:^3.2.1" @@ -6814,18 +6814,17 @@ __metadata: languageName: node linkType: hard -"@metamask/eth-snap-keyring@npm:@metamask-previews/eth-snap-keyring@22.4.0-5951828": - version: 22.4.0-5951828 - resolution: "@metamask-previews/eth-snap-keyring@npm:22.4.0-5951828" +"@metamask/eth-snap-keyring@npm:^23.0.0": + version: 23.0.0 + resolution: "@metamask/eth-snap-keyring@npm:23.0.0" dependencies: "@ethereumjs/tx": "npm:^5.4.0" "@metamask/eth-sig-util": "npm:^8.2.0" - "@metamask/keyring-api": "npm:23.4.0" - "@metamask/keyring-internal-api": "npm:11.0.1" - "@metamask/keyring-internal-snap-client": "npm:10.0.4" - "@metamask/keyring-sdk": "npm:2.2.0" - "@metamask/keyring-snap-sdk": "npm:9.1.0" - "@metamask/keyring-utils": "npm:3.3.1" + "@metamask/keyring-internal-api": "npm:^11.0.1" + "@metamask/keyring-internal-snap-client": "npm:^10.0.5" + "@metamask/keyring-sdk": "npm:^2.2.0" + "@metamask/keyring-snap-sdk": "npm:^9.2.0" + "@metamask/keyring-utils": "npm:^3.3.1" "@metamask/messenger": "npm:^1.1.1" "@metamask/snaps-controllers": "npm:^19.0.1" "@metamask/snaps-sdk": "npm:^11.0.0" @@ -6835,7 +6834,9 @@ __metadata: "@types/uuid": "npm:^9.0.8" async-mutex: "npm:^0.5.0" uuid: "npm:^9.0.1" - checksum: 10/b2dbd33b28a845e2e5234a879c18a6a99cc3cd257ab6b121c64469eb78a2bcb36087b10c752ee921d08a48f765636631ebec6132eb09005939469992717b2230 + peerDependencies: + "@metamask/keyring-api": ^23.0.0 + checksum: 10/a18dfe471ea048c4b4af9f554816df6330e8c859f3b382a0d0c61a546c22af832b6815054b2483c96505d442c38d5959a3d2d945ff3f794ab02fa31b5e62aa16 languageName: node linkType: hard @@ -7177,15 +7178,15 @@ __metadata: languageName: node linkType: hard -"@metamask/keyring-api@npm:@metamask-previews/keyring-api@23.4.0-5951828": - version: 23.4.0-5951828 - resolution: "@metamask-previews/keyring-api@npm:23.4.0-5951828" +"@metamask/keyring-api@npm:^23.1.0, @metamask/keyring-api@npm:^23.2.0, @metamask/keyring-api@npm:^23.5.0": + version: 23.5.0 + resolution: "@metamask/keyring-api@npm:23.5.0" dependencies: - "@metamask/keyring-utils": "npm:3.3.1" + "@metamask/keyring-utils": "npm:^3.3.1" "@metamask/superstruct": "npm:^3.3.0" "@metamask/utils": "npm:^11.11.0" bitcoin-address-validation: "npm:^2.2.3" - checksum: 10/4b378bc4b2ddd3affe51c4ec06aaa979ebcc7786488b04ad5cc6af44255b50af4d55f216d18739249c6a52a96a8110fc382261fdd5426d0bc0b1f0ea6557cad3 + checksum: 10/2d8a4b378f5bad77284feb58bc7f3b64d541751527c693e43f7e4d860333bc55593e27fc5264fc908d719912b374f77dabec3f5e491bb49f72501fd2f5c43e68 languageName: node linkType: hard @@ -7204,7 +7205,7 @@ __metadata: "@metamask/eth-hd-keyring": "npm:^14.1.1" "@metamask/eth-sig-util": "npm:^8.2.0" "@metamask/eth-simple-keyring": "npm:^12.0.2" - "@metamask/keyring-api": "npm:^23.3.0" + "@metamask/keyring-api": "npm:^23.5.0" "@metamask/keyring-internal-api": "npm:^11.0.1" "@metamask/keyring-utils": "npm:^3.3.1" "@metamask/messenger": "npm:^2.0.0" @@ -7229,31 +7230,31 @@ __metadata: languageName: unknown linkType: soft -"@metamask/keyring-internal-api@npm:@metamask-previews/keyring-internal-api@11.0.1-5951828": - version: 11.0.1-5951828 - resolution: "@metamask-previews/keyring-internal-api@npm:11.0.1-5951828" +"@metamask/keyring-internal-api@npm:^11.0.1": + version: 11.0.1 + resolution: "@metamask/keyring-internal-api@npm:11.0.1" dependencies: - "@metamask/keyring-api": "npm:23.4.0" - "@metamask/keyring-utils": "npm:3.3.1" - "@metamask/superstruct": "npm:^3.3.0" - checksum: 10/826576eb366fcf61b7e4536b68355bd87a5223d475d2f2eaa817f02c17105d2dd83e3c4a939c014237df70a4d4f2b7add74a1141ec3d775251c0eb2dfe94a33b + "@metamask/keyring-api": "npm:^23.1.0" + "@metamask/keyring-utils": "npm:^3.2.0" + "@metamask/superstruct": "npm:^3.1.0" + checksum: 10/d35a5b45887566f47e41c1109e9f4eca5108fd1b8402773d40a882c904de0f33cc70e0cc7d1e1a34397ae434646c597d9793c31dbedc83cdf64e41f8e719ccc4 languageName: node linkType: hard -"@metamask/keyring-internal-snap-client@npm:@metamask-previews/keyring-internal-snap-client@10.0.4-5951828": - version: 10.0.4-5951828 - resolution: "@metamask-previews/keyring-internal-snap-client@npm:10.0.4-5951828" +"@metamask/keyring-internal-snap-client@npm:^10.0.5": + version: 10.0.5 + resolution: "@metamask/keyring-internal-snap-client@npm:10.0.5" dependencies: - "@metamask/keyring-api": "npm:23.4.0" - "@metamask/keyring-internal-api": "npm:11.0.1" - "@metamask/keyring-snap-client": "npm:9.1.0" - "@metamask/keyring-utils": "npm:3.3.1" + "@metamask/keyring-api": "npm:^23.5.0" + "@metamask/keyring-internal-api": "npm:^11.0.1" + "@metamask/keyring-snap-client": "npm:^9.2.0" + "@metamask/keyring-utils": "npm:^3.3.1" "@metamask/messenger": "npm:^1.1.1" - checksum: 10/420bb2f414a12cc66c03b37a2969a8203aa6e9f4404696523c8e9e979f4e50b1b29a3878d709b1475fc52333dd0d2597efdd9600eb64d6b104b85f5a36605840 + checksum: 10/ae81aee3ed4ed75785d22fd57a7b169b7e540e208afb917640bd47eadbc30c52e64df2f05e3a429447951ac40da1c0a568e9d5c29a5d10b844c28ae919bf1d50 languageName: node linkType: hard -"@metamask/keyring-sdk@npm:2.2.0, @metamask/keyring-sdk@npm:^2.0.2, @metamask/keyring-sdk@npm:^2.2.0": +"@metamask/keyring-sdk@npm:^2.0.2, @metamask/keyring-sdk@npm:^2.2.0": version: 2.2.0 resolution: "@metamask/keyring-sdk@npm:2.2.0" dependencies: @@ -7271,39 +7272,39 @@ __metadata: languageName: node linkType: hard -"@metamask/keyring-snap-client@npm:@metamask-previews/keyring-snap-client@9.1.0-5951828": - version: 9.1.0-5951828 - resolution: "@metamask-previews/keyring-snap-client@npm:9.1.0-5951828" +"@metamask/keyring-snap-client@npm:^9.2.0": + version: 9.2.0 + resolution: "@metamask/keyring-snap-client@npm:9.2.0" dependencies: - "@metamask/keyring-api": "npm:23.4.0" - "@metamask/keyring-utils": "npm:3.3.1" + "@metamask/keyring-api": "npm:^23.5.0" + "@metamask/keyring-utils": "npm:^3.3.1" "@metamask/superstruct": "npm:^3.3.0" "@types/uuid": "npm:^9.0.8" uuid: "npm:^9.0.1" webextension-polyfill: "npm:^0.12.0" peerDependencies: "@metamask/providers": ^19.0.0 - checksum: 10/36a34a06e0eb65ba514d3c7339b4aa0db660790469649c493c3bb319d940285e828f1704833edc89ddd102a41d50ece12f8105ce710175cdcc4e0845550b00d4 + checksum: 10/2ccf7a36386f26f248f58527a1757d7a793488e27ad2d978cc15ffa98277f57941eed377360b7cf258cdc935de9311380fb46eb83242b3ff8959ad6e155b78b4 languageName: node linkType: hard -"@metamask/keyring-snap-sdk@npm:@metamask-previews/keyring-snap-sdk@9.1.0-5951828": - version: 9.1.0-5951828 - resolution: "@metamask-previews/keyring-snap-sdk@npm:9.1.0-5951828" +"@metamask/keyring-snap-sdk@npm:^9.2.0": + version: 9.2.0 + resolution: "@metamask/keyring-snap-sdk@npm:9.2.0" dependencies: - "@metamask/keyring-api": "npm:23.4.0" - "@metamask/keyring-utils": "npm:3.3.1" + "@metamask/keyring-utils": "npm:^3.3.1" "@metamask/snaps-sdk": "npm:^11.0.0" "@metamask/superstruct": "npm:^3.3.0" "@metamask/utils": "npm:^11.11.0" webextension-polyfill: "npm:^0.12.0" peerDependencies: + "@metamask/keyring-api": ^23.0.0 "@metamask/providers": ^19.0.0 - checksum: 10/f1793bc56a1dbe58ad5ca149845e22c378fa88531a134c3de38208af5e38ab500279937d8e84e45132ed03fb2df758c321ca243e86216e163934df5d0925c17a + checksum: 10/2fcce104e314f9dcea3125741d50875c26b4b9d0241f66568e513e221abd121af88f6842c2a459f7984d9147158936c58ae9936561d703429fbc4d2eccd9611c languageName: node linkType: hard -"@metamask/keyring-utils@npm:3.3.1, @metamask/keyring-utils@npm:^3.2.0, @metamask/keyring-utils@npm:^3.3.1": +"@metamask/keyring-utils@npm:^3.2.0, @metamask/keyring-utils@npm:^3.3.1": version: 3.3.1 resolution: "@metamask/keyring-utils@npm:3.3.1" dependencies: @@ -7487,7 +7488,7 @@ __metadata: "@metamask/auto-changelog": "npm:^6.1.0" "@metamask/base-controller": "npm:^9.1.0" "@metamask/eth-money-keyring": "npm:^2.0.4" - "@metamask/keyring-api": "npm:^23.3.0" + "@metamask/keyring-api": "npm:^23.5.0" "@metamask/keyring-controller": "npm:^27.1.0" "@metamask/keyring-utils": "npm:^3.3.1" "@metamask/messenger": "npm:^2.0.0" @@ -7544,12 +7545,12 @@ __metadata: "@metamask/base-controller": "npm:^9.1.0" "@metamask/controller-utils": "npm:^12.3.0" "@metamask/eth-hd-keyring": "npm:^14.1.1" - "@metamask/eth-snap-keyring": "npm:^22.3.0" + "@metamask/eth-snap-keyring": "npm:^23.0.0" "@metamask/key-tree": "npm:^10.1.1" - "@metamask/keyring-api": "npm:^23.3.0" + "@metamask/keyring-api": "npm:^23.5.0" "@metamask/keyring-controller": "npm:^27.1.0" "@metamask/keyring-internal-api": "npm:^11.0.1" - "@metamask/keyring-snap-client": "npm:^9.0.2" + "@metamask/keyring-snap-client": "npm:^9.2.0" "@metamask/keyring-utils": "npm:^3.3.1" "@metamask/messenger": "npm:^2.0.0" "@metamask/providers": "npm:^22.1.0" @@ -7620,7 +7621,7 @@ __metadata: "@metamask/auto-changelog": "npm:^6.1.0" "@metamask/base-controller": "npm:^9.1.0" "@metamask/controller-utils": "npm:^12.3.0" - "@metamask/keyring-api": "npm:^23.3.0" + "@metamask/keyring-api": "npm:^23.5.0" "@metamask/keyring-controller": "npm:^27.1.0" "@metamask/keyring-internal-api": "npm:^11.0.1" "@metamask/messenger": "npm:^2.0.0" @@ -7652,10 +7653,10 @@ __metadata: "@metamask/accounts-controller": "npm:^39.0.4" "@metamask/auto-changelog": "npm:^6.1.0" "@metamask/base-controller": "npm:^9.1.0" - "@metamask/keyring-api": "npm:^23.3.0" + "@metamask/keyring-api": "npm:^23.5.0" "@metamask/keyring-controller": "npm:^27.1.0" "@metamask/keyring-internal-api": "npm:^11.0.1" - "@metamask/keyring-snap-client": "npm:^9.0.2" + "@metamask/keyring-snap-client": "npm:^9.2.0" "@metamask/messenger": "npm:^2.0.0" "@metamask/polling-controller": "npm:^16.0.8" "@metamask/snaps-controllers": "npm:^19.0.0" @@ -7755,7 +7756,7 @@ __metadata: "@metamask/auto-changelog": "npm:^6.1.0" "@metamask/base-controller": "npm:^9.1.0" "@metamask/controller-utils": "npm:^12.3.0" - "@metamask/keyring-api": "npm:^23.3.0" + "@metamask/keyring-api": "npm:^23.5.0" "@metamask/messenger": "npm:^2.0.0" "@metamask/multichain-network-controller": "npm:^3.2.1" "@metamask/network-controller": "npm:^34.0.0" @@ -8143,7 +8144,7 @@ __metadata: "@metamask/address-book-controller": "npm:^7.1.2" "@metamask/auto-changelog": "npm:^6.1.0" "@metamask/base-controller": "npm:^9.1.0" - "@metamask/keyring-api": "npm:^23.3.0" + "@metamask/keyring-api": "npm:^23.5.0" "@metamask/keyring-controller": "npm:^27.1.0" "@metamask/keyring-internal-api": "npm:^11.0.1" "@metamask/messenger": "npm:^2.0.0" @@ -8528,11 +8529,11 @@ __metadata: "@metamask/account-api": "npm:^1.0.4" "@metamask/account-tree-controller": "npm:^7.5.3" "@metamask/auto-changelog": "npm:^6.1.0" - "@metamask/eth-snap-keyring": "npm:^22.3.0" - "@metamask/keyring-api": "npm:^23.3.0" + "@metamask/eth-snap-keyring": "npm:^23.0.0" + "@metamask/keyring-api": "npm:^23.5.0" "@metamask/keyring-controller": "npm:^27.1.0" - "@metamask/keyring-internal-snap-client": "npm:^10.0.4" - "@metamask/keyring-snap-sdk": "npm:^9.0.2" + "@metamask/keyring-internal-snap-client": "npm:^10.0.5" + "@metamask/keyring-snap-sdk": "npm:^9.2.0" "@metamask/keyring-utils": "npm:^3.3.1" "@metamask/messenger": "npm:^2.0.0" "@metamask/snaps-controllers": "npm:^19.0.0" From a5cabb747f158360d3dbefdd79f6fb7024b5013f Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Tue, 7 Jul 2026 12:15:30 +0200 Subject: [PATCH 15/24] chore: changelogs --- packages/account-tree-controller/CHANGELOG.md | 2 +- packages/accounts-controller/CHANGELOG.md | 2 ++ packages/assets-controller/CHANGELOG.md | 2 ++ packages/assets-controllers/CHANGELOG.md | 1 + packages/bridge-controller/CHANGELOG.md | 1 + packages/earn-controller/CHANGELOG.md | 1 + packages/keyring-controller/CHANGELOG.md | 2 +- packages/money-account-controller/CHANGELOG.md | 2 +- packages/multichain-account-service/CHANGELOG.md | 3 +++ packages/multichain-network-controller/CHANGELOG.md | 1 + packages/multichain-transactions-controller/CHANGELOG.md | 3 ++- packages/network-enablement-controller/CHANGELOG.md | 1 + packages/snap-account-service/CHANGELOG.md | 5 +++-- 13 files changed, 20 insertions(+), 6 deletions(-) diff --git a/packages/account-tree-controller/CHANGELOG.md b/packages/account-tree-controller/CHANGELOG.md index 26ad50d9ce..06bb22bf63 100644 --- a/packages/account-tree-controller/CHANGELOG.md +++ b/packages/account-tree-controller/CHANGELOG.md @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - The `Multichain Account Syncing - Full` trace is now only emitted when a backup-and-sync run actually mutates local or remote state (rolled-back local changes do not count) ([#9343](https://github.com/MetaMask/core/pull/9343)) -- Bump `@metamask/keyring-api` from `^23.1.0` to `^23.3.0` ([#9249](https://github.com/MetaMask/core/pull/9249)) +- Bump `@metamask/keyring-api` from `^23.1.0` to `^23.5.0` ([#9249](https://github.com/MetaMask/core/pull/9249), [#9390](https://github.com/MetaMask/core/pull/9390)) - Bump `@metamask/multichain-account-service` from `^11.0.0` to `^11.1.0` ([#9264](https://github.com/MetaMask/core/pull/9264)) - Bump `@metamask/accounts-controller` from `^39.0.3` to `^39.0.4` ([#9349](https://github.com/MetaMask/core/pull/9349)) - Bump `@metamask/messenger` from `^1.2.0` to `^2.0.0` ([#9392](https://github.com/MetaMask/core/pull/9392)) diff --git a/packages/accounts-controller/CHANGELOG.md b/packages/accounts-controller/CHANGELOG.md index 33d328b5ea..df1f2aedf7 100644 --- a/packages/accounts-controller/CHANGELOG.md +++ b/packages/accounts-controller/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Bump `@metamask/messenger` from `^1.2.0` to `^2.0.0` ([#9392](https://github.com/MetaMask/core/pull/9392)) +- Bump `@metamask/eth-snap-keyring` from `^22.3.0` to `^23.0.0` ([#9390](https://github.com/MetaMask/core/pull/9390)) +- Bump `@metamask/keyring-api` from `^23.3.0` to `^23.5.0` ([#9390](https://github.com/MetaMask/core/pull/9390)) ## [39.0.4] diff --git a/packages/assets-controller/CHANGELOG.md b/packages/assets-controller/CHANGELOG.md index cf2afd1720..6b57a800cd 100644 --- a/packages/assets-controller/CHANGELOG.md +++ b/packages/assets-controller/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Bump `@metamask/messenger` from `^1.2.0` to `^2.0.0` ([#9392](https://github.com/MetaMask/core/pull/9392)) +- Bump `@metamask/keyring-api` from `^23.3.0` to `^23.5.0` ([#9390](https://github.com/MetaMask/core/pull/9390)) +- Bump `@metamask/keyring-snap-client` from `^9.0.2` to `^9.2.0` ([#9390](https://github.com/MetaMask/core/pull/9390)) ### Fixed diff --git a/packages/assets-controllers/CHANGELOG.md b/packages/assets-controllers/CHANGELOG.md index 77ec42923a..ea2d4ed6bf 100644 --- a/packages/assets-controllers/CHANGELOG.md +++ b/packages/assets-controllers/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Bump `@metamask/messenger` from `^1.2.0` to `^2.0.0` ([#9392](https://github.com/MetaMask/core/pull/9392)) +- Bump `@metamask/keyring-api` from `^23.3.0` to `^23.5.0` ([#9390](https://github.com/MetaMask/core/pull/9390)) ## [109.3.0] diff --git a/packages/bridge-controller/CHANGELOG.md b/packages/bridge-controller/CHANGELOG.md index 78bde62db2..836c3b9f7b 100644 --- a/packages/bridge-controller/CHANGELOG.md +++ b/packages/bridge-controller/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Bump `@metamask/messenger` from `^1.2.0` to `^2.0.0` ([#9392](https://github.com/MetaMask/core/pull/9392)) +- Bump `@metamask/keyring-api` from `^23.3.0` to `^23.5.0` ([#9390](https://github.com/MetaMask/core/pull/9390)) ## [77.3.2] diff --git a/packages/earn-controller/CHANGELOG.md b/packages/earn-controller/CHANGELOG.md index 5cd393ca42..2046faf0e2 100644 --- a/packages/earn-controller/CHANGELOG.md +++ b/packages/earn-controller/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Bump `@metamask/messenger` from `^1.2.0` to `^2.0.0` ([#9392](https://github.com/MetaMask/core/pull/9392)) +- Bump `@metamask/keyring-api` from `^23.3.0` to `^23.5.0` ([#9390](https://github.com/MetaMask/core/pull/9390)) ## [12.2.2] diff --git a/packages/keyring-controller/CHANGELOG.md b/packages/keyring-controller/CHANGELOG.md index 9369c3251d..58bd9075d9 100644 --- a/packages/keyring-controller/CHANGELOG.md +++ b/packages/keyring-controller/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Bump `@metamask/keyring-api` from `^23.1.0` to `^23.3.0` ([#9249](https://github.com/MetaMask/core/pull/9249)) +- Bump `@metamask/keyring-api` from `^23.1.0` to `^23.5.0` ([#9249](https://github.com/MetaMask/core/pull/9249), [#9390](https://github.com/MetaMask/core/pull/9390)) - Bump `@metamask/messenger` from `^1.2.0` to `^2.0.0` ([#9392](https://github.com/MetaMask/core/pull/9392)) ## [27.1.0] diff --git a/packages/money-account-controller/CHANGELOG.md b/packages/money-account-controller/CHANGELOG.md index d317c727db..b1f15b0d95 100644 --- a/packages/money-account-controller/CHANGELOG.md +++ b/packages/money-account-controller/CHANGELOG.md @@ -12,7 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bump `@metamask/utils` from `^11.9.0` to `^11.11.0` ([#9074](https://github.com/MetaMask/core/pull/9074)) - Bump `@metamask/keyring-controller` from `^27.0.0` to `^27.1.0` ([#9129](https://github.com/MetaMask/core/pull/9129)) - Bump `@metamask/accounts-controller` from `^39.0.1` to `^39.0.4` ([#9218](https://github.com/MetaMask/core/pull/9218), [#9231](https://github.com/MetaMask/core/pull/9231), [#9349](https://github.com/MetaMask/core/pull/9349)) -- Bump `@metamask/keyring-api` from `^23.1.0` to `^23.3.0` ([#9249](https://github.com/MetaMask/core/pull/9249)) +- Bump `@metamask/keyring-api` from `^23.1.0` to `^23.5.0` ([#9249](https://github.com/MetaMask/core/pull/9249), [#9390](https://github.com/MetaMask/core/pull/9390)) - Bump `@metamask/messenger` from `^1.2.0` to `^2.0.0` ([#9392](https://github.com/MetaMask/core/pull/9392)) ## [0.3.3] diff --git a/packages/multichain-account-service/CHANGELOG.md b/packages/multichain-account-service/CHANGELOG.md index a7dad1c244..c10f938baf 100644 --- a/packages/multichain-account-service/CHANGELOG.md +++ b/packages/multichain-account-service/CHANGELOG.md @@ -18,6 +18,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Wallet alignment now only creates the missing `(provider, group index)` pairs instead of re-creating the whole range for every provider, avoiding redundant `createAccounts` calls (and their traces) ([#9269](https://github.com/MetaMask/core/pull/9269)) - Bump `@metamask/accounts-controller` from `^39.0.3` to `^39.0.4` ([#9349](https://github.com/MetaMask/core/pull/9349)) - Bump `@metamask/messenger` from `^1.2.0` to `^2.0.0` ([#9392](https://github.com/MetaMask/core/pull/9392)) +- Bump `@metamask/eth-snap-keyring` from `^22.3.0` to `^23.0.0` ([#9390](https://github.com/MetaMask/core/pull/9390)) +- Bump `@metamask/keyring-api` from `^23.3.0` to `^23.5.0` ([#9390](https://github.com/MetaMask/core/pull/9390)) +- Bump `@metamask/keyring-snap-client` from `^9.0.2` to `^9.2.0` ([#9390](https://github.com/MetaMask/core/pull/9390)) ## [11.1.0] diff --git a/packages/multichain-network-controller/CHANGELOG.md b/packages/multichain-network-controller/CHANGELOG.md index e8fb53d5df..485da1b7f3 100644 --- a/packages/multichain-network-controller/CHANGELOG.md +++ b/packages/multichain-network-controller/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Bump `@metamask/messenger` from `^1.2.0` to `^2.0.0` ([#9392](https://github.com/MetaMask/core/pull/9392)) +- Bump `@metamask/keyring-api` from `^23.3.0` to `^23.5.0` ([#9390](https://github.com/MetaMask/core/pull/9390)) ## [3.2.1] diff --git a/packages/multichain-transactions-controller/CHANGELOG.md b/packages/multichain-transactions-controller/CHANGELOG.md index 43ed7e1fd5..8224c4ace2 100644 --- a/packages/multichain-transactions-controller/CHANGELOG.md +++ b/packages/multichain-transactions-controller/CHANGELOG.md @@ -12,8 +12,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bump `@metamask/utils` from `^11.9.0` to `^11.11.0` ([#9074](https://github.com/MetaMask/core/pull/9074)) - Bump `@metamask/accounts-controller` from `^39.0.0` to `^39.0.4` ([#9058](https://github.com/MetaMask/core/pull/9058), [#9218](https://github.com/MetaMask/core/pull/9218), [#9231](https://github.com/MetaMask/core/pull/9231), [#9349](https://github.com/MetaMask/core/pull/9349)) - Bump `@metamask/polling-controller` from `^16.0.6` to `^16.0.8` ([#9218](https://github.com/MetaMask/core/pull/9218), [#9349](https://github.com/MetaMask/core/pull/9349)) -- Bump `@metamask/keyring-api` from `^23.1.0` to `^23.3.0` ([#9249](https://github.com/MetaMask/core/pull/9249)) +- Bump `@metamask/keyring-api` from `^23.1.0` to `^23.5.0` ([#9249](https://github.com/MetaMask/core/pull/9249), [#9390](https://github.com/MetaMask/core/pull/9390)) - Bump `@metamask/messenger` from `^1.2.0` to `^2.0.0` ([#9392](https://github.com/MetaMask/core/pull/9392)) +- Bump `@metamask/keyring-snap-client` from `^9.0.2` to `^9.2.0` ([#9390](https://github.com/MetaMask/core/pull/9390)) ## [7.1.1] diff --git a/packages/network-enablement-controller/CHANGELOG.md b/packages/network-enablement-controller/CHANGELOG.md index b445099204..580c0c0ee4 100644 --- a/packages/network-enablement-controller/CHANGELOG.md +++ b/packages/network-enablement-controller/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Restore Stellar to the Network Enablement Controller enabled network map by reverting the temporary rollback, as the corresponding Extension issue has already been resolved ([#9385](https://github.com/MetaMask/core/pull/9385)) - Bump `@metamask/messenger` from `^1.2.0` to `^2.0.0` ([#9392](https://github.com/MetaMask/core/pull/9392)) +- Bump `@metamask/keyring-api` from `^23.3.0` to `^23.5.0` ([#9390](https://github.com/MetaMask/core/pull/9390)) ## [5.4.1] diff --git a/packages/snap-account-service/CHANGELOG.md b/packages/snap-account-service/CHANGELOG.md index 3bb1ef7ece..f98732ac72 100644 --- a/packages/snap-account-service/CHANGELOG.md +++ b/packages/snap-account-service/CHANGELOG.md @@ -17,9 +17,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Bump `@metamask/keyring-api` from `^23.1.0` to `^23.3.0` ([#9249](https://github.com/MetaMask/core/pull/9249)) -- Bump `@metamask/keyring-snap-sdk` from `^9.0.1` to `^9.0.2` ([#9249](https://github.com/MetaMask/core/pull/9249)) +- Bump `@metamask/keyring-api` from `^23.1.0` to `^23.5.0` ([#9249](https://github.com/MetaMask/core/pull/9249), [#9390](https://github.com/MetaMask/core/pull/9390)) +- Bump `@metamask/keyring-snap-sdk` from `^9.0.1` to `^9.2.0` ([#9249](https://github.com/MetaMask/core/pull/9249), [#9390](https://github.com/MetaMask/core/pull/9390)) - Bump `@metamask/messenger` from `^1.2.0` to `^2.0.0` ([#9392](https://github.com/MetaMask/core/pull/9392)) +- Bump `@metamask/eth-snap-keyring` from `^22.3.0` to `^23.0.0` ([#9390](https://github.com/MetaMask/core/pull/9390)) ## [1.0.0] From ef8e6eb41ca0d0f51e36e18e52e593cdb16c4182 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Tue, 7 Jul 2026 12:17:32 +0200 Subject: [PATCH 16/24] fix: remove logs + add comment for setSelectedAccounts --- packages/snap-account-service/src/SnapAccountService.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/snap-account-service/src/SnapAccountService.ts b/packages/snap-account-service/src/SnapAccountService.ts index a12cf3229b..4c51290c46 100644 --- a/packages/snap-account-service/src/SnapAccountService.ts +++ b/packages/snap-account-service/src/SnapAccountService.ts @@ -709,10 +709,9 @@ export class SnapAccountService { accounts: AccountId[], ): Promise { if (keyring.v1) { - console.log('@@ SnapAccountService: setSelectedAccounts for v1 snap', { snapId, accounts }); + // We used to keep track of selected accounts in the v1 keyring, so we need to forward the call there for v1 Snaps. await keyring.v1.setSelectedAccounts(accounts); } else { - console.log('@@ SnapAccountService: setSelectedAccounts for v2 snap @@', { snapId, accounts }); await this.#client.withSnapId(snapId).setSelectedAccounts(accounts); } } From 3065189c800c5747002801a39517afdd7dee8c2f Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Tue, 7 Jul 2026 12:26:38 +0200 Subject: [PATCH 17/24] chore: yarn.lock --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 6e702285a8..9c85fb6928 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7185,7 +7185,7 @@ __metadata: languageName: node linkType: hard -"@metamask/keyring-api@npm:^23.1.0, @metamask/keyring-api@npm:^23.2.0, @metamask/keyring-api@npm:^23.5.0": +"@metamask/keyring-api@npm:^23.1.0, @metamask/keyring-api@npm:^23.2.0, @metamask/keyring-api@npm:^23.3.0, @metamask/keyring-api@npm:^23.5.0": version: 23.5.0 resolution: "@metamask/keyring-api@npm:23.5.0" dependencies: From 5dc0317fe147550b6809301f18baef597ef77129 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Tue, 7 Jul 2026 12:45:11 +0200 Subject: [PATCH 18/24] fix: fix missing keyring-api bump --- packages/client-utils/package.json | 2 +- yarn.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/client-utils/package.json b/packages/client-utils/package.json index 62e22bed77..8a41db06a9 100644 --- a/packages/client-utils/package.json +++ b/packages/client-utils/package.json @@ -56,7 +56,7 @@ "@metamask/contract-metadata": "^2.4.0", "@metamask/controller-utils": "^12.3.0", "@metamask/core-backend": "^6.5.0", - "@metamask/keyring-api": "^23.3.0", + "@metamask/keyring-api": "^23.5.0", "@metamask/transaction-controller": "^68.2.2", "@metamask/utils": "^11.11.0" }, diff --git a/yarn.lock b/yarn.lock index 9c85fb6928..68c6244697 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6134,7 +6134,7 @@ __metadata: "@metamask/contract-metadata": "npm:^2.4.0" "@metamask/controller-utils": "npm:^12.3.0" "@metamask/core-backend": "npm:^6.5.0" - "@metamask/keyring-api": "npm:^23.3.0" + "@metamask/keyring-api": "npm:^23.5.0" "@metamask/transaction-controller": "npm:^68.2.2" "@metamask/utils": "npm:^11.11.0" "@ts-bridge/cli": "npm:^0.6.4" @@ -7185,7 +7185,7 @@ __metadata: languageName: node linkType: hard -"@metamask/keyring-api@npm:^23.1.0, @metamask/keyring-api@npm:^23.2.0, @metamask/keyring-api@npm:^23.3.0, @metamask/keyring-api@npm:^23.5.0": +"@metamask/keyring-api@npm:^23.1.0, @metamask/keyring-api@npm:^23.2.0, @metamask/keyring-api@npm:^23.5.0": version: 23.5.0 resolution: "@metamask/keyring-api@npm:23.5.0" dependencies: From 803c8d6479020ac1f3b14431077b156e3d450afe Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Tue, 7 Jul 2026 12:53:18 +0200 Subject: [PATCH 19/24] fix: only use unsafe v1 createAccount for v1 keyrings, wrap other operations in withKeyringV2 --- .../src/providers/SnapAccountProvider.ts | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/packages/multichain-account-service/src/providers/SnapAccountProvider.ts b/packages/multichain-account-service/src/providers/SnapAccountProvider.ts index 5079d632cb..b53d864aed 100644 --- a/packages/multichain-account-service/src/providers/SnapAccountProvider.ts +++ b/packages/multichain-account-service/src/providers/SnapAccountProvider.ts @@ -163,20 +163,28 @@ export abstract class SnapAccountProvider extends BaseBip44AccountProvider { // Also, creating account that way won't invalidate the Snap keyring state. The // account will get created and persisted properly with the Snap account creation // flow "asynchronously" (with `notify:accountCreated`). - return this.#withSnapKeyring(async ({ keyring }) => { - let v1: RestrictedSnapKeyring['v1']; + const createAccount = await this.#withSnapKeyring(async ({ keyring }) => { if (keyring.v1) { - v1 = { - createAccount: keyring.v1.createAccount.bind(keyring.v1), - }; + return keyring.v1.createAccount.bind(keyring.v1); } - return { - v1, - createAccounts: keyring.createAccounts.bind(keyring), - deleteAccount: keyring.deleteAccount.bind(keyring), - }; + // This method does not exist in v2. + return undefined; }); + + return { + // V1 interface is only present for v1 Snaps, otherwise it's `undefined`. + v1: createAccount ? { createAccount } : undefined, + // Every v2 operations must be done through the `#withSnapKeyring` transaction: + createAccounts: async (options) => + await this.#withSnapKeyring( + async ({ keyring }) => await keyring.createAccounts(options), + ), + deleteAccount: async (id: string) => + await this.#withSnapKeyring(async ({ keyring }) => { + await keyring.deleteAccount(id); + }), + }; } #getKeyringClientFromSnapId(snapId: string): KeyringClient { From 3a27accda6db7fd4351f8846ef6e6d41379d7d09 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Tue, 7 Jul 2026 12:56:51 +0200 Subject: [PATCH 20/24] chore: changelogs --- packages/client-utils/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/client-utils/CHANGELOG.md b/packages/client-utils/CHANGELOG.md index fcf590480c..7c51d61271 100644 --- a/packages/client-utils/CHANGELOG.md +++ b/packages/client-utils/CHANGELOG.md @@ -16,4 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `mapLocalTransaction` for mapping local transaction groups to activity items - Shared activity types (`ActivityItem`, `ActivityKind`, `Status`, etc.) +### Changed + +- Bump `@metamask/keyring-api` from `^23.3.0` to `^23.5.0` ([#9390](https://github.com/MetaMask/core/pull/9390)) + [Unreleased]: https://github.com/MetaMask/core/ From 3c825b54c739aa8ea24219752062f6eb15c27f6b Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Tue, 7 Jul 2026 16:37:14 +0200 Subject: [PATCH 21/24] refactor: use import type Co-authored-by: Hassan Malik <41640681+hmalik88@users.noreply.github.com> --- .../src/providers/SnapAccountProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/multichain-account-service/src/providers/SnapAccountProvider.ts b/packages/multichain-account-service/src/providers/SnapAccountProvider.ts index b53d864aed..8f4631c1cc 100644 --- a/packages/multichain-account-service/src/providers/SnapAccountProvider.ts +++ b/packages/multichain-account-service/src/providers/SnapAccountProvider.ts @@ -1,7 +1,7 @@ import { assertIsBip44Account } from '@metamask/account-api'; import type { Bip44Account } from '@metamask/account-api'; import type { TraceCallback, TraceRequest } from '@metamask/controller-utils'; -import { SnapKeyringV1 } from '@metamask/eth-snap-keyring'; +import type { SnapKeyringV1 } from '@metamask/eth-snap-keyring'; import type { SnapKeyring as SnapKeyringV2 } from '@metamask/eth-snap-keyring/v2'; import { isSnapKeyring } from '@metamask/eth-snap-keyring/v2'; import { From 94b00b0c5e19d633b883ff6de7a438f16260394b Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Tue, 7 Jul 2026 16:37:32 +0200 Subject: [PATCH 22/24] chore: fix changelog Co-authored-by: Hassan Malik <41640681+hmalik88@users.noreply.github.com> --- packages/multichain-account-service/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/multichain-account-service/CHANGELOG.md b/packages/multichain-account-service/CHANGELOG.md index c10f938baf..9584312e2a 100644 --- a/packages/multichain-account-service/CHANGELOG.md +++ b/packages/multichain-account-service/CHANGELOG.md @@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- **BREAKING:** `RestrictedSnapKeyring.createAccount` has been replaced by `RestrictedSnapKeyring.v1`, which is `undefined` for v2-only Snaps ([#43446](https://github.com/MetaMask/core/pull/43446)) +- **BREAKING:** `RestrictedSnapKeyring.createAccount` has been replaced by `RestrictedSnapKeyring.v1`, which is `undefined` for v2-only Snaps ([#9390](https://github.com/MetaMask/core/pull/9390)) - Any subclass implementing `SnapAccountProvider.createAccountV1` must now check `keyring.v1` guard followed by `keyring.v1.createAccount(options)`. - Wallet alignment now only creates the missing `(provider, group index)` pairs instead of re-creating the whole range for every provider, avoiding redundant `createAccounts` calls (and their traces) ([#9269](https://github.com/MetaMask/core/pull/9269)) - Bump `@metamask/accounts-controller` from `^39.0.3` to `^39.0.4` ([#9349](https://github.com/MetaMask/core/pull/9349)) From 79242ae9a1cedec572b46829963461b8194fce56 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Tue, 7 Jul 2026 23:40:02 +0200 Subject: [PATCH 23/24] chore: changelog --- packages/bridge-controller/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bridge-controller/CHANGELOG.md b/packages/bridge-controller/CHANGELOG.md index ca29d15f0a..d1c49c50d8 100644 --- a/packages/bridge-controller/CHANGELOG.md +++ b/packages/bridge-controller/CHANGELOG.md @@ -10,8 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Bump `@metamask/messenger` from `^1.2.0` to `^2.0.0` ([#9392](https://github.com/MetaMask/core/pull/9392)) -- Bump `@metamask/keyring-api` from `^23.3.0` to `^23.5.0` ([#9390](https://github.com/MetaMask/core/pull/9390)) - Bump `@metamask/assets-controller` from `^10.0.1` to `^10.1.0` ([#9411](https://github.com/MetaMask/core/pull/9411)) +- Bump `@metamask/keyring-api` from `^23.3.0` to `^23.5.0` ([#9390](https://github.com/MetaMask/core/pull/9390)) ## [77.3.2] From 160b6bd6499fef5ccefafd123e1cb490427d4a79 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Tue, 7 Jul 2026 23:51:02 +0200 Subject: [PATCH 24/24] chore: fix assets-controller changelog --- packages/assets-controller/CHANGELOG.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/assets-controller/CHANGELOG.md b/packages/assets-controller/CHANGELOG.md index 7d382d3596..059b1ce5d0 100644 --- a/packages/assets-controller/CHANGELOG.md +++ b/packages/assets-controller/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Bump `@metamask/keyring-api` from `^23.3.0` to `^23.5.0` ([#9390](https://github.com/MetaMask/core/pull/9390)) +- Bump `@metamask/keyring-snap-client` from `^9.0.2` to `^9.2.0` ([#9390](https://github.com/MetaMask/core/pull/9390)) + ## [10.1.0] ### Added @@ -16,8 +21,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Bump `@metamask/messenger` from `^1.2.0` to `^2.0.0` ([#9392](https://github.com/MetaMask/core/pull/9392)) -- Bump `@metamask/keyring-api` from `^23.3.0` to `^23.5.0` ([#9390](https://github.com/MetaMask/core/pull/9390)) -- Bump `@metamask/keyring-snap-client` from `^9.0.2` to `^9.2.0` ([#9390](https://github.com/MetaMask/core/pull/9390)) ### Fixed