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/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/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/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/CHANGELOG.md b/packages/assets-controller/CHANGELOG.md index 55924b951e..3bebea71c0 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/transaction-controller` from `^68.2.2` to `^68.3.0` ([#9421](https://github.com/MetaMask/core/pull/9421)) +- 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] diff --git a/packages/assets-controller/package.json b/packages/assets-controller/package.json index 09c71c9b2a..ade191d715 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/CHANGELOG.md b/packages/assets-controllers/CHANGELOG.md index ec9ec161c8..95070f5353 100644 --- a/packages/assets-controllers/CHANGELOG.md +++ b/packages/assets-controllers/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bump `@metamask/messenger` from `^1.2.0` to `^2.0.0` ([#9392](https://github.com/MetaMask/core/pull/9392)) - Bump `@metamask/transaction-controller` from `^68.2.2` to `^68.3.0` ([#9421](https://github.com/MetaMask/core/pull/9421)) +- 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/assets-controllers/package.json b/packages/assets-controllers/package.json index 0ea548b04d..40d27344f2 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/CHANGELOG.md b/packages/bridge-controller/CHANGELOG.md index a851371371..e7147fb8a1 100644 --- a/packages/bridge-controller/CHANGELOG.md +++ b/packages/bridge-controller/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bump `@metamask/messenger` from `^1.2.0` to `^2.0.0` ([#9392](https://github.com/MetaMask/core/pull/9392)) - Bump `@metamask/assets-controller` from `^10.0.1` to `^10.1.0` ([#9411](https://github.com/MetaMask/core/pull/9411)) - Bump `@metamask/transaction-controller` from `^68.2.2` to `^68.3.0` ([#9421](https://github.com/MetaMask/core/pull/9421)) +- 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/bridge-controller/package.json b/packages/bridge-controller/package.json index 426966832a..58d4b11412 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/client-utils/CHANGELOG.md b/packages/client-utils/CHANGELOG.md index e6161e9ac5..b210185211 100644 --- a/packages/client-utils/CHANGELOG.md +++ b/packages/client-utils/CHANGELOG.md @@ -19,5 +19,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Bump `@metamask/transaction-controller` from `^68.2.2` to `^68.3.0` ([#9421](https://github.com/MetaMask/core/pull/9421)) +- 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/ diff --git a/packages/client-utils/package.json b/packages/client-utils/package.json index 0f4c44acaf..53b7776deb 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.3.0", "@metamask/utils": "^11.11.0" }, 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/earn-controller/package.json b/packages/earn-controller/package.json index 7941b192c2..6660986535 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/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/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/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/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/CHANGELOG.md b/packages/multichain-account-service/CHANGELOG.md index ffdd38b3a3..9584312e2a 100644 --- a/packages/multichain-account-service/CHANGELOG.md +++ b/packages/multichain-account-service/CHANGELOG.md @@ -13,9 +13,14 @@ 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 ([#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)) - 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-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-account-service/src/providers/BtcAccountProvider.test.ts b/packages/multichain-account-service/src/providers/BtcAccountProvider.test.ts index bde6eba947..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, @@ -132,6 +135,12 @@ class MockBtcKeyring { return account; }); }); + + deleteAccount = jest.fn().mockResolvedValue(undefined); + + get v1(): Required { + return { createAccount: this.createAccount }; + } } class MockBtcAccountProvider extends BtcAccountProvider { @@ -457,6 +466,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..4d2febe412 100644 --- a/packages/multichain-account-service/src/providers/SnapAccountProvider.test.ts +++ b/packages/multichain-account-service/src/providers/SnapAccountProvider.test.ts @@ -111,9 +111,14 @@ 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 +249,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..8f4631c1cc 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 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 { @@ -33,7 +34,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: SnapKeyringV1['createAccount'] }; createAccounts: SnapKeyringV2['createAccounts']; deleteAccount: SnapKeyringV2['deleteAccount']; }; @@ -154,25 +163,24 @@ 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), - })); + const createAccount = await this.#withSnapKeyring(async ({ keyring }) => { + if (keyring.v1) { + return keyring.v1.createAccount.bind(keyring.v1); + } + + // This method does not exist in v2. + return undefined; + }); 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), + // 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) => - // Though, when removing account, we can use the normal flow. await this.#withSnapKeyring(async ({ keyring }) => { await keyring.deleteAccount(id); }), diff --git a/packages/multichain-account-service/src/providers/SolAccountProvider.test.ts b/packages/multichain-account-service/src/providers/SolAccountProvider.test.ts index b2c424af5f..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, @@ -114,6 +117,12 @@ class MockSolanaKeyring { return account; }); }); + + deleteAccount = jest.fn().mockResolvedValue(undefined); + + get v1(): Required { + return { createAccount: this.createAccount }; + } } class MockSolAccountProvider extends SolAccountProvider { @@ -437,6 +446,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..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, @@ -103,6 +106,12 @@ class MockTronKeyring { // Add discoverAccounts method to match the provider's usage discoverAccounts = jest.fn().mockResolvedValue([]); + + deleteAccount = jest.fn().mockResolvedValue(undefined); + + get v1(): Required { + return { createAccount: this.createAccount }; + } } class MockTrxAccountProvider extends TrxAccountProvider { @@ -438,6 +447,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..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, @@ -49,9 +52,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 +110,12 @@ class MockStellarKeyring { }); discoverAccounts = jest.fn().mockResolvedValue([]); + + deleteAccount = jest.fn().mockResolvedValue(undefined); + + get v1(): Required { + return { createAccount: this.createAccount }; + } } class MockXlmAccountProvider extends XlmAccountProvider { @@ -207,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, }, @@ -311,6 +320,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, 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-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/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/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/CHANGELOG.md b/packages/network-enablement-controller/CHANGELOG.md index 82ee2b4627..8a25bf55e0 100644 --- a/packages/network-enablement-controller/CHANGELOG.md +++ b/packages/network-enablement-controller/CHANGELOG.md @@ -12,6 +12,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/transaction-controller` from `^68.2.2` to `^68.3.0` ([#9421](https://github.com/MetaMask/core/pull/9421)) +- 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/network-enablement-controller/package.json b/packages/network-enablement-controller/package.json index d9bb1bc2af..c6fb6a10e1 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/CHANGELOG.md b/packages/snap-account-service/CHANGELOG.md index 25324f0261..f98732ac72 100644 --- a/packages/snap-account-service/CHANGELOG.md +++ b/packages/snap-account-service/CHANGELOG.md @@ -7,11 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- **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 -- 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] diff --git a/packages/snap-account-service/package.json b/packages/snap-account-service/package.json index c072e2bffa..4205efa3c2 100644 --- a/packages/snap-account-service/package.json +++ b/packages/snap-account-service/package.json @@ -55,10 +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-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-method-action-types.ts b/packages/snap-account-service/src/SnapAccountService-method-action-types.ts index 8434c285eb..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,6 +50,73 @@ export type SnapAccountServiceEnsureMigratedAction = { handler: SnapAccountService['ensureMigrated']; }; +/** + * 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']; +}; + +/** + * 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']; +}; + /** * Handle a message from a Snap. * @@ -69,4 +136,9 @@ export type SnapAccountServiceMethodActions = | SnapAccountServiceGetSnapsAction | SnapAccountServiceEnsureReadyAction | SnapAccountServiceEnsureMigratedAction + | SnapAccountServiceGetAccountAssetsAction + | SnapAccountServiceGetAccountBalancesAction + | SnapAccountServiceGetAccountTransactionsAction + | SnapAccountServiceResolveAccountAddressAction + | SnapAccountServiceSetSelectedAccountsAction | SnapAccountServiceHandleKeyringSnapMessageAction; diff --git a/packages/snap-account-service/src/SnapAccountService.test.ts b/packages/snap-account-service/src/SnapAccountService.test.ts index 4beae87549..5b204ccb8c 100644 --- a/packages/snap-account-service/src/SnapAccountService.test.ts +++ b/packages/snap-account-service/src/SnapAccountService.test.ts @@ -6,6 +6,13 @@ 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'; @@ -19,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 { @@ -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', @@ -331,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 { @@ -406,6 +422,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 +444,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 +481,39 @@ 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; + setSelectedAccounts: jest.Mock; +} { + const clientMethods = { + getAccountAssets: jest.fn(), + getAccountBalances: jest.fn(), + getAccountTransactions: jest.fn(), + resolveAccountAddress: jest.fn(), + setSelectedAccounts: jest.fn(), + }; + ( + KeyringInternalSnapClient as jest.MockedClass< + typeof KeyringInternalSnapClient + > + ).mockImplementation( + () => + ({ + withSnapId: jest.fn().mockReturnValue(clientMethods), + }) as unknown as KeyringInternalSnapClient, + ); + return clientMethods; +} + describe('SnapAccountService', () => { describe('getSnaps', () => { it('exposes tracked Snaps seeded during construction', async () => { @@ -789,13 +843,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( @@ -807,6 +861,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( @@ -903,7 +970,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( @@ -920,7 +987,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(); @@ -942,7 +1009,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 @@ -1029,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], }, }); @@ -1059,7 +1126,7 @@ describe('SnapAccountService', () => { const setSelectedAccounts = jest.fn().mockResolvedValue(undefined); mockWithKeyringV2Unsafe(mocks, { [MOCK_SNAP_ID]: { - setSelectedAccounts, + v1: { setSelectedAccounts }, hasAccount: () => false, }, }); @@ -1080,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), @@ -1135,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); @@ -1156,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, @@ -1175,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)], @@ -1183,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), @@ -1214,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( @@ -1287,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, @@ -1322,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, @@ -1349,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(''); @@ -1372,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, @@ -1414,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, @@ -1436,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, @@ -1459,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(''); @@ -1486,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, @@ -1508,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, @@ -1526,4 +1618,333 @@ 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); + }); + }); + + 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 d3e369ad23..4c51290c46 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,19 @@ 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 +68,13 @@ import { projectLogger as log } from './logger'; import type { SnapAccountServiceEnsureReadyAction, SnapAccountServiceEnsureMigratedAction, + SnapAccountServiceGetAccountAssetsAction, + SnapAccountServiceGetAccountBalancesAction, + SnapAccountServiceGetAccountTransactionsAction, SnapAccountServiceGetSnapsAction, SnapAccountServiceHandleKeyringSnapMessageAction, + SnapAccountServiceResolveAccountAddressAction, + SnapAccountServiceSetSelectedAccountsAction, } from './SnapAccountService-method-action-types'; import { SnapPlatformWatcher } from './SnapPlatformWatcher'; import type { SnapPlatformWatcherConfig } from './SnapPlatformWatcher'; @@ -84,8 +102,13 @@ export const serviceName = 'SnapAccountService'; const MESSENGER_EXPOSED_METHODS = [ 'ensureMigrated', 'ensureReady', + 'getAccountAssets', + 'getAccountBalances', + 'getAccountTransactions', 'getSnaps', 'handleKeyringSnapMessage', + 'resolveAccountAddress', + 'setSelectedAccounts', ] as const; /** @@ -94,8 +117,13 @@ const MESSENGER_EXPOSED_METHODS = [ export type SnapAccountServiceActions = | SnapAccountServiceEnsureMigratedAction | SnapAccountServiceEnsureReadyAction + | SnapAccountServiceGetAccountAssetsAction + | SnapAccountServiceGetAccountBalancesAction + | SnapAccountServiceGetAccountTransactionsAction | SnapAccountServiceGetSnapsAction - | SnapAccountServiceHandleKeyringSnapMessageAction; + | SnapAccountServiceHandleKeyringSnapMessageAction + | SnapAccountServiceResolveAccountAddressAction + | SnapAccountServiceSetSelectedAccountsAction; /** * Actions from other messengers that {@link SnapAccountService} calls. @@ -104,6 +132,7 @@ type AllowedActions = | SnapControllerGetStateAction | SnapControllerGetSnapAction | SnapControllerGetRunnableSnapsAction + | SnapControllerHandleRequestAction | KeyringControllerGetStateAction | KeyringControllerWithControllerAction | KeyringControllerWithKeyringV2Action @@ -231,6 +260,8 @@ export class SnapAccountService { readonly #tracker: SnapTracker; + readonly #client: KeyringInternalSnapClient; + #migrated = false; #migratePromise: Promise | null = null; @@ -250,6 +281,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 +599,123 @@ 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); + } + + /** + * 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) { + // 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 { + await this.#client.withSnapId(snapId).setSelectedAccounts(accounts); + } + } + /** * Handle a message from a Snap. * @@ -623,9 +777,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( @@ -729,8 +893,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 6cda8e7a67..73409f011f 100644 --- a/packages/snap-account-service/src/index.ts +++ b/packages/snap-account-service/src/index.ts @@ -12,8 +12,13 @@ export type { export type { SnapAccountServiceEnsureMigratedAction, SnapAccountServiceEnsureReadyAction, + SnapAccountServiceGetAccountAssetsAction, + SnapAccountServiceGetAccountBalancesAction, + SnapAccountServiceGetAccountTransactionsAction, SnapAccountServiceGetSnapsAction, SnapAccountServiceHandleKeyringSnapMessageAction, + SnapAccountServiceResolveAccountAddressAction, + SnapAccountServiceSetSelectedAccountsAction, } from './SnapAccountService-method-action-types'; export { SnapPlatformWatcher } from './SnapPlatformWatcher'; export type { SnapPlatformWatcherConfig } from './SnapPlatformWatcher'; diff --git a/yarn.lock b/yarn.lock index 6100e0a8d4..1d70cbb70c 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" @@ -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.3.0" "@metamask/utils": "npm:^11.11.0" "@ts-bridge/cli": "npm:^0.6.4" @@ -6467,7 +6467,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" @@ -6821,29 +6821,29 @@ __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:^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-internal-api": "npm:^11.0.1" - "@metamask/keyring-internal-snap-client": "npm:^10.0.3" + "@metamask/keyring-internal-snap-client": "npm:^10.0.5" "@metamask/keyring-sdk": "npm:^2.2.0" - "@metamask/keyring-snap-sdk": "npm:^9.0.2" + "@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" - "@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/a18dfe471ea048c4b4af9f554816df6330e8c859f3b382a0d0c61a546c22af832b6815054b2483c96505d442c38d5959a3d2d945ff3f794ab02fa31b5e62aa16 languageName: node linkType: hard @@ -7185,15 +7185,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:^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/superstruct": "npm:^3.1.0" + "@metamask/superstruct": "npm:^3.3.0" "@metamask/utils": "npm:^11.11.0" bitcoin-address-validation: "npm:^2.2.3" - checksum: 10/c6ad3bf4294b09730d752d548b1cd0c1c622f357a12c7287af2b5889a01e99a4d20316cc9e96840d5c5619eee0f9305d4dfc03febd0bd136e72de95ce3fd0a1e + checksum: 10/2d8a4b378f5bad77284feb58bc7f3b64d541751527c693e43f7e4d860333bc55593e27fc5264fc908d719912b374f77dabec3f5e491bb49f72501fd2f5c43e68 languageName: node linkType: hard @@ -7212,7 +7212,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" @@ -7248,16 +7248,16 @@ __metadata: 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:^10.0.5": + version: 10.0.5 + resolution: "@metamask/keyring-internal-snap-client@npm:10.0.5" dependencies: - "@metamask/keyring-api": "npm:^23.1.0" + "@metamask/keyring-api": "npm:^23.5.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-snap-client": "npm:^9.2.0" + "@metamask/keyring-utils": "npm:^3.3.1" "@metamask/messenger": "npm:^1.1.1" - checksum: 10/30c00a2f39125e7f99bd6513096f23fa243f30cd7ce4e77aa96f52c66fa99c077dfba0544d2693e16c283a2a5610f6b844eaa39130d1f760390d6ca1f53f0507 + checksum: 10/ae81aee3ed4ed75785d22fd57a7b169b7e540e208afb917640bd47eadbc30c52e64df2f05e3a429447951ac40da1c0a568e9d5c29a5d10b844c28ae919bf1d50 languageName: node linkType: hard @@ -7279,35 +7279,35 @@ __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:^9.2.0": + version: 9.2.0 + resolution: "@metamask/keyring-snap-client@npm:9.2.0" 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.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/f4de52be334d941fffafb6458260875706a09315d68856577a66dd36b069de00260632cfcd0fe9b56528382512dee2c226f61430aafb619869f1b1711d3ffffd + checksum: 10/2ccf7a36386f26f248f58527a1757d7a793488e27ad2d978cc15ffa98277f57941eed377360b7cf258cdc935de9311380fb46eb83242b3ff8959ad6e155b78b4 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:^9.2.0": + version: 9.2.0 + resolution: "@metamask/keyring-snap-sdk@npm:9.2.0" dependencies: "@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/2fcce104e314f9dcea3125741d50875c26b4b9d0241f66568e513e221abd121af88f6842c2a459f7984d9147158936c58ae9936561d703429fbc4d2eccd9611c languageName: node linkType: hard @@ -7418,7 +7418,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: @@ -7519,7 +7519,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" @@ -7576,12 +7576,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" @@ -7652,7 +7652,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" @@ -7684,10 +7684,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" @@ -7787,7 +7787,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" @@ -8175,7 +8175,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" @@ -8560,10 +8560,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-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" @@ -8654,9 +8655,9 @@ __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" +"@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: "@metamask/key-tree": "npm:^10.1.1" "@metamask/providers": "npm:^22.1.1" @@ -8664,23 +8665,23 @@ __metadata: "@metamask/superstruct": "npm:^3.2.1" "@metamask/utils": "npm:^11.11.0" luxon: "npm:^3.5.0" - checksum: 10/138c616584d537b9976ae48123090ab5731848d79d5d1f4e979c797dfdfe061329cbf18a5e84d8bd068fe36d5b9d169337f6d74efab0736f30c31ddf4088f70b + 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" +"@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: "@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/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.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" @@ -8695,7 +8696,7 @@ __metadata: semver: "npm:^7.5.4" ses: "npm:^1.15.0" validate-npm-package-name: "npm:^5.0.0" - checksum: 10/0e7cb5a4deebad3dc98404486b7767be049e9f86173195e9b1ae577f197e67cdd392f1e05ebb146a199ffc3cd52013636637a4d1ac7f9fec164f3189be97df55 + checksum: 10/fda8caefa414d9755a2305b345b5bbbf30ee614215a49070449f314d5d906c645457cf0d7f4881a82f69d398aff5ad2fe9c648bbb2a5003c29c2d01e2f42f199 languageName: node linkType: hard @@ -8810,10 +8811,10 @@ __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 +"@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 languageName: node linkType: hard