# Walletconnect

> WalletConnect Integration Skill

- Skill: `hadv/walletconnect` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add hadv/walletconnect`
- Raw SKILL.md: https://api.skillmd.com/api/skills/hadv/walletconnect/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: hadv (https://skillmd.com/u/hadv)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/hadv/walletconnect

---

# WalletConnect Integration Skill

## Overview
Expert knowledge for implementing WalletConnect v2 (Sign protocol) in web applications, particularly for wallet applications that need to connect to dApps. This skill covers the complete lifecycle from initialization to session management and request handling.

> **⚠️ IMPORTANT UPDATE (2025)**: WalletConnect has been rebranded to **Reown**. The `@walletconnect/web3wallet` package is deprecated in favor of `@reown/walletkit`. This skill covers both the legacy WalletConnect v2 packages (still supported until February 2025) and migration guidance to Reown packages. See [Migration Guide](#migration-from-walletconnect-to-reown) section below.

## Table of Contents

- [Quick Start](#quick-start)
- [Core Concepts](#core-concepts)
- [Installation & Setup](#installation--setup)
- [Implementation Guide](#implementation-guide)
  - [TypeScript Setup](#typescript-setup)
  - [Initialization](#initialization)
  - [Event Listeners](#event-listeners)
  - [Pairing & Sessions](#pairing--sessions)
  - [Request Handling](#request-handling)
- [Advanced Topics](#advanced-topics)
  - [Multi-Chain Support](#multi-chain-support)
  - [Smart Contract Wallets (ERC-4337)](#smart-contract-wallet-integration-erc-4337)
  - [Request Queue Management](#request-queue-management)
  - [Session Expiration & Reconnection](#session-expiration--reconnection)
- [Error Handling & Debugging](#error-handling--debugging)
- [Testing](#testing)
- [Performance Optimization](#performance-optimization)
- [Migration from WalletConnect to Reown](#migration-from-walletconnect-to-reown)
- [API Reference](#api-reference)
- [FAQ](#faq)
- [References](#references)

## Quick Start

Get up and running with WalletConnect v2 in 5 minutes:

```bash
# Install packages (legacy - still supported until Feb 2025)
npm install @walletconnect/web3wallet @walletconnect/core @walletconnect/utils

# OR use new Reown packages (recommended)
npm install @reown/walletkit
```

**Minimal Working Example** (JavaScript):
```javascript
import { Web3Wallet } from '@walletconnect/web3wallet'
import { Core } from '@walletconnect/core'

// 1. Initialize
const core = new Core({ projectId: 'YOUR_PROJECT_ID' })
const web3wallet = await Web3Wallet.init({
  core,
  metadata: {
    name: 'My Wallet',
    description: 'My Wallet Description',
    url: 'https://mywallet.com',
    icons: ['https://mywallet.com/icon.png']
  }
})

// 2. Listen for session proposals
web3wallet.on('session_proposal', async (proposal) => {
  // Show approval UI to user, then:
  const session = await web3wallet.approveSession({
    id: proposal.id,
    namespaces: {
      eip155: {
        chains: ['eip155:1'],
        methods: ['eth_sendTransaction', 'personal_sign'],
        events: ['chainChanged', 'accountsChanged'],
        accounts: ['eip155:1:0xYourAddress']
      }
    }
  })
})

// 3. Pair with dApp
await web3wallet.core.pairing.pair({ uri: 'wc:...' })
```

## Core Concepts

### WalletConnect v2 Architecture
- **Web3Wallet**: Wallet-side SDK for receiving connection requests from dApps
- **Core**: Underlying protocol layer handling pairing, sessions, and messaging
- **Sign Protocol**: The protocol for signing transactions and messages
- **Pairing**: Initial connection establishment using URI
- **Session**: Persistent connection between wallet and dApp
- **Namespaces**: Define supported chains, methods, and events (e.g., `eip155` for Ethereum)

### Key Components
1. **Project ID**: Required from WalletConnect Cloud (https://cloud.walletconnect.com)
2. **Metadata**: Wallet information (name, description, URL, icons)
3. **Event Listeners**: Handle proposals, requests, and disconnections
4. **Session Management**: Approve/reject proposals, handle active sessions

## Installation & Setup

### 1. Installation

**Legacy WalletConnect v2 Packages** (supported until February 2025):
```bash
npm install @walletconnect/web3wallet @walletconnect/core @walletconnect/utils
```

**Package Versions** (tested and working):
- `@walletconnect/web3wallet`: `1.16.1`
- `@walletconnect/core`: `2.21.0`
- `@walletconnect/utils`: `2.21.0`

**New Reown Packages** (recommended):
```bash
npm install @reown/walletkit
```

> **Note**: Always check for the latest compatible versions using `npm info @walletconnect/web3wallet` or `npm info @reown/walletkit`. The WalletConnect v2 packages will reach end-of-life on February 18, 2025.

### 2. TypeScript Setup

**Type Definitions**:
```typescript
import type {
  Web3Wallet,
  Web3WalletTypes
} from '@walletconnect/web3wallet'
import type { SessionTypes, ProposalTypes, SignClientTypes } from '@walletconnect/types'

