Trezo
GitHub

@trezo/evm - EVM

Type-safe toolkit for interacting with EVM smart contracts and wallets.

Quick Overview

@trezo/evm gives you a type-safe layer for reading and writing to EVM smart contracts, connecting wallets, and reacting to on-chain events

Installation

Run the below command with your desired package manager

pnpm add @trezo/evm

Define your config

Set this up once at the module level.

config/evm.config.ts
import { create, Chains } from "@trezo/evm";
 
export const contractAbi = [...] as const; // must be as const
 
export const evmConfig = create({
  address: "0x...",
  abi: contractAbi,
  chains: [Chains.optimismSepolia],
  rpcUrls: {
    "sepolia": "https://optimism-sepolia-public.nodies.app",
  },
  wallet: {
    from: "family", // "family" | "reown"
    options: {
      projectId: "your_wallet_connect_project_id",
      appInfo: {
        name: "My App",
      },
    },
  },
});
 
export const { useConfig } = evmConfig;

Key fields:

  • address — deployed contract address
  • abi — contract ABI typed as const. Without as const you lose all type safety.
  • chains — supported chains. Chains re-exports all chains from wagmi/chains.
  • rpcUrls — custom RPC endpoints keyed by chain ID. Avoids rate-limited public nodes.
  • wallet — wallet modal provider. TypeScript autocompletes all supported options.

Wrap your app

Pass the config as an argument in the EvmProvider

main.tsx
import { EvmProvider } from "@trezo/evm/react";
import { evmConfig } from "@/config/evm.config";
 
export default function Root({ children }) {
  return <EvmProvider config={evmConfig}>{children}</EvmProvider>;
}

Basic Usage

App.tsx
"use client"; // required for Next.js
 
import { ConnectButton } from "@trezo/evm/react";
import { useConfig } from "@/config/evm.config";
 
export default function Home() {
  const { wallet, call, web3Provider } = useConfig();
 
  async function fetchTask() {
    const { data } = await call.queryFn("getTask", [BigInt(1)]);
  }
 
  async function addTask() {
    const { data } = await call.mutateFn("addTask", ["Buy milk"]);
    console.log(data?.hash);
  }
 
  function listenForChanges() {
    return call.listenFn("TaskAdded", (_, __, content) => {
      console.log(content);
    });
  }
 
  return (
    <div>
      <ConnectButton>
        {({ isConnected, truncatedAddress, open, isConnecting }) => (
          <button onClick={() => open()} disabled={isConnecting}>
            {isConnected ? truncatedAddress : "Connect Wallet"}
          </button>
        )}
      </ConnectButton>
 
      {wallet.account.isConnected ? (
        <p>Connected: {wallet.account.address}</p>
      ) : (
        <p>Not connected</p>
      )}
    </div>
  );
}
  • wallet — connection state
  • call — contract helpers (queryFn, mutateFn, listenFn)
  • web3Provider — injected provider info

Call methods

Read from Contract

For view and pure functions — reads that don't change state and cost no gas.

Example Usage

const { data, error } = await call.queryFn("fetchAllTasks", []);
 
if (error) {
  console.error(error.message); // human-readable, safe to show users
  console.error(error.raw); // full Error object for debugging
} else {
  console.log(data); // typed from your ABI
}

Options:

  • from: override sender (defaults to connected address)
  • blockTag: latest, earliest, pending, or number
  • gasLimit
  • value

Write to Contract

For nonpayable and payable functions — writes that change on-chain state and require a wallet signature.

Example Usage

const { data, error } = await call.mutateFn("addTask", ["Buy groceries"]);
 
if (error) {
  toast.error(error.message); // e.g. "CONTRACT_ERROR_MESSAGE"
} else {
  console.log(data?.hash); // transaction hash
  console.log(data?.receipt); // full TransactionReceipt
}

Options:

  • estimateGas: true or false
  • gasLimit
  • value
  • maxFeePerGas
  • maxPriorityFeePerGas
  • nonce

Listen to Events

Subscribes to contract events and fires your listener each time. Automatically falls back to block-polling on RPCs that don't support eth_newFilter (most public/free-tier nodes).

Single event

const cleanup = call.listenFn("TaskAdded", (user, taskId, content) => {
  console.log("Task added:", { user, taskId, content });
});
 
cleanup(); // stop listening

Listener args are fully typed from your ABI.

Multiple events

