# Rich Media: p5.js & three.js Over the On-Chain Floor

ChainRender supports **rich, dynamic, GPU-accelerated visuals** — p5.js sketches and three.js scenes — while
staying trustless. The trick is the **floor principle** applied to rendering libraries: the on-chain floor renders
first and always; the library runs as an *enhancement* over it, and the library itself is stored **on-chain**, not
fetched from a CDN.

## How it works

A rich-media component carries a **scene binding** — a library (by content hash), a seed, and an entry point.
When it renders:

1. **The floor renders immediately.** `renderFloor(id)` is a pure `eth_call` — a complete, fully on-chain SVG that
   needs nothing but the chain. This is what you always see; it's Grade A.
2. **The viewer then *attempts* the enhancement.** It reads the binding, fetches the named library from the
   on-chain **`DependencyRegistry`** by hash, **verifies `keccak` at every stage**, gunzips it, and runs the sketch
   in a **sandboxed iframe** (`sandbox="allow-scripts"`, CSP `default-src 'none'`).
3. **It fails closed to the floor** on *any* failure — no library, a hash mismatch, a script error, a strict CSP.
   You never get a broken render; worst case, you get the on-chain floor.

```ts
import { encodeSceneBinding } from "@chainrender/scene"; // (illustrative; see chainc/depscene.ts)

// bind a p5 sketch to an on-chain library, by content hash:
const binding = encodeSceneBinding({
  kind: "p5",              // "p5" | "three"
  libHash: p5LibraryHash,  // the DependencyRegistry content hash — the library, on-chain, by hash
  seed,                    // deterministic seed for the sketch
  entry: "sigil",          // the entry point to run
});
```

## The library lives on-chain

This is the key difference from a normal web app that loads `three.js` from a CDN. Here, the library is **stored
on-chain** in the `DependencyRegistry`:

- **Content-addressed & chunked.** A gzipped library is split into ≤72KB parts, each stored on-chain, registered as
  a content-addressed manifest, and round-trip-verified.
- **Immutable & pinned once.** You upload p5/three once (via `scripts/upload-dependency.ts`); components then
  reference it by hash forever — no re-upload per deploy.
- **Verifiable at render.** The viewer keccak-verifies every chunk as it reassembles the library. You're running
  *exactly* the pinned bytes, not whatever a CDN happens to serve.

> A gzipped modern p5 + three core is on the order of ~$20 to pin at sub-1-gwei gas — a one-time cost, after which
> every scene that references it is free to render.

## The two tiers

- **Library-free tier** (`libHash: ZERO`) — canvas-only sketches that need no external library. No upload; the
  floor + a canvas is all it takes.
- **Library tier** — full p5.js or three.js scenes, with the library pinned on-chain as above.

## Why this preserves trustlessness

A naive "on-chain three.js" would fetch the library from a CDN — reintroducing a trusted host and dropping the
grade. ChainRender's version doesn't:

- The **floor is always fully on-chain and Grade A** — the rich enhancement can vanish entirely and you still see a
  complete, correct, trustless render.
- The **library is on-chain and hash-verified** — no CDN, no trusted host, running exactly the pinned bytes.
- The sketch runs **sandboxed** (`allow-scripts` only, `default-src 'none'`) — it can't reach the network, read
  storage, or escape its frame.

This is the floor principle at its most powerful: **you get real p5/three richness *and* absolute trustlessness,
because the rich layer is an enhancement over an on-chain floor, and even the library is on-chain.** See
**[The Floor](../concepts/the-floor.md)**.

## Honest boundaries

- **Rendering the enhancement needs a capable client.** three.js/p5 run client-side (that's the point of an
  enhancement). A client without JS/WebGL, or under a stricter CSP than the sandbox needs, gets the floor. That's
  by design — fail-closed, never broken.
- **three.js is ESM-heavy.** The viewer decodes a *complete, self-contained* three.js (core + module + addons) and
  runs it via `eval` (no dynamic `import` of blob URLs, so it works under strict CSPs). Pin a *bundled complete
  build* — `three.module.min.js` alone isn't self-contained (it imports `three.core`).
- **This is a rich-media viewer capability.** The `renderFloor` path is universal; the library enhancement requires
  the rich-media viewer (or your own client implementing the fetch-verify-sandbox flow).

## Loading the library (the operational reality)

Both libraries load **from chain, as plain script tags** -- sandbox-safe and CSP-safe (no CDN):
- **p5** is a UMD build; it loads as a plain script and sets `window.p5`.
- **three.js** must also be the **UMD build (r160, `three.min.js`)** -- it sets `window.THREE`. Modern three (r161+)
  is ESM-only with no global, which does **not** work loaded as a plain script in a strict-CSP sandboxed iframe.
  Sketches use the **global `THREE`** (not `import`). The r160 UMD is the last version with a global build; it is
  ample for generative/artistic 3D.