// Session Proposal Type
type SessionProposal = SignClientTypes.EventArguments['session_proposal']

// Session Request Type
type SessionRequest = SignClientTypes.EventArguments['session_request']

// Namespace Configuration Type
interface Namespaces {
  [namespace: string]: {
    chains: string[]
    methods: string[]
    events: string[]
    accounts: string[]
  }
}

// Session Type
type Session = SessionTypes.Struct

// Common Request Methods
type RequestMethod =
  | 'eth_sendTransaction'
  | 'eth_signTransaction'
  | 'eth_sign'
  | 'personal_sign'
  | 'eth_signTypedData'
  | 'eth_signTypedData_v4'

// Transaction Request Params
interface TransactionRequest {
  from: string
  to: string
  value?: string
  data?: string
  gas?: string
  gasPrice?: string
  nonce?: string
}

// Response Format
interface JsonRpcResponse<T = any> {
  id: number
  jsonrpc: '2.0'
  result?: T
  error?: {
    code: number
    message: string
  }
}
```

### 3. Initialization (React Context Pattern)

```javascript
import { Web3Wallet } from '@walletconnect/web3wallet'
import { Core } from '@walletconnect/core'

// Initialize Core first
const core = new Core({
  projectId: 'YOUR_PROJECT_ID', // Get from https://cloud.walletconnect.com
})

// Initialize Web3Wallet with Core
const web3wallet = await Web3Wallet.init({
  core,
  metadata: {
    name: 'Your Wallet Name',
    description: 'Your wallet description',
    url: 'https://yourwallet.com',
    icons: ['https://yourwallet.com/logo.png'],
  },
})
```

**Critical Implementation Details**:
- Use refs to prevent multiple initializations in React (StrictMode causes double renders)
- Initialize Core separately before Web3Wallet
- Store project ID in environment variables
- Validate project ID before initialization

### 4. Event Listeners Setup

**JavaScript**:
```javascript
// Session proposal - when dApp wants to connect
web3wallet.on('session_proposal', (proposal) => {
  console.log('Session proposal received:', proposal)
  // Show UI to user for approval
  setPendingProposal(proposal)
})

// Session request - when dApp requests transaction/signature
web3wallet.on('session_request', (request) => {
  console.log('Session request received:', request)
  // Show UI to user for approval
  setPendingRequest(request)
})

// Session delete - when session is terminated
web3wallet.on('session_delete', ({ topic }) => {
  console.log('Session deleted:', topic)
  // Update UI to remove session
  updateActiveSessions()
})

// Session update - when session chains/accounts change
web3wallet.on('session_update', ({ topic, params }) => {
  console.log('Session updated:', topic, params)
  updateActiveSessions()
})
```

**TypeScript**:
```typescript
import type { Web3Wallet } from '@walletconnect/web3wallet'
import type { SignClientTypes } from '@walletconnect/types'

const setupEventListeners = (web3wallet: Web3Wallet) => {
  // Session proposal
  web3wallet.on('session_proposal',
    (proposal: SignClientTypes.EventArguments['session_proposal']) => {
      console.log('Session proposal received:', proposal)
      setPendingProposal(proposal)
    }
  )

  // Session request
  web3wallet.on('session_request',
    (request: SignClientTypes.EventArguments['session_request']) => {
      console.log('Session request received:', request)
      setPendingRequest(request)
    }
  )

  // Session delete
  web3wallet.on('session_delete',
    ({ topic }: { topic: string }) => {
      console.log('Session deleted:', topic)
      updateActiveSessions()
    }
  )

  // Session update
  web3wallet.on('session_update',
    ({ topic, params }: SignClientTypes.EventArguments['session_update']) => {
      console.log('Session updated:', topic, params)
      updateActiveSessions()
    }
  )
}

// Cleanup function
const cleanupEventListeners = (web3wallet: Web3Wallet) => {
  web3wallet.removeAllListeners('session_proposal')
  web3wallet.removeAllListeners('session_request')
  web3wallet.removeAllListeners('session_delete')
  web3wallet.removeAllListeners('session_update')
}
```

### 4. Pairing with dApp

```javascript
// User scans QR code or pastes URI from dApp
const uri = 'wc:...' // WalletConnect URI from dApp

// Use core.pairing.pair (not web3wallet.pair) for better event handling
await web3wallet.core.pairing.pair({ uri })

