Just Fucking Use Wagmi

You're building a web3 app. You need to connect wallets, read contracts, send transactions. You have options:

  1. Roll your own shit with window.ethereum
  2. Use ethers.js and write a bunch of boilerplate
  3. Just fucking use Wagmi

Let me show you why option 3 is the only sane choice.


Connecting a Wallet

The `window.ethereum` way (2017 called)

// Hope they have MetaMask,
// or a bunch of wallets fighting over `window.ethereum`!

if (typeof window.ethereum !== 'undefined') {
  try {
    const accounts = await window.ethereum.request({
      method: 'eth_requestAccounts'
    })
    const account = accounts[0]
    // Cool, now handle disconnects yourself
    // And account changes
    // And chain changes
    // And what if they don't have MetaMask?
    // What about WalletConnect? Coinbase Wallet?
    // Have fun writing 200 lines of event listeners
    window.ethereum.on('accountsChanged', handleAccountsChanged)
    window.ethereum.on('chainChanged', handleChainChanged)
    window.ethereum.on('disconnect', handleDisconnect)
  } catch (error) {
    // user rejected, probably
    // or maybe something else, who knows
  }
} else {
  alert('Install MetaMask bro')
}

Fucking brutal. And you haven't even done anything yet.

The ethers.js way (better, but still...)

import { BrowserProvider } from 'ethers'

// Still assuming MetaMask
const provider = new BrowserProvider(window.ethereum)
const signer = await provider.getSigner()
const address = await signer.getAddress()

// You still need to:
// - Handle multiple wallet types yourself
// - Manage connection state yourself
// - Set up all the event listeners yourself
// - Cache things yourself
// - Handle reconnection yourself
// - Build your own hooks if you're using React
// Cool library, wrong abstraction level for apps

The Wagmi way

import { useConnection, useConnect, useConnectors } from 'wagmi'

function App() {
  const { address, isConnected } = useConnection()
  const { connect } = useConnect()
  const connectors = useConnectors()

  if (isConnected) return <div>Connected: {address}</div>

  return (
    <div>
      {connectors.map((connector) => (
        <button key={connector.uid} onClick={() => connect({ connector })}>
          {connector.name}
        </button>
      ))}
    </div>
  )
}

// That's it. You're done.
// MetaMask, WalletConnect, Coinbase, Rainbow - all work.
// Connection state? Handled.
// Reconnection? Handled.
// Account changes? Handled.
// Chain changes? Handled.

Look at that. It's fucking beautiful.


Reading from a Contract

The painful way

// window.ethereum
const data = await window.ethereum.request({
  method: 'eth_call',
  params: [{
    to: contractAddress,
    data: '0x70a08231000000000000000000000000' + address.slice(2)
    // Hope you encoded that right!
    // Hope you know the function selector!
  }]
})
// Now decode the response yourself
const balance = BigInt(data)

The ethers.js way

import { Contract } from 'ethers'

const contract = new Contract(address, abi, provider)
const balance = await contract.balanceOf(userAddress)

// OK that's actually not bad
// But now you need to:
// - Refetch when the block changes? Write it yourself.
// - Cache results? Write it yourself.
// - Loading states? Write it yourself.
// - Error handling? Write it yourself.
// - Retry logic? Write it yourself.
// - React integration? Write it yourself.

The Wagmi way

import { useReadContract } from 'wagmi'

const { data: balance, isLoading, error } = useReadContract({
  address: contractAddress,
  abi: erc20Abi,
  functionName: 'balanceOf',
  args: [userAddress],
})

// Loading states? ✓
// Error handling? ✓
// Caching? ✓
// Auto-refetch on new blocks? Add `watch: true`
// TypeScript knows `balance` is bigint? ✓
// TypeScript validates your args? ✓

Writing to a Contract

The "I hate myself" way

// Raw window.ethereum
const txHash = await window.ethereum.request({
  method: 'eth_sendTransaction',
  params: [{
    from: account,
    to: contractAddress,
    data: encodeFunctionDataYourself(abi, 'transfer', [to, amount]),
    // Gas? Guess or call eth_estimateGas first
    // Nonce? Hope the provider handles it
  }]
})
// Now poll for the receipt yourself
// And handle replacements
// And handle stuck transactions
// And update your UI
// And...

The Wagmi way

import { useWriteContract, useWaitForTransactionReceipt } from 'wagmi'

function Transfer() {
  const { mutate, data: hash, isPending } = useWriteContract()

  const { isLoading: isConfirming, isSuccess } =
    useWaitForTransactionReceipt({ hash })

  return (
    <button
      disabled={isPending}
      onClick={() => mutate({
        address: contractAddress,
        abi: erc20Abi,
        functionName: 'transfer',
        args: [to, amount],
      })}
    >
      {isPending ? 'Confirming...' : 'Transfer'}
    </button>
  )
}

// Pending state? ✓
// Transaction confirmation? ✓
// Type-safe arguments? ✓
// Gas estimation? ✓
// Error handling? ✓

"But ethers.js is more flexible!"

Wagmi uses Viem under the hood. You can drop down to it any time:

import { usePublicClient, useWalletClient } from 'wagmi'

const publicClient = usePublicClient()   // Like ethers Provider
const walletClient = useWalletClient()   // Like ethers Signer

// Do whatever low-level shit you want
const block = await publicClient.getBlock()
const hash = await walletClient.sendTransaction({ ... })

All the flexibility. None of the boilerplate.


"But I'm not using React!"

Wagmi has you covered:


What you get with Wagmi


Get started

npm install wagmi viem @tanstack/react-query

Read the actual docs. They're good.


Stop writing wallet connection boilerplate. Stop managing blockchain state by hand. Stop reinventing caching and error handling.

Just fucking use Wagmi.

Inspired by motherfuckingwebsite.com and friends.
Made with rage and HTML.