const cleanup = call.listenFn({
  add: { eventName: "TaskAdded", listener: fetchAllTasks },
  update: { eventName: "TaskUpdated", listener: fetchAllTasks },
  remove: { eventName: "TaskRemoved", listener: fetchAllTasks },
  toggle: { eventName: "TaskToggled", listener: fetchAllTasks },
});
 
cleanup(); // stops all listeners at once

More efficient than multiple listenFn calls — all listeners share the same underlying connection.


ConnectButton

Supports a render prop for full UI control, or a default button when no children are passed. Import from @trezo/evm/react.

Default button

import { ConnectButton } from "@trezo/evm/react";
 
<ConnectButton label="Connect Wallet" />;

Custom render

import { ConnectButton } from "@trezo/evm/react";
 
<ConnectButton>
  {({ isConnected, truncatedAddress, ensName, open, isConnecting }) => (
    <button onClick={() => open()}>
      {isConnected ? (ensName ?? truncatedAddress) : "Connect"}
    </button>
  )}
</ConnectButton>;

Wallet State

const { wallet } = useConfig();
 
wallet.account.isConnected; // true when wallet is connected
wallet.account.isConnecting; // true during connection or page-reload reconnect
wallet.account.address; // `0x${string}` | undefined
wallet.account.chainId; // number | undefined

Use isConnecting to differentiate between "not connected" and "reconnecting on reload":

{
  wallet.account.isConnecting ? (
    <span>Reconnecting...</span>
  ) : wallet.account.isConnected ? (
    <span>{wallet.account.address}</span>
  ) : (
    <span>Not connected</span>
  );
}

Provider State

const { web3Provider } = useConfig();
 
web3Provider.isAvailable; // true if window.ethereum exists (MetaMask, etc.)

Use it to disable UI that requires an injected wallet:

<button disabled={!web3Provider.isAvailable}>Add Task</button>

Outside React (Vanilla JS)

evmConfig exposes static methods for use outside React components — useful for utility functions, server actions, or non-component files:

trezo.js
import { evmConfig } from "@/config/evm.config";
 
// Read current state snapshot
const state = evmConfig._store.getState();
console.log(state.wallet.address);
 
// Call contract functions
const result = await evmConfig._store.call.queryFn("fetchAllTasks", []);
 
// Subscribe to state changes
const unsubscribe = evmConfig._store.subscribe(() => {
  console.log("State changed:", evmConfig._store.getState());
});
unsubscribe();

Wallet Providers

KeyPackage requiredNotes
"family"connectkitConnectKit styled modal
"reown"@reown/appkit, @reown/appkit-adapter-wagmiAppKit (WalletConnect v2)

Family (ConnectKit)

wallet: {
  from: "family",
  options: {
    projectId: "your_walletconnect_project_id",
    appInfo: {
      name: "My App",
      description: "My dApp description",
      url: "https://myapp.xyz",
      icon: "https://myapp.xyz/icon.png",
    },
  },
},

Reown (AppKit)

wallet: {
  from: "reown",
  options: {
    projectId: "your_walletconnect_project_id",
    metadata: {
      name: "My App",
      description: "My dApp description",
      url: "https://myapp.xyz",
      icons: ["https://myapp.xyz/icon.png"],
    },
    ssr: false,           // set true for Next.js SSR
    features: {
      analytics: true,    // optional, defaults to Cloud config
    },
  },
},

Plugins Setup

Avoid duplicate dependencies across packages.

Vite Plugin

vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { trezoVitePlugin } from "@trezo/evm/plugins/vite";
 
export default defineConfig({
  plugins: [react(), trezoVitePlugin()],
});

Package Exports

Import pathContents
@trezo/evmcreate, Chains, all types
@trezo/evm/reactEvmProvider, ConnectButton, useConfig
@trezo/evm/plugins/vitetrezoVitePlugin

Summary

APIPurpose
create(config)Initialize the entire Web3 setup
EvmProviderApp wrapper (React) — required
ConnectButtonWallet connection UI with render props (React)
useConfig()React hook — access wallet, call, provider state
call.queryFnRead contract data (view/pure functions)
call.mutateFnWrite to contract (nonpayable/payable functions)
call.listenFnSingle or multiple event listeners
walletConnection state (address, chainId, isConnected)
web3ProviderInjected provider availability
evmConfig._store.getState()Static state access outside React
evmConfig._store.subscribe()Subscribe to state changes outside React
trezoVitePlugin()Vite plugin — prevents duplicate React/wagmi/viem