// This triggers 'session_proposal' event
```

**URI Validation**:
- Must start with `wc:`
- Contains pairing topic and relay information
- One-time use only

### 5. Approving Session Proposal

```javascript
const approveSession = async (proposal, accountAddress, chainId) => {
  const { id, params } = proposal

  // Build namespaces manually for full control
  const namespaces = {
    eip155: {
      chains: [`eip155:${chainId}`], // e.g., 'eip155:11155111' for Sepolia
      methods: [
        'eth_sendTransaction',
        'eth_signTransaction',
        'eth_sign',
        'personal_sign',
        'eth_signTypedData',
        'eth_signTypedData_v4',
      ],
      events: ['chainChanged', 'accountsChanged'],
      accounts: [`eip155:${chainId}:${accountAddress}`],
    },
  }

  const session = await web3wallet.approveSession({
    id,
    namespaces,
  })

  return session
}
```

**Namespace Structure**:
- `chains`: Array of supported chains in CAIP-2 format (`eip155:${chainId}`)
- `methods`: Array of JSON-RPC methods wallet supports
- `events`: Array of events wallet will emit
- `accounts`: Array of accounts in CAIP-10 format (`eip155:${chainId}:${address}`)

### 6. Rejecting Session Proposal

```javascript
await web3wallet.rejectSession({
  id: proposal.id,
  reason: {
    code: 5000,
    message: 'User rejected',
  },
})
```

**Standard Error Codes**:
- `5000`: User rejected
- `5001`: User rejected methods
- `5002`: User rejected chains

### 7. Handling Session Requests

```javascript
const handleSessionRequest = async (request) => {
  const { topic, params, id } = request
  const { request: { method, params: methodParams } } = params

  try {
    let result

    switch (method) {
      case 'eth_sendTransaction':
        result = await handleSendTransaction(methodParams[0])
        break

      case 'personal_sign':
        result = await handlePersonalSign(methodParams[0], methodParams[1])
        break

      case 'eth_signTypedData':
      case 'eth_signTypedData_v4':
        result = await handleSignTypedData(methodParams[0], methodParams[1])
        break

      default:
        throw new Error(`Unsupported method: ${method}`)
    }

    // Send success response
    await web3wallet.respondSessionRequest({
      topic,
      response: {
        id,
        jsonrpc: '2.0',
        result,
      },
    })
  } catch (error) {
    // Send error response
    await web3wallet.respondSessionRequest({
      topic,
      response: {
        id,
        jsonrpc: '2.0',
        error: {
          code: 5000,
          message: error.message,
        },
      },
    })
  }
}
```

### 8. Method Implementations

#### eth_sendTransaction
```javascript
const handleSendTransaction = async (tx) => {
  // tx contains: { to, value, data, from, gas, gasPrice }

  // For EOA wallets:
  const txHash = await signer.sendTransaction(tx)
  return txHash

  // For smart contract wallets (ERC-4337):
  // Build UserOperation, sign, and send to bundler
  // Return transaction hash after confirmation
}
```

#### personal_sign
```javascript
const handlePersonalSign = async (message, address) => {
  // message is hex-encoded
  const signature = await signer.signMessage(ethers.getBytes(message))
  return signature
}
```

#### eth_signTypedData / eth_signTypedData_v4
```javascript
const handleSignTypedData = async (address, typedData) => {
  const parsedData = JSON.parse(typedData)
  const signature = await signer.signTypedData(
    parsedData.domain,
    parsedData.types,
    parsedData.message
  )
  return signature
}
```

### 9. Session Management

#### Get Active Sessions
```javascript
const activeSessions = web3wallet.getActiveSessions()
// Returns object with topic as key, session as value
const sessionArray = Object.values(activeSessions)
```

#### Disconnect Session
```javascript
await web3wallet.disconnectSession({
  topic,
  reason: {
    code: 6000,
    message: 'User disconnected',
  },
})
```

## Common Patterns & Best Practices

### React Context Pattern
```javascript
// Create context for app-wide access
const WalletConnectContext = createContext()

export const WalletConnectProvider = ({ children }) => {
  const [web3wallet, setWeb3wallet] = useState(null)
  const [isInitialized, setIsInitialized] = useState(false)
  const [sessions, setSessions] = useState([])
  const [pendingProposal, setPendingProposal] = useState(null)
  const [pendingRequest, setPendingRequest] = useState(null)

  // Initialize in useEffect
  // Set up event listeners
  // Provide methods via context

  return (
    <WalletConnectContext.Provider value={{
      web3wallet,
      isInitialized,
      sessions,
      pendingProposal,
      pendingRequest,
      pair,
      approveSession,
      rejectSession,
      respondSessionRequest,
      disconnectSession,
    }}>
      {children}
    </WalletConnectContext.Provider>
  )
}
```

### Prevent Multiple Initializations
```javascript
const initializingRef = useRef(false)
const initializedRef = useRef(false)

