npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@balq/node

v0.4.0

Published

Local, verified archive of contract storage built from EIP-7928 BALs — Node.js bindings

Readme

@balq/node

Local, verified history of a contract's storage — read by variable name, at any block, from an ordinary full node. Node.js bindings for balq (EIP-7928 Block-Level Access Lists, Glamsterdam). In-process, no HTTP hop; reads keep working while sync() runs.

npm i @balq/node        # prebuilt: win32-x64 · linux-x64 · linux-arm64 · darwin-x64 · darwin-arm64

Read storage like the contract itself

const { Archive, Layout, NotAvailableError } = require("@balq/node");

const ar = Archive.open("./balq.redb");
ar.watch(proxy, 114563);                 // from here on (must be above the current head)
await ar.sync("http://localhost:8545");  // forward: fetch → verify keccak(rlp(bal)) against the header → apply
await ar.backfill("http://localhost:8545", proxy);   // backward: older blocks, down to the deploy

const layout = Layout.fromFile("./out/Playground.sol/Playground.json");   // solc storageLayout / forge artifact
const view = ar.view(proxy, layout).at(114591);                          // storage as of block 114591

view.counter            // 12n
view.balances[user]     // 37585n   — mapping by key
view.totals.index       // 5000000000000000146n
view.items[3]           // …
view.items.length       // 8n
view.c                  // true
view.lastPoker          // "0x61Cc…"
view.nested[user][7n]   // nested mappings

Integers are bigint (a number would silently lose precision), bools boolean, addresses/bytes/undecodable words string; a string variable is its text, however long (the data slots are read for you). private variables read the same as public ones — this is storage, not getters.

A missing value throws, never returns undefined:

try {
  ar.view(proxy, layout).at(100).counter;
} catch (e) {
  if (e instanceof NotAvailableError) console.log(e.code);   // "BeforeStart"
}

codeNotWatched · BeforeStart · AfterHead · NotSynced · InvalidRange · NeverRecorded · UnknownBefore · Internal.

Types: typegen

npx balq typegen out/Playground.sol/Playground.json --name PlaygroundView > Playground.d.ts

or from code: layout.typescript("PlaygroundView"). Then

const view = ar.view(proxy, layout).at<PlaygroundView>(114591);
view.balances[user];   // bigint
view.balanses;         // compile error

The generated interface mirrors the layout: bigint for integers, nested objects for structs, index signatures for mappings and arrays.

Sync and backfill — all from BALs

await ar.sync(rpcUrl);                                            // forward, one pass to the node's head
setInterval(() => ar.sync(rpcUrl).catch(console.error), 4000);   // follow mode; resumes after any downtime

await ar.backfill(rpcUrl, proxy);                                 // backward, to the contract's creation
await ar.backfill(rpcUrl, proxy, { to: 100_000 });                // …or to a block
await ar.backfill(rpcUrl, proxy, { resolveOnly: true });          // …or just enough to know every earlier value
await ar.backfillMany(rpcUrl, [proxy, vault, oracle]);           // a protocol: one walk, every block read once

Both read eth_getBlockAccessList from an ordinary full node and verify every block (BAL against the header, headers chained by parent_hash). A full node keeps every block, so backfill has no window: it stops at the deploy (stopped: "creation" — from then on every untouched slot is provably zero), at your to, or when the node no longer serves a block ("historyUnavailable" — pass a backupRpc that still has it).

sync returns { blocksApplied, slotsWritten, reorgedTo, … }; backfill returns { from, to, blocksScanned, recordsWritten, slotsResolved, unresolved, createdAt, stopped }. Reads are safe during either; a second concurrent one is refused with an error. sync(rpc, true) additionally proves newly seen slots' earlier values with eth_getProof while the node's state window allows — an optional shortcut, nothing more.

From viem without this package

If your code already reads through a viem client, @balq/viem makes readContract / getStorageAt come from a running balq index --serve instead of an archive RPC — no code change beyond the transport.

Lower level

| | | |---|---| | Archive.open(path, { fullDetail?, allowUnverified?, proofWindow? }) | open or create | | watch(addr, fromBlock) / unwatch(addr) / watchlist() / head() | watchlist and head | | storageAt(addr, slot, block): { value, provenance, setAt, index } | one raw slot, one ordered seek | | history(addr, slot, from, to) | every change in [from, to) | | changedSlots(addr, block) | from the block index | | bootstrapSlot(rpcUrl, addr, slot, backupRpc?) | optional: prove a never-changed slot at the head instead of backfilling | | layout.locate(path) / decode(loc, word) / describeSlot(slot) / kindOf(path) | what view is built on | | layout.describeSlotWithKeys(slot, ["0x…"]) | name mapping entries from candidate keys | | layout.bytesDataSlots(loc, word) / decodeBytes(loc, word, chunks) | dynamic bytes/string (the view does this for you) |

provenance is "bal" (verified against the header's BAL hash), "proof" (Merkle proof against state_root), or, only if you opted in, "unverified" / "imported".

What to know

  • History starts at the BAL fork. A contract that lived before Glamsterdam keeps its pre-fork storage unknown (stopped: "preBal") unless proven against an archive node. Contracts deployed after the fork have complete history.
  • Mappings cannot be enumerated (keccak is one-way): balances[user] works, "list all holders" does not.
  • Proxies. Watch the proxy; the layout is the implementation's.
  • ERC-7201 / Diamond. Pass a layout manifest to Layout.fromFile{ "base": "…", "namespaces": [{ "prefix": "erc20", "layout": "…", "erc7201": "openzeppelin.storage.ERC20" }] } — and read view.erc20.balances[user].

Docs, design notes, benchmarks and the security audit: github.com/artemmartyhin/balq.