diff --git a/package.json b/package.json index 79b52923..5e041374 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/accounts-monorepo", - "version": "20.0.0", + "version": "23.0.0", "private": true, "description": "Monorepo for MetaMask accounts related packages", "repository": { diff --git a/packages/keyring-api/CHANGELOG.md b/packages/keyring-api/CHANGELOG.md index a473f9f1..ab9b0c44 100644 --- a/packages/keyring-api/CHANGELOG.md +++ b/packages/keyring-api/CHANGELOG.md @@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [15.0.0] + +### Added + +- Add `account{AssetList,Balances,Transactions}UpdatedEventStruct` keyring events ([#154](https://github.com/MetaMask/accounts/pull/154)) + +### Changed + +- **BREAKING:** Make specific `*AccountStruct.scopes` more strict ([#159](https://github.com/MetaMask/accounts/pull/159)) + +## [14.0.0] + +### Added + +- Add `listAccountAssets` keyring method ([#148](https://github.com/MetaMask/accounts/pull/148)) + +### Changed + +- **BREAKING:** Make `CaipAssetType` type more restritive ([#150](https://github.com/MetaMask/accounts/pull/150)) + - It used to be a `string` but it has been restricted with a template literal type that matches CAIP-19 asset type. + ## [13.0.0] ### Added @@ -466,7 +487,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - SnapController keyring client. It is intended to be used by MetaMask to talk to the snap. - Helper functions to create keyring handler in the snap. -[Unreleased]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-api@13.0.0...HEAD +[Unreleased]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-api@15.0.0...HEAD +[15.0.0]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-api@14.0.0...@metamask/keyring-api@15.0.0 +[14.0.0]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-api@13.0.0...@metamask/keyring-api@14.0.0 [13.0.0]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-api@12.0.0...@metamask/keyring-api@13.0.0 [12.0.0]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-api@11.1.0...@metamask/keyring-api@12.0.0 [11.1.0]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-api@11.0.0...@metamask/keyring-api@11.1.0 diff --git a/packages/keyring-api/package.json b/packages/keyring-api/package.json index 3a9832b5..90131955 100644 --- a/packages/keyring-api/package.json +++ b/packages/keyring-api/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/keyring-api", - "version": "13.0.0", + "version": "15.0.0", "description": "MetaMask Keyring API", "keywords": [ "metamask", diff --git a/packages/keyring-api/src/api/account.ts b/packages/keyring-api/src/api/account.ts index 647909a2..0a09a66a 100644 --- a/packages/keyring-api/src/api/account.ts +++ b/packages/keyring-api/src/api/account.ts @@ -1,4 +1,4 @@ -import { object, UuidStruct } from '@metamask/keyring-utils'; +import { AccountIdStruct, object } from '@metamask/keyring-utils'; import type { Infer } from '@metamask/superstruct'; import { nonempty, @@ -56,7 +56,7 @@ export const KeyringAccountStruct = object({ /** * Account ID (UUIDv4). */ - id: UuidStruct, + id: AccountIdStruct, /** * Account type. @@ -74,7 +74,7 @@ export const KeyringAccountStruct = object({ address: string(), /** - * Account supported scopes (CAIP-2 chain IDs). + * Account supported scopes (CAIP-2 chain IDs or CAIP-2 namespaces). */ scopes: nonempty(array(union([CaipNamespaceStruct, CaipChainIdStruct]))), diff --git a/packages/keyring-api/src/api/asset.ts b/packages/keyring-api/src/api/asset.ts index f46623a7..154a3390 100644 --- a/packages/keyring-api/src/api/asset.ts +++ b/packages/keyring-api/src/api/asset.ts @@ -11,6 +11,21 @@ import { isPlainObject, } from '@metamask/utils'; +/** + * Fungible asset amount struct. + */ +export const FungibleAssetAmountStruct = object({ + /** + * Asset unit. + */ + unit: string(), + + /** + * Asset amount. + */ + amount: StringNumberStruct, +}); + /** * Fungible asset struct. */ @@ -25,15 +40,7 @@ export const FungibleAssetStruct = object({ */ type: CaipAssetTypeStruct, - /** - * Asset unit. - */ - unit: string(), - - /** - * Asset amount. - */ - amount: StringNumberStruct, + ...FungibleAssetAmountStruct.schema, }); /** diff --git a/packages/keyring-api/src/btc/constants.ts b/packages/keyring-api/src/btc/constants.ts index 954097ea..658611f6 100644 --- a/packages/keyring-api/src/btc/constants.ts +++ b/packages/keyring-api/src/btc/constants.ts @@ -3,7 +3,7 @@ /** * Scopes for Bitcoin account type. See {@link KeyringAccount.scopes}. */ -export enum BtcScopes { +export enum BtcScope { Namespace = 'bip122', Mainnet = 'bip122:000000000019d6689c085ae165831e93', Testnet = 'bip122:000000000933ea01ad0ee984209779ba', diff --git a/packages/keyring-api/src/btc/types.test.ts b/packages/keyring-api/src/btc/types.test.ts index 480a561a..329d0dd9 100644 --- a/packages/keyring-api/src/btc/types.test.ts +++ b/packages/keyring-api/src/btc/types.test.ts @@ -1,4 +1,20 @@ -import { BtcP2wpkhAddressStruct } from './types'; +import { BtcScope } from './constants'; +import type { BtcP2wpkhAccount } from './types'; +import { + BtcMethod, + BtcP2wpkhAccountStruct, + BtcP2wpkhAddressStruct, +} from './types'; +import { BtcAccountType } from '../api'; + +const MOCK_ACCOUNT = { + id: '55583f38-d81b-48f8-8494-fc543c2b5c95', + type: BtcAccountType.P2wpkh, + address: 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4', + methods: [BtcMethod.SendBitcoin], + options: {}, + scopes: [BtcScope.Mainnet], +}; describe('types', () => { describe('BtcP2wpkhAddressStruct', () => { @@ -37,5 +53,25 @@ describe('types', () => { `${errorPrefix}: No separator character for ${address}`, ); }); + + it('throws an error if there are multiple scopes', () => { + const account: BtcP2wpkhAccount = { + ...MOCK_ACCOUNT, + scopes: [BtcScope.Mainnet, BtcScope.Testnet], + }; + expect(() => BtcP2wpkhAccountStruct.assert(account)).toThrow( + 'At path: scopes -- Expected a array with a length of `1` but received one with a length of `2`', + ); + }); + + it('throws an error if there is no scope', () => { + const account: BtcP2wpkhAccount = { + ...MOCK_ACCOUNT, + scopes: [], + }; + expect(() => BtcP2wpkhAccountStruct.assert(account)).toThrow( + 'At path: scopes -- Expected a array with a length of `1` but received one with a length of `0`', + ); + }); }); }); diff --git a/packages/keyring-api/src/btc/types.ts b/packages/keyring-api/src/btc/types.ts index f610293d..d24f53cd 100644 --- a/packages/keyring-api/src/btc/types.ts +++ b/packages/keyring-api/src/btc/types.ts @@ -1,6 +1,14 @@ import { object } from '@metamask/keyring-utils'; import type { Infer } from '@metamask/superstruct'; -import { string, array, enums, refine, literal } from '@metamask/superstruct'; +import { + string, + array, + enums, + refine, + literal, + size, +} from '@metamask/superstruct'; +import { CaipChainIdStruct } from '@metamask/utils'; import { bech32 } from 'bech32'; import { BtcAccountType, KeyringAccountStruct } from '../api'; @@ -41,6 +49,13 @@ export const BtcP2wpkhAccountStruct = object({ */ type: literal(`${BtcAccountType.P2wpkh}`), + /** + * Account supported scope (CAIP-2 chain ID). + * + * NOTE: We consider a Bitcoin address to be valid on only 1 network at time. + */ + scopes: size(array(CaipChainIdStruct), 1), + /** * Account supported methods. */ diff --git a/packages/keyring-api/src/eth/constants.ts b/packages/keyring-api/src/eth/constants.ts index 2ba369d7..65c120f6 100644 --- a/packages/keyring-api/src/eth/constants.ts +++ b/packages/keyring-api/src/eth/constants.ts @@ -3,7 +3,8 @@ /** * Scopes for EVM account type. See {@link KeyringAccount.scopes}. */ -export enum EthScopes { +export enum EthScope { Namespace = 'eip155', Mainnet = 'eip155:1', + Testnet = 'eip155:11155111', } diff --git a/packages/keyring-api/src/eth/types.ts b/packages/keyring-api/src/eth/types.ts index e52993fa..f08b0240 100644 --- a/packages/keyring-api/src/eth/types.ts +++ b/packages/keyring-api/src/eth/types.ts @@ -1,8 +1,9 @@ import { object, definePattern } from '@metamask/keyring-utils'; import type { Infer } from '@metamask/superstruct'; import { nonempty, array, enums, literal } from '@metamask/superstruct'; +import { CaipChainIdStruct } from '@metamask/utils'; -import { EthScopes } from '.'; +import { EthScope } from '.'; import { EthAccountType, KeyringAccountStruct } from '../api'; export const EthBytesStruct = definePattern('EthBytes', /^0x[0-9a-f]*$/iu); @@ -50,7 +51,7 @@ export const EthEoaAccountStruct = object({ /** * Account scopes (must be ['eip155']). */ - scopes: nonempty(array(literal(EthScopes.Namespace))), + scopes: nonempty(array(literal(EthScope.Namespace))), /** * Account supported methods. @@ -82,6 +83,11 @@ export const EthErc4337AccountStruct = object({ */ type: literal(`${EthAccountType.Erc4337}`), + /** + * Account supported scopes (CAIP-2 chain IDs). + */ + scopes: nonempty(array(CaipChainIdStruct)), + /** * Account supported methods. */ diff --git a/packages/keyring-api/src/events.test.ts b/packages/keyring-api/src/events.test.ts index 4934f644..11d62f47 100644 --- a/packages/keyring-api/src/events.test.ts +++ b/packages/keyring-api/src/events.test.ts @@ -1,7 +1,7 @@ import { is } from '@metamask/superstruct'; import { EthAccountType } from './api'; -import { EthScopes } from './eth/constants'; +import { EthScope } from './eth/constants'; import { AccountCreatedEventStruct, AccountDeletedEventStruct, @@ -22,7 +22,7 @@ describe('events', () => { address: '0x0123', methods: [], options: {}, - scopes: [EthScopes.Namespace], + scopes: [EthScope.Namespace], type: EthAccountType.Eoa, }, }, @@ -40,7 +40,7 @@ describe('events', () => { address: '0x0123', methods: [], options: {}, - scopes: [EthScopes.Namespace], + scopes: [EthScope.Namespace], type: EthAccountType.Eoa, }, }, @@ -58,7 +58,7 @@ describe('events', () => { address: '0x0123', methods: [], options: {}, - scopes: [EthScopes.Namespace], + scopes: [EthScope.Namespace], type: EthAccountType.Eoa, }, displayConfirmation: true, @@ -79,7 +79,7 @@ describe('events', () => { address: '0x0123', methods: [], options: {}, - scopes: [EthScopes.Namespace], + scopes: [EthScope.Namespace], type: EthAccountType.Eoa, }, }, @@ -97,7 +97,7 @@ describe('events', () => { address: '0x0123', methods: [], options: {}, - scopes: [EthScopes.Namespace], + scopes: [EthScope.Namespace], type: EthAccountType.Eoa, }, }, diff --git a/packages/keyring-api/src/events.ts b/packages/keyring-api/src/events.ts index b5e433f6..c3c95295 100644 --- a/packages/keyring-api/src/events.ts +++ b/packages/keyring-api/src/events.ts @@ -1,8 +1,19 @@ -import { exactOptional, object, UuidStruct } from '@metamask/keyring-utils'; -import { boolean, literal, string } from '@metamask/superstruct'; -import { JsonStruct } from '@metamask/utils'; +import { + exactOptional, + object, + UuidStruct, + AccountIdStruct, +} from '@metamask/keyring-utils'; +import type { Infer } from '@metamask/superstruct'; +import { array, boolean, literal, record, string } from '@metamask/superstruct'; +import { CaipAssetTypeStruct, JsonStruct } from '@metamask/utils'; -import { KeyringAccountStruct } from './api'; +import { + CaipAssetTypeOrIdStruct, + FungibleAssetAmountStruct, + KeyringAccountStruct, + TransactionStruct, +} from './api'; /** * Supported keyring events. @@ -16,6 +27,11 @@ export enum KeyringEvent { // Request events RequestApproved = 'notify:requestApproved', RequestRejected = 'notify:requestRejected', + + // Assets related events + AccountBalancesUpdated = 'notify:accountBalancesUpdated', + AccountAssetListUpdated = 'notify:accountAssetListUpdated', + AccountTransactionsUpdated = 'notify:accountTransactionsUpdated', } export const AccountCreatedEventStruct = object({ @@ -87,3 +103,126 @@ export const RequestRejectedEventStruct = object({ id: UuidStruct, }), }); + +// Assets related events: +// ----------------------------------------------------------------------------------------------- + +export const AccountBalancesUpdatedEventStruct = object({ + method: literal(`${KeyringEvent.AccountBalancesUpdated}`), + params: object({ + /** + * Balances updates of accounts owned by the Snap. + */ + balances: record( + /** + * Account ID. + */ + AccountIdStruct, + + /** + * Mapping of each owned assets and their respective balances for that account. + */ + record( + /** + * Asset type (CAIP-19). + */ + CaipAssetTypeStruct, + + /** + * Balance information for a given asset. + */ + FungibleAssetAmountStruct, + ), + ), + }), +}); + +/** + * Event emitted when the balances of an account are updated. + * + * Only changes are reported. + * + * The Snap can choose to emit this event for multiple accounts at once. + */ +export type AccountBalancesUpdatedEvent = Infer< + typeof AccountBalancesUpdatedEventStruct +>; +export type AccountBalancesUpdatedEventPayload = + AccountBalancesUpdatedEvent['params']; + +export const AccountTransactionsUpdatedEventStruct = object({ + method: literal(`${KeyringEvent.AccountTransactionsUpdated}`), + params: object({ + /** + * Transactions updates of accounts owned by the Snap. + */ + transactions: record( + /** + * Account ID. + */ + AccountIdStruct, + + /** + * List of updated transactions for that account. + */ + array(TransactionStruct), + ), + }), +}); + +/** + * Event emitted when the transactions of an account are updated (added or + * changed). + * + * Only changes are reported. + * + * The Snap can choose to emit this event for multiple accounts at once. + */ +export type AccountTransactionsUpdatedEvent = Infer< + typeof AccountTransactionsUpdatedEventStruct +>; +export type AccountTransactionsUpdatedEventPayload = + AccountTransactionsUpdatedEvent['params']; + +export const AccountAssetListUpdatedEventStruct = object({ + method: literal(`${KeyringEvent.AccountAssetListUpdated}`), + params: object({ + /** + * Asset list update of accounts owned by the Snap. + */ + assets: record( + /** + * Account ID. + */ + AccountIdStruct, + + /** + * Asset list changes for that account. + */ + object({ + /** + * New assets detected. + */ + added: array(CaipAssetTypeOrIdStruct), + + /** + * Assets no longer available on that account. + */ + removed: array(CaipAssetTypeOrIdStruct), + }), + ), + }), +}); + +/** + * Event emitted when the assets of an account are updated. + * + * Only changes are reported. + * + * The Snap can choose to emit this event for multiple accounts at once. + */ +export type AccountAssetListUpdatedEvent = Infer< + typeof AccountAssetListUpdatedEventStruct +>; +export type AccountAssetListUpdatedEventPayload = + AccountAssetListUpdatedEvent['params']; diff --git a/packages/keyring-api/src/sol/constants.ts b/packages/keyring-api/src/sol/constants.ts index db6078cd..5bfc0536 100644 --- a/packages/keyring-api/src/sol/constants.ts +++ b/packages/keyring-api/src/sol/constants.ts @@ -3,7 +3,7 @@ /** * Scopes for Solana account type. See {@link KeyringAccount.scopes}. */ -export enum SolScopes { +export enum SolScope { Namespace = 'solana', Devnet = 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1', Mainnet = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', diff --git a/packages/keyring-api/src/sol/types.ts b/packages/keyring-api/src/sol/types.ts index 64dcaa66..da8caffd 100644 --- a/packages/keyring-api/src/sol/types.ts +++ b/packages/keyring-api/src/sol/types.ts @@ -1,6 +1,7 @@ import { object, definePattern } from '@metamask/keyring-utils'; import type { Infer } from '@metamask/superstruct'; -import { array, enums, literal } from '@metamask/superstruct'; +import { array, enums, literal, nonempty } from '@metamask/superstruct'; +import { CaipChainIdStruct } from '@metamask/utils'; import { KeyringAccountStruct, SolAccountType } from '../api'; @@ -35,6 +36,11 @@ export const SolDataAccountStruct = object({ */ type: literal(`${SolAccountType.DataAccount}`), + /** + * Account supported scopes (CAIP-2 chain IDs). + */ + scopes: nonempty(array(CaipChainIdStruct)), + /** * Account supported methods. */ diff --git a/packages/keyring-eth-ledger-bridge/CHANGELOG.md b/packages/keyring-eth-ledger-bridge/CHANGELOG.md index cb61b85e..2f3691fb 100644 --- a/packages/keyring-eth-ledger-bridge/CHANGELOG.md +++ b/packages/keyring-eth-ledger-bridge/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [8.0.3] + +### Fixed + +- Bump `@ledgerhq/hw-app-eth` dependency from `^6.39.0` to `^6.42.0` ([#153](https://github.com/MetaMask/accounts/pull/153)) + - Required to fix handling of EIP-712 content. + ## [8.0.2] ### Changed @@ -240,7 +247,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Support new versions of ethereumjs/tx ([#68](https://github.com/MetaMask/eth-ledger-bridge-keyring/pull/68)) -[Unreleased]: https://github.com/MetaMask/accounts/compare/@metamask/eth-ledger-bridge-keyring@8.0.2...HEAD +[Unreleased]: https://github.com/MetaMask/accounts/compare/@metamask/eth-ledger-bridge-keyring@8.0.3...HEAD +[8.0.3]: https://github.com/MetaMask/accounts/compare/@metamask/eth-ledger-bridge-keyring@8.0.2...@metamask/eth-ledger-bridge-keyring@8.0.3 [8.0.2]: https://github.com/MetaMask/accounts/compare/@metamask/eth-ledger-bridge-keyring@8.0.1...@metamask/eth-ledger-bridge-keyring@8.0.2 [8.0.1]: https://github.com/MetaMask/accounts/compare/@metamask/eth-ledger-bridge-keyring@8.0.0...@metamask/eth-ledger-bridge-keyring@8.0.1 [8.0.0]: https://github.com/MetaMask/accounts/compare/@metamask/eth-ledger-bridge-keyring@7.0.0...@metamask/eth-ledger-bridge-keyring@8.0.0 diff --git a/packages/keyring-eth-ledger-bridge/package.json b/packages/keyring-eth-ledger-bridge/package.json index 91f49447..94b5ec5a 100644 --- a/packages/keyring-eth-ledger-bridge/package.json +++ b/packages/keyring-eth-ledger-bridge/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/eth-ledger-bridge-keyring", - "version": "8.0.2", + "version": "8.0.3", "description": "A MetaMask compatible keyring, for ledger hardware wallets", "keywords": [ "ethereum", @@ -50,7 +50,7 @@ "@ethereumjs/rlp": "^5.0.2", "@ethereumjs/tx": "^4.2.0", "@ethereumjs/util": "^8.1.0", - "@ledgerhq/hw-app-eth": "^6.39.0", + "@ledgerhq/hw-app-eth": "^6.42.0", "@metamask/eth-sig-util": "^8.1.2", "hdkey": "^2.1.0" }, diff --git a/packages/keyring-internal-api/CHANGELOG.md b/packages/keyring-internal-api/CHANGELOG.md index cddb1bc5..0bcc0c15 100644 --- a/packages/keyring-internal-api/CHANGELOG.md +++ b/packages/keyring-internal-api/CHANGELOG.md @@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [3.0.0] + +### Changed + +- **BREAKING:** Bump `@metamask/keyring-api` from `^14.0.0` to `^15.0.0` ([#160](https://github.com/MetaMask/accounts/pull/160)) + - The `scopes` from each `*AccountStruct` types is now more strict which impact all `Internal*AccountStruct` types. + +## [2.0.1] + +### Changed + +- Bump `@metamask/keyring-api` from `^13.0.0` to `^14.0.0` ([#155](https://github.com/MetaMask/accounts/pull/155)) + ## [2.0.0] ### Changed @@ -29,7 +42,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - This new version fixes a bug with CJS re-exports. - Initial release ([#24](https://github.com/MetaMask/accounts/pull/24)) -[Unreleased]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-internal-api@2.0.0...HEAD +[Unreleased]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-internal-api@3.0.0...HEAD +[3.0.0]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-internal-api@2.0.1...@metamask/keyring-internal-api@3.0.0 +[2.0.1]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-internal-api@2.0.0...@metamask/keyring-internal-api@2.0.1 [2.0.0]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-internal-api@1.1.0...@metamask/keyring-internal-api@2.0.0 [1.1.0]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-internal-api@1.0.0...@metamask/keyring-internal-api@1.1.0 [1.0.0]: https://github.com/MetaMask/accounts/releases/tag/@metamask/keyring-internal-api@1.0.0 diff --git a/packages/keyring-internal-api/package.json b/packages/keyring-internal-api/package.json index 25e32834..51bd3369 100644 --- a/packages/keyring-internal-api/package.json +++ b/packages/keyring-internal-api/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/keyring-internal-api", - "version": "2.0.0", + "version": "3.0.0", "description": "MetaMask Keyring Internal API", "keywords": [ "metamask", diff --git a/packages/keyring-internal-snap-client/CHANGELOG.md b/packages/keyring-internal-snap-client/CHANGELOG.md index b3db6cdb..04dd69a1 100644 --- a/packages/keyring-internal-snap-client/CHANGELOG.md +++ b/packages/keyring-internal-snap-client/CHANGELOG.md @@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [3.0.1] + +### Changed + +- Bump `@metamask/keyring-api` from `^14.0.0` to `^15.0.0` ([#160](https://github.com/MetaMask/accounts/pull/160)) +- Bump `@metamask/keyring-snap-client` from `^3.0.0` to `^3.0.1` ([#160](https://github.com/MetaMask/accounts/pull/160)) + +## [3.0.0] + +### Added + +- Add `listAccountAssets` keyring method ([#148](https://github.com/MetaMask/accounts/pull/148)) + - This is inherited from the `KeyringClient` changes. + +### Changed + +- **BREAKING:** Bump `@metamask/keyring-api` from `^13.0.0` to `^14.0.0` ([#155](https://github.com/MetaMask/accounts/pull/155)) + - The `CaipAssetType` is now more restrictive which affects the existing `getAccountBalances` method. + ## [2.0.0] ### Changed @@ -33,7 +52,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - This new version fixes a bug with CJS re-exports. - Initial release ([#24](https://github.com/MetaMask/accounts/pull/24)) -[Unreleased]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-internal-snap-client@2.0.0...HEAD +[Unreleased]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-internal-snap-client@3.0.1...HEAD +[3.0.1]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-internal-snap-client@3.0.0...@metamask/keyring-internal-snap-client@3.0.1 +[3.0.0]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-internal-snap-client@2.0.0...@metamask/keyring-internal-snap-client@3.0.0 [2.0.0]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-internal-snap-client@1.1.0...@metamask/keyring-internal-snap-client@2.0.0 [1.1.0]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-internal-snap-client@1.0.0...@metamask/keyring-internal-snap-client@1.1.0 [1.0.0]: https://github.com/MetaMask/accounts/releases/tag/@metamask/keyring-internal-snap-client@1.0.0 diff --git a/packages/keyring-internal-snap-client/package.json b/packages/keyring-internal-snap-client/package.json index a50f9b48..1f766cf8 100644 --- a/packages/keyring-internal-snap-client/package.json +++ b/packages/keyring-internal-snap-client/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/keyring-internal-snap-client", - "version": "2.0.0", + "version": "3.0.1", "description": "MetaMask Keyring Snap internal clients", "keywords": [ "metamask", @@ -45,19 +45,18 @@ "test:watch": "jest --watch" }, "dependencies": { + "@metamask/base-controller": "^7.1.1", "@metamask/keyring-api": "workspace:^", "@metamask/keyring-snap-client": "workspace:^", - "@metamask/keyring-utils": "workspace:^", - "@metamask/snaps-controllers": "^9.10.0", - "@metamask/snaps-sdk": "^6.7.0", - "@metamask/snaps-utils": "^8.3.0", - "webextension-polyfill": "^0.12.0" + "@metamask/keyring-utils": "workspace:^" }, "devDependencies": { "@lavamoat/allow-scripts": "^3.2.1", "@lavamoat/preinstall-always-fail": "^2.1.0", "@metamask/auto-changelog": "^3.4.4", - "@metamask/providers": "^18.3.1", + "@metamask/snaps-controllers": "^9.10.0", + "@metamask/snaps-sdk": "^6.7.0", + "@metamask/snaps-utils": "^8.3.0", "@metamask/utils": "^11.0.1", "@ts-bridge/cli": "^0.6.1", "@types/jest": "^29.5.12", @@ -73,9 +72,6 @@ "typedoc": "^0.25.13", "typescript": "~5.6.3" }, - "peerDependencies": { - "@metamask/providers": "^18.3.1" - }, "engines": { "node": "^18.18 || >=20" }, diff --git a/packages/keyring-internal-snap-client/src/KeyringInternalSnapClient.test.ts b/packages/keyring-internal-snap-client/src/KeyringInternalSnapClient.test.ts new file mode 100644 index 00000000..891ed4fa --- /dev/null +++ b/packages/keyring-internal-snap-client/src/KeyringInternalSnapClient.test.ts @@ -0,0 +1,84 @@ +import type { KeyringAccount } from '@metamask/keyring-api'; +import type { SnapId } from '@metamask/snaps-sdk'; + +import { + KeyringInternalSnapClient, + type KeyringInternalSnapClientMessenger, +} from './KeyringInternalSnapClient'; + +describe('KeyringInternalSnapClient', () => { + const snapId = 'local:localhost:3000' as SnapId; + + const accountsList: KeyringAccount[] = [ + { + id: '13f94041-6ae6-451f-a0fe-afdd2fda18a7', + address: '0xE9A74AACd7df8112911ca93260fC5a046f8a64Ae', + options: {}, + methods: [], + scopes: ['eip155'], + type: 'eip155:eoa', + }, + ]; + + const messenger = { + call: jest.fn(), + }; + + describe('listAccounts', () => { + const request = { + snapId, + origin: 'metamask', + handler: 'onKeyringRequest', + request: { + id: expect.any(String), + jsonrpc: '2.0', + method: 'keyring_listAccounts', + }, + }; + + it('calls the listAccounts method and return the result', async () => { + const client = new KeyringInternalSnapClient({ + messenger: messenger as unknown as KeyringInternalSnapClientMessenger, + snapId, + }); + + messenger.call.mockResolvedValue(accountsList); + const accounts = await client.listAccounts(); + expect(messenger.call).toHaveBeenCalledWith( + 'SnapController:handleRequest', + request, + ); + expect(accounts).toStrictEqual(accountsList); + }); + + it('calls the listAccounts method and return the result (withSnapId)', async () => { + const client = new KeyringInternalSnapClient({ + messenger: messenger as unknown as KeyringInternalSnapClientMessenger, + }); + + messenger.call.mockResolvedValue(accountsList); + const accounts = await client.withSnapId(snapId).listAccounts(); + expect(messenger.call).toHaveBeenCalledWith( + 'SnapController:handleRequest', + request, + ); + expect(accounts).toStrictEqual(accountsList); + }); + + it('calls the default snapId value ("undefined")', async () => { + const client = new KeyringInternalSnapClient({ + messenger: messenger as unknown as KeyringInternalSnapClientMessenger, + }); + + messenger.call.mockResolvedValue(accountsList); + await client.listAccounts(); + expect(messenger.call).toHaveBeenCalledWith( + 'SnapController:handleRequest', + { + ...request, + snapId: 'undefined', + }, + ); + }); + }); +}); diff --git a/packages/keyring-internal-snap-client/src/KeyringInternalSnapClient.ts b/packages/keyring-internal-snap-client/src/KeyringInternalSnapClient.ts new file mode 100644 index 00000000..da1bdf04 --- /dev/null +++ b/packages/keyring-internal-snap-client/src/KeyringInternalSnapClient.ts @@ -0,0 +1,124 @@ +import type { RestrictedControllerMessenger } from '@metamask/base-controller'; +import { KeyringClient, type Sender } from '@metamask/keyring-snap-client'; +import type { JsonRpcRequest } from '@metamask/keyring-utils'; +import type { HandleSnapRequest } from '@metamask/snaps-controllers'; +import type { SnapId } from '@metamask/snaps-sdk'; +import type { HandlerType } from '@metamask/snaps-utils'; +import type { Json } from '@metamask/utils'; + +// We only need to dispatch Snap request to the Snaps controller for now. +type AllowedActions = HandleSnapRequest; + +/** + * A restricted-`Messenger` used by `KeyringInternalSnapClient` to dispatch + * internal Snap requests. + */ +export type KeyringInternalSnapClientMessenger = RestrictedControllerMessenger< + 'KeyringInternalSnapClient', + AllowedActions, + never, + AllowedActions['type'], + never +>; + +/** + * Implementation of the `Sender` interface that can be used to send requests + * to a Snap through a `Messenger`. + */ +class SnapControllerMessengerSender implements Sender { + readonly #snapId: SnapId; + + readonly #origin: string; + + readonly #messenger: KeyringInternalSnapClientMessenger; + + readonly #handler: HandlerType; + + /** + * Create a new instance of `SnapControllerSender`. + * + * @param messenger - The `Messenger` instance used when dispatching controllers actions. + * @param snapId - The ID of the Snap to use. + * @param origin - The sender's origin. + * @param handler - The handler type. + */ + constructor( + messenger: KeyringInternalSnapClientMessenger, + snapId: SnapId, + origin: string, + handler: HandlerType, + ) { + this.#messenger = messenger; + this.#snapId = snapId; + this.#origin = origin; + this.#handler = handler; + } + + /** + * Send a request to the Snap and return the response. + * + * @param request - JSON-RPC request to send to the Snap. + * @returns A promise that resolves to the response of the request. + */ + async send(request: JsonRpcRequest): Promise { + return this.#messenger.call('SnapController:handleRequest', { + snapId: this.#snapId, + origin: this.#origin, + handler: this.#handler, + request, + }) as Promise; + } +} + +/** + * A `KeyringClient` that allows the communication with a Snap through a + * `Messenger`. + */ +export class KeyringInternalSnapClient extends KeyringClient { + readonly #messenger: KeyringInternalSnapClientMessenger; + + /** + * Create a new instance of `KeyringInternalSnapClient`. + * + * The `handlerType` argument has a hard-coded default `string` value instead + * of a `HandlerType` value to prevent the `@metamask/snaps-utils` module + * from being required at runtime. + * + * @param args - Constructor arguments. + * @param args.messenger - The `KeyringInternalSnapClientMessenger` instance to use. + * @param args.snapId - The ID of the Snap to use (default: `'undefined'`). + * @param args.origin - The sender's origin (default: `'metamask'`). + * @param args.handler - The handler type (default: `'onKeyringRequest'`). + */ + constructor({ + messenger, + snapId = 'undefined' as SnapId, + origin = 'metamask', + handler = 'onKeyringRequest' as HandlerType, + }: { + messenger: KeyringInternalSnapClientMessenger; + snapId?: SnapId; + origin?: string; + handler?: HandlerType; + }) { + super( + new SnapControllerMessengerSender(messenger, snapId, origin, handler), + ); + this.#messenger = messenger; + } + + /** + * Create a new instance of `KeyringInternalSnapClient` with the specified + * `snapId`. + * + * @param snapId - The ID of the Snap to use in the new instance. + * @returns A new instance of `KeyringInternalSnapClient` with the + * specified Snap ID. + */ + withSnapId(snapId: SnapId): KeyringInternalSnapClient { + return new KeyringInternalSnapClient({ + messenger: this.#messenger, + snapId, + }); + } +} diff --git a/packages/keyring-internal-snap-client/src/KeyringSnapControllerClient.test.ts b/packages/keyring-internal-snap-client/src/KeyringSnapControllerClient.test.ts deleted file mode 100644 index a9d73773..00000000 --- a/packages/keyring-internal-snap-client/src/KeyringSnapControllerClient.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -import type { KeyringAccount } from '@metamask/keyring-api'; -import type { SnapController } from '@metamask/snaps-controllers'; -import type { SnapId } from '@metamask/snaps-sdk'; - -import { KeyringSnapControllerClient } from './KeyringSnapControllerClient'; - -describe('KeyringSnapControllerClient', () => { - const snapId = 'local:localhost:3000' as SnapId; - - const accountsList: KeyringAccount[] = [ - { - id: '13f94041-6ae6-451f-a0fe-afdd2fda18a7', - address: '0xE9A74AACd7df8112911ca93260fC5a046f8a64Ae', - options: {}, - methods: [], - scopes: ['eip155'], - type: 'eip155:eoa', - }, - ]; - - const controller = { - handleRequest: jest.fn(), - }; - - describe('listAccounts', () => { - const request = { - snapId, - origin: 'metamask', - handler: 'onKeyringRequest', - request: { - id: expect.any(String), - jsonrpc: '2.0', - method: 'keyring_listAccounts', - }, - }; - - it('should call the listAccounts method and return the result', async () => { - const client = new KeyringSnapControllerClient({ - controller: controller as unknown as SnapController, - snapId, - }); - - controller.handleRequest.mockResolvedValue(accountsList); - const accounts = await client.listAccounts(); - expect(controller.handleRequest).toHaveBeenCalledWith(request); - expect(accounts).toStrictEqual(accountsList); - }); - - it('should call the listAccounts method and return the result (withSnapId)', async () => { - const client = new KeyringSnapControllerClient({ - controller: controller as unknown as SnapController, - }); - - controller.handleRequest.mockResolvedValue(accountsList); - const accounts = await client.withSnapId(snapId).listAccounts(); - expect(controller.handleRequest).toHaveBeenCalledWith(request); - expect(accounts).toStrictEqual(accountsList); - }); - - it('should call the default snapId value ("undefined")', async () => { - const client = new KeyringSnapControllerClient({ - controller: controller as unknown as SnapController, - }); - - controller.handleRequest.mockResolvedValue(accountsList); - await client.listAccounts(); - expect(controller.handleRequest).toHaveBeenCalledWith({ - ...request, - snapId: 'undefined', - }); - }); - }); - - describe('getController', () => { - it('should return the controller', () => { - const client = new KeyringSnapControllerClient({ - controller: controller as unknown as SnapController, - }); - - expect(client.getController()).toBe(controller); - }); - }); -}); diff --git a/packages/keyring-internal-snap-client/src/KeyringSnapControllerClient.ts b/packages/keyring-internal-snap-client/src/KeyringSnapControllerClient.ts deleted file mode 100644 index c5d8d3b0..00000000 --- a/packages/keyring-internal-snap-client/src/KeyringSnapControllerClient.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { KeyringClient, type Sender } from '@metamask/keyring-snap-client'; -import type { JsonRpcRequest } from '@metamask/keyring-utils'; -import type { SnapController } from '@metamask/snaps-controllers'; -import type { SnapId } from '@metamask/snaps-sdk'; -import type { HandlerType } from '@metamask/snaps-utils'; -import type { Json } from '@metamask/utils'; - -/** - * Implementation of the `Sender` interface that can be used to send requests - * to a snap through a `SnapController`. - */ -class SnapControllerSender implements Sender { - readonly #snapId: SnapId; - - readonly #origin: string; - - readonly #controller: SnapController; - - readonly #handler: HandlerType; - - /** - * Create a new instance of `SnapControllerSender`. - * - * @param controller - The `SnapController` instance to send requests to. - * @param snapId - The ID of the snap to use. - * @param origin - The sender's origin. - * @param handler - The handler type. - */ - constructor( - controller: SnapController, - snapId: SnapId, - origin: string, - handler: HandlerType, - ) { - this.#controller = controller; - this.#snapId = snapId; - this.#origin = origin; - this.#handler = handler; - } - - /** - * Send a request to the snap and return the response. - * - * @param request - JSON-RPC request to send to the snap. - * @returns A promise that resolves to the response of the request. - */ - async send(request: JsonRpcRequest): Promise { - return this.#controller.handleRequest({ - snapId: this.#snapId, - origin: this.#origin, - handler: this.#handler, - request, - }) as Promise; - } -} - -/** - * A `KeyringClient` that allows the communication with a snap through the - * `SnapController`. - */ -export class KeyringSnapControllerClient extends KeyringClient { - readonly #controller: SnapController; - - /** - * Create a new instance of `KeyringSnapControllerClient`. - * - * The `handlerType` argument has a hard-coded default `string` value instead - * of a `HandlerType` value to prevent the `@metamask/snaps-utils` module - * from being required at runtime. - * - * @param args - Constructor arguments. - * @param args.controller - The `SnapController` instance to use. - * @param args.snapId - The ID of the snap to use (default: `'undefined'`). - * @param args.origin - The sender's origin (default: `'metamask'`). - * @param args.handler - The handler type (default: `'onKeyringRequest'`). - */ - constructor({ - controller, - snapId = 'undefined' as SnapId, - origin = 'metamask', - handler = 'onKeyringRequest' as HandlerType, - }: { - controller: SnapController; - snapId?: SnapId; - origin?: string; - handler?: HandlerType; - }) { - super(new SnapControllerSender(controller, snapId, origin, handler)); - this.#controller = controller; - } - - /** - * Create a new instance of `KeyringSnapControllerClient` with the specified - * `snapId`. - * - * @param snapId - The ID of the snap to use in the new instance. - * @returns A new instance of `KeyringSnapControllerClient` with the - * specified snap ID. - */ - withSnapId(snapId: SnapId): KeyringSnapControllerClient { - return new KeyringSnapControllerClient({ - controller: this.#controller, - snapId, - }); - } - - /** - * Get the `SnapController` instance used by this client. - * - * @returns The `SnapController` instance used by this client. - */ - getController(): SnapController { - return this.#controller; - } -} diff --git a/packages/keyring-internal-snap-client/src/index.ts b/packages/keyring-internal-snap-client/src/index.ts index bd358351..cbd73172 100644 --- a/packages/keyring-internal-snap-client/src/index.ts +++ b/packages/keyring-internal-snap-client/src/index.ts @@ -1 +1 @@ -export * from './KeyringSnapControllerClient'; +export * from './KeyringInternalSnapClient'; diff --git a/packages/keyring-snap-bridge/CHANGELOG.md b/packages/keyring-snap-bridge/CHANGELOG.md index c42a897a..4d7b1256 100644 --- a/packages/keyring-snap-bridge/CHANGELOG.md +++ b/packages/keyring-snap-bridge/CHANGELOG.md @@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [9.0.0] + +### Added + +- Add `account{AssetList,Balances,Transactions}Updated` keyring events and re-publish them through the new `Messenger` ([#154](https://github.com/MetaMask/accounts/pull/154)) + +### Changed + +- **BREAKING:** Use `Messenger` instead of `SnapsController` ([#152](https://github.com/MetaMask/accounts/pull/152)) + - This allows to break the runtime dependency we had with some `snaps-*` pacakges. +- **BREAKING:** Make `scopes` more strict ([#159](https://github.com/MetaMask/accounts/pull/159)) + - We now use specific `*AccountStucts` when checking created/updated accounts to make the `scopes` sent by the Snap are valid regarding their account type definition. + +## [8.1.1] + +### Changed + +- Bump `@metamask/keyring-api` from `^13.0.0` to `^14.0.0` ([#155](https://github.com/MetaMask/accounts/pull/155)) + ## [8.1.0] ### Added @@ -410,7 +429,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Initial release. -[Unreleased]: https://github.com/MetaMask/accounts/compare/@metamask/eth-snap-keyring@8.1.0...HEAD +[Unreleased]: https://github.com/MetaMask/accounts/compare/@metamask/eth-snap-keyring@9.0.0...HEAD +[9.0.0]: https://github.com/MetaMask/accounts/compare/@metamask/eth-snap-keyring@8.1.1...@metamask/eth-snap-keyring@9.0.0 +[8.1.1]: https://github.com/MetaMask/accounts/compare/@metamask/eth-snap-keyring@8.1.0...@metamask/eth-snap-keyring@8.1.1 [8.1.0]: https://github.com/MetaMask/accounts/compare/@metamask/eth-snap-keyring@8.0.0...@metamask/eth-snap-keyring@8.1.0 [8.0.0]: https://github.com/MetaMask/accounts/compare/@metamask/eth-snap-keyring@7.1.0...@metamask/eth-snap-keyring@8.0.0 [7.1.0]: https://github.com/MetaMask/accounts/compare/@metamask/eth-snap-keyring@7.0.0...@metamask/eth-snap-keyring@7.1.0 diff --git a/packages/keyring-snap-bridge/package.json b/packages/keyring-snap-bridge/package.json index 70e94867..28b4f7ff 100644 --- a/packages/keyring-snap-bridge/package.json +++ b/packages/keyring-snap-bridge/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/eth-snap-keyring", - "version": "8.1.0", + "version": "9.0.0", "description": "Snaps keyring bridge.", "repository": { "type": "git", @@ -38,25 +38,24 @@ }, "dependencies": { "@ethereumjs/tx": "^4.2.0", + "@metamask/base-controller": "^7.1.1", "@metamask/eth-sig-util": "^8.1.2", "@metamask/keyring-api": "workspace:^", "@metamask/keyring-internal-api": "workspace:^", "@metamask/keyring-internal-snap-client": "workspace:^", "@metamask/keyring-utils": "workspace:^", - "@metamask/snaps-controllers": "^9.10.0", - "@metamask/snaps-sdk": "^6.7.0", - "@metamask/snaps-utils": "^8.3.0", "@metamask/superstruct": "^3.1.0", "@metamask/utils": "^11.0.1", "@types/uuid": "^9.0.8", - "uuid": "^9.0.1", - "webextension-polyfill": "^0.12.0" + "uuid": "^9.0.1" }, "devDependencies": { "@lavamoat/allow-scripts": "^3.2.1", "@lavamoat/preinstall-always-fail": "^2.1.0", "@metamask/auto-changelog": "^3.4.4", - "@metamask/providers": "^18.3.1", + "@metamask/snaps-controllers": "^9.10.0", + "@metamask/snaps-sdk": "^6.7.0", + "@metamask/snaps-utils": "^8.3.0", "@ts-bridge/cli": "^0.6.1", "@types/jest": "^29.5.12", "@types/node": "^20.12.12", @@ -71,8 +70,7 @@ "typescript": "~5.6.3" }, "peerDependencies": { - "@metamask/keyring-api": "workspace:^", - "@metamask/providers": "^18.3.1" + "@metamask/keyring-api": "workspace:^" }, "engines": { "node": "^18.18 || >=20" diff --git a/packages/keyring-snap-bridge/src/SnapKeyring.test.ts b/packages/keyring-snap-bridge/src/SnapKeyring.test.ts index ebebdea1..c76b4222 100644 --- a/packages/keyring-snap-bridge/src/SnapKeyring.test.ts +++ b/packages/keyring-snap-bridge/src/SnapKeyring.test.ts @@ -1,4 +1,5 @@ import { TransactionFactory } from '@ethereumjs/tx'; +import { ControllerMessenger } from '@metamask/base-controller'; import { SignTypedDataVersion } from '@metamask/eth-sig-util'; import type { KeyringAccount, @@ -6,9 +7,12 @@ import type { EthBaseUserOperation, EthUserOperation, EthUserOperationPatch, + AccountBalancesUpdatedEventPayload, + AccountTransactionsUpdatedEventPayload, + AccountAssetListUpdatedEventPayload, } from '@metamask/keyring-api'; import { - EthScopes, + EthScope, BtcAccountType, EthAccountType, SolAccountType, @@ -16,10 +20,9 @@ import { EthMethod, SolMethod, KeyringEvent, - BtcScopes, - SolScopes, + BtcScope, + SolScope, } from '@metamask/keyring-api'; -import type { SnapController } from '@metamask/snaps-controllers'; import type { SnapId } from '@metamask/snaps-sdk'; import { toCaipChainId } from '@metamask/utils'; @@ -27,6 +30,11 @@ import type { KeyringState } from '.'; import { SnapKeyring } from '.'; import type { KeyringAccountV1 } from './account'; import { migrateAccountV1, getScopesForAccountV1 } from './migrations'; +import type { + SnapKeyringAllowedActions, + SnapKeyringEvents, + SnapKeyringMessenger, +} from './SnapKeyringMessenger'; const regexForUUIDInRequiredSyncErrorMessage = /Request '[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}' to snap 'local:snap.mock' is pending and noPending is true/u; @@ -61,9 +69,9 @@ function noScopes(account: KeyringAccount): KeyringAccountV1 { describe('SnapKeyring', () => { let keyring: SnapKeyring; - const mockSnapController = { - handleRequest: jest.fn(), + const mockMessenger = { get: jest.fn(), + handleRequest: jest.fn(), }; const mockCallbacks = { @@ -96,7 +104,7 @@ describe('SnapKeyring', () => { address: '0xC728514Df8A7F9271f4B7a4dd2Aa6d2D723d3eE3'.toLowerCase(), options: {}, methods: ETH_EOA_METHODS, - scopes: [EthScopes.Namespace], + scopes: [EthScope.Namespace], type: EthAccountType.Eoa, }; const ethEoaAccount2 = { @@ -104,15 +112,15 @@ describe('SnapKeyring', () => { address: '0x34b13912eAc00152bE0Cb409A301Ab8E55739e63'.toLowerCase(), options: {}, methods: ETH_EOA_METHODS, - scopes: [EthScopes.Namespace], + scopes: [EthScope.Namespace], type: EthAccountType.Eoa, }; const ethEoaAccount3 = { id: 'c6697bcf-5710-4751-a1cb-340e4b50617a', - address: '0xab1G3q98V7C67T9103g30C0417610237A137d763'.toLowerCase(), + address: '0xf7bDe8609231033c69E502C08f85153f8A1548F2'.toLowerCase(), options: {}, methods: ETH_EOA_METHODS, - scopes: [EthScopes.Namespace], + scopes: [EthScope.Namespace], type: EthAccountType.Eoa, }; const ethErc4337Account = { @@ -120,7 +128,7 @@ describe('SnapKeyring', () => { address: '0x2f15b30952aebe0ed5fdbfe5bf16fb9ecdb31d9a'.toLowerCase(), options: {}, methods: ETH_4337_METHODS, - scopes: [EthScopes.Namespace], + scopes: [EthScope.Testnet], type: EthAccountType.Erc4337, }; const btcP2wpkhAccount = { @@ -128,7 +136,7 @@ describe('SnapKeyring', () => { address: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', options: {}, methods: [...Object.values(BtcMethod)], - scopes: [BtcScopes.Mainnet], + scopes: [BtcScope.Mainnet], type: BtcAccountType.P2wpkh, }; const btcP2wpkhTestnetAccount = { @@ -136,7 +144,7 @@ describe('SnapKeyring', () => { address: 'tb1q6rmsq3vlfdhjdhtkxlqtuhhlr6pmj09y6w43g8', options: {}, methods: [...Object.values(BtcMethod)], - scopes: [BtcScopes.Testnet], + scopes: [BtcScope.Testnet], type: BtcAccountType.P2wpkh, }; const solDataAccount = { @@ -144,7 +152,7 @@ describe('SnapKeyring', () => { address: '3d4v35MRK57xM2Nte3E3rTQU1zyXGVrkXJ6FuEjVoKzM', options: {}, methods: [...Object.values(SolMethod)], - scopes: [SolScopes.Mainnet, SolScopes.Testnet, SolScopes.Devnet], + scopes: [SolScope.Mainnet, SolScope.Testnet, SolScope.Devnet], type: SolAccountType.DataAccount, }; const unknownAccount: KeyringAccount = { @@ -154,8 +162,8 @@ describe('SnapKeyring', () => { methods: [], // For unknown accounts, we consider them as EVM EOA for now, so just re-use the // same scopes. - scopes: [EthScopes.Namespace], - // This should be really possible to create such account, but since we potentially + scopes: [EthScope.Namespace], + // This should not be really possible to create such account, but since we potentially // migrate data upon the Snap keyring initialization, we want to cover edge-cases // like this one to avoid crashing and blocking everything... type: 'unknown:type' as KeyringAccount['type'], @@ -174,11 +182,31 @@ describe('SnapKeyring', () => { chainId: '1', }; + // Fake the ControllerMessenger and registers all mock actions here: + const controllerMessenger: ControllerMessenger< + SnapKeyringAllowedActions, + SnapKeyringEvents + > = new ControllerMessenger(); + controllerMessenger.registerActionHandler( + 'SnapController:get', + mockMessenger.get, + ); + controllerMessenger.registerActionHandler( + 'SnapController:handleRequest', + mockMessenger.handleRequest, + ); + + // Now extracts a rectricted messenger for the Snap keyring only. + const mockSnapKeyringMessenger: SnapKeyringMessenger = + controllerMessenger.getRestricted({ + name: 'SnapKeyring', + allowedEvents: [], + allowedActions: ['SnapController:get', 'SnapController:handleRequest'], + }); + beforeEach(async () => { - keyring = new SnapKeyring( - mockSnapController as unknown as SnapController, - mockCallbacks, - ); + keyring = new SnapKeyring(mockSnapKeyringMessenger, mockCallbacks); + mockCallbacks.addAccount.mockImplementation( async ( _address, @@ -190,8 +218,11 @@ describe('SnapKeyring', () => { await handleUserInput(true); }, ); + + mockMessenger.get.mockReset(); + mockMessenger.handleRequest.mockReset(); for (const account of accounts) { - mockSnapController.handleRequest.mockResolvedValue(accounts); + mockMessenger.handleRequest.mockResolvedValue(accounts); await keyring.handleKeyringSnapMessage(snapId, { method: KeyringEvent.AccountCreated, params: { account: account as unknown as KeyringAccount }, @@ -206,7 +237,7 @@ describe('SnapKeyring', () => { id: 'b05d918a-b37c-497a-bb28-3d15c0d56b7a', options: {}, methods: ETH_EOA_METHODS, - scopes: [EthScopes.Namespace], + scopes: [EthScope.Namespace], type: EthAccountType.Eoa, // Even checksummed address will be lower-cased by the bridge. address: '0x6431726EEE67570BF6f0Cf892aE0a3988F03903F', @@ -281,7 +312,7 @@ describe('SnapKeyring', () => { params: { account: { ...(ethEoaAccount1 as unknown as KeyringAccount), - address: '0x0', + address: ethEoaAccount2.address, }, }, }), @@ -332,10 +363,7 @@ describe('SnapKeyring', () => { // Reset mock mockCallbacks.addAccount.mockClear(); // Reset the keyring so it's empty. - keyring = new SnapKeyring( - mockSnapController as unknown as SnapController, - mockCallbacks, - ); + keyring = new SnapKeyring(mockSnapKeyringMessenger, mockCallbacks); const params = { account: account as unknown as KeyringAccount, @@ -363,10 +391,7 @@ describe('SnapKeyring', () => { it('creates an account and registers it properly', async () => { // Reset the keyring so it's empty. - keyring = new SnapKeyring( - mockSnapController as unknown as SnapController, - mockCallbacks, - ); + keyring = new SnapKeyring(mockSnapKeyringMessenger, mockCallbacks); const account = ethEoaAccount1; await keyring.handleKeyringSnapMessage(snapId, { @@ -386,10 +411,7 @@ describe('SnapKeyring', () => { it('creates an EOA account and set a default scopes if not provided', async () => { // Reset the keyring so it's empty. - keyring = new SnapKeyring( - mockSnapController as unknown as SnapController, - mockCallbacks, - ); + keyring = new SnapKeyring(mockSnapKeyringMessenger, mockCallbacks); // Omit `scopes` from `account`. const account = noScopes(ethEoaAccount1); @@ -407,16 +429,13 @@ describe('SnapKeyring', () => { metadata: expect.any(Object), // By default, new EVM accounts will have this scopes if it not provided // during the account creation flow. - scopes: [EthScopes.Namespace], + scopes: [EthScope.Namespace], }); }); it('creating a non-EVM account with the no scope will throw an error', async () => { // Reset the keyring so it's empty. - keyring = new SnapKeyring( - mockSnapController as unknown as SnapController, - mockCallbacks, - ); + keyring = new SnapKeyring(mockSnapKeyringMessenger, mockCallbacks); // Omit `scopes` from non-EVM `account`. const account = noScopes(btcP2wpkhAccount); @@ -428,15 +447,118 @@ describe('SnapKeyring', () => { }, }), ).rejects.toThrow( - 'Account scopes is required for non-EVM and ERC4337 accounts', + 'At path: scopes -- Expected an array value, but received: undefined', ); }); + + it('receives an account balances update event and re-publish it to the messenger', async () => { + const mockPublishedEventCallback = jest.fn(); + mockSnapKeyringMessenger.subscribe( + 'SnapKeyring:accountBalancesUpdated', + mockPublishedEventCallback, + ); + + const account = ethEoaAccount1; + const event: AccountBalancesUpdatedEventPayload = { + balances: { + [account.id]: { + 'bip122:000000000019d6689c085ae165831e93/slip44:0': { + amount: '0.1', + unit: 'BTC', + }, + }, + }, + }; + + await keyring.handleKeyringSnapMessage(snapId, { + method: KeyringEvent.AccountBalancesUpdated, + params: event, + }); + expect(mockPublishedEventCallback).toHaveBeenCalledWith(event); + }); + + it('receives an transactions update event and re-publish it to the messenger', async () => { + const mockPublishedEventCallback = jest.fn(); + mockSnapKeyringMessenger.subscribe( + 'SnapKeyring:accountTransactionsUpdated', + mockPublishedEventCallback, + ); + + const account = ethEoaAccount1; + const event: AccountTransactionsUpdatedEventPayload = { + transactions: { + [account.id]: [ + { + id: 'f5d8ee39a430901c91a5917b9f2dc19d6d1a0e9cea205b009ca73dd04470b9a6', + timestamp: null, + chain: 'eip155:1', + status: 'submitted', + type: 'receive', + account: '5cd17616-ea18-4d72-974f-6dbaa3c56d15', + from: [], + to: [], + fees: [ + { + type: 'base', + asset: { + fungible: true, + type: 'eip155:1/slip44:60', + unit: 'ETH', + amount: '0.0001', + }, + }, + { + type: 'priority', + asset: { + fungible: true, + type: 'eip155:1/slip44:60', + unit: 'ETH', + amount: '0.0001', + }, + }, + ], + events: [], + }, + ], + }, + }; + + await keyring.handleKeyringSnapMessage(snapId, { + method: KeyringEvent.AccountTransactionsUpdated, + params: event, + }); + expect(mockPublishedEventCallback).toHaveBeenCalledWith(event); + }); + + it('receives an asset list update event and re-publish it to the messenger', async () => { + const mockPublishedEventCallback = jest.fn(); + mockSnapKeyringMessenger.subscribe( + 'SnapKeyring:accountAssetListUpdated', + mockPublishedEventCallback, + ); + + const account = ethEoaAccount1; + const event: AccountAssetListUpdatedEventPayload = { + assets: { + [account.id]: { + added: ['bip122:000000000019d6689c085ae165831e93/slip44:0'], + removed: ['bip122:000000000933ea01ad0ee984209779ba/slip44:0'], + }, + }, + }; + + await keyring.handleKeyringSnapMessage(snapId, { + method: KeyringEvent.AccountAssetListUpdated, + params: event, + }); + expect(mockPublishedEventCallback).toHaveBeenCalledWith(event); + }); }); describe('#handleAccountUpdated', () => { it('updates the methods of an account', async () => { // Return the updated list of accounts when the keyring requests it. - mockSnapController.handleRequest.mockResolvedValue([ + mockMessenger.handleRequest.mockResolvedValue([ { ...ethEoaAccount1, methods: [] }, { ...ethEoaAccount2 }, ]); @@ -511,7 +633,7 @@ describe('SnapKeyring', () => { it('fails when the EthMethod is not supported after update', async () => { // Update first account to remove `EthMethod.PersonalSign` - let updatedMethods: EthMethod[] = Object.values(EthMethod).filter( + let updatedMethods: EthMethod[] = Object.values(ETH_EOA_METHODS).filter( (method) => method !== EthMethod.PersonalSign, ); expect( @@ -534,7 +656,7 @@ describe('SnapKeyring', () => { `Method '${EthMethod.PersonalSign}' not supported for account ${ethEoaAccount1.address}`, ); // Restore `EthMethod.PersonalSign` and remove `EthMethod.SignTransaction` - updatedMethods = Object.values(EthMethod).filter( + updatedMethods = Object.values(ETH_EOA_METHODS).filter( (method) => method !== EthMethod.SignTransaction, ); expect( @@ -574,7 +696,7 @@ describe('SnapKeyring', () => { const account = noScopes(ethEoaAccount1); // Return the updated list of accounts when the keyring requests it. - mockSnapController.handleRequest.mockResolvedValue([{ ...account }]); + mockMessenger.handleRequest.mockResolvedValue([{ ...account }]); expect( await keyring.handleKeyringSnapMessage(snapId, { @@ -585,7 +707,7 @@ describe('SnapKeyring', () => { const keyringAccounts = keyring.listAccounts(); expect(keyringAccounts.length).toBeGreaterThan(0); - expect(keyringAccounts[0]?.scopes).toStrictEqual([EthScopes.Namespace]); + expect(keyringAccounts[0]?.scopes).toStrictEqual([EthScope.Namespace]); }); it('updates a ERC4337 account with the no scope will throw an error', async () => { @@ -593,7 +715,7 @@ describe('SnapKeyring', () => { const account = noScopes(ethErc4337Account); // Return the updated list of accounts when the keyring requests it. - mockSnapController.handleRequest.mockResolvedValue([{ ...account }]); + mockMessenger.handleRequest.mockResolvedValue([{ ...account }]); await expect( keyring.handleKeyringSnapMessage(snapId, { @@ -601,7 +723,7 @@ describe('SnapKeyring', () => { params: { account }, }), ).rejects.toThrow( - 'Account scopes is required for non-EVM and ERC4337 accounts', + 'At path: scopes -- Expected an array value, but received: undefined', ); }); @@ -610,7 +732,7 @@ describe('SnapKeyring', () => { const account = noScopes(btcP2wpkhAccount); // Return the updated list of accounts when the keyring requests it. - mockSnapController.handleRequest.mockResolvedValue([{ ...account }]); + mockMessenger.handleRequest.mockResolvedValue([{ ...account }]); await expect( keyring.handleKeyringSnapMessage(snapId, { @@ -618,14 +740,14 @@ describe('SnapKeyring', () => { params: { account }, }), ).rejects.toThrow( - 'Account scopes is required for non-EVM and ERC4337 accounts', + 'At path: scopes -- Expected an array value, but received: undefined', ); }); }); describe('#handleAccountDeleted', () => { it('removes an account', async () => { - mockSnapController.handleRequest.mockResolvedValue(null); + mockMessenger.handleRequest.mockResolvedValue(null); mockCallbacks.removeAccount.mockImplementation( async (address, _snapId, handleUserInput) => { await keyring.removeAccount(address); @@ -692,7 +814,7 @@ describe('SnapKeyring', () => { describe('#handleRequestApproved', () => { it('approves an async request', async () => { - mockSnapController.handleRequest.mockResolvedValue({ + mockMessenger.handleRequest.mockResolvedValue({ pending: true, }); const requestPromise = keyring.signPersonalMessage( @@ -700,7 +822,7 @@ describe('SnapKeyring', () => { 'hello', ); - const { calls } = mockSnapController.handleRequest.mock; + const { calls } = mockMessenger.handleRequest.mock; const requestId = calls[calls.length - 1][0].request.params.id; await keyring.handleKeyringSnapMessage(snapId, { method: KeyringEvent.RequestApproved, @@ -726,13 +848,13 @@ describe('SnapKeyring', () => { }); it("cannot approve another Snap's request", async () => { - mockSnapController.handleRequest.mockResolvedValue({ + mockMessenger.handleRequest.mockResolvedValue({ pending: true, }); // eslint-disable-next-line no-void void keyring.signPersonalMessage(ethEoaAccount1.address, 'hello'); - const { calls } = mockSnapController.handleRequest.mock; + const { calls } = mockMessenger.handleRequest.mock; const requestId: string = calls[calls.length - 1][0].request.params.id; await expect( keyring.handleKeyringSnapMessage('another-snap-id' as SnapId, { @@ -743,13 +865,13 @@ describe('SnapKeyring', () => { }); it('fails to approve a request that failed when submitted', async () => { - mockSnapController.handleRequest.mockRejectedValue(new Error('error')); + mockMessenger.handleRequest.mockRejectedValue(new Error('error')); const mockMessage = 'Hello World!'; await expect( keyring.signPersonalMessage(ethEoaAccount1.address, mockMessage), ).rejects.toThrow('error'); - const { calls } = mockSnapController.handleRequest.mock; + const { calls } = mockMessenger.handleRequest.mock; const requestId = calls[calls.length - 1][0].request.params.id; const responsePromise = keyring.handleKeyringSnapMessage(snapId, { method: KeyringEvent.RequestApproved, @@ -766,7 +888,7 @@ describe('SnapKeyring', () => { describe('#handleRequestRejected', () => { it('rejects an async request', async () => { - mockSnapController.handleRequest.mockResolvedValue({ + mockMessenger.handleRequest.mockResolvedValue({ pending: true, }); const requestPromise = keyring.signPersonalMessage( @@ -774,7 +896,7 @@ describe('SnapKeyring', () => { 'hello', ); - const { calls } = mockSnapController.handleRequest.mock; + const { calls } = mockMessenger.handleRequest.mock; const requestId = calls[calls.length - 1][0].request.params.id; await keyring.handleKeyringSnapMessage(snapId, { method: KeyringEvent.RequestRejected, @@ -798,13 +920,13 @@ describe('SnapKeyring', () => { }); it("cannot reject another Snap's request", async () => { - mockSnapController.handleRequest.mockResolvedValue({ + mockMessenger.handleRequest.mockResolvedValue({ pending: true, }); // eslint-disable-next-line no-void void keyring.signPersonalMessage(ethEoaAccount1.address, 'hello'); - const { calls } = mockSnapController.handleRequest.mock; + const { calls } = mockMessenger.handleRequest.mock; const requestId: string = calls[calls.length - 1][0].request.params.id; await expect( keyring.handleKeyringSnapMessage('another-snap-id' as SnapId, { @@ -871,20 +993,14 @@ describe('SnapKeyring', () => { it('fails to restore an undefined state', async () => { // Reset the keyring so it's empty. - keyring = new SnapKeyring( - mockSnapController as unknown as SnapController, - mockCallbacks, - ); + keyring = new SnapKeyring(mockSnapKeyringMessenger, mockCallbacks); await keyring.deserialize(undefined as unknown as KeyringState); expect(await keyring.getAccounts()).toStrictEqual([]); }); it('fails to restore an empty state', async () => { // Reset the keyring so it's empty. - keyring = new SnapKeyring( - mockSnapController as unknown as SnapController, - mockCallbacks, - ); + keyring = new SnapKeyring(mockSnapKeyringMessenger, mockCallbacks); await expect( keyring.deserialize({} as unknown as KeyringState), ).rejects.toThrow('Cannot convert undefined or null to object'); @@ -928,7 +1044,7 @@ describe('SnapKeyring', () => { it('unknown v1 accounts scopes defaults to EOA scopes', () => { expect(getScopesForAccountV1(unknownAccount)).toStrictEqual([ - EthScopes.Namespace, + EthScope.Namespace, ]); }); }); @@ -949,8 +1065,8 @@ describe('SnapKeyring', () => { }, enabled: true, }; - mockSnapController.get.mockReturnValue(snapObject); - mockSnapController.handleRequest.mockResolvedValue({ + mockMessenger.get.mockReturnValue(snapObject); + mockMessenger.handleRequest.mockResolvedValue({ pending: true, redirect: { message: 'Go to dapp to continue.', @@ -985,9 +1101,9 @@ describe('SnapKeyring', () => { }, enabled: true, }; - mockSnapController.get.mockReturnValue(snapObject); + mockMessenger.get.mockReturnValue(snapObject); - mockSnapController.handleRequest.mockResolvedValue({ + mockMessenger.handleRequest.mockResolvedValue({ pending: true, redirect, }); @@ -996,7 +1112,7 @@ describe('SnapKeyring', () => { 'hello', ); - const { calls } = mockSnapController.handleRequest.mock; + const { calls } = mockMessenger.handleRequest.mock; const requestId = calls[calls.length - 1][0].request.params.id; await keyring.handleKeyringSnapMessage(snapId, { method: KeyringEvent.RequestRejected, @@ -1043,9 +1159,9 @@ describe('SnapKeyring', () => { url: 'https://example.com/sign?tx=1234', }; - mockSnapController.get.mockReturnValue(undefined); + mockMessenger.get.mockReturnValue(undefined); - mockSnapController.handleRequest.mockResolvedValue({ + mockMessenger.handleRequest.mockResolvedValue({ pending: true, redirect, }); @@ -1080,9 +1196,9 @@ describe('SnapKeyring', () => { }; const tx = TransactionFactory.fromTxData(mockTx); const expectedSignedTx = TransactionFactory.fromTxData(mockSignedTx); - const expectedScope = EthScopes.Mainnet; + const expectedScope = EthScope.Mainnet; - mockSnapController.handleRequest.mockResolvedValue({ + mockMessenger.handleRequest.mockResolvedValue({ pending: false, result: mockSignedTx, }); @@ -1091,7 +1207,7 @@ describe('SnapKeyring', () => { ethEoaAccount1.address, tx, ); - expect(mockSnapController.handleRequest).toHaveBeenCalledWith({ + expect(mockMessenger.handleRequest).toHaveBeenCalledWith({ snapId, handler: 'onKeyringRequest', origin: 'metamask', @@ -1158,12 +1274,12 @@ describe('SnapKeyring', () => { }, }; - const expectedScope = EthScopes.Mainnet; + const expectedScope = EthScope.Mainnet; const expectedSignature = '0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b915621c'; it('signs typed data without options', async () => { - mockSnapController.handleRequest.mockResolvedValue({ + mockMessenger.handleRequest.mockResolvedValue({ pending: false, result: expectedSignature, }); @@ -1172,7 +1288,7 @@ describe('SnapKeyring', () => { ethEoaAccount1.address, dataToSign, ); - expect(mockSnapController.handleRequest).toHaveBeenCalledWith({ + expect(mockMessenger.handleRequest).toHaveBeenCalledWith({ snapId, handler: 'onKeyringRequest', origin: 'metamask', @@ -1195,7 +1311,7 @@ describe('SnapKeyring', () => { }); it('signs typed data options (v4)', async () => { - mockSnapController.handleRequest.mockResolvedValue({ + mockMessenger.handleRequest.mockResolvedValue({ pending: false, result: expectedSignature, }); @@ -1205,7 +1321,7 @@ describe('SnapKeyring', () => { dataToSign, { version: SignTypedDataVersion.V4 }, ); - expect(mockSnapController.handleRequest).toHaveBeenCalledWith({ + expect(mockMessenger.handleRequest).toHaveBeenCalledWith({ snapId, handler: 'onKeyringRequest', origin: 'metamask', @@ -1228,7 +1344,7 @@ describe('SnapKeyring', () => { }); it('signs typed data invalid options (v2)', async () => { - mockSnapController.handleRequest.mockResolvedValue({ + mockMessenger.handleRequest.mockResolvedValue({ pending: false, result: expectedSignature, }); @@ -1238,7 +1354,7 @@ describe('SnapKeyring', () => { dataToSign, { version: 'V2' as any }, ); - expect(mockSnapController.handleRequest).toHaveBeenCalledWith({ + expect(mockMessenger.handleRequest).toHaveBeenCalledWith({ snapId, handler: 'onKeyringRequest', origin: 'metamask', @@ -1261,7 +1377,7 @@ describe('SnapKeyring', () => { }); it('signs typed data without domain chainId has no scope', async () => { - mockSnapController.handleRequest.mockResolvedValue({ + mockMessenger.handleRequest.mockResolvedValue({ pending: false, result: expectedSignature, }); @@ -1283,7 +1399,7 @@ describe('SnapKeyring', () => { dataToSignWithoutDomainChainId, { version: SignTypedDataVersion.V4 }, ); - expect(mockSnapController.handleRequest).toHaveBeenCalledWith({ + expect(mockMessenger.handleRequest).toHaveBeenCalledWith({ snapId, handler: 'onKeyringRequest', origin: 'metamask', @@ -1335,7 +1451,7 @@ describe('SnapKeyring', () => { bundlerUrl: 'https://bundler.example.com/rpc', }; - mockSnapController.handleRequest.mockReturnValueOnce({ + mockMessenger.handleRequest.mockReturnValueOnce({ pending: false, result: expectedBaseUserOp, }); @@ -1346,7 +1462,7 @@ describe('SnapKeyring', () => { executionContext, ); - expect(mockSnapController.handleRequest).toHaveBeenCalledWith({ + expect(mockMessenger.handleRequest).toHaveBeenCalledWith({ snapId, handler: 'onKeyringRequest', origin: 'metamask', @@ -1356,7 +1472,7 @@ describe('SnapKeyring', () => { method: 'keyring_submitRequest', params: { id: expect.any(String), - scope: toCaipChainId(EthScopes.Namespace, executionContext.chainId), + scope: toCaipChainId(EthScope.Namespace, executionContext.chainId), account: ethErc4337Account.id, request: { method: 'eth_prepareUserOperation', @@ -1388,7 +1504,7 @@ describe('SnapKeyring', () => { paymasterAndData: '0x1234', }; - mockSnapController.handleRequest.mockReturnValueOnce({ + mockMessenger.handleRequest.mockReturnValueOnce({ pending: false, result: expectedPatch, }); @@ -1399,7 +1515,7 @@ describe('SnapKeyring', () => { executionContext, ); - expect(mockSnapController.handleRequest).toHaveBeenCalledWith({ + expect(mockMessenger.handleRequest).toHaveBeenCalledWith({ snapId, handler: 'onKeyringRequest', origin: 'metamask', @@ -1409,7 +1525,7 @@ describe('SnapKeyring', () => { method: 'keyring_submitRequest', params: { id: expect.any(String), - scope: toCaipChainId(EthScopes.Namespace, executionContext.chainId), + scope: toCaipChainId(EthScope.Namespace, executionContext.chainId), account: ethErc4337Account.id, request: { method: 'eth_patchUserOperation', @@ -1437,7 +1553,7 @@ describe('SnapKeyring', () => { signature: '0x', }; - mockSnapController.handleRequest.mockReturnValueOnce({ + mockMessenger.handleRequest.mockReturnValueOnce({ pending: false, result: expectedSignature, }); @@ -1448,7 +1564,7 @@ describe('SnapKeyring', () => { executionContext, ); - expect(mockSnapController.handleRequest).toHaveBeenCalledWith({ + expect(mockMessenger.handleRequest).toHaveBeenCalledWith({ snapId, handler: 'onKeyringRequest', origin: 'metamask', @@ -1458,7 +1574,7 @@ describe('SnapKeyring', () => { method: 'keyring_submitRequest', params: { id: expect.any(String), - scope: toCaipChainId(EthScopes.Namespace, executionContext.chainId), + scope: toCaipChainId(EthScope.Namespace, executionContext.chainId), account: ethErc4337Account.id, request: { method: 'eth_signUserOperation', @@ -1476,7 +1592,7 @@ describe('SnapKeyring', () => { it('signs a personal message', async () => { const mockMessage = 'Hello World!'; const expectedSignature = '0x0'; - mockSnapController.handleRequest.mockResolvedValue({ + mockMessenger.handleRequest.mockResolvedValue({ pending: false, result: expectedSignature, }); @@ -1500,7 +1616,7 @@ describe('SnapKeyring', () => { const mockMessage = '0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8'; const expectedSignature = '0x0'; - mockSnapController.handleRequest.mockResolvedValue({ + mockMessenger.handleRequest.mockResolvedValue({ pending: false, result: expectedSignature, }); @@ -1509,7 +1625,7 @@ describe('SnapKeyring', () => { mockMessage, ); expect(signature).toStrictEqual(expectedSignature); - expect(mockSnapController.handleRequest).toHaveBeenCalledWith({ + expect(mockMessenger.handleRequest).toHaveBeenCalledWith({ handler: 'onKeyringRequest', origin: 'metamask', request: { @@ -1547,7 +1663,7 @@ describe('SnapKeyring', () => { }); it('removes an account', async () => { - mockSnapController.handleRequest.mockResolvedValue(null); + mockMessenger.handleRequest.mockResolvedValue(null); await keyring.removeAccount(ethEoaAccount1.address); expect(await keyring.getAccounts()).toStrictEqual([ accounts[1].address, @@ -1560,7 +1676,7 @@ describe('SnapKeyring', () => { it('removes the account and warn if Snap fails', async () => { const spy = jest.spyOn(console, 'error').mockImplementation(); - mockSnapController.handleRequest.mockRejectedValue('some error'); + mockMessenger.handleRequest.mockRejectedValue('some error'); await keyring.removeAccount(ethEoaAccount1.address); expect(await keyring.getAccounts()).toStrictEqual([ accounts[1].address, @@ -1591,7 +1707,7 @@ describe('SnapKeyring', () => { }, enabled: true, }; - mockSnapController.get.mockReturnValue(snapObject); + mockMessenger.get.mockReturnValue(snapObject); const result = keyring.listAccounts(); const expected = accounts.map((a) => ({ ...a, @@ -1624,7 +1740,7 @@ describe('SnapKeyring', () => { id: snapId, enabled: true, }; - mockSnapController.get.mockReturnValue(snapMetadata); + mockMessenger.get.mockReturnValue(snapMetadata); expect(keyring.getAccountByAddress(ethEoaAccount1.address)).toStrictEqual( { ...ethEoaAccount1, @@ -1663,7 +1779,7 @@ describe('SnapKeyring', () => { }; it('calls eth_prepareUserOperation', async () => { - mockSnapController.handleRequest.mockReturnValueOnce({ + mockMessenger.handleRequest.mockReturnValueOnce({ pending: false, result: mockExpectedUserOp, }); @@ -1674,7 +1790,7 @@ describe('SnapKeyring', () => { executionContext, ); - expect(mockSnapController.handleRequest).toHaveBeenCalledWith({ + expect(mockMessenger.handleRequest).toHaveBeenCalledWith({ handler: 'onKeyringRequest', origin: 'metamask', request: { @@ -1683,7 +1799,7 @@ describe('SnapKeyring', () => { method: 'keyring_submitRequest', params: { id: expect.any(String), - scope: toCaipChainId(EthScopes.Namespace, executionContext.chainId), + scope: toCaipChainId(EthScope.Namespace, executionContext.chainId), account: ethErc4337Account.id, request: { method: 'eth_prepareUserOperation', @@ -1696,7 +1812,7 @@ describe('SnapKeyring', () => { }); it('throws error if an pending response is returned from the Snap', async () => { - mockSnapController.handleRequest.mockReturnValueOnce({ + mockMessenger.handleRequest.mockReturnValueOnce({ pending: true, }); @@ -1730,7 +1846,7 @@ describe('SnapKeyring', () => { }; it('calls eth_patchUserOperation', async () => { - mockSnapController.handleRequest.mockReturnValueOnce({ + mockMessenger.handleRequest.mockReturnValueOnce({ pending: false, result: mockExpectedPatch, }); @@ -1741,7 +1857,7 @@ describe('SnapKeyring', () => { executionContext, ); - expect(mockSnapController.handleRequest).toHaveBeenCalledWith({ + expect(mockMessenger.handleRequest).toHaveBeenCalledWith({ handler: 'onKeyringRequest', origin: 'metamask', request: { @@ -1750,7 +1866,7 @@ describe('SnapKeyring', () => { method: 'keyring_submitRequest', params: { id: expect.any(String), - scope: toCaipChainId(EthScopes.Namespace, executionContext.chainId), + scope: toCaipChainId(EthScope.Namespace, executionContext.chainId), account: ethErc4337Account.id, request: { method: 'eth_patchUserOperation', @@ -1763,7 +1879,7 @@ describe('SnapKeyring', () => { }); it('throws error if an pending response is returned from the Snap', async () => { - mockSnapController.handleRequest.mockReturnValueOnce({ + mockMessenger.handleRequest.mockReturnValueOnce({ pending: true, }); diff --git a/packages/keyring-snap-bridge/src/SnapKeyring.ts b/packages/keyring-snap-bridge/src/SnapKeyring.ts index 68dd7800..d76d4b40 100644 --- a/packages/keyring-snap-bridge/src/SnapKeyring.ts +++ b/packages/keyring-snap-bridge/src/SnapKeyring.ts @@ -4,6 +4,7 @@ import type { TypedTransaction } from '@ethereumjs/tx'; import { TransactionFactory } from '@ethereumjs/tx'; +import type { ExtractEventPayload } from '@metamask/base-controller'; import type { TypedDataV1, TypedMessage } from '@metamask/eth-sig-util'; import { SignTypedDataVersion } from '@metamask/eth-sig-util'; import { @@ -13,6 +14,9 @@ import { EthUserOperationPatchStruct, isEvmAccountType, KeyringEvent, + AccountAssetListUpdatedEventStruct, + AccountBalancesUpdatedEventStruct, + AccountTransactionsUpdatedEventStruct, } from '@metamask/keyring-api'; import type { KeyringAccount, @@ -25,9 +29,8 @@ import type { EthUserOperationPatch, } from '@metamask/keyring-api'; import type { InternalAccount } from '@metamask/keyring-internal-api'; -import { KeyringSnapControllerClient } from '@metamask/keyring-internal-snap-client'; +import { KeyringInternalSnapClient } from '@metamask/keyring-internal-snap-client'; import { strictMask } from '@metamask/keyring-utils'; -import type { SnapController } from '@metamask/snaps-controllers'; import type { SnapId } from '@metamask/snaps-sdk'; import { type Snap } from '@metamask/snaps-utils'; import { assert, mask, object, string } from '@metamask/superstruct'; @@ -40,6 +43,7 @@ import { import { EventEmitter } from 'events'; import { v4 as uuid } from 'uuid'; +import { transformAccount } from './account'; import { DeferredPromise } from './DeferredPromise'; import { AccountCreatedEventStruct, @@ -49,12 +53,12 @@ import { RequestRejectedEventStruct, } from './events'; import { projectLogger as log } from './logger'; -import { - isAccountV1, - migrateAccountV1, - transformAccountV1, -} from './migrations'; +import { isAccountV1, migrateAccountV1 } from './migrations'; import { SnapIdMap } from './SnapIdMap'; +import type { + SnapKeyringEvents, + SnapKeyringMessenger, +} from './SnapKeyringMessenger'; import type { SnapMessage } from './types'; import { SnapMessageStruct } from './types'; import { @@ -131,10 +135,15 @@ export class SnapKeyring extends EventEmitter { type: string; + /** + * Messenger to dispatch requests to the Snaps controller. + */ + readonly #messenger: SnapKeyringMessenger; + /** * Client used to call the Snap keyring. */ - readonly #snapClient: KeyringSnapControllerClient; + readonly #snapClient: KeyringInternalSnapClient; /** * Mapping between account IDs and an object that contains the associated @@ -161,14 +170,18 @@ export class SnapKeyring extends EventEmitter { /** * Create a new Snap keyring. * - * @param controller - Snaps controller. + * @param messenger - Snap keyring messenger. * @param callbacks - Callbacks used to interact with other components. * @returns A new Snap keyring. */ - constructor(controller: SnapController, callbacks: SnapKeyringCallbacks) { + constructor( + messenger: SnapKeyringMessenger, + callbacks: SnapKeyringCallbacks, + ) { super(); this.type = SnapKeyring.type; - this.#snapClient = new KeyringSnapControllerClient({ controller }); + this.#messenger = messenger; + this.#snapClient = new KeyringInternalSnapClient({ messenger }); this.#requests = new SnapIdMap(); this.#accounts = new SnapIdMap(); this.#callbacks = callbacks; @@ -192,9 +205,8 @@ export class SnapKeyring extends EventEmitter { displayConfirmation, } = message.params; - // To keep the retro-compatibility with older keyring-api versions, we mark some fields - // as optional and provide them here if they are missing. - const account = transformAccountV1(newAccountFromEvent); + // Potentially migrate the account. + const account = transformAccount(newAccountFromEvent); // The UI still uses the account address to identify accounts, so we need // to block the creation of duplicate accounts for now to prevent accounts @@ -242,9 +254,8 @@ export class SnapKeyring extends EventEmitter { this.#accounts.get(snapId, newAccountFromEvent.id) ?? throwError(`Account '${newAccountFromEvent.id}' not found`); - // To keep the retro-compatibility with older keyring-api versions, we mark some fields - // as optional and provide them here if they are missing. - const newAccount = transformAccountV1(newAccountFromEvent); + // Potentially migrate the account. + const newAccount = transformAccount(newAccountFromEvent); // The address of the account cannot be changed. In the future, we will // support changing the address of an account since it will be required to @@ -340,6 +351,65 @@ export class SnapKeyring extends EventEmitter { return null; } + /** + * Re-publish an account event. + * + * @param event - The event type. This is a unique identifier for this event. + * @param payload - The event payload. The type of the parameters for each event handler must + * match the type of this payload. + * @template EventType - A Snap keyring event type. + * @returns `null`. + */ + async #rePublishAccountEvent( + event: EventType, + ...payload: ExtractEventPayload + ): Promise { + this.#messenger.publish(event, ...payload); + return null; + } + + /** + * Handle a balances updated event from a Snap. + * + * @param message - Event message. + * @returns `null`. + */ + async #handleAccountBalancesUpdated(message: SnapMessage): Promise { + assert(message, AccountBalancesUpdatedEventStruct); + return this.#rePublishAccountEvent( + 'SnapKeyring:accountBalancesUpdated', + message.params, + ); + } + + /** + * Handle a asset list updated event from a Snap. + * + * @param message - Event message. + * @returns `null`. + */ + async #handleAccountAssetListUpdated(message: SnapMessage): Promise { + assert(message, AccountAssetListUpdatedEventStruct); + return this.#rePublishAccountEvent( + 'SnapKeyring:accountAssetListUpdated', + message.params, + ); + } + + /** + * Handle a transactions updated event from a Snap. + * + * @param message - Event message. + * @returns `null`. + */ + async #handleAccountTransactionsUpdated(message: SnapMessage): Promise { + assert(message, AccountTransactionsUpdatedEventStruct); + return this.#rePublishAccountEvent( + 'SnapKeyring:accountTransactionsUpdated', + message.params, + ); + } + /** * Handle a message from a Snap. * @@ -373,6 +443,19 @@ export class SnapKeyring extends EventEmitter { return this.#handleRequestRejected(snapId, message); } + // Assets related events: + case `${KeyringEvent.AccountBalancesUpdated}`: { + return this.#handleAccountBalancesUpdated(message); + } + + case `${KeyringEvent.AccountAssetListUpdated}`: { + return this.#handleAccountAssetListUpdated(message); + } + + case `${KeyringEvent.AccountTransactionsUpdated}`: { + return this.#handleAccountTransactionsUpdated(message); + } + default: throw new Error(`Method not supported: ${message.method}`); } @@ -658,7 +741,7 @@ export class SnapKeyring extends EventEmitter { */ #validateRedirectUrl(url: string, snapId: SnapId): void { const { origin } = new URL(url); - const snap = this.#snapClient.getController().get(snapId); + const snap = this.#getSnap(snapId); if (!snap) { throw new Error(`Snap '${snapId}' not found.`); } @@ -934,6 +1017,16 @@ export class SnapKeyring extends EventEmitter { ); } + /** + * Get the Snap associated with the given Snap ID. + * + * @param snapId - Snap ID. + * @returns The Snap or undefined if the Snap cannot be found. + */ + #getSnap(snapId: SnapId): Snap | undefined { + return this.#messenger.call('SnapController:get', snapId); + } + /** * Get the metadata of a Snap keyring account. * @@ -943,7 +1036,7 @@ export class SnapKeyring extends EventEmitter { #getSnapMetadata( snapId: SnapId, ): InternalAccount['metadata']['snap'] | undefined { - const snap = this.#snapClient.getController().get(snapId); + const snap = this.#getSnap(snapId); return snap ? { id: snapId, name: snap.manifest.proposedName, enabled: snap.enabled } : undefined; diff --git a/packages/keyring-snap-bridge/src/SnapKeyringMessenger.ts b/packages/keyring-snap-bridge/src/SnapKeyringMessenger.ts new file mode 100644 index 00000000..8f1e4226 --- /dev/null +++ b/packages/keyring-snap-bridge/src/SnapKeyringMessenger.ts @@ -0,0 +1,42 @@ +import type { RestrictedControllerMessenger } from '@metamask/base-controller'; +import type { + AccountAssetListUpdatedEventPayload, + AccountBalancesUpdatedEventPayload, + AccountTransactionsUpdatedEventPayload, +} from '@metamask/keyring-api'; +import type { HandleSnapRequest, GetSnap } from '@metamask/snaps-controllers'; + +export type SnapKeyringGetAccountsAction = { + type: `SnapKeyring:getAccounts`; + handler: () => string[]; +}; + +export type SnapKeyringAccountBalancesUpdatedEvent = { + type: `SnapKeyring:accountBalancesUpdated`; + payload: [AccountBalancesUpdatedEventPayload]; +}; + +export type SnapKeyringAccountAssetListUpdatedEvent = { + type: `SnapKeyring:accountAssetListUpdated`; + payload: [AccountAssetListUpdatedEventPayload]; +}; + +export type SnapKeyringAccountTransactionsUpdatedEvent = { + type: `SnapKeyring:accountTransactionsUpdated`; + payload: [AccountTransactionsUpdatedEventPayload]; +}; + +export type SnapKeyringEvents = + | SnapKeyringAccountAssetListUpdatedEvent + | SnapKeyringAccountBalancesUpdatedEvent + | SnapKeyringAccountTransactionsUpdatedEvent; + +export type SnapKeyringAllowedActions = HandleSnapRequest | GetSnap; + +export type SnapKeyringMessenger = RestrictedControllerMessenger< + 'SnapKeyring', + SnapKeyringAllowedActions, + SnapKeyringEvents, + SnapKeyringAllowedActions['type'], + never +>; diff --git a/packages/keyring-snap-bridge/src/account.test.ts b/packages/keyring-snap-bridge/src/account.test.ts new file mode 100644 index 00000000..d56d62cc --- /dev/null +++ b/packages/keyring-snap-bridge/src/account.test.ts @@ -0,0 +1,18 @@ +import type { KeyringAccount } from '@metamask/keyring-api'; + +import { transformAccount } from './account'; + +describe('account', () => { + it('throws for unknown account type', () => { + const unknownAccount = { + // This should not be really possible to create such account, but since we potentially + // migrate data upon the Snap keyring initialization, we want to cover edge-cases + // like this one to avoid crashing and blocking everything... + type: 'unknown:type', + } as unknown as KeyringAccount; // Just testing the default case. + + expect(() => transformAccount(unknownAccount)).toThrow( + "Unknown account type: 'unknown:type'", + ); + }); +}); diff --git a/packages/keyring-snap-bridge/src/account.ts b/packages/keyring-snap-bridge/src/account.ts index 74c7bae1..69d0d974 100644 --- a/packages/keyring-snap-bridge/src/account.ts +++ b/packages/keyring-snap-bridge/src/account.ts @@ -1,5 +1,17 @@ -import { KeyringAccountStruct } from '@metamask/keyring-api'; -import { omit, type Infer } from '@metamask/superstruct'; +import type { KeyringAccount, KeyringAccountType } from '@metamask/keyring-api'; +import { + BtcAccountType, + BtcP2wpkhAccountStruct, + EthAccountType, + EthEoaAccountStruct, + EthErc4337AccountStruct, + KeyringAccountStruct, + SolAccountType, + SolDataAccountStruct, +} from '@metamask/keyring-api'; +import { assert, omit, type Infer } from '@metamask/superstruct'; + +import { isAccountV1, transformAccountV1 } from './migrations'; /** * A `KeyringAccount` with some optional fields which can be used to keep @@ -8,3 +20,60 @@ import { omit, type Infer } from '@metamask/superstruct'; export const KeyringAccountV1Struct = omit(KeyringAccountStruct, ['scopes']); export type KeyringAccountV1 = Infer; + +/** + * Assert that an account-like object matches its actual account type. + * + * @param account - The account-like object. + * @returns The account as normal `KeyringAccount`. + */ +export function assertKeyringAccount< + Account extends { type: KeyringAccountType }, +>(account: Account): KeyringAccount { + // TODO: We should use a `selectiveUnion` for this and probably use it to define + // the `KeyringAccount`. This would also required to have a "generic `KeyringAccount`" + // definition. + switch (account.type) { + case BtcAccountType.P2wpkh: { + assert(account, BtcP2wpkhAccountStruct); + return account; + } + case SolAccountType.DataAccount: { + assert(account, SolDataAccountStruct); + return account; + } + case EthAccountType.Erc4337: { + assert(account, EthErc4337AccountStruct); + return account; + } + case EthAccountType.Eoa: { + assert(account, EthEoaAccountStruct); + return account; + } + default: { + // For now, we cannot much more than this (this should also, never happen)! + // NOTE: We could use a "generic `KeyringAccount` type" here though. + throw new Error(`Unknown account type: '${account.type}'`); + } + } +} + +/** + * Transform any versionned account to a `KeyringAccount`. + * + * @param accountToTransform - The account to transform. + * @returns A valid transformed `KeyringAccount`. + */ +export function transformAccount( + // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents + accountToTransform: KeyringAccountV1 | KeyringAccount, +): KeyringAccount { + // To keep the retro-compatibility with older keyring-api versions, we identify the account's + // version and transform it to the latest `KeyringAccount` representation. + const account = isAccountV1(accountToTransform) + ? transformAccountV1(accountToTransform) + : accountToTransform; + + // We still assert that the converted account is valid according to their account's type. + return assertKeyringAccount(account); +} diff --git a/packages/keyring-snap-bridge/src/index.ts b/packages/keyring-snap-bridge/src/index.ts index d01d020e..ceb4da7e 100644 --- a/packages/keyring-snap-bridge/src/index.ts +++ b/packages/keyring-snap-bridge/src/index.ts @@ -1,2 +1,3 @@ export * from './types'; export * from './SnapKeyring'; +export type * from './SnapKeyringMessenger'; diff --git a/packages/keyring-snap-bridge/src/migrations/v1.ts b/packages/keyring-snap-bridge/src/migrations/v1.ts index d99e362c..56dc9c02 100644 --- a/packages/keyring-snap-bridge/src/migrations/v1.ts +++ b/packages/keyring-snap-bridge/src/migrations/v1.ts @@ -1,16 +1,20 @@ import { BtcAccountType, - BtcScopes, + BtcScope, EthAccountType, - EthScopes, + EthScope, SolAccountType, - SolScopes, + SolScope, type KeyringAccount, } from '@metamask/keyring-api'; import { isBtcMainnetAddress } from '@metamask/keyring-utils'; import { is } from '@metamask/superstruct'; -import { KeyringAccountV1Struct, type KeyringAccountV1 } from '../account'; +import { + assertKeyringAccount, + KeyringAccountV1Struct, + type KeyringAccountV1, +} from '../account'; /** * Checks if an account is an `KeyringAccount` v1. @@ -36,30 +40,31 @@ export function getScopesForAccountV1(accountV1: KeyringAccountV1): string[] { case EthAccountType.Eoa: { // EVM EOA account are compatible with any EVM networks, and we use CAIP-2 // namespaces when the scope relates to ALL chains (from that namespace). - return [EthScopes.Namespace]; + return [EthScope.Namespace]; } case EthAccountType.Erc4337: { // EVM Erc4337 account - // NOTE: A Smart Contract account might not be compatible with every chain, but we still use - // "generic" scope for now. Also, there's no official Snap as of today that uses this account type. So - // this case should never happen. - return [EthScopes.Namespace]; + // NOTE: A Smart Contract account might not be compatible with every chain, in this case we just default + // to testnet since we cannot really "guess" it from here. + // Also, there's no official Snap as of today that uses this account type. So this case should never happen + // in production. + return [EthScope.Testnet]; } case BtcAccountType.P2wpkh: { // Bitcoin uses different accounts for testnet and mainnet return [ isBtcMainnetAddress(accountV1.address) - ? BtcScopes.Mainnet - : BtcScopes.Testnet, + ? BtcScope.Mainnet + : BtcScope.Testnet, ]; } case SolAccountType.DataAccount: { // Solana account supports multiple chains. - return [SolScopes.Mainnet, SolScopes.Testnet, SolScopes.Devnet]; + return [SolScope.Mainnet, SolScope.Testnet, SolScope.Devnet]; } default: // We re-use EOA scopes if we don't know what to do for now. - return [EthScopes.Namespace]; + return [EthScope.Namespace]; } } @@ -77,24 +82,20 @@ export function transformAccountV1( ): KeyringAccount { const { type } = accountV1; - if (!isAccountV1(accountV1)) { - // Nothing to do in this case. - return accountV1 as KeyringAccount; - } - + // EVM EOA account are compatible with any EVM networks, and we use CAIP-2 + // namespaces when the scope relates to ALL chains (from that namespace). + // So we can automatically inject a valid `scopes` for this, but not for + // other kind of accounts. if (type === EthAccountType.Eoa) { - // EVM EOA account are compatible with any EVM networks, and we use CAIP-2 - // namespaces when the scope relates to ALL chains (from that namespace). return { ...accountV1, scopes: getScopesForAccountV1(accountV1), }; } - // For all non-EVM Snaps and ERC4337 Snaps, the scopes is required. - throw new Error( - `Account scopes is required for non-EVM and ERC4337 accounts`, - ); + // For all other non-EVM and ERC4337 Snap accounts, the `scopes` is required, and + // each `*AccountStruct` should assert that automatically. + return assertKeyringAccount(accountV1); } /** diff --git a/packages/keyring-snap-client/CHANGELOG.md b/packages/keyring-snap-client/CHANGELOG.md index 009cb80a..844a573e 100644 --- a/packages/keyring-snap-client/CHANGELOG.md +++ b/packages/keyring-snap-client/CHANGELOG.md @@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [3.0.1] + +### Changed + +- Bump `@metamask/keyring-api` from `^14.0.0` to `^15.0.0` ([#160](https://github.com/MetaMask/accounts/pull/160)) + +## [3.0.0] + +### Added + +- Add `listAccountAssets` keyring method ([#148](https://github.com/MetaMask/accounts/pull/148)) + +### Changed + +- **BREAKING:** Bump `@metamask/keyring-api` from `^13.0.0` to `^14.0.0` ([#155](https://github.com/MetaMask/accounts/pull/155)) + - The `CaipAssetType` is now more restrictive which affects the existing `getAccountBalances` method. + ## [2.0.0] ### Changed @@ -29,7 +46,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - This new version fixes a bug with CJS re-exports. - Initial release ([#24](https://github.com/MetaMask/accounts/pull/24)) -[Unreleased]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-snap-client@2.0.0...HEAD +[Unreleased]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-snap-client@3.0.1...HEAD +[3.0.1]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-snap-client@3.0.0...@metamask/keyring-snap-client@3.0.1 +[3.0.0]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-snap-client@2.0.0...@metamask/keyring-snap-client@3.0.0 [2.0.0]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-snap-client@1.1.0...@metamask/keyring-snap-client@2.0.0 [1.1.0]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-snap-client@1.0.0...@metamask/keyring-snap-client@1.1.0 [1.0.0]: https://github.com/MetaMask/accounts/releases/tag/@metamask/keyring-snap-client@1.0.0 diff --git a/packages/keyring-snap-client/package.json b/packages/keyring-snap-client/package.json index ef364ac4..0a162fe1 100644 --- a/packages/keyring-snap-client/package.json +++ b/packages/keyring-snap-client/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/keyring-snap-client", - "version": "2.0.0", + "version": "3.0.1", "description": "MetaMask Keyring Snap clients", "keywords": [ "metamask", diff --git a/packages/keyring-snap-sdk/CHANGELOG.md b/packages/keyring-snap-sdk/CHANGELOG.md index f9b62743..3f14a028 100644 --- a/packages/keyring-snap-sdk/CHANGELOG.md +++ b/packages/keyring-snap-sdk/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.1.0] + +### Added + +- Add support of `listAccountAssets` keyring method ([#148](https://github.com/MetaMask/accounts/pull/148)) + ## [2.0.0] ### Changed @@ -29,7 +35,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - This new version fixes a bug with CJS re-exports. - Initial release ([#24](https://github.com/MetaMask/accounts/pull/24)) -[Unreleased]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-snap-sdk@2.0.0...HEAD +[Unreleased]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-snap-sdk@2.1.0...HEAD +[2.1.0]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-snap-sdk@2.0.0...@metamask/keyring-snap-sdk@2.1.0 [2.0.0]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-snap-sdk@1.1.0...@metamask/keyring-snap-sdk@2.0.0 [1.1.0]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-snap-sdk@1.0.0...@metamask/keyring-snap-sdk@1.1.0 [1.0.0]: https://github.com/MetaMask/accounts/releases/tag/@metamask/keyring-snap-sdk@1.0.0 diff --git a/packages/keyring-snap-sdk/package.json b/packages/keyring-snap-sdk/package.json index 24df5c01..50b1f136 100644 --- a/packages/keyring-snap-sdk/package.json +++ b/packages/keyring-snap-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/keyring-snap-sdk", - "version": "2.0.0", + "version": "2.1.0", "description": "MetaMask Keyring Snap SDK", "keywords": [ "metamask", diff --git a/packages/keyring-utils/CHANGELOG.md b/packages/keyring-utils/CHANGELOG.md index ba1a4c2b..85e77a7b 100644 --- a/packages/keyring-utils/CHANGELOG.md +++ b/packages/keyring-utils/CHANGELOG.md @@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.3.0] + +### Added + +- Add `AccountIdStruct` alias ([#154](https://github.com/MetaMask/accounts/pull/154)) + +## [1.2.0] + +### Changed + +- Add generic type in `definePattern` ([#150](https://github.com/MetaMask/accounts/pull/150)) + - It allows to use template literal type that matches the pattern. + ## [1.1.0] ### Added @@ -25,6 +38,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - This new version fixes a bug with CJS re-exports. - Initial release ([#24](https://github.com/MetaMask/accounts/pull/24)) -[Unreleased]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-utils@1.1.0...HEAD +[Unreleased]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-utils@1.3.0...HEAD +[1.3.0]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-utils@1.2.0...@metamask/keyring-utils@1.3.0 +[1.2.0]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-utils@1.1.0...@metamask/keyring-utils@1.2.0 [1.1.0]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-utils@1.0.0...@metamask/keyring-utils@1.1.0 [1.0.0]: https://github.com/MetaMask/accounts/releases/tag/@metamask/keyring-utils@1.0.0 diff --git a/packages/keyring-utils/package.json b/packages/keyring-utils/package.json index 6615e525..93b79223 100644 --- a/packages/keyring-utils/package.json +++ b/packages/keyring-utils/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/keyring-utils", - "version": "1.1.0", + "version": "1.3.0", "description": "MetaMask Keyring utils", "keywords": [ "metamask", diff --git a/packages/keyring-utils/src/types.ts b/packages/keyring-utils/src/types.ts index f00db535..89d2dde6 100644 --- a/packages/keyring-utils/src/types.ts +++ b/packages/keyring-utils/src/types.ts @@ -9,6 +9,10 @@ export const UuidStruct = definePattern( 'UuidV4', /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu, ); +/** + * Account ID (UUIDv4). + */ +export const AccountIdStruct = UuidStruct; // Alias for better naming purposes. /** * Validates if a given value is a valid URL. diff --git a/yarn.lock b/yarn.lock index 11e34a30..4908738f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -676,7 +676,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/abi@npm:5.7.0, @ethersproject/abi@npm:^5.5.0, @ethersproject/abi@npm:^5.7.0": +"@ethersproject/abi@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/abi@npm:5.7.0" dependencies: @@ -693,7 +693,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/abstract-provider@npm:5.7.0, @ethersproject/abstract-provider@npm:^5.7.0": +"@ethersproject/abstract-provider@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/abstract-provider@npm:5.7.0" dependencies: @@ -708,7 +708,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/abstract-signer@npm:5.7.0, @ethersproject/abstract-signer@npm:^5.7.0": +"@ethersproject/abstract-signer@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/abstract-signer@npm:5.7.0" dependencies: @@ -721,7 +721,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/address@npm:5.7.0, @ethersproject/address@npm:^5.7.0": +"@ethersproject/address@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/address@npm:5.7.0" dependencies: @@ -734,7 +734,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/base64@npm:5.7.0, @ethersproject/base64@npm:^5.7.0": +"@ethersproject/base64@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/base64@npm:5.7.0" dependencies: @@ -743,17 +743,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/basex@npm:5.7.0, @ethersproject/basex@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/basex@npm:5.7.0" - dependencies: - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/properties": "npm:^5.7.0" - checksum: 10/840e333e109bff2fcf8d91dcfd45fa951835844ef0e1ba710037e87291c7b5f3c189ba86f6cee2ca7de2ede5b7d59fbb930346607695855bee20d2f9f63371ef - languageName: node - linkType: hard - -"@ethersproject/bignumber@npm:5.7.0, @ethersproject/bignumber@npm:^5.7.0": +"@ethersproject/bignumber@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/bignumber@npm:5.7.0" dependencies: @@ -764,7 +754,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/bytes@npm:5.7.0, @ethersproject/bytes@npm:^5.7.0": +"@ethersproject/bytes@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/bytes@npm:5.7.0" dependencies: @@ -773,7 +763,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/constants@npm:5.7.0, @ethersproject/constants@npm:^5.7.0": +"@ethersproject/constants@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/constants@npm:5.7.0" dependencies: @@ -782,25 +772,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/contracts@npm:5.7.0": - version: 5.7.0 - resolution: "@ethersproject/contracts@npm:5.7.0" - dependencies: - "@ethersproject/abi": "npm:^5.7.0" - "@ethersproject/abstract-provider": "npm:^5.7.0" - "@ethersproject/abstract-signer": "npm:^5.7.0" - "@ethersproject/address": "npm:^5.7.0" - "@ethersproject/bignumber": "npm:^5.7.0" - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/constants": "npm:^5.7.0" - "@ethersproject/logger": "npm:^5.7.0" - "@ethersproject/properties": "npm:^5.7.0" - "@ethersproject/transactions": "npm:^5.7.0" - checksum: 10/5df66179af242faabea287a83fd2f8f303a4244dc87a6ff802e1e3b643f091451295c8e3d088c7739970b7915a16a581c192d4e007d848f1fdf3cc9e49010053 - languageName: node - linkType: hard - -"@ethersproject/hash@npm:5.7.0, @ethersproject/hash@npm:^5.7.0": +"@ethersproject/hash@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/hash@npm:5.7.0" dependencies: @@ -817,48 +789,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/hdnode@npm:5.7.0, @ethersproject/hdnode@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/hdnode@npm:5.7.0" - dependencies: - "@ethersproject/abstract-signer": "npm:^5.7.0" - "@ethersproject/basex": "npm:^5.7.0" - "@ethersproject/bignumber": "npm:^5.7.0" - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/logger": "npm:^5.7.0" - "@ethersproject/pbkdf2": "npm:^5.7.0" - "@ethersproject/properties": "npm:^5.7.0" - "@ethersproject/sha2": "npm:^5.7.0" - "@ethersproject/signing-key": "npm:^5.7.0" - "@ethersproject/strings": "npm:^5.7.0" - "@ethersproject/transactions": "npm:^5.7.0" - "@ethersproject/wordlists": "npm:^5.7.0" - checksum: 10/2fbe6278c324235afaa88baa5dea24d8674c72b14ad037fe2096134d41025977f410b04fd146e333a1b6cac9482e9de62d6375d1705fd42667543f2d0eb66655 - languageName: node - linkType: hard - -"@ethersproject/json-wallets@npm:5.7.0, @ethersproject/json-wallets@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/json-wallets@npm:5.7.0" - dependencies: - "@ethersproject/abstract-signer": "npm:^5.7.0" - "@ethersproject/address": "npm:^5.7.0" - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/hdnode": "npm:^5.7.0" - "@ethersproject/keccak256": "npm:^5.7.0" - "@ethersproject/logger": "npm:^5.7.0" - "@ethersproject/pbkdf2": "npm:^5.7.0" - "@ethersproject/properties": "npm:^5.7.0" - "@ethersproject/random": "npm:^5.7.0" - "@ethersproject/strings": "npm:^5.7.0" - "@ethersproject/transactions": "npm:^5.7.0" - aes-js: "npm:3.0.0" - scrypt-js: "npm:3.0.1" - checksum: 10/4a1ef0912ffc8d18c392ae4e292948d86bffd715fe3dd3e66d1cd21f6c9267aeadad4da84261db853327f97cdfd765a377f9a87e39d4c6749223a69226faf0a1 - languageName: node - linkType: hard - -"@ethersproject/keccak256@npm:5.7.0, @ethersproject/keccak256@npm:^5.7.0": +"@ethersproject/keccak256@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/keccak256@npm:5.7.0" dependencies: @@ -868,14 +799,14 @@ __metadata: languageName: node linkType: hard -"@ethersproject/logger@npm:5.7.0, @ethersproject/logger@npm:^5.7.0": +"@ethersproject/logger@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/logger@npm:5.7.0" checksum: 10/683a939f467ae7510deedc23d7611d0932c3046137f5ffb92ba1e3c8cd9cf2fbbaa676b660c248441a0fa9143783137c46d6e6d17d676188dd5a6ef0b72dd091 languageName: node linkType: hard -"@ethersproject/networks@npm:5.7.1, @ethersproject/networks@npm:^5.7.0": +"@ethersproject/networks@npm:^5.7.0": version: 5.7.1 resolution: "@ethersproject/networks@npm:5.7.1" dependencies: @@ -884,17 +815,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/pbkdf2@npm:5.7.0, @ethersproject/pbkdf2@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/pbkdf2@npm:5.7.0" - dependencies: - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/sha2": "npm:^5.7.0" - checksum: 10/dea7ba747805e24b81dfb99e695eb329509bf5cad1a42e48475ade28e060e567458a3d5bf930f302691bded733fd3fa364f0c7adce920f9f05a5ef8c13267aaa - languageName: node - linkType: hard - -"@ethersproject/properties@npm:5.7.0, @ethersproject/properties@npm:^5.7.0": +"@ethersproject/properties@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/properties@npm:5.7.0" dependencies: @@ -903,45 +824,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/providers@npm:5.7.2": - version: 5.7.2 - resolution: "@ethersproject/providers@npm:5.7.2" - dependencies: - "@ethersproject/abstract-provider": "npm:^5.7.0" - "@ethersproject/abstract-signer": "npm:^5.7.0" - "@ethersproject/address": "npm:^5.7.0" - "@ethersproject/base64": "npm:^5.7.0" - "@ethersproject/basex": "npm:^5.7.0" - "@ethersproject/bignumber": "npm:^5.7.0" - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/constants": "npm:^5.7.0" - "@ethersproject/hash": "npm:^5.7.0" - "@ethersproject/logger": "npm:^5.7.0" - "@ethersproject/networks": "npm:^5.7.0" - "@ethersproject/properties": "npm:^5.7.0" - "@ethersproject/random": "npm:^5.7.0" - "@ethersproject/rlp": "npm:^5.7.0" - "@ethersproject/sha2": "npm:^5.7.0" - "@ethersproject/strings": "npm:^5.7.0" - "@ethersproject/transactions": "npm:^5.7.0" - "@ethersproject/web": "npm:^5.7.0" - bech32: "npm:1.1.4" - ws: "npm:7.4.6" - checksum: 10/8534a1896e61b9f0b66427a639df64a5fe76d0c08ec59b9f0cc64fdd1d0cc28d9fc3312838ae8d7817c8f5e2e76b7f228b689bc33d1cbb8e1b9517d4c4f678d8 - languageName: node - linkType: hard - -"@ethersproject/random@npm:5.7.0, @ethersproject/random@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/random@npm:5.7.0" - dependencies: - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/logger": "npm:^5.7.0" - checksum: 10/c23ec447998ce1147651bd58816db4d12dbeb404f66a03d14a13e1edb439879bab18528e1fc46b931502903ac7b1c08ea61d6a86e621a6e060fa63d41aeed3ac - languageName: node - linkType: hard - -"@ethersproject/rlp@npm:5.7.0, @ethersproject/rlp@npm:^5.5.0, @ethersproject/rlp@npm:^5.7.0": +"@ethersproject/rlp@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/rlp@npm:5.7.0" dependencies: @@ -951,18 +834,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/sha2@npm:5.7.0, @ethersproject/sha2@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/sha2@npm:5.7.0" - dependencies: - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/logger": "npm:^5.7.0" - hash.js: "npm:1.1.7" - checksum: 10/09321057c022effbff4cc2d9b9558228690b5dd916329d75c4b1ffe32ba3d24b480a367a7cc92d0f0c0b1c896814d03351ae4630e2f1f7160be2bcfbde435dbc - languageName: node - linkType: hard - -"@ethersproject/signing-key@npm:5.7.0, @ethersproject/signing-key@npm:^5.7.0": +"@ethersproject/signing-key@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/signing-key@npm:5.7.0" dependencies: @@ -976,21 +848,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/solidity@npm:5.7.0": - version: 5.7.0 - resolution: "@ethersproject/solidity@npm:5.7.0" - dependencies: - "@ethersproject/bignumber": "npm:^5.7.0" - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/keccak256": "npm:^5.7.0" - "@ethersproject/logger": "npm:^5.7.0" - "@ethersproject/sha2": "npm:^5.7.0" - "@ethersproject/strings": "npm:^5.7.0" - checksum: 10/9a02f37f801c96068c3e7721f83719d060175bc4e80439fe060e92bd7acfcb6ac1330c7e71c49f4c2535ca1308f2acdcb01e00133129aac00581724c2d6293f3 - languageName: node - linkType: hard - -"@ethersproject/strings@npm:5.7.0, @ethersproject/strings@npm:^5.7.0": +"@ethersproject/strings@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/strings@npm:5.7.0" dependencies: @@ -1001,7 +859,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/transactions@npm:5.7.0, @ethersproject/transactions@npm:^5.7.0": +"@ethersproject/transactions@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/transactions@npm:5.7.0" dependencies: @@ -1018,41 +876,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/units@npm:5.7.0": - version: 5.7.0 - resolution: "@ethersproject/units@npm:5.7.0" - dependencies: - "@ethersproject/bignumber": "npm:^5.7.0" - "@ethersproject/constants": "npm:^5.7.0" - "@ethersproject/logger": "npm:^5.7.0" - checksum: 10/304714f848cd32e57df31bf545f7ad35c2a72adae957198b28cbc62166daa929322a07bff6e9c9ac4577ab6aa0de0546b065ed1b2d20b19e25748b7d475cb0fc - languageName: node - linkType: hard - -"@ethersproject/wallet@npm:5.7.0": - version: 5.7.0 - resolution: "@ethersproject/wallet@npm:5.7.0" - dependencies: - "@ethersproject/abstract-provider": "npm:^5.7.0" - "@ethersproject/abstract-signer": "npm:^5.7.0" - "@ethersproject/address": "npm:^5.7.0" - "@ethersproject/bignumber": "npm:^5.7.0" - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/hash": "npm:^5.7.0" - "@ethersproject/hdnode": "npm:^5.7.0" - "@ethersproject/json-wallets": "npm:^5.7.0" - "@ethersproject/keccak256": "npm:^5.7.0" - "@ethersproject/logger": "npm:^5.7.0" - "@ethersproject/properties": "npm:^5.7.0" - "@ethersproject/random": "npm:^5.7.0" - "@ethersproject/signing-key": "npm:^5.7.0" - "@ethersproject/transactions": "npm:^5.7.0" - "@ethersproject/wordlists": "npm:^5.7.0" - checksum: 10/340f8e5c77c6c47c4d1596c200d97c53c1d4b4eb54d9166d0f2a114cb81685e7689255b0627e917fbcdc29cb54c4bd1f1a9909f3096ef9dff9acc0b24972f1c1 - languageName: node - linkType: hard - -"@ethersproject/web@npm:5.7.1, @ethersproject/web@npm:^5.7.0": +"@ethersproject/web@npm:^5.7.0": version: 5.7.1 resolution: "@ethersproject/web@npm:5.7.1" dependencies: @@ -1065,19 +889,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/wordlists@npm:5.7.0, @ethersproject/wordlists@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/wordlists@npm:5.7.0" - dependencies: - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/hash": "npm:^5.7.0" - "@ethersproject/logger": "npm:^5.7.0" - "@ethersproject/properties": "npm:^5.7.0" - "@ethersproject/strings": "npm:^5.7.0" - checksum: 10/737fca67ad743a32020f50f5b9e147e5683cfba2692367c1124a5a5538be78515865257b426ec9141daac91a70295e5e21bef7a193b79fe745f1be378562ccaa - languageName: node - linkType: hard - "@fivebinaries/coin-selection@npm:2.2.1": version: 2.2.1 resolution: "@fivebinaries/coin-selection@npm:2.2.1" @@ -1484,13 +1295,13 @@ __metadata: languageName: node linkType: hard -"@ledgerhq/cryptoassets-evm-signatures@npm:^13.5.0": - version: 13.5.0 - resolution: "@ledgerhq/cryptoassets-evm-signatures@npm:13.5.0" +"@ledgerhq/cryptoassets-evm-signatures@npm:^13.5.2": + version: 13.5.2 + resolution: "@ledgerhq/cryptoassets-evm-signatures@npm:13.5.2" dependencies: - "@ledgerhq/live-env": "npm:^2.3.0" + "@ledgerhq/live-env": "npm:^2.4.1" axios: "npm:1.7.7" - checksum: 10/ce6e3343fdf60255ede1d784a2fb47c8c5f49e8257559947e2678fac250700140695c85ae06bef777ccc4bb37577db813a4b3d031f3b0b84cbcd6e139613fbf9 + checksum: 10/2cf692c111523fa634a6eeadfbe1e9eca29fd9e8c03a04dac00f0c191becdc0300bf90d1a5a0190b6ac8cf1436bb14009b50039e3611c55dd6843a6ec45230ec languageName: node linkType: hard @@ -1506,18 +1317,18 @@ __metadata: languageName: node linkType: hard -"@ledgerhq/domain-service@npm:^1.2.6": - version: 1.2.6 - resolution: "@ledgerhq/domain-service@npm:1.2.6" +"@ledgerhq/domain-service@npm:^1.2.15": + version: 1.2.15 + resolution: "@ledgerhq/domain-service@npm:1.2.15" dependencies: "@ledgerhq/errors": "npm:^6.19.1" "@ledgerhq/logs": "npm:^6.12.0" - "@ledgerhq/types-live": "npm:^6.52.0" + "@ledgerhq/types-live": "npm:^6.56.0" axios: "npm:1.7.7" eip55: "npm:^2.1.1" react: "npm:^18.2.0" react-dom: "npm:^18.2.0" - checksum: 10/0b680af9deff24f608446cb7026b06e09ea693a367a5ef5e71068c70fabfbb98019cb618daefed278ed875a0ff5d296735fb743746ac0edee1357ae12fd58d99 + checksum: 10/04585a2558512fa0bf07df89d608449cb184c3ae61d33364e67701b42353bd28c922c2bd5e8b6f843beffa3ae5667f3bc7bc9802a5c6a977ddf75a15f10e3895 languageName: node linkType: hard @@ -1528,37 +1339,39 @@ __metadata: languageName: node linkType: hard -"@ledgerhq/evm-tools@npm:^1.2.3": - version: 1.2.3 - resolution: "@ledgerhq/evm-tools@npm:1.2.3" +"@ledgerhq/evm-tools@npm:^1.3.0": + version: 1.3.0 + resolution: "@ledgerhq/evm-tools@npm:1.3.0" dependencies: - "@ledgerhq/cryptoassets-evm-signatures": "npm:^13.5.0" - "@ledgerhq/live-env": "npm:^2.3.0" + "@ethersproject/constants": "npm:^5.7.0" + "@ethersproject/hash": "npm:^5.7.0" + "@ledgerhq/cryptoassets-evm-signatures": "npm:^13.5.2" + "@ledgerhq/live-env": "npm:^2.4.1" axios: "npm:1.7.7" crypto-js: "npm:4.2.0" - ethers: "npm:5.7.2" - checksum: 10/956a0a3ac26454ac350c5e34b5cfb911ed3a0f3f724bec1ce2c56f2de344635b582f977e4901841c353d98c50b076e40bfb03d865eed3a82e328744043e00ee7 + checksum: 10/e4394a3065391d44efe958a043df6fade68e789f18386d2a22f51574eb6b723151c8e631af2bce21b951b1908930145181cf8db41fe546e4a06c34a81cb3ff6b languageName: node linkType: hard -"@ledgerhq/hw-app-eth@npm:^6.39.0": - version: 6.39.0 - resolution: "@ledgerhq/hw-app-eth@npm:6.39.0" +"@ledgerhq/hw-app-eth@npm:^6.42.0": + version: 6.42.2 + resolution: "@ledgerhq/hw-app-eth@npm:6.42.2" dependencies: - "@ethersproject/abi": "npm:^5.5.0" - "@ethersproject/rlp": "npm:^5.5.0" - "@ledgerhq/cryptoassets-evm-signatures": "npm:^13.5.0" - "@ledgerhq/domain-service": "npm:^1.2.6" + "@ethersproject/abi": "npm:^5.7.0" + "@ethersproject/rlp": "npm:^5.7.0" + "@ethersproject/transactions": "npm:^5.7.0" + "@ledgerhq/cryptoassets-evm-signatures": "npm:^13.5.2" + "@ledgerhq/domain-service": "npm:^1.2.15" "@ledgerhq/errors": "npm:^6.19.1" - "@ledgerhq/evm-tools": "npm:^1.2.3" + "@ledgerhq/evm-tools": "npm:^1.3.0" "@ledgerhq/hw-transport": "npm:^6.31.4" "@ledgerhq/hw-transport-mocker": "npm:^6.29.4" "@ledgerhq/logs": "npm:^6.12.0" - "@ledgerhq/types-live": "npm:^6.52.0" + "@ledgerhq/types-live": "npm:^6.56.0" axios: "npm:1.7.7" bignumber.js: "npm:^9.1.2" semver: "npm:^7.3.5" - checksum: 10/5b50aac35989e09704557523efe5b6b29a1f31f5279088ecceb90164e32e05860f3ff20c32a084003a69723ed08165ae140c2987424657245f088c3f9a908cd6 + checksum: 10/58a80daa4a1d6881fc0188ee68f6d3ff76c753e558e9b299cc0ae04859a111881c372445c06bc7c68f25b03aa51a31c335cc3fc4188c25552fc490c7da85fcdc languageName: node linkType: hard @@ -1585,13 +1398,13 @@ __metadata: languageName: node linkType: hard -"@ledgerhq/live-env@npm:^2.3.0": - version: 2.3.0 - resolution: "@ledgerhq/live-env@npm:2.3.0" +"@ledgerhq/live-env@npm:^2.4.1": + version: 2.4.1 + resolution: "@ledgerhq/live-env@npm:2.4.1" dependencies: rxjs: "npm:^7.8.1" utility-types: "npm:^3.10.0" - checksum: 10/757ff834d6b94dce0487d60ab0efa45ef206ebbdfd29053c232b4c0fdce1594016531340c1603695b37f17deb1639dcb98078a9629eeb36c835a19f4180834ce + checksum: 10/e8f5f13d77619f0e2b83907fa2a4e80f9e1ed18aeba0cfb2dcafe4d505ed4dd811a1b508ca752a4fd3782f8e5cf651a9daea0cb17d57e9159f915042b94b867d languageName: node linkType: hard @@ -1616,13 +1429,13 @@ __metadata: languageName: node linkType: hard -"@ledgerhq/types-live@npm:^6.52.0": - version: 6.52.0 - resolution: "@ledgerhq/types-live@npm:6.52.0" +"@ledgerhq/types-live@npm:^6.52.0, @ledgerhq/types-live@npm:^6.56.0": + version: 6.56.0 + resolution: "@ledgerhq/types-live@npm:6.56.0" dependencies: bignumber.js: "npm:^9.1.2" rxjs: "npm:^7.8.1" - checksum: 10/c410f02159538d66f59956512fc5bab2cb17edee7f6a15a517c31d89d6c730e52691666bb2e1d98718c701e94306dd7498544ea3d7772ff0b5ad6522fb2c335c + checksum: 10/a3cdf2acc6b4fa41aa34993f8790d44aa4fa937378b0bce29a69889b43e5229662803530b948206fe9fe02498d25058e71db2b14f22dc53445df7f53e547c308 languageName: node linkType: hard @@ -1739,13 +1552,13 @@ __metadata: languageName: node linkType: hard -"@metamask/base-controller@npm:^7.0.0": - version: 7.0.0 - resolution: "@metamask/base-controller@npm:7.0.0" +"@metamask/base-controller@npm:^7.0.0, @metamask/base-controller@npm:^7.1.1": + version: 7.1.1 + resolution: "@metamask/base-controller@npm:7.1.1" dependencies: - "@metamask/utils": "npm:^9.1.0" + "@metamask/utils": "npm:^11.0.1" immer: "npm:^9.0.6" - checksum: 10/0ea307da4a7863224fd1fc83039165dbd06d2725922d4d4cf5854f5e7894e789a3c277f1e4592a38ae002de869217a26550f3b9687c259fc29153984dc5b4a4c + checksum: 10/d45abc9e0f3f42a0ea7f0a52734f3749fafc5fefc73608230ab0815578e83a9fc28fe57dc7000f6f8df2cdcee5b53f68bb971091075bec9de6b7f747de627c60 languageName: node linkType: hard @@ -1883,7 +1696,7 @@ __metadata: "@ethereumjs/util": "npm:^8.1.0" "@lavamoat/allow-scripts": "npm:^3.2.1" "@lavamoat/preinstall-always-fail": "npm:^2.1.0" - "@ledgerhq/hw-app-eth": "npm:^6.39.0" + "@ledgerhq/hw-app-eth": "npm:^6.42.0" "@ledgerhq/hw-transport": "npm:^6.31.3" "@ledgerhq/types-cryptoassets": "npm:^7.15.1" "@ledgerhq/types-devices": "npm:^6.25.3" @@ -1984,12 +1797,12 @@ __metadata: "@lavamoat/allow-scripts": "npm:^3.2.1" "@lavamoat/preinstall-always-fail": "npm:^2.1.0" "@metamask/auto-changelog": "npm:^3.4.4" + "@metamask/base-controller": "npm:^7.1.1" "@metamask/eth-sig-util": "npm:^8.1.2" "@metamask/keyring-api": "workspace:^" "@metamask/keyring-internal-api": "workspace:^" "@metamask/keyring-internal-snap-client": "workspace:^" "@metamask/keyring-utils": "workspace:^" - "@metamask/providers": "npm:^18.3.1" "@metamask/snaps-controllers": "npm:^9.10.0" "@metamask/snaps-sdk": "npm:^6.7.0" "@metamask/snaps-utils": "npm:^8.3.0" @@ -2009,10 +1822,8 @@ __metadata: typedoc: "npm:^0.25.13" typescript: "npm:~5.6.3" uuid: "npm:^9.0.1" - webextension-polyfill: "npm:^0.12.0" peerDependencies: "@metamask/keyring-api": "workspace:^" - "@metamask/providers": ^18.3.1 languageName: unknown linkType: soft @@ -2185,10 +1996,10 @@ __metadata: "@lavamoat/allow-scripts": "npm:^3.2.1" "@lavamoat/preinstall-always-fail": "npm:^2.1.0" "@metamask/auto-changelog": "npm:^3.4.4" + "@metamask/base-controller": "npm:^7.1.1" "@metamask/keyring-api": "workspace:^" "@metamask/keyring-snap-client": "workspace:^" "@metamask/keyring-utils": "workspace:^" - "@metamask/providers": "npm:^18.3.1" "@metamask/snaps-controllers": "npm:^9.10.0" "@metamask/snaps-sdk": "npm:^6.7.0" "@metamask/snaps-utils": "npm:^8.3.0" @@ -2206,9 +2017,6 @@ __metadata: tsd: "npm:^0.31.0" typedoc: "npm:^0.25.13" typescript: "npm:~5.6.3" - webextension-polyfill: "npm:^0.12.0" - peerDependencies: - "@metamask/providers": ^18.3.1 languageName: unknown linkType: soft @@ -4104,13 +3912,6 @@ __metadata: languageName: node linkType: hard -"aes-js@npm:3.0.0": - version: 3.0.0 - resolution: "aes-js@npm:3.0.0" - checksum: 10/1b3772e5ba74abdccb6c6b99bf7f50b49057b38c0db1612b46c7024414f16e65ba7f1643b2d6e38490b1870bdf3ba1b87b35e2c831fd3fdaeff015f08aad19d1 - languageName: node - linkType: hard - "aes-js@npm:^3.1.2": version: 3.1.2 resolution: "aes-js@npm:3.1.2" @@ -4515,13 +4316,6 @@ __metadata: languageName: node linkType: hard -"bech32@npm:1.1.4": - version: 1.1.4 - resolution: "bech32@npm:1.1.4" - checksum: 10/63ff37c0ce43be914c685ce89700bba1589c319af0dac1ea04f51b33d0e5ecfd40d14c24f527350b94f0a4e236385373bb9122ec276410f354ddcdbf29ca13f4 - languageName: node - linkType: hard - "bech32@npm:^2.0.0": version: 2.0.0 resolution: "bech32@npm:2.0.0" @@ -6345,44 +6139,6 @@ __metadata: languageName: node linkType: hard -"ethers@npm:5.7.2": - version: 5.7.2 - resolution: "ethers@npm:5.7.2" - dependencies: - "@ethersproject/abi": "npm:5.7.0" - "@ethersproject/abstract-provider": "npm:5.7.0" - "@ethersproject/abstract-signer": "npm:5.7.0" - "@ethersproject/address": "npm:5.7.0" - "@ethersproject/base64": "npm:5.7.0" - "@ethersproject/basex": "npm:5.7.0" - "@ethersproject/bignumber": "npm:5.7.0" - "@ethersproject/bytes": "npm:5.7.0" - "@ethersproject/constants": "npm:5.7.0" - "@ethersproject/contracts": "npm:5.7.0" - "@ethersproject/hash": "npm:5.7.0" - "@ethersproject/hdnode": "npm:5.7.0" - "@ethersproject/json-wallets": "npm:5.7.0" - "@ethersproject/keccak256": "npm:5.7.0" - "@ethersproject/logger": "npm:5.7.0" - "@ethersproject/networks": "npm:5.7.1" - "@ethersproject/pbkdf2": "npm:5.7.0" - "@ethersproject/properties": "npm:5.7.0" - "@ethersproject/providers": "npm:5.7.2" - "@ethersproject/random": "npm:5.7.0" - "@ethersproject/rlp": "npm:5.7.0" - "@ethersproject/sha2": "npm:5.7.0" - "@ethersproject/signing-key": "npm:5.7.0" - "@ethersproject/solidity": "npm:5.7.0" - "@ethersproject/strings": "npm:5.7.0" - "@ethersproject/transactions": "npm:5.7.0" - "@ethersproject/units": "npm:5.7.0" - "@ethersproject/wallet": "npm:5.7.0" - "@ethersproject/web": "npm:5.7.1" - "@ethersproject/wordlists": "npm:5.7.0" - checksum: 10/227dfa88a2547c799c0c3c9e92e5e246dd11342f4b495198b3ae7c942d5bf81d3970fcef3fbac974a9125d62939b2d94f3c0458464e702209b839a8e6e615028 - languageName: node - linkType: hard - "ethjs-util@npm:0.1.6, ethjs-util@npm:^0.1.3, ethjs-util@npm:^0.1.6": version: 0.1.6 resolution: "ethjs-util@npm:0.1.6" @@ -10391,7 +10147,7 @@ __metadata: languageName: node linkType: hard -"scrypt-js@npm:3.0.1, scrypt-js@npm:^3.0.0, scrypt-js@npm:^3.0.1": +"scrypt-js@npm:^3.0.0, scrypt-js@npm:^3.0.1": version: 3.0.1 resolution: "scrypt-js@npm:3.0.1" checksum: 10/2f8aa72b7f76a6f9c446bbec5670f80d47497bccce98474203d89b5667717223eeb04a50492ae685ed7adc5a060fc2d8f9fd988f8f7ebdaf3341967f3aeff116