useEffect(() => {
  const initWalletConnect = async () => {
    if (initializedRef.current || initializingRef.current) {
      return // Already initialized or initializing
    }

    initializingRef.current = true

    try {
      // Initialize...
      initializedRef.current = true
    } finally {
      initializingRef.current = false
    }
  }

  initWalletConnect()
}, []) // Empty deps - run once
```

### Session Persistence
Sessions are automatically persisted by WalletConnect SDK in IndexedDB. On page reload:
```javascript
// Load existing sessions after initialization
const activeSessions = web3wallet.getActiveSessions()
setSessions(Object.values(activeSessions))
```

## Common Issues & Solutions

### Issue: Multiple Initializations in React StrictMode
**Solution**: Use refs to track initialization state (see pattern above)

### Issue: Event listeners not firing
**Solution**: Use `web3wallet.core.pairing.pair({ uri })` instead of `web3wallet.pair(uri)`

### Issue: "Invalid project ID" error
**Solution**:
- Get project ID from https://cloud.walletconnect.com
- Store in environment variable
- Validate before initialization

### Issue: Session proposal not showing
**Solution**: Ensure event listeners are set up before pairing

### Issue: "Unsupported method" errors
**Solution**: Only include methods you actually support in namespace definition

## Security Considerations

1. **Always show user what they're signing**: Display decoded transaction/message details
2. **Validate requests**: Check sender, method, parameters before processing
3. **User confirmation required**: Never auto-approve transactions or signatures
4. **Session review**: Allow users to see and disconnect active sessions
5. **Error handling**: Don't expose sensitive information in error messages
6. **Rate limiting**: Consider limiting requests per session
7. **Timeout handling**: Set reasonable timeouts for user approval

## Smart Contract Wallet Integration (ERC-4337)

When integrating WalletConnect with Account Abstraction wallets:

### Transaction Flow
1. **Receive Request**: dApp sends `eth_sendTransaction` via WalletConnect
2. **Build UserOperation**: Convert transaction to UserOperation format
3. **Sign UserOperation**: Use passkey/owner to sign
4. **Submit to Bundler**: Send signed UserOp to bundler
5. **Wait for Confirmation**: Monitor transaction status
6. **Return Hash**: Send transaction hash back to dApp

### Example Implementation
```javascript
const handleSendTransaction = async (tx) => {
  // Build UserOperation from transaction
  const userOp = await sdk.buildUserOperation(
    accountAddress,
    tx.to,
    tx.value || '0',
    tx.data || '0x'
  )

  // Sign with passkey (and owner if 2FA enabled)
  const signedUserOp = await sdk.signUserOperation(
    userOp,
    passkeyCredential,
    twoFactorEnabled ? ownerSigner : null
  )

  // Send to bundler
  const userOpHash = await sdk.sendUserOperation(signedUserOp)

  // Wait for receipt
  const receipt = await sdk.waitForUserOperationReceipt(userOpHash)

  // Return transaction hash to dApp
  return receipt.transactionHash
}
```

### Message Signing for Smart Wallets
Smart contract wallets should implement EIP-1271 for signature validation:
```javascript
const handlePersonalSign = async (message, address) => {
  // Option 1: Sign with owner key (simpler, but less secure)
  const signature = await ownerSigner.signMessage(ethers.getBytes(message))

  // Option 2: Use EIP-1271 (recommended for production)
  // Implement isValidSignature on smart contract
  // Return signature that contract can verify

  return signature
}
```

## UI/UX Best Practices

### Session Proposal Modal
- Show dApp name, icon, and URL
- Display requested permissions (chains, methods)
- Show which account will be connected
- Clear approve/reject buttons
- Loading states during approval

### Session Request Modal
- Show dApp making the request
- Decode and display transaction details:
  - Recipient address
  - Amount (in ETH, not wei)
  - Function being called
  - Gas estimates
- For message signing: show decoded message
- Clear approve/reject buttons
- Loading states during signing

### Active Sessions List
- Show all connected dApps
- Display dApp icon, name, URL
- Show connected chains
- Disconnect button for each session
- Empty state when no sessions

## Testing

### Test with WalletConnect Example dApp
```
https://react-app.walletconnect.com/
```

### Manual Testing Checklist
- [ ] Initialize WalletConnect successfully
- [ ] Pair with dApp using URI
- [ ] Approve session proposal
- [ ] Reject session proposal
- [ ] Send transaction request
- [ ] Sign message request
- [ ] Sign typed data request
- [ ] Disconnect session from wallet
- [ ] Disconnect session from dApp
- [ ] Session persists after page reload
- [ ] Multiple concurrent sessions work
- [ ] Error handling for invalid URIs
- [ ] Error handling for rejected requests
- [ ] Timeout handling for user approval

## Performance Considerations

1. **Lazy Loading**: Only initialize WalletConnect when needed
2. **Event Cleanup**: Remove event listeners on unmount
3. **Session Caching**: Cache active sessions to avoid repeated queries
4. **Debouncing**: Debounce UI updates from events
5. **Background Processing**: Handle requests in background when possible

## References

- [WalletConnect Docs](https://docs.walletconnect.com/)
- [Web3Wallet SDK](https://docs.walletconnect.com/web3wallet/about)
- [Sign Protocol](https://docs.walletconnect.com/advanced/protocols/sign)
- [CAIP Standards](https://github.com/ChainAgnostic/CAIPs)
  - CAIP-2: Chain ID format (`eip155:${chainId}`)
  - CAIP-10: Account ID format (`eip155:${chainId}:${address}`)
- [WalletConnect Cloud](https://cloud.walletconnect.com/)
- [EIP-1271: Standard Signature Validation](https://eips.ethereum.org/EIPS/eip-1271)
- [ERC-4337: Account Abstraction](https://eips.ethereum.org/EIPS/eip-4337)

## Real-World Example

Based on the ΞTHΛURΛ wallet implementation, here's a complete working example:

### Context Provider
```javascript
import React, { createContext, useContext, useState, useEffect, useCallback, useRef } from 'react'
import { Web3Wallet } from '@walletconnect/web3wallet'
import { Core } from '@walletconnect/core'

