# How to Connect a MetaMask Wallet to GenLayer Contracts: A Complete Guide

> Learn how to connect your MetaMask wallet to GenLayer contracts. This guide covers network switching, account access, and creating an SDK client for signed transactions on chain ID 61999.

- Repository: [GenLayer Labs/genlayer-project-boilerplate](https://github.com/genlayerlabs/genlayer-project-boilerplate)
- Tags: how-to-guide
- Published: 2026-08-20

---

**Connecting MetaMask to GenLayer contracts requires detecting the wallet, requesting account access, switching to the GenLayer network (chain ID 61999), and creating a GenLayer SDK client for signed transactions.**

Integrating MetaMask with your GenLayer dApp enables users to sign transactions and interact with on-chain contracts directly from their browser wallet. The `genlayer-project-boilerplate` repository provides a complete, production-ready implementation that handles network detection, switching, and state persistence. This guide walks through the exact implementation found in the source code.

## Detecting and Connecting MetaMask

The connection flow begins with verifying that MetaMask is installed and available in the browser. According to the GenLayer boilerplate source code, this check is handled by a dedicated utility function.

### Verify MetaMask Installation

The `isMetaMaskInstalled()` helper in [`frontend/lib/genlayer/client.ts`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/frontend/lib/genlayer/client.ts) checks for the presence of the MetaMask provider:

```typescript
// frontend/lib/genlayer/client.ts (lines 61-64)
export function isMetaMaskInstalled(): boolean {
  return typeof window !== "undefined" && window.ethereum?.isMetaMask === true;
}

```

This guard is essential before attempting any Ethereum provider calls. If `window.ethereum` is undefined or lacks the `isMetaMask` flag, the user either doesn't have MetaMask installed or is using a different wallet provider.

### Request Account Access and Network Switch

Once MetaMask is confirmed, the `connectMetaMask()` function orchestrates the full connection sequence:

```typescript
// frontend/lib/genlayer/client.ts (lines 10-30 excerpt)
export async function connectMetaMask(): Promise<string> {
  if (!isMetaMaskInstalled()) {
    throw new Error("MetaMask is not installed");
  }

  // Request account access
  const accounts = await window.ethereum.request({
    method: "eth_requestAccounts"
  });
  
  if (!accounts.length) {
    throw new Error("No accounts found");
  }

  // Ensure we're on the correct network
  if (!(await isOnGenLayerNetwork())) {
    await switchToGenLayerNetwork();
  }

  return accounts[0]; // Return the connected wallet address
}

```

This function performs three critical operations in sequence:
- **Prompts the user** to unlock MetaMask and grant site access via `eth_requestAccounts`
- **Validates** that at least one account is available
- **Switches networks** if the user isn't already on GenLayer

## Ensuring the Correct Network (GenLayer Chain ID 61999)

Network management is a core requirement for multi-chain dApps. The GenLayer boilerplate includes robust utilities to detect, validate, and switch networks automatically.

### Reading the Current Chain ID

The `getCurrentChainId()` utility reads MetaMask's currently active network:

```typescript
// frontend/lib/genlayer/client.ts (lines 22-34)
export async function getCurrentChainId(): Promise<string> {
  const chainId = await window.ethereum.request({
    method: "eth_chainId"
  });
  return chainId; // Returns hex string, e.g., "0xf23f" for 61999
}

```

### Validating Against GenLayer

The `isOnGenLayerNetwork()` function compares the current chain ID against the expected GenLayer configuration:

```typescript
// frontend/lib/genlayer/client.ts (lines 94-104 excerpt)
export async function isOnGenLayerNetwork(): Promise<boolean> {
  const currentChainId = await getCurrentChainId();
  const expectedChainId = getGenLayerChainId(); // From environment, default: 61999 (0xf23f)
  return currentChainId.toLowerCase() === expectedChainId.toLowerCase();
}

```

The default GenLayer chain ID is **61999** (hex: `0xf23f`), defined in your environment configuration.

### Switching or Adding the GenLayer Network

If the user is on a different network, `switchToGenLayerNetwork()` handles the transition. If GenLayer isn't configured in MetaMask, it falls back to `addGenLayerNetwork()`:

```typescript
// frontend/lib/genlayer/client.ts (lines 66-88 excerpt)
export async function switchToGenLayerNetwork(): Promise<void> {
  const chainId = getGenLayerChainId(); // "0xf23f"
  
  try {
    await window.ethereum.request({
      method: "wallet_switchEthereumChain",
      params: [{ chainId }]
    });
  } catch (switchError: any) {
    // Error 4902: Chain not added to MetaMask
    if (switchError.code === 4902) {
      await addGenLayerNetwork();
    } else {
      throw switchError;
    }
  }
}

async function addGenLayerNetwork(): Promise<void> {
  await window.ethereum.request({
    method: "wallet_addEthereumChain",
    params: [{
      chainId: "0xf23f",
      chainName: "GenLayer",
      nativeCurrency: {
        name: "GEN",
        symbol: "GEN",
        decimals: 18
      },
      rpcUrls: [getGenLayerRpcUrl()],
      blockExplorerUrls: [getGenLayerExplorerUrl()]
    }]
  });
}

```

This approach follows **EIP-3085** and **EIP-3326** standards for wallet network management, ensuring compatibility with MetaMask's latest APIs.

## Managing Wallet State with React Context

The `WalletProvider` component in [`frontend/lib/genlayer/WalletProvider.tsx`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/frontend/lib/genlayer/WalletProvider.tsx) provides a centralized state management layer for wallet connections. It handles connection state, loading indicators, and persistence across page reloads.

### Connection State and Persistence

The provider stores critical wallet data and syncs with `localStorage`:

```tsx
// frontend/lib/genlayer/WalletProvider.tsx (lines 41-86 excerpt)
interface WalletState {
  address: string | null;
  chainId: string | null;
  isConnected: boolean;
  isLoading: boolean;
  isOnCorrectNetwork: boolean;
}

const DISCONNECT_FLAG = "genlayer-wallet-disconnected";

const connectWallet = useCallback(async () => {
  setState(prev => ({ ...prev, isLoading: true }));
  
  const address = await connectMetaMask();
  const chainId = await getCurrentChainId();
  const correctNetwork = await isOnGenLayerNetwork();
  
  // Remove disconnect flag to remember this connection
  localStorage.removeItem(DISCONNECT_FLAG);
  
  setState({
    address,
    chainId,
    isConnected: true,
    isLoading: false,
    isOnCorrectNetwork: correctNetwork,
  });
}, []);

const disconnectWallet = useCallback(() => {
  localStorage.setItem(DISCONNECT_FLAG, "true");
  setState({
    address: null,
    chainId: null,
    isConnected: false,
    isLoading: false,
    isOnCorrectNetwork: false,
  });
}, []);

```

The `DISCONNECT_FLAG` mechanism ensures that if a user explicitly disconnects, the app won't automatically reconnect on the next page load—respecting user intent while providing seamless reconnection otherwise.

### Listening to MetaMask Events

The provider also subscribes to MetaMask's native events to keep state synchronized:

- **`accountsChanged`**: Updates when the user switches accounts in MetaMask
- **`chainChanged`**: Updates when the user switches networks
- **`disconnect`**: Handles unexpected wallet disconnection

## Creating a GenLayer Client for Contract Calls

Once connected, you need a GenLayer SDK client that uses the MetaMask provider for transaction signing. The `createGenLayerClient(address)` function builds this client:

```typescript
// frontend/lib/genlayer/client.ts (lines 94-118 excerpt)
import { GenLayerClient, createClient } from "@genlayer/genlayer-js";
import { eip1193ProviderFromEthereum } from "@genlayer/genlayer-js/ethereum";

export async function createGenLayerClient(
  address: string
): Promise<GenLayerClient> {
  const provider = eip1193ProviderFromEthereum(window.ethereum);
  
  return createClient({
    provider,
    account: {
      address: address as `0x${string}`,
      type: "ethereum", // MetaMask uses Ethereum-style accounts
    },
    endpoint: getGenLayerEndpoint(),
  });
}

// Convenience function that auto-fetches connected account
export async function getClient(): Promise<GenLayerClient> {
  const [address] = await window.ethereum.request({
    method: "eth_accounts"
  });
  
  if (!address) {
    throw new Error("No connected account found");
  }
  
  return createGenLayerClient(address);
}

```

The `eip1193ProviderFromEthereum` adapter bridges MetaMask's EIP-1193 provider interface to the GenLayer SDK's expected format, enabling seamless transaction signing.

## Using the Client in Your UI Components

With the provider and client utilities in place, your React components can trigger connections and interact with contracts. The [`AccountPanel.tsx`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/AccountPanel.tsx) component demonstrates this pattern:

```tsx
// frontend/components/AccountPanel.tsx (lines 26-42 excerpt)
import { useWallet } from "@/lib/genlayer/WalletProvider";

export function AccountPanel() {
  const { 
    address, 
    isConnected, 
    isLoading, 
    connectWallet, 
    disconnectWallet 
  } = useWallet();

  if (!isMetaMaskInstalled()) {
    return <a href="https://metamask.io/download/">Install MetaMask</a>;
  }

  return isConnected ? (
    <div>
      <p>Connected: {address}</p>
      <button onClick={disconnectWallet}>Disconnect</button>
    </div>
  ) : (
    <button onClick={connectWallet} disabled={isLoading}>
      {isLoading ? "Connecting..." : "Connect MetaMask"}
    </button>
  );
}

```

### Example: Calling a GenLayer Contract

Here's how to use the connected client to execute a contract write operation:

```tsx
import { getClient } from "@/lib/genlayer/client";

async function placeBet(betAmount: number) {
  // Creates client with MetaMask account for signing
  const client = await getClient();
  
  // Target your deployed GenLayer contract
  const contract = client.contract({
    id: getContractAddress(), // Your contract's on-chain address
  });
  
  // Execute write method—MetaMask will prompt for signature
  const result = await contract.write.setBet(betAmount);
  
  return result;
}

```

The `client.contract().write.*` methods automatically route through MetaMask's signing flow, presenting the user with their familiar transaction confirmation interface.

## Complete Integration Checklist

When implementing MetaMask connection in your GenLayer project:

- **Install detection**: Always check `window.ethereum?.isMetaMask` before provider calls
- **Network configuration**: Verify chain ID 61999 and provide fallback RPC endpoints
- **Error handling**: Catch `4902` errors to trigger `wallet_addEthereumChain`
- **State persistence**: Use `localStorage` to remember user disconnections
- **Event subscriptions**: Listen for `accountsChanged` and `chainChanged` to keep UI synchronized
- **Client creation**: Wrap MetaMask's provider with GenLayer SDK's `eip1193ProviderFromEthereum`

## Summary

Connecting MetaMask to GenLayer contracts follows a structured flow implemented in the `genlayer-project-boilerplate` repository:

- **Detection**: Verify MetaMask availability with `isMetaMaskInstalled()` in [`frontend/lib/genlayer/client.ts`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/frontend/lib/genlayer/client.ts)
- **Connection**: Request accounts and auto-switch networks via `connectMetaMask()`
- **Network management**: Validate chain ID 61999 and use `switchToGenLayerNetwork()` or `addGenLayerNetwork()` as needed
- **State management**: Leverage `WalletProvider` for React state, persistence, and event handling
- **Transaction signing**: Create a GenLayer SDK client with `createGenLayerClient(address)` for contract interactions

This architecture provides a production-ready foundation that handles edge cases like missing networks, user-initiated disconnections, and account switching.

## Frequently Asked Questions

### What chain ID does GenLayer use for MetaMask connections?

GenLayer uses **chain ID 61999** (hex: `0xf23f`) as the default network identifier. This value is configured in your environment variables and validated by `isOnGenLayerNetwork()` in [`frontend/lib/genlayer/client.ts`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/frontend/lib/genlayer/client.ts). The `switchToGenLayerNetwork()` function automatically prompts users to add or switch to this network if they're on a different chain.

### How do I handle users who don't have MetaMask installed?

Check `isMetaMaskInstalled()` before any connection attempt. This function returns `false` if `window.ethereum?.isMetaMask` is unavailable. Your UI should then display an installation prompt linking to https://metamask.io/download/, as shown in [`frontend/components/AccountPanel.tsx`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/frontend/components/AccountPanel.tsx).

### Can users switch accounts without reconnecting the dApp?

Yes. The `WalletProvider` subscribes to MetaMask's `accountsChanged` event and updates React state automatically. When a user switches accounts in the MetaMask extension, your dApp receives the new address via the event listener and updates the connected account without requiring a manual reconnect.

### Where is the wallet connection state stored between page reloads?

The `WalletProvider` persists connection state using `localStorage`. A `DISCONNECT_FLAG` key tracks whether the user explicitly disconnected—if set, the app won't auto-reconnect. Otherwise, the provider can restore the connected state on page load by checking existing MetaMask permissions via `eth_accounts`.