# Frontend Patterns (React / Next.js)

## Architecture Principle

**Never open per-user WebSocket connections.** Use one connection, broadcast to users.

```
❌ Wrong: Each user → WebSocket → MegaETH
✅ Right: Server → WebSocket → MegaETH, Server → broadcast → Users
```

## Real-time Data Flow

### Server-Side WebSocket Manager

```typescript
// lib/megaeth-stream.ts
import WebSocket from 'ws';

class MegaETHStream {
  private ws: WebSocket | null = null;
  private subscribers = new Set<(data: any) => void>();
  private keepaliveInterval: NodeJS.Timeout | null = null;

  connect() {
    this.ws = new WebSocket('wss://mainnet.megaeth.com/ws');
    
    this.ws.on('open', () => {
      // Subscribe to mini-blocks
      this.ws!.send(JSON.stringify({
        jsonrpc: '2.0',
        method: 'eth_subscribe',
        params: ['miniBlocks'],
        id: 1
      }));
      
      // Keepalive every 30s
      this.keepaliveInterval = setInterval(() => {
        this.ws!.send(JSON.stringify({
          jsonrpc: '2.0',
          method: 'eth_chainId',
          params: [],
          id: Date.now()
        }));
      }, 30000);
    });

    this.ws.on('message', (data) => {
      const parsed = JSON.parse(data.toString());
      if (parsed.method === 'eth_subscription') {
        this.subscribers.forEach(fn => fn(parsed.params.result));
      }
    });

    this.ws.on('close', () => {
      if (this.keepaliveInterval) clearInterval(this.keepaliveInterval);
      setTimeout(() => this.connect(), 1000); // Reconnect
    });
  }

  subscribe(callback: (data: any) => void) {
    this.subscribers.add(callback);
    return () => this.subscribers.delete(callback);
  }
}

export const megaStream = new MegaETHStream();
```

### Client-Side Hook

```typescript
// hooks/useMiniBlocks.ts
import { useEffect, useState } from 'react';
import { io } from 'socket.io-client';

export function useMiniBlocks() {
  const [latestBlock, setLatestBlock] = useState<MiniBlock | null>(null);
  const [tps, setTps] = useState(0);

  useEffect(() => {
    const socket = io('/api/stream');
    
    socket.on('miniBlock', (block: MiniBlock) => {
      setLatestBlock(block);
      setTps(block.transactions.length * 100); // ~100 mini-blocks/sec
    });

    return () => { socket.disconnect(); };
  }, []);

  return { latestBlock, tps };
}
```

## Connection Warmup

First HTTP request to an RPC endpoint incurs connection overhead (DNS + TCP + TLS handshake). For latency-sensitive apps, warm up the connection on startup:

```typescript
// On app init or wallet connect — before user needs to transact
async function warmupRpcConnection(client: PublicClient) {
  await client.getChainId(); // Cheap call to establish connection
}

// Now first real transaction won't have cold-start latency
```

**Why it matters:** MegaETH has <10ms block times. A cold connection can add 50-200ms of overhead on the first request. Warming up ensures the connection pool is ready when users transact.

**Best practice:** Call `eth_chainId` or `eth_blockNumber` on:
- App initialization
- Wallet connection
- Network switch

## Transaction Submission

### Optimized Flow

```typescript
// lib/submit-tx.ts
import { createWalletClient, http, custom } from 'viem';
import { megaeth } from './chains';

export async function submitTransaction(signedTx: `0x${string}`) {
  const client = createWalletClient({
    chain: megaeth,
    transport: http('https://mainnet.megaeth.com/rpc')
  });

  // Use sync method for instant receipt
  const receipt = await client.request({
    method: 'eth_sendRawTransactionSync',
    params: [signedTx]
  });

  return receipt; // Receipt available immediately
}
```

### Chain Configuration (viem)

```typescript
// lib/chains.ts
import { defineChain } from 'viem';

export const megaeth = defineChain({
  id: 4326,
  name: 'MegaETH',
  nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
  rpcUrls: {
    default: { http: ['https://mainnet.megaeth.com/rpc'] }
  },
  blockExplorers: {
    default: { name: 'Etherscan', url: 'https://mega.etherscan.io' }
  }
});

export const megaethTestnet = defineChain({
  id: 6343,
  name: 'MegaETH Testnet',
  nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
  rpcUrls: {
    default: { http: ['https://carrot.megaeth.com/rpc'] }
  },
  blockExplorers: {
    default: { name: 'Blockscout', url: 'https://megaeth-testnet-v2.blockscout.com' }
  }
});
```

## Gas Configuration

```typescript
// ❌ Wrong: viem adds 20% buffer
const gasPrice = await publicClient.getGasPrice();

// ✅ Right: use base fee directly
const gasPrice = 1000000n; // 0.001 gwei

// ✅ Right: hardcode gas for known operations
const tx = await walletClient.sendTransaction({
  to: recipient,
  value: amount,
  gas: 21000n,
  maxFeePerGas: 1000000n,
  maxPriorityFeePerGas: 0n
});
```

## RPC Request Batching (v2.0.14+)

As of v2.0.14, **Multicall is preferred** for batching `eth_call` requests. The `eth_call` implementation is now 2-10x faster, and Multicall amortizes per-RPC overhead.

```typescript
// ✅ Preferred: Multicall (v2.0.14+)
import { multicall } from 'viem/actions';

const results = await multicall(client, {
  contracts: [
    { address: token1, abi: erc20Abi, functionName: 'balanceOf', args: [user] },
    { address: token2, abi: erc20Abi, functionName: 'balanceOf', args: [user] },
    { address: pool, abi: poolAbi, functionName: 'getReserves' },
  ]
});

// ❌ Still avoid: mixing slow with fast
// Don't batch eth_getLogs with eth_call — logs are always slower
```

**Note:** Earlier guidance recommended JSON-RPC batching over Multicall for caching benefits. With v2.0.14's performance improvements, Multicall is now the preferred approach.

## Historical Data

Never block UX waiting for historical queries:

```typescript
// Load historical in background
useEffect(() => {
  // Don't await - let it load async
  fetchHistoricalTrades().then(setTrades);
}, []);

// Use indexers for heavy queries
// Recommended: Envio HyperSync
// https://docs.envio.dev/docs/HyperSync/overview
```

## Error Handling

```typescript
const TX_ERRORS = {
  'nonce too low': 'Transaction already executed',
  'already known': 'Transaction pending',
  'intrinsic gas too low': 'Increase gas limit',
  'insufficient funds': 'Not enough ETH for gas'
};

function handleTxError(error: Error) {
  for (const [pattern, message] of Object.entries(TX_ERRORS)) {
    if (error.message.includes(pattern)) {
      return message;
    }
  }
  return 'Transaction failed';
}
```
