📈 GH Trending Explorer 🎲 Surprise me

← all repos

leekt / skim

git clone https://github.com/leekt/skim.git

Application-specific Ethereum state client: sync (and archive) only the state your app needs, straight from P2P. BAL-driven, verified, local eth_call in ms.

—
stars, 24h
+71
stars, 7 days · #708
+71
stars, 30 days · #3,194
71
★ total · +71% this week
3
forks · 0 open issues

Star history starts 2026-10-20; the chart appears after the second daily snapshot.

LanguageRust
LicenseApache-2.0
Created2026-10-15 (5d ago)
Last pushtoday
Watchers0
OwnerUser

GitHub topics

Links

README excerpt
Not every app needs a full node. Sync (and archive) only what you need. 
 Real recording, real Sepolia: skim follows the chain from P2P gossip, and unmodified viem gets Uniswap V3/V4 state, quotes and logs from it. ( MP4 · how it was recorded ) 
**skim** is an application-specific Ethereum state client. You declare the state your app cares about: accounts, storage slots, or whole simulations. skim keeps exactly that state current, straight from the Ethereum P2P network, and runs your `eth_call`s locally against it in milliseconds. Anything else is fetched on demand, with Merkle proofs verified, only when a request needs it.
skim never holds Ethereum's full state. It holds your app's working set and keeps it current block by block from Glamsterdam's block-level access lists (BALs): the values each block wrote, committed in its header. Anything else is fetched when a request needs it, with Merkle proofs. No full node, no RPC provider.
`skim run` defaults to Sepolia; Hoodi is built in too (`--network hoodi`, from its Glamsterdam fork on 2026-10-26), and custom Glamsterdam networks work with `--network `, using the `mainnet` or `minimal` consensus preset. See network configuration and the verified devnet example. The bundled app examples use Sepolia contracts.
 [code] 
---
## Why this is possible now
Until Glamsterdam, knowing the latest state meant executing every block, or asking a node that did. EIP-7928 changes that. Every block now commits to a **block access list**: every account and slot it touched, with the **post-values** it wrote. With ePBS, that list travels in the execution payload gossiped on the consensus layer.
Applying a block's BAL updates state without re-executing the block. skim applies it only to the keys it holds, so its cost follows your app's state, not the chain's.
skim builds on that:
| | How skim gets it |
|---|---|
| **New blocks + their state diffs** | CL `execution_payload` gossip. The EL header is rebuilt from the payload, so the BAL is bound to the block hash. eth/71 BAL requests fill gaps. |
| **State it has never seen** | snap/1 + snap/2 point lookups at the parent root, **verified against the state root with Merkle proofs**, then brought to the head by replaying the BAL |
| **Execution** | revm with Amsterdam rules over a per-key MVCC store |
## State age, history, and backfill limits
**State written before Glamsterdam can still be fetched at today's head, even if it has never changed since.** Its current value remains in the state trie. skim fetches it through SNAP, verifies its Merkle proof against a recent state root, and applies any later BAL writes to reach the head. The key does **not** need to appear in a recent BAL.
The age of a value and the age of the block you want to query are different:
| What you want | Can skim do it? | Why |
|---|---|---|
| Read today's value of an old account or slot, including one last changed before Glamsterdam | **Yes**, when a SNAP peer can serve the required state | SNAP suppli