Skip to content

MetaMask Integration

Connect your Civra application to MetaMask for Web3 functionality.

Overview

MetaMask is the most popular Ethereum wallet, allowing users to interact with your dApp directly from their browser.

Quick Start

Civra provides built-in MetaMask integration:

typescript
import { WalletButton } from '@civra/web3'

export function App() {
  return <WalletButton provider="metamask" />
}

Installation

MetaMask integration comes pre-configured in Civra projects. For custom setup:

bash
npm install @civra/web3

Basic Usage

Connect Wallet

typescript
import { useWallet } from '@civra/web3'

export function ConnectButton() {
  const { connect, isConnecting } = useWallet()

  return (
    <button onClick={() => connect('metamask')} disabled={isConnecting}>
      {isConnecting ? 'Connecting...' : 'Connect MetaMask'}
    </button>
  )
}

Get Wallet Info

typescript
import { useWallet } from '@civra/web3'

export function WalletInfo() {
  const { address, isConnected, balance } = useWallet()

  if (!isConnected) return <div>Not connected</div>

  return (
    <div>
      <p>Address: {address}</p>
      <p>Balance: {balance} ETH</p>
    </div>
  )
}

Disconnect

typescript
const { disconnect } = useWallet()

<button onClick={disconnect}>Disconnect</button>

Advanced Features

Check if MetaMask is Installed

typescript
import { checkMetaMaskInstalled } from '@civra/web3'

export function MetaMaskCheck() {
  const isInstalled = checkMetaMaskInstalled()

  if (!isInstalled) {
    return (
      <div>
        <p>MetaMask not detected</p>
        <a href="https://metamask.io" target="_blank">
          Install MetaMask
        </a>
      </div>
    )
  }

  return <WalletButton provider="metamask" />
}

Switch Network

typescript
import { useSwitchNetwork } from '@civra/web3'

export function NetworkSwitcher() {
  const { switchNetwork } = useSwitchNetwork()

  return (
    <div>
      <button onClick={() => switchNetwork(1)}>
        Ethereum Mainnet
      </button>
      <button onClick={() => switchNetwork(137)}>
        Polygon
      </button>
      <button onClick={() => switchNetwork(10)}>
        Optimism
      </button>
    </div>
  )
}

Add Network

typescript
import { addNetwork } from '@civra/web3'

const addPolygon = async () => {
  await addNetwork({
    chainId: 137,
    chainName: 'Polygon Mainnet',
    nativeCurrency: {
      name: 'MATIC',
      symbol: 'MATIC',
      decimals: 18
    },
    rpcUrls: ['https://polygon-rpc.com'],
    blockExplorerUrls: ['https://polygonscan.com']
  })
}

Add Token to MetaMask

typescript
import { addToken } from '@civra/web3'

const addCustomToken = async () => {
  await addToken({
    address: '0x...',
    symbol: 'MTK',
    decimals: 18,
    image: 'https://...'
  })
}

Signing Messages

Sign Message

typescript
import { useSignMessage } from '@civra/web3'

export function SignMessage() {
  const { signMessage, signature } = useSignMessage()

  const handleSign = async () => {
    const sig = await signMessage('Hello from Civra!')
    console.log('Signature:', sig)
  }

  return <button onClick={handleSign}>Sign Message</button>
}

Verify Signature

typescript
import { verifyMessage } from '@civra/web3'

const isValid = verifyMessage({
  message: 'Hello from Civra!',
  signature: '0x...',
  address: '0x...'
})

Transactions

Send Transaction

typescript
import { useSendTransaction } from '@civra/web3'

export function SendETH() {
  const { sendTransaction } = useSendTransaction()

  const handleSend = async () => {
    const tx = await sendTransaction({
      to: '0x...',
      value: parseEther('0.1')
    })
    await tx.wait()
  }

  return <button onClick={handleSend}>Send 0.1 ETH</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()

Events

Listen for Account Changes

typescript
import { useEffect } from 'react'
import { onAccountsChanged } from '@civra/web3'

useEffect(() => {
  const unsubscribe = onAccountsChanged((accounts) => {
    console.log('Account changed:', accounts[0])
  })

  return unsubscribe
}, [])

Listen for Network Changes

typescript
import { onChainChanged } from '@civra/web3'

useEffect(() => {
  const unsubscribe = onChainChanged((chainId) => {
    console.log('Network changed:', chainId)
  })

  return unsubscribe
}, [])

Error Handling

typescript
import { useWallet } from '@civra/web3'

export function SafeConnect() {
  const { connect } = useWallet()

  const handleConnect = async () => {
    try {
      await connect('metamask')
    } catch (error) {
      if (error.code === 4001) {
        // User rejected
        console.log('Connection rejected')
      } else if (error.code === -32002) {
        // Request pending
        console.log('Please check MetaMask')
      } else {
        console.error('Connection error:', error)
      }
    }
  }

  return <button onClick={handleConnect}>Connect</button>
}

Common Error Codes

  • 4001: User rejected the request
  • 4100: Unauthorized
  • 4200: Unsupported method
  • 4900: Disconnected
  • -32002: Request pending
  • -32603: Internal error

Best Practices

  1. Check Installation: Always verify MetaMask is installed
  2. Handle Errors: Provide clear error messages
  3. Show Status: Display connection and transaction states
  4. Network Validation: Ensure users are on correct network
  5. Mobile Support: Add WalletConnect for mobile users

Mobile Considerations

For mobile users without MetaMask:

typescript
import { isMobile } from '@civra/web3'

export function WalletConnect() {
  if (isMobile() && !checkMetaMaskInstalled()) {
    return <WalletConnectButton />
  }

  return <MetaMaskButton />
}

Testing

Use MetaMask's test networks:

  • Sepolia (Ethereum testnet)
  • Mumbai (Polygon testnet)
  • Goerli (deprecated, use Sepolia)

Get testnet ETH from faucets:

Security

Never Request Private Keys

typescript
// ❌ NEVER DO THIS
const privateKey = prompt('Enter private key')

// ✅ Always use wallet signing
const signature = await signMessage(message)

Validate Inputs

typescript
import { isAddress } from '@civra/web3'

if (!isAddress(recipientAddress)) {
  throw new Error('Invalid address')
}

Resources

Support

Having issues?

  • Check MetaMask is unlocked
  • Verify correct network
  • Clear browser cache
  • Try incognito mode
  • Contact Support

Built with Civra - Web3 Development Platform