diff --git a/src/components/connections/index.js b/src/components/connections/index.js
index 823238c4..6fc5d007 100644
--- a/src/components/connections/index.js
+++ b/src/components/connections/index.js
@@ -1,2 +1,3 @@
export * from './connections';
+export * from './networks';
export * from './permissions';
diff --git a/src/components/connections/networks-helpers.js b/src/components/connections/networks-helpers.js
new file mode 100644
index 00000000..c57427f3
--- /dev/null
+++ b/src/components/connections/networks-helpers.js
@@ -0,0 +1,221 @@
+import globalContext from '../..';
+
+const NETWORKS = [
+ // Main networks
+ {
+ name: 'Ethereum Mainnet',
+ chainId: '0x1',
+ color: '#627eea',
+ category: 'main',
+ },
+ {
+ name: 'Linea',
+ chainId: '0xe708',
+ color: '#000000',
+ category: 'main',
+ },
+ {
+ name: 'Base Mainnet',
+ chainId: '0x2105',
+ color: '#0052ff',
+ category: 'main',
+ },
+ {
+ name: 'Arbitrum One',
+ chainId: '0xa4b1',
+ color: '#28a0f0',
+ category: 'main',
+ },
+ {
+ name: 'Avalanche Network C-Chain',
+ chainId: '0xa86a',
+ color: '#e84142',
+ category: 'main',
+ },
+ {
+ name: 'Binance Smart Chain',
+ chainId: '0x38',
+ color: '#f3ba2f',
+ category: 'main',
+ },
+ {
+ name: 'OP Mainnet',
+ chainId: '0xa',
+ color: '#ff0420',
+ category: 'main',
+ },
+ {
+ name: 'Polygon Mainnet',
+ chainId: '0x89',
+ color: '#8247e5',
+ category: 'main',
+ },
+ {
+ name: 'Sei Network',
+ chainId: '0x1a',
+ color: '#ff6b35',
+ category: 'main',
+ },
+ {
+ name: 'zkSync Era Mainnet',
+ chainId: '0x144',
+ color: '#8e71c7',
+ category: 'main',
+ },
+
+ // Test networks
+ {
+ name: 'Sepolia',
+ chainId: '0xaa36a7',
+ color: '#f6c343',
+ category: 'test',
+ },
+ {
+ name: 'Linea Sepolia',
+ chainId: '0xe705',
+ color: '#000000',
+ category: 'test',
+ },
+ {
+ name: 'Mega Testnet',
+ chainId: '0x1a4',
+ color: '#ff6b35',
+ category: 'test',
+ },
+ {
+ name: 'Monad Testnet',
+ chainId: '0x1a5',
+ color: '#ff6b35',
+ category: 'test',
+ },
+];
+
+export function populateNetworkLists() {
+ const mainNetworks = document.getElementById('mainNetworks');
+ const testNetworks = document.getElementById('testNetworks');
+
+ NETWORKS.forEach((network) => {
+ const networkItem = createNetworkItem(network);
+
+ switch (network.category) {
+ case 'main':
+ mainNetworks.appendChild(networkItem);
+ break;
+ case 'test':
+ testNetworks.appendChild(networkItem);
+ break;
+ default:
+ break;
+ }
+ });
+}
+
+export function createNetworkItem(network) {
+ const item = document.createElement('div');
+ item.className = 'network-modal-item';
+ item.dataset.chainId = network.chainId;
+ item.innerHTML = `
+
+
+
+
${network.name}
+
${network.chainId}
+
+
+ `;
+
+ item.addEventListener('click', async () => {
+ hideNetworkError(); // Hide any existing error before attempting to switch
+ await switchNetwork(network.chainId);
+ document.querySelector('.network-modal').style.display = 'none';
+ });
+
+ return item;
+}
+
+export function showNetworkError(message) {
+ // Remove any existing error message
+ hideNetworkError();
+
+ // Create error message element
+ const errorDiv = document.createElement('div');
+ errorDiv.id = 'networkError';
+ errorDiv.className = 'error-message';
+ errorDiv.style.marginTop = '10px';
+ errorDiv.style.width = '100%';
+ errorDiv.innerHTML = `${message}
`;
+
+ // Find the network picker button and insert error after it
+ const networkButton = document.getElementById('openNetworkPicker');
+ const cardBody = networkButton.closest('.card-body');
+ cardBody.appendChild(errorDiv);
+
+ // Auto-hide after 5 seconds
+ setTimeout(() => {
+ hideNetworkError();
+ }, 5000);
+}
+
+export function hideNetworkError() {
+ const existingError = document.getElementById('networkError');
+ if (existingError) {
+ existingError.remove();
+ }
+}
+
+export async function switchNetwork(chainId) {
+ if (!globalContext.provider) {
+ console.error('No provider available');
+ return;
+ }
+
+ try {
+ await globalContext.provider.request({
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId }],
+ });
+ } catch (switchError) {
+ // This error code indicates that the chain has not been added to MetaMask.
+ if (switchError.code === 4902) {
+ const network = NETWORKS.find((n) => n.chainId === chainId);
+ const networkName = network ? network.name : `Chain ID ${chainId}`;
+ showNetworkError(`${networkName} is not available in your wallet`);
+ } else {
+ console.error('Error switching network:', switchError);
+ showNetworkError('Failed to switch network');
+ }
+ }
+}
+
+export function updateCurrentNetworkDisplay() {
+ const currentNetworkName = document.getElementById('currentNetworkName');
+
+ if (!globalContext.chainIdInt) {
+ currentNetworkName.textContent = 'Current Network: Not Connected';
+ return;
+ }
+ const network = NETWORKS.find((n) => {
+ const networkChainId = parseInt(n.chainId, 16);
+ return networkChainId === globalContext.chainIdInt;
+ });
+ // Fallback to chain ID if network not found
+ currentNetworkName.textContent = network
+ ? `Current Network: ${network.name}`
+ : `Current Network: Chain ID 0x${globalContext.chainIdInt.toString(16)}`;
+}
+
+export function updateActiveNetworkInModal() {
+ const networkItems = document.querySelectorAll('.network-modal-item');
+
+ networkItems.forEach((item) => {
+ const itemChainId = item.dataset.chainId;
+ const itemChainIdInt = parseInt(itemChainId, 16);
+ const isActive = itemChainIdInt === globalContext.chainIdInt;
+
+ if (isActive) {
+ item.classList.add('active');
+ } else {
+ item.classList.remove('active');
+ }
+ });
+}
diff --git a/src/components/connections/networks.js b/src/components/connections/networks.js
new file mode 100644
index 00000000..8c69ccb6
--- /dev/null
+++ b/src/components/connections/networks.js
@@ -0,0 +1,71 @@
+import {
+ populateNetworkLists,
+ updateCurrentNetworkDisplay,
+ updateActiveNetworkInModal,
+ hideNetworkError,
+} from './networks-helpers';
+
+export function networksComponent(parentContainer) {
+ parentContainer.insertAdjacentHTML(
+ 'beforeend',
+ `
+
+
+
+ Network Picker
+
+
+
+
+
`,
+ );
+
+ const modal = document.createElement('div');
+ modal.className = 'network-modal';
+ modal.innerHTML = `
+
+ `;
+ document.body.appendChild(modal);
+
+ const openButton = document.getElementById('openNetworkPicker');
+ const closeButton = modal.querySelector('.network-modal-close');
+
+ populateNetworkLists();
+
+ openButton.addEventListener('click', () => {
+ modal.style.display = 'flex';
+ updateActiveNetworkInModal();
+ });
+
+ closeButton.addEventListener('click', () => {
+ modal.style.display = 'none';
+ hideNetworkError();
+ });
+
+ modal.addEventListener('click', (e) => {
+ if (e.target === modal) {
+ modal.style.display = 'none';
+ hideNetworkError();
+ }
+ });
+
+ updateCurrentNetworkDisplay();
+}
diff --git a/src/index.css b/src/index.css
index 6afd3bcb..5a76cae4 100644
--- a/src/index.css
+++ b/src/index.css
@@ -108,3 +108,217 @@ header {
.warning-invisible {
display: none;
}
+
+.networks-list {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.network-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 12px 16px;
+ background: #f8f9fa;
+ border: 1px solid #dee2e6;
+ border-radius: 8px;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ text-align: left;
+ width: 100%;
+}
+
+.network-item:hover {
+ background: #e9ecef;
+ transform: translateY(-1px);
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+}
+
+.network-item.active {
+ background: #007bff;
+ color: white;
+ border-color: #007bff;
+}
+
+.network-item.active:hover {
+ background: #0056b3;
+}
+
+.network-name {
+ font-weight: 500;
+ font-size: 14px;
+}
+
+.network-chain-id {
+ font-size: 12px;
+ opacity: 0.7;
+ font-family: monospace;
+}
+
+.network-item.active .network-chain-id {
+ opacity: 0.9;
+}
+
+.network-modal {
+ display: none;
+ position: fixed;
+ z-index: 1000;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.5);
+ justify-content: center;
+ align-items: center;
+}
+
+.network-modal-content {
+ background-color: white;
+ border-radius: 12px;
+ width: 90%;
+ max-width: 500px;
+ max-height: 80vh;
+ overflow: hidden;
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
+}
+
+.network-modal-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 20px 24px;
+ border-bottom: 1px solid #e9ecef;
+}
+
+.network-modal-header h5 {
+ margin: 0;
+ font-weight: 600;
+ color: #212529;
+}
+
+.network-modal-close {
+ background: none;
+ border: none;
+ font-size: 24px;
+ cursor: pointer;
+ color: #6c757d;
+ padding: 0;
+ width: 30px;
+ height: 30px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+ transition: background-color 0.2s;
+}
+
+.network-modal-close:hover {
+ background-color: #f8f9fa;
+ color: #495057;
+}
+
+.network-modal-body {
+ padding: 20px 24px;
+ max-height: 60vh;
+ overflow-y: auto;
+}
+
+.network-category {
+ margin-bottom: 24px;
+}
+
+.network-category:last-child {
+ margin-bottom: 0;
+}
+
+.network-category h6 {
+ margin: 0 0 12px 0;
+ font-weight: 600;
+ color: #495057;
+ font-size: 14px;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+.network-list {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.network-modal-item {
+ display: flex;
+ align-items: center;
+ padding: 12px 16px;
+ border-radius: 8px;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ border: 1px solid transparent;
+}
+
+.network-modal-item:hover {
+ background-color: #f8f9fa;
+ border-color: #dee2e6;
+}
+
+.network-modal-item.active {
+ background-color: #e3f2fd;
+ border-color: #2196f3;
+}
+
+.network-modal-item-content {
+ display: flex;
+ align-items: center;
+ width: 100%;
+}
+
+.network-modal-item-icon {
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ margin-right: 12px;
+ flex-shrink: 0;
+}
+
+.network-modal-item-info {
+ flex: 1;
+}
+
+.network-modal-item-name {
+ font-weight: 500;
+ font-size: 14px;
+ color: #212529;
+ margin-bottom: 2px;
+}
+
+.network-modal-item-chain-id {
+ font-size: 12px;
+ color: #6c757d;
+ font-family: monospace;
+}
+
+.network-modal-item.active .network-modal-item-name {
+ color: #1976d2;
+ font-weight: 600;
+}
+
+.network-modal-item.active .network-modal-item-chain-id {
+ color: #1976d2;
+}
+
+/* Responsive adjustments */
+@media (max-width: 768px) {
+ .network-modal-content {
+ width: 95%;
+ margin: 20px;
+ }
+
+ .network-modal-body {
+ padding: 16px 20px;
+ }
+
+ .network-modal-header {
+ padding: 16px 20px;
+ }
+}
diff --git a/src/index.js b/src/index.js
index 1d6629cd..8f7bab13 100644
--- a/src/index.js
+++ b/src/index.js
@@ -10,6 +10,7 @@ import { NETWORKS_BY_CHAIN_ID } from './onchain-sample-contracts';
import {
connectionsComponent,
+ networksComponent,
permissionsComponent,
} from './components/connections';
import {
@@ -46,6 +47,10 @@ import {
} from './components/interactions';
import { sendFormComponent } from './components/forms/send-form';
import { eip5792Component } from './components/transactions/eip5792';
+import {
+ updateCurrentNetworkDisplay,
+ updateActiveNetworkInModal,
+} from './components/connections/networks-helpers';
const {
hstBytecode,
@@ -161,6 +166,7 @@ connectionsRow.className = 'row d-flex justify-content-center';
connectionsSection.appendChild(connectionsRow);
connectionsComponent(connectionsRow);
permissionsComponent(connectionsRow);
+networksComponent(connectionsRow);
// Connection buttons set up by this file
const onboardButton = document.getElementById('connectButton');
@@ -433,6 +439,8 @@ const handleNewChain = (chainId) => {
if (!scrollToHandled) {
handleScrollTo({ delay: true });
}
+ updateCurrentNetworkDisplay();
+ updateActiveNetworkInModal();
};
function handleNewNetwork(networkId) {