Nathaniel's blog
Back to posts

Integrating Wallets with ethers.js and wagmi

Nathaniel LinFebruary 18, 20269 min read3 views
Integrating Wallets with ethers.js and wagmi

Wallet integration is the front door of any web3 application. Users connect their wallet, sign transactions, and interact with smart contracts — all through your frontend. Two libraries dominate: ethers.js for direct provider interaction, and wagmi for React-native hooks. Let's explore both.

ethers.js: The Foundation

ethers.js provides a low-level interface to Ethereum:

import { BrowserProvider, Contract, formatEther } from "ethers";

async function connectWallet() {
  if (!window.ethereum) {
    throw new Error("No wallet detected");
  }

  const provider = new BrowserProvider(window.ethereum);
  const signer = await provider.getSigner();
  const address = await signer.getAddress();
  const balance = await provider.getBalance(address);

  console.log(`Connected: ${address}`);
  console.log(`Balance: ${formatEther(balance)} ETH`);

  return { provider, signer, address };
}

Reading from a contract:

const tokenContract = new Contract(TOKEN_ADDRESS, ERC20_ABI, provider);
const name = await tokenContract.name();
const balance = await tokenContract.balanceOf(address);

Writing to a contract (requires signer):

const tokenWithSigner = tokenContract.connect(signer);
const tx = await tokenWithSigner.transfer(recipient, parseEther("10"));
const receipt = await tx.wait();
console.log(`Transfer confirmed in block ${receipt.blockNumber}`);

wagmi: React Hooks for Ethereum

wagmi wraps ethers.js (and viem) in React hooks with built-in state management:

import { useAccount, useConnect, useBalance } from "wagmi";
import { injected } from "wagmi/connectors";

function ConnectButton() {
  const { address, isConnected } = useAccount();
  const { connect } = useConnect();
  const { data: balance } = useBalance({ address });

  if (isConnected) {
    return (
      <div>
        <p>{address}</p>
        <p>{balance?.formatted} {balance?.symbol}</p>
      </div>
    );
  }

  return (
    <button onClick={() => connect({ connector: injected() })}>
      Connect Wallet
    </button>
  );
}

Contract Reads with wagmi

import { useReadContract } from "wagmi";

function TokenBalance({ address }: { address: string }) {
  const { data, isLoading } = useReadContract({
    address: TOKEN_ADDRESS,
    abi: ERC20_ABI,
    functionName: "balanceOf",
    args: [address],
  });

  if (isLoading) return <span>Loading...</span>;
  return <span>{formatEther(data ?? 0n)} tokens</span>;
}

Contract Writes with wagmi

import { useWriteContract, useWaitForTransactionReceipt } from "wagmi";

function TransferButton() {
  const { writeContract, data: hash } = useWriteContract();
  const { isLoading: isConfirming } = useWaitForTransactionReceipt({ hash });

  return (
    <button
      disabled={isConfirming}
      onClick={() =>
        writeContract({
          address: TOKEN_ADDRESS,
          abi: ERC20_ABI,
          functionName: "transfer",
          args: [recipientAddress, parseEther("10")],
        })
      }
    >
      {isConfirming ? "Confirming..." : "Send 10 Tokens"}
    </button>
  );
}

Configuration

wagmi needs a config provider at the app root:

import { WagmiProvider, createConfig, http } from "wagmi";
import { mainnet, base, arbitrum } from "wagmi/chains";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const config = createConfig({
  chains: [mainnet, base, arbitrum],
  transports: {
    [mainnet.id]: http(),
    [base.id]: http(),
    [arbitrum.id]: http(),
  },
});

const queryClient = new QueryClient();

function App({ children }) {
  return (
    <WagmiProvider config={config}>
      <QueryClientProvider client={queryClient}>
        {children}
      </QueryClientProvider>
    </WagmiProvider>
  );
}

Error Handling

Wallet interactions fail often — user rejects transaction, insufficient gas, contract reverts. Always handle errors gracefully:

try {
  const tx = await writeContract(config);
} catch (error) {
  if (error.code === "ACTION_REJECTED") {
    toast.error("Transaction rejected by user");
  } else if (error.message.includes("insufficient funds")) {
    toast.error("Not enough ETH for gas");
  } else {
    toast.error("Transaction failed");
    console.error(error);
  }
}

Choosing Between Them

  • ethers.js: Use for scripts, backend services, or when you need full control

  • wagmi: Use for React frontends — it handles connection state, chain switching, reconnection, and caching automatically

  • viem: The modern alternative to ethers.js with better TypeScript types — wagmi uses it internally

For most React dApps, wagmi is the right choice. It abstracts the complexity of provider management and gives you a clean hook-based API that feels native to React.

Share this post

Reactions

Integrating Wallets with ethers.js and wagmi | Nathaniel's blog