-
-
Notifications
You must be signed in to change notification settings - Fork 242
chore: create network enablement controller #6028
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 24 commits
77477b2
2bf8d2f
0472fdb
1fdf600
5855bf1
da23ff3
7653511
d98194c
e701107
cb878b3
ec4ff6c
0670f59
39aa1af
52333b0
70ed7c0
aeced8d
e75b6b2
c4588b3
3979af4
c55fc62
5b8e81a
38f4ca9
535952d
e7b723f
2eb5a88
303ac2a
e672af4
e08e494
66dafc3
aecb083
0649edb
23aa99f
3d8813c
e3424ee
6d8897f
3bd1ba0
9f10a92
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# Changelog | ||
|
||
All notable changes to this project will be documented in this file. | ||
|
||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), | ||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | ||
|
||
## [Unreleased] | ||
|
||
### Added | ||
|
||
- Initial release ([#6028](https://github.com/MetaMask/core/pull/6028)) | ||
|
||
[Unreleased]: https://github.com/MetaMask/core/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
MIT License | ||
|
||
Copyright (c) 2025 MetaMask | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
Prithpal-Sooriya marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
# Network Enablement Controller | ||
|
||
A MetaMask controller for managing network enablement state across different blockchain networks. | ||
|
||
## Overview | ||
|
||
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. | ||
|
||
## Installation | ||
|
||
```bash | ||
npm install @metamask/network-enablement-controller | ||
``` | ||
|
||
## Usage | ||
|
||
### Basic Controller Usage | ||
|
||
```typescript | ||
import { NetworkEnablementController } from '@metamask/network-enablement-controller'; | ||
|
||
// Create controller instance | ||
const controller = new NetworkEnablementController({ | ||
messenger, | ||
state: { | ||
enabledNetworkMap: { | ||
eip155: { | ||
'0x1': true, // Ethereum mainnet enabled | ||
'0xa': false, // Optimism disabled | ||
}, | ||
solana: { | ||
'solana:mainnet': true, | ||
}, | ||
}, | ||
}, | ||
}); | ||
|
||
// Enable a network | ||
controller.setEnabledNetwork('0x1'); // Hex format for EVM | ||
controller.setEnabledNetwork('eip155:1'); // CAIP-2 format for EVM | ||
controller.setEnabledNetwork('solana:mainnet'); // CAIP-2 format for Solana | ||
Prithpal-Sooriya marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// Disable a network | ||
controller.setDisabledNetwork('0xa'); | ||
|
||
// Check if network is enabled | ||
const isEnabled = controller.isNetworkEnabled('0x1'); | ||
|
||
// Get all enabled networks for a namespace | ||
const evmNetworks = controller.getEnabledNetworksForNamespace('eip155'); | ||
|
||
// Get all enabled networks across all namespaces | ||
const allNetworks = controller.getAllEnabledNetworks(); | ||
``` | ||
|
||
### Using Selectors (Redux-style) | ||
|
||
The controller also provides selectors that can be used in Redux contexts or any state management system: | ||
|
||
```typescript | ||
import { | ||
selectIsNetworkEnabled, | ||
selectAllEnabledNetworks, | ||
selectEnabledNetworksForNamespace, | ||
selectEnabledEvmNetworks, | ||
selectEnabledSolanaNetworks, | ||
} from '@metamask/network-enablement-controller'; | ||
|
||
// Get controller state | ||
const state = controller.state; | ||
|
||
// Check if a specific network is enabled | ||
const isEthereumEnabled = selectIsNetworkEnabled('0x1')(state); | ||
const isSolanaEnabled = selectIsNetworkEnabled('solana:mainnet')(state); | ||
|
||
// Get all enabled networks across all namespaces | ||
const allEnabledNetworks = selectAllEnabledNetworks(state); | ||
// Returns: { eip155: ['0x1'], solana: ['solana:mainnet'] } | ||
|
||
// Get enabled networks for a specific namespace | ||
const evmNetworks = selectEnabledNetworksForNamespace('eip155')(state); | ||
const solanaNetworks = selectEnabledNetworksForNamespace('solana')(state); | ||
|
||
// Convenience selectors for specific network types | ||
const enabledEvmNetworks = selectEnabledEvmNetworks(state); | ||
const enabledSolanaNetworks = selectEnabledSolanaNetworks(state); | ||
|
||
// Get total count of enabled networks | ||
const totalEnabled = selectEnabledNetworksCount(state); | ||
|
||
// Check if any networks are enabled for a namespace | ||
const hasEvmNetworks = selectHasEnabledNetworksForNamespace('eip155')(state); | ||
``` | ||
|
||
## API Reference | ||
salimtb marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
### Controller Methods | ||
|
||
#### `setEnabledNetwork(chainId: Hex | CaipChainId): void` | ||
|
||
Enables a network for the user. Accepts either Hex chain IDs (for EVM networks) or CAIP-2 chain IDs (for any blockchain network). | ||
|
||
#### `setDisabledNetwork(chainId: Hex | CaipChainId): void` | ||
|
||
Disables a network for the user. Prevents disabling the last remaining enabled network. | ||
|
||
#### `isNetworkEnabled(chainId: Hex | CaipChainId): boolean` | ||
|
||
Checks if a network is currently enabled. Returns false for unknown networks. | ||
|
||
#### `getEnabledNetworksForNamespace(namespace: CaipNamespace): string[]` | ||
|
||
Gets all enabled networks for a specific namespace. | ||
|
||
#### `getAllEnabledNetworks(): Record<CaipNamespace, string[]>` | ||
|
||
Gets all enabled networks across all namespaces. | ||
|
||
### Selectors | ||
|
||
#### `selectIsNetworkEnabled(chainId: Hex | CaipChainId)` | ||
|
||
Returns a selector function that checks if a specific network is enabled. | ||
|
||
#### `selectAllEnabledNetworks` | ||
|
||
Returns a selector function that gets all enabled networks across all namespaces. | ||
|
||
#### `selectEnabledNetworksForNamespace(namespace: CaipNamespace)` | ||
|
||
Returns a selector function that gets enabled networks for a specific namespace. | ||
|
||
#### `selectEnabledNetworksCount` | ||
|
||
Returns a selector function that gets the total count of enabled networks. | ||
|
||
#### `selectHasEnabledNetworksForNamespace(namespace: CaipNamespace)` | ||
|
||
Returns a selector function that checks if any networks are enabled for a namespace. | ||
|
||
#### `selectEnabledEvmNetworks` | ||
|
||
Returns a selector function that gets all enabled EVM networks. | ||
|
||
#### `selectEnabledSolanaNetworks` | ||
|
||
Returns a selector function that gets all enabled Solana networks. | ||
|
||
## Chain ID Formats | ||
|
||
The controller supports two chain ID formats: | ||
|
||
1. **Hex format**: Traditional EVM chain IDs (e.g., `'0x1'` for Ethereum mainnet) | ||
2. **CAIP-2 format**: Chain Agnostic Improvement Proposal format (e.g., `'eip155:1'` for Ethereum mainnet, `'solana:mainnet'` for Solana) | ||
|
||
## Network Types | ||
|
||
### EVM Networks (eip155 namespace) | ||
|
||
- Ethereum Mainnet: `'0x1'` or `'eip155:1'` | ||
- Optimism: `'0xa'` or `'eip155:10'` | ||
- Arbitrum One: `'0xa4b1'` or `'eip155:42161'` | ||
|
||
### Solana Networks (solana namespace) | ||
|
||
- Solana Mainnet: `'solana:mainnet'` | ||
- Solana Testnet: `'solana:testnet'` | ||
|
||
## State Persistence | ||
|
||
The controller state is automatically persisted and restored between sessions. The `enabledNetworkMap` is stored anonymously to protect user privacy. | ||
|
||
## Safety Features | ||
|
||
- **At least one network enabled**: The controller ensures at least one network is always enabled | ||
- **Unknown network protection**: Prevents enabling networks not configured in the system | ||
- **Exclusive mode**: When enabling non-popular networks, all other networks are disabled | ||
- **Last network protection**: Prevents disabling the last remaining enabled network |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
/* | ||
* For a detailed explanation regarding each configuration property and type check, visit: | ||
* https://jestjs.io/docs/configuration | ||
*/ | ||
|
||
const merge = require('deepmerge'); | ||
const path = require('path'); | ||
|
||
const baseConfig = require('../../jest.config.packages'); | ||
|
||
const displayName = path.basename(__dirname); | ||
|
||
module.exports = merge(baseConfig, { | ||
// The display name when running multiple projects | ||
displayName, | ||
|
||
// An object that configures minimum threshold enforcement for coverage results | ||
coverageThreshold: { | ||
global: { | ||
branches: 88.47, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any way that we can start this coverage off at 100%? Not doing this will make it difficult to identify drops in coverage for new PRs later (i.e. you'll probably need to sort through the coverage report to figure out which changes introduced the drops). I've identified the missing tests below. |
||
functions: 97.43, | ||
lines: 94.47, | ||
statements: 94.29, | ||
}, | ||
}, | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
{ | ||
"name": "@metamask/network-enablement-controller", | ||
"version": "0.0.0", | ||
"description": "Provides an interface to the currently enabled network using a MetaMask-compatible provider object", | ||
"keywords": [ | ||
"MetaMask", | ||
"Ethereum" | ||
], | ||
"homepage": "https://github.com/MetaMask/core/tree/main/packages/network-enablement-controller#readme", | ||
"bugs": { | ||
"url": "https://github.com/MetaMask/core/issues" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/MetaMask/core.git" | ||
}, | ||
"license": "MIT", | ||
"sideEffects": false, | ||
"exports": { | ||
".": { | ||
"import": { | ||
"types": "./dist/index.d.mts", | ||
"default": "./dist/index.mjs" | ||
}, | ||
"require": { | ||
"types": "./dist/index.d.cts", | ||
"default": "./dist/index.cjs" | ||
} | ||
}, | ||
"./package.json": "./package.json" | ||
}, | ||
"main": "./dist/index.cjs", | ||
"types": "./dist/index.d.cts", | ||
"files": [ | ||
"dist/" | ||
], | ||
"scripts": { | ||
"build": "ts-bridge --project tsconfig.build.json --verbose --clean --no-references", | ||
"build:docs": "typedoc", | ||
"changelog:update": "../../scripts/update-changelog.sh @metamask/network-enablement-controller", | ||
"changelog:validate": "../../scripts/validate-changelog.sh @metamask/network-enablement-controller", | ||
"publish:preview": "yarn npm publish --tag preview", | ||
"since-latest-release": "../../scripts/since-latest-release.sh", | ||
"test": "NODE_OPTIONS=--experimental-vm-modules jest --reporters=jest-silent-reporter", | ||
"test:clean": "NODE_OPTIONS=--experimental-vm-modules jest --clearCache", | ||
"test:verbose": "NODE_OPTIONS=--experimental-vm-modules jest --verbose", | ||
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch" | ||
}, | ||
"devDependencies": { | ||
"@metamask/auto-changelog": "^3.4.4", | ||
"@types/jest": "^27.4.1", | ||
"deepmerge": "^4.2.2", | ||
"jest": "^27.5.1", | ||
"ts-jest": "^27.1.4", | ||
"typedoc": "^0.24.8", | ||
"typedoc-plugin-missing-exports": "^2.0.0", | ||
"typescript": "~5.2.2" | ||
}, | ||
"dependencies": { | ||
"@metamask/base-controller": "^8.0.1", | ||
"@metamask/controller-utils": "^11.10.0", | ||
"@metamask/keyring-api": "^18.0.0", | ||
"@metamask/multichain-network-controller": "^0.9.0", | ||
salimtb marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"@metamask/network-controller": "^24.0.0", | ||
salimtb marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"@metamask/utils": "^11.2.0", | ||
"reselect": "^5.1.1" | ||
}, | ||
"peerDependencies": { | ||
"@metamask/multichain-network-controller": "^0.9.0", | ||
"@metamask/network-controller": "^24.0.0" | ||
}, | ||
"engines": { | ||
"node": "^18.18 || >=20" | ||
}, | ||
"publishConfig": { | ||
"access": "public", | ||
"registry": "https://registry.npmjs.org/" | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.