The viewer discovers each library by name in the DependencyRegistry (**newest-wins**, so a re-uploaded UMD library
supersedes an older one), fetches the gzipped chunks via `eth_call`, decompresses with the browser's **native
`DecompressionStream`** (no external gzip library), and injects the source. No importmaps, no blob URLs, no CDN --
which is what keeps it working under a strict Content-Security-Policy.

### Large sketches — chunked SourceStore uploads

The *library* isn't the only thing that can exceed a single storage blob — so can your own **sketch**. When a
sketch is larger than one SourceStore entry, the builder splits it into chunk blobs and writes one extra
**manifest** blob whose payload is `CRCHUNKS1|<id>,<id>,…` (the ordered chunk ids). The SourceStore id the app
references is that manifest. At load, the viewer reads the entry: if it begins with `CRCHUNKS1|` it fetches each
listed chunk and concatenates them; otherwise it uses the entry as-is (a single blob). It then gunzips if the
bytes carry the gzip magic, and runs the sketch. Fully backward-compatible: a small, raw, single-blob sketch has
no manifest header and no gzip magic, so it's used verbatim. This is a deploy + runtime mechanism (builder writes,
viewer reassembles) — the authoring SDK only estimates chunk count (`buildManifest().chunkEstimate`); it never
needs to know how a large asset was split.

## The full-page viewer

A deployed rich-media app runs in the **full-page viewer** (`chainrender-viewer.html`), which takes the scene's
`sketch` (SourceStore id), `lib` (DependencyRegistry libHash), `seed`, and `kind` as URL params, loads them from
chain, and runs the sketch full-viewport over the floor. Every deploy produces a complete shareable viewer URL, and
the [Apps directory](../concepts/apps-directory.md) lists deployed rich-media apps automatically (read from
`SceneComponentRegistry`).

## Reading live on-chain data from a sketch (`window.CR_CHAIN`)

A rich-media sketch runs sandboxed and **cannot reach the network** — but it *can* read real, live on-chain data
through a single injected bridge: **`window.CR_CHAIN`**. This is how a p5/three sketch shows real balances, blocks,
gas, token metadata, logs, and ENS names without a CDN, an API, or a trusted host. Every read is a plain JSON-RPC
call to the chain.

> **Grade note.** A sketch that reads `CR_CHAIN` is still a **client-side enhancement over the on-chain floor**
> (Grade ⚙). The floor renders first and always; `CR_CHAIN` enriches the enhancement with *real* numbers instead
> of fabricated ones. The rule is simple: **never fake chain data.** If you show a block number, a balance, a price,
> or a transfer, it must come from `CR_CHAIN` — never from `random()` or a hardcoded literal presented as live.

### The snapshot (auto-polled)

`CR_CHAIN` exposes a live snapshot that refreshes every few seconds. Read these fields directly:

```js
window.CR_CHAIN.blockNumber    // latest block (number) or null until loaded
window.CR_CHAIN.gasPriceGwei   // current gas price in gwei
window.CR_CHAIN.baseFeeGwei    // current base fee in gwei
window.CR_CHAIN.blockHash      // latest block hash
window.CR_CHAIN.txCount        // tx count in the latest block
window.CR_CHAIN.chainId        // e.g. "11155111" (Sepolia)
window.CR_CHAIN.live           // true ONLY when real data has loaded
window.CR_CHAIN.source         // "live" | "loading" | "unavailable"
```

**Critical timing:** the snapshot loads **asynchronously** (~1–2s after start) — it is **not** ready in `setup()`.
Register a callback with `window.CR_CHAIN.onReady(fn)`, or check `window.CR_CHAIN.live` each frame in `draw()` and
only switch from a `"Loading…"` state to real numbers once `live === true`. Never read once in `setup()` and fall
back to fake data.

```js
window.setup = function () {
  createCanvas(800, 500);
  if (window.CR_CHAIN) window.CR_CHAIN.onReady(function () { /* real data is ready */ });
};
window.draw = function () {
  background(10, 12, 18);
  var live = window.CR_CHAIN && window.CR_CHAIN.live;
  text(live ? ("Block " + window.CR_CHAIN.blockNumber) : "Loading live data…", 20, 40);
};
```

### The read helpers (all Promise-returning, all fail-closed to `null`)

For anything beyond the snapshot, `CR_CHAIN` exposes async helpers. Each returns a Promise, guards for nulls, and
**fails closed** — on any RPC error it resolves to `null` rather than throwing, so your sketch degrades to a labeled
fallback instead of breaking. Prefer these over hand-rolling `eth_call` encoding.

**Generic:**

```js
CR_CHAIN.read(method, params)   // any read-only RPC method, returns the raw result
CR_CHAIN.call(to, data)         // eth_call to a contract (hex selector + args)
CR_CHAIN.balanceOf(addr)        // NATIVE ETH balance (in ether), NOT a token balance
CR_CHAIN.logs(filter)           // eth_getLogs with a raw filter object
CR_CHAIN.tx(hash)               // a transaction by hash
CR_CHAIN.block(numOrTag)        // a block (with transactions)
```

