@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
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");