# Guide: Live Data, Events & Local State

Beyond binding a single value, `@chainrender/client/runtime` gives you batched reads, event log access, and
opt-in local state — everything a live, reactive UI needs, while keeping the app itself fully on-chain.

## Batched reads

Read several on-chain values in one round-trip with `batchRead` (backed by multicall):

```ts
import { batchRead } from "@chainrender/client/runtime";

const [price, balance, supply] = await batchRead([
    { to: oracle, sig: "latestAnswer()" },
    { to: token, sig: "balanceOf(address)", args: [user] },
    { to: token, sig: "totalSupply()" },
  ], { rpc });
```

## Reading events

`getLogs` / `readEvents` read event history (for feeds, activity, indexing) with a topic helper:

```ts
import { eventTopic0, getLogs } from "@chainrender/client/runtime";

// getLogs(rpc) returns an EventSource; read a spec from it:
const source = getLogs(rpc);
const logs = await source.read({ to: token, event: "Transfer(address,address,uint256)" }, { fromBlock, toBlock });
```

For heavier discovery, the runtime exposes an `indexer` hook — but note the **honest boundary**: enumerating "all
of X" at scale needs an indexer, while reading a *specific* value or a bounded range is a direct on-chain call. See
**[The Render Pipeline](../concepts/render-pipeline.md)**.

## Local state (opt-in, client-side)

For client-side preferences (a chosen theme, a saved RPC, UI state), the runtime offers pluggable local stores —
`memory`, `localStorage`, `indexedDB` — so you don't reach for browser APIs directly and can keep behavior
consistent and testable:

```ts
import { localStorage as crStore } from "@chainrender/client/runtime";
const store = crStore("myapp");
store.set("theme", "dark");
```

Local state never affects the app's on-chain identity or grade — it's purely a client convenience.

## Binding data into a rendered component

`bindSlots` resolves an app's data slots against live sources at render time (this is what the interpreter does
on-chain; the runtime mirror lets you drive it client-side for previews):

```ts
import { bindSlots } from "@chainrender/client/runtime";
const resolved = await bindSlots({ rpc, html, resolvers });
```

## Live data inside a rich-media sketch (`window.CR_CHAIN`)

The reads above are the **declarative** path — data slots resolved on-chain by the interpreter. A **rich-media**
p5/three sketch reads live data a different way: through the injected **`window.CR_CHAIN`** bridge. Same chain, same
honesty rules, but callable imperatively from inside the sketch loop.

```js
// snapshot (auto-polled, ready ~1-2s after start — not in setup()):
CR_CHAIN.blockNumber, CR_CHAIN.gasPriceGwei, CR_CHAIN.baseFeeGwei, CR_CHAIN.live

// async helpers (Promise-returning, fail-closed to null):
await CR_CHAIN.erc20(token)        // { name, symbol, decimals, totalSupply, totalSupplyFloat }
await CR_CHAIN.receipt(txHash)     // { status: "success"|"reverted", gasUsed, ... }
await CR_CHAIN.feeHistory(30)      // real gas curve: { baseFeeGwei:[...], gasUsedRatio:[...], reward:[[...]] }
await CR_CHAIN.recentLogs(addr, transferTopic, 2000)  // recent events, auto-ranged
await CR_CHAIN.ensName(addr)       // "vitalik.eth" or null
```

Use `CR_CHAIN.onReady(fn)` (or gate on `CR_CHAIN.live` in `draw()`) for the initial fetch, scale token amounts by
the token’s **real** `decimals` (never a hardcoded `1e6`/`1e18`), cache one-time reads, throttle repeats, and only
show a "LIVE" label when `CR_CHAIN.live === true`. The full helper reference and honesty rules live in
**[Rich Media → Reading live on-chain data](../concepts/rich-media-p5-three.md)**.

> Same **honest boundary** as above: `erc20`, `receipt`, `feeHistory`, logs, and ENS are all direct on-chain reads;
> enumerating "all holders" or "every historical transfer" at scale still needs an indexer, not a single call.

## Related
- **[Resolvers & Data Binding](../concepts/resolvers.md)** · **[Bind Live On-Chain Data](./bind-data.md)**