const WalletConnectContext = createContext()

export const useWalletConnect = () => {
  const context = useContext(WalletConnectContext)
  if (!context) {
    throw new Error('useWalletConnect must be used within WalletConnectProvider')
  }
  return context
}

export const WalletConnectProvider = ({ children }) => {
  const [web3wallet, setWeb3wallet] = useState(null)
  const [isInitialized, setIsInitialized] = useState(false)
  const [sessions, setSessions] = useState([])
  const [pendingProposal, setPendingProposal] = useState(null)
  const [pendingRequest, setPendingRequest] = useState(null)
  const initializingRef = useRef(false)
  const initializedRef = useRef(false)

  useEffect(() => {
    const initWalletConnect = async () => {
      if (initializedRef.current || initializingRef.current) {
        return
      }

      initializingRef.current = true

      try {
        const projectId = import.meta.env.VITE_WALLETCONNECT_PROJECT_ID

        if (!projectId || projectId === 'YOUR_PROJECT_ID') {
          console.warn('WalletConnect Project ID not set')
          return
        }

        const core = new Core({ projectId })
        const wallet = await Web3Wallet.init({
          core,
          metadata: {
            name: 'Your Wallet',
            description: 'Your wallet description',
            url: 'https://yourwallet.com',
            icons: ['https://yourwallet.com/logo.png'],
          },
        })

        // Set up event listeners
        wallet.on('session_proposal', setPendingProposal)
        wallet.on('session_request', setPendingRequest)
        wallet.on('session_delete', ({ topic }) => {
          setSessions(Object.values(wallet.getActiveSessions()))
        })

        // Load existing sessions
        setSessions(Object.values(wallet.getActiveSessions()))

        setWeb3wallet(wallet)
        setIsInitialized(true)
        initializedRef.current = true
      } catch (err) {
        console.error('Failed to initialize WalletConnect:', err)
      } finally {
        initializingRef.current = false
      }
    }

    initWalletConnect()
  }, [])

  const pair = useCallback(async (uri) => {
    if (!web3wallet) throw new Error('WalletConnect not initialized')
    await web3wallet.core.pairing.pair({ uri })
  }, [web3wallet])

  const approveSession = useCallback(async (proposal, accountAddress, chainId) => {
    if (!web3wallet) throw new Error('WalletConnect not initialized')

    const namespaces = {
      eip155: {
        chains: [`eip155:${chainId}`],
        methods: ['eth_sendTransaction', 'personal_sign', 'eth_signTypedData_v4'],
        events: ['chainChanged', 'accountsChanged'],
        accounts: [`eip155:${chainId}:${accountAddress}`],
      },
    }

    const session = await web3wallet.approveSession({
      id: proposal.id,
      namespaces,
    })

    setSessions(Object.values(web3wallet.getActiveSessions()))
    setPendingProposal(null)
    return session
  }, [web3wallet])

  const rejectSession = useCallback(async (proposal) => {
    if (!web3wallet) throw new Error('WalletConnect not initialized')

    await web3wallet.rejectSession({
      id: proposal.id,
      reason: { code: 5000, message: 'User rejected' },
    })

    setPendingProposal(null)
  }, [web3wallet])

  const respondSessionRequest = useCallback(async (topic, response) => {
    if (!web3wallet) throw new Error('WalletConnect not initialized')

    await web3wallet.respondSessionRequest({ topic, response })
    setPendingRequest(null)
  }, [web3wallet])

  const disconnectSession = useCallback(async (topic) => {
    if (!web3wallet) throw new Error('WalletConnect not initialized')

    await web3wallet.disconnectSession({
      topic,
      reason: { code: 6000, message: 'User disconnected' },
    })

    setSessions(Object.values(web3wallet.getActiveSessions()))
  }, [web3wallet])

  return (
    <WalletConnectContext.Provider value={{
      web3wallet,
      isInitialized,
      sessions,
      pendingProposal,
      pendingRequest,
      pair,
      approveSession,
      rejectSession,
      respondSessionRequest,
      disconnectSession,
    }}>
      {children}
    </WalletConnectContext.Provider>
  )
}
```

This skill is based on real-world production experience with WalletConnect v2 in the ΞTHΛURΛ wallet project.

## Advanced Topics

### Multi-Chain Support

**Supporting Multiple Chains Simultaneously**:

```typescript
// TypeScript example with multi-chain support
const approveMultiChainSession = async (
  proposal: SessionProposal,
  accounts: { chainId: number; address: string }[]
) => {
  const { id, params } = proposal

  // Build multi-chain namespaces
  const chains = accounts.map(acc => `eip155:${acc.chainId}`)
  const accountsFormatted = accounts.map(
    acc => `eip155:${acc.chainId}:${acc.address}`
  )

  const namespaces = {
    eip155: {
      chains,
      methods: [
        'eth_sendTransaction',
        'eth_signTransaction',
        'personal_sign',
        'eth_signTypedData_v4',
      ],
      events: ['chainChanged', 'accountsChanged'],
      accounts: accountsFormatted,
    },
  }

  const session = await web3wallet.approveSession({
    id,
    namespaces,
  })

  return session
}

