WalletConnect Integration
Connect mobile wallets and support multi-wallet experiences in your Civra applications.
Overview
WalletConnect is an open protocol for connecting mobile wallets to dApps. It enables users on mobile devices to interact with your Web3 application securely.
Why WalletConnect?
Benefits:
- 📱 Mobile wallet support
- 🔐 Secure connections (end-to-end encrypted)
- 🌐 200+ wallet compatibility
- 🔄 Multi-chain support
- 📊 QR code & deep linking
Supported Wallets:
- MetaMask Mobile
- Trust Wallet
- Rainbow Wallet
- Argent
- Coinbase Wallet
- And 195+ more
Quick Start
Civra provides built-in WalletConnect support:
typescript
import { WalletConnectButton } from '@civra/web3'
export function App() {
return (
<div>
<WalletConnectButton />
</div>
)
}Setup
Installation
WalletConnect comes pre-installed in Civra projects. For custom setup:
bash
npm install @civra/web3Configuration
typescript
// lib/walletconnect.ts
import { WalletConnectProvider } from '@civra/web3'
export const walletConnect = new WalletConnectProvider({
projectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID!,
chains: [1, 137, 42161], // Ethereum, Polygon, Arbitrum
showQrModal: true
})Get Project ID
- Visit WalletConnect Cloud
- Create new project
- Copy Project ID
- Add to
.env.local:
bash
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=your_project_id_hereBasic Usage
Connect Wallet
typescript
import { useWalletConnect } from '@civra/web3'
export function ConnectButton() {
const { connect, disconnect, address, isConnected } = useWalletConnect()
if (isConnected) {
return (
<div>
<span>{address}</span>
<button onClick={disconnect}>Disconnect</button>
</div>
)
}
return <button onClick={connect}>Connect Wallet</button>
}Mobile-Optimized Connection
typescript
import { isMobile } from '@civra/web3'
export function SmartConnect() {
const { connect: connectWC } = useWalletConnect()
const { connect: connectMM } = useMetaMask()
const handleConnect = () => {
if (isMobile()) {
connectWC() // Show QR or deep link
} else {
connectMM() // Use browser extension
}
}
return <button onClick={handleConnect}>Connect</button>
}QR Code Modal
Default Modal
Built-in QR code modal for desktop users:
typescript
const { connect } = useWalletConnect({
showQrModal: true // Default
})
// Click triggers QR modal
await connect()Custom QR Modal
Build your own QR interface:
typescript
import { useWalletConnect, QRCodeSVG } from '@civra/web3'
export function CustomQRModal() {
const { uri, isLoading } = useWalletConnect({
showQrModal: false
})
if (isLoading) return <div>Generating QR...</div>
return (
<div className="modal">
<h2>Scan with Your Wallet</h2>
<QRCodeSVG value={uri} size={256} />
<p>Open your mobile wallet and scan</p>
</div>
)
}Deep Linking
Auto Deep Link (Mobile)
Automatically open wallet app on mobile:
typescript
import { useWalletConnect } from '@civra/web3'
const { connect } = useWalletConnect({
deepLink: {
enabled: true,
wallets: ['metamask', 'trust', 'rainbow']
}
})
// On mobile, opens wallet app directly
await connect()Manual Deep Link
typescript
const { generateDeepLink } = useWalletConnect()
const deepLink = generateDeepLink({
wallet: 'trust', // or 'metamask', 'rainbow', etc.
uri: walletConnectURI
})
// Open in wallet
window.location.href = deepLinkDeep Link Buttons
typescript
export function WalletButtons() {
const { connectWithWallet } = useWalletConnect()
return (
<div>
<button onClick={() => connectWithWallet('metamask')}>
MetaMask
</button>
<button onClick={() => connectWithWallet('trust')}>
Trust Wallet
</button>
<button onClick={() => connectWithWallet('rainbow')}>
Rainbow
</button>
</div>
)
}Multi-Chain Support
Configure Chains
typescript
import { mainnet, polygon, arbitrum, optimism } from '@civra/chains'
const { connect } = useWalletConnect({
chains: [mainnet, polygon, arbitrum, optimism],
defaultChain: polygon
})Switch Chains
typescript
import { useSwitchChain } from '@civra/web3'
export function ChainSwitcher() {
const { switchChain, currentChain } = useSwitchChain()
return (
<div>
<p>Current: {currentChain.name}</p>
<button onClick={() => switchChain(1)}>
Ethereum
</button>
<button onClick={() => switchChain(137)}>
Polygon
</button>
</div>
)
}Session Management
Session Persistence
WalletConnect sessions persist across browser refreshes:
typescript
import { useEffect } from 'react'
import { useWalletConnect } from '@civra/web3'
export function App() {
const { autoConnect } = useWalletConnect()
useEffect(() => {
// Auto-reconnect on page load
autoConnect()
}, [])
return <YourApp />
}Session Events
typescript
const { on, off } = useWalletConnect()
useEffect(() => {
// Listen to session events
const handleConnect = () => console.log('Connected')
const handleDisconnect = () => console.log('Disconnected')
const handleChainChanged = (chainId) => console.log('Chain:', chainId)
const handleAccountsChanged = (accounts) => console.log('Account:', accounts[0])
on('connect', handleConnect)
on('disconnect', handleDisconnect)
on('chainChanged', handleChainChanged)
on('accountsChanged', handleAccountsChanged)
return () => {
off('connect', handleConnect)
off('disconnect', handleDisconnect)
off('chainChanged', handleChainChanged)
off('accountsChanged', handleAccountsChanged)
}
}, [])Custom Session Timeout
typescript
const { connect } = useWalletConnect({
sessionTimeout: 7 * 24 * 60 * 60 * 1000 // 7 days
})Transactions
Send Transaction
typescript
import { useSendTransaction } from '@civra/web3'
export function SendButton() {
const { sendTransaction } = useSendTransaction()
const handleSend = async () => {
const tx = await sendTransaction({
to: '0x...',
value: parseEther('0.1')
})
console.log('Transaction hash:', tx.hash)
await tx.wait()
console.log('Confirmed!')
}
return <button onClick={handleSend}>Send 0.1 ETH</button>
}Sign Message
typescript
import { useSignMessage } from '@civra/web3'
export function SignButton() {
const { signMessage } = useSignMessage()
const handleSign = async () => {
const signature = await signMessage('Hello from Civra!')
console.log('Signature:', signature)
}
return <button onClick={handleSign}>Sign Message</button>
}Contract Interaction
typescript
import { useContract } from '@civra/web3'
const contract = useContract(CONTRACT_ADDRESS, ABI)
// Read
const balance = await contract.read.balanceOf(address)
// Write
const tx = await contract.write.transfer(to, amount)
await tx.wait()Advanced Features
Custom RPC URLs
typescript
const { connect } = useWalletConnect({
rpcMap: {
1: 'https://eth-mainnet.alchemyapi.io/v2/YOUR-KEY',
137: 'https://polygon-mainnet.g.alchemy.com/v2/YOUR-KEY'
}
})Network Metadata
typescript
const { connect } = useWalletConnect({
chains: [
{
chainId: 1,
name: 'Ethereum',
currency: 'ETH',
rpcUrl: 'https://...',
explorerUrl: 'https://etherscan.io'
}
]
})Custom Theme
typescript
const { connect } = useWalletConnect({
theme: {
primaryColor: '#FF6B6B',
background: '#1a1a1a',
fontFamily: 'Inter, sans-serif'
}
})Error Handling
Common Errors
typescript
import { useWalletConnect } from '@civra/web3'
export function SafeConnect() {
const { connect } = useWalletConnect()
const handleConnect = async () => {
try {
await connect()
} catch (error) {
if (error.code === 'USER_REJECTED') {
console.log('User rejected connection')
} else if (error.code === 'SESSION_EXPIRED') {
console.log('Session expired, please reconnect')
} else if (error.code === 'NETWORK_ERROR') {
console.log('Network error, please try again')
} else {
console.error('Connection failed:', error)
}
}
}
return <button onClick={handleConnect}>Connect</button>
}Error Codes
| Code | Meaning | Solution |
|---|---|---|
USER_REJECTED | User cancelled | Let user retry |
SESSION_EXPIRED | Session timed out | Reconnect |
NETWORK_ERROR | Connection issue | Check internet |
CHAIN_NOT_SUPPORTED | Wrong network | Add chain support |
WALLET_NOT_READY | Wallet loading | Wait and retry |
Mobile Optimization
Detect Mobile
typescript
import { isMobile, getMobileOS } from '@civra/web3'
if (isMobile()) {
const os = getMobileOS() // 'ios' | 'android'
if (os === 'ios') {
// iOS-specific logic
} else {
// Android-specific logic
}
}Responsive Connection
typescript
export function ResponsiveConnect() {
const { connect: wcConnect } = useWalletConnect()
const { connect: mmConnect } = useMetaMask()
if (isMobile()) {
return (
<div>
<button onClick={wcConnect}>
Connect Mobile Wallet
</button>
</div>
)
}
return (
<div>
<button onClick={mmConnect}>
Connect MetaMask
</button>
<button onClick={wcConnect}>
Show QR Code
</button>
</div>
)
}Deep Link Best Practices
typescript
const { connect } = useWalletConnect({
deepLink: {
// Auto-select based on installed apps
autoDetect: true,
// Fallback if no app detected
fallbackUrl: 'https://apps.apple.com/app/trust-wallet',
// Custom success redirect
redirect: '/dashboard'
}
})Testing
Mock WalletConnect
typescript
import { mockWalletConnect } from '@civra/web3/testing'
test('user can connect via WalletConnect', async () => {
const { connect } = mockWalletConnect({
address: '0x123...',
chainId: 1
})
await connect()
expect(getAddress()).toBe('0x123...')
})Test Networks
typescript
// Use testnets for development
const { connect } = useWalletConnect({
chains: [sepolia, mumbai], // Testnets
// chains: [mainnet, polygon] // Production
})Best Practices
1. Always Provide Fallbacks
typescript
// Good: Multiple options
<div>
<MetaMaskButton />
<WalletConnectButton />
<CoinbaseButton />
</div>
// Bad: Single option only
<div>
<WalletConnectButton />
</div>2. Handle Session Expiry
typescript
useEffect(() => {
const checkSession = setInterval(() => {
if (!isConnected && hasSession) {
autoConnect() // Try to reconnect
}
}, 5000)
return () => clearInterval(checkSession)
}, [])3. Show Connection Status
typescript
export function ConnectionStatus() {
const { isConnecting, isReconnecting, error } = useWalletConnect()
if (isConnecting) return <div>Connecting...</div>
if (isReconnecting) return <div>Reconnecting...</div>
if (error) return <div>Error: {error.message}</div>
return null
}4. Optimize for Mobile
- Use deep links for better UX
- Show QR codes on desktop only
- Detect and suggest installed wallets
- Provide app store links
5. Security
- Always verify signatures server-side
- Use HTTPS in production
- Implement session timeouts
- Validate chain IDs
Comparison: MetaMask vs WalletConnect
| Feature | MetaMask | WalletConnect |
|---|---|---|
| Platform | Browser extension | Mobile-first |
| Devices | Desktop primarily | Mobile + Desktop |
| Connection | Instant | QR/Deep link |
| Wallets | MetaMask only | 200+ wallets |
| Setup | Extension install | App install |
| UX | Popup | QR/App switch |
Use Both:
typescript
export function UniversalConnect() {
return (
<div>
{/* Desktop */}
{!isMobile() && <MetaMaskButton />}
{/* Mobile */}
{isMobile() && <WalletConnectButton />}
{/* Always available */}
<WalletConnectQRButton />
</div>
)
}Resources
Troubleshooting
QR code not showing?
typescript
// Enable QR modal explicitly
const { connect } = useWalletConnect({ showQrModal: true })Deep link not working?
typescript
// Check wallet app is installed
const isInstalled = await checkWalletInstalled('trust')
if (!isInstalled) {
// Redirect to app store
}Session expired?
typescript
// Implement auto-reconnect
useEffect(() => {
if (sessionExpired) {
disconnect()
connect()
}
}, [sessionExpired])