butr
Integrations

viem

Narrow butr's signer to its EIP-1193 provider and hand it to viem's createWalletClient.

butr owns wallet discovery and connection state. viem owns chain abstraction and RPC. The bridge is the signer: on an injected EVM wallet, getSigner() resolves { kind: "eip1193", provider }, and provider is exactly what viem's custom() transport takes.

Wire the wallet client

useSigner resolves the signer and re-resolves it when the wallet changes, so the client is a memo over it. Narrowing on kind types the provider; a Ledger or another non-EIP-1193 wallet yields a different kind and no client.

import { useSelectedWallet, useSigner } from "@usebutr/react";
import { useMemo } from "react";
import { createWalletClient, custom, getAddress } from "viem";
import { sepolia } from "viem/chains";

export const useWalletClient = () => {
  const wallet = useSelectedWallet("evm");
  const signer = useSigner(wallet);

  return useMemo(() => {
    if (wallet === undefined || signer.data?.kind !== "eip1193") {
      return null;
    }
    return createWalletClient({
      account: getAddress(wallet.account.walletAddress),
      chain: sepolia,
      transport: custom(signer.data.provider),
    });
  }, [signer.data, wallet]);
};

Outside React, narrow the same way after await wallet.connector.getSigner().

Read, sign, send

Reads go through viem's own public client, over your RPC rather than the wallet's:

import { createPublicClient, http } from "viem";

const publicClient = createPublicClient({ chain: sepolia, transport: http() });
const wei = await publicClient.getBalance({ address: walletClient.account.address });

Signing and sending go through the wallet client, so the real wallet prompts:

const signature = await walletClient.signMessage({ message: "Hello from butr + viem" });
const hash = await walletClient.sendTransaction({ to: BURN_ADDRESS, value: 0n });

viem compares the wallet's current chain with the client's chain and throws on a mismatch. Move the wallet first with butr:

import { EVM_CHAINS } from "@usebutr/core";

await wallet.connector.switchChain?.(EVM_CHAINS.sepolia);

For a plain transfer you do not need viem at all: wallet.connector.sendTx({ to, value: 0n }, { chain: EVM_CHAINS.sepolia }) switches the wallet and sends in one call.

The split is the whole point: butr handles discovery and connection; viem handles chain reads, signing, and transaction submission against the provider butr exposes.

Source: apps/demo-with-viem/src/app.tsx in the butr repository. Targets Sepolia. Run pnpm dev --filter=demo-with-viem → https://usebutr.demo-with-viem.localhost.