// Example: Approve with Ethereum Mainnet + Polygon
await approveMultiChainSession(proposal, [
  { chainId: 1, address: '0xYourAddress' },      // Ethereum Mainnet
  { chainId: 137, address: '0xYourAddress' },    // Polygon
  { chainId: 11155111, address: '0xYourAddress' } // Sepolia Testnet
])
```

**Switching Chains During Active Session**:

```typescript
const updateSessionChains = async (
  topic: string,
  newChains: { chainId: number; address: string }[]
) => {
  const session = web3wallet.getActiveSessions()[topic]

  if (!session) {
    throw new Error('Session not found')
  }

  const chains = newChains.map(c => `eip155:${c.chainId}`)
  const accounts = newChains.map(c => `eip155:${c.chainId}:${c.address}`)

  const updatedNamespaces = {
    ...session.namespaces,
    eip155: {
      ...session.namespaces.eip155,
      chains,
      accounts,
    },
  }

  await web3wallet.updateSession({
    topic,
    namespaces: updatedNamespaces,
  })

  // Emit chainChanged event to dApp
  await web3wallet.emitSessionEvent({
    topic,
    event: {
      name: 'chainChanged',
      data: newChains[0].chainId,
    },
    chainId: `eip155:${newChains[0].chainId}`,
  })
}
```



## Error Handling & Debugging

### Comprehensive Error Handling

**URI Validation and Pairing Errors**:

```typescript
const pairWithValidation = async (uri: string): Promise<void> => {
  try {
    // Validate URI format
    if (!uri || !uri.startsWith('wc:')) {
      throw new Error('Invalid WalletConnect URI format. Must start with "wc:"')
    }

    // Check if URI has already been used
    const pairings = web3wallet.core.pairing.getPairings()
    const existingPairing = pairings.find(p => p.topic === extractTopicFromUri(uri))
    if (existingPairing) {
      throw new Error('This pairing URI has already been used')
    }

    // Attempt pairing with timeout
    const pairingPromise = web3wallet.core.pairing.pair({ uri })
    const timeoutPromise = new Promise((_, reject) =>
      setTimeout(() => reject(new Error('Pairing timeout after 30s')), 30000)
    )

    await Promise.race([pairingPromise, timeoutPromise])
    console.log('Pairing successful')

  } catch (error) {
    if (error instanceof Error) {
      // Handle specific error types
      if (error.message.includes('No matching key')) {
        throw new Error('Invalid or expired pairing URI')
      } else if (error.message.includes('timeout')) {
        throw new Error('Connection timeout. Please check your internet connection')
      } else if (error.message.includes('Network')) {
        throw new Error('Network error. Please try again')
      }
    }
    throw error
  }
}

// Helper function
const extractTopicFromUri = (uri: string): string => {
  const params = new URLSearchParams(uri.split('?')[1])
  return params.get('topic') || ''
}
```

**Session Request Error Handling**:

```typescript
const handleSessionRequestWithErrorHandling = async (
  request: SessionRequest
): Promise<void> => {
  const { topic, params, id } = request
  const { request: { method, params: methodParams } } = params

  try {
    // Validate session exists
    const session = web3wallet.getActiveSessions()[topic]
    if (!session) {
      throw new Error('Session not found or expired')
    }

    // Validate method is supported
    const supportedMethods = session.namespaces.eip155?.methods || []
    if (!supportedMethods.includes(method)) {
      throw new Error(`Method ${method} not supported in this session`)
    }

    // Add timeout for user approval
    const userApprovalPromise = getUserApproval(request)
    const timeoutPromise = new Promise((_, reject) =>
      setTimeout(() => reject(new Error('User approval timeout')), 5 * 60 * 1000) // 5 min
    )

    const approved = await Promise.race([userApprovalPromise, timeoutPromise])

    if (!approved) {
      throw new Error('User rejected the request')
    }

    // Execute request
    let result
    switch (method) {
      case 'eth_sendTransaction':
        result = await handleSendTransaction(methodParams[0])
        break
      case 'personal_sign':
        result = await handlePersonalSign(methodParams[0], methodParams[1])
        break
      default:
        throw new Error(`Unsupported method: ${method}`)
    }

    // Send success response
    await web3wallet.respondSessionRequest({
      topic,
      response: {
        id,
        jsonrpc: '2.0',
        result,
      },
    })

  } catch (error) {
    console.error('Session request error:', error)

    // Determine error code
    let errorCode = 5000 // Generic error
    let errorMessage = 'Request failed'

    if (error instanceof Error) {
      if (error.message.includes('rejected')) {
        errorCode = 5000
        errorMessage = 'User rejected the request'
      } else if (error.message.includes('timeout')) {
        errorCode = 5001
        errorMessage = 'Request timeout'
      } else if (error.message.includes('insufficient funds')) {
        errorCode = 5002
        errorMessage = 'Insufficient funds for transaction'
      } else if (error.message.includes('gas')) {
        errorCode = 5003
        errorMessage = 'Gas estimation failed'
      } else {
        errorMessage = error.message
      }
    }

    // Send error response
    await web3wallet.respondSessionRequest({
      topic,
      response: {
        id,
        jsonrpc: '2.0',
        error: {
          code: errorCode,
          message: errorMessage,
        },
      },
    })
  }
}
```

### Common Error Codes

| Code | Meaning | Solution |
|------|---------|----------|
| 5000 | User rejected | User declined the request |
| 5001 | User rejected methods | Requested methods not approved |
| 5002 | User rejected chains | Requested chains not approved |
| 5003 | User rejected events | Requested events not approved |
| 6000 | User disconnected | User terminated the session |
| 1000 | Invalid request | Malformed request parameters |
| 1001 | Method not found | Unsupported method |
| 1002 | Invalid params | Invalid method parameters |

### Debugging Tips

**Enable Debug Logging**:

```typescript
// Set environment variable for detailed logs
localStorage.setItem('debug', '@walletconnect/*')