**Tokens** — read metadata and balances the honest way (never guess decimals):

```js
CR_CHAIN.erc20(tokenAddr)                 // -> { name, symbol, decimals, totalSupply, totalSupplyFloat }
CR_CHAIN.tokenBalanceOf(tokenAddr, holder)// -> decimal string of an ERC-20 balance
CR_CHAIN.tokenDecimals(tokenAddr)         // -> the token's decimals (number)
```

> **Always scale token amounts by the real `decimals`.** Call `erc20()` (or `tokenDecimals()`) once, cache it, and
> divide raw amounts by `10 ** decimals`. Hardcoding `/ 1e6` or `/ 1e18` will render a *real* number at the wrong
> scale — which is a data-honesty violation exactly like fabricating one.

**Transactions & accounts** — including whether a tx actually succeeded:

```js
CR_CHAIN.receipt(hash)  // -> { status: "success" | "reverted", gasUsed, effectiveGasPriceGwei, blockNumber, logs }
CR_CHAIN.nonceOf(addr)  // -> the address's transaction count
```

> `tx()` alone can’t tell you if a transaction reverted — use `receipt()` and read `status` before showing any
> “confirmed”/“success” state.

**Code & storage:**

```js
CR_CHAIN.isContract(addr)      // -> boolean (guards a hardcoded address before you read it)
CR_CHAIN.codeSize(addr)        // -> bytecode size in bytes
CR_CHAIN.storageAt(addr, slot) // -> raw storage slot (advanced)
```

**Gas fee history** — a real gas chart in one call (prefer over sampling `gasPriceGwei` over wall-clock time):

```js
CR_CHAIN.feeHistory(nBlocks, percentiles)
// -> { baseFeeGwei: [...], gasUsedRatio: [...], oldestBlock, reward: [[...]] }
```

**Event logs** — the honest source for *historical activity* (transfers, mints, swaps), with decode helpers:

```js
CR_CHAIN.logsBetween(addr, topic, fromBlock, toBlock)  // an explicit range
CR_CHAIN.recentLogs(addr, topic, lookbackBlocks)       // auto-ranges from the current block
CR_CHAIN.decodeTransfer(log)  // -> { from, to, valueRaw, tokenId, blockNumber, txHash } (handles ERC-20 vs ERC-721)
CR_CHAIN.topicAddr(topic32)   // extract an address from a 32-byte indexed topic
```

> When decoding events, **verify the shape** (`topics[0]` matches the event signature, `topics.length` matches the
> indexed-parameter count) — an ERC-721 `Transfer` indexes `tokenId` and has no `data`, unlike ERC-20. And when you
> show a bounded window, **disclose it** ("last ~2000 blocks"), never imply a complete history.

**ENS** — human-readable names instead of raw hex (see the [ENS guide](../guides/ens-integration.md)):

```js
CR_CHAIN.ensName(addr)       // reverse-resolve an address -> "vitalik.eth" (or null)
CR_CHAIN.ensAddr(name)       // forward-resolve a name -> address (or null)
CR_CHAIN.ensText(name, key)  // read a text record (avatar, url, com.twitter, …)
```

### The honesty rules (enforced by the builder, expected of every sketch)

- **Gate the "LIVE" label on `CR_CHAIN.live === true`.** Until then, show `"Loading…"`. If `CR_CHAIN` is missing or
  `live` is false, label the display `"Offline / Simulated"` and suffix any placeholder with `(SIM)` — never a fake
  number under a LIVE label.
- **Label each data region by its own read.** A successful `blockNumber` does *not* mean your `erc20()` or
  `feeHistory()` call succeeded — gate each region on its own result, not one global flag.
- **Cache one-time reads** (`erc20` metadata, `decimals`, `isContract`) and **throttle repeating reads** — never call
  a helper every frame; gate on a frame counter or a several-second interval.
- **Null-guard everything** and `.catch()` every call, falling back to a clearly-labeled state.
- **Only `CR_CHAIN` is reachable.** The sandbox blocks all other network access by design — do not attempt to `fetch`
  any other URL or CDN.

## Icons in rich-media (and any) apps

On-chain [icons](../concepts/icons.md) are available to rich-media and standard apps alike -- discovered by name,
injected, theme-adaptive. The viewer loads them the same CSP-safe way as p5/three.

## Related
- **[The Floor](../concepts/the-floor.md)** — the principle this is built on.
- **[Visual & Media Capabilities](../concepts/visual-media.md)** — the on-chain floors (SDF, generative art) these
  enhance.
- **[What's Fully On-Chain](../concepts/fully-on-chain.md)** — including the on-chain library storage.
