@polareth/evmstate
Skip to content

@polareth/evmstate

A TypeScript library for tracing and visualizing EVM state changes with detailed human-readable labeling.

Overview

@polareth/evmstate traces all state changes after a transaction execution in a local VM, or by watching transactions in incoming blocks. It retrieves and labels storage slots with semantic insights and provides a detailed diff of all changes.

As an alternative to event logs for tracking state, it captures and labels every state change with precise semantic information:

  • Variable names
  • Mapping keys
  • Array indices
  • Decoded values
  • Path tracing from base slots to final values

Powered by Tevm and whatsabi.

Installation

npm install @polareth/evmstate
# or
pnpm add @polareth/evmstate
# or
yarn add @polareth/evmstate

Quick start

example.ts
import { traceState, watchState } from "@polareth/evmstate";
 
// Trace a transaction
const trace = await traceState({
  client,
  from: "0x111...",
  to: "0x222...",
  abi: abi,
  functionName: "setBalance",
  args: ["0x333...", 100n],
});
 
// Watch an account's state
const unsubscribe = await watchState({
  rpcUrl: "https://1.rpc.thirdweb.com", // this or a Tevm client
  address: "0xContractAddress",
  // both storageLayout and abi are optional,
  // but providing them will avoid having to fetch
  storageLayout: layout, // this will type the state changes
  abi: abi,
  onStateChange: (stateChange) => {
    const trace = stateChange.storage?.balances.trace[0];
    if (trace?.modified) {
      console.log(`previous balance: ${trace.current?.decoded}`);
      console.log(`new balance: ${trace.next?.decoded}`);
      console.log(`mapping key: ${trace.path[0].key}`);
      console.log(`full expression: ${trace.fullExpression}`);
    }
  },
  onError: (err) => console.log(err),
  // ... same explorer options as traceState
});
 
// ...
 
unsubscribe();

Key features

  • Complete state tracing: Track all storage slots accessed during transaction execution
  • Human-readable labeling: Label slots with variable names and provide access paths
  • Intelligent key detection: Extract mapping keys and array indices from transaction data
  • Type-aware decoding: Convert raw storage values to appropriate TypeScript types with automatic type inference

Example output

Map {
  "0xContractAddress" => {
    "balance": {
      "current": 0n,
      "modified": false,
    },
    "nonce": {
      "current": 0n,
      "modified": false,
    },
    ...
    "storage": {
      "counter": {
        "kind": "primitive",
        "name": "counter",
        "type": "uint256",
        "trace": [
          {
            "current": { "hex": "0x05", "decoded": 5n },
            "modified": true,
            "next": { "hex": "0x06", "decoded": 6n },
            "fullExpression": "counter",
            "slots": ["0x0000000000000000000000000000000000000000000000000000000000000000"]
          }
        ]
      }
    }
  },
  "0xCallerAddress" => {
    "balance": {
      "current": 1000000000000000000n,
      "modified": true,
      "next": 999999998747698854n,
    },
    "nonce": {
      "current": 0n,
      "modified": true,
      "next": 1n,
    },
    ...
  },
}

You can get the result for any address (case-insensitive):

const result = trace.get("0xContractAddress");

Documentation