watchState — evmstate
Skip to content

watchState

Monitors state changes for a specific Ethereum address by watching new blocks and tracing transactions. It provides notifications when the address's state is accessed or modified.

Signature

import type { Address } from "tevm";
import type { WatchStateOptions, StateChange, SolcStorageLayout, DeepReadonly } from "@polareth/evmstate";
 
// @ts-expect-error - Function implementation is missing
function watchState<TStorageLayout extends DeepReadonly<SolcStorageLayout> | undefined = undefined>(
  options: WatchStateOptions<TStorageLayout>,
): Promise<() => void>;

Parameters

The function accepts a single options object with these properties:

PropertyTypeDescription
addressAddressThe Ethereum address to monitor
onStateChange(stateChange: StateChange<TStorageLayout>) => voidCallback for state changes
onError(error: Error) => void | undefinedOptional error handling callback
pollingIntervalnumber | undefinedOptional polling interval in ms (default: 1000)
storageLayoutTStorageLayout | undefinedOptional contract storage layout
abiAbi | undefinedOptional contract ABI
rpcUrlstring | undefinedEthereum RPC endpoint URL
clientMemoryClient | undefinedOptional Tevm client instance
explorersRecord<string, Explorer> | undefinedOptional contract explorers configuration

Return value

Returns a promise that resolves to an unsubscribe function:

type Unsubscribe = () => void;

Call this function to stop watching for state changes.

Type parameters

import type { SolcStorageLayout, DeepReadonly } from "@polareth/evmstate";
 
type TStorageLayout = /* extends */ DeepReadonly<SolcStorageLayout> | undefined;

By providing a storage layout with the as const assertion, TypeScript will infer precise types for all state change properties, enhancing the developer experience with autocompletion and type checking.

Examples

Basic usage

import { watchState } from "@polareth/evmstate";
 
// Start watching an address
const unsubscribe = await watchState({
  rpcUrl: "https://1.rpc.thirdweb.com",
  address: "0xContractAddress",
  onStateChange: (stateChange) => {
    console.log("Transaction:", stateChange.txHash);
 
    // Check balance changes
    if (stateChange.balance?.modified) {
      console.log("Balance changed:", {
        from: stateChange.balance.current,
        to: stateChange.balance.next,
      });
    }
 
    // Print storage changes
    if (stateChange.storage) {
      console.log("Storage accessed:", Object.keys(stateChange.storage));
    }
  },
  onError: (error) => {
    console.error("Watch error:", error);
  },
});
 
// Later, stop watching
unsubscribe();

With storage layout and ABI

example.ts
import { watchState } from "@polareth/evmstate";
 
// Watch with enhanced typing
const unsubscribe = await watchState({
  rpcUrl: "https://1.rpc.thirdweb.com",
  address: "0xTokenAddress",
  storageLayout: erc20Layout, // Type assertion with as const
  abi: erc20Abi,
  onStateChange: (stateChange) => {
    // TypeScript knows the exact structure of stateChange.storage
 
    // Access totalSupply (if changed)
    if (stateChange.storage?.totalSupply) {
      const totalSupply = stateChange.storage.totalSupply.trace[0];
      if (totalSupply.modified) {
        console.log("Total supply changed:", {
          from: totalSupply.current?.decoded,
          to: totalSupply.next?.decoded,
        });
      }
    }
 
    // Access balances mapping
    if (stateChange.storage?.balances) {
      const balances = stateChange.storage.balances.trace;
      balances.forEach((entry) => {
        if (entry.modified) {
          // TypeScript knows this is an address key
          const address = entry.path[0].key;
          console.log(`Balance changed for ${address}:`, {
            from: entry.current?.decoded,
            to: entry.next?.decoded,
          });
        }
      });
    }
  },
});

With custom Tevm client

import { createMemoryClient, http } from "tevm";
import { mainnet } from "tevm/common";
import { watchState } from "@polareth/evmstate";
 
// Create custom client
const client = createMemoryClient({
  common: mainnet,
  fork: {
    transport: http("https://1.rpc.thirdweb.com"),
    blockTag: "latest",
  },
});
 
// Watch with custom client
const unsubscribe = await watchState({
  client,
  address: "0xContractAddress",
  onStateChange: (stateChange) => {
    console.log("State changed:", stateChange);
  },
});