Basic usage
This guide covers the fundamental ways to use the library to analyze and label state access patterns.
Core functions
The library provides three main entry points:
traceState
: Analyze state changes from a single transactionTracer
: Create a reusable instance for multiple traceswatchState
: Monitor ongoing state changes for a specific address
Tracing a transaction
There are three ways to trace a transaction:
- Using transaction calldata
- Using a contract ABI
- Using a transaction hash
calldata.ts
import { traceState } from "@polareth/evmstate";
const trace = await traceState({
client,
from: "0x111...",
to: "0x222...",
data: "0xabcd...",
value: 0n,
});
Tracing options
There are various ways to configure the client:
- Using an existing Tevm client (likely in forking mode)
- Using a RPC url (which will internally create a Tevm memory client)
See the Tevm documentation for how to create and configure the client.
tevm-client.ts
import { createMemoryClient, http } from "tevm";
import { mainnet } from "tevm/common";
import { traceState } from "@polareth/evmstate";
const client = createMemoryClient({
common: mainnet,
fork: {
transport: http("https://1.rpc.thirdweb.com"),
blockTag: "latest",
},
});
const trace = await traceState({
client,
from: "0x111...",
to: "0x222...",
data: "0xabcd...",
});
Finally, you will need to provide an url & API key for at least one supported explorer, so it can fetch ABIs and storage layouts:
explorers.ts
import { traceState } from "@polareth/evmstate";
const trace = await traceState({
client,
from: "0x111...",
to: "0x222...",
data: "0xabcd...",
explorers: {
etherscan: {
baseUrl: "https://etherscan.io",
apiKey: "your-api-key",
},
blockscout: {
baseUrl: "https://blockscout.com",
apiKey: "your-api-key",
},
},
});
Watching state changes
To monitor an address for state changes in real-time:
example.ts
import { watchState } from "@polareth/evmstate";
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();