// Or in your app initialization
if (process.env.NODE_ENV === 'development') {
  localStorage.setItem('debug', '@walletconnect/*')
}
```

**Inspect IndexedDB for Session Data**:

```javascript
// Open browser DevTools > Application > IndexedDB > wc@2:*
// Look for:
// - wc@2:core:pairing - Active pairings
// - wc@2:client:session - Active sessions
// - wc@2:core:messages - Message queue

// Programmatically inspect
const inspectStorage = async () => {
  const pairings = web3wallet.core.pairing.getPairings()
  const sessions = web3wallet.getActiveSessions()

  console.log('Pairings:', pairings)
  console.log('Sessions:', sessions)
  console.log('Pending requests:', web3wallet.getPendingSessionRequests())
}
```

**Network Inspection**:

```typescript
// Monitor WebSocket connection
web3wallet.core.relayer.on('relayer_connect', () => {
  console.log('✅ Connected to WalletConnect relay')
})

web3wallet.core.relayer.on('relayer_disconnect', () => {
  console.log('❌ Disconnected from WalletConnect relay')
})

web3wallet.core.relayer.on('relayer_error', (error) => {
  console.error('Relay error:', error)
})

// Check connection status
const isConnected = web3wallet.core.relayer.connected
console.log('Relay connected:', isConnected)
```

**Common Console Errors and Solutions**:

1. **"No matching key. pairing topic doesn't exist"**
   - **Cause**: URI has already been used or is invalid
   - **Solution**: Generate a new pairing URI from the dApp

2. **"Missing or invalid. session topic doesn't exist"**
   - **Cause**: Session has expired or been deleted
   - **Solution**: Re-establish connection with dApp

3. **"Unsupported chains"**
   - **Cause**: Wallet doesn't support requested chains
   - **Solution**: Add chain support or reject with clear message

4. **"Invalid project ID"**
   - **Cause**: Missing or incorrect WalletConnect Cloud project ID
   - **Solution**: Get valid project ID from https://cloud.walletconnect.com

5. **"WebSocket connection failed"**
   - **Cause**: Network issues or firewall blocking WebSocket
   - **Solution**: Check internet connection and firewall settings


## Testing

### Unit Testing with Jest

**Mock Setup**:

```typescript
// __mocks__/@walletconnect/web3wallet.ts
export const mockWeb3Wallet = {
  init: jest.fn(),
  on: jest.fn(),
  off: jest.fn(),
  removeAllListeners: jest.fn(),
  approveSession: jest.fn(),
  rejectSession: jest.fn(),
  respondSessionRequest: jest.fn(),
  disconnectSession: jest.fn(),
  getActiveSessions: jest.fn(() => ({})),
  getPendingSessionRequests: jest.fn(() => []),
  updateSession: jest.fn(),
  emitSessionEvent: jest.fn(),
  core: {
    pairing: {
      pair: jest.fn(),
      getPairings: jest.fn(() => []),
    },
    relayer: {
      on: jest.fn(),
      connected: true,
    },
  },
}

export const Web3Wallet = {
  init: jest.fn(() => Promise.resolve(mockWeb3Wallet)),
}

export const Core = jest.fn()
```

**Test Examples**:

```typescript
import { render, waitFor, fireEvent } from '@testing-library/react'
import { WalletConnectProvider, useWalletConnect } from './WalletConnectContext'
import { mockWeb3Wallet } from './__mocks__/@walletconnect/web3wallet'

