Skip to content

Commit 3d18e70

Browse files
authored
Merge branch 'main' into swaps2702-parallel-fetchquotes
2 parents 9ba453c + 11d2ca4 commit 3d18e70

27 files changed

+1866
-2
lines changed

.github/CODEOWNERS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
## Assets Team
1616
/packages/assets-controllers @MetaMask/metamask-assets
17+
/packages/network-enablement-controller @MetaMask/metamask-assets
1718

1819
## Confirmations Team
1920
/packages/address-book-controller @MetaMask/confirmations
@@ -157,3 +158,5 @@
157158
/packages/foundryup/CHANGELOG.md @MetaMask/mobile-platform @MetaMask/extension-platform @MetaMask/core-platform
158159
/packages/seedless-onboarding-controller/package.json @MetaMask/web3auth @MetaMask/core-platform
159160
/packages/seedless-onboarding-controller/CHANGELOG.md @MetaMask/web3auth @MetaMask/core-platform
161+
/packages/network-enablement-controller/package.json @MetaMask/metamask-assets @MetaMask/core-platform
162+
/packages/network-enablement-controller/CHANGELOG.md @MetaMask/metamask-assets @MetaMask/core-platform

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ Each package in this repository has its own README where you can find installati
5454
- [`@metamask/multichain-transactions-controller`](packages/multichain-transactions-controller)
5555
- [`@metamask/name-controller`](packages/name-controller)
5656
- [`@metamask/network-controller`](packages/network-controller)
57+
- [`@metamask/network-enablement-controller`](packages/network-enablement-controller)
5758
- [`@metamask/notification-services-controller`](packages/notification-services-controller)
5859
- [`@metamask/permission-controller`](packages/permission-controller)
5960
- [`@metamask/permission-log-controller`](packages/permission-log-controller)
@@ -113,6 +114,7 @@ linkStyle default opacity:0.5
113114
multichain_transactions_controller(["@metamask/multichain-transactions-controller"]);
114115
name_controller(["@metamask/name-controller"]);
115116
network_controller(["@metamask/network-controller"]);
117+
network_enablement_controller(["@metamask/network-enablement-controller"]);
116118
notification_services_controller(["@metamask/notification-services-controller"]);
117119
permission_controller(["@metamask/permission-controller"]);
118120
permission_log_controller(["@metamask/permission-log-controller"]);
@@ -228,6 +230,10 @@ linkStyle default opacity:0.5
228230
network_controller --> eth_json_rpc_provider;
229231
network_controller --> json_rpc_engine;
230232
network_controller --> error_reporting_service;
233+
network_enablement_controller --> base_controller;
234+
network_enablement_controller --> controller_utils;
235+
network_enablement_controller --> multichain_network_controller;
236+
network_enablement_controller --> network_controller;
231237
notification_services_controller --> base_controller;
232238
notification_services_controller --> controller_utils;
233239
notification_services_controller --> keyring_controller;

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@metamask/core-monorepo",
3-
"version": "486.0.0",
3+
"version": "487.0.0",
44
"private": true,
55
"description": "Monorepo for packages shared between MetaMask clients",
66
"repository": {

packages/assets-controllers/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Fixed
1111

1212
- Use a narrow selector when listening to `CurrencyRateController:stateChange` ([#6217](https://github.com/MetaMask/core/pull/6217))
13+
- Fixed an issue where attempting to fetch asset conversions for accounts without assets would crash the snap ([#6207](https://github.com/MetaMask/core/pull/6207))
1314

1415
## [73.0.1]
1516

packages/assets-controllers/src/MultichainAssetsRatesController/MultichainAssetsRatesController.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,31 @@ describe('MultichainAssetsRatesController', () => {
503503
expect(controller.state.conversionRates).toStrictEqual({});
504504
});
505505

506+
it('does not make snap requests when updateAssetsRatesForNewAssets is called with no new assets', async () => {
507+
const { controller, messenger } = setupController();
508+
509+
const snapSpy = jest.fn().mockResolvedValue(fakeAccountRates);
510+
messenger.registerActionHandler('SnapController:handleRequest', snapSpy);
511+
512+
// Publish accountAssetListUpdated event with accounts that have no new assets (empty added arrays)
513+
messenger.publish('MultichainAssetsController:accountAssetListUpdated', {
514+
assets: {
515+
account1: {
516+
added: [], // No new assets added
517+
removed: [],
518+
},
519+
},
520+
});
521+
522+
// Wait for the asynchronous subscriber to process the event
523+
await Promise.resolve();
524+
525+
// Verify no snap requests were made since there are no new assets to process
526+
expect(snapSpy).not.toHaveBeenCalled();
527+
// Verify state remains empty
528+
expect(controller.state.conversionRates).toStrictEqual({});
529+
});
530+
506531
it('updates state when currency is updated', async () => {
507532
const { controller, messenger } = setupController();
508533

packages/assets-controllers/src/MultichainAssetsRatesController/MultichainAssetsRatesController.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,11 @@ export class MultichainAssetsRatesController extends StaticIntervalPollingContro
346346
): Promise<
347347
Record<string, UnifiedAssetConversion & { currency: CaipAssetType }>
348348
> {
349+
// Do not attempt to retrieve rates from Snap if there are no assets
350+
if (!assets.length) {
351+
return {};
352+
}
353+
349354
// Build the conversions array
350355
const conversions = this.#buildConversions(assets);
351356

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [Unreleased]
9+
10+
## [0.1.0]
11+
12+
### Added
13+
14+
- Initial release ([#6028](https://github.com/MetaMask/core/pull/6028))
15+
16+
[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/network-enablement-controller@0.1.0...HEAD
17+
[0.1.0]: https://github.com/MetaMask/core/releases/tag/@metamask/network-enablement-controller@0.1.0
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
MIT License
2+
3+
Copyright (c) 2025 MetaMask
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
# Network Enablement Controller
2+
3+
A MetaMask controller for managing network enablement state across different blockchain networks.
4+
5+
## Overview
6+
7+
The NetworkEnablementController tracks which networks are enabled/disabled for the user and provides methods to toggle network states. It supports both EVM (EIP-155) and non-EVM networks like Solana.
8+
9+
## Installation
10+
11+
```bash
12+
npm install @metamask/network-enablement-controller
13+
```
14+
15+
```bash
16+
yarn add @metamask/network-enablement-controller
17+
```
18+
19+
## Usage
20+
21+
### Basic Controller Usage
22+
23+
```typescript
24+
import { NetworkEnablementController } from '@metamask/network-enablement-controller';
25+
26+
// Create controller instance
27+
const controller = new NetworkEnablementController({
28+
messenger,
29+
state: {
30+
enabledNetworkMap: {
31+
eip155: {
32+
'0x1': true, // Ethereum mainnet enabled
33+
'0xa': false, // Optimism disabled
34+
},
35+
solana: {
36+
'solana:mainnet': true,
37+
},
38+
},
39+
},
40+
});
41+
42+
// Enable a network
43+
controller.setEnabledNetwork('0x1'); // Hex format for EVM
44+
controller.setEnabledNetwork('eip155:1'); // CAIP-2 format for EVM
45+
controller.setEnabledNetwork('solana:mainnet'); // CAIP-2 format for Solana
46+
47+
// Disable a network
48+
controller.setDisabledNetwork('0xa');
49+
50+
// Check if network is enabled
51+
const isEnabled = controller.isNetworkEnabled('0x1');
52+
53+
// Get all enabled networks for a namespace
54+
const evmNetworks = controller.getEnabledNetworksForNamespace('eip155');
55+
56+
// Get all enabled networks across all namespaces
57+
const allNetworks = controller.getAllEnabledNetworks();
58+
```
59+
60+
### Using Selectors (Redux-style)
61+
62+
The controller also provides selectors that can be used in Redux contexts or any state management system:
63+
64+
```typescript
65+
import {
66+
selectIsNetworkEnabled,
67+
selectAllEnabledNetworks,
68+
selectEnabledNetworksForNamespace,
69+
selectEnabledEvmNetworks,
70+
selectEnabledSolanaNetworks,
71+
} from '@metamask/network-enablement-controller';
72+
73+
// Get controller state
74+
const state = controller.state;
75+
76+
// Check if a specific network is enabled
77+
const isEthereumEnabled = selectIsNetworkEnabled('0x1')(state);
78+
const isSolanaEnabled = selectIsNetworkEnabled('solana:mainnet')(state);
79+
80+
// Get all enabled networks across all namespaces
81+
const allEnabledNetworks = selectAllEnabledNetworks(state);
82+
// Returns: { eip155: ['0x1'], solana: ['solana:mainnet'] }
83+
84+
// Get enabled networks for a specific namespace
85+
const evmNetworks = selectEnabledNetworksForNamespace('eip155')(state);
86+
const solanaNetworks = selectEnabledNetworksForNamespace('solana')(state);
87+
88+
// Convenience selectors for specific network types
89+
const enabledEvmNetworks = selectEnabledEvmNetworks(state);
90+
const enabledSolanaNetworks = selectEnabledSolanaNetworks(state);
91+
92+
// Get total count of enabled networks
93+
const totalEnabled = selectEnabledNetworksCount(state);
94+
95+
// Check if any networks are enabled for a namespace
96+
const hasEvmNetworks = selectHasEnabledNetworksForNamespace('eip155')(state);
97+
```
98+
99+
## API Reference
100+
101+
### Controller Methods
102+
103+
#### `setEnabledNetwork(chainId: Hex | CaipChainId): void`
104+
105+
Enables a network for the user. Accepts either Hex chain IDs (for EVM networks) or CAIP-2 chain IDs (for any blockchain network).
106+
107+
#### `setDisabledNetwork(chainId: Hex | CaipChainId): void`
108+
109+
Disables a network for the user. Prevents disabling the last remaining enabled network.
110+
111+
#### `isNetworkEnabled(chainId: Hex | CaipChainId): boolean`
112+
113+
Checks if a network is currently enabled. Returns false for unknown networks.
114+
115+
#### `getEnabledNetworksForNamespace(namespace: CaipNamespace): string[]`
116+
117+
Gets all enabled networks for a specific namespace.
118+
119+
#### `getAllEnabledNetworks(): Record<CaipNamespace, string[]>`
120+
121+
Gets all enabled networks across all namespaces.
122+
123+
### Selectors
124+
125+
#### `selectIsNetworkEnabled(chainId: Hex | CaipChainId)`
126+
127+
Returns a selector function that checks if a specific network is enabled.
128+
129+
#### `selectAllEnabledNetworks`
130+
131+
Returns a selector function that gets all enabled networks across all namespaces.
132+
133+
#### `selectEnabledNetworksForNamespace(namespace: CaipNamespace)`
134+
135+
Returns a selector function that gets enabled networks for a specific namespace.
136+
137+
#### `selectEnabledNetworksCount`
138+
139+
Returns a selector function that gets the total count of enabled networks.
140+
141+
#### `selectHasEnabledNetworksForNamespace(namespace: CaipNamespace)`
142+
143+
Returns a selector function that checks if any networks are enabled for a namespace.
144+
145+
#### `selectEnabledEvmNetworks`
146+
147+
Returns a selector function that gets all enabled EVM networks.
148+
149+
#### `selectEnabledSolanaNetworks`
150+
151+
Returns a selector function that gets all enabled Solana networks.
152+
153+
## Chain ID Formats
154+
155+
The controller supports two chain ID formats:
156+
157+
1. **Hex format**: Traditional EVM chain IDs (e.g., `'0x1'` for Ethereum mainnet)
158+
2. **CAIP-2 format**: Chain Agnostic Improvement Proposal format (e.g., `'eip155:1'` for Ethereum mainnet, `'solana:mainnet'` for Solana)
159+
160+
## Network Types
161+
162+
### EVM Networks (eip155 namespace)
163+
164+
- Ethereum Mainnet: `'0x1'` or `'eip155:1'`
165+
- Optimism: `'0xa'` or `'eip155:10'`
166+
- Arbitrum One: `'0xa4b1'` or `'eip155:42161'`
167+
168+
### Solana Networks (solana namespace)
169+
170+
- Solana Mainnet: `'solana:mainnet'`
171+
- Solana Testnet: `'solana:testnet'`
172+
173+
## State Persistence
174+
175+
The controller state is automatically persisted and restored between sessions. The `enabledNetworkMap` is stored anonymously to protect user privacy.
176+
177+
## Safety Features
178+
179+
- **At least one network enabled**: The controller ensures at least one network is always enabled
180+
- **Unknown network protection**: Prevents enabling networks not configured in the system
181+
- **Exclusive mode**: When enabling non-popular networks, all other networks are disabled
182+
- **Last network protection**: Prevents disabling the last remaining enabled network
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* For a detailed explanation regarding each configuration property and type check, visit:
3+
* https://jestjs.io/docs/configuration
4+
*/
5+
6+
const merge = require('deepmerge');
7+
const path = require('path');
8+
9+
const baseConfig = require('../../jest.config.packages');
10+
11+
const displayName = path.basename(__dirname);
12+
13+
module.exports = merge(baseConfig, {
14+
// The display name when running multiple projects
15+
displayName,
16+
17+
// An object that configures minimum threshold enforcement for coverage results
18+
coverageThreshold: {
19+
global: {
20+
branches: 100,
21+
functions: 100,
22+
lines: 100,
23+
statements: 100,
24+
},
25+
},
26+
});

0 commit comments

Comments
 (0)