Skip to content

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/web3

Configuration

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

  1. Visit WalletConnect Cloud
  2. Create new project
  3. Copy Project ID
  4. Add to .env.local:
bash
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=your_project_id_here

Basic 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

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()
typescript
const { generateDeepLink } = useWalletConnect()

const deepLink = generateDeepLink({
  wallet: 'trust', // or 'metamask', 'rainbow', etc.
  uri: walletConnectURI
})

// Open in wallet
window.location.href = deepLink
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

CodeMeaningSolution
USER_REJECTEDUser cancelledLet user retry
SESSION_EXPIREDSession timed outReconnect
NETWORK_ERRORConnection issueCheck internet
CHAIN_NOT_SUPPORTEDWrong networkAdd chain support
WALLET_NOT_READYWallet loadingWait 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>
  )
}
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

FeatureMetaMaskWalletConnect
PlatformBrowser extensionMobile-first
DevicesDesktop primarilyMobile + Desktop
ConnectionInstantQR/Deep link
WalletsMetaMask only200+ wallets
SetupExtension installApp install
UXPopupQR/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])

Next Steps

Built with Civra - Web3 Development Platform