describe('WalletConnect Integration', () => {
  beforeEach(() => {
    jest.clearAllMocks()
  })

  test('initializes WalletConnect on mount', async () => {
    const { result } = renderHook(() => useWalletConnect(), {
      wrapper: WalletConnectProvider,
    })

    await waitFor(() => {
      expect(result.current.isInitialized).toBe(true)
    })

    expect(Web3Wallet.init).toHaveBeenCalledWith({
      core: expect.any(Object),
      metadata: expect.objectContaining({
        name: expect.any(String),
        description: expect.any(String),
      }),
    })
  })

  test('handles session proposal correctly', async () => {
    const mockProposal = {
      id: 1,
      params: {
        proposer: {
          metadata: {
            name: 'Test dApp',
            description: 'Test Description',
            url: 'https://test.com',
            icons: ['https://test.com/icon.png'],
          },
        },
        requiredNamespaces: {
          eip155: {
            chains: ['eip155:1'],
            methods: ['eth_sendTransaction'],
            events: ['chainChanged'],
          },
        },
      },
    }

    const { result } = renderHook(() => useWalletConnect(), {
      wrapper: WalletConnectProvider,
    })

    await waitFor(() => expect(result.current.isInitialized).toBe(true))

    // Simulate session proposal event
    const proposalHandler = mockWeb3Wallet.on.mock.calls.find(
      call => call[0] === 'session_proposal'
    )[1]

    proposalHandler(mockProposal)

    await waitFor(() => {
      expect(result.current.pendingProposal).toEqual(mockProposal)
    })
  })

  test('approves session with correct namespaces', async () => {
    const mockProposal = { id: 1, params: {} }
    const accountAddress = '0x1234567890123456789012345678901234567890'
    const chainId = 1

    mockWeb3Wallet.approveSession.mockResolvedValue({
      topic: 'test-topic',
      acknowledged: Promise.resolve(),
    })

    const { result } = renderHook(() => useWalletConnect(), {
      wrapper: WalletConnectProvider,
    })

    await waitFor(() => expect(result.current.isInitialized).toBe(true))

    await result.current.approveSession(mockProposal, accountAddress, chainId)

    expect(mockWeb3Wallet.approveSession).toHaveBeenCalledWith({
      id: 1,
      namespaces: {
        eip155: {
          chains: ['eip155:1'],
          methods: expect.arrayContaining(['eth_sendTransaction', 'personal_sign']),
          events: expect.arrayContaining(['chainChanged', 'accountsChanged']),
          accounts: [`eip155:1:${accountAddress}`],
        },
      },
    })
  })

  test('handles pairing errors gracefully', async () => {
    const invalidUri = 'invalid-uri'

    mockWeb3Wallet.core.pairing.pair.mockRejectedValue(
      new Error('Invalid URI format')
    )

    const { result } = renderHook(() => useWalletConnect(), {
      wrapper: WalletConnectProvider,
    })

    await waitFor(() => expect(result.current.isInitialized).toBe(true))

    await expect(result.current.pair(invalidUri)).rejects.toThrow('Invalid URI format')
  })
})
```

### Integration Testing

**E2E Test with Playwright**:

```typescript
import { test, expect } from '@playwright/test'

test.describe('WalletConnect Integration', () => {
  test('should connect to dApp and approve session', async ({ page, context }) => {
    // Start wallet app
    await page.goto('http://localhost:3000')

    // Wait for WalletConnect to initialize
    await page.waitForSelector('[data-testid="wc-initialized"]')

    // Open new page for dApp
    const dAppPage = await context.newPage()
    await dAppPage.goto('https://react-app.walletconnect.com/')

    // Click connect button on dApp
    await dAppPage.click('button:has-text("Connect")')

    // Get WalletConnect URI
    const uri = await dAppPage.locator('[data-testid="wc-uri"]').textContent()

    // Paste URI in wallet
    await page.fill('[data-testid="wc-uri-input"]', uri)
    await page.click('[data-testid="wc-pair-button"]')

    // Wait for session proposal
    await page.waitForSelector('[data-testid="session-proposal-modal"]')

    // Verify dApp details
    const dAppName = await page.locator('[data-testid="dapp-name"]').textContent()
    expect(dAppName).toBe('React App')

    // Approve session
    await page.click('[data-testid="approve-session-button"]')

    // Verify connection on dApp
    await dAppPage.waitForSelector('[data-testid="connected"]', { timeout: 10000 })
    const connectedAddress = await dAppPage.locator('[data-testid="address"]').textContent()
    expect(connectedAddress).toMatch(/^0x[a-fA-F0-9]{40}$/)
  })

  test('should handle transaction request', async ({ page }) => {
    // Assume already connected from previous test

    // Trigger transaction from dApp
    // ... (dApp interaction)

    // Wait for transaction request in wallet
    await page.waitForSelector('[data-testid="transaction-request-modal"]')

    // Verify transaction details
    const recipient = await page.locator('[data-testid="tx-to"]').textContent()
    const value = await page.locator('[data-testid="tx-value"]').textContent()

    expect(recipient).toMatch(/^0x[a-fA-F0-9]{40}$/)
    expect(value).

…(truncated)
