Walletconnect
npx machina-cli add skill hadv/claude-skill-set/walletconnect --openclawWalletConnect 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/web3walletpackage 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 section below.
Table of Contents
- Quick Start
- Core Concepts
- Installation & Setup
- Implementation Guide
- Advanced Topics
- Error Handling & Debugging
- Testing
- Performance Optimization
- Migration from WalletConnect to Reown
- API Reference
- FAQ
- References
Quick Start
Get up and running with WalletConnect v2 in 5 minutes:
# 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):
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.,
eip155for Ethereum)
Key Components
- Project ID: Required from WalletConnect Cloud (https://cloud.walletconnect.com)
- Metadata: Wallet information (name, description, URL, icons)
- Event Listeners: Handle proposals, requests, and disconnections
- Session Management: Approve/reject proposals, handle active sessions
Installation & Setup
1. Installation
Legacy WalletConnect v2 Packages (supported until February 2025):
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):
npm install @reown/walletkit
Note: Always check for the latest compatible versions using
npm info @walletconnect/web3walletornpm info @reown/walletkit. The WalletConnect v2 packages will reach end-of-life on February 18, 2025.
2. TypeScript Setup
Type Definitions:
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)
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:
// 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:
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
// 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
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 supportsevents: Array of events wallet will emitaccounts: Array of accounts in CAIP-10 format (eip155:${chainId}:${address})
6. Rejecting Session Proposal
await web3wallet.rejectSession({
id: proposal.id,
reason: {
code: 5000,
message: 'User rejected',
},
})
Standard Error Codes:
5000: User rejected5001: User rejected methods5002: User rejected chains
7. Handling Session Requests
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
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
const handlePersonalSign = async (message, address) => {
// message is hex-encoded
const signature = await signer.signMessage(ethers.getBytes(message))
return signature
}
eth_signTypedData / eth_signTypedData_v4
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
const activeSessions = web3wallet.getActiveSessions()
// Returns object with topic as key, session as value
const sessionArray = Object.values(activeSessions)
Disconnect Session
await web3wallet.disconnectSession({
topic,
reason: {
code: 6000,
message: 'User disconnected',
},
})
Common Patterns & Best Practices
React Context Pattern
// 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
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:
// 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
- Always show user what they're signing: Display decoded transaction/message details
- Validate requests: Check sender, method, parameters before processing
- User confirmation required: Never auto-approve transactions or signatures
- Session review: Allow users to see and disconnect active sessions
- Error handling: Don't expose sensitive information in error messages
- Rate limiting: Consider limiting requests per session
- Timeout handling: Set reasonable timeouts for user approval
Smart Contract Wallet Integration (ERC-4337)
When integrating WalletConnect with Account Abstraction wallets:
Transaction Flow
- Receive Request: dApp sends
eth_sendTransactionvia WalletConnect - Build UserOperation: Convert transaction to UserOperation format
- Sign UserOperation: Use passkey/owner to sign
- Submit to Bundler: Send signed UserOp to bundler
- Wait for Confirmation: Monitor transaction status
- Return Hash: Send transaction hash back to dApp
Example Implementation
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:
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
- Lazy Loading: Only initialize WalletConnect when needed
- Event Cleanup: Remove event listeners on unmount
- Session Caching: Cache active sessions to avoid repeated queries
- Debouncing: Debounce UI updates from events
- Background Processing: Handle requests in background when possible
References
- WalletConnect Docs
- Web3Wallet SDK
- Sign Protocol
- CAIP Standards
- CAIP-2: Chain ID format (
eip155:${chainId}) - CAIP-10: Account ID format (
eip155:${chainId}:${address})
- CAIP-2: Chain ID format (
- WalletConnect Cloud
- EIP-1271: Standard Signature Validation
- ERC-4337: Account Abstraction
Real-World Example
Based on the ΞTHΛURΛ wallet implementation, here's a complete working example:
Context Provider
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 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:
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:
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:
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:
// 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:
// 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:
// 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:
-
"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
-
"Missing or invalid. session topic doesn't exist"
- Cause: Session has expired or been deleted
- Solution: Re-establish connection with dApp
-
"Unsupported chains"
- Cause: Wallet doesn't support requested chains
- Solution: Add chain support or reject with clear message
-
"Invalid project ID"
- Cause: Missing or incorrect WalletConnect Cloud project ID
- Solution: Get valid project ID from https://cloud.walletconnect.com
-
"WebSocket connection failed"
- Cause: Network issues or firewall blocking WebSocket
- Solution: Check internet connection and firewall settings
Testing
Unit Testing with Jest
Mock Setup:
// __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:
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:
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).toBeTruthy()
// Approve transaction
await page.click('[data-testid="approve-tx-button"]')
// Wait for confirmation
await page.waitForSelector('[data-testid="tx-confirmed"]')
})
})
Manual Testing Checklist
Basic Functionality:
- WalletConnect initializes successfully
- Can pair with dApp using QR code
- Can pair with dApp using pasted URI
- Session proposal modal displays correct dApp information
- Can approve session proposal
- Can reject session proposal
- Active sessions list shows connected dApps
- Can disconnect session from wallet
- Sessions persist after page reload
Request Handling:
-
eth_sendTransactionrequest displays correctly -
personal_signrequest displays correctly -
eth_signTypedData_v4request displays correctly - Can approve transaction requests
- Can reject transaction requests
- Transaction confirmation shows in dApp
- Error messages display for failed transactions
Multi-Chain:
- Can approve session with multiple chains
- Can switch chains during active session
- Requests route to correct chain
- Chain switching emits correct events
Error Handling:
- Invalid URI shows error message
- Expired URI shows error message
- Network errors handled gracefully
- Timeout errors handled gracefully
- User rejection sends correct error to dApp
Performance:
- Initialization completes within 3 seconds
- Pairing completes within 5 seconds
- Session approval completes within 2 seconds
- No memory leaks after multiple connections
- UI remains responsive during operations
Migration from WalletConnect to Reown
Overview
WalletConnect Inc has rebranded to Reown. All @walletconnect/* packages are being deprecated in favor of @reown/* packages.
Timeline:
- Limited Support Phase: September 17, 2024 - February 17, 2025
- Only critical bug fixes and security updates
- End-of-Life: February 18, 2025 onwards
- All support ceases for
@walletconnect/*packages
- All support ceases for
Package Migration Map
| Old Package | New Package |
|---|---|
@walletconnect/web3wallet | @reown/walletkit |
@walletconnect/core | Included in @reown/walletkit |
@walletconnect/utils | Included in @reown/walletkit |
@web3modal/* | @reown/appkit-* |
Step-by-Step Migration Guide
Step 1: Update Dependencies
# Remove old packages
npm uninstall @walletconnect/web3wallet @walletconnect/core @walletconnect/utils
# Install new package
npm install @reown/walletkit
Step 2: Update Imports
// Before
import { Web3Wallet } from '@walletconnect/web3wallet'
import { Core } from '@walletconnect/core'
// After
import { WalletKit } from '@reown/walletkit'
Step 3: Update Initialization
// Before
const core = new Core({ projectId })
const web3wallet = await Web3Wallet.init({
core,
metadata: { /* ... */ }
})
// After
const walletKit = await WalletKit.init({
projectId,
metadata: { /* ... */ }
})
Step 4: Update API Calls
Most API calls remain the same, but accessed through walletKit instead of web3wallet:
// Before
await web3wallet.approveSession({ /* ... */ })
await web3wallet.core.pairing.pair({ uri })
// After
await walletKit.approveSession({ /* ... */ })
await walletKit.core.pairing.pair({ uri })
Step 5: Test Thoroughly
Run your full test suite to ensure compatibility. Pay special attention to:
- Session proposal handling
- Request handling
- Event listeners
- Error handling
Migration Checklist
- Update package.json dependencies
- Update all imports
- Update initialization code
- Update API calls
- Update type imports (if using TypeScript)
- Run unit tests
- Run integration tests
- Test with real dApps
- Update documentation
- Deploy to staging
- Monitor for errors
- Deploy to production
Compatibility Notes
- The Reown packages maintain backward compatibility with the WalletConnect v2 protocol
- Existing sessions will continue to work after migration
- No changes required on the dApp side
- Project IDs from WalletConnect Cloud remain valid
Performance Optimization
Bundle Size Optimization
Tree Shaking:
// Import only what you need
import { WalletKit } from '@reown/walletkit'
// Avoid importing entire utils package
import { getSdkError } from '@walletconnect/utils' // ❌ Large bundle
import { getSdkError } from '@reown/walletkit/utils' // ✅ Tree-shakeable
Lazy Loading:
// Lazy load WalletConnect only when needed
const initWalletConnect = async () => {
const { WalletKit } = await import('@reown/walletkit')
return await WalletKit.init({ /* ... */ })
}
// Initialize on user action
button.addEventListener('click', async () => {
const walletKit = await initWalletConnect()
})
Memory Management
Cleanup on Unmount:
useEffect(() => {
const initWC = async () => {
const walletKit = await WalletKit.init({ /* ... */ })
setWalletKit(walletKit)
}
initWC()
return () => {
// Cleanup
if (walletKit) {
walletKit.removeAllListeners()
// Disconnect all sessions if needed
Object.keys(walletKit.getActiveSessions()).forEach(topic => {
walletKit.disconnectSession({
topic,
reason: { code: 6000, message: 'App closed' }
})
})
}
}
}, [])
Performance Metrics
Expected Performance:
- Initialization: < 2 seconds
- Pairing: < 3 seconds
- Session Approval: < 1 second
- Request Handling: < 500ms (excluding user interaction)
- Bundle Size: ~150KB (gzipped)
- Memory Usage: ~10-20MB
Monitoring:
// Track initialization time
const startTime = performance.now()
const walletKit = await WalletKit.init({ /* ... */ })
const initTime = performance.now() - startTime
console.log(`WalletConnect initialized in ${initTime}ms`)
// Track memory usage
if (performance.memory) {
console.log('Memory usage:', {
used: (performance.memory.usedJSHeapSize / 1048576).toFixed(2) + ' MB',
total: (performance.memory.totalJSHeapSize / 1048576).toFixed(2) + ' MB'
})
}
Production File Structure
Recommended file organization for a production WalletConnect implementation:
src/
├── contexts/
│ └── WalletConnectContext.tsx # Main context provider
├── hooks/
│ ├── useWalletConnect.ts # Context hook
│ └── useWalletConnectRequest.ts # Request handling hook
├── components/
│ ├── WalletConnect/
│ │ ├── SessionProposalModal.tsx # Session approval UI
│ │ ├── SessionRequestModal.tsx # Request approval UI
│ │ ├── ActiveSessionsList.tsx # Connected dApps list
│ │ ├── PairingInput.tsx # URI input component
│ │ └── TransactionPreview.tsx # Transaction details display
├── utils/
│ ├── walletconnect.ts # Helper functions
│ ├── formatters.ts # Data formatting utilities
│ └── validators.ts # Validation functions
├── types/
│ └── walletconnect.ts # TypeScript type definitions
└── constants/
└── walletconnect.ts # Constants and config
Example File Contents:
src/types/walletconnect.ts:
import type { SessionTypes, SignClientTypes } from '@walletconnect/types'
export type SessionProposal = SignClientTypes.EventArguments['session_proposal']
export type SessionRequest = SignClientTypes.EventArguments['session_request']
export type ActiveSession = SessionTypes.Struct
export interface WalletConnectContextValue {
web3wallet: Web3Wallet | null
isInitialized: boolean
sessions: ActiveSession[]
pendingProposal: SessionProposal | null
pendingRequest: SessionRequest | null
pair: (uri: string) => Promise<void>
approveSession: (proposal: SessionProposal, address: string, chainId: number) => Promise<void>
rejectSession: (proposal: SessionProposal) => Promise<void>
disconnectSession: (topic: string) => Promise<void>
}
src/constants/walletconnect.ts:
export const WALLETCONNECT_PROJECT_ID = import.meta.env.VITE_WALLETCONNECT_PROJECT_ID
export const SUPPORTED_CHAINS = [1, 137, 11155111] // Mainnet, Polygon, Sepolia
export const SUPPORTED_METHODS = [
'eth_sendTransaction',
'eth_signTransaction',
'personal_sign',
'eth_signTypedData_v4',
]
export const SUPPORTED_EVENTS = ['chainChanged', 'accountsChanged']
export const ERROR_CODES = {
USER_REJECTED: 5000,
UNAUTHORIZED_METHOD: 5001,
UNAUTHORIZED_CHAIN: 5002,
USER_DISCONNECTED: 6000,
} as const
FAQ
General Questions
Q: What's the difference between WalletConnect v1 and v2?
A: WalletConnect v2 is a complete rewrite with:
- Multi-chain support out of the box
- Better performance and reliability
- Improved security
- Session persistence
- Support for multiple simultaneous sessions
- v1 is deprecated and no longer supported
Q: Do I need a project ID?
A: Yes, you must obtain a free project ID from WalletConnect Cloud (now Reown Cloud). This is required for the relay server connection.
Q: Can I use WalletConnect without a backend?
A: Yes, WalletConnect is entirely client-side. The relay server (provided by WalletConnect/Reown) handles message passing between wallet and dApp.
Q: Is WalletConnect free?
A: Yes, WalletConnect is free for most use cases. There are usage limits on the free tier, but they're generous for most applications.
Technical Questions
Q: Why use web3wallet.core.pairing.pair() instead of web3wallet.pair()?
A: Using core.pairing.pair() provides better event handling and is the recommended approach. The web3wallet.pair() method may not trigger events reliably in all cases.
Q: How long do sessions last?
A: Sessions typically last 7 days by default. They can be extended before expiration using web3wallet.extendSession().
Q: Can I have multiple active sessions?
A: Yes, WalletConnect v2 supports multiple simultaneous sessions with different dApps.
Q: How do I handle session expiration?
A: Monitor session expiry timestamps and either:
- Extend sessions before they expire using
extendSession() - Handle
session_deleteevents and prompt users to reconnect
Q: What happens if the user closes the app during a request?
A: The request will timeout on the dApp side. Implement proper cleanup in your app's unmount/close handlers to send rejection responses for pending requests.
Q: Can I customize the supported methods per session?
A: Yes, you define supported methods when approving a session. Only include methods your wallet actually supports.
Q: How do I handle gas estimation for transactions?
A: Estimate gas before showing the transaction to the user:
const estimatedGas = await provider.estimateGas(transaction)
const gasPrice = await provider.getGasPrice()
const totalCost = estimatedGas.mul(gasPrice).add(transaction.value || 0)
Smart Contract Wallet Questions
Q: Does WalletConnect work with smart contract wallets (ERC-4337)?
A: Yes, but you need to:
- Build UserOperations from transaction requests
- Sign with the wallet owner key
- Submit to a bundler
- Return the transaction hash to the dApp
Q: How do I implement EIP-1271 signature validation?
A: For smart contract wallets, implement the isValidSignature function on-chain and return signatures that your contract can verify.
Q: Can I use passkeys with WalletConnect?
A: Yes, passkeys (WebAuthn) can be used as the signing mechanism for smart contract wallets. See the ERC-4337 section for implementation details.
Debugging Questions
Q: Why isn't my session proposal showing up?
A: Common causes:
- Event listeners not set up before pairing
- Using
web3wallet.pair()instead ofweb3wallet.core.pairing.pair() - Invalid or expired URI
- Network connectivity issues
Q: How do I debug WebSocket connection issues?
A: Enable debug logging and monitor relay events:
localStorage.setItem('debug', '@walletconnect/*')
web3wallet.core.relayer.on('relayer_connect', () => console.log('Connected'))
web3wallet.core.relayer.on('relayer_disconnect', () => console.log('Disconnected'))
web3wallet.core.relayer.on('relayer_error', (error) => console.error(error))
Q: Where is session data stored?
A: Sessions are stored in IndexedDB under wc@2:* databases. You can inspect this in browser DevTools > Application > IndexedDB.
Migration Questions
Q: Should I migrate to Reown packages now?
A: Yes, it's recommended to migrate before February 2025 when WalletConnect packages reach end-of-life.
Q: Will my existing sessions break after migration?
A: No, existing sessions will continue to work. The Reown packages maintain protocol compatibility.
Q: Do dApps need to update when I migrate?
A: No, the migration is wallet-side only. dApps don't need any changes.
Q: Can I use both old and new packages during migration?
A: Not recommended. Choose one and stick with it to avoid conflicts.
API Reference
Core Methods
Web3Wallet.init(options)
Initialize the Web3Wallet instance.
Parameters:
core: Core instancemetadata: Wallet metadata objectname: stringdescription: stringurl: stringicons: string[]
Returns: Promise<Web3Wallet>
web3wallet.core.pairing.pair({ uri })
Pair with a dApp using WalletConnect URI.
Parameters:
uri: WalletConnect URI string (starts withwc:)
Returns: Promise<void>
web3wallet.approveSession({ id, namespaces })
Approve a session proposal.
Parameters:
id: Proposal ID (number)namespaces: Namespace configuration object
Returns: Promise<SessionTypes.Struct>
web3wallet.rejectSession({ id, reason })
Reject a session proposal.
Parameters:
id: Proposal ID (number)reason: Rejection reason objectcode: numbermessage: string
Returns: Promise<void>
web3wallet.respondSessionRequest({ topic, response })
Respond to a session request.
Parameters:
topic: Session topic (string)response: JSON-RPC response objectid: numberjsonrpc: '2.0'result?: anyerror?: { code: number, message: string }
Returns: Promise<void>
web3wallet.disconnectSession({ topic, reason })
Disconnect a session.
Parameters:
topic: Session topic (string)reason: Disconnection reason objectcode: numbermessage: string
Returns: Promise<void>
web3wallet.getActiveSessions()
Get all active sessions.
Returns: Record<string, SessionTypes.Struct>
web3wallet.updateSession({ topic, namespaces })
Update session namespaces (chains/accounts).
Parameters:
topic: Session topic (string)namespaces: Updated namespace configuration
Returns: Promise<void>
web3wallet.emitSessionEvent({ topic, event, chainId })
Emit an event to the dApp.
Parameters:
topic: Session topic (string)event: Event objectname: string (e.g., 'chainChanged')data: any
chainId: Chain ID in CAIP-2 format (e.g., 'eip155:1')
Returns: Promise<void>
Events
session_proposal: Fired when dApp requests connectionsession_request: Fired when dApp requests transaction/signaturesession_delete: Fired when session is terminatedsession_update: Fired when session is updated
Source
git clone https://github.com/hadv/claude-skill-set/blob/main/walletconnect/SKILL.mdView on GitHub Overview
Expert knowledge for implementing WalletConnect v2 (Sign protocol) in web apps, enabling wallets to connect to dApps. Covers the complete lifecycle from initialization through session management and request handling, with migration guidance to Reown packages.
How This Skill Works
The integration uses Web3Wallet on the wallet side and Core as the protocol layer to manage pairing, sessions, and Sign protocol requests. Start by initializing Core with your projectId, then create and configure Web3Wallet with wallet metadata, listen for session_proposal events, approve sessions with correctly defined namespaces, and finally pair with the dApp URI.
When to Use It
- You need a wallet to connect to dApps using WalletConnect v2 or the Sign protocol
- You want to implement session proposal, user approval, and request handling flows
- You are migrating from legacy WalletConnect v2 packages to Reown packages
- You require multi chain support via namespaces across Ethereum mainnet and testnets
- You want robust session lifecycle management including expiration and reconnection
Quick Start
- Step 1: Install packages (legacy or Reown) like npm install @walletconnect/web3wallet @walletconnect/core @walletconnect/utils or npm install @reown/walletkit
- Step 2: Initialize Core with your projectId and create Web3Wallet with metadata (name, description, url, icons)
- Step 3: Listen for session_proposal, approve with proper namespaces, and pair with the dApp URI
Best Practices
- Define precise namespaces with correct chains, methods, and events (eg eip155 with eth methods and chain events)
- Keep Core projectId and metadata consistent and secure, and support both legacy and Reown packages during transition
- Implement clear session proposal UI and user flow to approve or reject via approved namespaces
- Handle session lifecycle events, including expiration, reconnection, and disconnections gracefully
- Test thoroughly across networks and package versions using the migration guidance to Reown
Example Use Cases
- A wallet app initializes Core and Web3Wallet, handles session_proposal events, and approves sessions with Ethereum mainnet namespaces
- A dApp pairs with a wallet using a wc URI and walletkit integration to establish a session for signing
- A project migrates from WalletConnect v2 packages to Reown w/ @reown/walletkit following the migration guide
- A multi-chain wallet config uses eip155 namespaces to support Ethereum mainnet and testnets with correct accounts
- An app implements session expiration handling and reconnection to maintain a seamless user experience